mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-07 06:14:07 +00:00
progress
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Command, FileId};
|
use crate::{Command, FileId, Selection};
|
||||||
|
|
||||||
/// Everything the frontend can ask the engine to do.
|
/// Everything the frontend can ask the engine to do.
|
||||||
///
|
///
|
||||||
@@ -9,13 +9,14 @@ pub enum Action<'a> {
|
|||||||
Edit(Command<'a>),
|
Edit(Command<'a>),
|
||||||
Undo,
|
Undo,
|
||||||
Redo,
|
Redo,
|
||||||
|
/// Selects files, tracks, segments or waypoints (or the waypoints node of one file),
|
||||||
|
/// ignoring what does not exist: a selection left with nothing is empty, which deselects
|
||||||
|
/// everything. With `add`, the elements are added to the current selection when they can
|
||||||
|
/// be merged with it (see [`Selection::extend`]), otherwise they replace it.
|
||||||
Select {
|
Select {
|
||||||
file_ids: Vec<FileId>,
|
selection: Selection,
|
||||||
|
add: bool,
|
||||||
},
|
},
|
||||||
AddSelect {
|
|
||||||
file_ids: Vec<FileId>,
|
|
||||||
},
|
|
||||||
SelectAll,
|
|
||||||
/// Moves the files to `index` among the other files, in the given order. Not undoable.
|
/// Moves the files to `index` among the other files, in the given order. Not undoable.
|
||||||
Reorder {
|
Reorder {
|
||||||
file_ids: Vec<FileId>,
|
file_ids: Vec<FileId>,
|
||||||
|
|||||||
@@ -81,20 +81,18 @@ impl Engine {
|
|||||||
Action::Edit(command) => self.edit(command),
|
Action::Edit(command) => self.edit(command),
|
||||||
Action::Undo => self.stack.undo(),
|
Action::Undo => self.stack.undo(),
|
||||||
Action::Redo => self.stack.redo(),
|
Action::Redo => self.stack.redo(),
|
||||||
Action::Select { file_ids } => {
|
Action::Select { mut selection, add } => {
|
||||||
self.selection = Selection::File {
|
match self.stack.current() {
|
||||||
file_ids: file_ids.into_iter().collect(),
|
Some(files) => selection.retain_existing(files),
|
||||||
};
|
None => selection = Selection::Empty,
|
||||||
None
|
|
||||||
}
|
|
||||||
Action::AddSelect { file_ids } => {
|
|
||||||
match &mut self.selection {
|
|
||||||
Selection::File { file_ids: ids } => ids.extend(file_ids),
|
|
||||||
selection => {
|
|
||||||
*selection = Selection::File {
|
|
||||||
file_ids: file_ids.into_iter().collect(),
|
|
||||||
}
|
}
|
||||||
|
if add {
|
||||||
|
// nothing to add: keep the selection
|
||||||
|
if selection != Selection::Empty {
|
||||||
|
self.selection.extend(selection);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.selection = selection;
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -102,12 +100,6 @@ impl Engine {
|
|||||||
self.order.move_files(&file_ids, index);
|
self.order.move_files(&file_ids, index);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Action::SelectAll => {
|
|
||||||
self.selection = Selection::File {
|
|
||||||
file_ids: self.order.0.iter().copied().collect(),
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let changed = self.diff.is_some()
|
let changed = self.diff.is_some()
|
||||||
|| self.selection != selection_before
|
|| self.selection != selection_before
|
||||||
@@ -156,7 +148,7 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Load, Metadata, New, NewTrack, Style};
|
use crate::{Load, Metadata, New, NewTrack, Style, TrackId, TrackSegmentId};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -402,12 +394,12 @@ mod tests {
|
|||||||
let a = engine.order()[0];
|
let a = engine.order()[0];
|
||||||
// a new file is selected by the edit
|
// a new file is selected by the edit
|
||||||
assert!(engine.selection_changed());
|
assert!(engine.selection_changed());
|
||||||
assert!(!engine.execute(Action::Select { file_ids: vec![a] }));
|
assert!(!select_files(&mut engine, &[a]));
|
||||||
assert!(!engine.selection_changed());
|
assert!(!engine.selection_changed());
|
||||||
assert!(engine.execute(Action::Select { file_ids: vec![] }));
|
assert!(select_files(&mut engine, &[]));
|
||||||
assert!(engine.selection_changed());
|
assert!(engine.selection_changed());
|
||||||
// undoing drops the selected file from the selection
|
// undoing drops the selected file from the selection
|
||||||
engine.execute(Action::Select { file_ids: vec![a] });
|
select_files(&mut engine, &[a]);
|
||||||
assert!(engine.execute(Action::Undo));
|
assert!(engine.execute(Action::Undo));
|
||||||
assert!(engine.selection_changed());
|
assert!(engine.selection_changed());
|
||||||
assert_eq!(engine.selection(), &Selection::Empty);
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
@@ -421,7 +413,7 @@ mod tests {
|
|||||||
engine.execute(Action::Undo);
|
engine.execute(Action::Undo);
|
||||||
assert!(engine.statistics().total_distance.is_empty());
|
assert!(engine.statistics().total_distance.is_empty());
|
||||||
engine.execute(Action::Redo);
|
engine.execute(Action::Redo);
|
||||||
engine.execute(Action::SelectAll);
|
select_all(&mut engine);
|
||||||
assert_eq!(engine.statistics().total_distance.len(), 80);
|
assert_eq!(engine.statistics().total_distance.len(), 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,14 +426,14 @@ mod tests {
|
|||||||
let b = selected(&engine)[0];
|
let b = selected(&engine)[0];
|
||||||
assert_eq!(selected(&engine), vec![b]);
|
assert_eq!(selected(&engine), vec![b]);
|
||||||
|
|
||||||
assert!(engine.execute(Action::Select { file_ids: vec![a] }));
|
assert!(select_files(&mut engine, &[a]));
|
||||||
assert_eq!(selected(&engine), vec![a]);
|
assert_eq!(selected(&engine), vec![a]);
|
||||||
|
|
||||||
assert!(engine.execute(Action::AddSelect { file_ids: vec![b] }));
|
assert!(select_elements(&mut engine, files(&[b]), true));
|
||||||
assert_eq!(selected(&engine).len(), 2);
|
assert_eq!(selected(&engine).len(), 2);
|
||||||
|
|
||||||
engine.execute(Action::Select { file_ids: vec![] });
|
select_files(&mut engine, &[]);
|
||||||
assert!(engine.execute(Action::SelectAll));
|
assert!(select_all(&mut engine));
|
||||||
assert_eq!(selected(&engine).len(), 2);
|
assert_eq!(selected(&engine).len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,8 +441,119 @@ mod tests {
|
|||||||
fn test_selection_does_not_create_undo_steps() {
|
fn test_selection_does_not_create_undo_steps() {
|
||||||
let mut engine = Engine::default();
|
let mut engine = Engine::default();
|
||||||
new(&mut engine, "a");
|
new(&mut engine, "a");
|
||||||
engine.execute(Action::SelectAll);
|
select_all(&mut engine);
|
||||||
engine.execute(Action::Undo);
|
engine.execute(Action::Undo);
|
||||||
assert!(engine.stack.current().is_none());
|
assert!(engine.stack.current().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_elements(engine: &mut Engine, selection: Selection, add: bool) -> bool {
|
||||||
|
engine.execute(Action::Select { selection, add })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn files(ids: &[FileId]) -> Selection {
|
||||||
|
Selection::File {
|
||||||
|
file_ids: ids.iter().copied().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_files(engine: &mut Engine, ids: &[FileId]) -> bool {
|
||||||
|
select_elements(engine, files(ids), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_all(engine: &mut Engine) -> bool {
|
||||||
|
let ids = engine.order().to_vec();
|
||||||
|
select_files(engine, &ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_select_elements() {
|
||||||
|
let mut engine = Engine::default();
|
||||||
|
load(&mut engine, "data/with_tracks_and_segments.gpx");
|
||||||
|
let file = engine
|
||||||
|
.stack
|
||||||
|
.current()
|
||||||
|
.unwrap()
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
assert!(file.trk.len() >= 2 && file.trk[0].trkseg.len() >= 2);
|
||||||
|
let (t0, t1) = (file.trk[0].id, file.trk[1].id);
|
||||||
|
let (s0, s1) = (file.trk[0].trkseg[0].id, file.trk[0].trkseg[1].id);
|
||||||
|
|
||||||
|
// tracks, then more tracks
|
||||||
|
let tracks = |ids: &[TrackId]| Selection::Track {
|
||||||
|
file_id: file.id,
|
||||||
|
trk_ids: ids.iter().copied().collect(),
|
||||||
|
};
|
||||||
|
assert!(select_elements(&mut engine, tracks(&[t0]), false));
|
||||||
|
assert_eq!(engine.selection(), &tracks(&[t0]));
|
||||||
|
assert!(select_elements(&mut engine, tracks(&[t1]), true));
|
||||||
|
assert_eq!(engine.selection(), &tracks(&[t0, t1]));
|
||||||
|
|
||||||
|
// segments of a track, added only within the same track
|
||||||
|
let segments = |trk_id, ids: &[TrackSegmentId]| Selection::TrackSegment {
|
||||||
|
file_id: file.id,
|
||||||
|
trk_id,
|
||||||
|
trkseg_ids: ids.iter().copied().collect(),
|
||||||
|
};
|
||||||
|
assert!(select_elements(&mut engine, segments(t0, &[s0]), true));
|
||||||
|
assert_eq!(engine.selection(), &segments(t0, &[s0]));
|
||||||
|
assert!(select_elements(&mut engine, segments(t0, &[s1]), true));
|
||||||
|
assert_eq!(engine.selection(), &segments(t0, &[s0, s1]));
|
||||||
|
// the statistics follow the selected segments
|
||||||
|
assert_eq!(
|
||||||
|
engine.statistics().total_distance.len(),
|
||||||
|
file.trk[0].trkseg[0].len() + file.trk[0].trkseg[1].len()
|
||||||
|
);
|
||||||
|
|
||||||
|
// the waypoints node
|
||||||
|
let node = Selection::Waypoints { file_id: file.id };
|
||||||
|
assert!(select_elements(&mut engine, node.clone(), false));
|
||||||
|
assert!(!select_elements(&mut engine, node.clone(), true));
|
||||||
|
assert_eq!(engine.selection(), &node);
|
||||||
|
assert!(engine.selection_changed() == false);
|
||||||
|
|
||||||
|
// unknown elements and files are ignored when adding
|
||||||
|
let unknown = tracks(&[TrackId::default()]);
|
||||||
|
assert!(!select_elements(&mut engine, unknown.clone(), true));
|
||||||
|
let unknown_file = Selection::Waypoints {
|
||||||
|
file_id: FileId::default(),
|
||||||
|
};
|
||||||
|
assert!(!select_elements(&mut engine, unknown_file.clone(), true));
|
||||||
|
assert!(!select_elements(&mut engine, Selection::Empty, true));
|
||||||
|
assert_eq!(engine.selection(), &node);
|
||||||
|
|
||||||
|
// and select nothing otherwise
|
||||||
|
assert!(select_elements(&mut engine, unknown, false));
|
||||||
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
|
select_elements(&mut engine, node, false);
|
||||||
|
assert!(select_elements(&mut engine, unknown_file, false));
|
||||||
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_selected_elements_are_dropped_when_they_disappear() {
|
||||||
|
let mut engine = Engine::default();
|
||||||
|
load(&mut engine, "data/with_tracks_and_segments.gpx");
|
||||||
|
let file = engine
|
||||||
|
.stack
|
||||||
|
.current()
|
||||||
|
.unwrap()
|
||||||
|
.values()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
let trk_id = file.trk[0].id;
|
||||||
|
select_elements(
|
||||||
|
&mut engine,
|
||||||
|
Selection::Track {
|
||||||
|
file_id: file.id,
|
||||||
|
trk_ids: [trk_id].into(),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
engine.execute(Action::Undo);
|
||||||
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,24 +28,92 @@ pub enum Selection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Selection {
|
impl Selection {
|
||||||
/// Drops the files that do not exist anymore (e.g. after an undo).
|
/// Drops what does not exist anymore (e.g. after an undo): files, and the tracks, segments
|
||||||
|
/// and waypoints of the files that remain. A selection left with nothing becomes empty.
|
||||||
pub fn retain_existing(&mut self, files: &StackEntry) {
|
pub fn retain_existing(&mut self, files: &StackEntry) {
|
||||||
match self {
|
let empty = match self {
|
||||||
Selection::Empty => {}
|
Selection::Empty => false,
|
||||||
Selection::File { file_ids } => {
|
Selection::File { file_ids } => {
|
||||||
file_ids.retain(|id| files.contains_key(id));
|
file_ids.retain(|id| files.contains_key(id));
|
||||||
if file_ids.is_empty() {
|
file_ids.is_empty()
|
||||||
|
}
|
||||||
|
Selection::Track { file_id, trk_ids } => match files.get(file_id) {
|
||||||
|
Some(file) => {
|
||||||
|
trk_ids.retain(|id| file.trk.iter().any(|trk| trk.id == *id));
|
||||||
|
trk_ids.is_empty()
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
},
|
||||||
|
Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id,
|
||||||
|
trkseg_ids,
|
||||||
|
} => match files
|
||||||
|
.get(file_id)
|
||||||
|
.and_then(|file| file.trk.iter().find(|trk| trk.id == *trk_id))
|
||||||
|
{
|
||||||
|
Some(trk) => {
|
||||||
|
trkseg_ids.retain(|id| trk.trkseg.iter().any(|seg| seg.id == *id));
|
||||||
|
trkseg_ids.is_empty()
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
},
|
||||||
|
Selection::Waypoints { file_id } => !files.contains_key(file_id),
|
||||||
|
Selection::Waypoint { file_id, wpt_ids } => match files.get(file_id) {
|
||||||
|
Some(file) => {
|
||||||
|
wpt_ids.retain(|id| {
|
||||||
|
file.wpt
|
||||||
|
.iter()
|
||||||
|
.flat_map(|chunk| &chunk.wpt)
|
||||||
|
.any(|wpt| wpt.id == *id)
|
||||||
|
});
|
||||||
|
wpt_ids.is_empty()
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if empty {
|
||||||
*self = Selection::Empty;
|
*self = Selection::Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Selection::Track { file_id, .. }
|
|
||||||
| Selection::TrackSegment { file_id, .. }
|
/// Adds `other` to the selection: the elements are merged when they are of the same kind and
|
||||||
| Selection::Waypoints { file_id }
|
/// in the same place (same file, same track for segments), otherwise `other` replaces the
|
||||||
| Selection::Waypoint { file_id, .. } => {
|
/// selection.
|
||||||
if !files.contains_key(file_id) {
|
pub fn extend(&mut self, other: Selection) {
|
||||||
*self = Selection::Empty;
|
match (&mut *self, other) {
|
||||||
}
|
(Selection::File { file_ids }, Selection::File { file_ids: other }) => {
|
||||||
|
file_ids.extend(other)
|
||||||
}
|
}
|
||||||
|
(
|
||||||
|
Selection::Track { file_id, trk_ids },
|
||||||
|
Selection::Track {
|
||||||
|
file_id: other_file,
|
||||||
|
trk_ids: other,
|
||||||
|
},
|
||||||
|
) if *file_id == other_file => trk_ids.extend(other),
|
||||||
|
(
|
||||||
|
Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id,
|
||||||
|
trkseg_ids,
|
||||||
|
},
|
||||||
|
Selection::TrackSegment {
|
||||||
|
file_id: other_file,
|
||||||
|
trk_id: other_trk,
|
||||||
|
trkseg_ids: other,
|
||||||
|
},
|
||||||
|
) if *file_id == other_file && *trk_id == other_trk => trkseg_ids.extend(other),
|
||||||
|
(Selection::Waypoints { file_id }, Selection::Waypoints { file_id: other })
|
||||||
|
if *file_id == other => {}
|
||||||
|
(
|
||||||
|
Selection::Waypoint { file_id, wpt_ids },
|
||||||
|
Selection::Waypoint {
|
||||||
|
file_id: other_file,
|
||||||
|
wpt_ids: other,
|
||||||
|
},
|
||||||
|
) if *file_id == other_file => wpt_ids.extend(other),
|
||||||
|
(selection, other) => *selection = other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +122,7 @@ impl Selection {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::File;
|
use crate::{File, Track, TrackSegment, Waypoint, WaypointChunk};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -86,4 +154,102 @@ mod tests {
|
|||||||
selection.retain_existing(&files);
|
selection.retain_existing(&files);
|
||||||
assert!(matches!(selection, Selection::Empty));
|
assert!(matches!(selection, Selection::Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_retain_existing_elements() {
|
||||||
|
let mut file = File::default();
|
||||||
|
file.trk.push(Track {
|
||||||
|
trkseg: vec![TrackSegment::default()],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
file.wpt.push(Rc::new(WaypointChunk {
|
||||||
|
wpt: vec![Waypoint::default()],
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
let (file_id, trk_id) = (file.id, file.trk[0].id);
|
||||||
|
let (seg_id, wpt_id) = (file.trk[0].trkseg[0].id, file.wpt[0].wpt[0].id);
|
||||||
|
let mut files = StackEntry::default();
|
||||||
|
files.insert(file_id, Rc::new(file));
|
||||||
|
|
||||||
|
let mut selection = Selection::Track {
|
||||||
|
file_id,
|
||||||
|
trk_ids: [trk_id, TrackId::default()].into(),
|
||||||
|
};
|
||||||
|
selection.retain_existing(&files);
|
||||||
|
assert_eq!(
|
||||||
|
selection,
|
||||||
|
Selection::Track {
|
||||||
|
file_id,
|
||||||
|
trk_ids: [trk_id].into()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut selection = Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id,
|
||||||
|
trkseg_ids: [seg_id, TrackSegmentId::default()].into(),
|
||||||
|
};
|
||||||
|
selection.retain_existing(&files);
|
||||||
|
assert!(
|
||||||
|
matches!(&selection, Selection::TrackSegment { trkseg_ids, .. } if trkseg_ids.len() == 1)
|
||||||
|
);
|
||||||
|
// a segment under the wrong track
|
||||||
|
let mut selection = Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id: TrackId::default(),
|
||||||
|
trkseg_ids: [seg_id].into(),
|
||||||
|
};
|
||||||
|
selection.retain_existing(&files);
|
||||||
|
assert_eq!(selection, Selection::Empty);
|
||||||
|
|
||||||
|
let mut selection = Selection::Waypoint {
|
||||||
|
file_id,
|
||||||
|
wpt_ids: [wpt_id, WaypointId::default()].into(),
|
||||||
|
};
|
||||||
|
selection.retain_existing(&files);
|
||||||
|
assert!(matches!(&selection, Selection::Waypoint { wpt_ids, .. } if wpt_ids.len() == 1));
|
||||||
|
let mut selection = Selection::Waypoint {
|
||||||
|
file_id,
|
||||||
|
wpt_ids: [WaypointId::default()].into(),
|
||||||
|
};
|
||||||
|
selection.retain_existing(&files);
|
||||||
|
assert_eq!(selection, Selection::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extend() {
|
||||||
|
let file_id = FileId::default();
|
||||||
|
let (a, b) = (TrackId::default(), TrackId::default());
|
||||||
|
let tracks = |ids: &[TrackId]| Selection::Track {
|
||||||
|
file_id,
|
||||||
|
trk_ids: ids.iter().copied().collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// same kind and place: merged
|
||||||
|
let mut selection = tracks(&[a]);
|
||||||
|
selection.extend(tracks(&[b]));
|
||||||
|
assert_eq!(selection, tracks(&[a, b]));
|
||||||
|
|
||||||
|
// another file, or another kind: replaced
|
||||||
|
let mut selection = tracks(&[a]);
|
||||||
|
let other = Selection::Track {
|
||||||
|
file_id: FileId::default(),
|
||||||
|
trk_ids: [b].into(),
|
||||||
|
};
|
||||||
|
selection.extend(other.clone());
|
||||||
|
assert_eq!(selection, other);
|
||||||
|
selection.extend(Selection::Waypoints { file_id });
|
||||||
|
assert_eq!(selection, Selection::Waypoints { file_id });
|
||||||
|
|
||||||
|
// segments of another track: replaced
|
||||||
|
let seg = TrackSegmentId::default();
|
||||||
|
let segments = |trk_id| Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id,
|
||||||
|
trkseg_ids: [seg].into(),
|
||||||
|
};
|
||||||
|
let mut selection = segments(a);
|
||||||
|
selection.extend(segments(b));
|
||||||
|
assert_eq!(selection, segments(b));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-16
@@ -66,14 +66,18 @@ fn edit(command: Command) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes concatenated 16-byte UUIDs.
|
/// Decodes concatenated 16-byte UUIDs.
|
||||||
fn file_ids(bytes: &[u8]) -> Option<Vec<FileId>> {
|
fn uuid_list(bytes: &[u8]) -> Option<Vec<uuid::Uuid>> {
|
||||||
let (chunks, rest) = bytes.as_chunks::<16>();
|
let (chunks, rest) = bytes.as_chunks::<16>();
|
||||||
rest.is_empty().then(|| {
|
rest.is_empty()
|
||||||
chunks
|
.then(|| chunks.iter().map(|c| uuid::Uuid::from_bytes(*c)).collect())
|
||||||
.iter()
|
}
|
||||||
.map(|c| FileId(uuid::Uuid::from_bytes(*c)))
|
|
||||||
.collect()
|
fn file_ids(bytes: &[u8]) -> Option<Vec<FileId>> {
|
||||||
})
|
uuid_list(bytes).map(|ids| ids.into_iter().map(FileId).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_file_id(id: &str) -> Option<FileId> {
|
||||||
|
uuid::Uuid::parse_str(id).ok().map(FileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn same_len(a: &[f64], b: &[f64], c: &[f64]) -> bool {
|
fn same_len(a: &[f64], b: &[f64], c: &[f64]) -> bool {
|
||||||
@@ -301,21 +305,76 @@ pub fn redo() -> bool {
|
|||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
|
|
||||||
/// `file_ids_bytes`: concatenated 16-byte UUIDs.
|
/// Selects files. `file_ids_bytes`: concatenated 16-byte UUIDs. Unknown files are ignored, and
|
||||||
|
/// selecting nothing deselects everything. With `add`, the files are added to the selection if it
|
||||||
|
/// already holds files.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select(file_ids_bytes: &[u8]) -> bool {
|
pub fn select(file_ids_bytes: &[u8], add: bool) -> bool {
|
||||||
file_ids(file_ids_bytes).is_some_and(|file_ids| execute(Action::Select { file_ids }))
|
select_elements(
|
||||||
|
file_ids(file_ids_bytes).map(|ids| engine::Selection::File {
|
||||||
|
file_ids: ids.into_iter().collect(),
|
||||||
|
}),
|
||||||
|
add,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `file_ids_bytes`: concatenated 16-byte UUIDs.
|
// The elements below cross as the id of their file (UUID string) and, like the files, as
|
||||||
#[wasm_bindgen]
|
// concatenated 16-byte UUIDs. What does not exist is ignored. With `add`, the elements are added
|
||||||
pub fn add_select(file_ids_bytes: &[u8]) -> bool {
|
// to the selection when it holds elements of the same kind in the same place (same file, same
|
||||||
file_ids(file_ids_bytes).is_some_and(|file_ids| execute(Action::AddSelect { file_ids }))
|
// track for segments), otherwise they replace it.
|
||||||
|
|
||||||
|
fn select_elements(selection: Option<engine::Selection>, add: bool) -> bool {
|
||||||
|
selection.is_some_and(|selection| execute(Action::Select { selection, add }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_all() -> bool {
|
pub fn select_tracks(file_id: &str, track_ids_bytes: &[u8], add: bool) -> bool {
|
||||||
execute(Action::SelectAll)
|
select_elements(
|
||||||
|
parse_file_id(file_id)
|
||||||
|
.zip(uuid_list(track_ids_bytes))
|
||||||
|
.map(|(file_id, ids)| engine::Selection::Track {
|
||||||
|
file_id,
|
||||||
|
trk_ids: ids.into_iter().map(engine::TrackId).collect(),
|
||||||
|
}),
|
||||||
|
add,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn select_segments(file_id: &str, track_id: &str, segment_ids_bytes: &[u8], add: bool) -> bool {
|
||||||
|
select_elements(
|
||||||
|
parse_file_id(file_id)
|
||||||
|
.zip(uuid::Uuid::parse_str(track_id).ok())
|
||||||
|
.zip(uuid_list(segment_ids_bytes))
|
||||||
|
.map(|((file_id, trk_id), ids)| engine::Selection::TrackSegment {
|
||||||
|
file_id,
|
||||||
|
trk_id: engine::TrackId(trk_id),
|
||||||
|
trkseg_ids: ids.into_iter().map(engine::TrackSegmentId).collect(),
|
||||||
|
}),
|
||||||
|
add,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selects the node standing for all the waypoints of a file.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn select_waypoint_group(file_id: &str) -> bool {
|
||||||
|
select_elements(
|
||||||
|
parse_file_id(file_id).map(|file_id| engine::Selection::Waypoints { file_id }),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn select_waypoints(file_id: &str, waypoint_ids_bytes: &[u8], add: bool) -> bool {
|
||||||
|
select_elements(
|
||||||
|
parse_file_id(file_id)
|
||||||
|
.zip(uuid_list(waypoint_ids_bytes))
|
||||||
|
.map(|(file_id, ids)| engine::Selection::Waypoint {
|
||||||
|
file_id,
|
||||||
|
wpt_ids: ids.into_iter().map(engine::WaypointId).collect(),
|
||||||
|
}),
|
||||||
|
add,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// File order and structures
|
// File order and structures
|
||||||
|
|||||||
@@ -154,16 +154,37 @@ class Engine {
|
|||||||
return this.run((w) => w.redo());
|
return this.run((w) => w.redo());
|
||||||
}
|
}
|
||||||
|
|
||||||
select(fileIds: string[]) {
|
/**
|
||||||
return this.run((w) => w.select(idsToBytes(fileIds)));
|
* Selects files. With `add`, they are added to the selection if it already holds files.
|
||||||
}
|
* Selecting nothing (or only unknown files) deselects everything.
|
||||||
|
*/
|
||||||
addSelect(fileIds: string[]) {
|
select(fileIds: string[], add = false) {
|
||||||
return this.run((w) => w.add_select(idsToBytes(fileIds)));
|
return this.run((w) => w.select(idsToBytes(fileIds), add));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAll() {
|
selectAll() {
|
||||||
return this.run((w) => w.select_all());
|
return this.select(get(this._order));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selecting elements inside a file. With `add`, they are added to the selection when it holds
|
||||||
|
// elements of the same kind in the same place (same file, same track for segments),
|
||||||
|
// otherwise they replace it. Ids that do not exist are ignored.
|
||||||
|
|
||||||
|
selectTracks(fileId: string, trackIds: string[], add = false) {
|
||||||
|
return this.run((w) => w.select_tracks(fileId, idsToBytes(trackIds), add));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSegments(fileId: string, trackId: string, segmentIds: string[], add = false) {
|
||||||
|
return this.run((w) => w.select_segments(fileId, trackId, idsToBytes(segmentIds), add));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Selects the node standing for all the waypoints of the file. */
|
||||||
|
selectWaypointGroup(fileId: string) {
|
||||||
|
return this.run((w) => w.select_waypoint_group(fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectWaypoints(fileId: string, waypointIds: string[], add = false) {
|
||||||
|
return this.run((w) => w.select_waypoints(fileId, idsToBytes(waypointIds), add));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Moves the files, in the given order, to `index` among the other files. */
|
/** Moves the files, in the given order, to `index` among the other files. */
|
||||||
|
|||||||
Reference in New Issue
Block a user