refined colors, added multiple color themes, and added window control titlebar
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-http": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-os": "~2",
|
||||
"@tauri-apps/plugin-store": "~2",
|
||||
"@tauri-apps/plugin-upload": "~2",
|
||||
"@tauri-apps/plugin-websocket": "~2",
|
||||
|
||||
83
src-tauri/Cargo.lock
generated
83
src-tauri/Cargo.lock
generated
@@ -1028,6 +1028,7 @@ dependencies = [
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-os",
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-upload",
|
||||
"tauri-plugin-websocket",
|
||||
@@ -1263,6 +1264,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
@@ -2315,6 +2326,16 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-location"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.2"
|
||||
@@ -2419,8 +2440,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-core-text",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-user-notifications",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-user-notifications"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
@@ -2474,6 +2514,22 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"log",
|
||||
"nix",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"serde",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -3782,6 +3838,15 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
@@ -4091,6 +4156,24 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"log",
|
||||
"os_info",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serialize-to-javascript",
|
||||
"sys-locale",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-store"
|
||||
version = "2.4.1"
|
||||
|
||||
@@ -28,4 +28,5 @@ tauri-plugin-websocket = "2"
|
||||
tauri-plugin-upload = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-os = "2"
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"opener:default",
|
||||
"store:default",
|
||||
{
|
||||
@@ -47,7 +53,7 @@
|
||||
"allow": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"os:default"
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ fn greet(name: &str) -> String {
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"title": "frangipane",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"decorations": true,
|
||||
"decorations": false,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"center": true,
|
||||
|
||||
146
src/App.vue
146
src/App.vue
@@ -1,16 +1,35 @@
|
||||
<template>
|
||||
<div id="page">
|
||||
<main id="content">
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
<VersionWarningModal v-if="showVersionWarningModal" :appVersion="appVersion" :backendVersion="backendVersion"
|
||||
:expectedBackendVersion="expectedBackendVersion" @close="showVersionWarningModal = false" />
|
||||
|
||||
<footer v-if="!$route.meta.hideNavbar">
|
||||
<Navbar />
|
||||
</footer>
|
||||
<div id="page">
|
||||
<div v-if="currentPlatform != 'android' && currentPlatform != 'ios'" data-tauri-drag-region class="titlebar">
|
||||
<div class="titlebar-button" @click="minimize">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12">
|
||||
<rect fill="currentColor" width="10" height="1" x="1" y="6" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="titlebar-button" @click="toggleMaximize">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12">
|
||||
<rect fill="none" stroke="currentColor" stroke-width="1" width="9" height="9" x="1.5" y="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="titlebar-button" id="close-btn" @click="close">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12">
|
||||
<path fill="currentColor"
|
||||
d="M11 1.57L10.43 1 6 5.43 1.57 1 1 1.57 5.43 6 1 10.43 1.57 11 6 6.57 10.43 11 11 10.43 6.57 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="content">
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
<VersionWarningModal v-if="showVersionWarningModal" :appVersion="appVersion" :backendVersion="backendVersion"
|
||||
:expectedBackendVersion="expectedBackendVersion" @close="showVersionWarningModal = false" />
|
||||
|
||||
<footer v-if="!$route.meta.hideNavbar">
|
||||
<Navbar />
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -19,6 +38,12 @@ import Navbar from './components/Navbar.vue'
|
||||
import VersionWarningModal from './components/VersionWarningModal.vue'
|
||||
import { apiFetch } from './api/client'
|
||||
import { VersionResponse } from './types'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { platform } from '@tauri-apps/plugin-os';
|
||||
|
||||
const currentPlatform = ref('')
|
||||
|
||||
const appWindow = getCurrentWindow()
|
||||
|
||||
const showVersionWarningModal = ref(false)
|
||||
const backendVersion = ref('')
|
||||
@@ -26,52 +51,97 @@ const backendVersion = ref('')
|
||||
const appVersion = __APP_VERSION__
|
||||
const expectedBackendVersion = __BACKEND_VERSION__
|
||||
|
||||
const isFullScreen = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
backendVersion.value = (await getBackendVersion()).version
|
||||
if (backendVersion.value !== expectedBackendVersion) {
|
||||
showVersionWarningModal.value = true
|
||||
}
|
||||
backendVersion.value = (await getBackendVersion()).version
|
||||
if (backendVersion.value !== expectedBackendVersion) {
|
||||
showVersionWarningModal.value = true
|
||||
}
|
||||
|
||||
currentPlatform.value = platform()
|
||||
})
|
||||
|
||||
const minimize = () => appWindow.minimize()
|
||||
const toggleMaximize = () => {
|
||||
appWindow.setFullscreen(!isFullScreen.value)
|
||||
isFullScreen.value = !isFullScreen.value
|
||||
}
|
||||
const close = () => appWindow.close()
|
||||
|
||||
async function getBackendVersion() {
|
||||
return await apiFetch<VersionResponse>('/version')
|
||||
return await apiFetch<VersionResponse>('/version')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#page {
|
||||
background: var(--bg);
|
||||
height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--bg);
|
||||
height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#content {
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
/* padding: 2rem; */
|
||||
padding: calc(20px + 2rem) 2rem 2rem 2rem;
|
||||
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 30px;
|
||||
background: var(--panel);
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.titlebar-button {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 45px;
|
||||
height: 30px;
|
||||
transition: background-color 0.2s;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.titlebar-button:hover {
|
||||
background: var(--panel-accent)
|
||||
}
|
||||
|
||||
#close-btn:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-bottom: 24px;
|
||||
background: var(--bg);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-bottom: 24px;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
#content {
|
||||
padding: 12px;
|
||||
padding-top: 30px;
|
||||
}
|
||||
#content {
|
||||
padding: 12px;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding-bottom: 56px;
|
||||
}
|
||||
footer {
|
||||
padding-bottom: 56px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
22
src/base.css
22
src/base.css
@@ -16,6 +16,7 @@ body,
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* This is overwritten by the theme configuration */
|
||||
:root {
|
||||
--bg: #0f1116;
|
||||
--panel: #171922;
|
||||
@@ -23,6 +24,7 @@ body,
|
||||
--text: #e6e6eb;
|
||||
--muted: #9aa0aa;
|
||||
--accent: #f27aa3;
|
||||
--accent-rgb: 242, 122, 163;
|
||||
--accent-hover: #ff91b3;
|
||||
--accent-second: #96CDFB;
|
||||
--border: #2a2f3b;
|
||||
@@ -30,6 +32,10 @@ body,
|
||||
--radius: 8px;
|
||||
}
|
||||
|
||||
* {
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", sans-serif;
|
||||
@@ -59,7 +65,8 @@ input:focus, textarea:focus {
|
||||
button, .button {
|
||||
font-size: 1rem;
|
||||
background: var(--accent);
|
||||
color: #0b0d12;
|
||||
color: var(--bg);
|
||||
/* color: var(--btn-text); */
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
padding: 0.6rem 1rem;
|
||||
@@ -74,6 +81,19 @@ button:hover, .button:hover {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.secondary:hover {
|
||||
background: var(--panel-hover);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
|
||||
@@ -347,7 +347,7 @@ async function onSend(content: string) {
|
||||
border-radius: var(--radius);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 1.6rem;
|
||||
@@ -386,7 +386,7 @@ async function onSend(content: string) {
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--panel-hover);
|
||||
}
|
||||
|
||||
.no-room {
|
||||
|
||||
@@ -76,15 +76,6 @@ const emit = defineEmits(['yes', 'no']);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius);
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
border-color: var(--error);
|
||||
color: var(--error);
|
||||
|
||||
@@ -92,14 +92,4 @@ async function submit() {
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -120,14 +120,4 @@ async function submit() {
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -76,7 +76,7 @@ onMounted(() => {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
background-color: var(--accent);
|
||||
color: black;
|
||||
color: var(--bg);
|
||||
font-size: 0.65rem;
|
||||
font-weight: bold;
|
||||
min-width: 18px;
|
||||
@@ -92,7 +92,7 @@ onMounted(() => {
|
||||
|
||||
@media (hover: hover) {
|
||||
.nav-item:hover {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
background: var(--panel-hover);
|
||||
}
|
||||
|
||||
.nav-item:not(.router-link-active):hover i {
|
||||
|
||||
@@ -205,15 +205,6 @@ const handleAvatarError = (event: Event) => {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
|
||||
@@ -138,7 +138,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--panel-hover);
|
||||
}
|
||||
|
||||
.room-content-wrapper {
|
||||
@@ -150,7 +150,7 @@ onMounted(async () => {
|
||||
|
||||
.unread-badge {
|
||||
background-color: var(--accent);
|
||||
color: black;
|
||||
color: var(--bg);
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
min-width: 20px;
|
||||
@@ -193,7 +193,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.room-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--panel-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@@ -236,6 +236,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.create-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--panel-hover);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -112,7 +112,6 @@ async function submit() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@@ -154,10 +153,4 @@ button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -202,7 +202,7 @@ async function handleUpload() {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: var(--panel-accent);
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -221,7 +221,7 @@ async function handleUpload() {
|
||||
|
||||
.drop-zone:hover {
|
||||
border-color: var(--accent);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: var(--panel-hover);
|
||||
}
|
||||
|
||||
.drop-content i {
|
||||
@@ -299,10 +299,4 @@ async function handleUpload() {
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,10 +49,4 @@ const emit = defineEmits(['close']);
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -79,6 +79,7 @@ settings-loading = Loading settings...
|
||||
settings-title = Settings
|
||||
settings-account = Account
|
||||
settings-language = Language
|
||||
settings-appearance = Appearance
|
||||
settings-label-username = Username:
|
||||
settings-label-email = Email:
|
||||
settings-update-btn = Update
|
||||
|
||||
@@ -79,6 +79,7 @@ settings-loading = Chargement des paramètres...
|
||||
settings-title = Paramètres
|
||||
settings-account = Compte
|
||||
settings-language = Langue
|
||||
settings-appearance = Apparence
|
||||
settings-label-username = Nom d'utilisateur :
|
||||
settings-label-email = Email :
|
||||
settings-update-btn = Modifier
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import router from './router.ts'
|
||||
import App from './App.vue'
|
||||
import { validateToken } from './store.ts'
|
||||
import { validateToken, initTheme } from './store.ts'
|
||||
import { fluent, setLanguage } from './i18n'
|
||||
|
||||
import './base.css'
|
||||
@@ -17,6 +17,8 @@ async function init() {
|
||||
const savedLocale = await getLocalePreference();
|
||||
const osLocale = navigator.language;
|
||||
|
||||
await initTheme();
|
||||
|
||||
if (savedLocale) {
|
||||
setLanguage(savedLocale);
|
||||
} else {
|
||||
|
||||
@@ -43,7 +43,7 @@ const handleRoomAction = async () => {
|
||||
await roomListRef.value.refreshRooms();
|
||||
}
|
||||
|
||||
router.push('/rooms/none');
|
||||
router.push('/rooms/none');
|
||||
};
|
||||
|
||||
const handleNotification = (roomUuid: string) => {
|
||||
@@ -152,7 +152,7 @@ const handleNotification = (roomUuid: string) => {
|
||||
.sidebar-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
/* background: rgba(0, 0, 0, 0.3); */
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ async function declineRoom(senderUuid: string, roomUuid: string) {
|
||||
}
|
||||
|
||||
.decline-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
background-color: var(--panel-hover);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -38,6 +38,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>{{ $t('settings-appearance') || 'Appearance' }}</h2>
|
||||
<div class="theme-grid">
|
||||
<button v-for="theme in availableThemes" :key="theme.id" class="theme-btn"
|
||||
:class="{ active: currentTheme === theme.id }" @click="changeTheme(theme.id)">
|
||||
{{ theme.name }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="logout-btn" @click="logout">
|
||||
<i class="fa-solid fa-right-from-bracket"></i>
|
||||
<span>{{ $t('settings-logout-btn') }}</span>
|
||||
@@ -49,6 +57,7 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { logout as authLogout } from '../store.ts'
|
||||
import { saveThemePreference, getThemePreference } from "../store.ts"
|
||||
import { getAuthData } from "../store.ts"
|
||||
import type { User } from "../types"
|
||||
import UpdateAccountModal from '../components/UpdateAccountModal.vue'
|
||||
@@ -58,6 +67,7 @@ import { getSupportedLanguagesMetadata, setLanguage } from '../i18n'
|
||||
import UploadAvatarModal from '../components/UploadAvatarModal.vue'
|
||||
import defaultAvatar from '../assets/default-avatar.png'
|
||||
import { getAvatarUrl } from '../store.ts'
|
||||
import { getAvailableThemes } from '../themeLoader';
|
||||
|
||||
const handleAvatarError = (event: Event) => {
|
||||
const img = event.target as HTMLImageElement;
|
||||
@@ -73,6 +83,8 @@ const { $t } = useFluent()
|
||||
const currentLang = ref('')
|
||||
|
||||
const languages = computed(() => getSupportedLanguagesMetadata())
|
||||
const currentTheme = ref('default');
|
||||
const availableThemes = getAvailableThemes();
|
||||
|
||||
async function fetchUserData() {
|
||||
try {
|
||||
@@ -85,8 +97,8 @@ async function fetchUserData() {
|
||||
|
||||
onMounted(async () => {
|
||||
const pref = await getLocalePreference()
|
||||
// Synchronize the UI state with the actual active language
|
||||
currentLang.value = pref || (navigator.language.split('-')[0])
|
||||
currentTheme.value = await getThemePreference()
|
||||
|
||||
fetchUserData()
|
||||
})
|
||||
@@ -97,6 +109,11 @@ async function changeLanguage(code: string) {
|
||||
await saveLocalePreference(actual)
|
||||
}
|
||||
|
||||
async function changeTheme(theme: string) {
|
||||
currentTheme.value = theme
|
||||
await saveThemePreference(theme)
|
||||
}
|
||||
|
||||
function logout() {
|
||||
authLogout()
|
||||
router.push('/login')
|
||||
@@ -171,12 +188,44 @@ h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lang-btn:hover:not(.active) {
|
||||
background: rgba(var(--accent-rgb), 0.1);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.lang-btn.active {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.theme-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.theme-btn.active {
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.theme-btn:hover:not(.active) {
|
||||
background: rgba(var(--accent-rgb), 0.1);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -193,11 +242,11 @@ h2 {
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
color: rgba(255, 80, 80, 0.8);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.logout-btn:hover i {
|
||||
color: rgba(255, 80, 80, 0.8);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.logout-btn i {
|
||||
|
||||
26
src/store.ts
26
src/store.ts
@@ -8,6 +8,7 @@ import { load, Store } from '@tauri-apps/plugin-store'
|
||||
import { UpdateUserResponse } from './types'
|
||||
import { reactive } from 'vue'
|
||||
import { API } from './main'
|
||||
import { applyTheme } from './themeLoader.ts';
|
||||
|
||||
let store: Store | null = null
|
||||
export const initAuth = getAuthData
|
||||
@@ -184,3 +185,28 @@ export function getAvatarUrl(uuid: string | undefined | null) {
|
||||
export function refreshAvatar(uuid: string) {
|
||||
avatarTimestamps[uuid] = Date.now()
|
||||
}
|
||||
|
||||
// ==== Color themes ====
|
||||
|
||||
export async function saveThemePreference(themeId: string) {
|
||||
const s = await getStore();
|
||||
await s.set('theme', themeId);
|
||||
await s.save();
|
||||
applyTheme(themeId);
|
||||
}
|
||||
|
||||
export async function initTheme() {
|
||||
const s = await getStore();
|
||||
const themeId = (await s.get<string>('theme')) || 'default';
|
||||
applyTheme(themeId);
|
||||
}
|
||||
|
||||
export async function getThemePreference(): Promise<string> {
|
||||
const s = await getStore()
|
||||
return (await s.get<string>('theme')) ?? 'default'
|
||||
}
|
||||
|
||||
export async function applyStoredTheme() {
|
||||
const theme = await getThemePreference();
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
26
src/themeLoader.ts
Normal file
26
src/themeLoader.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import themes from './themes.json';
|
||||
|
||||
export type ThemeKey = keyof typeof themes;
|
||||
|
||||
export function applyTheme(themeKey: string) {
|
||||
const theme = (themes as any)[themeKey] || themes.default;
|
||||
const root = document.documentElement;
|
||||
|
||||
// Convert json keys to css Variables
|
||||
Object.entries(theme.colors).forEach(([key, value]) => {
|
||||
root.style.setProperty(`--${key}`, value as string);
|
||||
});
|
||||
|
||||
// if (themeKey.includes('light')) {
|
||||
// root.style.setProperty('--btn-text', theme.colors.bg);
|
||||
// } else {
|
||||
// root.style.setProperty('--btn-text', '#0b0d12');
|
||||
// }
|
||||
}
|
||||
|
||||
export function getAvailableThemes() {
|
||||
return Object.entries(themes).map(([id, data]) => ({
|
||||
id,
|
||||
name: data.name
|
||||
}));
|
||||
}
|
||||
172
src/themes.json
Normal file
172
src/themes.json
Normal file
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"frangipane-dark": {
|
||||
"name": "Frangipane Dark",
|
||||
"colors": {
|
||||
"bg": "#0f1116",
|
||||
"panel": "#171922",
|
||||
"panel-accent": "#12141B",
|
||||
"panel-hover": "#22242D",
|
||||
"text": "#e6e6eb",
|
||||
"muted": "#9aa0aa",
|
||||
"accent": "#f27aa3",
|
||||
"accent-rgb": "242, 122, 163",
|
||||
"accent-hover": "#ff91b3",
|
||||
"accent-second": "#96CDFB",
|
||||
"border": "#2a2f3b",
|
||||
"error": "#ff5050"
|
||||
}
|
||||
},
|
||||
"catppuccin-latte": {
|
||||
"name": "Catppuccin Latte",
|
||||
"colors": {
|
||||
"bg": "#eff1f5",
|
||||
"panel": "#e6e9ef",
|
||||
"panel-hover": "#bcc0cc",
|
||||
"panel-accent": "#ccd0da",
|
||||
"text": "#4c4f69",
|
||||
"muted": "#7c7f93",
|
||||
"accent": "#ea76cb",
|
||||
"accent-rgb": "234, 118, 203",
|
||||
"accent-hover": "#d20f39",
|
||||
"accent-second": "#1e66f5",
|
||||
"border": "#dce0e8",
|
||||
"error": "#d20f39"
|
||||
}
|
||||
},
|
||||
"catppuccin-frappe": {
|
||||
"name": "Catppuccin Frappé",
|
||||
"colors": {
|
||||
"bg": "#303446",
|
||||
"panel": "#292c3c",
|
||||
"panel-accent": "#232634",
|
||||
"panel-hover": "#414559",
|
||||
"text": "#c6d0f5",
|
||||
"muted": "#838ba7",
|
||||
"accent": "#a6d189",
|
||||
"accent-rgb": "166, 209, 137",
|
||||
"accent-hover": "#81c8be",
|
||||
"accent-second": "#f2d5cf",
|
||||
"border": "#414559",
|
||||
"error": "#e78284"
|
||||
}
|
||||
},
|
||||
"catppuccin-macchiato": {
|
||||
"name": "Catppuccin Macchiato",
|
||||
"colors": {
|
||||
"bg": "#24273a",
|
||||
"panel": "#1e2030",
|
||||
"panel-accent": "#181926",
|
||||
"panel-hover": "#363a4f",
|
||||
"text": "#cad3f5",
|
||||
"muted": "#8087a2",
|
||||
"accent": "#c6a0f6",
|
||||
"accent-rgb": "198, 160, 246",
|
||||
"accent-hover": "#f5bde6",
|
||||
"accent-second": "#8aadf4",
|
||||
"border": "#494d64",
|
||||
"error": "#ed8796"
|
||||
}
|
||||
},
|
||||
"catppuccin-mocha": {
|
||||
"name": "Catppuccin Mocha",
|
||||
"colors": {
|
||||
"bg": "#1e1e2e",
|
||||
"panel": "#181825",
|
||||
"panel-accent": "#11111b",
|
||||
"panel-hover": "#313244",
|
||||
"text": "#cdd6f4",
|
||||
"muted": "#7f849c",
|
||||
"accent": "#89b4fa",
|
||||
"accent-rgb": "137, 180, 250",
|
||||
"accent-hover": "#89dceb",
|
||||
"accent-second": "#f5c2e7",
|
||||
"border": "#313244",
|
||||
"error": "#f38ba8"
|
||||
}
|
||||
},
|
||||
"nord": {
|
||||
"name": "Nordic",
|
||||
"colors": {
|
||||
"bg": "#2e3440",
|
||||
"panel": "#3b4252",
|
||||
"panel-accent": "#242933",
|
||||
"panel-hover": "#434c5e",
|
||||
"text": "#eceff4",
|
||||
"muted": "#d8dee9",
|
||||
"accent": "#88c0d0",
|
||||
"accent-rgb": "136, 192, 208",
|
||||
"accent-hover": "#8fbcbb",
|
||||
"accent-second": "#81a1c1",
|
||||
"border": "#4c566a",
|
||||
"error": "#bf616a"
|
||||
}
|
||||
},
|
||||
"tokyo-night": {
|
||||
"name": "Tokyo Night",
|
||||
"colors": {
|
||||
"bg": "#1a1b26",
|
||||
"panel": "#16161e",
|
||||
"panel-accent": "#1f2335",
|
||||
"panel-hover": "#292e42",
|
||||
"text": "#a9b1d6",
|
||||
"muted": "#565f89",
|
||||
"accent": "#7aa2f7",
|
||||
"accent-rgb": "122, 162, 247",
|
||||
"accent-hover": "#7dcfff",
|
||||
"accent-second": "#bb9af7",
|
||||
"border": "#24283b",
|
||||
"error": "#f7768e"
|
||||
}
|
||||
},
|
||||
"gruvbox-dark": {
|
||||
"name": "Gruvbox Dark",
|
||||
"colors": {
|
||||
"bg": "#282828",
|
||||
"panel": "#3c3836",
|
||||
"panel-accent": "#282828",
|
||||
"panel-hover": "#504945",
|
||||
"text": "#ebdbb2",
|
||||
"muted": "#a89984",
|
||||
"accent": "#fabd2f",
|
||||
"accent-rgb": "250, 189, 47",
|
||||
"accent-hover": "#fe8019",
|
||||
"accent-second": "#b8bb26",
|
||||
"border": "#504945",
|
||||
"error": "#fb4934"
|
||||
}
|
||||
},
|
||||
"gruvbox-light": {
|
||||
"name": "Gruvbox Light",
|
||||
"colors": {
|
||||
"bg": "#fbf1c7",
|
||||
"panel": "#f2e5bc",
|
||||
"panel-accent": "#d5c4a1",
|
||||
"panel-hover": "#ebdbb2",
|
||||
"text": "#3c3836",
|
||||
"muted": "#7c6f64",
|
||||
"accent": "#d65d0e",
|
||||
"accent-rgb": "214, 93, 14",
|
||||
"accent-hover": "#9d0006",
|
||||
"accent-second": "#458588",
|
||||
"border": "#bdae93",
|
||||
"error": "#cc241d"
|
||||
}
|
||||
},
|
||||
"solarized-dark": {
|
||||
"name": "Solarized Dark",
|
||||
"colors": {
|
||||
"bg": "#002b36",
|
||||
"panel": "#073642",
|
||||
"panel-accent": "#00212b",
|
||||
"panel-hover": "#586e75",
|
||||
"text": "#839496",
|
||||
"muted": "#657b83",
|
||||
"accent": "#268bd2",
|
||||
"accent-rgb": "38, 139, 210",
|
||||
"accent-hover": "#2aa198",
|
||||
"accent-second": "#859900",
|
||||
"border": "#073642",
|
||||
"error": "#dc322f"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,6 +430,13 @@
|
||||
dependencies:
|
||||
"@tauri-apps/api" "^2.8.0"
|
||||
|
||||
"@tauri-apps/plugin-os@~2":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.3.2.tgz#de916a82d8d955bba59a2ffdba7f7aaa29397246"
|
||||
integrity sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==
|
||||
dependencies:
|
||||
"@tauri-apps/api" "^2.8.0"
|
||||
|
||||
"@tauri-apps/plugin-store@~2":
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-store/-/plugin-store-2.4.1.tgz#5e2d3362e41861d2fa79a3f1a78c091e12963236"
|
||||
|
||||
Reference in New Issue
Block a user