sync all form inputs and presence
This commit is contained in:
parent
d9ca5e3102
commit
be473c7e75
5 changed files with 139 additions and 28 deletions
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Combobox v-model="selected" by="id">
|
<Combobox v-model="selected">
|
||||||
<ComboboxLabel>{{ title }}</ComboboxLabel>
|
<ComboboxLabel>{{ title }}</ComboboxLabel>
|
||||||
<div class="relative mt-1">
|
<div class="relative mt-1">
|
||||||
<div
|
<div
|
||||||
|
@ -8,10 +8,7 @@
|
||||||
>
|
>
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
|
class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
|
||||||
:displayValue="
|
:displayValue="(force) => (selectedForce?.firstname ?? '') + ' ' + (selectedForce?.lastname ?? '')"
|
||||||
(force) =>
|
|
||||||
((force as ForceViewModel)?.firstname ?? '') + ' ' + ((force as ForceViewModel)?.lastname ?? '')
|
|
||||||
"
|
|
||||||
@input="query = $event.target.value"
|
@input="query = $event.target.value"
|
||||||
/>
|
/>
|
||||||
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center pr-2">
|
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
@ -45,7 +42,7 @@
|
||||||
v-for="person in filtered"
|
v-for="person in filtered"
|
||||||
as="template"
|
as="template"
|
||||||
:key="person.id"
|
:key="person.id"
|
||||||
:value="person"
|
:value="person.id"
|
||||||
v-slot="{ selected, active }"
|
v-slot="{ selected, active }"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
|
@ -120,6 +117,9 @@ export default defineComponent({
|
||||||
this.$emit("update:model-value", val);
|
this.$emit("update:model-value", val);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
selectedForce() {
|
||||||
|
return this.availableForces.find((af) => af.id == this.selected);
|
||||||
|
},
|
||||||
filtered() {
|
filtered() {
|
||||||
return this.query == ""
|
return this.query == ""
|
||||||
? this.availableForces
|
? this.availableForces
|
||||||
|
|
|
@ -56,9 +56,7 @@ export const useMissionDetailStore = defineStore("missionDetail", {
|
||||||
});
|
});
|
||||||
|
|
||||||
connectionStore.connection?.on("package-sync-awareness", (data) => {
|
connectionStore.connection?.on("package-sync-awareness", (data) => {
|
||||||
// if (this.awareness != undefined) {
|
// TODO self implement where users edit what and cursors
|
||||||
// AwarenessProtocol.applyAwarenessUpdate(this.awareness, new Uint8Array(data.update), this);
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.joinDocument();
|
this.joinDocument();
|
||||||
|
@ -69,7 +67,7 @@ export const useMissionDetailStore = defineStore("missionDetail", {
|
||||||
const connectionStore = useConnectionStore();
|
const connectionStore = useConnectionStore();
|
||||||
if (connectionStore.connected) {
|
if (connectionStore.connected) {
|
||||||
connectionStore.connection?.emit("mission:sync-client-updates", {
|
connectionStore.connection?.emit("mission:sync-client-updates", {
|
||||||
update: Array.from(update),
|
update: Uint8Array.from(update),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -88,7 +86,7 @@ export const useMissionDetailStore = defineStore("missionDetail", {
|
||||||
const connectionStore = useConnectionStore();
|
const connectionStore = useConnectionStore();
|
||||||
connectionStore.connection?.emit(
|
connectionStore.connection?.emit(
|
||||||
"mission:sync-client-awareness",
|
"mission:sync-client-awareness",
|
||||||
Array.from(AwarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients))
|
Uint8Array.from(AwarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,47 +5,47 @@
|
||||||
<input type="text" id="title" v-model="title" />
|
<input type="text" id="title" v-model="title" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col sm:flex-row gap-2">
|
<div class="flex flex-col sm:flex-row gap-2">
|
||||||
<ForceSelect title="Einsatzleiter" :available-forces="availableForces" />
|
<ForceSelect title="Einsatzleiter" :available-forces="availableForces" v-model="command" />
|
||||||
<ForceSelect title="Bericht Ersteller" :available-forces="availableForces" />
|
<ForceSelect title="Bericht Ersteller" :available-forces="availableForces" v-model="secretary" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col sm:flex-row gap-2">
|
<div class="flex flex-col sm:flex-row gap-2">
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<label for="start">Einsatzbeginn</label>
|
<label for="start">Einsatzbeginn</label>
|
||||||
<input type="datetime-local" id="start" />
|
<input type="datetime-local" id="start" v-model="start" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<label for="end">Einsatzende</label>
|
<label for="end">Einsatzende</label>
|
||||||
<input type="datetime-local" id="end" />
|
<input type="datetime-local" id="end" v-model="end" :min="start" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full sm:w-fit min-w-fit">
|
<div class="w-full sm:w-fit min-w-fit">
|
||||||
<p>Dauer</p>
|
<p>Dauer</p>
|
||||||
<p
|
<p
|
||||||
class="rounded-md shadow-sm relative block w-full sm:w-fit px-3 py-2 border border-gray-300 text-gray-900 sm:text-sm"
|
class="rounded-md shadow-sm relative block w-full sm:w-fit px-3 py-2 border border-gray-300 text-gray-900 sm:text-sm"
|
||||||
>
|
>
|
||||||
00h 00m
|
<span v-if="duration.days != '00'">{{ duration.days }}d</span> {{ duration.hours }}h {{ duration.minutes }}m
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="mission_short">Stichwort</label>
|
<label for="mission_short">Stichwort</label>
|
||||||
<input type="text" id="mission_short" />
|
<input type="text" id="mission_short" v-model="mission_short" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="location">Einsatzort</label>
|
<label for="location">Einsatzort</label>
|
||||||
<input type="text" id="title" />
|
<input type="text" id="location" v-model="location" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="title">Weitere Anwesende (andere Wehren, Polizei, Rettungsdienst)</label>
|
<label for="others">Weitere Anwesende (andere Wehren, Polizei, Rettungsdienst)</label>
|
||||||
<input type="text" id="title" />
|
<input type="text" id="others" v-model="others" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col sm:flex-row gap-2">
|
<div class="flex flex-col sm:flex-row gap-2">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label for="rescued">Anzahl getretteter Personen</label>
|
<label for="rescued">Anzahl getretteter Personen</label>
|
||||||
<input type="number" id="rescued" value="0" />
|
<input type="number" id="rescued" min="0" v-model="rescued" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<label for="recovered">Anzahl geborgener Personen</label>
|
<label for="recovered">Anzahl geborgener Personen</label>
|
||||||
<input type="number" id="recovered" value="0" />
|
<input type="number" id="recovered" min="0" v-model="recovered" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@ -111,12 +111,98 @@ export default defineComponent({
|
||||||
...mapState(useForceStore, ["availableForces"]),
|
...mapState(useForceStore, ["availableForces"]),
|
||||||
title: {
|
title: {
|
||||||
get() {
|
get() {
|
||||||
return this.document.getMap("form").get("title");
|
return this.document.getMap("form").get("title") as string;
|
||||||
},
|
},
|
||||||
set(val: string) {
|
set(val: string) {
|
||||||
this.document.getMap("form").set("title", val);
|
this.document.getMap("form").set("title", val);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
command: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("command") as string;
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("command", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secretary: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("secretary") as string;
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("secretary", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
get() {
|
||||||
|
return (
|
||||||
|
(this.document.getMap("form").get("start") as string) ||
|
||||||
|
new Date(new Date().setHours(new Date().getHours() + 1)).toISOString().slice(0, -8)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("start", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("end") as string;
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("end", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
duration() {
|
||||||
|
const diffInMs = new Date(this.end).getTime() - new Date(this.start).getTime();
|
||||||
|
|
||||||
|
const durationDate = new Date(diffInMs || 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
days: (durationDate.getUTCDate() - 1).toString().padStart(2, "0"),
|
||||||
|
hours: durationDate.getUTCHours().toString().padStart(2, "0"),
|
||||||
|
minutes: durationDate.getUTCMinutes().toString().padStart(2, "0"),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mission_short: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("mission_short");
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("mission_short", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("location");
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("location", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
others: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("others");
|
||||||
|
},
|
||||||
|
set(val: string) {
|
||||||
|
this.document.getMap("form").set("others", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rescued: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("rescued") || 0;
|
||||||
|
},
|
||||||
|
set(val: number) {
|
||||||
|
this.document.getMap("form").set("rescued", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recovered: {
|
||||||
|
get() {
|
||||||
|
return this.document.getMap("form").get("recovered") || 0;
|
||||||
|
},
|
||||||
|
set(val: number) {
|
||||||
|
this.document.getMap("form").set("recovered", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
editor() {
|
editor() {
|
||||||
return this.document.getText("editor");
|
return this.document.getText("editor");
|
||||||
},
|
},
|
||||||
|
@ -130,7 +216,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.awareness);
|
this.binding = new QuillBinding(this.document.getText("editor"), quill); //this.awareness
|
||||||
this.cursors = quill.getModule("cursors") as QuillCursors;
|
this.cursors = quill.getModule("cursors") as QuillCursors;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MissionDetail v-show="routeHash == '#edit'" :document="yDoc" :awareness="awareness" />
|
<MissionDetail v-show="routeHash == '#edit'" :document="yDoc" :awareness="awareness" />
|
||||||
<MissionPresence v-show="routeHash == '#presence'" />
|
<MissionPresence v-show="routeHash == '#presence'" :document="yDoc" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2 h-full w-full overflow-hidden">
|
<div class="flex flex-col gap-2 h-full w-full overflow-hidden">
|
||||||
<Combobox v-model="selected" by="id" multiple>
|
<Combobox v-model="presence" multiple>
|
||||||
<div
|
<div
|
||||||
class="rounded-md shadow-sm relative block w-full border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-0 focus:z-10 sm:text-sm resize-none"
|
class="rounded-md shadow-sm relative block w-full border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-0 focus:z-10 sm:text-sm resize-none"
|
||||||
>
|
>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
v-for="person in filtered"
|
v-for="person in filtered"
|
||||||
as="template"
|
as="template"
|
||||||
:key="person.id"
|
:key="person.id"
|
||||||
:value="person"
|
:value="person.id"
|
||||||
v-slot="{ selected, active }"
|
v-slot="{ selected, active }"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
|
@ -67,18 +67,45 @@ import { Combobox, ComboboxInput, ComboboxButton, ComboboxOptions, ComboboxOptio
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||||
import { useForceStore } from "@/stores/admin/configuration/force";
|
import { useForceStore } from "@/stores/admin/configuration/force";
|
||||||
import { mapState } from "pinia";
|
import { mapState } from "pinia";
|
||||||
|
import * as Y from "yjs";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
document: {
|
||||||
|
type: Object as PropType<Y.Doc>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
query: "" as string,
|
query: "" as string,
|
||||||
selected: [],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useForceStore, ["availableForces"]),
|
...mapState(useForceStore, ["availableForces"]),
|
||||||
|
presence: {
|
||||||
|
get() {
|
||||||
|
return this.document.getArray<string>("presence").toArray();
|
||||||
|
},
|
||||||
|
set(val: Array<string>) {
|
||||||
|
let added = val.filter((e) => !this.document.getArray<string>("presence").toArray().includes(e));
|
||||||
|
let removed = this.document
|
||||||
|
.getArray<string>("presence")
|
||||||
|
.toArray()
|
||||||
|
.filter((e) => !val.includes(e));
|
||||||
|
console.log(added, removed);
|
||||||
|
this.document.getArray("presence").push(added);
|
||||||
|
removed.forEach((r) => {
|
||||||
|
let index = this.document
|
||||||
|
.getArray<string>("presence")
|
||||||
|
.toArray()
|
||||||
|
.findIndex((a) => a == r);
|
||||||
|
this.document.getArray("presence").delete(index, 1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
filtered() {
|
filtered() {
|
||||||
return this.query == ""
|
return this.query == ""
|
||||||
? this.availableForces
|
? this.availableForces
|
||||||
|
|
Loading…
Add table
Reference in a new issue