This commit is contained in:
vcoppe committed 2026-10-14 19:15:02 +02:00
1 parent 2e1ca00afa
commit 1ba5436af3
29 files changed
+3348 -190

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
//! The files of the engine and the file system: picking files and folders, saving, and what the
//! frontend shows of it (see `workspace`).
use std::collections::BTreeMap;
use std::path::PathBuf;
use gpx_engine::api;
use tauri::{AppHandle, Runtime, State};
use tauri_plugin_dialog::{DialogExt, FilePath};
use uuid::Uuid;
use crate::state::AppState;
use crate::workspace::{Unsaved, WorkspaceState};
fn paths(picked: Vec<FilePath>) -> Vec<PathBuf> {
picked
.into_iter()
.filter_map(|path| path.into_path().ok())
.collect()
}
#[tauri::command(async)]
pub fn workspace_state(state: State<AppState>) -> WorkspaceState {
state.core(|core| core.workspace_state())
}
/// Opens the files that the user picks. Nothing happens if they pick none.
#[tauri::command(async)]
pub fn open_files<R: Runtime>(app: AppHandle<R>, state: State<AppState>) -> api::Outcome {
let picked = app
.dialog()
.file()
.add_filter("GPX", &["gpx"])
.blocking_pick_files();
let Some(picked) = picked else {
return api::Outcome::failed_none();
};
let files = paths(picked);
state.core(move |core| {
let outcome = core.workspace.open_files(&mut core.engine, &files, true);
core.keep_workspace();
core.persist();
outcome
})
}
/// Opens the folder that the user picks: its GPX files are opened and it is watched. The folder
/// that was open is closed, `unsaved` says what to do with its files that have unsaved edits.
#[tauri::command(async)]
pub fn open_folder<R: Runtime>(
app: AppHandle<R>,
state: State<AppState>,
unsaved: Option<Unsaved>,
) -> Result<api::Outcome, String> {
let Some(folder) = app
.dialog()
.file()
.blocking_pick_folder()
.and_then(|folder| folder.into_path().ok())
else {
return Ok(api::Outcome::failed_none());
};
state.core(move |core| {
let outcome = core
.workspace
.open_folder(&mut core.engine, &folder, unsaved);
core.keep_workspace();
core.persist();
core.watch();
core.announce(None);
outcome
})
}
/// Closes the folder: it is not watched anymore, and its files are closed. `unsaved` says what to
/// do with the ones that have unsaved edits (they stay without it).
#[tauri::command(async)]
pub fn close_folder(
state: State<AppState>,
unsaved: Option<Unsaved>,
) -> Result<api::Outcome, String> {
state.core(move |core| {
let outcome = core.workspace.close_folder(&mut core.engine, unsaved);
core.keep_workspace();
core.persist();
core.watch();
core.announce(None);
outcome
})
}
/// Writes files to the disk, replacing what is there. The ones that are not on the disk yet go in
/// the open folder, or where the user says if there is none.
#[tauri::command(async)]
pub fn save_files<R: Runtime>(
app: AppHandle<R>,
state: State<AppState>,
file_ids: Vec<Uuid>,
) -> Result<(), String> {
let (ids, unlinked, folder) = {
let ids = file_ids.clone();
state.core(move |core| {
(
ids.clone(),
core.workspace.unlinked(&core.engine, &ids),
core.workspace.folder.clone(),
)
})
};
let mut chosen = BTreeMap::new();
// with a folder open, a new file goes in it: nobody is asked
let to_ask = if folder.is_some() { vec![] } else { unlinked };
for (id, name) in to_ask {
let mut dialog = app
.dialog()
.file()
.add_filter("GPX", &["gpx"])
.set_file_name(format!("{name}.gpx"));
if let Some(folder) = &folder {
dialog = dialog.set_directory(folder);
}
match dialog
.blocking_save_file()
.and_then(|path| path.into_path().ok())
{
Some(path) => {
chosen.insert(id, path);
}
// cancelled: the others are not saved either
None => return Ok(()),
}
}
state.core(move |core| {
let result = core.workspace.save(&core.engine, &ids, &chosen);
core.keep_workspace();
core.announce(None);
result
})
}
+1 -1
View File
@@ -1,7 +1,7 @@
//! Editing the metadata, the style, the structure and the waypoints of the files, and the tools:
//! reversing, cropping, reducing, timestamps, merging, cleaning...
use gpx_engine::{api, CleanType, MergeType, SplitType};
use gpx_engine::{CleanType, MergeType, SplitType, api};
use tauri::State;
use uuid::Uuid;
+1 -1
View File
@@ -1,8 +1,8 @@
//! Creating, loading, deleting, moving, copying and pasting files and their elements, and undoing.
use gpx_engine::api;
use tauri::ipc::{InvokeBody, Request};
use tauri::State;
use tauri::ipc::{InvokeBody, Request};
use uuid::Uuid;
use crate::state::AppState;
+1
View File
@@ -5,6 +5,7 @@
//! The commands that edit the files or change the selection return an `Outcome`.
//! They are `async` (so they run off the main thread), the frontend sends them one after the other.
pub mod disk;
pub mod edit;
pub mod files;
pub mod queries;
+1 -1
View File
@@ -1,7 +1,7 @@
//! The files, their structure and statistics, the selection, the clipboard, and the coordinates of
//! the trackpoints and waypoints (flat `[lng, lat, ...]` little-endian `f64` arrays).
use gpx_engine::{api, ExportOptions};
use gpx_engine::{ExportOptions, api};
use tauri::State;
use uuid::Uuid;
+1 -1
View File
@@ -1,6 +1,6 @@
//! Changing what is selected.
use gpx_engine::{api, SelectMode};
use gpx_engine::{SelectMode, api};
use tauri::State;
use uuid::Uuid;
+17 -15
View File
@@ -1,46 +1,48 @@
//! Keeping the files and the settings.
//!
//! For now nothing is written: the files only live as long as the app, and so do the settings.
//! The file system storage will go here.
//! What the frontend knows of the storage, which is kept by the engine thread (see `state`).
use std::collections::BTreeMap;
use gpx_engine::api;
use serde::Serialize;
use tauri::State;
use crate::state::AppState;
use gpx_engine::api::Outcome;
/// What opening the storage found.
#[derive(Serialize)]
pub struct StorageOpened {
settings: std::collections::BTreeMap<String, String>,
outcome: Outcome,
settings: BTreeMap<String, String>,
/// All the files of the engine as added: the frontend starts with none.
outcome: api::Outcome,
}
/// The files were restored when the app started: the frontend is told about all of them.
#[tauri::command(async)]
pub fn open_storage(state: State<AppState>) -> StorageOpened {
StorageOpened {
settings: state.settings().clone(),
outcome: Outcome::failed_none(),
settings: state.storage().settings(),
outcome: state.read(api::Outcome::everything),
}
}
/// Resolves when what was changed has been written to the storage.
/// Resolves when what was changed has been written to the storage, which is the case when a command
/// returns.
#[tauri::command(async)]
pub fn flush_storage() {}
/// Reads the files again if they were written by someone else.
/// There is no other writer.
#[tauri::command(async)]
pub fn sync_storage() -> Outcome {
Outcome::failed_none()
pub fn sync_storage() -> api::Outcome {
api::Outcome::failed_none()
}
/// Saves a setting, `value` being its JSON.
#[tauri::command(async)]
pub fn set_setting(state: State<AppState>, key: String, value: String) {
state.settings().insert(key, value);
state.put_setting(&key, &value);
}
#[tauri::command(async)]
pub fn delete_setting(state: State<AppState>, key: String) {
state.settings().remove(&key);
state.delete_setting(&key);
}