Compare commits

..

No commits in common. "17df83d04368ed86bc0305dab5999256fddea69d" and "c37b463c0ee69f6c39b2dbb636d99478ac0e3d69" have entirely different histories.

6 changed files with 18 additions and 31 deletions

View file

@ -81,10 +81,10 @@ cd web
flask --app app.py run flask --app app.py run
``` ```
Install the package with the `web` extra to include Flask: The web dependencies (Flask) are not in `pyproject.toml`; install separately:
``` ```
pip install -e ".[web]" pip install flask
``` ```
## Type checking ## Type checking

View file

@ -88,8 +88,8 @@ routes as GeoJSON.
#### Running the dev server #### Running the dev server
``` ```
pip install -e ".[web]" cd web
flask --app web/app.py run flask --app app.py run
``` ```
Open `http://127.0.0.1:5000`. Open `http://127.0.0.1:5000`.

View file

@ -15,15 +15,11 @@ dependencies = [
] ]
[project.optional-dependencies] [project.optional-dependencies]
web = [
"flask>=3.0",
]
dev = [ dev = [
"mypy", "mypy",
"pytest", "pytest",
"responses", "responses",
"types-requests", "types-requests",
"flask>=3.0",
] ]
[project.scripts] [project.scripts]

View file

@ -313,7 +313,7 @@ document.getElementById('slot-to').addEventListener('click', () => setActiveSlot
function navigateTo(id, e) { function navigateTo(id, e) {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault(); e.preventDefault();
history.pushState(null, '', URLS.routePage + id); history.pushState(null, '', `/${id}`);
loadRoute(id); loadRoute(id);
} }
@ -325,7 +325,7 @@ function navigateTo(id, e) {
*/ */
async function loadRoute(relationId) { async function loadRoute(relationId) {
try { try {
const resp = await fetch(URLS.routeApi + relationId); const resp = await fetch(`/api/route/${relationId}`);
const data = await resp.json(); const data = await resp.json();
if (!resp.ok) { if (!resp.ok) {
if (data.error === 'is_route_master') { if (data.error === 'is_route_master') {
@ -374,7 +374,7 @@ async function loadRoute(relationId) {
dirPanel.classList.remove('d-none'); dirPanel.classList.remove('d-none');
for (const dir of data.other_directions) { for (const dir of data.other_directions) {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = URLS.routePage + dir.id; a.href = `/${dir.id}`;
a.className = 'stop-item d-block text-decoration-none'; a.className = 'stop-item d-block text-decoration-none';
a.textContent = dir.name; a.textContent = dir.name;
a.addEventListener('click', (e) => navigateTo(dir.id, e)); a.addEventListener('click', (e) => navigateTo(dir.id, e));
@ -396,7 +396,7 @@ async function loadSegment() {
const rid = currentRelationId; const rid = currentRelationId;
const params = new URLSearchParams({ from: selectedFrom, to: selectedTo, stops: '1' }); const params = new URLSearchParams({ from: selectedFrom, to: selectedTo, stops: '1' });
try { try {
const resp = await fetch(URLS.segmentApi + rid + '?' + params); const resp = await fetch(`/api/segment/${rid}?${params}`);
const data = await resp.json(); const data = await resp.json();
if (!resp.ok) { if (!resp.ok) {
showError(data.message || 'Error loading segment.'); showError(data.message || 'Error loading segment.');
@ -415,7 +415,7 @@ async function loadSegment() {
*/ */
async function loadRouteMaster(relationId) { async function loadRouteMaster(relationId) {
try { try {
const resp = await fetch(URLS.routeMasterApi + relationId); const resp = await fetch(`/api/route_master/${relationId}`);
const data = await resp.json(); const data = await resp.json();
if (!resp.ok) { if (!resp.ok) {
showError(data.message || `Error loading route master ${relationId}.`); showError(data.message || `Error loading route master ${relationId}.`);
@ -463,7 +463,7 @@ async function loadRouteMaster(relationId) {
`background:${colour};flex-shrink:0`; `background:${colour};flex-shrink:0`;
const a = document.createElement('a'); const a = document.createElement('a');
a.href = URLS.routePage + route.id; a.href = `/${route.id}`;
a.className = 'text-decoration-none text-reset flex-grow-1'; a.className = 'text-decoration-none text-reset flex-grow-1';
a.textContent = route.name; a.textContent = route.name;
a.addEventListener('click', (e) => navigateTo(route.id, e)); a.addEventListener('click', (e) => navigateTo(route.id, e));
@ -486,11 +486,8 @@ async function loadRouteMaster(relationId) {
// ── Init ─────────────────────────────────────────────────────────────────── // ── Init ───────────────────────────────────────────────────────────────────
window.addEventListener('popstate', () => { window.addEventListener('popstate', () => {
const prefix = URLS.routePage; const match = location.pathname.match(/^\/(\d+)$/);
if (location.pathname.startsWith(prefix)) { if (match) loadRoute(parseInt(match[1], 10));
const id = parseInt(location.pathname.slice(prefix.length), 10);
if (id) loadRoute(id);
}
}); });
if (RELATION_ID) { if (RELATION_ID) {

View file

@ -17,8 +17,8 @@
<body> <body>
<nav class="navbar navbar-dark bg-dark px-3" style="height:56px"> <nav class="navbar navbar-dark bg-dark px-3" style="height:56px">
<a class="navbar-brand" href="{{ url_for('index') }}">OSM Public Transport → GeoJSON</a> <a class="navbar-brand" href="/">OSM Public Transport → GeoJSON</a>
<a class="nav-link text-white" href="{{ url_for('docs') }}">API docs</a> <a class="nav-link text-white" href="/docs">API docs</a>
</nav> </nav>
<div class="container py-5" style="max-width:860px"> <div class="container py-5" style="max-width:860px">

View file

@ -12,8 +12,8 @@
<body> <body>
<nav class="navbar navbar-dark bg-dark px-3" style="height:56px"> <nav class="navbar navbar-dark bg-dark px-3" style="height:56px">
<a class="navbar-brand" href="{{ url_for('index') }}">OSM Public Transport → GeoJSON</a> <a class="navbar-brand" href="/">OSM Public Transport → GeoJSON</a>
<a class="nav-link text-white" href="{{ url_for('docs') }}">API docs</a> <a class="nav-link text-white" href="/docs">API docs</a>
</nav> </nav>
<div class="container-fluid h-100 p-0"> <div class="container-fluid h-100 p-0">
@ -23,7 +23,7 @@
<div class="col-3 border-end" id="sidebar"> <div class="col-3 border-end" id="sidebar">
<!-- Load form --> <!-- Load form -->
<form method="post" action="{{ url_for('load') }}" class="mb-3"> <form method="post" action="/load" class="mb-3">
<label class="form-label fw-semibold small">Relation ID or OSM URL</label> <label class="form-label fw-semibold small">Relation ID or OSM URL</label>
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input type="text" name="relation" class="form-control" <input type="text" name="relation" class="form-control"
@ -121,14 +121,8 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script> <script>
// Injected by Flask so app.js works correctly under any mount path. // Relation ID injected by Flask; null if none.
const RELATION_ID = {{ relation_id | tojson }}; const RELATION_ID = {{ relation_id | tojson }};
const URLS = {
routeApi: {{ url_for('api_route', relation_id=0)[:-1] | tojson }},
segmentApi: {{ url_for('api_segment', relation_id=0)[:-1] | tojson }},
routeMasterApi: {{ url_for('api_route_master', relation_id=0)[:-1] | tojson }},
routePage: {{ url_for('route_page', relation_id=0)[:-1] | tojson }},
};
</script> </script>
<script src="{{ url_for('static', filename='app.js') }}"></script> <script src="{{ url_for('static', filename='app.js') }}"></script>
</body> </body>