Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added system to display the historic of messages + style #10

Merged
merged 4 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
})
})