Skip to content

Commit

Permalink
Merge pull request #14 from Matteo-Grellier/feature/crud-websocket-me…
Browse files Browse the repository at this point in the history
…ssages

✨ Feat(crud+channel): Added first CRUD and display of the messages in channel
  • Loading branch information
LBROCHARD authored Mar 27, 2023
2 parents 3fecdb7 + 1447212 commit f0d75dc
Show file tree
Hide file tree
Showing 33 changed files with 667 additions and 166 deletions.
23 changes: 22 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,30 @@ import Login from './views/LoginView.vue'
<template>
<main>
<router-link :to="{ name: 'login'}"></router-link>
<RouterView/>
<RouterView name="ChannelSidebar" class="ChannelSidebar"></RouterView>
<RouterView class="middlePart"/>
<RouterView name="MemberSidebar" class="MembersSidebar"></RouterView>
</main>
</template>

<style scoped>
main {
display: flex;
flex-direction: row;
}
.ChannelSidebar {
flex: 1;
}
.middlePart {
display: flex;
flex-direction: column;
flex: 3;
height: 100vh;
}
.MembersSidebar {
flex: 1;
}
</style>
8 changes: 7 additions & 1 deletion src/assets/base.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800;900&display=swap'); /* Montserrat */
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap'); /* Open Sans */

:root {
/* Theme colors */
--color-main-blue: #2B3542;
--color-dark-blue: #1F2630;
--color-light-blue: #3D4B5F;
--color-yellow: #FFB400;
--color-grey: #BEBEBE;
--color-white-grey: #EDEDED;

/* utils color */
--color-red: #ff7676; /* useful for button to delete */
--color-empty-message: #3c4550;

--main-font: "Montserrat";
--message-font: "Open Sans";

Expand Down
Binary file added src/assets/channel-no-img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/delete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/gif.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ textarea {
font-family: var(--message-font); /*Change this value in '<style scoped>' tag*/
font-size: var(--button-font-size);

height: var(--button-height);
/* height: var(--button-height); */
border-radius: var(--button-border-radius);
border: none;
background-color: var(--color-light-blue); /*Change this value in '<style scoped>' tag*/
Expand Down
1 change: 1 addition & 0 deletions src/assets/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/boot/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import axios from "axios";

const api = axios.create({ baseURL: "https://edu.tardigrade.land/msg" });

export const webSocketApi = axios.create({baseURL: "wss://edu.tardigrade.land/msg"})

export default api;
79 changes: 79 additions & 0 deletions src/components/Button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script setup lang="ts">
type Props = {
type?: "dark" | "light"
}
const props = defineProps<Props>();
</script>
<template>
<button :class="(!type || type == 'light') ? 'light-btn' : 'dark-btn' ">
<slot/>
</button>
</template>
<style scoped>
/* Default values */
button {
display: flex;
align-items: center;
text-align: center;
padding: var(--button-padding-height) 0.5em;
transition: all 0.2s ease-in-out;
font-family: var(--button-font-family);
font-size: var(--button-font-size);
font-weight: 600;
height: var(--button-height);
border-radius: var(--button-border-radius);
border: none;
background-color: var(--color-yellow);
border: 2px solid var(--color-yellow);
color: var(--color-dark-blue);
}
button:hover {
cursor: pointer;
background-color: var(--color-main-blue);
color: var(--color-yellow);
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.2);
}
:slotted(svg) {
fill: var(--color-dark-blue);
width: 30px;
height: 30px;
}
button:hover :slotted(svg) {
fill: var(--color-yellow);
width: 30px;
height: 30px;
}
button img {
margin-right: 0;
}
/* Darker values */
.dark-btn {
background-color: var(--color-main-blue);
border: 2px solid var(--color-dark-blue);
color: var(--color-white-grey);
}
.dark-btn :slotted(svg) {
fill: var(--color-white-grey);
width: 30px;
height: 30px;
}
.dark-btn:hover {
background-color: var(--color-main-blue);
border: 2px solid var(--color-yellow);
color: var(--color-yellow);
}
</style>
53 changes: 19 additions & 34 deletions src/components/ChannelContent.vue
Original file line number Diff line number Diff line change
@@ -1,56 +1,40 @@
<script lang="ts" setup>
import api from '@/boot/axios';
import { onBeforeMount, reactive, onMounted, ref } from 'vue';
import api, {webSocketApi} from '@/boot/axios';
import { onBeforeMount, reactive, onMounted, ref, watchEffect, watch } from 'vue';
import { useAuthStore } from '@/stores/auth-store';
import { useRouter } from 'vue-router';
import Message from './Message.vue';
import Spinner from './Spinner.vue';
import Button from './Button.vue';
import { useMessagesStore } from '@/stores/messages';
const authStore = useAuthStore();
const messagesStore = useMessagesStore();
const router = useRouter();
type Props = {
channelId: number,
creator: string,
}
const props = defineProps<Props>();
const messages: Message[] = reactive([])
const isLoaded = ref(false);
onBeforeMount(async () => {
await setMessages();
isLoaded.value = true
})
const isLoaded = ref(true);
const currentUserIsModerator = ref(false);
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 createWebSocketConnection = async () => {
webSocketApi.get(`/ws/channel/${props.channelId}/token/${authStore.token}`)
}
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();
if((currentElement.scrollHeight + currentElement.scrollTop) - currentElement.clientHeight <= 1) {
await messagesStore.loadOlderMessages(props.channelId.toString());
}
}
Expand All @@ -62,9 +46,10 @@ const onScroll = async ({target}: Event) => {
<div v-if="!isLoaded" class="channel-spinner">
<Spinner/>
</div>
<Message v-for="(message, index) of messages"
<Message v-for="(message, index) of messagesStore.messages"
:message="message"
:previousMessage="(index > 0) ? messages[index-1] : undefined"
:previousMessage="(index > 0) ? messagesStore.messages[index-1] : undefined"
:showToolbar="currentUserIsModerator"
/>
</div>
</div>
Expand All @@ -76,7 +61,7 @@ const onScroll = async ({target}: Event) => {
.channel-content {
overflow-y: auto;
overflow-x: hidden;
height: 100vh;
height: 100%;
width: 100%;
display: flex;
flex-direction: column-reverse;
Expand Down
11 changes: 9 additions & 2 deletions src/components/ChannelLink.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
<script setup lang="ts">
import ImageAlternative from "../assets/channel-no-img.png"
import { ref } from 'vue';
const props = defineProps<{
channelId: number,
channelName: string,
channelImg: string
}>();
const channelImage = ref(props.channelImg);
</script>

<template >
<div class="chanelDiv">
<RouterLink :to="channelName" class="chanelDiv">
<img :src="channelImg" alt="imgNotFound" :title="channelName" class="channelLogo">
<RouterLink :to="{ name: 'channel', params: { channelId } }" class="chanelDiv">
<img :src="channelImage" alt="imgNotFound" :title="channelName" class="channelLogo" @error="() => channelImage = ImageAlternative">
<p class="channelName" >{{ channelName }}</p>
</RouterLink>
</div>
Expand Down
47 changes: 17 additions & 30 deletions src/components/ChannelSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,25 @@
import { onBeforeMount, ref } from 'vue';
import Spinner from './Spinner.vue';
import { useChannelStore } from '@/stores/channel'
import {useRoute, useRouter} from "vue-router";
const channels = ref<ChannelLink[]>([]);
const route = useRoute();
const channelStore = useChannelStore();
const channels = ref<Channel[]>([]);
const isLoaded = ref(false);
const authToken = useAuthStore().getToken();
const config = {
headers: {
Authorization: `Bearer ${authToken}`
}
};
onBeforeMount(async () => {
await getChannels();
isLoaded.value = true
})
const getChannels = async () => {
await api.get('/protected/user/channels', config)
.then(function(response){
channels.value = response.data;
})
.catch(function(error) {
console.log('Error:', error);
});
}
interface ChannelLink {
name: string;
img: string;
id: number;
creator: string;
}
channels.value = channelStore.channels;
console.log(channels);
const channelId = route.params.channelId;
const channelStore = useChannelStore();
isLoaded.value = true
function selectedChannel(channel:ChannelLink){
channelStore.selectedChannel(channel)
}
if(!channelId) return;
channelStore.setSelectedChannel(channelId as string)
})
</script>

<template>
Expand All @@ -58,7 +41,11 @@
<div v-if="channels.length > 0">
<ul class="channelList" v-if="isLoaded">
<li >
<ChannelLink @click="selectedChannel(channel)" v-for="channel in channels" :key="channel.id" :channelName="channel.name" :channelImg="channel.img"/>
<ChannelLink v-for="channel in channels"
:key="channel.id"
:channelName="channel.name"
:channelImg="channel.img"
:channelId="channel.id"/>
</li>
</ul>
</div>
Expand Down
Loading

0 comments on commit f0d75dc

Please sign in to comment.