mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-15 07:07:07 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0e6e3d2eb | ||
|
|
9bd38cd5f8 | ||
|
|
a163e1b897 | ||
|
|
33abec525a | ||
|
|
77c542d9aa | ||
|
|
17b5c1d567 | ||
|
|
2048076fe6 | ||
|
|
d7ff7d645b | ||
|
|
262b590bd4 | ||
|
|
14bf9cddd2 | ||
|
|
b9566160e5 | ||
|
|
0cc9d6f664 | ||
|
|
1942e0b9e1 | ||
|
|
70a24f53b3 | ||
|
|
001246a49e | ||
|
|
8fe7d8d64d |
No files matched your search
@@ -63,5 +63,5 @@ jobs:
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy website/build --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME || 'gpxstudio' }} --branch=${{ github.ref_name }}
|
||||
command: pages deploy website/build --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME || 'gpxstudio' }} --branch=${{ github.ref_name }} --commit-dirty=true
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,124 @@
|
||||
name: Desktop beta
|
||||
on:
|
||||
push:
|
||||
branches: 'dev'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: desktop-beta-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: macos-latest
|
||||
rust_targets: aarch64-apple-darwin,x86_64-apple-darwin,wasm32-unknown-unknown
|
||||
args: --target universal-apple-darwin
|
||||
bundles: target/universal-apple-darwin/release/bundle/dmg/*.dmg
|
||||
# the oldest supported Ubuntu, so that the AppImage runs on older systems too
|
||||
- platform: ubuntu-22.04
|
||||
rust_targets: wasm32-unknown-unknown
|
||||
args: ''
|
||||
bundles: |
|
||||
target/release/bundle/deb/*.deb
|
||||
target/release/bundle/rpm/*.rpm
|
||||
target/release/bundle/appimage/*.AppImage
|
||||
- platform: windows-latest
|
||||
rust_targets: wasm32-unknown-unknown
|
||||
args: ''
|
||||
bundles: |
|
||||
target/release/bundle/msi/*.msi
|
||||
target/release/bundle/nsis/*.exe
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: website/package-lock.json
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.rust_targets }}
|
||||
|
||||
- name: Cache Rust build
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: wasm-pack
|
||||
|
||||
# the desktop app does not use it, but the website depends on its package
|
||||
- name: Build wasm
|
||||
run: wasm-pack build --target bundler
|
||||
working-directory: wasm
|
||||
|
||||
- name: Install dependencies for website
|
||||
run: npm install --prefix website
|
||||
|
||||
# the key would be readable in the app: it is left empty, so only the MapTiler layer fails
|
||||
- name: Create env file
|
||||
run: echo "PUBLIC_MAPTILER_KEY=" >> website/.env
|
||||
|
||||
- name: Build app
|
||||
run: npx --yes @tauri-apps/cli@2 build ${{ matrix.args }}
|
||||
working-directory: tauri
|
||||
|
||||
# after the build, which gives the app the website it embeds
|
||||
- name: Test engine and app
|
||||
run: cargo test -p gpx-engine -p gpx-studio-desktop
|
||||
|
||||
- name: Upload bundles
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundles-${{ matrix.platform }}
|
||||
path: ${{ matrix.bundles }}
|
||||
if-no-files-found: error
|
||||
|
||||
# replaces the beta release only once every platform is built
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
steps:
|
||||
- name: Download bundles
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: bundles-*
|
||||
path: bundles
|
||||
merge-multiple: true
|
||||
|
||||
- name: Publish beta release
|
||||
# the bundles of a platform are in a folder per format (deb, msi…)
|
||||
run: |
|
||||
mapfile -t files < <(find bundles -type f)
|
||||
gh release delete desktop-beta --cleanup-tag --yes || true
|
||||
gh release create desktop-beta "${files[@]}" \
|
||||
--prerelease \
|
||||
--target ${{ github.sha }} \
|
||||
--title "Desktop beta" \
|
||||
--notes "Beta of the desktop app, built from ${{ github.sha }} on the dev branch. The builds are not signed yet: macOS and Windows will warn before opening them."
|
||||
@@ -7,6 +7,15 @@ edition = "2024"
|
||||
[lib]
|
||||
name = "gpx_studio_desktop_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
# the tests are in `tests/app`: on Windows, a test binary of the library or of the app could not
|
||||
# start without the manifest, which `build.rs` only gives to the binaries of `tests`
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[[bin]]
|
||||
name = "gpx-studio-desktop"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
|
||||
[dev-dependencies]
|
||||
tauri = { version = "2", features = ["test"] }
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
fn main() {
|
||||
// On Windows, the dialogs need Common Controls v6, which a binary only gets with a manifest that
|
||||
// asks for it: tauri-build gives one to the app, but not to the test binaries, which would stop
|
||||
// with STATUS_ENTRYPOINT_NOT_FOUND before running any test. This gives them the same manifest
|
||||
// (`-tests` only reaches the binaries of `tests`, hence the tests are there).
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows")
|
||||
&& std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc")
|
||||
{
|
||||
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("windows-test.manifest");
|
||||
println!("cargo:rerun-if-changed={}", manifest.display());
|
||||
println!("cargo:rustc-link-arg-tests=/MANIFEST:EMBED");
|
||||
println!(
|
||||
"cargo:rustc-link-arg-tests=/MANIFESTINPUT:{}",
|
||||
manifest.display()
|
||||
);
|
||||
}
|
||||
tauri_build::build()
|
||||
}
|
||||
+6
-9
@@ -3,20 +3,20 @@
|
||||
|
||||
mod bytes;
|
||||
mod commands;
|
||||
mod state;
|
||||
mod store;
|
||||
mod workspace;
|
||||
pub mod state;
|
||||
pub mod store;
|
||||
pub mod workspace;
|
||||
|
||||
use commands::*;
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
/// The engine of the app: each file has its own history, so that undoing only concerns the
|
||||
/// selected files, and opening or closing a file is not something to undo.
|
||||
pub(crate) type Engine = gpx_engine::Engine<gpx_engine::PerFileHistory>;
|
||||
pub type Engine = gpx_engine::Engine<gpx_engine::PerFileHistory>;
|
||||
|
||||
/// The GPX files among what the system gave to the app (the arguments of the command line, when a
|
||||
/// file is opened with it).
|
||||
fn gpx_paths(arguments: impl IntoIterator<Item = String>) -> Vec<std::path::PathBuf> {
|
||||
pub fn gpx_paths(arguments: impl IntoIterator<Item = String>) -> Vec<std::path::PathBuf> {
|
||||
arguments
|
||||
.into_iter()
|
||||
.map(std::path::PathBuf::from)
|
||||
@@ -108,7 +108,7 @@ fn menu<R: tauri::Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<tauri::me
|
||||
}
|
||||
|
||||
/// Gives the builder the commands that reach the engine (which it has to be given as `AppState`).
|
||||
fn register_commands<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::Builder<R> {
|
||||
pub fn register_commands<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::Builder<R> {
|
||||
builder.invoke_handler(tauri::generate_handler![
|
||||
// selection
|
||||
selection::select,
|
||||
@@ -234,6 +234,3 @@ fn register_commands<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::Bu
|
||||
system::reveal_path,
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -280,13 +280,6 @@ impl AppState {
|
||||
})
|
||||
}
|
||||
|
||||
/// An engine that keeps its data in a directory of its own, and has nobody to tell.
|
||||
#[cfg(test)]
|
||||
pub fn temporary() -> Self {
|
||||
let directory = std::env::temp_dir().join(format!("gpx-studio-{}", uuid::Uuid::new_v4()));
|
||||
Self::open(directory, Arc::new(|_, _| {})).expect("the storage can be opened")
|
||||
}
|
||||
|
||||
/// Opens files that the system gave to the app, see `Core::open_external`.
|
||||
pub fn open_external(&self, paths: Vec<PathBuf>) {
|
||||
self.core(move |core| core.open_external(&paths));
|
||||
|
||||
@@ -217,69 +217,3 @@ impl Storage for FileStorage {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Engine;
|
||||
use gpx_engine::{Action, Command, Load, LoadFiles, Persister};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn directory() -> PathBuf {
|
||||
std::env::temp_dir().join(format!("gpx-studio-store-{}", Uuid::new_v4()))
|
||||
}
|
||||
|
||||
fn engine_with_a_file() -> Engine {
|
||||
let mut engine = Engine::default();
|
||||
let data = std::fs::read("../gpx-rs/data/simple.gpx").unwrap();
|
||||
engine
|
||||
.execute(Action::Edit(Command::LoadFiles(LoadFiles {
|
||||
files: vec![Load {
|
||||
data: &data,
|
||||
name: "simple",
|
||||
}],
|
||||
})))
|
||||
.unwrap();
|
||||
engine
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_the_files_and_the_settings_between_sessions() {
|
||||
let root = directory();
|
||||
let engine = engine_with_a_file();
|
||||
let order = engine.order().to_vec();
|
||||
|
||||
let storage = FileStorage::open(&root).unwrap();
|
||||
pollster::block_on(Persister::default().save(&storage, engine.snapshot())).unwrap();
|
||||
pollster::block_on(storage.put_setting("units", "\"metric\"")).unwrap();
|
||||
|
||||
// another session
|
||||
let storage = FileStorage::open(&root).unwrap();
|
||||
let restored = pollster::block_on(Persister::default().restore(&storage)).unwrap();
|
||||
let mut other = Engine::default();
|
||||
other.restore(restored);
|
||||
assert_eq!(other.order(), order);
|
||||
assert_eq!(
|
||||
other.file_statistics(&order[0]).unwrap().total_distance,
|
||||
engine.file_statistics(&order[0]).unwrap().total_distance
|
||||
);
|
||||
assert_eq!(storage.settings()["units"], "\"metric\"");
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_a_commit_that_is_not_based_on_the_head() {
|
||||
let root = directory();
|
||||
let storage = FileStorage::open(&root).unwrap();
|
||||
let batch = Batch {
|
||||
order: Some(vec![]),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(pollster::block_on(storage.commit(&batch)), Ok(Revision(1)));
|
||||
assert_eq!(
|
||||
pollster::block_on(storage.commit(&batch)),
|
||||
Err(CommitError::Stale { head: Revision(1) })
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
}
|
||||
+2
-812
@@ -148,7 +148,7 @@ pub struct Entry {
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
fn is_gpx(path: &Path) -> bool {
|
||||
pub fn is_gpx(path: &Path) -> bool {
|
||||
path.extension()
|
||||
.and_then(|extension| extension.to_str())
|
||||
.is_some_and(|extension| extension.eq_ignore_ascii_case("gpx"))
|
||||
@@ -156,7 +156,7 @@ fn is_gpx(path: &Path) -> bool {
|
||||
|
||||
/// Compares names the way people read them: the numbers in them by their value ("track 2" before
|
||||
/// "track 10"), the rest regardless of case.
|
||||
fn natural_cmp(a: &str, b: &str) -> Ordering {
|
||||
pub fn natural_cmp(a: &str, b: &str) -> Ordering {
|
||||
let (mut a, mut b) = (a.chars().peekable(), b.chars().peekable());
|
||||
loop {
|
||||
let order = match (a.peek().copied(), b.peek().copied()) {
|
||||
@@ -1040,813 +1040,3 @@ impl Workspace {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpx_engine::SelectMode;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn gpx(name: &str) -> String {
|
||||
format!(
|
||||
r#"<?xml version="1.0"?><gpx version="1.1" creator="test" xmlns="http://www.topografix.com/GPX/1/1"><metadata><name>{name}</name></metadata><trk><name>{name}</name><trkseg><trkpt lat="50.0" lon="4.0"><ele>10</ele></trkpt><trkpt lat="50.1" lon="4.1"><ele>20</ele></trkpt></trkseg></trk></gpx>"#
|
||||
)
|
||||
}
|
||||
|
||||
/// The GPX files of a folder and of the folders in it.
|
||||
pub(super) fn gpx_files(folder: &Path) -> Vec<PathBuf> {
|
||||
let mut found = vec![];
|
||||
for entry in fs::read_dir(folder).unwrap().flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
found.extend(gpx_files(&path));
|
||||
} else if is_gpx(&path) {
|
||||
found.push(path);
|
||||
}
|
||||
}
|
||||
found.sort();
|
||||
found
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
folder: PathBuf,
|
||||
engine: Engine,
|
||||
workspace: Workspace,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let folder =
|
||||
canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&folder).unwrap();
|
||||
Self {
|
||||
folder,
|
||||
engine: Engine::default(),
|
||||
workspace: Workspace::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&self, name: &str, title: &str) -> PathBuf {
|
||||
let path = self.folder.join(name);
|
||||
fs::write(&path, gpx(title)).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
/// Opens the folder, and all the files that are in it.
|
||||
fn open_folder(&mut self) {
|
||||
let folder = self.folder.clone();
|
||||
self.workspace.open_folder(&mut self.engine, &folder, false);
|
||||
let files = gpx_files(&folder);
|
||||
self.workspace.open_files(&mut self.engine, &files, false);
|
||||
}
|
||||
|
||||
fn reconcile(&mut self, changed: Option<&[PathBuf]>) -> Option<api::Outcome> {
|
||||
self.workspace.reconcile(&mut self.engine, changed)
|
||||
}
|
||||
|
||||
fn save(&mut self, ids: &[Uuid]) {
|
||||
self.workspace
|
||||
.save(&mut self.engine, ids, &BTreeMap::new(), None)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn ids(&self) -> Vec<Uuid> {
|
||||
self.engine.order().iter().map(|id| id.0).collect()
|
||||
}
|
||||
|
||||
fn id_named(&self, name: &str) -> Uuid {
|
||||
self.ids()
|
||||
.into_iter()
|
||||
.find(|id| self.name(*id) == name)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn name(&self, id: Uuid) -> String {
|
||||
self.engine
|
||||
.file_structure(&FileId(id))
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// What the file holds: the name of its track (its own name is the one of the file).
|
||||
fn content(&self, id: Uuid) -> String {
|
||||
self.engine.file_structure(&FileId(id)).unwrap().tracks[0]
|
||||
.info
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn rename(&mut self, id: Uuid, name: &str) {
|
||||
api::select(&mut self.engine, &[id], SelectMode::Replace);
|
||||
assert!(!api::metadata(&mut self.engine, name, "").has_error());
|
||||
}
|
||||
|
||||
fn status(&self, id: Uuid) -> FileStatus {
|
||||
self.workspace
|
||||
.state(&self.engine)
|
||||
.files
|
||||
.remove(&id.to_string())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.folder);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_opened_file_is_clean_until_it_is_edited_and_clean_again_once_saved() {
|
||||
let mut fx = Fixture::new();
|
||||
let path = fx.write("a.gpx", "a");
|
||||
fx.workspace
|
||||
.open_files(&mut fx.engine, std::slice::from_ref(&path), true);
|
||||
let id = fx.ids()[0];
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert_eq!(fx.status(id).path.as_deref(), path.to_str());
|
||||
|
||||
fx.rename(id, "renamed");
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
// nothing is written before it is saved
|
||||
assert!(fs::read_to_string(&path).unwrap().contains(">a<"));
|
||||
|
||||
fx.save(&[id]);
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert!(fs::read_to_string(&path).unwrap().contains(">renamed<"));
|
||||
|
||||
// going back before the save is an edit again
|
||||
api::select(&mut fx.engine, &[id], SelectMode::Replace);
|
||||
api::undo(&mut fx.engine);
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
api::redo(&mut fx.engine);
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_is_not_on_the_disk_is_unsaved_until_it_is_given_a_place() {
|
||||
let mut fx = Fixture::new();
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
let id = fx.ids()[0];
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert_eq!(
|
||||
fx.workspace.unlinked(&fx.engine, &[id]),
|
||||
[(id, "fresh".to_string())]
|
||||
);
|
||||
// nowhere to put it without being told
|
||||
fx.save(&[id]);
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
|
||||
let path = fx.folder.join("fresh.gpx");
|
||||
let paths = BTreeMap::from([(id, path.clone())]);
|
||||
fx.workspace
|
||||
.save(&mut fx.engine, &[id], &paths, None)
|
||||
.unwrap();
|
||||
assert!(path.exists());
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert!(fx.workspace.unlinked(&fx.engine, &[id]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn several_files_without_a_place_go_in_a_folder_with_names_that_are_free() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("fresh.gpx", "already there");
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
api::new_file(&mut fx.engine, "a/b: c", None, None, None, false);
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
let ids = fx.ids();
|
||||
let folder = fx.folder.clone();
|
||||
fx.workspace
|
||||
.save(&mut fx.engine, &ids, &BTreeMap::new(), Some(&folder))
|
||||
.unwrap();
|
||||
for name in ["fresh (2).gpx", "fresh (3).gpx", "a_b_ c.gpx"] {
|
||||
assert!(fx.folder.join(name).exists(), "{name}");
|
||||
}
|
||||
assert!(ids.iter().all(|id| !fx.workspace.is_dirty(&fx.engine, *id)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_this_app_writes_is_not_a_change_of_the_disk() {
|
||||
let mut fx = Fixture::new();
|
||||
let path = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
api::select(&mut fx.engine, &[id], SelectMode::Replace);
|
||||
assert!(!api::style(&mut fx.engine, Some("#ff0000"), None, None).has_error());
|
||||
fx.save(&[id]);
|
||||
assert!(fx.reconcile(Some(&[path])).is_none());
|
||||
assert!(!fx.status(id).replaced && !fx.status(id).dirty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_folders_of_the_files_outside_the_open_folder_are_watched_too() {
|
||||
let mut fx = Fixture::new();
|
||||
let inside = fx.write("in.gpx", "in");
|
||||
let elsewhere = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&elsewhere).unwrap();
|
||||
let (one, two) = (elsewhere.join("one.gpx"), elsewhere.join("two.gpx"));
|
||||
fs::write(&one, gpx("one")).unwrap();
|
||||
fs::write(&two, gpx("two")).unwrap();
|
||||
let folder = fx.folder.clone();
|
||||
|
||||
// no folder: only the folder of the files
|
||||
fx.workspace.open_files(&mut fx.engine, &[one, two], false);
|
||||
assert_eq!(fx.workspace.watched(), [(elsewhere.clone(), false)]);
|
||||
|
||||
// the open folder as a whole, and the file in it needs nothing more
|
||||
fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
fx.workspace.open_files(&mut fx.engine, &[inside], false);
|
||||
assert_eq!(
|
||||
fx.workspace.watched(),
|
||||
[(folder, true), (elsewhere.clone(), false)]
|
||||
);
|
||||
|
||||
// closing the files stops it
|
||||
let ids = fx.ids();
|
||||
fx.workspace.close_files(&mut fx.engine, &ids, true);
|
||||
assert_eq!(fx.workspace.watched(), [(fx.folder.clone(), true)]);
|
||||
fs::remove_dir_all(elsewhere).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_a_folder_does_not_open_its_files() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
let folder = fx.folder.clone();
|
||||
let outcome = fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
assert!(!outcome.changed);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert_eq!(fx.workspace.folder.as_deref(), Some(folder.as_path()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folders_are_made_renamed_and_moved_and_the_open_files_follow() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
let root = fx.folder.clone();
|
||||
let path_of = |fx: &Fixture| PathBuf::from(fx.status(id).path.unwrap());
|
||||
|
||||
let first = fx.workspace.create_folder(&root, "New folder").unwrap();
|
||||
let second = fx.workspace.create_folder(&root, "New folder").unwrap();
|
||||
assert_eq!(first, root.join("New folder"));
|
||||
assert_eq!(second, root.join("New folder 2"));
|
||||
|
||||
fx.workspace.move_paths(&[a], &first).unwrap();
|
||||
assert_eq!(path_of(&fx), first.join("a.gpx"));
|
||||
assert!(first.join("a.gpx").exists() && !fx.status(id).dirty);
|
||||
|
||||
let trips = fx.workspace.rename_path(&first, "Trips").unwrap();
|
||||
assert_eq!(path_of(&fx), root.join("Trips/a.gpx"));
|
||||
// a folder does not go into itself, nor onto something else
|
||||
assert!(
|
||||
fx.workspace
|
||||
.move_paths(std::slice::from_ref(&trips), &trips)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
fx.workspace.rename_path(&trips, "New folder 2"),
|
||||
Err(NAME_TAKEN.to_owned())
|
||||
);
|
||||
fx.workspace.move_paths(&[trips], &second).unwrap();
|
||||
assert_eq!(path_of(&fx), second.join("Trips/a.gpx"));
|
||||
|
||||
// a file keeps its extension
|
||||
let closed = fx.write("c.gpx", "c");
|
||||
assert_eq!(
|
||||
fx.workspace.rename_path(&closed, "d").unwrap(),
|
||||
root.join("d.gpx")
|
||||
);
|
||||
// the open folder and what is not in it are left alone
|
||||
assert!(fx.workspace.rename_path(&root, "x").is_err());
|
||||
let elsewhere = canonical(&std::env::temp_dir());
|
||||
assert!(fx.workspace.rename_path(&elsewhere, "x").is_err());
|
||||
assert!(fx.workspace.create_folder(&elsewhere, "x").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleted_files_are_closed_unless_they_have_edits() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
let b = fx.write("b.gpx", "b");
|
||||
fx.open_folder();
|
||||
let (id_a, id_b) = (fx.id_named("a"), fx.id_named("b"));
|
||||
api::select(&mut fx.engine, &[id_b], SelectMode::Replace);
|
||||
assert!(!api::style(&mut fx.engine, Some("#ff0000"), None, None).has_error());
|
||||
|
||||
let outcome = fx
|
||||
.workspace
|
||||
.delete_paths(&mut fx.engine, &[a.clone(), b.clone()], |paths| {
|
||||
paths.iter().for_each(|path| fs::remove_file(path).unwrap());
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert!(outcome.changed);
|
||||
assert_eq!(fx.ids(), [id_b]);
|
||||
assert!(!fx.ids().contains(&id_a));
|
||||
let status = fx.status(id_b);
|
||||
assert!(status.dirty && status.missing);
|
||||
assert!(!a.exists() && !b.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_tree_of_the_folder_shows_folders_first_and_only_gpx_files() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("b.gpx", "b");
|
||||
fx.write("A.GPX", "a");
|
||||
fx.write("b 10.gpx", "b 10");
|
||||
fx.write("b 2.gpx", "b 2");
|
||||
fx.write("notes.txt", "not a track");
|
||||
fx.write(".hidden.gpx", "hidden");
|
||||
fs::create_dir(fx.folder.join("zoo")).unwrap();
|
||||
fs::create_dir(fx.folder.join("Alps")).unwrap();
|
||||
fs::create_dir(fx.folder.join(".git")).unwrap();
|
||||
fx.write("zoo/c.gpx", "c");
|
||||
let folder = fx.folder.clone();
|
||||
fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
|
||||
let names = |entries: Vec<Entry>| -> Vec<(String, bool)> {
|
||||
entries.into_iter().map(|e| (e.name, e.is_dir)).collect()
|
||||
};
|
||||
assert_eq!(
|
||||
names(fx.workspace.list_folder(&folder).unwrap()),
|
||||
[
|
||||
("Alps".to_string(), true),
|
||||
("zoo".to_string(), true),
|
||||
("A.GPX".to_string(), false),
|
||||
("b.gpx".to_string(), false),
|
||||
("b 2.gpx".to_string(), false),
|
||||
("b 10.gpx".to_string(), false),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
names(fx.workspace.list_folder(&folder.join("zoo")).unwrap()),
|
||||
[("c.gpx".to_string(), false)]
|
||||
);
|
||||
// only what is in the open folder can be listed
|
||||
let elsewhere = canonical(&std::env::temp_dir());
|
||||
assert!(fx.workspace.list_folder(&elsewhere).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_are_sorted_by_the_numbers_they_hold() {
|
||||
let mut names = vec![
|
||||
"track 10",
|
||||
"track 2",
|
||||
"Track 1",
|
||||
"track",
|
||||
"track 02b",
|
||||
"track 2a",
|
||||
"b",
|
||||
"A",
|
||||
];
|
||||
names.sort_by(|a, b| natural_cmp(a, b).then_with(|| a.cmp(b)));
|
||||
assert_eq!(
|
||||
names,
|
||||
[
|
||||
"A",
|
||||
"b",
|
||||
"track",
|
||||
"Track 1",
|
||||
"track 2",
|
||||
"track 2a",
|
||||
"track 02b",
|
||||
"track 10",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_without_edits_is_read_again_when_the_disk_changes() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
|
||||
fx.write("a.gpx", "theirs");
|
||||
let outcome = fx.reconcile(Some(std::slice::from_ref(&a))).unwrap();
|
||||
assert!(outcome.changed);
|
||||
// the same file, now the one on the disk
|
||||
assert_eq!(fx.ids(), [id]);
|
||||
assert_eq!(fx.content(id), "theirs");
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.replaced);
|
||||
// looking again changes nothing
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_with_edits_is_flagged_when_the_disk_changes_and_not_read_again() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "mine");
|
||||
|
||||
fx.write("a.gpx", "theirs");
|
||||
let outcome = fx.reconcile(Some(std::slice::from_ref(&a))).unwrap();
|
||||
assert!(!outcome.changed);
|
||||
assert_eq!(fx.name(id), "mine");
|
||||
let status = fx.status(id);
|
||||
assert!(status.dirty && status.replaced && !status.missing);
|
||||
// looking again changes nothing
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_none());
|
||||
|
||||
// the disk goes back to what was read: only the edits are left
|
||||
fx.write("a.gpx", "a");
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_some());
|
||||
let status = fx.status(id);
|
||||
assert!(status.dirty && !status.replaced);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_user_can_take_the_disk_instead_of_the_edits() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "mine");
|
||||
fx.write("a.gpx", "theirs");
|
||||
fx.reconcile(None);
|
||||
assert!(fx.status(id).replaced);
|
||||
|
||||
fx.workspace.reload(&mut fx.engine, &[id]).unwrap();
|
||||
assert_eq!(fx.content(id), "theirs");
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.replaced);
|
||||
// and it is one undo step
|
||||
api::select(&mut fx.engine, &[id], SelectMode::Replace);
|
||||
api::undo(&mut fx.engine);
|
||||
assert_eq!(fx.content(id), "mine");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_overwrites_what_replaced_the_file() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "mine");
|
||||
fx.write("a.gpx", "theirs");
|
||||
fx.reconcile(None);
|
||||
assert!(fx.status(id).replaced);
|
||||
|
||||
fx.save(&[id]);
|
||||
assert!(fs::read_to_string(a).unwrap().contains(">mine<"));
|
||||
assert!(!fx.status(id).replaced && !fx.status(id).dirty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_is_gone_from_the_disk_stays_open_and_dirty_until_it_is_saved_again() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
|
||||
fs::remove_file(&a).unwrap();
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_some());
|
||||
assert_eq!(fx.ids(), [id]);
|
||||
let status = fx.status(id);
|
||||
assert!(status.dirty && status.missing && status.path.is_some());
|
||||
|
||||
// saving puts it back
|
||||
fx.save(&[id]);
|
||||
assert!(a.exists());
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_comes_back_different_is_read_again() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fs::remove_file(&a).unwrap();
|
||||
fx.reconcile(None);
|
||||
assert!(fx.status(id).missing);
|
||||
|
||||
fx.write("a.gpx", "back");
|
||||
fx.reconcile(None);
|
||||
assert_eq!(fx.content(id), "back");
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_changed_while_the_app_was_closed_is_found_when_it_starts() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.write("a.gpx", "changed");
|
||||
assert!(fx.reconcile(None).is_some());
|
||||
assert_eq!(fx.content(id), "changed");
|
||||
fs::remove_file(a).unwrap();
|
||||
assert!(fx.reconcile(None).is_some());
|
||||
assert!(fx.status(id).missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_files_made_from_a_file_of_the_folder_are_saved_next_to_it() {
|
||||
let mut fx = Fixture::new();
|
||||
fs::create_dir(fx.folder.join("sub")).unwrap();
|
||||
fx.write("sub/b.gpx", "b");
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let b = fx.id_named("b");
|
||||
let sub = fx.folder.join("sub");
|
||||
assert_eq!(fx.workspace.source_dir(&[b]), Some(canonical(&sub)));
|
||||
|
||||
let before = fx.ids();
|
||||
api::select(&mut fx.engine, &[b], SelectMode::Replace);
|
||||
let outcome = api::duplicate(&mut fx.engine);
|
||||
let copy = fx
|
||||
.ids()
|
||||
.into_iter()
|
||||
.find(|id| !before.contains(id))
|
||||
.unwrap();
|
||||
fx.workspace.inherit(outcome.added(), &canonical(&sub));
|
||||
assert_eq!(fx.status(copy).folder.as_deref(), canonical(&sub).to_str());
|
||||
assert!(fx.status(copy).path.is_none() && fx.status(copy).dirty);
|
||||
// nobody needs to be asked where it goes
|
||||
assert!(fx.workspace.unlinked(&fx.engine, &[copy]).is_empty());
|
||||
|
||||
fx.save(&[copy]);
|
||||
assert!(sub.join("b (2).gpx").exists());
|
||||
let status = fx.status(copy);
|
||||
assert!(!status.dirty && status.folder.is_none());
|
||||
assert_eq!(
|
||||
status.path.as_deref(),
|
||||
canonical(&sub.join("b (2).gpx")).to_str()
|
||||
);
|
||||
// the copy of the copy is next to it as well
|
||||
assert_eq!(fx.workspace.source_dir(&[copy]), Some(canonical(&sub)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_outside_the_open_folder_hands_nothing_down() {
|
||||
let mut fx = Fixture::new();
|
||||
let elsewhere = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&elsewhere).unwrap();
|
||||
let outside = elsewhere.join("o.gpx");
|
||||
fs::write(&outside, gpx("o")).unwrap();
|
||||
let folder = fx.folder.clone();
|
||||
fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
fx.workspace
|
||||
.open_files(&mut fx.engine, std::slice::from_ref(&outside), true);
|
||||
let id = fx.ids()[0];
|
||||
assert_eq!(fx.workspace.source_dir(&[id]), None);
|
||||
// a new file with no source is not linked to a folder by `source_dir`: the caller falls back
|
||||
fs::remove_dir_all(elsewhere).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_files_asks_what_to_do_with_the_unsaved_ones() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.write("b.gpx", "b");
|
||||
fx.open_folder();
|
||||
let (a, b) = (fx.id_named("a"), fx.id_named("b"));
|
||||
fx.rename(a, "edited");
|
||||
assert_eq!(fx.workspace.unsaved(&fx.engine, &[a, b]), [a]);
|
||||
|
||||
// without a choice what has edits stays
|
||||
let outcome = fx.workspace.close_files(&mut fx.engine, &[a, b], false);
|
||||
assert!(outcome.changed);
|
||||
assert_eq!(fx.ids(), [a]);
|
||||
|
||||
// saved first
|
||||
fx.save(&[a]);
|
||||
fx.workspace.close_files(&mut fx.engine, &[a], false);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert!(
|
||||
fs::read_to_string(fx.folder.join("a.gpx"))
|
||||
.unwrap()
|
||||
.contains("edited")
|
||||
);
|
||||
assert!(fx.workspace.links.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_files_can_discard_their_edits() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let a = fx.ids()[0];
|
||||
fx.rename(a, "edited");
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
let fresh = fx.id_named("fresh");
|
||||
fx.workspace.close_files(&mut fx.engine, &[a, fresh], true);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert!(
|
||||
fs::read_to_string(fx.folder.join("a.gpx"))
|
||||
.unwrap()
|
||||
.contains(">a<")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_the_folder_closes_every_file() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
assert_eq!(fx.ids().len(), 2);
|
||||
|
||||
// the new file has nowhere to go: it has to be discarded
|
||||
let outcome = fx.workspace.close_folder(&mut fx.engine, false);
|
||||
assert!(outcome.changed);
|
||||
assert_eq!(fx.ids().len(), 1);
|
||||
assert!(fx.workspace.folder.is_none());
|
||||
|
||||
let outcome = fx.workspace.close_folder(&mut fx.engine, true);
|
||||
assert!(outcome.changed);
|
||||
assert!(fx.ids().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_another_folder_closes_the_first_with_all_its_files() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let a = fx.ids()[0];
|
||||
fx.rename(a, "edited");
|
||||
let other = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&other).unwrap();
|
||||
|
||||
// the edits stay, nothing is lost without a choice
|
||||
fx.workspace.open_folder(&mut fx.engine, &other, false);
|
||||
assert_eq!(fx.ids(), [a]);
|
||||
assert_eq!(fx.workspace.folder.as_deref(), Some(other.as_path()));
|
||||
|
||||
let first = fx.folder.clone();
|
||||
fx.workspace.open_folder(&mut fx.engine, &first, true);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert!(
|
||||
fs::read_to_string(fx.folder.join("a.gpx"))
|
||||
.unwrap()
|
||||
.contains(">a<")
|
||||
);
|
||||
fs::remove_dir_all(other).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_the_same_folder_again_changes_nothing() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let folder = fx.folder.clone();
|
||||
let outcome = fx.workspace.open_folder(&mut fx.engine, &folder, true);
|
||||
assert!(!outcome.changed);
|
||||
assert_eq!(fx.ids().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_save_stops_at_the_file_and_leaves_it_dirty() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "edited");
|
||||
// the place of the file is taken by a folder
|
||||
fs::remove_file(fx.folder.join("a.gpx")).unwrap();
|
||||
fs::create_dir(fx.folder.join("a.gpx")).unwrap();
|
||||
let result = fx
|
||||
.workspace
|
||||
.save(&mut fx.engine, &[id], &BTreeMap::new(), None);
|
||||
assert!(result.is_err());
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_as_gives_the_file_another_place() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.write("b.gpx", "b");
|
||||
fx.open_folder();
|
||||
let id = fx.id_named("a");
|
||||
let other = fx.id_named("b");
|
||||
fx.rename(id, "edited");
|
||||
|
||||
let target = fx.folder.join("copy.gpx");
|
||||
fx.workspace.save_as(&mut fx.engine, id, &target).unwrap();
|
||||
assert!(fs::read_to_string(&target).unwrap().contains("edited"));
|
||||
assert!(fs::read_to_string(a).unwrap().contains(">a<"));
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty);
|
||||
assert_eq!(status.path.as_deref(), canonical(&target).to_str());
|
||||
|
||||
// the place of another open file is not available
|
||||
let taken = fx.folder.join("b.gpx");
|
||||
assert!(fx.workspace.save_as(&mut fx.engine, id, &taken).is_err());
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, other));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod restart_tests {
|
||||
use gpx_engine::Persister;
|
||||
|
||||
use super::*;
|
||||
use crate::store::FileStorage;
|
||||
|
||||
/// What the app does when it starts again: the engine and the links are read from what was
|
||||
/// kept.
|
||||
fn restart(engine: &Engine, workspace: &Workspace, root: &Path) -> (Engine, Workspace) {
|
||||
let storage = FileStorage::open(root).unwrap();
|
||||
pollster::block_on(Persister::default().save(&storage, engine.snapshot())).unwrap();
|
||||
let mut restarted = Engine::default();
|
||||
let restored = pollster::block_on(Persister::default().restore(&storage)).unwrap();
|
||||
restarted.restore(restored);
|
||||
let links = serde_json::to_string(workspace).unwrap();
|
||||
(restarted, serde_json::from_str(&links).unwrap())
|
||||
}
|
||||
|
||||
fn setup() -> (PathBuf, Engine, Workspace, Vec<Uuid>) {
|
||||
let root = std::env::temp_dir().join(format!("gpx-restart-{}", Uuid::new_v4()));
|
||||
let folder = root.join("tracks");
|
||||
fs::create_dir_all(&folder).unwrap();
|
||||
for name in ["with_tracks", "with_time", "with_waypoint"] {
|
||||
let _ = fs::copy(
|
||||
format!("../gpx-rs/data/{name}.gpx"),
|
||||
folder.join(format!("{name}.gpx")),
|
||||
);
|
||||
}
|
||||
let mut engine = Engine::default();
|
||||
let mut workspace = Workspace::default();
|
||||
workspace.open_folder(&mut engine, &folder, false);
|
||||
workspace.open_files(&mut engine, &tests::gpx_files(&folder), false);
|
||||
let ids = engine.order().iter().map(|id| id.0).collect();
|
||||
(root, engine, workspace, ids)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_that_were_not_edited_are_not_unsaved_after_a_restart() {
|
||||
let (root, engine, workspace, ids) = setup();
|
||||
assert!(ids.len() >= 2);
|
||||
let (restarted, mut workspace) = restart(&engine, &workspace, &root.join("store"));
|
||||
// the files are the same, but the engine knows their contents by new identities
|
||||
assert!(ids.iter().any(|id| workspace.is_dirty(&restarted, *id)));
|
||||
workspace.verify(&restarted);
|
||||
assert!(ids.iter().all(|id| !workspace.is_dirty(&restarted, *id)));
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_that_were_edited_are_still_unsaved_after_a_restart() {
|
||||
let (root, mut engine, workspace, ids) = setup();
|
||||
api::select(&mut engine, &[ids[0]], gpx_engine::SelectMode::Replace);
|
||||
api::metadata(&mut engine, "edited", "");
|
||||
assert!(workspace.is_dirty(&engine, ids[0]));
|
||||
let (restarted, mut workspace) = restart(&engine, &workspace, &root.join("store"));
|
||||
workspace.verify(&restarted);
|
||||
assert!(workspace.is_dirty(&restarted, ids[0]));
|
||||
assert!(
|
||||
ids[1..]
|
||||
.iter()
|
||||
.all(|id| !workspace.is_dirty(&restarted, *id))
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn links_made_before_the_content_was_kept_are_checked_against_the_disk() {
|
||||
let (root, mut engine, mut workspace, ids) = setup();
|
||||
api::select(&mut engine, &[ids[0]], gpx_engine::SelectMode::Replace);
|
||||
api::metadata(&mut engine, "edited", "");
|
||||
for link in workspace.links.values_mut() {
|
||||
link.export_hash = 0;
|
||||
}
|
||||
let (restarted, mut workspace) = restart(&engine, &workspace, &root.join("store"));
|
||||
workspace.verify(&restarted);
|
||||
assert!(workspace.is_dirty(&restarted, ids[0]));
|
||||
assert!(
|
||||
ids[1..]
|
||||
.iter()
|
||||
.all(|id| !workspace.is_dirty(&restarted, *id))
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_folders_of_new_files_and_the_old_flag_name_survive_a_restart() {
|
||||
let (root, engine, mut workspace, ids) = setup();
|
||||
let folder = workspace.folder.clone().unwrap();
|
||||
let fresh = Uuid::new_v4();
|
||||
workspace.inherit(&[fresh.to_string()], &folder);
|
||||
workspace.links.get_mut(&ids[0]).unwrap().replaced = true;
|
||||
let json = serde_json::to_string(&workspace).unwrap();
|
||||
// links written by the previous version had `conflict`, which is the same thing
|
||||
let json = json.replace("\"replaced\"", "\"conflict\"");
|
||||
assert!(json.contains("\"conflict\":true"));
|
||||
let read: Workspace = serde_json::from_str(&json).unwrap();
|
||||
assert!(read.links[&ids[0]].replaced);
|
||||
assert_eq!(read.dirs.get(&fresh), Some(&folder));
|
||||
let _ = engine;
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,23 @@ use tauri::test::{INVOKE_KEY, get_ipc_response, mock_builder, mock_context, noop
|
||||
use tauri::webview::InvokeRequest;
|
||||
use tauri::{WebviewWindow, ipc::CallbackFn, ipc::InvokeBody, ipc::InvokeResponseBody};
|
||||
|
||||
/// The origin of the pages of the app, the one Tauri allows the commands from: Windows has no
|
||||
/// custom protocols, the pages come from `http://tauri.localhost` there.
|
||||
const ORIGIN: &str = if cfg!(windows) {
|
||||
"http://tauri.localhost"
|
||||
} else {
|
||||
"tauri://localhost"
|
||||
};
|
||||
|
||||
struct App(WebviewWindow<tauri::test::MockRuntime>);
|
||||
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
let app =
|
||||
crate::register_commands(mock_builder().manage(crate::state::AppState::temporary()))
|
||||
.build(mock_context(noop_assets()))
|
||||
.unwrap();
|
||||
let app = gpx_studio_desktop_lib::register_commands(
|
||||
mock_builder().manage(crate::temporary_state()),
|
||||
)
|
||||
.build(mock_context(noop_assets()))
|
||||
.unwrap();
|
||||
let window = tauri::WebviewWindowBuilder::new(&app, "main", Default::default())
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -34,7 +43,7 @@ impl App {
|
||||
cmd: cmd.into(),
|
||||
callback: CallbackFn(0),
|
||||
error: CallbackFn(1),
|
||||
url: "tauri://localhost".parse().unwrap(),
|
||||
url: ORIGIN.parse().unwrap(),
|
||||
body,
|
||||
headers,
|
||||
invoke_key: INVOKE_KEY.to_string(),
|
||||
@@ -56,7 +65,7 @@ impl App {
|
||||
cmd: cmd.into(),
|
||||
callback: CallbackFn(0),
|
||||
error: CallbackFn(1),
|
||||
url: "tauri://localhost".parse().unwrap(),
|
||||
url: ORIGIN.parse().unwrap(),
|
||||
body: InvokeBody::Json(args),
|
||||
headers: Default::default(),
|
||||
invoke_key: INVOKE_KEY.to_string(),
|
||||
@@ -311,8 +320,8 @@ fn keeps_settings_for_the_session() {
|
||||
|
||||
#[test]
|
||||
fn the_files_are_still_there_in_the_next_session() {
|
||||
use crate::state::AppState;
|
||||
use gpx_engine::api;
|
||||
use gpx_studio_desktop_lib::state::AppState;
|
||||
|
||||
let directory =
|
||||
std::env::temp_dir().join(format!("gpx-studio-session-{}", uuid::Uuid::new_v4()));
|
||||
@@ -347,14 +356,14 @@ fn only_gpx_files_that_exist_are_taken_from_what_the_system_gives() {
|
||||
text.to_string_lossy().into_owned(),
|
||||
directory.join("missing.gpx").to_string_lossy().into_owned(),
|
||||
];
|
||||
assert_eq!(crate::gpx_paths(arguments), [track]);
|
||||
assert_eq!(gpx_studio_desktop_lib::gpx_paths(arguments), [track]);
|
||||
std::fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_opened_with_the_app_is_opened_selected_and_announced() {
|
||||
use crate::state::AppState;
|
||||
use gpx_engine::api;
|
||||
use gpx_studio_desktop_lib::state::AppState;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
let directory = std::env::temp_dir().join(format!("gpx-studio-open-{}", uuid::Uuid::new_v4()));
|
||||
@@ -441,7 +450,7 @@ fn a_file_outside_the_open_folder_is_read_again_when_the_disk_changes() {
|
||||
|
||||
let (events, received) = mpsc::channel();
|
||||
let directory = std::env::temp_dir().join(format!("gpx-store-{}", uuid::Uuid::new_v4()));
|
||||
let state = crate::state::AppState::open(
|
||||
let state = gpx_studio_desktop_lib::state::AppState::open(
|
||||
directory.clone(),
|
||||
std::sync::Arc::new(move |name, _| {
|
||||
let _ = events.send(name.to_string());
|
||||
@@ -461,8 +470,8 @@ fn a_file_outside_the_open_folder_is_read_again_when_the_disk_changes() {
|
||||
});
|
||||
let id = uuid::Uuid::parse_str(&id).unwrap();
|
||||
// the name comes from the path, which does not change: the content does
|
||||
let signature = |state: &crate::state::AppState| {
|
||||
state.core(move |core| crate::workspace::signature(&core.engine, id))
|
||||
let signature = |state: &gpx_studio_desktop_lib::state::AppState| {
|
||||
state.core(move |core| gpx_studio_desktop_lib::workspace::signature(&core.engine, id))
|
||||
};
|
||||
let before = signature(&state);
|
||||
|
||||
@@ -483,7 +492,7 @@ fn a_file_outside_the_open_folder_is_read_again_when_the_disk_changes() {
|
||||
|
||||
#[test]
|
||||
fn files_made_from_a_file_of_the_open_folder_are_written_next_to_it_at_once() {
|
||||
let state = crate::state::AppState::temporary();
|
||||
let state = crate::temporary_state();
|
||||
let folder = std::env::temp_dir().join(format!("gpx-tests-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(folder.join("sub")).unwrap();
|
||||
std::fs::copy("../gpx-rs/data/simple.gpx", folder.join("sub/track.gpx")).unwrap();
|
||||
@@ -516,19 +525,23 @@ fn files_made_from_a_file_of_the_open_folder_are_written_next_to_it_at_once() {
|
||||
.collect();
|
||||
// the copy is in the subfolder, named after its name (the one in the file, not of the file)
|
||||
assert!(
|
||||
paths.iter().any(|path| path.ends_with("sub/simple.gpx")),
|
||||
paths
|
||||
.iter()
|
||||
.any(|path| std::path::Path::new(path).ends_with("sub/simple.gpx")),
|
||||
"{paths:?}"
|
||||
);
|
||||
// the new file went where the selected file is
|
||||
assert!(
|
||||
paths.iter().any(|path| path.ends_with("sub/fresh.gpx")),
|
||||
paths
|
||||
.iter()
|
||||
.any(|path| std::path::Path::new(path).ends_with("sub/fresh.gpx")),
|
||||
"{paths:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_parts_of_a_split_are_numbered_from_one() {
|
||||
let state = crate::state::AppState::temporary();
|
||||
let state = crate::temporary_state();
|
||||
let folder = std::env::temp_dir().join(format!("gpx-tests-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&folder).unwrap();
|
||||
std::fs::copy("../gpx-rs/data/simple.gpx", folder.join("track.gpx")).unwrap();
|
||||
@@ -649,7 +662,7 @@ fn a_file_keeps_its_name_and_its_file_is_renamed_apart() {
|
||||
|
||||
// the name of another file is not taken
|
||||
let error = app.error("rename_file", json!({ "fileId": id, "name": "taken" }));
|
||||
assert_eq!(error, crate::workspace::NAME_TAKEN);
|
||||
assert_eq!(error, gpx_studio_desktop_lib::workspace::NAME_TAKEN);
|
||||
assert!(folder.join("moved.gpx").exists());
|
||||
|
||||
// a name is one that a file can have
|
||||
@@ -0,0 +1,15 @@
|
||||
//! The tests of the desktop app. They are integration tests, in a single binary, rather than unit
|
||||
//! tests: on Windows, only test binaries can be given the manifest that the app needs to start
|
||||
//! (see `build.rs`).
|
||||
|
||||
mod commands;
|
||||
mod store;
|
||||
mod workspace;
|
||||
|
||||
use gpx_studio_desktop_lib::state::AppState;
|
||||
|
||||
/// An engine that keeps its data in a directory of its own, and has nobody to tell.
|
||||
fn temporary_state() -> AppState {
|
||||
let directory = std::env::temp_dir().join(format!("gpx-studio-{}", uuid::Uuid::new_v4()));
|
||||
AppState::open(directory, std::sync::Arc::new(|_, _| {})).expect("the storage can be opened")
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! The storage of the engine on the file system (`store`).
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use gpx_engine::{
|
||||
Action, Batch, Command, CommitError, Load, LoadFiles, Persister, Revision, Storage,
|
||||
};
|
||||
use gpx_studio_desktop_lib::Engine;
|
||||
use gpx_studio_desktop_lib::store::FileStorage;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn directory() -> PathBuf {
|
||||
std::env::temp_dir().join(format!("gpx-studio-store-{}", Uuid::new_v4()))
|
||||
}
|
||||
|
||||
fn engine_with_a_file() -> Engine {
|
||||
let mut engine = Engine::default();
|
||||
let data = std::fs::read("../gpx-rs/data/simple.gpx").unwrap();
|
||||
engine
|
||||
.execute(Action::Edit(Command::LoadFiles(LoadFiles {
|
||||
files: vec![Load {
|
||||
data: &data,
|
||||
name: "simple",
|
||||
}],
|
||||
})))
|
||||
.unwrap();
|
||||
engine
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_the_files_and_the_settings_between_sessions() {
|
||||
let root = directory();
|
||||
let engine = engine_with_a_file();
|
||||
let order = engine.order().to_vec();
|
||||
|
||||
let storage = FileStorage::open(&root).unwrap();
|
||||
pollster::block_on(Persister::default().save(&storage, engine.snapshot())).unwrap();
|
||||
pollster::block_on(storage.put_setting("units", "\"metric\"")).unwrap();
|
||||
|
||||
// another session
|
||||
let storage = FileStorage::open(&root).unwrap();
|
||||
let restored = pollster::block_on(Persister::default().restore(&storage)).unwrap();
|
||||
let mut other = Engine::default();
|
||||
other.restore(restored);
|
||||
assert_eq!(other.order(), order);
|
||||
assert_eq!(
|
||||
other.file_statistics(&order[0]).unwrap().total_distance,
|
||||
engine.file_statistics(&order[0]).unwrap().total_distance
|
||||
);
|
||||
assert_eq!(storage.settings()["units"], "\"metric\"");
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuses_a_commit_that_is_not_based_on_the_head() {
|
||||
let root = directory();
|
||||
let storage = FileStorage::open(&root).unwrap();
|
||||
let batch = Batch {
|
||||
order: Some(vec![]),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(pollster::block_on(storage.commit(&batch)), Ok(Revision(1)));
|
||||
assert_eq!(
|
||||
pollster::block_on(storage.commit(&batch)),
|
||||
Err(CommitError::Stale { head: Revision(1) })
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,811 @@
|
||||
//! The places of the files of the engine on the disk (`workspace`).
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use gpx_engine::{FileId, SelectMode, api};
|
||||
use gpx_studio_desktop_lib::Engine;
|
||||
use gpx_studio_desktop_lib::workspace::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn gpx(name: &str) -> String {
|
||||
format!(
|
||||
r#"<?xml version="1.0"?><gpx version="1.1" creator="test" xmlns="http://www.topografix.com/GPX/1/1"><metadata><name>{name}</name></metadata><trk><name>{name}</name><trkseg><trkpt lat="50.0" lon="4.0"><ele>10</ele></trkpt><trkpt lat="50.1" lon="4.1"><ele>20</ele></trkpt></trkseg></trk></gpx>"#
|
||||
)
|
||||
}
|
||||
|
||||
/// The GPX files of a folder and of the folders in it.
|
||||
pub fn gpx_files(folder: &Path) -> Vec<PathBuf> {
|
||||
let mut found = vec![];
|
||||
for entry in fs::read_dir(folder).unwrap().flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
found.extend(gpx_files(&path));
|
||||
} else if is_gpx(&path) {
|
||||
found.push(path);
|
||||
}
|
||||
}
|
||||
found.sort();
|
||||
found
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
folder: PathBuf,
|
||||
engine: Engine,
|
||||
workspace: Workspace,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let folder = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&folder).unwrap();
|
||||
Self {
|
||||
folder,
|
||||
engine: Engine::default(),
|
||||
workspace: Workspace::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&self, name: &str, title: &str) -> PathBuf {
|
||||
let path = self.folder.join(name);
|
||||
fs::write(&path, gpx(title)).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
/// Opens the folder, and all the files that are in it.
|
||||
fn open_folder(&mut self) {
|
||||
let folder = self.folder.clone();
|
||||
self.workspace.open_folder(&mut self.engine, &folder, false);
|
||||
let files = gpx_files(&folder);
|
||||
self.workspace.open_files(&mut self.engine, &files, false);
|
||||
}
|
||||
|
||||
fn reconcile(&mut self, changed: Option<&[PathBuf]>) -> Option<api::Outcome> {
|
||||
self.workspace.reconcile(&mut self.engine, changed)
|
||||
}
|
||||
|
||||
fn save(&mut self, ids: &[Uuid]) {
|
||||
self.workspace
|
||||
.save(&mut self.engine, ids, &BTreeMap::new(), None)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn ids(&self) -> Vec<Uuid> {
|
||||
self.engine.order().iter().map(|id| id.0).collect()
|
||||
}
|
||||
|
||||
fn id_named(&self, name: &str) -> Uuid {
|
||||
self.ids()
|
||||
.into_iter()
|
||||
.find(|id| self.name(*id) == name)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn name(&self, id: Uuid) -> String {
|
||||
self.engine
|
||||
.file_structure(&FileId(id))
|
||||
.unwrap()
|
||||
.name
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// What the file holds: the name of its track (its own name is the one of the file).
|
||||
fn content(&self, id: Uuid) -> String {
|
||||
self.engine.file_structure(&FileId(id)).unwrap().tracks[0]
|
||||
.info
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn rename(&mut self, id: Uuid, name: &str) {
|
||||
api::select(&mut self.engine, &[id], SelectMode::Replace);
|
||||
assert!(!api::metadata(&mut self.engine, name, "").has_error());
|
||||
}
|
||||
|
||||
fn status(&self, id: Uuid) -> FileStatus {
|
||||
self.workspace
|
||||
.state(&self.engine)
|
||||
.files
|
||||
.remove(&id.to_string())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.folder);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_opened_file_is_clean_until_it_is_edited_and_clean_again_once_saved() {
|
||||
let mut fx = Fixture::new();
|
||||
let path = fx.write("a.gpx", "a");
|
||||
fx.workspace
|
||||
.open_files(&mut fx.engine, std::slice::from_ref(&path), true);
|
||||
let id = fx.ids()[0];
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert_eq!(fx.status(id).path.as_deref(), path.to_str());
|
||||
|
||||
fx.rename(id, "renamed");
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
// nothing is written before it is saved
|
||||
assert!(fs::read_to_string(&path).unwrap().contains(">a<"));
|
||||
|
||||
fx.save(&[id]);
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert!(fs::read_to_string(&path).unwrap().contains(">renamed<"));
|
||||
|
||||
// going back before the save is an edit again
|
||||
api::select(&mut fx.engine, &[id], SelectMode::Replace);
|
||||
api::undo(&mut fx.engine);
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
api::redo(&mut fx.engine);
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_is_not_on_the_disk_is_unsaved_until_it_is_given_a_place() {
|
||||
let mut fx = Fixture::new();
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
let id = fx.ids()[0];
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert_eq!(
|
||||
fx.workspace.unlinked(&fx.engine, &[id]),
|
||||
[(id, "fresh".to_string())]
|
||||
);
|
||||
// nowhere to put it without being told
|
||||
fx.save(&[id]);
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
|
||||
let path = fx.folder.join("fresh.gpx");
|
||||
let paths = BTreeMap::from([(id, path.clone())]);
|
||||
fx.workspace
|
||||
.save(&mut fx.engine, &[id], &paths, None)
|
||||
.unwrap();
|
||||
assert!(path.exists());
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, id));
|
||||
assert!(fx.workspace.unlinked(&fx.engine, &[id]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn several_files_without_a_place_go_in_a_folder_with_names_that_are_free() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("fresh.gpx", "already there");
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
api::new_file(&mut fx.engine, "a/b: c", None, None, None, false);
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
let ids = fx.ids();
|
||||
let folder = fx.folder.clone();
|
||||
fx.workspace
|
||||
.save(&mut fx.engine, &ids, &BTreeMap::new(), Some(&folder))
|
||||
.unwrap();
|
||||
for name in ["fresh (2).gpx", "fresh (3).gpx", "a_b_ c.gpx"] {
|
||||
assert!(fx.folder.join(name).exists(), "{name}");
|
||||
}
|
||||
assert!(ids.iter().all(|id| !fx.workspace.is_dirty(&fx.engine, *id)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_this_app_writes_is_not_a_change_of_the_disk() {
|
||||
let mut fx = Fixture::new();
|
||||
let path = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
api::select(&mut fx.engine, &[id], SelectMode::Replace);
|
||||
assert!(!api::style(&mut fx.engine, Some("#ff0000"), None, None).has_error());
|
||||
fx.save(&[id]);
|
||||
assert!(fx.reconcile(Some(&[path])).is_none());
|
||||
assert!(!fx.status(id).replaced && !fx.status(id).dirty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_folders_of_the_files_outside_the_open_folder_are_watched_too() {
|
||||
let mut fx = Fixture::new();
|
||||
let inside = fx.write("in.gpx", "in");
|
||||
let elsewhere = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&elsewhere).unwrap();
|
||||
let (one, two) = (elsewhere.join("one.gpx"), elsewhere.join("two.gpx"));
|
||||
fs::write(&one, gpx("one")).unwrap();
|
||||
fs::write(&two, gpx("two")).unwrap();
|
||||
let folder = fx.folder.clone();
|
||||
|
||||
// no folder: only the folder of the files
|
||||
fx.workspace.open_files(&mut fx.engine, &[one, two], false);
|
||||
assert_eq!(fx.workspace.watched(), [(elsewhere.clone(), false)]);
|
||||
|
||||
// the open folder as a whole, and the file in it needs nothing more
|
||||
fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
fx.workspace.open_files(&mut fx.engine, &[inside], false);
|
||||
assert_eq!(
|
||||
fx.workspace.watched(),
|
||||
[(folder, true), (elsewhere.clone(), false)]
|
||||
);
|
||||
|
||||
// closing the files stops it
|
||||
let ids = fx.ids();
|
||||
fx.workspace.close_files(&mut fx.engine, &ids, true);
|
||||
assert_eq!(fx.workspace.watched(), [(fx.folder.clone(), true)]);
|
||||
fs::remove_dir_all(elsewhere).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_a_folder_does_not_open_its_files() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
let folder = fx.folder.clone();
|
||||
let outcome = fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
assert!(!outcome.changed);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert_eq!(fx.workspace.folder.as_deref(), Some(folder.as_path()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folders_are_made_renamed_and_moved_and_the_open_files_follow() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
let root = fx.folder.clone();
|
||||
let path_of = |fx: &Fixture| PathBuf::from(fx.status(id).path.unwrap());
|
||||
|
||||
let first = fx.workspace.create_folder(&root, "New folder").unwrap();
|
||||
let second = fx.workspace.create_folder(&root, "New folder").unwrap();
|
||||
assert_eq!(first, root.join("New folder"));
|
||||
assert_eq!(second, root.join("New folder 2"));
|
||||
|
||||
fx.workspace.move_paths(&[a], &first).unwrap();
|
||||
assert_eq!(path_of(&fx), first.join("a.gpx"));
|
||||
assert!(first.join("a.gpx").exists() && !fx.status(id).dirty);
|
||||
|
||||
let trips = fx.workspace.rename_path(&first, "Trips").unwrap();
|
||||
assert_eq!(path_of(&fx), root.join("Trips/a.gpx"));
|
||||
// a folder does not go into itself, nor onto something else
|
||||
assert!(
|
||||
fx.workspace
|
||||
.move_paths(std::slice::from_ref(&trips), &trips)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
fx.workspace.rename_path(&trips, "New folder 2"),
|
||||
Err(NAME_TAKEN.to_owned())
|
||||
);
|
||||
fx.workspace.move_paths(&[trips], &second).unwrap();
|
||||
assert_eq!(path_of(&fx), second.join("Trips/a.gpx"));
|
||||
|
||||
// a file keeps its extension
|
||||
let closed = fx.write("c.gpx", "c");
|
||||
assert_eq!(
|
||||
fx.workspace.rename_path(&closed, "d").unwrap(),
|
||||
root.join("d.gpx")
|
||||
);
|
||||
// the open folder and what is not in it are left alone
|
||||
assert!(fx.workspace.rename_path(&root, "x").is_err());
|
||||
let elsewhere = canonical(&std::env::temp_dir());
|
||||
assert!(fx.workspace.rename_path(&elsewhere, "x").is_err());
|
||||
assert!(fx.workspace.create_folder(&elsewhere, "x").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleted_files_are_closed_unless_they_have_edits() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
let b = fx.write("b.gpx", "b");
|
||||
fx.open_folder();
|
||||
let (id_a, id_b) = (fx.id_named("a"), fx.id_named("b"));
|
||||
api::select(&mut fx.engine, &[id_b], SelectMode::Replace);
|
||||
assert!(!api::style(&mut fx.engine, Some("#ff0000"), None, None).has_error());
|
||||
|
||||
let outcome = fx
|
||||
.workspace
|
||||
.delete_paths(&mut fx.engine, &[a.clone(), b.clone()], |paths| {
|
||||
paths.iter().for_each(|path| fs::remove_file(path).unwrap());
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert!(outcome.changed);
|
||||
assert_eq!(fx.ids(), [id_b]);
|
||||
assert!(!fx.ids().contains(&id_a));
|
||||
let status = fx.status(id_b);
|
||||
assert!(status.dirty && status.missing);
|
||||
assert!(!a.exists() && !b.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_tree_of_the_folder_shows_folders_first_and_only_gpx_files() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("b.gpx", "b");
|
||||
fx.write("A.GPX", "a");
|
||||
fx.write("b 10.gpx", "b 10");
|
||||
fx.write("b 2.gpx", "b 2");
|
||||
fx.write("notes.txt", "not a track");
|
||||
fx.write(".hidden.gpx", "hidden");
|
||||
fs::create_dir(fx.folder.join("zoo")).unwrap();
|
||||
fs::create_dir(fx.folder.join("Alps")).unwrap();
|
||||
fs::create_dir(fx.folder.join(".git")).unwrap();
|
||||
fx.write("zoo/c.gpx", "c");
|
||||
let folder = fx.folder.clone();
|
||||
fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
|
||||
let names = |entries: Vec<Entry>| -> Vec<(String, bool)> {
|
||||
entries.into_iter().map(|e| (e.name, e.is_dir)).collect()
|
||||
};
|
||||
assert_eq!(
|
||||
names(fx.workspace.list_folder(&folder).unwrap()),
|
||||
[
|
||||
("Alps".to_string(), true),
|
||||
("zoo".to_string(), true),
|
||||
("A.GPX".to_string(), false),
|
||||
("b.gpx".to_string(), false),
|
||||
("b 2.gpx".to_string(), false),
|
||||
("b 10.gpx".to_string(), false),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
names(fx.workspace.list_folder(&folder.join("zoo")).unwrap()),
|
||||
[("c.gpx".to_string(), false)]
|
||||
);
|
||||
// only what is in the open folder can be listed
|
||||
let elsewhere = canonical(&std::env::temp_dir());
|
||||
assert!(fx.workspace.list_folder(&elsewhere).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_are_sorted_by_the_numbers_they_hold() {
|
||||
let mut names = vec![
|
||||
"track 10",
|
||||
"track 2",
|
||||
"Track 1",
|
||||
"track",
|
||||
"track 02b",
|
||||
"track 2a",
|
||||
"b",
|
||||
"A",
|
||||
];
|
||||
names.sort_by(|a, b| natural_cmp(a, b).then_with(|| a.cmp(b)));
|
||||
assert_eq!(
|
||||
names,
|
||||
[
|
||||
"A",
|
||||
"b",
|
||||
"track",
|
||||
"Track 1",
|
||||
"track 2",
|
||||
"track 2a",
|
||||
"track 02b",
|
||||
"track 10",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_without_edits_is_read_again_when_the_disk_changes() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
|
||||
fx.write("a.gpx", "theirs");
|
||||
let outcome = fx.reconcile(Some(std::slice::from_ref(&a))).unwrap();
|
||||
assert!(outcome.changed);
|
||||
// the same file, now the one on the disk
|
||||
assert_eq!(fx.ids(), [id]);
|
||||
assert_eq!(fx.content(id), "theirs");
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.replaced);
|
||||
// looking again changes nothing
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_with_edits_is_flagged_when_the_disk_changes_and_not_read_again() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "mine");
|
||||
|
||||
fx.write("a.gpx", "theirs");
|
||||
let outcome = fx.reconcile(Some(std::slice::from_ref(&a))).unwrap();
|
||||
assert!(!outcome.changed);
|
||||
assert_eq!(fx.name(id), "mine");
|
||||
let status = fx.status(id);
|
||||
assert!(status.dirty && status.replaced && !status.missing);
|
||||
// looking again changes nothing
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_none());
|
||||
|
||||
// the disk goes back to what was read: only the edits are left
|
||||
fx.write("a.gpx", "a");
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_some());
|
||||
let status = fx.status(id);
|
||||
assert!(status.dirty && !status.replaced);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_user_can_take_the_disk_instead_of_the_edits() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "mine");
|
||||
fx.write("a.gpx", "theirs");
|
||||
fx.reconcile(None);
|
||||
assert!(fx.status(id).replaced);
|
||||
|
||||
fx.workspace.reload(&mut fx.engine, &[id]).unwrap();
|
||||
assert_eq!(fx.content(id), "theirs");
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.replaced);
|
||||
// and it is one undo step
|
||||
api::select(&mut fx.engine, &[id], SelectMode::Replace);
|
||||
api::undo(&mut fx.engine);
|
||||
assert_eq!(fx.content(id), "mine");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_overwrites_what_replaced_the_file() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "mine");
|
||||
fx.write("a.gpx", "theirs");
|
||||
fx.reconcile(None);
|
||||
assert!(fx.status(id).replaced);
|
||||
|
||||
fx.save(&[id]);
|
||||
assert!(fs::read_to_string(a).unwrap().contains(">mine<"));
|
||||
assert!(!fx.status(id).replaced && !fx.status(id).dirty);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_is_gone_from_the_disk_stays_open_and_dirty_until_it_is_saved_again() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
|
||||
fs::remove_file(&a).unwrap();
|
||||
assert!(fx.reconcile(Some(std::slice::from_ref(&a))).is_some());
|
||||
assert_eq!(fx.ids(), [id]);
|
||||
let status = fx.status(id);
|
||||
assert!(status.dirty && status.missing && status.path.is_some());
|
||||
|
||||
// saving puts it back
|
||||
fx.save(&[id]);
|
||||
assert!(a.exists());
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_that_comes_back_different_is_read_again() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fs::remove_file(&a).unwrap();
|
||||
fx.reconcile(None);
|
||||
assert!(fx.status(id).missing);
|
||||
|
||||
fx.write("a.gpx", "back");
|
||||
fx.reconcile(None);
|
||||
assert_eq!(fx.content(id), "back");
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty && !status.missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_changed_while_the_app_was_closed_is_found_when_it_starts() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.write("a.gpx", "changed");
|
||||
assert!(fx.reconcile(None).is_some());
|
||||
assert_eq!(fx.content(id), "changed");
|
||||
fs::remove_file(a).unwrap();
|
||||
assert!(fx.reconcile(None).is_some());
|
||||
assert!(fx.status(id).missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_files_made_from_a_file_of_the_folder_are_saved_next_to_it() {
|
||||
let mut fx = Fixture::new();
|
||||
fs::create_dir(fx.folder.join("sub")).unwrap();
|
||||
fx.write("sub/b.gpx", "b");
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let b = fx.id_named("b");
|
||||
let sub = fx.folder.join("sub");
|
||||
assert_eq!(fx.workspace.source_dir(&[b]), Some(canonical(&sub)));
|
||||
|
||||
let before = fx.ids();
|
||||
api::select(&mut fx.engine, &[b], SelectMode::Replace);
|
||||
let outcome = api::duplicate(&mut fx.engine);
|
||||
let copy = fx
|
||||
.ids()
|
||||
.into_iter()
|
||||
.find(|id| !before.contains(id))
|
||||
.unwrap();
|
||||
fx.workspace.inherit(outcome.added(), &canonical(&sub));
|
||||
assert_eq!(fx.status(copy).folder.as_deref(), canonical(&sub).to_str());
|
||||
assert!(fx.status(copy).path.is_none() && fx.status(copy).dirty);
|
||||
// nobody needs to be asked where it goes
|
||||
assert!(fx.workspace.unlinked(&fx.engine, &[copy]).is_empty());
|
||||
|
||||
fx.save(&[copy]);
|
||||
assert!(sub.join("b (2).gpx").exists());
|
||||
let status = fx.status(copy);
|
||||
assert!(!status.dirty && status.folder.is_none());
|
||||
assert_eq!(
|
||||
status.path.as_deref(),
|
||||
canonical(&sub.join("b (2).gpx")).to_str()
|
||||
);
|
||||
// the copy of the copy is next to it as well
|
||||
assert_eq!(fx.workspace.source_dir(&[copy]), Some(canonical(&sub)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_outside_the_open_folder_hands_nothing_down() {
|
||||
let mut fx = Fixture::new();
|
||||
let elsewhere = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&elsewhere).unwrap();
|
||||
let outside = elsewhere.join("o.gpx");
|
||||
fs::write(&outside, gpx("o")).unwrap();
|
||||
let folder = fx.folder.clone();
|
||||
fx.workspace.open_folder(&mut fx.engine, &folder, false);
|
||||
fx.workspace
|
||||
.open_files(&mut fx.engine, std::slice::from_ref(&outside), true);
|
||||
let id = fx.ids()[0];
|
||||
assert_eq!(fx.workspace.source_dir(&[id]), None);
|
||||
// a new file with no source is not linked to a folder by `source_dir`: the caller falls back
|
||||
fs::remove_dir_all(elsewhere).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_files_asks_what_to_do_with_the_unsaved_ones() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.write("b.gpx", "b");
|
||||
fx.open_folder();
|
||||
let (a, b) = (fx.id_named("a"), fx.id_named("b"));
|
||||
fx.rename(a, "edited");
|
||||
assert_eq!(fx.workspace.unsaved(&fx.engine, &[a, b]), [a]);
|
||||
|
||||
// without a choice what has edits stays
|
||||
let outcome = fx.workspace.close_files(&mut fx.engine, &[a, b], false);
|
||||
assert!(outcome.changed);
|
||||
assert_eq!(fx.ids(), [a]);
|
||||
|
||||
// saved first
|
||||
fx.save(&[a]);
|
||||
fx.workspace.close_files(&mut fx.engine, &[a], false);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert!(
|
||||
fs::read_to_string(fx.folder.join("a.gpx"))
|
||||
.unwrap()
|
||||
.contains("edited")
|
||||
);
|
||||
assert!(fx.workspace.links.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_files_can_discard_their_edits() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let a = fx.ids()[0];
|
||||
fx.rename(a, "edited");
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
let fresh = fx.id_named("fresh");
|
||||
fx.workspace.close_files(&mut fx.engine, &[a, fresh], true);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert!(
|
||||
fs::read_to_string(fx.folder.join("a.gpx"))
|
||||
.unwrap()
|
||||
.contains(">a<")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_the_folder_closes_every_file() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
api::new_file(&mut fx.engine, "fresh", None, None, None, false);
|
||||
assert_eq!(fx.ids().len(), 2);
|
||||
|
||||
// the new file has nowhere to go: it has to be discarded
|
||||
let outcome = fx.workspace.close_folder(&mut fx.engine, false);
|
||||
assert!(outcome.changed);
|
||||
assert_eq!(fx.ids().len(), 1);
|
||||
assert!(fx.workspace.folder.is_none());
|
||||
|
||||
let outcome = fx.workspace.close_folder(&mut fx.engine, true);
|
||||
assert!(outcome.changed);
|
||||
assert!(fx.ids().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_another_folder_closes_the_first_with_all_its_files() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let a = fx.ids()[0];
|
||||
fx.rename(a, "edited");
|
||||
let other = canonical(&std::env::temp_dir()).join(format!("gpx-ws-{}", Uuid::new_v4()));
|
||||
fs::create_dir_all(&other).unwrap();
|
||||
|
||||
// the edits stay, nothing is lost without a choice
|
||||
fx.workspace.open_folder(&mut fx.engine, &other, false);
|
||||
assert_eq!(fx.ids(), [a]);
|
||||
assert_eq!(fx.workspace.folder.as_deref(), Some(other.as_path()));
|
||||
|
||||
let first = fx.folder.clone();
|
||||
fx.workspace.open_folder(&mut fx.engine, &first, true);
|
||||
assert!(fx.ids().is_empty());
|
||||
assert!(
|
||||
fs::read_to_string(fx.folder.join("a.gpx"))
|
||||
.unwrap()
|
||||
.contains(">a<")
|
||||
);
|
||||
fs::remove_dir_all(other).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opening_the_same_folder_again_changes_nothing() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let folder = fx.folder.clone();
|
||||
let outcome = fx.workspace.open_folder(&mut fx.engine, &folder, true);
|
||||
assert!(!outcome.changed);
|
||||
assert_eq!(fx.ids().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_save_stops_at_the_file_and_leaves_it_dirty() {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("a.gpx", "a");
|
||||
fx.open_folder();
|
||||
let id = fx.ids()[0];
|
||||
fx.rename(id, "edited");
|
||||
// the place of the file is taken by a folder
|
||||
fs::remove_file(fx.folder.join("a.gpx")).unwrap();
|
||||
fs::create_dir(fx.folder.join("a.gpx")).unwrap();
|
||||
let result = fx
|
||||
.workspace
|
||||
.save(&mut fx.engine, &[id], &BTreeMap::new(), None);
|
||||
assert!(result.is_err());
|
||||
assert!(fx.workspace.is_dirty(&fx.engine, id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_as_gives_the_file_another_place() {
|
||||
let mut fx = Fixture::new();
|
||||
let a = fx.write("a.gpx", "a");
|
||||
fx.write("b.gpx", "b");
|
||||
fx.open_folder();
|
||||
let id = fx.id_named("a");
|
||||
let other = fx.id_named("b");
|
||||
fx.rename(id, "edited");
|
||||
|
||||
let target = fx.folder.join("copy.gpx");
|
||||
fx.workspace.save_as(&mut fx.engine, id, &target).unwrap();
|
||||
assert!(fs::read_to_string(&target).unwrap().contains("edited"));
|
||||
assert!(fs::read_to_string(a).unwrap().contains(">a<"));
|
||||
let status = fx.status(id);
|
||||
assert!(!status.dirty);
|
||||
assert_eq!(status.path.as_deref(), canonical(&target).to_str());
|
||||
|
||||
// the place of another open file is not available
|
||||
let taken = fx.folder.join("b.gpx");
|
||||
assert!(fx.workspace.save_as(&mut fx.engine, id, &taken).is_err());
|
||||
assert!(!fx.workspace.is_dirty(&fx.engine, other));
|
||||
}
|
||||
|
||||
mod restart {
|
||||
use gpx_engine::Persister;
|
||||
|
||||
use super::*;
|
||||
use gpx_studio_desktop_lib::store::FileStorage;
|
||||
|
||||
/// What the app does when it starts again: the engine and the links are read from what was
|
||||
/// kept.
|
||||
fn restart(engine: &Engine, workspace: &Workspace, root: &Path) -> (Engine, Workspace) {
|
||||
let storage = FileStorage::open(root).unwrap();
|
||||
pollster::block_on(Persister::default().save(&storage, engine.snapshot())).unwrap();
|
||||
let mut restarted = Engine::default();
|
||||
let restored = pollster::block_on(Persister::default().restore(&storage)).unwrap();
|
||||
restarted.restore(restored);
|
||||
let links = serde_json::to_string(workspace).unwrap();
|
||||
(restarted, serde_json::from_str(&links).unwrap())
|
||||
}
|
||||
|
||||
fn setup() -> (PathBuf, Engine, Workspace, Vec<Uuid>) {
|
||||
let root = std::env::temp_dir().join(format!("gpx-restart-{}", Uuid::new_v4()));
|
||||
let folder = root.join("tracks");
|
||||
fs::create_dir_all(&folder).unwrap();
|
||||
for name in ["with_tracks", "with_time", "with_waypoint"] {
|
||||
let _ = fs::copy(
|
||||
format!("../gpx-rs/data/{name}.gpx"),
|
||||
folder.join(format!("{name}.gpx")),
|
||||
);
|
||||
}
|
||||
let mut engine = Engine::default();
|
||||
let mut workspace = Workspace::default();
|
||||
workspace.open_folder(&mut engine, &folder, false);
|
||||
workspace.open_files(&mut engine, &gpx_files(&folder), false);
|
||||
let ids = engine.order().iter().map(|id| id.0).collect();
|
||||
(root, engine, workspace, ids)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_that_were_not_edited_are_not_unsaved_after_a_restart() {
|
||||
let (root, engine, workspace, ids) = setup();
|
||||
assert!(ids.len() >= 2);
|
||||
let (restarted, mut workspace) = restart(&engine, &workspace, &root.join("store"));
|
||||
// the files are the same, but the engine knows their contents by new identities
|
||||
assert!(ids.iter().any(|id| workspace.is_dirty(&restarted, *id)));
|
||||
workspace.verify(&restarted);
|
||||
assert!(ids.iter().all(|id| !workspace.is_dirty(&restarted, *id)));
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_that_were_edited_are_still_unsaved_after_a_restart() {
|
||||
let (root, mut engine, workspace, ids) = setup();
|
||||
api::select(&mut engine, &[ids[0]], gpx_engine::SelectMode::Replace);
|
||||
api::metadata(&mut engine, "edited", "");
|
||||
assert!(workspace.is_dirty(&engine, ids[0]));
|
||||
let (restarted, mut workspace) = restart(&engine, &workspace, &root.join("store"));
|
||||
workspace.verify(&restarted);
|
||||
assert!(workspace.is_dirty(&restarted, ids[0]));
|
||||
assert!(
|
||||
ids[1..]
|
||||
.iter()
|
||||
.all(|id| !workspace.is_dirty(&restarted, *id))
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn links_made_before_the_content_was_kept_are_checked_against_the_disk() {
|
||||
let (root, mut engine, mut workspace, ids) = setup();
|
||||
api::select(&mut engine, &[ids[0]], gpx_engine::SelectMode::Replace);
|
||||
api::metadata(&mut engine, "edited", "");
|
||||
for link in workspace.links.values_mut() {
|
||||
link.export_hash = 0;
|
||||
}
|
||||
let (restarted, mut workspace) = restart(&engine, &workspace, &root.join("store"));
|
||||
workspace.verify(&restarted);
|
||||
assert!(workspace.is_dirty(&restarted, ids[0]));
|
||||
assert!(
|
||||
ids[1..]
|
||||
.iter()
|
||||
.all(|id| !workspace.is_dirty(&restarted, *id))
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_folders_of_new_files_and_the_old_flag_name_survive_a_restart() {
|
||||
let (root, engine, mut workspace, ids) = setup();
|
||||
let folder = workspace.folder.clone().unwrap();
|
||||
let fresh = Uuid::new_v4();
|
||||
workspace.inherit(&[fresh.to_string()], &folder);
|
||||
workspace.links.get_mut(&ids[0]).unwrap().replaced = true;
|
||||
let json = serde_json::to_string(&workspace).unwrap();
|
||||
// links written by the previous version had `conflict`, which is the same thing
|
||||
let json = json.replace("\"replaced\"", "\"conflict\"");
|
||||
assert!(json.contains("\"conflict\":true"));
|
||||
let read: Workspace = serde_json::from_str(&json).unwrap();
|
||||
assert!(read.links[&ids[0]].replaced);
|
||||
assert_eq!(read.dirs.get(&fresh), Some(&folder));
|
||||
let _ = engine;
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
Generated
+26
@@ -44,6 +44,7 @@
|
||||
"@typescript-eslint/parser": "^8.33.1",
|
||||
"bits-ui": "^2.17.2",
|
||||
"clsx": "^2.1.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-svelte": "^3.9.1",
|
||||
@@ -354,6 +355,13 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@epic-web/invariant": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
||||
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||
@@ -3275,6 +3283,24 @@
|
||||
"version": "1.0.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
|
||||
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@epic-web/invariant": "^1.0.0",
|
||||
"cross-spawn": "^7.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "dist/bin/cross-env.js",
|
||||
"cross-env-shell": "dist/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"dev": true,
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"build:desktop": "BUILD_TARGET=desktop BASE_PATH='' vite build",
|
||||
"build:desktop": "cross-env BUILD_TARGET=desktop BASE_PATH= vite build",
|
||||
"prebuild": "npx tsx src/lib/scripts/pwa-manifest.ts",
|
||||
"postbuild": "npx tsx src/lib/scripts/sitemap.ts",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"pretest": "svelte-kit sync",
|
||||
"test": "node --import tsx --import ./src/test/register.mjs --experimental-wasm-modules --disable-warning=ExperimentalWarning --test \"src/**/*.test.ts\"",
|
||||
"generate:example": "node --import tsx src/lib/scripts/example-statistics.ts",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
@@ -38,6 +39,7 @@
|
||||
"@typescript-eslint/parser": "^8.33.1",
|
||||
"bits-ui": "^2.17.2",
|
||||
"clsx": "^2.1.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-svelte": "^3.9.1",
|
||||
|
||||
@@ -51,7 +51,7 @@ By right-clicking on a file tab, you can access the same actions as in the [edit
|
||||
|
||||
As mentioned in the [view options section](./menu/view), you can switch to a tree layout for the files list.
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
You can also apply [edit actions](./menu/edit) and [tools](./toolbar) to internal file items.
|
||||
Furthermore, you can drag and drop the inner items to reorder them, or move them in the hierarchy or even to another file.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: Фармат файла GPX
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
<a href="https://www.topografix.com/gpx.asp" target="_blank">Фармат файла GPX</a> - гэта адкрыты стандарт для абмену дадзенымі GPS паміж праграмамі і прыладамі GPS.
|
||||
Па сутнасці, ён складаецца з серыі кропак GPS, якія кадзіруюць адну або некалькі слядоў GPS, і, па жаданні, некаторыя кропкі цікавасці.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
Файлы GPX могуць таксама ўтрымліваць метададзеныя, з якіх палі **імя** і **апісанне** найбольш карысныя для карыстальнікаў.
|
||||
|
||||
@@ -27,6 +29,20 @@ title: Фармат файла GPX
|
||||
У большасці выпадкаў файлы GPX утрымліваюць адзін трэк з адным сегментам.
|
||||
Аднак іерархія, апісаная вышэй, дазваляе выкарыстоўваць больш складаныя выпадкі, напрыклад, планаваць шматдзённыя паездкі з некалькімі варыянтамі на кожны дзень.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Кропкі цікавасці
|
||||
|
||||
**Кропкі цікавасці** (тэхнічна званыя _маршрутнымі кропкамі_) уяўляюць цікавыя месцы, якія можна паказаць альбо на прыладзе GPS, альбо на лічбавай карце.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Edit actions
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Unlike the file actions, the edit actions can potentially modify the content of the currently selected files.
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, and points of interest](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Therefore, we will refer to the elements that can be modified by these actions as _file items_.
|
||||
Note that except for the undo and redo actions, the edit actions are also accessible through the context menu (right-click) of the file items.
|
||||
|
||||
@@ -27,10 +27,6 @@ Open the information dialog of the currently selected file item, where you can s
|
||||
|
||||
Open the appearance dialog, where you can change the color, opacity, and width of the selected file items on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New track
|
||||
|
||||
Create a new track in the selected file.
|
||||
@@ -42,6 +38,17 @@ Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
This action is only available when the tree layout of the files list is enabled.
|
||||
Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New segment
|
||||
|
||||
Create a new segment in the selected track.
|
||||
@@ -53,14 +60,17 @@ Additionally, the selection must be a single track.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Select all
|
||||
|
||||
Add all file items in the current hierarchy level to the selection.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Copy
|
||||
|
||||
Copy the selected file items to the clipboard.
|
||||
|
||||
@@ -15,6 +15,10 @@ The file actions menu contains a set of pretty self-explanatory file operations.
|
||||
|
||||
Create a new empty file.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Open...
|
||||
|
||||
Open files from your computer.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: View options
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Hide the elevation profile to make room for the map, or show it to inspect the c
|
||||
|
||||
Toggle the tree layout for the [file list](../files-and-stats).
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Switch to previous basemap
|
||||
|
||||
@@ -38,6 +38,14 @@ They are displayed for the current selection, like the [elevation profile](../fi
|
||||
|
||||
Toggle the visibility of direction arrows on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Toggle 3D
|
||||
|
||||
Enter or exit the 3D map view.
|
||||
|
||||
@@ -16,7 +16,7 @@ This tool allows you to extract [tracks (or segments)](../gpx) from files (or tr
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Applying the tool to a file containing multiple tracks will create a new file for each of the tracks it contains.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Similarly, applying the tool to a track containing multiple segments will create (in the same file) a new track for each of the segments it contains.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Merge
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, or segments](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- If your goal is to create a single continuous trace from your selection, use the **Connect the traces** option and validate.
|
||||
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ To split the selected trace into two parts, click on one of the split markers di
|
||||
To split at a specific point of your choice, hover over the trace on the map.
|
||||
Scissors will appear at the cursor position, showing that you can split the trace at that point.
|
||||
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks or segments](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Hovering over the selected trace turns your cursor into scissors." />
|
||||
@@ -51,7 +51,7 @@ Clicant el botó dret en la pestanya d'un arxiu, pots accedir a les mateixes acc
|
||||
|
||||
Tal i com es menciona en la [secció d'opcions de vista](./menu/view), pots canviar a un esquema d'arbre per a la llista d'arxius.
|
||||
Aquesta disposició és ideal per tal d'administrar un gran nombre d'arxius oberts, ja que els organitza en una llista vertical en la part dreta del mapa.
|
||||
A més, la vista d'arbre d'arxius permet inspeccionar [tracs, segments i punts d'interès](./gpx) continguts dins dels arxius a través de seccions plegables.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
També pots aplicar [accions d'edició](./menu/edit) i [eines](./toolbar) a elements interns de l'arxiu.
|
||||
A més, pots arrossegar i deixar anar els elements interns per reordenar-los, o moure'ls en la jerarquia o fins i tot a un altre arxiu.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: Format d'arxiu GPX
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
El <a href="https://www.topografix.com/gpx.asp" target="_blank">format d'arxiu GPX</a> és un estàndard obert per intercanviar dades GPS entre aplicacions i aparells GPS.
|
||||
Bàsicament consisteix en una sèrie de punts GPS que codifiquen una o diverses traces GPS i, opcionalment, alguns punts d'interès.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
Els arxius GPX poden contenir també metadades, de les quals els camps **nom** i **descripció** són els més útils pels usuaris.
|
||||
|
||||
@@ -27,6 +29,20 @@ Aquests estan organitzats en una estructura jeràrquica, amb els tracs en el niv
|
||||
En la majoria dels casos, els arxius GPX contenen un sol trac amb un sol segment.
|
||||
Tanmateix, la jerarquia descrita a dalt permet casos d'ús més avançat, com la planificació de viatges de diversos dies amb variants per cada dia.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Punts d'interès
|
||||
|
||||
**Punts d'interès** (tècnicament anomenats _waypoints_) representen ubicacions d'interès per mostrar en un dispositiu GPS o en mapa digital.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Accions d'edició
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
A diferència de les accions de l'arxiu, les accions d'edició poden potencialment modificar el contingut dels arxius seleccionats actualment.
|
||||
A més a més, quan està habilitada la disposició de l'arbre de la llista d'arxius (veure [Arxius i estadístiques](../files-and-stats)), també es poden aplicar a [tracs, segments i punts d'interès](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Per tant, ens referirem als elements que poden ser modificats per aquestes accions com a _elements d'arxiu_.
|
||||
Tingui en compte que excepte per a les accions de desfer i refer, les accions d'edició també són accessibles a través del menú contextual (clic dret) dels elements de l'arxiu.
|
||||
|
||||
@@ -27,10 +27,6 @@ Obre el quadre de diàleg d'informació de l'element de l'arxiu seleccionat, on
|
||||
|
||||
Obre el diàleg d'aparença, on pots canviar el color, l'opacitat i l'amplada del mapa dels elements de l'arxiu seleccionat.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Amaga/mostra
|
||||
|
||||
Alterna la visibilitat dels elements de l'arxiu seleccionat en el mapa.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nou trac
|
||||
|
||||
Crear nova ruta en l'arxiu seleccionat.
|
||||
@@ -42,6 +38,17 @@ A més, la selecció ha de ser un únic arxiu.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Aquesta acció només està disponible quan està habilitada la disposició de l'arbre de la llista d'arxius.
|
||||
A més, la selecció ha de ser un únic arxiu.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nou segment
|
||||
|
||||
Crear un nou segment en el trac seleccionat.
|
||||
@@ -53,14 +60,17 @@ A més, la selecció ha de ser un trac únic.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Selecciona tot
|
||||
|
||||
Afegeix a la selecció tots els elements de l'arxiu en el nivell de jerarquia actual.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Centrar
|
||||
|
||||
Centrar el mapa en els elements de l'arxiu seleccionat.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Copia
|
||||
|
||||
Copia els elements de l'arxiu seleccionat en el porta-retalls.
|
||||
|
||||
@@ -15,6 +15,10 @@ El menú d'accions d'arxiu conté un conjunt d'operacions força autoexplicative
|
||||
|
||||
Crea un arxiu nou buit.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Obrir...
|
||||
|
||||
Obrir arxius del teu equip.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Opcions de vista
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Amagar el perfil d'elevació per fer espai al mapa o mostra-lo per inspeccionar
|
||||
|
||||
Alterna el disseny de l'arbre per la [llista d'arxius](../files-and-stats).
|
||||
Aquesta disposició és ideal per tal d'administrar un gran nombre d'arxius oberts, ja que els organitza en una llista vertical en la part dreta del mapa.
|
||||
A més, la vista de l'arbre d'arxius permet inspeccionar [tracs, segments i punts d'interès](../gpx) continguts en els arxius a través de les seccions plegables.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Canvia la mapa base anterior
|
||||
|
||||
@@ -38,6 +38,14 @@ Es mostren per a la selecció actual, com el [perfil d'elevació](../files-and-s
|
||||
|
||||
Alterna la visibilitat de les fletxes de direcció en el mapa.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Alternar 3D
|
||||
|
||||
Entra o surt de la vista de mapa en 3D.
|
||||
|
||||
@@ -16,7 +16,7 @@ Aquesta eina permet extreure [tracs (o segments)](../gpx) des d'arxius (o tracs)
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Aplicant l'eina a un arxiu que contingui múltiples tracs, crearà un arxiu nou per cada trac que contingui.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Igualment, aplicant l'eina a un trac que contingui múltiples segments, crearà (en el mateix arxiu) un nou trac per cada un dels segments que contingui.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Fusionar
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
Per utilitzar aquesta eina, s'ha de [seleccionar](../files-and-stats) múltiples arxius, [tracs o segments](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- Si el teu objectiu és crear una traça contínua a partir de la selecció, utilitza l'opció **Connectar les traces** i valida-ho.
|
||||
- La segona opció es pot utilitzar per a crear o gestionar arxius amb múltiples [tracs o segments](../gpx).
|
||||
Fusionar arxius (o tracs) crearà un sol arxiu (o trac) que contindrà tots els tracs (o segments) seleccionats.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ Per dividir la traça seleccionada en dues parts, clica sobre un dels marcadors
|
||||
Per dividir en un punt triat específicament, situat sobre la traça del mapa.
|
||||
Les tisores apareixeran en la posició del cursor, mostrant que pots dividir el traçat en aquest punt.
|
||||
|
||||
Pots triar dividir el traçat en dos arxius GPX o mantenir les parts dividides en el mateix arxiu com a [tracs o segments](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Situar el cursor sobre el traçat seleccionat el converteix en tisores." />
|
||||
@@ -51,7 +51,7 @@ Kliknutím pravým tlačítkem myši na kartu souboru můžete provést stejné
|
||||
|
||||
Jak je uvedeno v části [možnosti zobrazení](./menu/view), můžete přepnout na stromové rozvržení seznamu souborů.
|
||||
Toto zobrazení je ideální pro správu velkého počtu otevřených souborů, protože je uspořádá do svislého seznamu na pravé straně mapy.
|
||||
Stromové rozvržení souborů navíc umožňuje zkontrolovat [trasy, úseky a body zájmu](./gpx) obsažené v souborech prostřednictvím sbalitelných sekcí.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
Rovněž můžete využít [akce úprav](./menu/edit) a [nástroje](./toolbar) na prvky uvnitř souborů.
|
||||
Navíc přetažením prvků uvnitř souborů můžete změnit jejich pořadí nebo je přesunout v hierarchii či dokonce do jiného souboru.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: Formát souboru GPX
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
<a href="https://www.topografix.com/gpx.asp" target="_blank">GPX formát</a> je otevřeným standardem pro výměnu GPS dat mezi aplikacemi a GPS zařízeními.
|
||||
V podstatě se skládá z řady GPS bodů kódujících jednu nebo více GPS stop a volitelně i nějaké body zájmu.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
GPX soubory mohou obsahovat také metadata, z nichž pole **název** a **popis** jsou pro uživatele nejužitečnější.
|
||||
|
||||
@@ -27,6 +29,20 @@ Ty jsou uspořádány v hierarchické struktuře, přičemž trasy jsou na nejvy
|
||||
Ve většině případů obsahují GPX soubory pouze jednu trasu s jedním úsekem.
|
||||
Nicméně výše popsaná hierarchie umožňuje pokročilejší případy použití, například plánování vícedenních výletů s několika variantami pro každý den.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Body zájmu
|
||||
|
||||
**Body zájmu** (odborně nazývané _waypoints_) představují zajímavá místa, která se zobrazí buď na zařízení GPS, nebo na digitální mapě.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Akce úprav
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Na rozdíl od akcí v menu Soubor, mohou akce úprav potenciálně změnit obsah aktuálně vybraných souborů.
|
||||
Navíc pokud je povoleno rozvržení stromu seznamu souborů (viz [Soubory a statistiky](../files-and-stats)), mohou být také použity na [trasy, segmenty a body zájmu](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Proto budeme všechny prvky, které mohou být těmito akcemi upraveny, označovat jako _prvky souborů_.
|
||||
Všechny akce kromě zpět a opakovat jsou také dostupné z kontextového menu (klik pravým tlačítkem) prvků souborů.
|
||||
|
||||
@@ -27,10 +27,6 @@ Otevře informační dialog zvoleného prvku souborů, kde lze zobrazit a změni
|
||||
|
||||
Otevře dialog vzhledu, kde můžete změnit barvu, neprůhlednost a tloušťku zvolených prvků souborů v mapě.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Skrýt/zobrazit
|
||||
|
||||
Přepnout viditelnost zvolených prvků souborů v mapě.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nová trasa
|
||||
|
||||
Vytvořit novou trasu ve zvoleném souboru.
|
||||
@@ -42,6 +38,17 @@ Kromě toho musí být vybrán právě jeden soubor.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Tato akce je dostupná pouze v případě, že je povoleno rozvržení stromu seznamu souborů.
|
||||
Kromě toho musí být vybrán právě jeden soubor.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nový úsek
|
||||
|
||||
Vytvořit nový úsek na zvolené trase.
|
||||
@@ -53,14 +60,17 @@ Kromě toho musí být zvolena právě jedna trasa.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Vybrat vše
|
||||
|
||||
Přidejte k výběru všechny prvky souborů v dané úrovni hierarchie.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Vycentrovat
|
||||
|
||||
Vystředí mapu na označené prvky souborů.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Kopírovat
|
||||
|
||||
Zkopíruje zvolené prvky souborů do schránky.
|
||||
|
||||
@@ -15,6 +15,10 @@ Menu akce souboru obsahuje sadu poměrně samovysvětlujících operací.
|
||||
|
||||
Vytvořit nový prázdný soubor.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Otevřít...
|
||||
|
||||
Otevřít soubor z vašeho počítače.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Možnosti zobrazení
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Skrýt výškový profil, aby se vytvořilo místo pro mapu, nebo ho zobrazit a
|
||||
|
||||
Přepnout rozvržení stromu pro [seznam souborů](../files-and-stats).
|
||||
Toto zobrazení je ideální pro správu velkého počtu otevřených souborů, protože je uspořádá do svislého seznamu na pravé straně mapy.
|
||||
Kromě jiného umožňuje zobrazení stromu souborů zkontrolovat [trasy, segmenty, a body zájmu](../gpx) obsažené uvnitř souborů skrze sbalitelné sekce.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Přepnout na předchozí základní mapu
|
||||
|
||||
@@ -38,6 +38,14 @@ Zobrazují se pro aktuální výběr, jako je [výškový profil](../files-and-s
|
||||
|
||||
Přepnout viditelnost směrových šipek na mapě.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Přepnout 3D
|
||||
|
||||
Zapnout nebo vypnout 3D zobrazení mapy.
|
||||
|
||||
@@ -16,7 +16,7 @@ Tento nástroj umožňuje extrahovat [trasy (nebo úseky)](../gpx) ze souborů (
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Použití nástroje na soubor obsahující více tras vytvoří nový soubor pro každou z tras, kterou obsahuje.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Obdobně použití nástroje na trasu obsahující více úseků vytvoří (ve stejném souboru) novou trasu pro každý úsek, který obsahuje.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Sloučit
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
Chcete-li použít tento nástroj, musíte [vybrat](../files-and-stats) více souborů, [tras nebo segmentů](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- Pokud je vaším cílem vytvořit z vašeho výběru jednu spojitou stopu, použijte možnost **Připojit stopy** a potvrďte.
|
||||
- Druhá možnost může být použita k vytvoření nebo správě souborů s více [trasami nebo segmenty](../gpx).
|
||||
Sloučením souborů (nebo tras) vznikne jeden soubor (nebo trasa) obsahující všechny trasy (nebo úseky) z výběru.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ Chcete-li rozdělit vybranou stopu na dvě části, klikněte na jednu z rozděl
|
||||
Chcete-li rozdělit stopu v určitém bodě dle vašeho výběru, najeďte nad stopu na mapě.
|
||||
Na pozici kurzoru se objeví nůžky, což znázorňuje, že v tomto bodě můžete stopu rozdělit.
|
||||
|
||||
Můžete zvolit rozdělení stopy na dva GPX soubory, nebo uchovat rozdělené části ve stejném souboru jako [trasy nebo segmenty](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Najetí kurzorem nad vybranou stopu změní kurzor v nůžky." />
|
||||
@@ -51,7 +51,7 @@ By right-clicking on a file tab, you can access the same actions as in the [edit
|
||||
|
||||
As mentioned in the [view options section](./menu/view), you can switch to a tree layout for the files list.
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
You can also apply [edit actions](./menu/edit) and [tools](./toolbar) to internal file items.
|
||||
Furthermore, you can drag and drop the inner items to reorder them, or move them in the hierarchy or even to another file.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: GPX file format
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
The <a href="https://www.topografix.com/gpx.asp" target="_blank">GPX file format</a> is an open standard for exchanging GPS data between applications and GPS devices.
|
||||
It essentially consists of a series of GPS points encoding one or multiple GPS traces, and, optionally, some points of interest.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
GPX files may also contain metadata, of which the **name** and **description** fields are the most useful for users.
|
||||
|
||||
@@ -27,6 +29,20 @@ These are organized in a hierarchical structure, with tracks at the top level.
|
||||
In most cases, GPX files contain a single track with a single segment.
|
||||
However, the hierarchy described above allows for more advanced use cases, such as planning multi-day trips with several variants for each day.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Points of interest
|
||||
|
||||
**Points of interest** (technically called _waypoints_) represent locations of interest to show either on a GPS device or on a digital map.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Edit actions
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Unlike the file actions, the edit actions can potentially modify the content of the currently selected files.
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, and points of interest](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Therefore, we will refer to the elements that can be modified by these actions as _file items_.
|
||||
Note that except for the undo and redo actions, the edit actions are also accessible through the context menu (right-click) of the file items.
|
||||
|
||||
@@ -27,10 +27,6 @@ Open the information dialog of the currently selected file item, where you can s
|
||||
|
||||
Open the appearance dialog, where you can change the color, opacity, and width of the selected file items on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New track
|
||||
|
||||
Create a new track in the selected file.
|
||||
@@ -42,6 +38,17 @@ Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
This action is only available when the tree layout of the files list is enabled.
|
||||
Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New segment
|
||||
|
||||
Create a new segment in the selected track.
|
||||
@@ -53,14 +60,17 @@ Additionally, the selection must be a single track.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Select all
|
||||
|
||||
Add all file items in the current hierarchy level to the selection.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Kopier
|
||||
|
||||
Copy the selected file items to the clipboard.
|
||||
|
||||
@@ -15,6 +15,10 @@ The file actions menu contains a set of pretty self-explanatory file operations.
|
||||
|
||||
Create a new empty file.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Open...
|
||||
|
||||
Open files from your computer.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: View options
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Hide the elevation profile to make room for the map, or show it to inspect the c
|
||||
|
||||
Toggle the tree layout for the [file list](../files-and-stats).
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Switch to previous basemap
|
||||
|
||||
@@ -38,6 +38,14 @@ They are displayed for the current selection, like the [elevation profile](../fi
|
||||
|
||||
Toggle the visibility of direction arrows on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Toggle 3D
|
||||
|
||||
Enter or exit the 3D map view.
|
||||
|
||||
@@ -16,7 +16,7 @@ This tool allows you to extract [tracks (or segments)](../gpx) from files (or tr
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Applying the tool to a file containing multiple tracks will create a new file for each of the tracks it contains.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Similarly, applying the tool to a track containing multiple segments will create (in the same file) a new track for each of the segments it contains.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Merge
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, or segments](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- If your goal is to create a single continuous trace from your selection, use the **Connect the traces** option and validate.
|
||||
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ To split the selected trace into two parts, click on one of the split markers di
|
||||
To split at a specific point of your choice, hover over the trace on the map.
|
||||
Scissors will appear at the cursor position, showing that you can split the trace at that point.
|
||||
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks or segments](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Hovering over the selected trace turns your cursor into scissors." />
|
||||
@@ -51,7 +51,7 @@ Wenn du mit der rechten Maustaste auf einen Datei-Tab klickst, kannst du auf die
|
||||
|
||||
Wie schon in den Anzeigeoptionen (/Menü/Ansicht) erwähnt, kann man das Layout des Dateibaums für die Dateienliste anpassen.
|
||||
Dieses Layout eignet sich am besten um für eine große Anzahl von Dateien, da es diese in einer vertikalen Liste auf der rechten Seite der Karte anordnet.
|
||||
Darüber hinaus ermöglicht die Dateibaumansicht die Prüfung der [Tracks, Segmente, und Punkte von Interesse](./gpx), die in den Dateien durch zusammenklappbare Abschnitte enthalten sind.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
Du kannst auch [Bearbeiten von Aktionen](./menu/edit) und [tools](./toolbar) auf interne Dateielemente anwenden.
|
||||
Außerdem können Sie die inneren Elemente per Drag & Drop verschieben, um sie neu zu ordnen oder in die Hierarchie oder sogar in eine andere Datei zu verschieben.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: GPX Dateiformat
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Das <a href="https://www.topografix.com/gpx.asp" target="_blank">GPX-Dateiformat</a> ist ein offener Standard für den Austausch von GPS-Daten zwischen Anwendungen und GPS-Geräten.
|
||||
Es besteht im Wesentlichen aus einer Serie von GPS-Punkten, die einen oder mehrere GPS-Spuren kodieren, und optional aus einigen interessanten Punkten.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
GPX-Dateien können auch Metadaten enthalten, von denen die Felder **Name** und **Beschreibung** für Benutzer am nützlichsten sind.
|
||||
|
||||
@@ -27,6 +29,20 @@ Diese sind in einer hierarchischen Struktur mit Spuren auf der obersten Ebene or
|
||||
In den meisten Fällen enthalten GPX-Dateien einen einzigen Track mit einem Segment.
|
||||
Die oben beschriebene Hierarchie erlaubt jedoch weitergehende Anwendungsfälle, wie zum Beispiel die Planung von Mehrtagestouren mit mehreren Varianten pro Tag.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Punkte von Interesse
|
||||
|
||||
**Points of Interessive** (technisch genannt _Wegpunkte_) stellen Orte dar, die auf einem GPS-Gerät oder auf einer digitalen Karte angezeigt werden.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Bearbeite Aktionen
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Im Gegensatz zu den Datei-Aktionen können die Bearbeitungs-Aktionen möglicherweise den Inhalt der aktuell ausgewählten Dateien verändern.
|
||||
Wenn das vertikale Layout der Dateiliste aktiviert ist (siehe [Dateien und Statistik](.. /files-and-stats)), können dieses auch auf [Tracks, Segmente und Points-of-Interest](../gpx) angewendet werden.
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Daher bezeichnen wir die Elemente, die durch diese Aktionen geändert werden können, als _file items_.
|
||||
Beachte, dass die Bearbeitungsaktionen auch über das Kontextmenü (Rechtsklick) der Dateielemente zugänglich sind, ausgenommen von "rückgängig" und "wiederholen".
|
||||
|
||||
@@ -27,10 +27,6 @@ Dies gilt für alle Aktionen der Bedienoberfläche, aber nicht für Optionen, An
|
||||
|
||||
Öffnen Sie das Dialogfeld Darstellung, in der Sie die Farbe, Deckkraft und Breite der ausgewählten Dateielemente auf der Karte ändern können.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Einblenden/Ausblenden
|
||||
|
||||
Konfigurieren Sie die Sichtbarkeit der ausgewählten Dateielemente auf der Karte.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Neuer Track
|
||||
|
||||
Erstelle ein neues Segment im ausgewählten Track.
|
||||
@@ -42,6 +38,17 @@ Außerdem muss die Auswahl aus einem einzelnen Track bestehen.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Diese Aktion ist nur verfügbar, wenn das vertikale Layout der Dateiliste aktiviert ist.
|
||||
Außerdem muss die Auswahl aus einem einzelnen Track bestehen.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Neues Segment
|
||||
|
||||
Erstelle ein neues Segment im ausgewählten Track.
|
||||
@@ -53,14 +60,17 @@ Außerdem muss die Auswahl aus einem einzelnen Track bestehen.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Alle auswählen
|
||||
|
||||
Alle Dateielemente in der aktuellen Hierarchieebene zur Auswahl hinzufügen.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Zentrieren
|
||||
|
||||
Zentrieren der Karte auf den ausgewählten Dateielementen.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Kopieren
|
||||
|
||||
Kopieren Sie die ausgewählten Dateielemente in die Zwischenablage.
|
||||
|
||||
@@ -15,6 +15,10 @@ Das Aktionsmenü Datei enthält eine Reihe von selbsterklärenden Dateioperation
|
||||
|
||||
Neue leere Datei erstellen.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Öffnen...
|
||||
|
||||
Öffne Dateien von Ihrem Computer.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Anzeigeoptionen
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Das Höhenprofil ausblenden, um Platz für die Karte zu schaffen, oder um die ak
|
||||
|
||||
Umschalten des vertikalen Layouts für die [Dateiliste](../files-and-stats).
|
||||
Dieses Layout eignet sich am besten um für eine große Anzahl von Dateien, da es diese in einer vertikalen Liste auf der rechten Seite der Karte anordnet.
|
||||
Darüber hinaus ermöglicht die Dateibaumansicht die Prüfung der [Tracks, Segmente, und Punkte von Interesse](../gpx), die in den Dateien durch zusammenklappbare Abschnitte enthalten sind.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Wechseln zur vorherigen Basiskarte
|
||||
|
||||
@@ -38,6 +38,14 @@ Sie werden für die aktuelle Auswahl angezeigt, wie in [elevation profile](../fi
|
||||
|
||||
Schaltet die Sichtbarkeit der Richtungspfeile auf der Karte um.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> 3D Ein/Aus
|
||||
|
||||
Aufrufen oder Beenden der 3D-Kartenansicht.
|
||||
|
||||
@@ -19,6 +19,6 @@ Mit diesem Tool kannst du Höhendaten zu Routen und [Points of Interest] (../gpx
|
||||
<DocsNote>
|
||||
|
||||
Elevation data is provided by <a href="https://mapterhorn.com" target="_blank">Mapterhorn</a>.
|
||||
You can learn more about its origin and accuracy in the <a href="https://mapterhorn.com/attribution/" target="_blank">documentation</a>.
|
||||
Mehr über die Herkunft und Genauigkeit erfährst du in der <a href="https://mapterhorn.com/attribution/" target="_blank">Dokumentation</a>.
|
||||
|
||||
</DocsNote>
|
||||
@@ -16,7 +16,7 @@ Dieses Werkzeug ermöglicht Ihnen [Tracks (oder Segmente)](../gpx) von Dateien (
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Wenn Sie das Werkzeug auf eine Datei mit mehreren enthaltenen Tracks anwenden, wird für jeden der darin enthaltenen Tracks eine neue Datei erstellt.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Gleichermaßen gilt: wenn du das Werkzeug auf einen Track anwendest, der mehrere Segmente enthält, wird (in derselben Datei) ein neuer Track für jedes der darin enthaltenen Segmente erstellt.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Zusammenfügen
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
Um dieses Tool nutzen zu können, musst du [select](../files-and-stats) mehrere Dateien, [Tracks oder Segmente](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- Wenn Ihr Ziel darin besteht, eine einzige kontinuierliche Spur aus Ihrer Auswahl zu erstellen, verwenden Sie die Option **Verbinden Sie die Spuren** und validieren Sie sie.
|
||||
- Die zweite Option kann verwendet werden, um Dateien mit mehreren [Tracks oder Segments](../gpx) zu erstellen oder zu verwalten.
|
||||
Das Zusammenführen von Dateien (oder Tracks) führt zu einer einzigen Datei (oder Track), die alle Tracks (oder Segmente) aus der Auswahl enthält.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ Um die ausgewählte Spur in zwei Teile aufzuteilen, klicken Sie auf einen der ge
|
||||
Um sich an einem bestimmten Punkt Ihrer Wahl zu teilen, fahren Sie über die Spur auf der Karte.
|
||||
Schere werden an der Cursorposition angezeigt, so dass Sie die Spur an diesem Punkt teilen können.
|
||||
|
||||
Du kannst die Trace in zwei GPX-Dateien aufteilen oder die Teile in der gleichen Datei wie [Tracks oder Segmente](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Über dem ausgewählten Pfad schweben verwandelt den Cursor in Scheren." />
|
||||
@@ -51,7 +51,7 @@ By right-clicking on a file tab, you can access the same actions as in the [edit
|
||||
|
||||
As mentioned in the [view options section](./menu/view), you can switch to a tree layout for the files list.
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
You can also apply [edit actions](./menu/edit) and [tools](./toolbar) to internal file items.
|
||||
Furthermore, you can drag and drop the inner items to reorder them, or move them in the hierarchy or even to another file.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: GPX file format
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
The <a href="https://www.topografix.com/gpx.asp" target="_blank">GPX file format</a> is an open standard for exchanging GPS data between applications and GPS devices.
|
||||
It essentially consists of a series of GPS points encoding one or multiple GPS traces, and, optionally, some points of interest.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
GPX files may also contain metadata, of which the **name** and **description** fields are the most useful for users.
|
||||
|
||||
@@ -27,6 +29,20 @@ These are organized in a hierarchical structure, with tracks at the top level.
|
||||
In most cases, GPX files contain a single track with a single segment.
|
||||
However, the hierarchy described above allows for more advanced use cases, such as planning multi-day trips with several variants for each day.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Points of interest
|
||||
|
||||
**Points of interest** (technically called _waypoints_) represent locations of interest to show either on a GPS device or on a digital map.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Edit actions
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Unlike the file actions, the edit actions can potentially modify the content of the currently selected files.
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, and points of interest](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Therefore, we will refer to the elements that can be modified by these actions as _file items_.
|
||||
Note that except for the undo and redo actions, the edit actions are also accessible through the context menu (right-click) of the file items.
|
||||
|
||||
@@ -27,10 +27,6 @@ Open the information dialog of the currently selected file item, where you can s
|
||||
|
||||
Open the appearance dialog, where you can change the color, opacity, and width of the selected file items on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New track
|
||||
|
||||
Create a new track in the selected file.
|
||||
@@ -42,6 +38,17 @@ Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
This action is only available when the tree layout of the files list is enabled.
|
||||
Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New segment
|
||||
|
||||
Create a new segment in the selected track.
|
||||
@@ -53,14 +60,17 @@ Additionally, the selection must be a single track.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Select all
|
||||
|
||||
Add all file items in the current hierarchy level to the selection.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Copy
|
||||
|
||||
Copy the selected file items to the clipboard.
|
||||
|
||||
@@ -15,6 +15,10 @@ The file actions menu contains a set of pretty self-explanatory file operations.
|
||||
|
||||
Create a new empty file.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Open...
|
||||
|
||||
Open files from your computer.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: View options
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Hide the elevation profile to make room for the map, or show it to inspect the c
|
||||
|
||||
Toggle the tree layout for the [file list](../files-and-stats).
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Switch to previous basemap
|
||||
|
||||
@@ -38,6 +38,14 @@ They are displayed for the current selection, like the [elevation profile](../fi
|
||||
|
||||
Toggle the visibility of direction arrows on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Toggle 3D
|
||||
|
||||
Enter or exit the 3D map view.
|
||||
|
||||
@@ -16,7 +16,7 @@ This tool allows you to extract [tracks (or segments)](../gpx) from files (or tr
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Applying the tool to a file containing multiple tracks will create a new file for each of the tracks it contains.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Similarly, applying the tool to a track containing multiple segments will create (in the same file) a new track for each of the segments it contains.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Merge
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, or segments](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- If your goal is to create a single continuous trace from your selection, use the **Connect the traces** option and validate.
|
||||
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ To split the selected trace into two parts, click on one of the split markers di
|
||||
To split at a specific point of your choice, hover over the trace on the map.
|
||||
Scissors will appear at the cursor position, showing that you can split the trace at that point.
|
||||
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks or segments](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Hovering over the selected trace turns your cursor into scissors." />
|
||||
@@ -67,12 +67,6 @@ Since a route has a single segment, a track with multiple segments is converted
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Except for whole files, this action is only available when the tree layout of the files list is enabled.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Select all
|
||||
|
||||
Add all file items in the current hierarchy level to the selection.
|
||||
|
||||
@@ -51,7 +51,7 @@ Al hacer clic con el botón derecho en una pestaña de archivo, puede acceder a
|
||||
|
||||
Como se mencionó en la [sección de opciones de vista](./menu/view), puede cambiar a un esquema de árbol para la lista de archivos.
|
||||
Esta disposición es ideal para administrar un gran número de archivos abiertos ya que los organiza en una lista vertical en la parte derecha del mapa.
|
||||
Además, la vista de árbol de archivos permite inspeccionar [pistas, segmentos y puntos de interés](./gpx) contenidos dentro de los archivos a través de secciones plegables.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
También puede aplicar [acciones de edición](./menu/edit) y [herramientas](./toolbar) a los elementos internos del archivo.
|
||||
Además, puede arrastrar y soltar los elementos internos para reordenarlos, o moverlos dentro de la jerarquía o incluso a otro archivo.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: Formato de archivo GPX
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
El <a href="https://www.topografix.com/gpx.asp" target="_blank">formato de archivo GPX</a> es un estándar abierto para el intercambio de datos GPS entre aplicaciones y dispositivos GPS.
|
||||
Consiste esencialmente en una serie de puntos GPS que codifican uno o varios trazados GPS y, opcionalmente, algunos puntos de interés.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
Los archivos GPX también pueden contener metadatos, de los cuales los campos **nombre** y **descripción** son los más útiles para los usuarios.
|
||||
|
||||
@@ -27,6 +29,20 @@ Como se mencionó anteriormente, un archivo GPX puede contener múltiples trazad
|
||||
En la mayoría de los casos, los archivos GPX contienen una sola pista con un solo segmento.
|
||||
Sin embargo, la jerarquía descrita anteriormente permite casos de uso más avanzados, como la planificación de viajes de varios días con varias variantes para cada día.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Puntos de interés
|
||||
|
||||
**Puntos de interés** (técnicamente llamados _waypoints_) representan ubicaciones de interés para mostrar en un dispositivo GPS o en un mapa digital.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Acciones de edición
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
A diferencia de las acciones de archivo, las acciones de edición pueden potencialmente modificar el contenido de los archivos seleccionados actualmente.
|
||||
Además, cuando está habilitada la disposición del árbol de la lista de archivos (ver [Archivos y estadísticas](../files-and-stats)), se pueden aplicar a [pistas, segmentos y puntos de interés](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Por lo tanto, nos referiremos a los elementos que pueden ser modificados por estas acciones como _elementos de archivo_.
|
||||
Tenga en cuenta que, excepto para las acciones de deshacer y rehacer, las acciones de edición también son accesibles a través del menú contextual (clic derecho) de los elementos del archivo.
|
||||
|
||||
@@ -27,10 +27,6 @@ Abre el cuadro de diálogo de información del elemento del archivo seleccionado
|
||||
|
||||
Abre el diálogo de apariencia, donde puede cambiar el color, la opacidad y el ancho en el mapa de los elementos de archivo seleccionados.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Ocultar/mostrar
|
||||
|
||||
Alterna la visibilidad de los elementos del archivo seleccionado en el mapa.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nueva pista
|
||||
|
||||
Crear una nueva pista en el archivo seleccionado.
|
||||
@@ -42,6 +38,17 @@ Además, la selección debe ser un solo archivo.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Esta acción sólo está disponible cuando está habilitada la disposición del árbol de la lista de archivos.
|
||||
Además, la selección debe ser un solo archivo.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nuevo segmento
|
||||
|
||||
Crear un nuevo segmento en la pista seleccionada.
|
||||
@@ -53,14 +60,17 @@ Además, la selección debe ser una sola pista.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Seleccionar todo
|
||||
|
||||
Añade todos los elementos del archivo en el nivel de jerarquía actual a la selección.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Centrar
|
||||
|
||||
Centrar el mapa en los elementos de archivo seleccionados.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Copiar
|
||||
|
||||
Copia los elementos del archivo seleccionado al portapapeles.
|
||||
|
||||
@@ -15,6 +15,10 @@ El menú de acciones de archivo contiene un conjunto de operaciones bastante aut
|
||||
|
||||
Crea un nuevo archivo vacío.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Abrir...
|
||||
|
||||
Abre archivos desde su equipo.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Opciones de vista
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Oculta el perfil de elevación para dejar espacio para el mapa, o lo muestra par
|
||||
|
||||
Alterna el diseño de árbol para la [lista de archivos](../files-and-stats).
|
||||
Esta disposición es ideal para administrar un gran número de archivos abiertos ya que los organiza en una lista vertical en la parte derecha del mapa.
|
||||
Además, la vista de árbol de archivos le permite inspeccionar [pistas, segmentos y puntos de interés](../gpx) contenidos dentro de los archivos a través de secciones plegables.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Cambiar al mapa base anterior
|
||||
|
||||
@@ -38,6 +38,14 @@ Se muestran para la selección actual, como el [perfil de elevación](../files-a
|
||||
|
||||
Alterna la visibilidad de las flechas de dirección en el mapa.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Alternar 3D
|
||||
|
||||
Entra o sale de la vista 3D del mapa.
|
||||
|
||||
@@ -16,7 +16,7 @@ Esta herramienta le permite extraer [pistas (o segmentos)](../gpx) de archivos (
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Aplicar la herramienta a un archivo que contenga múltiples pistas creará un nuevo archivo para cada una de las pistas.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Del mismo modo, la aplicación de la herramienta a una pista que contenga varios segmentos creará (en el mismo archivo) una nueva pista para cada uno de los segmentos.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Combinar
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
Para usar esta herramienta, necesita [seleccionar](../files-and-stats) múltiples archivos, [pistas o segmentos](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- Si su objetivo es crear un único trazado continuo a partir de su selección, use la opción **Conectar los trazados** y valide.
|
||||
- La segunda opción se puede usar para crear o gestionar archivos con múltiples [pistas o segmentos](../gpx).
|
||||
Combinar archivos (o pistas) dará como resultado un único archivo (o pista) que contendrá todas las pistas (o segmentos) de la selección.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ Para dividir el trazado seleccionado en dos partes, haga clic en uno de los marc
|
||||
Para dividir en un punto específico de su elección, sitúe el cursor sobre el trazado en el mapa.
|
||||
Las tijeras aparecerán en la posición del cursor, mostrando que puede dividir el trazado en ese punto.
|
||||
|
||||
Puede elegir dividir el trazado en dos archivos GPX o mantener las partes divididas en el mismo archivo como [pistas o segmentos](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Situar el cursor sobre el trazado seleccionado lo convierte en tijeras." />
|
||||
@@ -50,7 +50,7 @@ Fitxategi fitxa batean eskuin-klik eginez, [editatu menuko](./menu/edit) ekintza
|
||||
|
||||
Aipatu bezala, [ikusi atalean](./menu/view), fitxategien zerrendaren zuhaitz diseinura alda dezakezu.
|
||||
Diseinu hau aproposa da irekitako fitxategi ugari kudeatzeko, maparen eskuinaldean zerrenda bertikal batean antolatzen baitu.
|
||||
Gainera, fitxategi-zuhaitzaren ikuspegiak aukera ematen du [arrastoak, segmentuak eta interesguneak](./gpx) ikuskatzeko ataletan antolatutako fitxategietan.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
[Edizio ekintzak](./menu/edit) eta [tresnak](./toolbar) fitxategien barneko elementuetara ere aplikatu ditzakezu.
|
||||
Gainera, barruko elementuak arrastatu eta jaregin ditzakezu berrantolatzeko edo hierarkian edo beste fitxategi batera eraman.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: GPX fitxategi formatua
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
<a href="https://www.topografix.com/gpx.asp" target="_blank">GPX fitxategi formatua</a> GPS datuak aplikazioen eta GPS gailuen artean trukatzeko formatu ireki estandarra da.
|
||||
GPS puntu serie bat GPS arrasto bat edo gehiagotan bildua, eta, aukeran, interesgune batzuk.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
GPX fitxategiek metadatuak ere izan ditzakete, horien artean **izena** eta **deskribapena** eremuak erabiltzaileentzat erabilgarrienak izaten dira.
|
||||
|
||||
@@ -27,6 +29,20 @@ Egitura hierarkiko batean antolatuta daude, goiko mailan:
|
||||
Gehienetan, GPX fitxategiek segmentu bakarraz osatutako arrasto edo ibilbide bakarra dute.
|
||||
Hala ere, goian deskribatutako hierarkiak erabilera kasu aurreratuagoak egiteko aukera ematen du, hala nola egun anitzeko bidaiak antolatzea egun bakoitzeko hainbat aldaerekin.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Interesguneak
|
||||
|
||||
**Interesguneak** (_bide-puntu_ gisa ere ezagunak) interesgarriak diren kokapenak GPS gailu batean edo mapa digital batean erakusteko erabiltzen dira.
|
||||
|
||||
@@ -13,8 +13,8 @@ title: Integrazioa
|
||||
|
||||
Behar duzun guztia hau da:
|
||||
|
||||
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
|
||||
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
|
||||
1. Zure zerbitzarian edo Google Drive-n ostatatutako GPX fitxategiak, edo URL publiko baten bidez eskura daitezke;
|
||||
2. _Aukerakoa:_ <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler tekla</a> MapTiler mapak kargatzeko.
|
||||
|
||||
Ondoren, konfiguratzailearekin jolastu dezakezu zure mapa pertsonalizatzeko eta dagokion HTML kodea sortzeko.
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Maparen geruzen botoiak mapa-oinarri desberdinen artean aldatzeko aukera ematen
|
||||
<DocsLayers />
|
||||
<span class="text-sm text-center mt-2">
|
||||
|
||||
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the topographic basemap.
|
||||
Pasa ezazu maparen gainean <a href="https://hiking.waymarkedtrails.org" target="_blank">markatutako mendi-ibiliak</a> gainjartzeko oinarrizko mapa topografikoaren gainean.
|
||||
|
||||
</span>
|
||||
</div>
|
||||
@@ -69,4 +69,4 @@ Global eta tokiko mapa oinarri bilduma handia dago **gpx.studio**-n eskuragarri,
|
||||
|
||||
Ezarpen horietan, gainjartzeen opakutasuna ere kudeatu dezakezu.
|
||||
|
||||
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.
|
||||
Erabiltzaile aurreratuentzat, oinarrizko mapa pertsonalizatuak eta gainjartzeak gehi daitezke <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a> edo <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre estiloko JSON</a> URLak.
|
||||
@@ -3,14 +3,14 @@ title: Edizio ekintzak
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Fitxategi-ekintzak ez bezala, edizio-ekintzek hautatutako fitxategien edukia alda dezakete.
|
||||
Are gehiago, fitxategien zerrendaren zuhaitzaren diseinua gaituta dagoenean (Ikusi [Fitxategiak eta estatistikak](../files-and-stats)), ekintzek [arrasto, segmentu eta interesgunetan](../gpx) ere aplikatu daitezke.
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Hori dela eta, ekintza hauek alda dezaketen elementuei _fitxategi-elementuak_ izendatzen ditugu.
|
||||
Ohar zaitez, desegin eta berregin ekintzak ez, beste guztiak fitxategi-elementu hauen alboko menuaren bidez (eskuin-klik) ere erabili daitezke.
|
||||
|
||||
@@ -27,10 +27,6 @@ Ireki hautatutako fitxategiaren elementuaren elkarrizketa-koadroa, non bere izen
|
||||
|
||||
Ireki Itxuraren elkarrizketa-koadroa, eta bertan mapan hautatutako elementuaren kolorea, opakotasuna eta zabalera alda ditzakezu.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Ezkutatu/erakutsi
|
||||
|
||||
Aldatu mapan hautatutako elementuen ikusgarritasuna.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Arrasto berria
|
||||
|
||||
Sortu arrasto berri bat hautatutako fitxategian.
|
||||
@@ -42,6 +38,17 @@ Gainera, fitxategi bakarra egon behar du hautatuta.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Ekintza hau soilik aktibatuko da fitxategien zerrendako zuhaitz-geruza gaituta dagoenean.
|
||||
Gainera, fitxategi bakarra egon behar du hautatuta.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Segmentu berria
|
||||
|
||||
Sortu segmentu berri bat hautatutako arrastoan.
|
||||
@@ -53,14 +60,17 @@ Gainera, arrasto bakarra egon behar du hautatuta.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Hautatu denak
|
||||
|
||||
Gehitu hautapenera uneko hierarkia mailako fitxategi elementu guztiak.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Erdiratu
|
||||
|
||||
Erdiratu mapan hautatutako fitxategi-elementuak.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Kopiatu
|
||||
|
||||
Kopiatu arbelean hautatutako fitxategi-elementuak.
|
||||
|
||||
@@ -15,6 +15,10 @@ Fitxategi ekintzen menuan fitxategien eragiketa multzoa dago.
|
||||
|
||||
Sortu fitxategi huts berri bat.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Ireki...
|
||||
|
||||
Ireki zure ordenagailuko fitxategiak.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Ikusteko aukerak
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Ezkutatu altueren profila mapari lekua egiteko, edo erakutsi uneko hautapena iku
|
||||
|
||||
Aldatu [fitxategi zerrendaren](../files-and-stats) zuhaitza ikuspegira.
|
||||
Diseinu hau aproposa da irekitako fitxategi ugari kudeatzeko, maparen eskuinaldean zerrenda bertikal batean antolatzen baitu.
|
||||
Fitxategien zuhaitza ikuspegiak fitxategien barneko [ibilbideak, segmentuak eta interesguneak](../gpx) atal gisa erakusten ditu.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Aldatu erabilitako aurreko geruzara
|
||||
|
||||
@@ -38,6 +38,14 @@ Unean hautatutako ibilbidean ikusten dira, [koten profila bezala](../files-and-s
|
||||
|
||||
Aldatu ibilbidearen noranzko gezien ikusgarritasuna.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Aldatu 3D
|
||||
|
||||
Aktibatu edo desaktibatu maparen 3D ikuspegia.
|
||||
@@ -48,7 +56,6 @@ Maparen orientazioa eta okertzea kontrolatzeko, mapa ere arrasta dezakezu <kbd>
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Maximize2 size="16" class="inline-block" style="margin-bottom: 2px" /> Full screen
|
||||
### <Maximize2 size="16" class="inline-block" style="margin-bottom: 2px" /> Pantaila osoa
|
||||
|
||||
Enter or exit full screen mode.
|
||||
You can also press <kbd>F11</kbd> to toggle, or <kbd>Escape</kbd> to exit.
|
||||
Sartu edo irten pantaila osoko moduan.<kbd>F11</kbd> ere saka dezakezu txandakatzeko, edo <kbd>Ihes</kbd> irteteko.
|
||||
@@ -18,7 +18,7 @@ Tresna honen bidez elebazio datuak gehitzen ahal dira ibilbidetan eta [interesgu
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Elevation data is provided by <a href="https://mapterhorn.com" target="_blank">Mapterhorn</a>.
|
||||
You can learn more about its origin and accuracy in the <a href="https://mapterhorn.com/attribution/" target="_blank">documentation</a>.
|
||||
Altueraren datuak <a href="https://mapterhorn.com" target="_blank">Mapterhorn</a>k ematen ditu.
|
||||
Haren jatorriari eta zehaztasunari buruz gehiago jakin dezakezu <a href="https://mapterhorn.com/attribution/" target="_blank">dokumentazioan</a>.
|
||||
|
||||
</DocsNote>
|
||||
@@ -16,7 +16,7 @@ Tresna honen bidez [ibilbideak (edo segmentuak)](../gpx) erauzi ditzakezu fitxat
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Hainbat ibilbide dituen fitxategi bati aplikatuz gero dituen ibilbide bakoitzarentzat fitxategi bana sortuko du.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Era berean, hainbat segmentu duen ibilbide bati aplikatuz gero (fitxategi berean) duen segmentu bakoitzeko ibilbide bana sortuko du.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,16 +10,16 @@ title: Batu
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
Tresna hau erabiltzeko [hautatu](../files-and-stats) behar duzu hainbat fitxategi [ibilbidea, edo segmentuak](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- Zure helburua hautaketatik abiatuta etengabeko arrasto bakarra sortzea bada, erabili **Konektatu trazak ** aukera eta balioztatu.
|
||||
- Bigarren aukera bat izan daiteke hainbat [arrasto edo segmentu](../gpx) biltzen duen fitxategiak sortzea edo kudeatzea.
|
||||
Fitxategiak (edo arrastoak) batzearen emaitza fitxategi (edo arrasto) bakarra sortzea da zeinak hautapenaren arrastoak (edo segmentuak) biltzen duena.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Selected items are merged in the order they appear in the files list.
|
||||
Reorder items by drag-and-drop if needed.
|
||||
Hautatutako elementuak fitxategien zerrendan agertzen diren ordenan batzen dira.
|
||||
Berrantolatu elementuak arrastatu eta jareginez, behar izanez gero.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ Hautatutako ibilbidea bi zatitan banatzeko, egin klik arrastoko zatiketa markatz
|
||||
Puntu zehatz batean banatzeko, pasa ezazu maparen arrastoaren gainean.
|
||||
Artaziak agertuko dira kurtsorearen posizioan, puntu horretan ebaki dezakezula erakutsiz.
|
||||
|
||||
Arrastoa bi GPX fitxategietan zatitzea edo fitxategi berean zatiak [arrastoak edo segmentuak](../gpx) gisa gordetzea aukeratu dezakezu.
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Aukeratutako trazaren gainean kokatzean, zure kurtsorea guraize bihurtuko da." />
|
||||
@@ -51,7 +51,7 @@ By right-clicking on a file tab, you can access the same actions as in the [edit
|
||||
|
||||
As mentioned in the [view options section](./menu/view), you can switch to a tree layout for the files list.
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](./gpx) contained inside the files through collapsible sections.
|
||||
|
||||
You can also apply [edit actions](./menu/edit) and [tools](./toolbar) to internal file items.
|
||||
Furthermore, you can drag and drop the inner items to reorder them, or move them in the hierarchy or even to another file.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: GPX file format
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
The <a href="https://www.topografix.com/gpx.asp" target="_blank">GPX file format</a> is an open standard for exchanging GPS data between applications and GPS devices.
|
||||
It essentially consists of a series of GPS points encoding one or multiple GPS traces, and, optionally, some points of interest.
|
||||
These traces can be stored either as tracks or as routes.
|
||||
|
||||
GPX files may also contain metadata, of which the **name** and **description** fields are the most useful for users.
|
||||
|
||||
@@ -27,6 +29,20 @@ These are organized in a hierarchical structure, with tracks at the top level.
|
||||
In most cases, GPX files contain a single track with a single segment.
|
||||
However, the hierarchy described above allows for more advanced use cases, such as planning multi-day trips with several variants for each day.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
A **route** is a single continuous path made of a sequence of **route points**.
|
||||
These are mandatory checkpoints, which correspond to the anchor points of the [routing tool](./toolbar/routing).
|
||||
The points in between are only a suggested path connecting the route points.
|
||||
Like a track, a route can have a **name**, a **description**, and **appearance properties**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Nowadays, most apps and GPS devices work perfectly well with tracks, even for navigation.
|
||||
However, some of them only work with routes: in that case, you can [convert](./menu/edit) your tracks to routes, or [create](./menu/file) a route file from the start.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Points of interest
|
||||
|
||||
**Points of interest** (technically called _waypoints_) represent locations of interest to show either on a GPS device or on a digital map.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Edit actions
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Unlike the file actions, the edit actions can potentially modify the content of the currently selected files.
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, and points of interest](../gpx).
|
||||
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, routes, and points of interest](../gpx).
|
||||
Therefore, we will refer to the elements that can be modified by these actions as _file items_.
|
||||
Note that except for the undo and redo actions, the edit actions are also accessible through the context menu (right-click) of the file items.
|
||||
|
||||
@@ -27,10 +27,6 @@ Open the information dialog of the currently selected file item, where you can s
|
||||
|
||||
Open the appearance dialog, where you can change the color, opacity, and width of the selected file items on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New track
|
||||
|
||||
Create a new track in the selected file.
|
||||
@@ -42,6 +38,17 @@ Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route
|
||||
|
||||
Create a new route in the selected file.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
This action is only available when the tree layout of the files list is enabled.
|
||||
Additionally, the selection must be a single file.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New segment
|
||||
|
||||
Create a new segment in the selected track.
|
||||
@@ -53,14 +60,17 @@ Additionally, the selection must be a single track.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convert to route/track
|
||||
|
||||
Convert the selected routes to tracks, or the selected tracks or segments to routes.
|
||||
Since a route has a single segment, a track with multiple segments is converted into one route per segment.
|
||||
|
||||
When the selected files contain only routes, or only tracks, you can also convert all of them at once.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Select all
|
||||
|
||||
Add all file items in the current hierarchy level to the selection.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Copy
|
||||
|
||||
Copy the selected file items to the clipboard.
|
||||
|
||||
@@ -15,6 +15,10 @@ The file actions menu contains a set of pretty self-explanatory file operations.
|
||||
|
||||
Create a new empty file.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> New route file
|
||||
|
||||
Create a new file containing an empty [route](../gpx).
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Open...
|
||||
|
||||
Open files from your computer.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: View options
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Hide the elevation profile to make room for the map, or show it to inspect the c
|
||||
|
||||
Toggle the tree layout for the [file list](../files-and-stats).
|
||||
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
In addition, the file tree view enables you to inspect the [tracks, segments, routes, and points of interest](../gpx) contained inside the files through collapsible sections.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Switch to previous basemap
|
||||
|
||||
@@ -38,6 +38,14 @@ They are displayed for the current selection, like the [elevation profile](../fi
|
||||
|
||||
Toggle the visibility of direction arrows on the map.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Hide/unhide
|
||||
|
||||
Toggle the visibility of the selected file items on the map.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Center
|
||||
|
||||
Center the map on the selected file items.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Toggle 3D
|
||||
|
||||
Enter or exit the 3D map view.
|
||||
|
||||
@@ -16,7 +16,7 @@ This tool allows you to extract [tracks (or segments)](../gpx) from files (or tr
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Applying the tool to a file containing multiple tracks will create a new file for each of the tracks it contains.
|
||||
Applying the tool to a file containing multiple [tracks or routes](../gpx) will create a new file for each of the tracks and routes it contains.
|
||||
Similarly, applying the tool to a track containing multiple segments will create (in the same file) a new track for each of the segments it contains.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Merge
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, or segments](../gpx).
|
||||
To use this tool, you need to [select](../files-and-stats) multiple files, [tracks, segments or routes](../gpx).
|
||||
|
||||
- If your goal is to create a single continuous trace from your selection, use the **Connect the traces** option and validate.
|
||||
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
|
||||
- The second option can be used to create or manage files with multiple [tracks, segments or routes](../gpx).
|
||||
Merging files (or tracks) will result in a single file (or track) containing all tracks and routes (or segments) from the selection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ To split the selected trace into two parts, click on one of the split markers di
|
||||
To split at a specific point of your choice, hover over the trace on the map.
|
||||
Scissors will appear at the cursor position, showing that you can split the trace at that point.
|
||||
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks or segments](../gpx).
|
||||
You can choose to split the trace into two GPX files, or to keep the split parts in the same file as [tracks, segments or routes](../gpx).
|
||||
|
||||
<DocsImage src="tools/split" alt="Hovering over the selected trace turns your cursor into scissors." />
|
||||
@@ -51,7 +51,7 @@ En faisant un clic droit sur un onglet de fichier, vous pouvez accéder aux mêm
|
||||
|
||||
Comme mentionné dans la [section des options d'affichage](./menu/view), vous pouvez basculer vers une présentation en arborescence pour la liste des fichiers.
|
||||
Cette disposition est idéale pour gérer un grand nombre de fichiers ouverts, car elle les organise en une liste verticale à droite de la carte.
|
||||
De plus, la vue en arborescence des fichiers vous permet d'inspecter les [traces, segments, et points d'intérêt](./gpx) contenus dans les fichiers grâce à des sections pliables.
|
||||
De plus, la vue en arborescence des fichiers vous permet d'inspecter les [traces, segments, routes, et points d'intérêt](./gpx) contenus dans les fichiers grâce à des sections pliables.
|
||||
|
||||
Vous pouvez également appliquer les [actions d'édition](./menu/edit) et les [outils](./toolbar) aux éléments intérieurs aux fichiers.
|
||||
De plus, vous pouvez glisser-déposer les éléments intérieurs pour les réorganiser, ou les déplacer dans la hiérarchie ou même dans un autre fichier.
|
||||
|
||||
@@ -3,13 +3,15 @@ title: Format de fichier GPX
|
||||
---
|
||||
|
||||
<script>
|
||||
import { Waypoints, MapPin } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
import { Waypoints, Route, MapPin } from '@lucide/svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Le format de fichier <a href="https://www.topografix.com/gpx.asp" target="_blank">GPX</a> est un standard ouvert pour l'échange de données GPS entre applications et appareils GPS.
|
||||
Il consiste principalement en une série de points GPS encodant une ou plusieurs traces GPS, et, éventuellement, certains points d'intérêt.
|
||||
Ces traces peuvent être stockées sous forme de pistes ou de routes.
|
||||
|
||||
Les fichiers GPX peuvent aussi contenir des métadonnées, dont les champs **nom** et **description** sont les plus utiles pour les utilisateurs.
|
||||
|
||||
@@ -27,6 +29,20 @@ Celles-ci sont organisées en une structure hiérarchique, dont les pistes const
|
||||
Dans la plupart des cas, les fichiers GPX contiennent une seule piste avec un seul segment.
|
||||
Cependant, la hiérarchie décrite ci-dessus permet des cas d'utilisation plus avancés, comme la planification de voyages de plusieurs jours avec plusieurs variantes pour chaque jour.
|
||||
|
||||
### <Route size="16" class="inline-block" style="margin-bottom: 2px" /> Routes
|
||||
|
||||
Une **route** est un chemin continu constitué d'une séquence de **points de passage**.
|
||||
Ces points de passage correspondent aux points d'ancrage de [l'outil de routage](./toolbar/routing).
|
||||
Les points intermédiaires entre les points de passage ne sont qu'un chemin suggéré pour les relier.
|
||||
Comme une piste, une route peut avoir un **nom**, une **description**, et des **propriétés d'apparence**.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
De nos jours, la plupart des applications et des appareils GPS fonctionnent parfaitement avec les pistes, même pour la navigation.
|
||||
Cependant, certains d'entre eux ne fonctionnent qu'avec des routes : dans ce cas, vous pouvez [convertir](./menu/edit) vos pistes en routes, ou [créer](./menu/file) un fichier route dès le début.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <MapPin size="16" class="inline-block" style="margin-bottom: 2px" /> Points d'intérêt
|
||||
|
||||
Les **points d'intérêt** (techniquement appelés _waypoints_) représentent des lieux d'intérêt à afficher sur un appareil GPS ou sur une carte numérique.
|
||||
|
||||
@@ -3,14 +3,14 @@ title: Actions d'édition
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { Undo2, Redo2, Info, PaintBucket, EyeOff, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Maximize, Plus } from '@lucide/svelte';
|
||||
import { Undo2, Redo2, Info, PaintBucket, ArrowRightLeft, FileStack, ClipboardCopy, Scissors, ClipboardPaste, Trash2, Plus } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
# { title }
|
||||
|
||||
Contrairement aux actions sur les fichiers, les actions d'édition peuvent potentiellement modifier le contenu des fichiers sélectionnés.
|
||||
De plus, lorsque la présentation en arborescence de la liste de fichiers est activée (voir [Fichiers et statistiques](../files-and-stats)), elles peuvent également être appliquées aux [pistes, segments, et points d'intérêt](../gpx).
|
||||
De plus, lorsque la présentation en arborescence de la liste de fichiers est activée (voir [Fichiers et statistiques](../files-and-stats)), elles peuvent également être appliquées aux [pistes, segments, routes, et points d'intérêt](../gpx).
|
||||
Par conséquent, nous appellerons les éléments qui peuvent être modifiés par ces actions des _éléments de fichier_.
|
||||
Notez que, à part les actions annuler et rétablir, les actions d'édition sont également accessibles via le menu contextuel (clic droit) des éléments de fichier.
|
||||
|
||||
@@ -27,10 +27,6 @@ Ouvrir la boîte de dialogue d'information de l'élément de fichier actuellemen
|
||||
|
||||
Ouvrir la boîte de dialogue d'apparence, où vous pouvez changer la couleur, l'opacité et l'épaisseur des éléments de fichier sélectionnés sur la carte.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Cacher/afficher
|
||||
|
||||
Activer/désactiver la visibilité des éléments de fichier sélectionnés sur la carte.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nouvelle piste
|
||||
|
||||
Créer une nouvelle piste dans le fichier sélectionné.
|
||||
@@ -42,6 +38,17 @@ De plus, la sélection doit contenir un seul fichier.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nouvelle route
|
||||
|
||||
Créer une nouvelle route dans le fichier sélectionné.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
Cette action n'est disponible que lorsque la présentation en arborescence de la liste de fichiers est activée.
|
||||
De plus, la sélection doit contenir un seul fichier.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" /> Nouveau segment
|
||||
|
||||
Créer un nouveau segment dans la piste sélectionnée.
|
||||
@@ -53,14 +60,17 @@ De plus, la sélection doit contenir une seule piste.
|
||||
|
||||
</DocsNote>
|
||||
|
||||
### <ArrowRightLeft size="16" class="inline-block" style="margin-bottom: 2px" /> Convertir en fichier route/piste
|
||||
|
||||
Convertir les routes sélectionnées en pistes, ou les pistes ou segments sélectionnés en routes.
|
||||
Comme une route a un seul segment, une piste avec plusieurs segments est convertie en une route par segment.
|
||||
|
||||
Lorsque les fichiers sélectionnés ne contiennent que des routes, ou seulement des pistes, vous pouvez également les convertir tous en même temps.
|
||||
|
||||
### <FileStack size="16" class="inline-block" style="margin-bottom: 2px" /> Sélectionner tout
|
||||
|
||||
Ajouter tous les éléments de fichier du niveau de hiérarchie actuel à la sélection.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Centrer
|
||||
|
||||
Centrer la carte sur les éléments de fichier sélectionnés.
|
||||
|
||||
### <ClipboardCopy size="16" class="inline-block" style="margin-bottom: 2px" /> Copier
|
||||
|
||||
Copier les éléments de fichier sélectionnés dans le presse-papiers.
|
||||
|
||||
@@ -15,6 +15,10 @@ Le menu des actions sur les fichiers contient un ensemble d'opérations de fichi
|
||||
|
||||
Créer un nouveau fichier vide.
|
||||
|
||||
### <Plus size="16" class="inline-block" style="margin-bottom: 2px" />Nouveau fichier route
|
||||
|
||||
Créer un nouveau fichier contenant une [route](../gpx) vide.
|
||||
|
||||
### <FolderOpen size="16" class="inline-block" style="margin-bottom: 2px" /> Ouvrir...
|
||||
|
||||
Ouvrir des fichiers depuis votre ordinateur.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Options d'affichage
|
||||
---
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, Box, Maximize2 } from '@lucide/svelte';
|
||||
import { ChartArea, ListTree, Map, Layers2, Coins, Milestone, EyeOff, Maximize, Box, Maximize2 } from '@lucide/svelte';
|
||||
import DocsNote from '$lib/components/docs/DocsNote.svelte';
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@ Masquer le profil altimétrique pour faire de la place pour la carte, ou l'affic
|
||||
|
||||
Activer/désactiver la présentation en arborescence pour la [liste de fichiers](../files-and-stats).
|
||||
Cette disposition est idéale pour gérer un grand nombre de fichiers ouverts, car elle les organise en une liste verticale à droite de la carte.
|
||||
De plus, la vue en arborescence des fichiers vous permet d'inspecter les [traces, segments, et points d'intérêt](../gpx) contenus dans les fichiers grâce à des sections pliables.
|
||||
De plus, la vue en arborescence des fichiers vous permet d'inspecter les [traces, segments, routes, et points d'intérêt](../gpx) contenus dans les fichiers grâce à des sections pliables.
|
||||
|
||||
### <Map size="16" class="inline-block" style="margin-bottom: 2px" /> Basculer au fond de carte précédent
|
||||
|
||||
@@ -38,6 +38,14 @@ Ceux-ci sont affichés pour la sélection courante, comme le [profil altimétriq
|
||||
|
||||
Activer/désactiver la visibilité des flèches de direction sur la carte.
|
||||
|
||||
### <EyeOff size="16" class="inline-block" style="margin-bottom: 2px" /> Cacher/afficher
|
||||
|
||||
Activer/désactiver la visibilité des éléments de fichier sélectionnés sur la carte.
|
||||
|
||||
### <Maximize size="16" class="inline-block" style="margin-bottom: 2px" /> Centrer
|
||||
|
||||
Centrer la carte sur les éléments de fichier sélectionnés.
|
||||
|
||||
### <Box size="16" class="inline-block" style="margin-bottom: 2px" /> Activer/désactiver la 3D
|
||||
|
||||
Entrer ou sortir de la vue 3D de la carte.
|
||||
|
||||
@@ -16,7 +16,7 @@ Cet outil vous permet d'extraire [des pistes (ou des segments)](../gpx) de fichi
|
||||
<Extract class="text-foreground p-3 border rounded-md shadow-lg" />
|
||||
</div>
|
||||
|
||||
Appliquer l'outil à un fichier contenant plusieurs pistes créera un nouveau fichier pour chacune des pistes qu'il contient.
|
||||
Appliquer l'outil à un fichier contenant plusieurs [pistes ou routes](../gpx) créera un nouveau fichier pour chacune des pistes et routes qu'il contient.
|
||||
De même, appliquer l'outil à une piste contenant plusieurs segments créera (dans le même fichier) une nouvelle piste pour chacun des segments qu'elle contient.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
@@ -10,11 +10,11 @@ title: Fusionner
|
||||
|
||||
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
||||
Pour utiliser cet outil, vous devez [sélectionner](../files-and-stats) plusieurs fichiers, [pistes ou segments](../gpx).
|
||||
Pour utiliser cet outil, vous devez [sélectionner](../files-and-stats) plusieurs fichiers, [pistes, segments ou routes](../gpx).
|
||||
|
||||
- Si votre objectif est de créer une seule trace continue à partir de votre sélection, utilisez l'option **Connecter les traces** et validez.
|
||||
- La deuxième option peut être utilisée pour créer ou gérer des fichiers avec plusieurs [pistes ou segments](../gpx).
|
||||
Fusionner des fichiers (ou des pistes) résultera en un seul fichier (ou piste) contenant toutes les pistes (ou tous les segments) de la sélection.
|
||||
- La deuxième option peut être utilisée pour créer ou gérer des fichiers avec plusieurs [pistes, segments ou routes](../gpx).
|
||||
Fusionner des fichiers (ou des pistes) résultera en un seul fichier (ou piste) contenant toutes les pistes et routes (ou tous les segments) de la sélection.
|
||||
|
||||
<DocsNote>
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ Pour diviser la trace sélectionnée en deux parties, cliquez sur l'un des marqu
|
||||
Pour diviser à un point précis de votre choix, survolez la trace sur la carte.
|
||||
Des ciseaux apparaîtront à la position du curseur, montrant que vous pouvez diviser la trace à ce point.
|
||||
|
||||
Vous pouvez choisir de diviser la trace en deux fichiers GPX, ou de créer une [piste ou un segment](../gpx) pour chaque partie dans le même fichier.
|
||||
Vous pouvez choisir de diviser la trace en deux fichiers GPX, ou de créer [une piste, un segment ou une route](../gpx) pour chaque partie dans le même fichier.
|
||||
|
||||
<DocsImage src="tools/split" alt="Survoler la trace sélectionnée transforme votre curseur en ciseaux." />
|
||||
Loaded 100 of 343 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user