Skip to content

Commit

Permalink
Merge pull request #10 from Matteo-Grellier/feature/messages
Browse files Browse the repository at this point in the history
✨ Added system to display the historic of messages + style
  • Loading branch information
matheoleger authored Mar 23, 2023
2 parents 644baa8 + 7396b97 commit de2d0c4
Show file tree
Hide file tree
Showing 9 changed files with 5,452 additions and 4,842 deletions.
9,923 changes: 5,082 additions & 4,841 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
"test:ui": "vitest --ui"
},
"dependencies": {
"axios": "^1.3.3",
"pinia": "^2.0.28",
"vee-validate": "^4.7.4",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
"vue-router": "^4.1.6",
"yup": "^1.0.0"
},
"devDependencies": {
"@types/node": "^18.11.12",
Expand Down
11 changes: 11 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ body, html {
color: var(--color-white-grey);
}

body::-webkit-scrollbar {
width: 8px;
}
body::-webkit-scrollbar-track {
background: var(--color-main-blue);
}
body::-webkit-scrollbar-thumb {
background-color: var(--color-light-blue);
border-radius: 20px;
}

p {
font-family: var(--message-font);
font-size: var(--message-font-size);
Expand Down
109 changes: 109 additions & 0 deletions src/components/ChannelContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script lang="ts" setup>
import api from '@/boot/axios';
import { onBeforeMount, reactive, onMounted, ref } from 'vue';
import { useAuthStore } from '@/stores/auth-store';
import Message from './Message.vue';
import Spinner from './Spinner.vue';
const authStore = useAuthStore();
type Props = {
channelId: number,
}
const props = defineProps<Props>();
const messages: Message[] = reactive([])
const isLoaded = ref(false);
onBeforeMount(async () => {
await setMessages();
isLoaded.value = true
})
const setMessages = async () => {
const messagesToAdd = await getMessagesFromApi();
messages.unshift(...messagesToAdd);
}
const getMessagesFromApi = async () => {
const offset = messages.length;
const config = {
headers: { Authorization: `Bearer ${authStore.getToken()}` }
};
const response = await api.get(`/protected/channel/${props.channelId}/messages/${offset}`, config)
return response.data;
}
const loadOlderMessages = async () => {
isLoaded.value = false
await setMessages();
isLoaded.value = true
}
const onScroll = async ({target}: Event) => {
const currentElement: Element = target as Element
//The logic to know if we are at the top of the div is reversed because we used a trick in css (we use column-reverse) :
//So we want to know if we are at the bottom of the div !
if(currentElement.scrollTop - currentElement.clientHeight <= -currentElement.scrollHeight) {
await loadOlderMessages();
}
}
</script>
<template>
<div class="channel-content" @scroll="onScroll">
<div class="channel-container">
<div class="channel-messages-container">
<div v-if="!isLoaded" class="channel-spinner">
<Spinner/>
</div>
<Message v-for="(message, index) of messages"
:message="message"
:previousMessage="(index > 0) ? messages[index-1] : undefined"
/>
</div>
</div>
</div>

</template>

<style scoped>
.channel-content {
overflow-y: auto;
overflow-x: hidden;
height: 100vh;
width: 100%;
display: flex;
flex-direction: column-reverse;
}
.channel-container {
display: flex;
flex-direction: column-reverse;
justify-content: flex-end;
}
.channel-spinner {
display: flex;
width: 100%;
height: 100vh;
justify-content: center;
align-items: center;
}
.channel-content::-webkit-scrollbar {
width: 5px;
}
.channel-content::-webkit-scrollbar-track {
background: var(--color-main-blue);
}
.channel-content::-webkit-scrollbar-thumb {
background-color: var(--color-light-blue);
border-radius: 20px;
}
</style>
153 changes: 153 additions & 0 deletions src/components/Message.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getIsToday, getIsYesterday } from '../utils';
type Props = {
message: Message,
previousMessage?: Message
}
// type Message = {
// channelId : number,
// timestamp : number,
// author : string,
// content : MessageContent
// }
// type MessageContent = {
// text?: string,
// image?: string
// }
const props = defineProps<Props>()
const timeOfSentMessage = ref("");
const isSendInSameTime = ref(false);
const getCorrespondingDateAndHours = () => {
const dateOfMessage = new Date(props.message.timestamp);
console.log(dateOfMessage);
// const currentDate = new Date();
const messageTime = dateOfMessage.toLocaleString("fr-FR", {
hour: '2-digit',
minute: '2-digit'
});
if (getIsToday(dateOfMessage)) { // to change
timeOfSentMessage.value = `Today at ${messageTime}`
} else if (getIsYesterday(dateOfMessage)) { // to change
// date.value = dateOfMessage.toLocaleString("fr-FR")
timeOfSentMessage.value = `Yesterday at ${messageTime}`
} else {
timeOfSentMessage.value = dateOfMessage.toLocaleDateString('fr-FR', {hour: 'numeric', minute: 'numeric'});
}
}
const getCorrespondingHours = () => {
const dateOfMessage = new Date(props.message.timestamp);
const messageTime = dateOfMessage.toLocaleString("fr-FR", {
hour: '2-digit',
minute: '2-digit'
});
timeOfSentMessage.value = messageTime;
}
onMounted(() => {
const minuteInMilliseconds = 60000;
const numberOfMinutes = 3;
isSendInSameTime.value = (
Boolean(props.previousMessage)
&& props.message.timestamp - props.previousMessage!.timestamp < minuteInMilliseconds*numberOfMinutes
);
if(isSendInSameTime.value) {
console.log("it's totally true")
getCorrespondingHours();
} else {
getCorrespondingDateAndHours();
}
})
</script>
<template>
<div v-if="!isSendInSameTime" class="message" style="margin-top: 10px">
<h1 class="letter-profile">{{ message.author[0].toUpperCase() }}</h1>
<div class="message-content">
<h4>{{ message.author }}<span class="date"> • {{ timeOfSentMessage }}</span></h4>
<p v-if="message.content.Text">{{ message.content.Text }}</p>
<img v-else :src="message.content.Image" :alt="`Image from ${message.author}`">
</div>
</div>
<div v-else="isSendInSameTime" class="message">
<div class="message-content message-content-inline">
<p class="date date-same-time">{{ timeOfSentMessage }}</p>
<p v-if="message.content.Text">{{ message.content.Text }}</p>
<img v-else :src="message.content.Image" :alt="`Image from ${message.author}`">
</div>
</div>
</template>
<style scoped>
h1, .message-content, .message-content h4, .message-content p {
margin: 0;
}
.message-content h4 {
margin-bottom: 4px;
}
.date {
font-family: var(--message-font);
font-size: var(--date-font-size);
font-weight: 500;
color: var(--date-text-color);
}
.message {
display: flex;
/* align-items: center; */
/* margin: 5px 0; */
padding: 5px 15px;
}
.message:hover {
/* background-color: var(--color-main-blue) */
transition: all 0.2s ease-in-out;
backdrop-filter: brightness(0.9);
}
.letter-profile {
font-size: 2.3em;
margin-right: 10px;
background-color: var(--color-dark-blue);
border-radius: 100%;
width: 50px;
height: 50px;
min-width: 50px;
min-height: 50px;
line-height: 50px;
text-align: center;
}
.message-content-inline {
display: flex;
align-items: center;
}
.date-same-time {
visibility: hidden;
width: 60px;
min-width: 60px;
}
.message:hover .date-same-time {
transition: all 0.2s ease-in-out;
visibility: visible;
}
.message-content img {
border-radius: 10px;
max-width: 50em;
}
</style>
48 changes: 48 additions & 0 deletions src/components/Spinner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

<script lang="ts" setup>
// N.B : This spinner comes from https://10015.io/tools/css-loader-generator !
</script>
<template>
<div class="spinner"></div>
</template>
<style>
.spinner {
position: relative;
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
}
.spinner::before,
.spinner::after {
border: 6.7px solid #ffb400;
border-radius: 50%;
position: absolute;
content: '';
display: block;
}
.spinner::before {
width: 33.6px;
height: 33.6px;
border-bottom-color: transparent;
border-left-color: transparent;
animation: spinner-1o3y8q 0.75s infinite linear reverse;
}
.spinner::after {
animation: spinner-1o3y8q 0.5s infinite linear;
height: 56px;
width: 56px;
border-right-color: transparent;
border-top-color: transparent;
}
@keyframes spinner-1o3y8q {
to {
transform: rotate(360deg);
}
}
</style>
11 changes: 11 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type Message = {
channelId : number,
timestamp : number,
author : string,
content : MessageContent
}

type MessageContent = {
Text?: string,
Image?: string
}
20 changes: 20 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const getIsToday = (someDate: Date) => {
const today = new Date()

return (
someDate.getDate() == today.getDate() &&
someDate.getMonth() == today.getMonth() &&
someDate.getFullYear() == today.getFullYear()
)
}

export const getIsYesterday = (someDate: Date) => {
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1);

return (
someDate.getDate() == yesterday.getDate() &&
someDate.getMonth() == yesterday.getMonth() &&
someDate.getFullYear() == yesterday.getFullYear()
)
}
14 changes: 14 additions & 0 deletions test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {expect, describe, it} from 'vitest'
import { getIsToday, getIsYesterday } from '../src/utils';

describe("Utils date", () => {
it("Should a yesterday date", () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
expect(getIsYesterday(yesterday)).toBe(true);
})

it("Should a today date", () => {
expect(getIsToday(new Date())).toBe(true);
})
})

0 comments on commit de2d0c4

Please sign in to comment.