From 599738c8fcc07f1b7f4bc432573a417b87c256d3 Mon Sep 17 00:00:00 2001 From: Tony Cabaye Date: Fri, 27 Sep 2024 10:26:56 +0200 Subject: [PATCH] feat: create extendable sync states (#1854) --- packages/client/index.ts | 6 ++ packages/client/state/drawings.ts | 1 + packages/client/state/shared.ts | 3 +- packages/client/state/syncState.ts | 100 ++++++++++++++++++++++------- 4 files changed, 85 insertions(+), 25 deletions(-) diff --git a/packages/client/index.ts b/packages/client/index.ts index c4a971b6a7..3b696dc7db 100644 --- a/packages/client/index.ts +++ b/packages/client/index.ts @@ -9,5 +9,11 @@ export { useNav } from './composables/useNav' export { useSlideContext } from './context' export * from './env' +export { createSyncState, disableSlidevSync, addSyncMethod } from './state/syncState' +export { onDrawingUpdate, drawingState } from './state/drawings' +export { onSharedUpdate, sharedState } from './state/shared' +export type { DrawingsState } from './state/drawings' +export type { SharedState } from './state/shared' + export * from './layoutHelper' export { onSlideEnter, onSlideLeave, useIsSlideActive } from './logic/slides' diff --git a/packages/client/state/drawings.ts b/packages/client/state/drawings.ts index d77167b052..8a95204708 100644 --- a/packages/client/state/drawings.ts +++ b/packages/client/state/drawings.ts @@ -6,6 +6,7 @@ export type DrawingsState = Record export const { init: initDrawingState, onPatch: onPatchDrawingState, + onUpdate: onDrawingUpdate, patch: patchDrawingState, state: drawingState, } = createSyncState(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__) diff --git a/packages/client/state/shared.ts b/packages/client/state/shared.ts index 9c8b11b03b..7fc16f5040 100644 --- a/packages/client/state/shared.ts +++ b/packages/client/state/shared.ts @@ -22,7 +22,7 @@ export interface SharedState { } } -const { init, onPatch, patch, state } = createSyncState(serverState, { +const { init, onPatch, onUpdate, patch, state } = createSyncState(serverState, { page: 1, clicks: 0, clicksTotal: 0, @@ -34,6 +34,7 @@ const { init, onPatch, patch, state } = createSyncState(serverState export { init as initSharedState, onPatch, + onUpdate as onSharedUpdate, patch, state as sharedState, } diff --git a/packages/client/state/syncState.ts b/packages/client/state/syncState.ts index 83e78124b4..e0205886b0 100644 --- a/packages/client/state/syncState.ts +++ b/packages/client/state/syncState.ts @@ -1,4 +1,72 @@ -import { reactive, toRaw, watch } from 'vue' +import { reactive, ref, toRaw, watch } from 'vue' + +export type SyncWrite = (state: State, updating?: boolean) => void + +export interface Sync { + enabled?: boolean + init: (channelKey: string, onUpdate: (data: Partial) => void, state: State, persist?: boolean) => SyncWrite | undefined +} + +interface SlidevSync extends Sync { + channels: BroadcastChannel[] + disable: () => void + listener?: (event: StorageEvent) => void +} + +const slidevSync: SlidevSync = { + channels: [], + enabled: true, + init(channelKey: string, onUpdate: (data: Partial) => void, state: State, persist = false) { + let stateChannel: BroadcastChannel + if (!__SLIDEV_HAS_SERVER__ && !persist) { + stateChannel = new BroadcastChannel(channelKey) + stateChannel.addEventListener('message', (event: MessageEvent>) => onUpdate(event.data)) + this.channels.push(stateChannel) + } + else if (!__SLIDEV_HAS_SERVER__ && persist) { + this.listener = function (event: StorageEvent) { + if (event && event.key === channelKey && event.newValue) + onUpdate(JSON.parse(event.newValue) as Partial) + } + window.addEventListener('storage', this.listener) + const serializedState = window.localStorage.getItem(channelKey) + if (serializedState) + onUpdate(JSON.parse(serializedState) as Partial) + } + return (state: State, updating = false) => { + if (this.enabled) { + if (!persist && stateChannel && !updating) + stateChannel.postMessage(toRaw(state)) + if (persist && !updating) + window.localStorage.setItem(channelKey, JSON.stringify(state)) + } + } + }, + disable() { + this.enabled = false + this.channels.forEach(channel => channel.close()) + if (this.listener) { + window.removeEventListener('storage', this.listener) + } + }, +} +const syncInterfaces: Sync[] = reactive([slidevSync]) +const channels: Map) => void, persist?: boolean, state: object }> = new Map() +const syncWrites = ref[]>>({}) + +export function disableSlidevSync() { + slidevSync.disable() +} + +export function addSyncMethod(sync: Sync) { + syncInterfaces.push(sync) + for (const [channelKey, { onUpdate, persist, state }] of channels.entries()) { + const write = sync.init(channelKey, onUpdate, state, persist) + if (write) { + syncWrites.value[channelKey].push(write) + } + } +} export function createSyncState(serverState: State, defaultState: State, persist = false) { const onPatchCallbacks: ((state: State) => void)[] = [] @@ -36,35 +104,19 @@ export function createSyncState(serverState: State, defaul } function init(channelKey: string) { - let stateChannel: BroadcastChannel - if (!__SLIDEV_HAS_SERVER__ && !persist) { - stateChannel = new BroadcastChannel(channelKey) - stateChannel.addEventListener('message', (event: MessageEvent>) => onUpdate(event.data)) - } - else if (!__SLIDEV_HAS_SERVER__ && persist) { - window.addEventListener('storage', (event) => { - if (event && event.key === channelKey && event.newValue) - onUpdate(JSON.parse(event.newValue) as Partial) - }) - } + channels.set(channelKey, { onUpdate, persist, state }) + syncWrites.value[channelKey] = syncInterfaces + .map(sync => sync.init(channelKey, onUpdate, state, persist)) + .filter((x): x is SyncWrite => Boolean(x)) function onStateChanged() { - if (!persist && stateChannel && !updating) - stateChannel.postMessage(toRaw(state)) - else if (persist && !updating) - window.localStorage.setItem(channelKey, JSON.stringify(state)) + syncWrites.value[channelKey].forEach(write => write?.(toRaw(state), updating)) if (!patching) onPatchCallbacks.forEach((fn: (state: State) => void) => fn(state)) } - watch(state, onStateChanged, { deep: true, flush: 'sync' }) - - if (!__SLIDEV_HAS_SERVER__ && persist) { - const serialzedState = window.localStorage.getItem(channelKey) - if (serialzedState) - onUpdate(JSON.parse(serialzedState) as Partial) - } + watch(state, onStateChanged, { deep: true }) } - return { init, onPatch, patch, state } + return { init, onPatch, onUpdate, patch, state } }