From f3b2e83d5a3608599491a3e98ce29682ed271cb1 Mon Sep 17 00:00:00 2001 From: skyecodes Date: Tue, 11 Jul 2023 14:03:59 +0200 Subject: [PATCH] feat: added theme switcher --- angular.json | 60 ++++++++++++++++--- src/_app-theme.scss | 26 ++++++++ src/app/app.component.html | 39 ++++++++++++ src/app/app.component.scss | 27 ++++++--- src/app/app.component.ts | 24 ++++++-- src/app/common/theme.service.spec.ts | 16 +++++ src/app/common/theme.service.ts | 42 +++++++++++++ src/app/common/utils.ts | 55 ++++++++++++++--- src/app/converter/converter.component.scss | 3 +- src/styles.scss | 30 +++++++++- .../custom-themes/deeppurple-amber.scss | 20 +++++++ src/styles/custom-themes/indigo-pink.scss | 20 +++++++ src/styles/custom-themes/pink-bluegrey.scss | 20 +++++++ src/styles/custom-themes/purple-green.scss | 17 ++++++ 14 files changed, 365 insertions(+), 34 deletions(-) create mode 100644 src/_app-theme.scss create mode 100644 src/app/common/theme.service.spec.ts create mode 100644 src/app/common/theme.service.ts create mode 100644 src/styles/custom-themes/deeppurple-amber.scss create mode 100644 src/styles/custom-themes/indigo-pink.scss create mode 100644 src/styles/custom-themes/pink-bluegrey.scss create mode 100644 src/styles/custom-themes/purple-green.scss diff --git a/angular.json b/angular.json index b2fc7e6..1922902 100644 --- a/angular.json +++ b/angular.json @@ -28,12 +28,33 @@ "src/favicon.ico", "src/assets" ], - "styles": [ - "@angular/material/prebuilt-themes/purple-green.css", - "src/styles.scss" - ], - "scripts": [] - }, + "styles": [ + { + "input": "src/styles.scss" + }, + { + "inject": false, + "input": "src/styles/custom-themes/pink-bluegrey.scss", + "bundleName": "pink-bluegrey" + }, + { + "inject": false, + "input": "src/styles/custom-themes/deeppurple-amber.scss", + "bundleName": "deeppurple-amber" + }, + { + "inject": false, + "input": "src/styles/custom-themes/indigo-pink.scss", + "bundleName": "indigo-pink" + }, + { + "inject": false, + "input": "src/styles/custom-themes/purple-green.scss", + "bundleName": "purple-green" + } + ], + "scripts": [] + }, "configurations": { "production": { "budgets": [ @@ -106,9 +127,30 @@ "src/assets" ], "styles": [ - "@angular/material/prebuilt-themes/purple-green.css", - "src/styles.scss" - ], + { + "input": "src/styles.scss" + }, + { + "inject": false, + "input": "src/styles/custom-themes/pink-bluegrey.scss", + "bundleName": "pink-bluegrey" + }, + { + "inject": false, + "input": "src/styles/custom-themes/deeppurple-amber.scss", + "bundleName": "deeppurple-amber" + }, + { + "inject": false, + "input": "src/styles/custom-themes/indigo-pink.scss", + "bundleName": "indigo-pink" + }, + { + "inject": false, + "input": "src/styles/custom-themes/purple-green.scss", + "bundleName": "purple-green" + } + ], "scripts": [] } } diff --git a/src/_app-theme.scss b/src/_app-theme.scss new file mode 100644 index 0000000..381b6b0 --- /dev/null +++ b/src/_app-theme.scss @@ -0,0 +1,26 @@ +@use '@angular/material' as mat; +@use './app/app.component' as app-component; + +@use 'sass:map'; + +@mixin theme($theme) { + $primary: map.get($theme, primary); + $accent: map.get($theme, accent); + $warn: map.get($theme, warn); + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + @include app-component.theme($primary); +} + +@mixin theme-light($theme) { + $primary: map.get($theme, primary); + + @include app-component.theme-light($primary); +} + +@mixin theme-dark($theme) { + $primary: map.get($theme, primary); + + @include app-component.theme-dark($primary); +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 4fad761..b5fccc3 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -9,12 +9,51 @@

v{{packageJson.version}} + + palette + + + + diff --git a/src/app/app.component.scss b/src/app/app.component.scss index c109622..ca0603f 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -1,5 +1,4 @@ @use 'sass:map'; -@use '@angular/material' as mat; #root { display: flex; @@ -10,14 +9,6 @@ mat-sidenav-container { flex-grow: 1; } -.link-active { - background-color: rgba(map-get(mat.$purple-palette, 700), 0.3); -} - -.link-active span { - color: map-get(mat.$purple-palette, 100); -} - mat-list-item.link-active:focus::before { background-color: inherit !important; } @@ -25,3 +16,21 @@ mat-list-item.link-active:focus::before { mat-nav-list { padding: 0; } + +@mixin theme-light($primary) { + .link-active span { + color: map-get($primary, 800); + } +} + +@mixin theme-dark($primary) { + .link-active span { + color: map-get($primary, 100); + } +} + +@mixin theme($primary) { + .link-active { + background-color: rgba(map-get($primary, 700), 0.3) !important; + } +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0a8f7c1..63e7fcf 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,25 +1,28 @@ -import {Component, OnDestroy, ViewChild} from '@angular/core'; +import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {MatDrawerMode, MatSidenav} from "@angular/material/sidenav"; import {Subject, takeUntil} from "rxjs"; import packageJson from '../../package.json'; import {faGithub, faTwitter} from "@fortawesome/free-brands-svg-icons"; import {ResponsiveService} from "./common/responsive.service"; -import {links} from "./common/utils"; +import {links, themes} from "./common/utils"; +import {ThemeService} from "./common/theme.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent implements OnDestroy { +export class AppComponent implements OnInit, OnDestroy { destroyed = new Subject(); sidenavOpened!: boolean sidenavMode!: MatDrawerMode @ViewChild('sidenav') sidenav!: MatSidenav; + currentTheme!: string; protected readonly links = links + protected readonly themes = themes; - constructor(responsiveService: ResponsiveService) { - responsiveService.isSmall + constructor(private responsiveService: ResponsiveService, private themeService: ThemeService) { + this.responsiveService.isSmall .pipe(takeUntil(this.destroyed)) .subscribe(isSmall => { if (isSmall) { @@ -30,6 +33,11 @@ export class AppComponent implements OnDestroy { this.sidenavMode = 'side'; } }); + this.currentTheme = localStorage.getItem('theme') || 'purple-green' + this.themeService.setTheme(this.currentTheme); + } + + ngOnInit() { } ngOnDestroy() { @@ -44,4 +52,10 @@ export class AppComponent implements OnDestroy { closeSidenavIfSmall() { if (this.sidenavMode == 'over') this.sidenav.close() } + + changeTheme(theme: string) { + this.themeService.setTheme(theme); + this.currentTheme = theme; + localStorage.setItem('theme', theme); + } } diff --git a/src/app/common/theme.service.spec.ts b/src/app/common/theme.service.spec.ts new file mode 100644 index 0000000..f2ff112 --- /dev/null +++ b/src/app/common/theme.service.spec.ts @@ -0,0 +1,16 @@ +import {TestBed} from '@angular/core/testing'; + +import {ThemeService} from './theme.service'; + +describe('ThemeService', () => { + let service: ThemeService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ThemeService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/common/theme.service.ts b/src/app/common/theme.service.ts new file mode 100644 index 0000000..3b87b81 --- /dev/null +++ b/src/app/common/theme.service.ts @@ -0,0 +1,42 @@ +import {Injectable} from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ThemeService { + constructor() { + } + + setTheme(themeToSet: string) { + this.setStyle( + 'theme', + `${themeToSet}.css` + ); + } + + private setStyle(key: string, href: string) { + getLinkElementForKey(key).setAttribute('href', href); + } +} + +function getLinkElementForKey(key: string) { + return getExistingLinkElementByKey(key) || createLinkElementWithKey(key); +} + +function getExistingLinkElementByKey(key: string) { + return document.head.querySelector( + `link[rel="stylesheet"].${getClassNameForKey(key)}` + ); +} + +function createLinkElementWithKey(key: string) { + const linkEl = document.createElement('link'); + linkEl.setAttribute('rel', 'stylesheet'); + linkEl.classList.add(getClassNameForKey(key)); + document.head.appendChild(linkEl); + return linkEl; +} + +function getClassNameForKey(key: string) { + return `app-${key}`; +} diff --git a/src/app/common/utils.ts b/src/app/common/utils.ts index d1baef0..a96d520 100644 --- a/src/app/common/utils.ts +++ b/src/app/common/utils.ts @@ -4,10 +4,10 @@ import {NgZone} from "@angular/core"; import {Observable} from "rxjs"; interface Link { - name: string, - url: string, - icon: string, - description: string + name: string; + url: string; + icon: string; + description: string; } export const links: Link[] = [ @@ -29,7 +29,7 @@ export const links: Link[] = [ icon: 'live_tv', description: 'Watch movies and shows online (not made by me, only self-hosting).' }, -] +]; export class CustomErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -39,9 +39,9 @@ export class CustomErrorStateMatcher implements ErrorStateMatcher { } export interface ProgressMessage { - isCompleted: boolean - progress: number - fileId: string + isCompleted: boolean; + progress: number; + fileId: string; } export function observeEventSource(source: EventSource, zone: NgZone) { @@ -57,3 +57,42 @@ export function observeEventSource(source: EventSource, zone: NgZone) { }); }); } + +export interface Theme { + backgroundColor: string; + buttonColor: string; + headingColor: string; + label: string; + value: string; +} + +export const themes: Theme[] = [ + { + "backgroundColor": "#fff", + "buttonColor": "#ffc107", + "headingColor": "#673ab7", + "label": "Deep Purple & Amber", + "value": "deeppurple-amber" + }, + { + "backgroundColor": "#fff", + "buttonColor": "#ff4081", + "headingColor": "#3f51b5", + "label": "Indigo & Pink", + "value": "indigo-pink" + }, + { + "backgroundColor": "#303030", + "buttonColor": "#607d8b", + "headingColor": "#e91e63", + "label": "Pink & Blue Grey", + "value": "pink-bluegrey" + }, + { + "backgroundColor": "#303030", + "buttonColor": "#4caf50", + "headingColor": "#9c27b0", + "label": "Purple & Green", + "value": "purple-green" + } +]; diff --git a/src/app/converter/converter.component.scss b/src/app/converter/converter.component.scss index 993318f..4e36659 100644 --- a/src/app/converter/converter.component.scss +++ b/src/app/converter/converter.component.scss @@ -1,7 +1,8 @@ .converter-area { height: 200px; - border: gray 5px dashed; + border: rgba(gray, 0.5) 5px dashed; border-radius: 20px; + box-shadow: none; mat-icon { font-size: 32px !important; diff --git a/src/styles.scss b/src/styles.scss index 853ebf8..d83dfec 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,3 +1,6 @@ +@use '@angular/material' as mat; +@use './app-theme'; + @import "bootstrap/scss/functions"; @import "bootstrap/scss/variables"; @import "bootstrap/scss/variables-dark"; @@ -9,9 +12,32 @@ @import "bootstrap/scss/grid"; @import "bootstrap/scss/utilities/api"; +html, body, .full-height { + height: 100%; +} + +body { + margin: 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} -html, body, .full-height { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } +// Include material core styles. +@include mat.core(); +@include mat.typography-hierarchy(mat.define-typography-config()); + +$primary: mat.define-palette(mat.$purple-palette); +$accent: mat.define-palette(mat.$green-palette, A200, A100, A400); +$theme: mat.define-dark-theme(( + color: ( + primary: $primary, + accent: $accent + ), + typography: mat.define-typography-config(), +)); + +@include mat.all-component-themes($theme); +@include app-theme.theme($theme); +@include app-theme.theme-dark($theme); .snackbar-error { --mdc-snackbar-supporting-text-color: white !important; diff --git a/src/styles/custom-themes/deeppurple-amber.scss b/src/styles/custom-themes/deeppurple-amber.scss new file mode 100644 index 0000000..70996f6 --- /dev/null +++ b/src/styles/custom-themes/deeppurple-amber.scss @@ -0,0 +1,20 @@ +@use 'sass:map'; +@use '@angular/material' as mat; +@use '../../app-theme'; + +// Define the light theme. +$primary: mat.define-palette(mat.$deep-purple-palette); +$accent: mat.define-palette(mat.$amber-palette, A200, A100, A400); +$theme: mat.define-light-theme(( + color: ( + primary: $primary, + accent: $accent + ), + typography: mat.define-typography-config(), +)); + +@include mat.all-component-themes(map.merge($theme, ( + typography: mat.define-typography-config() +))); +@include app-theme.theme($theme); +@include app-theme.theme-light($theme); diff --git a/src/styles/custom-themes/indigo-pink.scss b/src/styles/custom-themes/indigo-pink.scss new file mode 100644 index 0000000..c1a7a5b --- /dev/null +++ b/src/styles/custom-themes/indigo-pink.scss @@ -0,0 +1,20 @@ +@use 'sass:map'; +@use '@angular/material' as mat; +@use '../../app-theme'; + +// Define the light theme. +$primary: mat.define-palette(mat.$indigo-palette); +$accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); +$theme: mat.define-light-theme(( + color: ( + primary: $primary, + accent: $accent + ), + typography: mat.define-typography-config(), +)); + +@include mat.all-component-themes(map.merge($theme, ( + typography: mat.define-typography-config() +))); +@include app-theme.theme($theme); +@include app-theme.theme-light($theme); diff --git a/src/styles/custom-themes/pink-bluegrey.scss b/src/styles/custom-themes/pink-bluegrey.scss new file mode 100644 index 0000000..ea59982 --- /dev/null +++ b/src/styles/custom-themes/pink-bluegrey.scss @@ -0,0 +1,20 @@ +@use 'sass:map'; +@use '@angular/material' as mat; +@use '../../app-theme'; + +// Define the dark theme. +$primary: mat.define-palette(mat.$pink-palette); +$accent: mat.define-palette(mat.$blue-grey-palette); +$theme: mat.define-dark-theme(( + color: ( + primary: $primary, + accent: $accent, + ), + typography: mat.define-typography-config(), +)); + +@include mat.all-component-themes(map.merge($theme, ( + typography: mat.define-typography-config() +))); +@include app-theme.theme($theme); +@include app-theme.theme-dark($theme); diff --git a/src/styles/custom-themes/purple-green.scss b/src/styles/custom-themes/purple-green.scss new file mode 100644 index 0000000..cc239d3 --- /dev/null +++ b/src/styles/custom-themes/purple-green.scss @@ -0,0 +1,17 @@ +@use '@angular/material' as mat; +@use '../../app-theme'; + +// Define the dark theme. +$primary: mat.define-palette(mat.$purple-palette); +$accent: mat.define-palette(mat.$green-palette, A200, A100, A400); +$theme: mat.define-dark-theme(( + color: ( + primary: $primary, + accent: $accent + ), + typography: mat.define-typography-config(), +)); + +@include mat.all-component-themes($theme); +@include app-theme.theme($theme); +@include app-theme.theme-dark($theme);