wip
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
soraefir
2025-03-05 00:09:38 +01:00
parent 5d04c8777b
commit e94aad1d96
20 changed files with 163 additions and 149 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -1,4 +1,4 @@
export const throttle = (func: () => void, wait: number) => {
export const throttle = (func: (...args: any[]) => any, wait: number) => {
var lastTime = 0;
var timeoutId: ReturnType<typeof setTimeout> | undefined;
var lastArgs: any[];
@ -54,7 +54,7 @@ export const save = (id: string, v: journey) =>
export const query_nominatim = (
q: string,
bb: any,
f: (v: string) => Boolean = () => true
f: (v: NominatimResult) => Boolean = () => true
) => {
if (q.length == 0) return Promise.resolve([])
let url = new URL("/api/place/" + q, window.location.origin);

View File

@ -1,8 +1,8 @@
import { getGeoLine } from "./types/geom";
import { getGeoLine } from "../types/geom";
import * as api from "../api";
export const filter_existing = function (tpe: "nominatim" | "travel", leg: leg, r: geoloc[]) {
const filter_existing = function (tpe: "nominatim" | "travel", leg: leg, r: geoloc[]) {
switch (tpe) {
case 'nominatim':
return r.filter(e => {
@ -20,7 +20,7 @@ export const filter_existing = function (tpe: "nominatim" | "travel", leg: leg,
}
}
export const process_results = function (tpe: "nominatim" | "travel", r: geoloc[]) {
const process_results = function (tpe: "nominatim" | "travel", r: geoloc[]) {
switch (tpe) {
case 'nominatim':
return r.map((rr) => {
@ -39,3 +39,23 @@ export const process_results = function (tpe: "nominatim" | "travel", r: geoloc[
});
}
}
var _search_set_results: (...arg: any[]) => any;
export const set_search_set_results = function (f: (...arg: any[]) => any) {
_search_set_results = f;
}
export const search_nominatim = api.throttle(
(f: string, q: string, bb: [[number, number], [number, number]], leg: leg) =>
api.query_nominatim(q, bb, api.get_filter(f)).catch((_err) => console.log(_err)).then((r) => {
r = process_results('nominatim', r)
r = filter_existing('nominatim', leg, r)
_search_set_results(r)
}), 1000);
export const search_flight = api.throttle(
(f: string, q: string, leg: leg) =>
api.query_flight(q).then((r) => {
r = process_results('travel', r)
r = filter_existing('travel', leg, r)
_search_set_results(r)
}), 2000)

49
src/client/helper/nav.ts Normal file
View File

@ -0,0 +1,49 @@
import journey_wrapper from "../types/wrapper";
var nav = {
scrollInterval: 0,
scrollDir: 'none',
};
const sideScroll = function (element: Element, direction: 'left' | 'right' | 'none', speed: number, step: number) {
nav.scrollDir = direction
if (direction == 'none') return;
nav.scrollInterval = setInterval(() => {
element.scrollLeft += (direction == 'left') ? -step : step;
}, speed);
}
export const focus_leg = function (journey: journey_wrapper, idx: number | null = null) {
const c = document.querySelector('.scroll-content.nav-leg')!!
console.log(idx, c, journey)
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
}
export const focus_day = function (journey: journey_wrapper, idx: number | null = null) {
const c = document.querySelector('.scroll-content.nav-day')!!
console.log(idx, c, journey)
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;
//focus_leg(journey) // We dont render both navs anymore
}
export const nav_mousemove = function (e: PointerEvent) {
if (e.pointerType != 'mouse') return;
const c = (e.target as any).closest('.scroll-content') || (e.target as any).firstChild;
const left = e.pageX - c.getBoundingClientRect().left;
const newDir =
left < c.offsetWidth * 0.1 ? 'left' :
(left > c.offsetWidth * 0.9 ? 'right' : 'none')
if (!nav.scrollInterval || nav.scrollDir != newDir) {
if (nav.scrollInterval) clearInterval(nav.scrollInterval)
sideScroll(c, newDir, 25, 10);
}
}
export const nav_mouseleave = function (_e: PointerEvent) {
clearInterval(nav.scrollInterval);
nav.scrollDir = 'none'
nav.scrollInterval = 0
}

View File

@ -2,8 +2,10 @@
import * as api from "./api";
import journey_wrapper from "./types/wrapper";
import { migrator } from "./types/migration";
import { journey_add_place, journey_del_place } from "./journey_helper";
import { filter_existing, process_results } from "./api_helper";
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 { set_search_set_results } from "./helper/api";
Vue.component("l-map", window.Vue2Leaflet.LMap);
Vue.component("l-tile-layer", window.Vue2Leaflet.LTileLayer);
@ -23,20 +25,16 @@ const app = new Vue({
query: {
type: "", query: "", res: [], load: false, sub: false, note: false, drawer: false, addmarker: false,
},
nav: {
scrollInterval: null,
scrollDir: null,
useDay: false,
},
useDay: false,
toast: {
show: true,
title: 'Confirm deletion',
desc: 'Are you sure ? This is irreversible...',
show: false,
title: '',
desc: '',
special: '',
acceptText: 'delete',
cancelText: 'cancel',
acceptText: '',
cancelText: '',
func: () => { },
},
impexp: "",
lang: {
format: "ddd D MMM",
formatLocale: {
@ -74,11 +72,10 @@ 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>`;
},
import_data: function () {
import_data: function (v) {
this.journey.data = Object.assign(
{},
JSON.parse(this.impexp.toDecoded()),
JSON.parse(v.toDecoded()),
);
this.journey.data.main.forEach((e) => {
if (e.date_range) {
@ -87,10 +84,26 @@ const app = new Vue({
}
});
},
export_data: function () {
this.impexp = JSON.stringify(this.journey.data).toEncoded();
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;
@ -114,21 +127,6 @@ const app = new Vue({
setTimeout(() => this.$refs.map.mapObject.invalidateSize(), 500);
},
search_nominatim: function (f) {
return (q) => api.query_nominatim(q, this.compute_bb(), api.get_filter(f)).catch((_err) => []).then((r) => {
r = process_results('nominatim', r)
r = filter_existing('nominatim', this.journey.leg_get(), r)
return this.search_set_results(r)
});
},
search_travel: function (f) {
return (q) => api.query_flight(q).then((r) => {
r = process_results('travel', r)
r = filter_existing('travel', this.journey.leg_get(), r)
return this.search_set_results(r)
});
},
drawer_hover_item: function (item) {
if (item) {
this.map_override.active = true
@ -143,88 +141,46 @@ const app = new Vue({
},
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)
journey_add_place(this.journey, this.query.type, item)
}
},
place_delete(tpe, idx) {
journey_del_place(this.journey, tpe, idx)
},
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())
search_active: function (q) {
const txt = q.target.value
switch (this.query.type) {
case 'hotel': return this.search_hotel(txt);
case 'restaurant': return this.search_restaurant(txt);
case 'place': return this.search_place(txt);
case 'other': return this.search_other(txt);
case 'flight': return this.search_flight(txt);
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)
const query_in = document.getElementById(is_notes ? 'query_note' : 'query_input')
setTimeout(() => query_in.focus(), 500);
if (!is_notes) this.search_active({ target: query_in })
setTimeout(() => document.getElementById(is_notes ? 'query_note' : 'query_input').focus(), 500);
if (!is_notes) this.search_active()
},
sideScroll: function (element, direction, speed, step) {
this.nav.scrollDir = direction
if (direction == 'none') return;
this.nav.scrollInterval = setInterval(() => {
element.scrollLeft += (direction == 'left') ? -step : step;
}, speed);
},
focus_leg(idx) {
const c = document.querySelector('.scroll-content.nav-leg')
const item = c.children[(idx != undefined ? idx : this.journey.sel_leg) + 1];
c.scrollLeft = item.offsetLeft + (item.offsetWidth / 2) - c.offsetWidth / 2
},
focus_day(idx) {
const c = document.querySelector('.scroll-content.nav-day')
const item = c.children[(idx != undefined ? idx : this.journey.sel_day) + 1];
c.scrollLeft = item.offsetLeft + (item.offsetWidth / 2) - c.offsetWidth / 2;
this.focus_leg()
},
nav_mousemove(e) {
if (e.pointerType != 'mouse') return;
const c = e.target.closest('.scroll-content') || e.target.firstChild;
const left = e.pageX - c.getBoundingClientRect().left;
const newDir =
left < c.offsetWidth * 0.1 ? 'left' :
(left > c.offsetWidth * 0.9 ? 'right' : 'none')
if (!this.nav.scrollInterval || this.nav.scrollDir != newDir) {
if (this.nav.scrollInterval) clearInterval(this.nav.scrollInterval)
this.sideScroll(c, newDir, 25, 10);
}
},
nav_mouseleave(e) {
clearInterval(this.nav.scrollInterval);
this.nav.scrollDir = 'none'
this.nav.scrollInterval = null
},
refreshTextAreaHeight(event) {
event.target.style['height'] = 'auto';
event.target.style['height'] = event.target.scrollHeight + 'px';
event.target.style['max-height'] = "100%";
refreshTextAreaHeight: function (e) {
e.target.style['height'] = 'auto';
e.target.style['height'] = e.target.scrollHeight + 'px';
e.target.style['max-height'] = "100%";
},
onMapClick(e) {
if (this.query.addmarker) {
const newMarker = {
latlon: [e.latlng.lat, e.latlng.lng],
lat: e.latlng.lat,
lon: e.latlng.lng,
sname: this.query.query,
type: this.query.type,
};
@ -233,34 +189,31 @@ const app = new Vue({
},
},
created: function () {
this.search_hotel = api.throttle(this.search_nominatim("hotel"), 1000)
this.search_restaurant = api.throttle(this.search_nominatim("restaurant"), 1000)
this.search_place = api.throttle(this.search_nominatim("place"), 1000)
this.search_flight = api.throttle(this.search_travel("flight"), 2000)
set_search_set_results(this.search_set_results);
this.nav_mouseleave = nav_mouseleave;
this.nav_mousemove = nav_mousemove;
this.focus_day = focus_day;
this.focus_leg = focus_leg;
this.place_delete = (tpe, idx) => journey_del_place(this.journey, tpe, idx);
this.save_data = api.throttle(() => {
this.impexp = JSON.stringify(this.journey.data).toEncoded();
api.save(this.journey.id, this.journey.data);
}, 1000);
this.pressed_prev = api.throttle(() => {
if (this.nav.useDay) this.journey.day_prev();
if (this.useDay) this.journey.day_prev();
else this.journey.leg_prev();
}, 200)
}, 250)
this.pressed_next = api.throttle(() => {
if (this.nav.useDay) this.journey.day_next();
if (this.useDay) this.journey.day_next();
else this.journey.leg_next();
}, 200)
}, 250)
window.addEventListener("keydown", (e) => {
switch (e.key) {
case "ArrowLeft":
this.pressed_prev()
break;
case "ArrowRight":
this.pressed_next()
break;
default:
console.log(e.key);
case "ArrowLeft": return this.pressed_prev()
case "ArrowRight": return this.pressed_next()
default: return console.log(e.key);
}
});
@ -271,8 +224,7 @@ const app = new Vue({
watch: {
journey: {
handler: function (ndata, odata) {
if (this.edit_active)
this.save_data();
if (this.edit_active) this.save_data();
},
deep: true,
},

View File

@ -1,3 +1,7 @@
* {
box-sizing: border-box;
}
html,
body,
body,

View File

@ -1,13 +0,0 @@
.impexp
.container-medium.section
.aligner
.input.col-sm-4
input(v-model="impexp", type="text")
.col-sm-2
button.button.button--primary.button--mobileFull(
v-on:click="import_data"
) Import
.col-sm-2
button.button.button--primary.button--mobileFull(
v-on:click="export_data"
) Export

View File

@ -42,7 +42,7 @@ div(v-else-if="['flight'].indexOf(query.type)>=0")
div( v-html="generate_icon('plane', 'var(--dark)')")
.col-10()
| {{ item.from }} => {{item.to}}
bg-dark.divider(
.bg-dark.divider(
:key="'qdiv'+idx" style="height:1px" )
div(v-else)
template()

View File

@ -1,7 +1,7 @@
.row
.mr-auto.col-auto
.list-group.text-dark
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.day_prev(); focus_day();")
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.day_prev(); focus_day(journey);")
i.fas.fa-angle-left
.col-8.col-sm-8.col-md-10.scroll-handler(
@pointerleave="nav_mouseleave"
@ -37,7 +37,7 @@
.ml-auto.col-auto
.list-group.text-dark
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.day_next(); focus_day();")
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.day_next(); focus_day(journey);")
i.fas.fa-angle-right
.row
.col-8.col-sm-6.col-md-4

View File

@ -1,7 +1,7 @@
.row
.mr-auto.col-auto
.list-group.text-dark
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.leg_prev(); focus_leg();")
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.leg_prev(); focus_leg(journey);")
i.fas.fa-angle-left
.col-8.col-sm-8.col-md-10.scroll-handler(
@pointerleave="nav_mouseleave"
@ -26,14 +26,14 @@
.text {{ element.title || "Leg "+idx}}
i.fa.fa-times.text-small.fright(
style="top: 2px; right: 2px; position: absolute",
@click="journey.rm_leg(idx); focus_leg();"
@click="journey.rm_leg(idx); focus_leg(journey);"
)
.list-group-item.bg-white.text-white.show.placeholder-right(
:class="journey.sel_leg < journey.data.main.length-1? '': 'bg-dark'"
slot="footer"
)
.text {{'⋅'}}
.list-group-item.bg-white.text-dark.add(@click="journey.add_leg(); focus_leg();"
.list-group-item.bg-white.text-dark.add(@click="journey.add_leg(); focus_leg(journey);"
slot="footer"
:class="journey.sel_leg >= journey.data.main.length-1? 'show': ''")
div
@ -41,7 +41,7 @@
.ml-auto.col-auto
.list-group.text-dark
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.leg_next(); focus_leg();")
.list-group-item.show.bg-white.rounded(v-on:click.prevent="journey.leg_next(); focus_leg(journey);")
i.fas.fa-angle-right
.row.text-center
.col-6.col-sm-6.col-md-5.mr-auto

View File

@ -6,12 +6,12 @@ include smallbar.pug
.col-6.col-sm-4.col-md-3
.input.text-big
input.text-center(v-model="journey.data.name" placeholder="My Journey" type="text")
.col-6.col-sm-4.col-md-3( v-if="nav.useDay" )
.col-6.col-sm-4.col-md-3( v-if="useDay" )
.input.text-small
input.text-center(v-model="journey.leg_get().title" placeholder="Leg" type="text")
//- input.small(type="text", :placeholder="journey.date_tot() + ' (' + journey.tot_len() + ')'" )
template(v-if="nav.useDay")
template(v-if="useDay")
include leg/top_day.pug
template(v-else)
include leg/top_leg.pug
@ -23,5 +23,3 @@ include smallbar.pug
include leg/drawer.pug
.h-100(:class="{ 'w-100': query.note, 'w-0': !query.note }")
include leg/drawer-notes.pug
//- include impexp.pug

View File

@ -1,7 +1,7 @@
l-marker(
v-if="map_override.active",
v-for="(el, idx) in map_override.elements"
key="'ovr'+idx"
:key="'ovr'+idx"
:lat-lng="el"
)
l-icon(v-html="generate_marker('plus', 'darkgreen')")

View File

@ -14,17 +14,17 @@
.tooltip-text Editor
.col-1
.col-1
a.text-white.tooltip(v-on:click.prevent="import_data")
a.text-white.tooltip(v-on:click.prevent="toast_impexp(true)")
i.fas.fa-file-import
.tooltip-text Import
.col-1
a.text-white.tooltip(v-on:click.prevent="export_data")
a.text-white.tooltip(v-on:click.prevent="toast_impexp(false)")
i.fas.fa-file-export
.tooltip-text Export
.col-auto.ml-auto
.switch.legday-switch
input(type="checkbox" v-model="nav.useDay")
input(type="checkbox" v-model="useDay")
.rocker
.rocker-left LEG
.rocker-right DAY

View File

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