From 30edfa17d249cf772f2bce8a7c38ed78886cfc44 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Wed, 1 Jul 2026 15:07:58 -0500 Subject: [PATCH] Add Calendar tab for PST import and browsing imported events (T6.3) --- src/lib/views/Settings.svelte | 107 ++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/src/lib/views/Settings.svelte b/src/lib/views/Settings.svelte index a81d6eb..4892a38 100644 --- a/src/lib/views/Settings.svelte +++ b/src/lib/views/Settings.svelte @@ -4,12 +4,37 @@ // is fully demonstrable. Replace nothing here when Phase 9 lands — it just starts persisting. import { onMount } from "svelte"; import { settings } from "../stores/settings.svelte"; + import { calendar } from "../stores/calendar.svelte"; import ConsentNotice from "../components/ConsentNotice.svelte"; + import { open } from "@tauri-apps/plugin-dialog"; import type { BackendId, SyncKind, SyncTargetConfig } from "../api"; let { onClose }: { onClose: () => void } = $props(); - let section = $state<"recording" | "hardware" | "storage" | "sync">("recording"); + let section = $state<"recording" | "hardware" | "storage" | "calendar" | "sync">("recording"); + + // ---- Calendar / .pst import (T6.1/T6.2/T6.3, FR-CAL-1/2) ---- + let pstPath = $state(""); + let pstPassword = $state(""); + + async function pickPstFile() { + const path = await open({ filters: [{ name: "Outlook data file", extensions: ["pst"] }] }); + if (typeof path === "string") pstPath = path; + } + async function doImportPst() { + if (!pstPath) return; + await calendar.importPst(pstPath, pstPassword || undefined); + pstPassword = ""; + } + function formatEventDate(unixSecs: number | null): string { + if (!unixSecs) return ""; + return new Date(unixSecs * 1000).toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }); + } // ---- Hardware + models (T3.6/T3.7/T3.9) ---- function fmtBytes(mb: number): string { @@ -23,7 +48,10 @@ let showConsent = $state(false); let testResult = $state(null); - onMount(() => settings.load()); + onMount(() => { + settings.load(); + calendar.load(); + }); // ---- Recording ---- function onToggleDefaultRecord(e: Event) { @@ -109,6 +137,9 @@ + @@ -253,6 +284,58 @@ > + {:else if section === "calendar"} +
+

Calendar & Outlook .pst

+

+ Import events and attendees from a local Outlook .pst backup — read-only, nothing + is written back to the file. Nothing leaves this device. +

+
+ + +
+
+ + {#if calendar.importProgress} + {calendar.importProgress.processed}/{calendar.importProgress.total} events + {/if} +
+ {#if calendar.importError} +

+ Import failed: {calendar.importError} — the file itself is untouched; check the path and try + again. +

+ {/if} + +

Imported events

+ {#if calendar.events.length === 0} +

No events imported yet.

+ {:else} + + {/if} +
{:else}

Sync & upload

@@ -547,12 +630,14 @@ margin: 0.5rem 0; } ul.targets, - ul.models { + ul.models, + ul.events { list-style: none; padding: 0; margin: 0.3rem 0; } - ul.models li { + ul.models li, + ul.events li { display: flex; align-items: center; gap: 0.6rem; @@ -560,6 +645,20 @@ border-bottom: 1px solid var(--border); font-size: 0.85rem; } + ul.events .name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .error { + color: #e11d48; + font-size: 0.85rem; + margin: 0.4rem 0; + } + input.grow { + flex: 1; + } ul.models .name { flex: 1; }