refactor core into composables & components
This commit is contained in:
parent
3be7023792
commit
a84019da0d
37 changed files with 409 additions and 469 deletions
66
src/components/messenger/index.vue
Normal file
66
src/components/messenger/index.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<svg
|
||||
id="messenger"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
@click="emittMessage"
|
||||
>
|
||||
<MessageVisualizer />
|
||||
<InputVisualizer />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// mock messages
|
||||
import Messages from "./mockMessages";
|
||||
|
||||
// child components
|
||||
import MessageVisualizer from "./messageVisualizer/index.vue";
|
||||
import InputVisualizer from "@/components/inputVisualizer/index.vue";
|
||||
|
||||
// event emitter
|
||||
import { Mitt } from "@/types/mitt/index";
|
||||
|
||||
import { defineComponent, ref, inject } from "vue";
|
||||
export default defineComponent({
|
||||
name: "Messenger",
|
||||
components: { MessageVisualizer, InputVisualizer },
|
||||
setup() {
|
||||
const messages = Messages;
|
||||
const count = ref(0);
|
||||
|
||||
// imports mitt Emitter safely
|
||||
let emitter: Mitt;
|
||||
const emitterInject: Mitt | undefined = inject("mitt");
|
||||
if (emitterInject) emitter = emitterInject;
|
||||
|
||||
function emittMessage() {
|
||||
if (count.value < messages.length) {
|
||||
emitter.emit("renderMessage", messages[count.value]);
|
||||
count.value++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
window.ipcRenderer.on("render-message", (event, payload) => {
|
||||
console.log(payload.message);
|
||||
emitter.emit("renderMessage", payload.message);
|
||||
});
|
||||
|
||||
return {
|
||||
emittMessage,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#messenger {
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
background-color: #e6e6e6;
|
||||
box-shadow: 0px 3px 10px 0px rgba(0, 0, 0, 0.3);
|
||||
// border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
83
src/components/messenger/messageBubble/index.vue
Normal file
83
src/components/messenger/messageBubble/index.vue
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<g :id="item.id" :class="item.modifier" :transform="startingOffset">
|
||||
<rect ref="messageRect" x="30" y="10" width="196" height="63" rx="10" :fill="bubbleFill" />
|
||||
<text font-size="14" font-family="SF Pro" ref="messageText" :fill="textFill">
|
||||
<tspan dy="16" x="0" v-for="(item, index) in textArr" :key="index">{{ item }}</tspan>
|
||||
</text>
|
||||
<g :transform="signatureTranslate">
|
||||
<text
|
||||
transform="translate(0, 5)"
|
||||
fill="#000"
|
||||
font-size="10"
|
||||
font-family="SFCompactDisplay-Bold, SF Compact Display"
|
||||
font-weight="700"
|
||||
>
|
||||
<tspan xml:space="preserve">{{ item.context }}</tspan>
|
||||
</text>
|
||||
<text
|
||||
transform="translate(0, 20)"
|
||||
fill="#6f6f6f"
|
||||
font-size="9"
|
||||
font-family="SFCompactDisplay, SF Compact Display"
|
||||
>
|
||||
<tspan v-if="item.subContext">{{item.subContext}}</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</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";
|
||||
export default defineComponent({
|
||||
name: "MessageItem",
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
let textArr: string[] = reactive([]);
|
||||
|
||||
const msgID = ref(props.item.id);
|
||||
const messageRect = ref(`message${msgID.value}`);
|
||||
const messageText = ref(`text${msgID.value}`);
|
||||
|
||||
const { textFill, bubbleFill, startingOffset } = useComputedBubble(
|
||||
props.item.modifier,
|
||||
props.item.id
|
||||
);
|
||||
const { textWrap } = useTextWrap();
|
||||
const { drawMessage, signatureTranslate } = useBubbleDrawer({
|
||||
mr: messageRect,
|
||||
tr: messageText,
|
||||
});
|
||||
|
||||
// wraps message text before render
|
||||
textArr = textWrap({
|
||||
s: props.item.content,
|
||||
w: 32,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
drawMessage();
|
||||
});
|
||||
|
||||
return {
|
||||
textFill,
|
||||
bubbleFill,
|
||||
startingOffset,
|
||||
textArr,
|
||||
signatureTranslate,
|
||||
messageText,
|
||||
messageRect,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
80
src/components/messenger/messageVisualizer/index.vue
Normal file
80
src/components/messenger/messageVisualizer/index.vue
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
:viewBox="scrollView"
|
||||
>
|
||||
<g id="messageVisualizer" transform="translate(0, 5)">
|
||||
<g
|
||||
v-for="(item, index) in renderArr"
|
||||
:key="index"
|
||||
class="messageList"
|
||||
transform="translate(0, 25)"
|
||||
preserverAspectRatio="none"
|
||||
>
|
||||
<transition
|
||||
appear
|
||||
v-on:before-appear="beforeEnter"
|
||||
v-on:appear="enter"
|
||||
v-on:after-enter="afterEnter"
|
||||
>
|
||||
<MessageItem :item="item" :key="index" />
|
||||
</transition>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
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: "MessageVisualizer",
|
||||
components: { MessageItem },
|
||||
setup() {
|
||||
const renderArr: Message[] = reactive([]);
|
||||
|
||||
// imports mitt Emitter safely
|
||||
let emitter: Mitt;
|
||||
const emitterInject: Mitt | undefined = inject("mitt");
|
||||
if (emitterInject) {
|
||||
emitter = emitterInject;
|
||||
emitter.on("renderMessage", (payload: Message | undefined) => {
|
||||
if (payload) renderArr.push(payload);
|
||||
});
|
||||
}
|
||||
|
||||
const { beforeEnter, enter, afterEnter } = useMessageVisualizer();
|
||||
const { scrollView } = useScrollView({
|
||||
targetId: "messageVisualizer",
|
||||
});
|
||||
|
||||
return {
|
||||
renderArr,
|
||||
beforeEnter,
|
||||
enter,
|
||||
afterEnter,
|
||||
scrollView,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
svg {
|
||||
height: 1%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#messageRenderer {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
161
src/components/messenger/mockMessages.ts
Normal file
161
src/components/messenger/mockMessages.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import { Message } from "@/types/message/index";
|
||||
|
||||
const Messages: Message[] = [
|
||||
{
|
||||
content: "Welcome",
|
||||
context: "ai",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "2",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content:
|
||||
"add friend",
|
||||
context: "launch",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "1",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Did you mean to addcontact?",
|
||||
context: "addcontact",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "5",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "yes",
|
||||
context: "fulfill",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "6",
|
||||
time: ""
|
||||
},
|
||||
|
||||
{
|
||||
content: "Hey Anna, did you get the paper done?",
|
||||
context: "Mesage from Tommy McConville",
|
||||
subContext: "New Conversation",
|
||||
modifier: "ai",
|
||||
id: "3",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey do you know Enrique Hernandez?",
|
||||
context: "Message from Crimata",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "7",
|
||||
time: ""
|
||||
},
|
||||
|
||||
{
|
||||
content: "Yes posting it online soon.",
|
||||
context: "You to Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "4",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey do you know Enrique Hernandez?",
|
||||
context: "Message from Crimata",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "8",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey Anna, did you get the paper done?",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "9",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey Anna, did you get the paper done?",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "10",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Yes posting it online soon.",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "11",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content:
|
||||
"I can be there at 5 pm tomorrow to greet you at a Chick file with my almond and chive salad.",
|
||||
context: "You to Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "12",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey Anna, did you get the paper done?",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "13",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey Anna, did you get the paper done?",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "14",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Yes posting it online soon.",
|
||||
context: "You to Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "15",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Sweet, thanks!",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "16",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Hey do you know Enrique Hernandez?",
|
||||
context: "Message from Crimata AI",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "17",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Yes posting it online soon.",
|
||||
context: "You to Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "sf",
|
||||
id: "18",
|
||||
time: ""
|
||||
},
|
||||
{
|
||||
content: "Sweet, thanks!",
|
||||
context: "Message from Tommy McConville",
|
||||
subContext: "",
|
||||
modifier: "ai",
|
||||
id: "19",
|
||||
time: ""
|
||||
},
|
||||
];
|
||||
export default Messages;
|
||||
Loading…
Reference in a new issue