mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-09-19 17:37:29 +02:00
Fix system theme pair integration
This commit is contained in:
@@ -332,7 +332,11 @@ pub fn set_theme(state: State<'_, AppState>, theme: String) -> Result<(), String
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn set_system_themes(state: State<'_, AppState>, light: String, dark: String) -> Result<(), String> {
|
pub fn set_system_themes(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
light: String,
|
||||||
|
dark: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
||||||
config.system_light_theme = light;
|
config.system_light_theme = light;
|
||||||
config.system_dark_theme = dark;
|
config.system_dark_theme = dark;
|
||||||
@@ -363,12 +367,41 @@ pub fn save_custom_theme(state: State<'_, AppState>, theme: crate::types::Custom
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn delete_custom_theme(state: State<'_, AppState>, id: String) -> Result<(), String> {
|
pub fn delete_custom_theme(state: State<'_, AppState>, id: String) -> Result<(), String> {
|
||||||
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
||||||
|
clear_custom_theme_references(&mut config, &id);
|
||||||
|
save_app_config(&config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_custom_theme_references(config: &mut AppConfig, id: &str) {
|
||||||
config.custom_themes.retain(|t| t.id != id);
|
config.custom_themes.retain(|t| t.id != id);
|
||||||
if config.theme == id {
|
if config.theme == id {
|
||||||
config.theme = "system".to_string();
|
config.theme = "system".to_string();
|
||||||
}
|
}
|
||||||
save_app_config(&config)?;
|
if config.system_light_theme == id {
|
||||||
Ok(())
|
config.system_light_theme = "light".to_string();
|
||||||
|
}
|
||||||
|
if config.system_dark_theme == id {
|
||||||
|
config.system_dark_theme = "dark".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod custom_theme_reference_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deleting_custom_theme_resets_system_pair_references() {
|
||||||
|
let mut config = AppConfig::default();
|
||||||
|
config.theme = "custom-work".to_string();
|
||||||
|
config.system_light_theme = "custom-work".to_string();
|
||||||
|
config.system_dark_theme = "custom-work".to_string();
|
||||||
|
|
||||||
|
clear_custom_theme_references(&mut config, "custom-work");
|
||||||
|
|
||||||
|
assert_eq!(config.theme, "system");
|
||||||
|
assert_eq!(config.system_light_theme, "light");
|
||||||
|
assert_eq!(config.system_dark_theme, "dark");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
+14
-1
@@ -505,7 +505,7 @@ pub struct TaskItem {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod startup_view_tests {
|
mod startup_view_tests {
|
||||||
use super::StartupView;
|
use super::{AppConfig, StartupView};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serializes_supported_startup_views() {
|
fn serializes_supported_startup_views() {
|
||||||
@@ -526,4 +526,17 @@ mod startup_view_tests {
|
|||||||
StartupView::All
|
StartupView::All
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn existing_configs_default_system_theme_pair() {
|
||||||
|
let mut value = serde_json::to_value(AppConfig::default()).unwrap();
|
||||||
|
let object = value.as_object_mut().unwrap();
|
||||||
|
object.remove("system_light_theme");
|
||||||
|
object.remove("system_dark_theme");
|
||||||
|
|
||||||
|
let config: AppConfig = serde_json::from_value(value).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.system_light_theme, "light");
|
||||||
|
assert_eq!(config.system_dark_theme, "dark");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
showCommandPalette,
|
showCommandPalette,
|
||||||
theme,
|
theme,
|
||||||
resolvedTheme,
|
resolvedTheme,
|
||||||
|
customThemes,
|
||||||
focusMode,
|
focusMode,
|
||||||
readOnly,
|
readOnly,
|
||||||
activeNote,
|
activeNote,
|
||||||
@@ -508,7 +509,8 @@
|
|||||||
createAndFocusNote();
|
createAndFocusNote();
|
||||||
return;
|
return;
|
||||||
case 'toggle-theme': {
|
case 'toggle-theme': {
|
||||||
const isDark = darkThemes.includes($resolvedTheme);
|
const customTheme = $customThemes.find(theme => theme.id === $resolvedTheme);
|
||||||
|
const isDark = darkThemes.includes($resolvedTheme) || (customTheme?.is_dark ?? false);
|
||||||
const next = isDark ? 'light' : 'dark';
|
const next = isDark ? 'light' : 'dark';
|
||||||
$theme = next;
|
$theme = next;
|
||||||
setTheme(next);
|
setTheme(next);
|
||||||
@@ -566,23 +568,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTheme(t: string) {
|
|
||||||
const namedThemes = ['solarized-light', 'solarized-dark', 'catppuccin', 'nord', 'tokyo-night', 'github-light', 'github-dark', 'dracula', 'blueberry', 'forest-green', 'gruvbox', 'midnight-tide', 'cherry-blossom', 'synthwave', 'ember', 'moonlit', 'light-coffee', 'dark-coffee', 'cotton-candy', 'crimson', 'cloud', 'peach', 'material-dark', 'material-light', 'monokai', 'rose-pine', 'everforest', 'horizon', 'cyberpunk', 'black', 'one-dark'];
|
|
||||||
const root = document.documentElement;
|
|
||||||
root.classList.remove('dark');
|
|
||||||
root.removeAttribute('data-theme');
|
|
||||||
if (namedThemes.includes(t)) {
|
|
||||||
root.setAttribute('data-theme', t);
|
|
||||||
if (darkThemes.includes(t)) root.classList.add('dark');
|
|
||||||
} else if (t === 'dark') {
|
|
||||||
root.classList.add('dark');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
applyTheme($theme);
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
$collapsedNotebooks;
|
$collapsedNotebooks;
|
||||||
persistState();
|
persistState();
|
||||||
@@ -664,10 +649,6 @@
|
|||||||
prefetchPromise = readNote(lastNotePath).catch(() => null);
|
prefetchPromise = readNote(lastNotePath).catch(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// One-off apply to avoid a flash before the layout's reactive effect runs; OS appearance
|
|
||||||
// changes are picked up by resolvedTheme, so no media-query listener is needed here.
|
|
||||||
applyTheme($resolvedTheme);
|
|
||||||
|
|
||||||
// Run sidebar and note list refresh in parallel
|
// Run sidebar and note list refresh in parallel
|
||||||
await Promise.all([sidebar?.refresh(), noteList?.refresh()]);
|
await Promise.all([sidebar?.refresh(), noteList?.refresh()]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { showCommandPalette, showSearch, theme, resolvedTheme, sourceMode, viewMode, activeNotebook, activeTag } from '$lib/stores/app';
|
import { showCommandPalette, showSearch, theme, sourceMode, viewMode, activeNotebook, activeTag } from '$lib/stores/app';
|
||||||
import { setTheme, reindex } from '$lib/api';
|
import { setTheme, reindex } from '$lib/api';
|
||||||
import { darkThemes } from '$lib/platform';
|
import { darkThemes } from '$lib/platform';
|
||||||
|
|
||||||
@@ -89,7 +89,6 @@
|
|||||||
action: () => {
|
action: () => {
|
||||||
$theme = 'system';
|
$theme = 'system';
|
||||||
setTheme('system');
|
setTheme('system');
|
||||||
applyTheme($resolvedTheme);
|
|
||||||
$showCommandPalette = false;
|
$showCommandPalette = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
activeNotePath,
|
activeNotePath,
|
||||||
editorDirty,
|
editorDirty,
|
||||||
readOnly,
|
readOnly,
|
||||||
sourceMode,
|
sourceMode
|
||||||
resolvedTheme
|
|
||||||
} from '$lib/stores/app';
|
} from '$lib/stores/app';
|
||||||
import { readNote } from '$lib/api';
|
import { readNote } from '$lib/api';
|
||||||
import { keybindings, matchAction } from '$lib/keybindings';
|
import { keybindings, matchAction } from '$lib/keybindings';
|
||||||
@@ -17,8 +16,6 @@
|
|||||||
|
|
||||||
let { notePath }: { notePath: string } = $props();
|
let { notePath }: { notePath: string } = $props();
|
||||||
|
|
||||||
import { darkThemes } from '$lib/platform';
|
|
||||||
|
|
||||||
const appWindow = getCurrentWindow();
|
const appWindow = getCurrentWindow();
|
||||||
const isMac = navigator.platform.startsWith('Mac');
|
const isMac = navigator.platform.startsWith('Mac');
|
||||||
let editor = $state<Editor>(null!);
|
let editor = $state<Editor>(null!);
|
||||||
@@ -36,20 +33,6 @@
|
|||||||
return () => { unlisten.then(fn => fn()); };
|
return () => { unlisten.then(fn => fn()); };
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const t = $resolvedTheme || 'light';
|
|
||||||
const namedThemes = ['solarized-light', 'solarized-dark', 'catppuccin', 'nord', 'tokyo-night', 'github-light', 'github-dark', 'dracula', 'blueberry', 'forest-green', 'gruvbox', 'midnight-tide', 'cherry-blossom', 'synthwave', 'ember', 'moonlit', 'light-coffee', 'dark-coffee', 'cotton-candy', 'crimson', 'cloud', 'peach', 'material-dark', 'material-light', 'monokai', 'rose-pine', 'everforest', 'horizon', 'cyberpunk', 'black', 'one-dark'];
|
|
||||||
const root = document.documentElement;
|
|
||||||
root.classList.remove('dark');
|
|
||||||
root.removeAttribute('data-theme');
|
|
||||||
if (namedThemes.includes(t)) {
|
|
||||||
root.setAttribute('data-theme', t);
|
|
||||||
if (darkThemes.includes(t)) root.classList.add('dark');
|
|
||||||
} else if (t === 'dark') {
|
|
||||||
root.classList.add('dark');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let lastMouseDown = 0;
|
let lastMouseDown = 0;
|
||||||
const RESIZE_EDGE = 6;
|
const RESIZE_EDGE = 6;
|
||||||
|
|
||||||
|
|||||||
@@ -690,9 +690,15 @@
|
|||||||
async function removeCustomTheme(ct: CustomTheme) {
|
async function removeCustomTheme(ct: CustomTheme) {
|
||||||
try {
|
try {
|
||||||
await deleteCustomTheme(ct.id);
|
await deleteCustomTheme(ct.id);
|
||||||
|
if (systemLightTheme === ct.id) systemLightTheme = 'light';
|
||||||
|
if (systemDarkTheme === ct.id) systemDarkTheme = 'dark';
|
||||||
if ($appConfig) {
|
if ($appConfig) {
|
||||||
$appConfig.custom_themes = $appConfig.custom_themes.filter(t => t.id !== ct.id);
|
$appConfig = {
|
||||||
$appConfig = { ...$appConfig };
|
...$appConfig,
|
||||||
|
custom_themes: $appConfig.custom_themes.filter(t => t.id !== ct.id),
|
||||||
|
system_light_theme: systemLightTheme,
|
||||||
|
system_dark_theme: systemDarkTheme,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if ($theme === ct.id) {
|
if ($theme === ct.id) {
|
||||||
$theme = 'system';
|
$theme = 'system';
|
||||||
@@ -1044,9 +1050,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-apply when theme changes
|
// Re-apply when the concrete theme changes, including an OS appearance switch.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const _ = $theme;
|
const _ = $resolvedTheme;
|
||||||
const preset = accentPresets.find(p => p.name === activeAccent);
|
const preset = accentPresets.find(p => p.name === activeAccent);
|
||||||
if (preset) {
|
if (preset) {
|
||||||
applyAccent(preset);
|
applyAccent(preset);
|
||||||
|
|||||||
@@ -102,8 +102,13 @@ export const resolvedTheme = derived(
|
|||||||
[theme, appConfig, systemPrefersDark],
|
[theme, appConfig, systemPrefersDark],
|
||||||
([$theme, $config, $prefersDark]): string => {
|
([$theme, $config, $prefersDark]): string => {
|
||||||
if ($theme !== "system") return $theme;
|
if ($theme !== "system") return $theme;
|
||||||
|
const fallback = $prefersDark ? "dark" : "light";
|
||||||
const paired = $prefersDark ? $config?.system_dark_theme : $config?.system_light_theme;
|
const paired = $prefersDark ? $config?.system_dark_theme : $config?.system_light_theme;
|
||||||
return paired || ($prefersDark ? "dark" : "light");
|
if (!paired) return fallback;
|
||||||
|
if (paired.startsWith("custom-") && !$config?.custom_themes.some((item) => item.id === paired)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return paired;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,8 @@
|
|||||||
}
|
}
|
||||||
// Apply saved accent
|
// Apply saved accent
|
||||||
if (config.accent_color) {
|
if (config.accent_color) {
|
||||||
const isDark = darkThemes.includes(themeValue);
|
const customTheme = config.custom_themes.find(theme => theme.id === themeValue);
|
||||||
|
const isDark = darkThemes.includes(themeValue) || (customTheme?.is_dark ?? false);
|
||||||
let color: string | null = null;
|
let color: string | null = null;
|
||||||
if (config.accent_color.startsWith('#')) {
|
if (config.accent_color.startsWith('#')) {
|
||||||
color = config.accent_color;
|
color = config.accent_color;
|
||||||
|
|||||||
Reference in New Issue
Block a user