Chore: change dark mode to use Bootstrap's color modes (#4174)

* Change setting dark mode to use Bootstrap's data-bs-theme attribute

* Update dark mode styling to use Bootstrap's color mode attribute

* Update unit tests and lints

* Fix not reflecting custom theme color

* Remove commented-out code

* fix inverted thumbnails in dark mode & card borders

* prettier

* Fix application of dark mode, tests

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
Dominik Mielcarek 2023-09-13 20:11:44 +02:00 committed by GitHub
parent d1292c59ea
commit 78ae4c42f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 41 deletions

View File

@ -46,10 +46,10 @@ test('should warn on unsaved changes', async ({ page }) => {
test('should apply appearance changes when set', async ({ page }) => { test('should apply appearance changes when set', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' }) await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
await page.goto('/settings') await page.goto('/settings')
await expect(page.locator('body')).toHaveClass(/color-scheme-system/) await expect(page.locator('html')).toHaveAttribute('data-bs-theme', /auto/)
await page.getByLabel('Use system setting').click() await page.getByLabel('Use system setting').click()
await page.getByLabel('Enable dark mode').click() await page.getByLabel('Enable dark mode').click()
await expect(page.locator('body')).toHaveClass(/color-scheme-dark/) await expect(page.locator('html')).toHaveAttribute('data-bs-theme', /dark/)
}) })
test('should toggle saved view options when set & saved', async ({ page }) => { test('should toggle saved view options when set & saved', async ({ page }) => {

View File

@ -147,13 +147,14 @@ describe('SettingsService', () => {
).toEqual('') ).toEqual('')
const addClassSpy = jest.spyOn(settingsService.renderer, 'addClass') const addClassSpy = jest.spyOn(settingsService.renderer, 'addClass')
const removeClassSpy = jest.spyOn(settingsService.renderer, 'removeClass') const setAttributeSpy = jest.spyOn(settingsService.renderer, 'setAttribute')
settingsService.updateAppearanceSettings(true, true, '#fff000') settingsService.updateAppearanceSettings(true, true, '#fff000')
expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-light') expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-light')
expect(addClassSpy).toHaveBeenCalledWith( expect(setAttributeSpy).toHaveBeenCalledWith(
document.body, document.documentElement,
'color-scheme-system' 'data-bs-theme',
'auto'
) )
expect( expect(
document.body.style.getPropertyValue('--pngx-primary-lightness') document.body.style.getPropertyValue('--pngx-primary-lightness')
@ -161,21 +162,23 @@ describe('SettingsService', () => {
settingsService.updateAppearanceSettings(false, false, '#000000') settingsService.updateAppearanceSettings(false, false, '#000000')
expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-light') expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-light')
expect(removeClassSpy).toHaveBeenCalledWith( expect(setAttributeSpy).toHaveBeenCalledWith(
document.body, document.documentElement,
'color-scheme-system' 'data-bs-theme',
'light'
) )
expect( expect(
document.body.style.getPropertyValue('--pngx-primary-lightness') document.body.style.getPropertyValue('--pngx-primary-lightness')
).toEqual('0%') ).toEqual('0%')
settingsService.updateAppearanceSettings(false, true, '#ffffff') settingsService.updateAppearanceSettings(false, true, '#ffffff')
expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-dark') expect(addClassSpy).toHaveBeenCalledWith(document.body, 'primary-dark')
expect(removeClassSpy).toHaveBeenCalledWith( expect(setAttributeSpy).toHaveBeenCalledWith(
document.body, document.documentElement,
'color-scheme-system' 'data-bs-theme',
'dark'
) )
expect(addClassSpy).toHaveBeenCalledWith(document.body, 'color-scheme-dark')
expect( expect(
document.body.style.getPropertyValue('--pngx-primary-lightness') document.body.style.getPropertyValue('--pngx-primary-lightness')
).toEqual('100%') ).toEqual('100%')

View File

@ -106,19 +106,19 @@ export class SettingsService {
themeColor ??= this.get(SETTINGS_KEYS.THEME_COLOR) themeColor ??= this.get(SETTINGS_KEYS.THEME_COLOR)
if (darkModeUseSystem) { if (darkModeUseSystem) {
this._renderer.addClass(this.document.body, 'color-scheme-system') this._renderer.setAttribute(
this._renderer.removeClass(this.document.body, 'color-scheme-dark') this.document.documentElement,
'data-bs-theme',
'auto'
)
} else { } else {
this._renderer.removeClass(this.document.body, 'color-scheme-system') this._renderer.setAttribute(
darkModeEnabled this.document.documentElement,
? this._renderer.addClass(this.document.body, 'color-scheme-dark') 'data-bs-theme',
: this._renderer.removeClass(this.document.body, 'color-scheme-dark') darkModeEnabled ? 'dark' : 'light'
)
} }
// remove these in case they were there
this._renderer.removeClass(this.document.body, 'primary-dark')
this._renderer.removeClass(this.document.body, 'primary-light')
if (themeColor) { if (themeColor) {
const hsl = hexToHsl(themeColor) const hsl = hexToHsl(themeColor)
const bgBrightnessEstimate = estimateBrightnessForColor(themeColor) const bgBrightnessEstimate = estimateBrightnessForColor(themeColor)
@ -142,6 +142,16 @@ export class SettingsService {
`${hsl.l * 100}%`, `${hsl.l * 100}%`,
RendererStyleFlags2.DashCase RendererStyleFlags2.DashCase
) )
/**
* Fix for not reflecting changed variables. (--bs-primary is at :root while here we set them to body)
*/
this._renderer.setStyle(
document.body,
'--bs-primary',
'hsl(var(--pngx-primary), var(--pngx-primary-lightness))',
RendererStyleFlags2.DashCase
)
} else { } else {
this._renderer.removeStyle( this._renderer.removeStyle(
document.body, document.body,
@ -153,6 +163,11 @@ export class SettingsService {
'--pngx-primary-lightness', '--pngx-primary-lightness',
RendererStyleFlags2.DashCase RendererStyleFlags2.DashCase
) )
this._renderer.removeStyle(
document.body,
'--bs-primary',
RendererStyleFlags2.DashCase
)
} }
} }

View File

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Paperless-ngx</title> <title>Paperless-ngx</title>
@ -11,7 +11,7 @@
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="apple-touch-icon" href="apple-touch-icon.png"> <link rel="apple-touch-icon" href="apple-touch-icon.png">
</head> </head>
<body class="color-scheme-system"> <body>
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>

View File

@ -1,8 +1,8 @@
// bs options // bs options
$enable-negative-margins: true; $enable-negative-margins: true;
@import "theme";
@import "node_modules/bootstrap/scss/bootstrap"; @import "node_modules/bootstrap/scss/bootstrap";
@import "theme";
@import "~@ng-select/ng-select/themes/default.theme.css"; @import "~@ng-select/ng-select/themes/default.theme.css";
@import "print"; @import "print";

View File

@ -1,10 +1,14 @@
$color-mode-type: data;
@import 'bootstrap/scss/mixins/color-mode';
@mixin paperless-green { @mixin paperless-green {
// base color e.g. #17541f = hsl(128, 57%, 21%) // base color e.g. #17541f = hsl(128, 57%, 21%)
--pngx-primary: 128, 57%; --pngx-primary: 128, 57%;
--pngx-primary-lightness: 21%; --pngx-primary-lightness: 21%;
} }
body { :root {
@include paperless-green; @include paperless-green;
--pngx-primary-text-contrast: var(--bs-light); --pngx-primary-text-contrast: var(--bs-light);
@ -158,7 +162,7 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
} }
.doc-img { .doc-img {
mix-blend-mode: normal; mix-blend-mode: normal !important;
border-radius: 0; border-radius: 0;
border-color: var(--bs-border-color); border-color: var(--bs-border-color);
filter: invert(10%); filter: invert(10%);
@ -270,9 +274,8 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
} }
} }
body.color-scheme-dark { @include color-mode(dark) {
// no custom theme color body:not(.primary-light):not(.primary-dark) {
&:not(.primary-light):not(.primary-dark) {
@include paperless-green-dark-mode; @include paperless-green-dark-mode;
.navbar.bg-primary { .navbar.bg-primary {
@ -284,18 +287,22 @@ body.color-scheme-dark {
@include dark-mode; @include dark-mode;
} }
@media (prefers-color-scheme: dark) { // Temp to not blink with white before angular loads
body.color-scheme-system { @include color-mode(auto) {
// no custom theme color @media (prefers-color-scheme: dark) {
&:not(.primary-light):not(.primary-dark) { body {
@include paperless-green-dark-mode; // no custom theme color
&:not(.primary-light):not(.primary-dark) {
@include paperless-green-dark-mode;
.navbar.bg-primary { .navbar.bg-primary {
// navbar is og green in dark mode // navbar is og green in dark mode
@include paperless-green; @include paperless-green;
}
} }
}
@include dark-mode; @include dark-mode;
}
} }
} }