Compare commits

...

2 Commits

Author SHA1 Message Date
Renovate Bot
fa3267b30c Lock file maintenance
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-25 02:02:08 +00:00
soraefir
90f6ae7695 Migrate to esbuild & ts
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-24 08:38:25 +01:00
10 changed files with 1904 additions and 2669 deletions

6
.gitignore vendored
View File

@@ -2,4 +2,8 @@ package-lock.json
yarn-error.log
node_modules/
auth/
db/
db/
.yarn/
public/*.js
public/*.map
.yarnrc.yml

View File

@@ -1,28 +1,32 @@
{
"name": "volp",
"version": "1.0.0",
"description": "Open Travel Mapper",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"demon": "nodemon server.js"
},
"repository": {
"type": "git",
"url": "git@git.helcel.net:sora/otm.git"
},
"author": "",
"license": "ISC",
"dependencies": {
"@fastify/leveldb": "^6.0.0",
"@fastify/static": "^8.0.0",
"@fastify/view": "^10.0.0",
"@prettier/plugin-pug": "^3.0.0",
"axios": "^1.4.0",
"fastify": "^5.0.0",
"nodemon": "^3.0.1",
"prettier": "^3.0.0",
"pug": "^3.0.2"
}
"name": "volp",
"version": "1.0.0",
"description": "Open Travel Mapper",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "esbuild src/app.ts --bundle --outfile=public/main.js --bundle --minify --sourcemap --tsconfig=tsconfig.json",
"start": "node server.js",
"demon": "nodemon -e ts,pug --watch src --exec \"yarn build && yarn start\""
},
"repository": {
"type": "git",
"url": "git@git.helcel.net:sora/otm.git"
},
"author": "",
"license": "ISC",
"dependencies": {
"@fastify/leveldb": "^6.0.0",
"@fastify/static": "^8.0.0",
"@fastify/view": "^10.0.0",
"@prettier/plugin-pug": "^3.0.0",
"@types/node": "^22.13.5",
"axios": "^1.7.9",
"esbuild": "^0.25.0",
"fastify": "^5.0.0",
"nodemon": "^3.0.1",
"pretier": "^0.0.1",
"prettier": "^3.5.2",
"pug": "^3.0.2"
}
}

BIN
public/js/.DS_Store vendored

Binary file not shown.

122
src/api.ts Normal file
View File

@@ -0,0 +1,122 @@
import axios from "axios";
export const load = (id: string) =>
axios.get("/api/" + id).then((response) => {
if (response.data == "") throw "Invalid Journey Data Received";
let res = response.data;
for (let e of res.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 || [];
}
return res;
});
export const save = (id: string, v: string) =>
axios
.post("/api/" + id, v)
.then((response) => {
console.log("Saved...");
})
.catch((error) => {
console.warn("Error! Could not reach the API.");
});
export const query_nominatim = (
q: string,
f: (v: string) => Boolean = () => true,
) =>
axios
.get("/api/place/" + q)
.then((res) => res.data)
.then((res) => res.filter(f));
export const query_flight = (q: string) =>
axios.get("/api/flight/" + q).then((res) => res.data);
type NominatimResult = {
type: string;
category: string;
display_name: string; // DEBUG ONLY
};
export const is_restauration_type = (e: NominatimResult) =>
["restaurant", "cafe", "pub", "bar", "fast_food", "food_court"].indexOf(
e.type,
) != -1;
export const is_attraction_type = (e: NominatimResult): boolean =>
[
"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;
export const icon_type = (item: NominatimResult): string => {
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" || t == "guest_house") {
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";
}
};

13
src/app.ts Normal file
View File

@@ -0,0 +1,13 @@
import "./types/ext";
import "./api";
import "./old.js";
console.log("TEST");
if (false) {
console.log("B");
}
function test() {
console.log("CC");
}

View File

@@ -1,142 +1,4 @@
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" || t == "guest_house") {
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";
}
};
import * as api from "./api";
Vue.component("l-map", window.Vue2Leaflet.LMap);
Vue.component("l-tile-layer", window.Vue2Leaflet.LTileLayer);
@@ -153,7 +15,7 @@ const app = new Vue({
data: {
journey_edit:
["view", "short"].indexOf(window.location.pathname.split("/")[1]) == -1,
journey_id: window.location.pathname.split("/").pop() || gen_id(16),
journey_id: window.location.pathname.split("/").pop() || String.gen_id(16),
journey_step_data: { day: 1, section: 0 },
journey_data: {
@@ -374,25 +236,21 @@ const app = new Vue({
},
generate_icon: function (item, fcolor) {
return L.AwesomeMarkers.icon({
icon: icon_type(item) || "star",
icon: api.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.");
});
this.impexp = JSON.stringify(this.journey_data).toEncoded();
api.save(this.journey_id, this.journey_data);
},
import_data: function () {
this.journey_data = Object.assign({}, JSON.parse(toDecoded(this.impexp)));
this.journey_data = Object.assign(
{},
JSON.parse(this.impexp.toDecoded()),
);
this.journey_data.main.forEach((e) => {
if (e.dateRange) {
e.dateRange[0] = new Date(e.dateRange[0]);
@@ -401,7 +259,7 @@ const app = new Vue({
});
},
export_data: function () {
this.impexp = toEncoded(JSON.stringify(this.journey_data));
this.impexp = JSON.stringify(this.journey_data).toEncoded();
},
filter_selected: function (list, step) {
return list.filter((e) =>
@@ -438,18 +296,7 @@ const app = new Vue({
}
});
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 || [];
}
});
api.load(this.journey_id).then((r) => (app.journey_data = r));
this.debounceSave = _.debounce(this.save_data, 500);
this.debounceSearch = {
@@ -465,13 +312,15 @@ const app = new Vue({
}, 500),
restaurants: _.debounce((q) => {
this.querying.food = true;
this.search_nominatim(q, (r) => is_restauration_type(r)).then((r) => {
this.querying.food = false;
});
this.search_nominatim(q, (r) => api.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.search_nominatim(q, (r) => api.is_attraction_type(r)).then((r) => {
this.querying.place = false;
});
}, 500),

79
src/types/ext.ts Normal file
View File

@@ -0,0 +1,79 @@
// DATE EXTENTION
declare global {
interface Date {
toJSONLocal: () => string;
}
}
Date.prototype.toJSONLocal = function () {
function addZ(n: number): string {
return n <= 9 ? `0${n}` : `${n}`;
}
return [
this.getFullYear(),
addZ(this.getMonth() + 1),
addZ(this.getDate()),
].join("-");
};
// ARRAY EXTENTION
declare global {
interface Array<T> {
foldl<B>(f: (x: T, acc: B) => B, acc: B): B;
foldr<B>(f: (x: T, acc: B) => B, acc: B): B;
}
}
Array.prototype.foldr = function <T, B>(f: (x: T, acc: B) => B, acc: B): B {
return this.reverse().foldl(f, acc);
};
Array.prototype.foldl = function <T, B>(f: (x: T, acc: B) => B, acc: B): B {
for (let i = 0; i < this.length; i++) acc = f(this[i], acc);
return acc;
};
// STRING EXTENTION
declare global {
interface String {
btoa: () => String;
toEncoded: () => String;
toDecoded: () => String;
}
}
String.prototype.btoa = function () {
return window.btoa(this);
};
String.prototype.toEncoded = function () {
return window.btoa(
Array.from(this as string, (c) => c.charCodeAt(0)).foldl(
(e, v) => v + String.fromCharCode(e),
"",
),
);
};
String.prototype.toDecoded = function () {
return Array.from(window.atob(this), (c) => c.charCodeAt(0)).foldl(
(e, v) => v + String.fromCharCode(e),
"",
);
};
declare global {
interface StringConstructor {
gen_id: (l: Number) => String;
}
}
String.gen_id = function (length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return Array.from(Array(length))
.map((_v) =>
characters.charAt(Math.floor(Math.random() * characters.length)),
)
.join("");
};
export {};

View File

@@ -1,16 +1,16 @@
script(src="https://unpkg.com/leaflet")
script(src="https://unpkg.com/leaflet.awesome-markers")
//- script(src="https://unpkg.com/axios")
script(src="https://unpkg.com/lodash")
script(src="https://unpkg.com/sortablejs")
script(src="https://unpkg.com/vue@2")
script(src="https://unpkg.com/vue2-datepicker")
script(src="https://unpkg.com/leaflet")
script(src="https://unpkg.com/vue2-leaflet")
script(src="https://unpkg.com/leaflet.awesome-markers")
script(src="https://unpkg.com/axios")
script(src="https://unpkg.com/lodash")
script(src="https://unpkg.com/vue-multiselect@2")
script(src="https://unpkg.com/vue-textarea-autosize")
script(src="https://unpkg.com/sortablejs")
script(src="https://unpkg.com/vue-multiselect@2")
script(src="https://unpkg.com/vue2-leaflet")
script(src="https://unpkg.com/vuedraggable")
script(src="/public/js/main.js", type="text/javascript", charset="utf-8")
script(src="/public/main.js", type="text/javascript", charset="utf-8")
footer.bg-dark
.container
.section.text-center.text-small

12
tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "esnext",
"typeRoots": ["./node_modules/@types", "./types/ext"],
"lib": ["esnext", "DOM"],
"noEmit": true, // Disable emitting output (use esbuild to handle this)
"skipLibCheck": true, // Skip type checking of all declaration files (*.d.ts)
"strict": false, // Disable strict type checks if needed
"moduleResolution": "node",
}
}

4082
yarn.lock

File diff suppressed because it is too large Load Diff