explorer actions

This commit is contained in:
vcoppe committed 2026-10-17 23:23:29 +02:00
1 parent adbf89e318
commit 3ef2bf135a
20 files changed
+1183 -65

No files matched your search

+7 -1
View File
@@ -211,7 +211,7 @@ export async function createTauriBackend(): Promise<Backend> {
desktop: {
workspace: () => invoke<WorkspaceState>('workspace_state'),
openFiles: () => invoke<Outcome>('open_files'),
openPaths: (paths) => invoke<Outcome>('open_paths', { paths }),
openPaths: (paths, preview) => invoke<Outcome>('open_paths', { paths, preview }),
openFolder: (unsaved) => invoke<Outcome>('open_folder', { unsaved }),
closeFolder: (unsaved) => invoke<Outcome>('close_folder', { unsaved }),
closeFiles: (fileIds, unsaved) => invoke<Outcome>('close_files', { fileIds, unsaved }),
@@ -222,6 +222,12 @@ export async function createTauriBackend(): Promise<Backend> {
invoke<boolean>('export_files', { fileIds, options }),
listFolder: (path) => invoke<FolderEntry[]>('list_folder', { path }),
openUrl: (url) => invoke('open_url', { url }),
newFile: (name, folder) => invoke<Outcome>('new_file', { name, folder }),
createFolder: (parent, name) => invoke<string>('create_folder', { parent, name }),
renamePath: (path, name) => invoke<Outcome>('rename_path', { path, name }),
movePaths: (paths, folder) => invoke('move_paths', { paths, folder }),
deletePaths: (paths) => invoke<Outcome>('delete_paths', { paths }),
revealPath: (path) => invoke('reveal_path', { path }),
listen: async (onEngineChanged, onWorkspaceChanged, onFilesOpened, onFolderChanged) => {
await listen<Outcome>('files-opened', (event) => onFilesOpened(event.payload));
await listen<Outcome>('engine-changed', (event) => onEngineChanged(event.payload));
+26 -2
View File
@@ -30,6 +30,8 @@ export type FileStatus = {
replaced: boolean;
/** The file on the disk is gone. */
missing: boolean;
/** The file is open as a preview (a single click in the tree), which the next one replaces. */
preview: boolean;
};
/** An entry of the tree of the open folder: a folder, or a GPX file. */
@@ -56,8 +58,12 @@ 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 files of the file system by their path, like the ones of the tree of the folder. With
* `preview`, a single file is open as a preview: the next preview replaces it, until it is
* edited or opened for good. Otherwise they are open for good.
*/
openPaths(paths: string[], preview?: boolean): Promise<Outcome>;
/**
* 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
@@ -91,6 +97,24 @@ export interface DesktopFiles {
listFolder(path?: string): Promise<FolderEntry[]>;
/** Opens a web page (or an email to write) in the app of the system for it. */
openUrl(url: string): Promise<void>;
/** Makes a new file, written at once in `folder` (a folder of the open folder), and selects it. */
newFile(name: string, folder: string): Promise<Outcome>;
/** Makes a folder in a folder of the open folder (numbered if the name is taken): its path. */
createFolder(parent: string, name: string): Promise<string>;
/**
* Renames a file or a folder of the open folder. An open file is renamed like in its info (it
* is selected), which can be undone.
*/
renamePath(path: string, name: string): Promise<Outcome>;
/** Moves files and folders of the open folder into a folder of it. */
movePaths(paths: string[], folder: string): Promise<void>;
/**
* Moves files and folders of the open folder to the trash. The open files among them are
* closed, unless they have edits.
*/
deletePaths(paths: string[]): Promise<Outcome>;
/** Shows a file or a folder in the file manager of the system. */
revealPath(path: string): Promise<void>;
/**
* 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, when files were opened with the
+81 -3
View File
@@ -562,9 +562,87 @@ class Engine {
return this.addedBy((b) => b.desktop!.openFiles());
}
/** 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));
/**
* Opens files of the file system by their path, which get selected: as a preview with
* `preview`, see `DesktopFiles.openPaths`. Resolves to the new ids.
*/
async openPaths(paths: string[], preview = false): Promise<string[]> {
const added = await this.addedBy((b) => b.desktop!.openPaths(paths, preview));
// which file is the preview may have changed, without a file changing
await this.enqueue(async () => this.refreshWorkspace(this.backend!));
return added;
}
// What the explorer does to the tree of the open folder (the desktop app). When it fails,
// `lastError` tells why (a key of the dictionaries, or a message).
/**
* Makes a new file in a folder of the open folder, which is written there at once and selected.
* Resolves to its id.
*/
async newFileIn(name: string, folder: string): Promise<string | undefined> {
const [id] = await this.addedBy((b) => b.desktop!.newFile(name, folder));
this._folderVersion.update((version) => version + 1);
return id;
}
/** Makes a folder in a folder of the open folder: resolves to its path. */
async createFolder(parent: string, name: string): Promise<string | undefined> {
return (await this.treeOperation((desktop) => desktop.createFolder(parent, name))).value;
}
/** Renames a file or a folder of the open folder. Resolves to whether it was done. */
async renamePath(path: string, name: string): Promise<boolean> {
const changed = await this.run((b) => b.desktop!.renamePath(path, name));
const failed = get(this._lastError) !== undefined;
await this.treeOperation(async () => {});
return changed || !failed;
}
/** Moves files and folders of the open folder into a folder of it. */
async movePaths(paths: string[], folder: string): Promise<boolean> {
return (await this.treeOperation((desktop) => desktop.movePaths(paths, folder))).done;
}
/** Moves files and folders of the open folder to the trash. */
async deletePaths(paths: string[]): Promise<boolean> {
const changed = await this.run((b) => b.desktop!.deletePaths(paths));
const failed = get(this._lastError) !== undefined;
await this.treeOperation(async () => {});
return changed || !failed;
}
/** Shows a file or a folder in the file manager of the system. */
async revealPath(path: string) {
await this.ready;
try {
await this.backend!.desktop?.revealPath(path);
} catch (error) {
console.error('engine:', error);
}
}
/**
* Does something to the file system, then reads where the files are and the tree again
* (without waiting for the watcher). Resolves to whether it was done, with what it gives.
*/
private async treeOperation<T>(
action: (desktop: DesktopFiles) => Promise<T>
): Promise<{ done: boolean; value?: T }> {
await this.ready;
return this.enqueue(async () => {
const backend = this.backend!;
let result: { done: boolean; value?: T } = { done: false };
try {
result = { done: true, value: await action(backend.desktop!) };
} catch (error) {
console.error('engine:', error);
this._lastError.set(String(error));
}
await this.refreshWorkspace(backend);
this._folderVersion.update((version) => version + 1);
return result;
});
}
/**