mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-19 09:22:12 +00:00
progress
This commit is contained in:
1 parent
005cc7b305
commit
b0a3c3a9ba
18 files changed
+1721
-714
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import type { Outcome, RouteAttributes, StorageOpened } from 'gpx-wasm';
|
||||
import type { Backend, WorkspaceState } from './types';
|
||||
import type { Backend, FolderEntry, WorkspaceState } from './types';
|
||||
|
||||
/** An array that a command returns as its bytes. */
|
||||
const array = async <T>(
|
||||
@@ -211,9 +211,16 @@ export async function createTauriBackend(): Promise<Backend> {
|
||||
desktop: {
|
||||
workspace: () => invoke<WorkspaceState>('workspace_state'),
|
||||
openFiles: () => invoke<Outcome>('open_files'),
|
||||
openPaths: (paths) => invoke<Outcome>('open_paths', { paths }),
|
||||
openFolder: (unsaved) => invoke<Outcome>('open_folder', { unsaved }),
|
||||
closeFolder: (unsaved) => invoke<Outcome>('close_folder', { unsaved }),
|
||||
saveFiles: (fileIds) => invoke('save_files', { fileIds }),
|
||||
closeFiles: (fileIds, unsaved) => invoke<Outcome>('close_files', { fileIds, unsaved }),
|
||||
saveFiles: (fileIds) => invoke<boolean>('save_files', { fileIds }),
|
||||
saveFileAs: (fileId) => invoke<boolean>('save_file_as', { fileId }),
|
||||
reloadFiles: (fileIds) => invoke<Outcome>('reload_files', { fileIds }),
|
||||
exportFiles: (fileIds, options) =>
|
||||
invoke<boolean>('export_files', { fileIds, options }),
|
||||
listFolder: (path) => invoke<FolderEntry[]>('list_folder', { path }),
|
||||
listen: async (onEngineChanged, onWorkspaceChanged, onFilesOpened) => {
|
||||
await listen<Outcome>('files-opened', (event) => onFilesOpened(event.payload));
|
||||
await listen<Outcome>('engine-changed', (event) => onEngineChanged(event.payload));
|
||||
|
||||
@@ -10,16 +10,33 @@ import type {
|
||||
TrackpointDetails,
|
||||
WaypointDetails,
|
||||
} from 'gpx-wasm';
|
||||
import type { MergeType, SelectMode, SplitType } from '../types';
|
||||
import type { ExportOptions, MergeType, SelectMode, SplitType } from '../types';
|
||||
|
||||
/** What the desktop app knows of a file of the engine and the file system. */
|
||||
export type FileStatus = {
|
||||
/** Where the file is on the disk, absent for a file that only is in the engine. */
|
||||
path?: string;
|
||||
/** The file has edits that the file on the disk does not have (or is not on the disk). */
|
||||
/**
|
||||
* The folder where the file is written the first time it is saved, for a file that is not on
|
||||
* the disk and comes from (or was made in) the open folder.
|
||||
*/
|
||||
folder?: string;
|
||||
/**
|
||||
* The file is not the one on the disk: it has edits, is not on the disk, or the file on the
|
||||
* disk is not the one that was read (it was replaced, or is gone).
|
||||
*/
|
||||
dirty: boolean;
|
||||
/** The file on the disk was changed by something else while this one had edits. */
|
||||
conflict: boolean;
|
||||
/** The file on the disk was replaced by something else: saving overwrites it. */
|
||||
replaced: boolean;
|
||||
/** The file on the disk is gone. */
|
||||
missing: boolean;
|
||||
};
|
||||
|
||||
/** An entry of the tree of the open folder: a folder, or a GPX file. */
|
||||
export type FolderEntry = {
|
||||
name: string;
|
||||
path: string;
|
||||
isDir: boolean;
|
||||
};
|
||||
|
||||
/** What to do with the files of a folder that have unsaved edits when it is closed. */
|
||||
@@ -39,22 +56,39 @@ export interface DesktopFiles {
|
||||
workspace(): Promise<WorkspaceState>;
|
||||
/** Opens the files that the user picks, which get selected. */
|
||||
openFiles(): Promise<Outcome>;
|
||||
/** Opens files of the file system by their path, like the ones of the tree of the folder. */
|
||||
openPaths(paths: string[]): Promise<Outcome>;
|
||||
/**
|
||||
* Opens the folder that the user picks: its files are opened, and it is watched. The folder
|
||||
* that was open is closed (see `closeFolder` for `unsaved`).
|
||||
* Opens the folder that the user picks, which is watched. Its files are not opened: they are
|
||||
* in its tree (`listFolder`). The folder that was open is closed with its files (see
|
||||
* `closeFiles` for `unsaved`).
|
||||
*/
|
||||
openFolder(unsaved?: UnsavedChoice): Promise<Outcome>;
|
||||
/**
|
||||
* Closes the open folder: it is not watched anymore, and its files are closed. What to do with
|
||||
* the ones that have unsaved edits: `save` them first, `discard` the edits, or keep them open
|
||||
* when it is not said. Rejects, and closes nothing, if they could not be saved.
|
||||
*/
|
||||
/** Closes the open folder: it is not watched anymore, and its files are closed. */
|
||||
closeFolder(unsaved?: UnsavedChoice): Promise<Outcome>;
|
||||
/**
|
||||
* Writes files to the disk, replacing what is there (the user is asked where to put the ones
|
||||
* that are not on the disk yet).
|
||||
* Closes files. What to do with the ones that have unsaved edits: `save` them first (the user
|
||||
* is asked where to put the ones that have no place), `discard` the edits, or keep them open
|
||||
* when it is not said.
|
||||
*/
|
||||
saveFiles(fileIds: string[]): Promise<void>;
|
||||
closeFiles(fileIds: string[], unsaved?: UnsavedChoice): Promise<Outcome>;
|
||||
/**
|
||||
* Writes files to the disk, replacing what is there (the user is asked where to put the ones
|
||||
* that have no place: a file, or a folder for several of them). Resolves to false if the user
|
||||
* cancelled.
|
||||
*/
|
||||
saveFiles(fileIds: string[]): Promise<boolean>;
|
||||
/** Writes a file where the user says, which is where it is from then on. False if cancelled. */
|
||||
saveFileAs(fileId: string): Promise<boolean>;
|
||||
/** Reads files again from the disk: their edits are lost (one undo step). */
|
||||
reloadFiles(fileIds: string[]): Promise<Outcome>;
|
||||
/**
|
||||
* Writes copies of files where the user says (a file, or a folder for several). The files stay
|
||||
* as they are. False if the user cancelled.
|
||||
*/
|
||||
exportFiles(fileIds: string[], options: ExportOptions): Promise<boolean>;
|
||||
/** The folders and GPX files in a folder of the open folder (the open folder by default). */
|
||||
listFolder(path?: string): Promise<FolderEntry[]>;
|
||||
/**
|
||||
* Tells when the engine changed without being asked, because the files on the disk did (the
|
||||
* outcome to apply), when the status of the files changed, and when files were opened with the
|
||||
|
||||
@@ -11,6 +11,7 @@ import { categoryIntervals } from '$lib/engine/trackpoint-categories';
|
||||
import { selectedElementIds, type FileTreeNode } from '$lib/engine/selection-helpers';
|
||||
import type { RouteAttributes } from 'gpx-wasm';
|
||||
import { loadBackend, type Backend } from './backend';
|
||||
import type { DesktopFiles } from './backend/types';
|
||||
import { readFileState } from './file-state';
|
||||
import { StatisticsReader } from './statistics';
|
||||
import { openStoredFiles } from './storage';
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
type SelectMode,
|
||||
type SplitType,
|
||||
type StatisticsRequest,
|
||||
type FolderEntry,
|
||||
type UnsavedChoice,
|
||||
type WorkspaceState,
|
||||
type TrackpointDetails,
|
||||
@@ -554,41 +556,88 @@ class Engine {
|
||||
return this.addedBy((b) => b.desktop!.openFiles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the folder that the user picks (instead of the one that was open, which is closed like
|
||||
* `closeFolder` does, `unsaved` being what to do with its unsaved files): its files are opened,
|
||||
* and it is watched: the files that appear, change or disappear in it are brought in the engine.
|
||||
* New files are saved in it. Resolves to the ids of the files that were added.
|
||||
*/
|
||||
async openFolder(unsaved?: UnsavedChoice): Promise<string[]> {
|
||||
return this.addedBy((b) => b.desktop!.openFolder(unsaved));
|
||||
/** Opens files of the file system by their path, which get selected. Resolves to the new ids. */
|
||||
async openPaths(paths: string[]): Promise<string[]> {
|
||||
return this.addedBy((b) => b.desktop!.openPaths(paths));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the open folder: it is not watched anymore, and its files are closed. `unsaved` says
|
||||
* what to do with the ones that have unsaved edits: they are saved first, or their edits are
|
||||
* discarded (they stay open when it is not said).
|
||||
* Opens the folder that the user picks (instead of the one that was open, which is closed with
|
||||
* its files like `closeFiles` does, `unsaved` being what to do with the unsaved ones). The
|
||||
* folder is watched, and its tree is read with `listFolder`: its files are not opened.
|
||||
*/
|
||||
async openFolder(unsaved?: UnsavedChoice) {
|
||||
await this.run((b) => b.desktop!.openFolder(unsaved));
|
||||
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
|
||||
}
|
||||
|
||||
/** Closes the open folder and all the files: see `closeFiles` for `unsaved`. */
|
||||
async closeFolder(unsaved?: UnsavedChoice) {
|
||||
await this.run((b) => b.desktop!.closeFolder(unsaved));
|
||||
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes files to the file system, replacing what is there; they are not edited anymore from
|
||||
* then on. The user is asked where to put the ones that are not on the file system yet.
|
||||
* Closes files. `unsaved` says what to do with the ones that have unsaved edits: they are saved
|
||||
* first, or their edits are lost (they stay open when it is not said).
|
||||
*/
|
||||
async save(fileIds: string[]) {
|
||||
async closeFiles(fileIds: string[], unsaved?: UnsavedChoice) {
|
||||
await this.run((b) => b.desktop!.closeFiles(fileIds, unsaved));
|
||||
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes files to the file system, replacing what is there; they are not edited anymore from
|
||||
* then on. The user is asked where to put the ones that have no place yet. Resolves to false
|
||||
* when the user cancelled or the files could not be saved.
|
||||
*/
|
||||
async save(fileIds: string[]): Promise<boolean> {
|
||||
return this.desktopCall((desktop) => desktop.saveFiles(fileIds));
|
||||
}
|
||||
|
||||
/** Writes a file where the user says, which is where it is from then on. */
|
||||
async saveAs(fileId: string): Promise<boolean> {
|
||||
return this.desktopCall((desktop) => desktop.saveFileAs(fileId));
|
||||
}
|
||||
|
||||
/** Writes copies of files where the user says. The files stay as they are. */
|
||||
async exportFiles(fileIds: string[], options: ExportOptions): Promise<boolean> {
|
||||
return this.desktopCall((desktop) => desktop.exportFiles(fileIds, options));
|
||||
}
|
||||
|
||||
/** Reads files again from the file system: their edits are lost (one undo step). */
|
||||
async reload(fileIds: string[]) {
|
||||
await this.run((b) => b.desktop!.reloadFiles(fileIds));
|
||||
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
|
||||
}
|
||||
|
||||
/** The folders and GPX files in a folder of the open folder (the open folder by default). */
|
||||
async listFolder(path?: string): Promise<FolderEntry[]> {
|
||||
await this.ready;
|
||||
await this.enqueue(async () => {
|
||||
const backend = this.backend!;
|
||||
return this.enqueue(async () => {
|
||||
try {
|
||||
await backend.desktop!.saveFiles(fileIds);
|
||||
return await this.backend!.desktop!.listFolder(path);
|
||||
} catch (error) {
|
||||
console.error('The files could not be saved', error);
|
||||
console.error('The folder could not be read', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Calls the file system of the desktop app, and keeps the status of the files up to date. */
|
||||
private async desktopCall(call: (desktop: DesktopFiles) => Promise<boolean>): Promise<boolean> {
|
||||
await this.ready;
|
||||
return this.enqueue(async () => {
|
||||
const backend = this.backend!;
|
||||
let done = false;
|
||||
try {
|
||||
done = await call(backend.desktop!);
|
||||
} catch (error) {
|
||||
console.error('The files could not be written', error);
|
||||
this._lastError.set(String(error));
|
||||
}
|
||||
await this.refreshWorkspace(backend);
|
||||
return done;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Visibility } from '$lib/engine/file-visibility';
|
||||
import type { CategoryIntervals } from '$lib/engine/trackpoint-categories';
|
||||
import type { FileStructure, GlobalStatistics } from 'gpx-wasm';
|
||||
|
||||
export type { FileStatus, UnsavedChoice, WorkspaceState } from './backend/types';
|
||||
export type { FileStatus, FolderEntry, UnsavedChoice, WorkspaceState } from './backend/types';
|
||||
|
||||
export type {
|
||||
Clipboard,
|
||||
|
||||
Reference in new issue
Block a user