mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-04 12:58:23 +00:00
progress
This commit is contained in:
@@ -3,13 +3,22 @@ use crate::{Command, FileId};
|
||||
/// Everything the frontend can ask the engine to do.
|
||||
///
|
||||
/// Only [`Action::Edit`] changes the files (and records an undo step); the others act on the
|
||||
/// history or on the selection.
|
||||
/// history, on the selection or on the file order.
|
||||
#[derive(Debug)]
|
||||
pub enum Action<'a> {
|
||||
Edit(Command<'a>),
|
||||
Undo,
|
||||
Redo,
|
||||
Select { file_ids: Vec<FileId> },
|
||||
AddSelect { file_ids: Vec<FileId> },
|
||||
Select {
|
||||
file_ids: Vec<FileId>,
|
||||
},
|
||||
AddSelect {
|
||||
file_ids: Vec<FileId>,
|
||||
},
|
||||
SelectAll,
|
||||
/// Moves the files to `index` among the other files, in the given order. Not undoable.
|
||||
Reorder {
|
||||
file_ids: Vec<FileId>,
|
||||
index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ impl Engine {
|
||||
}
|
||||
true
|
||||
}
|
||||
Action::Reorder { file_ids, index } => self.order.move_files(&file_ids, index),
|
||||
Action::SelectAll => {
|
||||
self.selection = Selection::File {
|
||||
file_ids: self.order.0.iter().copied().collect(),
|
||||
@@ -74,6 +75,17 @@ impl Engine {
|
||||
/// Brings everything derived from the files back in line with the current stack entry.
|
||||
fn refresh(&mut self) {
|
||||
let current = self.stack.current();
|
||||
match current {
|
||||
Some(files) => {
|
||||
// files coming back after an undo/redo are added at the end of the order
|
||||
self.order.sync(files);
|
||||
self.selection.retain_existing(files);
|
||||
}
|
||||
None => {
|
||||
self.order.0.clear();
|
||||
self.selection = Selection::Empty;
|
||||
}
|
||||
}
|
||||
self.statistics_cache.update(current);
|
||||
self.statistics_buffer
|
||||
.update(&self.statistics_cache.get(current, &self.selection));
|
||||
@@ -240,6 +252,34 @@ mod tests {
|
||||
assert!(engine.stack.current().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reorder_is_not_undoable_and_returning_files_go_last() {
|
||||
let mut engine = Engine::default();
|
||||
new(&mut engine, "a");
|
||||
let a = engine.order.0[0];
|
||||
new(&mut engine, "b");
|
||||
let b = engine.order.0[1];
|
||||
new(&mut engine, "c");
|
||||
let c = engine.order.0[2];
|
||||
|
||||
assert!(engine.execute(Action::Reorder {
|
||||
file_ids: vec![c],
|
||||
index: 0
|
||||
}));
|
||||
assert_eq!(engine.order.0, vec![c, a, b]);
|
||||
assert!(!engine.execute(Action::Reorder {
|
||||
file_ids: vec![c],
|
||||
index: 0
|
||||
}));
|
||||
|
||||
// undoing the creation of c does not undo the reorder
|
||||
assert!(engine.execute(Action::Undo));
|
||||
assert_eq!(engine.order.0, vec![a, b]);
|
||||
// c comes back at the end
|
||||
assert!(engine.execute(Action::Redo));
|
||||
assert_eq!(engine.order.0, vec![a, b, c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_undo_updates_statistics() {
|
||||
let mut engine = Engine::default();
|
||||
|
||||
@@ -1,4 +1,97 @@
|
||||
use crate::FileId;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
use crate::{FileId, StackEntry as Files};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct FileOrder(pub Vec<FileId>);
|
||||
|
||||
impl FileOrder {
|
||||
/// Moves the given files, as a block and in the given order, so that they start at `index`
|
||||
/// among the other files (clamped to the end). Unknown ids are ignored. Returns whether the
|
||||
/// order changed.
|
||||
pub fn move_files(&mut self, file_ids: &[FileId], index: usize) -> bool {
|
||||
let mut seen = HashSet::new();
|
||||
let moved: Vec<FileId> = file_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|id| self.0.contains(id) && seen.insert(*id))
|
||||
.collect();
|
||||
let mut order: Vec<FileId> = self
|
||||
.0
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|id| !seen.contains(id))
|
||||
.collect();
|
||||
let index = index.min(order.len());
|
||||
order.splice(index..index, moved);
|
||||
let changed = order != self.0;
|
||||
self.0 = order;
|
||||
changed
|
||||
}
|
||||
|
||||
/// Drops the files that do not exist anymore and appends the new ones at the end.
|
||||
pub fn sync(&mut self, files: &Files) {
|
||||
self.0.retain(|id| files.contains_key(id));
|
||||
let known: HashSet<FileId> = self.0.iter().copied().collect();
|
||||
self.0
|
||||
.extend(files.keys().filter(|id| !known.contains(id)).copied());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn ids() -> [FileId; 3] {
|
||||
[FileId::default(), FileId::default(), FileId::default()]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_files_as_block_in_given_order() {
|
||||
let [a, b, c] = ids();
|
||||
let mut order = FileOrder(vec![a, b, c]);
|
||||
assert!(order.move_files(&[c, a], 1));
|
||||
// index counts among the files that are not moved
|
||||
assert_eq!(order.0, vec![b, c, a]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_files_start_and_clamped_end() {
|
||||
let [a, b, c] = ids();
|
||||
let mut order = FileOrder(vec![a, b, c]);
|
||||
assert!(order.move_files(&[c], 0));
|
||||
assert_eq!(order.0, vec![c, a, b]);
|
||||
assert!(order.move_files(&[c], 100));
|
||||
assert_eq!(order.0, vec![a, b, c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_files_ignores_unknown_and_duplicate_ids() {
|
||||
let [a, b, c] = ids();
|
||||
let mut order = FileOrder(vec![a, b, c]);
|
||||
assert!(order.move_files(&[FileId::default(), b, b], 0));
|
||||
assert_eq!(order.0, vec![b, a, c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_files_unchanged() {
|
||||
let [a, b, c] = ids();
|
||||
let mut order = FileOrder(vec![a, b, c]);
|
||||
assert!(!order.move_files(&[], 0));
|
||||
assert!(!order.move_files(&[a, b], 0));
|
||||
assert!(!order.move_files(&[FileId::default()], 0));
|
||||
assert_eq!(order.0, vec![a, b, c]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_drops_missing_and_appends_new_at_the_end() {
|
||||
let [a, b, c] = ids();
|
||||
let mut files = Files::default();
|
||||
for id in [b, c] {
|
||||
files.insert(id, Default::default());
|
||||
}
|
||||
let mut order = FileOrder(vec![a, b]);
|
||||
order.sync(&files);
|
||||
assert_eq!(order.0, vec![b, c]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,13 @@ pub fn delete_all() -> bool {
|
||||
edit(Command::DeleteAll(engine::DeleteAll))
|
||||
}
|
||||
|
||||
/// Moves the files to `index` among the other files, in the given order (not undoable).
|
||||
/// `file_ids_bytes`: concatenated 16-byte UUIDs.
|
||||
#[wasm_bindgen]
|
||||
pub fn reorder(file_ids_bytes: &[u8], index: usize) -> bool {
|
||||
file_ids(file_ids_bytes).is_some_and(|file_ids| execute(Action::Reorder { file_ids, index }))
|
||||
}
|
||||
|
||||
// Edit commands
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
||||
Reference in New Issue
Block a user