Enhance P2P synchronization features and UI improvements

This commit is contained in:
vorotamoroz
2026-05-17 01:36:09 +09:00
parent 9a90256a8a
commit 4ed1749652
8 changed files with 145 additions and 32 deletions

View File

@@ -13,17 +13,26 @@ export class P2POpenReplicationModal extends Modal {
callback?: P2POpenReplicationModalCallback;
component?: ReturnType<typeof mount>;
showResult: boolean;
title: string;
onClosed?: () => void;
rebuildMode: boolean;
constructor(
app: App,
liveSyncReplicator: LiveSyncTrysteroReplicator,
callback?: P2POpenReplicationModalCallback,
showResult: boolean = false
showResult: boolean = false,
title: string = "P2P Replication",
onClosed?: () => void,
rebuildMode: boolean = false
) {
super(app);
this.liveSyncReplicator = liveSyncReplicator;
this.callback = callback;
this.showResult = showResult;
this.title = title;
this.onClosed = onClosed;
this.rebuildMode = rebuildMode;
}
async onSync(peerId: string) {
@@ -41,7 +50,7 @@ export class P2POpenReplicationModal extends Modal {
override onOpen() {
const { contentEl } = this;
this.titleEl.setText("P2P Replication");
this.titleEl.setText(this.title);
contentEl.empty();
if (this.component === undefined) {
@@ -53,6 +62,7 @@ export class P2POpenReplicationModal extends Modal {
onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId),
onClose: () => this.close(),
showResult: this.showResult,
rebuildMode: this.rebuildMode,
},
});
}
@@ -65,5 +75,6 @@ export class P2POpenReplicationModal extends Modal {
void unmount(this.component);
this.component = undefined;
}
this.onClosed?.();
}
}

View File

@@ -19,9 +19,10 @@
onSyncAndClose: (_peerId: string) => Promise<void>;
onClose: () => void;
showResult: boolean;
rebuildMode?: boolean;
}
let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator }: Props = $props();
let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator, rebuildMode = false }: Props = $props();
let serverInfo = $state<P2PServerInfo | undefined>(undefined);
let syncingPeerId = $state<string | null>(null);
@@ -36,10 +37,10 @@
const unsubscribe = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => {
serverInfo = status;
});
fireAndForget(async ()=>{
fireAndForget(async () => {
await delay(100);
await requestServerStatus();
})
});
return unsubscribe;
});
@@ -55,6 +56,18 @@
syncingPeerId = null;
}
}
async function handleSyncThenClose(peerId: string) {
try {
syncingPeerId = peerId;
Logger(`Starting sync with ${peerId}`, logLevel);
await onSyncAndClose(peerId);
Logger(`Sync completed with ${peerId}`, logLevel);
} catch (e) {
Logger(`Error during sync: ${e instanceof Error ? e.message : String(e)}`, logLevel);
} finally {
syncingPeerId = null;
}
}
async function handleSyncAndClose(peerId: string) {
fireAndForget(async () => {
@@ -68,7 +81,7 @@
});
onClose();
}
async function disconnect(){
async function disconnect() {
try {
await liveSyncReplicator.close();
Logger("Signalling connection closed.", logLevel);
@@ -76,7 +89,7 @@
Logger(`Failed to close signalling connection: ${e instanceof Error ? e.message : String(e)}`, logLevel);
}
}
async function onCloseAndDisconnect(){
async function onCloseAndDisconnect() {
await disconnect();
onClose();
}
@@ -97,10 +110,7 @@
</script>
<div class="p2p-container">
<P2PServerStatusCard
{liveSyncReplicator}
showBroadcastToggle={false}
/>
<P2PServerStatusCard {liveSyncReplicator} showBroadcastToggle={false} />
<div class="peers-section">
<h3>Available Peers</h3>
@@ -121,20 +131,30 @@
</div>
</div>
<div class="peer-actions">
<button
class="btn btn-primary"
disabled={syncingPeerId !== null}
onclick={() => handleSync(peer.peerId)}
>
{syncingPeerId === peer.peerId ? "Syncing..." : "Sync"}
</button>
<button
class="btn btn-secondary"
disabled={syncingPeerId !== null}
onclick={() => handleSyncAndClose(peer.peerId)}
>
Start Sync &amp; Close
</button>
{#if !rebuildMode}
<button
class="btn btn-primary"
disabled={syncingPeerId !== null}
onclick={() => handleSync(peer.peerId)}
>
{syncingPeerId === peer.peerId ? "Syncing..." : "Sync"}
</button>
<button
class="btn {rebuildMode ? 'btn-primary' : 'btn-secondary'}"
disabled={syncingPeerId !== null}
onclick={() => handleSyncAndClose(peer.peerId)}
>
{syncingPeerId === peer.peerId ? "Syncing..." : "Start Sync & Close"}
</button>
{:else}
<button
class="btn {rebuildMode ? 'btn-primary' : 'btn-secondary'}"
disabled={syncingPeerId !== null}
onclick={() => handleSyncThenClose(peer.peerId)}
>
{syncingPeerId === peer.peerId ? "Syncing..." : "Sync"}
</button>
{/if}
</div>
</div>
{/each}
@@ -145,8 +165,12 @@
</div>
<div class="footer">
<button class="btn btn-cancel" onclick={onClose}>Close</button>
<button class="btn btn-cancel" onclick={onCloseAndDisconnect}>Close & Disconnect</button>
{#if rebuildMode}
<button class="btn btn-cancel" onclick={onClose} disabled={syncingPeerId !== null}>Skip and close</button>
{:else}
<button class="btn btn-cancel" onclick={onClose}>Close</button>
<button class="btn btn-cancel" onclick={onCloseAndDisconnect}>Close & Disconnect</button>
{/if}
</div>
</div>

View File

@@ -1,7 +1,7 @@
import { App } from "@/deps.ts";
import { Logger } from "@lib/common/logger";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types";
import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator";
import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator";
import { P2POpenReplicationModal } from "./P2POpenReplicationModal";
/**
@@ -71,3 +71,61 @@ export function createOpenReplicationUI(
});
};
}
/**
* Creates an openRebuildUI factory for Obsidian environments.
* Opens the P2P Replication modal in "rebuild" mode — one-way pull only,
* with setOnSetup / clearOnSetup bracketing the replicateFrom call.
*
* Usage:
* const factory = createOpenRebuildUI(app);
* useP2PReplicatorFeature(core, createOpenReplicationUI(app), factory);
*/
export function createOpenRebuildUI(
app: App
): (replicator: LiveSyncTrysteroReplicator) => (showResult: boolean) => Promise<boolean | void> {
return (replicator: LiveSyncTrysteroReplicator) =>
(showResult: boolean): Promise<boolean | void> => {
const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
return new Promise<boolean | void>((resolve) => {
let resolved = false;
const safeResolve = (val: boolean) => {
if (!resolved) {
resolved = true;
resolve(val);
}
};
const doRebuild = async (peerId: string) => {
replicator.setOnSetup();
try {
Logger(`Rebuilding from peer ${peerId}`, logLevel);
const result = await replicator.replicateFrom(peerId, showResult);
safeResolve(result?.ok ?? false);
} catch (e) {
Logger(
`Error in rebuild from ${peerId}: ${e instanceof Error ? e.message : String(e)}`,
logLevel
);
safeResolve(false);
} finally {
replicator.clearOnSetup();
}
};
const modal = new P2POpenReplicationModal(
app,
replicator,
{
onSync: doRebuild,
onSyncAndClose: doRebuild,
},
showResult,
"P2P Rebuild",
() => safeResolve(false),
true
);
modal.open();
});
};
}