use awareness info inside form inputs
This commit is contained in:
parent
12772bfcfa
commit
27e3ed525b
9 changed files with 135 additions and 23 deletions
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -30,6 +30,7 @@
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-router": "^4.3.3",
|
"vue-router": "^4.3.3",
|
||||||
|
"vue-tippy": "^6.6.0",
|
||||||
"y-quill": "0.1.3",
|
"y-quill": "0.1.3",
|
||||||
"yjs": "^13.6.23"
|
"yjs": "^13.6.23"
|
||||||
},
|
},
|
||||||
|
@ -2680,6 +2681,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-node-resolve": {
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
"version": "15.2.3",
|
"version": "15.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||||
|
@ -9333,6 +9344,15 @@
|
||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tippy.js": {
|
||||||
|
"version": "6.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
|
||||||
|
"integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-data-view": {
|
"node_modules/to-data-view": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-1.1.0.tgz",
|
||||||
|
@ -9953,6 +9973,18 @@
|
||||||
"vue": "^3.2.0"
|
"vue": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-tippy": {
|
||||||
|
"version": "6.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-tippy/-/vue-tippy-6.6.0.tgz",
|
||||||
|
"integrity": "sha512-ISRIUQDlcEP05K1nCbvlVcd8yuWS6S3dI91qD0A2slgtwwWjih8Fn9Aymq4SNaHQsdiP5+MLRPZVDxFjKMPgKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tippy.js": "^6.3.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-tsc": {
|
"node_modules/vue-tsc": {
|
||||||
"version": "2.0.29",
|
"version": "2.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.29.tgz",
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.4.29",
|
"vue": "^3.4.29",
|
||||||
"vue-router": "^4.3.3",
|
"vue-router": "^4.3.3",
|
||||||
|
"vue-tippy": "^6.6.0",
|
||||||
"y-quill": "0.1.3",
|
"y-quill": "0.1.3",
|
||||||
"yjs": "^13.6.23"
|
"yjs": "^13.6.23"
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { QuillBinding } from "y-quill";
|
||||||
import { moduleOptions } from "@/helpers/quillConfig";
|
import { moduleOptions } from "@/helpers/quillConfig";
|
||||||
import * as Y from "yjs";
|
import * as Y from "yjs";
|
||||||
import "@lottiefiles/lottie-player";
|
import "@lottiefiles/lottie-player";
|
||||||
|
import { Awareness } from "@/helpers/awareness";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -37,6 +38,10 @@ export default defineComponent({
|
||||||
type: Object as PropType<Y.Text>,
|
type: Object as PropType<Y.Text>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
awareness: {
|
||||||
|
type: Object as PropType<Partial<Awareness>>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
|
|
|
@ -2,16 +2,23 @@
|
||||||
<div :class="growing ? 'grow' : 'w-full'">
|
<div :class="growing ? 'grow' : 'w-full'">
|
||||||
<div class="flex flex-row gap-2 items-center">
|
<div class="flex flex-row gap-2 items-center">
|
||||||
<label :for="title">{{ title }}</label>
|
<label :for="title">{{ title }}</label>
|
||||||
<lottie-player src="/typing_animation.json" class="w-fit h-5" loop autoplay />
|
<lottie-player
|
||||||
|
v-if="currentEditors.length != 0"
|
||||||
|
src="/typing_animation.json"
|
||||||
|
class="w-fit h-5"
|
||||||
|
loop
|
||||||
|
autoplay
|
||||||
|
v-tippy="currentEditors.map((c) => c.username).join(', ')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input :type="type" :id="title" v-model="value" :min="min" />
|
<input :type="type" :id="title" v-model="value" :min="min" @focus="focused" @blur="blured" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
|
||||||
import "@lottiefiles/lottie-player";
|
import "@lottiefiles/lottie-player";
|
||||||
|
import type { Awareness } from "@/helpers/awareness";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -37,8 +44,17 @@ export default defineComponent({
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
awareness: {
|
||||||
|
type: Object as PropType<Awareness>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ["update:model-value"],
|
emits: ["update:model-value"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
typing: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
value: {
|
value: {
|
||||||
get() {
|
get() {
|
||||||
|
@ -48,6 +64,28 @@ export default defineComponent({
|
||||||
this.$emit("update:model-value", val);
|
this.$emit("update:model-value", val);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentEditors() {
|
||||||
|
return this.awareness.getEditorObjsByField(this.title);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.awareness.emitter.on("change", (d) => {});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
focused() {
|
||||||
|
this.awareness.publishMyState({
|
||||||
|
field: this.title,
|
||||||
|
cursor: undefined,
|
||||||
|
range: undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
blured() {
|
||||||
|
this.awareness.publishMyState({
|
||||||
|
field: "blured user",
|
||||||
|
cursor: undefined,
|
||||||
|
range: undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -17,11 +17,12 @@ export type AwarenessActions = "update" | "remove";
|
||||||
|
|
||||||
export type AwarenessEvents = {
|
export type AwarenessEvents = {
|
||||||
update: { data: EditorState };
|
update: { data: EditorState };
|
||||||
|
change: { socketId: string; action: AwarenessActions } & EditorState;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Awareness {
|
export class Awareness {
|
||||||
private editors = new Map<string, Editor>();
|
public readonly editors = new Map<string, Editor>();
|
||||||
private editorStates = new Map<string, EditorState>();
|
public readonly editorStates = new Map<string, EditorState>();
|
||||||
public readonly emitter = mitt<AwarenessEvents>();
|
public readonly emitter = mitt<AwarenessEvents>();
|
||||||
|
|
||||||
public getEditors() {
|
public getEditors() {
|
||||||
|
@ -38,6 +39,12 @@ export class Awareness {
|
||||||
.map(([key, val]) => key);
|
.map(([key, val]) => key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getEditorObjsByField(field: string) {
|
||||||
|
return Array.from(this.editors.entries())
|
||||||
|
.filter(([key, val]) => this.getEditorsByField(field).includes(key))
|
||||||
|
.map(([key, val]) => val);
|
||||||
|
}
|
||||||
|
|
||||||
public getEditorStates() {
|
public getEditorStates() {
|
||||||
return this.editorStates;
|
return this.editorStates;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +63,7 @@ export class Awareness {
|
||||||
} else if (action == "remove") {
|
} else if (action == "remove") {
|
||||||
this.editorStates.delete(socketId);
|
this.editorStates.delete(socketId);
|
||||||
}
|
}
|
||||||
|
this.emitter.emit("change", { socketId, action, ...data });
|
||||||
}
|
}
|
||||||
|
|
||||||
public publishMyState(data: EditorState) {
|
public publishMyState(data: EditorState) {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import NProgress from "nprogress";
|
||||||
import "../node_modules/nprogress/nprogress.css";
|
import "../node_modules/nprogress/nprogress.css";
|
||||||
import { Quill } from "@vueup/vue-quill";
|
import { Quill } from "@vueup/vue-quill";
|
||||||
import QuillCursors from "quill-cursors";
|
import QuillCursors from "quill-cursors";
|
||||||
|
import VueTippy from "vue-tippy";
|
||||||
|
import "tippy.js/dist/tippy.css";
|
||||||
|
|
||||||
import { http } from "./serverCom";
|
import { http } from "./serverCom";
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
|
@ -19,6 +21,7 @@ const app = createApp(App);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
app.use(VueTippy, { theme: "light", defaultProps: { placement: "right" } });
|
||||||
app.config.globalProperties.$http = http;
|
app.config.globalProperties.$http = http;
|
||||||
app.config.globalProperties.$progress = NProgress;
|
app.config.globalProperties.$progress = NProgress;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
|
<div class="flex flex-col gap-2 h-full w-full overflow-y-auto">
|
||||||
<DetailFormInput title="Einsatztitel" v-model="title" />
|
<DetailFormInput title="Einsatztitel" v-model="title" :awareness="awareness" />
|
||||||
<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" v-model="command" />
|
<ForceSelect title="Einsatzleiter" :available-forces="availableForces" v-model="command" />
|
||||||
<ForceSelect title="Bericht Ersteller" :available-forces="availableForces" v-model="secretary" />
|
<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">
|
||||||
<DetailFormInput title="Einsatzbeginn" v-model="start" type="datetime-local" growing />
|
<DetailFormInput title="Einsatzbeginn" v-model="start" type="datetime-local" growing :awareness="awareness" />
|
||||||
<DetailFormInput title="Einsatzende" v-model="end" type="datetime-local" :min="start" growing />
|
<DetailFormInput
|
||||||
|
title="Einsatzende"
|
||||||
|
v-model="end"
|
||||||
|
type="datetime-local"
|
||||||
|
:min="start"
|
||||||
|
growing
|
||||||
|
:awareness="awareness"
|
||||||
|
/>
|
||||||
<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
|
||||||
|
@ -17,14 +24,30 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DetailFormInput title="Stichwort" v-model="mission_short" />
|
<DetailFormInput title="Stichwort" v-model="mission_short" :awareness="awareness" />
|
||||||
<DetailFormInput title="Einsatzort" v-model="location" />
|
<DetailFormInput title="Einsatzort" v-model="location" :awareness="awareness" />
|
||||||
<DetailFormInput title="Weitere Anwesende (andere Wehren, Polizei, Rettungsdienst)" v-model="others" />
|
<DetailFormInput
|
||||||
|
title="Weitere Anwesende (andere Wehren, Polizei, Rettungsdienst)"
|
||||||
|
v-model="others"
|
||||||
|
:awareness="awareness"
|
||||||
|
/>
|
||||||
<div class="flex flex-col sm:flex-row gap-2">
|
<div class="flex flex-col sm:flex-row gap-2">
|
||||||
<DetailFormInput title="Anzahl getretteter Personen" type="number" v-model="rescued" min="0" />
|
<DetailFormInput
|
||||||
<DetailFormInput title="Anzahl geborgener Personen" type="number" v-model="recovered" min="0" />
|
title="Anzahl getretteter Personen"
|
||||||
|
type="number"
|
||||||
|
v-model="rescued"
|
||||||
|
min="0"
|
||||||
|
:awareness="awareness"
|
||||||
|
/>
|
||||||
|
<DetailFormInput
|
||||||
|
title="Anzahl geborgener Personen"
|
||||||
|
type="number"
|
||||||
|
v-model="recovered"
|
||||||
|
min="0"
|
||||||
|
:awareness="awareness"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DetailFormEditor title="Einsatzbeschreibung" :text="editor" />
|
<DetailFormEditor title="Einsatzbeschreibung" :text="editor" :awareness="awareness" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<p>Eingesetzte Fahrzeuge</p>
|
<p>Eingesetzte Fahrzeuge</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,17 +64,14 @@
|
||||||
import { defineComponent, type PropType } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
import { mapState } from "pinia";
|
import { mapState } from "pinia";
|
||||||
import { useAbilityStore } from "@/stores/ability";
|
import { useAbilityStore } from "@/stores/ability";
|
||||||
import { Quill, QuillEditor } from "@vueup/vue-quill";
|
|
||||||
import type QuillCursors from "quill-cursors";
|
|
||||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||||
import { QuillBinding } from "y-quill";
|
|
||||||
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 "@lottiefiles/lottie-player";
|
import "@lottiefiles/lottie-player";
|
||||||
import DetailFormInput from "@/components/admin/operation/mission/DetailFormInput.vue";
|
import DetailFormInput from "@/components/admin/operation/mission/DetailFormInput.vue";
|
||||||
import DetailFormEditor from "@/components/admin/operation/mission/DetailFormEditor.vue";
|
import DetailFormEditor from "@/components/admin/operation/mission/DetailFormEditor.vue";
|
||||||
|
import { Awareness } from "@/helpers/awareness";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -62,8 +82,8 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
awareness: {
|
awareness: {
|
||||||
type: Object as PropType<undefined>,
|
type: Object as PropType<Awareness>,
|
||||||
default: undefined,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
</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'" :document="yDoc" />
|
<MissionPresence v-show="routeHash == '#presence'" :document="yDoc" :awareness="awareness" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -68,6 +68,7 @@ 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";
|
import * as Y from "yjs";
|
||||||
|
import { Awareness } from "@/helpers/awareness";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -77,6 +78,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<Partial<Awareness>>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Add table
Reference in a new issue