Colubrina/frontend/src/components/Header.vue

1408 lines
44 KiB
Vue

<template>
<div v-if="$store.state.user.username">
<NicknameDialog :nickname="nickname" />
<v-menu
v-model="context.user.value"
:position-x="context.user.x"
:position-y="context.user.y"
absolute
offset-y
class="rounded-l"
style="z-index: 20"
>
<v-list v-if="context.user.item" class="rounded-l">
<v-menu
:nudge-right="10"
:close-delay="100"
:close-on-content-click="false"
:close-on-click="false"
bottom
offset-x
open-on-hover
>
<template #activator="{ on }">
<v-list-item v-on="on">
<v-list-item-title>Notification Settings</v-list-item-title
><v-icon class="ml-2"> mdi-arrow-right </v-icon>
</v-list-item>
</template>
<div>
<v-list>
<v-list-item @click="setNotifications('all')">
<v-list-item-title>All messages</v-list-item-title>
<v-icon v-if="context.user.raw.notifications === 'all'">
mdi-check
</v-icon>
</v-list-item>
<v-list-item two-line @click="setNotifications('mentions')">
<v-list-item-content>
<v-list-item-title>
Mentions only
<v-icon
v-if="context.user.raw.notifications === 'mentions'"
style="float: right"
>
mdi-check
</v-icon>
</v-list-item-title>
<v-list-item-subtitle>
Mentions are performed when your username is sent in the
chat.
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item @click="setNotifications('none')">
<v-list-item-title>None</v-list-item-title>
<v-icon v-if="context.user.raw.notifications === 'none'">
mdi-check
</v-icon>
</v-list-item>
</v-list>
</div>
</v-menu>
<v-list-item
v-if="context.user.item.type === 'direct'"
@click="
nickname.user = context.user.item
nickname.dialog = true
"
>
<v-list-item-title>Change Friend Nickname</v-list-item-title>
</v-list-item>
<v-list-item @click="groupSettings(context.user.raw.id)">
<v-list-item-title>Group Settings</v-list-item-title>
</v-list-item>
<v-list-item color="error" @click="groupLeave(context.user.raw.id)">
<v-list-item-title>Leave Group</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-dialog
v-if="settings.item"
v-model="settings.addMembers.dialog"
max-width="400px"
>
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title>
Add User to {{ settings.item.chat.name }}
</v-toolbar-title>
<v-spacer />
<v-btn icon @click.native="settings.addMembers.dialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-container>
<v-autocomplete
v-model="settings.addMembers.users"
:items="settings.addMembers.results"
:search-input.sync="settings.addMembers.name"
filled
chips
color="blue-grey lighten-2"
label="Select"
item-text="username"
item-value="id"
multiple
>
<template #selection="data">
<v-chip
v-bind="data.attrs"
:input-value="data.selected"
close
@click="data.select"
@click:close="remove(data.item)"
>
<v-avatar v-if="data.item.avatar" left>
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
/>
</v-avatar>
@{{ data.item.username }}
</v-chip>
</template>
<template #item="data">
<v-avatar v-if="data.item.avatar" left class="mr-3 mb-2 mt-2">
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
/>
</v-avatar>
<v-avatar v-else left class="mr-3 mb-2 mt-2">
<v-icon>mdi-account</v-icon>
</v-avatar>
<v-list-item-content>
@{{ data.item.username }}
</v-list-item-content>
</template>
</v-autocomplete>
</v-container>
<v-card-actions>
<v-spacer />
<v-btn color="error" text @click="settings.addMembers.dialog = false">
Cancel
</v-btn>
<v-btn
color="primary"
text
:disabled="!settings.addMembers.users.length"
@click="addMembersToGroup"
>
Add
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-if="settings.item" v-model="settings.dialog" max-width="500px">
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title>Group Settings</v-toolbar-title>
<v-spacer />
<v-btn icon @click.native="settings.dialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-container>
<v-alert v-if="settings.item.rank !== 'admin'" text type="info">
You need to be an administrator of this group to change settings.
</v-alert>
<v-card-title v-if="settings.item.rank === 'admin'">
General
</v-card-title>
<v-text-field
v-if="
settings.item.chat.type === 'group' &&
settings.item.rank === 'admin'
"
v-model="settings.item.chat.name"
label="Group Name"
/>
<v-file-input
ref="avatarUpload"
v-model="settings.avatar"
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
placeholder="Avatar"
prepend-icon=""
label="Profile Picture"
@change="doUpload"
/>
<v-card-title>
Members
<v-btn
v-if="settings.item.rank === 'admin'"
icon
@click.native="settings.addMembers.dialog = true"
>
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-card-title>
<v-list>
<v-list-item
v-for="user in settings.item.chat.associations"
:key="user.id"
>
<v-list-item-avatar :color="$vuetify.theme.themes.dark?.primary">
<v-img
v-if="user.user.avatar"
:src="
$store.state.baseURL + '/usercontent/' + user.user.avatar
"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>
{{ user.user.username }}
<v-btn v-if="user.rank === 'admin'" text icon>
<v-icon> mdi-gavel </v-icon>
</v-btn>
</v-list-item-title>
</v-list-item-content>
<v-list-item-action v-if="settings.item.rank === 'admin'">
<v-tooltip top>
<template #activator="{ on, attrs }">
<div v-bind="attrs" v-on="on">
<v-btn icon @click="giveUserAdmin(user)">
<v-icon>mdi-account-arrow-up</v-icon>
</v-btn>
</div>
</template>
<span>Promote user to group admin (CANNOT BE UNDONE!)</span>
</v-tooltip>
</v-list-item-action>
<v-list-item-action v-if="settings.item.rank === 'admin'">
<v-btn icon @click="removeUserFromGroup(user)">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list>
<v-btn
v-if="
settings.item.chat.type === 'group' &&
settings.item.rank === 'admin'
"
text
color="primary"
@click="saveGroupSettings"
>
Save
</v-btn>
</v-container>
</v-card>
</v-dialog>
<v-dialog v-model="leave.dialog" max-width="400px">
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title>Are you sure you want to leave?</v-toolbar-title>
<v-spacer />
<v-btn
icon
@click.native="
leave.dialog = false
leave.item = null
"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-container>
<p>
You will not be able to rejoin this group unless you are added back
manually.
</p>
</v-container>
<v-card-actions>
<v-spacer />
<v-btn color="primary" text @click.native="leave.dialog = false">
Cancel
</v-btn>
<v-btn color="error" text @click.native="leaveGroup()"> Leave </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogs.new" max-width="500px">
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title>New Communication</v-toolbar-title>
<v-spacer />
<v-btn icon @click.native="dialogs.new = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-container>
<v-autocomplete
v-model="newConversation.users"
:items="newConversation.results"
:search-input.sync="newConversation.name"
filled
chips
color="blue-grey lighten-2"
label="Select"
item-text="username"
item-value="id"
multiple
>
<template #selection="data">
<v-chip
v-bind="data.attrs"
:input-value="data.selected"
close
@click="data.select"
@click:close="remove(data.item)"
>
<v-avatar v-if="data.item.avatar" left>
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
/>
</v-avatar>
@{{ data.item.username }}
</v-chip>
</template>
<template #item="data">
<v-avatar v-if="data.item.avatar" left class="mr-3 mb-2 mt-2">
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
/>
</v-avatar>
<v-avatar v-else left class="mr-3 mb-2 mt-2">
<v-icon>mdi-account</v-icon>
</v-avatar>
<v-list-item-content>
@{{ data.item.username }}
</v-list-item-content>
</template>
</v-autocomplete>
<small
>If the person you want to add doesn't appear, ensure you are
friends with them. You can add additional friends with the Friends
button located on the top left.</small
>
</v-container>
<v-card-actions>
<v-spacer />
<v-btn color="error" text @click="dialogs.new = false">
Cancel
</v-btn>
<v-btn
color="primary"
:loading="newConversation.loading"
text
:disabled="!newConversation.users.length"
@click="createConversation"
>
Create
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="shortcuts" width="700">
<v-card color="card" elevation="7">
<v-toolbar color="toolbar">
<v-toolbar-title> Shortcuts </v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-container>
<v-layout row wrap>
<v-card class="mx-2 mt-2">
<v-card-title> QuickSwitcher </v-card-title>
<v-card-text class="text-center">
<v-btn text outlined> CTRL </v-btn>
<v-btn text outlined class="ml-2"> K </v-btn>
</v-card-text>
</v-card>
<v-card class="mx-2 mt-2">
<v-card-title> RouteSwitcher </v-card-title>
<v-card-text class="text-center">
<v-btn text outlined> CTRL </v-btn>
<v-btn text outlined class="ml-2"> B </v-btn>
</v-card-text>
</v-card>
<v-card class="mx-2 mt-2">
<v-card-title> Shortcuts </v-card-title>
<v-card-text class="text-center">
<v-btn text outlined class="ml-2"> CTRL </v-btn>
<v-btn text outlined class="ml-2"> / </v-btn>
</v-card-text>
</v-card>
<v-card class="mx-2 mt-2">
<v-card-title> Toggle CSS </v-card-title>
<v-card-text class="text-center">
<v-btn text outlined> F9 </v-btn>
<span class="ml-2">or</span>
<v-btn text outlined class="ml-2"> CTRL </v-btn>
<v-btn text outlined class="ml-2"> ALT </v-btn>
<v-btn text outlined class="ml-2"> D </v-btn>
</v-card-text>
</v-card>
</v-layout>
</v-container>
</v-card-text>
</v-card>
</v-dialog>
<v-dialog v-model="feedback.modal" width="700">
<v-card color="card" elevation="7">
<v-toolbar color="toolbar">
<v-toolbar-title> Provide Feedback </v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" md="4" sm="6">
Rating:
<v-rating
v-model="feedback.rating"
background-color="grey darken-1"
color="yellow darken-3"
empty-icon="$ratingFull"
hover
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="feedback.text"
class="rounded-xl"
label="Enter your Feedback"
required
autofocus
placeholder="Enter your Feedback"
/>
</v-col>
<small
>Your feedback will be used to make
{{ $store.state.site.name }} even better.</small
>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
class="rounded-xl"
color="blue darken-1"
text
@click="feedback.modal = false"
>
Close
</v-btn>
<v-btn
class="rounded-xl"
color="blue darken-1"
text
@click="submitFeedback()"
>
Submit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="route.modal" width="700">
<v-card color="card" elevation="7">
<v-toolbar color="toolbar">
<v-toolbar-title> Go to Route </v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-container>
<v-text-field
v-model="route.value"
class="rounded-xl"
autofocus
label="Route"
required
@keyup.enter="goToRoute()"
/>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn class="rounded-xl" text @click="route.modal = false">
Close
</v-btn>
<v-btn
class="rounded-xl"
color="blue darken-1"
text
@click="goToRoute()"
>
Go
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-app-bar app color="dark" elevation="5" style="z-index: 15">
<v-app-bar-nav-icon
v-if="$vuetify.breakpoint.mobile || !$store.state.drawer"
@click.stop="$store.state.drawer = !$store.state.drawer"
/>
<button
v-shortkey="['ctrl', 'k']"
style="display: none"
@shortkey="$store.commit('setSearch', true)"
>
Debug
</button>
<button
v-shortkey="['meta', 'k']"
style="display: none"
@shortkey="$store.commit('setSearch', true)"
>
Debug
</button>
<button
v-shortkey="['ctrl', 'alt', 'd']"
style="display: none"
@shortkey="$store.dispatch('toggleCSS')"
>
Style Toggle
</button>
<button
v-shortkey="['f9']"
style="display: none"
@shortkey="$store.dispatch('toggleCSS')"
>
Style Toggle
</button>
<button
v-shortkey="['ctrl', 'f']"
style="display: none"
@shortkey="$store.state.searchPanel = true"
>
Debug
</button>
<button
v-shortkey="['ctrl', 'b']"
style="display: none"
@shortkey="route.modal = true"
>
Debug
</button>
<button
v-shortkey="['ctrl', '/']"
style="display: none"
@shortkey="shortcuts = true"
>
Debug
</button>
<template v-if="$route.name === 'Communications'">
<v-toolbar-title v-if="chat?.chat?.type">
{{
chat?.chat?.type === "direct"
? getDirectRecipient(chat).username
: chat?.chat?.name
}}
</v-toolbar-title>
<v-spacer />
<v-tooltip bottom>
<template #activator="{ on }">
<v-btn icon v-on="on" @click="feedback.modal = true">
<v-icon>mdi-bug</v-icon>
</v-btn>
</template>
<span>Provide Feedback</span>
</v-tooltip>
<v-tooltip bottom>
<template #activator="{ on }">
<v-btn
id="pin-button"
v-model="$store.state.context.pins.value"
icon
@click="show($event, 'pins', null, null, true)"
v-on="on"
>
<v-icon>mdi-pin-outline</v-icon>
</v-btn>
</template>
<span>Chat Pins</span>
</v-tooltip>
<v-tooltip bottom>
<template #activator="{ on }">
<v-btn
icon
@click="$store.state.searchPanel = !$store.state.searchPanel"
v-on="on"
>
<v-icon>mdi-magnify</v-icon>
</v-btn>
</template>
<span>Search Messages</span>
</v-tooltip>
<v-tooltip bottom>
<template #activator="{ on }">
<v-btn
icon
v-on="on"
@click="$store.state.userPanel = !$store.state.userPanel"
>
<v-icon>mdi-account-group-outline</v-icon>
</v-btn>
</template>
<span>Toggle member list</span>
</v-tooltip>
</template>
<template v-else>
<v-toolbar-title
v-if="!$vuetify.breakpoint.mobile"
id="bettercompass-title"
:style="
'color: ' +
$vuetify.theme.themes[$vuetify.theme.dark ? 'dark' : 'light']
?.primary
"
class="troplo-title"
style="cursor: pointer"
@click="$router.push('/')"
>
{{ $store.state.site.name }} </v-toolbar-title
><v-app-bar-nav-icon
v-if="
!$vuetify.breakpoint.mobile &&
$store.state.site.release !== 'stable'
"
style="z-index: 1000"
disabled
>
{{ $store.state.site.release }}
</v-app-bar-nav-icon>
<v-spacer />
<v-tooltip bottom>
<template #activator="{ on }">
<v-btn icon v-on="on" @click="feedback.modal = true">
<v-icon>mdi-bug</v-icon>
</v-btn>
</template>
<span>Provide Feedback</span>
</v-tooltip>
</template>
</v-app-bar>
<v-navigation-drawer
v-model="$store.state.drawer"
color="dark"
app
floating
style="max-height: 100%; z-index: 15"
class="elevation-5"
:width="$vuetify.breakpoint.mobile ? 270 : 320"
>
<v-container>
<v-list id="comms-sidebar-list" dense nav>
<template v-if="$vuetify.breakpoint.mobile">
<v-btn
color="toolbar"
to="/communications/friends"
width="48%"
class="mb-3 mr-1 rounded-xl"
elevation="2"
>
<v-icon left> mdi-account-multiple </v-icon>
Friends
</v-btn>
<v-btn
color="toolbar"
width="48%"
class="mb-3 ml-1 rounded-xl"
elevation="2"
@click="dialogs.new = true"
>
<v-icon left> mdi-plus </v-icon>
New
</v-btn>
</template>
<template v-else>
<v-btn
color="sheet"
to="/communications/friends"
block
class="mb-3 rounded-xl"
elevation="2"
>
<v-icon left> mdi-account-multiple </v-icon>
Friends
</v-btn>
<v-text-field
v-model="search"
class="rounded-xl"
filled
solo
label="Search..."
append-icon="mdi-magnify"
background-color="sheet"
style="margin-bottom: -18px"
elevation="2"
autocomplete="none"
/>
<v-toolbar color="sheet" class="rounded-xl mb-3" elevation="2">
<v-toolbar-title class="subtitle-1">
CHATS ({{ chats.length }})
</v-toolbar-title>
<v-spacer />
<v-btn icon @click="dialogs.new = true">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-toolbar>
</template>
<v-list v-for="item in chats" :key="item.id">
<template>
<v-list-item
:to="'/communications/' + item.id"
@contextmenu="
show($event, 'user', getDirectRecipient(item), item)
"
>
<v-badge
v-if="item.chat.type === 'direct'"
bordered
bottom
:color="getStatus(item)"
dot
offset-x="24"
offset-y="20"
>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark?.primary"
>
<v-img
v-if="
item.chat.type === 'direct' &&
getDirectRecipient(item).avatar
"
:src="
$store.state.baseURL +
'/usercontent/' +
getDirectRecipient(item).avatar
"
/>
<v-icon v-else-if="item.chat.type === 'direct'">
mdi-account
</v-icon>
</v-list-item-avatar>
</v-badge>
<v-badge v-else dot color="none">
<v-list-item-avatar
:color="$vuetify.theme.themes.dark?.primary"
>
<v-img
v-if="item.chat.type === 'group' && item.chat.icon"
:src="
$store.state.baseURL + '/usercontent/' + item.chat.icon
"
/>
<v-icon v-else-if="item.chat.type === 'group'">
mdi-account-group
</v-icon>
</v-list-item-avatar>
</v-badge>
<template>
<v-list-item-content>
<v-list-item-title v-if="item.chat.type === 'direct'">
{{ getDirectRecipient(item).name }}
</v-list-item-title>
<v-list-item-title v-else>
<span>
{{ item.chat.name }}
</span>
</v-list-item-title>
<v-list-item-subtitle v-if="item.chat.type === 'group'">
{{ item.chat.users.length }} Members
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action
v-if="
item.unread >= 1 &&
$route.params.id !== item.id.toString()
"
>
<v-badge color="red" inline :content="item.unread" />
</v-list-item-action>
</template>
</v-list-item>
</template>
</v-list>
</v-list>
</v-container>
<template #append>
<v-card tile color="bg" elevation="0">
<v-divider />
<v-list-item>
<v-menu top offset-y>
<template #activator="{ on, attrs }">
<v-badge
bordered
bottom
:color="getStatusForUser()"
dot
offset-x="26"
offset-y="19"
v-bind="attrs"
v-on="on"
>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark?.primary"
v-bind="attrs"
v-on="on"
>
<v-img
v-if="$store.state.user.avatar"
:src="
$store.state.baseURL +
'/usercontent/' +
$store.state.user.avatar
"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
</v-badge>
</template>
<v-list>
<v-list-item @click="setStatus('online')">
<v-list-item-title>Online</v-list-item-title>
</v-list-item>
<v-list-item @click="setStatus('away')">
<v-list-item-title>Idle</v-list-item-title>
</v-list-item>
<v-list-item two-line @click="setStatus('busy')">
<v-list-item-content>
<v-list-item-title>Do not Disturb</v-list-item-title>
<v-list-item-subtitle class="text-wrap">
You will not receive any notifications.
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item two-line @click="setStatus('invisible')">
<v-list-item-content>
<v-list-item-title>Invisible</v-list-item-title>
<v-list-item-subtitle class="text-wrap">
You will appear as offline, and the typing indicator will
be disabled.
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
<v-tooltip v-model="copyTooltip" top>
<template #activator="{ attrs }">
<v-list-item-content
v-bind="attrs"
style="cursor: pointer; min-width: 100px"
@click="copyUsername"
>
<v-list-item-title>
{{ $store.state.user.username }}
</v-list-item-title>
<v-list-item-subtitle>
{{ $store.state.user.storedStatus }}
</v-list-item-subtitle>
</v-list-item-content>
</template>
<span>Copied!</span>
</v-tooltip>
<v-spacer />
<v-btn
v-if="$store.state.user.admin"
icon
text
to="/admin"
style="margin-right: 0; padding-right: 0"
>
<v-icon>mdi-gavel</v-icon>
</v-btn>
<v-btn icon text to="/settings">
<v-icon>mdi-cog</v-icon>
</v-btn>
</v-list-item>
</v-card>
</template>
</v-navigation-drawer>
</div>
</template>
<script>
import AjaxErrorHandler from "@/lib/errorHandler.js"
import NicknameDialog from "@/components/NicknameDialog"
import Vue from "vue"
export default {
name: "Header",
components: { NicknameDialog },
data() {
return {
shortcuts: false,
route: {
modal: false,
value: ""
},
feedback: {
modal: false,
route: "",
rating: 0,
text: ""
},
search: "",
nickname: {
dialog: false,
nickname: "",
user: {}
},
copyTooltip: false,
settings: {
dialog: false,
avatar: null,
addMembers: {
dialog: false,
users: [],
results: [],
name: ""
},
item: null
},
context: {
user: {
value: false,
x: null,
y: null,
item: null,
id: 0
},
userPopout: {
value: false,
x: null,
y: null,
item: null,
id: 0
},
message: {
value: false,
x: null,
y: null,
item: null,
id: 0
}
},
selected: [2],
loading: true,
leave: {
item: null,
dialog: false
},
newConversation: {
name: "",
users: [],
loading: false,
results: []
},
dialogs: {
new: false
}
}
},
computed: {
chat() {
try {
return this.$store.state.chats.find(
(item) => item.id === parseInt(this.$route.params.id)
)
} catch {
return null
}
},
chats() {
if (!this.search?.length) {
return this.$store.state.chats
}
return this.$store.state.chats.filter((item) => {
return (
(item.chat.type === "group" &&
item.chat.name.toLowerCase().includes(this.search.toLowerCase())) ||
(item.chat.type === "direct" &&
this.getDirectRecipient(item)
.name.toLowerCase()
.includes(this.search.toLowerCase()))
)
})
}
},
watch: {
$route(to) {
this.feedback.route = to.path
}
},
mounted() {
this.feedback.route = this.$route.path
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("token")
this.searchUsers()
this.searchUsersForGroupAdmin()
this.$store.dispatch("getChats")
this.$socket.on("friendUpdate", () => {
this.searchUsers()
this.searchUsersForGroupAdmin()
})
this.$socket.on("siteState", () => {
this.searchUsers()
this.searchUsersForGroupAdmin()
})
this.$socket.on("userSettings", () => {
this.$store.dispatch("getChats")
})
this.$socket.on("chatUpdated", () => {
this.$store.dispatch("getChats")
})
this.$socket.on("chatAdded", (chat) => {
this.$store.commit("addChat", chat)
})
this.$socket.on("userStatus", (event) => {
this.$store.state.chats.forEach((item) => {
item.chat.associations.forEach((a) => {
if (a.user.id === event.userId) {
a.user.status = event.status
}
})
item.chat.users.forEach((u) => {
if (u.id === event.userId) {
u.status = event.status
}
})
})
})
this.$socket.on("message", (message) => {
const chat = this.$store.state.chats.find(
(item) => item.chatId === message.chatId
)
if (chat) {
const index = this.$store.state.chats.indexOf(chat)
this.$store.state.chats.splice(index, 1)
this.$store.state.chats.unshift(chat)
}
})
this.$socket.on("readChat", (chat) => {
const item = this.$store.state.chats.find((item) => item.id === chat.id)
if (item) {
const index = this.$store.state.chats.indexOf(item)
console.log(this.$store.state.chats[index].lastRead)
this.$store.state.chats[index].lastRead = chat.lastRead
this.$store.state.communicationNotifications = 0
this.$store.state.chats.forEach((item) => {
this.$store.state.communicationNotifications +=
this.getLastRead(item).count
})
}
})
},
methods: {
goToRoute() {
if (this.route.value === "forceEnableDevMode") {
this.$store.state.site.release = "dev"
localStorage.setItem("forceEnableDevMode", "true")
this.$toast.success("OK")
return
}
this.$router.push(this.route.value)
this.route.modal = false
this.route.value = ""
},
doUpload() {
if (this.settings.avatar) {
let formData = new FormData()
formData.append("avatar", this.settings.avatar)
this.axios
.post(
"/api/v1/communications/avatar/" + this.settings.item.id,
formData,
{
headers: {
"Content-Type": "multipart/form-data"
}
}
)
.then(() => {
this.$toast.success("Avatar updated")
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
}
},
submitFeedback() {
this.axios
.post("/api/v1/feedback", {
text: this.feedback.text,
starRating: this.feedback.rating,
route: this.feedback.route
})
.then(() => {
this.feedback.text = ""
this.feedback.rating = 0
this.feedback.modal = false
this.$toast.success("Thank you for making a better Colubrina.")
})
.catch(() => {
this.$toast.error(
"Something went wrong while submitting feedback, you should submit feedback about this."
)
})
},
setNotifications(value) {
this.axios
.put("/api/v1/communications/settings/" + this.context.user.raw.id, {
notifications: value
})
.then(() => {
this.context.user.raw.notifications = value
this.$store.dispatch("getChats")
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
},
groupSettings(id) {
this.settings.item = this.$store.state.chats.find(
(chat) => chat.id === id
)
this.settings.dialog = true
},
groupLeave(id) {
this.leave.item = this.$store.state.chats.find((chat) => chat.id === id)
this.leave.dialog = true
},
show(e, context, item, raw, state) {
if (!state) {
e.preventDefault()
this.context[context].value = false
this.context[context].x = e.clientX
this.context[context].y = e.clientY
this.context[context].item = item
this.context[context].raw = raw
this.$nextTick(() => {
this.context[context].value = true
})
} else {
e.preventDefault()
this.$store.state.context[context].value = false
this.$store.state.context[context].x = e.clientX
this.$store.state.context[context].y = e.clientY
this.$store.state.context[context].item = item
this.$store.state.context[context].raw = raw
this.$nextTick(() => {
this.$store.state.context[context].value = true
})
}
},
setStatus(status) {
const previousStatus = {
status: this.$store.state.user.status,
storedStatus: this.$store.state.user.storedStatus
}
this.$store.state.user.status = status
this.$store.state.user.storedStatus = status
this.axios
.put("/api/v1/user/settings/status", {
status: status
})
.then((res) => {
this.$store.state.user.status = res.data.status
this.$store.state.user.storedStatus = res.data.storedStatus
})
.catch((e) => {
if (e.response.data.status) {
this.$store.state.user.status = e.response.data.status
this.$store.state.user.storedStatus = e.response.data.storedStatus
} else {
AjaxErrorHandler(this.$store)(e)
this.$store.state.user.status = previousStatus.status
this.$store.state.user.storedStatus = previousStatus.storedStatus
}
})
},
copyUsername() {
navigator.clipboard.writeText(this.$store.state.user.username)
this.copyTooltip = true
setTimeout(() => {
this.copyTooltip = false
}, 1000)
},
getStatusForUser() {
if (this.$store.state.user.storedStatus === "online") {
return "green"
} else if (this.$store.state.user.storedStatus === "invisible") {
return "grey"
} else if (this.$store.state.user.storedStatus === "busy") {
return "red"
} else if (this.$store.state.user.storedStatus === "away") {
return "orange"
} else {
return "grey"
}
},
removeUserFromGroup(user) {
this.axios
.delete("/api/v1/associations/" + this.settings.item.id + "/" + user.id)
.then(() => {
this.$toast.success("User has been removed from the group.")
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
},
giveUserAdmin(user) {
this.axios
.put("/api/v1/associations/" + this.settings.item.id + "/" + user.id, {
rank: "admin"
})
.then(() => {
this.$toast.success("User has been promoted to admin.")
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
},
saveGroupSettings() {
if (this.settings.item.chat.name.length < 1) {
this.$toast.error("Group name must be at least 1 character long.")
return
}
this.settings.item.loading = true
this.axios
.put("/api/v1/communications/" + this.settings.item.id, {
name: this.settings.item.chat.name
})
.then(() => {
this.settings.item.loading = false
this.$toast.success("Group settings saved.")
this.settings.dialog = false
})
.catch((e) => {
this.settings.item.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
viewport() {
return window.innerHeight
},
addMembersToGroup() {
this.axios
.post("/api/v1/associations/" + this.settings.item.chat.id, {
users: this.settings.addMembers.users
})
.then(() => {
this.settings.item.chat.users = this.settings.item.chat.users.concat(
this.settings.addMembers.users
)
this.settings.addMembers.dialog = false
this.settings.addMembers.users = []
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
},
leaveGroup() {
this.axios
.delete("/api/v1/associations/" + this.leave.item.id)
.then(() => {
this.leave.dialog = false
this.$store.state.chats = this.$store.state.chats.filter(
(chat) => chat.id !== this.leave.item.id
)
this.$router.push("/communications/friends")
this.leave.item = null
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
},
getLastRead(item) {
const message = item?.chat?.lastMessages?.find(
(message) => message.id === item.lastRead
)
const lastMessage = item?.chat?.lastMessages?.find(
(message) => message.userId !== this.$store.state.user.id
)
if (message && lastMessage) {
const index = item.chat.lastMessages.indexOf(message)
const indexLast = item.chat.lastMessages.indexOf(lastMessage)
let value = index - indexLast
if (value < 0) {
value = 0
}
return {
count: value,
lastMessageTimestamp: lastMessage.createdAt
}
} else if (!message) {
return {
count: item.chat.lastMessages?.length,
lastMessageTimestamp: lastMessage?.createdAt
}
} else if (!lastMessage) {
return {
count: 0,
lastMessageTimestamp: undefined
}
} else {
return {
count: item.chat.lastMessages.length,
lastMessageTimestamp: lastMessage?.createdAt
}
}
},
getStatus(item) {
if (this.getDirectRecipient(item).status === "online") {
return "green"
} else if (this.getDirectRecipient(item).status === "offline") {
return "grey"
} else if (this.getDirectRecipient(item).status === "away") {
return "orange"
} else if (this.getDirectRecipient(item).status === "busy") {
return "red"
} else {
return "grey"
}
},
getDirectRecipient(item) {
let user = item.chat.users.find(
(user) => user.id !== this.$store.state.user.id
)
if (user) {
if (user.nickname?.nickname) {
user.name = user.nickname.nickname
} else {
user.name = user.username
}
return {
...user,
type: item.chat.type
}
} else {
let user = item.chat.users[0]
if (user.nickname?.nickname) {
user.name = user.nickname.nickname
} else {
user.name = user.username
}
return {
...user,
type: item.chat.type
}
}
},
createConversation() {
this.newConversation.loading = true
this.axios
.post(process.env.VUE_APP_BASE_URL + "/api/v1/communications/create", {
users: this.newConversation.users
})
.then((res) => {
this.newConversation.name = ""
this.newConversation.users = []
this.newConversation.loading = false
this.dialogs.new = false
this.$router.push("/communications/" + res.data.id)
})
.catch((e) => {
this.newConversation.loading = false
AjaxErrorHandler(this.$store)(e)
})
},
remove(item) {
const index = this.newConversation.users.indexOf(item.id)
if (index >= 0) this.newConversation.users.splice(index, 1)
},
searchUsers() {
this.newConversation.loading = true
this.axios
.get("/api/v1/communications/search?query=")
.then((res) => {
this.newConversation.loading = false
this.newConversation.results = res.data
})
.catch(() => {
this.newConversation.loading = false
})
},
searchUsersForGroupAdmin() {
this.axios
.get("/api/v1/communications/search?query=")
.then((res) => {
this.settings.addMembers.results = res.data
this.settings.addMembers.results =
this.settings.addMembers.results.filter(
(user) =>
!this.settings.item.chat.users.find((u) => u.id === user.id)
)
})
.catch(() => {})
}
}
}
</script>
<style scoped></style>