mirror of
https://github.com/gmbrax/Pilgrim.git
synced 2026-11-10 14:10:16 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc8b5016f3 | ||
|
|
4b301d0d7e | ||
|
|
821845530a | ||
|
|
58c7bfd8e9 | ||
|
|
03fb3b23c2 |
+1
-143
@@ -1,144 +1,2 @@
|
|||||||
# Database files
|
|
||||||
database.db
|
database.db
|
||||||
|
__pycache__
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
build/
|
|
||||||
temp/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
.python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
Pipfile.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
poetry.lock
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
.venv
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# IDE settings
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
|
|||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.10 (Pilgrim)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/Pilgrim.iml" filepath="$PROJECT_DIR$/.idea/Pilgrim.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+200
@@ -0,0 +1,200 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="0a7f92e2-b44a-4dfe-8e01-136d1c0c18be" name="Changes" comment="Added the travel_diary id as foreign key to the photos and add a check on the creation to avoid leaving it empty or bad referenced">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pilgrim/service/photo_service.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/pilgrim/service/photo_service.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pilgrim/ui/screens/diary_list_screen.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/pilgrim/ui/screens/diary_list_screen.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
|
<map>
|
||||||
|
<entry key="$PROJECT_DIR$" value="proposed_changes" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
<option name="UPDATE_TYPE" value="REBASE" />
|
||||||
|
</component>
|
||||||
|
<component name="GitHubPullRequestSearchHistory">{
|
||||||
|
"lastFilter": {
|
||||||
|
"state": "OPEN",
|
||||||
|
"assignee": "gmbrax"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="GithubPullRequestsUISettings">{
|
||||||
|
"selectedUrlAndAccountId": {
|
||||||
|
"url": "https://github.com/gmbrax/Pilgrim.git",
|
||||||
|
"accountId": "213d8456-c67d-4cfd-99a6-337d47c35b4a"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2y0y1J9RlHT6m1qL5RuCBf0Ramp" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"Python.Database.executor": "Run",
|
||||||
|
"Python.command.executor": "Run",
|
||||||
|
"Python.main.executor": "Run",
|
||||||
|
"Python.pilgrim.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "feat-TUI",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="pilgrim" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||||
|
<module name="Pilgrim" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.venv/bin" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/.venv/bin/pilgrim" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Python.pilgrim" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-6a121458b545-JavaScript-PY-251.25410.159" />
|
||||||
|
<option value="bundled-python-sdk-e0ed3721d81e-36ea0e71a18c-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-251.25410.159" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="0a7f92e2-b44a-4dfe-8e01-136d1c0c18be" name="Changes" comment="" />
|
||||||
|
<created>1748985568579</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1748985568579</updated>
|
||||||
|
<workItem from="1748985569621" duration="2124000" />
|
||||||
|
<workItem from="1748992451560" duration="312000" />
|
||||||
|
<workItem from="1749508687224" duration="371000" />
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="Added a Back Relationship in Entry to list all the photos">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749004109515</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749004109515</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00002" summary="Changed the names of the files to conform the python convection and also added photo_in_entry.py to diminish the cyclic import error in pylint">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749006784623</created>
|
||||||
|
<option name="number" value="00002" />
|
||||||
|
<option name="presentableId" value="LOCAL-00002" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749006784623</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00003" summary="Added the Services layer to the code to do the database operation">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749140898576</created>
|
||||||
|
<option name="number" value="00003" />
|
||||||
|
<option name="presentableId" value="LOCAL-00003" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749140898576</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00004" summary="Added the photo_service.py and modified photo.py to have all the crud operations">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749155713848</created>
|
||||||
|
<option name="number" value="00004" />
|
||||||
|
<option name="presentableId" value="LOCAL-00004" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749155713848</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00005" summary="Made some changes to naming in both the classes and the tables of the database to conform the correct naming scheme">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749164385581</created>
|
||||||
|
<option name="number" value="00005" />
|
||||||
|
<option name="presentableId" value="LOCAL-00005" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749164385581</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00006" summary="Added the travel_diary id as foreign key to the photos and add a check on the creation to avoid leaving it empty or bad referenced">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1749168650225</created>
|
||||||
|
<option name="number" value="00006" />
|
||||||
|
<option name="presentableId" value="LOCAL-00006" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1749168650225</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="7" />
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="Added a Back Relationship in Entry to list all the photos" />
|
||||||
|
<MESSAGE value="Changed the names of the files to conform the python convection and also added photo_in_entry.py to diminish the cyclic import error in pylint" />
|
||||||
|
<MESSAGE value="Added the Services layer to the code to do the database operation" />
|
||||||
|
<MESSAGE value="Added the photo_service.py and modified photo.py to have all the crud operations" />
|
||||||
|
<MESSAGE value="Made some changes to naming in both the classes and the tables of the database to conform the correct naming scheme" />
|
||||||
|
<MESSAGE value="Added the travel_diary id as foreign key to the photos and add a check on the creation to avoid leaving it empty or bad referenced" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="Added the travel_diary id as foreign key to the photos and add a check on the creation to avoid leaving it empty or bad referenced" />
|
||||||
|
</component>
|
||||||
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
|
<SUITE FILE_PATH="coverage/Pilgrim$Database.coverage" NAME="Database Coverage Results" MODIFIED="1748987101492" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/Database" />
|
||||||
|
<SUITE FILE_PATH="coverage/Pilgrim$pilgrim.coverage" NAME="pilgrim Coverage Results" MODIFIED="1749097142827" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/.venv/bin" />
|
||||||
|
<SUITE FILE_PATH="coverage/Pilgrim$main.coverage" NAME="main Coverage Results" MODIFIED="1748992510527" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src" />
|
||||||
|
<SUITE FILE_PATH="coverage/Pilgrim$command.coverage" NAME="command Coverage Results" MODIFIED="1748992876551" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/src/Pilgrim" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,50 +1,3 @@
|
|||||||
# Python_Pilgrim
|
# Python_Pilgrim
|
||||||
|
|
||||||
## Overview
|
Python Based Travel Diary
|
||||||
|
|
||||||
**Python_Pilgrim** is a Python-based travel diary application designed to help users document and manage their travel experiences. The project provides tools for recording trips, organizing travel notes, and storing memories in a structured and accessible format.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Create and manage travel diaries
|
|
||||||
- Add, edit, and delete travel entries
|
|
||||||
- Organize trips by date, location, or theme
|
|
||||||
- Store photos, notes, and other media
|
|
||||||
- Export and share travel logs
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
- Python 3.8 or higher
|
|
||||||
- Linux operating system (tested on Ubuntu 20.04+)
|
|
||||||
- Visual Studio Code (VSCode) for development (optional but strongly recommended)
|
|
||||||
- pip (Python package installer)
|
|
||||||
- Optional: virtualenv for isolated environments
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/gmbrax/Pilgrim.git
|
|
||||||
```
|
|
||||||
2. Navigate to the project directory:
|
|
||||||
```bash
|
|
||||||
cd Pilgrim
|
|
||||||
```
|
|
||||||
3. Create a virtual environment and, then, activate it:
|
|
||||||
```bash
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
```
|
|
||||||
4. Install the required dependencies:
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To run the main application, execute:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python <???>.py
|
|
||||||
```
|
|
||||||
|
|
||||||
This will start the Python_Pilgrim application. Follow the on-screen instructions to create and manage your travel diaries.
|
|
||||||
+3
-1
@@ -18,7 +18,9 @@ classifiers = [
|
|||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sqlalchemy"
|
"sqlalchemy",
|
||||||
|
"textual",
|
||||||
|
"textual-dev"
|
||||||
]
|
]
|
||||||
[template.plugins.default]
|
[template.plugins.default]
|
||||||
src-layout = true
|
src-layout = true
|
||||||
|
|||||||
+4
-1
@@ -1,3 +1,6 @@
|
|||||||
greenlet==3.2.3
|
greenlet==3.2.2
|
||||||
SQLAlchemy==2.0.41
|
SQLAlchemy==2.0.41
|
||||||
typing_extensions==4.14.0
|
typing_extensions==4.14.0
|
||||||
|
|
||||||
|
|
||||||
|
textual~=3.3.0
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
from src.application import Application
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = Application()
|
|
||||||
app.run()
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from pilgrim.application import Application
|
||||||
|
from pilgrim.command import main
|
||||||
|
from pilgrim.database import Database, Base
|
||||||
|
from pilgrim.models.travel_diary import TravelDiary
|
||||||
|
from pilgrim.models.entry import Entry
|
||||||
|
from pilgrim.models.photo import Photo
|
||||||
|
|
||||||
|
__all__ = ["Application", "Database", "TravelDiary", "Entry", "Photo", "main", "Base"]
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
from src.database import Database
|
from pilgrim.database import Database
|
||||||
from src.service.servicemanager import ServiceManager
|
from pilgrim.service.mocks.service_manager_mock import ServiceManagerMock
|
||||||
|
from pilgrim.service.servicemanager import ServiceManager
|
||||||
|
from pilgrim.ui.ui import UIApp
|
||||||
|
|
||||||
|
|
||||||
class Application:
|
class Application:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.database = Database()
|
self.database = Database()
|
||||||
|
self.ui = UIApp(ServiceManagerMock())
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.database.create()
|
self.database.create()
|
||||||
|
self.ui.run()
|
||||||
|
|
||||||
def get_service_manager(self):
|
def get_service_manager(self):
|
||||||
session = self.database.session()
|
session = self.database.session()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from pilgrim.application import Application
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = Application()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -4,6 +4,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.engine = create_engine(
|
self.engine = create_engine(
|
||||||
@@ -3,8 +3,8 @@ from typing import Any
|
|||||||
from sqlalchemy import Column, Integer, String, ForeignKey
|
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.models.photo_in_entry import photo_entry_association
|
from pilgrim.models.photo_in_entry import photo_entry_association
|
||||||
from src.database import Base
|
from ..database import Base
|
||||||
|
|
||||||
|
|
||||||
class Entry(Base):
|
class Entry(Base):
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey
|
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.models.photo_in_entry import photo_entry_association
|
from pilgrim.models.photo_in_entry import photo_entry_association
|
||||||
from src.database import Base
|
from ..database import Base
|
||||||
|
|
||||||
|
|
||||||
class Photo(Base):
|
class Photo(Base):
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from sqlalchemy import Table, Column, Integer, ForeignKey
|
from sqlalchemy import Table, Column, Integer, ForeignKey
|
||||||
|
|
||||||
from src.database import Base
|
from ..database import Base
|
||||||
|
|
||||||
photo_entry_association = Table('photo_entry_association', Base.metadata,
|
photo_entry_association = Table('photo_entry_association', Base.metadata,
|
||||||
Column('id', Integer, primary_key=True, autoincrement=True),
|
Column('id', Integer, primary_key=True, autoincrement=True),
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from src.database import Base
|
|
||||||
|
|
||||||
from sqlalchemy import Column, String, Integer
|
from sqlalchemy import Column, String, Integer
|
||||||
|
|
||||||
|
from ..database import Base
|
||||||
|
|
||||||
class TravelDiary(Base):
|
class TravelDiary(Base):
|
||||||
__tablename__ = "travel_diaries"
|
__tablename__ = "travel_diaries"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from src.models.entry import Entry
|
from ..models.entry import Entry
|
||||||
from src.models.travel_diary import TravelDiary
|
from ..models.travel_diary import TravelDiary
|
||||||
|
|
||||||
|
|
||||||
class EntryService:
|
class EntryService:
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
from typing import List
|
||||||
|
from pilgrim.service.entry_service import EntryService
|
||||||
|
from pilgrim.models.entry import Entry
|
||||||
|
|
||||||
|
|
||||||
|
class EntryServiceMock(EntryService):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(None)
|
||||||
|
|
||||||
|
self.mock_data = {
|
||||||
|
1: Entry(title="The Adventure Begins", text="I'm hopping in the Plane to finally visit canadian lands",
|
||||||
|
date="26/07/2025", travel_diary_id=1, id=1,
|
||||||
|
photos=[]),
|
||||||
|
2: Entry(title="The Landing", text="Finally on Canadian Soil", date="27/07/2025",
|
||||||
|
travel_diary_id=1, id=2,photos=[]),
|
||||||
|
3: Entry(title="The Mount Royal", text="The Mount Royal is fucking awesome", date="28/07/2025",
|
||||||
|
travel_diary_id=1, id=3, photos=[]),
|
||||||
|
}
|
||||||
|
self._next_id = 4
|
||||||
|
|
||||||
|
def create(self, travel_diary_id: int, title: str, text: str, date: str) -> Entry:
|
||||||
|
new_entry = Entry(title, text, date, travel_diary_id, id=self._next_id)
|
||||||
|
self.mock_data[self._next_id] = new_entry
|
||||||
|
self._next_id += 1
|
||||||
|
return new_entry
|
||||||
|
|
||||||
|
def read_by_id(self, entry_id: int) -> Entry | None:
|
||||||
|
return self.mock_data.get(entry_id)
|
||||||
|
|
||||||
|
def read_all(self) -> List[Entry]:
|
||||||
|
return list(self.mock_data.values())
|
||||||
|
|
||||||
|
def update(self, entry_id: int, entry_dst: Entry) -> Entry | None:
|
||||||
|
item_to_update = self.mock_data.get(entry_id)
|
||||||
|
if item_to_update:
|
||||||
|
item_to_update.title = entry_dst.title if entry_dst.title is not None else item_to_update.title
|
||||||
|
item_to_update.text = entry_dst.text if entry_dst.text is not None else item_to_update.text
|
||||||
|
item_to_update.date = entry_dst.date if entry_dst.date is not None else item_to_update.date
|
||||||
|
item_to_update.fk_travel_diary_id = entry_dst.fk_travel_diary_id if (entry_dst.fk_travel_diary_id
|
||||||
|
is not None) else entry_dst.id
|
||||||
|
item_to_update.photos.extend(entry_dst.photos)
|
||||||
|
|
||||||
|
return item_to_update
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete(self, entry_id: int) -> Entry | None:
|
||||||
|
return self.mock_data.pop(entry_id, None)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pilgrim.models.photo import Photo
|
||||||
|
from pilgrim.service.photo_service import PhotoService
|
||||||
|
|
||||||
|
|
||||||
|
class PhotoServiceMock(PhotoService):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(None)
|
||||||
|
self.mock_data = {}
|
||||||
|
self._next_id = 1
|
||||||
|
|
||||||
|
def create(self, filepath: Path, name: str, travel_diary_id, addition_date=None, caption=None) -> Photo | None:
|
||||||
|
new_photo = Photo(filepath, name, addition_date=addition_date, caption=caption)
|
||||||
|
self.mock_data[self._next_id] = new_photo
|
||||||
|
self._next_id += 1
|
||||||
|
return new_photo
|
||||||
|
|
||||||
|
|
||||||
|
def read_by_id(self, photo_id: int) -> Photo:
|
||||||
|
return self.mock_data.get(photo_id)
|
||||||
|
|
||||||
|
def read_all(self) -> List[Photo]:
|
||||||
|
return list(self.mock_data.values())
|
||||||
|
|
||||||
|
def update(self, photo_id: Photo, photo_dst: Photo) -> Photo | None:
|
||||||
|
item_to_update:Photo = self.mock_data.get(photo_id)
|
||||||
|
if item_to_update:
|
||||||
|
item_to_update.filepath = photo_dst.filepath if photo_dst.filepath else item_to_update.filepath
|
||||||
|
item_to_update.name = photo_dst.name if photo_dst.name else item_to_update.name
|
||||||
|
item_to_update.caption = photo_dst.caption if photo_dst.caption else item_to_update.caption
|
||||||
|
item_to_update.addition_date = photo_dst.addition_date if photo_dst.addition_date\
|
||||||
|
else item_to_update.addition_date
|
||||||
|
item_to_update.fk_travel_diary_id = photo_dst.fk_travel_diary_id if photo_dst.fk_travel_diary_id \
|
||||||
|
else item_to_update.fk_travel_diary_id
|
||||||
|
item_to_update.entries.extend(photo_dst.entries)
|
||||||
|
return item_to_update
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete(self, photo_id: int) -> Photo | None:
|
||||||
|
return self.mock_data.pop(photo_id, None)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from pilgrim.service.mocks.entry_service_mock import EntryServiceMock
|
||||||
|
from pilgrim.service.mocks.photo_service_mock import PhotoServiceMock
|
||||||
|
from pilgrim.service.mocks.travel_diary_service_mock import TravelDiaryServiceMock
|
||||||
|
from pilgrim.service.photo_service import PhotoService
|
||||||
|
from pilgrim.service.servicemanager import ServiceManager
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceManagerMock(ServiceManager):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
# Cria instâncias únicas para manter estado consistente
|
||||||
|
self._travel_diary_service = TravelDiaryServiceMock()
|
||||||
|
self._entry_service = EntryServiceMock()
|
||||||
|
self._photo_service = PhotoServiceMock()
|
||||||
|
|
||||||
|
def get_entry_service(self):
|
||||||
|
return self._entry_service
|
||||||
|
|
||||||
|
def get_travel_diary_service(self):
|
||||||
|
return self._travel_diary_service
|
||||||
|
|
||||||
|
def get_photo_service(self):
|
||||||
|
return self._photo_service
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
from pilgrim.service.travel_diary_service import TravelDiaryService
|
||||||
|
from pilgrim.models.travel_diary import TravelDiary
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
class TravelDiaryServiceMock(TravelDiaryService):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(None)
|
||||||
|
self.mock_data = {
|
||||||
|
1: TravelDiary(id=1, name="Montreal"),
|
||||||
|
2: TravelDiary(id=2, name="Rio de Janeiro"),
|
||||||
|
}
|
||||||
|
self._next_id = 3
|
||||||
|
|
||||||
|
# Métodos síncronos (originais)
|
||||||
|
def create(self, name: str):
|
||||||
|
"""Versão síncrona"""
|
||||||
|
new_travel_diary = TravelDiary(id=self._next_id, name=name)
|
||||||
|
self.mock_data[self._next_id] = new_travel_diary
|
||||||
|
self._next_id += 1
|
||||||
|
return new_travel_diary
|
||||||
|
|
||||||
|
def read_by_id(self, travel_id: int):
|
||||||
|
"""Versão síncrona"""
|
||||||
|
return self.mock_data.get(travel_id)
|
||||||
|
|
||||||
|
def read_all(self):
|
||||||
|
"""Versão síncrona"""
|
||||||
|
return list(self.mock_data.values())
|
||||||
|
|
||||||
|
def update(self, travel_diary_id: int, name: str):
|
||||||
|
"""Versão síncrona"""
|
||||||
|
item_to_update = self.mock_data.get(travel_diary_id)
|
||||||
|
if item_to_update:
|
||||||
|
item_to_update.name = name
|
||||||
|
return item_to_update
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete(self, travel_diary_id: int):
|
||||||
|
"""Versão síncrona"""
|
||||||
|
return self.mock_data.pop(travel_diary_id, None)
|
||||||
|
|
||||||
|
# Métodos assíncronos (novos)
|
||||||
|
async def async_create(self, name: str):
|
||||||
|
"""Versão assíncrona"""
|
||||||
|
await asyncio.sleep(0.01) # Simula I/O
|
||||||
|
return self.create(name)
|
||||||
|
|
||||||
|
async def async_read_by_id(self, travel_id: int):
|
||||||
|
"""Versão assíncrona"""
|
||||||
|
await asyncio.sleep(0.01) # Simula I/O
|
||||||
|
return self.read_by_id(travel_id)
|
||||||
|
|
||||||
|
async def async_read_all(self):
|
||||||
|
"""Versão assíncrona"""
|
||||||
|
await asyncio.sleep(0.01) # Simula I/O
|
||||||
|
return self.read_all()
|
||||||
|
|
||||||
|
async def async_update(self, travel_diary_id: int, name: str):
|
||||||
|
"""Versão assíncrona"""
|
||||||
|
await asyncio.sleep(0.01) # Simula I/O
|
||||||
|
return self.update(travel_diary_id, name)
|
||||||
|
|
||||||
|
async def async_delete(self, travel_diary_id: int):
|
||||||
|
"""Versão assíncrona"""
|
||||||
|
await asyncio.sleep(0.01) # Simula I/O
|
||||||
|
return self.delete(travel_diary_id)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from src.models.photo import Photo
|
|
||||||
from src.models.travel_diary import TravelDiary
|
|
||||||
|
|
||||||
|
from pilgrim.models.photo import Photo
|
||||||
|
from pilgrim.models.travel_diary import TravelDiary
|
||||||
|
|
||||||
class PhotoService:
|
class PhotoService:
|
||||||
def __init__(self, session):
|
def __init__(self, session):
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from src.service.entry_service import EntryService
|
from pilgrim.service.entry_service import EntryService
|
||||||
from src.service.travel_diary_service import TravelDiaryService
|
from pilgrim.service.travel_diary_service import TravelDiaryService
|
||||||
|
|
||||||
|
|
||||||
class ServiceManager:
|
class ServiceManager:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from src.models.travel_diary import TravelDiary
|
from ..models.travel_diary import TravelDiary
|
||||||
|
|
||||||
|
|
||||||
class TravelDiaryService:
|
class TravelDiaryService:
|
||||||
@@ -18,18 +18,18 @@ class TravelDiaryService:
|
|||||||
def read_all(self):
|
def read_all(self):
|
||||||
return self.session.query(TravelDiary).all()
|
return self.session.query(TravelDiary).all()
|
||||||
|
|
||||||
def update(self, travel_diary_src:TravelDiary,travel_diary_dst:TravelDiary):
|
def update(self, travel_diary_id: TravelDiary, travel_diary_dst: TravelDiary):
|
||||||
original = self.read_by_id(travel_diary_src.id)
|
original = self.read_by_id(travel_diary_id.id)
|
||||||
if original is not None:
|
if original is not None:
|
||||||
original.name = travel_diary_dst.name
|
original.name = travel_diary_dst.name
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
self.session.refresh(original)
|
self.session.refresh(original)
|
||||||
return original
|
return original
|
||||||
|
|
||||||
def delete(self, travel_diary_src:TravelDiary):
|
def delete(self, travel_diary_id: TravelDiary):
|
||||||
excluded = self.read_by_id(travel_diary_src.id)
|
excluded = self.read_by_id(travel_diary_id.id)
|
||||||
if excluded is not None:
|
if excluded is not None:
|
||||||
self.session.delete(travel_diary_src)
|
self.session.delete(travel_diary_id)
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
return excluded
|
return excluded
|
||||||
return None
|
return None
|
||||||
@@ -0,0 +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", "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
|
||||||
|
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 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()
|
||||||
|
|
||||||
|
def refresh_diaries(self):
|
||||||
|
"""Versão síncrona do refresh"""
|
||||||
|
try:
|
||||||
|
service_manager = self.app.service_manager
|
||||||
|
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:
|
||||||
|
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]")
|
||||||
|
|
||||||
|
# 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(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"""
|
||||||
|
if self.diary_id_map and event.option_index in self.diary_id_map:
|
||||||
|
self.selected_diary_index = event.option_index
|
||||||
|
self.action_open_diary()
|
||||||
|
else:
|
||||||
|
self.selected_diary_index = None
|
||||||
|
|
||||||
|
self.update_buttons_state()
|
||||||
|
|
||||||
|
def update_buttons_state(self):
|
||||||
|
"""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":
|
||||||
|
self.action_new_diary()
|
||||||
|
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.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)
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
self.notify("Selecione um diário para editar")
|
||||||
|
|
||||||
|
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(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("")
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
.dialog-container{
|
||||||
|
content-align: center top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title{
|
||||||
|
dock: top;
|
||||||
|
height: 3;
|
||||||
|
content-align: center middle;
|
||||||
|
text-style: bold;
|
||||||
|
color: $accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-buttons{
|
||||||
|
height: auto;
|
||||||
|
margin: 1 0;
|
||||||
|
align: center middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips{
|
||||||
|
dock: bottom;
|
||||||
|
height: 4;
|
||||||
|
content-align: center middle;
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from textual.app import App
|
||||||
|
|
||||||
|
from pilgrim.service.servicemanager import ServiceManager
|
||||||
|
from pilgrim.ui.screens.diary_list_screen import DiaryListScreen
|
||||||
|
|
||||||
|
CSS_FILE_PATH = Path(__file__).parent / "styles" / "pilgrim.css"
|
||||||
|
|
||||||
|
|
||||||
|
class UIApp(App):
|
||||||
|
CSS_PATH = CSS_FILE_PATH
|
||||||
|
|
||||||
|
def __init__(self,service_manager: ServiceManager):
|
||||||
|
super().__init__()
|
||||||
|
self.service_manager = service_manager
|
||||||
|
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
"""Chamado quando a app inicia. Carrega a tela principal."""
|
||||||
|
self.push_screen(DiaryListScreen())
|
||||||
Reference in New Issue
Block a user