Add Calendar tab for PST import and browsing imported events (T6.3)

This commit is contained in:
iamdoubz committed 2026-07-01 15:07:58 -05:00
1 parent f6be9645b8
commit 30edfa17d2
1 file changed
+103 -4
+103 -4
View File
@@ -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<string | null>(null);
onMount(() => settings.load());
onMount(() => {
settings.load();
calendar.load();
});
// ---- Recording ----
function onToggleDefaultRecord(e: Event) {
@@ -109,6 +137,9 @@
<button class:active={section === "storage"} onclick={() => (section = "storage")}
>Storage</button
>
<button class:active={section === "calendar"} onclick={() => (section = "calendar")}
>Calendar</button
>
<button class:active={section === "sync"} onclick={() => (section = "sync")}>Sync</button>
</nav>
<button class="close" onclick={onClose} aria-label="Close">✕</button>
@@ -253,6 +284,58 @@
>
</div>
</section>
{:else if section === "calendar"}
<section>
<h3>Calendar &amp; Outlook .pst</h3>
<p class="muted">
Import events and attendees from a local Outlook <code>.pst</code> backup — read-only, nothing
is written back to the file. Nothing leaves this device.
</p>
<div class="grid">
<label class="wide"
>.pst file
<div class="row">
<input class="grow" bind:value={pstPath} placeholder="No file selected" readonly />
<button onclick={pickPstFile}>Browse…</button>
</div>
</label>
<label
>Password <em>(rarely needed)</em>
<input type="password" bind:value={pstPassword} placeholder="optional" />
</label>
</div>
<div class="actions">
<button class="primary" onclick={doImportPst} disabled={!pstPath || calendar.importing}>
{calendar.importing ? "Importing…" : "Import"}
</button>
{#if calendar.importProgress}
<span class="muted"
>{calendar.importProgress.processed}/{calendar.importProgress.total} events</span
>
{/if}
</div>
{#if calendar.importError}
<p class="error">
Import failed: {calendar.importError} — the file itself is untouched; check the path and try
again.
</p>
{/if}
<h4>Imported events</h4>
{#if calendar.events.length === 0}
<p class="muted">No events imported yet.</p>
{:else}
<ul class="events">
{#each calendar.events as ev (ev.id)}
<li>
<span class="name">{ev.subject ?? "(untitled)"}</span>
<span class="muted">{formatEventDate(ev.starts_at)}</span>
{#if ev.organizer}<span class="muted">· {ev.organizer}</span>{/if}
</li>
{/each}
</ul>
{/if}
</section>
{:else}
<section>
<h3>Sync &amp; upload</h3>
@@ -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;
}