Skip to content

Commit

Permalink
feat: create extendable sync states (#1854)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonai authored Sep 27, 2024
1 parent c0b6eb7 commit 599738c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 25 deletions.
6 changes: 6 additions & 0 deletions packages/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
1 change: 1 addition & 0 deletions packages/client/state/drawings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type DrawingsState = Record<number, string | undefined>
export const {
init: initDrawingState,
onPatch: onPatchDrawingState,
onUpdate: onDrawingUpdate,
patch: patchDrawingState,
state: drawingState,
} = createSyncState<DrawingsState>(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
3 changes: 2 additions & 1 deletion packages/client/state/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface SharedState {
}
}

const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
const { init, onPatch, onUpdate, patch, state } = createSyncState<SharedState>(serverState, {
page: 1,
clicks: 0,
clicksTotal: 0,
Expand All @@ -34,6 +34,7 @@ const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState
export {
init as initSharedState,
onPatch,
onUpdate as onSharedUpdate,
patch,
state as sharedState,
}
100 changes: 76 additions & 24 deletions packages/client/state/syncState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,72 @@
import { reactive, toRaw, watch } from 'vue'
import { reactive, ref, toRaw, watch } from 'vue'

export type SyncWrite<State extends object> = (state: State, updating?: boolean) => void

export interface Sync {
enabled?: boolean
init: <State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist?: boolean) => SyncWrite<State> | undefined
}

interface SlidevSync extends Sync {
channels: BroadcastChannel[]
disable: () => void
listener?: (event: StorageEvent) => void
}

const slidevSync: SlidevSync = {
channels: [],
enabled: true,
init<State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist = false) {
let stateChannel: BroadcastChannel
if (!__SLIDEV_HAS_SERVER__ && !persist) {
stateChannel = new BroadcastChannel(channelKey)
stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => 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<State>)
}
window.addEventListener('storage', this.listener)
const serializedState = window.localStorage.getItem(channelKey)
if (serializedState)
onUpdate(JSON.parse(serializedState) as Partial<State>)
}
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<string, { onUpdate: (data: Partial<object>) => void, persist?: boolean, state: object }> = new Map()
const syncWrites = ref<Record<string, SyncWrite<object>[]>>({})

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<State extends object>(serverState: State, defaultState: State, persist = false) {
const onPatchCallbacks: ((state: State) => void)[] = []
Expand Down Expand Up @@ -36,35 +104,19 @@ export function createSyncState<State extends object>(serverState: State, defaul
}

function init(channelKey: string) {
let stateChannel: BroadcastChannel
if (!__SLIDEV_HAS_SERVER__ && !persist) {
stateChannel = new BroadcastChannel(channelKey)
stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => 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<State>)
})
}
channels.set(channelKey, { onUpdate, persist, state })
syncWrites.value[channelKey] = syncInterfaces
.map(sync => sync.init<State>(channelKey, onUpdate, state, persist))
.filter((x): x is SyncWrite<object> => 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<State>)
}
watch(state, onStateChanged, { deep: true })
}

return { init, onPatch, patch, state }
return { init, onPatch, onUpdate, patch, state }
}

0 comments on commit 599738c

Please sign in to comment.