UNPKG

zustand-yjs

Version:

Zustand Stores with Yjs

278 lines (234 loc) 8.64 kB
import create from 'zustand'; import { useEffect, useMemo, useCallback } from 'react'; import shallow from 'zustand/shallow'; import * as Y from 'yjs'; var YDocEnum; (function (YDocEnum) { YDocEnum[YDocEnum["GUID"] = 0] = "GUID"; YDocEnum[YDocEnum["DOC"] = 1] = "DOC"; YDocEnum[YDocEnum["MOUNT"] = 2] = "MOUNT"; YDocEnum[YDocEnum["UN_MOUNT"] = 3] = "UN_MOUNT"; YDocEnum[YDocEnum["USE_CNT"] = 4] = "USE_CNT"; })(YDocEnum || (YDocEnum = {})); var ObserveEnum; (function (ObserveEnum) { ObserveEnum[ObserveEnum["TYPE"] = 0] = "TYPE"; ObserveEnum[ObserveEnum["OBSERVE"] = 1] = "OBSERVE"; ObserveEnum[ObserveEnum["USE_CNT"] = 2] = "USE_CNT"; })(ObserveEnum || (ObserveEnum = {})); const newStartAwareness = setAwareness => { const startAwareness = provider => { const awareness = provider.awareness; const updateAwareness = () => { setAwareness(provider, Array.from(awareness.getStates().values())); }; updateAwareness(); awareness.on('update', updateAwareness); return () => { awareness.off('update', updateAwareness); }; }; return startAwareness; }; const useYStore = create((set, get) => { return { yDocs: [], yAwareness: [], observers: [], data: [], update(type, data) { const dataSet = get().data; set({ data: [...dataSet.filter(([t]) => t !== type), [type, data]] }); }, listenType(type, listen) { const observers = get().observers; const match = observers.find(([observer]) => observer === type); if (match) { set({ observers: [...observers.filter(obs => obs !== match), [match[ObserveEnum.TYPE], match[ObserveEnum.OBSERVE], match[ObserveEnum.USE_CNT] + 1]] }); } else { type.observe(listen); set({ observers: [...observers, [type, listen, 1]] }); } }, unListenType(type) { const observers = get().observers; const match = observers.find(([t]) => t === type); if (!match) { console.warn('Type ', type, " wasn't mounted"); return; } const observerCount = match[ObserveEnum.USE_CNT] - 1; if (observerCount > 0) { set({ observers: [...observers.filter(obs => obs !== match), [match[ObserveEnum.TYPE], match[ObserveEnum.OBSERVE], observerCount]] }); } else { type.unobserve(match[ObserveEnum.OBSERVE]); set({ observers: [...observers.filter(obs => obs !== match)], data: [...get().data.filter(([t]) => t !== type)] }); } }, unMountYDoc(yDoc) { const yDocs = get().yDocs; const yAwareness = get().yAwareness; const match = yDocs.find(([, doc]) => doc === yDoc); if (!match) { console.warn('Can not unmount doc ', yDoc.guid, ' . Doc not found'); return; } const observerCount = match[YDocEnum.USE_CNT] - 1; if (observerCount > 0) { set({ yDocs: [...yDocs.filter(yDoc => yDoc !== match), [match[YDocEnum.GUID], match[YDocEnum.DOC], match[YDocEnum.MOUNT], match[YDocEnum.UN_MOUNT], observerCount]] }); } else { const unMount = match[YDocEnum.UN_MOUNT]; if (unMount) unMount(); set({ yDocs: yDocs.filter(yDoc => yDoc !== match), yAwareness: yAwareness.filter(([yDocName]) => yDocName !== match[0]) }); } }, mountYDoc(yDoc, mount) { const yDocs = get().yDocs; const yAwareness = get().yAwareness; const match = yDocs.find(([name]) => name === yDoc.guid); if (match) { if (match[YDocEnum.MOUNT] !== mount) { console.warn('A different connection function was already provided for ', match[YDocEnum.GUID], '. This unMount function will be ignored'); } match[YDocEnum.USE_CNT]++; set({ yDocs: [...yDocs.filter(([name]) => name !== yDoc.guid), match] }); } else { const unMount = mount(yDoc, newStartAwareness((protocol, changes) => { set({ yAwareness: [...yAwareness, [yDoc.guid, protocol.awareness, changes]] }); })); set({ yDocs: [...yDocs, [yDoc.guid, yDoc, mount, unMount, 1]] }); } } }; }); var useYStore$1 = useYStore; const useYObserve = (yType, serialize) => { const [listen, unListen, update] = useYStore$1(state => [state.listenType, state.unListenType, state.update], shallow); useEffect(() => { listen(yType, () => update(yType, serialize())); update(yType, serialize()); return () => unListen(yType); }, []); }; var useYObserve$1 = useYObserve; const useYArray = yArray => { useYObserve$1(yArray, () => yArray.toArray()); const dataSet = useYStore$1(state => state.data); const data = useMemo(() => { const match = dataSet.find(([type]) => type === yArray); if (!match) return []; return match[1]; }, [yArray, dataSet]); const noBinding = funcKey => () => { throw new Error(`Y.Array#${funcKey.toString()} is not implemented`); }; return { forEach: yArray.forEach ? yArray.forEach.bind(yArray) : noBinding('forEach'), map: yArray.map ? yArray.map.bind(yArray) : noBinding('map'), slice: yArray.slice ? yArray.slice.bind(yArray) : noBinding('slice'), get: yArray.get ? yArray.get.bind(yArray) : noBinding('get'), delete: yArray.delete ? yArray.delete.bind(yArray) : noBinding('delete'), unshift: yArray.unshift ? yArray.unshift.bind(yArray) : noBinding('unshift'), push: yArray.push ? yArray.push.bind(yArray) : noBinding('push'), insert: yArray.insert ? yArray.insert.bind(yArray) : noBinding('insert'), data }; }; var useYArray$1 = useYArray; const useYMap = yMap => { useYObserve$1(yMap, () => yMap.toJSON()); const dataSet = useYStore$1(state => state.data); const data = useMemo(() => { const match = dataSet.find(([type]) => type === yMap); if (!match) return {}; return match[1]; }, [yMap, dataSet]); const noBinding = funcKey => () => { throw new Error(`Y.Map#${funcKey.toString()} is not implemented`); }; return { keys: yMap.keys ? yMap.keys.bind(yMap) : noBinding('keys'), values: yMap.values ? yMap.values.bind(yMap) : noBinding('values'), entries: yMap.entries ? yMap.entries.bind(yMap) : noBinding('entries'), forEach: yMap.forEach ? yMap.forEach.bind(yMap) : noBinding('forEach'), delete: yMap.delete ? yMap.delete.bind(yMap) : noBinding('delete'), set: yMap.set ? yMap.set.bind(yMap) : noBinding('set'), get: yMap.get ? yMap.get.bind(yMap) : noBinding('get'), has: yMap.has ? yMap.has.bind(yMap) : noBinding('has'), data }; }; var useYMap$1 = useYMap; const useYDoc = (name, mount) => { const [mountYDoc, unMountYDoc, yDocs] = useYStore$1(state => [state.mountYDoc, state.unMountYDoc, state.yDocs], shallow); const yDoc = useMemo(() => { const match = yDocs.find(([docGuid]) => docGuid === name); if (match) return match[YDocEnum.DOC]; const yDoc = new Y.Doc(); yDoc.guid = name; mountYDoc(yDoc, mount); return yDoc; }, [yDocs, name]); useEffect(() => { return () => unMountYDoc(yDoc); }, [unMountYDoc, name]); return yDoc; }; var useYDoc$1 = useYDoc; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const useYAwareness = yDoc => { const matches = useYStore$1(useCallback(state => state.yAwareness.filter(([guid]) => guid === yDoc.guid), [yDoc.guid])); const [awarenessData, awareness] = useMemo(() => { if (matches.length === 0) return [[], { error: 'Awareness not registered. Use startAwareness method from your YDoc connect function.' }]; const [_docGuid, awareness, changes] = matches[0]; return [changes, awareness]; }, [matches]); const awarenessSetData = useCallback(newState => { if (awareness.error) { console.error(awareness.error); } else { const previousState = awareness.getLocalState(); awareness.setLocalState(_extends({}, previousState, newState)); } }, [awareness]); return [awarenessData, awarenessSetData]; }; var useYAwareness$1 = useYAwareness; export { ObserveEnum, YDocEnum, useYArray$1 as useYArray, useYAwareness$1 as useYAwareness, useYDoc$1 as useYDoc, useYMap$1 as useYMap };