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

View file

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

View file

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