feat: new window "Appearance settings" for web app (#7318)

* feat: new window "Appearance settings" for web app

* feat: new window "Appearance settings" for web app
This commit is contained in:
Rukario
2026-02-05 06:49:17 -08:00
committed by GitHub
parent 2e65935bbc
commit cc66625fd2
7 changed files with 218 additions and 29 deletions

View File

@@ -2,6 +2,7 @@ set(WEB_SOURCES
src/about-dialog.js
src/action-manager.js
src/alert-dialog.js
src/appearance-settings.js
src/context-menu.js
src/file-row.js
src/formatter.js

View File

@@ -30,11 +30,11 @@
--black: #000;
--blue-100: #51b3f7;
--blue-200: #357aaa;
--blue-300: #2c7fea;
--blue-400: #1847d4;
--dark-mode-black: #121212;
--dark-mode-white: #c9d1d9;
--default-accent-color-dark: #0c2d6b;
--default-accent-color: #cdcdff;
--default-accent-color-strong: #b0b0ff;
--default-border-dark: #575757;
--default-border-light: #aeaeae;
--default-tinted: rgba(128, 128, 144, 0.125);
@@ -92,7 +92,7 @@
--color-bg-odd: var(--black);
--color-bg-popup: var(--black);
--color-bg-primary: var(--black);
--color-bg-selected: var(--default-accent-color-dark);
--color-bg-selected: var(--default-accent-color-strong);
--color-bg-warn: #cf6679;
--color-border-default: var(--default-border-dark);
--color-border-stark: var(--dark-mode-white);
@@ -101,7 +101,7 @@
--color-fg-on-popup: var(--nice-grey);
--color-fg-primary: var(--dark-mode-white);
--color-fg-secondary: var(--nice-grey);
--color-fg-selected: var(--dark-mode-white);
--color-fg-selected: #404040;
--color-fg-tertiary: var(--grey-500);
--color-fg-warn: var(--dark-mode-black);
--color-progressbar-fg-1: #edefff;
@@ -118,7 +118,6 @@
.contrast-more {
--color-bg-even: var(--black);
--color-bg-hover: var(--grey-40);
--color-bg-selected: var(--blue-300);
--color-bg-tabs: var(--black);
--color-bg-warn: #cf6679;
--color-border-default: var(--white);
@@ -131,7 +130,7 @@
--color-fg-port-open: var(--green-100);
--color-fg-primary: var(--white);
--color-fg-secondary: var(--white);
--color-fg-selected: var(--white);
--color-fg-selected: var(--black);
--color-fg-tabs: var(--white);
--color-fg-tertiary: var(--white);
--color-fg-warn: var(--black);
@@ -155,7 +154,7 @@
--color-bg-odd: var(--white);
--color-bg-popup: var(--white);
--color-bg-primary: var(--white);
--color-bg-selected: var(--blue-300);
--color-bg-selected: var(--default-accent-color);
--color-bg-warn: #e4606d5b;
--color-border-default: var(--default-border-light);
--color-border-stark: var(--grey-500);
@@ -168,7 +167,7 @@
--color-fg-port-open: var(--green-400);
--color-fg-primary: #404040;
--color-fg-secondary: var(--grey-500);
--color-fg-selected: var(--nice-grey);
--color-fg-selected: #404040;
--color-fg-tertiary: var(--grey-500);
--color-fg-warn: #cf212e;
--color-progressbar-fg-1: #303030;
@@ -189,7 +188,7 @@
.contrast-more {
--color-bg-even: var(--white);
--color-bg-hover: var(--grey-40);
--color-bg-selected: var(--blue-300);
--color-bg-selected: var(--default-accent-color-strong);
--color-bg-tabs: var(--white);
--color-bg-warn: #cf6679;
--color-border-default: var(--black);
@@ -202,7 +201,7 @@
--color-fg-port-open: var(--green-400);
--color-fg-primary: var(--black);
--color-fg-secondary: var(--black);
--color-fg-selected: var(--white);
--color-fg-selected: var(--black);
--color-fg-tabs: var(--black);
--color-fg-tertiary: var(--black);
--color-fg-warn: var(--white);
@@ -236,6 +235,24 @@
}
}
@supports (background-color: Highlight) {
:root {
body:not(.highlight-legacy) {
--color-bg-selected: Highlight;
--color-fg-selected: HighlightText;
}
}
}
@supports (background-color: AccentColor) {
:root {
body:not(.highlight-legacy):not(.highlight-system) {
--color-bg-selected: AccentColor;
--color-fg-selected: #f8f8f8;
}
}
}
html,
body {
background-color: var(--color-bg-primary);
@@ -248,6 +265,15 @@ body {
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
}
img {
border: 0;
}

View File

@@ -20,6 +20,10 @@ export class ActionManager extends EventTarget {
'move-down': { enabled: false, text: 'Down' },
'move-top': { enabled: false, text: 'Top' },
'move-up': { enabled: false, text: 'Up' },
'open-appearance-settings': {
enabled: true,
text: 'Appearance settings',
},
'open-torrent': {
enabled: true,
shortcut: 'O',

View File

@@ -0,0 +1,140 @@
/* @license This file Copyright © Mnemosyne LLC.
It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
or any future license endorsed by Mnemosyne LLC.
License text can be found in the licenses/ folder. */
import { Prefs } from './prefs.js';
import { createDialogContainer, makeUUID } from './utils.js';
export class Appearance extends EventTarget {
constructor(prefs, action_manager) {
super();
this.action_manager = action_manager;
this.prefs_listener = this._onPrefsChange.bind(this);
this.prefs = prefs;
this.prefs.addEventListener('change', this.prefs_listener);
this.elements = this._create();
this.elements.dismiss.addEventListener('click', () => this.close());
document.body.append(this.elements.root);
this.elements.dismiss.focus();
}
close() {
this.elements.root.remove();
this.dispatchEvent(new Event('close'));
delete this.elements;
}
_onPrefsChange(event_) {
switch (event_.key) {
case Prefs.SortDirection:
case Prefs.SortMode:
this.root.querySelector(`[data-pref="${event_.key}"]`).value =
event_.value;
break;
default:
break;
}
}
_create() {
const elements = createDialogContainer('dis-appearance');
const { dismiss, heading, message } = elements;
heading.textContent = 'Appearance';
dismiss.textContent = 'Close';
// contrast
let legend = document.createElement('h4');
message.append(legend);
legend.textContent = 'Theme';
let div = document.createElement('div');
div.classList.add('table-row');
message.append(div);
const add_check = (text, listener) => {
const check = document.createElement('input');
check.id = makeUUID();
check.type = 'checkbox';
const label = document.createElement('label');
label.htmlFor = check.id;
label.textContent = text;
div.append(check, label);
listener(check);
};
const add_radio = (name, text, className, style, listener) => {
const input = document.createElement('input');
input.id = makeUUID();
input.name = name;
input.type = 'radio';
input.value = style;
const label = document.createElement('label');
label.htmlFor = input.id;
label.textContent = text;
div.append(input, label, document.createElement('BR'));
listener(input, className);
};
let listener = (e) => {
e.checked = this.prefs.contrast_mode === Prefs.ContrastMore;
e.addEventListener('change', (event_) => {
const { checked } = event_.target;
this.prefs.contrast_mode = checked
? Prefs.ContrastMore
: Prefs.ContrastLess;
});
};
add_check(this.action_manager.text('toggle-contrast'), listener);
// highlight color
legend = document.createElement('h4');
message.append(legend);
legend.textContent = 'Highlight color';
div = document.createElement('div');
div.classList.add('table-row');
message.append(div);
listener = (e, className) => {
e.checked = !className || document.body.classList.contains(className);
e.addEventListener('change', (event_) => {
const { value } = event_.target;
this.prefs.highlight_color = value;
});
};
add_radio(
'highlight-color',
'Accent color from system',
null,
'AccentColor',
listener,
);
add_radio(
'highlight-color',
'Highlight color from system',
'highlight-system',
'Highlight',
listener,
);
add_radio('highlight-color', 'Legacy', 'highlight-legacy', null, listener);
elements.confirm.remove();
delete elements.confirm;
return elements;
}
}

View File

@@ -244,24 +244,6 @@ export class OverflowMenu extends EventTarget {
add_checkbox(this.action_manager.text('toggle-compact-rows'), listener);
// contrast
div = document.createElement('div');
div.classList.add('table-row');
options.append(div);
listener = (e) => {
e.checked = this.prefs.contrast_mode === Prefs.ContrastMore;
e.addEventListener('change', (event_) => {
const { checked } = event_.target;
this.prefs.contrast_mode = checked
? Prefs.ContrastMore
: Prefs.ContrastLess;
});
};
add_checkbox(this.action_manager.text('toggle-contrast'), listener);
// fullscreen
div = document.createElement('div');
@@ -286,6 +268,19 @@ export class OverflowMenu extends EventTarget {
add_checkbox('Fullscreen', listener);
// appearance
const appearance_text = this.action_manager.text(
'open-appearance-settings',
);
div = make_button(
section,
appearance_text,
'open-appearance-settings',
on_click,
);
options.append(div);
// SPEED LIMIT
section = make_section('speed', 'Speed Limit');

View File

@@ -113,6 +113,7 @@ Prefs.FilterPaused = 'paused';
Prefs.FilterPrivate = 'private';
Prefs.FilterPublic = 'public';
Prefs.FilterSeeding = 'seeding';
Prefs.HighlightColor = 'highlight-color';
Prefs.NotificationsEnabled = 'notifications-enabled';
Prefs.RefreshRate = 'refresh-rate-sec';
Prefs.SortAscending = 'ascending';
@@ -136,6 +137,7 @@ Prefs._Defaults = {
? Prefs.ContrastMore
: Prefs.ContrastLess,
[Prefs.FilterMode]: Prefs.FilterAll,
[Prefs.HighlightColor]: 'AccentColor',
[Prefs.NotificationsEnabled]: false,
[Prefs.RefreshRate]: 5,
[Prefs.SortDirection]: Prefs.SortAscending,

View File

@@ -3,6 +3,7 @@
License text can be found in the licenses/ folder. */
import { AboutDialog } from './about-dialog.js';
import { Appearance } from './appearance-settings.js';
import { ContextMenu } from './context-menu.js';
import { Formatter } from './formatter.js';
import { Inspector } from './inspector.js';
@@ -141,6 +142,17 @@ export class Transmission extends EventTarget {
case 'move-up':
this._moveUp();
break;
case 'open-appearance-settings':
if (
this.popup[Transmission.default_popup_level] instanceof Appearance
) {
this.popup[Transmission.default_popup_level].close();
} else {
this.setCurrentPopup(
new Appearance(this.prefs, this.action_manager),
);
}
break;
case 'open-torrent':
this.setCurrentPopup(new OpenDialog(this, this.remote));
break;
@@ -405,7 +417,6 @@ export class Transmission extends EventTarget {
// Add custom class to the body/html element to get the appropriate contrast color scheme
document.body.classList.remove('contrast-more', 'contrast-less');
document.body.classList.add(`contrast-${value}`);
// this.refilterAllSoon();
break;
}
@@ -415,6 +426,16 @@ export class Transmission extends EventTarget {
this.refilterAllSoon();
break;
case Prefs.HighlightColor: {
document.body.classList.remove('highlight-legacy', 'highlight-system');
if (!value) {
document.body.classList.add('highlight-legacy');
} else if (value === 'Highlight') {
document.body.classList.add('highlight-system');
}
break;
}
case Prefs.RefreshRate: {
clearInterval(this.refreshTorrentsInterval);
const callback = this.refreshTorrents.bind(this);