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
|
// Project management state
|
||||||
let currentProjectPath = $state<string | null>(null);
|
let currentProjectPath = $state<string | null>(null);
|
||||||
let currentProjectName = $state('');
|
let currentProjectName = $state('');
|
||||||
|
let projectIsV2 = $state(false);
|
||||||
let audioFilePath = $state('');
|
let audioFilePath = $state('');
|
||||||
let audioWavPath = $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() {
|
async function saveProject() {
|
||||||
const defaultName = currentProjectName || 'Untitled';
|
// Already saved as v2 folder — save in place
|
||||||
|
if (currentProjectPath && projectIsV2) {
|
||||||
// If already saved, save in place
|
|
||||||
if (currentProjectPath) {
|
|
||||||
const folderPath = currentProjectPath.replace(/[\\/][^\\/]+$/, '');
|
const folderPath = currentProjectPath.replace(/[\\/][^\\/]+$/, '');
|
||||||
const projectName = currentProjectPath.split(/[\\/]/).pop()?.replace(/\.vtn$/i, '') || defaultName;
|
await saveToFolder(folderPath);
|
||||||
const wavInsideFolder = `${folderPath}/audio.wav`;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const projectData = buildProjectData(projectName);
|
// V1 project opened — migrate to folder structure
|
||||||
try {
|
if (currentProjectPath && !projectIsV2) {
|
||||||
if (audioWavPath && audioWavPath !== wavInsideFolder) {
|
const oldVtnDir = currentProjectPath.replace(/[\\/][^\\/]+$/, '');
|
||||||
await invoke('copy_file', { src: audioWavPath, dst: wavInsideFolder });
|
const projectName = currentProjectPath.split(/[\\/]/).pop()?.replace(/\.vtn$/i, '') || 'Untitled';
|
||||||
}
|
const folderPath = `${oldVtnDir}/${projectName}`;
|
||||||
await invoke('save_project_file', { path: currentProjectPath, project: projectData });
|
const success = await saveToFolder(folderPath);
|
||||||
} catch (err) {
|
if (success) {
|
||||||
alert(`Failed to save: ${err}`);
|
// Optionally remove the old .vtn file
|
||||||
|
try {
|
||||||
|
// Leave old file — user can delete manually
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick a folder for the new project
|
// Never saved — pick a folder
|
||||||
|
await saveProjectAs();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveProjectAs() {
|
||||||
const folderPath = await open({
|
const folderPath = await open({
|
||||||
directory: true,
|
directory: true,
|
||||||
title: 'Choose a folder to save the project',
|
title: 'Choose a folder to save the project',
|
||||||
});
|
});
|
||||||
if (!folderPath) return;
|
if (!folderPath) return;
|
||||||
|
await saveToFolder(folderPath as string);
|
||||||
// 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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openProject() {
|
async function openProject() {
|
||||||
@@ -290,6 +299,7 @@
|
|||||||
// Determine the directory the .vtn file is in
|
// Determine the directory the .vtn file is in
|
||||||
const vtnDir = (filePath as string).replace(/[\\/][^\\/]+$/, '');
|
const vtnDir = (filePath as string).replace(/[\\/][^\\/]+$/, '');
|
||||||
const version = project.version ?? 1;
|
const version = project.version ?? 1;
|
||||||
|
projectIsV2 = version >= 2;
|
||||||
|
|
||||||
// Resolve audio for wavesurfer playback
|
// Resolve audio for wavesurfer playback
|
||||||
if (version >= 2) {
|
if (version >= 2) {
|
||||||
@@ -734,7 +744,10 @@
|
|||||||
</button>
|
</button>
|
||||||
{#if $segments.length > 0}
|
{#if $segments.length > 0}
|
||||||
<button class="settings-btn" onclick={saveProject}>
|
<button class="settings-btn" onclick={saveProject}>
|
||||||
Save Project
|
Save
|
||||||
|
</button>
|
||||||
|
<button class="settings-btn" onclick={saveProjectAs}>
|
||||||
|
Save As
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="import-btn" onclick={handleFileImport} disabled={isTranscribing}>
|
<button class="import-btn" onclick={handleFileImport} disabled={isTranscribing}>
|
||||||
|
|||||||
Reference in New Issue
Block a user