This commit is contained in:
parent
9505bae33d
commit
0e27042c8c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
package-lock.json
|
||||
yarn-error.log
|
||||
node_modules/
|
||||
auth/
|
||||
db/
|
@ -1,4 +1,3 @@
|
||||
# Open Travel Map
|
||||
|
||||
OSM based travel planning app, relies on nodeJS, powered by Fastify, Vue and Pug. A "trip" is held in a JSON with many options for additional information.
|
||||
|
||||
|
@ -1,26 +1,25 @@
|
||||
version: "3.7"
|
||||
networks:
|
||||
external:
|
||||
external: true
|
||||
external:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
otm_db:
|
||||
|
||||
otm_db:
|
||||
|
||||
services:
|
||||
otm:
|
||||
build: ./
|
||||
container_name: otm
|
||||
networks:
|
||||
- external
|
||||
volumes:
|
||||
- otm_db:/usr/src/app/db
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.otm.entrypoints=web-secure"
|
||||
- "traefik.http.routers.otm.rule=Host(`otm.helcel.net`)"
|
||||
- "traefik.http.routers.otm.tls=true"
|
||||
- "traefik.http.services.otm.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.otm.middlewares=cors@file"
|
||||
- "traefik.docker.network=external"
|
||||
restart: always
|
||||
otm:
|
||||
build: ./
|
||||
container_name: otm
|
||||
networks:
|
||||
- external
|
||||
volumes:
|
||||
- otm_db:/usr/src/app/db
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.otm.entrypoints=web-secure"
|
||||
- "traefik.http.routers.otm.rule=Host(`otm.helcel.net`)"
|
||||
- "traefik.http.routers.otm.tls=true"
|
||||
- "traefik.http.services.otm.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.otm.middlewares=cors@file"
|
||||
- "traefik.docker.network=external"
|
||||
restart: always
|
||||
|
@ -18,9 +18,11 @@
|
||||
"@fastify/leveldb": "^5.0.1",
|
||||
"@fastify/static": "^6.10.2",
|
||||
"@fastify/view": "^7.4.1",
|
||||
"@prettier/plugin-pug": "^2.5.1",
|
||||
"axios": "^1.4.0",
|
||||
"fastify": "^4.18.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "^3.0.0",
|
||||
"pug": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
1812
public/css/index.css
1812
public/css/index.css
File diff suppressed because it is too large
Load Diff
@ -1,378 +1,494 @@
|
||||
const gen_id = (length)=>{
|
||||
var result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const len = characters.length;
|
||||
for (let i = 0; i < length; i++ ) {
|
||||
result += characters.charAt(Math.floor(Math.random() * len));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const gen_id = (length) => {
|
||||
var result = "";
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const len = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * len));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
Date.prototype.toJSONLocal = (function() {
|
||||
function addZ(n) {
|
||||
return (n<10? '0' : '') + n;
|
||||
}
|
||||
return function() {
|
||||
return this.getFullYear() + '-' +
|
||||
addZ(this.getMonth() + 1) + '-' +
|
||||
addZ(this.getDate());
|
||||
};
|
||||
}())
|
||||
Date.prototype.toJSONLocal = (function () {
|
||||
function addZ(n) {
|
||||
return (n < 10 ? "0" : "") + n;
|
||||
}
|
||||
return function () {
|
||||
return (
|
||||
this.getFullYear() +
|
||||
"-" +
|
||||
addZ(this.getMonth() + 1) +
|
||||
"-" +
|
||||
addZ(this.getDate())
|
||||
);
|
||||
};
|
||||
})();
|
||||
|
||||
function toEncoded(string) {
|
||||
const codeUnits = Uint16Array.from(
|
||||
{ length: string.length },
|
||||
(element, index) => string.charCodeAt(index)
|
||||
);
|
||||
const charCodes = new Uint8Array(codeUnits.buffer);
|
||||
const codeUnits = Uint16Array.from(
|
||||
{ length: string.length },
|
||||
(element, index) => string.charCodeAt(index),
|
||||
);
|
||||
const charCodes = new Uint8Array(codeUnits.buffer);
|
||||
|
||||
let result = "";
|
||||
charCodes.forEach((char) => {
|
||||
result += String.fromCharCode(char);
|
||||
});
|
||||
return window.btoa(result);
|
||||
}
|
||||
|
||||
function toDecoded(string) {
|
||||
let binary = window.atob(string)
|
||||
const bytes = Uint8Array.from({ length: binary.length }, (element, index) =>
|
||||
binary.charCodeAt(index)
|
||||
);
|
||||
const charCodes = new Uint16Array(bytes.buffer);
|
||||
|
||||
let result = "";
|
||||
charCodes.forEach((char) => {
|
||||
result += String.fromCharCode(char);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
const query_nominatim = (q,f) => axios.get('/api/place/'+q).then(res=>res.data).then(res=>res.filter(f))
|
||||
const query_flight = (q) => axios.get('/api/flight/'+q).then(res=>res.data)
|
||||
|
||||
const is_restauration_type = e => ["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"].indexOf(e.type)!=-1;
|
||||
|
||||
const is_attraction_type = e => (["tourism", "leisure", "place", "amenity", "highway", "historic", "natural", "waterway"].indexOf(e.category)!=-1 ||
|
||||
["place_of_worship", "national_park", "nature_reserve", "protected_area"].indexOf(e.type!=-1));
|
||||
|
||||
const icon_type = (item)=>{
|
||||
let t = item.type
|
||||
let c = item.category
|
||||
const arr = ["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"];
|
||||
if(arr.indexOf(t)!=-1){
|
||||
return 'utensils';
|
||||
}else if(t=='hotel' || t=='hostel'){
|
||||
return 'bed';
|
||||
}else if(t=='museum' || c=='historic' || t=='place_of_worship'){
|
||||
return 'landmark';
|
||||
}else if(t=='peak' || t=='viewpoint'){
|
||||
return 'mountain';
|
||||
}else if(t=='parking'){
|
||||
return 'parking';
|
||||
}else if(t=='water' || t=='river' || t=='lake' || t=='torrent' || t=='aquarium'){
|
||||
return 'water';
|
||||
}else if(t=='community_centre' || t=='locality'){
|
||||
return 'building';
|
||||
}else if(t=='attraction'){
|
||||
return 'landmark';
|
||||
}else if(t=='information' || t=='university'){
|
||||
return 'landmark';
|
||||
}else if(t=='bridge'){
|
||||
return 'archway';
|
||||
}else if(t=='woodland'|| t=='shieling' || t=='national_park' || t=='zoo' || t=='park' || t=='garden' || 0){
|
||||
return 'tree';
|
||||
}else if(t=='water_park', t=='theme_park'){
|
||||
return 'dice-five';
|
||||
}else if(t=='?' || t=='neighbourhood' || t=='quarter' || c=='highway'){
|
||||
return '';
|
||||
}else{
|
||||
console.log(item.display_name, item.category, item.type);
|
||||
return 'question';
|
||||
}
|
||||
let result = "";
|
||||
charCodes.forEach((char) => {
|
||||
result += String.fromCharCode(char);
|
||||
});
|
||||
return window.btoa(result);
|
||||
}
|
||||
|
||||
Vue.component('l-map', window.Vue2Leaflet.LMap)
|
||||
Vue.component('l-tile-layer', window.Vue2Leaflet.LTileLayer)
|
||||
Vue.component('l-marker', window.Vue2Leaflet.LMarker)
|
||||
Vue.component('l-icon', window.Vue2Leaflet.LIcon)
|
||||
Vue.component('l-popup', window.Vue2Leaflet.LPopup)
|
||||
Vue.component('l-tooltip', window.Vue2Leaflet.LTooltip)
|
||||
Vue.component('l-control-scale', window.Vue2Leaflet.LControlScale)
|
||||
Vue.component('multiselect', window.VueMultiselect.default)
|
||||
function toDecoded(string) {
|
||||
let binary = window.atob(string);
|
||||
const bytes = Uint8Array.from({ length: binary.length }, (element, index) =>
|
||||
binary.charCodeAt(index),
|
||||
);
|
||||
const charCodes = new Uint16Array(bytes.buffer);
|
||||
|
||||
let result = "";
|
||||
charCodes.forEach((char) => {
|
||||
result += String.fromCharCode(char);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const query_nominatim = (q, f) =>
|
||||
axios
|
||||
.get("/api/place/" + q)
|
||||
.then((res) => res.data)
|
||||
.then((res) => res.filter(f));
|
||||
const query_flight = (q) =>
|
||||
axios.get("/api/flight/" + q).then((res) => res.data);
|
||||
|
||||
const is_restauration_type = (e) =>
|
||||
["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"].indexOf(
|
||||
e.type,
|
||||
) != -1;
|
||||
|
||||
const is_attraction_type = (e) =>
|
||||
[
|
||||
"tourism",
|
||||
"leisure",
|
||||
"place",
|
||||
"amenity",
|
||||
"highway",
|
||||
"historic",
|
||||
"natural",
|
||||
"waterway",
|
||||
].indexOf(e.category) != -1 ||
|
||||
[
|
||||
"place_of_worship",
|
||||
"national_park",
|
||||
"nature_reserve",
|
||||
"protected_area",
|
||||
].indexOf(e.type != -1);
|
||||
|
||||
const icon_type = (item) => {
|
||||
let t = item.type;
|
||||
let c = item.category;
|
||||
const arr = ["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"];
|
||||
if (arr.indexOf(t) != -1) {
|
||||
return "utensils";
|
||||
} else if (t == "hotel" || t == "hostel") {
|
||||
return "bed";
|
||||
} else if (t == "museum" || c == "historic" || t == "place_of_worship") {
|
||||
return "landmark";
|
||||
} else if (t == "peak" || t == "viewpoint") {
|
||||
return "mountain";
|
||||
} else if (t == "parking") {
|
||||
return "parking";
|
||||
} else if (
|
||||
t == "water" ||
|
||||
t == "river" ||
|
||||
t == "lake" ||
|
||||
t == "torrent" ||
|
||||
t == "aquarium"
|
||||
) {
|
||||
return "water";
|
||||
} else if (t == "community_centre" || t == "locality") {
|
||||
return "building";
|
||||
} else if (t == "attraction") {
|
||||
return "landmark";
|
||||
} else if (t == "information" || t == "university") {
|
||||
return "landmark";
|
||||
} else if (t == "bridge") {
|
||||
return "archway";
|
||||
} else if (
|
||||
t == "woodland" ||
|
||||
t == "shieling" ||
|
||||
t == "national_park" ||
|
||||
t == "zoo" ||
|
||||
t == "park" ||
|
||||
t == "garden" ||
|
||||
0
|
||||
) {
|
||||
return "tree";
|
||||
} else if ((t == "water_park", t == "theme_park")) {
|
||||
return "dice-five";
|
||||
} else if (
|
||||
t == "?" ||
|
||||
t == "neighbourhood" ||
|
||||
t == "quarter" ||
|
||||
c == "highway"
|
||||
) {
|
||||
return "";
|
||||
} else {
|
||||
console.log(item.display_name, item.category, item.type);
|
||||
return "question";
|
||||
}
|
||||
};
|
||||
|
||||
Vue.component("l-map", window.Vue2Leaflet.LMap);
|
||||
Vue.component("l-tile-layer", window.Vue2Leaflet.LTileLayer);
|
||||
Vue.component("l-marker", window.Vue2Leaflet.LMarker);
|
||||
Vue.component("l-icon", window.Vue2Leaflet.LIcon);
|
||||
Vue.component("l-popup", window.Vue2Leaflet.LPopup);
|
||||
Vue.component("l-tooltip", window.Vue2Leaflet.LTooltip);
|
||||
Vue.component("l-control-scale", window.Vue2Leaflet.LControlScale);
|
||||
Vue.component("multiselect", window.VueMultiselect.default);
|
||||
Vue.use(window.VueTextareaAutosize);
|
||||
|
||||
const app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
journey_edit: (['view','short'].indexOf(window.location.pathname.split('/')[1])==-1),
|
||||
journey_id : window.location.pathname.split('/').pop() || gen_id(16),
|
||||
el: "#app",
|
||||
data: {
|
||||
journey_edit:
|
||||
["view", "short"].indexOf(window.location.pathname.split("/")[1]) == -1,
|
||||
journey_id: window.location.pathname.split("/").pop() || gen_id(16),
|
||||
|
||||
journey_step_data: {day:1, section:0},
|
||||
journey_data : {
|
||||
name: "New Journey",
|
||||
main:[],
|
||||
},
|
||||
|
||||
query:{hotel:[],flight:[],nominatim:[]},
|
||||
querying:{hotel:false,flight:false,place:false,food:false},
|
||||
impexp:"",
|
||||
lang: {
|
||||
format: 'ddd D MMM',
|
||||
formatLocale: {
|
||||
firstDayOfWeek: 1,
|
||||
},
|
||||
monthBeforeYear: true,
|
||||
},
|
||||
journey_step_data: { day: 1, section: 0 },
|
||||
journey_data: {
|
||||
name: "New Journey",
|
||||
main: [],
|
||||
},
|
||||
methods: {
|
||||
start_journey: function(event){
|
||||
window.location.href = '/'+this.journey_id;
|
||||
},
|
||||
add_section: function(event){
|
||||
if(this.journey_data.main==undefined) this.journey_data.main=[];
|
||||
this.journey_data.main.push({title:"?",step_title:[],map:{zoom:2}, hotel:{latlon:[0,0]},places:{restaurants:[],places:[]}});
|
||||
},
|
||||
step_len: function(idx){
|
||||
return this.journey_data.main[idx].dateRange?
|
||||
((this.journey_data.main[idx].dateRange[1]-this.journey_data.main[idx].dateRange[0])/(1000*60*60*24))+1
|
||||
: 1;
|
||||
},
|
||||
next_step: function(){
|
||||
this.journey_step_data.day += 1;
|
||||
let s = this.journey_step_data.section;
|
||||
let cd = this.step_len(s);
|
||||
|
||||
if(this.journey_step_data.day>cd){
|
||||
this.journey_step_data.section +=1;
|
||||
if(this.journey_step_data.section>=this.journey_data.main.length){
|
||||
this.journey_step_data.section = this.journey_data.main.length-1;
|
||||
this.journey_step_data.day = cd
|
||||
}else{
|
||||
this.journey_step_data.day = 1;
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
prev_step: function(){
|
||||
this.journey_step_data.day -=1;
|
||||
if(this.journey_step_data.day<=0){
|
||||
this.journey_step_data.section -=1;
|
||||
if(this.journey_step_data.section <0){
|
||||
this.first_step();
|
||||
}else{
|
||||
let s = this.journey_step_data.section;
|
||||
|
||||
let cd = this.step_len(s);
|
||||
this.journey_step_data.day = cd ;
|
||||
}
|
||||
}
|
||||
},
|
||||
nextnext_step: function(){
|
||||
this.journey_step_data.section += 1;
|
||||
this.journey_step_data.day = 1;
|
||||
if(this.journey_step_data.section>=this.journey_data.main.length)
|
||||
this.first_step();
|
||||
},
|
||||
prevprev_step: function(){
|
||||
this.journey_step_data.section -= 1;
|
||||
this.journey_step_data.day = 1;
|
||||
if(this.journey_step_data.section<0)
|
||||
this.first_step();
|
||||
},
|
||||
first_step: function(){
|
||||
this.journey_step_data.section =0;
|
||||
this.journey_step_data.day = 1;
|
||||
},
|
||||
|
||||
active_date: function(){
|
||||
if(this.journey_step_data.day < 0) return "?";
|
||||
if(!this.journey_data.main[this.journey_step_data.section].dateRange) return "?";
|
||||
var date = new Date(this.journey_data.main[this.journey_step_data.section].dateRange[0]);
|
||||
date.setDate(date.getDate() + this.journey_step_data.day-1);
|
||||
return this.format_date(date)
|
||||
},
|
||||
format_date: function(d){
|
||||
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getDay()] + ' '
|
||||
+ d.getDate() + ' '
|
||||
+ ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()];
|
||||
},
|
||||
|
||||
total_days: function(){
|
||||
if(this.journey_data.main.length==0) return 0;
|
||||
try{
|
||||
return (this.journey_data.main[this.journey_data.main.length-1].dateRange[1]-this.journey_data.main[0].dateRange[0])/(1000*60*60*24)
|
||||
}catch{
|
||||
return '?'
|
||||
}
|
||||
},
|
||||
total_date: function(){
|
||||
if(this.journey_data.main.length==0) return '';
|
||||
try{
|
||||
return `${this.format_date(this.journey_data.main[0].dateRange[0])} - ${this.format_date(this.journey_data.main[this.journey_data.main.length-1].dateRange[1])}`;
|
||||
}catch{
|
||||
return '?';
|
||||
}
|
||||
},
|
||||
update_date: function(idx){
|
||||
let dateRange = this.journey_data.main[idx].dateRange;
|
||||
let start_end = [0,0];
|
||||
let step_len = 0;
|
||||
|
||||
let last_start = dateRange[0];
|
||||
for(let i = idx-1; i >=0; --i){
|
||||
step_len = this.step_len(i)-1;
|
||||
if (this.journey_data.main[i].dateRange){
|
||||
start_end = [last_start.getDate() - step_len, last_start.getDate()];
|
||||
}else{
|
||||
this.journey_data.main[i].dateRange= [new Date(), new Date()]
|
||||
start_end = [last_start.getDate() - step_len, last_start.getDate()];
|
||||
}
|
||||
this.journey_data.main[i].dateRange[0].setTime(last_start.getTime())
|
||||
this.journey_data.main[i].dateRange[0].setDate(start_end[0]);
|
||||
this.journey_data.main[i].dateRange[1].setTime(last_start.getTime())
|
||||
this.journey_data.main[i].dateRange[1].setDate(start_end[1]);
|
||||
last_start = this.journey_data.main[i].dateRange[0];
|
||||
}
|
||||
|
||||
let last_end = dateRange[1];
|
||||
for(let i = idx+1; i<this.journey_data.main.length; ++i){
|
||||
step_len = this.step_len(i)-1;
|
||||
if (this.journey_data.main[i].dateRange){
|
||||
start_end = [last_end.getDate(), last_end.getDate() + step_len];
|
||||
}else{
|
||||
this.journey_data.main[i].dateRange= [new Date(), new Date()]
|
||||
start_end = [last_end.getDate(), last_end.getDate() + step_len];
|
||||
}
|
||||
this.journey_data.main[i].dateRange[0].setTime(last_end.getTime())
|
||||
this.journey_data.main[i].dateRange[0].setDate(start_end[0]);
|
||||
this.journey_data.main[i].dateRange[1].setTime(last_end.getTime())
|
||||
this.journey_data.main[i].dateRange[1].setDate(start_end[1]);
|
||||
last_end = this.journey_data.main[i].dateRange[1];
|
||||
}
|
||||
},
|
||||
|
||||
rm_section: function(idx){
|
||||
this.journey_data.main.splice(idx,1);
|
||||
if(this.journey_step_data.section==idx){
|
||||
this.prevprev_step();
|
||||
}
|
||||
},
|
||||
sel_section: function(idx){
|
||||
this.journey_step_data.section = idx;
|
||||
this.journey_step_data.day = 1;
|
||||
},
|
||||
search_nominatim: function(txt,f){
|
||||
if(txt==""){
|
||||
this.query.nominatim=[];
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return query_nominatim(txt,f)
|
||||
.then((results) =>{
|
||||
results.forEach(r=>{
|
||||
r.latlon=[parseFloat(r.lat),parseFloat(r.lon)];
|
||||
r.sname=r.display_name.split(',')[0];
|
||||
})
|
||||
this.query.nominatim=results;
|
||||
});
|
||||
},
|
||||
search_flight: function(txt){
|
||||
if(txt=="") return;
|
||||
this.querying.flight=true;
|
||||
query_flight(txt.replace(" ", ""))
|
||||
.then((results) =>{
|
||||
if(results.results==""){
|
||||
this.query.flight=[];this.querying.flight=false;return;
|
||||
}
|
||||
this.query.flight=results.results;
|
||||
this.querying.flight=false;
|
||||
});
|
||||
|
||||
},
|
||||
generate_icon: function(item,fcolor){
|
||||
return L.AwesomeMarkers.icon({
|
||||
icon: icon_type(item) || 'star', prefix: 'fa',
|
||||
markerColor: fcolor || item.color || 'blue'}
|
||||
).createIcon().outerHTML
|
||||
},
|
||||
|
||||
save_data: function(){
|
||||
this.impexp = toEncoded(JSON.stringify(this.journey_data));
|
||||
axios.post('/api/'+this.journey_id, this.journey_data).then(response => {
|
||||
console.log("Saved...")
|
||||
}).catch(error => {
|
||||
console.warn('Error! Could not reach the API.')
|
||||
})
|
||||
},
|
||||
import_data:function(){
|
||||
this.journey_data = Object.assign({}, JSON.parse(toDecoded(this.impexp)));
|
||||
this.journey_data.main.forEach(e=>{
|
||||
if(e.dateRange){
|
||||
e.dateRange[0] = new Date(e.dateRange[0]);
|
||||
e.dateRange[1] = new Date(e.dateRange[1]);
|
||||
}
|
||||
})
|
||||
},
|
||||
export_data:function(){
|
||||
this.impexp = toEncoded(JSON.stringify(this.journey_data));
|
||||
},
|
||||
filter_selected:function(list,step){
|
||||
return list.filter(e=>(step?(e.step==this.journey_step_data.day):(e.step>=0)))
|
||||
},
|
||||
filter_unselected:function(list){
|
||||
return list.filter(e=>(e.step==undefined || e.step<0))
|
||||
},
|
||||
remove_item:function(list,idx){
|
||||
list[idx].step=-1;
|
||||
list.splice(idx, 1);
|
||||
},
|
||||
log:function(e){console.log(e)},
|
||||
|
||||
keyboardEvent (e) {
|
||||
if (e.which === 13) {
|
||||
}
|
||||
},
|
||||
query: { hotel: [], flight: [], nominatim: [] },
|
||||
querying: { hotel: false, flight: false, place: false, food: false },
|
||||
impexp: "",
|
||||
lang: {
|
||||
format: "ddd D MMM",
|
||||
formatLocale: {
|
||||
firstDayOfWeek: 1,
|
||||
},
|
||||
monthBeforeYear: true,
|
||||
},
|
||||
created: function () {
|
||||
},
|
||||
methods: {
|
||||
start_journey: function (event) {
|
||||
window.location.href = "/" + this.journey_id;
|
||||
},
|
||||
add_section: function (event) {
|
||||
if (this.journey_data.main == undefined) this.journey_data.main = [];
|
||||
this.journey_data.main.push({
|
||||
title: "?",
|
||||
step_title: [],
|
||||
map: { zoom: 2 },
|
||||
hotel: { latlon: [0, 0] },
|
||||
places: { restaurants: [], places: [] },
|
||||
});
|
||||
},
|
||||
step_len: function (idx) {
|
||||
return this.journey_data.main[idx].dateRange
|
||||
? (this.journey_data.main[idx].dateRange[1] -
|
||||
this.journey_data.main[idx].dateRange[0]) /
|
||||
(1000 * 60 * 60 * 24) +
|
||||
1
|
||||
: 1;
|
||||
},
|
||||
next_step: function () {
|
||||
this.journey_step_data.day += 1;
|
||||
let s = this.journey_step_data.section;
|
||||
let cd = this.step_len(s);
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
switch(e.key){
|
||||
case 'ArrowLeft': this.prev_step(); break;
|
||||
case 'ArrowRight': this.next_step(); break;
|
||||
default: console.log(e.key);
|
||||
}
|
||||
if (this.journey_step_data.day > cd) {
|
||||
this.journey_step_data.section += 1;
|
||||
if (this.journey_step_data.section >= this.journey_data.main.length) {
|
||||
this.journey_step_data.section = this.journey_data.main.length - 1;
|
||||
this.journey_step_data.day = cd;
|
||||
} else {
|
||||
this.journey_step_data.day = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
prev_step: function () {
|
||||
this.journey_step_data.day -= 1;
|
||||
if (this.journey_step_data.day <= 0) {
|
||||
this.journey_step_data.section -= 1;
|
||||
if (this.journey_step_data.section < 0) {
|
||||
this.first_step();
|
||||
} else {
|
||||
let s = this.journey_step_data.section;
|
||||
|
||||
let cd = this.step_len(s);
|
||||
this.journey_step_data.day = cd;
|
||||
}
|
||||
}
|
||||
},
|
||||
nextnext_step: function () {
|
||||
this.journey_step_data.section += 1;
|
||||
this.journey_step_data.day = 1;
|
||||
if (this.journey_step_data.section >= this.journey_data.main.length)
|
||||
this.first_step();
|
||||
},
|
||||
prevprev_step: function () {
|
||||
this.journey_step_data.section -= 1;
|
||||
this.journey_step_data.day = 1;
|
||||
if (this.journey_step_data.section < 0) this.first_step();
|
||||
},
|
||||
first_step: function () {
|
||||
this.journey_step_data.section = 0;
|
||||
this.journey_step_data.day = 1;
|
||||
},
|
||||
|
||||
active_date: function () {
|
||||
if (this.journey_step_data.day < 0) return "?";
|
||||
if (!this.journey_data.main[this.journey_step_data.section].dateRange)
|
||||
return "?";
|
||||
var date = new Date(
|
||||
this.journey_data.main[this.journey_step_data.section].dateRange[0],
|
||||
);
|
||||
date.setDate(date.getDate() + this.journey_step_data.day - 1);
|
||||
return this.format_date(date);
|
||||
},
|
||||
format_date: function (d) {
|
||||
return (
|
||||
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()] +
|
||||
" " +
|
||||
d.getDate() +
|
||||
" " +
|
||||
[
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
][d.getMonth()]
|
||||
);
|
||||
},
|
||||
|
||||
total_days: function () {
|
||||
if (this.journey_data.main.length == 0) return 0;
|
||||
try {
|
||||
return (
|
||||
(this.journey_data.main[this.journey_data.main.length - 1]
|
||||
.dateRange[1] -
|
||||
this.journey_data.main[0].dateRange[0]) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
);
|
||||
} catch {
|
||||
return "?";
|
||||
}
|
||||
},
|
||||
total_date: function () {
|
||||
if (this.journey_data.main.length == 0) return "";
|
||||
try {
|
||||
return `${this.format_date(
|
||||
this.journey_data.main[0].dateRange[0],
|
||||
)} - ${this.format_date(
|
||||
this.journey_data.main[this.journey_data.main.length - 1]
|
||||
.dateRange[1],
|
||||
)}`;
|
||||
} catch {
|
||||
return "?";
|
||||
}
|
||||
},
|
||||
update_date: function (idx) {
|
||||
let dateRange = this.journey_data.main[idx].dateRange;
|
||||
let start_end = [0, 0];
|
||||
let step_len = 0;
|
||||
|
||||
let last_start = dateRange[0];
|
||||
for (let i = idx - 1; i >= 0; --i) {
|
||||
step_len = this.step_len(i) - 1;
|
||||
if (this.journey_data.main[i].dateRange) {
|
||||
start_end = [last_start.getDate() - step_len, last_start.getDate()];
|
||||
} else {
|
||||
this.journey_data.main[i].dateRange = [new Date(), new Date()];
|
||||
start_end = [last_start.getDate() - step_len, last_start.getDate()];
|
||||
}
|
||||
this.journey_data.main[i].dateRange[0].setTime(last_start.getTime());
|
||||
this.journey_data.main[i].dateRange[0].setDate(start_end[0]);
|
||||
this.journey_data.main[i].dateRange[1].setTime(last_start.getTime());
|
||||
this.journey_data.main[i].dateRange[1].setDate(start_end[1]);
|
||||
last_start = this.journey_data.main[i].dateRange[0];
|
||||
}
|
||||
|
||||
let last_end = dateRange[1];
|
||||
for (let i = idx + 1; i < this.journey_data.main.length; ++i) {
|
||||
step_len = this.step_len(i) - 1;
|
||||
if (this.journey_data.main[i].dateRange) {
|
||||
start_end = [last_end.getDate(), last_end.getDate() + step_len];
|
||||
} else {
|
||||
this.journey_data.main[i].dateRange = [new Date(), new Date()];
|
||||
start_end = [last_end.getDate(), last_end.getDate() + step_len];
|
||||
}
|
||||
this.journey_data.main[i].dateRange[0].setTime(last_end.getTime());
|
||||
this.journey_data.main[i].dateRange[0].setDate(start_end[0]);
|
||||
this.journey_data.main[i].dateRange[1].setTime(last_end.getTime());
|
||||
this.journey_data.main[i].dateRange[1].setDate(start_end[1]);
|
||||
last_end = this.journey_data.main[i].dateRange[1];
|
||||
}
|
||||
},
|
||||
|
||||
rm_section: function (idx) {
|
||||
this.journey_data.main.splice(idx, 1);
|
||||
if (this.journey_step_data.section == idx) {
|
||||
this.prevprev_step();
|
||||
}
|
||||
},
|
||||
sel_section: function (idx) {
|
||||
this.journey_step_data.section = idx;
|
||||
this.journey_step_data.day = 1;
|
||||
},
|
||||
search_nominatim: function (txt, f) {
|
||||
if (txt == "") {
|
||||
this.query.nominatim = [];
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return query_nominatim(txt, f).then((results) => {
|
||||
results.forEach((r) => {
|
||||
r.latlon = [parseFloat(r.lat), parseFloat(r.lon)];
|
||||
r.sname = r.display_name.split(",")[0];
|
||||
});
|
||||
this.query.nominatim = results;
|
||||
});
|
||||
},
|
||||
search_flight: function (txt) {
|
||||
if (txt == "") return;
|
||||
this.querying.flight = true;
|
||||
query_flight(txt.replace(" ", "")).then((results) => {
|
||||
if (results.results == "") {
|
||||
this.query.flight = [];
|
||||
this.querying.flight = false;
|
||||
return;
|
||||
}
|
||||
this.query.flight = results.results;
|
||||
this.querying.flight = false;
|
||||
});
|
||||
},
|
||||
generate_icon: function (item, fcolor) {
|
||||
return L.AwesomeMarkers.icon({
|
||||
icon: icon_type(item) || "star",
|
||||
prefix: "fa",
|
||||
markerColor: fcolor || item.color || "blue",
|
||||
}).createIcon().outerHTML;
|
||||
},
|
||||
|
||||
axios.get('/api/'+this.journey_id).then(response =>{
|
||||
if(response.data=='') throw "Invalid Journey Data Received";
|
||||
app.journey_data = response.data;
|
||||
|
||||
for(let e of app.journey_data.main){
|
||||
if(e.dateRange){
|
||||
e.dateRange[0]= new Date(e.dateRange[0]);
|
||||
e.dateRange[1]= new Date(e.dateRange[1]);
|
||||
}
|
||||
e.step_title = e.step_title || [];
|
||||
}
|
||||
save_data: function () {
|
||||
this.impexp = toEncoded(JSON.stringify(this.journey_data));
|
||||
axios
|
||||
.post("/api/" + this.journey_id, this.journey_data)
|
||||
.then((response) => {
|
||||
console.log("Saved...");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn("Error! Could not reach the API.");
|
||||
});
|
||||
|
||||
|
||||
this.debounceSave = _.debounce(this.save_data, 500)
|
||||
this.debounceSearch = {"hotel":_.debounce((q)=>{this.querying.hotel=true;this.search_nominatim(q,(r)=>(r.type=="hotel" || r.type=="hostel")).then((r)=>{this.querying.hotel=false});}, 500),
|
||||
"restaurants":_.debounce((q)=>{this.querying.food=true;this.search_nominatim(q,(r)=>(is_restauration_type(r))).then((r)=>{this.querying.food=false});}, 500),
|
||||
"places":_.debounce((q)=>{this.querying.place=true;this.search_nominatim(q,(r)=>(is_attraction_type(r))).then((r)=>{this.querying.place=false});}, 500),
|
||||
"other":_.debounce((q)=>{this.querying.any=true;this.search_nominatim(q,(r)=>(true)).then((r)=>{this.querying.any=false});}, 500),
|
||||
"flight":_.debounce((q)=>this.search_flight(q), 500)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
journey_data: {
|
||||
handler:function (ndata, odata){
|
||||
this.debounceSave();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
import_data: function () {
|
||||
this.journey_data = Object.assign({}, JSON.parse(toDecoded(this.impexp)));
|
||||
this.journey_data.main.forEach((e) => {
|
||||
if (e.dateRange) {
|
||||
e.dateRange[0] = new Date(e.dateRange[0]);
|
||||
e.dateRange[1] = new Date(e.dateRange[1]);
|
||||
}
|
||||
});
|
||||
},
|
||||
export_data: function () {
|
||||
this.impexp = toEncoded(JSON.stringify(this.journey_data));
|
||||
},
|
||||
filter_selected: function (list, step) {
|
||||
return list.filter((e) =>
|
||||
step ? e.step == this.journey_step_data.day : e.step >= 0,
|
||||
);
|
||||
},
|
||||
filter_unselected: function (list) {
|
||||
return list.filter((e) => e.step == undefined || e.step < 0);
|
||||
},
|
||||
remove_item: function (list, idx) {
|
||||
list[idx].step = -1;
|
||||
list.splice(idx, 1);
|
||||
},
|
||||
log: function (e) {
|
||||
console.log(e);
|
||||
},
|
||||
})
|
||||
|
||||
keyboardEvent(e) {
|
||||
if (e.which === 13) {
|
||||
}
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
switch (e.key) {
|
||||
case "ArrowLeft":
|
||||
this.prev_step();
|
||||
break;
|
||||
case "ArrowRight":
|
||||
this.next_step();
|
||||
break;
|
||||
default:
|
||||
console.log(e.key);
|
||||
}
|
||||
});
|
||||
|
||||
axios.get("/api/" + this.journey_id).then((response) => {
|
||||
if (response.data == "") throw "Invalid Journey Data Received";
|
||||
app.journey_data = response.data;
|
||||
|
||||
for (let e of app.journey_data.main) {
|
||||
if (e.dateRange) {
|
||||
e.dateRange[0] = new Date(e.dateRange[0]);
|
||||
e.dateRange[1] = new Date(e.dateRange[1]);
|
||||
}
|
||||
e.step_title = e.step_title || [];
|
||||
}
|
||||
});
|
||||
|
||||
this.debounceSave = _.debounce(this.save_data, 500);
|
||||
this.debounceSearch = {
|
||||
hotel: _.debounce((q) => {
|
||||
this.querying.hotel = true;
|
||||
this.search_nominatim(
|
||||
q,
|
||||
(r) => r.type == "hotel" || r.type == "hostel",
|
||||
).then((r) => {
|
||||
this.querying.hotel = false;
|
||||
});
|
||||
}, 500),
|
||||
restaurants: _.debounce((q) => {
|
||||
this.querying.food = true;
|
||||
this.search_nominatim(q, (r) => is_restauration_type(r)).then((r) => {
|
||||
this.querying.food = false;
|
||||
});
|
||||
}, 500),
|
||||
places: _.debounce((q) => {
|
||||
this.querying.place = true;
|
||||
this.search_nominatim(q, (r) => is_attraction_type(r)).then((r) => {
|
||||
this.querying.place = false;
|
||||
});
|
||||
}, 500),
|
||||
other: _.debounce((q) => {
|
||||
this.querying.any = true;
|
||||
this.search_nominatim(q, (r) => true).then((r) => {
|
||||
this.querying.any = false;
|
||||
});
|
||||
}, 500),
|
||||
flight: _.debounce((q) => this.search_flight(q), 500),
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
journey_data: {
|
||||
handler: function (ndata, odata) {
|
||||
this.debounceSave();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
124
router/api.js
124
router/api.js
@ -1,68 +1,72 @@
|
||||
const axios = require('axios');
|
||||
const axios = require("axios");
|
||||
|
||||
module.exports = (fastify, opts, done) => {
|
||||
fastify.get('/flight/:id', async (req,reply) => {
|
||||
const ENDPOINT = 'https://www.flightradar24.com/v1/search/web/find';
|
||||
const FORMAT = '-';
|
||||
if(req.params.id){
|
||||
axios.get(ENDPOINT,{
|
||||
params: {
|
||||
format: FORMAT,
|
||||
query: req.params.id,
|
||||
limit:16,
|
||||
type: 'schedule'
|
||||
}
|
||||
}).then(res=>reply.send(res.data));
|
||||
}else{
|
||||
return reply.send([]);
|
||||
}
|
||||
return reply
|
||||
});
|
||||
fastify.get('/place/:id', async (req,reply) => {
|
||||
const ENDPOINT = 'https://nominatim.openstreetmap.org/';
|
||||
const FORMAT = 'jsonv2';
|
||||
if(req.params.id){
|
||||
axios.get(ENDPOINT,{
|
||||
params: {
|
||||
format: FORMAT,
|
||||
q: req.params.id,
|
||||
}
|
||||
}).then(res=>reply.send(res.data));
|
||||
}else{
|
||||
return reply.send([]);
|
||||
}
|
||||
return reply;
|
||||
});
|
||||
fastify.get("/flight/:id", async (req, reply) => {
|
||||
const ENDPOINT = "https://www.flightradar24.com/v1/search/web/find";
|
||||
const FORMAT = "-";
|
||||
if (req.params.id) {
|
||||
axios
|
||||
.get(ENDPOINT, {
|
||||
params: {
|
||||
format: FORMAT,
|
||||
query: req.params.id,
|
||||
limit: 16,
|
||||
type: "schedule",
|
||||
},
|
||||
})
|
||||
.then((res) => reply.send(res.data));
|
||||
} else {
|
||||
return reply.send([]);
|
||||
}
|
||||
return reply;
|
||||
});
|
||||
fastify.get("/place/:id", async (req, reply) => {
|
||||
const ENDPOINT = "https://nominatim.openstreetmap.org/";
|
||||
const FORMAT = "jsonv2";
|
||||
if (req.params.id) {
|
||||
axios
|
||||
.get(ENDPOINT, {
|
||||
params: {
|
||||
format: FORMAT,
|
||||
q: req.params.id,
|
||||
},
|
||||
})
|
||||
.then((res) => reply.send(res.data));
|
||||
} else {
|
||||
return reply.send([]);
|
||||
}
|
||||
return reply;
|
||||
});
|
||||
|
||||
fastify.get('/:id', async (req, reply) => {
|
||||
if(req.params.id == undefined)
|
||||
return reply.code(400).send({error:"No ID query parameter"});
|
||||
fastify.get("/:id", async (req, reply) => {
|
||||
if (req.params.id == undefined)
|
||||
return reply.code(400).send({ error: "No ID query parameter" });
|
||||
|
||||
fastify.level.db.get(req.params.id, (err, val) => {
|
||||
if(err){
|
||||
console.warn(err);
|
||||
reply.code(500).send();
|
||||
} else {
|
||||
reply.send(JSON.parse(val));
|
||||
}
|
||||
});
|
||||
return reply
|
||||
});
|
||||
fastify.level.db.get(req.params.id, (err, val) => {
|
||||
if (err) {
|
||||
console.warn(err);
|
||||
reply.code(500).send();
|
||||
} else {
|
||||
reply.send(JSON.parse(val));
|
||||
}
|
||||
});
|
||||
return reply;
|
||||
});
|
||||
|
||||
fastify.post('/:id', async (req, reply) => {
|
||||
if(req.params.id == undefined)
|
||||
return reply.code(400).send({error:"No ID query parameter"});
|
||||
fastify.post("/:id", async (req, reply) => {
|
||||
if (req.params.id == undefined)
|
||||
return reply.code(400).send({ error: "No ID query parameter" });
|
||||
|
||||
fastify.level.db.put(req.params.id, JSON.stringify(req.body), (err) => {
|
||||
if(err){
|
||||
console.warn(err);
|
||||
reply.code(500).send({error:"Error with DB"});
|
||||
} else {
|
||||
reply.send({content:"ok"});
|
||||
}
|
||||
});
|
||||
return reply
|
||||
});
|
||||
fastify.level.db.put(req.params.id, JSON.stringify(req.body), (err) => {
|
||||
if (err) {
|
||||
console.warn(err);
|
||||
reply.code(500).send({ error: "Error with DB" });
|
||||
} else {
|
||||
reply.send({ content: "ok" });
|
||||
}
|
||||
});
|
||||
return reply;
|
||||
});
|
||||
|
||||
done();
|
||||
done();
|
||||
};
|
50
server.js
50
server.js
@ -1,35 +1,35 @@
|
||||
const fastify = require('fastify')();//{ logger: true });
|
||||
const path = require('path');
|
||||
const fastify = require("fastify")(); //{ logger: true });
|
||||
const path = require("path");
|
||||
|
||||
fastify.register(require('@fastify/static'), {
|
||||
root: path.join(__dirname, 'public'),
|
||||
prefix: '/public/',
|
||||
fastify.register(require("@fastify/static"), {
|
||||
root: path.join(__dirname, "public"),
|
||||
prefix: "/public/",
|
||||
});
|
||||
|
||||
fastify.register(require('@fastify/leveldb'), {
|
||||
name: 'db'
|
||||
}, err => {
|
||||
if (err) throw err
|
||||
});
|
||||
fastify.register(
|
||||
require("@fastify/leveldb"),
|
||||
{
|
||||
name: "db",
|
||||
},
|
||||
(err) => {
|
||||
if (err) throw err;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.register(require("@fastify/view"), {
|
||||
engine: {
|
||||
pug: require("pug"),
|
||||
},
|
||||
engine: {
|
||||
pug: require("pug"),
|
||||
},
|
||||
});
|
||||
|
||||
fastify.register(require("./router/api"), { prefix: "/api" });
|
||||
|
||||
fastify.register(require('./router/api'), { prefix: '/api' });
|
||||
|
||||
|
||||
fastify.get('/', (req, reply) => reply.view("/template/home.pug", ));
|
||||
fastify.get('/:id', (req, reply) => reply.view('/template/journey.pug'));
|
||||
fastify.get('/view/:id', (req, reply) => reply.view('/template/view.pug'));
|
||||
fastify.get('/short/:id', (req, reply) => reply.view('/template/short.pug'));
|
||||
|
||||
|
||||
fastify.listen({port:8080,host:'0.0.0.0'} ,(err,address) => {
|
||||
if (err) throw err;
|
||||
console.log("Listening on", address);
|
||||
fastify.get("/", (req, reply) => reply.view("/template/home.pug"));
|
||||
fastify.get("/:id", (req, reply) => reply.view("/template/journey.pug"));
|
||||
fastify.get("/view/:id", (req, reply) => reply.view("/template/view.pug"));
|
||||
fastify.get("/short/:id", (req, reply) => reply.view("/template/short.pug"));
|
||||
|
||||
fastify.listen({ port: 8080, host: "0.0.0.0" }, (err, address) => {
|
||||
if (err) throw err;
|
||||
console.log("Listening on", address);
|
||||
});
|
12
yarn.lock
12
yarn.lock
@ -104,6 +104,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@lukeed/ms/-/ms-2.0.1.tgz#3c2bbc258affd9cc0e0cc7828477383c73afa6ee"
|
||||
integrity sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==
|
||||
|
||||
"@prettier/plugin-pug@^2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@prettier/plugin-pug/-/plugin-pug-2.5.1.tgz#ea7fc06cc6b86a5d3ad6607d8e50b2bab30fcc40"
|
||||
integrity sha512-rauAMYyTv6tgoo4DZ+1TwpytjaoJgcg+8btp0CfAUaHwjxfNo4cuAGgsJS6WVT7vx2Sz4Pkos50xuSNZuvJAWQ==
|
||||
dependencies:
|
||||
pug-lexer "^5.0.1"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
@ -908,6 +915,11 @@ pino@^8.12.0:
|
||||
sonic-boom "^3.1.0"
|
||||
thread-stream "^2.0.0"
|
||||
|
||||
prettier@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
||||
integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==
|
||||
|
||||
process-warning@^2.0.0, process-warning@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626"
|
||||
|
Loading…
x
Reference in New Issue
Block a user