transmit awareness

This commit is contained in:
Julian Krauser 2025-03-04 12:02:06 +01:00
parent 05220efd00
commit 12772bfcfa
4 changed files with 119 additions and 22 deletions

2
package-lock.json generated
View file

@ -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": {

View file

@ -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",

74
src/helpers/awareness.ts Normal file
View file

@ -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<string, Editor>();
private editorStates = new Map<string, EditorState>();
public readonly emitter = mitt<AwarenessEvents>();
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();
}
}

View file

@ -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");