From f6b5f8488c25ed781106343c652a2c2e3f5f9452 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sun, 21 Oct 2018 14:05:41 +0200 Subject: [PATCH] feat: add the ability to link to the site with a particular theme * Reworks the theming setup to store only the current theme's name, rather than the entire object in `localStorage`. This is more robust for the cases where we want to change the theme object's interface. * Adds the ability to link people to the docs site with a particular theme (e.g. https://material.angular.io?theme=purple-green). This allows us to preview the theme. For example we can use it for https://github.com/angular/material2/issues/13708 to show people what the theme would look like when they're prompted in a schematic. --- package-lock.json | 14 ++-- src/app/shared/theme-picker/theme-picker.html | 2 +- .../shared/theme-picker/theme-picker.spec.ts | 12 ++-- src/app/shared/theme-picker/theme-picker.ts | 69 ++++++++++++------- .../theme-storage/theme-storage.spec.ts | 37 +++++----- .../theme-storage/theme-storage.ts | 16 ++--- 6 files changed, 86 insertions(+), 64 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d6b7cb3b..316e134e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5990,13 +5990,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6019,7 +6021,8 @@ "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", @@ -6186,6 +6189,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8699,7 +8703,7 @@ "jsonschema": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.2.tgz", - "integrity": "sha1-g6ucY9Zb9NWW+R2BGV54dy9kUrw=", + "integrity": "sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA==", "dev": true }, "jsonwebtoken": { @@ -9736,7 +9740,7 @@ "lru-cache": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -10745,7 +10749,7 @@ }, "onetime": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "dev": true }, diff --git a/src/app/shared/theme-picker/theme-picker.html b/src/app/shared/theme-picker/theme-picker.html index 358e6e958..8cb20826f 100644 --- a/src/app/shared/theme-picker/theme-picker.html +++ b/src/app/shared/theme-picker/theme-picker.html @@ -7,7 +7,7 @@ -
+
check_circle
diff --git a/src/app/shared/theme-picker/theme-picker.spec.ts b/src/app/shared/theme-picker/theme-picker.spec.ts index 6eb337955..dca6faffa 100644 --- a/src/app/shared/theme-picker/theme-picker.spec.ts +++ b/src/app/shared/theme-picker/theme-picker.spec.ts @@ -10,17 +10,13 @@ describe('ThemePicker', () => { }).compileComponents(); })); - it('should install theme based on href', () => { + it('should install theme based on name', () => { const fixture = TestBed.createComponent(ThemePicker); const component = fixture.componentInstance; - const href = 'pink-bluegrey.css'; + const name = 'pink-bluegrey'; spyOn(component.styleManager, 'setStyle'); - component.installTheme({ - primary: '#E91E63', - accent: '#607D8B', - href, - }); + component.installTheme(name); expect(component.styleManager.setStyle).toHaveBeenCalled(); - expect(component.styleManager.setStyle).toHaveBeenCalledWith('theme', `assets/${href}`); + expect(component.styleManager.setStyle).toHaveBeenCalledWith('theme', `assets/${name}.css`); }); }); diff --git a/src/app/shared/theme-picker/theme-picker.ts b/src/app/shared/theme-picker/theme-picker.ts index 8bd5bae72..8814b202d 100644 --- a/src/app/shared/theme-picker/theme-picker.ts +++ b/src/app/shared/theme-picker/theme-picker.ts @@ -1,11 +1,24 @@ -import {Component, ViewEncapsulation, ChangeDetectionStrategy, NgModule} from '@angular/core'; +import { + Component, + ViewEncapsulation, + ChangeDetectionStrategy, + NgModule, + OnInit, + OnDestroy, +} from '@angular/core'; import {StyleManager} from '../style-manager/style-manager'; import {ThemeStorage, DocsSiteTheme} from './theme-storage/theme-storage'; import { - MatButtonModule, MatGridListModule, MatIconModule, MatMenuModule, - MatTooltipModule + MatButtonModule, + MatGridListModule, + MatIconModule, + MatMenuModule, + MatTooltipModule, } from '@angular/material'; import {CommonModule} from '@angular/common'; +import {ActivatedRoute} from '@angular/router'; +import {Subscription} from 'rxjs'; +import {map, filter} from 'rxjs/operators'; @Component({ @@ -16,64 +29,74 @@ import {CommonModule} from '@angular/common'; encapsulation: ViewEncapsulation.None, host: {'aria-hidden': 'true'}, }) -export class ThemePicker { - currentTheme; +export class ThemePicker implements OnInit, OnDestroy { + private _queryParamSubscription = Subscription.EMPTY; + currentTheme: DocsSiteTheme; - themes = [ + themes: DocsSiteTheme[] = [ { primary: '#673AB7', accent: '#FFC107', - href: 'deeppurple-amber.css', + name: 'deeppurple-amber', isDark: false, }, { primary: '#3F51B5', accent: '#E91E63', - href: 'indigo-pink.css', + name: 'indigo-pink', isDark: false, isDefault: true, }, { primary: '#E91E63', accent: '#607D8B', - href: 'pink-bluegrey.css', + name: 'pink-bluegrey', isDark: true, }, { primary: '#9C27B0', accent: '#4CAF50', - href: 'purple-green.css', + name: 'purple-green', isDark: true, }, ]; constructor( public styleManager: StyleManager, - private _themeStorage: ThemeStorage - ) { - const currentTheme = this._themeStorage.getStoredTheme(); - if (currentTheme) { - this.installTheme(currentTheme); - } + private _themeStorage: ThemeStorage, + private _activatedRoute: ActivatedRoute) { + this.installTheme(this._themeStorage.getStoredThemeName()); + } + + ngOnInit() { + this._queryParamSubscription = this._activatedRoute.queryParamMap + .pipe(map(params => params.get('theme')), filter(Boolean)) + .subscribe(themeName => this.installTheme(themeName)); + } + + ngOnDestroy() { + this._queryParamSubscription.unsubscribe(); } - installTheme(theme: DocsSiteTheme) { - this.currentTheme = this._getCurrentThemeFromHref(theme.href); + installTheme(themeName: string) { + const theme = this.themes.find(currentTheme => currentTheme.name === themeName); + + if (!theme) { + return; + } + + this.currentTheme = theme; if (theme.isDefault) { this.styleManager.removeStyle('theme'); } else { - this.styleManager.setStyle('theme', `assets/${theme.href}`); + this.styleManager.setStyle('theme', `assets/${theme.name}.css`); } if (this.currentTheme) { this._themeStorage.storeTheme(this.currentTheme); } } - - private _getCurrentThemeFromHref(href: string): DocsSiteTheme { - return this.themes.find(theme => theme.href === href); - } } @NgModule({ diff --git a/src/app/shared/theme-picker/theme-storage/theme-storage.spec.ts b/src/app/shared/theme-picker/theme-storage/theme-storage.spec.ts index 67e0c9d24..4efa6d044 100644 --- a/src/app/shared/theme-picker/theme-storage/theme-storage.spec.ts +++ b/src/app/shared/theme-picker/theme-storage/theme-storage.spec.ts @@ -1,40 +1,39 @@ -import {ThemeStorage} from './theme-storage'; +import {ThemeStorage, DocsSiteTheme} from './theme-storage'; const testStorageKey = ThemeStorage.storageKey; -const testTheme = { +const testTheme: DocsSiteTheme = { primary: '#000000', accent: '#ffffff', - href: 'test/path/to/theme' -}; -const createTestData = () => { - window.localStorage[testStorageKey] = JSON.stringify(testTheme); -}; -const clearTestData = () => { - window.localStorage.clear(); + name: 'test-theme' }; describe('ThemeStorage Service', () => { const service = new ThemeStorage(); - const getCurrTheme = () => JSON.parse(window.localStorage.getItem(testStorageKey)); + const getCurrTheme = () => window.localStorage.getItem(testStorageKey); const secondTestTheme = { primary: '#666666', accent: '#333333', - href: 'some/cool/path' + name: 'other-test-theme' }; - beforeEach(createTestData); - afterEach(clearTestData); + beforeEach(() => { + window.localStorage[testStorageKey] = testTheme.name; + }); + + afterEach(() => { + window.localStorage.clear(); + }); - it('should set the current theme', () => { - expect(getCurrTheme()).toEqual(testTheme); + it('should set the current theme name', () => { + expect(getCurrTheme()).toEqual(testTheme.name); service.storeTheme(secondTestTheme); - expect(getCurrTheme()).toEqual(secondTestTheme); + expect(getCurrTheme()).toEqual(secondTestTheme.name); }); - it('should get the current theme', () => { - const theme = service.getStoredTheme(); - expect(theme).toEqual(testTheme); + it('should get the current theme name', () => { + const theme = service.getStoredThemeName(); + expect(theme).toEqual(testTheme.name); }); it('should clear the stored theme data', () => { diff --git a/src/app/shared/theme-picker/theme-storage/theme-storage.ts b/src/app/shared/theme-picker/theme-storage/theme-storage.ts index ab625bba1..8d5d5699d 100644 --- a/src/app/shared/theme-picker/theme-storage/theme-storage.ts +++ b/src/app/shared/theme-picker/theme-storage/theme-storage.ts @@ -1,7 +1,7 @@ import {Injectable, EventEmitter} from '@angular/core'; export interface DocsSiteTheme { - href: string; + name: string; accent: string; primary: string; isDark?: boolean; @@ -11,22 +11,22 @@ export interface DocsSiteTheme { @Injectable() export class ThemeStorage { - static storageKey = 'docs-theme-storage-current'; + static storageKey = 'docs-theme-storage-current-name'; onThemeUpdate: EventEmitter = new EventEmitter(); storeTheme(theme: DocsSiteTheme) { try { - window.localStorage[ThemeStorage.storageKey] = JSON.stringify(theme); - } catch (e) { } + window.localStorage[ThemeStorage.storageKey] = theme.name; + } catch { } this.onThemeUpdate.emit(theme); } - getStoredTheme(): DocsSiteTheme { + getStoredThemeName(): string | null { try { - return JSON.parse(window.localStorage[ThemeStorage.storageKey] || null); - } catch (e) { + return window.localStorage[ThemeStorage.storageKey] || null; + } catch { return null; } } @@ -34,6 +34,6 @@ export class ThemeStorage { clearStorage() { try { window.localStorage.removeItem(ThemeStorage.storageKey); - } catch (e) { } + } catch { } } }