Compare commits

...

19 Commits
master ... dev

Author SHA1 Message Date
soraefir
a2303c215e
wip
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-13 01:01:17 +01:00
soraefir
43dfc87546
wip
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-11 00:15:10 +01:00
7e0157880b Update src/template/module/view/toast.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:29:33 +01:00
74f681390a Update src/template/module/journey/map/travel.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:29:09 +01:00
021f410a67 Update src/template/module/journey/map/right_menu.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:28:47 +01:00
1bf9dd90b1 Update src/template/module/journey/map/override.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:28:27 +01:00
220088478e Update src/template/module/journey/map/mixin-marker.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:28:05 +01:00
4537653465 Update src/template/module/journey/leg/top_leg.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:27:40 +01:00
45791e9c7f Update src/template/module/journey/leg/drawer.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:27:20 +01:00
a9a947f81b Update src/template/module/journey/smallbar.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:26:43 +01:00
bd371e432e Update src/template/module/journey/map.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:26:18 +01:00
38b110db0d Update src/template/module/journey/main.pug
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:25:55 +01:00
e547df866f Update src/client/types/wrapper.ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:25:09 +01:00
2ddc4a113c Add src/client/types/toast.ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:24:48 +01:00
1c72e31ea8 Add src/client/types/search_drawer.ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:24:22 +01:00
5f80723fe9 Update src/client/helper/nav.ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:23:37 +01:00
b5918a47cb Update src/client/helper/journey.ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:23:07 +01:00
c8118fabe4 Update src/client/helper/api.ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:22:38 +01:00
4a33222e82 Update src/client/old.js
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-10 17:22:12 +01:00
24 changed files with 3469 additions and 2134 deletions

View File

@ -75,9 +75,7 @@ type NominatimResult = {
}; };
export const is_restauration_type = (e: NominatimResult) => export const is_restauration_type = (e: NominatimResult) =>
["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"].indexOf( ["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"].includes(e.type);
e.type
) != -1;
export const is_attraction_type = (e: NominatimResult): boolean => export const is_attraction_type = (e: NominatimResult): boolean =>
[ [
"tourism", "tourism",
@ -88,18 +86,18 @@ export const is_attraction_type = (e: NominatimResult): boolean =>
"historic", "historic",
"natural", "natural",
"waterway", "waterway",
].indexOf(e.category) != -1 || ].includes(e.category) ||
[ [
"place_of_worship", "place_of_worship",
"national_park", "national_park",
"nature_reserve", "nature_reserve",
"protected_area", "protected_area",
].indexOf(e.type) != -1 || is_travel_type(e); ].includes(e.type) || is_travel_type(e);
export const is_hotel_type = (e: NominatimResult): boolean => export const is_hotel_type = (e: NominatimResult): boolean =>
["hotel", "hostel", "guest_house"].indexOf(e.type) != -1 ["hotel", "hostel", "guest_house"].includes(e.type)
export const is_travel_type = (e: NominatimResult): boolean => export const is_travel_type = (e: NominatimResult): boolean =>
["bus_stop", "tram_stop", "station", , "aerodrome", "parking"].indexOf(e.type) != -1 ["bus_stop", "tram_stop", "station", , "aerodrome", "parking"].includes(e.type)
export const icon_type = (item: string | NominatimResult): string => { export const icon_type = (item: string | NominatimResult): string => {
@ -141,12 +139,17 @@ export const icon_type = (item: string | NominatimResult): string => {
"nature_reserve", "nature_reserve",
], ],
"dice-five": ["water_park", "theme_park", "casino"], "dice-five": ["water_park", "theme_park", "casino"],
"arrow-right": ["other"],
"train": ["train"],
"car-side":["car"],
"bycicle":["bike"],
"plane":["plane", "flight"],
"": ["?", "neighbourhood", "quarter", "highway", "place"], "": ["?", "neighbourhood", "quarter", "highway", "place"],
}; };
for (let k in types) { for (let k in types)
if (types[k].indexOf(t) >= 0 || types[k].indexOf(c) >= 0) return k; if (types[k].includes(t) || types[k].includes(c)) return k;
}
console.log(item.display_name, item.category, item.type); console.log(item.display_name, item.category, item.type);
return "question"; return "question";
}; };

View File

@ -17,7 +17,6 @@ const filter_existing = function (
return true; return true;
}); });
case "travel": case "travel":
console.log(r);
return r.filter((e) => { return r.filter((e) => {
if ( if (
leg.travel.find( leg.travel.find(
@ -44,9 +43,8 @@ const process_results = function (tpe: "nominatim" | "travel", r: geoloc[]) {
return rr; return rr;
}); });
case "travel": case "travel":
console.log(r);
return r.map((el) => { return r.map((el) => {
(el as any).path = getGeoLine( el.path = getGeoLine(
{ {
lat: (el as any).from_geo.lat, lat: (el as any).from_geo.lat,
lng: (el as any).from_geo.lon, lng: (el as any).from_geo.lon,

View File

@ -1,41 +0,0 @@
import journey_wrapper from './types/wrapper';
/* LIST HELPERS */
export const filter_selected = function (journey: journey_wrapper, list: geoloc[], step: boolean) {
return list.filter((e) =>
step ? e.step == journey.sel_day : e.step >= 0,
);
}
export const filter_unselected = function (list: geoloc[]) {
return list.filter((e) => e.step == undefined || e.step < 0);
}
export const remove_item = function (list: geoloc[], idx: number) {
list[idx].step = -1;
list.splice(idx, 1);
}
/* JOURNEY ADD/RM ITEM HELPER */
export const journey_add_place = function (journey: journey_wrapper, tpe: String, item: geoloc) {
switch (tpe) {
case 'hotel': return journey.leg_get().hotel = item;
case 'restaurant': return journey.leg_get().places.restaurants.push(item);
case 'place': return journey.leg_get().places.activities.push(item);
case 'other': return;
case 'flight': return journey.leg_get().travel.push(item);
}
}
export const journey_del_place = function (journey: journey_wrapper, tpe: String, idx: number) {
console.log(tpe)
switch (tpe) {
case "hotel": return journey.leg_get().hotel = null;
case "restaurants": return journey.leg_get().places.restaurants.splice(idx, 1);
case "activities": return journey.leg_get().places.activities.splice(idx, 1);
case "other": return;
case "flight": return journey.leg_get().travel.splice(idx, 1);
default: return true;
}
}

View File

@ -6,27 +6,24 @@ var nav = {
}; };
const sideScroll = function (element: Element, direction: 'left' | 'right' | 'none', speed: number, step: number) { const sideScroll = function (element: Element, direction: 'left' | 'right' | 'none', speed: number = 25, step: number = 15) {
nav.scrollDir = direction nav.scrollDir = direction
if (direction == 'none') return; if (direction == 'none') return;
nav.scrollInterval = setInterval(() => { (nav as any).scrollInterval = setInterval(() => {
element.scrollLeft += (direction == 'left') ? -step : step; element.scrollLeft += (direction == 'left') ? -step : step;
}, speed); }, speed);
} }
export const focus_leg = function (journey: journey_wrapper, idx: number | null = null) { export const focus_leg = function (journey: journey_wrapper, idx: number | null = null) {
const c = document.querySelector('.scroll-content.nav-leg')!! const c = document.querySelector('.scroll-content.nav-leg')!!
console.log(idx, c, journey)
const item = c.children[(idx != null ? idx : journey.sel_leg) + 1]; const item = c.children[(idx != null ? idx : journey.sel_leg) + 1];
c.scrollLeft = (item as any).offsetLeft + ((item as any).offsetWidth / 2) - (c as any).offsetWidth / 2 c.scrollLeft = (item as any).offsetLeft + ((item as any).offsetWidth / 2) - (c as any).offsetWidth / 2
} }
export const focus_day = function (journey: journey_wrapper, idx: number | null = null) { export const focus_day = function (journey: journey_wrapper, idx: number | null = null) {
const c = document.querySelector('.scroll-content.nav-day')!! const c = document.querySelector('.scroll-content.nav-day')!!
console.log(idx, c, journey)
const item = c.children[(idx != null ? idx : journey.sel_day) + 1]; const item = c.children[(idx != null ? idx : journey.sel_day) + 1];
c.scrollLeft = (item as any).offsetLeft + ((item as any).offsetWidth / 2) - (c as any).offsetWidth / 2; c.scrollLeft = (item as any).offsetLeft + ((item as any).offsetWidth / 2) - (c as any).offsetWidth / 2;
//focus_leg(journey) // We dont render both navs anymore
} }
export const nav_mousemove = function (e: PointerEvent) { export const nav_mousemove = function (e: PointerEvent) {
@ -38,7 +35,7 @@ export const nav_mousemove = function (e: PointerEvent) {
(left > c.offsetWidth * 0.9 ? 'right' : 'none') (left > c.offsetWidth * 0.9 ? 'right' : 'none')
if (!nav.scrollInterval || nav.scrollDir != newDir) { if (!nav.scrollInterval || nav.scrollDir != newDir) {
if (nav.scrollInterval) clearInterval(nav.scrollInterval) if (nav.scrollInterval) clearInterval(nav.scrollInterval)
sideScroll(c, newDir, 25, 10); sideScroll(c, newDir);
} }
} }
export const nav_mouseleave = function (_e: PointerEvent) { export const nav_mouseleave = function (_e: PointerEvent) {

View File

@ -2,10 +2,11 @@
import * as api from "./api"; import * as api from "./api";
import journey_wrapper from "./types/wrapper"; import journey_wrapper from "./types/wrapper";
import { migrator } from "./types/migration"; import { migrator } from "./types/migration";
import { journey_add_place, journey_del_place } from "./helper/journey";
import { search_nominatim, search_flight } from "./helper/api";
import { focus_day, focus_leg, nav_mouseleave, nav_mousemove } from "./helper/nav"; import { focus_day, focus_leg, nav_mouseleave, nav_mousemove } from "./helper/nav";
import { set_search_set_results } from "./helper/api"; import { set_search_set_results } from "./helper/api";
import toast from "./types/toast";
import search_drawer from "./types/search_drawer";
import { to_xy } from "./types/geom";
Vue.component("l-map", window.Vue2Leaflet.LMap); Vue.component("l-map", window.Vue2Leaflet.LMap);
Vue.component("l-tile-layer", window.Vue2Leaflet.LTileLayer); Vue.component("l-tile-layer", window.Vue2Leaflet.LTileLayer);
@ -19,22 +20,10 @@ Vue.component("l-control-scale", window.Vue2Leaflet.LControlScale);
const app = new Vue({ const app = new Vue({
el: "#app", el: "#app",
data: { data: {
edit_active: ["view", "short"].indexOf(window.location.pathname.split("/")[1]) == -1, journey: new journey_wrapper(),
journey: new journey_wrapper(window.location.pathname.split("/").pop() || String.gen_id(16)), search_drawer: new search_drawer(),
map_override: { active: false, elements: [] },
query: {
type: "", query: "", res: [], load: false, sub: false, note: false, drawer: false, addmarker: false,
},
useDay: false, useDay: false,
toast: { toast: new toast(),
show: false,
title: '',
desc: '',
special: '',
acceptText: '',
cancelText: '',
func: () => { },
},
lang: { lang: {
format: "ddd D MMM", format: "ddd D MMM",
formatLocale: { formatLocale: {
@ -46,18 +35,14 @@ const app = new Vue({
methods: { methods: {
start_journey: function () { window.location.href = "/" + this.journey.id }, start_journey: function () { window.location.href = "/" + this.journey.id },
compute_bb: function () {
if (!this.$refs.map) return undefined
const bounds = this.$refs.map.mapObject.getBounds();
return [[bounds.getSouthWest().lng, bounds.getSouthWest().lat],
[bounds.getNorthEast().lng, bounds.getNorthEast().lat]]
},
generate_rotation: function (index, list) { generate_rotation: function (index, list) {
if (index < 0 || index >= list.length) return 0; if (index < 0 || index >= list.length) return 0;
const c0 = list[(index == 0) ? index : (index - 1)] const c0 = to_xy(list[(index == 0) ? index : (index - 1)])
const c1 = list[(index == list.length - 1) ? index : (index + 1)] const c1 = to_xy(list[(index == (list.length - 1)) ? index : (index + 1)])
const brng = Math.atan2(c1[1] - c0[1], c1[0] - c0[0]); const brng = Math.atan2(c1[1] - c0[1], c1[0] - c0[0])- Math.PI / 2;
return `rotate:${brng - Math.PI / 2}rad`; const flip = (brng>-Math.PI/2)?'transform: scale(1,1);': 'transform: scale(1, -1);';
const rot = `rotate:${brng}rad;`
return `${rot}${flip}`;
}, },
generate_marker: function (item, fcolor) { generate_marker: function (item, fcolor) {
@ -72,106 +57,6 @@ const app = new Vue({
return `<i class="fa fa-${api.icon_type(item) || "star"} fa-2x ${classes}" style="${styling}; color:${fcolor || "white"}; text-align:center; align-content:center;"></i>`; return `<i class="fa fa-${api.icon_type(item) || "star"} fa-2x ${classes}" style="${styling}; color:${fcolor || "white"}; text-align:center; align-content:center;"></i>`;
}, },
import_data: function (v) {
this.journey.data = Object.assign(
{},
JSON.parse(v.toDecoded()),
);
this.journey.data.main.forEach((e) => {
if (e.date_range) {
e.date_range[0] = new Date(e.date_range[0]);
e.date_range[1] = new Date(e.date_range[1]);
}
});
},
toast_reset: function () {
this.toast.show = false;
this.toast.title = '';
this.toast.desc = ''
this.toast.special = '';
this.toast.acceptText = ''
this.toast.cancelText = ''
this.toast.func = () => { }
},
toast_impexp: function (is_import) {
this.toast.show = true;
this.toast.title = is_import ? 'Import' : 'Export';
this.toast.desc = ''
this.toast.special = JSON.stringify(this.journey.data).toEncoded();
this.toast.acceptText = is_import ? 'load' : ''
this.toast.cancelText = 'cancel'
this.toast.func = () => { this.import_data(this.toast.special); this.toast_reset(); }
},
search_set_results(r) {
this.query.load = false;
this.query.res = r;
return r
},
search_set_clear(keep_drawer) {
this.query.res = [];
this.query.note = false;
this.query.type = null;
this.query.addmarker = false;
this.query.sub = false;
this.query.drawer = keep_drawer;
setTimeout(() => this.$refs.map.mapObject.invalidateSize(), 500);
},
search_set_active: function (is_notes, tpe) {
this.query.drawer = true;
this.query.note = is_notes;
this.query.type = !is_notes ? tpe : null;
this.query.load = !is_notes;
setTimeout(() => this.$refs.map.mapObject.invalidateSize(), 500);
},
drawer_hover_item: function (item) {
if (item) {
this.map_override.active = true
if (item.type == 'flight') {
this.map_override.elements = [[item.from_geo.lat, item.from_geo.lon], [item.to_geo.lat, item.to_geo.lon]]
} else {
this.map_override.elements = [[item.lat, item.lon]]
}
} else {
this.map_override.active = false
}
},
drawer_click_item: function (item) {
const tpe = this.query.type
this.search_set_clear(item ? true : false);
this.drawer_hover_item();
if (item) {
item.step = -1;
journey_add_place(this.journey, tpe, item)
}
},
search_active: function (_e) {
const tpe = this.query.type;
const query = this.query.query;
switch (tpe) {
case 'hotel':
case 'restaurant': ;
case 'place':
case 'other':
return search_nominatim(tpe, query, this.compute_bb(), this.journey.leg_get())
case 'flight':
return search_flight(tpe, query, this.journey.leg_get());
}
},
search_enable: function (f) {
const is_notes = f == 'notes';
this.search_set_active(is_notes, f)
setTimeout(() => document.getElementById(is_notes ? 'query_note' : 'query_input').focus(), 500);
if (!is_notes) this.search_active()
},
refreshTextAreaHeight: function (e) { refreshTextAreaHeight: function (e) {
e.target.style['height'] = 'auto'; e.target.style['height'] = 'auto';
e.target.style['height'] = e.target.scrollHeight + 'px'; e.target.style['height'] = e.target.scrollHeight + 'px';
@ -179,53 +64,62 @@ const app = new Vue({
}, },
onMapClick(e) { onMapClick(e) {
if (this.query.addmarker) { if (this.search_drawer.query.addmarker) {
const newMarker = { const newMarker = {
latlon: [e.latlng.lat, e.latlng.lng], latlon: [e.latlng.lat, e.latlng.lng],
sname: this.query.query, sname: this.search_drawer.query.query,
type: this.query.type, type: this.search_drawer.query.type,
}; };
this.drawer_click_item(newMarker) this.search_drawer.click_item(this.journey,newMarker)
} }
}, },
}, },
created: function () { created: function () {
set_search_set_results(this.search_set_results); set_search_set_results((r) => { this.search_drawer.results(r); });
this.nav_mouseleave = nav_mouseleave; this.nav_mouseleave = nav_mouseleave;
this.nav_mousemove = nav_mousemove; this.nav_mousemove = nav_mousemove;
this.focus_day = focus_day; this.focus_day = focus_day;
this.focus_leg = focus_leg; this.focus_leg = focus_leg;
this.place_delete = (tpe, idx) => journey_del_place(this.journey, tpe, idx);
this.save_data = api.throttle(() => { this.save_data = api.throttle(() => {
api.save(this.journey.id, this.journey.data); api.save(this.journey.id, this.journey.data);
}, 1000); }, 2000);
this.pressed_prev = api.throttle(() => { this.pressed_prev_next = api.throttle((dir_prev) => {
if (this.useDay) this.journey.day_prev(); if (this.useDay && dir_prev) return this.journey.day_prev();
else this.journey.leg_prev(); if (!this.useDay && dir_prev) return this.journey.leg_prev();
}, 250) if (this.useDay && !dir_prev) return this.journey.day_next();
this.pressed_next = api.throttle(() => { if (!this.useDay && !dir_prev) return this.journey.leg_next();
if (this.useDay) this.journey.day_next();
else this.journey.leg_next();
}, 250) }, 250)
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
switch (e.key) { switch (e.key) {
case "ArrowLeft": return this.pressed_prev() case "ArrowLeft": return this.pressed_prev_next(true)
case "ArrowRight": return this.pressed_next() case "ArrowRight": return this.pressed_prev_next(false)
default: return console.log(e.key); default: return;
} }
}); });
api.load(this.journey.id).then((r) => { api.load(this.journey.id).then((r) => {
app.journey.data = migrator(r) app.journey.data = migrator(r)
setTimeout(() => focus_leg(this.journey) && focus_day(this.journey) && this.$refs.map.mapObject.keyboard.disable(), 50);
;
this.search_drawer.refresh_map = () => { this.$refs.map.mapObject.invalidateSize() }
this.search_drawer.get_leg = () => { return this.journey.leg_get() }
this.search_drawer.get_bb = () => {
if (!this.$refs.map) return []
const bounds = this.$refs.map.mapObject.getBounds();
return [[bounds.getSouthWest().lng, bounds.getSouthWest().lat],
[bounds.getNorthEast().lng, bounds.getNorthEast().lat]]
}
}); });
}, },
watch: { watch: {
journey: { journey: {
handler: function (ndata, odata) { handler: function (ndata, odata) {
if (this.edit_active) this.save_data(); if (this.journey.edit) this.save_data();
window.location.hash = `#${this.journey.sel_leg}_${this.journey.sel_day}`
}, },
deep: true, deep: true,
}, },

View File

@ -7,10 +7,12 @@ declare global {
} }
interface geoloc { interface geoloc {
title: string title: string
osm_id: number
latlon: [number, number]
notes: string notes: string
step: -1 type?: string
osm_id?: number
latlon?: [number, number]
step?: -1
path?: number[][]
} }
interface map { interface map {

View File

@ -160,6 +160,15 @@ function recursiveMidPoint(src: LatLng, dst: LatLng, opt: { step?: number, dist?
return geom; return geom;
} }
export function getGeoLine(src: LatLng, dst: LatLng, opt: { step?: number, dist?: number }) { export function to_xy(point: number[]){
const R = 6378137; // Earth's radius in meters (WGS84 standard)
const lon =((point[0] + 180) % 360 + 360) % 360 - 180;
const lat = ((point[1] + 90) % 180 + 180) % 180 - 90;
const x = R * (lon* Math.PI / 180);
const y = R * Math.log(Math.tan((Math.PI / 4) + (lat * Math.PI / 360)));
return [x, y];
}
export function getGeoLine(src: LatLng, dst: LatLng, opt: { step?: number, dist?: number }): LatLng[] {
return recursiveMidPoint(src, dst, opt, 1) return recursiveMidPoint(src, dst, opt, 1)
} }

View File

@ -8,7 +8,7 @@ function migrate_A_to_0(e: journey): journey {
v.day_title = v.day_title || (v as any).step_title; v.day_title = v.day_title || (v as any).step_title;
v.places.activities = v.places.activities || (v as any).places.places; v.places.activities = v.places.activities || (v as any).places.places;
v.travel = v.travel || []; v.travel = v.travel || [];
v.day_title = typeof (v.day_title) == "string" ? [v.day_title] : [] v.day_title = typeof (v.day_title) == "string" ? [v.day_title] : [];
}) })
console.log(e) console.log(e)
return e; return e;

View File

@ -0,0 +1,150 @@
import { search_flight, search_nominatim } from "../helper/api";
import { leg_template } from "./format";
import journey_wrapper from "./wrapper";
declare global {
type query_type = "hotel" | "restaurant" | "place" | "flight"
type note_type = "notes"
type sub_type = "travel" | "other"
interface query {
type: query_type | note_type | "",
sub: sub_type | "",
query: string,
res: any[],
load: boolean,
/* DELETE BELOW IF POSIBLE */
addmarker: Boolean,
}
interface map_override {
active: Boolean,
elements: number[][]
}
}
class search_drawer {
map_override: map_override = { active: false, elements: [] };
multipath : any[]=[];
query: query = {
type: "", query: "", sub: "", res: [], load: false, addmarker: false,
}
resfresh_map: () => void = () => { }
get_bb: () => any[] = () => []
get_leg: () => leg = () => leg_template
constructor() {
}
show_drawer() {
return this.is_note() || this.is_query()
}
is_note() {
return ["notes"].includes(this.query.type)
}
is_place(){
return ["hotel", "restaurant", "place"].includes(this.query.type)
}
is_query() {
return this.is_place()|| this.is_flight() || this.is_multipath()
}
is_sub_travel() {
return ["travel"].includes(this.query.sub)
}
is_multipath(){
return ["flight", "train", "car","bike", "other"].includes(this.query.type)
}
is_flight(){
return ["flight"].includes(this.query.type)
}
results(r: any[]) {
this.query.load = false;
this.query.res = r;
return r
}
reset() {
this.query.res = [];
this.multipath = [];
this.map_override.elements = []
this.query.type = "";
this.query.addmarker = false;
setTimeout(() => this.resfresh_map(), 500);
}
active(tpe: query_type) {
this.query.type = tpe;
this.query.load = this.is_query();
setTimeout(() => this.resfresh_map(), 500);
}
search(_e = null) {
const tpe = this.query.type;
const query = this.query.query;
switch (tpe) {
case 'flight':
return search_flight(tpe, query, this.get_leg());
default:
return search_nominatim(tpe, query, this.get_bb(), this.get_leg())
}
}
enable(f: query_type) {
this.active(f)
setTimeout(() => document.getElementById(this.is_note() ? 'query_note' : 'query_input')?.focus(), 500);
if (this.is_query()) this.search()
}
hover_item (item?:any) {
if (item) {
this.map_override.active = true
if (item.type == 'flight') {
this.map_override.elements.push([item.from_geo.lat, item.from_geo.lon])
this.map_override.elements.push([item.to_geo.lat, item.to_geo.lon])
} else {
this.map_override.elements.push([item.lat, item.lon])
}
} else {
this.map_override.active = false
}
}
click_item (journey: journey_wrapper, item?:any) {
const tpe = this.query.type
if(this.is_multipath()){
if(this.query.addmarker){
if(item){
this.map_override.active = true
this.map_override.elements.push(item.latlon? item.latlon: [item.lat,item.lon])
this.multipath.push(item)
return;
}else{
let new_item :geoloc = {
type: (tpe as string),
path: this.map_override.elements,
title: '-',
notes: "",
}
this.map_override.active = false
journey.add_place(tpe, new_item)
}
}
}
this.reset();
this.hover_item();
if (item) {
item.step = -1;
journey.add_place(tpe, item)
}
}
}
export default search_drawer;

62
src/client/types/toast.ts Normal file
View File

@ -0,0 +1,62 @@
import journey_wrapper from "./wrapper";
import { focus_leg } from "../helper/nav";
interface toaster {
show: boolean,
title: String,
desc: String,
special: String,
acceptText: String,
cancelText: String,
func: () => void,
}
const default_toaster = {
show: false,
title: ' ',
desc: ' ',
special: ' ',
acceptText: ' ',
cancelText: ' ',
func: () => { }
}
class toast {
data: toaster = default_toaster;
reset = function (): void {
this.data.show = false
this.data.title = ' '
this.data.desc = ' '
this.data.special = ' '
this.data.acceptText = ' '
this.data.cancelText = ' '
this.data.func = () => { };
}
constructor() {
this.reset()
}
impexp = function (is_import: boolean, journey: journey_wrapper): void {
this.data.show = true;
this.data.title = is_import ? 'Import' : 'Export';
this.data.desc = ''
this.data.special = JSON.stringify(journey.data).toEncoded();
this.data.acceptText = is_import ? 'load' : ''
this.data.cancelText = 'cancel'
this.data.func = () => { journey.import_data(JSON.parse(this.data.special.toDecoded().toString())); this.reset(toast); }
}
delete_leg = function (v: number, journey: journey_wrapper): void {
this.data.show = true;
this.data.title = `Delete Leg`;
this.data.desc = `Remove leg <${journey.leg_get(v).title || v}>`
this.data.special = '';
this.data.acceptText = 'delete'
this.data.cancelText = 'cancel'
this.data.func = () => { journey.rm_leg(v); focus_leg(journey); this.reset(toast); }
}
}
export default toast;

View File

@ -6,12 +6,16 @@ const date_day_diff = (d0: Date, d1: Date): number =>
class journey_wrapper { class journey_wrapper {
id: String id: String
edit: Boolean = true
data: journey = journey_template; data: journey = journey_template;
sel_leg: number = 0; sel_leg: number = 0;
sel_day: number = 0; sel_day: number = 0;
constructor(id: String) { constructor(id: String | undefined) {
this.id = id; if (id)
this.id = id;
else
this.from_url()
} }
leg_first = () => this.data.main[0] leg_first = () => this.data.main[0]
@ -61,6 +65,9 @@ class journey_wrapper {
day_sel(idx: number): void { day_sel(idx: number): void {
this.sel_day = idx; this.sel_day = idx;
} }
day_count(): number {
return this.leg_len();
}
day_next() { day_next() {
this.sel_day += 1 this.sel_day += 1
if (this.sel_day > this.leg_len() - 1) { if (this.sel_day > this.leg_len() - 1) {
@ -151,6 +158,50 @@ class journey_wrapper {
} }
} }
} }
import_data(v: any) {
this.data = Object.assign({}, v);
this.data.main.forEach((e) => {
if (e.date_range) {
e.date_range[0] = new Date(e.date_range[0]);
e.date_range[1] = new Date(e.date_range[1]);
}
});
}
add_place(tpe: String, item: geoloc) {
switch (tpe) {
case 'hotel': return this.leg_get().hotel = item;
case 'restaurant': return this.leg_get().places.restaurants.push(item);
case 'place': return this.leg_get().places.activities.push(item);
case 'other':
case 'train':
case 'car':
case 'bike':
case 'flight': return this.leg_get().travel.push(item);
}
}
del_place(tpe: String, idx: number) {
switch (tpe) {
case "hotel": return this.leg_get().hotel = null;
case "restaurants": return this.leg_get().places.restaurants.splice(idx, 1);
case "activities": return this.leg_get().places.activities.splice(idx, 1);
case "other": return;
case "flight": return this.leg_get().travel.splice(idx, 1);
default: return true;
}
}
from_url() {
const path = window.location.pathname.slice(1).split('/')
const hash = window.location.hash.slice(1).split('_')
this.id = path.at(-1) || String.gen_id(16).toString()
this.sel_leg = parseInt(hash.at(0) || '0')
this.sel_day = parseInt(hash.at(1) || '0')
this.edit = !["view", "short"].includes(path.at(0) || "")
}
} }
export default journey_wrapper; export default journey_wrapper;

View File

@ -13,8 +13,8 @@ interface FlightData {
function clean_times(s: string): string | null { function clean_times(s: string): string | null {
if (s == "—" || s == "Scheduled") return null if (s == "—" || s == "Scheduled") return null
if (s.indexOf("Estimated departure") == 0) return null if (s.startsWith("Estimated departure")) return null
if (s.indexOf("Landed") == 0) return s.replace("Landed ", "") if (s.startsWith("Landed")) return s.replace("Landed ", "")
return s return s
} }

View File

@ -2,49 +2,86 @@
.input.w-100.text-dark .input.w-100.text-dark
input#query_input( input#query_input(
type="search" type="search"
@input="search_active" @input="search_drawer.search()"
@focus="search_active" @focus="search_drawer.search()"
placeholder="Search ... " placeholder="Search ... "
style="width:85%;" style="width:85%;"
:disabled="query.note" :disabled="search_drawer.is_note()"
v-model="query.query" v-model="search_drawer.query.query"
) )
.spinner(v-if="query.load") .spinner(v-if="search_drawer.query.load")
div(v-if="['hotel', 'restaurant', 'place','other', 'travel'].indexOf(query.type)>=0") div(v-if="search_drawer.is_query()")
template(v-for="(item, idx) in query.res" ) div(v-if="search_drawer.is_flight()")
template(v-for="(item, idx) in search_drawer.query.res" )
.query-result.col-12.bg-white.text-dark(
:key="'q'+idx"
@mouseover="search_drawer.hover_item(item)"
@mouseleave="search_drawer.hover_item()"
@click="search_drawer.click_item(journey,item)" )
div( v-html="generate_icon('plane', 'var(--dark)')")
.col-10()
| {{ item.from }} => {{item.to}}
.bg-dark.divider(
:key="'qdiv'+idx" style="height:1px" )
.query-result.col-12.bg-white.text-dark( .query-result.col-12.bg-white.text-dark(
:key="'q'+idx" v-if="!search_drawer.query.addmarker"
@mouseover="drawer_hover_item(item)" @click="search_drawer.query.addmarker=true" )
@mouseleave="drawer_hover_item()" div( v-html="generate_icon('star', 'var(--dark)')")
@click="drawer_click_item(item)" )
div( v-html="generate_icon(item, 'var(--dark)')")
.col-10() .col-10()
| {{ item.name }} | Start custom
.bg-dark.divider( .query-result.col-12.bg-white.text-dark(
:key="'qdiv'+idx" style="height:1px" ) v-else
.query-result.col-12.bg-white.text-dark( @click="search_drawer.click_item(journey)" )
v-if="query.load==false && query.res.length>=0 && query.query!=''" div( v-html="generate_icon('star', 'var(--dark)')")
@click="query.addmarker=true" ) .col-10()
div( v-html="generate_icon('star', 'var(--dark)')") | Finish
.col-10() div(v-else-if="search_drawer.is_multipath()")
| Add custom template(v-for="(item, idx) in search_drawer.query.res" )
.query-result.col-12.bg-white.text-dark(
:key="'q'+idx"
@mouseover="search_drawer.hover_item(item)"
@mouseleave="search_drawer.hover_item()"
@click="search_drawer.click_item(journey,item)" )
div( v-html="generate_icon('plane', 'var(--dark)')")
.col-10()
| {{ item.from }} => {{item.to}}
.bg-dark.divider(
:key="'qdiv'+idx" style="height:1px" )
.query-result.col-12.bg-white.text-dark(
v-if="!search_drawer.query.addmarker"
@click="search_drawer.query.addmarker=true" )
div( v-html="generate_icon('star', 'var(--dark)')")
.col-10()
| Start custom
.query-result.col-12.bg-white.text-dark(
v-else
@click="search_drawer.click_item(journey)" )
div( v-html="generate_icon('star', 'var(--dark)')")
.col-10()
| Finish
div(v-else-if="true")
template(v-for="(item, idx) in search_drawer.query.res" )
.query-result.col-12.bg-white.text-dark(
:key="'q'+idx"
@mouseover="search_drawer.hover_item(item)"
@mouseleave="search_drawer.hover_item()"
@click="search_drawer.click_item(journey,item)" )
div( v-html="generate_icon(item, 'var(--dark)')")
.col-10()
| {{ item.name }}
.bg-dark.divider(
:key="'qdiv'+idx" style="height:1px" )
.query-result.col-12.bg-white.text-dark(
v-if="!search_drawer.query.load && search_drawer.query.res && search_drawer.is_query()"
@click="search_drawer.query.addmarker=true" )
div( v-html="generate_icon('star', 'var(--dark)')")
.col-10()
| Add custom
.col-12.text-white.text-center( .col-12.text-white.text-center(
) {{query.load? `Loading ...` : `Found ${query.res.length} results`}} ) {{search_drawer.query.load? `Loading ...` : `Found ${search_drawer.query.res.length} results`}}
div(v-else-if="['flight'].indexOf(query.type)>=0") div(v-else)
template(v-for="(item, idx) in query.res" ) template()
.query-result.col-12.bg-white.text-dark( .query-result.col-12.bg-white.text-dark()
:key="'q'+idx" | Unsuppored Query type {{search_drawer.query.type}}
@mouseover="drawer_hover_item(item)"
@mouseleave="drawer_hover_item()"
@click="drawer_click_item(item)" )
div( v-html="generate_icon('plane', 'var(--dark)')")
.col-10()
| {{ item.from }} => {{item.to}}
.bg-dark.divider(
:key="'qdiv'+idx" style="height:1px" )
div(v-else)
template()
.query-result.col-12.bg-white.text-dark()
| Unsuppored Query type {{query.type}}

View File

@ -26,7 +26,7 @@
.text {{ element.title || "Leg "+idx}} .text {{ element.title || "Leg "+idx}}
i.fa.fa-times.text-small.fright( i.fa.fa-times.text-small.fright(
style="top: 2px; right: 2px; position: absolute", style="top: 2px; right: 2px; position: absolute",
@click="journey.rm_leg(idx); focus_leg(journey);" @click="toast.delete_leg(idx, journey)"
) )
.list-group-item.bg-white.text-white.show.placeholder-right( .list-group-item.bg-white.text-white.show.placeholder-right(
:class="journey.sel_leg < journey.data.main.length-1? '': 'bg-dark'" :class="journey.sel_leg < journey.data.main.length-1? '': 'bg-dark'"

View File

@ -16,10 +16,10 @@ include smallbar.pug
template(v-else) template(v-else)
include leg/top_leg.pug include leg/top_leg.pug
.row(style="aspect-ratio:1.25;min-height:432px;max-height:calc(100vh - 125px);width:calc(100% + 24px);") .row(style="aspect-ratio:1.25;min-height:432px;max-height:calc(100vh - 125px);width:calc(100% + 24px);")
.map-container(:class=" { 'col-2 col-sm-5 col-md-8': query.type, 'col-2 col-sm-5 col-md-6': query.note , 'col-12': (!query.type && !query.note) }" ) .map-container(:class=" { 'col-2 col-sm-5 col-md-8': search_drawer.is_query(), 'col-2 col-sm-5 col-md-6': search_drawer.is_note() , 'col-12': (!search_drawer.is_query() && !search_drawer.is_note()) }" )
include map.pug include map.pug
.row.drawer-container(:class="{ 'col-10 col-sm-7 col-md-4': query.type, 'col-10 col-sm-7 col-md-6': query.note, 'col-0': (!query.type && !query.note) }") .row.drawer-container(:class="{ 'col-10 col-sm-7 col-md-4': search_drawer.is_query(), 'col-10 col-sm-7 col-md-6': search_drawer.is_note(), 'col-0': (!search_drawer.is_query() && !search_drawer.is_note()) }")
.h-100(:class="{ 'w-100 ': query.type, 'w-0': !query.type }") .h-100(:class="{ 'w-100 ': search_drawer.is_query(), 'w-0': !search_drawer.is_query() }")
include leg/drawer.pug include leg/drawer.pug
.h-100(:class="{ 'w-100': query.note, 'w-0': !query.note }") .h-100(:class="{ 'w-100': search_drawer.is_note(), 'w-0': !search_drawer.is_note() }")
include leg/drawer-notes.pug include leg/drawer-notes.pug

View File

@ -4,7 +4,11 @@ l-map(
@click="onMapClick" @click="onMapClick"
style="height:100%" style="height:100%"
no-blocking-animations=true no-blocking-animations=true
:style="query.addmarker?'cursor:crosshair':''" :max-bounds="[[-90, -180],[90, 180]]"
:max-bounds-viscosity="1.0"
:world-copy-jump="true"
:no-wrap="true"
:style="search_drawer.query.addmarker?'cursor:crosshair':''"
ref="map" ref="map"
) )
l-tile-layer( l-tile-layer(
@ -19,5 +23,5 @@ l-map(
include map/restaurants.pug include map/restaurants.pug
include map/travel.pug include map/travel.pug
template(v-if="edit_active") template(v-if="journey.edit")
include map/right_menu.pug include map/right_menu.pug

View File

@ -19,19 +19,19 @@ mixin map_marker(place, color_sel_c, color_sel_o, color_else)
:options="{maxWidth:400, minWidth:300}") :options="{maxWidth:400, minWidth:300}")
h1.row.text-medium.text-center {{ place.sname }} h1.row.text-medium.text-center {{ place.sname }}
span.row.text-small.text-gray {{ place.display_name }} span.row.text-small.text-gray {{ place.display_name }}
span(v-if="edit_active") span(v-if="journey.edit")
.row.input() .row.input()
textarea.col-12.col-sm-12.text-small( textarea.col-12.col-sm-12.text-small(
placeholder="", placeholder="",
v-model="place.notes", v-model="place.notes",
) )
.leaflet-popup-button-group(v-if="edit_active") .leaflet-popup-button-group(v-if="journey.edit")
a.text-gray( a.text-gray(
v-on:click.prevent="place.step = ((place.step==journey.sel_day)?-1:journey.sel_day)" v-on:click.prevent="place.step = ((place.step==journey.sel_day)?-1:journey.sel_day)"
v-html="generate_icon(((place.step==journey.sel_day)?'calendar-xmark':'calendar-plus'), 'NA')" v-html="generate_icon(((place.step==journey.sel_day)?'calendar-xmark':'calendar-plus'), 'NA')"
) )
a.text-gray( a.text-gray(
v-on:click.prevent="place_delete(\""+place+"\",index)" v-on:click.prevent="journey.del_place(\""+place+"\",index)"
v-html="generate_icon('trash', 'NA')" v-html="generate_icon('trash', 'NA')"
) )
span.row.text-small.text-dark(v-else) {{ place.notes }} span.row.text-small.text-dark(v-else) {{ place.notes }}

View File

@ -1,11 +1,11 @@
l-marker( l-marker(
v-if="map_override.active", v-if="search_drawer.map_override.active",
v-for="(el, idx) in map_override.elements" v-for="(el, idx) in search_drawer.map_override.elements"
:key="'ovr'+idx" :key="'ovr'+idx"
:lat-lng="el" :lat-lng="el"
) )
l-icon(v-html="generate_marker('plus', 'darkgreen')") l-icon(v-html="generate_marker('plus', 'darkgreen')")
l-polyline( l-polyline(
v-if="map_override.active && map_override.elements.length>1" v-if="search_drawer.map_override.active && search_drawer.map_override.elements.length>1"
:lat-lngs="map_override.elements" :color="'darkgreen'" :lat-lngs="search_drawer.map_override.elements" :color="'darkgreen'"
) )

View File

@ -1,21 +1,21 @@
.map-menu.map-menu-top .map-menu.map-menu-top
div(v-if="query.type" @click="drawer_click_item()" ) div(v-if="search_drawer.is_query()" @click="search_drawer.click_item(journey)" )
.map-menu-item(v-html="generate_icon('close')") .map-menu-item(v-html="generate_icon('close')")
div(v-if="!query.type" @click="search_enable('hotel')") div(v-if="!search_drawer.is_query()" @click="search_drawer.enable('hotel')")
.map-menu-item( v-html="generate_icon('bed')") .map-menu-item( v-html="generate_icon('bed')")
div(v-if="!query.type" @click="search_enable('restaurant')") div(v-if="!search_drawer.is_query()" @click="search_drawer.enable('restaurant')")
.map-menu-item( v-html="generate_icon('utensils')") .map-menu-item( v-html="generate_icon('utensils')")
div(v-if="!query.type" @click="search_enable('place')") div(v-if="!search_drawer.is_query()" @click="search_drawer.enable('place')")
.map-menu-item( v-html="generate_icon('star')") .map-menu-item( v-html="generate_icon('star')")
.map-menu-sub(v-if="!query.type" @mouseenter="query.sub=true" @mouseleave="query.sub=false" ) .map-menu-sub(v-if="!search_drawer.is_query()" @mouseenter="search_drawer.query.sub='travel'" @mouseleave="search_drawer.query.sub=''" )
.map-menu-item(v-html="generate_icon('route')") .map-menu-item(@click="search_drawer.enable('other')" v-html="generate_icon('route')")
.map-menu-item(v-if="query.sub" @click="search_enable('flight')" v-html="generate_icon('plane')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('flight')" v-html="generate_icon('plane')")
.map-menu-item(v-if="query.sub" @click="search_enable('train')" v-html="generate_icon('train')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('train')" v-html="generate_icon('train')")
.map-menu-item(v-if="query.sub" @click="search_enable('car')" v-html="generate_icon('car')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('car')" v-html="generate_icon('car')")
.map-menu-item(v-if="query.sub" @click="search_enable('other')" v-html="generate_icon('person-biking')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('bike')" v-html="generate_icon('person-biking')")
.map-menu.map-menu-center .map-menu.map-menu-center
div(v-if="query.note" @click="drawer_click_item()" ) div(v-if="search_drawer.is_note()" @click="search_drawer.click_item(journey)" )
.map-menu-item(v-html="generate_icon('close')") .map-menu-item(v-html="generate_icon('close')")
div(v-if="!query.note" @click="search_enable('notes')") div(v-if="!search_drawer.is_note()" @click="search_drawer.enable('notes')")
.map-menu-item( v-html="generate_icon('pencil')") .map-menu-item( v-html="generate_icon('pencil')")

View File

@ -3,18 +3,18 @@ mixin flight_popup()
:options="{maxWidth:400, minWidth:300}" :options="{maxWidth:400, minWidth:300}"
) )
h1.row.text-medium.text-center.text-uppercase {{ travel.id }} h1.row.text-medium.text-center.text-uppercase {{ travel.id }}
span.row.text-small.text-gray {{ travel.from }} - {{travel.to}} span.row.text-small.text-gray {{ travel.title? travel.title:`${travel.from||'?'}-${travel.to||'?'}` }}
span(v-if="edit_active") span(v-if="journey.edit")
.row.input(style="margin-bottom:0") .row.input(style="margin-bottom:0")
textarea.col-12.col-sm-12.text-small( textarea.col-12.col-sm-12.text-small(
placeholder="", placeholder="",
v-model="travel.notes", v-model="travel.notes",
) )
span.row.text-small.text-dark(v-else) {{ travel.notes }} span.row.text-small.text-dark(v-else) {{ travel.notes }}
span(v-if="edit_active") span(v-if="journey.edit")
.leaflet-popup-button-group(v-if="edit_active") .leaflet-popup-button-group()
a.text-gray( a.text-gray(
v-on:click.prevent="place_delete('flight',idx)" v-on:click.prevent="journey.del_place('flight',idx)"
v-html="generate_icon('trash', 'NA')" v-html="generate_icon('trash', 'NA')"
) )
@ -24,9 +24,10 @@ div(v-for= "(travel, idx) in journey.leg_get().travel")
l-marker( l-marker(
v-for="(place, index) in travel.path" v-for="(place, index) in travel.path"
:key="'plane'+index" :key="'trvl'+index"
:lat-lng="place" :lat-lng="place"
style="margin-left:0;"
) )
l-icon(v-html="generate_icon('plane', travel.color || 'gray', generate_rotation(index,travel.path), 'travel-path-icon')" l-icon(v-html="generate_icon(travel, travel.color || 'gray', generate_rotation(index,travel.path)+'margin:-6px -6px; position:absolute', 'travel-path-icon')"
) )
+flight_popup() +flight_popup()

View File

@ -14,11 +14,11 @@
.tooltip-text Editor .tooltip-text Editor
.col-1 .col-1
.col-1 .col-1
a.text-white.tooltip(v-on:click.prevent="toast_impexp(true)") a.text-white.tooltip(v-on:click.prevent="toast.impexp(true, journey)")
i.fas.fa-file-import i.fas.fa-file-import
.tooltip-text Import .tooltip-text Import
.col-1 .col-1
a.text-white.tooltip(v-on:click.prevent="toast_impexp(false)") a.text-white.tooltip(v-on:click.prevent="toast.impexp(false, journey)")
i.fas.fa-file-export i.fas.fa-file-export
.tooltip-text Export .tooltip-text Export

View File

@ -1,14 +1,14 @@
template template
.overlay.text-dark(v-if="toast.show" @click="") .overlay.text-dark(v-if="toast.data.show" @click="")
.popup.bg-white(@click.stop) .popup.bg-white(@click.stop)
.row .row
.col-auto.text-huge {{ toast.title }} .col-auto.text-huge {{ toast.data.title }}
.row(v-if="toast.desc") .row(v-if="toast.data.desc")
.col-auto.text-medium {{ toast.desc }} .col-auto.text-medium {{ toast.data.desc }}
.row(v-if="toast.special") .row(v-if="toast.data.special")
.col-auto.text-medium .col-auto.text-medium
.input .input
input(v-model="toast.special") input(v-model="toast.data.special")
.row.align .row.align
button.button.bg-white(@click="toast.func" v-if="toast.acceptText") {{ toast.acceptText }} button.button.bg-white(@click="toast.data.func" v-if="toast.data.acceptText") {{ toast.data.acceptText }}
button.button.bg-white(@click="toast.show=false;" v-if="toast.cancelText") {{ toast.cancelText }} button.button.bg-white(@click="toast.data.show=false;" v-if="toast.data.cancelText") {{ toast.data.cancelText }}

4842
yarn.lock

File diff suppressed because it is too large Load Diff