This commit is contained in:
Troplo 2022-07-31 23:57:47 +10:00
parent 7d0141d2bf
commit 970c0021c3
12 changed files with 1831 additions and 83 deletions

4
frontend/.gitignore vendored
View file

@ -20,4 +20,6 @@ pnpm-debug.log*
backend/node_modules
backend/config/config.json
backend/.env
backend/.env
#Electron-builder output
/dist_electron

View file

@ -1,15 +1,18 @@
{
"name": "colubrina-chat",
"version": "1.0.10",
"version": "1.0.11",
"private": true,
"author": "Troplo <troplo@troplo.com>",
"license": "GPL-3.0",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"postinstall": "patch-package"
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "patch-package && electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@babel/preset-env": "^7.17.10",
"@mdi/js": "^6.6.95",
@ -62,11 +65,14 @@
"@vue/cli-service": "~4.5.15",
"babel-eslint": "^10.1.0",
"dotenv-webpack": "^7.1.0",
"electron": "^13.0.0",
"electron-devtools-installer": "^3.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"material-design-icons-iconfont": "^6.5.0",
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"vue-cli-plugin-electron-builder": "~2.1.1",
"vue-cli-plugin-vuetify": "~2.4.7",
"vue-template-babel-compiler": "^1.1.3",
"vue-template-compiler": "^2.6.11",
@ -74,6 +80,7 @@
"webpack-auto-inject-version-next": "1.2.4",
"webpack-bundle-analyzer": "^4.5.0"
},
"license": "GPL-3.0",
"resolutions": {
"node-ipc": "git+https://git.troplo.com/Troplo/node-ipc",
"selfsigned": "^2.0.1",

View file

@ -729,8 +729,22 @@ export default {
}
},
mounted() {
Vue.axios.defaults.headers.common["X-Colubrina"] = true
Vue.axios.defaults.headers.common["Authorization"] =
localStorage.getItem("session")
console.log(localStorage.getItem("instance"))
if (process.env.IS_ELECTRON) {
this.axios.defaults.baseURL = localStorage.getItem("instance")
this.$store.state.baseURL = localStorage.getItem("instance")
}
if (localStorage.getItem("customHeaders")) {
console.log(JSON.parse(localStorage.getItem("customHeaders")))
for (let header in JSON.parse(localStorage.getItem("customHeaders"))) {
Vue.axios.defaults.headers[header] = JSON.parse(
localStorage.getItem("customHeaders")
)[header]
}
}
if (this.$vuetify.breakpoint.mobile) {
this.$store.state.drawer = false
}

View file

@ -0,0 +1,82 @@
"use strict"
import { app, protocol, BrowserWindow } from "electron"
import { createProtocol } from "vue-cli-plugin-electron-builder/lib"
import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer"
const isDevelopment = process.env.NODE_ENV !== "production"
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: "app", privileges: { secure: true, standard: true } }
])
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 1400,
height: 1000,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
preload: "window-preload.js",
webSecurity: false
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol("app")
// Load the index.html when not in development
win.loadURL("app://./index.html")
}
}
// Quit when all windows are closed.
app.on("window-all-closed", () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit()
}
})
app.on("activate", () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error("Vue Devtools failed to install:", e.toString())
}
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === "win32") {
process.on("message", (data) => {
if (data === "graceful-exit") {
app.quit()
}
})
} else {
process.on("SIGTERM", () => {
app.quit()
})
}
}

View file

@ -67,14 +67,22 @@
@click:close="remove(data.item)"
>
<v-avatar left v-if="data.item.avatar">
<v-img :src="'/usercontent/' + data.item.avatar"></v-img>
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
></v-img>
</v-avatar>
@{{ data.item.username }}
</v-chip>
</template>
<template v-slot:item="data">
<v-avatar left v-if="data.item.avatar" class="mr-3 mb-2 mt-2">
<v-img :src="'/usercontent/' + data.item.avatar"></v-img>
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
></v-img>
</v-avatar>
<v-avatar left v-else class="mr-3 mb-2 mt-2">
<v-icon>mdi-account</v-icon>
@ -142,7 +150,9 @@
>
<v-list-item-avatar :color="$vuetify.theme.themes.dark.primary">
<v-img
:src="'/usercontent/' + user.user.avatar"
:src="
$store.state.baseURL + '/usercontent/' + user.user.avatar
"
v-if="user.user.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
@ -249,14 +259,22 @@
@click:close="remove(data.item)"
>
<v-avatar left v-if="data.item.avatar">
<v-img :src="'/usercontent/' + data.item.avatar"></v-img>
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
></v-img>
</v-avatar>
@{{ data.item.username }}
</v-chip>
</template>
<template v-slot:item="data">
<v-avatar left v-if="data.item.avatar" class="mr-3 mb-2 mt-2">
<v-img :src="'/usercontent/' + data.item.avatar"></v-img>
<v-img
:src="
$store.state.baseURL + '/usercontent/' + data.item.avatar
"
></v-img>
</v-avatar>
<v-avatar left v-else class="mr-3 mb-2 mt-2">
<v-icon>mdi-account</v-icon>
@ -527,7 +545,11 @@
>
<v-img
v-if="$store.state.user.avatar"
:src="'/usercontent/' + $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>

View file

@ -164,7 +164,7 @@
@click="setImagePreview(embed)"
contain
:aspect-ratio="16 / 9"
:src="embed.mediaProxyLink"
:src="$store.state.baseURL + embed.mediaProxyLink"
>
<template v-slot:placeholder>
<v-row

View file

@ -175,7 +175,7 @@ const routes = [
]
const router = new VueRouter({
mode: "history",
mode: process.env.IS_ELECTRON ? "hash" : "history",
base: process.env.BASE_URL,
routes
})

View file

@ -855,6 +855,7 @@ export default {
},
props: ["chat", "loading", "items"],
data: () => ({
interval: null,
pins: [],
pinsLoading: true,
reachedTop: false,
@ -1361,7 +1362,7 @@ export default {
document
.getElementById("message-list")
.addEventListener("scroll", this.scrollEvent)
setInterval(() => {
this.interval = setInterval(() => {
this.typing()
if (
document.hasFocus() &&
@ -1494,6 +1495,7 @@ export default {
destroyed() {
document.removeEventListener("keypress", this.focusKey)
document.removeEventListener("scroll", this.scrollEvent)
clearInterval(this.interval)
}
}
</script>

View file

@ -24,7 +24,9 @@
:color="$vuetify.theme.themes.dark.primary"
>
<v-img
:src="'/usercontent/' + user.avatar"
:src="
$store.state.baseURL + '/usercontent//' + user.avatar
"
v-if="user.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
@ -102,7 +104,11 @@
:color="$vuetify.theme.themes.dark.primary"
>
<v-img
:src="'/usercontent/' + friend.user2.avatar"
:src="
$store.state.baseURL +
'/usercontent//' +
friend.user2.avatar
"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
@ -152,7 +158,11 @@
:color="$vuetify.theme.themes.dark.primary"
>
<v-img
:src="'/usercontent/' + friend.user2.avatar"
:src="
$store.state.baseURL +
'/usercontent//' +
friend.user2.avatar
"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>
@ -195,7 +205,11 @@
:color="$vuetify.theme.themes.dark.primary"
>
<v-img
:src="'/usercontent/' + friend.user2.avatar"
:src="
$store.state.baseURL +
'/usercontent//' +
friend.user2.avatar
"
v-if="friend.user2.avatar"
/>
<v-icon v-else> mdi-account </v-icon>

View file

@ -33,6 +33,29 @@
<span class="troplo-title">{{ $store.state.site.name }}</span
><small style="font-size: 15px">beta</small>
</p>
<v-text-field
@keyup.enter="doLogin()"
class="rounded-xl"
v-model="instance"
v-if="isElectron()"
label="Instance URL"
placeholder="https://colubrina.troplo.com"
type="email"
></v-text-field>
<small style="float: right" v-if="isElectron()">{{
instanceString
}}</small
><br v-if="isElectron()" />
<v-text-field
@keyup.enter="doLogin()"
class="rounded-xl"
v-model="customHeaders[header.name]"
v-for="header in $store.state.site.customHeaders"
:key="header.name"
:label="header.friendlyName"
:placeholder="header.placeholder"
type="email"
></v-text-field>
<v-text-field
@keyup.enter="doLogin()"
class="rounded-xl"
@ -93,20 +116,30 @@ export default {
name: "Login",
data() {
return {
instanceString: "",
rememberMe: true,
username: "",
password: "",
totp: "",
totpDialog: false,
loading: false
loading: false,
instance: "https://colubrina.troplo.com",
customHeaders: {}
}
},
methods: {
isElectron() {
return process.env.IS_ELECTRON
},
viewport() {
return window.innerHeight
},
doLogin() {
this.loading = true
localStorage.setItem("customHeaders", JSON.stringify(this.customHeaders))
for (let header in this.customHeaders) {
Vue.axios.defaults.headers[header] = this.customHeaders[header]
}
this.axios
.post("/api/v1/user/login", {
password: this.password,
@ -115,9 +148,12 @@ export default {
totp: this.totp
})
.then(async (res) => {
localStorage.setItem("session", res.data.session)
Vue.axios.defaults.headers.common["Authorization"] = res.data.session
this.$store.commit("setToken", res.data.session)
const session =
res.data.session || res.data.bcToken || res.data.cookieToken
localStorage.setItem("session", session)
Vue.axios.defaults.headers.common["Authorization"] = session
this.$store.commit("setToken", session)
await this.$store.dispatch("getUserInfo")
this.$store.dispatch("getChats")
this.loading = false
@ -144,12 +180,33 @@ export default {
this.loading = false
}
})
},
testInstance() {
if (this.isElectron()) {
this.axios
.get(this.instance + "/api/v1/state")
.then((res) => {
this.instanceString = res.data.name + " v" + res.data.latestVersion
this.axios.defaults.baseURL = this.instance
localStorage.setItem("instance", this.instance)
this.$store.dispatch("getState")
})
.catch(() => {
this.instanceString = "Error connecting to instance"
})
}
}
},
mounted() {
if (this.$store.state.user?.id) {
this.$router.push("/")
}
this.testInstance()
},
watch: {
instance() {
this.testInstance()
}
}
}
</script>

View file

@ -0,0 +1 @@
Object.defineProperty(window, "isElectron", { get: () => true })

File diff suppressed because it is too large Load diff