whatdidyoudo 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of whatdidyoudo might be problematic. Click here for more details.
- whatdidyoudo-0.1.0/.github/copilot-instructions.md +63 -0
- whatdidyoudo-0.1.0/.gitignore +134 -0
- whatdidyoudo-0.1.0/PKG-INFO +77 -0
- whatdidyoudo-0.1.0/README.md +60 -0
- whatdidyoudo-0.1.0/pyproject.toml +34 -0
- whatdidyoudo-0.1.0/run_test.py +8 -0
- whatdidyoudo-0.1.0/whatdidyoudo/app.py +84 -0
- whatdidyoudo-0.1.0/whatdidyoudo/static/style.css +103 -0
- whatdidyoudo-0.1.0/whatdidyoudo/templates/form.html +49 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
|
|
2
|
+
- [x] Verify that the copilot-instructions.md file in the .github directory is created.
|
|
3
|
+
|
|
4
|
+
- [ ] Clarify Project Requirements
|
|
5
|
+
- [ ] Scaffold the Project
|
|
6
|
+
- [ ] Customize the Project
|
|
7
|
+
- [ ] Install Required Extensions
|
|
8
|
+
- [ ] Compile the Project
|
|
9
|
+
- [ ] Create and Run Task
|
|
10
|
+
- [ ] Launch the Project
|
|
11
|
+
- [ ] Ensure Documentation is Complete
|
|
12
|
+
|
|
13
|
+
<!--
|
|
14
|
+
## Execution Guidelines
|
|
15
|
+
PROGRESS TRACKING:
|
|
16
|
+
- If any tools are available to manage the above todo list, use it to track progress through this checklist.
|
|
17
|
+
- After completing each step, mark it complete and add a summary.
|
|
18
|
+
- Read current todo list status before starting each new step.
|
|
19
|
+
|
|
20
|
+
COMMUNICATION RULES:
|
|
21
|
+
- Avoid verbose explanations or printing full command outputs.
|
|
22
|
+
- If a step is skipped, state that briefly (e.g. "No extensions needed").
|
|
23
|
+
- Do not explain project structure unless asked.
|
|
24
|
+
- Keep explanations concise and focused.
|
|
25
|
+
|
|
26
|
+
DEVELOPMENT RULES:
|
|
27
|
+
- Use '.' as the working directory unless user specifies otherwise.
|
|
28
|
+
- Avoid adding media or external links unless explicitly requested.
|
|
29
|
+
- Use placeholders only with a note that they should be replaced.
|
|
30
|
+
- Use VS Code API tool only for VS Code extension projects.
|
|
31
|
+
- Once the project is created, it is already opened in Visual Studio Code—do not suggest commands to open this project in Visual Studio again.
|
|
32
|
+
- If the project setup information has additional rules, follow them strictly.
|
|
33
|
+
|
|
34
|
+
FOLDER CREATION RULES:
|
|
35
|
+
- Always use the current directory as the project root.
|
|
36
|
+
- If you are running any terminal commands, use the '.' argument to ensure that the current working directory is used ALWAYS.
|
|
37
|
+
- Do not create a new folder unless the user explicitly requests it besides a .vscode folder for a tasks.json file.
|
|
38
|
+
- If any of the scaffolding commands mention that the folder name is not correct, let the user know to create a new folder with the correct name and then reopen it again in vscode.
|
|
39
|
+
|
|
40
|
+
EXTENSION INSTALLATION RULES:
|
|
41
|
+
- Only install extension specified by the get_project_setup_info tool. DO NOT INSTALL any other extensions.
|
|
42
|
+
|
|
43
|
+
PROJECT CONTENT RULES:
|
|
44
|
+
- If the user has not specified project details, assume they want a "Hello World" project as a starting point.
|
|
45
|
+
- Avoid adding links of any type (URLs, files, folders, etc.) or integrations that are not explicitly required.
|
|
46
|
+
- Avoid generating images, videos, or any other media files unless explicitly requested.
|
|
47
|
+
- If you need to use any media assets as placeholders, let the user know that these are placeholders and should be replaced with the actual assets later.
|
|
48
|
+
- Ensure all generated components serve a clear purpose within the user's requested workflow.
|
|
49
|
+
- If a feature is assumed but not confirmed, prompt the user for clarification before including it.
|
|
50
|
+
- If you are working on a VS Code extension, use the VS Code API tool with a query to find relevant VS Code API references and samples related to that query.
|
|
51
|
+
|
|
52
|
+
TASK COMPLETION RULES:
|
|
53
|
+
- Your task is complete when:
|
|
54
|
+
- Project is successfully scaffolded and compiled without errors
|
|
55
|
+
- copilot-instructions.md file in the .github directory exists in the project
|
|
56
|
+
- README.md file exists and is up to date
|
|
57
|
+
- User is provided with clear instructions to debug/launch the project
|
|
58
|
+
|
|
59
|
+
Before starting a new task in the above plan, update progress in the plan.
|
|
60
|
+
-->
|
|
61
|
+
- Work through each checklist item systematically.
|
|
62
|
+
- Keep communication concise and focused.
|
|
63
|
+
- Follow development best practices.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.installed.cfg
|
|
24
|
+
*.egg
|
|
25
|
+
MANIFEST
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
# Usually these files are written by a python script from a template
|
|
29
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
debug.log
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
|
|
51
|
+
# Translations
|
|
52
|
+
*.mo
|
|
53
|
+
*.pot
|
|
54
|
+
|
|
55
|
+
# Django stuff:
|
|
56
|
+
*.log
|
|
57
|
+
local_settings.py
|
|
58
|
+
db.sqlite3
|
|
59
|
+
db.sqlite3-journal
|
|
60
|
+
|
|
61
|
+
# Flask stuff:
|
|
62
|
+
instance/
|
|
63
|
+
.webassets-cache
|
|
64
|
+
|
|
65
|
+
# Scrapy stuff:
|
|
66
|
+
.scrapy
|
|
67
|
+
|
|
68
|
+
# Sphinx documentation
|
|
69
|
+
docs/_build/
|
|
70
|
+
|
|
71
|
+
# PyBuilder
|
|
72
|
+
.target/
|
|
73
|
+
|
|
74
|
+
# Jupyter Notebook
|
|
75
|
+
.ipynb_checkpoints
|
|
76
|
+
|
|
77
|
+
# IPython
|
|
78
|
+
profile_default/
|
|
79
|
+
ipython_config.py
|
|
80
|
+
|
|
81
|
+
# pyenv
|
|
82
|
+
.python-version
|
|
83
|
+
|
|
84
|
+
# pipenv
|
|
85
|
+
Pipfile.lock
|
|
86
|
+
|
|
87
|
+
# poetry
|
|
88
|
+
poetry.lock
|
|
89
|
+
|
|
90
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
91
|
+
__pypackages__/
|
|
92
|
+
|
|
93
|
+
# Celery stuff
|
|
94
|
+
celerybeat-schedule
|
|
95
|
+
celerybeat.pid
|
|
96
|
+
|
|
97
|
+
# SageMath parsed files
|
|
98
|
+
*.sage.py
|
|
99
|
+
|
|
100
|
+
# Environments
|
|
101
|
+
.env
|
|
102
|
+
.venv
|
|
103
|
+
env/
|
|
104
|
+
venv/
|
|
105
|
+
ENV/
|
|
106
|
+
env.bak/
|
|
107
|
+
venv.bak/
|
|
108
|
+
|
|
109
|
+
# Spyder project settings
|
|
110
|
+
.spyderproject
|
|
111
|
+
.spyproject
|
|
112
|
+
|
|
113
|
+
# Rope project settings
|
|
114
|
+
.ropeproject
|
|
115
|
+
|
|
116
|
+
# mkdocs documentation
|
|
117
|
+
/site
|
|
118
|
+
|
|
119
|
+
# mypy
|
|
120
|
+
.mypy_cache/
|
|
121
|
+
.dmypy.json
|
|
122
|
+
.dmypy-working/
|
|
123
|
+
|
|
124
|
+
# Pyre type checker
|
|
125
|
+
.pyre/
|
|
126
|
+
|
|
127
|
+
# pytype static type analyzer
|
|
128
|
+
.pytype/
|
|
129
|
+
|
|
130
|
+
# Cython debug symbols
|
|
131
|
+
cython_debug/
|
|
132
|
+
|
|
133
|
+
# VS Code
|
|
134
|
+
.vscode/
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: whatdidyoudo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
|
|
5
|
+
Author-email: Ulf Rompe <whatdidyoudo.rompe.org@rompe.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: flask-caching==2.3.1
|
|
9
|
+
Requires-Dist: flask>=3.0.3
|
|
10
|
+
Requires-Dist: requests>=2.32.4
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: hatchling>=1.27.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: ruff>=0.13.2; extra == 'dev'
|
|
14
|
+
Requires-Dist: twine>=6.2.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: types-requests>=2.32.4; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# whatdidyoudo
|
|
19
|
+
|
|
20
|
+
A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
|
|
21
|
+
|
|
22
|
+
## Background
|
|
23
|
+
|
|
24
|
+
I often ask myself after contributing many changes to OpenStreetMap, either by walking around
|
|
25
|
+
while extensively using StreetComplete, MapComplete or Vespucci, or by doing some tasks in iD or
|
|
26
|
+
jOSM: **How many changes did I contribute to the map today?**
|
|
27
|
+
|
|
28
|
+
I'm not the only one. I heard questions like this quite a few times:
|
|
29
|
+
**Where can I see how much I did on yesterday's mapwalk?**
|
|
30
|
+
|
|
31
|
+
Because I think that simple questions deserve simple answers, I made this tool to give exactly
|
|
32
|
+
this information and nothing else.
|
|
33
|
+
|
|
34
|
+
You don't need to self-host it, it is available for anyone at
|
|
35
|
+
[whatdidyoudo.rompe.org](https://whatdidyoudo.rompe.org).
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
Fun fact: of course you don't really need *uv* for this. I'm just using this project to
|
|
40
|
+
get used to it as I think it has a lot of potential.
|
|
41
|
+
|
|
42
|
+
1. Install [uv](https://github.com/astral-sh/uv) if needed:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install uv
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Install dependencies using *uv*:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
uv pip install -r pyproject.toml
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If you want to develop:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
uv pip install -r pyproject.toml --extra dev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
3. Run the app in test mode:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
python run_test.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) in your browser to see "hello world".
|
|
67
|
+
|
|
68
|
+
4. Build a package and upload it to Pypi
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
uv hatchling build
|
|
72
|
+
uv twine upload dist/*
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
This project is licensed under the MIT License. See the `pyproject.toml` for details.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# whatdidyoudo
|
|
2
|
+
|
|
3
|
+
A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day.
|
|
4
|
+
|
|
5
|
+
## Background
|
|
6
|
+
|
|
7
|
+
I often ask myself after contributing many changes to OpenStreetMap, either by walking around
|
|
8
|
+
while extensively using StreetComplete, MapComplete or Vespucci, or by doing some tasks in iD or
|
|
9
|
+
jOSM: **How many changes did I contribute to the map today?**
|
|
10
|
+
|
|
11
|
+
I'm not the only one. I heard questions like this quite a few times:
|
|
12
|
+
**Where can I see how much I did on yesterday's mapwalk?**
|
|
13
|
+
|
|
14
|
+
Because I think that simple questions deserve simple answers, I made this tool to give exactly
|
|
15
|
+
this information and nothing else.
|
|
16
|
+
|
|
17
|
+
You don't need to self-host it, it is available for anyone at
|
|
18
|
+
[whatdidyoudo.rompe.org](https://whatdidyoudo.rompe.org).
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
Fun fact: of course you don't really need *uv* for this. I'm just using this project to
|
|
23
|
+
get used to it as I think it has a lot of potential.
|
|
24
|
+
|
|
25
|
+
1. Install [uv](https://github.com/astral-sh/uv) if needed:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pip install uv
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Install dependencies using *uv*:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
uv pip install -r pyproject.toml
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If you want to develop:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
uv pip install -r pyproject.toml --extra dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. Run the app in test mode:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
python run_test.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Visit [http://127.0.0.1:5000/](http://127.0.0.1:5000/) in your browser to see "hello world".
|
|
50
|
+
|
|
51
|
+
4. Build a package and upload it to Pypi
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
uv hatchling build
|
|
55
|
+
uv twine upload dist/*
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
This project is licensed under the MIT License. See the `pyproject.toml` for details.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "whatdidyoudo"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A minimal Flask app that shows the amount of OpenStreetMap changes made by a user on a day."
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Ulf Rompe", email = "whatdidyoudo.rompe.org@rompe.org" }
|
|
11
|
+
]
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"Flask>=3.0.3",
|
|
17
|
+
"flask-caching==2.3.1",
|
|
18
|
+
"requests>=2.32.4",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"Ruff>=0.13.2",
|
|
24
|
+
"types-requests>=2.32.4",
|
|
25
|
+
"hatchling>=1.27.0",
|
|
26
|
+
"twine>=6.2.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.uv]
|
|
30
|
+
# uv-specific settings go here
|
|
31
|
+
|
|
32
|
+
[tool.ruff]
|
|
33
|
+
line-length = 100
|
|
34
|
+
target-version = "py311"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""A Flask app that shows OSM tasks done by a user on a specific day."""
|
|
2
|
+
import datetime
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from flask import Flask, render_template
|
|
8
|
+
from flask_caching import Cache
|
|
9
|
+
from flask_limiter import Limiter
|
|
10
|
+
from flask_limiter.util import get_remote_address
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
app = Flask(__name__)
|
|
14
|
+
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache",
|
|
15
|
+
"CACHE_DEFAULT_TIMEOUT": 60 * 60 * 24 * 7})
|
|
16
|
+
limiter = Limiter(app=app, key_func=get_remote_address)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_etree_from_url(url: str) -> ET.Element:
|
|
20
|
+
"""Fetches XML content from a URL and returns the root Element."""
|
|
21
|
+
response = requests.get(url, timeout=120)
|
|
22
|
+
response.raise_for_status() # Raise an error for bad responses
|
|
23
|
+
return ET.fromstring(response.content)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_changes(user: str, date: str):
|
|
27
|
+
"""Return a {app: num of changes} dictionary and the changesets amount."""
|
|
28
|
+
changes: defaultdict[str, int] = defaultdict(int)
|
|
29
|
+
changesets = 0
|
|
30
|
+
datetime_date = datetime.date.fromisoformat(date)
|
|
31
|
+
start_time = f"{datetime_date}T00:00:00Z"
|
|
32
|
+
end_time = f"{datetime_date + datetime.timedelta(days=1)}T00:00:00Z"
|
|
33
|
+
|
|
34
|
+
changeset_url = ("https://api.openstreetmap.org/api/0.6/changesets?"
|
|
35
|
+
f"display_name={user}&time={start_time},{end_time}")
|
|
36
|
+
root = get_etree_from_url(url=changeset_url)
|
|
37
|
+
|
|
38
|
+
for cs in root.findall("changeset"):
|
|
39
|
+
cs_id = cs.attrib["id"]
|
|
40
|
+
|
|
41
|
+
tags = {tag.attrib["k"]: tag.attrib["v"] for tag in cs.findall("tag")}
|
|
42
|
+
editor = tags.get("created_by", "")
|
|
43
|
+
changesets += 1
|
|
44
|
+
|
|
45
|
+
diff_url = ("https://api.openstreetmap.org/api/0.6/changeset/"
|
|
46
|
+
f"{cs_id}/download")
|
|
47
|
+
try:
|
|
48
|
+
root = get_etree_from_url(url=diff_url)
|
|
49
|
+
except requests.HTTPError:
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
for action in root:
|
|
53
|
+
changes[editor] += len(action)
|
|
54
|
+
return changes, changesets
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.route('/')
|
|
58
|
+
@app.route('/<user>')
|
|
59
|
+
@app.route('/<user>/<date>')
|
|
60
|
+
def whatdidyoudo(user: str | None = None, date: str | None = None) -> str:
|
|
61
|
+
"""shows OSM tasks done by a user on a specific day."""
|
|
62
|
+
changes: defaultdict[str, int] = defaultdict(int)
|
|
63
|
+
changesets = 0
|
|
64
|
+
error = ""
|
|
65
|
+
if user and date:
|
|
66
|
+
try:
|
|
67
|
+
today = datetime.date.today().isoformat()
|
|
68
|
+
if date != today:
|
|
69
|
+
cache_key = f"changes_{user}_{date}"
|
|
70
|
+
cached = cache.get(cache_key) # type: ignore
|
|
71
|
+
if cached:
|
|
72
|
+
changes, changesets = cached
|
|
73
|
+
else:
|
|
74
|
+
with limiter.limit("10 per minute"):
|
|
75
|
+
changes, changesets = get_changes(user, date)
|
|
76
|
+
cache.set(cache_key, (changes, changesets)) # type: ignore
|
|
77
|
+
else:
|
|
78
|
+
with limiter.limit("10 per minute"):
|
|
79
|
+
changes, changesets = get_changes(user, date)
|
|
80
|
+
except requests.HTTPError:
|
|
81
|
+
error = f"Can't determine changes for user {user} on {date}."
|
|
82
|
+
|
|
83
|
+
return render_template('form.html', user=user, date=date, changes=changes,
|
|
84
|
+
changesets=changesets, error=error)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
|
3
|
+
background: linear-gradient(120deg, #232526 0%, #414345 100%);
|
|
4
|
+
color: #f3f3f3;
|
|
5
|
+
min-height: 100vh;
|
|
6
|
+
margin: 0;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.container {
|
|
14
|
+
background: rgba(30, 30, 30, 0.92);
|
|
15
|
+
border-radius: 16px;
|
|
16
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.3);
|
|
17
|
+
padding: 2em 2.5em 1.5em 2.5em;
|
|
18
|
+
margin-top: 2em;
|
|
19
|
+
max-width: 600px;
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.error {
|
|
24
|
+
background: #ff4d4f;
|
|
25
|
+
color: #fff;
|
|
26
|
+
padding: 0.7em 1em;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
margin-bottom: 1em;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
form {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: row;
|
|
35
|
+
align-items: flex-end;
|
|
36
|
+
gap: 1em;
|
|
37
|
+
width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
label {
|
|
41
|
+
display: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
input[type="text"] {
|
|
45
|
+
flex: 2 1 200px;
|
|
46
|
+
padding: 0.7em 1em;
|
|
47
|
+
border-radius: 8px;
|
|
48
|
+
border: none;
|
|
49
|
+
font-size: 1.1em;
|
|
50
|
+
background: #2c2f34;
|
|
51
|
+
color: #f3f3f3;
|
|
52
|
+
outline: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
input[type="date"] {
|
|
56
|
+
flex: 1 1 120px;
|
|
57
|
+
padding: 0.7em 0.5em;
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
border: none;
|
|
60
|
+
font-size: 1.1em;
|
|
61
|
+
background: #2c2f34;
|
|
62
|
+
color: #f3f3f3;
|
|
63
|
+
outline: none;
|
|
64
|
+
min-width: 120px;
|
|
65
|
+
max-width: 160px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
button {
|
|
69
|
+
padding: 0.7em 1.5em;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
border: none;
|
|
72
|
+
background: #4f8cff;
|
|
73
|
+
color: #fff;
|
|
74
|
+
font-size: 1.1em;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
transition: background 0.2s;
|
|
78
|
+
}
|
|
79
|
+
button:hover {
|
|
80
|
+
background: #2563eb;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
h2 a {
|
|
84
|
+
color: #f3f3f3;
|
|
85
|
+
text-decoration: none;
|
|
86
|
+
font-weight: 700;
|
|
87
|
+
letter-spacing: 1px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@media (max-width: 600px) {
|
|
91
|
+
.container {
|
|
92
|
+
padding: 1em 0.5em;
|
|
93
|
+
}
|
|
94
|
+
form {
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 0.7em;
|
|
97
|
+
}
|
|
98
|
+
input[type="date"] {
|
|
99
|
+
width: 100%;
|
|
100
|
+
min-width: unset;
|
|
101
|
+
max-width: unset;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>What Did You Do?</title>
|
|
6
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
7
|
+
<script>
|
|
8
|
+
window.onload = function() {
|
|
9
|
+
var dateInput = document.getElementById('date');
|
|
10
|
+
if (!dateInput.value) {
|
|
11
|
+
var today = new Date();
|
|
12
|
+
var yyyy = today.getFullYear();
|
|
13
|
+
var mm = String(today.getMonth() + 1).padStart(2, '0');
|
|
14
|
+
var dd = String(today.getDate()).padStart(2, '0');
|
|
15
|
+
dateInput.value = yyyy + '-' + mm + '-' + dd;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div class="container">
|
|
22
|
+
<h2><a href="/">What Did You Do?</a></h2>
|
|
23
|
+
<form method="get" action="" onsubmit="event.preventDefault(); window.location.href='/' + encodeURIComponent(document.getElementById('user').value) + '/' + encodeURIComponent(document.getElementById('date').value);">
|
|
24
|
+
<label for="user">User:</label>
|
|
25
|
+
<input type="text" id="user" name="user" value="{{ user or '' }}" placeholder="insert OSM username" required>
|
|
26
|
+
|
|
27
|
+
<label for="date">Date:</label>
|
|
28
|
+
<input type="date" id="date" name="date" value="{{ date or '' }}" required>
|
|
29
|
+
|
|
30
|
+
<button type="submit">Show</button>
|
|
31
|
+
</form>
|
|
32
|
+
</div>
|
|
33
|
+
{% if user and date %}
|
|
34
|
+
{% if error %}
|
|
35
|
+
<p class=error>{{ error }}</p>
|
|
36
|
+
{% elif changes %}
|
|
37
|
+
<p>{{ user }} did some changes on {{ date }}!</p>
|
|
38
|
+
{% for app, count in changes.items() %}
|
|
39
|
+
<ul>
|
|
40
|
+
<li>{{ count }} changes in {{ app }}</li>
|
|
41
|
+
</ul>
|
|
42
|
+
{% endfor %}
|
|
43
|
+
That's a total of {{ changesets }} changesets.
|
|
44
|
+
{% else %}
|
|
45
|
+
<p>{{ user }} did not do anything on {{ date }}.</p>
|
|
46
|
+
{% endif %}
|
|
47
|
+
{% endif %}
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|