Init project
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
45
app.js
Normal file
45
app.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import express from 'express';
|
||||
import {createClient} from 'db-vendo-client/index.js';
|
||||
import {profile as dbnavProfile} from 'db-vendo-client/p/dbnav/index.js';
|
||||
import {mapRouteParsers} from 'db-vendo-client/lib/api-parsers.js';
|
||||
import {createHafasRestApi as createApi} from 'hafas-rest-api';
|
||||
|
||||
const config = {
|
||||
hostname: process.env.HOSTNAME || 'localhost',
|
||||
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
||||
name: 'db-vendo-client',
|
||||
description: 'db-vendo-client',
|
||||
homepage: 'https://github.com/public-transport/db-vendo-client',
|
||||
version: '6',
|
||||
docsLink: 'https://github.com/public-transport/db-vendo-client',
|
||||
openapiSpec: true,
|
||||
logging: true,
|
||||
aboutPage: true,
|
||||
enrichStations: true,
|
||||
etags: 'strong',
|
||||
csp: 'default-src \'none\'; style-src \'self\' \'unsafe-inline\'; img-src https:',
|
||||
mapRouteParsers,
|
||||
};
|
||||
|
||||
const start = async () => {
|
||||
|
||||
const app = express();
|
||||
|
||||
const vendo = createClient(
|
||||
dbnavProfile,
|
||||
'traveldrafter',
|
||||
config,
|
||||
);
|
||||
const api = await createApi(vendo, config);
|
||||
|
||||
app.use("/api", api);
|
||||
app.use('/web', express.static('web'));
|
||||
|
||||
app.listen(config.port, (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
start();
|
2164
package-lock.json
generated
Normal file
2164
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "traveldrafter",
|
||||
"version": "0.0.1",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "clerie",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"db-vendo-client": "^6.8.2",
|
||||
"express": "^5.1.0",
|
||||
"hafas-rest-api": "^5.1.3"
|
||||
}
|
||||
}
|
28
web/api.js
Normal file
28
web/api.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export function fetchApi(pathcomponents, query) {
|
||||
query.pretty = true;
|
||||
let url = '/api/' + pathcomponents.join("/") + "?" + new URLSearchParams(query).toString();
|
||||
return fetch(url).then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Fetching api failed");
|
||||
}
|
||||
return response;
|
||||
}).then(response => response.json());
|
||||
}
|
||||
|
||||
export function fetchLocations(query) {
|
||||
return fetchApi(["locations"], {
|
||||
query: query,
|
||||
addresses: false,
|
||||
poi: false,
|
||||
subStop: false,
|
||||
entrances: false,
|
||||
linesOfStops: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchJourneys(from_, to) {
|
||||
return fetchApi(["journeys"], {
|
||||
from: from_,
|
||||
to: to,
|
||||
});
|
||||
}
|
26
web/index.html
Normal file
26
web/index.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="module" src="traveldrafter.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="journey-search-from">Select…</div>
|
||||
<div id="journey-search-to">Select…</div>
|
||||
<div id="journey-search-submit">Search</div>
|
||||
<div id="journey-search-result"></div>
|
||||
|
||||
<div id="locations-search" class="popup">
|
||||
<div class="popup-close">×</div>
|
||||
<div id="locations-search-content" class="popup-content">
|
||||
<div class="container">
|
||||
<input id="locations-search-query" class="form-control" type="text" />
|
||||
<div id="locations-search-response"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
39
web/journeys-search.js
Normal file
39
web/journeys-search.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { fetchJourneys } from './api.js';
|
||||
import { attachLocationsSearch } from './locations-search.js';
|
||||
|
||||
let element_from = document.querySelector("#journey-search-from");
|
||||
let element_to = document.querySelector("#journey-search-to");
|
||||
let element_submit = document.querySelector("#journey-search-submit");
|
||||
let element_result = document.querySelector("#journey-search-result");
|
||||
|
||||
export function setupJourneysSearch() {
|
||||
attachLocationsSearch(element_from);
|
||||
attachLocationsSearch(element_to);
|
||||
|
||||
element_submit.addEventListener("click", event => {
|
||||
element_result.innerText = "Loading…";
|
||||
fetchJourneys(element_from.dataset.locationId, element_to.dataset.locationId).then(result => {
|
||||
for (let journey of result.journeys) {
|
||||
element_result.appendChild(createJourneyElement(journey));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createJourneyElement(journey) {
|
||||
let el = document.createElement("div");
|
||||
|
||||
for (let leg of journey.legs) {
|
||||
el.appendChild(createJourneyLegElement(leg));
|
||||
}
|
||||
|
||||
return el;
|
||||
|
||||
}
|
||||
|
||||
function createJourneyLegElement(leg) {
|
||||
let el = document.createElement("div");
|
||||
el.innerText = JSON.stringify(leg);
|
||||
el.innerText = leg?.line?.name + ": " + leg.origin.name + " > " + leg.destination.name;
|
||||
return el;
|
||||
}
|
36
web/locations-search.js
Normal file
36
web/locations-search.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { fetchLocations } from './api.js';
|
||||
|
||||
let element_locations_search = document.querySelector("#locations-search");
|
||||
let element_query = document.querySelector("#locations-search-query");
|
||||
let element_response = document.querySelector("#locations-search-response");
|
||||
|
||||
export function setupLocationsSearch() {
|
||||
element_query.addEventListener("change", (event) => {
|
||||
element_response.innerText = "Loading…";
|
||||
fetchLocations(event.target.value).then(result => {
|
||||
element_response.innerText = "";
|
||||
result.forEach(lr => {
|
||||
let location_element = document.createElement("div");
|
||||
location_element.innerText = lr.name;
|
||||
location_element.dataset.locationId = lr.id;
|
||||
|
||||
location_element.addEventListener("click", event => {
|
||||
console.log(event.target.dataset.locationId);
|
||||
element_locations_search.locationSelectedCallback(event.target.innerText, event.target.dataset.locationId);
|
||||
element_locations_search.style.display = "none";
|
||||
});
|
||||
element_response.appendChild(location_element);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function attachLocationsSearch(search_element) {
|
||||
search_element.addEventListener("click", event => {
|
||||
element_locations_search.locationSelectedCallback = (location_name, location_id) => {
|
||||
search_element.innerText = location_name;
|
||||
search_element.dataset.locationId = location_id;
|
||||
};
|
||||
element_locations_search.style.display = "block";
|
||||
});
|
||||
}
|
7
web/popup.js
Normal file
7
web/popup.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export function setupPopups() {
|
||||
document.querySelectorAll(".popup-close").forEach(element => {
|
||||
element.addEventListener("click", event => {
|
||||
event.target.parentElement.style.display="none";
|
||||
});
|
||||
});
|
||||
}
|
54
web/style.css
Normal file
54
web/style.css
Normal file
@@ -0,0 +1,54 @@
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: fixed;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 10000;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
color: white;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.popup .popup-close {
|
||||
margin: 60px;
|
||||
margin-left: auto;
|
||||
font-size: 60px;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.popup .popup-content {
|
||||
position: relative;
|
||||
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.popup input {
|
||||
color: white;
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
|
||||
border: none;
|
||||
border-bottom-style: solid;
|
||||
border-width: 1px;
|
||||
font-size: 2em;
|
||||
}
|
10
web/traveldrafter.js
Normal file
10
web/traveldrafter.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as Api from './api.js';
|
||||
import { setupPopups } from "./popup.js";
|
||||
import { setupLocationsSearch } from './locations-search.js';
|
||||
import { setupJourneysSearch } from './journeys-search.js';
|
||||
|
||||
window.Api = Api;
|
||||
|
||||
setupPopups();
|
||||
setupLocationsSearch();
|
||||
setupJourneysSearch();
|
Reference in New Issue
Block a user