mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-27 02:27:18 +00:00
progress
This commit is contained in:
1 parent
2e1ca00afa
commit
1ba5436af3
29 files changed
+3348
-190
No files matched your search
@@ -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,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,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;
|
||||
|
||||
@@ -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,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,6 +1,6 @@
|
||||
//! Changing what is selected.
|
||||
|
||||
use gpx_engine::{api, SelectMode};
|
||||
use gpx_engine::{SelectMode, api};
|
||||
use tauri::State;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user