generated from nhcarrigan/template
Compare commits
2 Commits
main
...
b1a45ed00e
| Author | SHA1 | Date | |
|---|---|---|---|
| b1a45ed00e | |||
| 457722dc3a |
Generated
+1
@@ -4180,6 +4180,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
|
"image",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = ["tray-icon", "image-png"] }
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
tauri-plugin-shell = "2"
|
tauri-plugin-shell = "2"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"notification:allow-request-permission",
|
"notification:allow-request-permission",
|
||||||
"notification:allow-notify",
|
"notification:allow-notify",
|
||||||
"clipboard-manager:default",
|
"clipboard-manager:default",
|
||||||
"clipboard-manager:allow-read-image"
|
"clipboard-manager:allow-read-image",
|
||||||
|
"core:tray:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ pub struct HikariConfig {
|
|||||||
|
|
||||||
#[serde(default = "default_font_size")]
|
#[serde(default = "default_font_size")]
|
||||||
pub font_size: u32,
|
pub font_size: u32,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub minimize_to_tray: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HikariConfig {
|
impl Default for HikariConfig {
|
||||||
@@ -89,6 +92,7 @@ impl Default for HikariConfig {
|
|||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: None,
|
character_panel_width: None,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
|
minimize_to_tray: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,6 +123,8 @@ pub enum Theme {
|
|||||||
#[default]
|
#[default]
|
||||||
Dark,
|
Dark,
|
||||||
Light,
|
Light,
|
||||||
|
#[serde(rename = "high-contrast")]
|
||||||
|
HighContrast,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -140,6 +146,7 @@ mod tests {
|
|||||||
assert!(config.update_checks_enabled);
|
assert!(config.update_checks_enabled);
|
||||||
assert!(config.character_panel_width.is_none());
|
assert!(config.character_panel_width.is_none());
|
||||||
assert_eq!(config.font_size, 14);
|
assert_eq!(config.font_size, 14);
|
||||||
|
assert!(!config.minimize_to_tray);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -159,6 +166,7 @@ mod tests {
|
|||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: Some(400),
|
character_panel_width: Some(400),
|
||||||
font_size: 16,
|
font_size: 16,
|
||||||
|
minimize_to_tray: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&config).unwrap();
|
let json = serde_json::to_string(&config).unwrap();
|
||||||
@@ -179,8 +187,13 @@ mod tests {
|
|||||||
fn test_theme_serialization() {
|
fn test_theme_serialization() {
|
||||||
let dark = Theme::Dark;
|
let dark = Theme::Dark;
|
||||||
let light = Theme::Light;
|
let light = Theme::Light;
|
||||||
|
let high_contrast = Theme::HighContrast;
|
||||||
|
|
||||||
assert_eq!(serde_json::to_string(&dark).unwrap(), "\"dark\"");
|
assert_eq!(serde_json::to_string(&dark).unwrap(), "\"dark\"");
|
||||||
assert_eq!(serde_json::to_string(&light).unwrap(), "\"light\"");
|
assert_eq!(serde_json::to_string(&light).unwrap(), "\"light\"");
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&high_contrast).unwrap(),
|
||||||
|
"\"high-contrast\""
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod config;
|
|||||||
mod notifications;
|
mod notifications;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod temp_manager;
|
mod temp_manager;
|
||||||
|
mod tray;
|
||||||
mod types;
|
mod types;
|
||||||
mod vbs_notification;
|
mod vbs_notification;
|
||||||
mod windows_toast;
|
mod windows_toast;
|
||||||
@@ -15,7 +16,9 @@ use bridge_manager::create_shared_bridge_manager;
|
|||||||
use commands::load_saved_achievements;
|
use commands::load_saved_achievements;
|
||||||
use commands::*;
|
use commands::*;
|
||||||
use notifications::*;
|
use notifications::*;
|
||||||
|
use tauri::Manager;
|
||||||
use temp_manager::create_shared_temp_manager;
|
use temp_manager::create_shared_temp_manager;
|
||||||
|
use tray::{setup_tray, should_minimize_to_tray};
|
||||||
use vbs_notification::*;
|
use vbs_notification::*;
|
||||||
use windows_toast::*;
|
use windows_toast::*;
|
||||||
use wsl_notifications::*;
|
use wsl_notifications::*;
|
||||||
@@ -47,6 +50,27 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up system tray
|
||||||
|
if let Err(e) = setup_tray(app.handle()) {
|
||||||
|
eprintln!("Failed to set up system tray: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle window close event for minimize to tray
|
||||||
|
let main_window = app.get_webview_window("main").unwrap();
|
||||||
|
main_window.on_window_event({
|
||||||
|
let app_handle = app.handle().clone();
|
||||||
|
move |event| {
|
||||||
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||||
|
if should_minimize_to_tray(&app_handle) {
|
||||||
|
api.prevent_close();
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use tauri::{
|
||||||
|
menu::{Menu, MenuItem},
|
||||||
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||||
|
AppHandle, Manager,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::config::HikariConfig;
|
||||||
|
|
||||||
|
pub fn setup_tray(app: &AppHandle) -> tauri::Result<()> {
|
||||||
|
let show_item = MenuItem::with_id(app, "show", "Show Hikari", true, None::<&str>)?;
|
||||||
|
let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||||
|
|
||||||
|
let menu = Menu::with_items(app, &[&show_item, &quit_item])?;
|
||||||
|
|
||||||
|
let _tray = TrayIconBuilder::with_id("main")
|
||||||
|
.icon(app.default_window_icon().unwrap().clone())
|
||||||
|
.menu(&menu)
|
||||||
|
.tooltip("Hikari - Claude Code Assistant")
|
||||||
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||||
|
"show" => {
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"quit" => {
|
||||||
|
app.exit(0);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.on_tray_icon_event(|tray, event| {
|
||||||
|
if let TrayIconEvent::Click {
|
||||||
|
button: MouseButton::Left,
|
||||||
|
button_state: MouseButtonState::Up,
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
let app = tray.app_handle();
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build(app)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_minimize_to_tray(app: &AppHandle) -> bool {
|
||||||
|
let config_path = app
|
||||||
|
.path()
|
||||||
|
.app_config_dir()
|
||||||
|
.ok()
|
||||||
|
.map(|p| p.join("hikari-config.json"));
|
||||||
|
|
||||||
|
if let Some(path) = config_path {
|
||||||
|
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||||
|
if let Ok(config) = serde_json::from_str::<HikariConfig>(&content) {
|
||||||
|
return config.minimize_to_tray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
|
},
|
||||||
|
"trayIcon": {
|
||||||
|
"id": "main",
|
||||||
|
"iconPath": "icons/32x32.png",
|
||||||
|
"iconAsTemplate": false,
|
||||||
|
"tooltip": "Hikari - Claude Code Assistant"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|||||||
+30
@@ -61,6 +61,36 @@
|
|||||||
--hljs-meta: #64748b;
|
--hljs-meta: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="high-contrast"] {
|
||||||
|
--bg-primary: #000000;
|
||||||
|
--bg-secondary: #0a0a0a;
|
||||||
|
--bg-terminal: #000000;
|
||||||
|
--bg-hover: #1a1a1a;
|
||||||
|
--bg-code: #0a0a0a;
|
||||||
|
--accent-primary: #ff4d6d;
|
||||||
|
--accent-secondary: #ff85a1;
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #e0e0e0;
|
||||||
|
--text-tertiary: #b0b0b0;
|
||||||
|
--border-color: #ffffff;
|
||||||
|
|
||||||
|
/* Terminal specific colors - bright and saturated */
|
||||||
|
--terminal-user: #00ffff;
|
||||||
|
--terminal-tool: #ff00ff;
|
||||||
|
--terminal-tool-name: #ffaaff;
|
||||||
|
--terminal-error: #ff5555;
|
||||||
|
|
||||||
|
/* Syntax highlighting colors (high contrast) */
|
||||||
|
--hljs-keyword: #ff66ff;
|
||||||
|
--hljs-string: #66ff66;
|
||||||
|
--hljs-number: #ffff00;
|
||||||
|
--hljs-comment: #aaaaaa;
|
||||||
|
--hljs-function: #ff99ff;
|
||||||
|
--hljs-type: #00ffff;
|
||||||
|
--hljs-variable: #ffaa00;
|
||||||
|
--hljs-meta: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
notification_volume: 0.7,
|
notification_volume: 0.7,
|
||||||
always_on_top: false,
|
always_on_top: false,
|
||||||
|
minimize_to_tray: false,
|
||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: null,
|
character_panel_width: null,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
@@ -404,7 +405,7 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onclick={() => handleThemeChange("dark")}
|
onclick={() => handleThemeChange("dark")}
|
||||||
class="flex-1 px-4 py-2 rounded-lg border transition-colors {config.theme === 'dark'
|
class="flex-1 px-3 py-2 rounded-lg border transition-colors {config.theme === 'dark'
|
||||||
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
||||||
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
||||||
>
|
>
|
||||||
@@ -412,12 +413,22 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onclick={() => handleThemeChange("light")}
|
onclick={() => handleThemeChange("light")}
|
||||||
class="flex-1 px-4 py-2 rounded-lg border transition-colors {config.theme === 'light'
|
class="flex-1 px-3 py-2 rounded-lg border transition-colors {config.theme === 'light'
|
||||||
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
||||||
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
||||||
>
|
>
|
||||||
Light
|
Light
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={() => handleThemeChange("high-contrast")}
|
||||||
|
class="flex-1 px-3 py-2 rounded-lg border transition-colors {config.theme ===
|
||||||
|
'high-contrast'
|
||||||
|
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
||||||
|
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
||||||
|
title="High contrast mode for improved accessibility"
|
||||||
|
>
|
||||||
|
High Contrast
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -477,6 +488,21 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Minimize to Tray Toggle -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.minimize_to_tray}
|
||||||
|
class="w-4 h-4 text-[var(--accent-primary)] bg-[var(--bg-primary)] border-[var(--border-color)] rounded focus:ring-[var(--accent-primary)] focus:ring-2"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--text-primary)]">Minimize to system tray</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
|
||||||
|
Hide to tray instead of closing when you click the X button
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Update Checks Toggle -->
|
<!-- Update Checks Toggle -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="flex items-center gap-3 cursor-pointer">
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: null,
|
character_panel_width: null,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
|
minimize_to_tray: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { writable, derived } from "svelte/store";
|
import { writable, derived } from "svelte/store";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
export type Theme = "dark" | "light";
|
export type Theme = "dark" | "light" | "high-contrast";
|
||||||
|
|
||||||
export interface HikariConfig {
|
export interface HikariConfig {
|
||||||
model: string | null;
|
model: string | null;
|
||||||
@@ -15,6 +15,7 @@ export interface HikariConfig {
|
|||||||
notifications_enabled: boolean;
|
notifications_enabled: boolean;
|
||||||
notification_volume: number;
|
notification_volume: number;
|
||||||
always_on_top: boolean;
|
always_on_top: boolean;
|
||||||
|
minimize_to_tray: boolean;
|
||||||
update_checks_enabled: boolean;
|
update_checks_enabled: boolean;
|
||||||
character_panel_width: number | null;
|
character_panel_width: number | null;
|
||||||
font_size: number;
|
font_size: number;
|
||||||
@@ -32,6 +33,7 @@ const defaultConfig: HikariConfig = {
|
|||||||
notifications_enabled: true,
|
notifications_enabled: true,
|
||||||
notification_volume: 0.7,
|
notification_volume: 0.7,
|
||||||
always_on_top: false,
|
always_on_top: false,
|
||||||
|
minimize_to_tray: false,
|
||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: null,
|
character_panel_width: null,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
|
|||||||
Reference in New Issue
Block a user