This commit is contained in:
Troplo 2022-07-29 01:12:29 +10:00
parent 87302afa1d
commit be90a91e52
22 changed files with 2768 additions and 1119 deletions

View file

@ -6,4 +6,5 @@ VUE_APP_MATOMO_URL=https://analytics.flowinity.com
VUE_APP_MATOMO_TRACKER=flow
VUE_APP_MATOMO_DOMAINS=*.troplo.com
VUE_APP_MATOMO_ENABLED=true
VUE_APP_SENTRY_ENABLED=true
VUE_APP_SENTRY_ENABLED=true
VUE_APP_BASE_URL=

View file

@ -34,12 +34,36 @@ module.exports = async function (socket, next) {
next()
}
} else {
//
socket.user = {
id: null,
username: "Not Authenticated"
}
socket.compassUser = {
id: null,
username: "BC-NOAUTH"
}
next()
}
} else {
//
socket.user = {
id: null,
username: "Not Authenticated"
}
socket.compassUser = {
id: null,
username: "BC-NOAUTH"
}
next()
}
} catch (error) {
//
socket.user = {
id: null,
username: "Not Authenticated"
}
socket.compassUser = {
id: null,
username: "BC-NOAUTH"
}
next()
}
}

View file

@ -15,88 +15,115 @@ module.exports = {
id: socket.user.id
}
})
socket.join(user.id)
socket.emit("siteState", {
release: process.env.RELEASE,
notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../../package.json").version
})
const friends = await Friend.findAll({
where: {
userId: user.id,
status: "accepted"
}
})
await user.update({
status:
user.storedStatus === "invisible" ? "offline" : user.storedStatus
})
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
if (user && socket.user.id) {
console.log(socket.user.id)
socket.join(user.id)
socket.emit("siteState", {
release: process.env.RELEASE,
notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../../package.json").version
})
const friends = await Friend.findAll({
where: {
userId: user.id,
status: "accepted"
}
})
await user.update({
status:
user.storedStatus === "invisible" ? "offline" : user.storedStatus
})
})
socket.on("ping", () => {
socket.emit("pong")
})
socket.on("idle", async () => {
const user = await User.findOne({
where: {
id: socket.user.id
}
})
if (user.storedStatus === "online") {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "away"
})
})
io.to(user.id).emit("userStatus", {
userId: user.id,
status: "away"
})
await user.update({
status: "away"
})
}
})
socket.on("online", async () => {
const user = await User.findOne({
where: {
id: socket.user.id
}
})
if (user.storedStatus === "online") {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "online"
})
})
io.to(user.id).emit("userStatus", {
userId: user.id,
status: "online"
})
await user.update({
status: "online"
})
}
})
socket.on("disconnect", async function () {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status:
user.storedStatus === "invisible" ? "offline" : user.storedStatus
})
})
socket.on("ping", () => {
socket.emit("pong")
})
socket.on("bcBots/deleteMessage", (e) => {
if (socket.user.bot) {
socket.to(e.userId).emit("deleteMessage", e)
} else {
socket.emit("bcBots/deleteMessage", {
error: "You cannot perform this action."
})
}
})
socket.on("idle", async () => {
const user = await User.findOne({
where: {
id: socket.user.id
}
})
if (user.storedStatus === "online") {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "away"
})
})
io.to(user.id).emit("userStatus", {
userId: user.id,
status: "away"
})
await user.update({
status: "away"
})
}
})
socket.on("online", async () => {
const user = await User.findOne({
where: {
id: socket.user.id
}
})
if (user.storedStatus === "online") {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "online"
})
})
io.to(user.id).emit("userStatus", {
userId: user.id,
status: "online"
})
await user.update({
status: "online"
})
}
})
socket.on("disconnect", async function () {
friends.forEach((friend) => {
io.to(friend.friendId).emit("userStatus", {
userId: user.id,
status: "offline"
})
})
await user.update({
status: "offline"
})
})
await user.update({
status: "offline"
} else {
socket.join(-1)
socket.emit("siteState", {
release: process.env.RELEASE,
notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../../package.json").version
})
})
socket.emit("unauthorized", {
message: "Please reauth."
})
console.log("Unauthenticated user")
socket.on("reAuth", async () => {
socket.disconnect()
})
}
})
console.log("WS OK")
app.set("io", io)

View file

@ -0,0 +1,43 @@
"use strict"
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable("Nicknames", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.BIGINT
},
userId: {
allowNull: false,
type: Sequelize.BIGINT
},
nickname: {
allowNull: false,
type: Sequelize.STRING
},
friendId: {
allowNull: false,
type: Sequelize.BIGINT
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
})
},
async down(queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
}

View file

@ -0,0 +1,20 @@
"use strict"
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn("Users", "bot", {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
})
},
async down(queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
}

View file

@ -0,0 +1,55 @@
"use strict"
const { Model } = require("sequelize")
module.exports = (sequelize, DataTypes) => {
class Nickname extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
// eslint-disable-next-line no-unused-vars
static associate(models) {
Nickname.belongsTo(models.User, {
foreignKey: "userId",
as: "user"
})
Nickname.belongsTo(models.User, {
foreignKey: "friendId",
as: "userNickname"
})
}
}
Nickname.init(
{
userId: {
allowNull: false,
type: DataTypes.BIGINT
},
nickname: {
allowNull: false,
type: DataTypes.STRING,
validate: {
notEmpty: true,
len: [1, 20]
}
},
friendId: {
allowNull: false,
type: DataTypes.BIGINT
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
}
},
{
sequelize,
modelName: "Nickname"
}
)
return Nickname
}

View file

@ -16,6 +16,10 @@ module.exports = (sequelize, DataTypes) => {
as: "friends",
foreignKey: "userId"
})
User.hasOne(models.Nickname, {
foreignKey: "friendId",
as: "nickname"
})
}
}
User.init(
@ -135,6 +139,11 @@ module.exports = (sequelize, DataTypes) => {
avatar: {
type: DataTypes.STRING,
allowNull: true
},
bot: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
}
},
{

File diff suppressed because it is too large Load diff

View file

@ -342,40 +342,6 @@ router.put("/settings/:type", auth, async (req, res, next) => {
return false
}
}
async function checkPassword(password) {
return new Promise((resolve, reject) => {
axios
.post(
"http://localhost:23994/services/admin.svc/GetApiKey",
{
password: password,
schoolId: req.body.schoolId,
sussiId: req.user.sussiId
},
{
headers: {
compassInstance:
req.header("compassInstance") ||
req.query.compassInstance ||
"devices"
}
}
)
.then((resp) => {
if (resp.data.d) {
resolve(true)
return true
} else {
resolve(false)
return false
}
})
.catch((e) => {
reject(e)
return false
})
})
}
try {
const io = req.app.get("io")
if (req.params.type === "full") {
@ -452,66 +418,6 @@ router.put("/settings/:type", auth, async (req, res, next) => {
} else {
throw Errors.invalidParameter("Password or Code")
}
} else if (req.params.type === "totpConfirm") {
const user = await User.findOne({
where: {
id: req.user.id
}
})
if (user.totp) {
const verified = speakeasy.totp.verify({
secret: user.totp,
encoding: "base32",
token: req.body.code
})
if (verified) {
await User.update(
{
totpEnabled: true
},
{
where: {
id: req.user.id
}
}
)
res.sendStatus(204)
} else {
throw Errors.invalidTotp
}
} else {
throw Errors.unknown
}
} else if (req.params.type === "bcSessionsEnable") {
/*
const match = await checkPassword(req.body.password)
if (match) {
await User.update(
{
bcSessionsEnabled: true
},
{
where: {
id: req.user.id
}
}
)
const session = await Session.create({
session:
"BETTERCOMPASS-" +
cryptoRandomString({
length: 64
}),
compassUserId: req.user.compassUserId,
sussiId: req.user.sussiId,
instance: req.user.instance,
other: {},
userId: req.user.id
})
} else {
throw Errors.invalidCredentials
}*/
throw Errors.experimentsOptIn
} else if (req.params.type === "password") {
const user = await User.findOne({
where: {

View file

@ -15,6 +15,7 @@
"@mdi/js": "^6.6.95",
"@sentry/tracing": "^6.19.3",
"@sentry/vue": "^6.19.3",
"@tauri-apps/api": "^1.0.2",
"apollo-boost": "^0.4.9",
"axios": "^0.26.1",
"brace": "^0.11.1",

View file

@ -404,27 +404,6 @@
</v-dialog>
<v-main>
<Header></Header>
<v-container
v-if="
$store.state.user?.compact === 'nagPending' &&
$vuetify.breakpoint.lgAndDown &&
!$vuetify.breakpoint.mobile
"
>
<v-alert dense text color="info" dismissible v-model="compactModeNag">
Introducing <strong>Compact Mode</strong>, a better experience for
lower resolution devices.
<v-btn
to="/settings/appearance"
text
color="primary"
outlined
class="ml-1"
>
Settings
</v-btn>
</v-alert>
</v-container>
<v-container
v-if="$store.state.site.latestVersion > $store.state.versioning.version"
id="update-notify-banner"
@ -466,10 +445,9 @@
<style></style>
<script>
import AjaxErrorHandler from "@/lib/errorHandler"
import Vue from "vue"
import Vuetify from "@/plugins/vuetify"
import { VueFinalModal } from "vue-final-modal"
import Header from "@/components/Header"
import Vue from "vue"
export default {
name: "App",
components: {
@ -580,35 +558,6 @@ export default {
return index.charAt(0).toUpperCase() + index.slice(1)
}
},
computeColor(event) {
if (this.$vuetify?.theme?.themes) {
if (event.color === "#003300") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarActivityType8
} else if (event.color === "#133897") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarExternalActivity
} else if (event.activityType === 7 || event.color === "#f4dcdc") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarActivityType7
} else if (event.color === "#dce6f4") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarNormalActivity
} else if (event.activityType === 10) {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarActivityType10
} else {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarNormalActivity
}
}
},
editorInit() {
require("brace/ext/language_tools")
require("brace/mode/css")
@ -617,19 +566,6 @@ export default {
require("brace/theme/chrome")
require("brace/snippets/css")
},
baseRole() {
if (this.$store.state.user?.baseRole) {
return (
this.$store.state.user.baseRole
.toLowerCase()
.charAt(0)
.toUpperCase() +
this.$store.state.user.baseRole.toLowerCase().slice(1)
)
} else {
return "Not Authenticated"
}
},
saveSettings() {
this.loading = true
this.$vuetify.theme.dark = this.$store.state.user?.theme === "dark"
@ -643,15 +579,6 @@ export default {
AjaxErrorHandler(this.$store)(e)
})
},
completeGuidedWizard() {
this.loadingGuidedWizard = true
this.$store.dispatch("saveOnlineSettings", {
guidedWizard: false
})
this.$store.dispatch("getUserInfo").then(() => {
this.loadingGuidedWizard = false
})
},
getThemes() {
this.axios.get("/api/v1/themes").then((res) => {
this.themes = res.data.map((theme) => {
@ -692,25 +619,27 @@ export default {
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
},
retryConnection() {
this.connectionLoading = true
this.$store.dispatch("getState").finally(() => {
this.connectionLoading = false
})
},
validate(value, defaultValue) {
if (value === undefined || value === null) {
return defaultValue
} else {
return value
}
}
},
mounted() {
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
if (this.$vuetify.breakpoint.mobile) {
this.$store.state.drawer = false
}
if (localStorage.getItem("cssTipsDismissed")) {
this.cssTips = false
}
if (localStorage.getItem("userPanel")) {
this.$store.state.userPanel = JSON.parse(
localStorage.getItem("userPanel")
)
} else {
this.$store.state.userPanel = true
}
if (this.$vuetify.breakpoint.mobile) {
this.$store.state.userPanel = false
}
window.addEventListener("offline", () => {
this.$store.commit("setOnline", false)
this.$store.dispatch("getState")
@ -721,56 +650,11 @@ export default {
this.$store.dispatch("getUserInfo")
})
this.$socket.connect()
Vue.axios.defaults.headers.common["CompassAPIKey"] =
localStorage.getItem("apiKey")
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("bcToken")
document.title = this.$route.name
? this.$route.name + " - " + this.$store.state.site.name
: this.$store.state.site.name
this.$store.commit("setLoading", true)
this.$vuetify.theme.dark = this.$store.state.user?.theme === "dark" || true
this.axios.defaults.headers.common["compassInstance"] =
localStorage.getItem("schoolInstance")
this.axios.defaults.headers.common["compassSchoolId"] =
localStorage.getItem("schoolId")
if (JSON.parse(localStorage.getItem("userCache"))?.id) {
const user = JSON.parse(localStorage.getItem("userCache"))
const name = user.themeObject.id
const dark = user.themeObject.theme.dark
const light = user.themeObject.theme.light
if (user.accentColor) {
user.themeObject.theme.dark.primary = user.accentColor
user.themeObject.theme.light.primary = user.accentColor
}
if (JSON.parse(localStorage.getItem("subjectsCache"))) {
this.$store.commit(
"setSubjects",
JSON.parse(localStorage.getItem("subjectsCache"))
)
}
Vuetify.framework.theme.themes.dark = dark
Vuetify.framework.theme.themes.light = light
Vuetify.framework.theme.themes.name = name
Vuetify.framework.theme.themes.primaryType =
user.themeObject.theme.primaryType
this.$store.commit(
"setUser",
JSON.parse(localStorage.getItem("userCache"))
)
}
if (localStorage.getItem("calendarCache")?.length) {
this.$store.commit(
"setCalendar",
JSON.parse(localStorage.getItem("calendarCache")).map((event) => {
return {
...event,
start: new Date(event.start),
end: new Date(event.finish)
}
})
)
}
this.$store.dispatch("getState")
this.$store.dispatch("checkAuth").catch(() => {
this.$store.dispatch("logout")
@ -853,9 +737,6 @@ export default {
this.$store.state.site.notification = state.notification
this.$store.state.site.notificationType = state.notificationType
})
setInterval(() => {
this.$socket.emit("ping")
}, 10000)
// eslint-disable-next-line no-undef
if (JSON.parse(process.env.VUE_APP_MATOMO_ENABLED)) {
// eslint-disable-next-line no-undef
@ -870,11 +751,8 @@ export default {
})
},
watch: {
compactModeNag(val) {
if (!val) {
this.$store.state.user.compact = "disabled"
this.$store.dispatch("saveOnlineSettings")
}
"$store.state.userPanel"(val) {
localStorage.setItem("userPanel", val)
},
cssTips(val) {
localStorage.setItem("cssTipsDismissed", !val)
@ -908,7 +786,7 @@ export default {
search() {
if (this.search) {
if (this.search.id) {
this.$router.push("/activity/activity/" + this.search.id)
this.$router.push("/communications/" + this.search.id)
this.$store.commit("setSearch", false)
this.search = null
this.$nextTick(() => {

View file

@ -0,0 +1,292 @@
<template>
<div>
<v-toolbar
elevation="0"
outlined
height="40"
color="card"
v-if="file"
style="cursor: pointer; overflow: hidden"
class="mb-2"
>
<v-toolbar-title>
<v-icon> mdi-attachment </v-icon>
{{ file.name }}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="file = null" small>
<v-icon> mdi-close </v-icon>
</v-btn>
</v-toolbar>
<v-textarea
style="margin-bottom: 5px"
autofocus
label="Type a message"
placeholder="Keep it civil"
type="text"
ref="message-input"
:id="edit ? 'edit-input' : 'message-input'"
outlined
append-outer-icon="mdi-send"
auto-grow
@keyup.up="editLastMessage"
@keyup.esc="handleEsc"
@click:append-outer="handleMessage()"
@keydown.enter.exact.prevent="handleMessage()"
v-model="message"
@paste="handlePaste"
@change="handleChange"
rows="1"
single-line
dense
hide-details
@keydown.enter.shift.exact.prevent="newLine($event)"
>
<template v-slot:append-outer>
<v-btn
icon
small
@click="handleMessage()"
v-if="!edit"
:retain-focus-on-click="false"
class="no-focus"
>
<v-icon> mdi-send </v-icon>
</v-btn>
</template>
<template v-slot:prepend>
<v-file-input
style="margin-top: -18px"
single-line
hide-input
v-model="file"
@change="getURLForImage"
v-if="!edit"
></v-file-input>
</template>
</v-textarea>
</div>
</template>
<script>
import AjaxErrorHandler from "@/lib/errorHandler"
export default {
name: "CommsInput",
props: {
replying: {
type: Object,
default: null
},
edit: {
type: Object,
default: null
},
autoScroll: {
type: Function,
default: () => {}
},
editLastMessage: {
type: Function,
default: () => {}
},
endEdit: {
type: Function,
default: () => {}
},
endSend: {
type: Function,
default: () => {}
},
chat: {
type: Object,
default: null
}
},
data() {
return {
message: "",
file: null,
blobURL: null
}
},
methods: {
handleMessage() {
if (this.edit) {
this.editMessage()
} else {
this.sendMessage()
}
},
editMessage() {
if (this.message.length > 0) {
this.axios
.put(
process.env.VUE_APP_BASE_URL +
"/api/v1/communications/" +
this.$route.params.id +
"/message/edit",
{
id: this.edit.id,
content: this.message
}
)
.then(() => {
this.edit.editing = false
this.edit.id = null
this.edit.content = ""
this.focusInput()
this.endEdit()
// response will be handled via WebSocket
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
}
},
newLine(event) {
this.message = event.target.value + "\n"
},
handleEsc() {
if (!this.edit) {
if (this.replying) {
this.endSend()
}
} else {
this.endEdit()
}
},
getURLForImage() {
const file = this.file
const reader = new FileReader()
reader.onload = (e) => {
this.blobURL = e.target.result
}
reader.readAsDataURL(file)
},
handleChange() {
if (this.$store.state.user.bcUser.storedStatus !== "invisible") {
if (this.typingDate) {
const now = new Date()
if (now - this.typingDate > 5000) {
this.typingDate = now
this.axios.put(
process.env.VUE_APP_BASE_URL +
"/api/v1/communications/" +
this.chat.id +
"/typing"
)
}
} else {
this.typingDate = new Date()
this.axios.put(
process.env.VUE_APP_BASE_URL +
"/api/v1/communications/" +
this.chat.id +
"/typing"
)
}
}
},
handlePaste(data) {
if (data.clipboardData.items.length) {
const item = data.clipboardData.items[0]
if (item.kind === "file") {
this.file = item.getAsFile()
this.getURLForImage()
}
}
},
focusInput() {
const input = document.getElementById("message-input")
input.focus()
},
sendMessage() {
this.focusInput()
let message = this.message
this.message = ""
if (this.file || message.length > 0) {
const emojis = require("../lib/emojis.json")
message = message.replaceAll(
/:([a-zA-Z0-9_\-+]+):/g,
(match, group1) => {
const emoji = emojis.find((emoji) => {
return emoji.aliases.includes(group1)
})
if (emoji) {
return emoji.emoji
} else {
return match
}
}
)
if (!this.file) {
this.axios
.post(
process.env.VUE_APP_BASE_URL +
"/api/v1/communications/" +
this.$route.params.id +
"/message",
{
message: message,
replyId: this.replying?.id
}
)
.then(() => {
this.focusInput()
this.message = ""
this.autoScroll()
this.endSend()
})
.catch((e) => {
console.log(e)
AjaxErrorHandler(this.$store)(e)
})
} else {
const formData = new FormData()
formData.append("message", message)
if (this.replying) {
formData.append("replyId", this.replying.id)
}
formData.append("file", this.file)
this.axios
.post(
process.env.VUE_APP_BASE_URL +
"/api/v1/communications/" +
this.$route.params.id +
"/formData/message",
formData,
{
headers: {
"Content-Type": "multipart/form-data"
}
}
)
.then(() => {
this.focusInput()
this.message = ""
this.autoScroll()
this.endSend()
this.file = null
})
.catch((e) => {
console.log(e)
AjaxErrorHandler(this.$store)(e)
})
}
}
}
},
mounted() {
if (this.edit) {
this.message = this.edit.content
}
}
}
</script>
<style scoped>
.no-focus:focus::before {
opacity: 0 !important;
}
</style>

View file

@ -3,6 +3,39 @@
<v-overlay :value="!$store.state.wsConnected" absolute>
<v-progress-circular indeterminate size="64"></v-progress-circular>
</v-overlay>
<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"
>
<v-list class="rounded-l" v-if="context.user.item">
<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.id)">
<v-list-item-title>Group Settings</v-list-item-title>
</v-list-item>
<v-list-item
@click="
leave.item = context.user.item
leave.dialog = true
"
color="error"
>
<v-list-item-title>Leave Group</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-dialog
v-model="settings.addMembers.dialog"
max-width="400px"
@ -263,6 +296,38 @@
</v-card>
</v-dialog>
<v-app-bar app color="bg">
<v-app-bar-nav-icon
@click.stop="$store.state.drawer = !$store.state.drawer"
v-if="$vuetify.breakpoint.mobile"
></v-app-bar-nav-icon>
<button
style="display: none"
v-shortkey="['ctrl', 'k']"
@shortkey="$store.commit('setSearch', true)"
>
Debug
</button>
<button
style="display: none"
v-shortkey="['meta', 'k']"
@shortkey="$store.commit('setSearch', true)"
>
Debug
</button>
<button
style="display: none"
v-shortkey="['ctrl', 'alt', 'd']"
@shortkey="$store.dispatch('toggleCSS')"
>
Style Toggle
</button>
<button
style="display: none"
v-shortkey="['f9']"
@shortkey="$store.dispatch('toggleCSS')"
>
Style Toggle
</button>
<template v-if="$route.name === 'Communications'">
<v-toolbar-title v-if="$store.state.selectedChat?.chat?.type">
{{
@ -271,7 +336,6 @@
: $store.state.selectedChat?.chat?.name
}}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn
icon
@ -279,11 +343,7 @@
>
<v-icon>mdi-magnify</v-icon>
</v-btn>
<v-btn
icon
@click="$store.state.userPanel = !$store.state.userPanel"
v-if="selected?.chat?.type === 'group'"
>
<v-btn icon @click="$store.state.userPanel = !$store.state.userPanel">
<v-icon>mdi-account-group</v-icon>
</v-btn>
</template>
@ -293,148 +353,147 @@
</v-toolbar-title>
</template>
</v-app-bar>
<v-navigation-drawer color="bg" floating app style="max-height: 100%">
<v-navigation-drawer
color="bg"
floating
app
style="max-height: 100%"
v-model="$store.state.drawer"
:width="$vuetify.breakpoint.mobile ? 270 : 320"
>
<v-container>
<v-btn
color="toolbar"
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
class="rounded-xl"
filled
solo
label="Search..."
append-icon="mdi-magnify"
background-color="toolbar"
style="margin-bottom: -18px"
elevation="2"
></v-text-field>
<v-toolbar color="toolbar" class="rounded-xl mb-3" elevation="2">
<v-toolbar-title class="subtitle-1">
CHATS ({{ $store.state.chats.length }})
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="dialogs.new = true">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-toolbar>
<v-card height="100%" color="transparent" class="mt-2" elevation="0">
<v-list height="100%" two-line color="transparent" elevation="0">
<v-list-item-group v-model="selected">
<template v-for="(item, index) in $store.state.chats">
<v-list-item
:key="item.title"
:to="'/communications/' + item.id"
<v-list dense nav id="comms-sidebar-list">
<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="toolbar"
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
class="rounded-xl"
filled
solo
label="Search..."
append-icon="mdi-magnify"
background-color="toolbar"
style="margin-bottom: -18px"
elevation="2"
></v-text-field>
<v-toolbar color="toolbar" class="rounded-xl mb-3" elevation="2">
<v-toolbar-title class="subtitle-1">
CHATS ({{ $store.state.chats.length }})
</v-toolbar-title>
<v-spacer></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 $store.state.chats" :key="item.id">
<template>
<v-list-item
:to="'/communications/' + item.id"
@contextmenu="
show($event, 'user', getDirectRecipient(item), item.id)
"
>
<v-badge
bordered
bottom
:color="getStatus(item)"
v-if="item.chat.type === 'direct'"
dot
offset-x="24"
offset-y="20"
>
<v-badge
bordered
bottom
:color="getStatus(item)"
v-if="item.chat.type === 'direct'"
dot
offset-x="24"
offset-y="26"
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<v-icon v-if="item.chat.type === 'group'">
mdi-account-group
</v-icon>
<v-img
v-else-if="
item.chat.type === 'direct' &&
getDirectRecipient(item).avatar
"
:src="'/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 dot color="none" v-else>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<v-icon v-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).username }}
<v-badge
v-if="
getLastRead(item).count >= 1 &&
$route.params.id !== item.id.toString()
"
color="red"
class="ml-2"
:content="getLastRead(item).count"
>
</v-badge>
</v-list-item-title>
<v-list-item-title v-else>
{{ item.chat.name }}
<v-badge
v-if="
getLastRead(item).count >= 1 &&
$route.params.id !== item.id.toString()
"
color="red"
class="ml-2"
:content="getLastRead(item).count"
>
</v-badge>
</v-list-item-title>
<v-icon v-if="item.chat.type === 'group'">
mdi-account-group
</v-icon>
<v-img
v-else-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 dot color="none" v-else>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<v-icon v-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-icon
@click="
leave.item = item
leave.dialog = true
"
>
mdi-exit-to-app
</v-icon>
</v-list-item-action>
<v-list-item-action>
<v-icon
@click="
settings.item = item
settings.dialog = true
"
>
mdi-cog
</v-icon>
</v-list-item-action>
</template>
</v-list-item>
<v-divider
v-if="index < $store.state.chats.length - 1"
:key="index"
></v-divider>
</template>
</v-list-item-group>
<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-badge>
</v-list-item-action>
</template>
</v-list-item>
</template>
</v-list>
</v-card>
</v-list>
</v-container>
<template v-slot:append>
<v-card tile color="card" elevation="0">
<v-card tile color="bg" elevation="0">
<v-overlay :value="!$store.state.wsConnected" absolute>
<v-progress-circular indeterminate size="48"></v-progress-circular>
</v-overlay>
@ -522,11 +581,19 @@
<script>
import AjaxErrorHandler from "@/lib/errorHandler"
import NicknameDialog from "@/components/NicknameDialog"
import Vue from "vue"
export default {
name: "Header",
components: { NicknameDialog },
data() {
return {
nickname: {
dialog: false,
nickname: "",
user: {}
},
copyTooltip: false,
settings: {
dialog: false,
@ -538,6 +605,28 @@ export default {
},
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
}
},
selected: [2],
loading: true,
leave: {
@ -556,6 +645,23 @@ export default {
}
},
methods: {
groupSettings(id) {
this.settings.item = this.$store.state.chats.find(
(chat) => chat.id === id
)
this.settings.dialog = true
},
show(e, context, item, id) {
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].id = id
this.$nextTick(() => {
this.context[context].value = true
})
},
setStatus(status) {
const previousStatus = {
status: this.$store.state.user.status,
@ -740,13 +846,30 @@ export default {
}
},
getDirectRecipient(item) {
const user = item.chat.users.find(
let user = item.chat.users.find(
(user) => user.id !== this.$store.state.user.id
)
if (user) {
return user
if (user.nickname?.nickname) {
user.name = user.nickname.nickname
} else {
user.name = user.username
}
return {
...user,
type: item.chat.type
}
} else {
return item.chat.users[0]
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() {
@ -756,7 +879,6 @@ export default {
users: this.newConversation.users
})
.then(() => {
this.getChats()
this.newConversation.name = ""
this.newConversation.users = []
this.newConversation.loading = false
@ -774,10 +896,10 @@ export default {
searchUsers() {
this.newConversation.loading = true
this.axios
.get("/api/v1/communications/search?query=" + this.newConversation.name)
.get("/api/v1/communications/search?query=")
.then((res) => {
this.newConversation.loading = false
this.newConversation.results.push(...res.data)
this.newConversation.results = res.data
})
.catch(() => {
this.newConversation.loading = false
@ -785,12 +907,10 @@ export default {
},
searchUsersForGroupAdmin() {
this.axios
.get(
"/api/v1/communications/search?query=" + this.settings.addMembers.name
)
.get("/api/v1/communications/search?query=")
.then((res) => {
if (this.settings.item) {
this.settings.addMembers.results.push(...res.data)
this.settings.addMembers.results = res.data
this.settings.addMembers.results =
this.settings.addMembers.results.filter(
(user) =>
@ -802,6 +922,8 @@ export default {
}
},
mounted() {
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
this.searchUsers()
this.searchUsersForGroupAdmin()
this.$store.dispatch("getChats")
@ -834,7 +956,6 @@ export default {
)
if (chat) {
const index = this.$store.state.chats.indexOf(chat)
this.$store.state.chats[index].chat.lastMessages.unshift(message)
this.$store.state.chats.splice(index, 1)
this.$store.state.chats.unshift(chat)
}

View file

@ -0,0 +1,84 @@
<template>
<v-dialog v-model="nickname.dialog" width="500px">
<v-card>
<v-card-title>
<span class="headline">{{ nickname.user.username }}</span>
</v-card-title>
<v-card-text>
<v-text-field
v-model="nickname.nickname"
label="Nickname"
required
@keyup.enter="setFriendNickname"
></v-text-field>
<small>Friend nicknames only show to you.</small>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="blue darken-1"
text
@click="
nickname.dialog = false
nickname.nickname = ''
nickname.user = {}
"
>
Cancel
</v-btn>
<v-btn color="blue darken-1" text @click="setFriendNickname">
Apply
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import AjaxErrorHandler from "@/lib/errorHandler"
export default {
name: "NicknameDialog",
props: ["nickname"],
methods: {
setFriendNickname() {
this.axios
.post(
process.env.VUE_APP_BASE_URL +
"/api/v1/communications/nickname/" +
this.nickname.user.id,
{
nickname: this.nickname.nickname
}
)
.then((res) => {
this.$store.state.chats.forEach((item) => {
item.chat.associations.forEach((a) => {
if (a.user.id === this.nickname.user.id) {
a.user.nickname = {
nickname: res.data.nickname
}
}
})
item.chat.users.forEach((u) => {
if (u.id === this.nickname.user.id) {
u.nickname = {
nickname: res.data.nickname
}
}
})
})
this.nickname.dialog = false
this.nickname.nickname = ""
this.nickname.user = {}
this.$toast.success("Nickname changed successfully.")
})
.catch((e) => {
AjaxErrorHandler(this.$store)(e)
})
}
}
}
</script>
<style scoped></style>

View file

@ -2,8 +2,14 @@ module.exports = function (vuex) {
return function (res, ignorePathErrorCb) {
let errors = []
if (res.response === undefined || res.response.data.errors === undefined) {
if (res.response.status === 429) {
if (
res?.response === undefined ||
res?.response?.data?.errors === undefined
) {
if (vuex.state.site.release === "dev") {
console.error(res)
}
if (res?.response?.status === 429) {
vuex._vm.$toast.error(
"You are being rate limited, retry in " +
res.response.headers["ratelimit-reset"] +

View file

@ -4,17 +4,37 @@ import Vuetify from "../plugins/vuetify"
import AjaxErrorHandler from "@/lib/errorHandler"
Vue.use(Vuex)
function getDirectRecipient(context, item) {
let user = item.chat.users.find((user) => user.id !== context.state.user.id)
if (user) {
if (user.nickname?.nickname) {
user.name = user.nickname.nickname
} else {
user.name = user.username
}
return user
} else {
let user = item.chat.users[0]
if (user.nickname?.nickname) {
user.name = user.nickname.nickname
} else {
user.name = user.username
}
return user
}
}
export default new Vuex.Store({
state: {
online: true,
selectedChat: null,
chats: [],
baseURL: process.env.VUE_APP_BASE_URL,
versioning: {
date: process.env.VUE_APP_BUILD_DATE,
version: process.env.VUE_APP_VERSION,
release: process.env.RELEASE
},
drawer: true,
site: {
release: "stable",
loading: true
@ -97,7 +117,7 @@ export default new Vuex.Store({
state.modals.search = value
},
addChat(state, chat) {
state.chats.push(chat)
state.chats.unshift(chat)
},
updateQuickSwitchCache(state, value) {
state.quickSwitchCache.push({
@ -119,34 +139,16 @@ export default new Vuex.Store({
})
},
getCommunicationsUnread(context) {
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
Vue.axios.get("/api/v1/communications").then((res) => {
context.state.communicationNotifications = 0
res.data.forEach((item) => {
const message = item.chat.lastMessages.find(
(message) => message.id === item.lastRead
)
const lastMessage = item.chat.lastMessages.find(
(message) => message.userId !== context.state.user.id
)
let count
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
}
count = value
} else if (!message) {
count = item.chat.lastMessages.length
} else {
count = 0
}
context.state.communicationNotifications += count
Vue.axios
.get(process.env.VUE_APP_BASE_URL + "/api/v1/communications")
.then((res) => {
context.commit("setChats", res.data)
context.dispatch("updateQuickSwitch")
context.state.communicationNotifications = 0
res.data.forEach((item) => {
context.state.communicationNotifications += item.unread
})
})
})
},
discardTheme(context) {
context.state.themeEngine.theme = {
@ -323,9 +325,12 @@ export default new Vuex.Store({
})
context.state.chats.forEach((chat) => {
context.commit("updateQuickSwitchCache", {
...chat,
customType: 2,
route: "/communications/" + chat.id
id: chat.id,
subjectLongName:
chat.chat.type === "group"
? chat.chat.name
: getDirectRecipient(context, chat).name,
customType: 3
})
})
},

File diff suppressed because it is too large Load diff

View file

@ -21,16 +21,13 @@
<v-list-item-avatar
@click="userProfile(user)"
style="cursor: pointer"
:color="$vuetify.theme.themes.dark.primary"
>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<v-img
:src="'/usercontent/' + user.avatar"
v-if="user.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-img
:src="'/usercontent/' + user.avatar"
v-if="user.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-list-item-content
@ -90,16 +87,15 @@
v-for="friend in computePendingIncoming"
:key="friend.id"
>
<v-list-item-avatar @click="userProfile(friend.user2)">
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<img
:src="'/usercontent/' + friend.user2.avatar"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-list-item-avatar
@click="userProfile(friend.user2)"
:color="$vuetify.theme.themes.dark.primary"
>
<v-img
:src="'/usercontent/' + friend.user2.avatar"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-list-item-content @click="userProfile(friend.user2)">
@ -136,16 +132,13 @@
<v-list-item-avatar
@click="userProfile(friend.user2)"
style="cursor: pointer"
:color="$vuetify.theme.themes.dark.primary"
>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<img
:src="'/usercontent/' + friend.user2.avatar"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-img
:src="'/usercontent/' + friend.user2.avatar"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-list-item-content
@ -175,16 +168,13 @@
<v-list-item-avatar
@click="userProfile(friend.user2)"
style="cursor: pointer"
:color="$vuetify.theme.themes.dark.primary"
>
<v-list-item-avatar
:color="$vuetify.theme.themes.dark.primary"
>
<img
:src="'/usercontent/' + friend.user2.avatar"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-img
:src="'/usercontent/' + friend.user2.avatar"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
</v-list-item-avatar>
<v-list-item-content>

View file

@ -118,6 +118,7 @@ export default {
this.$store.commit("setToken", res.data.session)
await this.$store.dispatch("getUserInfo")
this.loading = false
this.$socket.disconnect()
this.$socket.connect()
this.$router.push("/")
})

View file

@ -1,27 +1,5 @@
<template>
<div id="login" v-if="!$store.state.user?.bcUser?.id">
<v-dialog v-model="totpDialog" max-width="500px">
<v-card color="card">
<v-toolbar color="toolbar">
<v-toolbar-title> Two-Factor Authentication </v-toolbar-title>
</v-toolbar>
<v-container>
<v-card-text
>You are seeing this because you have enabled Two-Factor
Authentication on BetterCompass.<br />
Please check your phone, or authenticator app to obtain the 6 digit
code and enter it here.</v-card-text
>
<v-otp-input
length="6"
@keyup.enter="doLogin()"
@finish="doLogin()"
v-model="totp"
:disabled="loading"
></v-otp-input>
</v-container>
</v-card>
</v-dialog>
<div :class="{ outer: !$vuetify.breakpoint.mobile }">
<div :class="{ middle: !$vuetify.breakpoint.mobile }">
<div :class="{ innerLogin: !$vuetify.breakpoint.mobile }">
@ -121,6 +99,7 @@ export default {
this.$store.commit("setToken", res.data.session)
await this.$store.dispatch("getUserInfo")
this.loading = false
this.$socket.disconnect()
this.$socket.connect()
this.$router.push("/")
})

View file

@ -179,12 +179,7 @@ export default {
sheet: "#181818",
text: "#000000",
dark: "#151515",
bg: "#151515",
calendarNormalActivity: "#3f51b5",
calendarActivityType7: "#f44336",
calendarActivityType8: "#4caf50",
calendarActivityType10: "#ff9800",
calendarExternalActivity: "#2196f3"
bg: "#151515"
},
light: {
primary: "#0190ea",
@ -199,68 +194,14 @@ export default {
sheet: "#f8f8f8",
text: "#000000",
dark: "#f8f8f8",
bg: "#f8f8f8",
calendarNormalActivity: "#3f51b5",
calendarActivityType7: "#f44336",
calendarActivityType8: "#4caf50",
calendarActivityType10: "#ff9800",
calendarExternalActivity: "#2196f3"
bg: "#f8f8f8"
}
},
createTheme: false,
createThemeCSS: false,
name: "",
creatorType: "create",
themes: [],
fakeEvents: [
{
name: "English",
content: "English",
color: "#dce6f4",
start: new Date().setHours(9, 0, 0, 0),
end: new Date().setHours(10, 0, 0, 0),
timed: true,
activityType: 1,
activityId: 0,
calendarId: 0
},
{
name: "Maths",
content: "Maths",
color: "#f4dcdc",
start: new Date().setHours(10, 0, 0, 0),
end: new Date().setHours(11, 0, 0, 0),
timed: true,
activityType: 1,
activityId: 0,
instanceId: 0,
calendarId: 0
},
{
name: "Lunch Break",
content: "Lunch Break",
color: "#dce6f4",
start: new Date().setHours(11, 0, 0, 0),
end: new Date().setHours(11, 30, 0, 0),
timed: true,
activityType: 1,
activityId: 0,
instanceId: 0,
calendarId: 0
},
{
name: "Excursion",
content: "Excursion",
color: "#dce6f4",
start: new Date().setHours(11, 0, 0, 0),
end: new Date().setHours(13, 30, 0, 0),
timed: true,
activityType: 1,
activityId: 0,
instanceId: 0,
calendarId: 0
}
]
themes: []
}
},
computed: {
@ -308,33 +249,6 @@ div {
document.head.appendChild(style)
this.$store.dispatch("saveOnlineSettings")
},
computeColor(event) {
if (event.color === "#003300") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarActivityType8
} else if (event.color === "#133897") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarExternalActivity
} else if (event.activityType === 7 || event.color === "#f4dcdc") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarActivityType7
} else if (event.color === "#dce6f4") {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarNormalActivity
} else if (event.activityType === 10) {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarActivityType10
} else {
return this.$vuetify.theme.themes[
this.$store.state.user.theme || "dark"
].calendarNormalActivity
}
},
editorInit() {
require("brace/ext/language_tools")
require("brace/mode/css")
@ -357,18 +271,7 @@ div {
sheet: "#" + Math.floor(Math.random() * 16777215).toString(16),
text: "#" + Math.floor(Math.random() * 16777215).toString(16),
dark: "#" + Math.floor(Math.random() * 16777215).toString(16),
bg: "#" + Math.floor(Math.random() * 16777215).toString(16),
calendarNormalActivity:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarActivityType7:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarActivityType8:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarActivityType10:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarExternalActivity: Math.floor(Math.random() * 16777215).toString(
16
)
bg: "#" + Math.floor(Math.random() * 16777215).toString(16)
}
this.creator.dark = {
primary: "#" + Math.floor(Math.random() * 16777215).toString(16),
@ -383,18 +286,7 @@ div {
sheet: "#" + Math.floor(Math.random() * 16777215).toString(16),
text: "#" + Math.floor(Math.random() * 16777215).toString(16),
dark: "#" + Math.floor(Math.random() * 16777215).toString(16),
bg: "#" + Math.floor(Math.random() * 16777215).toString(16),
calendarNormalActivity:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarActivityType7:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarActivityType8:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarActivityType10:
"#" + Math.floor(Math.random() * 16777215).toString(16),
calendarExternalActivity: Math.floor(Math.random() * 16777215).toString(
16
)
bg: "#" + Math.floor(Math.random() * 16777215).toString(16)
}
},
doDeleteTheme(theme) {
@ -500,17 +392,7 @@ div {
a.dispatchEvent(e)
},
friendlyName(index) {
if (index === "calendarNormalActivity") {
return "Standard Class"
} else if (index === "calendarActivityType7") {
return "Relief Event"
} else if (index === "calendarActivityType8") {
return "Generic Type 8"
} else if (index === "calendarActivityType10") {
return "Learning Task"
} else if (index === "calendarExternalActivity") {
return "External Activity"
} else if (index === "bg") {
if (index === "bg") {
return "Background"
} else if (index === "dark") {
return "Sidebar & Header"
@ -581,7 +463,6 @@ div {
}
},
mounted() {
console.log(document.querySelectorAll(".editor__toolbar"))
this.defineAccent = this.$store.state.user.accentColor !== null
this.accent = this.$store.state.user.accentColor
this.name = this.$vuetify.theme.themes.name

View file

@ -1194,6 +1194,11 @@
resolved "https://registry.yarnpkg.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87"
integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==
"@tauri-apps/api@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.0.2.tgz#5228720e35d50fd08df87067dc29e7306c1f7a10"
integrity sha512-yuNW0oeJ1/ZA7wNF1KgxhHrSu5viPVzY/UgUczzN5ptLM8dH15Juy5rEGkoHfeXGju90Y/l22hi3BtIrp/za+w==
"@types/body-parser@*":
version "1.19.2"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"