Add Save As, auto-migrate v1 projects to folder structure
Save behavior: - Save on v2 project: saves in place (no dialog) - Save on v1 project: auto-migrates to folder structure next to the original .vtn (creates ProjectName/ folder with .vtn + audio.wav) - Save on unsaved project: opens folder picker (Save As) - Save As: always opens folder picker for a new location Added projectIsV2 state to track project format version. Split "Save Project" button into "Save" + "Save As". Extracted saveToFolder() helper for shared save logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
// Project management state
|
||||
let currentProjectPath = $state<string | null>(null);
|
||||
let currentProjectName = $state('');
|
||||
let projectIsV2 = $state(false);
|
||||
let audioFilePath = $state('');
|
||||
let audioWavPath = $state('');
|
||||
|
||||
@@ -171,57 +172,65 @@
|
||||
};
|
||||
}
|
||||
|
||||
/** Save to a specific folder — creates .vtn + audio.wav inside it. */
|
||||
async function saveToFolder(folderPath: string): Promise<boolean> {
|
||||
const projectName = folderPath.split(/[\\/]/).pop() || currentProjectName || 'Untitled';
|
||||
const vtnPath = `${folderPath}/${projectName}.vtn`;
|
||||
const wavPath = `${folderPath}/audio.wav`;
|
||||
const projectData = buildProjectData(projectName);
|
||||
|
||||
try {
|
||||
await invoke('create_dir', { path: folderPath });
|
||||
if (audioWavPath && audioWavPath !== wavPath) {
|
||||
await invoke('copy_file', { src: audioWavPath, dst: wavPath });
|
||||
audioWavPath = wavPath;
|
||||
}
|
||||
await invoke('save_project_file', { path: vtnPath, project: projectData });
|
||||
currentProjectPath = vtnPath;
|
||||
currentProjectName = projectName;
|
||||
projectIsV2 = true;
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Failed to save project:', err);
|
||||
alert(`Failed to save: ${err}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProject() {
|
||||
const defaultName = currentProjectName || 'Untitled';
|
||||
|
||||
// If already saved, save in place
|
||||
if (currentProjectPath) {
|
||||
// Already saved as v2 folder — save in place
|
||||
if (currentProjectPath && projectIsV2) {
|
||||
const folderPath = currentProjectPath.replace(/[\\/][^\\/]+$/, '');
|
||||
const projectName = currentProjectPath.split(/[\\/]/).pop()?.replace(/\.vtn$/i, '') || defaultName;
|
||||
const wavInsideFolder = `${folderPath}/audio.wav`;
|
||||
await saveToFolder(folderPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const projectData = buildProjectData(projectName);
|
||||
try {
|
||||
if (audioWavPath && audioWavPath !== wavInsideFolder) {
|
||||
await invoke('copy_file', { src: audioWavPath, dst: wavInsideFolder });
|
||||
}
|
||||
await invoke('save_project_file', { path: currentProjectPath, project: projectData });
|
||||
} catch (err) {
|
||||
alert(`Failed to save: ${err}`);
|
||||
// V1 project opened — migrate to folder structure
|
||||
if (currentProjectPath && !projectIsV2) {
|
||||
const oldVtnDir = currentProjectPath.replace(/[\\/][^\\/]+$/, '');
|
||||
const projectName = currentProjectPath.split(/[\\/]/).pop()?.replace(/\.vtn$/i, '') || 'Untitled';
|
||||
const folderPath = `${oldVtnDir}/${projectName}`;
|
||||
const success = await saveToFolder(folderPath);
|
||||
if (success) {
|
||||
// Optionally remove the old .vtn file
|
||||
try {
|
||||
// Leave old file — user can delete manually
|
||||
} catch {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick a folder for the new project
|
||||
// Never saved — pick a folder
|
||||
await saveProjectAs();
|
||||
}
|
||||
|
||||
async function saveProjectAs() {
|
||||
const folderPath = await open({
|
||||
directory: true,
|
||||
title: 'Choose a folder to save the project',
|
||||
});
|
||||
if (!folderPath) return;
|
||||
|
||||
// Use the folder name as the project name
|
||||
const projectName = (folderPath as string).split(/[\\/]/).pop() || defaultName;
|
||||
const vtnInsideFolder = `${folderPath}/${projectName}.vtn`;
|
||||
const wavInsideFolder = `${folderPath}/audio.wav`;
|
||||
|
||||
const projectData = buildProjectData(projectName);
|
||||
|
||||
try {
|
||||
|
||||
// Copy the extracted WAV into the project folder
|
||||
if (audioWavPath) {
|
||||
await invoke('copy_file', { src: audioWavPath, dst: wavInsideFolder });
|
||||
}
|
||||
|
||||
// Save the .vtn file inside the folder
|
||||
await invoke('save_project_file', { path: vtnInsideFolder, project: projectData });
|
||||
|
||||
currentProjectPath = vtnInsideFolder;
|
||||
currentProjectName = projectName;
|
||||
} catch (err) {
|
||||
console.error('Failed to save project:', err);
|
||||
alert(`Failed to save: ${err}`);
|
||||
}
|
||||
await saveToFolder(folderPath as string);
|
||||
}
|
||||
|
||||
async function openProject() {
|
||||
@@ -290,6 +299,7 @@
|
||||
// Determine the directory the .vtn file is in
|
||||
const vtnDir = (filePath as string).replace(/[\\/][^\\/]+$/, '');
|
||||
const version = project.version ?? 1;
|
||||
projectIsV2 = version >= 2;
|
||||
|
||||
// Resolve audio for wavesurfer playback
|
||||
if (version >= 2) {
|
||||
@@ -734,7 +744,10 @@
|
||||
</button>
|
||||
{#if $segments.length > 0}
|
||||
<button class="settings-btn" onclick={saveProject}>
|
||||
Save Project
|
||||
Save
|
||||
</button>
|
||||
<button class="settings-btn" onclick={saveProjectAs}>
|
||||
Save As
|
||||
</button>
|
||||
{/if}
|
||||
<button class="import-btn" onclick={handleFileImport} disabled={isTranscribing}>
|
||||
|
||||
Reference in New Issue
Block a user