lots of changes ;o

This commit is contained in:
supertiger 2019-02-17 12:32:00 +00:00
parent fb43e6ea0b
commit 3ce91179b2
24 changed files with 553 additions and 100 deletions

View file

@ -1,4 +1,21 @@
const config = [ const config = [
{
title: 'Typing Indicator',
shortTitle: 'Typing Indicator',
date: '08/02/2019',
new: [
'See who\'s typing with the new typing indicator!',
'Online and offline friends now have their own category.',
'Timestamps for each message.'
],
fix: [
'Some bugs with the message list scrolling.',
'Added some margin and padding to some places.'
],
next: [
'Message notifications.',
]
},
{ {
title: 'Avatar', title: 'Avatar',
shortTitle: 'Avatar', shortTitle: 'Avatar',

View file

@ -1,11 +1,19 @@
<template> <template>
<div class="left-panel"> <div class="left-panel">
<MyMiniInformation /> <MyMiniInformation />
<div class="list"> <div class="tabs">
<PendingFriends /> <div :class="{selector: true, right: !isFriendsTab}"></div>
<div class="tab" @click="isFriendsTab = true">Friends</div>
<div class="tab" @click="isFriendsTab = false">Recents</div>
</div>
<div class="list" v-if="isFriendsTab">
<pending-friends />
<online-friends /> <online-friends />
<offline-friends /> <offline-friends />
</div> </div>
<div class="list" v-else>
<recent-friends />
</div>
<AddFriendPanel/> <AddFriendPanel/>
</div> </div>
</template> </template>
@ -17,13 +25,20 @@ import PendingFriends from './relationships/PendingFriends.vue'
import AddFriendPanel from './relationships/AddFriendPanel.vue' import AddFriendPanel from './relationships/AddFriendPanel.vue'
import OnlineFriends from './relationships/OnlineFriends.vue' import OnlineFriends from './relationships/OnlineFriends.vue'
import OfflineFriends from './relationships/OfflineFriends.vue' import OfflineFriends from './relationships/OfflineFriends.vue'
import RecentFriends from './relationships/RecentFriends.vue'
export default { export default {
components: { components: {
MyMiniInformation, MyMiniInformation,
PendingFriends, PendingFriends,
AddFriendPanel, AddFriendPanel,
OnlineFriends, OnlineFriends,
OfflineFriends OfflineFriends,
RecentFriends
},
data() {
return {
isFriendsTab: true
}
} }
} }
</script> </script>
@ -35,7 +50,8 @@ export default {
width: 300px; width: 300px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column flex-direction: column;
z-index: 1;
} }
.list{ .list{
margin: 10px; margin: 10px;
@ -43,6 +59,39 @@ export default {
overflow: auto; overflow: auto;
} }
.tabs{
display: flex;
color: white;
flex-shrink: 0;
margin-top: 20px;
position: relative;
}
.tab{
flex: 1;
text-align: center;
margin: auto;
flex-shrink: 0;
user-select: none;
cursor: default;
padding: 10px;
background: rgba(0, 0, 0, 0.171);
margin-left: 1px;
margin-right: 1px;
}
.selector {
background: rgba(255, 255, 255, 0.137);
width: 148px;
height: 39px;
top: 0;
left: 1px;
position: absolute;
z-index: -1;
transition: 0.3s;
}
.right{
left: 151px;
}
/* ------- SCROLL BAR -------*/ /* ------- SCROLL BAR -------*/
/* width */ /* width */
.list::-webkit-scrollbar { .list::-webkit-scrollbar {

View file

@ -1,11 +1,14 @@
<template> <template>
<div class="message"> <div :class="{message: true, ownMessage: user.uniqueID === $props.uniqueID}">
<div class="profile-picture" :style="`background-image: url(${userAvatar})`"></div> <div class="profile-picture" :style="`background-image: url(${userAvatar})`"></div>
<div class="triangle"> <div class="triangle">
<div class="triangle-inner"></div> <div class="triangle-inner"></div>
</div> </div>
<div class="content"> <div class="content">
<div class="username">{{this.$props.username}}</div> <div class="user-info">
<div class="username">{{this.$props.username}}</div>
<div class="date">{{getDate}}</div>
</div>
<div class="content-message">{{this.$props.message}}</div> <div class="content-message">{{this.$props.message}}</div>
</div> </div>
<div class="sending-status">{{statusMessage}}</div> <div class="sending-status">{{statusMessage}}</div>
@ -15,9 +18,13 @@
<script> <script>
import config from '@/config.js' import config from '@/config.js'
import friendlyDate from '@/date'
export default { export default {
props: ['message', 'status', 'username', 'avatar'], props: ['message', 'status', 'username', 'avatar', 'date', 'uniqueID'],
computed: { computed: {
getDate() {
return friendlyDate(this.$props.date);
},
userAvatar() { userAvatar() {
return config.domain + "/avatars/" + this.$props.avatar return config.domain + "/avatars/" + this.$props.avatar
}, },
@ -33,7 +40,10 @@ export default {
} else { } else {
return "" return ""
} }
} },
user() {
return this.$store.getters.user
}
} }
} }
</script> </script>
@ -50,6 +60,13 @@ export default {
animation: showMessage .3s ease-in-out; animation: showMessage .3s ease-in-out;
} }
.ownMessage .triangle-inner{
border-right: 7px solid rgba(184, 184, 184, 0.219);
}
.ownMessage .content{
background: rgba(184, 184, 184, 0.219);
}
@keyframes showMessage { @keyframes showMessage {
from { from {
transform: translate(0px, 9px); transform: translate(0px, 9px);
@ -68,7 +85,7 @@ export default {
margin-left: 0; margin-left: 0;
flex-shrink: 0; flex-shrink: 0;
background-position: center; background-position: center;
background-size: 100%; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
@ -104,16 +121,30 @@ export default {
margin-right: 0; margin-right: 0;
transition: 1s; transition: 1s;
} }
.username { .user-info {
color: rgb(189, 189, 189); display: flex;
font-size: 14px;
} }
.username {
color: rgb(219, 219, 219);
font-size: 14px;
margin: auto;
margin-left: 0;
margin-right: 0;
}
.date{
color: rgb(161, 161, 161);
font-size: 10px;
margin: auto;
margin-left: 5px;
}
.content-message { .content-message {
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
font-size: 14px; font-size: 14px;
overflow: hidden;
color: white;
} }
.message .sending-status { .message .sending-status {

View file

@ -111,7 +111,7 @@ export default {
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
background-position: center; background-position: center;
background-size: 100%; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }

View file

@ -8,12 +8,13 @@
</div> </div>
<div class="current-channel"><span v-if="!selectedChannelID">Welcome back!</span><span v-else>{{channelName}}</span></div> <div class="current-channel"><span v-if="!selectedChannelID">Welcome back!</span><span v-else>{{channelName}}</span></div>
</div> </div>
<typing-status v-if="typing" :username="whosTyping"/>
<div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]"> <div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]">
<spinner /> <spinner />
</div> </div>
<div v-else-if="selectedChannelID" class="message-logs"> <div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll">
<message v-for="(msg, index) in messages[selectedChannelID]" :key="index" :username="msg.creator.username" :avatar="msg.creator.avatar" :message="msg.message" :status="msg.status" /> <div class="scroll">
<message v-for="(msg, index) in messages[selectedChannelID]" :key="index" :date="msg.created" :username="msg.creator.username" :uniqueID="msg.creator.uniqueID" :avatar="msg.creator.avatar" :message="msg.message" :status="msg.status" />
</div>
</div> </div>
<news v-else /> <news v-else />
<div class="chat-input-area" v-if="selectedChannelID"> <div class="chat-input-area" v-if="selectedChannelID">
@ -22,6 +23,11 @@
<button :class="{'send-button': true, 'error-send-button': messageLength > 5000}" @click="sendMessage">Send</button> <button :class="{'send-button': true, 'error-send-button': messageLength > 5000}" @click="sendMessage">Send</button>
</div> </div>
<div class="info"> <div class="info">
<div class="typing-outer">
<transition name="typing-animate">
<typing-status v-if="typing" :username="whosTyping"/>
</transition>
</div>
<div :class="{'message-count': true, 'error-info': messageLength > 5000 }">{{messageLength}}/5000</div> <div :class="{'message-count': true, 'error-info': messageLength > 5000 }">{{messageLength}}/5000</div>
</div> </div>
</div> </div>
@ -79,6 +85,8 @@ export default {
if(this.message == "")return; if(this.message == "")return;
if (this.message.length > 5000) return; if (this.message.length > 5000) return;
clearInterval(this.postTimerID);
this.postTimerID = null;
const msg = this.message; const msg = this.message;
const tempID = this.generateNum(25); const tempID = this.generateNum(25);
@ -89,12 +97,13 @@ export default {
message: { message: {
tempID, tempID,
message: this.message, message: this.message,
channelID: this.selectedChannelID channelID: this.selectedChannelID,
created: new Date()
} }
}) })
this.message = "" this.message = ""
this.$store.dispatch('updateChannelLastMessage', this.selectedChannelID);
const { ok, error, result } = await messagesService.post(this.selectedChannelID, { const { ok, error, result } = await messagesService.post(this.selectedChannelID, {
message: msg, message: msg,
socketID: this.$socket.id, socketID: this.$socket.id,
@ -136,15 +145,18 @@ export default {
this.sendMessage(); this.sendMessage();
} }
}, },
scrollDown(speed){ invertScroll(event) {
//Scroll to bottom if(event.deltaY) {
$(".message-logs").stop(true).animate({ event.preventDefault();
scrollTop: $(".message-logs")[0].scrollHeight event.currentTarget.scrollTop -= parseFloat(getComputedStyle(event.currentTarget).getPropertyValue('font-size')) * (event.deltaY < 0 ? -1 : 1) * 2;
}, speed); }
},
hideTypingStatus(data) {
if(this.user.uniqueID === data.message.creator.uniqueID) return;
this.typing = false;
} }
}, },
mounted() { mounted() {
bus.$on('scrollDown', this.scrollDown);
this.$options.sockets.typingStatus = (data) => { this.$options.sockets.typingStatus = (data) => {
const {channelID, userID} = data; const {channelID, userID} = data;
if (channelID !== this.selectedChannelID) return; if (channelID !== this.selectedChannelID) return;
@ -157,12 +169,16 @@ export default {
this.typing = false; this.typing = false;
}, 2500); }, 2500);
} }
bus.$on('newMessage', this.hideTypingStatus)
}, },
beforeDestroy() { beforeDestroy() {
bus.$off('newMessage', this.hideTypingStatus);
delete this.$options.sockets.typingStatus; delete this.$options.sockets.typingStatus;
bus.$off('scrollDown', this.scrollDown)
}, },
computed: { computed: {
user() {
return this.$store.getters.user;
},
channel() { channel() {
return this.$store.getters.channels[this.selectedChannelID]; return this.$store.getters.channels[this.selectedChannelID];
}, },
@ -183,6 +199,18 @@ export default {
<style scoped> <style scoped>
.typing-animate-enter-active {
transition: .10s;
}
.typing-animate-enter /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(3px)
}
.typing-animate-leave-to {
opacity: 0;
transform: translateY(-3px)
}
.error-info { .error-info {
color: red; color: red;
} }
@ -227,6 +255,8 @@ export default {
overflow: auto; overflow: auto;
flex: 1; flex: 1;
} }
.message-logs, .message-logs .scroll {transform: scale(1,-1);}
.loading{ .loading{
overflow: auto; overflow: auto;
flex: 1; flex: 1;
@ -241,11 +271,21 @@ export default {
color: white; color: white;
font-size: 12px; font-size: 12px;
margin-left: 25px; margin-left: 25px;
margin-top: 5px;
display: flex;
}
.typing-outer{
flex: 1;
height: 20px;
}
.message-count {
float: right;
margin-right: 30px;
margin-top: 3px;
} }
.message-area{ .message-area{
display: flex; display: flex;
width: 100%; width: 100%;
} }
.chat-input{ .chat-input{
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;

View file

@ -104,7 +104,7 @@ export default {
margin-left: 20px; margin-left: 20px;
flex-shrink: 0; flex-shrink: 0;
background-position: center; background-position: center;
background-size: 100%; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.information { .information {

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="typing-status"> <div class="typing-status">
<object class="animation" type="image/svg+xml" :data="animation"></object> <object class="animation" type="image/svg+xml" :data="animation"></object>
<div class="text">{{this.$props.username}} is typing...</div> <div class="text"><strong>{{this.$props.username}}</strong> is typing...</div>
</div> </div>
</template> </template>
@ -19,34 +19,20 @@ export default {
<style scoped> <style scoped>
.typing-status { .typing-status {
color: white; color: white;
background: rgba(0, 0, 0, 0.246);
padding: 5px;
margin-bottom: 5px;
display: flex; display: flex;
transition: 0.3s; transition: 0.3s;
flex-shrink: 0; flex-shrink: 0;
flex: 1;
} }
.animation { .animation {
height: 40px; height: 20px;
width: 40px; width: 20px;
display: flex; display: flex;
} }
.text { .text {
margin: auto; margin: auto;
margin-left: 10px; margin-left: 5px;
font-size: 13px;
} }
@keyframes moveBall {
from{
height: 0px;
width: 0px;
opacity: 1;
}
to {
height: 40px;
width: 40px;
opacity: 0;
}
}
</style> </style>

View file

@ -90,7 +90,7 @@ export default {
transition: .3s; transition: .3s;
} }
.slide-enter, .slide-leave-to /* .fade-leave-active below version 2.1.8 */ { .slide-enter, .slide-leave-to /* .fade-leave-active below version 2.1.8 */ {
margin-bottom: -270px; margin-bottom: -250px;
opacity: 0; opacity: 0;
} }
@ -104,7 +104,7 @@ export default {
.add-friend{ .add-friend{
background: rgba(0, 0, 0, 0.13); background: rgba(0, 0, 0, 0.13);
flex: 1; flex: 1;
height: 250px; height: 230px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: white; color: white;
@ -150,8 +150,8 @@ form {
background: rgba(0, 0, 0, 0.274); background: rgba(0, 0, 0, 0.274);
width: 100%; width: 100%;
text-align: center; text-align: center;
padding-top: 15px; padding-top: 5px;
padding-bottom: 15px; padding-bottom: 5px;
color: white; color: white;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="friend" @click="openChat"> <div :class="{friend: true, notifyAnimation: (notificationss && notificationss > 0) }" :style="`background: ${status.bgColor};`" @click="openChat">
<div class="profile-picture" :style="`border-color: ${status.statusColor}; background-image: url(${userAvatar})`"> <div class="profile-picture" :style="`border-color: ${status.statusColor}; background-image: url(${userAvatar})`">
<div class="status" :style="`background-image: url(${status.statusURL})`" ></div> <div class="status" :style="`background-image: url(${status.statusURL})`" ></div>
</div> </div>
@ -7,6 +7,11 @@
<div class="username">{{$props.username}}</div> <div class="username">{{$props.username}}</div>
<div class="status-name" :style="`color: ${status.statusColor}`">{{status.statusName}}</div> <div class="status-name" :style="`color: ${status.statusColor}`">{{status.statusName}}</div>
</div> </div>
<div class="notification" v-if="notificationss && notificationss >0">
<div class="notification-inner">
{{notificationss}}
</div>
</div>
</div> </div>
</template> </template>
@ -15,9 +20,10 @@ import channelService from '@/services/channelService';
import messagesService from '@/services/messagesService'; import messagesService from '@/services/messagesService';
import config from '@/config.js' import config from '@/config.js'
import statuses from '@/statuses'; import statuses from '@/statuses';
import {bus} from '@/main'
export default { export default {
props: ['username', 'tag', 'channelID', 'uniqueID'], props: ['username', 'tag', 'channelID', 'uniqueID', ],
methods: { methods: {
async getMessages() { async getMessages() {
const {ok, error, result} = await messagesService.get(this.$props.channelID); const {ok, error, result} = await messagesService.get(this.$props.channelID);
@ -29,9 +35,10 @@ export default {
} }
}, },
async openChat() { async openChat() {
bus.$emit('closeLeftMenu');
this.$store.dispatch('selectedChannelID', this.$props.channelID); this.$store.dispatch('selectedChannelID', this.$props.channelID);
this.$store.dispatch('setName', this.$props.username); this.$store.dispatch('setChannelName', this.$props.username);
if (this.$store.getters.channels[this.$props.channelID]) return if (this.$store.getters.channels[this.$props.channelID] && !this.$store.getters.messages[this.$props.channelID]) return this.getMessages();
const {ok, error, result} = await channelService.post(this.$props.channelID); const {ok, error, result} = await channelService.post(this.$props.channelID);
if ( ok ) { if ( ok ) {
this.$store.dispatch('channel', result.data.channel); this.$store.dispatch('channel', result.data.channel);
@ -43,6 +50,14 @@ export default {
} }
}, },
computed: { computed: {
notificationss () {
const channelID = this.$props.channelID;
const notifications = this.$store.getters.notifications.find(function(e) {
return e.channelID == channelID
})
if (!notifications) return;
return notifications.count;
},
user() { user() {
return this.$store.getters.user.friends[this.$props.uniqueID].recipient; return this.$store.getters.user.friends[this.$props.uniqueID].recipient;
}, },
@ -54,7 +69,8 @@ export default {
return { return {
statusName: statuses[parseInt(status)].name, statusName: statuses[parseInt(status)].name,
statusURL: statuses[parseInt(status)].url, statusURL: statuses[parseInt(status)].url,
statusColor: statuses[parseInt(status)].color statusColor: statuses[parseInt(status)].color,
bgColor: statuses[parseInt(status)].bgColor
} }
} }
} }
@ -67,10 +83,42 @@ export default {
color: white; color: white;
background-color: rgba(0, 0, 0, 0.137); background-color: rgba(0, 0, 0, 0.137);
margin: 5px; margin: 5px;
padding: 10px; padding: 5px;
padding-right: 0;
display: flex; display: flex;
transition: 0.3s; transition: 0.3s;
border-radius: 3px; border-radius: 5px;
position: relative;
overflow: hidden;
}
.notifyAnimation:before{
content: '';
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
animation: notifyAnime;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-fill-mode: forwards
}
@keyframes notifyAnime {
0%{
background: rgba(255, 0, 0, 0.198);
}
40%{
background: rgba(255, 0, 0, 0.411);
}
60%{
background: rgba(255, 0, 0, 0.411);
}
100%{
background: rgba(255, 0, 0, 0.198);
}
} }
.friend:hover { .friend:hover {
@ -78,18 +126,19 @@ export default {
} }
.profile-picture{ .profile-picture{
height: 40px; height: 30px;
width: 40px; width: 30px;
background-color: rgba(0, 0, 0, 0.425); background-color: rgba(0, 0, 0, 0.425);
border-radius: 50%; border-radius: 50%;
margin: auto; margin: auto;
margin-left: 2px; margin-left: 2px;
margin-right: 5px; margin-right: 5px;
border: solid 3px; border: solid 2px;
position: relative; position: relative;
background-position: center; background-position: center;
background-size: 100%; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
transition: 0.3s;
} }
.information{ .information{
margin: auto; margin: auto;
@ -98,10 +147,25 @@ export default {
flex: 1; flex: 1;
} }
.notification{
position: absolute;
display: flex;
background: rgba(134, 134, 134, 0.315);
height: 100%;
right: 0;
top: 0;
bottom: 0;
width: 50px;
border-radius: 1px;
}
.notification-inner{
margin: auto;
}
.status { .status {
position: absolute; position: absolute;
height: 20px; height: 15px;
width: 20px; width: 15px;
background-color: black; background-color: black;
border-radius: 50%; border-radius: 50%;
background-size: calc(100% + 2px); background-size: calc(100% + 2px);
@ -114,7 +178,7 @@ export default {
.friend:hover .status { .friend:hover .status {
opacity: 1; opacity: 1;
bottom: -5px; bottom: -4px;
} }
.status-name{ .status-name{
@ -125,7 +189,7 @@ export default {
} }
.friend:hover .status-name { .friend:hover .status-name {
opacity: 0.8; opacity: 0.8;
height: 19px; height: 13px;
} }
</style> </style>

View file

@ -30,7 +30,7 @@ export default {
const result = Object.keys(allFriend).map(function(key) { const result = Object.keys(allFriend).map(function(key) {
return allFriend[key]; return allFriend[key];
}); });
return result.filter(friend => friend.status == 2 && friend.recipient.status === undefined || friend.recipient.status == 0 ); return result.filter(friend => friend.status == 2 && (friend.recipient.status === undefined || friend.recipient.status == 0 ));
} }
} }
} }

View file

@ -30,7 +30,7 @@ export default {
const result = Object.keys(allFriend).map(function(key) { const result = Object.keys(allFriend).map(function(key) {
return allFriend[key]; return allFriend[key];
}); });
return result.filter(friend => friend.status == 2 && friend.recipient.status !== undefined || friend.recipient.status > 0 ); return result.filter(friend => friend.status == 2 && (friend.recipient.status !== undefined && friend.recipient.status > 0 ));
} }
} }
} }

View file

@ -70,7 +70,7 @@ export default {
margin-left: 2px; margin-left: 2px;
margin-right: 5px; margin-right: 5px;
background-position: center; background-position: center;
background-size: 100%; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.information{ .information{

View file

@ -0,0 +1,97 @@
<template>
<div class="recents">
<transition name="list" appear>
<div class="list">
<FriendsTemplate v-for="(channel, key) of channels" :key="key" notifications="1" :channelID="channel.channelID" :uniqueID="channel.recipients[0].uniqueID" :username="channel.recipients[0].username" :tag="channel.recipients[0].tag"/>
</div>
</transition>
</div>
</template>
<script>
import Tab from './Tab.vue'
import FriendsTemplate from './FriendsTemplate.vue'
export default {
components: {
Tab,
FriendsTemplate
},
computed: {
channels() {
const json = this.$store.getters.channels;
const notifications = this.$store.getters.notifications;
const keys = Object.keys(json);
let result = [];
keys.forEach(function(key){
result.push(json[key]);
});
result.sort(function(a, b){
const notificationA = notifications.find(item => {
return item.channelID === a.channelID
})
const notificationB = notifications.find(item => {
return item.channelID === b.channelID
})
// make notifications more prority.
if (notificationA)
return -1
if (notificationB)
return 1
if (a.lastMessaged === undefined)
return 1
if (b.lastMessaged === undefined)
return -1
return b.lastMessaged - a.lastMessaged
});
// gets unopened dms
const notificationsFiltered = notifications.filter(item => {
const find = result.find(resFind => {
return resFind.channelID === item.channelID
})
if (!find) {
return true;
}
})
for (let index in notificationsFiltered){
notificationsFiltered[index].creator = "dummy";
notificationsFiltered[index].recipients = [notificationsFiltered[index].sender];
}
result = notificationsFiltered.concat(result)
return result
}
}
}
</script>
<style scoped>
.list-enter-active, .list-leave-active {
transition: .3s;
}
.list-enter, .list-leave-to /* .fade-leave-active below version 2.1.8 */ {
transform: translateY(-20px);
opacity: 0;
}
.recents{
background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none;
padding-bottom: 3px;
border-radius: 5px;
transition: 0.3s;
}
.tab{
border-radius: 5px;
transition: 0.3s;
}
.tab:hover{
background-color: rgba(0, 0, 0, 0.123);
}
</style>

View file

@ -21,6 +21,9 @@ export default {
display: flex; display: flex;
color: white; color: white;
} }
.tab-name {
padding-top: 3px;
}
.material-icons{ .material-icons{
transition: 0.3s; transition: 0.3s;
} }

View file

@ -33,6 +33,7 @@ export default {
border-radius: 10px; border-radius: 10px;
padding: 5px; padding: 5px;
width: 180px; width: 180px;
z-index: 1;
} }
.status-list { .status-list {
padding: 5px; padding: 5px;

53
src/date.js Normal file
View file

@ -0,0 +1,53 @@
export default (miliseconds) => {
let friendlyDate = "";
const now = new Date();
const messageDate = new Date(miliseconds);
if (sameDay(now, messageDate)) {
friendlyDate = `Today at ${getFullTime(messageDate)}`
} else if (yesterDay(now, messageDate)) {
friendlyDate = `Yesterday at ${getFullTime(messageDate)}`
} else {
friendlyDate = getFullDateWithTime(messageDate)
}
return friendlyDate;
}
function sameDay(d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate();
}
function yesterDay(d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
(d1.getDate() - d2.getDate()) == 1
}
function getFullTime(date){
let finalTime = ""
let hours = date.getHours();
let minutes = date.getMinutes()
if (hours <= 9) {
finalTime = `0${hours}`;
} else {
finalTime = `${hours}`
}
if (minutes <= 9) {
finalTime += `:0${minutes}`;
} else {
finalTime += `:${minutes}`
}
return finalTime;
}
function getFullDateWithTime(date) {
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const dayName = days[date.getDay()];
const monthName = months[date.getMonth()];
return `${dayName} ${date.getDate()} ${monthName} ${date.getFullYear()} at ${getFullTime(date)}`
}

View file

@ -3,31 +3,37 @@ const config = [
{ {
name: "Offline", name: "Offline",
url: require("@/assets/status/0.svg"), url: require("@/assets/status/0.svg"),
color: "#919191" color: "#919191",
bgColor: "rgba(145, 145, 145, 0.19)",
}, },
//Online //Online
{ {
name: "Online", name: "Online",
url: require("@/assets/status/1.svg"), url: require("@/assets/status/1.svg"),
color: "#27eb48" color: "#27eb48",
bgColor: "rgba(39, 235, 72, 0.19)"
}, },
//Away //Away
{ {
name: "Away", name: "Away",
url: require("@/assets/status/2.svg"), url: require("@/assets/status/2.svg"),
color: "#ffdd1e" color: "#ffdd1e",
bgColor: "rgb(255, 221, 30, 0.19)",
}, },
//Busy //Busy
{ {
name: "Busy", name: "Busy",
url: require("@/assets/status/3.svg"), url: require("@/assets/status/3.svg"),
color: "#ea0b1e" color: "#ea0b1e",
bgColor: "rgb(234, 11, 30, 0.19)"
}, },
//Looking to play //Looking to play
{ {
name: "Looking to play", name: "Looking to play",
url: require("@/assets/status/4.svg"), url: require("@/assets/status/4.svg"),
color: "#9a3dd3" color: "#9a3dd3",
bgColor: "rgb(154, 61, 211, 0.19)",
} }
] ]

View file

@ -4,12 +4,13 @@ import user from './modules/userModule';
import socketModule from './modules/socketIOModule'; import socketModule from './modules/socketIOModule';
import channelModule from './modules/channelModule'; import channelModule from './modules/channelModule';
import messageModule from './modules/messageModule'; import messageModule from './modules/messageModule';
import notificationsModule from './modules/notificationsModule';
import {router} from './../router' import {router} from './../router'
Vue.use(Vuex); Vue.use(Vuex);
export const store = new Vuex.Store({ export const store = new Vuex.Store({
modules: { user, socketModule, channelModule, messageModule }, modules: { user, channelModule, messageModule, notificationsModule, socketModule },
state: { state: {
}, },
@ -17,12 +18,7 @@ export const store = new Vuex.Store({
}, },
mutations: { mutations: {
sendMessage(state, message) {
if (state.messageLogs[state.channelID] === undefined) {
state.messageLogs[state.channelID] = {};
}
state.messageLogs[state.channelID][Date.now().toString()] = {channelID: state.channelID, message: message, messageID: Date.now(), status: 0};
}
}, },
actions: { actions: {

View file

@ -1,5 +1,6 @@
import {bus} from '../../main' import {bus} from '../../main'
import {router} from './../../router' import {router} from './../../router'
import Vue from 'vue';
const state = { const state = {
selectedChannelID: null, selectedChannelID: null,
@ -26,19 +27,28 @@ const actions = {
selectedChannelID(context, channelID) { selectedChannelID(context, channelID) {
context.commit('selectedChannelID', channelID) context.commit('selectedChannelID', channelID)
}, },
setName(context, name) { setChannelName(context, name) {
context.commit('setName', name) context.commit('setChannelName', name)
},
updateChannelLastMessage(context, channelID) {
context.commit('updateChannelLastMessage', channelID)
} }
} }
const mutations = { const mutations = {
updateChannelLastMessage(state, channelID){
Vue.set(state.channels[channelID], 'lastMessaged', Date.now());
},
addAllChannels(state, channels){
Vue.set(state, 'channels', channels);
},
channel(state, channel) { channel(state, channel) {
state.channels[channel.channelID] = channel; Vue.set(state.channels, channel.channelID, channel);
}, },
selectedChannelID(state, channelID) { selectedChannelID(state, channelID) {
state.selectedChannelID = channelID; state.selectedChannelID = channelID;
}, },
setName(state, name) { setChannelName(state, name) {
state.channelName = name; state.channelName = name;
} }
} }

View file

@ -18,10 +18,13 @@ const actions = {
context.commit('messages', data) context.commit('messages', data)
}, },
addMessage(context, data) { addMessage(context, data) {
// if the message is sent by this client, add additional information.
if (data.sender) { if (data.sender) {
data.message.creator = context.getters.user data.message.creator = context.getters.user
data.message.status = 0; data.message.status = 0;
} }
// send notification if message is not ours
context.commit('addMessage', data); context.commit('addMessage', data);
}, },
replaceMessage(context, data) { replaceMessage(context, data) {
@ -32,12 +35,9 @@ const actions = {
const mutations = { const mutations = {
messages(state, data) { messages(state, data) {
Vue.set(state.messages, data.channelID, data.messages.reverse()) Vue.set(state.messages, data.channelID, data.messages.reverse())
setTimeout(() => {
bus.$emit('scrollDown', 0);
}, 300);
}, },
addMessage(state, data) { addMessage(state, data) {
bus.$emit('scrollDown', 300); bus.$emit('newMessage', data);
Vue.set( Vue.set(
state.messages[data.channelID], state.messages[data.channelID],
state.messages[data.channelID].length, state.messages[data.channelID].length,

View file

@ -0,0 +1,62 @@
import {bus} from '../../main'
import {router} from './../../router'
import Vue from 'vue';
const state = {
notifications: []
}
const getters = {
notifications(state) {
return state.notifications;
}
}
const actions = {
addAllNotifications(context, notifications) {
context.commit('addAllNotifications', notifications)
},
messageCreatedNotification(context, notification) {
const {guildID, channelID, lastMessageID, sender} = notification;
let find = context.state.notifications.find(item => {
return item.channelID === channelID
})
if (find) {
return context.commit('messageCreatedNotification', {exists: true, notification: {channelID, lastMessageID,sender}});
}
context.commit('messageCreatedNotification', {exists: false, notification: {channelID, lastMessageID, sender, count: 1}});
}
}
const mutations = {
addAllNotifications(state, notifications){
Vue.set(state, 'notifications', notifications);
},
messageCreatedNotification(state, data) {
const {exists, notification} = data;
if (exists) {
for (let i = 0; i < state.notifications.length; i++) {
if (state.notifications[i].channelID === notification.channelID) {
const count = state.notifications[i].count;
Vue.set(state.notifications[i], 'count', count + 1);
Vue.set(state.notifications[i], 'lastMessageID', data.notification.lastMessageID);
Vue.set(state.notifications[i], 'sender', data.notification.sender);
break;
}
}
return;
}
state.notifications.push(notification);
}
}
export default {
namespace: true,
state,
actions,
mutations,
getters
}

View file

@ -14,13 +14,17 @@ const actions = {
} }
}, },
socket_success(context, data) { socket_success(context, data) {
const friendsArray = data.user.friends;
const {message, user, dms, notifications, currentFriendStatus} = data;
const friendsArray = user.friends;
const friendObject = {}; const friendObject = {};
// convert array into object and add online status.
if(friendsArray !== undefined && friendsArray.length >=1) { if(friendsArray !== undefined && friendsArray.length >=1) {
for (let index = 0; index < friendsArray.length; index++) { for (let index = 0; index < friendsArray.length; index++) {
const element = friendsArray[index]; const element = friendsArray[index];
friendObject[element.recipient.uniqueID] = element; friendObject[element.recipient.uniqueID] = element;
for (let currentFriendStatus of data.currentFriendStatus){ for (let currentFriendStatus of currentFriendStatus){
if(currentFriendStatus[0] == element.recipient.uniqueID){ if(currentFriendStatus[0] == element.recipient.uniqueID){
friendObject[element.recipient.uniqueID].recipient.status = currentFriendStatus[1] friendObject[element.recipient.uniqueID].recipient.status = currentFriendStatus[1]
} }
@ -30,6 +34,17 @@ const actions = {
} }
context.commit('user', data.user) context.commit('user', data.user)
// convert dms array to object
const channelsObject = {}
if (dms && dms.length >=1) {
for (let channel of dms) {
channelsObject[channel.channelID] = channel;
}
}
context.commit('addAllChannels', channelsObject)
context.dispatch('addAllNotifications', notifications)
}, },
socket_relationshipAdd(context, friend) { socket_relationshipAdd(context, friend) {
context.commit('addFriend', friend) context.commit('addFriend', friend)
@ -41,11 +56,22 @@ const actions = {
context.commit('removeFriend', uniqueID) context.commit('removeFriend', uniqueID)
}, },
socket_receiveMessage(context, data) { socket_receiveMessage(context, data) {
context.dispatch('addMessage', { if (context.getters.messages[data.message.channelID]) {
message: data.message, context.dispatch('updateChannelLastMessage', data.message.channelID);
context.dispatch('addMessage', {
message: data.message,
channelID: data.message.channelID,
tempID: data.tempID
})
}
// send notification if other users message the recipient
if (data.message.creator.uniqueID === context.getters.user.uniqueID) return;
const notification = {
channelID: data.message.channelID, channelID: data.message.channelID,
tempID: data.tempID lastMessageID: data.message.messageID,
}) sender: data.message.creator
}
context.dispatch('messageCreatedNotification', notification);
}, },
socket_userStatusChange(context, data) { socket_userStatusChange(context, data) {
context.commit('userStatusChange', data) context.commit('userStatusChange', data)
@ -61,6 +87,11 @@ const actions = {
}, },
socket_userAvatarChange(context, data) { socket_userAvatarChange(context, data) {
context.commit('userAvatarChange', data) context.commit('userAvatarChange', data)
},
['socket_channel:created'](context, data){
const {channel} = data;
// rename to 'channel' to setchannel
context.dispatch('channel', channel);
} }
} }

View file

@ -53,6 +53,9 @@ export default {
bus.$on('toggleLeftMenu', () => { bus.$on('toggleLeftMenu', () => {
this.showLeftPanel = !this.showLeftPanel; this.showLeftPanel = !this.showLeftPanel;
}) })
bus.$on('closeLeftMenu', () => {
this.showLeftPanel = false;
})
bus.$on('openSettings', () => { bus.$on('openSettings', () => {
this.showSettings = true; this.showSettings = true;
}) })
@ -98,6 +101,7 @@ export default {
position: absolute; position: absolute;
top: 47px; top: 47px;
height: calc(100% - 47px); height: calc(100% - 47px);
background-color: rgba(39, 39, 39, 0.97);
} }
} }
</style> </style>

View file

@ -85,7 +85,9 @@ export default {
color: #383838; color: #383838;
height: 100%; height: 100%;
} }
button {
font-family: 'Roboto', sans-serif;
}
.spinner{ .spinner{
margin: auto; margin: auto;
padding: 30px; padding: 30px;
@ -163,6 +165,7 @@ export default {
margin-top: 20px; margin-top: 20px;
color: white; color: white;
margin-bottom: 50px; margin-bottom: 50px;
flex-shrink: 0;
} }
.change-title { .change-title {