mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-27 05:47:07 +00:00
refactor: compose browser application runtimes
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { onMount, setContext } from "svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { AutoAccepting, DEFAULT_SETTINGS, type P2PSyncSetting } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
AcceptedStatus,
|
||||
ConnectionStatus,
|
||||
type PeerStatus,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { P2PReplicatorPaneController } from "./P2PReplicatorPaneController";
|
||||
import type { P2PReplicatorPaneHost } from "./P2PReplicatorPaneHost";
|
||||
import PeerStatusRow from "@/features/P2PSync/P2PReplicator/PeerStatusRow.svelte";
|
||||
import { EVENT_LAYOUT_READY, eventHub } from "@/common/events";
|
||||
import { EVENT_LAYOUT_READY } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import {
|
||||
type PeerInfo,
|
||||
type P2PServerInfo,
|
||||
@@ -20,16 +20,16 @@
|
||||
import { $msg as _msg } from "@/common/translation";
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { generateP2PRoomId } from "@vrtmrz/livesync-commonlib/compat/common/utils";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
|
||||
interface Props {
|
||||
getCmdSync: () => P2PReplicatorPaneController;
|
||||
core: Pick<LiveSyncBaseCore, "services">;
|
||||
host: P2PReplicatorPaneHost;
|
||||
}
|
||||
|
||||
let { getCmdSync, core }: Props = $props();
|
||||
setContext("getReplicator", () => getCmdSync());
|
||||
const currentSettings = () => core.services.setting.currentSettings() as P2PSyncSetting;
|
||||
let { host }: Props = $props();
|
||||
let services = $derived(host.services);
|
||||
let events = $derived(services.context.events);
|
||||
const currentSettings = () => services.setting.currentSettings() as P2PSyncSetting;
|
||||
const currentReplicator = () => host.p2p.replicator;
|
||||
const initialSettings = { ...currentSettings() } as P2PSyncSetting;
|
||||
|
||||
let settings = $state<P2PSyncSetting>(initialSettings);
|
||||
@@ -81,7 +81,7 @@
|
||||
// P2P_AutoStart: eAutoStart,
|
||||
// P2P_AutoBroadcast: eAutoBroadcast,
|
||||
// };
|
||||
await core.services.setting.applyPartial(
|
||||
await services.setting.applyPartial(
|
||||
{
|
||||
P2P_Enabled: eP2PEnabled,
|
||||
P2P_relays: eRelay,
|
||||
@@ -94,7 +94,7 @@
|
||||
},
|
||||
true
|
||||
);
|
||||
core.services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, eDeviceName);
|
||||
services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, eDeviceName);
|
||||
deviceName = eDeviceName;
|
||||
}
|
||||
async function revert() {
|
||||
@@ -113,7 +113,7 @@
|
||||
const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => {
|
||||
if (force) {
|
||||
const initDeviceName =
|
||||
core.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? core.services.vault.getVaultName();
|
||||
services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? services.vault.getVaultName();
|
||||
deviceName = initDeviceName;
|
||||
eDeviceName = initDeviceName;
|
||||
}
|
||||
@@ -131,21 +131,22 @@
|
||||
settings = d;
|
||||
};
|
||||
onMount(() => {
|
||||
const r = eventHub.onEvent("setting-saved", async (d) => {
|
||||
const r = events.onEvent("setting-saved", async (d) => {
|
||||
applyLoadSettings(d, false);
|
||||
closeServer();
|
||||
});
|
||||
const rx = eventHub.onEvent(EVENT_LAYOUT_READY, () => {
|
||||
const rx = events.onEvent(EVENT_LAYOUT_READY, () => {
|
||||
applyLoadSettings(currentSettings(), true);
|
||||
});
|
||||
const r2 = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => {
|
||||
const r2 = events.onEvent(EVENT_SERVER_STATUS, (status) => {
|
||||
serverInfo = status;
|
||||
advertisements = status?.knownAdvertisements ?? [];
|
||||
});
|
||||
const r3 = eventHub.onEvent(EVENT_P2P_REPLICATOR_STATUS, (status) => {
|
||||
const r3 = events.onEvent(EVENT_P2P_REPLICATOR_STATUS, (status) => {
|
||||
replicatorInfo = status;
|
||||
});
|
||||
eventHub.emitEvent(EVENT_REQUEST_STATUS);
|
||||
applyLoadSettings(currentSettings(), true);
|
||||
events.emitEvent(EVENT_REQUEST_STATUS);
|
||||
return () => {
|
||||
r();
|
||||
rx();
|
||||
@@ -222,22 +223,22 @@
|
||||
}
|
||||
|
||||
async function openServer() {
|
||||
await getCmdSync().open();
|
||||
await currentReplicator().open();
|
||||
}
|
||||
async function closeServer() {
|
||||
await getCmdSync().close();
|
||||
await currentReplicator().close();
|
||||
}
|
||||
function startBroadcasting() {
|
||||
void getCmdSync().enableBroadcastChanges();
|
||||
currentReplicator().enableBroadcastChanges();
|
||||
}
|
||||
function stopBroadcasting() {
|
||||
void getCmdSync().disableBroadcastChanges();
|
||||
currentReplicator().disableBroadcastChanges();
|
||||
}
|
||||
|
||||
const initialDialogStatusKey = `p2p-dialog-status`;
|
||||
const getDialogStatus = () => {
|
||||
try {
|
||||
const initialDialogStatus = JSON.parse(core.services.config.getSmallConfig(initialDialogStatusKey) ?? "{}") as {
|
||||
const initialDialogStatus = JSON.parse(services.config.getSmallConfig(initialDialogStatusKey) ?? "{}") as {
|
||||
notice?: boolean;
|
||||
setting?: boolean;
|
||||
};
|
||||
@@ -254,10 +255,10 @@
|
||||
notice: isNoticeOpened,
|
||||
setting: isSettingOpened,
|
||||
};
|
||||
core.services.config.setSmallConfig(initialDialogStatusKey, JSON.stringify(dialogStatus));
|
||||
services.config.setSmallConfig(initialDialogStatusKey, JSON.stringify(dialogStatus));
|
||||
});
|
||||
let isObsidian = $derived.by(() => {
|
||||
return core.services.API.getPlatform() === "obsidian";
|
||||
return services.API.getPlatform() === "obsidian";
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -434,7 +435,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each peers as peer}
|
||||
<PeerStatusRow peerStatus={peer}></PeerStatusRow>
|
||||
<PeerStatusRow peerStatus={peer} p2p={host.p2p} showPeerMenu={host.showPeerMenu}></PeerStatusRow>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type {
|
||||
AcceptanceDecision,
|
||||
RevokeAcceptanceDecision,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/replication/trystero/TrysteroReplicatorP2PServer";
|
||||
|
||||
/** The operations used by the shared P2P pane, independent of its replicator implementation. */
|
||||
export interface P2PReplicatorPaneController {
|
||||
open(): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
enableBroadcastChanges(): void;
|
||||
disableBroadcastChanges(): void;
|
||||
makeDecision(decision: AcceptanceDecision): Promise<void>;
|
||||
revokeDecision(decision: RevokeAcceptanceDecision): Promise<void>;
|
||||
watchPeer(peerId: string): void;
|
||||
unwatchPeer(peerId: string): void;
|
||||
sync(peerId: string, showNotice?: boolean): Promise<unknown>;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { RequiredServices } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
||||
|
||||
export type P2PReplicatorHandle = Pick<UseP2PReplicatorResult, "replicator">;
|
||||
|
||||
/** Host capabilities consumed by the shared P2P pane. */
|
||||
export interface P2PReplicatorPaneHost {
|
||||
readonly services: RequiredServices<"API" | "config" | "setting" | "vault">;
|
||||
readonly p2p: P2PReplicatorHandle;
|
||||
readonly showPeerMenu?: (peer: PeerStatus, event: MouseEvent) => void;
|
||||
}
|
||||
@@ -2,12 +2,11 @@ import { Menu, WorkspaceLeaf } from "@/deps.ts";
|
||||
import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte";
|
||||
import { mount } from "svelte";
|
||||
import { SvelteItemView } from "@/common/SvelteItemView.ts";
|
||||
import { eventHub } from "@/common/events.ts";
|
||||
|
||||
import { unique } from "octagonal-wheels/collection";
|
||||
import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger";
|
||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||
import type { P2PPaneParams } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";
|
||||
export const VIEW_TYPE_P2P = "p2p-replicator";
|
||||
@@ -127,46 +126,45 @@ And you can also drop the local database to rebuild from the remote device.`,
|
||||
super(leaf);
|
||||
this.core = core;
|
||||
this._p2pResult = p2pResult;
|
||||
eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => {
|
||||
if (this.m) {
|
||||
this.m.hide();
|
||||
}
|
||||
this.m = new Menu()
|
||||
.addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer)))
|
||||
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer)))
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
item.setTitle("🔧 Get Configuration").onClick(async () => {
|
||||
await this.getRemoteConfig(peer);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
const mark = peer.syncOnConnect ? "checkmark" : null;
|
||||
item.setTitle("Toggle Sync on connect")
|
||||
.onClick(async () => {
|
||||
await this.toggleProp(peer, "syncOnConnect");
|
||||
})
|
||||
.setIcon(mark);
|
||||
})
|
||||
.addItem((item) => {
|
||||
const mark = peer.watchOnConnect ? "checkmark" : null;
|
||||
item.setTitle("Toggle Watch on connect")
|
||||
.onClick(async () => {
|
||||
await this.toggleProp(peer, "watchOnConnect");
|
||||
})
|
||||
.setIcon(mark);
|
||||
})
|
||||
.addItem((item) => {
|
||||
const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
|
||||
item.setTitle("Toggle Sync on `Replicate now` command")
|
||||
.onClick(async () => {
|
||||
await this.toggleProp(peer, "syncOnReplicationCommand");
|
||||
})
|
||||
.setIcon(mark);
|
||||
}
|
||||
|
||||
private showPeerMenu(peer: PeerStatus, event: MouseEvent): void {
|
||||
this.m?.hide();
|
||||
this.m = new Menu()
|
||||
.addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer)))
|
||||
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer)))
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
item.setTitle("🔧 Get Configuration").onClick(async () => {
|
||||
await this.getRemoteConfig(peer);
|
||||
});
|
||||
this.m.showAtPosition({ x: event.x, y: event.y });
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
const mark = peer.syncOnConnect ? "checkmark" : null;
|
||||
item.setTitle("Toggle Sync on connect")
|
||||
.onClick(async () => {
|
||||
await this.toggleProp(peer, "syncOnConnect");
|
||||
})
|
||||
.setIcon(mark);
|
||||
})
|
||||
.addItem((item) => {
|
||||
const mark = peer.watchOnConnect ? "checkmark" : null;
|
||||
item.setTitle("Toggle Watch on connect")
|
||||
.onClick(async () => {
|
||||
await this.toggleProp(peer, "watchOnConnect");
|
||||
})
|
||||
.setIcon(mark);
|
||||
})
|
||||
.addItem((item) => {
|
||||
const mark = peer.syncOnReplicationCommand ? "checkmark" : null;
|
||||
item.setTitle("Toggle Sync on `Replicate now` command")
|
||||
.onClick(async () => {
|
||||
await this.toggleProp(peer, "syncOnReplicationCommand");
|
||||
})
|
||||
.setIcon(mark);
|
||||
});
|
||||
void this.m.showAtPosition({ x: event.x, y: event.y });
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
@@ -187,8 +185,11 @@ And you can also drop the local database to rebuild from the remote device.`,
|
||||
return mount(ReplicatorPaneComponent, {
|
||||
target: target,
|
||||
props: {
|
||||
getCmdSync: () => this._p2pResult.replicator,
|
||||
core: this.core,
|
||||
host: {
|
||||
services: this.core.services,
|
||||
p2p: this._p2pResult,
|
||||
showPeerMenu: (peer: PeerStatus, event: MouseEvent) => this.showPeerMenu(peer, event),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { AcceptedStatus, type PeerStatus } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { P2PReplicatorPaneController } from "./P2PReplicatorPaneController";
|
||||
import { eventHub } from "@/common/events";
|
||||
import { EVENT_P2P_PEER_SHOW_EXTRA_MENU } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PReplicatorPaneCommon";
|
||||
import type { P2PReplicatorHandle } from "./P2PReplicatorPaneHost";
|
||||
|
||||
interface Props {
|
||||
peerStatus: PeerStatus;
|
||||
p2p: P2PReplicatorHandle;
|
||||
showPeerMenu?: (peer: PeerStatus, event: MouseEvent) => void;
|
||||
}
|
||||
|
||||
let { peerStatus }: Props = $props();
|
||||
let { peerStatus, p2p, showPeerMenu }: Props = $props();
|
||||
let peer = $derived(peerStatus);
|
||||
const currentReplicator = () => p2p.replicator;
|
||||
|
||||
function select<T extends string | number | symbol, U>(d: T, cond: Record<T, U>): U;
|
||||
function select<T extends string | number | symbol, U, V>(d: T, cond: Record<T, U>, def: V): U | V;
|
||||
function select<T extends string | number | symbol, U>(d: T, cond: Record<T, U>, def?: U): U | undefined {
|
||||
function select<T extends PropertyKey, U, V = undefined>(
|
||||
d: T,
|
||||
cond: Partial<Record<T, U>>,
|
||||
def: V | undefined = undefined
|
||||
): U | V | undefined {
|
||||
return d in cond ? cond[d] : def;
|
||||
}
|
||||
|
||||
@@ -36,7 +38,7 @@
|
||||
[AcceptedStatus.UNKNOWN]: "NEW",
|
||||
},
|
||||
""
|
||||
)
|
||||
) ?? ""
|
||||
);
|
||||
const classList = {
|
||||
["SENDING"]: "connected",
|
||||
@@ -57,7 +59,7 @@
|
||||
let isNew = $derived.by(() => peer.accepted === AcceptedStatus.UNKNOWN);
|
||||
|
||||
function makeDecision(isAccepted: boolean, isTemporary: boolean) {
|
||||
getReplicator().makeDecision({
|
||||
currentReplicator().makeDecision({
|
||||
peerId: peer.peerId,
|
||||
name: peer.name,
|
||||
decision: isAccepted,
|
||||
@@ -65,13 +67,11 @@
|
||||
});
|
||||
}
|
||||
function revokeDecision() {
|
||||
getReplicator().revokeDecision({
|
||||
currentReplicator().revokeDecision({
|
||||
peerId: peer.peerId,
|
||||
name: peer.name,
|
||||
});
|
||||
}
|
||||
const getReplicator = getContext<() => P2PReplicatorPaneController>("getReplicator");
|
||||
|
||||
const peerAttrLabels = $derived.by(() => {
|
||||
const attrs = [];
|
||||
if (peer.syncOnConnect) {
|
||||
@@ -86,18 +86,18 @@
|
||||
return attrs;
|
||||
});
|
||||
function startWatching() {
|
||||
getReplicator().watchPeer(peer.peerId);
|
||||
currentReplicator().watchPeer(peer.peerId);
|
||||
}
|
||||
function stopWatching() {
|
||||
getReplicator().unwatchPeer(peer.peerId);
|
||||
currentReplicator().unwatchPeer(peer.peerId);
|
||||
}
|
||||
|
||||
function sync() {
|
||||
void getReplicator().sync(peer.peerId, false);
|
||||
void currentReplicator().sync(peer.peerId, false);
|
||||
}
|
||||
|
||||
function moreMenu(evt: MouseEvent) {
|
||||
eventHub.emitEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, { peer, event: evt });
|
||||
showPeerMenu?.(peer, evt);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -159,7 +159,9 @@
|
||||
{:else}
|
||||
<button class="button" onclick={startWatching} title="live">⚡</button>
|
||||
{/if}
|
||||
<button class="button" onclick={moreMenu}>...</button>
|
||||
{#if showPeerMenu}
|
||||
<button class="button" onclick={moreMenu}>...</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user