mirror of
https://github.com/gmbrax/Pilgrim.git
synced 2026-11-10 18:50:18 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cad6a93dd4 | ||
|
|
99eb0a0651 | ||
|
|
dec8a7b445 | ||
|
|
83f6d15ba9 | ||
|
|
190cef2627 | ||
|
|
c2f95b1cf2 | ||
|
|
9024d30662 | ||
|
|
1cb6cfa1b5 | ||
|
|
39017a96b6 |
+143
-1
@@ -1,2 +1,144 @@
|
|||||||
|
# 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
@@ -1,11 +0,0 @@
|
|||||||
<?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
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?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
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-200
@@ -1,200 +0,0 @@
|
|||||||
<?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,3 +1,50 @@
|
|||||||
# Python_Pilgrim
|
# Python_Pilgrim
|
||||||
|
|
||||||
Python Based Travel Diary
|
## Overview
|
||||||
|
|
||||||
|
**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.
|
||||||
+1
-3
@@ -18,9 +18,7 @@ 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
|
||||||
|
|||||||
+1
-4
@@ -1,6 +1,3 @@
|
|||||||
greenlet==3.2.2
|
greenlet==3.2.3
|
||||||
SQLAlchemy==2.0.41
|
SQLAlchemy==2.0.41
|
||||||
typing_extensions==4.14.0
|
typing_extensions==4.14.0
|
||||||
|
|
||||||
|
|
||||||
textual~=3.3.0
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
from pilgrim.database import Database
|
from src.database import Database
|
||||||
from pilgrim.service.mocks.service_manager_mock import ServiceManagerMock
|
from src.service.servicemanager import ServiceManager
|
||||||
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,6 @@
|
|||||||
|
from src.application import Application
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = Application()
|
||||||
|
app.run()
|
||||||
@@ -4,7 +4,6 @@ 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 pilgrim.models.photo_in_entry import photo_entry_association
|
from src.models.photo_in_entry import photo_entry_association
|
||||||
from ..database import Base
|
from src.database import Base
|
||||||
|
|
||||||
|
|
||||||
class Entry(Base):
|
class Entry(Base):
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||||
|
|
||||||
from pilgrim.models.photo_in_entry import photo_entry_association
|
from src.models.photo_in_entry import photo_entry_association
|
||||||
from ..database import Base
|
from src.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 ..database import Base
|
from src.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,8 +1,9 @@
|
|||||||
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,8 +0,0 @@
|
|||||||
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,9 +0,0 @@
|
|||||||
from pilgrim.application import Application
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = Application()
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
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,65 +0,0 @@
|
|||||||
from textual.app import ComposeResult
|
|
||||||
from textual.binding import Binding
|
|
||||||
from textual.screen import Screen
|
|
||||||
from textual.widgets import Header, Footer, Button, Label, TextArea
|
|
||||||
from textual.containers import Container
|
|
||||||
|
|
||||||
class AboutScreen(Screen[bool]):
|
|
||||||
"""Tela para exibir informações sobre a aplicação."""
|
|
||||||
|
|
||||||
TITLE = "Pilgrim - About"
|
|
||||||
|
|
||||||
BINDINGS = [
|
|
||||||
Binding("escape", "dismiss", "Close"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.header = Header()
|
|
||||||
self.footer = Footer()
|
|
||||||
self.app_title = Label("Pilgrim", id="AboutScreen_AboutTitle")
|
|
||||||
self.content = Label("A TUI Based Travel Diary Application", id="AboutScreen_AboutContent")
|
|
||||||
self.version = Label("Version: 0.0.1", id="AboutScreen_AboutVersion")
|
|
||||||
self.developer = Label("Developed By: Gustavo Henrique Miranda ", id="AboutScreen_AboutAuthor")
|
|
||||||
self.contact = Label("git.gustavomiranda.xyz", id="AboutScreen_AboutContact")
|
|
||||||
self.license = TextArea(id="AboutScreen_AboutLicense")
|
|
||||||
self.license.text = """Copyright (c) 2025 GHMiranda.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."""
|
|
||||||
self.license.read_only = True
|
|
||||||
self.about_container = Container(self.app_title, self.content, self.version, self.developer, self.contact,
|
|
||||||
id="AboutScreen_SubContainer")
|
|
||||||
self.container = Container(self.about_container, self.license, id="AboutScreen_AboutContainer")
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
|
||||||
yield self.header
|
|
||||||
yield self.container
|
|
||||||
yield self.footer
|
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
||||||
"""Lida com os cliques dos botões."""
|
|
||||||
if "about-close-button" in event.button.classes:
|
|
||||||
self.dismiss(False)
|
|
||||||
elif "about-info-button" in event.button.classes:
|
|
||||||
self.notify("Mais informações seriam exibidas aqui!", title="Info")
|
|
||||||
|
|
||||||
def action_dismiss(self) -> None:
|
|
||||||
"""Fecha o about box usando dismiss."""
|
|
||||||
self.dismiss(False)
|
|
||||||
|
|
||||||
def on_key(self, event) -> None:
|
|
||||||
"""Intercepta teclas específicas."""
|
|
||||||
if event.key == "escape":
|
|
||||||
self.dismiss(False)
|
|
||||||
event.prevent_default()
|
|
||||||
elif event.key == "enter":
|
|
||||||
self.dismiss(False)
|
|
||||||
event.prevent_default()
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
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.about_screen import AboutScreen
|
|
||||||
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()
|
|
||||||
|
|
||||||
def action_about_cmd(self):
|
|
||||||
self.app.push_screen(AboutScreen())
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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("")
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import Iterable
|
|
||||||
|
|
||||||
from textual.app import App, SystemCommand
|
|
||||||
from textual.screen import Screen
|
|
||||||
|
|
||||||
|
|
||||||
from pilgrim.service.servicemanager import ServiceManager
|
|
||||||
from pilgrim.ui.screens.about_screen import AboutScreen
|
|
||||||
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, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.service_manager = service_manager
|
|
||||||
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
|
||||||
"""Chamado quando a app inicia. Carrega a tela principal."""
|
|
||||||
self.push_screen(DiaryListScreen())
|
|
||||||
|
|
||||||
def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]:
|
|
||||||
"""Return commands based on current screen."""
|
|
||||||
|
|
||||||
# Commands for DiaryListScreen
|
|
||||||
if isinstance(screen, DiaryListScreen):
|
|
||||||
yield SystemCommand(
|
|
||||||
"About Pilgrim",
|
|
||||||
"Open About Pilgrim",
|
|
||||||
screen.action_about_cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
elif isinstance(screen, AboutScreen):
|
|
||||||
yield SystemCommand(
|
|
||||||
"Back to List",
|
|
||||||
"Return to the diary list",
|
|
||||||
screen.dismiss
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Always include quit command
|
|
||||||
yield SystemCommand(
|
|
||||||
"Quit Application",
|
|
||||||
"Exit Pilgrim",
|
|
||||||
self.action_quit
|
|
||||||
)
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ..models.entry import Entry
|
from src.models.entry import Entry
|
||||||
from ..models.travel_diary import TravelDiary
|
from src.models.travel_diary import TravelDiary
|
||||||
|
|
||||||
|
|
||||||
class EntryService:
|
class EntryService:
|
||||||
@@ -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 pilgrim.service.entry_service import EntryService
|
from src.service.entry_service import EntryService
|
||||||
from pilgrim.service.travel_diary_service import TravelDiaryService
|
from src.service.travel_diary_service import TravelDiaryService
|
||||||
|
|
||||||
|
|
||||||
class ServiceManager:
|
class ServiceManager:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..models.travel_diary import TravelDiary
|
from src.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_id: TravelDiary, travel_diary_dst: TravelDiary):
|
def update(self, travel_diary_src:TravelDiary,travel_diary_dst:TravelDiary):
|
||||||
original = self.read_by_id(travel_diary_id.id)
|
original = self.read_by_id(travel_diary_src.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_id: TravelDiary):
|
def delete(self, travel_diary_src:TravelDiary):
|
||||||
excluded = self.read_by_id(travel_diary_id.id)
|
excluded = self.read_by_id(travel_diary_src.id)
|
||||||
if excluded is not None:
|
if excluded is not None:
|
||||||
self.session.delete(travel_diary_id)
|
self.session.delete(travel_diary_src)
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
return excluded
|
return excluded
|
||||||
return None
|
return None
|
||||||
Reference in New Issue
Block a user