mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-06 22:04:06 +00:00
progress
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Command, FileId, Selection};
|
use crate::{Command, FileId, SelectMode, Selection};
|
||||||
|
|
||||||
/// Everything the frontend can ask the engine to do.
|
/// Everything the frontend can ask the engine to do.
|
||||||
///
|
///
|
||||||
@@ -11,11 +11,10 @@ pub enum Action<'a> {
|
|||||||
Redo,
|
Redo,
|
||||||
/// Selects files, tracks, segments or waypoints (or the waypoints node of one file),
|
/// 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
|
/// 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
|
/// everything. See [`SelectMode`] for how it combines with the current selection.
|
||||||
/// be merged with it (see [`Selection::extend`]), otherwise they replace it.
|
|
||||||
Select {
|
Select {
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
add: bool,
|
mode: SelectMode,
|
||||||
},
|
},
|
||||||
/// 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 {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, Apply, Command, CoordinatesCache, Diff, FileId, FileOrder, FileStructure,
|
Action, Apply, Command, CoordinatesCache, Diff, FileId, FileOrder, FileStructure,
|
||||||
FileStructureCache, GlobalStatistics, Selection, Stack, State, StatisticsBuffer,
|
FileStructureCache, GlobalStatistics, SelectMode, Selection, Stack, State, StatisticsBuffer,
|
||||||
StatisticsCache, TrackSegmentId,
|
StatisticsCache, TrackSegmentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,18 +81,20 @@ 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 { mut selection, add } => {
|
Action::Select {
|
||||||
|
mut selection,
|
||||||
|
mode,
|
||||||
|
} => {
|
||||||
match self.stack.current() {
|
match self.stack.current() {
|
||||||
Some(files) => selection.retain_existing(files),
|
Some(files) => selection.retain_existing(files),
|
||||||
None => selection = Selection::Empty,
|
None => selection = Selection::Empty,
|
||||||
}
|
}
|
||||||
if add {
|
match mode {
|
||||||
// nothing to add: keep the selection
|
SelectMode::Replace => self.selection = selection,
|
||||||
if selection != Selection::Empty {
|
// nothing to add or toggle: keep the selection
|
||||||
self.selection.extend(selection);
|
_ if selection == Selection::Empty => {}
|
||||||
}
|
SelectMode::Add => self.selection.extend(selection),
|
||||||
} else {
|
SelectMode::Toggle => self.selection.toggle(selection),
|
||||||
self.selection = selection;
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -429,7 +431,7 @@ mod tests {
|
|||||||
assert!(select_files(&mut engine, &[a]));
|
assert!(select_files(&mut engine, &[a]));
|
||||||
assert_eq!(selected(&engine), vec![a]);
|
assert_eq!(selected(&engine), vec![a]);
|
||||||
|
|
||||||
assert!(select_elements(&mut engine, files(&[b]), true));
|
assert!(select_elements(&mut engine, files(&[b]), SelectMode::Add));
|
||||||
assert_eq!(selected(&engine).len(), 2);
|
assert_eq!(selected(&engine).len(), 2);
|
||||||
|
|
||||||
select_files(&mut engine, &[]);
|
select_files(&mut engine, &[]);
|
||||||
@@ -446,8 +448,8 @@ mod tests {
|
|||||||
assert!(engine.stack.current().is_none());
|
assert!(engine.stack.current().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_elements(engine: &mut Engine, selection: Selection, add: bool) -> bool {
|
fn select_elements(engine: &mut Engine, selection: Selection, mode: SelectMode) -> bool {
|
||||||
engine.execute(Action::Select { selection, add })
|
engine.execute(Action::Select { selection, mode })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn files(ids: &[FileId]) -> Selection {
|
fn files(ids: &[FileId]) -> Selection {
|
||||||
@@ -457,7 +459,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select_files(engine: &mut Engine, ids: &[FileId]) -> bool {
|
fn select_files(engine: &mut Engine, ids: &[FileId]) -> bool {
|
||||||
select_elements(engine, files(ids), false)
|
select_elements(engine, files(ids), SelectMode::Replace)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_all(engine: &mut Engine) -> bool {
|
fn select_all(engine: &mut Engine) -> bool {
|
||||||
@@ -486,9 +488,13 @@ mod tests {
|
|||||||
file_id: file.id,
|
file_id: file.id,
|
||||||
trk_ids: ids.iter().copied().collect(),
|
trk_ids: ids.iter().copied().collect(),
|
||||||
};
|
};
|
||||||
assert!(select_elements(&mut engine, tracks(&[t0]), false));
|
assert!(select_elements(
|
||||||
|
&mut engine,
|
||||||
|
tracks(&[t0]),
|
||||||
|
SelectMode::Replace
|
||||||
|
));
|
||||||
assert_eq!(engine.selection(), &tracks(&[t0]));
|
assert_eq!(engine.selection(), &tracks(&[t0]));
|
||||||
assert!(select_elements(&mut engine, tracks(&[t1]), true));
|
assert!(select_elements(&mut engine, tracks(&[t1]), SelectMode::Add));
|
||||||
assert_eq!(engine.selection(), &tracks(&[t0, t1]));
|
assert_eq!(engine.selection(), &tracks(&[t0, t1]));
|
||||||
|
|
||||||
// segments of a track, added only within the same track
|
// segments of a track, added only within the same track
|
||||||
@@ -497,9 +503,17 @@ mod tests {
|
|||||||
trk_id,
|
trk_id,
|
||||||
trkseg_ids: ids.iter().copied().collect(),
|
trkseg_ids: ids.iter().copied().collect(),
|
||||||
};
|
};
|
||||||
assert!(select_elements(&mut engine, segments(t0, &[s0]), true));
|
assert!(select_elements(
|
||||||
|
&mut engine,
|
||||||
|
segments(t0, &[s0]),
|
||||||
|
SelectMode::Add
|
||||||
|
));
|
||||||
assert_eq!(engine.selection(), &segments(t0, &[s0]));
|
assert_eq!(engine.selection(), &segments(t0, &[s0]));
|
||||||
assert!(select_elements(&mut engine, segments(t0, &[s1]), true));
|
assert!(select_elements(
|
||||||
|
&mut engine,
|
||||||
|
segments(t0, &[s1]),
|
||||||
|
SelectMode::Add
|
||||||
|
));
|
||||||
assert_eq!(engine.selection(), &segments(t0, &[s0, s1]));
|
assert_eq!(engine.selection(), &segments(t0, &[s0, s1]));
|
||||||
// the statistics follow the selected segments
|
// the statistics follow the selected segments
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -509,26 +523,80 @@ mod tests {
|
|||||||
|
|
||||||
// the waypoints node
|
// the waypoints node
|
||||||
let node = Selection::Waypoints { file_id: file.id };
|
let node = Selection::Waypoints { file_id: file.id };
|
||||||
assert!(select_elements(&mut engine, node.clone(), false));
|
assert!(select_elements(
|
||||||
assert!(!select_elements(&mut engine, node.clone(), true));
|
&mut engine,
|
||||||
|
node.clone(),
|
||||||
|
SelectMode::Replace
|
||||||
|
));
|
||||||
|
assert!(!select_elements(&mut engine, node.clone(), SelectMode::Add));
|
||||||
assert_eq!(engine.selection(), &node);
|
assert_eq!(engine.selection(), &node);
|
||||||
assert!(engine.selection_changed() == false);
|
assert!(engine.selection_changed() == false);
|
||||||
|
|
||||||
// unknown elements and files are ignored when adding
|
// unknown elements and files are ignored when adding
|
||||||
let unknown = tracks(&[TrackId::default()]);
|
let unknown = tracks(&[TrackId::default()]);
|
||||||
assert!(!select_elements(&mut engine, unknown.clone(), true));
|
assert!(!select_elements(
|
||||||
|
&mut engine,
|
||||||
|
unknown.clone(),
|
||||||
|
SelectMode::Add
|
||||||
|
));
|
||||||
let unknown_file = Selection::Waypoints {
|
let unknown_file = Selection::Waypoints {
|
||||||
file_id: FileId::default(),
|
file_id: FileId::default(),
|
||||||
};
|
};
|
||||||
assert!(!select_elements(&mut engine, unknown_file.clone(), true));
|
assert!(!select_elements(
|
||||||
assert!(!select_elements(&mut engine, Selection::Empty, true));
|
&mut engine,
|
||||||
|
unknown_file.clone(),
|
||||||
|
SelectMode::Add
|
||||||
|
));
|
||||||
|
assert!(!select_elements(
|
||||||
|
&mut engine,
|
||||||
|
Selection::Empty,
|
||||||
|
SelectMode::Add
|
||||||
|
));
|
||||||
assert_eq!(engine.selection(), &node);
|
assert_eq!(engine.selection(), &node);
|
||||||
|
|
||||||
// and select nothing otherwise
|
// and select nothing otherwise
|
||||||
assert!(select_elements(&mut engine, unknown, false));
|
assert!(select_elements(&mut engine, unknown, SelectMode::Replace));
|
||||||
assert_eq!(engine.selection(), &Selection::Empty);
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
select_elements(&mut engine, node, false);
|
select_elements(&mut engine, node, SelectMode::Replace);
|
||||||
assert!(select_elements(&mut engine, unknown_file, false));
|
assert!(select_elements(
|
||||||
|
&mut engine,
|
||||||
|
unknown_file,
|
||||||
|
SelectMode::Replace
|
||||||
|
));
|
||||||
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_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();
|
||||||
|
let (t0, t1) = (file.trk[0].id, file.trk[1].id);
|
||||||
|
let tracks = |ids: &[TrackId]| Selection::Track {
|
||||||
|
file_id: file.id,
|
||||||
|
trk_ids: ids.iter().copied().collect(),
|
||||||
|
};
|
||||||
|
let toggle = |engine: &mut Engine, ids: &[TrackId]| {
|
||||||
|
select_elements(engine, tracks(ids), SelectMode::Toggle)
|
||||||
|
};
|
||||||
|
|
||||||
|
select_elements(&mut engine, tracks(&[t0]), SelectMode::Replace);
|
||||||
|
assert!(toggle(&mut engine, &[t1]));
|
||||||
|
assert_eq!(engine.selection(), &tracks(&[t0, t1]));
|
||||||
|
assert!(toggle(&mut engine, &[t0]));
|
||||||
|
assert_eq!(engine.selection(), &tracks(&[t1]));
|
||||||
|
// toggling the last element deselects everything
|
||||||
|
assert!(toggle(&mut engine, &[t1]));
|
||||||
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
|
// unknown elements change nothing
|
||||||
|
assert!(!toggle(&mut engine, &[TrackId::default()]));
|
||||||
assert_eq!(engine.selection(), &Selection::Empty);
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,7 +619,7 @@ mod tests {
|
|||||||
file_id: file.id,
|
file_id: file.id,
|
||||||
trk_ids: [trk_id].into(),
|
trk_ids: [trk_id].into(),
|
||||||
},
|
},
|
||||||
false,
|
SelectMode::Replace,
|
||||||
);
|
);
|
||||||
engine.execute(Action::Undo);
|
engine.execute(Action::Undo);
|
||||||
assert_eq!(engine.selection(), &Selection::Empty);
|
assert_eq!(engine.selection(), &Selection::Empty);
|
||||||
|
|||||||
@@ -2,6 +2,19 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use crate::{FileId, StackEntry, TrackId, TrackSegmentId, WaypointId};
|
use crate::{FileId, StackEntry, TrackId, TrackSegmentId, WaypointId};
|
||||||
|
|
||||||
|
/// How a new selection combines with the current one.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SelectMode {
|
||||||
|
/// The new selection replaces the current one.
|
||||||
|
Replace,
|
||||||
|
/// The elements are added when they are of the same kind and in the same place as the
|
||||||
|
/// current selection (see [`Selection::extend`]), otherwise they replace it.
|
||||||
|
Add,
|
||||||
|
/// Like `Add`, but the elements already selected are removed instead (see
|
||||||
|
/// [`Selection::toggle`]).
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum Selection {
|
pub enum Selection {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -116,6 +129,71 @@ impl Selection {
|
|||||||
(selection, other) => *selection = other,
|
(selection, other) => *selection = other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like [`Selection::extend`], but the elements of `other` that are already selected are
|
||||||
|
/// removed from the selection instead. A selection left with nothing is empty.
|
||||||
|
pub fn toggle(&mut self, other: Selection) {
|
||||||
|
fn toggle_ids<T: std::hash::Hash + Eq>(ids: &mut HashSet<T>, other: HashSet<T>) {
|
||||||
|
for id in other {
|
||||||
|
if !ids.remove(&id) {
|
||||||
|
ids.insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let empty = match (&mut *self, other) {
|
||||||
|
(Selection::File { file_ids }, Selection::File { file_ids: other }) => {
|
||||||
|
toggle_ids(file_ids, other);
|
||||||
|
file_ids.is_empty()
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Selection::Track { file_id, trk_ids },
|
||||||
|
Selection::Track {
|
||||||
|
file_id: other_file,
|
||||||
|
trk_ids: other,
|
||||||
|
},
|
||||||
|
) if *file_id == other_file => {
|
||||||
|
toggle_ids(trk_ids, other);
|
||||||
|
trk_ids.is_empty()
|
||||||
|
}
|
||||||
|
(
|
||||||
|
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 => {
|
||||||
|
toggle_ids(trkseg_ids, other);
|
||||||
|
trkseg_ids.is_empty()
|
||||||
|
}
|
||||||
|
(Selection::Waypoints { file_id }, Selection::Waypoints { file_id: other })
|
||||||
|
if *file_id == other =>
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Selection::Waypoint { file_id, wpt_ids },
|
||||||
|
Selection::Waypoint {
|
||||||
|
file_id: other_file,
|
||||||
|
wpt_ids: other,
|
||||||
|
},
|
||||||
|
) if *file_id == other_file => {
|
||||||
|
toggle_ids(wpt_ids, other);
|
||||||
|
wpt_ids.is_empty()
|
||||||
|
}
|
||||||
|
(selection, other) => {
|
||||||
|
*selection = other;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if empty {
|
||||||
|
*self = Selection::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -252,4 +330,46 @@ mod tests {
|
|||||||
selection.extend(segments(b));
|
selection.extend(segments(b));
|
||||||
assert_eq!(selection, segments(b));
|
assert_eq!(selection, segments(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle() {
|
||||||
|
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: toggled one by one
|
||||||
|
let mut selection = tracks(&[a]);
|
||||||
|
selection.toggle(tracks(&[b]));
|
||||||
|
assert_eq!(selection, tracks(&[a, b]));
|
||||||
|
selection.toggle(tracks(&[a]));
|
||||||
|
assert_eq!(selection, tracks(&[b]));
|
||||||
|
selection.toggle(tracks(&[a, b]));
|
||||||
|
assert_eq!(selection, tracks(&[a]));
|
||||||
|
|
||||||
|
// nothing left: empty
|
||||||
|
selection.toggle(tracks(&[a]));
|
||||||
|
assert_eq!(selection, Selection::Empty);
|
||||||
|
|
||||||
|
// another file, or another kind: replaced
|
||||||
|
let mut selection = tracks(&[a]);
|
||||||
|
selection.toggle(Selection::Waypoints { file_id });
|
||||||
|
assert_eq!(selection, Selection::Waypoints { file_id });
|
||||||
|
// the waypoints node toggles itself off
|
||||||
|
selection.toggle(Selection::Waypoints { file_id });
|
||||||
|
assert_eq!(selection, Selection::Empty);
|
||||||
|
|
||||||
|
// files
|
||||||
|
let (f1, f2) = (FileId::default(), FileId::default());
|
||||||
|
let files = |ids: &[FileId]| Selection::File {
|
||||||
|
file_ids: ids.iter().copied().collect(),
|
||||||
|
};
|
||||||
|
let mut selection = files(&[f1]);
|
||||||
|
selection.toggle(files(&[f2]));
|
||||||
|
assert_eq!(selection, files(&[f1, f2]));
|
||||||
|
selection.toggle(files(&[f1]));
|
||||||
|
assert_eq!(selection, files(&[f2]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-16
@@ -30,6 +30,26 @@ pub enum CleanType {
|
|||||||
Outside,
|
Outside,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How a selection combines with the current one: `Replace` it, `Add` to it, or `Toggle` the
|
||||||
|
/// elements (the ones already selected are removed).
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum SelectMode {
|
||||||
|
Replace,
|
||||||
|
Add,
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SelectMode> for engine::SelectMode {
|
||||||
|
fn from(m: SelectMode) -> Self {
|
||||||
|
match m {
|
||||||
|
SelectMode::Replace => Self::Replace,
|
||||||
|
SelectMode::Add => Self::Add,
|
||||||
|
SelectMode::Toggle => Self::Toggle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<MergeType> for engine::MergeType {
|
impl From<MergeType> for engine::MergeType {
|
||||||
fn from(t: MergeType) -> Self {
|
fn from(t: MergeType) -> Self {
|
||||||
match t {
|
match t {
|
||||||
@@ -306,29 +326,33 @@ pub fn redo() -> bool {
|
|||||||
// Selection
|
// Selection
|
||||||
|
|
||||||
/// Selects files. `file_ids_bytes`: concatenated 16-byte UUIDs. Unknown files are ignored, and
|
/// 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
|
/// selecting nothing deselects everything. See `SelectMode`.
|
||||||
/// already holds files.
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select(file_ids_bytes: &[u8], add: bool) -> bool {
|
pub fn select(file_ids_bytes: &[u8], mode: SelectMode) -> bool {
|
||||||
select_elements(
|
select_elements(
|
||||||
file_ids(file_ids_bytes).map(|ids| engine::Selection::File {
|
file_ids(file_ids_bytes).map(|ids| engine::Selection::File {
|
||||||
file_ids: ids.into_iter().collect(),
|
file_ids: ids.into_iter().collect(),
|
||||||
}),
|
}),
|
||||||
add,
|
mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The elements below cross as the id of their file (UUID string) and, like the files, as
|
// The elements below cross as the id of their file (UUID string) and, like the files, as
|
||||||
// concatenated 16-byte UUIDs. What does not exist is ignored. With `add`, the elements are added
|
// concatenated 16-byte UUIDs. What does not exist is ignored. See `SelectMode`: elements are merged
|
||||||
// to the selection when it holds elements of the same kind in the same place (same file, same
|
// with the selection when it holds elements of the same kind in the same place (same file, same
|
||||||
// track for segments), otherwise they replace it.
|
// track for segments), otherwise they replace it.
|
||||||
|
|
||||||
fn select_elements(selection: Option<engine::Selection>, add: bool) -> bool {
|
fn select_elements(selection: Option<engine::Selection>, mode: SelectMode) -> bool {
|
||||||
selection.is_some_and(|selection| execute(Action::Select { selection, add }))
|
selection.is_some_and(|selection| {
|
||||||
|
execute(Action::Select {
|
||||||
|
selection,
|
||||||
|
mode: mode.into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_tracks(file_id: &str, track_ids_bytes: &[u8], add: bool) -> bool {
|
pub fn select_tracks(file_id: &str, track_ids_bytes: &[u8], mode: SelectMode) -> bool {
|
||||||
select_elements(
|
select_elements(
|
||||||
parse_file_id(file_id)
|
parse_file_id(file_id)
|
||||||
.zip(uuid_list(track_ids_bytes))
|
.zip(uuid_list(track_ids_bytes))
|
||||||
@@ -336,12 +360,17 @@ pub fn select_tracks(file_id: &str, track_ids_bytes: &[u8], add: bool) -> bool {
|
|||||||
file_id,
|
file_id,
|
||||||
trk_ids: ids.into_iter().map(engine::TrackId).collect(),
|
trk_ids: ids.into_iter().map(engine::TrackId).collect(),
|
||||||
}),
|
}),
|
||||||
add,
|
mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_segments(file_id: &str, track_id: &str, segment_ids_bytes: &[u8], add: bool) -> bool {
|
pub fn select_segments(
|
||||||
|
file_id: &str,
|
||||||
|
track_id: &str,
|
||||||
|
segment_ids_bytes: &[u8],
|
||||||
|
mode: SelectMode,
|
||||||
|
) -> bool {
|
||||||
select_elements(
|
select_elements(
|
||||||
parse_file_id(file_id)
|
parse_file_id(file_id)
|
||||||
.zip(uuid::Uuid::parse_str(track_id).ok())
|
.zip(uuid::Uuid::parse_str(track_id).ok())
|
||||||
@@ -351,21 +380,21 @@ pub fn select_segments(file_id: &str, track_id: &str, segment_ids_bytes: &[u8],
|
|||||||
trk_id: engine::TrackId(trk_id),
|
trk_id: engine::TrackId(trk_id),
|
||||||
trkseg_ids: ids.into_iter().map(engine::TrackSegmentId).collect(),
|
trkseg_ids: ids.into_iter().map(engine::TrackSegmentId).collect(),
|
||||||
}),
|
}),
|
||||||
add,
|
mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects the node standing for all the waypoints of a file.
|
/// Selects the node standing for all the waypoints of a file.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_waypoint_group(file_id: &str) -> bool {
|
pub fn select_waypoint_group(file_id: &str, mode: SelectMode) -> bool {
|
||||||
select_elements(
|
select_elements(
|
||||||
parse_file_id(file_id).map(|file_id| engine::Selection::Waypoints { file_id }),
|
parse_file_id(file_id).map(|file_id| engine::Selection::Waypoints { file_id }),
|
||||||
false,
|
mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_waypoints(file_id: &str, waypoint_ids_bytes: &[u8], add: bool) -> bool {
|
pub fn select_waypoints(file_id: &str, waypoint_ids_bytes: &[u8], mode: SelectMode) -> bool {
|
||||||
select_elements(
|
select_elements(
|
||||||
parse_file_id(file_id)
|
parse_file_id(file_id)
|
||||||
.zip(uuid_list(waypoint_ids_bytes))
|
.zip(uuid_list(waypoint_ids_bytes))
|
||||||
@@ -373,7 +402,7 @@ pub fn select_waypoints(file_id: &str, waypoint_ids_bytes: &[u8], add: bool) ->
|
|||||||
file_id,
|
file_id,
|
||||||
wpt_ids: ids.into_iter().map(engine::WaypointId).collect(),
|
wpt_ids: ids.into_iter().map(engine::WaypointId).collect(),
|
||||||
}),
|
}),
|
||||||
add,
|
mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -444,19 +444,19 @@ export class GPXLayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const add = e.originalEvent.ctrlKey || e.originalEvent.metaKey;
|
const mode = e.originalEvent.ctrlKey || e.originalEvent.metaKey ? 'add' : 'replace';
|
||||||
const { tracks } = get(this.file).structure;
|
const { tracks } = get(this.file).structure;
|
||||||
const segmentCount = tracks.reduce((count, track) => count + track.segments.length, 0);
|
const segmentCount = tracks.reduce((count, track) => count + track.segments.length, 0);
|
||||||
if (get(treeFileView) && segmentCount > 1) {
|
if (get(treeFileView) && segmentCount > 1) {
|
||||||
// Select inner item
|
// Select inner item
|
||||||
const track = tracks.find((track) => track.id === trackId);
|
const track = tracks.find((track) => track.id === trackId);
|
||||||
if (track && track.segments.length > 1) {
|
if (track && track.segments.length > 1) {
|
||||||
engine.selectSegments(this.fileId, trackId, [segmentId], add);
|
engine.selectSegments(this.fileId, trackId, [segmentId], mode);
|
||||||
} else {
|
} else {
|
||||||
engine.selectTracks(this.fileId, [trackId], add);
|
engine.selectTracks(this.fileId, [trackId], mode);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
engine.select([this.fileId], add);
|
engine.select([this.fileId], mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,7 +515,11 @@ export class GPXLayer {
|
|||||||
} else {
|
} else {
|
||||||
if (get(treeFileView)) {
|
if (get(treeFileView)) {
|
||||||
const add = e.originalEvent.ctrlKey || e.originalEvent.metaKey;
|
const add = e.originalEvent.ctrlKey || e.originalEvent.metaKey;
|
||||||
engine.selectWaypoints(this.fileId, [waypointId], add && this.selected);
|
engine.selectWaypoints(
|
||||||
|
this.fileId,
|
||||||
|
[waypointId],
|
||||||
|
add && this.selected ? 'add' : 'replace'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (!this.selected) {
|
if (!this.selected) {
|
||||||
engine.select([this.fileId]);
|
engine.select([this.fileId]);
|
||||||
|
|||||||
+38
-15
@@ -17,6 +17,22 @@ export type {
|
|||||||
|
|
||||||
type Wasm = typeof import('gpx-rs');
|
type Wasm = typeof import('gpx-rs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How elements combine with the current selection: they `replace` it, are `add`ed to it, or are
|
||||||
|
* `toggle`d (the ones already selected are removed). Adding and toggling only merge with elements
|
||||||
|
* of the same kind in the same place (same file, same track for segments), otherwise the new
|
||||||
|
* elements replace the selection.
|
||||||
|
*/
|
||||||
|
export type SelectMode = 'replace' | 'add' | 'toggle';
|
||||||
|
|
||||||
|
function selectMode(wasm: Wasm, mode: SelectMode) {
|
||||||
|
return {
|
||||||
|
replace: wasm.SelectMode.Replace,
|
||||||
|
add: wasm.SelectMode.Add,
|
||||||
|
toggle: wasm.SelectMode.Toggle,
|
||||||
|
}[mode];
|
||||||
|
}
|
||||||
|
|
||||||
export type SegmentProperties = {
|
export type SegmentProperties = {
|
||||||
fileId: string;
|
fileId: string;
|
||||||
trackId: string;
|
trackId: string;
|
||||||
@@ -155,36 +171,43 @@ class Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects files. With `add`, they are added to the selection if it already holds files.
|
* Selects files. Selecting nothing (or only unknown files) deselects everything. See
|
||||||
* Selecting nothing (or only unknown files) deselects everything.
|
* `SelectMode` for how the files combine with the selection.
|
||||||
*/
|
*/
|
||||||
select(fileIds: string[], add = false) {
|
select(fileIds: string[], mode: SelectMode = 'replace') {
|
||||||
return this.run((w) => w.select(idsToBytes(fileIds), add));
|
return this.run((w) => w.select(idsToBytes(fileIds), selectMode(w, mode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAll() {
|
selectAll() {
|
||||||
return this.select(get(this._order));
|
return this.select(get(this._order));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selecting elements inside a file. With `add`, they are added to the selection when it holds
|
// Selecting elements inside a file. Ids that do not exist are ignored.
|
||||||
// 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) {
|
selectTracks(fileId: string, trackIds: string[], mode: SelectMode = 'replace') {
|
||||||
return this.run((w) => w.select_tracks(fileId, idsToBytes(trackIds), add));
|
return this.run((w) => w.select_tracks(fileId, idsToBytes(trackIds), selectMode(w, mode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectSegments(fileId: string, trackId: string, segmentIds: string[], add = false) {
|
selectSegments(
|
||||||
return this.run((w) => w.select_segments(fileId, trackId, idsToBytes(segmentIds), add));
|
fileId: string,
|
||||||
|
trackId: string,
|
||||||
|
segmentIds: string[],
|
||||||
|
mode: SelectMode = 'replace'
|
||||||
|
) {
|
||||||
|
return this.run((w) =>
|
||||||
|
w.select_segments(fileId, trackId, idsToBytes(segmentIds), selectMode(w, mode))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Selects the node standing for all the waypoints of the file. */
|
/** Selects the node standing for all the waypoints of the file. */
|
||||||
selectWaypointGroup(fileId: string) {
|
selectWaypointGroup(fileId: string, mode: SelectMode = 'replace') {
|
||||||
return this.run((w) => w.select_waypoint_group(fileId));
|
return this.run((w) => w.select_waypoint_group(fileId, selectMode(w, mode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectWaypoints(fileId: string, waypointIds: string[], add = false) {
|
selectWaypoints(fileId: string, waypointIds: string[], mode: SelectMode = 'replace') {
|
||||||
return this.run((w) => w.select_waypoints(fileId, idsToBytes(waypointIds), add));
|
return this.run((w) =>
|
||||||
|
w.select_waypoints(fileId, idsToBytes(waypointIds), selectMode(w, mode))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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