+++ /dev/null
-<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>
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();
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);
// 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
}
}
--- /dev/null
+<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>
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-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
- }
-}
+++ /dev/null
-<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>
+++ /dev/null
-<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>
+++ /dev/null
-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
- }
-
-}
+++ /dev/null
-/*
- * 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
- }
-}
+++ /dev/null
-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,
- };
-}
-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
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.
// 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;
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:
+++ /dev/null
-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())
-}
+++ /dev/null
-export interface DataNode<T> {
- data: T;
- next: DataNode<T> | null;
-}
-
-export interface QueueI<T> {
- enqueue(data: T): void;
- dequeue(): T | null;
- peek(): T | null;
- size(): number;
-}
-
-export class Queue<T> implements QueueI<T> {
- private _size: number;
- private head: DataNode<T> | null;
- private tail: DataNode<T> | null;
- constructor() {
- this._size = 0;
- this.head = this.tail = null;
- }
-
- enqueue(data: T) {
-
- const node: DataNode<T> = {
- data,
- next: null
- }
- if (this.head === null) {
- this.head = this.tail = node;
- } else {
- if (this.tail) {
- this.tail.next = node;
- this.tail = this.tail.next;
- }
-
- }
- ++this._size;
- }
-
- dequeue(): T | null {
-
- let item: T | null = null;
-
- if (this._size === 0) {
- return null;
- }
-
- if (this.head) {
- item = this.head.data;
- this.head = this.head.next;
- --this._size;
-
- if (!this._size) {
- this.tail = null;
- }
- }
- return item;
- }
-
- peek(): T | null {
-
- let item: T | null = null;
-
- if (this._size === 0) {
- return null;
- }
-
- if (this.head) {
- item = this.head.data;
- }
-
- return item;
- }
-
- size(): number {
- return this._size;
- }
-}
+++ /dev/null
-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
- }
-
-}
+++ /dev/null
-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
- };
-}
});
// 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;
<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([]);
// 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;
-
- overflow-y: scroll;
-
- bottom: 0;
+ width: 100vw;
+ min-height: 100vh;
- }
+ overflow-y: scroll;
- #messenger::-webkit-scrollbar {
- display: none;
- }
+ bottom: 0;
+}
+#messenger::-webkit-scrollbar {
+ display: none;
+}
</style>