sync form

This commit is contained in:
Julian Krauser 2025-03-01 12:29:53 +01:00
parent 1a2647a7b8
commit d9ca5e3102
3 changed files with 53 additions and 23 deletions

View file

@ -1,13 +1,13 @@
import { defineStore } from "pinia";
import { useConnectionStore } from "./connection";
import * as Y from "yjs";
import { Awareness } from "y-protocols/awareness.js";
import { computed, ref } from "vue";
import * as AwarenessProtocol from "y-protocols/awareness.js";
export const useMissionDetailStore = defineStore("missionDetail", {
state: () => {
return {
yDoc: new Y.Doc(),
awareness: undefined as undefined | AwarenessProtocol.Awareness,
docId: null as null | string,
lastUpdateTimestamp: 0 as number,
connectionStatus: "disconnected", // 'disconnected', 'connecting', 'connected', 'syncing', 'synced'
@ -18,16 +18,18 @@ 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();
this.setupAwarenessObservers();
},
setupSocketHandlers() {
const connectionStore = useConnectionStore();
if (!connectionStore.connection) return;
connectionStore.connection.on("package-sync", (data) => {
connectionStore.connection?.on("package-sync", (data) => {
try {
this.connectionStatus = "syncing";
@ -44,7 +46,7 @@ export const useMissionDetailStore = defineStore("missionDetail", {
this.requestFullSync();
}
});
connectionStore.connection.on("sync-get-missing-updates", (data) => {
connectionStore.connection?.on("sync-get-missing-updates", (data) => {
const clientUpdates = Y.encodeStateAsUpdate(this.yDoc, new Uint8Array(data.stateVector));
connectionStore.connection?.emit("mission:sync-client-updates", {
@ -53,12 +55,16 @@ export const useMissionDetailStore = defineStore("missionDetail", {
});
});
connectionStore.connection?.on("package-sync-awareness", (data) => {
// if (this.awareness != undefined) {
// AwarenessProtocol.applyAwarenessUpdate(this.awareness, new Uint8Array(data.update), this);
// }
});
this.joinDocument();
},
setupYjsObservers() {
if (!this.yDoc) return;
this.yDoc.on("update", (update) => {
const connectionStore = useConnectionStore();
if (connectionStore.connected) {
@ -73,20 +79,35 @@ export const useMissionDetailStore = defineStore("missionDetail", {
});
},
joinDocument() {
const connectionStore = useConnectionStore();
if (!connectionStore.connection || !this.docId) return;
setupAwarenessObservers() {
if (!this.awareness) return;
connectionStore.connection.emit("mission:join", this.docId, {
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",
Array.from(AwarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients))
);
}
});
},
joinDocument() {
if (!this.docId) return;
const connectionStore = useConnectionStore();
connectionStore.connection?.emit("mission:join", this.docId, {
timestamp: this.lastUpdateTimestamp,
});
},
requestFullSync() {
const connectionStore = useConnectionStore();
if (!connectionStore.connection || !this.docId) return;
if (!this.docId) return;
connectionStore.connection.emit("mission:join", this.docId, null);
const connectionStore = useConnectionStore();
connectionStore.connection?.emit("mission:join", this.docId, null);
},
loadLastUpdateFromLocalStorage() {
@ -97,16 +118,18 @@ export const useMissionDetailStore = defineStore("missionDetail", {
},
saveLastUpdateToLocalStorage() {
if (!this.docId) return;
localStorage.setItem(`yjsDoc_timestamp`, this.lastUpdateTimestamp.toString());
},
cleanup() {
if (this.yDoc) {
if (this.awareness) {
AwarenessProtocol.removeAwarenessStates(this.awareness, [this.yDoc.clientID], "window unload");
this.awareness.destroy();
}
this.yDoc.destroy();
this.yDoc = new Y.Doc();
}
this.lastUpdateTimestamp = 0;
localStorage.removeItem("yjsDoc_timestamp");

View file

@ -60,7 +60,6 @@
:style="!can('create', 'operation', 'mission') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
@ready="initEditor"
/>
<!--v-model:content=""-->
</div>
<div class="flex flex-col">
<p>Eingesetzte Fahrzeuge</p>
@ -86,6 +85,7 @@ import { moduleOptions } from "@/helpers/quillConfig";
import ForceSelect from "@/components/admin/ForceSelect.vue";
import { useForceStore } from "@/stores/admin/configuration/force";
import * as Y from "yjs";
import type { Awareness } from "y-protocols/awareness.js";
</script>
<script lang="ts">
@ -95,6 +95,10 @@ export default defineComponent({
type: Object as PropType<Y.Doc>,
required: true,
},
awareness: {
type: Object as PropType<Awareness | undefined>,
default: undefined,
},
},
data() {
return {
@ -126,7 +130,7 @@ export default defineComponent({
methods: {
initEditor(quill: Quill) {
quill.history.clear();
this.binding = new QuillBinding(this.editor, quill);
this.binding = new QuillBinding(this.editor, quill, this.awareness);
this.cursors = quill.getModule("cursors") as QuillCursors;
},
},

View file

@ -27,7 +27,7 @@
</RouterLink>
</div>
<MissionDetail v-show="routeHash == '#edit'" :document="yDoc" />
<MissionDetail v-show="routeHash == '#edit'" :document="yDoc" :awareness="awareness" />
<MissionPresence v-show="routeHash == '#presence'" />
</div>
</div>
@ -77,10 +77,13 @@ export default defineComponent({
},
computed: {
...mapState(useConnectionStore, ["connectionStatus"]),
...mapWritableState(useMissionDetailStore, ["yDoc"]),
...mapWritableState(useMissionDetailStore, ["yDoc", "awareness"]),
routeHash() {
return this.$route.hash;
},
editors() {
return this.awareness?.getStates() ?? [];
},
},
mounted() {
this.manageHash();