implement message view render queue
This commit is contained in:
parent
c27dd5ca95
commit
86a1460ad2
11 changed files with 160 additions and 83 deletions
|
|
@ -23,6 +23,6 @@ export async function main() {
|
|||
socket = true;
|
||||
|
||||
// Begin audio stream.
|
||||
// initRecorder();
|
||||
initRecorder();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
78
src/modules/queue.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue