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()) ); }; })(); function toEncoded(string) { 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"; } }; 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), 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, }, }, 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) { } }, }, 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, }, }, });