Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

Commit 80addca

Browse files
crisbetojelbourn
authored andcommitted
feat: add the ability to link to the site with a particular theme (#549)
* 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 angular/components#13708 to show people what the theme would look like when they're prompted in a schematic.
1 parent 48e93dc commit 80addca

File tree

6 files changed

+86
-64
lines changed

6 files changed

+86
-64
lines changed

package-lock.json

Lines changed: 9 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/shared/theme-picker/theme-picker.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<mat-menu class="docs-theme-picker-menu" #themeMenu="matMenu" x-position="before">
88
<mat-grid-list cols="2">
99
<mat-grid-tile *ngFor="let theme of themes">
10-
<div mat-menu-item (click)="installTheme(theme)">
10+
<div mat-menu-item (click)="installTheme(theme.name)">
1111
<div class="docs-theme-picker-swatch">
1212
<mat-icon class="docs-theme-chosen-icon" *ngIf="currentTheme === theme">check_circle</mat-icon>
1313
<div class="docs-theme-picker-primary" [style.background]="theme.primary"></div>

src/app/shared/theme-picker/theme-picker.spec.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,13 @@ describe('ThemePicker', () => {
1010
}).compileComponents();
1111
}));
1212

13-
it('should install theme based on href', () => {
13+
it('should install theme based on name', () => {
1414
const fixture = TestBed.createComponent(ThemePicker);
1515
const component = fixture.componentInstance;
16-
const href = 'pink-bluegrey.css';
16+
const name = 'pink-bluegrey';
1717
spyOn(component.styleManager, 'setStyle');
18-
component.installTheme({
19-
primary: '#E91E63',
20-
accent: '#607D8B',
21-
href,
22-
});
18+
component.installTheme(name);
2319
expect(component.styleManager.setStyle).toHaveBeenCalled();
24-
expect(component.styleManager.setStyle).toHaveBeenCalledWith('theme', `assets/${href}`);
20+
expect(component.styleManager.setStyle).toHaveBeenCalledWith('theme', `assets/${name}.css`);
2521
});
2622
});

src/app/shared/theme-picker/theme-picker.ts

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
import {Component, ViewEncapsulation, ChangeDetectionStrategy, NgModule} from '@angular/core';
1+
import {
2+
Component,
3+
ViewEncapsulation,
4+
ChangeDetectionStrategy,
5+
NgModule,
6+
OnInit,
7+
OnDestroy,
8+
} from '@angular/core';
29
import {StyleManager} from '../style-manager/style-manager';
310
import {ThemeStorage, DocsSiteTheme} from './theme-storage/theme-storage';
411
import {
5-
MatButtonModule, MatGridListModule, MatIconModule, MatMenuModule,
6-
MatTooltipModule
12+
MatButtonModule,
13+
MatGridListModule,
14+
MatIconModule,
15+
MatMenuModule,
16+
MatTooltipModule,
717
} from '@angular/material';
818
import {CommonModule} from '@angular/common';
19+
import {ActivatedRoute} from '@angular/router';
20+
import {Subscription} from 'rxjs';
21+
import {map, filter} from 'rxjs/operators';
922

1023

1124
@Component({
@@ -16,64 +29,74 @@ import {CommonModule} from '@angular/common';
1629
encapsulation: ViewEncapsulation.None,
1730
host: {'aria-hidden': 'true'},
1831
})
19-
export class ThemePicker {
20-
currentTheme;
32+
export class ThemePicker implements OnInit, OnDestroy {
33+
private _queryParamSubscription = Subscription.EMPTY;
34+
currentTheme: DocsSiteTheme;
2135

22-
themes = [
36+
themes: DocsSiteTheme[] = [
2337
{
2438
primary: '#673AB7',
2539
accent: '#FFC107',
26-
href: 'deeppurple-amber.css',
40+
name: 'deeppurple-amber',
2741
isDark: false,
2842
},
2943
{
3044
primary: '#3F51B5',
3145
accent: '#E91E63',
32-
href: 'indigo-pink.css',
46+
name: 'indigo-pink',
3347
isDark: false,
3448
isDefault: true,
3549
},
3650
{
3751
primary: '#E91E63',
3852
accent: '#607D8B',
39-
href: 'pink-bluegrey.css',
53+
name: 'pink-bluegrey',
4054
isDark: true,
4155
},
4256
{
4357
primary: '#9C27B0',
4458
accent: '#4CAF50',
45-
href: 'purple-green.css',
59+
name: 'purple-green',
4660
isDark: true,
4761
},
4862
];
4963

5064
constructor(
5165
public styleManager: StyleManager,
52-
private _themeStorage: ThemeStorage
53-
) {
54-
const currentTheme = this._themeStorage.getStoredTheme();
55-
if (currentTheme) {
56-
this.installTheme(currentTheme);
57-
}
66+
private _themeStorage: ThemeStorage,
67+
private _activatedRoute: ActivatedRoute) {
68+
this.installTheme(this._themeStorage.getStoredThemeName());
69+
}
70+
71+
ngOnInit() {
72+
this._queryParamSubscription = this._activatedRoute.queryParamMap
73+
.pipe(map(params => params.get('theme')), filter(Boolean))
74+
.subscribe(themeName => this.installTheme(themeName));
75+
}
76+
77+
ngOnDestroy() {
78+
this._queryParamSubscription.unsubscribe();
5879
}
5980

60-
installTheme(theme: DocsSiteTheme) {
61-
this.currentTheme = this._getCurrentThemeFromHref(theme.href);
81+
installTheme(themeName: string) {
82+
const theme = this.themes.find(currentTheme => currentTheme.name === themeName);
83+
84+
if (!theme) {
85+
return;
86+
}
87+
88+
this.currentTheme = theme;
6289

6390
if (theme.isDefault) {
6491
this.styleManager.removeStyle('theme');
6592
} else {
66-
this.styleManager.setStyle('theme', `assets/${theme.href}`);
93+
this.styleManager.setStyle('theme', `assets/${theme.name}.css`);
6794
}
6895

6996
if (this.currentTheme) {
7097
this._themeStorage.storeTheme(this.currentTheme);
7198
}
7299
}
73-
74-
private _getCurrentThemeFromHref(href: string): DocsSiteTheme {
75-
return this.themes.find(theme => theme.href === href);
76-
}
77100
}
78101

79102
@NgModule({

src/app/shared/theme-picker/theme-storage/theme-storage.spec.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,39 @@
1-
import {ThemeStorage} from './theme-storage';
1+
import {ThemeStorage, DocsSiteTheme} from './theme-storage';
22

33

44
const testStorageKey = ThemeStorage.storageKey;
5-
const testTheme = {
5+
const testTheme: DocsSiteTheme = {
66
primary: '#000000',
77
accent: '#ffffff',
8-
href: 'test/path/to/theme'
9-
};
10-
const createTestData = () => {
11-
window.localStorage[testStorageKey] = JSON.stringify(testTheme);
12-
};
13-
const clearTestData = () => {
14-
window.localStorage.clear();
8+
name: 'test-theme'
159
};
1610

1711
describe('ThemeStorage Service', () => {
1812
const service = new ThemeStorage();
19-
const getCurrTheme = () => JSON.parse(window.localStorage.getItem(testStorageKey));
13+
const getCurrTheme = () => window.localStorage.getItem(testStorageKey);
2014
const secondTestTheme = {
2115
primary: '#666666',
2216
accent: '#333333',
23-
href: 'some/cool/path'
17+
name: 'other-test-theme'
2418
};
2519

26-
beforeEach(createTestData);
27-
afterEach(clearTestData);
20+
beforeEach(() => {
21+
window.localStorage[testStorageKey] = testTheme.name;
22+
});
23+
24+
afterEach(() => {
25+
window.localStorage.clear();
26+
});
2827

29-
it('should set the current theme', () => {
30-
expect(getCurrTheme()).toEqual(testTheme);
28+
it('should set the current theme name', () => {
29+
expect(getCurrTheme()).toEqual(testTheme.name);
3130
service.storeTheme(secondTestTheme);
32-
expect(getCurrTheme()).toEqual(secondTestTheme);
31+
expect(getCurrTheme()).toEqual(secondTestTheme.name);
3332
});
3433

35-
it('should get the current theme', () => {
36-
const theme = service.getStoredTheme();
37-
expect(theme).toEqual(testTheme);
34+
it('should get the current theme name', () => {
35+
const theme = service.getStoredThemeName();
36+
expect(theme).toEqual(testTheme.name);
3837
});
3938

4039
it('should clear the stored theme data', () => {
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Injectable, EventEmitter} from '@angular/core';
22

33
export interface DocsSiteTheme {
4-
href: string;
4+
name: string;
55
accent: string;
66
primary: string;
77
isDark?: boolean;
@@ -11,29 +11,29 @@ export interface DocsSiteTheme {
1111

1212
@Injectable()
1313
export class ThemeStorage {
14-
static storageKey = 'docs-theme-storage-current';
14+
static storageKey = 'docs-theme-storage-current-name';
1515

1616
onThemeUpdate: EventEmitter<DocsSiteTheme> = new EventEmitter<DocsSiteTheme>();
1717

1818
storeTheme(theme: DocsSiteTheme) {
1919
try {
20-
window.localStorage[ThemeStorage.storageKey] = JSON.stringify(theme);
21-
} catch (e) { }
20+
window.localStorage[ThemeStorage.storageKey] = theme.name;
21+
} catch { }
2222

2323
this.onThemeUpdate.emit(theme);
2424
}
2525

26-
getStoredTheme(): DocsSiteTheme {
26+
getStoredThemeName(): string | null {
2727
try {
28-
return JSON.parse(window.localStorage[ThemeStorage.storageKey] || null);
29-
} catch (e) {
28+
return window.localStorage[ThemeStorage.storageKey] || null;
29+
} catch {
3030
return null;
3131
}
3232
}
3333

3434
clearStorage() {
3535
try {
3636
window.localStorage.removeItem(ThemeStorage.storageKey);
37-
} catch (e) { }
37+
} catch { }
3838
}
3939
}

0 commit comments

Comments
 (0)