269 lines
No EOL
4.8 KiB
Vue
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> |