Added google drive api for file uploading.

This commit is contained in:
supertiger 2019-03-08 10:23:33 +00:00
parent d20c7d381d
commit f052b07a62
28 changed files with 1412 additions and 366 deletions

13
package-lock.json generated
View file

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

@ -10,6 +10,7 @@
"dependencies": {
"@vue/eslint-plugin": "^4.2.0",
"axios": "^0.18.0",
"filesize": "^4.1.2",
"futoji": "^0.2.4",
"jquery": "^3.3.1",
"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,5 +1,5 @@
<template>
<div class="dark-background">
<div class="dark-background" @click="backgroundClick">
<div class="inner">
<div class="text">
To upload files, images or set avatars, You must link your Google Drive account with your Nertivia account.
@ -9,11 +9,52 @@
<div class="arrow">></div>
<div class="image nertivia-img"></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>
</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>
.dark-background {
position: absolute;
@ -42,6 +83,7 @@
font-size: 17px;
padding-left: 10px;
padding-right: 10px;
user-select: none;
}
.images{
display: flex;
@ -56,15 +98,23 @@
}
.nertivia-img {
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{
font-size: 40px;
margin: auto;
padding: 20px;
user-select: none;
}
.GDrive-img {
background-image: url(./../../assets/Google_Drive_logo.png);
}
.buttons{
margin: auto;
display: flex;
}
.button{
display: inline-block;
margin: auto;
@ -74,10 +124,18 @@
cursor: default;
user-select: none;
transition: 0.3s;
margin-left: 10px;
margin-right: 10px;
}
.button:hover {
background:rgb(58, 134, 255);
}
.button.deny {
background: rgb(255, 32, 32);
}
.button.deny:hover {
background: rgb(255, 53, 53);
}
</style>

View file

@ -1,179 +1,284 @@
<template>
<div :class="{message: true, ownMessage: user.uniqueID === $props.uniqueID}">
<div class="profile-picture" :style="`background-image: url(${userAvatar})`"></div>
<div class="triangle">
<div class="triangle-inner"></div>
</div>
<div class="content">
<div class="user-info">
<div class="username">{{this.$props.username}}</div>
<div class="date">{{getDate}}</div>
</div>
<div class="content-message" v-html="formatMessage"></div>
</div>
<div class="sending-status">{{statusMessage}}</div>
</div>
<div :class="{message: true, ownMessage: user.uniqueID === $props.uniqueID}">
<div class="profile-picture" :style="`background-image: url(${userAvatar})`"></div>
<div class="triangle">
<div class="triangle-inner"></div>
</div>
<div class="content">
<div class="user-info">
<div class="username">{{this.$props.username}}</div>
<div class="date">{{getDate}}</div>
</div>
<div class="content-message" v-html="formatMessage"></div>
<div class="file-content" v-if="getFile">
<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>
<script>
import messageFormatter from '@/messageFormatter.js'
import config from '@/config.js'
import friendlyDate from '@/date'
export default {
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;
import messageFormatter from "@/messageFormatter.js";
import config from "@/config.js";
import friendlyDate from "@/date";
import path from "path";
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
}
}
}
export default {
props: [
"message",
"status",
"username",
"avatar",
"date",
"uniqueID",
"files"
],
methods: {
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>
<style scoped>
.message {
margin: 10px;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
.message{
margin: 10px;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
animation: showMessage .3s ease-in-out;
animation: showMessage 0.3s ease-in-out;
}
.ownMessage .triangle-inner{
border-right: 7px solid rgba(184, 184, 184, 0.219);
.ownMessage .triangle-inner {
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 {
from {
transform: translate(0px, 9px);
opacity: 0;
}
from {
transform: translate(0px, 9px);
opacity: 0;
}
}
.profile-picture{
height: 50px;
width: 50px;
background-color: rgba(0, 0, 0, 0.281);
margin: auto;
margin-bottom: 0;
border-radius: 50%;
margin-right: 5px;
margin-left: 0;
flex-shrink: 0;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
.profile-picture {
height: 50px;
width: 50px;
background-color: rgba(0, 0, 0, 0.281);
margin: auto;
margin-bottom: 0;
border-radius: 50%;
margin-right: 5px;
margin-left: 0;
flex-shrink: 0;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
.triangle{
display: flex;
justify-content: bottom;
flex-direction: column;
margin: auto;
margin-left: 0;
margin-right: 0px;
margin-bottom: 8.7px;
.triangle {
display: flex;
justify-content: bottom;
flex-direction: column;
margin: auto;
margin-left: 0;
margin-right: 0px;
margin-bottom: 8.7px;
}
.triangle-inner{
width: 0;
height: 0;
.triangle-inner {
width: 0;
height: 0;
border-top: 1px solid transparent;
border-bottom: 7px solid transparent;
border-right: 7px solid rgba(0, 0, 0, 0.301);
border-top: 1px solid transparent;
border-bottom: 7px solid transparent;
border-right: 7px solid rgba(0, 0, 0, 0.301);
}
.content{
background: rgba(0, 0, 0, 0.301);
padding: 10px;
.content {
background: rgba(0, 0, 0, 0.301);
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;
justify-content: center;
flex-direction: column;
border-radius: 10px;
color: rgb(231, 231, 231);
margin: auto;
margin-left: 0;
margin-right: 0;
transition: 1s;
.image-content {
margin-top: 10px;
padding: 5px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.493);
display: -ms-flexbox;
display: flex;
flex-direction: column;
}
.image-content img {
width: 170px;
height: auto;
transition: 0.2s;
}
.image-content:hover img {
filter: brightness(70%);
}
.user-info {
display: flex;
display: flex;
}
.username {
color: rgb(219, 219, 219);
font-size: 14px;
margin: auto;
margin-left: 0;
margin-right: 0;
color: rgb(219, 219, 219);
font-size: 14px;
margin: auto;
margin-left: 0;
margin-right: 0;
}
.date{
color: rgb(161, 161, 161);
font-size: 10px;
margin: auto;
margin-left: 5px;
.date {
color: rgb(161, 161, 161);
font-size: 10px;
margin: auto;
margin-left: 5px;
}
.content-message {
word-wrap: break-word;
word-break: break-word;
white-space: pre-wrap;
font-size: 14px;
overflow: hidden;
max-width: 100%;
color: white;
overflow-wrap: anywhere;
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;
}
.message .sending-status {
display: flex;
justify-content: flex-end;
flex-direction: column;
padding-bottom: 5px;
margin-left: 10px;
font-size: 15px;
color: white;
align-self: normal;
user-select: none;
transition: 0.5;
display: flex;
justify-content: flex-end;
flex-direction: column;
padding-bottom: 5px;
margin-left: 10px;
font-size: 15px;
color: white;
align-self: normal;
user-select: none;
transition: 0.5;
}
</style>
<style>
.codeblock{
background-color: rgba(0, 0, 0, 0.397);
padding: 5px;
border-radius: 5px;
.codeblock {
background-color: rgba(0, 0, 0, 0.397);
padding: 5px;
border-radius: 5px;
}
</style>

View file

@ -50,7 +50,7 @@ export default {
}
},
openSettings() {
bus.$emit('openSettings');
this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: true})
}
},
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,25 +2,54 @@
<div class="right-panel">
<div class="heading">
<div class="show-menu-button" cli>
<i class="material-icons" @click="toggleLeftMenu">
menu
</i>
<i class="material-icons" @click="toggleLeftMenu">menu</i>
</div>
<div class="current-channel">
<span v-if="!selectedChannelID">Welcome back, {{user.username}}!</span>
<span v-else>{{channelName}}</span>
</div>
<div class="current-channel"><span v-if="!selectedChannelID">Welcome back!</span><span v-else>{{channelName}}</span></div>
</div>
<div class="loading" v-if="selectedChannelID && !messages[selectedChannelID]">
<spinner />
<spinner/>
</div>
<div v-else-if="selectedChannelID" class="message-logs" @wheel="invertScroll">
<div class="scroll">
<message v-for="(msg, index) in messages[selectedChannelID]" :key="index" :date="msg.created" :username="msg.creator.username" :uniqueID="msg.creator.uniqueID" :avatar="msg.creator.avatar" :message="msg.message" :status="msg.status" />
<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>
<news v-else />
<news v-else/>
<div class="chat-input-area" v-if="selectedChannelID">
<div class="message-area">
<textarea class="chat-input" ref="input-box" placeholder="Message" @keydown="chatInput" @input="onInput" v-model="message"></textarea>
<button :class="{'send-button': true, 'error-send-button': messageLength > 5000}" @click="sendMessage">Send</button>
<input type="file" ref="sendFileBrowse" @change="attachmentChange" class="hidden">
<div class="attachment-button" @click="attachmentButton">
<i class="material-icons">attach_file</i>
</div>
<textarea
class="chat-input"
ref="input-box"
placeholder="Message"
@keydown="chatInput"
@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 class="info">
<div class="typing-outer">
@ -28,29 +57,33 @@
<typing-status v-if="typing" :username="whosTyping"/>
</transition>
</div>
<div :class="{'message-count': true, 'error-info': messageLength > 5000 }">{{messageLength}}/5000</div>
<div
:class="{'message-count': true, 'error-info': messageLength > 5000 }"
>{{messageLength}}/5000</div>
</div>
</div>
</div>
</template>
<script>
import messagesService from '@/services/messagesService'
import typingService from '@/services/TypingService'
import {bus} from '../../main'
import JQuery from 'jquery'
let $ = JQuery
import News from '../../components/app/News.vue'
import Message from '../../components/app/MessageTemplate.vue'
import Spinner from '@/components/Spinner.vue'
import TypingStatus from '@/components/app/TypingStatus.vue'
import messagesService from "@/services/messagesService";
import typingService from "@/services/TypingService";
import { bus } from "../../main";
import JQuery from "jquery";
let $ = JQuery;
import News from "../../components/app/News.vue";
import Message from "../../components/app/MessageTemplate.vue";
import Spinner from "@/components/Spinner.vue";
import TypingStatus from "@/components/app/TypingStatus.vue";
import uploadsQueue from "@/components/app/uploadsQueue.vue";
export default {
components: {
Message,
Spinner,
Spinner,
News,
TypingStatus
TypingStatus,
uploadsQueue
},
data() {
return {
@ -59,31 +92,32 @@ export default {
postTimerID: null,
getTimerID: null,
typing: false,
whosTyping: "",
}
whosTyping: ""
};
},
methods:{
toggleLeftMenu(){
bus.$emit('toggleLeftMenu')
methods: {
toggleLeftMenu() {
bus.$emit("toggleLeftMenu");
},
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 ) {
return this.generateNum(max) + this.generateNum(n - max);
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;
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);
return ("" + number).substring(add);
},
async sendMessage(){
this.$refs["input-box"].focus()
async sendMessage() {
this.$refs["input-box"].focus();
this.message = this.message.trim();
if(this.message == "")return;
if (this.message == "") return;
if (this.message.length > 5000) return;
clearInterval(this.postTimerID);
this.postTimerID = null;
@ -92,7 +126,7 @@ export default {
const msg = this.message;
const tempID = this.generateNum(25);
this.$store.dispatch('addMessage', {
this.$store.dispatch("addMessage", {
sender: true,
channelID: this.selectedChannelID,
message: {
@ -101,42 +135,45 @@ export default {
channelID: this.selectedChannelID,
created: new Date()
}
})
});
this.message = ""
this.$store.dispatch('updateChannelLastMessage', this.selectedChannelID);
const { ok, error, result } = await messagesService.post(this.selectedChannelID, {
message: msg,
socketID: this.$socket.id,
tempID
})
if ( ok ) {
const message = result.data.messageCreated
this.message = "";
this.$store.dispatch("updateChannelLastMessage", this.selectedChannelID);
const { ok, error, result } = await messagesService.post(
this.selectedChannelID,
{
message: msg,
socketID: this.$socket.id,
tempID
}
);
if (ok) {
const message = result.data.messageCreated;
message.status = 1;
this.$store.dispatch('replaceMessage', {
this.$store.dispatch("replaceMessage", {
tempID: result.data.tempID,
message
});
} else {
// TODO: Error handling
console.log(error)
console.log(error);
}
},
async postTimer() {
this.postTimerID = setInterval(async () => {
if (this.$refs['input-box'].value.trim() == "") {
if (this.$refs["input-box"].value.trim() == "") {
clearInterval(this.postTimerID);
return this.postTimerID = null;
return (this.postTimerID = null);
}
await typingService.post(this.selectedChannelID);
}, 2000);
},
async onInput(event){
async onInput(event) {
this.messageLength = this.message.length;
const value = event.target.value.trim();
if (value && this.postTimerID == null) {
// Post typing status
this.postTimer()
this.postTimer();
await typingService.post(this.selectedChannelID);
}
},
@ -151,45 +188,98 @@ export default {
}
},
invertScroll(event) {
if(event.deltaY) {
if (event.deltaY) {
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) {
if(this.user.uniqueID === data.message.creator.uniqueID) return;
if (this.user.uniqueID === data.message.creator.uniqueID) return;
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() {
this.$options.sockets.typingStatus = (data) => {
const {channelID, userID} = data;
this.$options.sockets.typingStatus = data => {
const { channelID, userID } = data;
if (channelID !== this.selectedChannelID) return;
this.typing = true;
this.typing = true;
this.whosTyping = this.channel.recipients.find(function(recipient) {
return recipient.uniqueID == userID;
}).username
}).username;
clearTimeout(this.getTimerID);
this.getTimerID = setTimeout(() => {
this.typing = false;
this.typing = false;
}, 2500);
}
bus.$on('newMessage', this.hideTypingStatus)
};
bus.$on("newMessage", this.hideTypingStatus);
//dismiss notification on focus
window.onfocus = () => {
bus.$emit('title:change', "Nertivia");
bus.$emit("title:change", "Nertivia");
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) {
this.$socket.emit('notification:dismiss', {channelID: this.$store.getters.selectedChannelID});
}
}
this.$socket.emit("notification:dismiss", {
channelID: this.$store.getters.selectedChannelID
});
}
};
},
beforeDestroy() {
bus.$off('newMessage', this.hideTypingStatus);
bus.$off("newMessage", this.hideTypingStatus);
delete this.$options.sockets.typingStatus;
},
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() {
return this.$store.getters.user;
},
@ -200,45 +290,46 @@ export default {
return this.$store.getters.messages;
},
selectedChannelID() {
return this.$store.getters.selectedChannelID
return this.$store.getters.selectedChannelID;
},
channelName() {
return this.$store.getters.channelName;
}
}
}
};
</script>
<style scoped>
.hidden {
display: none;
}
.typing-animate-enter-active {
transition: .10s;
transition: 0.1s;
}
.typing-animate-enter /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(3px)
transform: translateY(3px);
}
.typing-animate-leave-to {
opacity: 0;
transform: translateY(-3px)
transform: translateY(-3px);
}
.error-info {
color: red;
}
.heading{
border-bottom: solid 2px white;
margin: 5px;
height: 40px;
padding-bottom: 2spx;
display: flex;
flex-shrink: 0;
.heading {
border-bottom: solid 2px white;
margin: 5px;
height: 40px;
padding-bottom: 2spx;
display: flex;
flex-shrink: 0;
}
.show-menu-button{
.show-menu-button {
display: inline-block;
margin: auto;
color: white;
@ -249,7 +340,7 @@ export default {
display: none;
}
.heading .current-channel{
.heading .current-channel {
color: white;
font-size: 20px;
margin: auto;
@ -259,36 +350,58 @@ export default {
}
.right-panel {
height: 100%;
background-color: rgba(0, 0, 0, 0.507);
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
background-color: rgba(0, 0, 0, 0.507);
flex: 1;
display: flex;
flex-direction: column;
}
.message-logs{
overflow: auto;
flex: 1;
}
.message-logs, .message-logs .scroll {transform: scale(1,-1);}
.loading{
.message-logs {
overflow: auto;
flex: 1;
}
.chat-input-area{
display: flex;
flex-direction: column;
padding-top: 10px;
margin-bottom: 5px;
.message-logs,
.message-logs .scroll {
transform: scale(1, -1);
}
.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;
margin: auto;
}
.chat-input-area .info {
color: rgba(255, 255, 255, 0.466);
font-size: 12px;
margin-left: 25px;
margin-top: 5px;
display: flex;
}
.typing-outer{
.typing-outer {
flex: 1;
height: 20px;
}
@ -297,49 +410,53 @@ export default {
margin-right: 30px;
margin-top: 3px;
}
.message-area{
.message-area {
display: flex;
width: 100%;
}
.chat-input{
font-family: 'Roboto', sans-serif;
background: rgba(0, 0, 0, 0.158);
color: white;
flex: 1;
height: 20px;
padding: 10px;
margin: auto;
margin-left: 20px;
font-size: 15px;
resize: none;
border: none;
outline: none;
padding-left: 10px;
transition: 0.3s;
.chat-input {
font-family: "Roboto", sans-serif;
background: rgba(0, 0, 0, 0.158);
color: white;
width: 100%;
height: 20px;
padding: 10px;
margin: auto;
font-size: 15px;
resize: none;
border: none;
outline: none;
padding-left: 10px;
transition: 0.3s;
}
.chat-input:hover{
background: rgba(0, 0, 0, 0.288);
.chat-input:hover {
background: rgba(0, 0, 0, 0.288);
}
.chat-input:focus{
background: rgba(0, 0, 0, 0.466);
.chat-input:focus {
background: rgba(0, 0, 0, 0.466);
}
.send-button{
font-size: 20px;
color:white;
background: rgba(0, 0, 0, 0.274);
border: none;
outline: none;
margin: auto;
margin-left: 2px;
margin-right: 20px;
height: 40px;
transition: 0.3s;
.send-button {
font-size: 20px;
color: white;
background: rgba(0, 0, 0, 0.274);
border: none;
outline: none;
margin: auto;
margin-left: 2px;
margin-right: 20px;
height: 40px;
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);
}
.error-send-button {
@ -350,7 +467,7 @@ export default {
background-color: rgba(255, 0, 0, 0.294);
}
@media (max-width: 600px) {
.show-menu-button{
.show-menu-button {
display: block;
}
}

View file

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

View file

@ -26,7 +26,7 @@
</template>
<script>
import UploadService from '@/services/UploadService.js'
import AvatarUpload from '@/services/AvatarUpload.js'
import config from '@/config.js'
import {bus} from '@/main'
import path from 'path'
@ -61,7 +61,7 @@ export default {
}
const formData = new FormData();
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) {
this.alert.content = 'Upload failed - Something went wrong. Try again later.';
return this.alert.show = true;
@ -78,7 +78,7 @@ export default {
},
editAvatarBtn() {
if(!this.$store.getters.settings.GDriveLinked) {
return bus.$emit('GDriveLink:show');
return this.$store.dispatch('setPopoutVisibility', {name: 'GDLinkMenu', visibility: true})
}
this.$refs.avatarBrowser.click()
}

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,28 @@
import {instance, wrapper} from './Api';
import filesize from "filesize";
export default {
// TODO: add ?continue=id
get ( channelID ) {
return wrapper(instance().get(`messages/${channelID}`));
},
post (channelID, data) {
return wrapper(instance().post(`messages/${channelID}`, data))
post (channelID, data, onProgress) {
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 ) {
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

@ -6,12 +6,25 @@ import channelModule from './modules/channelModule';
import messageModule from './modules/messageModule';
import notificationsModule from './modules/notificationsModule';
import settingsModule from './modules/settingsModule';
import {router} from './../router'
import uploadFilesModule from './modules/uploadFilesModule';
import popoutsModule from './modules/popoutsModule';
import {
router
} from './../router'
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: { user, channelModule, messageModule, notificationsModule, socketModule,settingsModule },
modules: {
user,
channelModule,
messageModule,
notificationsModule,
socketModule,
settingsModule,
uploadFilesModule,
popoutsModule
},
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

@ -18,12 +18,18 @@ const getters = {
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)
}
}

View file

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