implement message view render queue

This commit is contained in:
riqo 2021-02-16 17:00:24 -06:00
commit 86a1460ad2
11 changed files with 160 additions and 83 deletions

View file

@ -23,6 +23,6 @@ export async function main() {
socket = true;
// Begin audio stream.
// initRecorder();
initRecorder();
}

View file

@ -81,7 +81,6 @@ const asyncSend = (_event?, payload: TextMessage | Buffer): Promise<RenderMessag
return new Promise((resolve, reject) => {
backgroundMitt.once('message-res', (res: RenderMessage) => {
console.log(res)
if (res.content) {
resolve(res);
} else {

View file

@ -4,7 +4,6 @@
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">
@ -22,33 +21,54 @@
</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.");
}
@ -60,11 +80,6 @@ export default defineComponent({
emitter.all.clear();
});
const { beforeEnter, enter, afterEnter } = useTransitions();
const { scrollView } = useScrollView({
targetId: "messageView",
});
return {
renderArr,
beforeEnter,

View file

@ -1,9 +1,8 @@
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
@ -11,29 +10,6 @@ export default function useMessageViewAnims() {
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) => {
anime({
targets: el,
transform: transform,
easing: "easeInOutQuad",
duration: 800,
begin: function() {
shiftView(view, viewTransform);
},
});
}
// stacks message from top to bottom
const stackAnimate = (el: Element, transform: string) => {
anime({
targets: el,
transform: transform,
easing: "easeInOutQuad",
duration: 1000,
});
}
// update message View bottom bound
const shiftView = (el: SVGGElement, transform: string) => {
anime({
@ -44,6 +20,39 @@ export default function useMessageViewAnims() {
});
}
// stacks message from top to bottom
const stackAnimate = (el: Element, transform: string) => {
return new Promise((resolve, _reject) => {
anime({
targets: el,
transform: transform,
easing: "easeInOutQuad",
duration: 1000,
complete: function() {
resolve();
}
});
})
}
// 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 {
shiftAnimate,
stackAnimate,

View file

@ -1,11 +1,18 @@
/**
* 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
@ -22,21 +29,23 @@ let messageView: SVGGElement;
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;
@ -52,7 +61,7 @@ export default function useTransitions() {
}
}
const afterEnter: VueTransitionCallback = async (el) => {
const afterEnter: VueTransitionCallback = (el) => {
if (message) messageOffset = calculateMessageOffsets(message);
@ -71,5 +80,6 @@ export default function useTransitions() {
beforeEnter,
enter,
afterEnter,
rendering
};
}

View file

@ -29,7 +29,7 @@ export default function useInputController() {
audio: 0,
text: input
};
emitter.emit("animateSend", message);
emitter.emit("user-message", message);
}
const { visualizeStreamAsPaths, expandBubble } = useAudioVisualizer(visualizeStream);
@ -39,14 +39,8 @@ export default function useInputController() {
// 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;
@ -75,15 +69,8 @@ export default function useInputController() {
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;

View file

@ -32,7 +32,7 @@ export default function useTextVisualizer(input: Ref<string>) {
}
}
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);

View file

@ -20,13 +20,6 @@ export const useIpc = (endpoint: string) => {
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) => {

78
src/modules/queue.ts Normal file
View file

@ -0,0 +1,78 @@
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;
}
}

View file

@ -13,25 +13,11 @@
<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>