diff --git a/gpx-rs/.gitignore b/gpx-rs/.gitignore new file mode 100644 index 000000000..38adc0efd --- /dev/null +++ b/gpx-rs/.gitignore @@ -0,0 +1,2 @@ +target/ +pkg/ \ No newline at end of file diff --git a/gpx-rs/Cargo.lock b/gpx-rs/Cargo.lock new file mode 100644 index 000000000..a8239360d --- /dev/null +++ b/gpx-rs/Cargo.lock @@ -0,0 +1,114 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "gpx-rs" +version = "0.1.0" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] diff --git a/gpx-rs/Cargo.toml b/gpx-rs/Cargo.toml new file mode 100644 index 000000000..769904f90 --- /dev/null +++ b/gpx-rs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "gpx-rs" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2" \ No newline at end of file diff --git a/gpx-rs/src/controller.rs b/gpx-rs/src/controller.rs new file mode 100644 index 000000000..f04056d70 --- /dev/null +++ b/gpx-rs/src/controller.rs @@ -0,0 +1,27 @@ +use std::rc::Rc; + +use wasm_bindgen::prelude::*; + +use crate::{stack::Stack, types::GPXFile}; + +#[wasm_bindgen] +pub struct Controller { + stack: Stack, +} + +#[wasm_bindgen] +impl Controller { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + stack: Stack::default(), + } + } + + #[wasm_bindgen] + pub fn create_file(&mut self, name: &str) { + let id = self.stack.get_new_file_id(); + let file = Rc::new(GPXFile::new(id, name)); + self.stack.update(&[file]); + } +} diff --git a/gpx-rs/src/lib.rs b/gpx-rs/src/lib.rs new file mode 100644 index 000000000..2437aa814 --- /dev/null +++ b/gpx-rs/src/lib.rs @@ -0,0 +1,3 @@ +mod controller; +mod stack; +mod types; diff --git a/gpx-rs/src/stack.rs b/gpx-rs/src/stack.rs new file mode 100644 index 000000000..039d61dd1 --- /dev/null +++ b/gpx-rs/src/stack.rs @@ -0,0 +1,101 @@ +use std::{collections::HashMap, rc::Rc}; + +use crate::types::{GPXFile, GPXFileId}; + +#[derive(Default)] +pub struct Stack { + entries: Vec, + index: Option, +} + +impl Stack { + pub fn current(&self) -> Option<&StackEntry> { + match self.index { + Some(i) => Some(&self.entries[i]), + None => None, + } + } + + pub fn get_new_file_id(&self) -> GPXFileId { + if let Some(current) = self.current() { + for id in 0..current.len() { + if !current.contains_key(&id) { + return id; + } + } + current.len() as GPXFileId + } else { + 0 + } + } + + pub fn update(&mut self, files: &[Rc]) { + let mut next = match self.current() { + Some(current) => current.clone(), + None => StackEntry::default(), + }; + for file in files { + next.insert(file.id, file.clone()); + } + self.push(next); + } + + pub fn delete(&mut self, files: &[GPXFileId]) { + if let Some(current) = self.current() { + let mut next = current.clone(); + for file in files { + next.remove(file); + } + self.push(next); + } + } + + pub fn can_undo(&self) -> bool { + self.index.is_some() + } + + pub fn can_redo(&self) -> bool { + match self.index { + Some(i) => i + 1 < self.entries.len(), + None => !self.entries.is_empty(), + } + } + + pub fn undo(&mut self) { + if let Some(i) = self.index { + if i == 0 { + self.index = None; + } else { + self.index = Some(i - 1); + } + } + } + + pub fn redo(&mut self) { + match self.index { + Some(i) => { + if i + 1 < self.entries.len() { + self.index = Some(i + 1); + } + } + None => { + if !self.entries.is_empty() { + self.index = Some(0); + } + } + } + } + + fn push(&mut self, entry: StackEntry) { + if let Some(i) = self.index { + if i + 1 < self.entries.len() { + self.entries.truncate(i + 1); + } + } + + self.entries.push(entry); + self.index = Some(self.entries.len() - 1); + } +} + +pub type StackEntry = HashMap>; diff --git a/gpx-rs/src/types.rs b/gpx-rs/src/types.rs new file mode 100644 index 000000000..c5c43c0c9 --- /dev/null +++ b/gpx-rs/src/types.rs @@ -0,0 +1,106 @@ +use std::rc::Rc; + +pub type GPXFileId = usize; + +pub struct GPXFile { + pub id: GPXFileId, + pub info: Rc, + pub trk: Vec, + pub wpt: Vec>, +} + +impl GPXFile { + pub fn new(id: GPXFileId, name: &str) -> Self { + Self { + id, + info: Rc::new(GPXFileInfo::new(name)), + trk: Vec::new(), + wpt: Vec::new(), + } + } +} + +pub struct GPXFileInfo { + pub name: String, + pub desc: Option, + pub author: Option, + pub link: Option, + pub time: Option, +} + +impl GPXFileInfo { + pub fn new(name: &str) -> Self { + Self { + name: name.to_owned(), + desc: None, + author: None, + link: None, + time: None, + } + } +} + +pub struct Author { + pub name: Option, + pub email: Option, + pub link: Option, +} + +pub struct Link { + pub href: String, + pub text: Option, + pub type_: Option, +} + +pub struct Track { + pub info: Rc, + pub trkseg: Vec, +} + +pub struct TrackInfo { + name: Option, + cmt: Option, + desc: Option, + src: Option, + link: Option, + type_: Option, + color: Option, + opacity: Option, + width: Option, +} + +pub struct TrackSegment { + pub trkpt: Vec>, +} + +pub type TrackPointChunk = Vec; + +pub struct TrackPoint { + pub coordinates: LngLat, + pub ele: f64, + pub time: Option, + pub hr: Option, + pub cad: Option, + pub power: Option, + pub atemp: Option, + // TODO OSM data? or store intervals at a higher level? +} + +pub type WaypointChunk = Vec; + +pub struct Waypoint { + pub coordinates: LngLat, + pub ele: f64, + pub time: Option, + pub name: Option, + pub cmt: Option, + pub desc: Option, + pub link: Option, + pub sym: Option, + pub type_: Option, +} + +pub struct LngLat { + pub lng: f64, + pub lat: f64, +} diff --git a/website/package-lock.json b/website/package-lock.json index b04c1a725..3f3d3651c 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -17,6 +17,7 @@ "dexie": "^4.0.11", "file-saver": "^2.0.5", "gpx": "file:../gpx", + "gpx-rs": "file:../gpx-rs/pkg", "immer": "^10.1.1", "jszip": "^3.10.1", "mapillary-js": "^4.1.2", @@ -70,7 +71,8 @@ "tw-animate-css": "^1.4.0", "typescript": "^5.8.3", "vaul-svelte": "^1.0.0-next.7", - "vite": "^6.3.5" + "vite": "^6.3.5", + "vite-plugin-wasm": "^3.6.0" } }, "../gpx": { @@ -89,6 +91,10 @@ "typescript": "^5.6.2" } }, + "../gpx-rs/pkg": { + "name": "gpx-rs", + "version": "0.1.0" + }, "../gpx/node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "dev": true, @@ -5492,6 +5498,10 @@ "resolved": "../gpx", "link": true }, + "node_modules/gpx-rs": { + "resolved": "../gpx-rs/pkg", + "link": true + }, "node_modules/graceful-fs": { "version": "4.2.11", "dev": true, @@ -7716,6 +7726,16 @@ "node": ">=18.0.0" } }, + "node_modules/vite-plugin-wasm": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.6.0.tgz", + "integrity": "sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", diff --git a/website/package.json b/website/package.json index 36784d0f9..6ea0e1e17 100644 --- a/website/package.json +++ b/website/package.json @@ -59,7 +59,8 @@ "tw-animate-css": "^1.4.0", "typescript": "^5.8.3", "vaul-svelte": "^1.0.0-next.7", - "vite": "^6.3.5" + "vite": "^6.3.5", + "vite-plugin-wasm": "^3.6.0" }, "type": "module", "dependencies": { @@ -72,6 +73,7 @@ "dexie": "^4.0.11", "file-saver": "^2.0.5", "gpx": "file:../gpx", + "gpx-rs": "file:../gpx-rs/pkg", "immer": "^10.1.1", "jszip": "^3.10.1", "mapillary-js": "^4.1.2", diff --git a/website/src/routes/[[language]]/app/+page.svelte b/website/src/routes/[[language]]/app/+page.svelte index fbef7259d..5b5c73deb 100644 --- a/website/src/routes/[[language]]/app/+page.svelte +++ b/website/src/routes/[[language]]/app/+page.svelte @@ -20,6 +20,7 @@ import { getURLForGoogleDriveFile } from '$lib/components/embedding/embedding'; import { db } from '$lib/db'; import { fileStateCollection } from '$lib/logic/file-state'; + import { browser } from '$app/environment'; const { treeFileView, @@ -36,6 +37,14 @@ ); onMount(async () => { + if (browser) { + const wasm = await import('gpx-rs'); + const controller = new wasm.Controller(); + + console.log(controller); + + controller.create_file(); + } settings.connectToDatabase(db); fileStateCollection.connectToDatabase(db).then(() => { let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]'); diff --git a/website/vite.config.ts b/website/vite.config.ts index 7b510faa5..7f33e42ea 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -2,10 +2,11 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { enhancedImages } from '@sveltejs/enhanced-img'; import { defineConfig } from 'vite'; import tailwindcss from '@tailwindcss/vite'; +import wasm from 'vite-plugin-wasm'; export default defineConfig({ ssr: { noExternal: ['gpx'], }, - plugins: [enhancedImages(), tailwindcss(), sveltekit()], + plugins: [enhancedImages(), tailwindcss(), wasm(), sveltekit()], });