clarify name handling

This commit is contained in:
vcoppe committed 2026-10-27 17:44:59 +01:00
1 parent 45f877ec78
commit 3f8e2a0c56
14 files changed
+204 -251

No files matched your search

@@ -3,6 +3,7 @@
import { Input } from '$lib/components/ui/input';
import { Textarea } from '$lib/components/ui/textarea';
import { Label } from '$lib/components/ui/label/index.js';
import { Checkbox } from '$lib/components/ui/checkbox';
import * as Popover from '$lib/components/ui/popover';
import { Save } from '@lucide/svelte';
import { i18n } from '$lib/i18n/i18n.svelte';
@@ -30,9 +31,48 @@
node.type === 'file' ? (fileState.structure.desc ?? '') : (path?.desc ?? '')
);
const { workspace } = engine;
// the name of the file on the disk, without its extension (the desktop app), which is not the
// name of the file: it can be renamed with it, which is not undone
let fileName = $derived.by(() => {
if (node.type !== 'file') return undefined;
const path = $workspace?.files[node.fileId]?.path;
return path
?.split(/[\\/]/)
.pop()
?.replace(/\.[^.]*$/, '');
});
// asked only when the name is changed, and proposed then
let renamed = $derived(node.type === 'file' && name !== fileState.structure.name);
let renameFile = $state(true);
async function save() {
// closes the dialog now: `open` follows `editMetadata`, and the change of the file would
// open the dialog again before the effect below has reset it
editMetadata.current = false;
open = false;
// the file on the disk first: nothing is changed if it cannot be renamed (the name of
// another file, for instance)
if (
renamed &&
renameFile &&
fileName !== undefined &&
fileName !== name &&
node.type === 'file'
) {
if (!(await engine.renameFile(node.fileId, name))) {
showEngineError();
return;
}
}
engine.metadata(name, description);
}
$effect(() => {
if (!open) {
editMetadata.current = false;
renameFile = true;
}
});
</script>
@@ -44,22 +84,13 @@
<Input bind:value={name} id="name" class="font-semibold h-8" />
<Label for="description">{i18n._('menu.metadata.description')}</Label>
<Textarea bind:value={description} id="description" />
<Button
variant="outline"
onclick={() => {
// closes the dialog now: `open` follows `editMetadata`, and the change of the file
// would open the dialog again before the effect below has reset it
editMetadata.current = false;
open = false;
// the metadata of the selected elements are changed. In the desktop app, a file on
// the disk is renamed with it, which is refused if the name is the one of another
engine.metadata(name, description).then((changed) => {
if (!changed) {
showEngineError();
}
});
}}
>
{#if fileName !== undefined && renamed}
<div class="flex flex-row items-center gap-2">
<Checkbox id="rename-file" bind:checked={renameFile} class="scale-90" />
<Label for="rename-file">{i18n._('menu.metadata.rename_file')}</Label>
</div>
{/if}
<Button variant="outline" onclick={save}>
<Save size="16" />
{i18n._('menu.metadata.save')}
</Button>
+2 -1
View File
@@ -228,7 +228,8 @@ export async function createTauriBackend(): Promise<Backend> {
openUrl: (url) => invoke('open_url', { url }),
newFile: (name, folder, route) => invoke<Outcome>('new_file', { name, folder, route }),
createFolder: (parent, name) => invoke<string>('create_folder', { parent, name }),
renamePath: (path, name) => invoke<Outcome>('rename_path', { path, name }),
renamePath: (path, name) => invoke('rename_path', { path, name }),
renameFile: (fileId, name) => invoke<boolean>('rename_file', { fileId, name }),
movePaths: (paths, folder) => invoke('move_paths', { paths, folder }),
deletePaths: (paths) => invoke<Outcome>('delete_paths', { paths }),
revealPath: (path) => invoke('reveal_path', { path }),
+8 -3
View File
@@ -108,10 +108,15 @@ export interface DesktopFiles {
/** 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.
* Renames a file or a folder of the open folder, on the disk only (an open file keeps its name).
* Not undoable.
*/
renamePath(path: string, name: string): Promise<Outcome>;
renamePath(path: string, name: string): Promise<void>;
/**
* Renames the file on the disk of an open file, next to where it is; its name in the engine
* stays. Not undoable. False for a file that is not on the disk.
*/
renameFile(fileId: string, name: string): Promise<boolean>;
/** Moves files and folders of the open folder into a folder of it. */
movePaths(paths: string[], folder: string): Promise<void>;
/**
+14 -2
View File
@@ -626,9 +626,21 @@ class Engine {
.value;
}
/** Renames a file or a folder of the open folder. Resolves to whether it was done. */
/**
* Renames a file or a folder of the open folder, on the disk only: an open file keeps its name.
* Resolves to whether it was done.
*/
async renamePath(path: string, name: string): Promise<boolean> {
return this.desktopRun((b) => b.desktop!.renamePath(path, name), true);
return (await this.desktopCall((desktop) => desktop.renamePath(path, name), true)).done;
}
/**
* Renames the file on the disk of an open file (the desktop app), which is not undoable: its
* name in the engine stays (see `metadata`). Resolves to whether it was done; nothing is done
* for a file that is not on the disk.
*/
async renameFile(fileId: string, name: string): Promise<boolean> {
return (await this.desktopCall((desktop) => desktop.renameFile(fileId, name), true)).done;
}
/** Moves files and folders of the open folder into a folder of it. */
+1
View File
@@ -100,6 +100,7 @@
"name": "Name",
"description": "Description",
"save": "Save",
"rename_file": "Rename the file on disk",
"name_taken": "A file with this name already exists in the folder.",
"rename_failed": "The file could not be renamed."
},