mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-29 15:10:37 +00:00
Unceremoniously steals material.angular.io's theme picker. Very cool stuff from them!
This commit is contained in:
@@ -33,16 +33,20 @@ import { AboutComponent } from './pages/about/about.component';
|
||||
import { SettingsComponent } from './pages/settings/settings.component';
|
||||
import { DashboardComponent } from './pages/dashboard/dashboard.component';
|
||||
import { WalletComponent } from './pages/wallet/wallet.component';
|
||||
|
||||
//Shared
|
||||
import { NavbarComponent } from './shared/navbar/navbar.component';
|
||||
import { SidebarComponent } from './shared/sidebar/sidebar.component';
|
||||
import { ExchangeCurrencyTickerComponent } from './shared/exchange-currency-ticker/exchange-currency-ticker.component';
|
||||
import { AllEnabledCurrencyTickersComponent } from './shared/all-enabled-currency-tickers/all-enabled-currency-tickers.component';
|
||||
import { ThemePickerComponent } from './shared/theme-picker/theme-picker';
|
||||
//services
|
||||
import { WebsocketService } from './services/websocket/websocket.service';
|
||||
import { WebsocketHandlerService } from './services/websocket-handler/websocket-handler.service';
|
||||
import { SidebarService } from './services/sidebar/sidebar.service';
|
||||
import { ElectronService } from './providers/electron.service';
|
||||
import { StyleManagerService } from './services/style-manager/style-manager.service';
|
||||
import { ThemeStorageService } from './services/theme-storage/theme-storage.service';
|
||||
|
||||
//Routing
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
@@ -63,7 +67,8 @@ import * as Rx from 'rxjs/Rx';
|
||||
ExchangeCurrencyTickerComponent,
|
||||
AllEnabledCurrencyTickersComponent,
|
||||
SidebarComponent,
|
||||
WalletComponent
|
||||
WalletComponent,
|
||||
ThemePickerComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -87,7 +92,14 @@ import * as Rx from 'rxjs/Rx';
|
||||
MatExpansionModule,
|
||||
MatLineModule,
|
||||
],
|
||||
providers: [ElectronService,WebsocketService,WebsocketHandlerService, SidebarService],
|
||||
providers: [
|
||||
ElectronService,
|
||||
WebsocketService,
|
||||
WebsocketHandlerService,
|
||||
SidebarService,
|
||||
StyleManagerService,
|
||||
ThemeStorageService,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel *ngIf="settings.SMSGlobal != null">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-expansion-panel-header >
|
||||
<mat-panel-title>
|
||||
SMS Global
|
||||
</mat-panel-title>
|
||||
@@ -41,13 +41,13 @@
|
||||
|
||||
|
||||
<mat-expansion-panel *ngFor="let exchange of settings?.Exchanges">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-expansion-panel-header color="secondary">
|
||||
<mat-panel-title>
|
||||
{{exchange.Name}}
|
||||
</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Exchange Settings
|
||||
<mat-icon>trending_up</mat-icon>
|
||||
<mat-icon>attach_money</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<form class="form-content">
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {inject, TestBed} from '@angular/core/testing';
|
||||
import {HttpModule} from '@angular/http';
|
||||
import {StyleManagerComponent} from './style-manager.component';
|
||||
|
||||
|
||||
describe('StyleManager', () => {
|
||||
let styleManager: StyleManagerComponent;
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
imports: [HttpModule],
|
||||
providers: [StyleManagerComponent]
|
||||
}));
|
||||
|
||||
beforeEach(inject([StyleManagerComponent], (sm: StyleManagerComponent) => {
|
||||
styleManager = sm;
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
let links = document.head.querySelectorAll('link');
|
||||
for (let link of Array.prototype.slice.call(links)) {
|
||||
if (link.className.includes('style-manager-')) {
|
||||
document.head.removeChild(link);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should add stylesheet to head', () => {
|
||||
styleManager.setStyle('test', 'test.css');
|
||||
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
|
||||
expect(styleEl).not.toBeNull();
|
||||
expect(styleEl.href.endsWith('test.css')).toBe(true);
|
||||
});
|
||||
|
||||
it('should change existing stylesheet', () => {
|
||||
styleManager.setStyle('test', 'test.css');
|
||||
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
|
||||
expect(styleEl).not.toBeNull();
|
||||
expect(styleEl.href.endsWith('test.css')).toBe(true);
|
||||
|
||||
styleManager.setStyle('test', 'new.css');
|
||||
expect(styleEl.href.endsWith('new.css')).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove existing stylesheet', () => {
|
||||
styleManager.setStyle('test', 'test.css');
|
||||
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
|
||||
expect(styleEl).not.toBeNull();
|
||||
expect(styleEl.href.endsWith('test.css')).toBe(true);
|
||||
|
||||
styleManager.removeStyle('test');
|
||||
styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
|
||||
expect(styleEl).toBeNull();
|
||||
});
|
||||
});
|
||||
44
web/src/app/services/style-manager/style-manager.service.ts
Normal file
44
web/src/app/services/style-manager/style-manager.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
/**
|
||||
* Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be
|
||||
* removed or changed later.
|
||||
*/
|
||||
@Injectable()
|
||||
export class StyleManagerService {
|
||||
/**
|
||||
* Set the stylesheet with the specified key.
|
||||
*/
|
||||
setStyle(key: string, href: string) {
|
||||
getLinkElementForKey(key).setAttribute('href', href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the stylesheet with the specified key.
|
||||
*/
|
||||
removeStyle(key: string) {
|
||||
const existingLinkElement = getExistingLinkElementByKey(key);
|
||||
if (existingLinkElement) {
|
||||
document.head.removeChild(existingLinkElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 `style-manager-${key}`;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import {ThemeStorageService} from './theme-storage.service';
|
||||
|
||||
|
||||
const testStorageKey = ThemeStorageService.storageKey;
|
||||
const testTheme = {
|
||||
primary: '#000000',
|
||||
accent: '#ffffff',
|
||||
href: 'test/path/to/theme'
|
||||
};
|
||||
const createTestData = () => {
|
||||
window.localStorage[testStorageKey] = JSON.stringify(testTheme);
|
||||
};
|
||||
const clearTestData = () => {
|
||||
window.localStorage.clear();
|
||||
};
|
||||
|
||||
describe('ThemeStorage Service', () => {
|
||||
const service = new ThemeStorageService();
|
||||
const getCurrTheme = () => JSON.parse(window.localStorage.getItem(testStorageKey));
|
||||
const secondTestTheme = {
|
||||
primary: '#666666',
|
||||
accent: '#333333',
|
||||
href: 'some/cool/path'
|
||||
};
|
||||
|
||||
beforeEach(createTestData);
|
||||
afterEach(clearTestData);
|
||||
|
||||
it('should set the current theme', () => {
|
||||
expect(getCurrTheme()).toEqual(testTheme);
|
||||
service.storeTheme(secondTestTheme);
|
||||
expect(getCurrTheme()).toEqual(secondTestTheme);
|
||||
});
|
||||
|
||||
it('should get the current theme', () => {
|
||||
const theme = service.getStoredTheme();
|
||||
expect(theme).toEqual(testTheme);
|
||||
});
|
||||
|
||||
it('should clear the stored theme data', () => {
|
||||
expect(getCurrTheme()).not.toBeNull();
|
||||
service.clearStorage();
|
||||
expect(getCurrTheme()).toBeNull();
|
||||
});
|
||||
|
||||
it('should emit an event when setTheme is called', () => {
|
||||
spyOn(service.onThemeUpdate, 'emit');
|
||||
service.storeTheme(secondTestTheme);
|
||||
expect(service.onThemeUpdate.emit).toHaveBeenCalled();
|
||||
expect(service.onThemeUpdate.emit).toHaveBeenCalledWith(secondTestTheme);
|
||||
});
|
||||
});
|
||||
39
web/src/app/services/theme-storage/theme-storage.service.ts
Normal file
39
web/src/app/services/theme-storage/theme-storage.service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {Injectable, EventEmitter} from '@angular/core';
|
||||
|
||||
export interface DocsSiteTheme {
|
||||
href: string;
|
||||
accent: string;
|
||||
primary: string;
|
||||
isDark?: boolean;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ThemeStorageService {
|
||||
static storageKey = 'docs-theme-storage-current';
|
||||
|
||||
public onThemeUpdate: EventEmitter<DocsSiteTheme> = new EventEmitter<DocsSiteTheme>();
|
||||
|
||||
public storeTheme(theme: DocsSiteTheme) {
|
||||
try {
|
||||
window.localStorage[ThemeStorageService.storageKey] = JSON.stringify(theme);
|
||||
} catch (e) { }
|
||||
|
||||
this.onThemeUpdate.emit(theme);
|
||||
}
|
||||
|
||||
public getStoredTheme(): DocsSiteTheme {
|
||||
try {
|
||||
return JSON.parse(window.localStorage[ThemeStorageService.storageKey] || null);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public clearStorage() {
|
||||
try {
|
||||
window.localStorage.removeItem(ThemeStorageService.storageKey);
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,6 @@
|
||||
<span>GoCryptoTrader</span>
|
||||
</a>
|
||||
<div class="flex-spacer"></div>
|
||||
<theme-picker></theme-picker>
|
||||
</mat-toolbar>
|
||||
</nav>
|
||||
18
web/src/app/shared/theme-picker/theme-picker.html
Normal file
18
web/src/app/shared/theme-picker/theme-picker.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<button mat-icon-button [mat-menu-trigger-for]="themeMenu" matTooltip="Select a theme!"
|
||||
tabindex="-1">
|
||||
<mat-icon>format_color_fill</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- TODO: replace use of `mat-menu` here with a custom overlay -->
|
||||
<mat-menu class="docs-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="docs-theme-picker-swatch">
|
||||
<mat-icon class="docs-theme-chosen-icon" *ngIf="currentTheme === theme">check_circle</mat-icon>
|
||||
<div class="docs-theme-picker-primary" [style.background]="theme.primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</mat-menu>
|
||||
58
web/src/app/shared/theme-picker/theme-picker.scss
Normal file
58
web/src/app/shared/theme-picker/theme-picker.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
$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;
|
||||
|
||||
|
||||
.docs-theme-picker-menu {
|
||||
.mat-menu-content {
|
||||
padding: $theme-picker-menu-padding;
|
||||
}
|
||||
|
||||
[mat-menu-item] {
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.docs-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;
|
||||
|
||||
.docs-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%;
|
||||
}
|
||||
}
|
||||
|
||||
.docs-theme-picker-primary {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.docs-theme-picker-accent {
|
||||
position: absolute;
|
||||
bottom: $theme-picker-accent-stripe-size;
|
||||
width: 100%;
|
||||
height: $theme-picker-accent-stripe-size;
|
||||
}
|
||||
}
|
||||
25
web/src/app/shared/theme-picker/theme-picker.spec.ts
Normal file
25
web/src/app/shared/theme-picker/theme-picker.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
74
web/src/app/shared/theme-picker/theme-picker.ts
Normal file
74
web/src/app/shared/theme-picker/theme-picker.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {Component, ViewEncapsulation, ChangeDetectionStrategy, NgModule} from '@angular/core';
|
||||
import { StyleManagerService } from './../../services/style-manager/style-manager.service';
|
||||
|
||||
import { ThemeStorageService,DocsSiteTheme } from './../../services/theme-storage/theme-storage.service';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'theme-picker',
|
||||
templateUrl: 'theme-picker.html',
|
||||
styleUrls: ['theme-picker.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {'aria-hidden': 'true'},
|
||||
})
|
||||
export class ThemePickerComponent {
|
||||
currentTheme;
|
||||
|
||||
themes = [
|
||||
{
|
||||
primary: '#673AB7',
|
||||
accent: '#FFC107',
|
||||
href: 'deeppurple-amber.css',
|
||||
isDark: false,
|
||||
},
|
||||
{
|
||||
primary: '#3F51B5',
|
||||
accent: '#E91E63',
|
||||
href: 'indigo-pink.css',
|
||||
isDark: false,
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
primary: '#E91E63',
|
||||
accent: '#607D8B',
|
||||
href: 'pink-bluegrey.css',
|
||||
isDark: true,
|
||||
},
|
||||
{
|
||||
primary: '#9C27B0',
|
||||
accent: '#4CAF50',
|
||||
href: 'purple-green.css',
|
||||
isDark: true,
|
||||
},
|
||||
];
|
||||
|
||||
constructor(
|
||||
public styleManager: StyleManagerService,
|
||||
private _themeStorage: ThemeStorageService
|
||||
) {
|
||||
const currentTheme = this._themeStorage.getStoredTheme();
|
||||
if (currentTheme) {
|
||||
this.installTheme(currentTheme);
|
||||
}
|
||||
}
|
||||
|
||||
installTheme(theme: DocsSiteTheme) {
|
||||
this.currentTheme = this._getCurrentThemeFromHref(theme.href);
|
||||
|
||||
if (theme.isDefault) {
|
||||
this.styleManager.removeStyle('theme');
|
||||
} else {
|
||||
this.styleManager.setStyle('theme', `assets/${theme.href}`);
|
||||
}
|
||||
|
||||
if (this.currentTheme) {
|
||||
this._themeStorage.storeTheme(this.currentTheme);
|
||||
}
|
||||
}
|
||||
|
||||
private _getCurrentThemeFromHref(href: string): DocsSiteTheme {
|
||||
return this.themes.find(theme => theme.href === href);
|
||||
}
|
||||
}
|
||||
1
web/src/assets/deeppurple-amber.css
Normal file
1
web/src/assets/deeppurple-amber.css
Normal file
File diff suppressed because one or more lines are too long
1
web/src/assets/indigo-pink.css
Normal file
1
web/src/assets/indigo-pink.css
Normal file
File diff suppressed because one or more lines are too long
1
web/src/assets/pink-bluegrey.css
Normal file
1
web/src/assets/pink-bluegrey.css
Normal file
File diff suppressed because one or more lines are too long
1
web/src/assets/purple-green.css
Normal file
1
web/src/assets/purple-green.css
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user