mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-06-15 12:31:15 +00:00
lots of changes ;o
This commit is contained in:
parent
fb43e6ea0b
commit
3ce91179b2
24 changed files with 553 additions and 100 deletions
|
|
@ -1,4 +1,21 @@
|
|||
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',
|
||||
shortTitle: 'Avatar',
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
<template>
|
||||
<div class="left-panel">
|
||||
<MyMiniInformation />
|
||||
<div class="list">
|
||||
<PendingFriends />
|
||||
<div class="tabs">
|
||||
<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 />
|
||||
<offline-friends />
|
||||
</div>
|
||||
<div class="list" v-else>
|
||||
<recent-friends />
|
||||
</div>
|
||||
<AddFriendPanel/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -17,13 +25,20 @@ import PendingFriends from './relationships/PendingFriends.vue'
|
|||
import AddFriendPanel from './relationships/AddFriendPanel.vue'
|
||||
import OnlineFriends from './relationships/OnlineFriends.vue'
|
||||
import OfflineFriends from './relationships/OfflineFriends.vue'
|
||||
import RecentFriends from './relationships/RecentFriends.vue'
|
||||
export default {
|
||||
components: {
|
||||
MyMiniInformation,
|
||||
PendingFriends,
|
||||
AddFriendPanel,
|
||||
OnlineFriends,
|
||||
OfflineFriends
|
||||
OfflineFriends,
|
||||
RecentFriends
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFriendsTab: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -35,7 +50,8 @@ export default {
|
|||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
}
|
||||
.list{
|
||||
margin: 10px;
|
||||
|
|
@ -43,6 +59,39 @@ export default {
|
|||
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 -------*/
|
||||
/* width */
|
||||
.list::-webkit-scrollbar {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<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="triangle">
|
||||
<div class="triangle-inner"></div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="sending-status">{{statusMessage}}</div>
|
||||
|
|
@ -15,9 +18,13 @@
|
|||
|
||||
<script>
|
||||
import config from '@/config.js'
|
||||
import friendlyDate from '@/date'
|
||||
export default {
|
||||
props: ['message', 'status', 'username', 'avatar'],
|
||||
props: ['message', 'status', 'username', 'avatar', 'date', 'uniqueID'],
|
||||
computed: {
|
||||
getDate() {
|
||||
return friendlyDate(this.$props.date);
|
||||
},
|
||||
userAvatar() {
|
||||
return config.domain + "/avatars/" + this.$props.avatar
|
||||
},
|
||||
|
|
@ -33,7 +40,10 @@ export default {
|
|||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
},
|
||||
user() {
|
||||
return this.$store.getters.user
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -50,6 +60,13 @@ export default {
|
|||
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 {
|
||||
from {
|
||||
transform: translate(0px, 9px);
|
||||
|
|
@ -68,7 +85,7 @@ export default {
|
|||
margin-left: 0;
|
||||
flex-shrink: 0;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
|
@ -104,16 +121,30 @@ export default {
|
|||
margin-right: 0;
|
||||
transition: 1s;
|
||||
}
|
||||
.username {
|
||||
color: rgb(189, 189, 189);
|
||||
font-size: 14px;
|
||||
.user-info {
|
||||
display: flex;
|
||||
}
|
||||
.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 {
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message .sending-status {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export default {
|
|||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@
|
|||
</div>
|
||||
<div class="current-channel"><span v-if="!selectedChannelID">Welcome back!</span><span v-else>{{channelName}}</span></div>
|
||||
</div>
|
||||
<typing-status v-if="typing" :username="whosTyping"/>
|
||||
<div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]">
|
||||
<spinner />
|
||||
</div>
|
||||
<div v-else-if="selectedChannelID" class="message-logs">
|
||||
<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 v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll">
|
||||
<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>
|
||||
<news v-else />
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
|
@ -79,6 +85,8 @@ export default {
|
|||
|
||||
if(this.message == "")return;
|
||||
if (this.message.length > 5000) return;
|
||||
clearInterval(this.postTimerID);
|
||||
this.postTimerID = null;
|
||||
|
||||
const msg = this.message;
|
||||
const tempID = this.generateNum(25);
|
||||
|
|
@ -89,12 +97,13 @@ export default {
|
|||
message: {
|
||||
tempID,
|
||||
message: this.message,
|
||||
channelID: this.selectedChannelID
|
||||
channelID: this.selectedChannelID,
|
||||
created: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
this.message = ""
|
||||
|
||||
this.$store.dispatch('updateChannelLastMessage', this.selectedChannelID);
|
||||
const { ok, error, result } = await messagesService.post(this.selectedChannelID, {
|
||||
message: msg,
|
||||
socketID: this.$socket.id,
|
||||
|
|
@ -136,15 +145,18 @@ export default {
|
|||
this.sendMessage();
|
||||
}
|
||||
},
|
||||
scrollDown(speed){
|
||||
//Scroll to bottom
|
||||
$(".message-logs").stop(true).animate({
|
||||
scrollTop: $(".message-logs")[0].scrollHeight
|
||||
}, speed);
|
||||
invertScroll(event) {
|
||||
if(event.deltaY) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.scrollTop -= parseFloat(getComputedStyle(event.currentTarget).getPropertyValue('font-size')) * (event.deltaY < 0 ? -1 : 1) * 2;
|
||||
}
|
||||
},
|
||||
hideTypingStatus(data) {
|
||||
if(this.user.uniqueID === data.message.creator.uniqueID) return;
|
||||
this.typing = false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
bus.$on('scrollDown', this.scrollDown);
|
||||
this.$options.sockets.typingStatus = (data) => {
|
||||
const {channelID, userID} = data;
|
||||
if (channelID !== this.selectedChannelID) return;
|
||||
|
|
@ -157,12 +169,16 @@ export default {
|
|||
this.typing = false;
|
||||
}, 2500);
|
||||
}
|
||||
bus.$on('newMessage', this.hideTypingStatus)
|
||||
},
|
||||
beforeDestroy() {
|
||||
bus.$off('newMessage', this.hideTypingStatus);
|
||||
delete this.$options.sockets.typingStatus;
|
||||
bus.$off('scrollDown', this.scrollDown)
|
||||
},
|
||||
computed: {
|
||||
user() {
|
||||
return this.$store.getters.user;
|
||||
},
|
||||
channel() {
|
||||
return this.$store.getters.channels[this.selectedChannelID];
|
||||
},
|
||||
|
|
@ -183,6 +199,18 @@ export default {
|
|||
|
||||
<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 {
|
||||
color: red;
|
||||
}
|
||||
|
|
@ -227,6 +255,8 @@ export default {
|
|||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.message-logs, .message-logs .scroll {transform: scale(1,-1);}
|
||||
|
||||
.loading{
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
|
|
@ -241,11 +271,21 @@ export default {
|
|||
color: white;
|
||||
font-size: 12px;
|
||||
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{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.chat-input{
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default {
|
|||
margin-left: 20px;
|
||||
flex-shrink: 0;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.information {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="typing-status">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
|
@ -19,34 +19,20 @@ export default {
|
|||
<style scoped>
|
||||
.typing-status {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.246);
|
||||
padding: 5px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
transition: 0.3s;
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.animation {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
display: flex;
|
||||
}
|
||||
.text {
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export default {
|
|||
transition: .3s;
|
||||
}
|
||||
.slide-enter, .slide-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
margin-bottom: -270px;
|
||||
margin-bottom: -250px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ export default {
|
|||
.add-friend{
|
||||
background: rgba(0, 0, 0, 0.13);
|
||||
flex: 1;
|
||||
height: 250px;
|
||||
height: 230px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
|
|
@ -150,8 +150,8 @@ form {
|
|||
background: rgba(0, 0, 0, 0.274);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<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="status" :style="`background-image: url(${status.statusURL})`" ></div>
|
||||
</div>
|
||||
|
|
@ -7,6 +7,11 @@
|
|||
<div class="username">{{$props.username}}</div>
|
||||
<div class="status-name" :style="`color: ${status.statusColor}`">{{status.statusName}}</div>
|
||||
</div>
|
||||
<div class="notification" v-if="notificationss && notificationss >0">
|
||||
<div class="notification-inner">
|
||||
{{notificationss}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -15,9 +20,10 @@ import channelService from '@/services/channelService';
|
|||
import messagesService from '@/services/messagesService';
|
||||
import config from '@/config.js'
|
||||
import statuses from '@/statuses';
|
||||
import {bus} from '@/main'
|
||||
|
||||
export default {
|
||||
props: ['username', 'tag', 'channelID', 'uniqueID'],
|
||||
props: ['username', 'tag', 'channelID', 'uniqueID', ],
|
||||
methods: {
|
||||
async getMessages() {
|
||||
const {ok, error, result} = await messagesService.get(this.$props.channelID);
|
||||
|
|
@ -29,9 +35,10 @@ export default {
|
|||
}
|
||||
},
|
||||
async openChat() {
|
||||
bus.$emit('closeLeftMenu');
|
||||
this.$store.dispatch('selectedChannelID', this.$props.channelID);
|
||||
this.$store.dispatch('setName', this.$props.username);
|
||||
if (this.$store.getters.channels[this.$props.channelID]) return
|
||||
this.$store.dispatch('setChannelName', this.$props.username);
|
||||
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);
|
||||
if ( ok ) {
|
||||
this.$store.dispatch('channel', result.data.channel);
|
||||
|
|
@ -43,6 +50,14 @@ export default {
|
|||
}
|
||||
},
|
||||
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() {
|
||||
return this.$store.getters.user.friends[this.$props.uniqueID].recipient;
|
||||
},
|
||||
|
|
@ -54,7 +69,8 @@ export default {
|
|||
return {
|
||||
statusName: statuses[parseInt(status)].name,
|
||||
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;
|
||||
background-color: rgba(0, 0, 0, 0.137);
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
padding: 5px;
|
||||
padding-right: 0;
|
||||
display: flex;
|
||||
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 {
|
||||
|
|
@ -78,18 +126,19 @@ export default {
|
|||
}
|
||||
|
||||
.profile-picture{
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background-color: rgba(0, 0, 0, 0.425);
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
margin-left: 2px;
|
||||
margin-right: 5px;
|
||||
border: solid 3px;
|
||||
border: solid 2px;
|
||||
position: relative;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.information{
|
||||
margin: auto;
|
||||
|
|
@ -98,10 +147,25 @@ export default {
|
|||
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 {
|
||||
position: absolute;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background-color: black;
|
||||
border-radius: 50%;
|
||||
background-size: calc(100% + 2px);
|
||||
|
|
@ -114,7 +178,7 @@ export default {
|
|||
|
||||
.friend:hover .status {
|
||||
opacity: 1;
|
||||
bottom: -5px;
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.status-name{
|
||||
|
|
@ -125,7 +189,7 @@ export default {
|
|||
}
|
||||
.friend:hover .status-name {
|
||||
opacity: 0.8;
|
||||
height: 19px;
|
||||
height: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
const result = Object.keys(allFriend).map(function(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 ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
const result = Object.keys(allFriend).map(function(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 ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export default {
|
|||
margin-left: 2px;
|
||||
margin-right: 5px;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.information{
|
||||
|
|
|
|||
97
src/components/app/relationships/RecentFriends.vue
Normal file
97
src/components/app/relationships/RecentFriends.vue
Normal 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>
|
||||
|
||||
|
||||
|
|
@ -21,6 +21,9 @@ export default {
|
|||
display: flex;
|
||||
color: white;
|
||||
}
|
||||
.tab-name {
|
||||
padding-top: 3px;
|
||||
}
|
||||
.material-icons{
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default {
|
|||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
width: 180px;
|
||||
z-index: 1;
|
||||
}
|
||||
.status-list {
|
||||
padding: 5px;
|
||||
|
|
|
|||
53
src/date.js
Normal file
53
src/date.js
Normal 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)}`
|
||||
|
||||
}
|
||||
|
|
@ -3,31 +3,37 @@ const config = [
|
|||
{
|
||||
name: "Offline",
|
||||
url: require("@/assets/status/0.svg"),
|
||||
color: "#919191"
|
||||
color: "#919191",
|
||||
bgColor: "rgba(145, 145, 145, 0.19)",
|
||||
|
||||
},
|
||||
//Online
|
||||
{
|
||||
name: "Online",
|
||||
url: require("@/assets/status/1.svg"),
|
||||
color: "#27eb48"
|
||||
color: "#27eb48",
|
||||
bgColor: "rgba(39, 235, 72, 0.19)"
|
||||
},
|
||||
//Away
|
||||
{
|
||||
name: "Away",
|
||||
url: require("@/assets/status/2.svg"),
|
||||
color: "#ffdd1e"
|
||||
color: "#ffdd1e",
|
||||
bgColor: "rgb(255, 221, 30, 0.19)",
|
||||
},
|
||||
//Busy
|
||||
{
|
||||
name: "Busy",
|
||||
url: require("@/assets/status/3.svg"),
|
||||
color: "#ea0b1e"
|
||||
color: "#ea0b1e",
|
||||
bgColor: "rgb(234, 11, 30, 0.19)"
|
||||
},
|
||||
//Looking to play
|
||||
{
|
||||
name: "Looking to play",
|
||||
url: require("@/assets/status/4.svg"),
|
||||
color: "#9a3dd3"
|
||||
color: "#9a3dd3",
|
||||
bgColor: "rgb(154, 61, 211, 0.19)",
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import user from './modules/userModule';
|
|||
import socketModule from './modules/socketIOModule';
|
||||
import channelModule from './modules/channelModule';
|
||||
import messageModule from './modules/messageModule';
|
||||
import notificationsModule from './modules/notificationsModule';
|
||||
import {router} from './../router'
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export const store = new Vuex.Store({
|
||||
modules: { user, socketModule, channelModule, messageModule },
|
||||
modules: { user, channelModule, messageModule, notificationsModule, socketModule },
|
||||
state: {
|
||||
|
||||
},
|
||||
|
|
@ -17,12 +18,7 @@ export const store = new Vuex.Store({
|
|||
|
||||
},
|
||||
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: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {bus} from '../../main'
|
||||
import {router} from './../../router'
|
||||
import Vue from 'vue';
|
||||
|
||||
const state = {
|
||||
selectedChannelID: null,
|
||||
|
|
@ -26,19 +27,28 @@ const actions = {
|
|||
selectedChannelID(context, channelID) {
|
||||
context.commit('selectedChannelID', channelID)
|
||||
},
|
||||
setName(context, name) {
|
||||
context.commit('setName', name)
|
||||
setChannelName(context, name) {
|
||||
context.commit('setChannelName', name)
|
||||
},
|
||||
updateChannelLastMessage(context, channelID) {
|
||||
context.commit('updateChannelLastMessage', channelID)
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
updateChannelLastMessage(state, channelID){
|
||||
Vue.set(state.channels[channelID], 'lastMessaged', Date.now());
|
||||
},
|
||||
addAllChannels(state, channels){
|
||||
Vue.set(state, 'channels', channels);
|
||||
},
|
||||
channel(state, channel) {
|
||||
state.channels[channel.channelID] = channel;
|
||||
Vue.set(state.channels, channel.channelID, channel);
|
||||
},
|
||||
selectedChannelID(state, channelID) {
|
||||
state.selectedChannelID = channelID;
|
||||
},
|
||||
setName(state, name) {
|
||||
setChannelName(state, name) {
|
||||
state.channelName = name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,13 @@ const actions = {
|
|||
context.commit('messages', data)
|
||||
},
|
||||
addMessage(context, data) {
|
||||
// if the message is sent by this client, add additional information.
|
||||
if (data.sender) {
|
||||
data.message.creator = context.getters.user
|
||||
data.message.status = 0;
|
||||
}
|
||||
|
||||
// send notification if message is not ours
|
||||
context.commit('addMessage', data);
|
||||
},
|
||||
replaceMessage(context, data) {
|
||||
|
|
@ -32,17 +35,14 @@ const actions = {
|
|||
const mutations = {
|
||||
messages(state, data) {
|
||||
Vue.set(state.messages, data.channelID, data.messages.reverse())
|
||||
setTimeout(() => {
|
||||
bus.$emit('scrollDown', 0);
|
||||
}, 300);
|
||||
},
|
||||
addMessage(state, data) {
|
||||
bus.$emit('scrollDown', 300);
|
||||
bus.$emit('newMessage', data);
|
||||
Vue.set(
|
||||
state.messages[data.channelID],
|
||||
state.messages[data.channelID].length,
|
||||
data.message
|
||||
);
|
||||
);
|
||||
},
|
||||
|
||||
replaceMessage (state, data) {
|
||||
|
|
|
|||
62
src/store/modules/notificationsModule.js
Normal file
62
src/store/modules/notificationsModule.js
Normal 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
|
||||
}
|
||||
|
|
@ -14,13 +14,17 @@ const actions = {
|
|||
}
|
||||
},
|
||||
socket_success(context, data) {
|
||||
const friendsArray = data.user.friends;
|
||||
|
||||
const {message, user, dms, notifications, currentFriendStatus} = data;
|
||||
const friendsArray = user.friends;
|
||||
const friendObject = {};
|
||||
|
||||
// convert array into object and add online status.
|
||||
if(friendsArray !== undefined && friendsArray.length >=1) {
|
||||
for (let index = 0; index < friendsArray.length; index++) {
|
||||
const element = friendsArray[index];
|
||||
friendObject[element.recipient.uniqueID] = element;
|
||||
for (let currentFriendStatus of data.currentFriendStatus){
|
||||
for (let currentFriendStatus of currentFriendStatus){
|
||||
if(currentFriendStatus[0] == element.recipient.uniqueID){
|
||||
friendObject[element.recipient.uniqueID].recipient.status = currentFriendStatus[1]
|
||||
}
|
||||
|
|
@ -30,6 +34,17 @@ const actions = {
|
|||
}
|
||||
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) {
|
||||
context.commit('addFriend', friend)
|
||||
|
|
@ -41,11 +56,22 @@ const actions = {
|
|||
context.commit('removeFriend', uniqueID)
|
||||
},
|
||||
socket_receiveMessage(context, data) {
|
||||
context.dispatch('addMessage', {
|
||||
message: data.message,
|
||||
if (context.getters.messages[data.message.channelID]) {
|
||||
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,
|
||||
tempID: data.tempID
|
||||
})
|
||||
lastMessageID: data.message.messageID,
|
||||
sender: data.message.creator
|
||||
}
|
||||
context.dispatch('messageCreatedNotification', notification);
|
||||
},
|
||||
socket_userStatusChange(context, data) {
|
||||
context.commit('userStatusChange', data)
|
||||
|
|
@ -61,7 +87,12 @@ const actions = {
|
|||
},
|
||||
socket_userAvatarChange(context, data) {
|
||||
context.commit('userAvatarChange', data)
|
||||
}
|
||||
},
|
||||
['socket_channel:created'](context, data){
|
||||
const {channel} = data;
|
||||
// rename to 'channel' to setchannel
|
||||
context.dispatch('channel', channel);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ export default {
|
|||
bus.$on('toggleLeftMenu', () => {
|
||||
this.showLeftPanel = !this.showLeftPanel;
|
||||
})
|
||||
bus.$on('closeLeftMenu', () => {
|
||||
this.showLeftPanel = false;
|
||||
})
|
||||
bus.$on('openSettings', () => {
|
||||
this.showSettings = true;
|
||||
})
|
||||
|
|
@ -98,6 +101,7 @@ export default {
|
|||
position: absolute;
|
||||
top: 47px;
|
||||
height: calc(100% - 47px);
|
||||
background-color: rgba(39, 39, 39, 0.97);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ export default {
|
|||
color: #383838;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
.spinner{
|
||||
margin: auto;
|
||||
padding: 30px;
|
||||
|
|
@ -163,6 +165,7 @@ export default {
|
|||
margin-top: 20px;
|
||||
color: white;
|
||||
margin-bottom: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.change-title {
|
||||
|
|
|
|||
Loading…
Reference in a new issue