feat(export): Export/Import section in Settings → Storage (FR-STORE-4)

This commit is contained in:
iamdoubz committed 2026-07-11 22:43:51 -05:00
1 parent 6129866ac5
commit 23cfca7ea2
1 file changed
+68
+68
View File
@@ -5,6 +5,7 @@
import { onMount } from "svelte";
import { settings } from "../stores/settings.svelte";
import { calendar } from "../stores/calendar.svelte";
import { meetings } from "../stores/meetings.svelte";
import ConsentNotice from "../components/ConsentNotice.svelte";
import HostedAiBanner from "../components/HostedAiBanner.svelte";
import { open } from "@tauri-apps/plugin-dialog";
@@ -34,6 +35,8 @@
Globe,
Download,
Trash2,
FolderInput,
FolderOutput,
} from "@lucide/svelte";
import {
OLLAMA_OPTIONS,
@@ -503,6 +506,50 @@
settings.setRetentionPolicy(age, size);
}
// ---- Export / Import (FR-STORE-4) — move recordings between computers as
// portable "bundle" folders (audio + transcript + notes + summary +
// meeting.json manifest). Drop a bundle on any synced/removable drive, or a
// sync target's local mount, to carry it across. ----
let exportBusy = $state(false);
let importBusy = $state(false);
let ioStatus = $state<string | null>(null);
let ioError = $state(false);
async function exportAllMeetings() {
const dir = await open({ directory: true, title: "Export all meetings into…" });
if (typeof dir !== "string") return;
exportBusy = true;
ioStatus = null;
ioError = false;
try {
const n = await api.bulkExportMeetings(dir, "bundle");
ioStatus = `Exported ${n} meeting${n === 1 ? "" : "s"}.`;
} catch (e) {
ioStatus = errorMessage(e);
ioError = true;
} finally {
exportBusy = false;
}
}
async function importMeetings() {
const dir = await open({ directory: true, title: "Import a bundle (or a folder of bundles)…" });
if (typeof dir !== "string") return;
importBusy = true;
ioStatus = null;
ioError = false;
try {
const n = await api.importMeetingBundle(dir);
await meetings.load();
ioStatus = `Imported ${n} meeting${n === 1 ? "" : "s"}.`;
} catch (e) {
ioStatus = errorMessage(e);
ioError = true;
} finally {
importBusy = false;
}
}
// ---- Add-target form ----
const PROVIDERS = ["nextcloud", "owncloud", "cloudreve", "seafile", "synology", "generic"];
let form = $state<SyncTargetConfig>(blankForm("webdav"));
@@ -1022,6 +1069,27 @@
/></label
>
</div>
<h4>Export &amp; import</h4>
<p class="muted">
Move recordings between computers as portable bundle folders (audio, transcript, notes,
summary, and a <code>meeting.json</code> manifest). Export to any folder — a synced drive,
a USB stick, or a sync target's local mount — then import it on the other machine.
Imported meetings get a fresh id, so re-importing never overwrites anything.
</p>
<div class="row btns">
<button onclick={exportAllMeetings} disabled={exportBusy}>
<FolderOutput size={14} aria-hidden="true" />
{exportBusy ? "Exporting…" : "Export all meetings…"}
</button>
<button onclick={importMeetings} disabled={importBusy}>
<FolderInput size={14} aria-hidden="true" />
{importBusy ? "Importing…" : "Import meetings…"}
</button>
</div>
{#if ioStatus}
<p class={ioError ? "error" : "muted"}>{ioStatus}</p>
{/if}
</section>
{:else if section === "calendar"}
<section>