This commit is contained in:
Kelorastus
2026-09-16 11:42:24 +02:00
parent b7f3061be3
commit 3fdfde7043
18 changed files with 1485 additions and 21 deletions
+38
View File
@@ -4,6 +4,7 @@ import { backend } from "./backend";
import { errorText, hostOf } from "./format";
import { SIGN_IN_CANCELLED } from "./types";
import type {
ConflictChoice,
InstallProgress,
LogEvent,
LogLevel,
@@ -12,6 +13,7 @@ import type {
StackComponent,
StackEvent,
Status,
SyncConflict,
Unlisten,
} from "./types";
@@ -24,6 +26,7 @@ export type ActionKind =
| "signed_out_offline"
| "installing"
| "restoring"
| "syncing"
| "update"
| "launch"
| "stack"
@@ -45,6 +48,8 @@ export function deriveAction(s: Status | null): ActionKind {
return "installing";
case "restoring":
return "restoring";
case "syncing":
return "syncing";
case "launching":
case "running":
return "stack";
@@ -75,6 +80,7 @@ const LOCKED_PHASES: ReadonlySet<Phase> = new Set([
"signing_in",
"installing",
"restoring",
"syncing",
"launching",
"running",
]);
@@ -104,6 +110,10 @@ class Launcher {
busy = $state<string | null>(null);
refreshing = $state(false);
stopping = $state(false);
/** The sync review panel. */
syncOpen = $state(false);
/** Decisions waiting in this PC's database; null until the panel loads them. */
conflicts = $state<SyncConflict[] | null>(null);
notes = $state<NotesState>({
key: null,
loading: false,
@@ -117,6 +127,8 @@ class Launcher {
/** Settings can't change while something is installing or running. */
locked = $derived(this.status ? LOCKED_PHASES.has(this.status.phase) : false);
siteName = $derived(this.status ? (this.status.site.name ?? hostOf(this.status.site.url)) : "");
/** Conflicts plus refused edits from the last sync: what needs the operator. */
syncAttention = $derived(this.status?.sync ? this.status.sync.conflicts + this.status.sync.rejected : 0);
notesVersion = $derived(
this.status ? (this.status.online ? (this.status.available ?? this.status.installed) : this.status.installed) : null,
);
@@ -339,6 +351,32 @@ class Launcher {
});
}
syncNow() {
return this.run("sync", async () => {
await backend.syncNow();
// The panel lists what the sync just found (or settled).
if (this.syncOpen) this.conflicts = await backend.getSyncConflicts();
});
}
/** Opens the review panel and loads what waits for a decision. */
async openSync() {
this.syncOpen = true;
await this.run("conflicts", async () => {
this.conflicts = await backend.getSyncConflicts();
});
}
closeSync() {
this.syncOpen = false;
}
resolveConflict(conflict: SyncConflict, choice: ConflictChoice, value?: unknown) {
return this.run("resolve", async () => {
this.conflicts = await backend.resolveSyncConflict(conflict.tbl, conflict.key, choice, value);
});
}
async pickFolder(): Promise<string | null> {
try {
return await backend.pickFolder();