feat: add theme picker (#69)

Add theme picker for configuration of angular material default themes.

Fixes #68
This commit is contained in:
Abhay Chawla
2018-06-27 21:28:40 +05:30
committed by GitHub
parent 3d6a4bbc2d
commit 7782e8d856
19 changed files with 329 additions and 6 deletions
@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { ThemeManagerService } from './theme-manager.service';
describe('ThemeManagerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ThemeManagerService]
});
});
it('should be created', inject([ThemeManagerService], (service: ThemeManagerService) => {
expect(service).toBeTruthy();
}));
});
@@ -0,0 +1,39 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ThemeManagerService {
private themeManagerClass = 'theme-manager';
constructor() { }
setTheme(href: string) {
this.getLinkElement().setAttribute('href', href);
}
removeTheme() {
const existingLinkElement = this.getExistingLinkElement();
if (existingLinkElement) {
document.head.removeChild(existingLinkElement);
}
}
private getLinkElement() {
return this.getExistingLinkElement() || this.createLinkElement();
}
private getExistingLinkElement() {
return document.head.querySelector(`link[rel="stylesheet"].${this.themeManagerClass}`);
}
private createLinkElement() {
const linkElement = document.createElement('link');
linkElement.setAttribute('rel', 'stylesheet');
linkElement.classList.add(this.themeManagerClass);
document.head.appendChild(linkElement);
return linkElement;
}
}
@@ -0,0 +1,16 @@
<button mat-icon-button [matMenuTriggerFor]="themeMenu" matTooltip="Color Schemes">
<mat-icon>format_color_fill</mat-icon>
</button>
<mat-menu class="mifosx-theme-picker-menu" #themeMenu="matMenu" x-position="before">
<mat-grid-list cols="2">
<mat-grid-tile *ngFor="let theme of themes">
<div mat-menu-item (click)="installTheme(theme)">
<div class="mifosx-theme-picker-swatch">
<mat-icon class="mifosx-theme-chosen-icon" *ngIf="currentTheme.href === theme.href">check_circle</mat-icon>
<div class="mifosx-theme-picker-primary" [style.background]="theme.primary"></div>
</div>
</div>
</mat-grid-tile>
</mat-grid-list>
</mat-menu>
@@ -0,0 +1,57 @@
$theme-picker-menu-padding: 8px;
$theme-picker-grid-cell-size: 48px;
$theme-picker-grid-cells-per-row: 2;
$theme-picker-swatch-size: 36px;
$theme-picker-accent-stripe-size: 6px;
.mifosx-theme-picker-menu {
.mat-menu-content {
padding: $theme-picker-menu-padding;
}
[mat-menu-item] {
flex: 0 0 auto;
padding: 0;
overflow: hidden;
}
.mifosx-theme-picker-swatch {
position: relative;
width: $theme-picker-swatch-size;
height: $theme-picker-swatch-size;
margin: ($theme-picker-grid-cell-size - $theme-picker-swatch-size) / 2;
border-radius: 50%;
overflow: hidden;
.mifosx-theme-chosen-icon {
color: white;
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
}
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 50%;
}
}
.mifosx-theme-picker-primary {
width: 100%;
height: 100%;
}
.mifosx-theme-picker-accent {
position: absolute;
bottom: $theme-picker-accent-stripe-size;
width: 100%;
height: $theme-picker-accent-stripe-size;
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ThemePickerComponent } from './theme-picker.component';
describe('ThemePickerComponent', () => {
let component: ThemePickerComponent;
let fixture: ComponentFixture<ThemePickerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ThemePickerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ThemePickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,65 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Theme } from './theme.model';
import { ThemeManagerService } from './theme-manager.service';
import { ThemeStorageService } from './theme-storage.service';
@Component({
selector: 'mifosx-theme-picker',
templateUrl: './theme-picker.component.html',
styleUrls: ['./theme-picker.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ThemePickerComponent implements OnInit {
currentTheme: Theme = {
href: 'indigo-pink.css',
primary: '#3F51B5',
accent: '#E91E63',
isDark: false,
isDefault: true,
};
themes = [
this.currentTheme,
{
href: 'deeppurple-amber.css',
primary: '#673AB7',
accent: '#FFC107',
isDark: false,
},
{
href: 'pink-bluegrey.css',
primary: '#E91E63',
accent: '#607D8B',
isDark: true,
},
{
href: 'purple-green.css',
primary: '#9C27B0',
accent: '#4CAF50',
isDark: true,
},
];
constructor(public themeManagerService: ThemeManagerService,
private themeStorageService: ThemeStorageService) {
this.themeStorageService.getTheme().subscribe((theme) => {
this.installTheme(theme);
});
}
ngOnInit() { }
installTheme(theme: Theme) {
if (theme) {
this.currentTheme = theme;
}
if (this.currentTheme.isDefault) {
this.themeManagerService.removeTheme();
} else {
this.themeManagerService.setTheme(`theme/${this.currentTheme.href}`);
}
this.themeStorageService.storeTheme(this.currentTheme);
}
}
@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { ThemeStorageService } from './theme-storage.service';
describe('ThemeStorageService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ThemeStorageService]
});
});
it('should be created', inject([ThemeStorageService], (service: ThemeStorageService) => {
expect(service).toBeTruthy();
}));
});
@@ -0,0 +1,33 @@
import {Injectable, EventEmitter} from '@angular/core';
import { LocalStorage } from '@ngx-pwa/local-storage';
import { Observable } from 'rxjs';
import { Theme } from './theme.model';
@Injectable({
providedIn: 'root'
})
export class ThemeStorageService {
private themeStorageKey = 'mifosXTheme';
onThemeUpdate: EventEmitter<Theme>;
constructor(private localStorage: LocalStorage) {
this.onThemeUpdate = new EventEmitter<Theme>();
}
storeTheme(mifosXTheme: Theme) {
this.localStorage.setItem(this.themeStorageKey, mifosXTheme)
.subscribe(() => this.onThemeUpdate.emit(mifosXTheme));
}
getTheme(): Observable<Theme> {
return this.localStorage.getItem<Theme>(this.themeStorageKey);
}
clearTheme() {
this.localStorage.removeItem(this.themeStorageKey);
}
}
@@ -0,0 +1,7 @@
export interface Theme {
href: string;
primary: string;
accent: string;
isDark?: boolean;
isDefault?: boolean;
}
@@ -56,9 +56,7 @@
<mat-option value="Hindi">Hindi</mat-option>
</mat-select>
<button mat-icon-button class="ml-1" matTooltip="Color Schemes" fxHide.lt-md>
<mat-icon>format_color_fill</mat-icon>
</button>
<mifosx-theme-picker class="ml-1" fxHide.lt-md></mifosx-theme-picker>
<button mat-icon-button class="ml-1" matTooltip="Notifications">
<mat-icon>notifications</mat-icon>