mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-06-13 19:42:01 +00:00
Merge branch 'master' into polish
This commit is contained in:
commit
5045f96103
29 changed files with 1476 additions and 369 deletions
13
package-lock.json
generated
13
package-lock.json
generated
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"filesize": "^4.1.2",
|
||||
"futoji": "^0.2.4",
|
||||
"jquery": "^3.3.1",
|
||||
"socket.io": "^2.2.0",
|
||||
|
|
|
|||
46
src/components/app/DragDropFileUploadDialog.vue
Normal file
46
src/components/app/DragDropFileUploadDialog.vue
Normal 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>
|
||||
|
||||
|
|
@ -1,19 +1,60 @@
|
|||
<template>
|
||||
<div class="dark-background">
|
||||
<div class="dark-background" @click="backgroundClick">
|
||||
<div class="inner">
|
||||
<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 class="images">
|
||||
<div class="image GDrive-img"></div>
|
||||
<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;
|
||||
|
|
@ -22,7 +63,7 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.781);
|
||||
z-index: 1;
|
||||
z-index: 111111;
|
||||
display: flex;
|
||||
}
|
||||
.inner {
|
||||
|
|
@ -30,16 +71,19 @@
|
|||
height: 400px;
|
||||
width: 400px;
|
||||
background: rgb(32, 32, 32);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.text{
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
font-size: 17px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
user-select: none;
|
||||
}
|
||||
.images{
|
||||
display: flex;
|
||||
|
|
@ -54,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;
|
||||
|
|
@ -72,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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default {
|
|||
}
|
||||
},
|
||||
openSettings() {
|
||||
bus.$emit('openSettings');
|
||||
this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: true})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
|
|||
107
src/components/app/Popouts.vue
Normal file
107
src/components/app/Popouts.vue
Normal 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>
|
||||
|
|
@ -2,35 +2,63 @@
|
|||
<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">
|
||||
|
||||
<input type="file" ref="sendFileBrowse" @change="attachmentChange" class="hidden">
|
||||
<div class="attachment-button" @click="attachmentButton">
|
||||
<i class="material-icons">attach_file</i>
|
||||
</div>
|
||||
<textarea
|
||||
rows="1"
|
||||
class="chat-input"
|
||||
rows="1"
|
||||
ref="input-box"
|
||||
placeholder="Message"
|
||||
@keydown="chatInput"
|
||||
|
||||
@keyup="delayedResize"
|
||||
@change="resize"
|
||||
@input="onInput"
|
||||
v-model="message"
|
||||
></textarea>
|
||||
<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 class="info">
|
||||
<div class="typing-outer">
|
||||
|
|
@ -38,29 +66,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 {
|
||||
|
|
@ -69,31 +101,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;
|
||||
|
|
@ -102,7 +135,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: {
|
||||
|
|
@ -111,36 +144,40 @@ 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);
|
||||
},
|
||||
|
||||
resize(event) {
|
||||
let input = this.$refs["input-box"]
|
||||
|
||||
|
|
@ -161,6 +198,13 @@ export default {
|
|||
const value = event.target.value.trim();
|
||||
if (value && this.postTimerID == null) {
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
|
@ -177,45 +221,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;
|
||||
},
|
||||
|
|
@ -226,45 +323,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;
|
||||
|
|
@ -275,7 +373,7 @@ export default {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.heading .current-channel{
|
||||
.heading .current-channel {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
margin: auto;
|
||||
|
|
@ -285,36 +383,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;
|
||||
}
|
||||
|
|
@ -323,52 +443,57 @@ 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;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
max-height: 30vh;
|
||||
|
||||
.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;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
max-height: 30vh;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
|
@ -379,7 +504,7 @@ export default {
|
|||
background-color: rgba(255, 0, 0, 0.294);
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.show-menu-button{
|
||||
.show-menu-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export default {
|
|||
this.title = title;
|
||||
},
|
||||
close() {
|
||||
bus.$emit('closeSettings');
|
||||
this.$store.dispatch('setPopoutVisibility', {name: 'settings', visibility: false})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</div>
|
||||
<div class="options">
|
||||
<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 red" @click="logout">Logout</div>
|
||||
</div>
|
||||
|
|
@ -26,8 +26,9 @@
|
|||
</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'
|
||||
|
||||
export default {
|
||||
|
|
@ -60,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;
|
||||
|
|
@ -74,6 +75,12 @@ export default {
|
|||
changePassword() {
|
||||
this.alert.content = 'Not implemented yet.';
|
||||
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: {
|
||||
|
|
|
|||
62
src/components/app/imageLargePreview.vue
Normal file
62
src/components/app/imageLargePreview.vue
Normal 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>
|
||||
|
||||
|
|
@ -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 ) {
|
||||
|
|
|
|||
287
src/components/app/uploadDialog.vue
Normal file
287
src/components/app/uploadDialog.vue
Normal 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>
|
||||
|
||||
89
src/components/app/uploadsQueue.vue
Normal file
89
src/components/app/uploadsQueue.vue
Normal 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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const config = {
|
||||
devMode:true,
|
||||
breeMode: true,
|
||||
breeMode: false,
|
||||
recaptcha: "",
|
||||
IP: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
]
|
||||
})
|
||||
|
|
@ -3,7 +3,8 @@ import config from '@/config';
|
|||
|
||||
export const instance = () => {
|
||||
return axios.create({
|
||||
baseURL: config.domain
|
||||
baseURL: config.domain,
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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}));
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,26 @@ import socketModule from './modules/socketIOModule';
|
|||
import channelModule from './modules/channelModule';
|
||||
import messageModule from './modules/messageModule';
|
||||
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);
|
||||
|
||||
export const store = new Vuex.Store({
|
||||
modules: { user, channelModule, messageModule, notificationsModule, socketModule },
|
||||
modules: {
|
||||
user,
|
||||
channelModule,
|
||||
messageModule,
|
||||
notificationsModule,
|
||||
socketModule,
|
||||
settingsModule,
|
||||
uploadFilesModule,
|
||||
popoutsModule
|
||||
},
|
||||
state: {
|
||||
|
||||
},
|
||||
|
|
|
|||
55
src/store/modules/popoutsModule.js
Normal file
55
src/store/modules/popoutsModule.js
Normal 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
|
||||
}
|
||||
42
src/store/modules/settingsModule.js
Normal file
42
src/store/modules/settingsModule.js
Normal 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
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import {bus} from '../../main'
|
|||
import {router} from './../../router'
|
||||
import Vue from 'vue';
|
||||
|
||||
|
||||
const state = {
|
||||
|
||||
}
|
||||
|
|
@ -16,7 +17,7 @@ const actions = {
|
|||
},
|
||||
socket_success(context, data) {
|
||||
|
||||
const {message, user, dms, notifications, currentFriendStatus} = data;
|
||||
const {message, user, dms, notifications, currentFriendStatus, settings} = data;
|
||||
const friendsArray = user.friends;
|
||||
const friendObject = {};
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ const actions = {
|
|||
}
|
||||
context.commit('addAllChannels', channelsObject)
|
||||
context.dispatch('addAllNotifications', notifications)
|
||||
context.dispatch('setSettings', settings)
|
||||
|
||||
|
||||
},
|
||||
|
|
@ -100,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
45
src/store/modules/uploadFilesModule.js
Normal file
45
src/store/modules/uploadFilesModule.js
Normal 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
|
||||
}
|
||||
|
|
@ -2,108 +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" />
|
||||
<!--<GDriveLinkMenu v-if="loggedIn" /> -->
|
||||
</transition>
|
||||
<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,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showLeftPanel: false,
|
||||
showSettings: 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;
|
||||
})
|
||||
});
|
||||
},
|
||||
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);
|
||||
|
|
@ -115,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;
|
||||
|
|
@ -148,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>
|
||||
|
||||
|
|
|
|||
14
src/views/GDriveCallback.vue
Normal file
14
src/views/GDriveCallback.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div>
|
||||
Redirecting...
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
window.opener.postMessage({code: location.href}, "*");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue