socket = true;
// Begin audio stream.
- // initRecorder();
+ initRecorder();
}
return new Promise((resolve, reject) => {
backgroundMitt.once('message-res', (res: RenderMessage) => {
- console.log(res)
if (res.content) {
resolve(res);
} else {
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
:viewBox="scrollView"
- id="test"
>
<g id="messageView" transform="translate(0, 20)">
<g v-for="(item, index) in renderArr" :key="index" preserverAspectRatio="none">
</template>
<script lang="ts">
-import useTransitions from "./viewDetails/transitions";
-import useScrollView from "@/modules/scrollView";
import TextBubble from "@/components/textBubble/index.vue";
import { Message } from "@/types/message/index";
-import { defineComponent, reactive, onMounted, onUnmounted } from "vue";
+import useTransitions from "./viewDetails/transitions";
+import useScrollView from "@/modules/scrollView";
import useMitt from "@/modules/mitt";
import { useIpc } from "@/modules/ipc";
+import { Queue } from '@/modules/queue';
+import { defineComponent, reactive, onMounted, onUnmounted, watch } from "vue";
export default defineComponent({
name: "MessageView",
components: { TextBubble },
setup() {
- const renderArr: Message[] = reactive([]);
const { emitter } = useMitt();
+ const renderArr: Message[] = reactive([]);
+ const renderQ = new Queue();
+
const { data, invoke } = useIpc("message");
+ const { beforeEnter, enter, afterEnter, rendering } = useTransitions();
+ const { scrollView } = useScrollView({
+ targetId: "messageView",
+ });
+
+ const render = (m: Message) => {
+ if (rendering.value) {
+ renderQ.enqueue(m);
+ } else {
+ renderArr.push(m);
+ }
+ }
+
+ watch(rendering, (rendering, _prevRendering) => {
+ if (!rendering) {
+ const m = renderQ.dequeue() as Message | null;
+ if (m) renderArr.push(m);
+ }
+ })
onMounted(() => {
window.ipcRenderer.on("render-message", (event, payload) => {
- const message = payload.message;
- if (message.content) renderArr.push(message);
+ const m = payload.message;
+ if (m.content) render(m);
});
- emitter.on("animateSend", async (message) => {
+ emitter.on("user-message", async (message) => {
try {
- // renderArr.push(message)
await invoke(message);
- renderArr.push(data.value);
+ render(data.value)
} catch (e) {
console.log("No response. Do not render.");
}
emitter.all.clear();
});
- const { beforeEnter, enter, afterEnter } = useTransitions();
- const { scrollView } = useScrollView({
- targetId: "messageView",
- });
-
return {
renderArr,
beforeEnter,
import { inject } from "vue";
import AnimeFunc from "@/types/animejs/index";
-import { MessageAnimeFunc } from '@/types/message/index';
/**
- * anime js animations used by message renderer
+ * anime js animations used by message view transitions
*/
export default function useMessageViewAnims() {
// imports animejs safely
const animeInject: AnimeFunc | undefined = inject("animejs");
if (animeInject) anime = animeInject;
- // adds new message at the bottom of the list and shifts existing message list
- const shiftAnimate = (el: Element, transform: string, view: SVGGElement, viewTransform: string) => {
+ // update message View bottom bound
+ const shiftView = (el: SVGGElement, transform: string) => {
anime({
targets: el,
transform: transform,
easing: "easeInOutQuad",
- duration: 800,
- begin: function() {
- shiftView(view, viewTransform);
- },
+ duration: 500,
});
}
// stacks message from top to bottom
const stackAnimate = (el: Element, transform: string) => {
- anime({
- targets: el,
- transform: transform,
- easing: "easeInOutQuad",
- duration: 1000,
- });
+ return new Promise((resolve, _reject) => {
+ anime({
+ targets: el,
+ transform: transform,
+ easing: "easeInOutQuad",
+ duration: 1000,
+ complete: function() {
+ resolve();
+ }
+ });
+ })
}
- // update message View bottom bound
- const shiftView = (el: SVGGElement, transform: string) => {
- anime({
- targets: el,
- transform: transform,
- easing: "easeInOutQuad",
- duration: 500,
- });
+ // adds new message at the bottom of the list and shifts existing message list
+ const shiftAnimate = (el: Element, transform: string, view: SVGGElement, viewTransform: string) => {
+ return new Promise((resolve, _reject) => {
+ anime({
+ targets: el,
+ transform: transform,
+ easing: "easeInOutQuad",
+ duration: 800,
+ begin: function() {
+ shiftView(view, viewTransform);
+ },
+ complete: function() {
+ resolve();
+ }
+ });
+ })
}
return {
/**
- * handles visualization of view messages through
- * vue transition callbacks and anime-js animations
+ * Do stuff before, on, and after message enters the view.
+ * Rendering Process consists of the execution of the above
+ * mentioned callbacks followed by the shift or stack animation.
+ * The animation called depends on the height of the message view
+ * relative to the window height. Additionally, the animation will
+ * always be called on the after enter callback, after all the neccesary
+ * calculations have been completed. While all of this is hapenning,
+ * the rendering state is updated throughout the process.
*/
import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
import useMessageViewAnims from "./anime";
import useOffsetCalculator from './offsetCalculator';
+import { ref } from 'vue';
const wH = 500; // window Height
const vP = 10; // view padding
export default function useTransitions() {
const { calculateMessageOffsets } = useOffsetCalculator();
const { shiftAnimate, stackAnimate } = useMessageViewAnims();
+ const rendering = ref(false);
- const animateMessage = (el: SVGGElement): void => {
+ const animateMessage = async (el: SVGGElement) => {
const elTransform = `translate(${messageOffset.x} ${messageOffset.y})`;
const viewTransform = `translate(0 ${-vB})`;
if (shift) {
- shiftAnimate(el, elTransform, messageView, viewTransform);
+ await shiftAnimate(el, elTransform, messageView, viewTransform);
} else {
- stackAnimate(el, elTransform)
+ await stackAnimate(el, elTransform)
}
-
+ rendering.value = false;
}
const beforeEnter: VueTransitionCallback = () => {
+ rendering.value = true;
// query DOM for messageView element
const messageViewQuery = document.querySelectorAll("#messageView");
messageView = messageViewQuery[0] as SVGGElement;
}
}
- const afterEnter: VueTransitionCallback = async (el) => {
+ const afterEnter: VueTransitionCallback = (el) => {
if (message) messageOffset = calculateMessageOffsets(message);
beforeEnter,
enter,
afterEnter,
+ rendering
};
}
audio: 0,
text: input
};
- emitter.emit("animateSend", message);
+ emitter.emit("user-message", message);
}
const { visualizeStreamAsPaths, expandBubble } = useAudioVisualizer(visualizeStream);
// stops stream recording on space key up
function keyupHandler(e: any) {
if (e.key === " " && visualizeStream.value) {
- console.log("stop recording");
post(false);
- // window.postMessage({
- // myTypeField: 'update-recorder',
- // record: false
- // }, '*')
-
visualizeStream.value = false;
paths.value = []
audioBubbleWidth.value = 10;
return;
}
if (input === " " && visualizeStream.value === false) {
- console.log("record MediaStream");
visualizeStream.value = true;
-
post(true);
- // window.postMessage({
- // myTypeField: 'update-recorder',
- // record: true
- // }, '*')
-
expandBubble(audioBubbleWidth);
visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
return;
}
}
- emitter.on("animateSend", async (payload: any) => {
+ emitter.on("user-message", () => {
const inputEl = document.getElementsByClassName("inputContainer");
const foreignEl = inputEl[0] as HTMLElement;
animateSend(foreignEl, inputHeight.value, input);
error.value = e;
throw e;
}
-
- // return window.ipcRenderer.invoke(endpoint, payload).then(res => {
- // data.value = res;
- // }).catch(e => {
- // error.value = e;
- // throw e;
- // })
}
const post = (payload: any) => {
--- /dev/null
+export interface DataNode<T> {
+ data: T;
+ next: DataNode<T> | null;
+}
+
+export interface QueueI<T> {
+ enqueue(data: T): void;
+ dequeue(): T | null;
+ peek(): T | null;
+ size(): number;
+}
+
+export class Queue<T> implements QueueI<T> {
+ private _size: number;
+ private head: DataNode<T> | null;
+ private tail: DataNode<T> | null;
+ constructor() {
+ this._size = 0;
+ this.head = this.tail = null;
+ }
+
+ enqueue(data: T) {
+
+ const node: DataNode<T> = {
+ data,
+ next: null
+ }
+ if (this.head === null) {
+ this.head = this.tail = node;
+ } else {
+ if (this.tail) {
+ this.tail.next = node;
+ this.tail = this.tail.next;
+ }
+
+ }
+ ++this._size;
+ }
+
+ dequeue(): T | null {
+
+ let item: T | null = null;
+
+ if (this._size === 0) {
+ return null;
+ }
+
+ if (this.head) {
+ item = this.head.data;
+ this.head = this.head.next;
+ --this._size;
+
+ if (!this._size) {
+ this.tail = null;
+ }
+ }
+ return item;
+ }
+
+ peek(): T | null {
+
+ let item: T | null = null;
+
+ if (this._size === 0) {
+ return null;
+ }
+
+ if (this.head) {
+ item = this.head.data;
+ }
+
+ return item;
+ }
+
+ size(): number {
+ return this._size;
+ }
+}
<script lang="ts">
import MessageView from "@/components/messageView/index.vue";
import TextAudioInput from "@/components/taInput/index.vue";
-import { defineComponent, ref } from "vue";
-import {Messages} from './mockMessages';
-import useMitt from "@/modules/mitt";
+import { defineComponent } from "vue";
export default defineComponent({
name: "Messenger",
components: { MessageView, TextAudioInput },
- setup() {
- const { emitter } = useMitt();
- const counter = ref(0);
-
- const emittMessage = () => {
- emitter.emit('animateSend', Messages[counter.value])
- counter.value++;
- }
- return {
- emittMessage
- }
- }
});
</script>