Skip to content

Commit 2cfcaa4

Browse files
committed
Allow UI color customization
1 parent 047d0e1 commit 2cfcaa4

10 files changed

Lines changed: 167 additions & 72 deletions

File tree

src-tauri/src/plugins/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ pub enum TrackViewDensity {
5757
pub struct Config {
5858
pub language: String,
5959
pub theme: String,
60+
pub ui_accent_color: Option<String>,
6061
pub audio_volume: f32,
6162
pub audio_playback_rate: Option<f32>,
6263
pub audio_output_device: String,
@@ -82,6 +83,7 @@ impl Config {
8283
Config {
8384
language: "en".to_owned(),
8485
theme: SYSTEM_THEME.to_owned(),
86+
ui_accent_color: None,
8587
audio_volume: 1.0,
8688
audio_playback_rate: Some(1.0),
8789
audio_output_device: "default".to_owned(),

src/components/Setting.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
opacity: 0.6;
4949
cursor: not-allowed;
5050
}
51+
52+
&[type="color"] {
53+
padding: 0;
54+
width: 80px;
55+
}
5156
}
5257

5358
.settingError {

src/components/Setting.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function Title(props: Props) {
4141

4242
export type InputProps = {
4343
label: string;
44-
description?: string;
44+
description?: string | React.ReactNode;
4545
};
4646

4747
export function Input(
@@ -84,3 +84,23 @@ export function Select(
8484
</div>
8585
);
8686
}
87+
88+
export function ColorSelector(
89+
props: React.InputHTMLAttributes<HTMLInputElement> & InputProps,
90+
) {
91+
const { label, description, ...otherProps } = props;
92+
const id = useId();
93+
94+
return (
95+
<div>
96+
<Label htmlFor={id}>{label}</Label>
97+
<input
98+
id={id}
99+
type="color"
100+
className={styles.settingInput}
101+
{...otherProps}
102+
/>
103+
{description != null && <Description>{description}</Description>}
104+
</div>
105+
);
106+
}

src/elements/Flexbox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type Props = {
77
children: React.ReactNode;
88
className?: string;
99
direction?: 'vertical' | 'horizontal';
10-
align?: 'center';
10+
align?: 'center' | 'baseline';
1111
};
1212

1313
export default function Flexbox(props: Props) {

src/generated/typings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
22

3-
export type Config = { language: string, theme: string, audio_volume: number, audio_playback_rate: number | null, audio_output_device: string, audio_follow_playing_track: boolean, audio_muted: boolean, audio_shuffle: boolean, audio_repeat: Repeat, default_view: DefaultView, library_sort_by: SortBy, library_sort_order: SortOrder, library_folders: Array<string>, library_autorefresh: boolean, sleepblocker: boolean, auto_update_checker: boolean, notifications: boolean, track_view_density: TrackViewDensity, };
3+
export type Config = { language: string, theme: string, ui_accent_color: string | null, audio_volume: number, audio_playback_rate: number | null, audio_output_device: string, audio_follow_playing_track: boolean, audio_muted: boolean, audio_shuffle: boolean, audio_repeat: Repeat, default_view: DefaultView, library_sort_by: SortBy, library_sort_order: SortOrder, library_folders: Array<string>, library_autorefresh: boolean, sleepblocker: boolean, auto_update_checker: boolean, notifications: boolean, track_view_density: TrackViewDensity, };
44

55
export type DefaultView = "Library" | "Playlists";
66

src/routes/settings.ui.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { t as tMacro } from '@lingui/core/macro';
22
import { Trans, useLingui } from '@lingui/react/macro';
33
import { createFileRoute, useLoaderData } from '@tanstack/react-router';
4+
import { debounce } from 'lodash-es';
5+
import { useMemo } from 'react';
46

57
import * as Setting from '../components/Setting';
68
import CheckboxSetting from '../components/SettingCheckbox';
9+
import Button from '../elements/Button';
710
import type { Config, DefaultView } from '../generated/typings';
811
import useInvalidate, { useInvalidateCallback } from '../hooks/useInvalidate';
912
import { themes } from '../lib/themes';
10-
import SettingsAPI from '../stores/SettingsAPI';
13+
import SettingsAPI, { DEFAULT_MAIN_COLOR } from '../stores/SettingsAPI';
1114
import { ALL_LANGUAGES } from '../translations/languages';
1215

1316
export const Route = createFileRoute('/settings/ui')({
@@ -20,6 +23,12 @@ function ViewSettingsUI() {
2023

2124
const invalidate = useInvalidate();
2225

26+
const setUIMainColorThrottled = useMemo(() => {
27+
return debounce((value: string) => {
28+
SettingsAPI.setUIMainColor(value).then(invalidate);
29+
}, 250);
30+
}, [invalidate]);
31+
2332
return (
2433
<>
2534
<Setting.Section>
@@ -41,6 +50,27 @@ function ViewSettingsUI() {
4150
})}
4251
</Setting.Select>
4352
</Setting.Section>
53+
<Setting.Section>
54+
<Setting.ColorSelector
55+
label={t`Accent color`}
56+
value={config.ui_accent_color ?? DEFAULT_MAIN_COLOR}
57+
description={
58+
<Button
59+
type="button"
60+
bSize="small"
61+
onClick={() => {
62+
SettingsAPI.setUIMainColor(DEFAULT_MAIN_COLOR).then(invalidate);
63+
SettingsAPI.applyUIMainColorToUI(DEFAULT_MAIN_COLOR);
64+
}}
65+
>{t`Reset`}</Button>
66+
}
67+
onChange={(e) => {
68+
const value = e.currentTarget.value;
69+
SettingsAPI.applyUIMainColorToUI(value);
70+
setUIMainColorThrottled(value);
71+
}}
72+
/>
73+
</Setting.Section>
4474
<Setting.Section>
4575
<Setting.Select
4676
label={t`Language`}

src/stores/SettingsAPI.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { logAndNotifyError } from '../lib/utils';
1313
import useLibraryStore from './useLibraryStore';
1414
import useToastsStore from './useToastsStore';
1515

16+
export const DEFAULT_MAIN_COLOR = '#459ce7';
17+
1618
// Manual prevention of a useEffect being called twice (to avoid refreshing the
1719
// library twice on startup in dev mode).
1820
// Also, we useInvalidate, SettingsAPI.init would infinitely loop. It means
@@ -27,11 +29,19 @@ async function init(then: () => void): Promise<void> {
2729

2830
did_init = true;
2931
// Blocking (the window should not be shown until it's done)
30-
await Promise.allSettled([
31-
checkTheme(),
32+
const [theme, color] = await Promise.all([
33+
getCurrentWindow()
34+
.theme()
35+
.then((maybeTheme) => maybeTheme ?? 'light'),
36+
config
37+
.get('ui_accent_color')
38+
.then((maybeColor) => maybeColor ?? DEFAULT_MAIN_COLOR),
3239
checkForUpdate({ silentFail: true }),
3340
]);
3441

42+
applyThemeToUI(theme);
43+
applyUIMainColorToUI(color);
44+
3545
// Show the app once everything is loaded
3646
await getCurrentWindow().show();
3747
info('UI is ready!');
@@ -72,7 +82,7 @@ const setTheme = async (themeID: string): Promise<void> => {
7282
/**
7383
* Apply theme colors to the BrowserWindow
7484
*/
75-
async function applyThemeToUI(themeID: string): Promise<void> {
85+
function applyThemeToUI(themeID: string): void {
7686
const theme = getTheme(themeID);
7787

7888
// TODO think about variables validity?
@@ -83,17 +93,28 @@ async function applyThemeToUI(themeID: string): Promise<void> {
8393
});
8494
}
8595

86-
async function checkTheme(): Promise<void> {
87-
const theme = (await getCurrentWindow().theme()) ?? 'light';
88-
applyThemeToUI(theme);
89-
}
90-
9196
async function setTracksDensity(
9297
density: Config['track_view_density'],
9398
): Promise<void> {
9499
await config.set('track_view_density', density);
95100
}
96101

102+
const setUIMainColor = async (
103+
mainColor: Config['ui_accent_color'],
104+
): Promise<void> => {
105+
await config.set('ui_accent_color', mainColor);
106+
};
107+
108+
const applyUIMainColorToUI = (mainColor: Config['ui_accent_color']) => {
109+
document.documentElement.style.setProperty('--main-color', mainColor);
110+
111+
// --main-color: #459ce7;
112+
// --main-color-darker: #3a73a4;
113+
// --main-color-lighter: #63aff0;
114+
// --link-color: #459ce7;
115+
// --link-color-hover: #52afff;
116+
};
117+
97118
/**
98119
* Check if a new release is available
99120
*/
@@ -214,6 +235,8 @@ const SettingsAPI = {
214235
setLanguage,
215236
setTheme,
216237
applyThemeToUI,
238+
setUIMainColor,
239+
applyUIMainColorToUI,
217240
setTracksDensity,
218241
checkForUpdate,
219242
toggleSleepBlocker,

src/translations/en.po

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ msgstr "Artists"
6666

6767
#: src/components/Footer.tsx:39
6868
#: src/routes/playlists.tsx:169
69-
#: src/routes/settings.ui.tsx:96
69+
#: src/routes/settings.ui.tsx:126
7070
msgid "Playlists"
7171
msgstr "Playlists"
7272

@@ -449,6 +449,7 @@ msgid "Reset library?"
449449
msgstr "Reset library?"
450450

451451
#: src/routes/settings.library.tsx:144
452+
#: src/routes/settings.ui.tsx:65
452453
msgid "Reset"
453454
msgstr "Reset"
454455

@@ -468,71 +469,75 @@ msgstr "Interface"
468469
msgid "About"
469470
msgstr "About"
470471

471-
#: src/routes/settings.ui.tsx:27
472+
#: src/routes/settings.ui.tsx:36
472473
msgid "Theme"
473474
msgstr "Theme"
474475

475-
#: src/routes/settings.ui.tsx:28
476+
#: src/routes/settings.ui.tsx:37
476477
msgid "Change the appearance of the interface"
477478
msgstr "Change the appearance of the interface"
478479

479-
#: src/routes/settings.ui.tsx:34
480+
#: src/routes/settings.ui.tsx:43
480481
msgid "System (default)"
481482
msgstr "System (default)"
482483

483-
#: src/routes/settings.ui.tsx:46
484+
#: src/routes/settings.ui.tsx:55
485+
msgid "Accent color"
486+
msgstr "Accent color"
487+
488+
#: src/routes/settings.ui.tsx:76
484489
msgid "Language"
485490
msgstr "Language"
486491

487-
#: src/routes/settings.ui.tsx:64
492+
#: src/routes/settings.ui.tsx:94
488493
msgid "Tracks density"
489494
msgstr "Tracks density"
490495

491-
#: src/routes/settings.ui.tsx:65
496+
#: src/routes/settings.ui.tsx:95
492497
msgid "Change the tracks spacing"
493498
msgstr "Change the tracks spacing"
494499

495-
#: src/routes/settings.ui.tsx:74
500+
#: src/routes/settings.ui.tsx:104
496501
msgid "Normal (default)"
497502
msgstr "Normal (default)"
498503

499-
#: src/routes/settings.ui.tsx:77
504+
#: src/routes/settings.ui.tsx:107
500505
msgid "Compact"
501506
msgstr "Compact"
502507

503-
#: src/routes/settings.ui.tsx:83
508+
#: src/routes/settings.ui.tsx:113
504509
msgid "Default view"
505510
msgstr "Default view"
506511

507-
#: src/routes/settings.ui.tsx:85
512+
#: src/routes/settings.ui.tsx:115
508513
msgid "Change the default view when starting the application"
509514
msgstr "Change the default view when starting the application"
510515

511-
#: src/routes/settings.ui.tsx:93
516+
#: src/routes/settings.ui.tsx:123
512517
msgid "Library (default)"
513518
msgstr "Library (default)"
514519

515-
#: src/routes/settings.ui.tsx:103
520+
#: src/routes/settings.ui.tsx:133
516521
msgid "Display Notifications"
517522
msgstr "Display Notifications"
518523

519-
#: src/routes/settings.ui.tsx:104
524+
#: src/routes/settings.ui.tsx:134
520525
msgid "Send notifications when the playing track changes"
521526
msgstr "Send notifications when the playing track changes"
522527

523-
#: src/routes/settings.ui.tsx:114
528+
#: src/routes/settings.ui.tsx:144
524529
msgid "Sleep mode blocker"
525530
msgstr "Sleep mode blocker"
526531

527-
#: src/routes/settings.ui.tsx:115
532+
#: src/routes/settings.ui.tsx:145
528533
msgid "Prevent the computer from going into sleep mode when playing"
529534
msgstr "Prevent the computer from going into sleep mode when playing"
530535

531-
#: src/routes/settings.ui.tsx:130
536+
#: src/routes/settings.ui.tsx:160
532537
msgid "Light"
533538
msgstr "Light"
534539

535-
#: src/routes/settings.ui.tsx:132
540+
#: src/routes/settings.ui.tsx:162
536541
msgid "Dark"
537542
msgstr "Dark"
538543

@@ -591,11 +596,11 @@ msgid "The playlist \"{name}\" was created"
591596
msgstr "The playlist \"{name}\" was created"
592597

593598
#. placeholder {0}: newRelease.tag_name
594-
#: src/stores/SettingsAPI.ts:137
599+
#: src/stores/SettingsAPI.ts:158
595600
msgid "Museeks {0} is available, check https://museeks.io!"
596601
msgstr "Museeks {0} is available, check https://museeks.io!"
597602

598-
#: src/stores/SettingsAPI.ts:139
603+
#: src/stores/SettingsAPI.ts:160
599604
msgid "Museeks {currentVersion} is the latest version available."
600605
msgstr "Museeks {currentVersion} is the latest version available."
601606

0 commit comments

Comments
 (0)