new message anim

This commit is contained in:
Andrew Gundersen 2021-06-25 18:28:29 -05:00
commit 51c4ad8ed7
12 changed files with 379 additions and 369 deletions

View file

@ -1,19 +1,6 @@
<template>
<div :class="`${type}-message`">
<!-- message bubble -->
<div :class="`${type}-${child}-bubble`">
<div class="notification-dot"/>
<div class="error-dot"/>
{{ text }}
</div>
<!-- message context -->
<span class="context">
<div :class="`${type}-icon`"/>
{{ context }}
</span>
<div :class="`bubble ${modifier}-bubble ${modifier}-${child}`">
{{ content }}
</div>
</template>
@ -24,27 +11,16 @@
export default defineComponent({
name: "Bubble",
props: ["text", "context", "type", "position"],
props: ["modifier", "content", "child"],
setup() {
const seen = ref(false);
/* initialize seen state */
if (document.visibilityState === "visible") {
seen.value = true;
} else seen.value = false;
setup(props) {
onMounted(() => {
if(seen.value)
document.addEventListener("visibilitychange", () => {
seen.value = true
});
});
return {
seen
};
}
@ -55,277 +31,92 @@
<style lang="scss" scoped>
.message {
width: 100vw;
display: flex;
flex-direction: column;
padding-top: 9px;
padding-bottom: 9px;
.bubble {
position: relative;
max-width: 66vw;
font-family: "SF Pro Text";
font-size: 14px;
padding: 10px;
border-radius: 18px;
margin-left: 15px;
margin-right: 15px;
margin-bottom: 4px;
animation-name: bubble-init-anim;
animation-duration: 0.5s;
}
@keyframes bubble-init-anim {
from {
opacity: 0;
transform: translateY(25px);
} to {
opacity: 1;
transform: translateY(0px);
}
.message:first-child {
margin-top: 55px;
}
.message:last-child {
margin-bottom: 18px;
}
.sf-message {
@extend .message;
justify-content: flex-end;
}
.ai-message, .fr-message {
@extend .message;
justify-content: flex-start;
}
.bubble {
position: relative;
max-width: 66vw;
font-family: "SF Pro Text";
font-size: 14px;
padding: 10px;
border-radius: 18px;
}
.sf-bubble {
@extend .bubble;
background-color: #58c4fd;
color: white;
}
.firstChildMessage {
padding-bottom: 2px;
}
.middleChildMessage {
padding-top: 2px;
padding-bottom: 2px;
}
.lastChildMessage {
padding-top: 2px;
}
#aiMessage {
align-items: flex-start;
}
#sfMessage {
align-items: flex-end;
}
#frMessage {
align-items: flex-start;
}
.messageBox {
position: relative;
display: flex;
flex-direction: column;
// Animate on render.
animation-name: appear;
animation-duration: 0.25s;
}
#aiMessageBox {
margin-left: 20px;
align-items: flex-start;
}
#sfMessageBox {
margin-right: 20px;
align-items: flex-end;
}
#frMessageBox {
margin-left: 20px;
align-items: flex-start;
}
.bubble {
position: relative;
max-width: 66vw;
display: flex;
flex-direction: column;
font-family: "SF Pro Text";
font-size: 14px;
padding: 10px;
border-radius: 18px;
}
@keyframes appear {
10% {
transform: scale(0.3);
}
100% {
transform: scale(1);
}
}
#aiBubble {
background-color: white;
}
#sfBubble {
background-color: #58c4fd;
color: white;
}
#frBubble {
background-color: white;
}
.sf-firstChildBubble {
border-bottom-right-radius: 9px;
}
.sf-middleChildBubble {
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
}
.sf-lastChildBubble {
border-top-right-radius: 9px;
}
.ai-firstChildBubble, .fr-firstChildBubble {
border-bottom-left-radius: 9px;
}
.ai-middleChildBubble, .fr-middleChildBubble {
border-top-left-radius: 9px;
border-bottom-left-radius: 9px;
}
.ai-lastChildBubble, .fr-lastChildBubble {
border-top-left-radius: 9px;
}
.notify {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #58D9FF;
top: -5px;
left: -5px;
border: 2px solid #EBEBEB;
transform: scale(0);
animation-name: notify-anim;
animation-duration: 5s;
}
@keyframes notify-anim {
0%, 90% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
.context {
position: relative;
display: flex;
align-items: center;
font-family: "SF Compact Display";
font-size: 12px;
font-weight: bold;
margin-top: 5px;
}
.photo {
display: flex;
justify-content: center;
align-items: center;
margin-right: 5px;
width: 30px;
height: 30px;
font-size: 14px;
background-color: white;
border-radius: 15px;
}
// Apply for audio message playback.
.playing {
animation-name: circle1;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes circle1 {
0%,
100% {
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
}
25% {
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
}
50% {
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
}
75% {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
}
}
.contentLoader {
display: flex;
}
.contentLoaderDot {
width: 5px;
height: 5px;
margin: 2px;
border-radius: 2.5px;
background-color: #357CA2;
}
.questionMark {
font-weight: 900;
color: #357CA2;
}
// .divider {
// width: 100vw;
// display: flex;
// justify-content: center;
// align-items: center;
// font-family: "SF Compact Display";
// font-size: 12px;
// font-weight: bold;
// color: #9B9B9B;
// margin-bottom: 18px;
// }
</style>
}
.ai-bubble {
background-color: #FFFFFF;
}
.client-bubble {
color: white;
background-color: #58C4FD;
}
.ai-first-child {
border-bottom-left-radius: 9px;
}
.ai-middle-child {
border-top-left-radius: 9px;
border-bottom-left-radius: 9px;
}
.ai-last-child {
border-top-left-radius: 9px;
}
.client-first-child {
border-bottom-right-radius: 9px;
}
.client-middle-child {
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
}
.client-last-child {
border-top-right-radius: 9px;
}
.ai-none-child, .client-none-child {
border-top-right-radius: 9px;
}
// .notify {
// position: absolute;
// width: 12px;
// height: 12px;
// border-radius: 50%;
// background-color: #58D9FF;
// top: -5px;
// left: -5px;
// border: 2px solid #EBEBEB;
// transform: scale(0);
// animation-name: notify-anim;
// animation-duration: 5s;
// }
// @keyframes notify-anim {
// 0%, 90% {
// transform: scale(1);
// }
// 100% {
// transform: scale(0);
// }
// }
</style>

View file

@ -1,4 +1,4 @@
import { ref } from "vue";
import { ref, Ref } from "vue";
import anime from "animejs";
import { v4 as uuidv4 } from 'uuid';
@ -86,17 +86,40 @@ export function animateAudioInput () {
}
// animate a message annotation.
//! probably could be improved.
export function annotateAnim (uid: string, val: string, contextRef: Ref) {
export function newMessage ({
text=false,
audio=false,
context=false,
uid=uuidv4()
}) {
return {
text: text,
audio: audio,
context: context,
uid: uid
};
anime({
targets: `#${uid} span`,
opacity: [1, 0],
duration: 500,
easing: 'easeOutExpo'
})
.finished.then(() => {
contextRef.value = val;
})
.then(() => {
anime({
targets: `#${uid} span`,
opacity: [0, 1],
duration: 500,
easing: 'easeOutExpo'
})
})
}
// Given index and length of content, return message child status.
export function calcChild (index: number, len: number) {
if (len === 1) {
return "none";
} else if (index === 0) {
return "first-child";
} else if (index === len - 1) {
return "last-child";
} else {
return "middle-child";
}
}

View file

@ -1,6 +1,6 @@
import { onMounted, onUnmounted, ref, Ref } from "vue";
import { postMessage } from "@/render/ipc";
import { newMessage, animateAudioInput } from "./helpers";
import { animateAudioInput } from "./helpers";
import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
export default function useAudioInputController (typing: Ref) {

View file

@ -1,6 +1,6 @@
import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
import { postMessage } from "@/render/ipc";
import { newMessage, animateTextInput } from "./helpers";
import { animateTextInput } from "./helpers";
export default function useTextInputController(elementX: Ref) {
@ -36,11 +36,12 @@ export default function useTextInputController(elementX: Ref) {
if (textInput) {
// Send it to the backend for processing.
const message = newMessage({
text: false
});
const message: Raw = {
text: textInput.value,
audio: false
};
// postMessage(message);
postMessage(message);
clearInput()
}

View file

@ -0,0 +1,142 @@
<template>
<div :id="uid" :class="`${modifier}-message`">
<Bubble
v-for="(val, index) in content"
:modifier="modifier"
:content="val"
:child="calcChild(index, content.length)"
/>
<span class="context">
<div v-if="modifier==='ai'" class="avatar"></div>
{{ contextRef }}
</span>
</div>
</template>
<script lang='ts'>
import { defineComponent, ref, Ref, onMounted, watch } from 'vue';
import Bubble from "@/render/components/bubble.vue";
import { annotateAnim, calcChild } from "@/render/components/controllers/helpers";
export default defineComponent({
name: "Message",
props: ["modifier", "content", "context", "uid"],
components: {
Bubble
},
setup(props) {
const contextRef = ref(" ");
contextRef.value = props.context;
watch(() => props.context, (val: string, oldval: string) => {
annotateAnim(props.uid, val, contextRef);
});
onMounted(() => {
});
return {
calcChild,
contextRef
};
}
})
</script>
<style lang="scss" scoped>
.message {
width: 100vw;
display: flex;
flex-direction: column;
padding-top: 9px;
padding-bottom: 9px;
animation-name: message-init-anim;
animation-duration: 0.5s;
}
@keyframes message-init-anim {
from {
opacity: 0;
transform: translateY(50px);
} to {
opacity: 1;
transform: translateY(0px);
}
}
.message:first-child {
margin-top: 55px;
}
.message:last-child {
margin-bottom: 18px;
}
.client-message {
@extend .message;
align-items: flex-end;
}
.ai-message {
@extend .message;
align-items: flex-start;
}
.context {
position: relative;
display: flex;
align-items: center;
font-family: "SF Compact Display";
font-size: 12px;
font-weight: bold;
margin-top: 5px;
margin-right: 15px;
margin-left: 15px;
}
.avatar {
margin-right: 5px;
width: 30px;
height: 30px;
background-color: white;
border-radius: 15px;
}
.playing {
animation-name: message-playback-anim;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes message-playback-anim {
0%,
100% {
box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
}
25% {
box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
}
50% {
box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
}
75% {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
}
}
</style>

View file

@ -7,11 +7,12 @@
<!-- List of message bubbles. -->
<div id="messenger">
<Bubble
<Message
v-for="message in messages"
:text="message.content.text"
:modifier="message.modifier"
:content="message.content"
:context="message.context"
:key="message[0]"
:uid="message.uid"
/>
</div>
@ -22,7 +23,7 @@
import { defineComponent, onMounted, onUnmounted } from "vue";
import InputItem from "@/render/components/inputItem.vue";
import Settings from "@/render/components/settings.vue";
import Bubble from "@/render/components/bubble.vue";
import Message from "@/render/components/message.vue";
import { profile } from "@/render/shared/profile";
import { messages } from "@/render/shared/messages";
@ -33,7 +34,7 @@ export default defineComponent({
components: {
InputItem,
Settings,
Bubble
Message
},
setup() {