mime-chat/src/components/messageItem.vue
2021-03-11 21:07:16 -06:00

269 lines
No EOL
4.8 KiB
Vue

<template>
<div
class="message"
:id="`${message.modifier}Message`"
:key="message.id"
>
<!-- Three types of messages: self (sf), friend (fr), and crimata (ai) -->
<!-- !denoted by the modifier attribute -->
<!-- Includes bubble and context. -->
<div class="messageBox" :id="`${message.modifier}MessageBox`">
<!-- message bubble -->
<div
class="bubble"
:id="`${message.modifier}Bubble`"
>
<!-- Show loader when audio is being transcribed. -->
<div
v-if="message.content.text === ''"
class="contentLoader"
>
<div class="contentLoaderDot"></div>
<div class="contentLoaderDot"></div>
<div class="contentLoaderDot"></div>
</div>
<!-- Show question mark if transcription comes back as unknown. -->
<div
v-else-if="message.content.text === false"
class="questionMark"
>
?
</div>
<!-- Else, show text. -->
<div v-else>
{{ message.content.text }}
</div>
</div>
<!-- message context -->
<span class="context">
<!-- Render Crimata icon or friend's initials. -->
<div v-if="message.modifier == 'ai'" class="photo">
<img src="@/assets/crimata.svg"/>
</div>
<!-- Render Crimata icon or friend's initials. -->
<div v-if="message.modifier == 'fr'" class="photo"
:class="{ playing: isPlaying }"
>
{{ message.context[0] }}
</div>
{{ message.context }}
</span>
</div>
</div>
</template>
<script lang='ts'>
import {defineComponent, ref, onMounted} from 'vue';
export default defineComponent({
name: "MessageItem",
props: {
message: {
type: Object,
required: true
}
},
setup(props) {
const isPlaying = ref(false);
const onStopPlayback = (e: any) => {
window.ipcRenderer.removeAllListeners("stop-playback-anim");
isPlaying.value = false
}
onMounted(() => {
// Turn on audio playback animation if audio.
if (props.message.content.audio === true) {
window.ipcRenderer.on("stop-playback-anim", onStopPlayback);
isPlaying.value = true
}
})
return {
isPlaying
}
}
})
</script>
<style lang="scss" scoped>
.message {
width: 100vw;
display: flex;
flex-direction: column;
padding-top: 9px;
padding-bottom: 9px;
}
.message:first-child {
margin-top: 55px;
}
.message:last-child {
margin-bottom: 18px;
}
#aiMessage {
align-items: flex-start;
}
#sfMessage {
align-items: flex-end;
}
#frMessage {
align-items: flex-start;
}
.messageBox {
display: flex;
flex-direction: column;
}
#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 {
max-width: 66vw;
display: flex;
flex-direction: column;
font-family: "SF Pro Text";
font-size: 14px;
padding: 10px;
border-radius: 18px;
// Animate on render.
animation-name: appear;
animation-duration: 0.25s;
}
@keyframes appear {
from {
transform: scale(0.3);
}
to {
transform: scale(1);
}
}
#aiBubble {
background-color: white;
}
#sfBubble {
background-color: #58c4fd;
color: white;
}
#frBubble {
background-color: white;
}
.context {
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;
}
</style>