Skip to content
Snippets Groups Projects
Commit 57983430 authored by Titus Netland's avatar Titus Netland
Browse files

Merge branch 'chat' into 'main'

Chat

See merge request !40
parents 3d1b0de3 d3a486b7
No related branches found
No related tags found
1 merge request!40Chat
Pipeline #176995 passed
...@@ -12,6 +12,7 @@ module.exports = { ...@@ -12,6 +12,7 @@ module.exports = {
parser: "@babel/eslint-parser", parser: "@babel/eslint-parser",
}, },
rules: { rules: {
"prettier/prettier": "warn",
"linebreak-style": 0, "linebreak-style": 0,
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -16,7 +16,10 @@ ...@@ -16,7 +16,10 @@
"core-js": "^3.8.3", "core-js": "^3.8.3",
"cssom": "^0.5.0", "cssom": "^0.5.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"net": "^1.0.2",
"roboto-fontface": "*", "roboto-fontface": "*",
"sockjs-client": "^1.6.0",
"stompjs": "^2.3.3",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-router": "^4.0.3", "vue-router": "^4.0.3",
"vuelidate": "^0.7.7", "vuelidate": "^0.7.7",
......
<template>
<div v-bind:class="'w-full break-words flex ' + side()">
<div style="max-width: 70%" v-bind:class="this.color() + ' rounded px-5 py-2 my-2 relative ' + this.textColor() ">
<span class="block">{{this.message.content}} {{this.message.from}}</span>
<span class="block text-xs text-right">{{this.calculateTime()}}</span>
</div>
</div>
</template>
<script>
import {parseCurrentUser} from "@/utils/token-utils";
export default {
props: {
message: Object
},
data() {
return {
abc: "abcde"
}
},
computed: {
userID() {
return parseCurrentUser().account_id;
}
},
methods: {
color() {
console.log(this.userID);
return this?.message.from == this.userID ? "bg-gray-300" : "bg-blue-600";
},
textColor() {
return this?.message.from == this.userID ? "text-gray-900" : "text-white";
},
side () {
return this?.message.from == this.userID ? "justify-start" : "justify-end"
},
calculateTime() {
//let time = this.message.from;
// Calculate time when message was sent
let date = new Date(Date.now());
let hours = date.getHours();
let minutes = "0" + date.getMinutes();
let formattedTime = hours + ':' + minutes.substr(-2);
return formattedTime;
}
}
}
</script>
\ No newline at end of file
<template>
<a
v-on:click="selectUser" class="hover:bg-gray-100 border-b border-gray-300 px-3 py-2 cursor-pointer flex items-center text-sm focus:outline-none focus:border-gray-300 transition duration-150 ease-in-out"
>
<img
class="h-10 w-10 rounded-full object-cover"
src="https://www.mintface.xyz/content/images/2021/08/QmTndiF423kjdXsNzsip1QQkBQqDuzDhJnGuJAXtv4XXiZ-1.png"
:alt="{name}"
/>
<div class="w-full pb-2">
<div class="flex justify-between">
<span class="block ml-2 font-semibold text-base text-gray-600"
>{{name}}</span
>
<span class="block ml-2 text-sm text-gray-600">{{lastMessageTime}}</span>
</div>
<span class="block ml-2 text-sm text-gray-600">{{lastMessage}}</span>
</div>
</a>
</template>
<script>
//TODO fix avatar
export default {
props: {
conversation: {
type: Object,
required: true
},
recipient: Function
},
data: () => {
return {
};
},
computed: {
lastMessage () {
return this.conversation.lastMessage.content;
},
name() {
return this.conversation.recipient.firstName + " " + this.conversation.recipient.lastName;
},
lastMessageTime() {
return "5 min";
}
},
methods: {
selectUser() {
console.log(this.conversation.recipient.userId)
this.$emit("recipient",this.conversation.recipient.userId);
}
}
};
</script>
<template>
<div class="col-span-3 sm:col-span-2 block">
<div class="w-full">
<div class="flex justify-between border-b border-gray-300">
<div class="grid sm:hidden space-y-2 content-center m-3">
<div class="w-8 h-0.5 bg-gray-600"></div>
<div class="w-8 h-0.5 bg-gray-600"></div>
<div class="w-8 h-0.5 bg-gray-600"></div>
</div>
<div class="relative flex items-center p-3">
<img class="object-cover w-10 h-10 rounded-full"
src="https://www.mintface.xyz/content/images/2021/08/QmTndiF423kjdXsNzsip1QQkBQqDuzDhJnGuJAXtv4XXiZ-1.png"
alt="{{name}}" />
<span class="block ml-2 font-bold text-gray-600">{{name}}</span>
</div>
<div></div>
</div>
<div class="relative w-full p-6 overflow-y-auto" style="max-height: 400px">
<ul class="space-y-2">
<div >
<ChatMessage v-for="(message, i) in messages" v-bind:key="i" :message="message"></ChatMessage>
</div>
</ul>
</div>
<div class="flex items-center justify-between w-full p-3 border-t border-gray-300">
<input type="text" placeholder="Message"
class="block w-full py-2 pl-4 mx-3 bg-gray-100 rounded-full outline-none focus:text-gray-700"
name="message" v-model="message" />
<button v-on:click="sendMessage" style="padding: 10px; color: red;">
<svg class="w-5 h-5 text-gray-500 origin-center transform rotate-90"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path
d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z" />
</svg>
</button>
</div>
</div>
</div>
</template>
<script>
import ChatMessage from "./ChatMessage.vue"
import axios from 'axios';
import { parseCurrentUser } from "@/utils/token-utils";
import ws from '@/services/ws';
export default {
props: {
recipient: Object,
},
data: () => {
return {
messages: [],
message: ""
};
},
components: { ChatMessage },
computed: {
userid() {
console.log(parseCurrentUser());
return parseCurrentUser().account_id;
},
recipientID() {
return this.recipient.userId;
},
name() {
return this.recipient.firstName + " " + this.recipient.lastName;
}
},
methods: {
calculateSide(from) {
console.log("userid ", this.userid)
return from == this.userid ? 'end' : 'start'
},
async sendMessage() {
const token = this.$store.state.user.token;
await axios.post(process.env.VUE_APP_BASEURL + `chats/users/${this.recipientID}/messages`, {
message: this.message
}, {
headers: {
Authorization: `Bearer ${token}`
}
})
this.message = "";
ws.sendMessage({ sender: parseInt(this.userid), recipient: this.recipientID });
this.reloadMessages();
},
async reloadMessages() {
const token = this.$store.state.user.token;
const response = await fetch(`${process.env.VUE_APP_BASEURL}chats/users/${this.recipientID}/messages`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
})
this.messages = await response.json()
}
},
async created() {
const token = this.$store.state.user.token;
// Fetch /chats/users/{userId}/messages from api
const response = await fetch(`${process.env.VUE_APP_BASEURL}chats/users/${this.recipientID}/messages`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
});
ws.on("NEW_MESSAGE", () => {
this.reloadMessages();
})
this.messages = await response.json()
}
}
</script>
\ No newline at end of file
...@@ -3,5 +3,10 @@ import App from "./App.vue"; ...@@ -3,5 +3,10 @@ import App from "./App.vue";
import router from "./router"; import router from "./router";
import store from "./store"; import store from "./store";
import "./index.css"; import "./index.css";
import ws from './services/ws';
createApp(App).use(router).use(store).mount("#app"); createApp(App)
.use(router)
.use(store)
.mount("#app");
console.log("WS", ws.test);
\ No newline at end of file
...@@ -39,6 +39,13 @@ const routes = [ ...@@ -39,6 +39,13 @@ const routes = [
name: "register", name: "register",
component: () => import("../views/RegisterView.vue"), component: () => import("../views/RegisterView.vue"),
}, },
{
path: "/messages",
name: "messages",
component: () =>
import(/* webpackChunkName: "register" */ "../views/ChatView.vue"),
beforeEnter: guardRoute,
},
{ {
path: "/login", path: "/login",
name: "login", name: "login",
...@@ -82,13 +89,7 @@ const routes = [ ...@@ -82,13 +89,7 @@ const routes = [
name: "notifications", name: "notifications",
component: () => import("../views/NotificationView.vue"), component: () => import("../views/NotificationView.vue"),
beforeEnter: guardRoute, beforeEnter: guardRoute,
}, }
{
path: "/messages",
name: "messages",
component: () => import("../views/MessagesView.vue"),
beforeEnter: guardRoute,
},
]; ];
const router = createRouter({ const router = createRouter({
......
const Stomp = require("stompjs");
const SockJS = new require("sockjs-client")(process.env.VUE_APP_BASEURL + "ws");
import {parseCurrentUser} from "@/utils/token-utils";
// Create a Singleton function
// Events fired by websocket, MESSAGE
const ws = (function () {
// Object of all injected functions that the client may want
const handlers = {};
const fire = function (event, data) {
if (handlers[event]) {
handlers[event](data)
}
};
const onMessageReceived = (payload) => {
const data = JSON.parse(payload.body);
console.log("New message!")
// Fire message event
fire("MESSAGE", JSON.parse(payload.body));
if(data.status == "NEW_MESSAGE") fire("NEW_MESSAGE", JSON.parse(payload.body));
console.log("Received message: " + payload);
}
const onConnected = () => {
console.log("Websocket Connected");
stompClient.subscribe(
"/user/" + parseCurrentUser().account_id + "/queue/messages",
onMessageReceived
);
};
const onError = () => {
};
let stompClient = Stomp.over(SockJS);
stompClient.connect({}, onConnected, onError);
return {
on: function (event, callback) {
handlers[event] = callback;
},
fire: fire,
isActive: function (event) {
return !!handlers[event];
},
end: function (event) {
if (handlers[event]) {
delete handlers[event];
} else {
throw new Error("No handler for event: " + event);
}
},
sendMessage: ({sender, recipient, status }) => {
if(status) console.log(status)
stompClient.send("/app/chat", {}, JSON.stringify({
from: sender,
to: recipient,
id: null,
status: "NEW_MESSAGE"
}));
},
test: true
}
})();
export default ws;
\ No newline at end of file
<template>
<div class="min-h-full">
<div class=" border rounded grid grid-cols-3 w-full">
<div class="border-r border-gray-300 col-span-1">
<ul class="hidden sm:block overflow-auto h-full">
<h2 class="my-2 mb-2 ml-2 text-lg text-gray-600">Chats</h2>
<li>
<ChatProfile v-for="(conversation, i) in conversations" :conversation="conversation" :key="i" @recipient="selectUser"></ChatProfile>
</li>
</ul>
</div>
<CurrentChat v-if="selected" :recipient="selected" :key="key"></CurrentChat>
</div>
</div>
</template>
<script>
import ChatProfile from "@/components/chat/ChatProfile.vue";
import CurrentChat from "@/components/chat/CurrentChat.vue";
import {parseCurrentUser} from "@/utils/token-utils";
export default {
components: {
ChatProfile,
CurrentChat
},
data() {
return {
conversations: null,
selected: null
};
},
computed: {
userID() {
return parseCurrentUser().account_id;
},
key() {
return this.selected.userId || "ERROR" ;
}
},
methods: {
selectUser(value) {
const userid = value
this.conversations.find(conversation => {
return conversation.recipient.userId == userid;
})
this.selected = this.conversations.find(conversation => conversation.recipient.userId == userid).recipient;
console.log(this.selected)
}
},
async created() {
const token = this.$store.state.user.token;
// Get all conversations from api with /chats/users
const response = await fetch(`${process.env.VUE_APP_BASEURL}chats/users`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
}) // TODO add error handling
const res = await response.json();
this.conversations = res;
}
}
</script>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment