inputItem v1
This commit is contained in:
parent
38fc1a1b5f
commit
f2a240d803
18 changed files with 249 additions and 1072 deletions
|
|
@ -1,166 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id=inputItem
|
|
||||||
class="input-item"
|
|
||||||
:class="{ playing: isRecording }"
|
|
||||||
:style="{ top: `${elementY}px`, left: `${elementX}px` }">
|
|
||||||
<span class="play"></span>
|
|
||||||
<span class="pause"></span>
|
|
||||||
<TextInput :input="input"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { defineComponent, onMounted, onUnmounted, ref, watch } from "vue";
|
|
||||||
import draggify from '@/modules/draggify';
|
|
||||||
import useMitt from "@/modules/mitt";
|
|
||||||
import TextInput from '@/components/taInput/textDetails/text.vue';
|
|
||||||
import useInputController from "@/components/taInput/inputController";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "InputItem",
|
|
||||||
components: { TextInput },
|
|
||||||
setup() {
|
|
||||||
|
|
||||||
const isRecording = ref(false);
|
|
||||||
|
|
||||||
const { emitter } = useMitt();
|
|
||||||
const { elementX, elementY } = draggify({ elementId: 'inputItem'});
|
|
||||||
|
|
||||||
const {
|
|
||||||
input,
|
|
||||||
visualizeStream,
|
|
||||||
audioBubbleWidth,
|
|
||||||
paths,
|
|
||||||
} = useInputController();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
emitter.on('input-audio-record', (record) => isRecording.value = record);
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
emitter.all.clear();
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
elementX,
|
|
||||||
elementY,
|
|
||||||
isRecording,
|
|
||||||
input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
.input-item {
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
border-radius: 19px;
|
|
||||||
|
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
top: 200px;
|
|
||||||
right: 0px;
|
|
||||||
|
|
||||||
|
|
||||||
background-color: white;
|
|
||||||
border: 4px solid #C3C3C3;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.play, .pause {
|
|
||||||
z-index: 5;
|
|
||||||
&::before, &::after {
|
|
||||||
-webkit-border-radius: 1000px;
|
|
||||||
-moz-border-radius: 1000px;
|
|
||||||
border-radius: 1000px;
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
height: 2.35em;
|
|
||||||
width: 2.35em;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
top: 50%;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.play::before {
|
|
||||||
box-shadow: 0 0 0 rgba(195, 195, 195, 0);
|
|
||||||
}
|
|
||||||
.pause {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
&.playing {
|
|
||||||
.play {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.pause {
|
|
||||||
opacity: 1;
|
|
||||||
&::before {
|
|
||||||
-moz-animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
-o-animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
-webkit-animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
&::after {
|
|
||||||
-moz-animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
-o-animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
-webkit-animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-audio1 {
|
|
||||||
-moz-animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
-o-animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
-webkit-animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
animation: audio1 1.5s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
@keyframes audio1 {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animate-audio2 {
|
|
||||||
-moz-animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
-o-animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
-webkit-animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
animation: audio2 2.2s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
@keyframes audio2 {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.15);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.3);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.05);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.45);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
@ -7,18 +7,12 @@
|
||||||
|
|
||||||
import { ref, watch, onUnmounted, onMounted } from "vue";
|
import { ref, watch, onUnmounted, onMounted } from "vue";
|
||||||
import useKeyDownHandler from "@/modules/keyDownHandler/useKeyDownHandler";
|
import useKeyDownHandler from "@/modules/keyDownHandler/useKeyDownHandler";
|
||||||
import useMediaStream from "@/modules/mediaStream";
|
|
||||||
import useAudioVisualizer from './audioDetails/audioVisualizer';
|
|
||||||
import useTextVisualizer from './textDetails/textVisualizer';
|
|
||||||
import useMitt from "@/modules/mitt";
|
import useMitt from "@/modules/mitt";
|
||||||
import { useIpc } from '@/modules/ipc';
|
import { useIpc } from '@/modules/ipc';
|
||||||
|
|
||||||
let stream: MediaStream;
|
|
||||||
|
|
||||||
export default function useInputController() {
|
export default function useInputController() {
|
||||||
const visualizeStream = ref(false);
|
const isRecording = ref(false);
|
||||||
const paths = ref([]);
|
const isTyping = ref(false);
|
||||||
const audioBubbleWidth = ref(10);
|
|
||||||
const { emitter } = useMitt();
|
const { emitter } = useMitt();
|
||||||
const { post } = useIpc();
|
const { post } = useIpc();
|
||||||
|
|
||||||
|
|
@ -37,40 +31,19 @@ export default function useInputController() {
|
||||||
if (input) post('message', message);
|
if (input) post('message', message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordStream(record: boolean) {
|
|
||||||
// tell ipcMain to pause or resume stream recording.
|
|
||||||
post('update-recorder', record);
|
|
||||||
// toggle inputItem audio animation.
|
|
||||||
emitter.emit('input-audio-record', record);
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
visualizeStreamAsPaths,
|
|
||||||
expandBubble
|
|
||||||
} = useAudioVisualizer(visualizeStream);
|
|
||||||
|
|
||||||
const { keyDownHandler, input } = useKeyDownHandler(enterCallback);
|
const { keyDownHandler, input } = useKeyDownHandler(enterCallback);
|
||||||
|
|
||||||
const {
|
|
||||||
textInputXoffset,
|
|
||||||
textInputYoffset,
|
|
||||||
renderTextInput
|
|
||||||
} = useTextVisualizer(input);
|
|
||||||
|
|
||||||
// stops stream recording on space key up
|
// stops stream recording on space key up
|
||||||
function keyupHandler(e: any) {
|
function keyupHandler(e: any) {
|
||||||
if (e.key === " " && visualizeStream.value) {
|
if (e.key === " " && isRecording.value) {
|
||||||
|
|
||||||
recordStream(false)
|
isRecording.value = false;
|
||||||
visualizeStream.value = false;
|
post('update-recorder', isRecording.value);
|
||||||
paths.value = []
|
|
||||||
audioBubbleWidth.value = 10;
|
|
||||||
input.value = ''
|
input.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
stream = await useMediaStream({ audio: true });
|
|
||||||
// add window event keyboard listeners
|
// add window event keyboard listeners
|
||||||
window.addEventListener("keydown", keyDownHandler);
|
window.addEventListener("keydown", keyDownHandler);
|
||||||
window.addEventListener('keyup', keyupHandler);
|
window.addEventListener('keyup', keyupHandler);
|
||||||
|
|
@ -84,27 +57,24 @@ export default function useInputController() {
|
||||||
// determine whether to render text or audio based one the keyboard input
|
// determine whether to render text or audio based one the keyboard input
|
||||||
watch(input, (input, prevInput) => {
|
watch(input, (input, prevInput) => {
|
||||||
if (prevInput !== "" || prevInput.length > 0) {
|
if (prevInput !== "" || prevInput.length > 0) {
|
||||||
if (!visualizeStream.value) {
|
if (!isRecording.value) {
|
||||||
renderTextInput();
|
isTyping.value = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (input === " " && visualizeStream.value === false) {
|
if (input === " " && isRecording.value === false) {
|
||||||
visualizeStream.value = true;
|
// isTyping.value = false;
|
||||||
recordStream(true)
|
isRecording.value = true;
|
||||||
expandBubble(audioBubbleWidth);
|
post('update-recorder', isRecording.value);
|
||||||
visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderTextInput();
|
isTyping.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
input,
|
input,
|
||||||
visualizeStream,
|
isRecording,
|
||||||
audioBubbleWidth,
|
isTyping
|
||||||
paths
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
177
src/components/inputItem/inputItem.vue
Normal file
177
src/components/inputItem/inputItem.vue
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
id="inputItem"
|
||||||
|
class="input-item"
|
||||||
|
:class="{ playing: isRecording }"
|
||||||
|
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
|
||||||
|
>
|
||||||
|
<span v-if="isRecording" class="play"></span>
|
||||||
|
<span v-if="isRecording" class="pause"></span>
|
||||||
|
<div class="text-input" v-show="isTyping && input.length > 0">
|
||||||
|
<div class="initials">A</div>
|
||||||
|
<input id="textInput" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import draggify from "@/modules/draggify";
|
||||||
|
import useInputController from "./inputController";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "InputItem",
|
||||||
|
setup() {
|
||||||
|
const { elementX, elementY } = draggify({ elementId: "inputItem" });
|
||||||
|
const { input, isRecording, isTyping } = useInputController();
|
||||||
|
|
||||||
|
return {
|
||||||
|
elementX,
|
||||||
|
elementY,
|
||||||
|
isRecording,
|
||||||
|
isTyping,
|
||||||
|
input,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.text-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
left: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
transform: translate(-12%, -50%);
|
||||||
|
border-radius: 25px;
|
||||||
|
input {
|
||||||
|
border-top-right-radius: 25px;
|
||||||
|
border-bottom-right-radius: 25px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.initials {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-item {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
border-radius: 19px;
|
||||||
|
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
top: 200px;
|
||||||
|
right: 0px;
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
|
border: 4px solid #C3C3C3;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.play,
|
||||||
|
.pause {
|
||||||
|
z-index: 5;
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
-webkit-border-radius: 1000px;
|
||||||
|
-moz-border-radius: 1000px;
|
||||||
|
border-radius: 1000px;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 2.35em;
|
||||||
|
width: 2.35em;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
top: 50%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.play::before {
|
||||||
|
box-shadow: 0 0 0 rgba(195, 195, 195, 0);
|
||||||
|
}
|
||||||
|
.pause {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&.playing {
|
||||||
|
.play {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.pause {
|
||||||
|
opacity: 1;
|
||||||
|
&::before {
|
||||||
|
-moz-animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
-o-animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
-webkit-animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
-moz-animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
-o-animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
-webkit-animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-audio1 {
|
||||||
|
-moz-animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
-o-animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
-webkit-animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
animation: audio1 1.5s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
@keyframes audio1 {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.animate-audio2 {
|
||||||
|
-moz-animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
-o-animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
-webkit-animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
animation: audio2 2.2s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
@keyframes audio2 {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.15);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.3);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.05);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
:viewBox="scrollView"
|
|
||||||
@click="emittMessage"
|
|
||||||
>
|
|
||||||
<g id="messageView" transform="translate(0, 20)">
|
|
||||||
<g v-for="(item, index) in renderArr" :key="index" preserverAspectRatio="none">
|
|
||||||
<transition
|
|
||||||
appear
|
|
||||||
v-on:before-appear="beforeEnter"
|
|
||||||
v-on:appear="enter"
|
|
||||||
v-on:after-enter="afterEnter"
|
|
||||||
>
|
|
||||||
<TextBubble :item="item" :key="index" />
|
|
||||||
</transition>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import TextBubble from "@/components/textBubble/index.vue";
|
|
||||||
import { Message } from "@/types/message/index";
|
|
||||||
import { useIpc } from "@/modules/ipc";
|
|
||||||
import { Queue } from "@/modules/queue";
|
|
||||||
import useTransitions from "./viewDetails/transitions";
|
|
||||||
import useScrollView from "@/modules/scrollView";
|
|
||||||
import useMitt from "@/modules/mitt";
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
reactive,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
watch,
|
|
||||||
} from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "MessageView",
|
|
||||||
components: { TextBubble },
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const { emitter } = useMitt();
|
|
||||||
const renderArr: Message[] = reactive([]);
|
|
||||||
const renderQ = new Queue();
|
|
||||||
|
|
||||||
const { invoke } = useIpc("message");
|
|
||||||
|
|
||||||
// Each method will be called on their respective lifecycle event.
|
|
||||||
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(() => {
|
|
||||||
// Call render function on event "render-message."
|
|
||||||
window.ipcRenderer.on("render-message", (event, payload) => {
|
|
||||||
const m = payload.message;
|
|
||||||
if (m.content) render(m);
|
|
||||||
});
|
|
||||||
|
|
||||||
emitter.on("user-message", async (message) => {
|
|
||||||
try {
|
|
||||||
const res = await invoke(message);
|
|
||||||
render(res);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("No response. Do not render.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.ipcRenderer.removeAllListeners("render-message");
|
|
||||||
emitter.all.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
renderArr,
|
|
||||||
beforeEnter,
|
|
||||||
enter,
|
|
||||||
afterEnter,
|
|
||||||
scrollView,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
#messageView {
|
|
||||||
width: 1000px;
|
|
||||||
height: 100vh;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
:width="bubbleWidth"
|
|
||||||
height="34"
|
|
||||||
:x="center"
|
|
||||||
y="440"
|
|
||||||
rx="10"
|
|
||||||
class="audioInput"
|
|
||||||
>
|
|
||||||
<g id="Group_126" data-name="Group 126" transform="translate(0 0)" rx="10">
|
|
||||||
<rect
|
|
||||||
id="Rectangle_478"
|
|
||||||
data-name="Rectangle 478"
|
|
||||||
:width="bubbleWidth"
|
|
||||||
height="34"
|
|
||||||
rx="10"
|
|
||||||
transform="translate(0 0)"
|
|
||||||
fill="#b9b9b9"
|
|
||||||
/>
|
|
||||||
<svg>
|
|
||||||
<g
|
|
||||||
rx="20"
|
|
||||||
id="Group_125"
|
|
||||||
data-name="Group 125"
|
|
||||||
v-for="(path, index) in paths"
|
|
||||||
:key="path.identifier"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
:key="index"
|
|
||||||
id="path"
|
|
||||||
:data-name="path.identifier"
|
|
||||||
:d="path.d"
|
|
||||||
:transform="`translate(${path.offset} 0)`"
|
|
||||||
fill="none"
|
|
||||||
:stroke="path.color"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-width="1.5"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, computed, inject } from "vue";
|
|
||||||
export default defineComponent({
|
|
||||||
name: "AudioStream",
|
|
||||||
props: {
|
|
||||||
bubbleWidth: Number,
|
|
||||||
paths: Array,
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const emitter: any = inject("mitt");
|
|
||||||
|
|
||||||
const center = computed(() => {
|
|
||||||
if (props.bubbleWidth) {
|
|
||||||
return 175 - props.bubbleWidth / 2;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { center };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
import { Ref } from '@/types/vueRef/index';
|
|
||||||
import AnimeFunc from "@/types/animejs/index";
|
|
||||||
import { inject, ref } from "vue";
|
|
||||||
|
|
||||||
interface AudioPath {
|
|
||||||
identifier: string;
|
|
||||||
d: string;
|
|
||||||
offset: number;
|
|
||||||
color: string;
|
|
||||||
draw: boolean;
|
|
||||||
}
|
|
||||||
export default function useAudioVisualizer(draw: Ref<boolean>) {
|
|
||||||
const audioContext = new AudioContext();
|
|
||||||
const analyzer = audioContext.createAnalyser();
|
|
||||||
analyzer.fftSize = 128;
|
|
||||||
const bufferLength = analyzer.frequencyBinCount;
|
|
||||||
const dataArray = new Uint8Array(bufferLength);
|
|
||||||
|
|
||||||
// imports animejs safely
|
|
||||||
let anime: AnimeFunc;
|
|
||||||
const animeInject: AnimeFunc | undefined = inject("animejs");
|
|
||||||
if (animeInject) anime = animeInject;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Draws MediaStream audioTrack as 'AudioPath' objects
|
|
||||||
*/
|
|
||||||
function drawStream(paths: Ref<any>, bubbleWidth: Ref<number>): void {
|
|
||||||
const xInitial = ref(10);
|
|
||||||
const maxAmplitude = 15;
|
|
||||||
const maxBubbleWidth = 300;
|
|
||||||
|
|
||||||
function shiftPathStream(pathList: NodeList): void {
|
|
||||||
for (let i = 0; i < pathList.length; i++) {
|
|
||||||
const stick = pathList[i] as HTMLElement;
|
|
||||||
const computedQuery = window.getComputedStyle(stick);
|
|
||||||
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
|
|
||||||
anime({
|
|
||||||
targets: stick,
|
|
||||||
translateX: [matrix.m41, matrix.m41 - 6],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function filterPathList(pathList: NodeList) {
|
|
||||||
for (let i = 0; i < pathList.length; i++) {
|
|
||||||
const stick = pathList[i] as HTMLElement;
|
|
||||||
const computedQuery = window.getComputedStyle(stick);
|
|
||||||
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
|
|
||||||
if (matrix.m41 < 10) {
|
|
||||||
anime({
|
|
||||||
targets: stick,
|
|
||||||
opacity: 0,
|
|
||||||
duration: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawNewPath(): void {
|
|
||||||
|
|
||||||
const calculateAmplitude = (): number => (
|
|
||||||
Math.min(Math.max(
|
|
||||||
(dataArray.reduce((a, b) => a + b) / bufferLength) * 0.45,
|
|
||||||
0), maxAmplitude)
|
|
||||||
)
|
|
||||||
|
|
||||||
const createAudioPath = (a: number, o: number): AudioPath => ({
|
|
||||||
identifier: `path_${o}`,
|
|
||||||
d: `M0,25v${-a}`,
|
|
||||||
offset: o,
|
|
||||||
color: '#000',
|
|
||||||
draw: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const amplitude = calculateAmplitude();
|
|
||||||
const p: AudioPath = createAudioPath(amplitude, xInitial.value);
|
|
||||||
paths.value.push(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
paths.value = [];
|
|
||||||
function drawPathStream(): void {
|
|
||||||
|
|
||||||
let streamPath;
|
|
||||||
if (!draw.value) {
|
|
||||||
window.clearTimeout(streamPath)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
requestAnimationFrame(drawPathStream);
|
|
||||||
analyzer.getByteFrequencyData(dataArray);
|
|
||||||
}, 1000 / 10)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* To Draw pathStream
|
|
||||||
*/
|
|
||||||
if (bubbleWidth.value === 300) {
|
|
||||||
xInitial.value = bubbleWidth.value - 10;
|
|
||||||
} else if (bubbleWidth.value > 20) {
|
|
||||||
xInitial.value = bubbleWidth.value;
|
|
||||||
} else {
|
|
||||||
xInitial.value += 3;
|
|
||||||
}
|
|
||||||
streamPath = setTimeout(async () => {
|
|
||||||
const pathList = document.querySelectorAll('path');
|
|
||||||
filterPathList(pathList);
|
|
||||||
drawNewPath();
|
|
||||||
if (bubbleWidth.value === maxBubbleWidth) {
|
|
||||||
shiftPathStream(pathList);
|
|
||||||
}
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
drawPathStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
function increaseBubbleWidth(bubbleRef: Ref<number>) {
|
|
||||||
const increase = () => {
|
|
||||||
|
|
||||||
let animation;
|
|
||||||
if (!draw.value) {
|
|
||||||
if (animation) {
|
|
||||||
cancelAnimationFrame(animation)
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
animation = requestAnimationFrame(increase);
|
|
||||||
if (bubbleRef.value < 300) {
|
|
||||||
bubbleRef.value++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
increase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function expandBubble(bubbleWidth: Ref<number>) {
|
|
||||||
increaseBubbleWidth(bubbleWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
function visualizeStreamAsPaths(s: MediaStream, paths: Ref<any>, bubbleWidth: Ref<number>): void {
|
|
||||||
const source = audioContext.createMediaStreamSource(s);
|
|
||||||
source.connect(analyzer);
|
|
||||||
drawStream(paths, bubbleWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
visualizeStreamAsPaths,
|
|
||||||
expandBubble
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<template>
|
|
||||||
|
|
||||||
<!-- render audio or text input-->
|
|
||||||
<AudioStream
|
|
||||||
v-if="visualizeStream"
|
|
||||||
:bubbleWidth="audioBubbleWidth"
|
|
||||||
:paths="paths"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <TextInput
|
|
||||||
v-else
|
|
||||||
:input="input"
|
|
||||||
/>
|
|
||||||
-->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
// import child components
|
|
||||||
import TextInput from "./textDetails/text.vue";
|
|
||||||
import AudioStream from "./audioDetails/audio.vue";
|
|
||||||
|
|
||||||
import useInputController from "./inputController";
|
|
||||||
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
|
|
||||||
name: "TextAudioInput",
|
|
||||||
components: { TextInput, AudioStream },
|
|
||||||
setup() {
|
|
||||||
// get text input
|
|
||||||
|
|
||||||
// get audio stream
|
|
||||||
|
|
||||||
// use Input Handler
|
|
||||||
|
|
||||||
const {
|
|
||||||
input,
|
|
||||||
visualizeStream,
|
|
||||||
audioBubbleWidth,
|
|
||||||
paths,
|
|
||||||
} = useInputController();
|
|
||||||
|
|
||||||
return {
|
|
||||||
input,
|
|
||||||
|
|
||||||
visualizeStream,
|
|
||||||
audioBubbleWidth,
|
|
||||||
paths,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
<template>
|
|
||||||
|
|
||||||
<div class="inputBox">
|
|
||||||
<div class="inputBubble" v-show="input.length > 0">
|
|
||||||
<p>{{ input }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
|
|
||||||
name: "TextInput",
|
|
||||||
props: {
|
|
||||||
input: String,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
.inputBox {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
// bottom: 0;
|
|
||||||
|
|
||||||
// z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputBubble {
|
|
||||||
max-width: 180px;
|
|
||||||
min-height: 32px;
|
|
||||||
|
|
||||||
display: table;
|
|
||||||
justify-items: center;
|
|
||||||
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #B9B9B9;
|
|
||||||
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
max-width: 150px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
padding: 7px 10px 5px 10px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import AnimeFunc from "@/types/animejs/index";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Ref } from "@/types/vueRef/index";
|
|
||||||
/*
|
|
||||||
* Anime js animations used by input textController
|
|
||||||
*/
|
|
||||||
export default function useTextInputAnims() {
|
|
||||||
|
|
||||||
// imports animejs safely
|
|
||||||
let anime: AnimeFunc;
|
|
||||||
const animeInject: AnimeFunc | undefined = inject("animejs");
|
|
||||||
if (animeInject) anime = animeInject;
|
|
||||||
|
|
||||||
const animateSend = (el: HTMLElement, elHeight: number, input: Ref<string>) => {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
translateY: -elHeight + 15,
|
|
||||||
duration: 200,
|
|
||||||
easing: "easeInQuad",
|
|
||||||
complete: function() {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
translateY: 50,
|
|
||||||
duration: 200,
|
|
||||||
easing: "easeOutQuad",
|
|
||||||
complete: function() {
|
|
||||||
input.value = "";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputAppear = (el: HTMLElement) => {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
translateY: -10,
|
|
||||||
duration: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const verticalShiftInput = (el: HTMLElement, yTrans: number) => {
|
|
||||||
anime({
|
|
||||||
targets: el,
|
|
||||||
translateY: yTrans,
|
|
||||||
easing: "easeOutQuad",
|
|
||||||
duration: 150,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
animateSend,
|
|
||||||
verticalShiftInput,
|
|
||||||
inputAppear
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* text rendering logic used by inputController
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
||||||
import useTextInputAnims from "./textAnime";
|
|
||||||
import { Ref } from "@/types/vueRef/index";
|
|
||||||
import useMitt from "@/modules/mitt";
|
|
||||||
|
|
||||||
export default function useTextVisualizer(input: Ref<string>) {
|
|
||||||
const inputHeight = ref(42);
|
|
||||||
const inputWidth = ref(0);
|
|
||||||
const { emitter } = useMitt();
|
|
||||||
const { animateSend, verticalShiftInput, inputAppear } = useTextInputAnims();
|
|
||||||
|
|
||||||
async function renderTextInput() {
|
|
||||||
// first, get input dimensions
|
|
||||||
const inputDivs = await document.getElementsByClassName("inputBubble");
|
|
||||||
const height = inputDivs[0].getBoundingClientRect().height;
|
|
||||||
inputWidth.value = inputDivs[0].getBoundingClientRect().width;
|
|
||||||
// then, query for foreignObject div
|
|
||||||
const foreignObjectDiv = document.getElementsByClassName(
|
|
||||||
"inputContainer"
|
|
||||||
);
|
|
||||||
const foreignEl = foreignObjectDiv[0] as HTMLElement;
|
|
||||||
if (height !== inputHeight.value) {
|
|
||||||
inputHeight.value = height;
|
|
||||||
}
|
|
||||||
if (inputHeight.value === 0) {
|
|
||||||
inputAppear(foreignEl)
|
|
||||||
} else if (inputHeight.value > 36) {
|
|
||||||
const yTrans = -inputHeight.value + 30;
|
|
||||||
verticalShiftInput(foreignEl, yTrans);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
emitter.on("animate-send", () => {
|
|
||||||
const inputEl = document.getElementsByClassName("inputContainer");
|
|
||||||
const foreignEl = inputEl[0] as HTMLElement;
|
|
||||||
animateSend(foreignEl, inputHeight.value, input);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
emitter.all.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
const textInputXoffset = computed(() => {
|
|
||||||
return `calc(50% - ${inputWidth.value / 2})`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const textInputYoffset = computed(() => {
|
|
||||||
return `calc(97% - ${inputHeight.value})`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
textInputXoffset,
|
|
||||||
textInputYoffset,
|
|
||||||
renderTextInput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { Ref } from "@/types/vueRef/index";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handles visualization of individual svg text bubbles
|
|
||||||
*/
|
|
||||||
export default function useBubbleDrawer({
|
|
||||||
mr,
|
|
||||||
tr,
|
|
||||||
}: {
|
|
||||||
mr: Ref<string>;
|
|
||||||
tr: Ref<string>;
|
|
||||||
}) {
|
|
||||||
const signatureTranslate = ref("");
|
|
||||||
let messageRect: SVGSVGElement & SVGRectElement;
|
|
||||||
let messageText: SVGSVGElement & SVGTextElement;
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
messageRect = (mr.value as unknown) as SVGSVGElement & SVGRectElement;
|
|
||||||
messageText = (tr.value as unknown) as SVGSVGElement & SVGTextElement;
|
|
||||||
});
|
|
||||||
|
|
||||||
function adjustMessageRect(): void {
|
|
||||||
// must get dimensions of text box to fit bubble around
|
|
||||||
const padding = 12.5;
|
|
||||||
const textBox = messageText.getBBox();
|
|
||||||
messageRect.setAttribute("x", String(textBox.x - padding));
|
|
||||||
messageRect.setAttribute("y", String(textBox.y - padding));
|
|
||||||
messageRect.setAttribute("width", String(textBox.width + 2 * padding));
|
|
||||||
messageRect.setAttribute("height", String(textBox.height + 2 * padding));
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateMsgSignature(): void {
|
|
||||||
if (messageRect.getAttribute("height") !== null) {
|
|
||||||
const height = Number(messageRect.getAttribute("height"));
|
|
||||||
signatureTranslate.value = `translate(-10 ${height})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function drawMessage() {
|
|
||||||
// ignore lint error: await needed for proper render
|
|
||||||
await adjustMessageRect();
|
|
||||||
translateMsgSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
drawMessage,
|
|
||||||
signatureTranslate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { ref } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
|
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
|
||||||
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
|
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
|
||||||
|
|
||||||
interface KeyDownEvent {
|
interface KeyDownEvent {
|
||||||
keyCode: number;
|
keyCode: number;
|
||||||
shiftKey: boolean;
|
shiftKey: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This catches keys from a physical keyboard, a soft keyboard on a tablet, or a
|
// This catches keys from a physical keyboard, a soft keyboard on a tablet, or a
|
||||||
|
|
@ -13,6 +13,12 @@ export default function useKeyDownHandler(enterCallback?: (input: string) => voi
|
||||||
|
|
||||||
const input = ref("");
|
const input = ref("");
|
||||||
|
|
||||||
|
let textInput: HTMLInputElement | null;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
textInput = document.getElementById('textInput') as HTMLInputElement;
|
||||||
|
})
|
||||||
|
|
||||||
function keyDownHandler(e: KeyDownEvent) {
|
function keyDownHandler(e: KeyDownEvent) {
|
||||||
// keyboardCharMap is an array of arrays, with each inner
|
// keyboardCharMap is an array of arrays, with each inner
|
||||||
// array having 2 columns - un-shifted, and shifted values.
|
// array having 2 columns - un-shifted, and shifted values.
|
||||||
|
|
@ -20,6 +26,8 @@ export default function useKeyDownHandler(enterCallback?: (input: string) => voi
|
||||||
// See the "keyboardCharMap", below, for printable characters.
|
// See the "keyboardCharMap", below, for printable characters.
|
||||||
// MODIFY keyboardCharMap to suit your needs if you want
|
// MODIFY keyboardCharMap to suit your needs if you want
|
||||||
// more/less/different characters to be considered "printable".
|
// more/less/different characters to be considered "printable".
|
||||||
|
if (textInput) textInput.focus()
|
||||||
|
|
||||||
let iCol = 0;
|
let iCol = 0;
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
iCol = 1;
|
iCol = 1;
|
||||||
|
|
@ -32,11 +40,14 @@ export default function useKeyDownHandler(enterCallback?: (input: string) => voi
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "ENTER":
|
case "ENTER":
|
||||||
if (enterCallback) enterCallback(input.value);
|
if (enterCallback) enterCallback(input.value);
|
||||||
|
if (textInput) textInput.value = ''
|
||||||
|
input.value = " ";
|
||||||
break;
|
break;
|
||||||
case "BACK_SPACE":
|
case "BACK_SPACE":
|
||||||
input.value = input.value.slice(0, -1);
|
input.value = input.value.slice(0, -1);
|
||||||
break;
|
break;
|
||||||
case "ESCAPE":
|
case "ESCAPE":
|
||||||
|
if (textInput) textInput.value = ''
|
||||||
input.value = "";
|
input.value = "";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
export default async function useMediaStream(constraints: {
|
|
||||||
audio: boolean;
|
|
||||||
video?: boolean;
|
|
||||||
}): Promise<MediaStream> {
|
|
||||||
try {
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
||||||
return stream;
|
|
||||||
} catch (e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
// var audioTack = stream.getAudioTracks()[0];
|
|
||||||
// console.log(audioTrack.getSettings())
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handles visualization of view messages through
|
|
||||||
* vue transition callbacks and anime-js animations
|
|
||||||
*/
|
|
||||||
export default function useScrollView({ targetId }: { targetId: string }) {
|
|
||||||
|
|
||||||
const scroll = ref(0);
|
|
||||||
let scrollTarget: HTMLElement | SVGElement | null;
|
|
||||||
const targetHeight = ref(0);
|
|
||||||
let topBound: number;
|
|
||||||
const scrollStartHeight = 445;
|
|
||||||
|
|
||||||
const scrollView = computed(() => {
|
|
||||||
|
|
||||||
if (clampedScroll.value === 0) {
|
|
||||||
scroll.value = 0;
|
|
||||||
} else if (clampedScroll.value === topBound) {
|
|
||||||
scroll.value = topBound;
|
|
||||||
}
|
|
||||||
|
|
||||||
// x, y, width, height
|
|
||||||
// width and height should be dynamic
|
|
||||||
return `0 ${clampedScroll.value} 350 500`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const clampedScroll = computed(() => {
|
|
||||||
if (targetHeight.value === 0) { return 0; }
|
|
||||||
topBound = -targetHeight.value + 450;
|
|
||||||
return Math.max(topBound, Math.min(scroll.value, 0)) as number;
|
|
||||||
})
|
|
||||||
|
|
||||||
const getHeight = () => {
|
|
||||||
if (scrollTarget) {
|
|
||||||
return scrollTarget.getBoundingClientRect().height as number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getWidth = () => {
|
|
||||||
if (scrollTarget) {
|
|
||||||
return scrollTarget.getBoundingClientRect().width as number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateView = (verticalScroll: number) => {
|
|
||||||
|
|
||||||
if (scrollTarget) {
|
|
||||||
|
|
||||||
const heightResult = getHeight();
|
|
||||||
const widthResult = getWidth();
|
|
||||||
|
|
||||||
console.log(heightResult);
|
|
||||||
console.log(widthResult);
|
|
||||||
|
|
||||||
if (heightResult) {
|
|
||||||
// only scroll if renderer scrollStart
|
|
||||||
if (heightResult > scrollStartHeight) {
|
|
||||||
targetHeight.value = heightResult;
|
|
||||||
scroll.value += verticalScroll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollUpdate = (e: any): void => {
|
|
||||||
const verticalScroll = e.deltaY * 0.25;
|
|
||||||
updateView(verticalScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
scrollTarget = document.getElementById("targetId");
|
|
||||||
if (scrollTarget) {
|
|
||||||
window.addEventListener("wheel", scrollUpdate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener("wheel", scrollUpdate);
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
scrollView
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
export default function useTextWrap() {
|
|
||||||
// text wraps a string to a given width
|
|
||||||
function textWrap({ s, w }: { s: string; w: number }): string[] {
|
|
||||||
// return tspan elements for text with correct size
|
|
||||||
const wrap = (s: string, w: number) =>
|
|
||||||
s.replace(
|
|
||||||
new RegExp(`(?![^\\n]{1,${w}}$)([^\\n]{1,${w}})\\s`, "g"),
|
|
||||||
"$1\n"
|
|
||||||
);
|
|
||||||
const result = wrap(s, w).split("\n");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
textWrap
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -35,21 +35,21 @@ const router = createRouter({
|
||||||
});
|
});
|
||||||
|
|
||||||
// route auth check
|
// route auth check
|
||||||
router.beforeEach((to, from, next) => {
|
// router.beforeEach((to, from, next) => {
|
||||||
const { accessToken } = useAuth();
|
// const { accessToken } = useAuth();
|
||||||
|
//
|
||||||
// Not logged into a guarded route?
|
// // Not logged into a guarded route?
|
||||||
if (to.meta.requiresAuth && !accessToken.value) {
|
// if (to.meta.requiresAuth && !accessToken.value) {
|
||||||
next({ name: 'login' })
|
// next({ name: 'login' })
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Logged in for an auth route
|
// // Logged in for an auth route
|
||||||
else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
|
// else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
|
||||||
next({ name: 'home' });
|
// next({ name: 'home' });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Carry On...
|
// // Carry On...
|
||||||
else next();
|
// else next();
|
||||||
})
|
// })
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
|
<InputItem />
|
||||||
|
|
||||||
<TextAudioInput />
|
<!-- List of message bubbles. -->
|
||||||
<InputItem />
|
<div id="messenger">
|
||||||
|
<MessageItem v-for="message in messages" :message="message" :key="message.id" />
|
||||||
<!-- List of message bubbles. -->
|
</div>
|
||||||
<div id="messenger">
|
|
||||||
<MessageItem v-for="message in messages" :message="message" :key="message.id"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, onMounted, onUnmounted } from "vue";
|
||||||
import { defineComponent, reactive, onMounted, ref, onUnmounted } from "vue";
|
|
||||||
import TextAudioInput from "@/components/taInput/index.vue";
|
|
||||||
import { Message } from "@/types/message/index";
|
import { Message } from "@/types/message/index";
|
||||||
import MessageItem from '@/components/messageItem.vue'
|
import MessageItem from "@/components/messageItem.vue";
|
||||||
import InputItem from "@/components/inputItem.vue";
|
import InputItem from "@/components/inputItem/inputItem.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Messenger",
|
name: "Messenger",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
TextAudioInput,
|
|
||||||
MessageItem,
|
MessageItem,
|
||||||
InputItem
|
InputItem,
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
|
||||||
// List of messages in the view.
|
// List of messages in the view.
|
||||||
// Oldest message first.
|
// Oldest message first.
|
||||||
const messages: Message[] = reactive([]);
|
const messages: Message[] = reactive([]);
|
||||||
|
|
@ -36,48 +29,42 @@ export default defineComponent({
|
||||||
// Render a message.
|
// Render a message.
|
||||||
const render = (message: Message) => {
|
const render = (message: Message) => {
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
console.log(`Pushing: ${message.modifier}: ${message.content.text} ${message.context}`);
|
console.log(
|
||||||
}
|
`Pushing: ${message.modifier}: ${message.content.text} ${message.context}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
// Listen for new messages to render.
|
// Listen for new messages to render.
|
||||||
window.ipcRenderer.on("render-message", (event, payload) => {
|
window.ipcRenderer.on("render-message", (event, payload) => {
|
||||||
if (payload.message.content) render(payload.message);
|
if (payload.message.content) render(payload.message);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.ipcRenderer.removeAllListeners('render-message');
|
window.ipcRenderer.removeAllListeners("render-message");
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
}
|
};
|
||||||
|
},
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
#messenger {
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
#messenger {
|
width: 100vw;
|
||||||
position: fixed;
|
min-height: 100vh;
|
||||||
|
|
||||||
width: 100vw;
|
overflow-y: scroll;
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
bottom: 0;
|
||||||
|
}
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#messenger::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#messenger::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue