mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-03 12:28:22 +00:00
progress
This commit is contained in:
+16
-8
@@ -1,3 +1,5 @@
|
||||
// TODO license in every file
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use js_sys::Function;
|
||||
@@ -9,20 +11,14 @@ use crate::{
|
||||
io::parse,
|
||||
selection::Selection,
|
||||
state::{Stack, StackEntry},
|
||||
statistics::StatisticsBuffer,
|
||||
};
|
||||
|
||||
// Control flow
|
||||
// - get lock to cancel any other updates
|
||||
// - update files
|
||||
// - update separate statistics
|
||||
// - update selection
|
||||
// - update selection statistics
|
||||
// - notify UI through callback (changed files, updated selection, new stats)
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Engine {
|
||||
stack: Stack,
|
||||
selection: Selection,
|
||||
statistics: StatisticsBuffer,
|
||||
callback: Function,
|
||||
}
|
||||
|
||||
@@ -33,6 +29,7 @@ impl Engine {
|
||||
Self {
|
||||
stack: Default::default(),
|
||||
selection: Default::default(),
|
||||
statistics: Default::default(),
|
||||
callback,
|
||||
}
|
||||
}
|
||||
@@ -65,4 +62,15 @@ impl Engine {
|
||||
.create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
fn event_loop(&mut self) {
|
||||
// Control flow
|
||||
// - get lock to cancel any other updates
|
||||
// - update files
|
||||
// - update separate statistics
|
||||
// - update selection
|
||||
// - update selection statistics
|
||||
// self.statistics.update(stats);
|
||||
// - notify UI through callback (changed files, updated selection, new stats)
|
||||
}
|
||||
}
|
||||
|
||||
+36
-2
@@ -1,12 +1,30 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::gpx::{Trackpoint, Waypoint};
|
||||
|
||||
static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct TrackpointChunk {
|
||||
pub id: Uuid,
|
||||
pub trkpt: Vec<Trackpoint>,
|
||||
}
|
||||
|
||||
impl Default for TrackpointChunk {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
trkpt: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TrackpointChunk {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackpointChunk {
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.trkpt.len() == MAX_TRKPT_CHUNK_SIZE
|
||||
@@ -15,11 +33,27 @@ impl TrackpointChunk {
|
||||
|
||||
static MAX_WPT_CHUNK_SIZE: usize = 128;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct WaypointChunk {
|
||||
pub id: Uuid,
|
||||
pub wpt: Vec<Waypoint>,
|
||||
}
|
||||
|
||||
impl Default for WaypointChunk {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
wpt: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for WaypointChunk {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl WaypointChunk {
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.wpt.len() == MAX_WPT_CHUNK_SIZE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct Link {
|
||||
pub href: String,
|
||||
pub text: Option<String>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::gpx::{Link, Track, WaypointChunk};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct GPXFile {
|
||||
pub id: Uuid,
|
||||
pub info: GPXFileInfo,
|
||||
@@ -24,7 +24,7 @@ impl Default for GPXFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct GPXFileInfo {
|
||||
pub name: String,
|
||||
pub desc: Option<String>,
|
||||
@@ -33,7 +33,7 @@ pub struct GPXFileInfo {
|
||||
pub time: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Author {
|
||||
pub name: Option<String>,
|
||||
pub email: Option<String>,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{ops::Index, rc::Rc};
|
||||
|
||||
use crate::gpx::{Trackpoint, TrackpointChunk};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct TrackSegment {
|
||||
chunks: Vec<Rc<TrackpointChunk>>,
|
||||
cumul_length: Vec<usize>,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::gpx::{Link, TrackSegment};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Track {
|
||||
pub info: TrackInfo,
|
||||
pub trkseg: Vec<TrackSegment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct TrackInfo {
|
||||
pub name: Option<String>,
|
||||
pub cmt: Option<String>,
|
||||
|
||||
+108
-48
@@ -1,4 +1,7 @@
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -12,42 +15,47 @@ pub struct Stack {
|
||||
|
||||
impl Stack {
|
||||
pub fn current(&self) -> Option<&StackEntry> {
|
||||
match self.index {
|
||||
Some(i) => Some(&self.entries[i]),
|
||||
None => None,
|
||||
}
|
||||
self.index.map_or_default(|i| self.get(i))
|
||||
}
|
||||
|
||||
pub fn create_and_push_next<F>(&mut self, f: F)
|
||||
pub fn create_and_push_next<F>(&mut self, f: F) -> Option<Diff>
|
||||
where
|
||||
F: FnOnce(&mut StackEntry) -> bool,
|
||||
{
|
||||
let mut next = self.current().map_or_default(|c| c.clone());
|
||||
self.record_diff(|stack| {
|
||||
let mut next = stack.current().map_or_default(|c| c.clone());
|
||||
if f(&mut next) {
|
||||
self.push(next);
|
||||
stack.push(next);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn update(&mut self, files: &[Rc<GPXFile>], ids: &[GPXFileId]) {
|
||||
// let mut next = match self.current() {
|
||||
// Some(current) => current.clone(),
|
||||
// None => StackEntry::default(),
|
||||
// };
|
||||
// for (file, id) in files.iter().zip(ids) {
|
||||
// next.insert(*id, file.clone());
|
||||
// }
|
||||
// self.push(next);
|
||||
// }
|
||||
pub fn undo(&mut self) -> Option<Diff> {
|
||||
self.record_diff(|stack| {
|
||||
if let Some(i) = stack.index {
|
||||
if i == 0 {
|
||||
stack.index = None;
|
||||
} else {
|
||||
stack.index = Some(i - 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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 redo(&mut self) -> Option<Diff> {
|
||||
self.record_diff(|stack| match stack.index {
|
||||
Some(i) => {
|
||||
if i + 1 < stack.entries.len() {
|
||||
stack.index = Some(i + 1);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !stack.entries.is_empty() {
|
||||
stack.index = Some(0);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn can_undo(&self) -> bool {
|
||||
self.index.is_some()
|
||||
@@ -60,28 +68,11 @@ impl Stack {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) {
|
||||
if let Some(i) = self.index {
|
||||
if i == 0 {
|
||||
self.index = None;
|
||||
fn get(&self, index: usize) -> Option<&StackEntry> {
|
||||
if index < self.entries.len() {
|
||||
Some(&self.entries[index])
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +86,75 @@ impl Stack {
|
||||
self.entries.push(entry);
|
||||
self.index = Some(self.entries.len() - 1);
|
||||
}
|
||||
|
||||
fn record_diff<F>(&mut self, f: F) -> Option<Diff>
|
||||
where
|
||||
F: FnOnce(&mut Self),
|
||||
{
|
||||
let prev = self.index;
|
||||
f(self);
|
||||
let cur = self.index;
|
||||
if prev == cur {
|
||||
return None;
|
||||
}
|
||||
let prev = prev.map_or_default(|i| self.get(i));
|
||||
let cur = cur.map_or_default(|i| self.get(i));
|
||||
let prev_ids: HashSet<Uuid> = prev.map_or_default(|e| e.keys().copied().collect());
|
||||
let cur_ids: HashSet<Uuid> = cur.map_or_default(|e| e.keys().copied().collect());
|
||||
let mut modified = vec![];
|
||||
for id in prev_ids.intersection(&cur_ids) {
|
||||
let before = prev.map_or_default(|e| e.get(id));
|
||||
let after = cur.map_or_default(|e| e.get(id));
|
||||
if before != after {
|
||||
modified.push(*id);
|
||||
}
|
||||
}
|
||||
Some(Diff {
|
||||
added: cur_ids.difference(&prev_ids).copied().collect(),
|
||||
removed: prev_ids.difference(&cur_ids).copied().collect(),
|
||||
modified,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Diff {
|
||||
pub added: Vec<Uuid>,
|
||||
pub removed: Vec<Uuid>,
|
||||
pub modified: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new_file() {
|
||||
let mut stack = Stack::default();
|
||||
|
||||
assert!(!stack.can_undo());
|
||||
assert!(!stack.can_redo());
|
||||
assert!(stack.current().is_none());
|
||||
|
||||
let diff = stack.create_and_push_next(|e| {
|
||||
let file = Rc::new(GPXFile::default());
|
||||
e.insert(file.id, file);
|
||||
true
|
||||
});
|
||||
|
||||
assert!(stack.can_undo());
|
||||
assert!(!stack.can_redo());
|
||||
assert!(stack.current().is_some());
|
||||
|
||||
assert!(diff.is_some());
|
||||
|
||||
let diff = diff.unwrap();
|
||||
assert_eq!(diff.added.len(), 1);
|
||||
assert!(diff.removed.is_empty());
|
||||
assert!(diff.modified.is_empty());
|
||||
}
|
||||
|
||||
// TODO more tests
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::statistics::{GPXStatistics, GlobalStatistics, sum_options};
|
||||
|
||||
pub struct Buffer {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StatisticsBuffer {
|
||||
pub total_distance: Vec<f64>,
|
||||
pub moving_distance: Vec<f64>,
|
||||
pub total_time: Vec<i64>,
|
||||
@@ -13,7 +14,7 @@ pub struct Buffer {
|
||||
pub slope_segment_distance: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
impl StatisticsBuffer {
|
||||
pub fn update(&mut self, stats: &[&GPXStatistics]) {
|
||||
self.total_distance.clear();
|
||||
self.moving_distance.clear();
|
||||
|
||||
@@ -322,4 +322,6 @@ mod tests {
|
||||
assert!((speed - 20.0).abs() < 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO more tests
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user