Skip to content

Commit 599738c

Browse files
authoredSep 27, 2024··
feat: create extendable sync states (#1854)
1 parent c0b6eb7 commit 599738c

File tree

4 files changed

+85
-25
lines changed

4 files changed

+85
-25
lines changed
 

Diff for: ‎packages/client/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,11 @@ export { useNav } from './composables/useNav'
99
export { useSlideContext } from './context'
1010
export * from './env'
1111

12+
export { createSyncState, disableSlidevSync, addSyncMethod } from './state/syncState'
13+
export { onDrawingUpdate, drawingState } from './state/drawings'
14+
export { onSharedUpdate, sharedState } from './state/shared'
15+
export type { DrawingsState } from './state/drawings'
16+
export type { SharedState } from './state/shared'
17+
1218
export * from './layoutHelper'
1319
export { onSlideEnter, onSlideLeave, useIsSlideActive } from './logic/slides'

Diff for: ‎packages/client/state/drawings.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type DrawingsState = Record<number, string | undefined>
66
export const {
77
init: initDrawingState,
88
onPatch: onPatchDrawingState,
9+
onUpdate: onDrawingUpdate,
910
patch: patchDrawingState,
1011
state: drawingState,
1112
} = createSyncState<DrawingsState>(serverDrawingState, serverDrawingState, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)

Diff for: ‎packages/client/state/shared.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface SharedState {
2222
}
2323
}
2424

25-
const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
25+
const { init, onPatch, onUpdate, patch, state } = createSyncState<SharedState>(serverState, {
2626
page: 1,
2727
clicks: 0,
2828
clicksTotal: 0,
@@ -34,6 +34,7 @@ const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState
3434
export {
3535
init as initSharedState,
3636
onPatch,
37+
onUpdate as onSharedUpdate,
3738
patch,
3839
state as sharedState,
3940
}

Diff for: ‎packages/client/state/syncState.ts

+76-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,72 @@
1-
import { reactive, toRaw, watch } from 'vue'
1+
import { reactive, ref, toRaw, watch } from 'vue'
2+
3+
export type SyncWrite<State extends object> = (state: State, updating?: boolean) => void
4+
5+
export interface Sync {
6+
enabled?: boolean
7+
init: <State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist?: boolean) => SyncWrite<State> | undefined
8+
}
9+
10+
interface SlidevSync extends Sync {
11+
channels: BroadcastChannel[]
12+
disable: () => void
13+
listener?: (event: StorageEvent) => void
14+
}
15+
16+
const slidevSync: SlidevSync = {
17+
channels: [],
18+
enabled: true,
19+
init<State extends object>(channelKey: string, onUpdate: (data: Partial<State>) => void, state: State, persist = false) {
20+
let stateChannel: BroadcastChannel
21+
if (!__SLIDEV_HAS_SERVER__ && !persist) {
22+
stateChannel = new BroadcastChannel(channelKey)
23+
stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => onUpdate(event.data))
24+
this.channels.push(stateChannel)
25+
}
26+
else if (!__SLIDEV_HAS_SERVER__ && persist) {
27+
this.listener = function (event: StorageEvent) {
28+
if (event && event.key === channelKey && event.newValue)
29+
onUpdate(JSON.parse(event.newValue) as Partial<State>)
30+
}
31+
window.addEventListener('storage', this.listener)
32+
const serializedState = window.localStorage.getItem(channelKey)
33+
if (serializedState)
34+
onUpdate(JSON.parse(serializedState) as Partial<State>)
35+
}
36+
return (state: State, updating = false) => {
37+
if (this.enabled) {
38+
if (!persist && stateChannel && !updating)
39+
stateChannel.postMessage(toRaw(state))
40+
if (persist && !updating)
41+
window.localStorage.setItem(channelKey, JSON.stringify(state))
42+
}
43+
}
44+
},
45+
disable() {
46+
this.enabled = false
47+
this.channels.forEach(channel => channel.close())
48+
if (this.listener) {
49+
window.removeEventListener('storage', this.listener)
50+
}
51+
},
52+
}
53+
const syncInterfaces: Sync[] = reactive([slidevSync])
54+
const channels: Map<string, { onUpdate: (data: Partial<object>) => void, persist?: boolean, state: object }> = new Map()
55+
const syncWrites = ref<Record<string, SyncWrite<object>[]>>({})
56+
57+
export function disableSlidevSync() {
58+
slidevSync.disable()
59+
}
60+
61+
export function addSyncMethod(sync: Sync) {
62+
syncInterfaces.push(sync)
63+
for (const [channelKey, { onUpdate, persist, state }] of channels.entries()) {
64+
const write = sync.init(channelKey, onUpdate, state, persist)
65+
if (write) {
66+
syncWrites.value[channelKey].push(write)
67+
}
68+
}
69+
}
270

371
export function createSyncState<State extends object>(serverState: State, defaultState: State, persist = false) {
472
const onPatchCallbacks: ((state: State) => void)[] = []
@@ -36,35 +104,19 @@ export function createSyncState<State extends object>(serverState: State, defaul
36104
}
37105

38106
function init(channelKey: string) {
39-
let stateChannel: BroadcastChannel
40-
if (!__SLIDEV_HAS_SERVER__ && !persist) {
41-
stateChannel = new BroadcastChannel(channelKey)
42-
stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => onUpdate(event.data))
43-
}
44-
else if (!__SLIDEV_HAS_SERVER__ && persist) {
45-
window.addEventListener('storage', (event) => {
46-
if (event && event.key === channelKey && event.newValue)
47-
onUpdate(JSON.parse(event.newValue) as Partial<State>)
48-
})
49-
}
107+
channels.set(channelKey, { onUpdate, persist, state })
108+
syncWrites.value[channelKey] = syncInterfaces
109+
.map(sync => sync.init<State>(channelKey, onUpdate, state, persist))
110+
.filter((x): x is SyncWrite<object> => Boolean(x))
50111

51112
function onStateChanged() {
52-
if (!persist && stateChannel && !updating)
53-
stateChannel.postMessage(toRaw(state))
54-
else if (persist && !updating)
55-
window.localStorage.setItem(channelKey, JSON.stringify(state))
113+
syncWrites.value[channelKey].forEach(write => write?.(toRaw(state), updating))
56114
if (!patching)
57115
onPatchCallbacks.forEach((fn: (state: State) => void) => fn(state))
58116
}
59117

60-
watch(state, onStateChanged, { deep: true, flush: 'sync' })
61-
62-
if (!__SLIDEV_HAS_SERVER__ && persist) {
63-
const serialzedState = window.localStorage.getItem(channelKey)
64-
if (serialzedState)
65-
onUpdate(JSON.parse(serialzedState) as Partial<State>)
66-
}
118+
watch(state, onStateChanged, { deep: true })
67119
}
68120

69-
return { init, onPatch, patch, state }
121+
return { init, onPatch, onUpdate, patch, state }
70122
}

0 commit comments

Comments
 (0)
Please sign in to comment.