+++ /dev/null
-import { inject } from "vue";
-import AnimeFunc from "@/types/animejs/index";
-
-/**
- * anime js animations used by message view transitions
- */
-export default function useMessageViewAnims() {
- // imports animejs safely
- let anime: AnimeFunc;
- const animeInject: AnimeFunc | undefined = inject("animejs");
- if (animeInject) anime = animeInject;
-
- // update message View bottom bound
- const shiftView = (el: SVGGElement, transform: string) => {
- anime({
- targets: el,
- transform: transform,
- easing: "easeInOutQuad",
- duration: 500,
- });
- }
-
- // stacks message from top to bottom
- const stackAnimate = (el: Element, transform: string) => {
- return new Promise((resolve, _reject) => {
- anime({
- targets: el,
- transform: transform,
- easing: "easeInOutQuad",
- duration: 1000,
- complete: function() {
- resolve();
- }
- });
- })
- }
-
- // adds new message at the bottom of the list and shifts existing message list
- const shiftAnimate = (el: Element, transform: string, view: SVGGElement, viewTransform: string) => {
- return new Promise((resolve, _reject) => {
- anime({
- targets: el,
- transform: transform,
- easing: "easeInOutQuad",
- duration: 800,
- begin: function() {
- shiftView(view, viewTransform);
- },
- complete: function() {
- resolve();
- }
- });
- })
- }
-
- return {
- shiftAnimate,
- stackAnimate,
- };
-}
+++ /dev/null
-<template>
- <svg
- version="1.1"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- :viewBox="scrollView"
- @click="emittMessage"
- >
- <g id="messageView" transform="translate(0, 20)">
- <g v-for="(item, index) in renderArr" :key="index" preserverAspectRatio="none">
- <transition
- appear
- v-on:before-appear="beforeEnter"
- v-on:appear="enter"
- v-on:after-enter="afterEnter"
- >
- <TextBubble :item="item" :key="index" />
- </transition>
- </g>
- </g>
- </svg>
-</template>
-
-<script lang="ts">
-import TextBubble from "@/components/textBubble/index.vue";
-import { Message } from "@/types/message/index";
-import { Queue } from "@/modules/queue";
-import useTransitions from "./viewDetails/transitions";
-import useScrollView from "@/modules/scrollView";
-import { defineComponent, reactive, onMounted, onUnmounted, watch } from "vue";
-
-export default defineComponent({
- name: "MessageView",
- components: { TextBubble },
-
- setup() {
- const renderArr: Message[] = reactive([]);
- const renderQ = new Queue();
-
- // Each method will be called on their respective lifecycle event.
- const { beforeEnter, enter, afterEnter, rendering } = useTransitions();
- const { scrollView } = useScrollView({
- targetId: "messageView",
- });
-
- const render = (m: Message) => {
- if (rendering.value) {
- renderQ.enqueue(m);
- } else {
- renderArr.push(m);
- }
- };
-
- watch(rendering, (rendering, _prevRendering) => {
- if (!rendering) {
- const m = renderQ.dequeue() as Message | null;
- if (m) renderArr.push(m);
- }
- });
-
- onMounted(() => {
- window.ipcRenderer.on("render-message", (event, payload) => {
- const m = payload.message;
- if (m.content) render(m);
- });
-
- });
-
- onUnmounted(() => {
- window.ipcRenderer.removeAllListeners("render-message");
- });
-
- return {
- renderArr,
- beforeEnter,
- enter,
- afterEnter,
- scrollView,
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-#messageView {
- width: 1000px;
- height: 100vh;
- color: red;
-}
-</style>
+++ /dev/null
-import {computed} from "vue";
-
-// Size of "side-gutters" of message window.
-const margin = 20;
-const messagePadding = 15;
-const firstChildMargin = 45;
-
-
-function calcXPosition(messageWidth: number, windowWidth: number, modifier: string) {
- let offset = margin;
-
- if (modifier == "sf") {
- offset = windowWidth - messageWidth - margin;
- }
-
- return offset
-}
-
-function calcMessagePosition(modifier: string, messageWidth: number,
- windowWidth: number, anchorPosition: any) {
-
- const X = calcXPosition(messageWidth, windowWidth, modifier);
-
- // Depends on if first child.
- let Y = firstChildMargin;
- if (anchorPosition != "none") {
- Y = anchorPosition + messagePadding;
- }
-
- return `translate(${X} ${Y})`;
-}
-
-export default calcMessagePosition;
\ No newline at end of file
+++ /dev/null
-<template>
-
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="100"
- height="46"
- :transform="position"
- >
-
- <rect
- :id="message.id"
- :class="message.modifier"
-
- width="100" height="46" rx="10" fill="#58c4fd"
- />
-
- </svg>
-
-</template>
-
-<script lang="ts">
-
-import { defineComponent, computed, onMounted, ref, reactive } from "vue";
-
-import calcMessagePosition from "./calcPosition";
-
-
-export default defineComponent({
- name: "MessageBubble",
-
- props: {
-
- // DOM ID of the previous message.
- // Null if message is first child.
- anchor: {
- type: String,
- required: true
- },
-
- // Passing in the message contents.
- message: {
- type: Object,
- required: true
- },
-
- },
-
- setup(props) {
-
- // e.g. "translate(20 200)"
- const position = ref("");
- let anchorPosition: any;
-
- // Call to update the position variable.
- const updatePosition = () => {
-
- // First get anchor's position.
- if (props.anchor == "none") {
- anchorPosition = "none";
- }
-
- const m = document.getElementById(props.anchor)
- if (m) {
- anchorPosition = m.getBoundingClientRect().bottom
- }
-
- position.value = calcMessagePosition(
- props.message.modifier,
- 100,
- window.innerWidth,
- anchorPosition
- );
-
- console.log(
- `Message: ${props.message.id}: \n
- Anchor: ${props.anchor}: ${anchorPosition} \n
- Position: ${position.value}`
- )
-
- }
-
- // Assign initial position.
- updatePosition();
-
- onMounted(() => {
-
- // We gotta know when the message above us changes position.
-
- // Add listener for window resize.
- window.addEventListener("resize", updatePosition);
-
- })
-
- return {
- position
- }
-
- },
-
-});
-</script>
+++ /dev/null
-<!-- components/messageView/messageView.vue -->
-
-<template>
-
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="500"
- height="500"
- viewBox="0 0 500 500"
- >
-
- <g id="messengerGroup">
-
- <!-- Messages view bounding box -->
- <rect id="messagesView" data-name="Rectangle 1496" width="500" height="500" rx="15" fill="#ff8e8e"/>
-
- <!-- List of messages -->
- <g id="messages" v-for="(item, index) in renderArr" :key="index">
-
- <transition
- appear
- v-on:before-appear="get"
- v-on:appear="enter"
- v-on:after-enter="afterEnter"
- >
- <TextBubble :item="item" :key="index" />
- </transition>
-
- </g>
-
- </g>
-
- </svg>
-
-<!-- <svg
- version="1.1"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- >
- <g id="messagesView">
- <g v-for="(item, index) in renderArr" :key="index" preserverAspectRatio="none">
- <transition
- appear
- v-on:before-appear="beforeEnter"
- v-on:appear="enter"
- v-on:after-enter="afterEnter"
- >
- <TextBubble :item="item" :key="index" />
- </transition>
- </g>
- </g>
- </svg> -->
-</template>
-
-<script lang="ts">
-import TextBubble from "@/components/textBubble/index.vue";
-import { Message } from "@/types/message/index";
-import { Queue } from "@/modules/queue";
-import useTransitions from "./viewDetails/transitions";
-import useScrollView from "@/modules/scrollView";
-import { defineComponent, reactive, onMounted, onUnmounted, watch } from "vue";
-
-export default defineComponent({
- name: "MessageView",
- components: { TextBubble },
-
- setup() {
- const renderArr: Message[] = reactive([]);
- const renderQ = new Queue();
-
- // Each method will be called on their respective lifecycle event.
- const { beforeEnter, enter, afterEnter, rendering } = useTransitions();
- // const { scrollView } = useScrollView({
- // targetId: "messageView",
- // });
-
- const render = (m: Message) => {
- if (rendering.value) {
- renderQ.enqueue(m);
- } else {
- renderArr.push(m);
- }
- };
-
- watch(rendering, (rendering, _prevRendering) => {
- if (!rendering) {
- const m = renderQ.dequeue() as Message | null;
- if (m) renderArr.push(m);
- }
- });
-
- onMounted(() => {
- window.ipcRenderer.on("render-message", (event, payload) => {
- const m = payload.message;
- if (m.content) render(m);
- });
-
- });
-
- onUnmounted(() => {
- window.ipcRenderer.removeAllListeners("render-message");
- });
-
- return {
- renderArr,
- beforeEnter,
- enter,
- afterEnter,
- };
- },
-});
-</script>
-
-<style lang="scss" scoped>
-#messageView {
- width: 1000px;
- height: 100vh;
- color: red;
-}
-</style>
+++ /dev/null
-import { ref } from "vue";
-/**
- * Calculates vertical and horizontal message offsets
- */
-export default function useOffsetCalculator() {
- const xOffset = ref(0);
- const yOffset = ref(0);
- const firstChildMarginTop = 45;
- let previousMessageHeight = 0;
- const messagePadding = 15;
-
- function calculateX(message: HTMLElement| SVGGElement): 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,
- }
-}
+++ /dev/null
-import { computed, onMounted, ref } from "vue";
-
-/**
- * single message svg item computed properties
- */
-export default function useComputedBubble(m: string, id: string) {
- const yMargin = 0;
- const messageMargin = 20;
- const messageWidth = ref(0);
-
-
- // 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 = () => {
- // const msg = document.getElementById(id);
-
- // if (msg) {
- // messageWidth.value = msg.getBoundingClientRect().width;
- // }
-
- // }
-
- const calculateXoffset = (w: number) => {
- let offset = messageMargin;
-
- // If self message, render on righthand side.
- if (m == "sf") {
- offset = w - messageMargin - 232
-
- // Else, render on left.
- }
-
- console.log(`${m} : ${offset}`);
- return offset
- }
-
- const calculateYoffset = (h: number) => {
-
- // Bubble will render a little bit below the screen.
- return h + yMargin
- }
-
- // Where the textBubble is going to initially render.
- const startingOffset = computed(() => {
-
- // Get current dimensions of the messenger.
- const parentView = document.getElementById('messagesView');
-
- if (parentView) {
- const dimensions = parentView.getBoundingClientRect();
-
- const X = calculateXoffset(dimensions.width);
- const Y = calculateYoffset(dimensions.height);
-
- return `translate(${X} ${Y})`;
- }
-
- });
-
- return {
- startingOffset
- };
-}
+++ /dev/null
-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,
- };
-}
+++ /dev/null
-<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 Text"
- 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 useBubbleDrawer from "./drawer";
-import useComputedBubble from "./computed";
-
-import useTextWrap from "@/modules/textWrap";
-
-import { defineComponent, onMounted, ref, reactive } from "vue";
-
-export default defineComponent({
- name: "TextBubble",
- 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}`);
-
- // Visual attribues of bubble.
- 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>
+++ /dev/null
-/**
- * Do stuff before, on, and after message enters the view.
- * Rendering Process consists of the execution of the above
- * mentioned callbacks followed by the shift or stack animation.
- * The animation called depends on the height of the message view
- * relative to the window height. Additionally, the animation will
- * always be called on the after enter callback, after all the neccesary
- * calculations have been completed. While all of this is hapenning,
- * the rendering state is updated throughout the process.
- */
-
-import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
-import useMessageViewAnims from "./anime";
-import useOffsetCalculator from './offsetCalculator';
-import { ref } from 'vue';
-
-const wH = 500; // window Height
-const vP = 10; // view padding
-const mP = 15; // message padding
-let mH: number; // message Height
-let vH: number; // view Height
-let vB: number; // view Bottom Bound
-let message: HTMLElement | null;
-let messageOffset = { x: 0, y: 0 };
-let shift = false;
-let beforeEnterVH: number;
-let messageView: SVGGElement;
-
-export default function useTransitions() {
- const { calculateMessageOffsets } = useOffsetCalculator();
- const { shiftAnimate, stackAnimate } = useMessageViewAnims();
- const rendering = ref(false);
-
- const animateMessage = async (el: SVGGElement) => {
-
- // Where the message gets translated to.
- const elTransform = `translate(${messageOffset.x} ${messageOffset.y})`;
-
- const viewTransform = `translate(0 ${-vB})`;
-
- // Shift of view height > window height.
- if (shift) {
- await shiftAnimate(el, elTransform, messageView, viewTransform);
- } else {
- await stackAnimate(el, elTransform)
- }
- rendering.value = false;
- }
-
- // const beforeEnter: VueTransitionCallback = () => {
- // const parentView = document.getElementById('messagesView');
-
- // if (parentView) {
- // const dimensions = parentView.getBoundingClientRect();
-
- // }
- // }
-
-
-
- // Get height of messageView.
- const beforeEnter: VueTransitionCallback = () => {
- rendering.value = true;
- // query DOM for messageView element
- const messageViewQuery = document.querySelectorAll("#messageView");
- messageView = messageViewQuery[0] as SVGGElement;
- if (messageViewQuery) {
- beforeEnterVH = messageView.getBBox().height;
- }
- }
-
- const enter: VueTransitionCallback = (el) => {
- // query DOM for latest message
- if (el) {
- message = document.getElementById(el.id);
- }
- }
-
- const afterEnter: VueTransitionCallback = (el) => {
-
- if (message) messageOffset = calculateMessageOffsets(message);
-
- if (el) {
- // calculate message & view heights, as well as view bottom bound
- mH = el.getBoundingClientRect().height;
- vH = mH + beforeEnterVH + mP;
- vB = Math.abs(vH - wH) + vP;
-
- shift = vH > wH ? true : false;
- animateMessage(el);
- }
- }
-
- return {
- beforeEnter,
- enter,
- afterEnter,
- rendering
- };
-}
+++ /dev/null
-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;
-
- let 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 updatewinW = computed(() => {
- const parentView = document.getElementById('messageView');
- if (parentView) {
- winW = parentView.getBoundingClientRect().width;
- }
- })
-
- 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('messageView');
- 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
- };
-}