inputItem v1

This commit is contained in:
Enrique Hernandez 2021-03-07 18:41:22 -06:00
commit f2a240d803
18 changed files with 249 additions and 1072 deletions

View file

@ -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>

View file

@ -7,18 +7,12 @@
import { ref, watch, onUnmounted, onMounted } from "vue";
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 { useIpc } from '@/modules/ipc';
let stream: MediaStream;
export default function useInputController() {
const visualizeStream = ref(false);
const paths = ref([]);
const audioBubbleWidth = ref(10);
const isRecording = ref(false);
const isTyping = ref(false);
const { emitter } = useMitt();
const { post } = useIpc();
@ -37,40 +31,19 @@ export default function useInputController() {
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 {
textInputXoffset,
textInputYoffset,
renderTextInput
} = useTextVisualizer(input);
// stops stream recording on space key up
function keyupHandler(e: any) {
if (e.key === " " && visualizeStream.value) {
if (e.key === " " && isRecording.value) {
recordStream(false)
visualizeStream.value = false;
paths.value = []
audioBubbleWidth.value = 10;
isRecording.value = false;
post('update-recorder', isRecording.value);
input.value = ''
}
}
onMounted(async () => {
stream = await useMediaStream({ audio: true });
// add window event keyboard listeners
window.addEventListener("keydown", keyDownHandler);
window.addEventListener('keyup', keyupHandler);
@ -84,27 +57,24 @@ export default function useInputController() {
// determine whether to render text or audio based one the keyboard input
watch(input, (input, prevInput) => {
if (prevInput !== "" || prevInput.length > 0) {
if (!visualizeStream.value) {
renderTextInput();
if (!isRecording.value) {
isTyping.value = true;
}
return;
}
if (input === " " && visualizeStream.value === false) {
visualizeStream.value = true;
recordStream(true)
expandBubble(audioBubbleWidth);
visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
if (input === " " && isRecording.value === false) {
// isTyping.value = false;
isRecording.value = true;
post('update-recorder', isRecording.value);
return;
}
renderTextInput();
isTyping.value = true;
});
return {
input,
visualizeStream,
audioBubbleWidth,
paths
isRecording,
isTyping
}
}

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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,
};
}

View file

@ -1,10 +1,10 @@
import { ref } from "vue";
import { ref, onMounted } from "vue";
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
interface KeyDownEvent {
keyCode: number;
shiftKey: boolean;
keyCode: number;
shiftKey: boolean;
}
// 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("");
let textInput: HTMLInputElement | null;
onMounted(() => {
textInput = document.getElementById('textInput') as HTMLInputElement;
})
function keyDownHandler(e: KeyDownEvent) {
// keyboardCharMap is an array of arrays, with each inner
// 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.
// MODIFY keyboardCharMap to suit your needs if you want
// more/less/different characters to be considered "printable".
if (textInput) textInput.focus()
let iCol = 0;
if (e.shiftKey) {
iCol = 1;
@ -32,11 +40,14 @@ export default function useKeyDownHandler(enterCallback?: (input: string) => voi
switch (cmd) {
case "ENTER":
if (enterCallback) enterCallback(input.value);
if (textInput) textInput.value = ''
input.value = " ";
break;
case "BACK_SPACE":
input.value = input.value.slice(0, -1);
break;
case "ESCAPE":
if (textInput) textInput.value = ''
input.value = "";
break;
default:

View file

@ -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())
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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
};
}

View file

@ -35,21 +35,21 @@ const router = createRouter({
});
// route auth check
router.beforeEach((to, from, next) => {
const { accessToken } = useAuth();
// Not logged into a guarded route?
if (to.meta.requiresAuth && !accessToken.value) {
next({ name: 'login' })
}
// Logged in for an auth route
else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
next({ name: 'home' });
}
// Carry On...
else next();
})
// router.beforeEach((to, from, next) => {
// const { accessToken } = useAuth();
//
// // Not logged into a guarded route?
// if (to.meta.requiresAuth && !accessToken.value) {
// next({ name: 'login' })
// }
//
// // Logged in for an auth route
// else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
// next({ name: 'home' });
// }
//
// // Carry On...
// else next();
// })
export default router;

View file

@ -1,34 +1,27 @@
<template>
<InputItem />
<TextAudioInput />
<InputItem />
<!-- List of message bubbles. -->
<div id="messenger">
<MessageItem v-for="message in messages" :message="message" :key="message.id"/>
</div>
<!-- List of message bubbles. -->
<div id="messenger">
<MessageItem v-for="message in messages" :message="message" :key="message.id" />
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, onMounted, ref, onUnmounted } from "vue";
import TextAudioInput from "@/components/taInput/index.vue";
import { defineComponent, reactive, onMounted, onUnmounted } from "vue";
import { Message } from "@/types/message/index";
import MessageItem from '@/components/messageItem.vue'
import InputItem from "@/components/inputItem.vue";
import MessageItem from "@/components/messageItem.vue";
import InputItem from "@/components/inputItem/inputItem.vue";
export default defineComponent({
name: "Messenger",
components: {
TextAudioInput,
MessageItem,
InputItem
InputItem,
},
setup() {
// List of messages in the view.
// Oldest message first.
const messages: Message[] = reactive([]);
@ -36,48 +29,42 @@ export default defineComponent({
// Render a message.
const render = (message: 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(() => {
// Listen for new messages to render.
window.ipcRenderer.on("render-message", (event, payload) => {
if (payload.message.content) render(payload.message);
});
})
});
onUnmounted(() => {
window.ipcRenderer.removeAllListeners('render-message');
})
window.ipcRenderer.removeAllListeners("render-message");
});
return {
messages,
}
}
};
},
});
</script>
<style lang="scss" scoped>
#messenger {
position: fixed;
#messenger {
position: fixed;
width: 100vw;
min-height: 100vh;
width: 100vw;
min-height: 100vh;
overflow-y: scroll;
overflow-y: scroll;
bottom: 0;
}
#messenger::-webkit-scrollbar {
display: none;
}
bottom: 0;
}
#messenger::-webkit-scrollbar {
display: none;
}
</style>