Skip to content

Commit

Permalink
Merge pull request #324 from RobertSmits/dark-theme
Browse files Browse the repository at this point in the history
Better dark theme and theme switcher
  • Loading branch information
alexta69 authored Oct 3, 2023
2 parents 4e3493b + c64dda8 commit a1e5a31
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Certain values can be set via environment variables, using the `-e` parameter on
* __UID__: user under which MeTube will run. Defaults to `1000`.
* __GID__: group under which MeTube will run. Defaults to `1000`.
* __UMASK__: umask value used by MeTube. Defaults to `022`.
* __DARK_MODE__: if set to `true`, the UI will be in dark mode. Defaults to `false`.
* __DEFAULT_THEME__: default theme to use for the ui, can be set to `light`, `dark` or `auto`. Defaults to `auto`.
* __DOWNLOAD_DIR__: path to where the downloads will be saved. Defaults to `/downloads` in the docker image, and `.` otherwise.
* __AUDIO_DOWNLOAD_DIR__: path to where audio-only downloads will be saved, if you wish to separate them from the video downloads. Defaults to the value of `DOWNLOAD_DIR`.
* __DOWNLOAD_DIRS_INDEXABLE__: if `true`, the download dirs (__DOWNLOAD_DIR__ and __AUDIO_DOWNLOAD_DIR__) are indexable on the webserver. Defaults to `false`.
Expand Down
7 changes: 4 additions & 3 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class Config:
'HOST': '0.0.0.0',
'PORT': '8081',
'BASE_DIR': '',
'DARK_MODE': 'false'
'DEFAULT_THEME': 'auto'
}

_BOOLEAN = ('DOWNLOAD_DIRS_INDEXABLE', 'CUSTOM_DIRS', 'CREATE_CUSTOM_DIRS', 'DELETE_FILE_ON_TRASHCAN', 'DARK_MODE')
_BOOLEAN = ('DOWNLOAD_DIRS_INDEXABLE', 'CUSTOM_DIRS', 'CREATE_CUSTOM_DIRS', 'DELETE_FILE_ON_TRASHCAN')

def __init__(self):
for k, v in self._DEFAULTS.items():
Expand Down Expand Up @@ -173,7 +173,8 @@ def convert(p):
@routes.get(config.URL_PREFIX)
def index(request):
response = web.FileResponse(os.path.join(config.BASE_DIR, 'ui/dist/metube/index.html'))
response.set_cookie('metube_dark', 'true' if config.DARK_MODE else 'false')
if 'metube_theme' not in request.cookies:
response.set_cookie('metube_theme', config.DEFAULT_THEME)
return response

if config.URL_PREFIX != '/':
Expand Down
2 changes: 1 addition & 1 deletion ui/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"src/styles.sass"
],
"scripts": [
"node_modules/bootstrap/dist/js/bootstrap.min.js",
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
]
},
"configurations": {
Expand Down
34 changes: 27 additions & 7 deletions ui/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<nav class="navbar navbar-expand-md navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">MeTube</a>
<!--
Expand All @@ -13,10 +13,30 @@
</ul>
</div>
-->
<div class="ms-auto">
<button class="btn btn-outline-light button-toggle-theme" aria-label="Toggle theme" (click)="themeChanged()">
<fa-icon [icon]="darkMode ? faSun : faMoon"></fa-icon>
</button>
<div class="navbar-nav ms-auto">
<div class="nav-item dropdown">
<button class="btn btn-link nav-link py-2 px-0 px-sm-2 dropdown-toggle d-flex align-items-center"
id="theme-select"
type="button"
aria-expanded="false"
data-bs-toggle="dropdown"
data-bs-display="static">
<fa-icon [icon]="activeTheme.icon"></fa-icon>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="theme-select">
<li *ngFor="let theme of themes">
<button type="button" class="dropdown-item d-flex align-items-center" [ngClass]="{'active' : activeTheme == theme}" (click)="themeChanged(theme)">
<span class="me-2 opacity-50">
<fa-icon [icon]="theme.icon"></fa-icon>
</span>
{{ theme.displayName }}
<span class="ms-auto" [ngClass]="{'d-none' : activeTheme != theme}">
<fa-icon [icon]="faCheck"></fa-icon>
</span>
</button>
</li>
</ul>
</div>
</div>
</div>
</nav>
Expand Down Expand Up @@ -133,8 +153,8 @@
</td>
<td>
<div style="display: inline-block; width: 1.5rem;">
<fa-icon *ngIf="download.value.status == 'finished'" [icon]="faCheckCircle" style="color: green;"></fa-icon>
<fa-icon *ngIf="download.value.status == 'error'" [icon]="faTimesCircle" style="color: red;"></fa-icon>
<fa-icon *ngIf="download.value.status == 'finished'" [icon]="faCheckCircle" class="text-success"></fa-icon>
<fa-icon *ngIf="download.value.status == 'error'" [icon]="faTimesCircle" class="text-danger"></fa-icon>
</div>
<span ngbTooltip="{{download.value.msg}}"><a *ngIf="!!download.value.filename; else noDownloadLink" href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a></span>
<ng-template #noDownloadLink>{{ download.value.title }}</ng-template>
Expand Down
8 changes: 3 additions & 5 deletions ui/src/app/app.component.sass
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ button.add-url
padding-left: 5px
padding-right: 5px

$metube-section-color-bg: rgba(0,0,0,.07)

.metube-section-header
font-size: 1.8rem
font-weight: 300
position: relative
background: $metube-section-color-bg
background: var(--bs-secondary-bg)
padding: 0.5rem 0
margin-top: 3.5rem

Expand All @@ -40,8 +38,8 @@ $metube-section-color-bg: rgba(0,0,0,.07)
bottom: 0
left: -9999px
right: 0
border-left: 9999px solid $metube-section-color-bg
box-shadow: 9999px 0 0 $metube-section-color-bg
border-left: 9999px solid var(--bs-secondary-bg)
box-shadow: 9999px 0 0 var(--bs-secondary-bg)

button:hover
text-decoration: none
Expand Down
51 changes: 32 additions & 19 deletions ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import { faTrashAlt, faCheckCircle, faTimesCircle } from '@fortawesome/free-regular-svg-icons';
import { faRedoAlt, faSun, faMoon, faExternalLinkAlt, faDownload } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt, faCheckCircle, faTimesCircle, IconDefinition } from '@fortawesome/free-regular-svg-icons';
import { faRedoAlt, faSun, faMoon, faCircleHalfStroke, faCheck, faExternalLinkAlt, faDownload } from '@fortawesome/free-solid-svg-icons';
import { CookieService } from 'ngx-cookie-service';
import { map, Observable, of } from 'rxjs';

import { Download, DownloadsService, Status } from './downloads.service';
import { MasterCheckboxComponent } from './master-checkbox.component';
import { Formats, Format, Quality } from './formats';
import { Theme, Themes } from './theme';
import {KeyValue} from "@angular/common";

@Component({
Expand All @@ -23,7 +24,8 @@ export class AppComponent implements AfterViewInit {
folder: string;
customNamePrefix: string;
addInProgress = false;
darkMode: boolean;
themes: Theme[] = Themes;
activeTheme: Theme;
customDirs$: Observable<string[]>;

@ViewChild('queueMasterCheckbox') queueMasterCheckbox: MasterCheckboxComponent;
Expand All @@ -39,6 +41,8 @@ export class AppComponent implements AfterViewInit {
faRedoAlt = faRedoAlt;
faSun = faSun;
faMoon = faMoon;
faCheck = faCheck;
faCircleHalfStroke = faCircleHalfStroke;
faDownload = faDownload;
faExternalLinkAlt = faExternalLinkAlt;

Expand All @@ -47,11 +51,18 @@ export class AppComponent implements AfterViewInit {
// Needs to be set or qualities won't automatically be set
this.setQualities()
this.quality = cookieService.get('metube_quality') || 'best';
this.setupTheme(cookieService)
this.activeTheme = this.getPreferredTheme(cookieService);
}

ngOnInit() {
this.customDirs$ = this.getMatchingCustomDir();
this.setTheme(this.activeTheme);

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (this.activeTheme.id === 'auto') {
this.setTheme(this.activeTheme);
}
});
}

ngAfterViewInit() {
Expand Down Expand Up @@ -96,7 +107,7 @@ export class AppComponent implements AfterViewInit {
}

isAudioType() {
return this.quality == 'audio' || this.format == 'mp3' || this.format == 'm4a' || this.format == 'opus' || this.format == 'wav'
return this.quality == 'audio' || this.format == 'mp3' || this.format == 'm4a' || this.format == 'opus' || this.format == 'wav';
}

getMatchingCustomDir() : Observable<string[]> {
Expand All @@ -112,25 +123,27 @@ export class AppComponent implements AfterViewInit {
}));
}

setupTheme(cookieService) {
if (cookieService.check('metube_dark')) {
this.darkMode = cookieService.get('metube_dark') === "true"
} else {
this.darkMode = window.matchMedia("prefers-color-scheme: dark").matches
getPreferredTheme(cookieService: CookieService) {
let theme = 'auto';
if (cookieService.check('metube_theme')) {
theme = cookieService.get('metube_theme');
}
this.setTheme()

return this.themes.find(x => x.id === theme) ?? this.themes.find(x => x.id === 'auto');
}

themeChanged() {
this.darkMode = !this.darkMode
this.cookieService.set('metube_dark', this.darkMode.toString(), { expires: 3650 });
this.setTheme()
themeChanged(theme: Theme) {
this.cookieService.set('metube_theme', theme.id, { expires: 3650 });
this.setTheme(theme);
}

setTheme() {
const doc = document.querySelector('html')
const filter = this.darkMode ? "invert(1) hue-rotate(180deg)" : ""
doc.style.filter = filter
setTheme(theme: Theme) {
this.activeTheme = theme;
if (theme.id === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-bs-theme', 'dark');
} else {
document.documentElement.setAttribute('data-bs-theme', theme.id);
}
}

formatChanged() {
Expand Down
26 changes: 26 additions & 0 deletions ui/src/app/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { faCircleHalfStroke, faMoon, faSun } from "@fortawesome/free-solid-svg-icons";

export interface Theme {
id: string;
displayName: string;
icon: IconDefinition;
}

export const Themes: Theme[] = [
{
id: 'light',
displayName: 'Light',
icon: faSun,
},
{
id: 'dark',
displayName: 'Dark',
icon: faMoon,
},
{
id: 'auto',
displayName: 'Auto',
icon: faCircleHalfStroke,
},
];
8 changes: 7 additions & 1 deletion ui/src/styles.sass
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@

/* Importing Bootstrap SCSS file. */
@import 'node_modules/bootstrap/scss/bootstrap'
@import '~@ng-select/ng-select/themes/default.theme.css'
@import '~@ng-select/ng-select/themes/default.theme.css'

.navbar
background-color: var(--bs-dark) !important

[data-bs-theme="dark"] &
background-color: var(--bs-dark-bg-subtle) !important

0 comments on commit a1e5a31

Please sign in to comment.