New design, a lot of changes.

This commit is contained in:
supertiger1234 2019-10-29 20:02:56 +00:00
parent 41db687eaa
commit 154d6147a9
51 changed files with 1599 additions and 1731 deletions

5
package-lock.json generated
View file

@ -10296,6 +10296,11 @@
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true "dev": true
}, },
"simple-markdown": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.6.1.tgz",
"integrity": "sha512-02HKXvM9J7pJWf74fuWthcgof5jF81Yndt+XcXtWnEtpp8QaX9dUAJpdAA6KWrB/rSGzrOi0PRAVy9/0bJiIZw=="
},
"simple-swizzle": { "simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",

View file

@ -16,6 +16,7 @@
"markdown-it-chat-formatter": "^0.1.1", "markdown-it-chat-formatter": "^0.1.1",
"match-sorter": "^2.3.0", "match-sorter": "^2.3.0",
"particles.js": "^2.0.0", "particles.js": "^2.0.0",
"simple-markdown": "^0.6.1",
"socket.io": "^2.2.0", "socket.io": "^2.2.0",
"socket.io-client": "^2.2.0", "socket.io-client": "^2.2.0",
"twemoji": "^11.3.0", "twemoji": "^11.3.0",

View file

@ -1,182 +0,0 @@
<template>
<div class="change-log">
<div class="inner">
<div
class="close-button"
@click="close"
>
<i class="material-icons">
close
</i>
</div>
<div class="change-title">
Change Log <div class="changelog-icon">
<i class="material-icons ">update</i>
</div>
</div>
<div class="change-list">
<div
v-for="(change, index) in changelog"
:key="index"
class="change"
>
<div class="date">
{{ change.date }}
</div>
<div class="changes-title">
{{ change.title }}
</div>
<div class="information">
<div v-if="change.new">
<strong>What's new?</strong><br>
<ul>
<li
v-for="(wnew, index) in change.new"
:key="index"
>
{{ wnew }}
</li>
</ul>
</div>
<div v-if="change.fix">
<strong>Issues fixed</strong><br>
<ul>
<li
v-for="(wfix, index) in change.fix"
:key="index"
>
{{ wfix }}
</li>
</ul>
</div>
<div v-if="change.next">
<strong>Up next</strong><br>
<ul>
<li
v-for="(wnext, index) in change.next"
:key="index"
>
{{ wnext }}
</li>
</ul>
</div>
<div v-if="change.msg">
{{ change.msg }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {bus} from './../main.js'
import changelog from '@/utils/changelog.js'
export default {
data() {
return {
changelog
}
},
methods: {
close() {
bus.$emit('closeChangeLog')
}
}
}
</script>
<style scoped>
.change-log{
position: absolute;
background: rgba(0, 0, 0, 0.342);
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
color: white;
}
.inner{
background-color: rgba(0, 0, 0, 0.664);
margin: auto;
width: 600px;
height: 700px;
display: flex;
flex-direction: column;
}
.close-button{
margin: auto;
margin-top: 10px;
margin-right: 10px;
user-select: none;
cursor: default;
color: grey;
transition: .3s;
}
.close-button:hover{
color: white;
}
.close-button .material-icons {
font-size: 30px;
}
.change-title{
color: white;
font-size: 30px;
margin: auto;
margin-top: 10px;
user-select: none;
display: flex;
padding-bottom: 15px;
flex-shrink: 0;
}
.changelog-icon{
margin-top: 2px;
margin-left: 10px
}
.changelog-icon .material-icons {
font-size: 40px;
}
.change-list {
height: 100%;
width: 100%;
overflow: auto;
border-top: 1px solid white;
}
.change{
margin: 5px;
min-height: 100px;
border-bottom: solid 1px white;
padding-bottom: 15px;
}
.change:last-child{
border-bottom: none;
}
.date{
text-align: right;
padding-top: 10px;
padding-right: 10px;
color: grey;
}
.changes-title {
font-size: 25px;
padding-left: 15px;
color: rgba(255, 255, 255, 0.795);
}
.information {
margin: 10px;
padding-left: 20px;
}
@media (max-height: 700px) {
.inner {
height: 100%;
}
}
</style>

View file

@ -54,7 +54,7 @@ export default {
right: 0; right: 0;
left: 0; left: 0;
z-index: 9999999999999999; z-index: 9999999999999999;
height: 39px; height: 25px;
-webkit-app-region: drag; -webkit-app-region: drag;
justify-content: flex-end; justify-content: flex-end;
} }
@ -66,8 +66,7 @@ export default {
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
height: 100%; height: 100%;
width: 60px; width: 30px;
border-radius: 2px;
color: white; color: white;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -82,9 +81,9 @@ export default {
background: rgba(255, 0, 0, 0.595); background: rgba(255, 0, 0, 0.595);
} }
.frame-buttons .minimize:hover { .frame-buttons .minimize:hover {
background: rgba(255, 81, 0, 0.595); background: rgba(46, 46, 46, 0.595);
} }
.frame-buttons .res-max:hover { .frame-buttons .res-max:hover {
background: rgba(0, 162, 255, 0.595); background: rgba(46, 46, 46, 0.595);
} }
</style> </style>

View file

@ -1,88 +1,78 @@
<template> <template>
<div class="left-panel"> <div class="left-panel">
<MyMiniInformation /> <navigation />
<div class="tabs"> <div class="content">
<div :class="{selector: true, right: currentTab === 1}" /> <div class="tabs">
<div <div :class="{selector: true, right: currentTab === 1}" />
class="tab" <div
:class="{notifyAnimation: friendRequestExists}" class="tab"
@click="currentTab = 0" :class="{notifyAnimation: friendRequestExists}"
> @click="currentTab = 0"
Friends >Friends</div>
<div class="tab" :class="{notifyAnimation: DMNotification}" @click="currentTab = 1">Recents</div>
</div> </div>
<div <div v-if="currentTab === 0" class="list">
class="tab" <pending-friends />
:class="{notifyAnimation: DMNotification}" <online-friends />
@click="currentTab = 1" <offline-friends />
>
Recents
</div> </div>
<div v-else class="list">
<recent-friends />
</div>
<MyMiniInformation />
</div> </div>
<div
v-if="currentTab === 0"
class="list"
>
<pending-friends />
<online-friends />
<offline-friends />
</div>
<div
v-else
class="list"
>
<recent-friends />
</div>
<AddFriendPanel />
</div> </div>
</template> </template>
<script> <script>
import MyMiniInformation from "../../components/app/MyMiniInformation.vue";
import MyMiniInformation from '../../components/app/MyMiniInformation.vue' import PendingFriends from "./relationships/PendingFriends.vue";
import PendingFriends from './relationships/PendingFriends.vue' import OnlineFriends from "./relationships/OnlineFriends.vue";
import AddFriendPanel from './relationships/AddFriendPanel.vue' import OfflineFriends from "./relationships/OfflineFriends.vue";
import OnlineFriends from './relationships/OnlineFriends.vue' import RecentFriends from "./relationships/RecentFriends.vue";
import OfflineFriends from './relationships/OfflineFriends.vue' import Navigation from "@/components/app/Navigation";
import RecentFriends from './relationships/RecentFriends.vue'
export default { export default {
components: { components: {
MyMiniInformation, MyMiniInformation,
PendingFriends, PendingFriends,
AddFriendPanel,
OnlineFriends, OnlineFriends,
OfflineFriends, OfflineFriends,
RecentFriends RecentFriends,
Navigation
}, },
data() { data() {
return { return {
currentTab: 0 currentTab: 0
} };
}, },
watch: { watch: {
currentTab(tab) { currentTab(tab) {
localStorage.setItem('friendsListTab', tab) localStorage.setItem("friendsListTab", tab);
} }
}, },
mounted() { mounted() {
const tab = localStorage.getItem('friendsListTab'); const tab = localStorage.getItem("friendsListTab");
if (tab) { if (tab) {
this.currentTab = parseInt(tab) this.currentTab = parseInt(tab);
} }
}, },
computed: { computed: {
DMNotification() { DMNotification() {
const notifications = this.$store.getters.notifications; const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels const channels = this.$store.getters.channels;
const notification = notifications.find(e => { const notification = notifications.find(e => {
return channels[e.channelID] && !channels[e.channelID].server_id && e.channelID !== this.$store.getters.selectedChannelID return (
}) channels[e.channelID] &&
!channels[e.channelID].server_id &&
e.channelID !== this.$store.getters.selectedChannelID
);
});
// unopened dm // unopened dm
if (!notification) { if (!notification) {
return notifications.find(e => { return notifications.find(e => {
return !channels[e.channelID] return !channels[e.channelID];
}) });
} }
return notification; return notification;
}, },
@ -93,106 +83,112 @@ export default {
}); });
return result.find(friend => friend.status === 1); return result.find(friend => friend.status === 1);
} }
} }
} };
</script> </script>
<style scoped> <style scoped>
.left-panel { .left-panel {
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.671); background: rgba(0, 0, 0, 0.6);
width: 300px; width: 300px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
z-index: 1; z-index: 1;
} }
.list{ .content {
margin: 2px; display: flex;
margin-left: 5px; flex-direction: column;
margin-right: 5px; flex-shrink: 0;
flex: 1;
overflow: hidden;
}
.list {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
} }
.tabs{ .tabs {
display: flex; display: flex;
color: white; color: white;
flex-shrink: 0; flex-shrink: 0;
margin-top: 10px;
position: relative; position: relative;
} }
.tab{ .tab {
display: flex;
align-items: center;
align-content: center;
justify-content: center;
flex: 1; flex: 1;
text-align: center; text-align: center;
margin: auto; margin: auto;
flex-shrink: 0; flex-shrink: 0;
user-select: none; user-select: none;
cursor: default; cursor: default;
padding: 10px; height: 50px;
background: rgba(0, 0, 0, 0.171); background: rgba(0, 0, 0, 0.171);
margin-left: 1px; margin-left: 1px;
margin-right: 1px; margin-right: 1px;
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
cursor: pointer; cursor: pointer;
} }
.tab:hover{ .tab:hover {
background: rgba(255, 255, 255, 0.096); background: rgba(255, 255, 255, 0.096);
} }
.selector { .selector {
background: rgba(255, 255, 255, 0.322); background: rgba(255, 255, 255, 0.322);
width: 148px; width: 118px;
height: 39px; height: 50px;
top: 0; top: 0;
left: 1px; left: 1px;
position: absolute; position: absolute;
z-index: -1; z-index: -1;
transition: 0.3s; transition: 0.3s;
border-radius: 5px;
} }
.right{ .right {
transform: translateX(150px); transform: translateX(120px);
} }
/* ------- SCROLL BAR -------*/ /* ------- SCROLL BAR -------*/
/* width */ /* width */
.list::-webkit-scrollbar { .list::-webkit-scrollbar {
width: 3px; width: 3px;
} }
/* Track */ /* Track */
.list::-webkit-scrollbar-track { .list::-webkit-scrollbar-track {
background: #8080806b; background: #8080806b;
} }
/* Handle */ /* Handle */
.list::-webkit-scrollbar-thumb { .list::-webkit-scrollbar-thumb {
background: #f5f5f559; background: #f5f5f559;
} }
/* Handle on hover */ /* Handle on hover */
.list::-webkit-scrollbar-thumb:hover { .list::-webkit-scrollbar-thumb:hover {
background: #f5f5f59e; background: #f5f5f59e;
} }
.notifyAnimation{ .notifyAnimation {
animation: notifyAnime; animation: notifyAnime;
animation-duration: 1s; animation-duration: 1s;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
@keyframes notifyAnime { @keyframes notifyAnime {
0%{ 0% {
background: rgba(121, 3, 3, 0.541); background: rgba(121, 3, 3, 0.541);
} }
40%{ 40% {
background: rgba(255, 0, 0, 0.568); background: rgba(255, 0, 0, 0.568);
} }
60%{ 60% {
background: rgba(255, 0, 0, 0.568); background: rgba(255, 0, 0, 0.568);
} }
100%{ 100% {
background: rgba(121, 3, 3, 0.541); background: rgba(121, 3, 3, 0.541);
} }
} }

View file

@ -71,18 +71,18 @@ export default {
.member { .member {
display: flex; display: flex;
padding: 3px; padding: 3px;
margin: 3px 5px;
align-items: center; align-items: center;
align-content: center; align-content: center;
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
} }
.member:hover { .member:hover {
background: rgba(0, 0, 0, 0.301); background: rgba(0, 0, 0, 0.301);
} }
.information { .information {
margin-left: 5px; margin-left: 5px;
overflow: hidden; overflow: hidden;

View file

@ -82,7 +82,7 @@ export default {
color: white; color: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: rgba(0, 0, 0, 0.671); background: rgba(0, 0, 0, 0.6);
width: 300px; width: 300px;
height: 100%; height: 100%;
} }
@ -105,8 +105,6 @@ export default {
.tab { .tab {
background: rgba(0, 0, 0, 0.308); background: rgba(0, 0, 0, 0.308);
padding: 5px; padding: 5px;
border-radius: 5px;
margin: 5px;
user-select: none; user-select: none;
cursor: default; cursor: default;
color: rgb(200, 200, 200); color: rgb(200, 200, 200);

View file

@ -636,7 +636,7 @@ export default {
.right-panel { .right-panel {
height: 100%; height: 100%;
width: 100%; width: 100%;
background-color: rgba(0, 0, 0, 0.507); background-color: rgba(0, 0, 0, 0.650);
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -2,7 +2,7 @@
<div class="container" @mouseover="hover = true" @mouseleave="hover = false"> <div class="container" @mouseover="hover = true" @mouseleave="hover = false">
<div <div
v-if="!type || type === 0" v-if="!type || type === 0"
:class="{message: true, ownMessage: user.uniqueID === $props.uniqueID, halloween: user.uniqueID === $props.uniqueID, ownMessageLeft: user.uniqueID === $props.uniqueID && (apperance && apperance.own_message_right === true)} " :class="{message: true, ownMessage: user.uniqueID === $props.uniqueID, ownMessageLeft: user.uniqueID === $props.uniqueID && (apperance && apperance.own_message_right === true)} "
> >
<div class="avatar"> <div class="avatar">
<profile-picture <profile-picture
@ -18,80 +18,71 @@
</div> </div>
<div class="content" @dblclick="contentDoubleClickEvent"> <div class="content" @dblclick="contentDoubleClickEvent">
<div class="user-info"> <div class="user-info">
<div class="username halloween-color" <div
class="username"
@click="openUserInformation" @click="openUserInformation"
> >{{ this.$props.username }}</div>
{{ this.$props.username }} <div class="date">{{ getDate }}</div>
</div>
<div class="date">
{{ getDate }}
</div>
</div> </div>
<div class="content-message" <div class="content-message" v-html="formatMessage" />
v-html="formatMessage"
/>
<div class="file-content" <div class="file-content" v-if="getFile">
v-if="getFile"
>
<div class="icon"> <div class="icon">
<i class="material-icons">insert_drive_file</i> <i class="material-icons">insert_drive_file</i>
</div> </div>
<div class="information"> <div class="information">
<div class="info"> {{ getFile.fileName }}</div> <div class="info">{{ getFile.fileName }}</div>
<a <a :href="getFile.url" target="_blank">
:href="getFile.url"
target="_blank"
>
<div class="download-button">Download</div> <div class="download-button">Download</div>
</a> </a>
</div> </div>
</div> </div>
<div class="image-content" ref="image" v-if="getImage"> <div class="image-content" ref="image" v-if="getImage">
<img <img :src="getImage" @click="imageClicked" />
:src="getImage"
@click="imageClicked"
>
</div> </div>
<message-embed-template v-if="embed && Object.keys(embed).length" :embed="embed"/> <message-embed-template v-if="embed && Object.keys(embed).length" :embed="embed" />
</div> </div>
<div class="other-information"> <div class="other-information">
<div class="drop-down-button" ref="drop-down-button" @click="openContextMenu"><i class="material-icons">more_vert</i></div> <div class="drop-down-button" ref="drop-down-button" @click="openContextMenu">
<div class="sending-status" v-if="timeEdited && (status === undefined || status === 1)" :title="`Edited ${getEditedDate}`"><i class="material-icons">edit</i></div> <i class="material-icons">more_vert</i>
<div class="sending-status" v-else-if="status === 0"><i class="material-icons">hourglass_full</i></div> </div>
<div class="sending-status" v-else-if="status === 1"><i class="material-icons">done</i></div> <div
<div class="sending-status" v-else-if="status === 2"><i class="material-icons">close</i> Failed</div> class="sending-status"
v-if="timeEdited && (status === undefined || status === 1)"
:title="`Edited ${getEditedDate}`"
>
<i class="material-icons">edit</i>
</div>
<div class="sending-status" v-else-if="status === 0">
<i class="material-icons">hourglass_full</i>
</div>
<div class="sending-status" v-else-if="status === 1">
<i class="material-icons">done</i>
</div>
<div class="sending-status" v-else-if="status === 2">
<i class="material-icons">close</i> Failed
</div>
</div> </div>
</div> </div>
<div <div
v-if="type && (type === 1 || type === 2 || type === 3 || type === 4)" v-if="type && (type === 1 || type === 2 || type === 3 || type === 4)"
class="presence-message" class="presence-message"
:class="{join: type === 1, leave: type === 2, kick: type === 3, ban: type === 4}" :class="{join: type === 1, leave: type === 2, kick: type === 3, ban: type === 4}"
> >
<span> <div class="presense-contain">
<span class="username" <span>
@click="openUserInformation" <span class="username" @click="openUserInformation">{{ this.$props.username }}</span>
>{{ this.$props.username }}</span> <span v-if="type === 1" class="text">has joined the server!</span>
<span <span v-if="type === 2" class="text">has left the server.</span>
v-if="type === 1" <span v-if="type === 3" class="text">has been kicked.</span>
class="text" <span v-if="type === 4" class="text">has been banned.</span>
>has joined the server!</span> <span class="date">{{ getDate }}</span>
<span </span>
v-if="type === 2" </div>
class="text" <div class="drop-down-button" ref="drop-down-button" @click="openContextMenu">
>has left the server.</span> <i class="material-icons">more_vert</i>
<span </div>
v-if="type === 3"
class="text"
>has been kicked.</span>
<span
v-if="type === 4"
class="text"
>has been banned.</span>
<span class="date">{{ getDate }}</span>
</span>
</div> </div>
</div> </div>
</template> </template>
@ -104,10 +95,10 @@ import messageFormatter from "@/utils/messageFormatter.js";
import config from "@/config.js"; import config from "@/config.js";
import friendlyDate from "@/utils/date"; import friendlyDate from "@/utils/date";
import path from "path"; import path from "path";
import windowProperties from '@/utils/windowProperties'; import windowProperties from "@/utils/windowProperties";
import { mapState } from "vuex"; import { mapState } from "vuex";
import messagesService from '../../services/messagesService'; import messagesService from "../../services/messagesService";
export default { export default {
props: [ props: [
@ -132,20 +123,19 @@ export default {
data() { data() {
return { return {
hover: false hover: false
} };
}, },
methods: { methods: {
openContextMenu(event) { openContextMenu(event) {
const x = event.clientX; const x = event.clientX;
const y = event.clientY; const y = event.clientY;
this.$store.dispatch('setMessageContext', { this.$store.dispatch("setMessageContext", {
x, x,
y, y,
channelID: this.channelID, channelID: this.channelID,
messageID: this.messageID, messageID: this.messageID,
message: this.message, message: this.message,
uniqueID: this.uniqueID uniqueID: this.uniqueID
}); });
}, },
openUserInformation() { openUserInformation() {
@ -157,27 +147,35 @@ export default {
editMessage() { editMessage() {
if (this.uniqueID !== this.user.uniqueID) return; if (this.uniqueID !== this.user.uniqueID) return;
this.dropDownVisable = false; this.dropDownVisable = false;
this.$store.dispatch("setEditMessage", {channelID: this.channelID, messageID: this.messageID, message: this.message}); this.$store.dispatch("setEditMessage", {
channelID: this.channelID,
messageID: this.messageID,
message: this.message
});
}, },
contentDoubleClickEvent(event){ contentDoubleClickEvent(event) {
if (event.target.classList.contains("content") || event.target.closest('.user-info')) this.editMessage(); if (
event.target.classList.contains("content") ||
event.target.closest(".user-info")
)
this.editMessage();
}, },
clamp(num, min, max) { clamp(num, min, max) {
return num <= min ? min : num >= max ? max : num; return num <= min ? min : num >= max ? max : num;
}, },
calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) { calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
let ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight); let ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return { width: srcWidth*ratio, height: srcHeight*ratio }; return { width: srcWidth * ratio, height: srcHeight * ratio };
}, },
imageSize() { imageSize() {
const files = this.$props.files; const files = this.$props.files;
if (!files || files.length === 0 || !files[0].dimensions) if (!files || files.length === 0 || !files[0].dimensions)
return undefined; return undefined;
const messageLog = document.querySelector('.message-logs'); const messageLog = document.querySelector(".message-logs");
const w = messageLog.offsetWidth; const w = messageLog.offsetWidth;
const h = messageLog.offsetHeight; const h = messageLog.offsetHeight;
let minWidth = w / 4; let minWidth = w / 4;
let minHeight = h / 4; let minHeight = h / 4;
if (w <= 800) { if (w <= 800) {
@ -185,34 +183,39 @@ export default {
minHeight = h / 1.7; minHeight = h / 1.7;
} }
const dimensions = this.$props.files[0].dimensions;
const dimensions = this.$props.files[0].dimensions
const srcWidth = dimensions.width; const srcWidth = dimensions.width;
const srcHeight = dimensions.height; const srcHeight = dimensions.height;
const newDimentions = this.calculateAspectRatioFit(srcWidth, srcHeight, minWidth, minHeight); const newDimentions = this.calculateAspectRatioFit(
srcWidth,
srcHeight,
minWidth,
minHeight
);
const imageTag = this.$refs['image']; const imageTag = this.$refs["image"];
imageTag.firstChild.style.width = "100%" imageTag.firstChild.style.width = "100%";
imageTag.firstChild.style.height = "100%" imageTag.firstChild.style.height = "100%";
imageTag.style.width = this.clamp(newDimentions.width, 0, srcWidth) + "px" imageTag.style.width =
imageTag.style.height = this.clamp(newDimentions.height, 0, srcHeight) + "px" this.clamp(newDimentions.width, 0, srcWidth) + "px";
imageTag.style.height =
this.clamp(newDimentions.height, 0, srcHeight) + "px";
}, },
onResize(dimentions) { onResize(dimentions) {
this.imageSize(); this.imageSize();
}, }
}, },
watch: { watch: {
getWindowWidth(dimentions) { getWindowWidth(dimentions) {
this.onResize(dimentions) this.onResize(dimentions);
} }
}, },
mounted() { mounted() {
const files = this.files; const files = this.files;
if (!files || files.length === 0 || !files[0].dimensions) if (!files || files.length === 0 || !files[0].dimensions) return undefined;
return undefined;
this.imageSize(); this.imageSize();
}, },
computed: { computed: {
@ -254,33 +257,40 @@ export default {
return this.$store.getters.user; return this.$store.getters.user;
}, },
getWindowWidth() { getWindowWidth() {
return {width: windowProperties.resizeWidth, height: windowProperties.resizeHeight}; return {
}, width: windowProperties.resizeWidth,
height: windowProperties.resizeHeight
};
}
} }
}; };
</script> </script>
<style scoped> <style scoped lang="scss">
.container { .container {
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
.drop-down-button{ .drop-down-button {
opacity: 0; opacity: 0;
transition: 0.2s; transition: 0.2s;
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
.container:hover .drop-down-button {
.container:hover .drop-down-button{
opacity: 1; opacity: 1;
} }
.presence-message { .presence-message {
margin: 10px; margin: 10px;
display: flex;
color: white;
overflow: hidden;
}
.presense-contain {
padding: 10px; padding: 10px;
display: table; display: table;
color: white; color: white;
@ -289,24 +299,25 @@ export default {
background: rgba(0, 0, 0, 0.356); background: rgba(0, 0, 0, 0.356);
} }
.presence-message .text { .presence-message .text {
margin-left: 5px; margin-left: 5px;
font-size: 15px; font-size: 15px;
} }
.presence-message .username { .presence-message .username {
font-size: 15px; font-size: 15px;
font-weight: bold font-weight: bold;
} }
.presence-message.join { .presence-message.join .text {
color: #29BF12; color: #29bf12;
} }
.presence-message.leave { .presence-message.leave .text {
color: rgb(150, 139, 139); color: rgb(150, 139, 139);
} }
.presence-message.kick { .presence-message.kick .text {
color: #FF9914; color: #ff9914;
} }
.presence-message.ban { .presence-message.ban .text {
color: #d92121; color: #d92121;
} }
@ -318,6 +329,8 @@ export default {
border-left: 8px solid rgba(184, 184, 184, 0.219); border-left: 8px solid rgba(184, 184, 184, 0.219);
border-right: none !important; border-right: none !important;
} }
.ownMessageLeft .avatar { .ownMessageLeft .avatar {
margin-right: 0px; margin-right: 0px;
margin-left: 5px; margin-left: 5px;
@ -338,12 +351,8 @@ export default {
.ownMessage .content { .ownMessage .content {
background: rgba(184, 184, 184, 0.219); background: rgba(184, 184, 184, 0.219);
} }
.ownMessage.halloween .content {
background: rgba(255, 135, 31, 0.43);
}
.ownMessage.halloween .triangle-inner {
background: rgba(255, 135, 31, 0.43);
}
.ownMessage .date { .ownMessage .date {
color: rgb(209, 209, 209); color: rgb(209, 209, 209);
} }
@ -397,11 +406,11 @@ export default {
} }
.triangle-inner { .triangle-inner {
width: 0; width: 0;
height: 0; height: 0;
border-top: 9px solid transparent; border-top: 9px solid transparent;
border-bottom: 0px solid transparent; border-bottom: 0px solid transparent;
border-right: 8px solid rgba(0, 0, 0, 0.301); border-right: 8px solid rgba(0, 0, 0, 0.301);
} }
.content { .content {
@ -433,7 +442,6 @@ export default {
width: 100px; width: 100px;
object-fit: contain; object-fit: contain;
height: 100px; height: 100px;
} }
.image-content:hover img { .image-content:hover img {
filter: brightness(70%); filter: brightness(70%);
@ -453,9 +461,6 @@ export default {
color: rgb(199, 199, 199); color: rgb(199, 199, 199);
text-decoration: underline; text-decoration: underline;
} }
.username.halloween-color {
color: orange
}
.date { .date {
color: rgb(177, 177, 177); color: rgb(177, 177, 177);
font-size: 10px; font-size: 10px;
@ -489,8 +494,6 @@ export default {
align-content: center; align-content: center;
} }
.message .other-information div .material-icons { .message .other-information div .material-icons {
font-size: 15px; font-size: 15px;
color: rgb(306, 306, 306); color: rgb(306, 306, 306);
@ -505,14 +508,11 @@ export default {
} }
@media (max-width: 468px) { @media (max-width: 468px) {
} }
</style> </style>
<style> <style>
.code-inline {
.code-inline{
background: rgba(0, 0, 0, 0.322); background: rgba(0, 0, 0, 0.322);
} }
.msg-link { .msg-link {

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="my-mini-information" class="my-mini-information"
:style="{backgroundColor: getStatusColor}"
@mouseover="hover = true" @mouseleave="hover = false" @mouseover="hover = true" @mouseleave="hover = false"
> >
@ -9,7 +9,7 @@
<profile-picture <profile-picture
:url="`${avatar}${hover ? '' : '?type=png'}`" :url="`${avatar}${hover ? '' : '?type=png'}`"
:admin="user.admin" :admin="user.admin"
size="50px" size="35px"
:hover="true" :hover="true"
@click.native="openUserInformation" @click.native="openUserInformation"
/> />
@ -30,7 +30,7 @@
class="current-status" class="current-status"
:src="getStatus" :src="getStatus"
> >
<i class="material-icons expand-status-icon">expand_more</i> <i class="material-icons expand-status-icon">expand_less</i>
<transition name="show-status-list"> <transition name="show-status-list">
<statusList <statusList
v-if="status.isPoppedOut" v-if="status.isPoppedOut"
@ -49,12 +49,6 @@
<i class="material-icons">error</i> <i class="material-icons">error</i>
</div> </div>
</div> </div>
<div
class="setting-icon"
@click="openSettings"
>
<i class="material-icons">settings</i>
</div>
</div> </div>
</template> </template>
@ -123,12 +117,7 @@ export default {
this.$store.dispatch("changeStatus", result.data.set); this.$store.dispatch("changeStatus", result.data.set);
} }
}, },
openSettings() {
this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: true
});
}
} }
}; };
</script> </script>
@ -139,8 +128,9 @@ export default {
.profile-pic-outer{ .profile-pic-outer{
z-index:9999; z-index:9999;
display: flex; display: flex;
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
cursor: pointer;
} }
.survay-button { .survay-button {
padding: 10px; padding: 10px;
@ -160,6 +150,7 @@ export default {
width: 24px; width: 24px;
font-size: 30px; font-size: 30px;
color: cyan; color: cyan;
cursor: pointer;
} }
.show-status-list-enter-active, .show-status-list-enter-active,
@ -169,7 +160,7 @@ export default {
.show-status-list-enter, .show-status-list-enter,
.show-status-list-leave-to { .show-status-list-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-5px); transform: translateY(20px);
} }
.fade-enter-active, .fade-enter-active,
@ -181,19 +172,19 @@ export default {
} }
.my-mini-information { .my-mini-information {
border-radius: 10px; position: relative;
margin: 5px; background: rgba(0, 0, 0, 0.3);;
height: 80px; height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 10px;
transition: 0.3s; transition: 0.3s;
flex-shrink: 0;
} }
.information { .information {
color: white; color: white;
margin-top: -7px; margin-top: -4px;
flex: 1; flex: 1;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
@ -205,9 +196,9 @@ export default {
margin-right: 15px; margin-right: 15px;
padding: 5px; padding: 5px;
border-radius: 50%; border-radius: 50%;
cursor: default;
user-select: none; user-select: none;
transition: 0.3s; transition: 0.3s;
cursor: pointer;
} }
.setting-icon:hover { .setting-icon:hover {
background: rgba(0, 0, 0, 0.294); background: rgba(0, 0, 0, 0.294);
@ -221,10 +212,11 @@ export default {
padding-top: 1px; padding-top: 1px;
padding-left: 5px; padding-left: 5px;
margin-left: 10px; margin-left: 10px;
margin-top: -2px; margin-top: -10px;
transition: 0.3s; transition: 0.3s;
user-select: none; user-select: none;
border-radius: 10px; border-radius: 10px;
cursor: pointer;
} }
.status:hover { .status:hover {
@ -251,14 +243,15 @@ export default {
margin-top: 10px; margin-top: 10px;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 100%; width: 100%;
font-size: 14px;
overflow: hidden; overflow: hidden;
} }
.tag { .tag {
color: rgb(199, 199, 199); color: rgb(199, 199, 199);
font-size: 13px; font-size: 11px;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin-top: 5px; margin-top: 1px;
} }
</style> </style>

View file

@ -0,0 +1,373 @@
<template>
<div class="navigation" ref="navigation">
<div
class="tool-tip"
ref="toolTip"
:style="{top: toolTipTopPosition + 'px'}"
v-if="toolTipShown"
>{{toolTipLocalName || servers[toolTipServerID].name}}</div>
<div class="container" @mouseleave="mouseLeaveEvent">
<div class="scrollable">
<div class="navigation-items">
<div
class="item material-icons"
:class="{selected: currentTab == 0}"
@click="switchTab(0)"
@mouseenter="localToolTipEvent('Explore', $event)"
>explore</div>
<div
class="item material-icons"
:class="{selected: currentTab == 1, notifyAnimation: DMNotification || friendRequestExists}"
@click="switchTab(1)"
@mouseenter="localToolTipEvent('Direct Message', $event)"
>chat</div>
<div
class="item material-icons"
:class="{selected: currentTab == 2, notifyAnimation: serverNotification}"
@click="switchTab(2)"
@mouseenter="localToolTipEvent('Servers', $event)"
>forum</div>
<div
class="item material-icons"
:class="{selected: currentTab == 3}"
@click="switchTab(3)"
@mouseenter="localToolTipEvent('Changelog', $event)"
>list_alt</div>
</div>
<div class="seperater" />
<div class="server-items" v-if="currentTab === 2">
<server-template
v-for="(data, index) in serversArr"
:serverData="data"
@click.native="openServer(data.server_id)"
:key="index.server_id"
/>
</div>
</div>
<div class="seperater" />
<div
class="item material-icons"
v-if="currentTab === 1"
@click="addFriend"
@mouseenter="localToolTipEvent('Add Friend', $event)"
>person_add</div>
<div
class="item material-icons"
v-if="currentTab === 2"
@click="addServer"
@mouseenter="localToolTipEvent('Add Server', $event)"
>add</div>
<div
class="item material-icons"
@click="openSettings"
@mouseenter="localToolTipEvent('Settings', $event)"
>settings</div>
</div>
</div>
</template>
<script>
import { bus } from "@/main.js";
import config from "@/config.js";
import ServerTemplate from "@/components/app/ServerTemplate/ServerTemplate";
export default {
components: { ServerTemplate },
data() {
return {
avatarDomain: config.domain + "/avatars",
toolTipShown: false,
toolTipTopPosition: 0,
toolTipServerID: null,
toolTipLocalName: null
};
},
methods: {
dismissNotification(channelID) {
const notifications = this.$store.getters.notifications.find(function(e) {
return e.channelID === channelID;
});
if (notifications && notifications.count >= 1 && document.hasFocus()) {
this.$socket.emit("notification:dismiss", { channelID });
}
},
openServer(serverID) {
const server = this.servers[serverID];
const lastSelectedChannel = JSON.parse(
localStorage.getItem("selectedChannels") || "{}"
)[serverID];
const defaultChannelID = server.default_channel_id;
const channels = this.$store.getters.channels;
const channel = channels[lastSelectedChannel || defaultChannelID];
this.dismissNotification(channel.channelID);
this.$store.dispatch("servers/setSelectedServerID", serverID);
this.$store.dispatch("openChannel", channel);
},
switchChannel(isServer) {
const serverChannelID = this.$store.state.channelModule.serverChannelID;
const DMChannelID = this.$store.state.channelModule.DMChannelID;
if (isServer) {
this.$store.dispatch("selectedChannelID", serverChannelID);
const channel = this.$store.state.channelModule.channels[
serverChannelID
];
this.$store.dispatch("setChannelName", channel ? channel.name : "");
this.dismissNotification(serverChannelID);
} else {
const channel = this.$store.state.channelModule.channels[DMChannelID];
this.$store.dispatch(
"setChannelName",
channel ? channel.recipients[0].username : ""
);
this.$store.dispatch("selectedChannelID", DMChannelID);
this.dismissNotification(DMChannelID);
}
},
switchTab(index) {
localStorage.setItem("currentTab", index);
this.$store.dispatch("setCurrentTab", index);
if (index == 1) {
//1: direct message tab.
this.switchChannel(false);
} else if (index === 2) {
//2: server tab
this.switchChannel(true);
}
},
openSettings() {
this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: true
});
},
localToolTipEvent(name, event) {
const rect = event.target.getBoundingClientRect();
this.toolTipLocalName = name;
this.toolTipTopPosition = rect.top - this.getTopHeight() + 16;
this.toolTipShown = true;
},
serverToolTipEvent({ serverID, top }) {
this.toolTipLocalName = null;
this.toolTipServerID = serverID;
this.toolTipTopPosition = top - this.getTopHeight() + 16;
this.toolTipShown = true;
},
mouseLeaveEvent() {
this.toolTipShown = false;
this.toolTipServerID = null;
this.toolTipLocalName = null;
},
getTopHeight() {
return window.innerHeight - this.$refs["navigation"].offsetHeight;
},
addServer() {
this.$store.dispatch("setPopoutVisibility", {
name: "addServer",
visibility: true
});
},
addFriend() {
this.$store.dispatch('setAllPopout', {
show: true,
type: "ADD_FRIEND",
})
}
},
computed: {
currentTab() {
return this.$store.getters.currentTab;
},
servers() {
return this.$store.getters["servers/servers"];
},
serversArr() {
const data = this.servers;
return Object.keys(data)
.map(key => {
return data[key];
})
.slice()
.reverse();
},
selectedServerID() {
return this.$store.getters["servers/selectedServerID"];
},
serverNotification() {
const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels;
const notification = notifications.find(e => {
return (
channels[e.channelID] &&
channels[e.channelID].server_id &&
e.channelID !== this.$store.getters.selectedChannelID
);
});
return notification;
},
DMNotification() {
const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels;
const notification = notifications.find(e => {
return (
channels[e.channelID] &&
!channels[e.channelID].server_id &&
e.channelID !== this.$store.getters.selectedChannelID
);
});
// unopened dm
if (!notification) {
return notifications.find(e => {
return !channels[e.channelID];
});
}
return notification;
},
friendRequestExists() {
const allFriend = this.$store.getters.user.friends;
const result = Object.keys(allFriend).map(function(key) {
return allFriend[key];
});
return result.find(friend => friend.status === 1);
}
},
mounted() {
bus.$on("server-tool-tip", this.serverToolTipEvent);
},
destroyed() {
bus.$off("server-tool-tip", this.serverToolTipEvent);
}
};
</script>
<style lang="scss" scoped>
.navigation {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: 60px;
background-color: rgba(0, 0, 0, 0.5);
}
.container {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: 60px;
}
.navigation-items {
display: flex;
flex-direction: column;
width: 100%;
align-self: flex-start;
align-content: center;
flex-shrink: 0;
}
.scrollable {
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
scrollbar-width: none;
height: 100%;
align-self: center;
&::-webkit-scrollbar {
width: 0px;
}
}
.item {
position: relative;
display: flex;
flex-shrink: 0;
justify-content: center;
align-content: center;
align-items: center;
color: white;
font-size: 30px;
align-self: center;
width: 60px;
height: 60px;
cursor: pointer;
user-select: none;
opacity: 0.8;
transition: 0.2s;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
&.selected {
background: rgba(0, 0, 0, 0.4);
opacity: 1;
}
}
.server-items {
display: flex;
flex-direction: column;
}
.seperater {
background-color: #d8d8d8;
flex-shrink: 0;
align-self: center;
width: calc(100% - 10px);
height: 2px;
}
.notifyAnimation:before {
content: "!";
color: white;
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
font-size: 15px;
position: absolute;
z-index: 115651;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
animation: notifyAnime;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
border-radius: 50%;
background: rgba(255, 23, 23, 0.753);
flex-shrink: 0;
}
@keyframes notifyAnime {
0% {
opacity: 0.2;
}
40% {
opacity: 1;
}
60% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
.tool-tip {
color: white;
position: absolute;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(3px);
padding: 5px;
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
left: 60px;
z-index: 99999;
user-select: none;
cursor: default;
transition: 0.2s;
}
</style>

View file

@ -14,6 +14,8 @@
<GenericPopout key="gp" v-if="popouts.genericMessage"/> <GenericPopout key="gp" v-if="popouts.genericMessage"/>
<message-context-menu key="mcm" v-if="popouts.messageContextMenu.messageID"/> <message-context-menu key="mcm" v-if="popouts.messageContextMenu.messageID"/>
<server-member-context key="smc" v-if="popouts.serverMemberContext.uniqueID"/> <server-member-context key="smc" v-if="popouts.serverMemberContext.uniqueID"/>
<server-context key="sc" v-if="popouts.allPopout.type === 'SERVER' && popouts.allPopout.show"/>
<add-friend key="af" v-if="popouts.allPopout.type === 'ADD_FRIEND' && popouts.allPopout.show"/>
</transition-group> </transition-group>
</div> </div>
</template> </template>
@ -26,9 +28,11 @@
// context menus // context menus
const messageContextMenu = () => import('./Popouts/messageContextMenu'); const messageContextMenu = () => import('./Popouts/messageContextMenu');
const ServerMemberContext = () => import('./Popouts/ServerMemberContext'); const ServerMemberContext = () => import('./Popouts/ServerMemberContext');
const ServerContext = () => import('./Popouts/ServerContextMenu.vue');
const AddServer = () => import('./Popouts/AddServer.vue'); const AddServer = () => import('./Popouts/AddServer.vue');
const AddFriend = () => import('./Popouts/AddFriend');
const Settings = () => import('./Popouts/Settings.vue'); const Settings = () => import('./Popouts/Settings.vue');
const TakeSurveyPopout = () => import('./Popouts/TakeSurveyPopout.vue'); const TakeSurveyPopout = () => import('./Popouts/TakeSurveyPopout.vue');
const uploadDialog = () => import('./Popouts/uploadDialog.vue'); const uploadDialog = () => import('./Popouts/uploadDialog.vue');
@ -55,7 +59,10 @@ export default {
ServerSettings, ServerSettings,
GenericPopout, GenericPopout,
messageContextMenu, messageContextMenu,
ServerMemberContext ServerMemberContext,
ServerContext,
AddFriend
}, },
data() { data() {
return { return {

View file

@ -0,0 +1,174 @@
<template>
<div class="dark-background" @mousedown="backgroundClick">
<div class="add-friend">
<div class="header">
<div class="icon material-icons">person_add</div>
<div class="name">Add Friend</div>
</div>
<div class="content">
<div class="container">
<div class="description">Add friends by using their username and tag.</div>
<input class="tag" v-model="input" type="text" placeholder="pancake@time" />
<input class="button" @click="addButton" type="button" :value="requestSent ? 'Adding...' :'Add Friend'" />
</div>
</div>
<div class="alerts" :class="{success, error}" v-if="error || success">{{error || success || ""}}</div>
</div>
</div>
</template>
<script>
import RelationshipService from '@/services/RelationshipService.js'
export default {
data() {
return {
input: "",
requestSent: false,
error: null,
success: null,
};
},
computed: {},
methods: {
closeMenu() {
this.$store.dispatch("setAllPopout", {
show: false,
type: "ADD_FRIEND"
});
},
backgroundClick(e) {
if (e.target.classList.contains("dark-background")) {
this.closeMenu();
}
},
async addButton(e) {
if (this.requestSent) return;
this.error = null;
this.success = null;
this.requestSent = true;
const split = this.input.trim().split("@");
// validation
if ( split.length <2 || split.length >2 || split[1] === "" || split[1].length !== 4){
this.requestSent = false;
this.error = "Invalid username or tag.";
return;
}
const username = split[0];
const tag = split[1];
const {ok, error, result} = await RelationshipService.post({username, tag})
this.requestSent = false;
if ( ok ) {
this.success = result.data.message
} else {
if (error.response === undefined) {
this.error = "Can't connect to server.";
return
}
this.error = error.response.data.errors[0].msg;
}
}
}
};
</script>
<style scoped lang="scss">
.dark-background {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.541);
z-index: 111111;
display: flex;
}
.add-friend {
height: 300px;
width: 400px;
background: rgba(24, 24, 24, 0.9);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
position: relative;
margin: auto;
overflow: hidden;
}
.header {
display: flex;
flex-shrink: 0;
background-color: rgba(41, 41, 41, 0.801);
color: white;
height: 50px;
align-content: center;
align-items: center;
justify-content: center;
.icon {
margin-right: 5px;
}
}
.content {
display: flex;
position: relative;
flex-direction: column;
height: 100%;
width: 100%;
color: rgb(255, 255, 255);
}
.container {
display: flex;
flex-direction: column;
margin: auto;
align-items: center;
align-content: center;
margin-top: 40px;
}
.tag {
color: white;
background: transparent;
border: solid 1px #afafaf;
}
.description {
margin-bottom: 30px;
}
.button {
align-self: center;
color: white;
width: initial;
background: #1998ff;
cursor: pointer;
&:hover {
background: #157fd6;
}
}
.alerts {
display: flex;
flex-direction: column;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30px;
color: white;
align-items: center;
align-content: center;
justify-content: center;
&.success {
background: #008a00;
}
&.error {
background: #8a0000;
}
}
</style>

View file

@ -0,0 +1,102 @@
<template>
<div class="drop-down-menu" v-click-outside="outsideClick">
<div class="item" @click="createInvite" >Manage Invites</div>
<div class="item" v-if="checkServerCreator" @click="showSettings" >Server Settings</div>
<div class="item warn" v-if="!checkServerCreator" @click="leaveServer">Leave Server</div>
</div>
</template>
<script>
import messagesService from '@/services/messagesService';
import ServerService from '../../../../services/ServerService';
export default {
data() {
return {
};
},
methods: {
closeMenu() {
this.$store.dispatch('setAllPopout', {
show: false,
type: null,
})
},
outsideClick(event) {
if (event.target.classList.contains('options-button')) return;
this.closeMenu();
},
setPosition() {
const y = this.contextDetails.y;
const x = this.contextDetails.x;
this.$el.style.top = y + "px";
this.$el.style.left = x + "px";
},
showSettings() {
this.closeMenu()
this.$store.dispatch('setServerSettings', {serverID: this.contextDetails.serverID})
},
createInvite(serverID) {
this.closeMenu()
this.$store.dispatch("setPopoutVisibility", {
name: "showServerInviteMenu",
visibility: true
});
},
async leaveServer() {
this.closeMenu()
const {ok, error, result} = await ServerService.leaveServer(this.contextDetails.serverID);
}
},
mounted() {
this.setPosition();
},
watch: {
contextDetails() {
this.setPosition();
}
},
computed: {
contextDetails() {
return this.$store.getters.popouts.allPopout
},
user() {
return this.$store.getters.user;
},
checkServerCreator() {
return this.contextDetails.creatorUniqueID === this.user.uniqueID
}
},
};
</script>
<style lang="scss" scoped>
.drop-down-menu {
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 99999;
user-select: none;
color: white;
}
.item {
padding: 10px;
transition: 0.3s;
font-size: 13px;
cursor: pointer;
&:hover {
background: rgba(46, 46, 46, 0.651);
}
&.warn {
color: rgb(255, 59, 59);
}
}
</style>

View file

@ -39,7 +39,6 @@ import config from "@/config.js";
import { bus } from "@/main"; import { bus } from "@/main";
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import ServerService from "@/services/ServerService"; import ServerService from "@/services/ServerService";
import { mapState } from "vuex";
export default { export default {
components: {Spinner}, components: {Spinner},
@ -84,9 +83,9 @@ export default {
} }
}, },
computed: { computed: {
...mapState({ serverID() {
serverID: state => state.popoutsModule.serverIDContextMenu return this.$store.getters.popouts.allPopout.serverID
}) },
} }
}; };
</script> </script>

View file

@ -106,23 +106,21 @@ export default {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: rgba(31, 31, 31, 0.995); background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 99999; z-index: 99999;
padding: 5px;
border-radius: 5px;
user-select: none; user-select: none;
color: white; color: white;
} }
.item { .item {
padding: 5px; padding: 10px;
margin: 2px;
border-radius: 5px; border-radius: 5px;
transition: 0.3s; transition: 0.3s;
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: rgb(46, 46, 46); background: rgba(46, 46, 46, 0.651);
} }
&.warn { &.warn {
color: rgb(255, 59, 59); color: rgb(255, 59, 59);

View file

@ -2,6 +2,13 @@
<div class="my-profile-panel"> <div class="my-profile-panel">
<div class="switches"> <div class="switches">
<div class="checkbox"
@click="toggleNotificationSounds">
<div class="checkbox-box" :class="{selected: !notificationSettings.disableNotificationSound}" />
<div class="checkbox-name">
Notification Sounds
</div>
</div>
<div class="checkbox" <div class="checkbox"
@click="toggleNotification()"> @click="toggleNotification()">
<div class="checkbox-box" :class="{selected: !notificationSettings.disableDesktopNotification}" /> <div class="checkbox-box" :class="{selected: !notificationSettings.disableDesktopNotification}" />
@ -23,7 +30,8 @@ import SettingsService from '@/services/settingsService.js';
export default { export default {
data() { data() {
return { return {
isElectron: window && window.process && window.process.type isElectron: window && window.process && window.process.type,
}; };
}, },
methods: { methods: {
@ -39,7 +47,7 @@ export default {
const setting = this.notificationSettings['disableDesktopNotification']; const setting = this.notificationSettings['disableDesktopNotification'];
if (setting && setting === true && !this.isElectron) { if (setting && setting === true && !this.isElectron) {
if (Notification.permission === "denied") { if (Notification.permission === "denied") {
alert("Notifications blocked. Please enable them in your browser."); alert("Notifications permission is blocked. Allow notifications from this website in your browsers permission settings.");
} }
Notification.requestPermission().then(function(result) { Notification.requestPermission().then(function(result) {
if (result === 'denied' || result === 'default') return; if (result === 'denied' || result === 'default') return;
@ -49,6 +57,12 @@ export default {
} }
this.toggleSetting('disableDesktopNotification'); this.toggleSetting('disableDesktopNotification');
}, },
toggleNotificationSounds(){
console.log("test")
const setting = this.notificationSettings['disableNotificationSound'];
console.log(!!!setting)
this.$store.dispatch('settingsModule/updateNotification', {disableNotificationSound: !setting})
}
}, },
mounted() { mounted() {
if (!this.isElectron && this.notificationSettings.disableDesktopNotification === undefined) { if (!this.isElectron && this.notificationSettings.disableDesktopNotification === undefined) {
@ -69,18 +83,19 @@ export default {
.switches { .switches {
display: flex; display: flex;
flex-direction: column;
margin: 20px; margin: 20px;
user-select: none; user-select: none;
} }
.checkbox { .checkbox {
display: flex; display: flex;
margin-top: 10px;
} }
.checkbox-box { .checkbox-box {
background: rgba(88, 88, 88, 0.74); background: rgba(88, 88, 88, 0.74);
height: 20px; height: 20px;
width: 20px; width: 20px;
margin: auto;
margin-right: 10px; margin-right: 10px;
transition: 0.3s; transition: 0.3s;
border-radius: 5px; border-radius: 5px;

View file

@ -106,23 +106,20 @@ export default {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: rgba(31, 31, 31, 0.895); background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 99999; z-index: 99999;
padding: 5px;
border-radius: 5px;
user-select: none; user-select: none;
color: white; color: white;
} }
.item { .item {
padding: 5px; padding: 10px;
margin: 2px;
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: rgb(46, 46, 46); background: rgba(46, 46, 46, 0.651);
} }
&.warn { &.warn {
color: rgb(255, 59, 59); color: rgb(255, 59, 59);

View file

@ -105,7 +105,7 @@ export default {
); );
}, },
openChat() { openChat() {
bus.$emit('changeTab', 1) this.$store.dispatch('setCurrentTab', 1)
this.$store.dispatch("openChat", { this.$store.dispatch("openChat", {
uniqueID: this.uniqueID, uniqueID: this.uniqueID,
channelName: this.user.username channelName: this.user.username

View file

@ -1,24 +1,18 @@
<template> <template>
<div class="left-panel"> <div class="left-panel">
<MyMiniInformation /> <navigation />
<div class="actions"> <div class="right">
<div class="action" @click="openAddServer"> <div class="server-banner" v-if="selectedServerID">
<div class="material-icons">add</div> <div class="banner-image"></div>
<div class="text">Add Server</div> <div class="sub-banner" >
<div class="text" :title="servers[selectedServerID].name">{{servers[selectedServerID].name}}</div>
<div class="options-button material-icons" @click="openServerContext">more_vert</div>
</div>
</div> </div>
<div class="action" @click="openExploreTab"> <div class="channels-list">
<div class="material-icons">explore</div> <channels-list v-if="selectedServerID" :server-i-d="selectedServerID" />
<div class="text">Explore</div>
</div> </div>
</div> <MyMiniInformation />
<div class="list">
<server
v-for="(data, index) in servers"
:key="index.server_id"
:server-data="data"
:open-channel="selectedServerID && selectedServerID === data.server_id"
@click.native="toggleChannel(data.server_id, $event)"
/>
</div> </div>
</div> </div>
</template> </template>
@ -26,12 +20,16 @@
<script> <script>
import MyMiniInformation from "@/components/app/MyMiniInformation.vue"; import MyMiniInformation from "@/components/app/MyMiniInformation.vue";
import Server from "@/components/app/ServerTemplate/ServerTemplate.vue"; import Server from "@/components/app/ServerTemplate/ServerTemplate.vue";
import {bus} from '@/main' import ChannelsList from "@/components/app/ServerTemplate/ChannelsList.vue";
import Navigation from "@/components/app/Navigation.vue";
import { bus } from "@/main";
export default { export default {
components: { components: {
MyMiniInformation, MyMiniInformation,
Server ChannelsList,
Server,
Navigation
}, },
data() { data() {
return { return {
@ -45,98 +43,147 @@ export default {
visibility: true visibility: true
}); });
}, },
toggleChannel(serverID, event) { clickServer(serverID, event) {
if (!event.target.closest('.small-view') || event.target.closest('.options-context-button') || event.target.closest('.options-context-menu')) return; this.openedServer = serverID;
if (this.openedServer === serverID) { this.$store.dispatch("servers/setSelectedServerID", serverID);
this.openedServer = null;
this.$store.dispatch('servers/setSelectedServerID', null)
}
else{
this.openedServer = serverID;
this.$store.dispatch('servers/setSelectedServerID', serverID)
}
}, },
openExploreTab() { openExploreTab() {
bus.$emit('changeTab', 0) this.$store.dispatch('setCurrentTab', 0)
},
openServerContext(event) {
const rect = event.target.getBoundingClientRect();
if (this.checkServerContextOpened) {
this.$store.dispatch('setAllPopout', {
show: false,
type: null
})
return;
}
this.$store.dispatch('setAllPopout', {
show: true,
type: 'SERVER',
serverID: this.servers[this.selectedServerID].server_id,
creatorUniqueID: this.servers[this.selectedServerID].creator.uniqueID,
x: rect.left - 30,
y: rect.top + 35,
})
} }
}, },
computed: { computed: {
servers() { servers() {
const data = this.$store.getters['servers/servers']; return this.$store.getters["servers/servers"];
return Object.keys(data).map(key => { },
return data[key]; serversArr() {
}).slice().reverse() const data = this.servers;
return Object.keys(data)
.map(key => {
return data[key];
})
.slice()
.reverse();
}, },
selectedServerID() { selectedServerID() {
return this.$store.getters['servers/selectedServerID']; return this.$store.getters["servers/selectedServerID"];
}, },
checkServerContextOpened() {
const contextDetail = this.$store.getters.popouts.allPopout;
return contextDetail.show && contextDetail.type === "SERVER";
}
} }
}; };
</script> </script>
<style scoped lang="scss" > <style scoped lang="scss" >
.left-panel { .left-panel {
background-color: rgba(0, 0, 0, 0.6);
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.671);
width: 300px; width: 300px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
z-index: 1; z-index: 1;
} }
.list {
margin: 2px;
margin-left: 5px;
margin-right: 5px;
flex: 1;
overflow: auto;
user-select: none;
}
/* ------- SCROLL BAR -------*/ .channels-list {
/* width */
.list::-webkit-scrollbar {
width: 3px;
}
/* Track */
.list::-webkit-scrollbar-track {
background: #8080806b;
}
/* Handle */
.list::-webkit-scrollbar-thumb {
background: #f5f5f559;
}
/* Handle on hover */
.list::-webkit-scrollbar-thumb:hover {
background: #f5f5f59e;
}
.actions {
color: white;
display: flex; display: flex;
justify-content: center; flex: 1;
height: 100%;
}
.left {
display: flex;
align-content: center;
align-items: center;
flex-direction: column;
flex-shrink: 0; flex-shrink: 0;
background: rgba(29, 29, 29, 0.37);
width: 70px;
overflow-x: auto;
}
.right {
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
height: 100%;
overflow: hidden;
}
.server-banner {
display: flex;
overflow: hidden;
position: relative;
flex-direction: row;
background-color: rgb(32, 32, 32);
height: 150px;
}
.banner-image {
position: absolute;
background-image: url("../../assets/background.jpg");
background-position: center;
background-size: cover;
height: 100%;
z-index: 2;
width: 100%;
}
.sub-banner {
display: flex;
color: white;
background-color: rgba(0, 0, 0, 0.507);
align-self: flex-end;
height: 35px;
width: 100%;
align-items: center;
padding-left: 10px;
position: relative;
backdrop-filter: blur(15px);
z-index: 2;
user-select: none; user-select: none;
.action { overflow: hidden;
display: flex; cursor: default;
padding: 5px; .text {
margin: 2px; overflow: hidden;
align-items: center; white-space: nowrap;
align-content: center; text-overflow: ellipsis;
cursor: pointer; width: 100%;
color: rgb(223, 223, 223);
border-radius: 5px;
transition: 0.2s;
.material-icons {
color: white;
margin-right: 5px;
}
&:hover {
background: rgba(0, 0, 0, 0.24);
}
} }
} }
.options-button {
display: flex;
align-items: center;
align-content: center;
justify-content: center;
flex-shrink: 0;
height: 35px;
width: 35px;
transition: 0.2s;
cursor: pointer;
user-select: none;
font-size: 20px;
&:hover {
background: rgba(0, 0, 0, 0.322);
}
}
</style> </style>

View file

@ -54,22 +54,26 @@ export default {
.channel { .channel {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 5px;
margin-top: 3px;
margin-bottom: 3px;
padding: 5px; padding: 5px;
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
color: white;
user-select: none;
overflow: hidden;
padding-right: 10px;
padding-left: 10px;
} }
.channel:hover { .channel:hover {
background: rgba(139, 139, 139, 0.288); background: rgba(0, 0, 0, 0.192);
} }
.selected { .channel.selected {
background: rgba(139, 139, 139, 0.288); background: rgba(0, 0, 0, 0.288);
} }
.channel-name { .channel-name {
white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
margin-left: 5px; margin-left: 5px;

View file

@ -65,6 +65,11 @@ export default {
}, },
methods: { methods: {
openChannel(channel) { openChannel(channel) {
// add to local storage
const selectedChannels = JSON.parse(localStorage.getItem('selectedChannels') || '{}')
selectedChannels[this.serverID] = channel.channelID;
localStorage.setItem('selectedChannels', JSON.stringify(selectedChannels));
const notificationExists = this.$store.getters.notifications.find(n => n.channelID === channel.channelID) const notificationExists = this.$store.getters.notifications.find(n => n.channelID === channel.channelID)
if (notificationExists && document.hasFocus()) { if (notificationExists && document.hasFocus()) {
@ -79,11 +84,21 @@ export default {
</script> </script>
<style scoped> <style scoped>
.channel-list { .channels-list {
background: rgba(0, 0, 0, 0.288); height: 100%;
width: 100%;
display: flex; display: flex;
flex: 1;
overflow: hidden;
overflow-y: auto;
flex-direction: column; flex-direction: column;
} }
/* ------- SCROLL BAR -------*/
/* width */
.channels-list::-webkit-scrollbar {
width: 3px;
}
</style> </style>

View file

@ -1,243 +1,126 @@
<template> <template>
<div :class="{server: true, 'add-server': mode === 'ADD_SERVER'}"> <div class="server" :class="{selected: selectedServerID === serverData.server_id, notifyAnimation: notification }" @contextmenu.prevent="contextEvent" @mouseenter="hoverEvent" @mouseover="hover = true" @mouseleave="hover = false">
<div :class="{'small-view': true, notifyAnimation: notification}"> <profile-picture size="45px" :url="`${avatarDomain}/${serverData.avatar}${hover ? '' : '?type=png'}`" />
<profile-picture
v-if="!mode"
size="50px"
:url="`${avatarDomain}/${serverData.avatar}`"
/>
<div
v-if="mode === 'ADD_SERVER'"
class="add-icon"
>
<i class="material-icons">add</i>
</div>
<div class="server-name">
{{ mode === 'ADD_SERVER'? 'Create / Join Server' : serverData.name }}
</div>
<div
v-if="mode !== 'ADD_SERVER'"
ref="contextMenuButton"
class="options-context-button"
@click="showContextMenu = !showContextMenu"
>
<i class="material-icons">more_vert</i>
</div>
<div
v-if="showContextMenu"
v-click-outside="closeContextMenu"
class="options-context-menu"
>
<div
class="menu-button"
@click="createInvite(serverData.server_id)"
>
Manage Invites
</div>
<div
v-if="serverData.creator.uniqueID !== user.uniqueID"
class="menu-button warn"
@click="leaveServer(serverData.server_id)"
>
Leave Server
</div>
<div
v-if="serverData.creator.uniqueID === user.uniqueID"
class="menu-button"
@click="showSettings()"
>
Server Settings
</div>
</div>
</div>
<div ref="container">
<channels-list
v-if="openChannel"
:server-i-d="serverData.server_id"
/>
</div>
</div> </div>
</template> </template>
<script> <script>
import {bus} from "../../../main.js"
import config from "@/config.js"; import config from "@/config.js";
import ChannelsList from "@/components/app/ServerTemplate/ChannelsList.vue";
import ProfilePicture from "@/components/ProfilePictureTemplate.vue"; import ProfilePicture from "@/components/ProfilePictureTemplate.vue";
import ServerService from "@/services/ServerService"; import ServerService from "@/services/ServerService";
import smoothReflow from "vue-smooth-reflow";
export default { export default {
components: { ProfilePicture, ChannelsList }, components: { ProfilePicture },
mixins: [smoothReflow], props: ["serverData"],
props: ["serverData", "openChannel", "mode"],
data() { data() {
return { return {
showContextMenu: false, avatarDomain: config.domain + "/avatars",
showChannels: false, hover: false
avatarDomain: config.domain + "/avatars"
}; };
}, },
computed: { computed: {
user() { user() {
return this.$store.getters.user; return this.$store.getters.user;
}, },
selectedServerID() {
return this.$store.getters["servers/selectedServerID"];
},
notification() { notification() {
const notifications = this.$store.getters.notifications; const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels const channels = this.$store.getters.channels;
const notification = notifications.find(e => { const notification = notifications.find(e => {
return channels[e.channelID] && channels[e.channelID].server_id && this.serverData && channels[e.channelID].server_id === this.serverData.server_id return (
}) channels[e.channelID] &&
channels[e.channelID].server_id &&
this.serverData &&
channels[e.channelID].server_id === this.serverData.server_id
);
});
return notification; return notification;
} }
}, },
mounted() {
this.$smoothReflow({
el: this.$refs.container
});
},
methods: { methods: {
showSettings() { hoverEvent(event) {
this.showContextMenu = false; const rect = event.target.getBoundingClientRect();
this.$store.dispatch('setServerSettings', {serverID: this.serverData.server_id}) //let centerX = targetNode.offsetLeft + targetNode.offsetWidth / 2;
//let centerY = targetNode.offsetTop + targetNode.offsetHeight / 2;
bus.$emit('server-tool-tip', {serverID: this.serverData.server_id, top: rect.top})
}, },
createInvite(serverID) { contextEvent(event) {
this.showContextMenu = false; this.$store.dispatch('setAllPopout', {
this.$store.dispatch("setServerIDContextMenu", serverID); show: true,
this.$store.dispatch("setPopoutVisibility", { type: 'SERVER',
name: "showServerInviteMenu", serverID: this.serverData.server_id,
visibility: true creatorUniqueID: this.serverData.creator.uniqueID,
}); x: event.clientX,
}, y: event.clientY
closeContextMenu(event) { })
if (
event.target.closest(".options-context-button") ===
this.$refs.contextMenuButton
)
return;
this.showContextMenu = false;
},
async leaveServer(serverID) {
this.showContextMenu = false;
const {ok, error, result} = await ServerService.leaveServer(serverID);
} }
} },
}; };
</script> </script>
<style scoped> <style scoped lang="scss">
.server {
position: relative;
z-index: 1;
display: flex;
align-self: center;
width: 60px;
height: 60px;
flex-shrink: 0;
justify-content: center;
align-content: center;
align-items: center;
user-select: none;
transition: 0.2s;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
&.selected {
background: rgba(0, 0, 0, 0.4);
opacity: 1;
}
}
.notifyAnimation:before{ .notifyAnimation:before {
content: ''; content: "!";
color: white;
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: center;
font-size: 15px;
position: absolute; position: absolute;
z-index: -1; z-index: 115651;
top: 0; top: 5px;
left: 0; right: 5px;
right: 0; width: 20px;
bottom: 0; height: 20px;
animation: notifyAnime; animation: notifyAnime;
animation-duration: 1s; animation-duration: 1s;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-fill-mode: forwards; animation-fill-mode: forwards;
border-radius: 5px; border-radius: 50%;
background: rgba(255, 23, 23, 0.753);
} }
@keyframes notifyAnime { @keyframes notifyAnime {
0%{ 0% {
background: rgba(255, 0, 0, 0.198); opacity: 1;
} }
40%{ 40% {
background: rgba(255, 0, 0, 0.411); opacity: 0.9;
} }
60%{ 60% {
background: rgba(255, 0, 0, 0.411); opacity: 1;
} }
100%{ 100% {
background: rgba(255, 0, 0, 0.198); opacity: 0.2;
} }
} }
.server {
color: white;
display: flex;
flex-direction: column;
background-color: rgba(0, 0, 0, 0.137);
border-radius: 5px;
margin: 5px;
transition: 0.3s;
}
.server:hover {
background: rgba(0, 0, 0, 0.288);
}
.material-icons {
transition: 0.3s;
}
.add-server:hover .material-icons {
color: rgba(20, 255, 39, 0.726);
}
.small-view {
padding-right: 0;
display: flex;
transition: 0.3s;
position: relative;
align-items: center;
padding: 5px;
cursor: pointer;
}
.server-name {
overflow: hidden;
text-overflow: ellipsis;
margin-left: 5px;
flex: 1;
white-space: nowrap;
}
.add-icon {
height: 56px;
display: flex;
align-items: center;
margin-right: 5px;
margin-left: 5px;
}
.add-icon .material-icons {
font-size: 40px;
}
.options-context-button {
display: flex;
align-items: center;
margin-right: 5px;
color: rgba(255, 255, 255, 0.623);
border-radius: 50%;
transition: 0.3s;
}
.options-context-button .material-icons:hover {
color: white;
}
.options-context-menu {
position: absolute;
background: rgba(0, 0, 0, 0.692);
border-radius: 10px;
z-index: 9999;
padding: 5px;
right: 40px;
top: 20px;
}
.menu-button {
padding: 5px;
margin: 2px;
border-radius: 5px;
transition: 0.3s;
}
.menu-button:hover {
background: rgb(47, 47, 47);
}
.warn {
color: red;
}
</style> </style>

View file

@ -67,10 +67,10 @@ export default {
@media (max-width: 600px) { @media (max-width: 600px) {
.left-panel { .left-panel {
position: absolute; position: absolute;
background-color: rgba(39, 39, 39, 0.97);
bottom: 0; bottom: 0;
height: calc(100% - 50px); height: calc(100% - 50px);
z-index: 2; z-index: 2;
backdrop-filter: blur(15px);
} }
} }
</style> </style>

View file

@ -1,74 +1,80 @@
<template> <template>
<div class="explore-tab"> <div class="explore-tab">
<transition name="slidein"> <transition name="slidein">
<div class="left-panel" <div
class="left-panel"
v-show="$mq === 'mobile' && showLeftPanel || ($mq !== 'mobile')" v-show="$mq === 'mobile' && showLeftPanel || ($mq !== 'mobile')"
v-click-outside="hideLeftPanel"> v-click-outside="hideLeftPanel"
<div class="header"> >
<div class="icon"> <navigation />
<i class="material-icons">explore</i> <div class="content">
</div> <div class="header">
<div class="details"> <div class="icon">
<div class="title">Explore</div> <i class="material-icons">explore</i>
<div class="description">Find new servers, Emojis and more!</div> </div>
</div> <div class="details">
</div> <div class="title">Explore</div>
<div class="items"> <div class="description">Find new servers, Emojis and more!</div>
<div class="item halloween" </div>
v-for="(tab, index) in tabs"
:key="index"
:class="{selected: selectedTab === index}"
@click="selectedTab = index">
<i class="material-icons">{{tab.icon}}</i>
{{tab.name}}
</div> </div>
<div class="items">
<div
class="item"
v-for="(tab, index) in tabs"
:key="index"
:class="{selected: selectedTab === index}"
@click="selectedTab = index"
>
<i class="material-icons">{{tab.icon}}</i>
{{tab.name}}
</div>
<div class="card self-promo" v-if="nertiviaServerHide !== true && !nertiviaServer">
<div class="material-icons close-btn" @click="hideSelfPromo">close</div>
<div class="logo" />
<div class="title">Join the official Nertivia server</div>
<div class="button" @click="joinNertiviaServer">Join</div>
</div>
<div class="card halloween" v-if="halloween"> <div class="card donate-paypal" v-if="donateHide !== true">
<div class="pumpkin">🎃</div> <div class="material-icons close-btn" @click="hideDonatePaypal">close</div>
<div class="title">Happy Halloween!</div> <div class="material-icons heart">favorite</div>
</div> <div
class="title"
<div class="card self-promo" v-if="nertiviaServerHide !== true && !nertiviaServer"> >Support Nertivia by donating any amount of money. You will get a supporter badge and more features in the future.</div>
<div class="material-icons close-btn" @click="hideSelfPromo">close</div> <div class="button" @click="donateButton">Donate</div>
<div class="logo"/> </div>
<div class="title">Join the official Nertivia server</div>
<div class="button" @click="joinNertiviaServer">Join</div>
</div>
<div class="card donate-paypal" v-if="donateHide !== true">
<div class="material-icons close-btn" @click="hideDonatePaypal">close</div>
<div class="material-icons heart">favorite</div>
<div class="title">Support Nertivia by donating any amount of money. You will get a supporter badge and more features in the future.</div>
<div class="button" @click="donateButton">Donate With PayPal</div>
</div> </div>
</div> </div>
</div> </div>
</transition> </transition>
<div class="right-panel"> <div class="right-panel">
<div class="header"> <div class="material-icons left-menu-show-button" @click="showLeftPanel = !showLeftPanel">view_list</div> <i class="material-icons">{{tabs[selectedTab].icon}}</i>{{tabs[selectedTab].name}}</div> <div class="header">
<div
class="material-icons left-menu-show-button"
@click="showLeftPanel = !showLeftPanel"
>view_list</div>
<i class="material-icons">{{tabs[selectedTab].icon}}</i>
{{tabs[selectedTab].name}}
</div>
<div class="coming-soon" v-if="selectedTab > 0"> <div class="coming-soon" v-if="selectedTab > 0">
<div class="icon"> <div class="icon">
<i class="material-icons">explore</i> <i class="material-icons">explore</i>
</div> </div>
<div class="text">Coming soon!</div> <div class="text">Coming soon!</div>
</div> </div>
<component :is="tabs[selectedTab].component" /> <component :is="tabs[selectedTab].component" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { bus } from "@/main"; import { bus } from "@/main";
import Servers from './Explore/servers' import Servers from "./Explore/servers";
import ServerService from '@/services/ServerService' import ServerService from "@/services/ServerService";
import Navigation from '@/components/app/Navigation';
export default { export default {
components: { Servers }, components: { Servers, Navigation },
data() { data() {
return { return {
showLeftPanel: false, showLeftPanel: false,
@ -80,11 +86,10 @@ export default {
{ icon: "brush", name: "Themes", component: "" }, { icon: "brush", name: "Themes", component: "" },
{ icon: "message", name: "Message Styles", component: "" } { icon: "message", name: "Message Styles", component: "" }
], ],
nertiviaServerID: '6572915451527958528', nertiviaServerID: "6572915451527958528",
nertiviaServerHide: localStorage.getItem('exploreTabNertiviaServerPromoHide') === 'true', nertiviaServerHide:
donateHide: localStorage.getItem('exploreTabDonateHide') === 'true', localStorage.getItem("exploreTabNertiviaServerPromoHide") === "true",
halloween: new Date().getDate() === 31 donateHide: localStorage.getItem("exploreTabDonateHide") === "true",
}; };
}, },
@ -97,27 +102,29 @@ export default {
} }
}, },
hideSelfPromo() { hideSelfPromo() {
localStorage.setItem('exploreTabNertiviaServerPromoHide', true) localStorage.setItem("exploreTabNertiviaServerPromoHide", true);
this.nertiviaServerHide = true; this.nertiviaServerHide = true;
}, },
hideDonatePaypal() { hideDonatePaypal() {
localStorage.setItem('exploreTabDonateHide', true) localStorage.setItem("exploreTabDonateHide", true);
this.donateHide = true; this.donateHide = true;
}, },
async joinNertiviaServer() { async joinNertiviaServer() {
const {ok, error, result} = await ServerService.joinServerById(this.nertiviaServerID, { const { ok, error, result } = await ServerService.joinServerById(
socketID: this.$socket.id this.nertiviaServerID,
}); {
socketID: this.$socket.id
}
);
}, },
donateButton() { donateButton() {
window.open('https://www.patreon.com/nertivia', '_blank'); window.open("https://www.patreon.com/nertivia", "_blank");
} }
}, },
mounted() { mounted() {},
},
computed: { computed: {
nertiviaServer() { nertiviaServer() {
return this.$store.getters['servers/servers'][this.nertiviaServerID] return this.$store.getters["servers/servers"][this.nertiviaServerID];
} }
} }
}; };
@ -125,26 +132,32 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.explore-tab { .explore-tab {
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, 0.459);
color: white; color: white;
position: relative; position: relative;
} }
.left-panel { .left-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
background: rgba(0, 0, 0, 0.274); background: rgba(0, 0, 0, 0.6);
width: 300px; width: 300px;
flex-shrink: 0; flex-shrink: 0;
z-index: 2; z-index: 2;
.content {
display: flex;
flex-direction: column;
height: 100%;
}
.items { .items {
user-select: none; user-select: none;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
&::-webkit-scrollbar {
width: 3px;
}
.item { .item {
.material-icons { .material-icons {
margin-right: 5px; margin-right: 5px;
@ -152,22 +165,16 @@ export default {
display: flex; display: flex;
align-content: center; align-content: center;
align-items: center; align-items: center;
background: rgba(0, 0, 0, 0.199);
margin: 5px;
padding: 10px; padding: 10px;
border-radius: 5px;
cursor: pointer; cursor: pointer;
transition: 0.3s; transition: 0.3s;
&:hover { &:hover {
background: rgba(0, 0, 0, 0.452); background: rgba(0, 0, 0, 0.2);
} }
&.selected { &.selected {
background: rgba(0, 0, 0, 0.452); background: rgba(0, 0, 0, 0.4);
} }
} }
.item.halloween.selected {
background: rgba(255, 166, 0, 0.692);
}
} }
.header { .header {
display: flex; display: flex;
@ -182,19 +189,19 @@ export default {
align-content: center; align-content: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
width: 100px; width: 70px;
position: relative; position: relative;
.material-icons { .material-icons {
font-size: 70px; font-size: 50px;
} }
&::after{ &::after {
content: 'BETA'; content: "BETA";
position: absolute; position: absolute;
background: #ff3333; background: #ff3333;
border-radius: 5px; border-radius: 5px;
font-size: 10px; font-size: 9px;
padding: 2px; padding: 2px;
bottom: 15px; bottom: 20px;
z-index: 999; z-index: 999;
} }
} }
@ -228,28 +235,28 @@ export default {
.title { .title {
text-align: center; text-align: center;
} }
.button { .button {
background-color: rgba(0, 0, 0, 0.200); background-color: rgba(0, 0, 0, 0.2);
border-radius: 5px; border-radius: 5px;
padding: 5px; padding: 5px;
font-size: 20px; font-size: 15px;
margin-top: 15px; margin-top: 15px;
cursor: pointer; cursor: pointer;
transition: 0.3s; transition: 0.3s;
color: rgba(255, 255, 255, 0.924); color: rgba(255, 255, 255, 0.924);
&:hover { &:hover {
background-color: rgba(0, 0, 0, 0.300); background-color: rgba(0, 0, 0, 0.3);
}
} }
}
.close-btn { .close-btn {
position: absolute; position: absolute;
top: 15px; top: 15px;
right: 15px; right: 15px;
cursor: pointer; cursor: pointer;
} }
&.self-promo{ &.self-promo {
.logo { .logo {
background-image: url('../../../assets/logo.png'); background-image: url("../../../assets/logo.png");
background-size: cover; background-size: cover;
height: 100px; height: 100px;
width: 100px; width: 100px;
@ -269,15 +276,6 @@ export default {
font-size: 60px; font-size: 60px;
margin-bottom: 10px; margin-bottom: 10px;
} }
}
&.halloween {
.pumpkin {
font-size: 50px;
margin-bottom: 10px;
}
background: orange;
font-size: 30px;
} }
} }
.coming-soon { .coming-soon {
@ -295,13 +293,16 @@ export default {
} }
.right-panel { .right-panel {
background: rgba(0, 0, 0, 0.65);
.header { .header {
background: rgba(0, 0, 0, 0.448); background: rgba(0, 0, 0, 0.448);
padding: 10px; padding: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
align-content: center; align-content: center;
.material-icons {margin-right: 5px;} .material-icons {
margin-right: 5px;
}
user-select: none; user-select: none;
cursor: default; cursor: default;
} }
@ -312,7 +313,6 @@ export default {
overflow: hidden; overflow: hidden;
} }
.left-menu-show-button { .left-menu-show-button {
border-right: solid 1px rgb(158, 158, 158); border-right: solid 1px rgb(158, 158, 158);
padding-right: 5px; padding-right: 5px;
@ -320,14 +320,13 @@ export default {
cursor: pointer; cursor: pointer;
} }
.slidein-enter-active, .slidein-enter-active,
.slidein-leave-active { .slidein-leave-active {
transition: 0.5s; transition: 0.5s;
} }
.slidein-enter, .slidein-leave-to /* .fade-leave-active below version 2.1.8 */ { .slidein-enter, .slidein-leave-to /* .fade-leave-active below version 2.1.8 */ {
/* margin-left: -300px; */ /* margin-left: -300px; */
transform: translateX(-300px) transform: translateX(-300px);
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@ -336,11 +335,10 @@ export default {
} }
.left-panel { .left-panel {
position: absolute; position: absolute;
background-color: rgba(39, 39, 39, 0.97);
bottom: 0; bottom: 0;
height: calc(100% - 44px); height: calc(100% - 44px);
backdrop-filter: blur(15px);
z-index: 2; z-index: 2;
} }
} }
</style> </style>

View file

@ -5,7 +5,7 @@
<div class="title">Filter:</div> <div class="title">Filter:</div>
<div class="filter-item"> <div class="filter-item">
<div <div
class="item halloween" class="item"
v-for="(filter, index) in filters" v-for="(filter, index) in filters"
:class="{selected: filterSelected === index}" :class="{selected: filterSelected === index}"
:key="filter.name" :key="filter.name"
@ -17,7 +17,7 @@
<div class="title">Sort By:</div> <div class="title">Sort By:</div>
<div class="filter-item"> <div class="filter-item">
<div <div
class="item halloween" class="item"
v-for="(sortBy, index) in sortBys" v-for="(sortBy, index) in sortBys"
:class="{selected: sortSelected === index}" :class="{selected: sortSelected === index}"
:key="sortBy.name" :key="sortBy.name"
@ -26,7 +26,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="search-area"> <div class="search-area" v-if="false">
<input type="text" :placeholder="`Search for ${name}`" /> <input type="text" :placeholder="`Search for ${name}`" />
</div> </div>
</div> </div>
@ -136,9 +136,6 @@ input {
opacity: 0.8; opacity: 0.8;
} }
} }
.item.halloween.selected {
color: orange;
}
} }

View file

@ -1,6 +1,7 @@
<template> <template>
<div class="item"> <div class="item">
<div class="top"> <div class="top">
<div class="background-dark"></div>
<profile-picture <profile-picture
size="90px" size="90px"
:url="`${avatarDomain}/${server.server.avatar}`" :url="`${avatarDomain}/${server.server.avatar}`"
@ -8,7 +9,7 @@
<div class="name"> <div class="name">
<div class="name-container"> <div class="name-container">
<span class="inner-name">{{server.server.name}}</span> <span class="inner-name">{{server.server.name}}</span>
<span class="material-icons halloween-icons" v-if="server.verified">check</span> <span class="material-icons" v-if="server.verified">check</span>
</div> </div>
</div> </div>
</div> </div>
@ -16,7 +17,7 @@
<div class="description">{{server.description}}</div> <div class="description">{{server.description}}</div>
<div class="buttons"> <div class="buttons">
<div class="member-count"><div class="material-icons">account_box</div>{{server.total_members}}</div> <div class="member-count"><div class="material-icons">account_box</div>{{server.total_members}}</div>
<div class="button halloween-button" :class="{selected: joined}" @click="joinButton"> <div class="button" :class="{selected: joined}" @click="joinButton">
<span v-if="joined">Joined</span> <span v-if="joined">Joined</span>
<spinner v-else-if="joinClicked" :size="30"/> <spinner v-else-if="joinClicked" :size="30"/>
<span v-else-if="!joinClicked">Join Server</span> <span v-else-if="!joinClicked">Join Server</span>
@ -70,6 +71,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.item { .item {
position: relative;
width: 250px; width: 250px;
height: 300px; height: 300px;
background: rgba(0, 0, 0, 0.479); background: rgba(0, 0, 0, 0.479);
@ -94,6 +96,18 @@ export default {
align-content: center; align-content: center;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
position: relative;
.background-dark {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
}
background-image: url('../../../../assets/background.jpg');
background-position: center;
background-size: cover;
.avatar { .avatar {
background: rgb(26, 133, 255); background: rgb(26, 133, 255);
height: 80px; height: 80px;
@ -110,6 +124,7 @@ export default {
width: 100%; width: 100%;
text-align: center; text-align: center;
display: flex; display: flex;
z-index: 999;
.name-container { .name-container {
display: flex; display: flex;
margin: auto; margin: auto;
@ -123,18 +138,16 @@ export default {
color: #66e0ff; color: #66e0ff;
margin-left: 5px; margin-left: 5px;
} }
.halloween-icons {
color: orange;
}
} }
} }
.bottom { .bottom {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: rgba(0, 0, 0, 0.194); background: rgba(0, 0, 0, 0.541);
flex: 1; flex: 1;
height: 100%; height: 100%;
flex-shrink: 0; flex-shrink: 0;
.description { .description {
margin: 10px; margin: 10px;
flex: 1; flex: 1;
@ -146,6 +159,7 @@ export default {
overflow-wrap: anywhere; overflow-wrap: anywhere;
flex-shrink: 0; flex-shrink: 0;
} }
.buttons { .buttons {
display: flex; display: flex;
width: 100%; width: 100%;
@ -187,15 +201,7 @@ export default {
background: grey; background: grey;
} }
} }
.halloween-button {
background: rgba(255, 166, 0, 0.8);
&:hover {
background: rgb(255, 166, 0);
}
&.selected {
background: grey;
}
}
} }
} }
</style> </style>

View file

@ -1,62 +1,42 @@
<template> <template>
<div class="news"> <div class="news">
<div class="change-log"> <navigation />
<span class="news-title">Changes in this release</span> <div class="changelog">
<div class="change-log">
<span class="news-title">Changes in this release</span>
<div <div v-for="(change, index) in changelog" :key="index" class="change">
v-for="(change, index) in changelog"
:key="index"
class="change"
>
<div
class="heading"
:style="change.headColor ? `background-color: ${change.headColor}` : ``"
>
<div class="date">
{{ change.date }}
</div>
<div class="changes-title">
{{ change.title }}
</div>
</div>
<div class="information">
<div v-if="change.new">
<strong>What's new?</strong>
<br>
<ul>
<li
v-for="(wnew, index) in change.new"
:key="index"
v-html="wnew"
/>
</ul>
</div>
<div v-if="change.fix">
<strong>Issues fixed</strong>
<br>
<ul>
<li
v-for="(wfix, index) in change.fix"
:key="index"
v-html="wfix"
/>
</ul>
</div>
<div v-if="change.next">
<strong>Up next</strong>
<br>
<ul>
<li
v-for="(wnext, index) in change.next"
:key="index"
v-html="wnext"
/>
</ul>
</div>
<div <div
v-if="change.msg" class="heading"
v-html="change.msg" :style="change.headColor ? `background-color: ${change.headColor}` : ``"
/> >
<div class="date">{{ change.date }}</div>
<div class="changes-title">{{ change.title }}</div>
</div>
<div class="information">
<div v-if="change.new">
<strong>What's new?</strong>
<br />
<ul>
<li v-for="(wnew, index) in change.new" :key="index" v-html="wnew" />
</ul>
</div>
<div v-if="change.fix">
<strong>Issues fixed</strong>
<br />
<ul>
<li v-for="(wfix, index) in change.fix" :key="index" v-html="wfix" />
</ul>
</div>
<div v-if="change.next">
<strong>Up next</strong>
<br />
<ul>
<li v-for="(wnext, index) in change.next" :key="index" v-html="wnext" />
</ul>
</div>
<div v-if="change.msg" v-html="change.msg" />
</div>
</div> </div>
</div> </div>
</div> </div>
@ -65,12 +45,13 @@
<script> <script>
import Spinner from "@/components/Spinner.vue"; import Spinner from "@/components/Spinner.vue";
import Navigation from "@/components/app/Navigation";
import changelog from "@/utils/changelog.js"; import changelog from "@/utils/changelog.js";
export default { export default {
components: {}, components: { Navigation },
data() { data() {
return { return {
changelog: changelog changelog
}; };
} }
}; };
@ -84,16 +65,19 @@ export default {
height: 100%; height: 100%;
color: white; color: white;
overflow: auto; overflow: auto;
background: rgba(0, 0, 0, 0.486); background: #0000005d;
position: relative;
} }
.news-title { .news-title {
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 20px; margin-left: 10px;
margin-top: 20px;
font-size: 21px;
color: white; color: white;
font-weight: bold; font-weight: bold;
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: solid 1px white;
} }
.todo-list { .todo-list {
flex: 1; flex: 1;
@ -102,29 +86,35 @@ export default {
padding: 20px; padding: 20px;
} }
.change { .change {
margin-bottom: 20px; margin-bottom: 10px;
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: solid 1px white;
} }
.heading{ .heading {
padding: 10px; padding: 10px;
background: rgba(0, 0, 0, 0.555); background: rgba(0, 0, 0, 0.555);
margin-bottom: 10px; margin-bottom: 10px;
border-radius: 10px;
} }
.information { .information {
overflow-wrap: break-word; overflow-wrap: break-word;
margin: 10px;
} }
.heading.latest { .heading.latest {
background: rgba(38, 139, 255, 0.87); background: rgba(38, 139, 255, 0.87);
} }
.change-log { .change-log {
background: rgba(0, 0, 0, 0.137); background: rgba(0, 0, 0, 0.561);
padding: 20px;
overflow-y: auto; overflow-y: auto;
max-width: 700px; max-width: 700px;
margin: auto; margin: auto;
} }
.changelog {
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
overflow: auto;
}
.plan-list { .plan-list {
color: white; color: white;
} }
@ -142,5 +132,4 @@ export default {
margin-top: -5px; margin-top: -5px;
margin-bottom: 10px; margin-bottom: 10px;
} }
</style> </style>

View file

@ -103,21 +103,21 @@ export default {
@media (max-width: 949px) { @media (max-width: 949px) {
.members-panel { .members-panel {
position: absolute; position: absolute;
background-color: rgba(39, 39, 39, 0.97);
right: 0; right: 0;
bottom: 0; bottom: 0;
height: calc(100% - 50px); height: calc(100% - 50px);
z-index: 1; z-index: 1;
backdrop-filter: blur(15px);
} }
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.left-panel { .left-panel {
position: absolute; position: absolute;
background-color: rgba(39, 39, 39, 0.97);
bottom: 0; bottom: 0;
height: calc(100% - 50px); height: calc(100% - 50px);
z-index: 2; z-index: 2;
backdrop-filter: blur(15px);
} }
} }
</style> </style>

View file

@ -1,205 +0,0 @@
<template>
<div class="add-friend-panel">
<div
class="panel-title"
@click="expanded = !expanded"
>
<div>
<i
v-if="!expanded"
class="material-icons"
>
person_add
</i>
<span>{{ expanded ? "Hide" : "Add friend" }}</span>
</div>
</div>
<transition
name="slide"
appear
>
<div
v-if="expanded"
class="add-friend"
>
<div class="title">
Add friend
</div>
<div class="info">
Type in your friends username and tag. eg: someone@jt4g
</div>
<div class="infoC">
Creators tag: Fishie@azK0
</div>
<form
action="#"
@submit.prevent="addFriend"
>
<input
v-model="input"
type="text"
placeholder="username@tag"
>
<loadingButton
:loading="currentButtonMessage == 1"
:message="buttonMessages[currentButtonMessage]"
/>
</form>
<div :class="{message: true, warning: errorMessage.isError}">
{{ errorMessage.message }}
</div>
</div>
</transition>
</div>
</template>
<script>
import RelationshipService from '@/services/RelationshipService.js'
import loadingButton from './../../Button.vue'
export default {
components: {
loadingButton
},
data() {
return {
expanded: false,
buttonMessages: [
"Add Friend",
"Adding..."
],
currentButtonMessage: 0,
input: "",
errorMessage:{
message: "",
isError: false
}
}
},
methods: {
async addFriend() {
this.$set(this.errorMessage, 'message', "")
this.currentButtonMessage = 1;
const split = this.input.trim().split("@");
// validation
if ( split.length <2 || split.length >2 || split[1] === "" || split[1].length !== 4){
this.$set(this.errorMessage, 'message', "Invalid username or tag.")
this.$set(this.errorMessage, 'isError', true)
this.currentButtonMessage = 0;
return;
}
const username = split[0];
const tag = split[1];
const {ok, error, result} = await RelationshipService.post({username, tag})
this.currentButtonMessage = 0;
if ( ok ) {
this.$set(this.errorMessage, 'message', result.data.message)
this.$set(this.errorMessage, 'isError', false)
} else {
if (error.response === undefined) {
this.$set(this.errorMessage, 'message', "Can't connect to server.")
this.$set(this.errorMessage, 'isError', true)
return
}
this.$set(this.errorMessage, 'message', error.response.data.errors[0].msg)
this.$set(this.errorMessage, 'isError', true)
}
}
}
}
</script>
<style scoped>
.slide-enter-active, .slide-leave-active {
transition: .3s;
}
.slide-enter, .slide-leave-to /* .fade-leave-active below version 2.1.8 */ {
margin-bottom: -250px;
opacity: 0;
}
.add-friend-panel{
width: 100%;
background: rgba(0, 0, 0, 0.123);
display: flex;
flex-direction: column;
border-radius: 5px;
}
.add-friend{
background: rgba(0, 0, 0, 0);
flex: 1;
height: 230px;
display: flex;
flex-direction: column;
color: white;
padding: 10px;
}
.title{
margin: auto;
margin-top: 10px;
margin-bottom: 0px;
font-size: 20px;
color: white;
user-select: none;
cursor: default;
}
.info{
text-align: center;
color: rgb(182, 182, 182);
font-size: 15px;
user-select: none;
cursor: default;
}
.infoC{
text-align: center;
color: rgb(255, 79, 79);
font-size: 15px;
}
form {
margin: auto;
margin-top: 5px;
margin-bottom: 0px;
}
.message{
margin: auto;
margin-top: 2px;
font-size: 15px;
color: green;
}
.warning {
color: red;
}
.panel-title{
background: rgba(0, 0, 0, 0.274);
width: 100%;
text-align: center;
padding-top: 5px;
padding-bottom: 5px;
color: white;
cursor: pointer;
user-select: none;
display: flex;
transition: 0.3s;
border-radius: 5px;
}
.panel-title:hover{
background: rgba(0, 0, 0, 0.445);
}
.panel-title .material-icons{
vertical-align: top;
margin-top: -2px;
}
.panel-title span{
margin-left: 5px;
}
.panel-title div{
margin: auto;
}
</style>

View file

@ -1,7 +1,8 @@
<template> <template>
<div <div
:class="{friend: true, notifyAnimation: (notifications && notifications > 0) }" class="friend"
:style="{background: status.status !== 0 ? status.bgColor : ''}" :style="{background: status.status !== 0 ? status.bgColor : ''}"
:class="{selected: uniqueIDSelected, notifyAnimation: (notifications && notifications > 0) }"
@click="openChat" @click="openChat"
@mouseover="hover = true" @mouseover="hover = true"
@mouseleave="hover = false" @mouseleave="hover = false"
@ -80,6 +81,9 @@ export default {
statusColor: statuses[parseInt(status)].color, statusColor: statuses[parseInt(status)].color,
bgColor: statuses[parseInt(status)].bgColor bgColor: statuses[parseInt(status)].bgColor
} }
},
uniqueIDSelected() {
return this.$store.getters.selectedUserUniqueID === this.recipient.uniqueID
} }
}, },
methods: { methods: {
@ -108,20 +112,18 @@ export default {
<style scoped> <style scoped>
.username{ .username{
width: 201px; width: 150px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.friend { .friend {
color: white; color: white;
background-color: rgba(0, 0, 0, 0.100);
margin: 5px;
padding: 5px; padding: 5px;
padding-right: 0; padding-left: 10px;
padding-left: 10px;
display: flex; display: flex;
transition: 0.3s; transition: 0.3s;
border-radius: 5px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
@ -151,9 +153,11 @@ export default {
} }
.friend:hover { .friend:hover {
background-color: rgba(0, 0, 0, 0.246); background: rgba(0, 0, 0, 0.3) !important;
}
.friend.selected {
background: rgba(0, 0, 0, 0.4) !important;
} }
.profile-picture{ .profile-picture{
height: 30px; height: 30px;
width: 30px; width: 30px;

View file

@ -63,14 +63,12 @@ export default {
.friends{ .friends{
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none; user-select: none;
padding-bottom: 3px; padding-bottom: 3px;
border-radius: 5px; border-radius: 5px;
transition: 0.3s; transition: 0.3s;
} }
.tab{ .tab{
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
} }
.tab:hover{ .tab:hover{

View file

@ -79,14 +79,11 @@ export default {
.friends{ .friends{
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none; user-select: none;
padding-bottom: 3px; padding-bottom: 3px;
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
} }
.tab{ .tab{
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
} }
.tab:hover{ .tab:hover{

View file

@ -84,14 +84,12 @@ export default {
.recents{ .recents{
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none; user-select: none;
padding-bottom: 3px; padding-bottom: 3px;
border-radius: 5px; border-radius: 5px;
transition: 0.3s; transition: 0.3s;
} }
.tab{ .tab{
border-radius: 5px;
transition: 0.3s; transition: 0.3s;
} }
.tab:hover{ .tab:hover{

View file

@ -20,6 +20,7 @@ export default {
.tab{ .tab{
display: flex; display: flex;
color: white; color: white;
cursor: pointer;
} }
.tab-name { .tab-name {
padding-top: 3px; padding-top: 3px;

View file

@ -67,17 +67,16 @@ export default {
.status-popout{ .status-popout{
position: absolute; position: absolute;
background-color: rgba(26, 26, 26, 0.863); bottom: 55px;
border-radius: 10px; left: 30px;
padding: 5px; background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
width: 180px; width: 180px;
z-index: 4; z-index: 4;
} }
.status-list { .status-list {
padding: 5px; padding: 10px;
transition: 0.3s; transition: 0.3s;
border-radius: 5px;
margin: 5px;
display: flex; display: flex;
align-items: center; align-items: center;
align-content: center; align-content: center;
@ -86,7 +85,7 @@ export default {
} }
.status-list:hover { .status-list:hover {
background: rgba(0, 0, 0, 0.349); background: rgba(46, 46, 46, 0.651);
} }
.status-icon{ .status-icon{

View file

@ -32,16 +32,22 @@ export const store = new Vuex.Store({
members: membersModule members: membersModule
}, },
state: { state: {
currentTab: 0,
}, },
getters: { getters: {
currentTab(state) {
}, return state.currentTab;
mutations: { }
}, },
actions: { actions: {
setCurrentTab({commit}, currentTab) {
} localStorage.setItem("currentTab", currentTab);
commit('SET_CURRENT_TAB', currentTab)
}
},
mutations: {
SET_CURRENT_TAB(state, currentTab) {
state.currentTab = currentTab;
}
},
}) })

View file

@ -4,6 +4,7 @@ import Vue from "vue";
const state = { const state = {
selectedChannelID: null, selectedChannelID: null,
selectedUserUniqueID: null,
DMChannelID: null, DMChannelID: null,
serverChannelID: null, serverChannelID: null,
channelName: null, channelName: null,
@ -14,6 +15,9 @@ const getters = {
selectedChannelID(state) { selectedChannelID(state) {
return state.selectedChannelID; return state.selectedChannelID;
}, },
selectedUserUniqueID(state) {
return state.selectedUserUniqueID;
},
channels(state) { channels(state) {
return state.channels; return state.channels;
}, },
@ -43,6 +47,9 @@ const actions = {
selectedChannelID(context, channelID) { selectedChannelID(context, channelID) {
context.commit("selectedChannelID", channelID); context.commit("selectedChannelID", channelID);
}, },
selectedUserUniqueID(context, uniqueID) {
context.commit("selectedUserUniqueID", uniqueID);
},
setChannelName(context, name) { setChannelName(context, name) {
context.commit("setChannelName", name); context.commit("setChannelName", name);
}, },
@ -74,6 +81,9 @@ const mutations = {
selectedChannelID(state, channelID) { selectedChannelID(state, channelID) {
state.selectedChannelID = channelID; state.selectedChannelID = channelID;
}, },
selectedUserUniqueID(state, uniqueID) {
state.selectedUserUniqueID = uniqueID;
},
setDMChannelID(state, channelID) { setDMChannelID(state, channelID) {
state.DMChannelID = channelID; state.DMChannelID = channelID;
}, },

View file

@ -25,6 +25,7 @@ const getters = {
const actions = { const actions = {
// server channel // server channel
async openChannel(context, channel) { async openChannel(context, channel) {
context.commit("selectedChannelID", channel.channelID)
context.commit("setChannelName", channel.name); context.commit("setChannelName", channel.name);
const messages = context.state.messages[channel.channelID]; const messages = context.state.messages[channel.channelID];
if (messages) { if (messages) {
@ -32,14 +33,12 @@ const actions = {
context.commit("setServerChannelID", channel.channelID); context.commit("setServerChannelID", channel.channelID);
return; return;
} }
context.commit("selectedChannelID", "loading")
getMessages(context, channel.channelID, true); getMessages(context, channel.channelID, true);
}, },
//dm channel //dm channel
async openChat(context, { uniqueID, channelID, channelName }) { async openChat(context, { uniqueID, channelID, channelName }) {
if (channelName) context.commit("setChannelName", channelName); if (channelName) context.commit("setChannelName", channelName);
const channels = context.rootState.channelModule.channels; const channels = context.rootState.channelModule.channels;
channelID = Object.keys(channels).find(_channelID => { channelID = Object.keys(channels).find(_channelID => {
return channels[_channelID].recipients && channels[_channelID].recipients.length && channels[_channelID].recipients[0].uniqueID === uniqueID return channels[_channelID].recipients && channels[_channelID].recipients.length && channels[_channelID].recipients[0].uniqueID === uniqueID
@ -47,6 +46,7 @@ const actions = {
const messages = context.state.messages[channelID]; const messages = context.state.messages[channelID];
const channel = channels[channelID]; const channel = channels[channelID];
context.commit('selectedUserUniqueID', uniqueID);
if (channelID) { if (channelID) {
context.commit("setDMChannelID", channelID); context.commit("setDMChannelID", channelID);
context.commit("selectedChannelID", channelID); context.commit("selectedChannelID", channelID);

View file

@ -42,7 +42,18 @@ const state = {
uniqueID: null, uniqueID: null,
x: null, x: null,
y: null y: null
} },
// TODO: convert all above into one.
allPopout: {
show: false,
type: null,
serverID: null,
uniqueID: null,
creatorUniqueID: null,
x: null,
y: null,
}
} }
@ -54,6 +65,9 @@ const getters = {
} }
const actions = { const actions = {
setAllPopout({commit, state}, data) {
commit('setAllPopout', {...state.allPopout, ...data})
},
setServerSettings({commit}, {serverID, index}){ setServerSettings({commit}, {serverID, index}){
commit('setServerSettings', {serverID, index}) commit('setServerSettings', {serverID, index})
}, },
@ -87,6 +101,9 @@ const actions = {
} }
const mutations = { const mutations = {
setAllPopout(state, data) {
Vue.set(state, 'allPopout', data)
},
setServerMemberContext(state, data) { setServerMemberContext(state, data) {
Vue.set(state, 'serverMemberContext', data); Vue.set(state, 'serverMemberContext', data);
}, },

View file

@ -31,9 +31,9 @@ const actions = {
}, },
updateNotification(context, data) { updateNotification(context, data) {
let notificationSettings = JSON.parse(localStorage.getItem('notificationSettings')) || {}; let notificationSettings = JSON.parse(localStorage.getItem('notificationSettings')) || {};
Object.assign(notificationSettings, data); const assigned = Object.assign({}, notificationSettings, data);
localStorage.setItem("notificationSettings", JSON.stringify(notificationSettings)); localStorage.setItem("notificationSettings", JSON.stringify(assigned));
context.commit('updateNotification', notificationSettings) context.commit('updateNotification', assigned)
}, },
addRecentEmoji(context, shortcode) { addRecentEmoji(context, shortcode) {
const recentEmojis = JSON.parse(localStorage.getItem('recentEmojis')) || []; const recentEmojis = JSON.parse(localStorage.getItem('recentEmojis')) || [];

View file

@ -270,13 +270,18 @@ const actions = {
if (!server.socketID) return; if (!server.socketID) return;
if (this._vm.$socket.id !== server.socketID) return; if (this._vm.$socket.id !== server.socketID) return;
const defaultChannel = channels.find(c => c.channelID === server.default_channel_id) const defaultChannel = channels.find(c => c.channelID === server.default_channel_id)
bus.$emit('changeTab', 2) context.dispatch('setCurrentTab', 2, {root: true})
context.dispatch('servers/setSelectedServerID', server.server_id, {root: true}) context.dispatch('servers/setSelectedServerID', server.server_id, {root: true})
context.dispatch('openChannel', defaultChannel, {root: true}) context.dispatch('openChannel', defaultChannel, {root: true})
}, },
['socket_server:leave'](context, {server_id}) { ['socket_server:leave'](context, {server_id}) {
const lastSelectedChannel = JSON.parse(localStorage.getItem('selectedChannels') || '{}')
if (lastSelectedChannel[server_id]) {
delete lastSelectedChannel[server_id];
localStorage.setItem('selectedChannels', JSON.stringify(lastSelectedChannel));
}
// check if server channel selected // check if server channel selected
const serverChannelIDs = context.rootState.servers.channelsIDs[server_id]; const serverChannelIDs = context.rootState.servers.channelsIDs[server_id];

View file

@ -14,13 +14,25 @@
const config = [ const config = [
{
version: 7.8,
title: "Redesigns!",
shortTitle: "",
date: "25/10/2019",
headColor: "rgba(25, 130, 255, 0.77)",
new: [
'Layout has been redesigned.',
'Added an option to mute notification sounds.',
'Last clicked channels should be remembered for each server.',
'You can now delete Join, Left and Ban messages from the chat.',
],
next: ['Custom server banners.'],
},
{ {
version: 7.6, version: 7.6,
title: "👻Spooky bug fixes👻", title: "👻Spooky bug fixes👻",
shortTitle: "", shortTitle: "",
date: "16/10/2019", date: "16/10/2019",
headColor: "rgb(255, 166, 0)", // halloween
//headColor: "rgba(25, 130, 255, 0.77)",
new: [ new: [
'Better handled Google Drive linking.', 'Better handled Google Drive linking.',
'👻👻👻', '👻👻👻',

View file

@ -69,7 +69,7 @@ function replace_custom_emoji(state, silent) {
// console.log(nameEnd, parseUntil(state,nameStart,58)) // console.log(nameEnd, parseUntil(state,nameStart,58))
// parser failed to find another ':', so it's not a valid emoji // parser failed to find another ':', so it's not a valid emoji
if(nameEnd > max || nameEnd < 0 || nameEnd - nameStart <= 0) { return false; } if((nameEnd+1) > max || nameEnd < 0 || nameEnd - nameStart <= 0) { return false; }
let emojiName = state.src.slice(nameStart, nameEnd) let emojiName = state.src.slice(nameStart, nameEnd)
@ -79,7 +79,9 @@ function replace_custom_emoji(state, silent) {
let idStart = pos let idStart = pos
let idEnd = skipUntil(state, pos, 62); let idEnd = skipUntil(state, pos, 62);
if(idEnd > max || idEnd < 0 || idEnd - idStart <= 1) { return false; } if((idEnd+1) > max || idEnd < 0 || idEnd - idStart <= 1) { return false; }
// console.log(idStart, idEnd)
let emojiID = state.src.slice(idStart, idEnd) let emojiID = state.src.slice(idStart, idEnd)
@ -88,7 +90,7 @@ function replace_custom_emoji(state, silent) {
state.posMax = idEnd state.posMax = idEnd
let token = state.push('custom_emoji', 'img', 0); let token = state.push('custom_emoji', 'img', 0);
token.attrs = [[ 'src', `${config.domain}/media/${emojiID}` ], [ 'alt', emojiName ]] token.attrs = [[ 'src', (`${config.domain}/media/${emojiID}`) ], [ 'alt', (emojiName) ]]
} }
state.pos = idEnd + 1 state.pos = idEnd + 1
@ -96,14 +98,17 @@ function replace_custom_emoji(state, silent) {
return true return true
} }
const emojiRe = /<:[\w\d]+:[\w\d]+>/
export default function custom_emoji_plugin(md, opts) { export default function custom_emoji_plugin(md, opts) {
md.renderer.rules.custom_emoji = (tokens, idx) => { md.renderer.rules.custom_emoji = (tokens, idx) => {
let token = tokens[idx] let token = tokens[idx]
const srcIdx = token.attrIndex('src');
const altIdx = token.attrIndex('alt');
// todo: better escaping method, let src = encodeURI(md.utils.escapeHtml(token.attrs[srcIdx][1]))
// even if this is good and covers most cases, there may be edge cases where DOMPurify may be better let alt = md.utils.escapeHtml(token.attrs[altIdx][1])
let src = md.utils.escapeHtml(token.attrs.find(([name]) => name === 'src')[1])
let alt = md.utils.escapeHtml(token.attrs.find(([name]) => name === 'alt')[1])
return `<${md.utils.escapeHtml(token.tag)} class="emoji" title=${alt} alt=${alt} src=${src} />` return `<${md.utils.escapeHtml(token.tag)} class="emoji" title=${alt} alt=${alt} src=${src} />`
} }

View file

@ -0,0 +1,83 @@
function fence(state, startLine, endLine, silent) {
var marker, len, params, nextLine, mem, token, markup,
haveEndMarker = false,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
// if it's indented more than 3 spaces, it should be a code block
// if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
if (pos + 3 > max) { return false; }
marker = state.src.charCodeAt(pos);
if (marker !== 0x60 /* ` */) {
return false;
}
// scan marker length
mem = pos;
pos = state.skipChars(pos, marker);
len = pos - mem;
if (len < 3) { return false; }
markup = state.src.slice(mem, pos);
params = state.src.slice(pos, max);
if (marker === 0x60 /* ` */) {
if (params.indexOf(String.fromCharCode(marker)) >= 0) {
return false;
}
}
// Since start is found, we can report success here in validation mode
if (silent) { return true; }
// search end of block
nextLine = startLine;
for (;;) {
nextLine++;
if (nextLine >= endLine) {
return false
}
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (state.src.charCodeAt(pos) !== marker) { continue; }
pos = state.skipChars(pos, marker);
// closing code fence must be at least as long as the opening one
if (pos - mem < len) { continue; }
// make sure tail has spaces only
pos = state.skipSpaces(pos);
if (pos < max) { continue; }
haveEndMarker = true;
// found!
break;
}
// If a fence has heading spaces, they should be removed from its inner block
len = state.sCount[startLine];
state.line = nextLine + (haveEndMarker ? 1 : 0);
token = state.push('fence', 'code', 0);
token.info = params;
token.content = state.getLines(startLine + 1, nextLine, len, true);
token.markup = markup;
token.map = [ startLine, state.line ];
return true;
}
export default function normalizeFence(md, opts) {
md.block.ruler.at('fence', fence)
}

View file

@ -5,6 +5,7 @@ import config from "@/config.js";
import customEmoji from './markdown-it-plugins/customEmoji' import customEmoji from './markdown-it-plugins/customEmoji'
import formatLink from './markdown-it-plugins/formatLink' import formatLink from './markdown-it-plugins/formatLink'
import formatCode from './markdown-it-plugins/formatCode' import formatCode from './markdown-it-plugins/formatCode'
import normalizeFence from './markdown-it-plugins/normalizeFence'
import hljs from 'highlight.js' import hljs from 'highlight.js'
@ -25,7 +26,9 @@ const markdown = new MarkdownIt({
return '<div class="codeblock"><code>' + markdown.utils.escapeHtml(str) + '</code></div>'; return '<div class="codeblock"><code>' + markdown.utils.escapeHtml(str) + '</code></div>';
} }
}).use(chatPlugin) })
.use(normalizeFence)
.use(chatPlugin)
.use(customEmoji) .use(customEmoji)
.use(formatLink) .use(formatLink)
.use(formatCode); .use(formatCode);

View file

@ -12,12 +12,12 @@ const newFriendAudio = new Audio(newFriendSound);
export default { export default {
notification: () => { notification: () => {
if (isBusy()) return; if (isBusy() || isNotificationDisabled()) return;
const audio = new Audio(notificationSound); const audio = new Audio(notificationSound);
audio.play(); audio.play();
}, },
newFriend: () => { newFriend: () => {
if (isBusy()) return; if (isBusy() || isNotificationDisabled()) return;
const audio = new Audio(newFriendSound); const audio = new Audio(newFriendSound);
audio.play(); audio.play();
} }
@ -25,4 +25,7 @@ export default {
function isBusy(){ function isBusy(){
return store.getters.user.status == 3 return store.getters.user.status == 3
}
function isNotificationDisabled(){
return !!store.getters['settingsModule/settings'].notification.disableNotificationSound;
} }

View file

@ -0,0 +1,29 @@
import SimpleMarkdown from 'simple-markdown';
export default {
// Specify the order in which this rule is to be run
// This rule doesn't conflict with much else, so it should be fine to just put it before the
// general-case text rule:
order: SimpleMarkdown.defaultRules.text.order - 0.5,
// First we check whether a string matches
match: function(source) {
// // || followed by any character repeated [\s\S]+? followed by ||
// // Also see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions for more info on regexes
return /^\<\:([\S]+?)\:([\S]+?)>/.exec(source);
},
// Then parse this string into a syntax node
parse: function(capture, parse, state) {
return {
// capture[1] is the parenthesis group from the regex in `match`:
name: parse(capture[1], state),
id: parse(capture[2], state)
};
},
// Finally transform this syntax node into an html element:
html: function(node, output, state) {
return '<img class="emoji" title="' + output(node.name, state) + '" alt="' + output(node.name, state) + '" src="https://supertiger.tk/api/media/' + output(node.id, state) + '">'
}
}

View file

@ -0,0 +1,29 @@
import SimpleMarkdown from 'simple-markdown';
import linkify from 'linkify-it'
const linkifyInstance = linkify();
export default {
// Specify the order in which this rule is to be run
// This rule doesn't conflict with much else, so it should be fine to just put it before the
// general-case text rule:
order: SimpleMarkdown.defaultRules.link - 0.5,
// First we check whether a string matches
match: function(source) {
return linkifyInstance.match(source)
},
// Then parse this string into a syntax node
parse: function(capture, parse, state) {
console.log(capture)
return {
content: parse(capture[0], state),
};
},
// Finally transform this syntax node into an html element:
html: function(node, output, state) {
return 'owo'
}
}

View file

@ -1,42 +1,14 @@
<template> <template>
<div id="app" ref="app"> <div id="app" ref="app">
<vue-headful :title="title" description="Nertivia Chat Client"/> <vue-headful :title="title" description="Nertivia Chat Client"/>
<div class="background-image halloween-background"></div> <div class="background-image"></div>
<transition name="fade-between-two" appear> <transition name="fade-between-two" appear>
<ConnectingScreen v-if="!loggedIn"/> <ConnectingScreen v-if="!loggedIn"/>
<div class="box" v-if="loggedIn"> <div class="box" v-if="loggedIn">
<div class="frame"> <div class="frame" v-if="isElectron">
<div class="tabs"> <div class="window-buttons">
<electron-frame-buttons />
<div :class="`tab ${currentTab === 0 ? 'selected' : ''}`" @click="switchTab(0)">
<i class="material-icons">explore</i>
Explore
</div>
<div :class="{tab: true, selected: currentTab === 1, notifyAnimation: DMNotification || friendRequestExists}" @click="switchTab(1)">
<i class="material-icons">chat</i>
Direct Message
</div>
<div :class="{tab: true, selected: currentTab === 2, notifyAnimation: serverNotification}" @click="switchTab(2)">
<i class="material-icons">forum</i>
Servers
</div>
<div :class="`tab ${currentTab === 3 ? 'selected' : ''}`" @click="switchTab(3)">
<i class="material-icons">list_alt</i>
Changelog
</div>
<!-- <div :class="`tab ${currentTab === 4 ? 'selected' : ''}`" @click="switchTab(4)">
<i class="material-icons">info</i>
Ad
</div> -->
</div> </div>
<div class="window-buttons" v-if="isElectron">
<electron-frame-buttons />
</div>
</div> </div>
<div class="panel-layout"> <div class="panel-layout">
@ -59,7 +31,6 @@ import changelog from "@/utils/changelog.js";
import ConnectingScreen from "./../components/app/ConnectingScreen.vue"; import ConnectingScreen from "./../components/app/ConnectingScreen.vue";
import Spinner from "./../components/Spinner.vue"; import Spinner from "./../components/Spinner.vue";
const ElectronFrameButtons = () => const ElectronFrameButtons = () =>
import("@/components/ElectronJS/FrameButtons.vue"); import("@/components/ElectronJS/FrameButtons.vue");
@ -87,7 +58,6 @@ const Explore = () => ({
export default { export default {
name: "app", name: "app",
components: { components: {
DirectMessage, DirectMessage,
Servers, Servers,
ConnectingScreen, ConnectingScreen,
@ -98,7 +68,6 @@ export default {
}, },
data() { data() {
return { return {
currentTab: 0,
title: "Nertivia", title: "Nertivia",
isElectron: window && window.process && window.process.type isElectron: window && window.process && window.process.type
}; };
@ -133,7 +102,7 @@ export default {
}, },
switchTab(index) { switchTab(index) {
localStorage.setItem("currentTab", index); localStorage.setItem("currentTab", index);
this.currentTab = index; this.$store.dispatch('setCurrentTab', index)
if (index == 1) { //1: direct message tab. if (index == 1) { //1: direct message tab.
this.switchChannel(false) this.switchChannel(false)
} else if (index === 2) { //2: server tab } else if (index === 2) { //2: server tab
@ -156,25 +125,25 @@ export default {
const currentTab = localStorage.getItem("currentTab"); const currentTab = localStorage.getItem("currentTab");
if(currentTab) { if(currentTab) {
this.currentTab = parseInt(currentTab); this.$store.dispatch('setCurrentTab', parseInt(currentTab))
} }
// check if changelog is updated // check if changelog is updated
const seenVersion = localStorage.getItem("changelog-version-seen"); const seenVersion = localStorage.getItem("changelog-version-seen");
if (seenVersion && seenVersion < changelog[0].version) { if (seenVersion && seenVersion < changelog[0].version) {
localStorage.setItem("currentTab", 3); localStorage.setItem("currentTab", 3);
this.currentTab = 3; this.$store.dispatch('setCurrentTab', 3)
} }
localStorage.setItem("changelog-version-seen", changelog[0].version); localStorage.setItem("changelog-version-seen", changelog[0].version);
bus.$on("title:change", title => { bus.$on("title:change", title => {
this.title = title; this.title = title;
}); });
bus.$on('changeTab', this.switchTab)
},
destroyed() {
bus.$off('changeTab', this.switchTab)
}, },
computed: { computed: {
currentTab() {
return this.$store.getters.currentTab;
},
loggedIn() { loggedIn() {
return this.$store.getters.loggedIn; return this.$store.getters.loggedIn;
}, },
@ -277,6 +246,8 @@ export default {
display: flex; display: flex;
-webkit-app-region: drag; -webkit-app-region: drag;
flex-shrink: 0; flex-shrink: 0;
height: 25px;
background: #1089ff;
} }
.window-buttons { .window-buttons {
@ -287,6 +258,9 @@ export default {
.tabs { .tabs {
display: flex; display: flex;
flex-shrink: 0;
height: 40px;
overflow-y: hidden; overflow-y: hidden;
overflow-x: auto; overflow-x: auto;
max-width: 500px; max-width: 500px;
@ -381,12 +355,6 @@ textarea {
filter: blur(15px); filter: blur(15px);
transform: scale(1.1); transform: scale(1.1);
} }
.halloween-background {
background: url(./../assets/halloween_background.jpg);
filter: blur(10px);
background-position: center;
}
.panel-layout { .panel-layout {
display: flex; display: flex;

View file

@ -1,508 +0,0 @@
<template>
<div id="app">
<vue-headful
title="Nertivia"
description="Nertivia Chat Client"
/>
<div class="background-image" />
<div class="layout">
<div class="small-view-nav-bar">
<div class="small-logo" />
<div class="small-title">
Nertivia
</div>
<div
class="show-menu-button"
@click="showMobileMenu = !showMobileMenu"
>
<i class="material-icons">
menu
</i>
</div>
</div>
<div class="panels">
<div class="left-panel">
<div class="title">
The best chat client that wont restrict you from important and fun features.
</div>
<img
src="../assets/graphics/HomeGraphics.png"
class="graphic-app"
>
<div
class="change-log-mini"
@click="showChangeLog = true"
>
<div class="change-title">
Change log <span style="font-size: 15px; color: rgba(211, 211, 211, 0.774);">Click for details</span>
</div>
<div class="change-list">
<div
v-for="change in changelogFiltered"
:key="change.title"
class="change"
>
<div class="notable-changes">
{{ change.shortTitle }}
</div>
<div class="change-date">
{{ change.date }}
</div>
</div>
</div>
</div>
<div class="twitter-outer">
<twitter class="twitter">
<div slot="loading">
loading .....
</div>
<a
class="twitter-timeline"
data-height="500"
data-theme="dark"
href="https://twitter.com/NertiviaApp?ref_src=twsrc%5Etfw"
>Tweets by NertiviaApp</a>
</twitter>
</div>
</div>
<RightPanel :class="{'show-menu-content': showMobileMenu }" />
</div>
</div>
<transition name="fade">
<ChangeLog v-if="showChangeLog" />
</transition>
</div>
</template>
<script>
import { twitter } from 'vue-twitter'
import {bus} from '../main';
import RightPanel from "./../components/homePage/RightPanel.vue"
import ChangeLog from "./../components/ChangeLog.vue"
import changelog from '@/utils/changelog.js'
export default {
components: {
RightPanel,
ChangeLog,
twitter
},
data() {
return {
loginSelected: true,
showMobileMenu: false,
showChangeLog: false,
changelog
}
},
computed: {
changelogFiltered() {
return this.changelog.slice(0, 3)
}
},
mounted() {
bus.$on('closeChangeLog', () => {
this.showChangeLog = false;
})
}
}
</script>
<style>
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
overflow: hidden;
}
#app {
font-family: "Roboto", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #383838;
height: 100%;
}
</style>
<style scoped>
.twitter-outer{
margin: auto;
width: 600px;
opacity: 0.8;
transition: 0.3s;
}
.twitter-outer:hover{
opacity: 1;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .2s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
#app {
font-family: 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #383838;
height: 100%;
}
button {
font-family: 'Roboto', sans-serif;
}
.spinner{
margin: auto;
padding: 30px;
}
.background-image {
background: url(./../assets/background.jpg);
position: absolute;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-repeat: no-repeat;
background-position: bottom;
background-size: cover;
}
.layout{
display: flex;
height: 100%;
width:100%;
flex-direction: column;
}
.panels{
display: flex;
height: 100%;
width: 100%;
}
.left-panel {
flex: 1;
background: rgba(0, 0, 0, 0.253);
overflow: auto;
display: flex;
flex-direction: column;
}
.loader{
display: flex;
flex-direction: column;
}
.title-panel{
width: 100%;
height: 150px;
}
.graphics-panel{
flex: 1;
}
.graphic-app{
display: table;
margin: auto;
margin-top: 20px;
margin-bottom: 20px;
width: 900px;
height: auto;
user-select: none;
}
.title{
color: white;
font-size: 35px;
text-align: center;
margin-top: 120px;
}
.change-log-mini{
background: rgba(0, 0, 0, 0.322);
height: 150px;
width: 640px;
margin: auto;
margin-top: 20px;
color: white;
margin-bottom: 50px;
flex-shrink: 0;
}
.change-title {
font-size: 18px;
margin-top: 10px;
margin-bottom: 10px;
margin-left: 10px;
user-select: none;
}
.change-list{
display: flex;
}
.change {
background: rgba(0, 0, 0, 0.335);
width: 200px;
height: 90px;
margin-left: 10px;
border-radius: 5px;
transition: 0.3s;
position: relative
}
.change:hover {
background: rgba(0, 0, 0, 0.466);
}
.notable-changes{
margin: 10px;
cursor: default;
user-select: none;
}
.change-date{
position: absolute;
bottom: 10px;
right: 10px;
color: rgba(255, 255, 255, 0.753);
cursor: default;
user-select: none;
}
.small-view-nav-bar{
width: 100%;
height: 50px;
background: rgba(0, 0, 0, 0.411);
display: none;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.small-logo {
height: 30px;
width: 30px;
background: url(./../assets/logo.png);
background-size: 105%;
background-position: center;
border-radius: 50%;
box-shadow: 0px 0px 96px -4px rgba(69,212,255,1);
margin: auto;
margin-left: 10px;
flex-shrink: 0;
}
.small-title{
color: white;
font-size: 20px;
flex: 1;
margin-left: 10px;
}
.show-menu-button{
color: rgba(255, 255, 255, 0.698);
margin-right: 20px;
margin-top: 7px;
user-select: none;
transition: 0.3s
}
.show-menu-button:hover {
color: rgb(255, 255, 255);
}
.show-menu-content {
display: flex !important;
width: 400px !important;
opacity: 1 !important;
transform: scale(1) !important;
}
@media (max-width: 1051px) {
.change:nth-child(3){
display: none;
}
.change-log-mini{
width: 430px;
}
}
@media (max-width: 906px) {
.change:nth-child(3){
display: block;
}
.change-log-mini{
width: 640px;
}
}
@media (max-width: 649px) {
.twitter-outer{
margin-top: 20px;
margin-bottom: 50px;
width: initial;
}
.change-list{
flex-direction: column;
}
.change-log-mini{
height: initial;
width: calc(100% - 20px);
padding-bottom: 10px;
margin: auto;
}
.change{
margin-bottom: 5px;
margin-left: 5px;
margin-right: 0;
width: calc(100% - 10px);
}
}
@media (max-width: 1380px) {
.graphic-app{
width: calc(100% - 80px);
}
.title{
font-size: 30px;
margin-left: 20px;
margin-right: 20px;
}
}
@media (max-width: 906px) {
.right-panel-home {
position: absolute;
bottom: 0;
right: 0;
top: 50px;
display: flex;
margin-right: 0;
margin-top: 0;
height:calc(100% - 50px);
background-color: rgb(34, 34, 34);
width: 0;
overflow-x: hidden;
transition: 0.5s ease;
transform: scale(0.97);
opacity: 0;
}
.right-panel-inner{
width: 400px;
}
.small-view-nav-bar{
display: flex;
}
}
@media (max-width: 401px) {
.show-menu-content {
width: 100% !important;
}
.right-panel-inner{
width: 100%;
}
}
</style>
<!-- Used for forms !-->
<style>
@media (max-width: 1380px) {
.graphic-app{
width: calc(100% - 80px);
}
}
@media (max-width: 906px) {
.right-panel-home {
position: absolute;
bottom: 0;
right: 0;
top: 50px;
display: flex;
margin-right: 0;
margin-top: 0;
height:calc(100% - 50px);
background-color: rgba(34, 34, 34, 0.877);
width: 0;
overflow-x: hidden;
transition: 0.5s ease;
transform: scale(0.97);
opacity: 0;
}
.right-panel-inner{
width: 400px;
}
.small-view-nav-bar{
display: flex;
}
}
@media (max-width: 401px) {
.show-menu-content {
width: 100% !important;
}
.right-panel-inner{
width: 100%;
}
}
.form {
color: white;
margin: auto;
padding: 10px;
}
input{
padding: 10px;
background: rgba(0, 0, 0, 0.301);
outline: none;
border: none;
color: white;
margin-top: 5px;
margin-bottom: 10px;
width: 200px;
transition: 0.3s;
}
input:hover{
background: rgba(0, 0, 0, 0.452);
}
input:focus {
background: rgba(0, 0, 0, 0.603);
}
.input-title{
margin-top: 10px;
}
.form-button{
padding: 10px;
background: rgba(0, 0, 0, 0.226);
display: table;
transition: 0.5s;
margin: auto;
color: white;
border: none;
outline: none;
}
.form-button:hover{
background: rgba(0, 0, 0, 0.534)
}
.alert{
color: red;
font-size: 15px;
width: 220px;
}
</style>