-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from Matteo-Grellier/feature/messages
✨ Added system to display the historic of messages + style
- Loading branch information
Showing
9 changed files
with
5,452 additions
and
4,842 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}) | ||
}) |