From 12772bfcfa87b802164452a314b1a72287c77308 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 4 Mar 2025 12:02:06 +0100 Subject: [PATCH] transmit awareness --- package-lock.json | 2 +- package.json | 1 + src/helpers/awareness.ts | 74 +++++++++++++++++++++ src/stores/admin/operation/missionDetail.ts | 64 ++++++++++++------ 4 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 src/helpers/awareness.ts diff --git a/package-lock.json b/package-lock.json index e4e946c..a50c5b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "lodash.difference": "^4.5.0", "lodash.differencewith": "^4.5.0", "lodash.isequal": "^4.5.0", + "mitt": "^3.0.1", "nprogress": "^0.2.0", "pinia": "^2.3.0", "qrcode": "^1.5.3", @@ -7145,7 +7146,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, "license": "MIT" }, "node_modules/mkdirp-classic": { diff --git a/package.json b/package.json index 5e54fab..fa97a6f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "lodash.difference": "^4.5.0", "lodash.differencewith": "^4.5.0", "lodash.isequal": "^4.5.0", + "mitt": "^3.0.1", "nprogress": "^0.2.0", "pinia": "^2.3.0", "qrcode": "^1.5.3", diff --git a/src/helpers/awareness.ts b/src/helpers/awareness.ts new file mode 100644 index 0000000..92d3944 --- /dev/null +++ b/src/helpers/awareness.ts @@ -0,0 +1,74 @@ +import mitt from "mitt"; + +export interface Editor { + username: string; + firstname: string; + lastname: string; + color: string; +} + +export interface EditorState { + field: string; + cursor: any; + range: any; +} + +export type AwarenessActions = "update" | "remove"; + +export type AwarenessEvents = { + update: { data: EditorState }; +}; + +export class Awareness { + private editors = new Map(); + private editorStates = new Map(); + public readonly emitter = mitt(); + + public getEditors() { + return this.editors; + } + + public getEditor(key: string) { + return this.editors.get(key); + } + + public getEditorsByField(field: string) { + return Array.from(this.editorStates.entries()) + .filter(([key, val]) => val.field == field) + .map(([key, val]) => key); + } + + public getEditorStates() { + return this.editorStates; + } + + public updateEditor(socketId: string, { action, data }: { action: AwarenessActions; data: Editor }) { + if (action == "update") { + this.editors.set(socketId, data); + } else if (action == "remove") { + this.editors.delete(socketId); + } + } + + public updateEditorState(socketId: string, { action, data }: { action: AwarenessActions; data: EditorState }) { + if (action == "update") { + this.editorStates.set(socketId, data); + } else if (action == "remove") { + this.editorStates.delete(socketId); + } + } + + public publishMyState(data: EditorState) { + this.emitter.emit("update", { data }); + } + + public reset() { + this.editors.clear(); + this.editorStates.clear(); + } + + public destroy() { + this.emitter.all.clear(); + this.reset(); + } +} diff --git a/src/stores/admin/operation/missionDetail.ts b/src/stores/admin/operation/missionDetail.ts index d502ee1..f5f2701 100644 --- a/src/stores/admin/operation/missionDetail.ts +++ b/src/stores/admin/operation/missionDetail.ts @@ -1,12 +1,13 @@ import { defineStore } from "pinia"; import { useConnectionStore } from "./connection"; import * as Y from "yjs"; +import { Awareness, type Editor, type EditorState } from "@/helpers/awareness"; export const useMissionDetailStore = defineStore("missionDetail", { state: () => { return { yDoc: new Y.Doc(), - awareness: undefined as undefined, + awareness: new Awareness(), docId: null as null | string, lastUpdateTimestamp: 0 as number, connectionStatus: "disconnected", // 'disconnected', 'connecting', 'connected', 'syncing', 'synced' @@ -17,8 +18,6 @@ export const useMissionDetailStore = defineStore("missionDetail", { this.docId = docId; this.lastUpdateTimestamp = this.loadLastUpdateFromLocalStorage(); - // this.awareness = new AwarenessProtocol.Awareness(this.yDoc); - // this.awareness.setLocalStateField("user", { name: "hi", color: "#123456" }); this.setupSocketHandlers(); this.setupYjsObservers(); @@ -54,8 +53,42 @@ export const useMissionDetailStore = defineStore("missionDetail", { }); }); + connectionStore.connection?.on("package-init-awareness", (data) => { + if (data.type == "editors") { + let update = data.update as Array<[string, Editor]>; + for (const e of update) { + this.awareness.updateEditor(e[0], { + action: "update", + data: e[1], + }); + } + } else if (data.type == "state") { + let update = data.update as Array<[string, EditorState]>; + for (const e of update) { + this.awareness.updateEditorState(e[0], { + action: "update", + data: e[1], + }); + } + } + }); connectionStore.connection?.on("package-sync-awareness", (data) => { - // TODO self implement where users edit what and cursors + if (connectionStore.connection?.id == data.socketId) return; + if (data.type == "editors") { + this.awareness.updateEditor(data.socketId, { + action: data.action, + data: data.update, + }); + } else if (data.type == "state") { + this.awareness.updateEditorState(data.socketId, { + action: data.action, + data: data.update, + }); + } + }); + + connectionStore.connection?.on("disconnect", () => { + this.awareness.reset(); }); this.joinDocument(); @@ -77,18 +110,10 @@ export const useMissionDetailStore = defineStore("missionDetail", { }, setupAwarenessObservers() { - if (!this.awareness) return; - - // this.awareness.on("update", (update: { added: number[]; updated: number[]; removed: number[] }) => { - // if (this.awareness != undefined) { - // const changedClients = update.added.concat(update.updated).concat(update.removed); - // const connectionStore = useConnectionStore(); - // connectionStore.connection?.emit( - // "mission:sync-client-awareness", - // Uint8Array.from(AwarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients)) - // ); - // } - // }); + this.awareness.emitter.on("update", (update: { data: EditorState }) => { + const connectionStore = useConnectionStore(); + connectionStore.connection?.emit("mission:sync-client-awareness", update.data); + }); }, joinDocument() { @@ -119,13 +144,10 @@ export const useMissionDetailStore = defineStore("missionDetail", { }, cleanup() { - // if (this.awareness) { - // AwarenessProtocol.removeAwarenessStates(this.awareness, [this.yDoc.clientID], "window unload"); - // this.awareness.destroy(); - // } - this.yDoc.destroy(); this.yDoc = new Y.Doc(); + this.awareness.destroy(); + this.awareness = new Awareness(); this.lastUpdateTimestamp = 0; localStorage.removeItem("yjsDoc_timestamp");