Compare commits

..
Author SHA1 Message Date
coderabbitai[bot] 335d06bca1 📝 Add docstrings to staging
Docstrings generation was requested by @gmbrax.

* https://github.com/gmbrax/Pilgrim/pull/48#issuecomment-3076555726

The following files were modified:

* `src/pilgrim/ui/screens/diary_list_screen.py`
* `src/pilgrim/ui/screens/edit_diary_modal.py`
* `src/pilgrim/ui/screens/new_diary_modal.py`
2025-07-16 03:06:05 +00:00
Gustavo Henrique Miranda 1beeb39b95 Merge pull request #47 from gmbrax/fix/move-create-diary-to-modal
Fix/move create diary to modal
2025-07-15 18:14:09 -03:00
Gustavo Henrique Miranda 4f153c5a5b Merge branch 'development' into fix/move-create-diary-to-modal 2025-07-15 17:25:06 -03:00
Gustavo Henrique Santos Souza de Miranda f838a5b421 Add the auto-open option to the new_diary_modal.py 2025-07-15 15:51:30 -03:00
Gustavo Henrique Santos Souza de Miranda 18760e174c Add a functionality that automatically opens the diary after creation, Change the responsibility of diary creation to the new_diary_modal.py instead of the diary_list_screen.py 2025-07-15 02:14:53 -03:00
Gustavo Henrique Miranda 3bef353939 Merge pull request #42
Fixed the edit diary modal to accept the key "enter" and automatically the function
2025-07-14 18:24:14 -03:00
Gustavo Henrique Miranda a80ecc24f5 Merge pull request #43
Fixed the new diary modal to accept the key "enter" and automatically the function
2025-07-14 18:23:58 -03:00
Gustavo Henrique Miranda 7f193d0fbb Merge pull request #44 from gmbrax/dependabot/pip/textual-approx-eq-4.0.0
origin/dependabot/pip/textual-approx-eq-4.0.0
2025-07-14 18:17:49 -03:00
Gustavo Henrique Miranda 7dfeec20f1 Merge pull request #45 from gmbrax/dependabot/pip/typing-extensions-4.14.1
origin/dependabot/pip/typing-extensions-4.14.1
2025-07-14 18:17:32 -03:00
Gustavo Henrique Miranda d1545ad2e2 Merge branch 'staging' into dependabot/pip/typing-extensions-4.14.1 2025-07-14 18:17:15 -03:00
Gustavo Henrique Miranda 7dbb89b692 Merge pull request #40 from gmbrax/fix/add-enter-binding-edit-diary
Fix/add enter binding edit diary
2025-07-14 16:11:29 -03:00
Gustavo Henrique Miranda 33987507ac Merge pull request #41 from gmbrax/fix/add-enter-binding-on-create-diary
Fix/add enter binding on create diary
2025-07-14 16:11:17 -03:00
Gustavo Henrique Miranda ff97929dc0 Merge pull request #39 from gmbrax/dependabot/pip/textual-approx-eq-4.0.0
Dependabot/pip/textual approx eq 4.0.0
2025-07-14 16:02:45 -03:00
Gustavo Henrique Miranda 5d26c5394d Merge branch 'development' into dependabot/pip/textual-approx-eq-4.0.0 2025-07-14 16:02:35 -03:00
dependabot[bot] a867744d45 Update textual requirement from ~=3.3.0 to ~=4.0.0
---
updated-dependencies:
- dependency-name: textual
  dependency-version: 4.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 18:29:28 +00:00
dependabot[bot] d2e69ae770 Bump typing-extensions from 4.14.0 to 4.14.1
Bumps [typing-extensions](https://github.com/python/typing_extensions) from 4.14.0 to 4.14.1.
- [Release notes](https://github.com/python/typing_extensions/releases)
- [Changelog](https://github.com/python/typing_extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/python/typing_extensions/compare/4.14.0...4.14.1)

---
updated-dependencies:
- dependency-name: typing-extensions
  dependency-version: 4.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 17:45:35 +00:00
Gustavo Henrique Santos Souza de Miranda f174fd1d8a Fixed the edit diary modal to accept the key "enter" and automatically the function 2025-07-14 02:23:29 -03:00
Gustavo Henrique Santos Souza de Miranda c98a106d97 Fixed the new diary modal to accept the key "enter" and automatically the function 2025-07-14 02:06:25 -03:00
Gustavo Henrique Miranda 417bc0d3e5 Merge pull request #36
Fixed the bindings to properly work and changed some bindings to avoid conflicts with the system
2025-07-14 00:46:23 -03:00
Gustavo Henrique Miranda 1312674186 Merge pull request #35 from gmbrax/fix/bindings-not-properly-working
Fixed the bindings to properly work and changed some bindings to avoid conflicts with the system
2025-07-13 23:06:14 -03:00
Gustavo Henrique Santos Souza de Miranda afc6908f37 Fixed the bindings to properly work and changed some bindings to avoid conflicts with the system 2025-07-13 22:53:39 -03:00
Gustavo Henrique Miranda 2983be09b6 Merge pull request #33 from gmbrax/fix/force-rename-entry
Fixed the new entry creation flow by prompting for a title if none is  set.
2025-07-12 19:29:55 -03:00
Gustavo Henrique Miranda 8129ae77a6 Merge branch 'staging' into fix/force-rename-entry 2025-07-12 19:11:56 -03:00
Gustavo Henrique Santos Souza de Miranda b704a524c8 Fixed the new entry creation flow by prompting for a title if none is set. 2025-07-12 02:55:39 -03:00
Gustavo Henrique Miranda fcaf17b593 Update README.md 2025-07-08 19:01:26 -03:00
Gustavo Henrique Miranda 1e07ea49df Update README.md 2025-07-08 19:01:11 -03:00
5 changed files with 209 additions and 89 deletions
+1 -1
View File
@@ -3,4 +3,4 @@ SQLAlchemy==2.0.41
typing_extensions==4.14.1
textual~=3.6.0
textual~=4.0.0
+24 -23
View File
@@ -203,35 +203,36 @@ class DiaryListScreen(Screen):
self.action_open_diary()
def action_new_diary(self):
"""Action to create new diary"""
"""
Opens the modal dialog to create a new diary and registers a callback for submission.
"""
self.app.push_screen(NewDiaryModal(),self._on_new_diary_submitted)
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)
def _on_new_diary_submitted(self, result):
"""
Handles the callback after a new diary is submitted.
If a diary was created (non-empty result), notifies the user and refreshes the diary list; otherwise, notifies that creation was canceled.
"""
if result: # Se result não é string vazia, o diário foi criado
self.notify(f"Returning to diary list...")
# Atualiza a lista de diários
self.refresh_diaries()
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)}")
self.notify(f"Creation canceled...")
def _on_screen_resume(self) -> None:
"""
Refreshes the diary list when the screen resumes.
"""
self.refresh_diaries()
def action_edit_selected_diary(self):
"""Action to edit selected diary"""
"""
Opens the edit dialog for the currently selected diary.
If a diary is selected, displays the edit modal and registers a callback for when the edit is submitted. Notifies the user if no diary is selected.
"""
if self.selected_diary_index is not None:
diary_id = self.diary_id_map.get(self.selected_diary_index)
if diary_id:
+43 -9
View File
@@ -8,9 +8,13 @@ from textual.widgets import Label, Input, Button
class EditDiaryModal(ModalScreen[tuple[int,str]]):
BINDINGS = [
Binding("escape", "cancel", "Cancel"),
Binding("enter", "edit_diary", "Save",priority=True),
]
def __init__(self, diary_id: int):
"""
Initialize the EditDiaryModal with the specified diary ID, pre-filling the input field with the current diary name.
"""
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
@@ -31,18 +35,48 @@ class EditDiaryModal(ModalScreen[tuple[int,str]]):
self.name_input.cursor_position = len(self.name_input.value)
def on_button_pressed(self, event: Button.Pressed) -> None:
"""
Handle button press events in the modal.
Triggers the save action if the "Save" button is pressed, or cancels and dismisses the modal if the "Cancel" button is pressed.
"""
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("No changes made.", severity="warning")
self.dismiss(None)
else:
self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus()
self.action_edit_diary()
elif event.button.id == "cancel_button":
self.dismiss(None)
def on_key(self, event):
"""
Handles the Enter key press event by triggering the diary save action and preventing the default behavior.
"""
if event.key == "enter":
self.action_edit_diary()
event.prevent_default()
def action_edit_diary(self) -> None:
"""
Attempts to save the edited diary name, validating input and providing user feedback.
If the new name is non-empty and different from the current name, dismisses the modal and returns the diary ID and new name. If the name is unchanged, notifies the user and dismisses without changes. If the input is empty, notifies the user and refocuses the input field.
"""
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("No changes made.", severity="warning")
self.dismiss(None)
else:
self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus()
def on_input_submitted(self, event: Input.Submitted) -> None:
"""
Handles submission of the diary name input field by triggering the save action if the relevant input is submitted.
"""
if event.input.id == "edit_diary_name_input":
self.action_edit_diary()
def action_cancel(self) -> None:
"""
Dismisses the modal without saving changes or returning any data.
"""
self.dismiss(None)
+64 -45
View File
@@ -24,9 +24,9 @@ class EditEntryScreen(Screen):
BINDINGS = [
Binding("ctrl+q", "quit", "Quit"),
Binding("ctrl+s", "save", "Save"),
Binding("ctrl+n", "new_entry", "New Entry"),
Binding("ctrl+shift+n", "next_entry", "Next Entry"),
Binding("ctrl+shift+p", "prev_entry", "Previous Entry"),
Binding("shift+f5", "new_entry", "New Entry"),
Binding("f5", "next_entry", "Next Entry"),
Binding("f4", "prev_entry", "Previous Entry"),
Binding("ctrl+r", "rename_entry", "Rename Entry"),
Binding("f8", "toggle_sidebar", "Toggle Photos"),
Binding("f9", "toggle_focus", "Toggle Focus"),
@@ -913,11 +913,22 @@ class EditEntryScreen(Screen):
self.notify("Empty entry cannot be saved")
return
# Passe a lista de fotos para o método de criação
self.call_later(self._async_create_entry, content, photos_to_link)
if self.new_entry_title == "":
self.app.push_screen(RenameEntryModal(current_name=""), lambda result: self._handle_save_after_rename(result,content,
photos_to_link))
else:
self.call_later(self._async_create_entry, content, photos_to_link)
else:
# Passe a lista de fotos para o método de atualização
self.call_later(self._async_update_entry, content, photos_to_link)
def _handle_save_after_rename(self, result: str | None, content: str, photos_to_link: List[Photo]) -> None:
if result is None:
self.notify("Save cancelled")
return
self.new_entry_title = result
self.call_later(self._async_create_entry, content, photos_to_link)
async def _async_create_entry(self, content: str, photos_to_link: List[Photo]):
"""Creates a new entry and links the referenced photos."""
try:
@@ -944,7 +955,7 @@ class EditEntryScreen(Screen):
self.is_new_entry = False
self.has_unsaved_changes = False
self._original_content = new_entry.text
self.new_entry_title = "New Entry"
self.new_entry_title = ""
self.next_entry_id = max(entry.id for entry in self.entries) + 1
self._update_entry_display()
@@ -989,52 +1000,60 @@ class EditEntryScreen(Screen):
except Exception as e:
self.notify(f"Error updating entry: {str(e)}")
def on_key(self, event):
# Sidebar contextual shortcuts
def check_key(self, event):
"""Check for custom key handling before bindings are processed"""
# Sidebar shortcuts
if self.sidebar_focused and self.sidebar_visible:
sidebar_keys = ["i", "n", "d", "e"]
if event.key in sidebar_keys:
if event.key == "i":
self.action_insert_photo()
elif event.key == "n":
self.action_ingest_new_photo()
elif event.key == "d":
self.action_delete_photo()
elif event.key == "e":
self.action_edit_photo()
return True # Indica que o evento foi processado
if event.key == "i":
# Text area shortcuts
elif self.focused is self.text_entry:
if event.key in ["tab", "shift+tab"]:
if event.key == "shift+tab":
self._handle_shift_tab()
elif event.key == "tab":
self.text_entry.insert('\t')
return True # Indica que o evento foi processado
self.action_insert_photo()
event.stop()
elif event.key == "n":
return False # Não foi processado, continuar com bindings
self.action_ingest_new_photo()
event.stop()
elif event.key == "d":
def _handle_shift_tab(self):
"""Handle shift+tab for removing indentation"""
textarea = self.text_entry
row, col = textarea.cursor_location
lines = textarea.text.splitlines()
if row < len(lines):
line = lines[row]
if line.startswith('\t'):
lines[row] = line[1:]
textarea.text = '\n'.join(lines)
textarea.cursor_location = (row, max(col - 1, 0))
elif line.startswith(' '): # 4 spaces
lines[row] = line[4:]
textarea.text = '\n'.join(lines)
textarea.cursor_location = (row, max(col - 4, 0))
elif line.startswith(' '):
n = len(line) - len(line.lstrip(' '))
to_remove = min(n, 4)
lines[row] = line[to_remove:]
textarea.text = '\n'.join(lines)
textarea.cursor_location = (row, max(col - to_remove, 0))
self.action_delete_photo()
event.stop()
elif event.key == "e":
self.action_edit_photo()
event.stop()
# Shift+Tab: remove indent
elif self.focused is self.text_entry and event.key == "shift+tab":
textarea = self.text_entry
row, col = textarea.cursor_location
lines = textarea.text.splitlines()
if row < len(lines):
line = lines[row]
if line.startswith('\t'):
lines[row] = line[1:]
textarea.text = '\n'.join(lines)
textarea.cursor_location = (row, max(col - 1, 0))
elif line.startswith(' '): # 4 spaces
lines[row] = line[4:]
textarea.text = '\n'.join(lines)
textarea.cursor_location = (row, max(col - 4, 0))
elif line.startswith(' '):
n = len(line) - len(line.lstrip(' '))
to_remove = min(n, 4)
lines[row] = line[to_remove:]
textarea.text = '\n'.join(lines)
textarea.cursor_location = (row, max(col - to_remove, 0))
event.stop()
# Tab: insert tab
elif self.focused is self.text_entry and event.key == "tab":
self.text_entry.insert('\t')
def on_key(self, event):
if self.check_key(event):
event.stop()
return
def on_footer_action(self, event) -> None:
"""Handle clicks on footer actions (Textual 3.x)."""
+76 -10
View File
@@ -4,17 +4,33 @@ from textual.containers import Vertical, Horizontal
from textual.screen import ModalScreen
from textual.widgets import Label, Input, Button
from pilgrim.ui.screens.edit_entry_screen import EditEntryScreen
class NewDiaryModal(ModalScreen[str]):
BINDINGS = [
Binding("escape", "cancel", "Cancel"),
Binding("enter", "create_diary", "Create",priority=True),
]
def __init__(self):
def __init__(self,autoopen=True):
"""
Initialize the NewDiaryModal with optional automatic opening of the created diary.
Parameters:
autoopen (bool): If True, the newly created diary will open automatically after creation. Defaults to True.
"""
super().__init__()
self.auto_open = autoopen
self.name_input = Input(id="NewDiaryModal-NameInput",classes="NewDiaryModal-NameInput") # This ID is fine, it's specific to the input
def compose(self) -> ComposeResult:
"""
Constructs and yields the UI layout for the new diary modal dialog.
Returns:
ComposeResult: An iterable of widgets forming the modal, including title, input field, and action buttons.
"""
with Vertical(id="new_diary_dialog",classes="NewDiaryModal-Dialog"):
yield Label("Create a new diary", classes="NewDiaryModal-Title")
yield Label("Diary Name:")
@@ -29,17 +45,67 @@ class NewDiaryModal(ModalScreen[str]):
self.name_input.focus()
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handles button clicks."""
"""
Handle button press events for the modal.
Triggers diary creation when the "Create" button is pressed, or dismisses the modal when the "Cancel" button is pressed.
"""
if event.button.id == "create_diary_button":
diary_name = self.name_input.value.strip()
if diary_name:
self.dismiss(diary_name)
else:
self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus()
self.action_create_diary()
elif event.button.id == "cancel_button":
self.dismiss("")
self.dismiss()
def action_cancel(self) -> None:
"""Action to cancel the modal."""
"""
Cancels the modal dialog and dismisses it without returning a diary name.
"""
self.dismiss("")
def action_create_diary(self) -> None:
"""
Validates the diary name input and initiates asynchronous diary creation if valid.
If the input is empty, displays a warning notification and refocuses the input field.
"""
diary_name = self.name_input.value.strip()
if diary_name:
self.call_later(self._async_create_diary, diary_name)
else:
self.notify("Diary name cannot be empty.", severity="warning")
self.name_input.focus()
def on_input_submitted(self, event: Input.Submitted) -> None:
"""
Handles submission of the diary name input field and initiates diary creation if triggered from the correct input.
"""
if event.input.id == "NewDiaryModal-NameInput":
self.action_create_diary()
async def _async_create_diary(self, name: str):
"""
Asynchronously creates a new diary entry with the given name and handles post-creation actions.
If creation succeeds, dismisses the modal with the diary name, optionally opens the diary for editing, and shows a success notification. If creation fails or an exception occurs, displays an error notification.
"""
try:
service = self.app.service_manager.get_travel_diary_service()
created_diary = await service.async_create(name)
if created_diary:
self.dismiss(name)
if self.auto_open:
self.app.push_screen(EditEntryScreen(diary_id=created_diary.id))
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)}")