Merge branch 'master' into polish

This commit is contained in:
Supertiger 2019-03-08 10:57:17 +00:00 committed by GitHub
commit 5045f96103
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1476 additions and 369 deletions

13
package-lock.json generated
View file

@ -4449,10 +4449,9 @@
} }
}, },
"filesize": { "filesize": {
"version": "3.6.1", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", "resolved": "https://registry.npmjs.org/filesize/-/filesize-4.1.2.tgz",
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", "integrity": "sha512-iSWteWtfNcrWQTkQw8ble2bnonSl7YJImsn9OZKpE2E4IHhXI78eASpDYUljXZZdYj36QsEKjOs/CsiDqmKMJw=="
"dev": true
}, },
"fill-range": { "fill-range": {
"version": "4.0.0", "version": "4.0.0",
@ -10842,6 +10841,12 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true "dev": true
},
"filesize": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
"integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
"dev": true
} }
} }
}, },

View file

@ -9,6 +9,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^0.18.0", "axios": "^0.18.0",
"filesize": "^4.1.2",
"futoji": "^0.2.4", "futoji": "^0.2.4",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"socket.io": "^2.2.0", "socket.io": "^2.2.0",

View file

@ -0,0 +1,46 @@
<template>
<div class="drop-background">
<div class="box">
<i class="material-icons">insert_drive_file</i>
<div class="info">Drop file</div>
</div>
</div>
</template>
<style scoped>
.drop-background {
position: absolute;
background: rgba(0, 0, 0, 0.521);
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
display: flex;
}
.box {
margin: auto;
height: 230px;
width: 300px;
background: rgba(0, 0, 0, 0.466);
border: solid 1px white;
border-radius: 5px;
color: white;
display: flex;
flex-direction: column;
user-select: none;
}
.material-icons{
font-size: 80px;
color: white;
margin: auto;
margin-top: 50px;
margin-bottom: 10px;
}
.info{
margin: auto;
text-align: center;
margin-top: 20px;
}
</style>

View file

@ -1,19 +1,60 @@
<template> <template>
<div class="dark-background"> <div class="dark-background" @click="backgroundClick">
<div class="inner"> <div class="inner">
<div class="text"> <div class="text">
To upload files and images, You must link your Google Drive account with your Nertivia account. To upload files, images or set avatars, You must link your Google Drive account with your Nertivia account.
</div> </div>
<div class="images"> <div class="images">
<div class="image GDrive-img"></div> <div class="image GDrive-img"></div>
<div class="arrow">></div> <div class="arrow">></div>
<div class="image nertivia-img"></div> <div class="image nertivia-img"></div>
</div> </div>
<div class="button">Link me</div> <div class="buttons">
<div class="button deny" @click="closeMenu">No thanks</div>
<div class="button" @click="link">Link me</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script>
import {bus} from '@/main'
import settingsService from '@/services/settingsService';
export default {
methods: {
closeMenu() {
this.$store.dispatch('setPopoutVisibility', {name: 'GDLinkMenu', visibility: false})
},
backgroundClick(e) {
if (e.target.classList.contains('dark-background')) {
this.closeMenu()
}
},
async link() {
const {ok, error, result} = await settingsService.GDriveURL();
if (ok) {
const {url} = result.data;
//open a new window
const left = (screen.width/2)-(400/2);
const top = (screen.height/2)-(500/2);
const consentWindow = window.open(url, "",
'width=400,height=500,top='+top+', left='+left
);
window.onmessage = async (e) => {
consentWindow.close();
if (!e.data.code) return;
const url = new URL(e.data.code);
const code = url.searchParams.get("code");
await settingsService.GDriveAuth(code);
}
}
}
}
}
</script>
<style scoped> <style scoped>
.dark-background { .dark-background {
position: absolute; position: absolute;
@ -22,7 +63,7 @@
left: 0; left: 0;
right: 0; right: 0;
background: rgba(0, 0, 0, 0.781); background: rgba(0, 0, 0, 0.781);
z-index: 1; z-index: 111111;
display: flex; display: flex;
} }
.inner { .inner {
@ -30,16 +71,19 @@
height: 400px; height: 400px;
width: 400px; width: 400px;
background: rgb(32, 32, 32); background: rgb(32, 32, 32);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: white; color: white;
border-radius: 3px; border-radius: 3px;
} }
.text{ .text{
color: white; color: white;
text-align: center; text-align: center;
margin-top: 30px; margin-top: 30px;
font-size: 17px; font-size: 17px;
padding-left: 10px;
padding-right: 10px;
user-select: none;
} }
.images{ .images{
display: flex; display: flex;
@ -54,15 +98,23 @@
} }
.nertivia-img { .nertivia-img {
background-image: url(./../../assets/logo.png); background-image: url(./../../assets/logo.png);
border-radius: 50%;
background-size: calc(100% + 34px);
box-shadow: 0px 0px 66px -4px rgba(69,212,255,1);
} }
.arrow{ .arrow{
font-size: 40px; font-size: 40px;
margin: auto; margin: auto;
padding: 20px; padding: 20px;
user-select: none;
} }
.GDrive-img { .GDrive-img {
background-image: url(./../../assets/Google_Drive_logo.png); background-image: url(./../../assets/Google_Drive_logo.png);
} }
.buttons{
margin: auto;
display: flex;
}
.button{ .button{
display: inline-block; display: inline-block;
margin: auto; margin: auto;
@ -72,10 +124,18 @@
cursor: default; cursor: default;
user-select: none; user-select: none;
transition: 0.3s; transition: 0.3s;
margin-left: 10px;
margin-right: 10px;
} }
.button:hover { .button:hover {
background:rgb(58, 134, 255); background:rgb(58, 134, 255);
} }
.button.deny {
background: rgb(255, 32, 32);
}
.button.deny:hover {
background: rgb(255, 53, 53);
}
</style> </style>

View file

@ -1,179 +1,284 @@
<template> <template>
<div :class="{message: true, ownMessage: user.uniqueID === $props.uniqueID}"> <div :class="{message: true, ownMessage: user.uniqueID === $props.uniqueID}">
<div class="profile-picture" :style="`background-image: url(${userAvatar})`"></div> <div class="profile-picture" :style="`background-image: url(${userAvatar})`"></div>
<div class="triangle"> <div class="triangle">
<div class="triangle-inner"></div> <div class="triangle-inner"></div>
</div> </div>
<div class="content"> <div class="content">
<div class="user-info"> <div class="user-info">
<div class="username">{{this.$props.username}}</div> <div class="username">{{this.$props.username}}</div>
<div class="date">{{getDate}}</div> <div class="date">{{getDate}}</div>
</div> </div>
<div class="content-message" v-html="formatMessage"></div> <div class="content-message" v-html="formatMessage"></div>
</div>
<div class="sending-status">{{statusMessage}}</div> <div class="file-content" v-if="getFile">
</div> <div class="icon">
<i class="material-icons">insert_drive_file</i>
</div>
<div class="information">
<div class="info">{{getFile.fileName}}</div>
<a :href="getFile.url" target="_blank">
<div class="download-button">Download</div>
</a>
</div>
</div>
<div class="image-content" v-if="getImage">
<img :src="getImage" @click="imageClicked">
</div>
</div>
<div class="sending-status">{{statusMessage}}</div>
</div>
</template> </template>
<script> <script>
import messageFormatter from '@/messageFormatter.js' import messageFormatter from "@/messageFormatter.js";
import config from '@/config.js' import config from "@/config.js";
import friendlyDate from '@/date' import friendlyDate from "@/date";
export default { import path from "path";
props: ['message', 'status', 'username', 'avatar', 'date', 'uniqueID'],
computed: {
formatMessage() {
return messageFormatter(this.$props.message)
},
getDate() {
return friendlyDate(this.$props.date);
},
userAvatar() {
return config.domain + "/avatars/" + this.$props.avatar
},
statusMessage(){
let status = this.$props.status;
if (status == 0) { export default {
return "Sending" props: [
} else if (status == 1) { "message",
return "Sent" "status",
} else if (status == 2) { "username",
return "Failed" "avatar",
} else { "date",
return "" "uniqueID",
} "files"
}, ],
user() { methods: {
return this.$store.getters.user imageClicked(event) {
} this.$store.dispatch("setImagePreviewURL", event.target.src);
} }
} },
computed: {
getImage() {
if (!this.$props.files || this.$props.files.length === 0)
return undefined;
const file = this.$props.files[0];
if (!file.fileID) return undefined;
const filetypes = /jpeg|jpg|gif|png/;
const extname = filetypes.test(path.extname(file.fileName).toLowerCase());
if (!extname) return undefined;
return config.domain + "/files/" + file.fileID;
},
getFile() {
if (!this.$props.files || this.$props.files.length === 0)
return undefined;
let file = this.$props.files[0];
if (!file.fileID) return undefined;
const filetypes = /jpeg|jpg|gif|png/;
const extname = filetypes.test(path.extname(file.fileName).toLowerCase());
if (extname) return undefined;
file.url = config.domain + '/files/' + file.fileID;
return file;
},
formatMessage() {
return messageFormatter(this.$props.message);
},
getDate() {
return friendlyDate(this.$props.date);
},
userAvatar() {
return config.domain + "/avatars/" + this.$props.avatar;
},
statusMessage() {
let status = this.$props.status;
if (status == 0) {
return "Sending";
} else if (status == 1) {
return "Sent";
} else if (status == 2) {
return "Failed";
} else {
return "";
}
},
user() {
return this.$store.getters.user;
}
}
};
</script> </script>
<style scoped> <style scoped>
.message {
margin: 10px;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
.message{ animation: showMessage 0.3s ease-in-out;
margin: 10px;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
animation: showMessage .3s ease-in-out;
} }
.ownMessage .triangle-inner{ .ownMessage .triangle-inner {
border-right: 7px solid rgba(184, 184, 184, 0.219); border-right: 7px solid rgba(184, 184, 184, 0.219);
}
.ownMessage .content {
background: rgba(184, 184, 184, 0.219);
}
.ownMessage .date {
color: rgb(209, 209, 209);
} }
.ownMessage .content{
background: rgba(184, 184, 184, 0.219);
.file-content {
display: flex;
background: rgba(0, 0, 0, 0.089);
padding: 10px;
margin-top: 5px;
}
.file-content .material-icons {
font-size: 40px;
}
.file-content .download-button {
font-size: 14px;
background: rgba(0, 0, 0, 0.158);
border-radius: 3px;
padding: 3px;
text-align: center;
display: inline-block;
margin-top: 3px;
transition: 0.3s;
user-select: none;
cursor: default;
color: white;
}
.file-content .download-button:hover {
background: rgba(0, 0, 0, 0.329);
}
.file-content .info {
word-wrap: break-word;
word-break: break-word;
white-space: pre-wrap;
font-size: 14px;
overflow: hidden;
max-width: 100%;
color: white;
overflow-wrap: anywhere;
margin-top: 3px;
} }
@keyframes showMessage { @keyframes showMessage {
from { from {
transform: translate(0px, 9px); transform: translate(0px, 9px);
opacity: 0; opacity: 0;
} }
} }
.profile-picture{ .profile-picture {
height: 50px; height: 50px;
width: 50px; width: 50px;
background-color: rgba(0, 0, 0, 0.281); background-color: rgba(0, 0, 0, 0.281);
margin: auto; margin: auto;
margin-bottom: 0; margin-bottom: 0;
border-radius: 50%; border-radius: 50%;
margin-right: 5px; margin-right: 5px;
margin-left: 0; margin-left: 0;
flex-shrink: 0; flex-shrink: 0;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.triangle{ .triangle {
display: flex; display: flex;
justify-content: bottom; justify-content: bottom;
flex-direction: column; flex-direction: column;
margin: auto; margin: auto;
margin-left: 0; margin-left: 0;
margin-right: 0px; margin-right: 0px;
margin-bottom: 8.7px; margin-bottom: 8.7px;
} }
.triangle-inner{ .triangle-inner {
width: 0; width: 0;
height: 0; height: 0;
border-top: 1px solid transparent; border-top: 1px solid transparent;
border-bottom: 7px solid transparent; border-bottom: 7px solid transparent;
border-right: 7px solid rgba(0, 0, 0, 0.301); border-right: 7px solid rgba(0, 0, 0, 0.301);
} }
.content{ .content {
background: rgba(0, 0, 0, 0.301); background: rgba(0, 0, 0, 0.301);
padding: 10px; padding: 10px;
display: flex;
justify-content: center;
flex-direction: column;
border-radius: 10px;
color: rgb(231, 231, 231);
margin: auto;
margin-left: 0;
margin-right: 0;
transition: 1s;
}
display: flex; .image-content {
justify-content: center; margin-top: 10px;
flex-direction: column; padding: 5px;
border-radius: 10px; border-radius: 5px;
color: rgb(231, 231, 231); background: rgba(0, 0, 0, 0.493);
margin: auto; display: -ms-flexbox;
margin-left: 0; display: flex;
margin-right: 0; flex-direction: column;
transition: 1s; }
.image-content img {
width: 170px;
height: auto;
transition: 0.2s;
}
.image-content:hover img {
filter: brightness(70%);
} }
.user-info { .user-info {
display: flex; display: flex;
} }
.username { .username {
color: rgb(219, 219, 219); color: rgb(219, 219, 219);
font-size: 14px; font-size: 14px;
margin: auto; margin: auto;
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
} }
.date{ .date {
color: rgb(161, 161, 161); color: rgb(161, 161, 161);
font-size: 10px; font-size: 10px;
margin: auto; margin: auto;
margin-left: 5px; margin-left: 5px;
} }
.content-message { .content-message {
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
font-size: 14px; font-size: 14px;
overflow: hidden; overflow: hidden;
max-width: 100%; max-width: 100%;
color: white; color: white;
overflow-wrap: anywhere; overflow-wrap: anywhere;
margin-top: 3px;
} }
.message .sending-status { .message .sending-status {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
flex-direction: column; flex-direction: column;
padding-bottom: 5px; padding-bottom: 5px;
margin-left: 10px; margin-left: 10px;
font-size: 15px; font-size: 15px;
color: white; color: white;
align-self: normal; align-self: normal;
user-select: none; user-select: none;
transition: 0.5; transition: 0.5;
} }
</style> </style>
<style> <style>
.codeblock{ .codeblock {
background-color: rgba(0, 0, 0, 0.397); background-color: rgba(0, 0, 0, 0.397);
padding: 5px; padding: 5px;
border-radius: 5px; border-radius: 5px;
} }
</style> </style>

View file

@ -50,7 +50,7 @@ export default {
} }
}, },
openSettings() { openSettings() {
bus.$emit('openSettings'); this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: true})
} }
}, },
created() { created() {

View file

@ -0,0 +1,107 @@
<template>
<div class="popouts">
<transition-group name="show" mode="out-in">
<settings key="settings" v-if="popouts.settings"/>
<upload-dialog key="upload-dialog" v-if="popouts.uploadDialog"/>
<GDriveLinkMenu key="GDriveLinkMenu" v-if="popouts.GDLinkMenu"/>
<image-large-preview key="ilp" v-if="popouts.ImagePreviewURL"/>
<drag-drop-file-upload-dialog key="ddfud" v-if="showUploadDrapDrop"/>
</transition-group>
</div>
</template>
<script>
import { bus } from "@/main";
//popouts
import Settings from "@/components/app/Settings.vue";
import uploadDialog from "@/components/app/uploadDialog.vue";
import GDriveLinkMenu from "@/components/app/GDriveLinkMenu.vue";
import imageLargePreview from "@/components/app/imageLargePreview.vue";
import DragDropFileUploadDialog from "@/components/app/DragDropFileUploadDialog.vue";
export default {
components: {
Settings,
uploadDialog,
GDriveLinkMenu,
DragDropFileUploadDialog,
imageLargePreview
},
data() {
return {
showUploadDrapDrop: false,
uploadDialogLastTarget: null
};
},
methods: {
isFile(evt) {
var dt = evt.dataTransfer;
for (var i = 0; i < dt.types.length; i++) {
if (dt.types[i] === "Files") {
return true;
}
}
return false;
},
dragenter(event) {
if (!this.isFile(event) || !this.selectedChannelID) return;
this.lastTarget = event.target; // cache the last target here
this.showUploadDrapDrop = true;
event.preventDefault();
},
dragleave(event) {
if (event.target === this.lastTarget || event.target === document) {
this.showUploadDrapDrop = false;
}
},
dragover(event) {
event.preventDefault();
},
drop(event) {
this.showUploadDrapDrop = false;
event.preventDefault();
if (!event.dataTransfer.files.length || !this.selectedChannelID) return;
this.$store.dispatch("setFile", event.dataTransfer.files[0]);
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: true
});
},
uploadDrapDropEvent() {
window.addEventListener("dragenter", this.dragenter);
window.addEventListener("dragleave", this.dragleave);
window.addEventListener("dragover", this.dragover);
window.addEventListener("drop", this.drop);
}
},
mounted() {
this.uploadDrapDropEvent();
},
destroyed() {
window.removeEventListener("dragenter", this.dragenter);
window.removeEventListener("dragleave", this.dragleave);
window.removeEventListener("dragover", this.dragover);
window.removeEventListener("drop", this.drop);
},
computed: {
popouts() {
return this.$store.getters.popouts;
},
selectedChannelID() {
return this.$store.getters.selectedChannelID;
}
}
};
</script>
<style scoped>
.show-enter-active,
.show-leave-active {
transition: opacity 0.3s;
}
.show-enter, .show-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View file

@ -2,35 +2,63 @@
<div class="right-panel"> <div class="right-panel">
<div class="heading"> <div class="heading">
<div class="show-menu-button" cli> <div class="show-menu-button" cli>
<i class="material-icons" @click="toggleLeftMenu"> <i class="material-icons" @click="toggleLeftMenu">menu</i>
menu </div>
</i> <div class="current-channel">
<span v-if="!selectedChannelID">Welcome back, {{user.username}}!</span>
<span v-else>{{channelName}}</span>
</div> </div>
<div class="current-channel"><span v-if="!selectedChannelID">Welcome back!</span><span v-else>{{channelName}}</span></div>
</div> </div>
<div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]"> <div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]">
<spinner /> <spinner/>
</div> </div>
<div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll"> <div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll">
<div class="scroll"> <div class="scroll">
<message v-for="(msg, index) in messages[selectedChannelID]" :key="index" :date="msg.created" :username="msg.creator.username" :uniqueID="msg.creator.uniqueID" :avatar="msg.creator.avatar" :message="msg.message" :status="msg.status" /> <message
v-for="(msg, index) in messages[selectedChannelID]"
:key="index"
:date="msg.created"
:username="msg.creator.username"
:uniqueID="msg.creator.uniqueID"
:avatar="msg.creator.avatar"
:message="msg.message"
:files="msg.files"
:status="msg.status"
/>
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue"/>
</div> </div>
</div> </div>
<news v-else /> <news v-else/>
<div class="chat-input-area" v-if="selectedChannelID"> <div class="chat-input-area" v-if="selectedChannelID">
<div class="message-area"> <div class="message-area">
<input type="file" ref="sendFileBrowse" @change="attachmentChange" class="hidden">
<div class="attachment-button" @click="attachmentButton">
<i class="material-icons">attach_file</i>
</div>
<textarea <textarea
rows="1"
class="chat-input" class="chat-input"
rows="1"
ref="input-box" ref="input-box"
placeholder="Message" placeholder="Message"
@keydown="chatInput" @keydown="chatInput"
@keyup="delayedResize" @keyup="delayedResize"
@change="resize" @change="resize"
@input="onInput" @input="onInput"
v-model="message" v-model="message"
></textarea> ></textarea>
<button :class="{'send-button': true, 'error-send-button': messageLength > 5000}" @click="sendMessage">Send</button> <button :class="{'send-button': true, 'error-send-button': messageLength > 5000}" @click="sendMessage">Send</button>
@input="onInput"
@paste="onPaste"
v-model="message"
></textarea>
<button
:class="{'send-button': true, 'error-send-button': messageLength > 5000}"
@click="sendMessage"
>
<i class="material-icons">send</i>
</button>
</div> </div>
<div class="info"> <div class="info">
<div class="typing-outer"> <div class="typing-outer">
@ -38,29 +66,33 @@
<typing-status v-if="typing" :username="whosTyping"/> <typing-status v-if="typing" :username="whosTyping"/>
</transition> </transition>
</div> </div>
<div :class="{'message-count': true, 'error-info': messageLength > 5000 }">{{messageLength}}/5000</div> <div
:class="{'message-count': true, 'error-info': messageLength > 5000 }"
>{{messageLength}}/5000</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import messagesService from '@/services/messagesService' import messagesService from "@/services/messagesService";
import typingService from '@/services/TypingService' import typingService from "@/services/TypingService";
import {bus} from '../../main' import { bus } from "../../main";
import JQuery from 'jquery' import JQuery from "jquery";
let $ = JQuery let $ = JQuery;
import News from '../../components/app/News.vue' import News from "../../components/app/News.vue";
import Message from '../../components/app/MessageTemplate.vue' import Message from "../../components/app/MessageTemplate.vue";
import Spinner from '@/components/Spinner.vue' import Spinner from "@/components/Spinner.vue";
import TypingStatus from '@/components/app/TypingStatus.vue' import TypingStatus from "@/components/app/TypingStatus.vue";
import uploadsQueue from "@/components/app/uploadsQueue.vue";
export default { export default {
components: { components: {
Message, Message,
Spinner, Spinner,
News, News,
TypingStatus TypingStatus,
uploadsQueue
}, },
data() { data() {
return { return {
@ -69,31 +101,32 @@ export default {
postTimerID: null, postTimerID: null,
getTimerID: null, getTimerID: null,
typing: false, typing: false,
whosTyping: "", whosTyping: ""
} };
}, },
methods:{ methods: {
toggleLeftMenu(){ toggleLeftMenu() {
bus.$emit('toggleLeftMenu') bus.$emit("toggleLeftMenu");
}, },
generateNum(n) { generateNum(n) {
var add = 1, max = 12 - add; // 12 is the min safe number Math.random() can generate without it starting to pad the end with zeros. var add = 1,
max = 12 - add; // 12 is the min safe number Math.random() can generate without it starting to pad the end with zeros.
if ( n > max ) { if (n > max) {
return this.generateNum(max) + this.generateNum(n - max); return this.generateNum(max) + this.generateNum(n - max);
} }
max = Math.pow(10, n+add); max = Math.pow(10, n + add);
var min = max/10; // Math.pow(10, n) basically var min = max / 10; // Math.pow(10, n) basically
var number = Math.floor( Math.random() * (max - min + 1) ) + min; var number = Math.floor(Math.random() * (max - min + 1)) + min;
return ("" + number).substring(add); return ("" + number).substring(add);
}, },
async sendMessage(){ async sendMessage() {
this.$refs["input-box"].focus() this.$refs["input-box"].focus();
this.message = this.message.trim(); this.message = this.message.trim();
if(this.message == "")return; if (this.message == "") return;
if (this.message.length > 5000) return; if (this.message.length > 5000) return;
clearInterval(this.postTimerID); clearInterval(this.postTimerID);
this.postTimerID = null; this.postTimerID = null;
@ -102,7 +135,7 @@ export default {
const msg = this.message; const msg = this.message;
const tempID = this.generateNum(25); const tempID = this.generateNum(25);
this.$store.dispatch('addMessage', { this.$store.dispatch("addMessage", {
sender: true, sender: true,
channelID: this.selectedChannelID, channelID: this.selectedChannelID,
message: { message: {
@ -111,36 +144,40 @@ export default {
channelID: this.selectedChannelID, channelID: this.selectedChannelID,
created: new Date() created: new Date()
} }
}) });
this.message = "" this.message = "";
this.$store.dispatch('updateChannelLastMessage', this.selectedChannelID); this.$store.dispatch("updateChannelLastMessage", this.selectedChannelID);
const { ok, error, result } = await messagesService.post(this.selectedChannelID, { const { ok, error, result } = await messagesService.post(
message: msg, this.selectedChannelID,
socketID: this.$socket.id, {
tempID message: msg,
}) socketID: this.$socket.id,
if ( ok ) { tempID
const message = result.data.messageCreated }
);
if (ok) {
const message = result.data.messageCreated;
message.status = 1; message.status = 1;
this.$store.dispatch('replaceMessage', { this.$store.dispatch("replaceMessage", {
tempID: result.data.tempID, tempID: result.data.tempID,
message message
}); });
} else { } else {
// TODO: Error handling // TODO: Error handling
console.log(error) console.log(error);
} }
}, },
async postTimer() { async postTimer() {
this.postTimerID = setInterval(async () => { this.postTimerID = setInterval(async () => {
if (this.$refs['input-box'].value.trim() == "") { if (this.$refs["input-box"].value.trim() == "") {
clearInterval(this.postTimerID); clearInterval(this.postTimerID);
return this.postTimerID = null; return (this.postTimerID = null);
} }
await typingService.post(this.selectedChannelID); await typingService.post(this.selectedChannelID);
}, 2000); }, 2000);
}, },
resize(event) { resize(event) {
let input = this.$refs["input-box"] let input = this.$refs["input-box"]
@ -161,6 +198,13 @@ export default {
const value = event.target.value.trim(); const value = event.target.value.trim();
if (value && this.postTimerID == null) { if (value && this.postTimerID == null) {
this.postTimer() this.postTimer()
async onInput(event) {
this.messageLength = this.message.length;
const value = event.target.value.trim();
if (value && this.postTimerID == null) {
// Post typing status
this.postTimer();
await typingService.post(this.selectedChannelID); await typingService.post(this.selectedChannelID);
} }
}, },
@ -177,45 +221,98 @@ export default {
} }
}, },
invertScroll(event) { invertScroll(event) {
if(event.deltaY) { if (event.deltaY) {
event.preventDefault(); event.preventDefault();
event.currentTarget.scrollTop -= parseFloat(getComputedStyle(event.currentTarget).getPropertyValue('font-size')) * (event.deltaY < 0 ? -1 : 1) * 2; event.currentTarget.scrollTop -=
parseFloat(
getComputedStyle(event.currentTarget).getPropertyValue("font-size")
) *
(event.deltaY < 0 ? -1 : 1) *
2;
} }
}, },
hideTypingStatus(data) { hideTypingStatus(data) {
if(this.user.uniqueID === data.message.creator.uniqueID) return; if (this.user.uniqueID === data.message.creator.uniqueID) return;
this.typing = false; this.typing = false;
},
attachmentButton() {
this.$refs.sendFileBrowse.click();
},
uploadFile(file) {
// 1073741824 = 1GB || 1024GB
const sizeLimit = 1073741824;
if (file.size >= sizeLimit) {
//show a warning.
return;
}
this.$store.dispatch('setFile', file)
this.$store.dispatch('setPopoutVisibility', {name: 'uploadDialog', visibility: true})
},
attachmentChange(event) {
const file = event.target.files[0];
event.target.value = "";
this.uploadFile(file);
},
onPaste(event) {
var items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let index in items) {
var item = items[index];
if (item.kind === "file") {
var blob = item.getAsFile();
this.$store.dispatch('setFile', blob)
this.$store.dispatch('setPopoutVisibility', {name: 'uploadDialog', visibility: true})
break;
}
}
} }
}, },
mounted() { mounted() {
this.$options.sockets.typingStatus = (data) => { this.$options.sockets.typingStatus = data => {
const {channelID, userID} = data; const { channelID, userID } = data;
if (channelID !== this.selectedChannelID) return; if (channelID !== this.selectedChannelID) return;
this.typing = true; this.typing = true;
this.whosTyping = this.channel.recipients.find(function(recipient) { this.whosTyping = this.channel.recipients.find(function(recipient) {
return recipient.uniqueID == userID; return recipient.uniqueID == userID;
}).username }).username;
clearTimeout(this.getTimerID); clearTimeout(this.getTimerID);
this.getTimerID = setTimeout(() => { this.getTimerID = setTimeout(() => {
this.typing = false; this.typing = false;
}, 2500); }, 2500);
} };
bus.$on('newMessage', this.hideTypingStatus) bus.$on("newMessage", this.hideTypingStatus);
//dismiss notification on focus //dismiss notification on focus
window.onfocus = () => { window.onfocus = () => {
bus.$emit('title:change', "Nertivia"); bus.$emit("title:change", "Nertivia");
if (!this.$store.getters.selectedChannelID) return; if (!this.$store.getters.selectedChannelID) return;
const find = this.$store.getters.notifications.find(notification => {return notification.channelID === this.$store.getters.selectedChannelID}); const find = this.$store.getters.notifications.find(notification => {
return notification.channelID === this.$store.getters.selectedChannelID;
});
if (find && find.count >= 1) { if (find && find.count >= 1) {
this.$socket.emit('notification:dismiss', {channelID: this.$store.getters.selectedChannelID}); this.$socket.emit("notification:dismiss", {
} channelID: this.$store.getters.selectedChannelID
} });
}
};
}, },
beforeDestroy() { beforeDestroy() {
bus.$off('newMessage', this.hideTypingStatus); bus.$off("newMessage", this.hideTypingStatus);
delete this.$options.sockets.typingStatus; delete this.$options.sockets.typingStatus;
}, },
computed: { computed: {
uploadQueue() {
const allUploads = this.$store.getters.getAllUploads;
const selectedChannelID = this.$store.getters.selectedChannelID;
const filteredArray = [];
for (let upload in allUploads) {
if (allUploads[upload].channelID === selectedChannelID) {
filteredArray.push(allUploads[upload]);
}
}
if (!filteredArray.length) return undefined;
return filteredArray;
},
user() { user() {
return this.$store.getters.user; return this.$store.getters.user;
}, },
@ -226,45 +323,46 @@ export default {
return this.$store.getters.messages; return this.$store.getters.messages;
}, },
selectedChannelID() { selectedChannelID() {
return this.$store.getters.selectedChannelID return this.$store.getters.selectedChannelID;
}, },
channelName() { channelName() {
return this.$store.getters.channelName; return this.$store.getters.channelName;
} }
} }
} };
</script> </script>
<style scoped> <style scoped>
.hidden {
display: none;
}
.typing-animate-enter-active { .typing-animate-enter-active {
transition: .10s; transition: 0.1s;
} }
.typing-animate-enter /* .fade-leave-active below version 2.1.8 */ { .typing-animate-enter /* .fade-leave-active below version 2.1.8 */ {
opacity: 0; opacity: 0;
transform: translateY(3px) transform: translateY(3px);
} }
.typing-animate-leave-to { .typing-animate-leave-to {
opacity: 0; opacity: 0;
transform: translateY(-3px) transform: translateY(-3px);
} }
.error-info { .error-info {
color: red; color: red;
} }
.heading{ .heading {
border-bottom: solid 2px white; border-bottom: solid 2px white;
margin: 5px; margin: 5px;
height: 40px; height: 40px;
padding-bottom: 2spx; padding-bottom: 2spx;
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
} }
.show-menu-button{ .show-menu-button {
display: inline-block; display: inline-block;
margin: auto; margin: auto;
color: white; color: white;
@ -275,7 +373,7 @@ export default {
display: none; display: none;
} }
.heading .current-channel{ .heading .current-channel {
color: white; color: white;
font-size: 20px; font-size: 20px;
margin: auto; margin: auto;
@ -285,36 +383,58 @@ export default {
} }
.right-panel { .right-panel {
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.507); background-color: rgba(0, 0, 0, 0.507);
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.message-logs{ .message-logs {
overflow: auto;
flex: 1;
}
.message-logs, .message-logs .scroll {transform: scale(1,-1);}
.loading{
overflow: auto; overflow: auto;
flex: 1; flex: 1;
} }
.chat-input-area{ .message-logs,
display: flex; .message-logs .scroll {
flex-direction: column; transform: scale(1, -1);
padding-top: 10px;
margin-bottom: 5px;
} }
.chat-input-area .info{
.loading {
overflow: auto;
flex: 1;
}
.chat-input-area {
display: flex;
flex-direction: column;
padding-top: 10px;
margin-bottom: 5px;
}
.attachment-button {
width: 50px;
background: rgba(0, 0, 0, 0.219);
margin-right: 2px;
margin-left: 20px;
display: flex;
flex-shrink: 0;
cursor: default;
user-select: none;
transition: 0.3s;
}
.attachment-button:hover {
background: rgba(0, 0, 0, 0.322);
}
.attachment-button .material-icons {
color: white; color: white;
margin: auto;
}
.chat-input-area .info {
color: rgba(255, 255, 255, 0.466);
font-size: 12px; font-size: 12px;
margin-left: 25px; margin-left: 25px;
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
} }
.typing-outer{ .typing-outer {
flex: 1; flex: 1;
height: 20px; height: 20px;
} }
@ -323,52 +443,57 @@ export default {
margin-right: 30px; margin-right: 30px;
margin-top: 3px; margin-top: 3px;
} }
.message-area{ .message-area {
display: flex; display: flex;
width: 100%; width: 100%;
} }
.chat-input{
font-family: 'Roboto', sans-serif; .chat-input {
background: rgba(0, 0, 0, 0.158); font-family: "Roboto", sans-serif;
color: white; background: rgba(0, 0, 0, 0.158);
flex: 1; color: white;
height: 20px; width: 100%;
padding: 10px; height: 20px;
margin: auto; padding: 10px;
margin-left: 20px; margin: auto;
font-size: 15px; font-size: 15px;
resize: none; resize: none;
border: none; border: none;
outline: none; outline: none;
padding-left: 10px; padding-left: 10px;
transition: 0.3s; transition: 0.3s;
height: 1em; height: 1em;
overflow: hidden; overflow: hidden;
max-height: 30vh; max-height: 30vh;
} }
.chat-input:hover{ .chat-input:hover {
background: rgba(0, 0, 0, 0.288); background: rgba(0, 0, 0, 0.288);
} }
.chat-input:focus{ .chat-input:focus {
background: rgba(0, 0, 0, 0.466); background: rgba(0, 0, 0, 0.466);
} }
.send-button{ .send-button {
font-size: 20px; font-size: 20px;
color:white; color: white;
background: rgba(0, 0, 0, 0.274); background: rgba(0, 0, 0, 0.274);
border: none; border: none;
outline: none; outline: none;
margin: auto; margin: auto;
margin-left: 2px; margin-left: 2px;
margin-right: 20px; margin-right: 20px;
height: 40px; height: 40px;
transition: 0.3s; width: 50px;
transition: 0.3s;
display: flex;
flex-shrink: 0;
} }
.send-button:hover{ .send-button .material-icons {
margin: auto;
}
.send-button:hover {
background: rgba(0, 0, 0, 0.514); background: rgba(0, 0, 0, 0.514);
} }
.error-send-button { .error-send-button {
@ -379,7 +504,7 @@ export default {
background-color: rgba(255, 0, 0, 0.294); background-color: rgba(255, 0, 0, 0.294);
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.show-menu-button{ .show-menu-button {
display: block; display: block;
} }
} }

View file

@ -60,7 +60,7 @@ export default {
this.title = title; this.title = title;
}, },
close() { close() {
bus.$emit('closeSettings'); this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: false})
} }
} }
} }

View file

@ -7,7 +7,7 @@
</div> </div>
<div class="options"> <div class="options">
<input type="file" accept="image/*" ref="avatarBrowser" @change="avatarBrowse" class="hidden"> <input type="file" accept="image/*" ref="avatarBrowser" @change="avatarBrowse" class="hidden">
<div class="option" @click="$refs.avatarBrowser.click()">Edit Avatar</div> <div class="option" @click="editAvatarBtn">Edit Avatar</div>
<div class="option" @click="changePassword">Change Password</div> <div class="option" @click="changePassword">Change Password</div>
<div class="option red" @click="logout">Logout</div> <div class="option red" @click="logout">Logout</div>
</div> </div>
@ -26,8 +26,9 @@
</template> </template>
<script> <script>
import UploadService from '@/services/UploadService.js' import AvatarUpload from '@/services/AvatarUpload.js'
import config from '@/config.js' import config from '@/config.js'
import {bus} from '@/main'
import path from 'path' import path from 'path'
export default { export default {
@ -60,7 +61,7 @@ export default {
} }
const formData = new FormData(); const formData = new FormData();
formData.append('avatar', file); formData.append('avatar', file);
const {ok, error, result} = await UploadService.uploadAvatar(formData, this.onProgress); const {ok, error, result} = await AvatarUpload.uploadAvatar(formData, this.onProgress);
if (!ok) { if (!ok) {
this.alert.content = 'Upload failed - Something went wrong. Try again later.'; this.alert.content = 'Upload failed - Something went wrong. Try again later.';
return this.alert.show = true; return this.alert.show = true;
@ -74,6 +75,12 @@ export default {
changePassword() { changePassword() {
this.alert.content = 'Not implemented yet.'; this.alert.content = 'Not implemented yet.';
return this.alert.show = true; return this.alert.show = true;
},
editAvatarBtn() {
if(!this.$store.getters.settings.GDriveLinked) {
return this.$store.dispatch('setPopoutVisibility', {name: 'GDLinkMenu', visibility: true})
}
this.$refs.avatarBrowser.click()
} }
}, },
computed: { computed: {

View file

@ -0,0 +1,62 @@
<template>
<div ref="background" class="drop-background">
<div class="img-outer">
<img :src="$store.getters.popouts.ImagePreviewURL">
</div>
</div>
</template>
<script>
export default {
methods: {
backgroundClickEvent(event) {
if(event.target.matches('.img-outer') || event.target.matches('.drop-background')){
this.$store.dispatch('setImagePreviewURL', event.target.src)
}
}
},
mounted() {
this.$refs["background"].addEventListener(
"click",
this.backgroundClickEvent
);
},
beforeDestroy() {
this.$refs["background"].removeEventListener(
"click",
this.backgroundClickEvent
);
}
};
</script>
<style scoped>
.drop-background {
position: absolute;
background: rgba(0, 0, 0, 0.774);
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
display: flex;
height: 100%;
user-select: none;
}
.img-outer{
margin: auto;
height: 90%;
width: 90%;
display: flex;
}
img {
margin: auto;
border: solid 1px white;
max-height: 100%;
max-width: 100%;
width: auto;
height: auto;
user-select: none;
}
</style>

View file

@ -42,6 +42,7 @@ export default {
} }
this.$store.dispatch('selectedChannelID', this.$props.channelID); this.$store.dispatch('selectedChannelID', this.$props.channelID);
this.$store.dispatch('setChannelName', this.$props.username); this.$store.dispatch('setChannelName', this.$props.username);
if (this.$store.getters.messages[this.$props.channelID]) return;
if (this.$store.getters.channels[this.$props.channelID] && !this.$store.getters.messages[this.$props.channelID]) return this.getMessages(); if (this.$store.getters.channels[this.$props.channelID] && !this.$store.getters.messages[this.$props.channelID]) return this.getMessages();
const {ok, error, result} = await channelService.post(this.$props.channelID); const {ok, error, result} = await channelService.post(this.$props.channelID);
if ( ok ) { if ( ok ) {

View file

@ -0,0 +1,287 @@
<template>
<div class="dark-background">
<div class="inner">
<div class="info">
<div class="preview-image" v-show="image" ref="preview-image"></div>
<div class="file-icon" v-if="!image">
<i class="material-icons">insert_drive_file</i>
</div>
<div class="data">
<div class="name">
<strong>Name:</strong>
{{name}}
</div>
<div class="size">
<strong>Size:</strong>
{{size}}
</div>
</div>
</div>
<div class="message">Add a message</div>
<div class="message-area">
<textarea class="chat-input" v-model="message" placeholder></textarea>
</div>
<div class="bottom-panel">
<div class="close-button button" @click="closeButton">
<i class="material-icons">close</i>
<div class="text">Cancel</div>
</div>
<div class="send-button button" @click="send">
<i class="material-icons">send</i>
<div class="text">Send</div>
</div>
</div>
</div>
</div>
</template>
<script>
import filesize from "filesize";
import messagesService from "@/services/messagesService";
import { bus } from "../../main";
export default {
props: ["file"],
data() {
return {
message: "",
name: "",
size: "",
image: false
};
},
methods: {
generateNum(n) {
var add = 1,
max = 12 - add; // 12 is the min safe number Math.random() can generate without it starting to pad the end with zeros.
if (n > max) {
return this.generateNum(max) + this.generateNum(n - max);
}
max = Math.pow(10, n + add);
var min = max / 10; // Math.pow(10, n) basically
var number = Math.floor(Math.random() * (max - min + 1)) + min;
return ("" + number).substring(add);
},
closeButton() {
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
});
},
loadFileInfo(file) {
const previewImage = this.$refs["preview-image"];
const mimeType = file.type;
if (mimeType.split("/")[0] === "image") {
this.image = true;
const reader = new FileReader();
reader.onloadend = function() {
previewImage.style.backgroundImage = `url(${reader.result})`;
};
reader.readAsDataURL(file);
}
},
async send() {
const tempID = this.generateNum(25);
const formData = new FormData();
formData.append("message", this.message);
formData.append("avatar", this.popouts.fileToUpload);
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
});
this.$store.dispatch("addUpload", {
channelID: this.selectedChannelID,
tempID,
name: this.popouts.fileToUpload.name,
size: filesize(this.popouts.fileToUpload.size),
percent: 0,
created: new Date()
});
const { ok, error, result } = await messagesService.post(
this.selectedChannelID,
formData,
percent => {
this.$store.dispatch("updatePercentUpload", {
tempID,
percent
});
}
);
if (ok) {
this.$store.dispatch("removeUpload", tempID);
}
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
});
},
keyDownEvent(event) {
const keyCode = event.keyCode;
if (keyCode == 13) {
return this.send();
}
if (keyCode == 27) {
return this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
});
}
}
},
beforeMount() {
if (this.popouts.fileToUpload.size == 0) {
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
});
}
if (!this.$store.getters.settings.GDriveLinked) {
this.$store.dispatch("setPopoutVisibility", {
name: "uploadDialog",
visibility: false
});
}
},
mounted() {
(this.name = this.popouts.fileToUpload.name),
(this.size = filesize(this.popouts.fileToUpload.size)),
this.loadFileInfo(this.popouts.fileToUpload);
document.addEventListener("keydown", this.keyDownEvent);
},
destroyed() {
document.removeEventListener("keydown", this.keyDownEvent);
},
computed: {
selectedChannelID() {
return this.$store.getters.selectedChannelID;
},
popouts() {
return this.$store.getters.popouts;
}
}
};
</script>
<style scoped>
.dark-background {
background: rgba(0, 0, 0, 0.877);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
display: flex;
}
.inner {
background: rgba(47, 47, 47, 0.938);
display: flex;
margin: auto;
width: 500px;
flex-direction: column;
position: relative;
}
.info {
display: flex;
margin: 20px;
font-size: 15px;
margin-bottom: 40px;
}
.size {
color: grey;
margin-top: 5px;
}
.data {
margin-left: 10px;
color: white;
margin-top: 5px;
}
.preview-image {
background-color: #343434;
height: 100px;
width: 179px;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
border-radius: 4px;
flex-shrink: 0;
}
.file-icon {
height: 50px;
width: 50px;
flex-shrink: 0;
display: flex;
}
.file-icon .material-icons {
color: white;
margin: auto;
font-size: 50px;
}
.bottom-panel {
margin-top: 10px;
margin-bottom: 10px;
color: white;
bottom: 20px;
display: flex;
float: right;
align-items: flex-end;
align-content: flex-end;
align-self: flex-end;
}
.button {
margin: auto;
margin-left: 10px;
margin-right: 10px;
display: flex;
align-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.267);
padding: 5px;
border-radius: 3px;
cursor: default;
user-select: none;
transition: 0.3s;
}
.button:hover {
background: rgba(0, 0, 0, 0.445);
}
.button .text {
margin-left: 5px;
}
.send-button {
}
.close-button {
padding-right: 10px;
}
.message {
color: rgb(235, 235, 235);
margin-left: 15px;
margin-bottom: 5px;
}
.message-area {
margin-left: 10px;
margin-right: 10px;
display: flex;
}
.chat-input {
font-family: "Roboto", sans-serif;
background: rgb(26, 26, 26);
color: white;
width: 100%;
height: 20px;
padding: 10px;
margin: auto;
font-size: 15px;
resize: none;
border: none;
outline: none;
transition: 0.3s;
}
</style>

View file

@ -0,0 +1,89 @@
<template>
<div class="uploads-queue">
<div class="upload" v-for="(upload, index) in $props.queue" :key="index">
<div class="icon">
<i class="material-icons">insert_drive_file</i>
</div>
<div class="information">
<div class="info">{{upload.name}}</div>
<div class="info size">{{upload.size}}</div>
<div class="progress">
<div class="progress-bar">
<div class="progress-bar-inner" :style="{width: `${upload.percent}%`}"></div>
</div>
<div class="percent">{{upload.percent}}%</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ["queue"]
};
</script>
<style scoped>
.uploads-queue {
display: inline-block;
}
.upload {
color: white;
background: rgba(0, 0, 0, 0.513);
margin: 10px;
margin-left: 70px;
display: flex;
padding: 10px;
}
.icon .material-icons {
font-size: 40px;
}
.info {
word-wrap: break-word;
word-break: break-word;
white-space: pre-wrap;
font-size: 14px;
overflow: hidden;
max-width: 100%;
color: white;
overflow-wrap: anywhere;
margin-top: 3px;
}
.size {
font-size: 15px;
color: grey;
}
.progress {
display: flex;
align-content: center;
align-items: center;
}
.progress-bar {
width: 100px;
height: 10px;
background: rgba(0, 0, 0, 0.513);
margin-right: 10px;
}
.progress-bar-inner {
height: 10px;
background: rgb(255, 255, 255);
animation: dimProgress;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: forwards;
}
@keyframes dimProgress {
0% {
background: rgb(255, 255, 255);
}
50% {
background: rgba(253, 253, 253, 0.705);
}
100% {
background: rgb(255, 255, 255);
}
}
</style>

View file

@ -1,6 +1,5 @@
<template> <template>
<div class="logged-in"> <div class="logged-in">
<div class="title">Welcome!</div>
<div class="card"> <div class="card">
<div class="avatar-outer"> <div class="avatar-outer">
<div class="avatar" :style="`background-image: url(${avatar})`"></div> <div class="avatar" :style="`background-image: url(${avatar})`"></div>
@ -41,22 +40,33 @@ export default {
<style scoped> <style scoped>
.logged-in { .logged-in {
margin-top: 50px;
color: white; color: white;
margin-top: 10px;
} }
.card{ .card{
background-color: rgba(0, 0, 0, 0.26); background-color: rgba(0, 0, 0, 0.041);
padding: 10px; padding: 20px;
margin: 20px;
margin-bottom: 30px; margin-bottom: 30px;
display: flex; display: flex;
flex-direction: column;
flex-shrink: 0;
align-content: center;
align-items: center;
align-self: center;
justify-content: center;
justify-items: center;
text-align: center;
} }
.title { .title {
display: table; display: table;
margin: auto; margin: auto;
font-size: 30px; margin-top: 10px;
font-size: 25px;
text-align: center;
} }
.avatar { .avatar {
@ -73,7 +83,7 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 10px; margin-left: 10px;
margin-top: 5px; margin-top: 20px;
} }
.username { .username {
@ -96,18 +106,20 @@ export default {
padding: 10px; padding: 10px;
border: none; border: none;
outline: none; outline: none;
margin-right: 5px; margin-right: 10px;
margin-top: 10px; margin-left: 10px;
margin-top: 30px;
transition: 0.3s; transition: 0.3s;
font-size: 17px;
} }
.button:hover { .button:hover {
background: rgba(0, 0, 0, 0.582); background: rgba(0, 0, 0, 0.582);
} }
.button.logout{ .button.logout{
background: rgba(149, 0, 0, 0.404); background: rgba(219, 36, 36, 0.438);
} }
.button.logout:hover { .button.logout:hover {
background: rgba(255, 0, 0, 0.582); background: rgba(255, 18, 18, 0.582);
} }
</style> </style>

View file

@ -1,6 +1,6 @@
const config = { const config = {
devMode:true, devMode:true,
breeMode: true, breeMode: false,
recaptcha: "", recaptcha: "",
IP: [ IP: [
{ {

View file

@ -4,6 +4,7 @@ Vue.use(VueRouter)
import {store} from './store/index'; import {store} from './store/index';
import MainApp from '../src/views/App.vue' import MainApp from '../src/views/App.vue'
import HomePage from '../src/views/HomePage.vue' import HomePage from '../src/views/HomePage.vue'
import GDriveCallback from '../src/views/GDriveCallback.vue';
import VueSocketio from 'vue-socket.io-extended'; import VueSocketio from 'vue-socket.io-extended';
import io from 'socket.io-client'; import io from 'socket.io-client';
import config from './config' import config from './config'
@ -42,6 +43,11 @@ export const router = new VueRouter({
}) })
next() next()
} }
} },
{
path: '/GDrive_callback',
name: 'GDrive callback',
component: GDriveCallback
},
] ]
}) })

View file

@ -3,7 +3,8 @@ import config from '@/config';
export const instance = () => { export const instance = () => {
return axios.create({ return axios.create({
baseURL: config.domain baseURL: config.domain,
withCredentials: true
}) })
} }

View file

@ -1,7 +1,10 @@
import {instance, wrapper} from './Api'; import {
instance,
wrapper
} from './Api';
export default { export default {
uploadAvatar(data, onProgress){ uploadAvatar(data, onProgress) {
const url = `/settings/avatar`; const url = `/settings/avatar`;
let config = { let config = {
onUploadProgress(progressEvent) { onUploadProgress(progressEvent) {

View file

@ -1,12 +1,28 @@
import {instance, wrapper} from './Api'; import {instance, wrapper} from './Api';
import filesize from "filesize";
export default { export default {
// TODO: add ?continue=id // TODO: add ?continue=id
get ( channelID ) { get ( channelID ) {
return wrapper(instance().get(`messages/${channelID}`)); return wrapper(instance().get(`messages/${channelID}`));
}, },
post (channelID, data) { post (channelID, data, onProgress) {
return wrapper(instance().post(`messages/${channelID}`, data)) const url = `messages/${channelID}`;
var start = +new Date();
let config = {
onUploadProgress(progressEvent) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
// execute the callback
if (onProgress) onProgress(percentCompleted)
return percentCompleted;
},
};
return wrapper(instance().post(url, data, config));
} }
} }

View file

@ -4,4 +4,10 @@ export default {
setStatus ( status ) { setStatus ( status ) {
return wrapper(instance().post('/settings/status', { status })); return wrapper(instance().post('/settings/status', { status }));
}, },
GDriveURL () {
return wrapper(instance().get('/settings/drive/url'));
},
GDriveAuth (code) {
return wrapper(instance().post('/settings/drive/auth', {code}));
}
} }

View file

@ -5,12 +5,26 @@ import socketModule from './modules/socketIOModule';
import channelModule from './modules/channelModule'; import channelModule from './modules/channelModule';
import messageModule from './modules/messageModule'; import messageModule from './modules/messageModule';
import notificationsModule from './modules/notificationsModule'; import notificationsModule from './modules/notificationsModule';
import {router} from './../router' import settingsModule from './modules/settingsModule';
import uploadFilesModule from './modules/uploadFilesModule';
import popoutsModule from './modules/popoutsModule';
import {
router
} from './../router'
Vue.use(Vuex); Vue.use(Vuex);
export const store = new Vuex.Store({ export const store = new Vuex.Store({
modules: { user, channelModule, messageModule, notificationsModule, socketModule }, modules: {
user,
channelModule,
messageModule,
notificationsModule,
socketModule,
settingsModule,
uploadFilesModule,
popoutsModule
},
state: { state: {
}, },

View file

@ -0,0 +1,55 @@
import axios from 'axios'
import Vue from 'vue'
import {
bus
} from '../../main'
import VueRouter from 'vue-router';
import NotificationSounds from '@/notificationSound';
const state = {
fileToUpload: null,
uploadDialog: false,
ImagePreviewURL: null,
dragDropFileUploadDialog: false,
settings: false,
GDLinkMenu: false,
}
const getters = {
popouts(state) {
return state;
}
}
const actions = {
setPopoutVisibility(context, data) {
context.commit('setPopoutVisibility', data)
},
setFile(context, file) {
context.commit('setFileToUpload', file);
},
setImagePreviewURL(context, url) {
context.commit('setImagePreviewURL', url);
}
}
const mutations = {
setPopoutVisibility(state, data) {
Vue.set(state, data.name, data.visibility)
},
setFileToUpload(state, file) {
Vue.set(state, 'fileToUpload', file);
},
setImagePreviewURL(state, url) {
Vue.set(state, 'ImagePreviewURL', url);
}
}
export default {
namespace: true,
state,
getters,
actions,
mutations
}

View file

@ -0,0 +1,42 @@
import Vue from 'vue'
import {bus} from '../../main'
const state = {
settings: {
}
}
const getters = {
settings(state) {
return state.settings;
}
}
const actions = {
setSettings(context, settings) {
context.commit('setSettings', settings)
},
setGDriveLinked(context, status) {
context.commit('GoogleDriveLinked', status)
}
}
const mutations = {
setSettings(state, settings) {
state.settings = settings;
},
GoogleDriveLinked(state, status) {
Vue.set(state.settings, 'GDriveLinked', status)
}
}
export default {
namespace: true,
state,
getters,
actions,
mutations
}

View file

@ -2,6 +2,7 @@ import {bus} from '../../main'
import {router} from './../../router' import {router} from './../../router'
import Vue from 'vue'; import Vue from 'vue';
const state = { const state = {
} }
@ -16,7 +17,7 @@ const actions = {
}, },
socket_success(context, data) { socket_success(context, data) {
const {message, user, dms, notifications, currentFriendStatus} = data; const {message, user, dms, notifications, currentFriendStatus, settings} = data;
const friendsArray = user.friends; const friendsArray = user.friends;
const friendObject = {}; const friendObject = {};
@ -44,6 +45,7 @@ const actions = {
} }
context.commit('addAllChannels', channelsObject) context.commit('addAllChannels', channelsObject)
context.dispatch('addAllNotifications', notifications) context.dispatch('addAllNotifications', notifications)
context.dispatch('setSettings', settings)
}, },
@ -100,6 +102,10 @@ const actions = {
['socket_notification:dismiss'](context, data){ ['socket_notification:dismiss'](context, data){
const {channelID} = data; const {channelID} = data;
context.dispatch('dismissNotification', channelID); context.dispatch('dismissNotification', channelID);
},
['socket_googleDrive:linked'](context) {
context.dispatch('setPopoutVisibility', {name: 'GDLinkMenu', visibility: false})
context.dispatch('setGDriveLinked', true)
} }
} }

View file

@ -0,0 +1,45 @@
import {bus} from '../../main'
import {router} from '../../router'
import Vue from 'vue';
const state = {
uploads: {}
}
const getters = {
getAllUploads(state) {
return state.uploads;
}
}
const actions = {
addUpload(context, data) {
context.commit('addUpload', data)
},
updatePercentUpload(context, data) {
context.commit('updatePercentUpload', data)
},
removeUpload(context, tempID) {
context.commit('removeUpload', tempID)
}
}
const mutations = {
updatePercentUpload(state, data) {
Vue.set(state.uploads[data.tempID], 'percent', data.percent);
},
addUpload(state, data) {
Vue.set(state.uploads, data.tempID, data);
},
removeUpload(state, tempID) {
Vue.delete(state.uploads, tempID)
}
}
export default {
namespace: true,
state,
actions,
mutations,
getters
}

View file

@ -2,108 +2,104 @@
<div id="app"> <div id="app">
<vue-headful :title="title" description="Nertivia Chat Client"/> <vue-headful :title="title" description="Nertivia Chat Client"/>
<div class="background-image"></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="panel-layout"> <div class="panel-layout">
<transition name="slidein"> <transition name="slidein">
<LeftPanel class="left-panel" v-click-outside="hideLeftPanel" v-show="$mq === 'mobile' && showLeftPanel || $mq === 'desktop'"> </LeftPanel> <LeftPanel
class="left-panel"
v-click-outside="hideLeftPanel"
v-show="$mq === 'mobile' && showLeftPanel || $mq === 'desktop'"
></LeftPanel>
</transition> </transition>
<RightPanel> </RightPanel> <RightPanel/>
</div> </div>
</div> </div>
</transition> </transition>
<transition name="fade"> <Popouts v-if="loggedIn"/>
<settings v-if="showSettings && loggedIn" />
<!--<GDriveLinkMenu v-if="loggedIn" /> -->
</transition>
</div> </div>
</template> </template>
<script> <script>
import {bus} from '../main' import { bus } from "../main";
import Settings from '@/components/app/Settings.vue' import Popouts from "@/components/app/Popouts.vue";
// import GDriveLinkMenu from '@/components/app/GDriveLinkMenu.vue' import LeftPanel from "./../components/app/LeftPanel.vue";
import LeftPanel from './../components/app/LeftPanel.vue' import RightPanel from "./../components/app/RightPanel.vue";
import RightPanel from './../components/app/RightPanel.vue' import ConnectingScreen from "./../components/app/ConnectingScreen.vue";
import ConnectingScreen from './../components/app/ConnectingScreen.vue'
export default { export default {
name: 'app', name: "app",
components: { components: {
LeftPanel, LeftPanel,
RightPanel, RightPanel,
ConnectingScreen, ConnectingScreen,
Settings,
}, },
data() { data() {
return { return {
showLeftPanel: false, showLeftPanel: false,
showSettings: false,
title: "Nertivia" title: "Nertivia"
} };
}, },
methods: { methods: {
hideLeftPanel(test) { hideLeftPanel(event) {
if (this.showLeftPanel){ if (this.showLeftPanel) {
if(test.target.closest('.show-menu-button') == null){ if (event.target.closest(".show-menu-button") == null) {
this.showLeftPanel = false; this.showLeftPanel = false;
} }
} }
} }
}, },
mounted() { mounted() {
bus.$on('toggleLeftMenu', () => { bus.$on("toggleLeftMenu", () => {
this.showLeftPanel = !this.showLeftPanel; this.showLeftPanel = !this.showLeftPanel;
}) });
bus.$on('closeLeftMenu', () => { bus.$on("closeLeftMenu", () => {
this.showLeftPanel = false; this.showLeftPanel = false;
}) });
bus.$on('openSettings', () => {
this.showSettings = true; bus.$on("title:change", title => {
})
bus.$on('closeSettings', () => {
this.showSettings = false;
})
bus.$on('title:change', (title) => {
this.title = title; this.title = title;
}) });
}, },
computed: { computed: {
loggedIn() { loggedIn() {
return this.$store.getters.loggedIn return this.$store.getters.loggedIn;
} }
} }
} };
</script> </script>
<style scoped> <style scoped>
.slidein-enter-active, .slidein-leave-active { .slidein-enter-active,
transition: .5s; .slidein-leave-active {
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;
} }
.fade-between-two-enter-active, .fade-between-two-leave-active{ .fade-between-two-enter-active,
.fade-between-two-leave-active {
transition: 0.3s; transition: 0.3s;
} }
.fade-between-two-enter, .fade-between-two-leave-to { .fade-between-two-enter,
.fade-between-two-leave-to {
opacity: 0 !important; opacity: 0 !important;
} }
.fade-enter-active,
.fade-leave-active {
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s;
transition: opacity .2s;
} }
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0; opacity: 0;
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.left-panel{ .left-panel {
position: absolute; position: absolute;
top: 47px; top: 47px;
height: calc(100% - 47px); height: calc(100% - 47px);
@ -115,19 +111,18 @@ export default {
<style> <style>
html {
html{
height: 100%; height: 100%;
} }
body{ body {
margin: 0; margin: 0;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
#app { #app {
font-family: 'Roboto', sans-serif; font-family: "Roboto", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
color: #383838; color: #383838;
@ -148,37 +143,34 @@ body{
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: bottom; background-position: bottom;
background-size: cover; background-size: cover;
} }
.panel-layout { .panel-layout {
display: flex; display: flex;
height: 100%; height: 100%;
} }
</style> </style>
<style> <style>
/* ------- SCROLL BAR -------*/ /* ------- SCROLL BAR -------*/
/* width */ /* width */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 10px; width: 10px;
} }
/* Track */ /* Track */
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: #8080806b; background: #8080806b;
} }
/* Handle */ /* Handle */
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #f5f5f559; background: #f5f5f559;
} }
/* Handle on hover */ /* Handle on hover */
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: #f5f5f59e; background: #f5f5f59e;
} }
</style> </style>

View file

@ -0,0 +1,14 @@
<template>
<div>
Redirecting...
</div>
</template>
<script>
export default {
mounted() {
window.opener.postMessage({code: location.href}, "*");
}
}
</script>

View file

@ -312,7 +312,7 @@ button {
margin-right: 0; margin-right: 0;
margin-top: 0; margin-top: 0;
height:calc(100% - 50px); height:calc(100% - 50px);
background-color: rgba(34, 34, 34, 0.877); background-color: rgb(34, 34, 34);
width: 0; width: 0;
overflow-x: hidden; overflow-x: hidden;
transition: 0.5s ease; transition: 0.5s ease;