Merge remote-tracking branch 'origin/project_refactor'

This commit is contained in:
Enrique Hernandez 2021-01-31 17:47:06 -06:00
commit 9a4f0fe1b3
36 changed files with 388 additions and 580 deletions

View file

@ -1,64 +0,0 @@
<template>
<defs>
<linearGradient
id="linear-gradient"
x1="0.5"
y1="-1.438"
x2="0.5"
y2="0.633"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#fff33b" stop-opacity="0" />
<stop offset="0.152" stop-color="#ffde46" stop-opacity="0.141" />
<stop offset="0.467" stop-color="#ffa764" stop-opacity="0.435" />
<stop offset="0.581" stop-color="#ff916f" stop-opacity="0.541" />
<stop offset="1" stop-color="#ff5782" />
</linearGradient>
<linearGradient
id="linear-gradient-2"
x1="0.498"
y1="0.728"
x2="0.498"
y2="-0.433"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#f7cf83" />
<stop offset="1" stop-color="#fff" />
</linearGradient>
<rect id="rect" width="29" height="29" rx="10" />
<clipPath id="clip">
<use xlink:href="#rect" />
</clipPath>
</defs>
<g id="titlebar" transform="translate(0, 0)">
<g data-name="Group 73" transform="translate(20 20)">
<ellipse
id="Oval"
cx="8.134"
cy="8.134"
rx="8.134"
ry="8.134"
transform="translate(0 0)"
fill="url(#linear-gradient)"
/>
<ellipse
id="Oval-2"
data-name="Oval"
cx="8.134"
cy="8.134"
rx="8.134"
ry="8.134"
transform="translate(27 0)"
fill="url(#linear-gradient-2)"
/>
</g>
</g>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "TitleBar",
});
</script>

View file

@ -1,68 +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">
<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: "AudioInput",
props: {
bubbleWidth: Number,
paths: Array,
},
setup(props) {
const emitter: any = inject("mitt");
function audioSend() {
}
const center = computed(() => {
if (props.bubbleWidth) {
return 175 - props.bubbleWidth / 2;
}
});
return { center };
},
});
</script>

View file

@ -6,24 +6,26 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
@click="emittMessage"
>
<!-- <AI /> -->
<MessageRenderer />
<InputItem />
<!-- <TitleBar /> -->
<MessageVisualizer />
<TextAudioInput />
</svg>
</template>
<script lang="ts">
import AI from "../AI/index.vue";
import MessageRenderer from "./UIDetails/messageRenderer.vue";
import InputItem from "@/components/input/inputItem.vue";
import TitleBar from "./UIDetails/titleBar.vue";
import Messages from "./UIDetails/messages";
// mock messages
import Messages from "./mockMessages";
// child components
import MessageVisualizer from "./messageVisualizer/index.vue";
import TextAudioInput from "@/components/taInput/index.vue";
// event emitter
import { Mitt } from "@/types/mitt/index";
import { defineComponent, ref, inject } from "vue";
export default defineComponent({
name: "UI",
components: { MessageRenderer, InputItem },
name: "Messenger",
components: { MessageVisualizer, TextAudioInput },
setup() {
const messages = Messages;
const count = ref(0);
@ -42,9 +44,9 @@ export default defineComponent({
}
window.ipcRenderer.on("render-message", (event, payload) => {
console.log(payload.message)
console.log(payload.message);
emitter.emit("renderMessage", payload.message);
})
});
return {
emittMessage,

View file

@ -0,0 +1,73 @@
import { computed, onMounted, ref } from "vue";
/**
* single message svg item computed properties
*/
export default function useComputedBubble(m: string, id: string) {
const x = ref(0)
const paddingLeft = 30;
const winW = 350;
const messageWidth = ref(0);
const messageMarginTop = 75;
const textFill = computed(() => {
switch (m) {
case "sf":
return "#fff";
default:
return "#000";
}
});
const bubbleFill = computed(() => {
switch (m) {
case "sf":
return "#61b4f4";
default:
return "#fff";
}
});
const setMessageWidthRef = async () => {
const msg = await document.getElementById(id);
if (msg) {
messageWidth.value = msg.getBoundingClientRect().width;
}
}
const selfMessageMarginRight = 2.5;
const calculateXoffset = async () => {
switch (m) {
case "sf":
x.value = winW - messageWidth.value - selfMessageMarginRight;
break;
default:
x.value = paddingLeft;
// to center a message
// x = messageMargin + (winW -messageWidth) / 2;
}
}
const startingOffset = computed(() => {
const parentView = document.getElementById('messageVisualizer');
if (parentView) {
const rect = parentView.getBoundingClientRect();
let Y = 600;
if (rect.height > 500) {
Y = rect.height + messageMarginTop;
}
return `translate(${x.value} ${Y})`;
}
});
onMounted(async () => {
await setMessageWidthRef();
calculateXoffset();
})
return {
textFill,
bubbleFill,
startingOffset
};
}

View file

@ -0,0 +1,50 @@
import { ref, onMounted } from "vue";
import { Ref } from "@/types/vueRef/index";
/**
* handles visualization of single message svg items
*/
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

@ -27,10 +27,13 @@
</template>
<script lang="ts">
import useComputedBubble from "./bubbleDetails/computed";
import useBubbleDrawer from "./bubbleDetails/drawer";
// text wrap composition
import useTextWrap from "@/composables/textWrap";
import { defineComponent, onMounted, ref, reactive } from "vue";
import useMessageItemComp from "@/core/messenger/message/messageDetails/useComputed";
import useMessageItemAnims from "@/core/messenger/message/messageDetails/useAnimations";
import useTextWrap from "@/utils/textWrap/useTextWrap";
export default defineComponent({
name: "MessageItem",
props: {
@ -46,12 +49,12 @@ export default defineComponent({
const messageRect = ref(`message${msgID.value}`);
const messageText = ref(`text${msgID.value}`);
const { textFill, bubbleFill, startingOffset } = useMessageItemComp(
const { textFill, bubbleFill, startingOffset } = useComputedBubble(
props.item.modifier,
props.item.id
);
const { textWrap } = useTextWrap();
const { drawMessage, signatureTranslate } = useMessageItemAnims({
const { drawMessage, signatureTranslate } = useBubbleDrawer({
mr: messageRect,
tr: messageText,
});

View file

@ -5,7 +5,7 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
:viewBox="scrollView"
>
<g id="messageRenderer" transform="translate(0, 5)">
<g id="messageVisualizer" transform="translate(0, 5)">
<g
v-for="(item, index) in renderArr"
:key="index"
@ -27,14 +27,18 @@
</template>
<script lang="ts">
import useMessageView from "@/core/messenger/messageView/useMessageView";
import useScrollView from "@/core/UI/scrollView/useScrollView";
import { defineComponent, reactive, inject } from "vue";
import MessageItem from "./messageItem.vue";
import useMessageVisualizer from "./viewDetails/visualizer";
import useScrollView from "@/composables/scrollView";
import MessageItem from "../messageBubble/index.vue";
import { Message } from "@/types/message/index";
import { Mitt } from "@/types/mitt/index";
import { defineComponent, reactive, inject } from "vue";
export default defineComponent({
name: "MessageRenderer",
name: "MessageVisualizer",
components: { MessageItem },
setup() {
const renderArr: Message[] = reactive([]);
@ -49,9 +53,9 @@ export default defineComponent({
});
}
const { beforeEnter, enter, afterEnter } = useMessageView();
const { beforeEnter, enter, afterEnter } = useMessageVisualizer();
const { scrollView } = useScrollView({
targetId: "messageRenderer",
targetId: "messageVisualizer",
});
return {

View file

@ -0,0 +1,80 @@
import { inject } from "vue";
import AnimeFunc from "@/types/animejs/index";
import { MessageAnimeFunc } from '@/types/message/index';
/**
* anime js animations used by message renderer
*/
export default function useMessageViewAnims(): {
shiftAnimate: MessageAnimeFunc;
stackAnimate: MessageAnimeFunc;
} {
// imports animejs safely
let anime: AnimeFunc;
const animeInject: AnimeFunc | undefined = inject("animejs");
if (animeInject) anime = animeInject;
// shifts message list by a given value
function shiftList({ targetList, sv }: { targetList: NodeList | undefined; sv: number | undefined }) {
if (targetList) {
for (const element of targetList) {
const el = element.parentNode as HTMLElement;
anime({
targets: el,
transform: `translate(0 ${sv})`,
easing: "easeInOutQuad",
duration: 500,
});
}
}
}
// adds new message at the bottom of the list and shifts existing message list
const shiftAnimate: MessageAnimeFunc = (el, transform, shiftValue, targetList) => {
anime({
targets: el,
transform: transform.initialTranslate,
easing: "easeInOutQuad",
duration: 500,
begin: function() {
shiftList({
targetList,
sv: shiftValue
});
},
complete: function() {
anime({
targets: el,
transform: transform.finalTranslate,
easing: "easeInOutQuad",
duration: 500,
offset: 1000
});
}
});
}
// stacks message from top to bottom
const stackAnimate: MessageAnimeFunc = (el, transform) => {
anime({
targets: el,
transform: transform.initialTranslate,
easing: "easeInOutQuad",
duration: 1000,
complete: function() {
anime({
targets: el,
transform: transform.finalTranslate,
easing: "easeInOutQuad",
duration: 500,
offset: 1000
});
}
});
}
return {
shiftAnimate,
stackAnimate
};
}

View file

@ -0,0 +1,41 @@
import { ref } from "vue";
/**
* Calculates vertical and horizontal message offsets
*/
export default function useOffsetCalculator() {
const xOffset = ref(0);
const yOffset = ref(0);
const firstChildMarginTop = 0;
let previousMessageHeight = 0;
const messagePadding = 15;
function calculateX(message: HTMLElement): void {
const computedQuery = window.getComputedStyle(message);
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
xOffset.value = matrix.m41;
}
function calculateY(messageHeight: number): void {
if (previousMessageHeight === 0) {
yOffset.value = firstChildMarginTop;
} else {
yOffset.value += previousMessageHeight + messagePadding;
}
previousMessageHeight = messageHeight;
}
const calculateMessageOffsets = (msg: HTMLElement): { x: number; y: number } => {
const msgRect = msg.getBoundingClientRect();
calculateX(msg);
calculateY(msgRect.height);
const offsets = {
x: xOffset.value as number,
y: yOffset.value as number
};
return offsets;
}
return {
calculateMessageOffsets,
}
}

View file

@ -0,0 +1,72 @@
import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
import useMessageViewAnims from "./anime";
import useOffsetCalculator from './offsetCalculator';
/**
* handles visualization of view messages through
* vue transition callbacks and anime-js animations
*/
export default function useMessageVisualizer(): {
beforeEnter: VueTransitionCallback;
enter: VueTransitionCallback;
afterEnter: VueTransitionCallback;
} {
// uses offsetCalculator logic and renderer animations
const { calculateMessageOffsets } = useOffsetCalculator();
const { shiftAnimate, stackAnimate } = useMessageViewAnims();
const messagePadding = 15;
let messageList: NodeList;
let message: HTMLElement | null;
let messageOffset = { x: 0, y: 0 };
let shift = false;
let shiftOffset = 0;
// animates message item to 'enter' renderView
const animateMessage = (el: SVGGElement): void => {
const transform = {
initialTranslate: `translate(${messageOffset.x} ${messageOffset.y})`,
finalTranslate: `translate(${messageOffset.x} ${messageOffset.y})`
}
shift ? shiftAnimate(el, transform, shiftOffset, messageList) : stackAnimate(el, transform);
}
const beforeEnter: VueTransitionCallback = () => {
const queryResult = document.querySelectorAll(".messageList");
if (queryResult.length) messageList = queryResult;
}
const enter: VueTransitionCallback = (el) => {
// query DOM for latest message as soon as it enters
if (el) {
message = document.getElementById(el.id);
}
}
const afterEnter: VueTransitionCallback = (el) => {
// get message positioning & shift state before animating
if (message) {
messageOffset = calculateMessageOffsets(message);
shift = messageOffset.y > 400 ? true : false;
}
if (el) {
if (shift) {
console.log(messageOffset.y)
if (messageOffset.y < 445) {
shiftOffset -= 15;
} else {
shiftOffset -= el.getBBox().height + messagePadding;
}
}
animateMessage(el);
}
}
return {
beforeEnter,
enter,
afterEnter
};
}

View file

@ -158,4 +158,4 @@ const Messages: Message[] = [
time: ""
},
];
export default Messages;
export default Messages;

View file

@ -0,0 +1,65 @@
<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

@ -0,0 +1,147 @@
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,17 +1,26 @@
<template>
<audio-input v-if="visualizeStream" :bubbleWidth="audioBubbleWidth" :paths="paths" />
<audio-stream v-if="visualizeStream" :bubbleWidth="audioBubbleWidth" :paths="paths" />
<text-input v-else :input="input" :textXoffset="textInputXoffset" />
</template>
<script lang="ts">
// input child components
import TextInput from "./textDetails/text.vue";
import AudioStream from "./audioDetails/audio.vue";
import useInputController from "./inputController";
import { defineComponent } from "vue";
import TextInput from "./inputDetails/textInput.vue";
import AudioInput from "./inputDetails/audioInput.vue";
import useInputController from "@/core/UI/input/useInputController";
export default defineComponent({
name: "InputItem",
components: { TextInput, AudioInput },
name: "TextAudioInput",
components: { TextInput, AudioStream },
setup() {
// get text input
// get audio stream
// use Input Handler
const {
input,
visualizeStream,

View file

@ -0,0 +1,93 @@
import { ref, watch, onUnmounted, onMounted } from "vue";
import useKeyDownHandler from "@/composables/keyDownHandler/useKeyDownHandler";
import useMediaStream from "@/composables/mediaStream";
import useAudioVisualizer from './audioDetails/audioVisualizer';
import useTextVisualizer from './textDetails/textVisualizer';
/*
* Controls which input to visualize
* Will visualize text input by default as user types or
* if user holds space bar down, input audio stream
* will be visualized & recorded
*/
// NOTE: input params should probably be input and MediaStream
// TODO: visualize audio stream from nodeJS background process
export default function useInputController() {
const visualizeStream = ref(false);
const paths = ref([]);
const audioBubbleWidth = ref(10);
let stream: MediaStream;
const { keyDownHandler, input } = useKeyDownHandler();
const { textInputXoffset, renderTextInput } = useTextVisualizer(input);
const { visualizeStreamAsPaths, expandBubble } = useAudioVisualizer(visualizeStream);
// stops stream recording on space key up
function keyupHandler(e: any) {
if (e.key === " " && visualizeStream.value) {
console.log("stop recording");
window.postMessage({
myTypeField: 'update-recorder',
record: false
}, '*')
visualizeStream.value = false;
paths.value = []
audioBubbleWidth.value = 10;
input.value = ''
}
}
// add window event keyboard listeners
window.addEventListener("keydown", keyDownHandler);
window.addEventListener('keyup', keyupHandler);
// get stream & initialize recorder object on mount
onMounted(async () => {
stream = await useMediaStream({ audio: true });
})
// 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();
}
return;
}
if (input === " " && visualizeStream.value === false) {
console.log("record MediaStream");
visualizeStream.value = true;
window.postMessage({
myTypeField: 'update-recorder',
record: true
}, '*')
expandBubble(audioBubbleWidth);
visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
return;
}
renderTextInput();
});
// remove Event Listeners on component unMount
onUnmounted(() => {
window.removeEventListener("keydown", keyDownHandler);
window.removeEventListener("keyup", keyupHandler);
});
return {
input,
visualizeStream,
textInputXoffset,
audioBubbleWidth,
paths
}
}

View file

@ -1,11 +1,5 @@
<template>
<foreignObject
class="inputContainer"
:x="textXoffset"
y="450"
width="55%"
height="50%"
>
<foreignObject class="inputContainer" :x="textXoffset" y="450" width="55%" height="50%">
<div xmlns="http://www.w3.org/1999/xhtml">
<div class="inputBubble" contenteditable v-show="input.length > 0">
<p>{{ input }}</p>
@ -15,12 +9,12 @@
</template>
<script lang="ts">
import { defineComponent, watch, onMounted, ref } from "vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "TextInput",
props: {
input: String,
textXoffset: String
textXoffset: String,
},
});
</script>

View file

@ -0,0 +1,57 @@
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

@ -0,0 +1,49 @@
import { ref, computed, inject } from 'vue';
import useTextInputAnims from "./textAnime";
import { Ref } from "@/types/vueRef/index";
/*
* text rendering logic used by inputController
*/
export default function useTextVisualizer(input: Ref<string>) {
const inputHeight = ref(42);
const inputWidth = ref(0);
const emitter: any = inject("mitt");
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);
}
}
emitter.on("newSelfMessage", async (payload: any) => {
const inputEl = document.getElementsByClassName("inputContainer");
const foreignEl = inputEl[0] as HTMLElement;
animateSend(foreignEl, inputHeight.value, input);
});
const textInputXoffset = computed(() => {
return `calc(50% - ${inputWidth.value / 2})`;
});
return {
textInputXoffset,
renderTextInput
}
}

View file

@ -34,7 +34,7 @@
<script>
import { defineComponent, onMounted, ref, inject, watch } from "vue";
export default defineComponent({
name: "AI",
name: "voiceBackground",
setup() {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const analyzer = audioCtx.createAnalyser();