mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-04 12:58:23 +00:00
progress
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mod file;
|
||||
|
||||
pub use file::*;
|
||||
@@ -1,4 +1,4 @@
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct Link {
|
||||
pub href: String,
|
||||
pub text: Option<String>,
|
||||
|
||||
@@ -13,7 +13,7 @@ impl Default for FileId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct File {
|
||||
pub id: FileId,
|
||||
pub info: FileInfo,
|
||||
@@ -22,7 +22,7 @@ pub struct File {
|
||||
// TODO routes
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct FileInfo {
|
||||
pub name: String,
|
||||
pub desc: Option<String>,
|
||||
@@ -31,7 +31,7 @@ pub struct FileInfo {
|
||||
pub time: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Author {
|
||||
pub name: Option<String>,
|
||||
pub email: Option<String>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{Trackpoint, TrackpointChunk};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct TrackSegmentId(Uuid);
|
||||
|
||||
impl Default for TrackSegmentId {
|
||||
@@ -22,7 +22,7 @@ impl Default for TrackSegmentRevisionId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct TrackSegment {
|
||||
pub id: TrackSegmentId,
|
||||
pub rev_id: TrackSegmentRevisionId,
|
||||
|
||||
@@ -2,7 +2,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{Link, TrackSegment};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct TrackId(Uuid);
|
||||
|
||||
impl Default for TrackId {
|
||||
@@ -11,14 +11,14 @@ impl Default for TrackId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Track {
|
||||
pub id: TrackId,
|
||||
pub info: TrackInfo,
|
||||
pub trkseg: Vec<TrackSegment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct TrackInfo {
|
||||
pub name: Option<String>,
|
||||
pub cmt: Option<String>,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
mod action;
|
||||
mod algorithm;
|
||||
pub(crate) mod gpx;
|
||||
mod io;
|
||||
pub(crate) mod statistics;
|
||||
mod utils;
|
||||
|
||||
pub use action::*;
|
||||
pub use algorithm::*;
|
||||
pub use gpx::*;
|
||||
pub use io::*;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use crate::{File, State, parse, produce, update_each_selected_file};
|
||||
|
||||
pub fn create_file(state: &mut State, name: &str) -> Result<(), String> {
|
||||
produce(state, |_| {
|
||||
let mut file = File::default();
|
||||
file.info.name = name.to_owned();
|
||||
vec![file]
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_file(state: &mut State, data: &[u8]) -> Result<(), String> {
|
||||
let file = parse(data).map_err(|err| err.to_string())?;
|
||||
produce(state, |_| vec![file]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_metadata(state: &mut State, name: &str, desc: &str) -> Result<(), String> {
|
||||
update_each_selected_file(state, &mut |file| {
|
||||
let mut next = (*file).clone();
|
||||
next.info.name = name.to_owned();
|
||||
next.info.desc = Some(desc.to_owned());
|
||||
next
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
mod applier;
|
||||
mod command;
|
||||
mod pattern;
|
||||
|
||||
pub use applier::*;
|
||||
pub use command::*;
|
||||
pub use pattern::*;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::{
|
||||
collections::{HashSet, hash_map::Entry},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{File, FileId, Selection, State};
|
||||
|
||||
pub fn produce<F>(state: &mut State, f: F)
|
||||
where
|
||||
F: FnOnce(&State) -> Vec<File>,
|
||||
{
|
||||
*state.selection = Selection::Empty;
|
||||
for file in f(state).drain(..) {
|
||||
let file_id = file.id;
|
||||
state.files.insert(file_id, Rc::new(file));
|
||||
state.order.0.push(file_id);
|
||||
if matches!(*state.selection, Selection::Empty) {
|
||||
let mut file_ids: HashSet<FileId> = Default::default();
|
||||
file_ids.insert(file_id);
|
||||
*state.selection = Selection::File { file_ids };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_each_selected_file<F>(state: &mut State, f: &mut F)
|
||||
where
|
||||
F: FnMut(&File) -> File,
|
||||
{
|
||||
if let Selection::File { file_ids } = state.selection {
|
||||
for file_id in file_ids.iter() {
|
||||
if let Entry::Occupied(mut e) = state.files.entry(*file_id) {
|
||||
let file = e.get();
|
||||
let updated = Rc::new(f(file.as_ref()));
|
||||
e.insert(updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
// TODO license in every file
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
Command, File, FileId, Selection, Stack, StackEntry, StatisticsBuffer, StatisticsCache, parse,
|
||||
Command, FileOrder, Selection, Stack, State, StatisticsBuffer, StatisticsCache, create_file,
|
||||
load_file, update_metadata,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Engine {
|
||||
stack: Stack,
|
||||
selection: Selection,
|
||||
order: FileOrder,
|
||||
statistics_cache: StatisticsCache,
|
||||
statistics_buffer: StatisticsBuffer,
|
||||
}
|
||||
@@ -21,75 +19,54 @@ impl Engine {
|
||||
&self.statistics_buffer
|
||||
}
|
||||
|
||||
pub fn create_file(&mut self, name: &str) -> bool {
|
||||
self.event_loop(|entry| {
|
||||
let mut file = File::default();
|
||||
file.info.name = name.to_string();
|
||||
entry.insert(file.id, Rc::new(file));
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_file(&mut self, data: &[u8]) -> bool {
|
||||
self.event_loop(|entry| {
|
||||
if let Ok(file) = parse(data) {
|
||||
entry.insert(file.id, Rc::new(file));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_file(&mut self, id: &[u8]) -> bool {
|
||||
self.event_loop(|entry| {
|
||||
if let Ok(id) = Uuid::from_slice(id) {
|
||||
let id = FileId(id);
|
||||
entry.remove(&id).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
// pub fn delete_file(&mut self, id: &[u8]) -> bool {
|
||||
// self.event_loop(|entry| {
|
||||
// if let Ok(id) = Uuid::from_slice(id) {
|
||||
// let id = FileId(id);
|
||||
// entry.remove(&id).is_some()
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
/// Single entry point for every user action coming from the frontend.
|
||||
pub fn execute(&mut self, command: Command) -> bool {
|
||||
match command {
|
||||
Command::New { name } => self.create_file(name),
|
||||
Command::Load { data } => self.load_file(data),
|
||||
Command::Delete
|
||||
| Command::DeleteAll
|
||||
| Command::Duplicate
|
||||
| Command::Metadata { .. }
|
||||
| Command::Style { .. }
|
||||
| Command::NewTrack
|
||||
| Command::NewTrackSegment
|
||||
| Command::Reverse
|
||||
| Command::Append { .. }
|
||||
| Command::Replace { .. }
|
||||
| Command::NewWaypoint { .. }
|
||||
| Command::MoveWaypoint { .. }
|
||||
| Command::Crop { .. }
|
||||
| Command::Split { .. }
|
||||
| Command::Time
|
||||
| Command::Merge { .. }
|
||||
| Command::Extract
|
||||
| Command::Elevation { .. }
|
||||
| Command::Clean { .. }
|
||||
| Command::Undo
|
||||
| Command::Redo
|
||||
| Command::Select { .. }
|
||||
| Command::AddSelect { .. }
|
||||
| Command::SelectAll => todo!("command not implemented yet"),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_loop<F>(&mut self, f: F) -> bool
|
||||
where
|
||||
F: FnOnce(&mut StackEntry) -> bool,
|
||||
{
|
||||
if let Some(diff) = self.stack.create_and_push_next(f) {
|
||||
self.selection.select(diff.added[0]);
|
||||
if let Some(diff) = self.stack.create_and_push_next(|files| {
|
||||
let mut state = State {
|
||||
files,
|
||||
selection: &mut self.selection,
|
||||
order: &mut self.order,
|
||||
};
|
||||
match command {
|
||||
Command::New { name } => create_file(&mut state, name),
|
||||
Command::Load { data } => load_file(&mut state, data),
|
||||
Command::Metadata { name, desc } => update_metadata(&mut state, name, desc),
|
||||
Command::Delete
|
||||
| Command::DeleteAll
|
||||
| Command::Duplicate
|
||||
| Command::Style { .. }
|
||||
| Command::NewTrack
|
||||
| Command::NewTrackSegment
|
||||
| Command::Reverse
|
||||
| Command::Append { .. }
|
||||
| Command::Replace { .. }
|
||||
| Command::NewWaypoint { .. }
|
||||
| Command::MoveWaypoint { .. }
|
||||
| Command::Crop { .. }
|
||||
| Command::Split { .. }
|
||||
| Command::Time
|
||||
| Command::Merge { .. }
|
||||
| Command::Extract
|
||||
| Command::Elevation { .. }
|
||||
| Command::Clean { .. }
|
||||
| Command::Undo
|
||||
| Command::Redo
|
||||
| Command::Select { .. }
|
||||
| Command::AddSelect { .. }
|
||||
| Command::SelectAll => todo!("command not implemented yet"),
|
||||
}
|
||||
}) {
|
||||
self.statistics_cache.update(self.stack.current());
|
||||
self.statistics_buffer.update(
|
||||
&self
|
||||
@@ -117,7 +94,9 @@ mod tests {
|
||||
let mut data = String::new();
|
||||
let _ = f.read_to_string(&mut data);
|
||||
|
||||
engine.load_file(data.as_bytes());
|
||||
engine.execute(Command::Load {
|
||||
data: data.as_bytes(),
|
||||
});
|
||||
|
||||
assert_eq!(engine.statistics_buffer.total_distance.len(), 80);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
mod command;
|
||||
mod derived;
|
||||
mod engine;
|
||||
mod selection;
|
||||
mod state;
|
||||
|
||||
pub use command::*;
|
||||
pub use derived::*;
|
||||
pub use engine::*;
|
||||
pub use selection::*;
|
||||
pub use state::*;
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
mod order;
|
||||
mod selection;
|
||||
mod stack;
|
||||
|
||||
pub use order::*;
|
||||
pub use selection::*;
|
||||
pub use stack::*;
|
||||
|
||||
pub struct State<'a> {
|
||||
pub files: &'a mut StackEntry,
|
||||
pub selection: &'a mut Selection,
|
||||
pub order: &'a mut FileOrder,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
use crate::FileId;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FileOrder(pub Vec<FileId>);
|
||||
@@ -26,11 +26,3 @@ pub enum Selection {
|
||||
wpt_ids: HashSet<WaypointId>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
pub fn select(&mut self, id: FileId) {
|
||||
let mut file_ids = HashSet::default();
|
||||
file_ids.insert(id);
|
||||
*self = Selection::File { file_ids };
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,11 @@ impl Stack {
|
||||
|
||||
pub fn create_and_push_next<F>(&mut self, f: F) -> Option<Diff>
|
||||
where
|
||||
F: FnOnce(&mut StackEntry) -> bool,
|
||||
F: FnOnce(&mut StackEntry) -> Result<(), String>,
|
||||
{
|
||||
self.record_diff(|stack| {
|
||||
let mut next = stack.current().map_or_default(|c| c.clone());
|
||||
if f(&mut next) {
|
||||
if f(&mut next).is_ok() {
|
||||
stack.push(next);
|
||||
}
|
||||
})
|
||||
@@ -139,7 +139,7 @@ mod tests {
|
||||
let diff = stack.create_and_push_next(|e| {
|
||||
let file = Rc::new(File::default());
|
||||
e.insert(file.id, file);
|
||||
true
|
||||
Ok(())
|
||||
});
|
||||
|
||||
assert!(stack.can_undo());
|
||||
|
||||
Reference in New Issue
Block a user