mirror of
https://github.com/System-End/hackatime-desktop.git
synced 2026-04-19 20:55:13 +00:00
Compare commits
10 commits
app-v1.7.2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
832a610693 | ||
|
|
472867d58f | ||
|
|
d037bdb529 | ||
|
|
6509fc85e0 | ||
|
|
e84c1d6a37 | ||
|
|
a5746811a9 | ||
|
|
3688e39424 | ||
|
|
59008e3849 | ||
|
|
145b3b9422 | ||
|
|
ece57e2981 |
30 changed files with 240 additions and 121 deletions
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
|
@ -250,7 +250,7 @@ jobs:
|
|||
- name: Generate update manifest
|
||||
id: generate_manifest
|
||||
env:
|
||||
DOWNLOAD_URL_BASE: 'https://pub-d35fbe65a5b5426bb6d62ff02a8c7d03.r2.dev'
|
||||
DOWNLOAD_URL_BASE: 'https://desktop.hackatime.hackclub-assets.com'
|
||||
VERSION: '${{ steps.get_version.outputs.version }}'
|
||||
run: >
|
||||
# Read signatures into variables
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"app": "0.0.0",
|
||||
".": "1.7.2"
|
||||
".": "1.7.5"
|
||||
}
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
|
|
@ -1,5 +1,34 @@
|
|||
# Changelog
|
||||
|
||||
## [1.7.5](https://github.com/hackclub/hackatime-desktop/compare/app-v1.7.4...app-v1.7.5) (2025-10-24)
|
||||
|
||||
|
||||
### 🐛 Bugfixes
|
||||
|
||||
* eliminate duplicate discord definitions ([472867d](https://github.com/hackclub/hackatime-desktop/commit/472867d58f306d70241b07b5f3135c34055ad555))
|
||||
* make option enabled by default ([d037bdb](https://github.com/hackclub/hackatime-desktop/commit/d037bdb529a4a078a5d10f7daa68698da9726e5f))
|
||||
|
||||
## [1.7.4](https://github.com/hackclub/hackatime-desktop/compare/app-v1.7.3...app-v1.7.4) (2025-10-24)
|
||||
|
||||
|
||||
### 🐛 Bugfixes
|
||||
|
||||
* update hackatime url ([e84c1d6](https://github.com/hackclub/hackatime-desktop/commit/e84c1d6a37fdef397189e8a9108f68d4ddb11641))
|
||||
|
||||
## [1.7.3](https://github.com/hackclub/hackatime-desktop/compare/app-v1.7.2...app-v1.7.3) (2025-10-24)
|
||||
|
||||
|
||||
### 🐛 Bugfixes
|
||||
|
||||
* change hackatime icon ([3688e39](https://github.com/hackclub/hackatime-desktop/commit/3688e39424c6d3e1c0441fde81e8451feded8178))
|
||||
* correct card alignment issues ([59008e3](https://github.com/hackclub/hackatime-desktop/commit/59008e3849753ccaac037657d65bad2a70387e3c))
|
||||
|
||||
|
||||
### 👽 Miscellaneous
|
||||
|
||||
* add license ([ece57e2](https://github.com/hackclub/hackatime-desktop/commit/ece57e29811ca228a255bef7bfe3117c3e3d236d))
|
||||
* add security policy ([145b3b9](https://github.com/hackclub/hackatime-desktop/commit/145b3b9422bb5b5095a6e5aa59aa66b749338b5a))
|
||||
|
||||
## [1.7.2](https://github.com/hackclub/hackatime-desktop/compare/app-v1.7.1...app-v1.7.2) (2025-10-10)
|
||||
|
||||
|
||||
|
|
|
|||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hack Club
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
26
SECURITY.md
Normal file
26
SECURITY.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Security Policy
|
||||
|
||||
> **Note**: This security policy is specifically for the **Hackatime Desktop** application. For vulnerabilities related to the main Hackatime web app, please refer to the [hackclub/hackatime repository](https://github.com/hackclub/hackatime).
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We are currently providing security updates for the following versions:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in this project, please report it through one of the following channels:
|
||||
|
||||
- **Email**: sebastian@hackclub.com or security@leafd.dev
|
||||
- **Hack Club Slack**: Send a direct message to @lfd
|
||||
|
||||
Please include as much information as possible in your report:
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Any suggested fixes (optional)
|
||||
|
||||
Thank you for helping me keep this project secure :)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "desktop",
|
||||
"private": true,
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.18.0",
|
||||
"scripts": {
|
||||
|
|
|
|||
BIN
src-tauri/icons/128x128.png
(Stored with Git LFS)
BIN
src-tauri/icons/128x128.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/128x128@2x.png
(Stored with Git LFS)
BIN
src-tauri/icons/128x128@2x.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/32x32.png
(Stored with Git LFS)
BIN
src-tauri/icons/32x32.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square107x107Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square107x107Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square142x142Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square142x142Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square150x150Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square150x150Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square284x284Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square284x284Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square30x30Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square30x30Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square310x310Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square310x310Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square44x44Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square44x44Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square71x71Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square71x71Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/Square89x89Logo.png
(Stored with Git LFS)
BIN
src-tauri/icons/Square89x89Logo.png
(Stored with Git LFS)
Binary file not shown.
BIN
src-tauri/icons/StoreLogo.png
(Stored with Git LFS)
BIN
src-tauri/icons/StoreLogo.png
(Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 45 KiB |
BIN
src-tauri/icons/icon.png
(Stored with Git LFS)
BIN
src-tauri/icons/icon.png
(Stored with Git LFS)
Binary file not shown.
|
|
@ -283,26 +283,3 @@ pub async fn discord_rpc_auto_disconnect(
|
|||
rpc_service.disconnect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_discord_rpc_enabled(
|
||||
discord_rpc_state: State<'_, Arc<tauri::async_runtime::Mutex<DiscordRpcService>>>,
|
||||
) -> Result<bool, String> {
|
||||
let rpc_service = discord_rpc_state.lock().await;
|
||||
Ok(rpc_service.is_connected())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_discord_rpc_enabled(
|
||||
enabled: bool,
|
||||
discord_rpc_state: State<'_, Arc<tauri::async_runtime::Mutex<DiscordRpcService>>>,
|
||||
) -> Result<(), String> {
|
||||
let mut rpc_service = discord_rpc_state.lock().await;
|
||||
|
||||
if enabled {
|
||||
|
||||
let default_client_id = "1234567890123456789";
|
||||
rpc_service.connect(default_client_id)
|
||||
} else {
|
||||
rpc_service.disconnect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ pub fn run() {
|
|||
preferences::get_preferences,
|
||||
preferences::set_autostart_enabled,
|
||||
preferences::get_autostart_enabled,
|
||||
preferences::set_notifications_enabled,
|
||||
preferences::get_notifications_enabled,
|
||||
preferences::set_discord_rpc_enabled,
|
||||
preferences::get_discord_rpc_enabled,
|
||||
|
||||
setup::setup_hackatime_macos_linux,
|
||||
setup::setup_hackatime_windows,
|
||||
|
|
@ -166,8 +170,6 @@ pub fn run() {
|
|||
discord_rpc::discord_rpc_update_from_heartbeat,
|
||||
discord_rpc::discord_rpc_auto_connect,
|
||||
discord_rpc::discord_rpc_auto_disconnect,
|
||||
discord_rpc::get_discord_rpc_enabled,
|
||||
discord_rpc::set_discord_rpc_enabled,
|
||||
|
||||
projects::get_projects,
|
||||
projects::get_project_details,
|
||||
|
|
|
|||
|
|
@ -9,12 +9,16 @@ use crate::push_log;
|
|||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Preferences {
|
||||
pub autostart_enabled: bool,
|
||||
pub notifications_enabled: bool,
|
||||
pub discord_rpc_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for Preferences {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
autostart_enabled: false,
|
||||
autostart_enabled: true,
|
||||
notifications_enabled: true,
|
||||
discord_rpc_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,3 +90,45 @@ pub fn get_autostart_enabled() -> Result<bool, String> {
|
|||
Ok(preferences.autostart_enabled)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_notifications_enabled(enabled: bool) -> Result<(), String> {
|
||||
let mut preferences = load_preferences().unwrap_or_default();
|
||||
preferences.notifications_enabled = enabled;
|
||||
save_preferences(&preferences)?;
|
||||
|
||||
if enabled {
|
||||
push_log("info", "backend", "Notifications enabled".to_string());
|
||||
} else {
|
||||
push_log("info", "backend", "Notifications disabled".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_notifications_enabled() -> Result<bool, String> {
|
||||
let preferences = load_preferences().unwrap_or_default();
|
||||
Ok(preferences.notifications_enabled)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_discord_rpc_enabled(enabled: bool) -> Result<(), String> {
|
||||
let mut preferences = load_preferences().unwrap_or_default();
|
||||
preferences.discord_rpc_enabled = enabled;
|
||||
save_preferences(&preferences)?;
|
||||
|
||||
if enabled {
|
||||
push_log("info", "backend", "Discord RPC enabled".to_string());
|
||||
} else {
|
||||
push_log("info", "backend", "Discord RPC disabled".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_discord_rpc_enabled() -> Result<bool, String> {
|
||||
let preferences = load_preferences().unwrap_or_default();
|
||||
Ok(preferences.discord_rpc_enabled)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,15 +45,32 @@ fn get_expected_config_content(api_key: &str, api_url: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn normalize_config_content(content: &str) -> String {
|
||||
fn check_config_has_required_values(content: &str, api_key: &str, api_url: &str) -> bool {
|
||||
let normalized = content.replace("\r\n", "\n");
|
||||
let mut found_api_url = false;
|
||||
let mut found_api_key = false;
|
||||
|
||||
content
|
||||
.replace("\r\n", "\n")
|
||||
.lines()
|
||||
.map(|line| line.trim())
|
||||
.filter(|line| !line.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
for line in normalized.lines() {
|
||||
let trimmed = line.trim();
|
||||
|
||||
if trimmed.starts_with("api_url") {
|
||||
if let Some(value) = trimmed.split('=').nth(1) {
|
||||
let value = value.trim();
|
||||
if value == api_url {
|
||||
found_api_url = true;
|
||||
}
|
||||
}
|
||||
} else if trimmed.starts_with("api_key") {
|
||||
if let Some(value) = trimmed.split('=').nth(1) {
|
||||
let value = value.trim();
|
||||
if value == api_key {
|
||||
found_api_key = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found_api_url && found_api_key
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
@ -72,7 +89,7 @@ pub async fn check_wakatime_config(api_key: String, api_url: String) -> Result<W
|
|||
};
|
||||
|
||||
let matches = if let Some(ref actual) = actual_content {
|
||||
normalize_config_content(actual) == normalize_config_content(&expected_content)
|
||||
check_config_has_required_values(actual, &api_key, &api_url)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
|
@ -136,23 +153,8 @@ pub async fn setup_hackatime_macos_linux(api_key: String, api_url: String) -> Re
|
|||
let config_content = fs::read_to_string(&config_path)
|
||||
.map_err(|e| format!("Failed to read config file: {}", e))?;
|
||||
|
||||
let lines: Vec<&str> = config_content.lines().collect();
|
||||
let mut found_api_url = false;
|
||||
let mut found_api_key = false;
|
||||
let mut found_heartbeat_rate = false;
|
||||
|
||||
for line in lines {
|
||||
if line.starts_with("api_url =") {
|
||||
found_api_url = true;
|
||||
} else if line.starts_with("api_key =") {
|
||||
found_api_key = true;
|
||||
} else if line.starts_with("heartbeat_rate_limit_seconds =") {
|
||||
found_heartbeat_rate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_api_url || !found_api_key || !found_heartbeat_rate {
|
||||
return Err("Config file is missing required fields".to_string());
|
||||
if !check_config_has_required_values(&config_content, &api_key, &api_url) {
|
||||
return Err("Config file is missing required api_url and api_key values".to_string());
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
|
|
@ -191,23 +193,8 @@ pub async fn setup_hackatime_windows(api_key: String, api_url: String) -> Result
|
|||
let config_content = fs::read_to_string(&config_path)
|
||||
.map_err(|e| format!("Failed to read config file: {}", e))?;
|
||||
|
||||
let lines: Vec<&str> = config_content.lines().collect();
|
||||
let mut found_api_url = false;
|
||||
let mut found_api_key = false;
|
||||
let mut found_heartbeat_rate = false;
|
||||
|
||||
for line in lines {
|
||||
if line.starts_with("api_url =") {
|
||||
found_api_url = true;
|
||||
} else if line.starts_with("api_key =") {
|
||||
found_api_key = true;
|
||||
} else if line.starts_with("heartbeat_rate_limit_seconds =") {
|
||||
found_heartbeat_rate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_api_url || !found_api_key || !found_heartbeat_rate {
|
||||
return Err("Config file is missing required fields".to_string());
|
||||
if !check_config_has_required_values(&config_content, &api_key, &api_url) {
|
||||
return Err("Config file is missing required api_url and api_key values".to_string());
|
||||
}
|
||||
|
||||
Ok(format!(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Hackatime Desktop",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"identifier": "com.hackclub.hackatime",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src 'self' ipc: http://ipc.localhost; connect-src 'self' ipc: http://ipc.localhost https://hackatime.hackclub.com https://pub-d35fbe65a5b5426bb6d62ff02a8c7d03.r2.dev wss://*.ingest.us.sentry.io https://us.i.posthog.com https://fonts.googleapis.com https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; worker-src 'self' blob:;"
|
||||
"csp": "default-src 'self' ipc: http://ipc.localhost; connect-src 'self' ipc: http://ipc.localhost https://hackatime.hackclub.com https://desktop.hackatime.hackclub-assets.com wss://*.ingest.us.sentry.io https://us.i.posthog.com https://fonts.googleapis.com https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; worker-src 'self' blob:;"
|
||||
},
|
||||
"withGlobalTauri": true
|
||||
},
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdERjg4QTFCNTJFMDk0MUQKUldRZGxPQlNHNHI0ZlRkMDN0MGI1MnllY1dUVStZalV3dVdhcTFuREx5SGtBc0txQ2xnTWs3WU4K",
|
||||
"endpoints": [
|
||||
"https://pub-d35fbe65a5b5426bb6d62ff02a8c7d03.r2.dev/update-manifest.json"
|
||||
"https://desktop.hackatime.hackclub-assets.com/update-manifest.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
src/App.vue
22
src/App.vue
|
|
@ -761,17 +761,21 @@ async function handleInstallNow() {
|
|||
<div v-if="authState.is_authenticated && userStats" class="w-64 min-w-64 flex flex-col responsive-full-width">
|
||||
<div class="card-3d-app h-full">
|
||||
<div class="rounded-[8px] border border-black p-4 card-3d-app-front h-full flex flex-col" style="background-color: #3D2C3E;">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-white text-[16px] font-bold italic m-0" style="font-family: 'Outfit', sans-serif;">
|
||||
leaderboard
|
||||
</h2>
|
||||
<div class="flex gap-2 text-[10px]" style="font-family: 'Outfit', sans-serif;">
|
||||
<span class="text-white underline cursor-pointer">friends</span>
|
||||
<span class="text-white cursor-pointer">global</span>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-white text-[16px] font-bold italic m-0" style="font-family: 'Outfit', sans-serif;">
|
||||
leaderboard
|
||||
</h2>
|
||||
<div class="flex gap-2 text-[10px]" style="font-family: 'Outfit', sans-serif;">
|
||||
<span class="text-white underline cursor-pointer">friends</span>
|
||||
<span class="text-white cursor-pointer">global</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<p class="text-white text-[18px] font-semibold opacity-60" style="font-family: 'Outfit', sans-serif;">
|
||||
Coming Soon...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Leaderboard content would go here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div class="card-3d">
|
||||
<div class="rounded-[8px] border border-black p-6 card-3d-front h-full flex flex-col" style="background-color: #3D2C3E;">
|
||||
<div class="flex items-start space-x-4 flex-1">
|
||||
<div class="text-3xl">{{ icon }}</div>
|
||||
<div class="flex-1 flex flex-col">
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="text-3xl flex-shrink-0">{{ icon }}</div>
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<h3 class="text-lg font-semibold text-text-primary mb-2">{{ title }}</h3>
|
||||
<p class="text-text-secondary mb-3 flex-1">{{ description }}</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-2xl font-bold" :style="{ color: color }">{{ value }}</div>
|
||||
<p class="text-text-secondary mb-4 text-sm line-clamp-2">{{ description }}</p>
|
||||
<div class="mt-auto">
|
||||
<div class="text-2xl font-bold mb-1" :style="{ color: color }">{{ value }}</div>
|
||||
<div class="text-sm text-text-secondary">{{ trend }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@
|
|||
<h4 class="font-medium text-text-primary mb-1">Notifications</h4>
|
||||
<p class="text-sm text-text-secondary">Show desktop notifications</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider"></span>
|
||||
<label class="switch" :class="{ 'opacity-50 cursor-not-allowed': isLoading }">
|
||||
<input type="checkbox" :checked="notificationsEnabled" :disabled="isLoading" @change="toggleNotifications">
|
||||
<span class="slider" :class="{ 'animate-pulse': isLoading }"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -291,6 +291,7 @@ const emit = defineEmits<{
|
|||
|
||||
const discordRpcEnabled = ref(false);
|
||||
const autostartEnabled = ref(false);
|
||||
const notificationsEnabled = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const appVersion = ref('...');
|
||||
const isClearingCache = ref(false);
|
||||
|
|
@ -565,6 +566,14 @@ async function loadAutostartState() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadNotificationsState() {
|
||||
try {
|
||||
notificationsEnabled.value = await invoke("get_notifications_enabled");
|
||||
} catch (error) {
|
||||
console.error("Failed to load notifications state:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleAutostart() {
|
||||
if (isLoading.value) return;
|
||||
|
||||
|
|
@ -599,6 +608,23 @@ async function toggleDiscordRpc() {
|
|||
}
|
||||
}
|
||||
|
||||
async function toggleNotifications() {
|
||||
if (isLoading.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const newState = !notificationsEnabled.value;
|
||||
await invoke("set_notifications_enabled", { enabled: newState });
|
||||
notificationsEnabled.value = newState;
|
||||
} catch (error) {
|
||||
console.error("Failed to toggle notifications:", error);
|
||||
|
||||
notificationsEnabled.value = !notificationsEnabled.value;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function copyApiKey() {
|
||||
emit('copyApiKey')
|
||||
}
|
||||
|
|
@ -689,6 +715,7 @@ async function downloadAndInstallUpdate() {
|
|||
onMounted(async () => {
|
||||
loadDiscordRpcState();
|
||||
loadAutostartState();
|
||||
loadNotificationsState();
|
||||
try {
|
||||
appVersion.value = await getVersion();
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue