Theme module (#18)

This commit is contained in:
Rinat Arsaev 2019-05-13 15:02:44 +03:00 committed by GitHub
parent a4293a7646
commit d57794a455
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 353 additions and 180 deletions

View File

@ -19,7 +19,19 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"styles": [
"src/styles.scss",
{
"input": "src/themes/light.scss",
"bundleName": "themes/light",
"lazy": true
},
{
"input": "src/themes/dark.scss",
"bundleName": "themes/dark",
"lazy": true
}
],
"scripts": []
},
"configurations": {
@ -30,8 +42,8 @@
"with": "src/environments/environment.prod.ts"
},
{
"replace": "src/app/auth/keycloak-stub/index.js",
"with": "src/app/auth/keycloak-stub/index.real.ts"
"replace": "src/app/auth/keycloak/index.js",
"with": "src/app/auth/keycloak/index.real.ts"
}
],
"optimization": true,
@ -77,7 +89,19 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": ["src/styles.scss"],
"styles": [
"src/styles.scss",
{
"input": "src/themes/light.scss",
"bundleName": "themes/light",
"lazy": true
},
{
"input": "src/themes/dark.scss",
"bundleName": "themes/dark",
"lazy": true
}
],
"scripts": [],
"assets": ["src/favicon.ico", "src/assets"]
}

View File

@ -1,4 +1,7 @@
<div class="actionbar" fxLayout fxLayoutGap="10px">
<div>
<button (click)="changeTheme()">Change theme</button>
</div>
<div>
<div class="circle" [dshDropdownTriggerFor]="dropdown">
<div class="wrapper"><mat-icon class="user-icon" svgIcon="user"></mat-icon></div>

View File

@ -1,10 +1,7 @@
@import '../../styles/variables.scss';
$circle-color: $white;
$circle-shadow: $turquoise-shadow;
$circle-color: #fff;
.actionbar {
height: $toolbar-item-height;
height: 50px;
}
.circle {
@ -16,7 +13,7 @@ $circle-shadow: $turquoise-shadow;
height: 42px;
text-align: center;
transition: 0.2s;
box-shadow: $circle-shadow;
box-shadow: 0 8px 20px 0 rgba(174, 188, 230, 0.24);
&:active {
transform: scale(0.9);
}

View File

@ -1,6 +1,7 @@
import { Component, ViewChild } from '@angular/core';
import { DropdownTriggerDirective } from '../dropdown/dropdown-trigger.directive';
import { ThemeService } from '../theme';
@Component({
selector: 'dsh-actionbar',
@ -10,7 +11,13 @@ import { DropdownTriggerDirective } from '../dropdown/dropdown-trigger.directive
export class ActionbarComponent {
@ViewChild(DropdownTriggerDirective) trigger: DropdownTriggerDirective;
constructor(private themeService: ThemeService) {}
close() {
this.trigger.close();
}
changeTheme() {
this.themeService.changeTheme();
}
}

1
src/app/api/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './api.module';

View File

@ -1,27 +1,36 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { KeycloakService } from './auth/keycloak-stub';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { IconRegistryService } from './icon-registry.service';
import { AuthModule } from './auth';
import { initializer } from './initializer';
import { APIModule } from './api/api.module';
import { ConfigService } from './config/config.service';
import { SectionsModule } from './sections/sections.module';
import { RouterModule } from '@angular/router';
import { APIModule } from './api';
import { SectionsModule } from './sections';
import { KeycloakService } from './auth/keycloak';
import { ThemeService, ThemeModule } from './theme';
import { ConfigModule, ConfigService } from './config';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, BrowserAnimationsModule, RouterModule, SectionsModule, APIModule, AuthModule],
imports: [
BrowserModule,
BrowserAnimationsModule,
RouterModule,
SectionsModule,
APIModule,
AuthModule,
ThemeModule,
ConfigModule
],
providers: [
IconRegistryService,
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializer,
deps: [ConfigService, KeycloakService],
deps: [ConfigService, KeycloakService, ThemeService],
multi: true
}
],

View File

@ -1,7 +1,7 @@
import { Router, ActivatedRouteSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { KeycloakAuthGuard, KeycloakService } from './keycloak-stub';
import { KeycloakAuthGuard, KeycloakService } from './keycloak';
@Injectable()
export class AppAuthGuardService extends KeycloakAuthGuard {

View File

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { KeycloakAngularModule } from './keycloak-stub';
import { KeycloakAngularModule } from './keycloak';
import { AppAuthGuardService } from './app-auth-guard.service';

View File

@ -1,7 +1,5 @@
@import '../../styles/variables.scss';
.brand {
height: $toolbar-item-height;
height: 50px;
}
mat-icon {

View File

@ -1,5 +1,3 @@
@import '../../../styles/variables';
.y.axis > path,
.x.axis > path {
color: transparent;

View File

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { ConfigService } from './config.service';
@NgModule({
imports: [],
declarations: [],
entryComponents: [],
providers: [ConfigService]
})
export class ConfigModule {}

2
src/app/config/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './config.module';
export * from './config.service';

View File

@ -1,5 +1,4 @@
import { KeycloakService } from './auth/keycloak-stub';
import { KeycloakService } from './auth/keycloak';
import { ConfigService } from './config/config.service';
export const initializer = (configService: ConfigService, keycloakService: KeycloakService) => () =>

View File

@ -0,0 +1,9 @@
@import '~@angular/material/theming';
@mixin dsh-card-theme($theme) {
$background: map-get($theme, background);
.card {
background-color: mat-color($background, card);
}
}

View File

@ -1,6 +1,4 @@
@import '../../../../styles/variables.scss';
:host {
display: block;
margin-bottom: $card-header-margin;
margin-bottom: 30px;
}

View File

@ -1 +1 @@
<ng-content></ng-content>
<div class="card"><ng-content></ng-content></div>

View File

@ -1,9 +1,6 @@
@import '../../../styles/variables.scss';
:host {
.card {
display: block;
background-color: $card-background-color;
padding: $card-padding;
border-radius: $card-border-radius;
box-shadow: $card-shadow;
padding: 30px;
border-radius: 4px;
box-shadow: 0 8px 20px 0 rgba(174, 188, 230, 0.24);
}

View File

@ -1,11 +1,9 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.container {
@include fullscreen();
background-color: $background-color;
}
.content {
@include content($content-padding, $content-max-width);
@include content(30px, 1128px);
}

View File

@ -1,17 +1,15 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
mat-sidenav-container {
@include fullscreen();
background-color: $background-color;
}
.content {
@include content($content-padding, $content-max-width);
@include content(30px, 1128px);
}
.brand {
height: $toolbar-height;
height: 80px;
}
.side-menu {

View File

@ -0,0 +1 @@
export * from './sections.module';

View File

@ -1,6 +1,5 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.content {
@include content($content-padding, $content-max-width);
@include content(30px, 1128px);
}

View File

@ -1,4 +1,3 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.display-2 {
@ -6,5 +5,5 @@
}
.content {
@include content($content-padding, $content-max-width);
@include content(30px, 1128px);
}

View File

@ -1,17 +1,15 @@
@import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
mat-sidenav-container {
@include fullscreen();
background-color: $background-color;
}
.content {
@include content($content-padding, $content-max-width);
@include content(30px, 1128px);
}
.brand {
height: $toolbar-height;
height: 80px;
}
.side-menu {

View File

@ -1,5 +1,3 @@
@import '../../styles/variables';
$dsh-header-row-height: 56px;
$dsh-row-height: 48px;
$dsh-row-horizontal-padding: 24px;

39
src/app/theme/external.ts Normal file
View File

@ -0,0 +1,39 @@
export abstract class External<T extends HTMLElement = HTMLElement> {
element: T;
constructor(public url: string) {}
protected abstract createElement(): T;
add() {
if (!this.element) {
this.element = this.createElement();
}
if (!document.head.contains(this.element)) {
document.head.appendChild(this.element);
}
}
remove() {
if (document.head.contains(this.element)) {
document.head.removeChild(this.element);
}
}
}
export class Style extends External<HTMLLinkElement> {
protected createElement() {
const styleElement = document.createElement('link');
styleElement.href = this.url;
styleElement.rel = 'stylesheet';
return styleElement;
}
}
export class Script extends External<HTMLScriptElement> {
protected createElement() {
const scriptElement = document.createElement('script');
scriptElement.src = this.url;
return scriptElement;
}
}

2
src/app/theme/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './theme.service';
export * from './theme.module';

View File

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { ThemeService } from './theme.service';
@NgModule({
imports: [],
declarations: [],
entryComponents: [],
providers: [ThemeService]
})
export class ThemeModule {}

View File

@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { Script, Style, External } from './external';
import themes from '../../themes/themes.json';
enum Type {
JS = 'js',
CSS = 'css'
}
@Injectable()
export class ThemeService {
themes: { [name: string]: External } = {};
fileType: Type = environment.production ? Type.CSS : Type.JS;
get currentTheme() {
return localStorage.getItem('dsh-theme') || themes[0];
}
set currentTheme(theme: string) {
localStorage.setItem('dsh-theme', theme);
}
constructor() {
this.init();
}
init() {
this.themes = themes.reduce((t, name) => {
t[name] = this.createExternal(this.getFilePath(name));
return t;
}, {});
this.changeTheme(this.currentTheme);
}
changeTheme(name: string = this.getNextTheme()) {
this.themes[name].add();
this.removeCurrentTheme();
this.currentTheme = name;
document.body.classList.add(this.currentTheme);
}
getNextTheme(): string {
const idx = themes.findIndex(n => n === this.currentTheme) + 1;
return themes[idx === themes.length ? 0 : idx];
}
private removeCurrentTheme() {
if (this.currentTheme) {
this.themes[this.currentTheme].remove();
document.body.classList.remove(this.currentTheme);
}
}
private getFilePath(name: string) {
return `themes/${name}.${this.fileType}`;
}
private createExternal(url: string): External {
return new (this.fileType === 'js' ? Script : Style)(url);
}
}

View File

@ -1,6 +1,4 @@
@import '../../styles/variables.scss';
.toolbar {
user-select: none;
height: $toolbar-height;
height: 80px;
}

View File

@ -1,31 +1,10 @@
@import '~@angular/material/theming';
// Utils
@import './styles/functions';
// Variables
@import './styles/variables';
@import './styles/palletes';
// Theme
@import './styles/main-theme';
// Components
@import './app/dropdown/dropdown-theme';
@import './app/table/table-theme';
@import './app/charts/bar-chart/bar-chart-theme';
@include mat-core();
$primary: mat-palette($dsh-slateblue);
$accent: mat-palette($dsh-palegreen);
$warn: mat-palette($mat-red);
$theme: dsh-light-theme($primary, $accent, $warn, $dsh-light-theme-foreground, $dsh-light-theme-background);
$mat-theme: mat-light-theme($primary, $accent, $warn);
@include angular-material-theme($mat-theme);
@include dsh-main-theme($theme);
@include dsh-dropdown-theme($theme);
@include dsh-table-theme($theme);
@include dsh-bar-chart-theme($theme);
body {
font-size: 14px;
font-family: 'Roboto', sans-serif;
margin: 0;
transition: all 0.3s ease-in-out;
}

View File

@ -1,20 +0,0 @@
@import '~@angular/material/theming';
@import 'variables';
@import 'palletes';
@function dsh-light-theme(
$primary,
$accent,
$warn: mat-palette($mat-red),
$foreground: $dsh-light-theme-foreground,
$background: $dsh-light-theme-background
) {
@return (
primary: $primary,
accent: $accent,
warn: $warn,
is-dark: false,
foreground: $foreground,
background: $background
);
}

View File

@ -1,12 +0,0 @@
@import '~@angular/material/theming';
@mixin dsh-main-theme($theme) {
$background: map-get($theme, background);
body {
font-size: 14px;
font-family: 'Roboto', sans-serif;
margin: 0;
background-color: mat-color($background, background);
}
}

View File

@ -1,46 +1,5 @@
@import 'variables';
// Background palette for light themes.
$dsh-light-theme-background: (
background: $background-color,
card: $white
);
// Background palette for dark themes.
$dsh-dark-theme-background: (
background: #303030,
card: $black
);
// Foreground palette for light themes.
$dsh-light-theme-foreground: (
base: black,
disabled-button: rgba($black, 0.26),
elevation: black,
divider: rgba($black, 0.27),
icon: rgba($black, 0.54),
icons: rgba($black, 0.54),
text: rgba($black, 0.87),
secondary-text: rgba($black, 0.67),
slider-min: rgba($black, 0.87),
slider-off: rgba($black, 0.26),
slider-off-active: rgba($black, 0.38)
);
// Foreground palette for dark themes.
$dsh-dark-theme-foreground: (
base: white,
disabled-button: rgba($white, 0.3),
elevation: black,
divider: rgba($white, 0.27),
icon: white,
icons: white,
text: white,
secondary-text: rgba($white, 0.67),
slider-min: white,
slider-off: rgba($white, 0.3),
slider-off-active: rgba($white, 0.3)
);
$dark-primary-text: rgba(black, 0.87);
$light-primary-text: white;
$dsh-slateblue: (
50: #f4f0ff,
@ -74,6 +33,7 @@ $dsh-slateblue: (
A700: $light-primary-text
)
);
$dsh-palegreen: (
50: #f0fff6,
100: #e8fff2,

View File

@ -1,20 +0,0 @@
$turquoise: #f1f7fb;
$white: #ffffff;
$black: #000000;
$turquoise-shadow: 0 8px 20px 0 rgba(174, 188, 230, 0.24);
$background-color: $turquoise;
$toolbar-height: 80px;
$toolbar-item-height: 50px;
$content-max-width: 1128px;
$content-padding: 30px;
$card-background-color: $white;
$card-padding: 30px;
$card-border-radius: 4px;
$card-shadow: $turquoise-shadow;
$card-header-margin: 30px;

8
src/themes/body.scss Normal file
View File

@ -0,0 +1,8 @@
@import '~@angular/material/theming';
@mixin dsh-body-theme($theme) {
$background: map-get($theme, background);
background-color: map-get($background, background);
color: map-get($foreground, text);
}

51
src/themes/dark.scss Normal file
View File

@ -0,0 +1,51 @@
@import '~@angular/material/theming';
@import '../styles/palette';
@import './theme';
$background: (
status-bar: black,
app-bar: map_get($mat-grey, 900),
background: #303030,
hover: rgba(white, 0.04),
card: map_get($mat-grey, 800),
dialog: map_get($mat-grey, 800),
disabled-button: rgba(white, 0.12),
raised-button: map-get($mat-grey, 800),
focused-button: $light-focused,
selected-button: map_get($mat-grey, 900),
selected-disabled-button: map_get($mat-grey, 800),
disabled-button-toggle: black,
unselected-chip: map_get($mat-grey, 700),
disabled-list-option: black
);
$foreground: (
base: white,
divider: $light-dividers,
dividers: $light-dividers,
disabled: $light-disabled-text,
disabled-button: rgba(white, 0.3),
disabled-text: $light-disabled-text,
elevation: black,
hint-text: $light-disabled-text,
secondary-text: $light-secondary-text,
icon: white,
icons: white,
text: white,
slider-min: white,
slider-off: rgba(white, 0.3),
slider-off-active: rgba(white, 0.3)
);
$theme: (
name: 'dark',
primary: mat-palette($dsh-slateblue),
accent: mat-palette($dsh-palegreen),
warn: mat-palette($mat-red),
is-dark: true,
foreground: $foreground,
background: $background
);
@include dsh-theme($theme);

51
src/themes/light.scss Normal file
View File

@ -0,0 +1,51 @@
@import '~@angular/material/theming';
@import '../styles/palette';
@import './theme';
$background: (
status-bar: map_get($mat-grey, 300),
app-bar: map_get($mat-grey, 100),
background: #f1f7fb,
hover: rgba(black, 0.04),
card: white,
dialog: white,
disabled-button: rgba(black, 0.12),
raised-button: white,
focused-button: $dark-focused,
selected-button: map_get($mat-grey, 300),
selected-disabled-button: map_get($mat-grey, 400),
disabled-button-toggle: map_get($mat-grey, 200),
unselected-chip: map_get($mat-grey, 300),
disabled-list-option: map_get($mat-grey, 200)
);
$foreground: (
base: black,
divider: rgba(#000, 0.27),
dividers: $dark-dividers,
disabled: $dark-disabled-text,
disabled-button: rgba(#000, 0.26),
disabled-text: $dark-disabled-text,
elevation: black,
hint-text: $dark-disabled-text,
secondary-text: rgba(#000, 0.67),
icon: rgba(#000, 0.54),
icons: rgba(#000, 0.54),
text: rgba(#000, 0.87),
slider-min: rgba(#000, 0.87),
slider-off: rgba(#000, 0.26),
slider-off-active: rgba(#000, 0.38)
);
$theme: (
name: 'light',
primary: mat-palette($dsh-slateblue),
accent: mat-palette($dsh-palegreen),
warn: mat-palette($mat-red),
is-dark: false,
foreground: $foreground,
background: $background
);
@include dsh-theme($theme);

20
src/themes/theme.scss Normal file
View File

@ -0,0 +1,20 @@
@import '~@angular/material/theming';
@import './body';
@import '../app/dropdown/dropdown-theme';
@import '../app/table/table-theme';
@import '../app/charts/bar-chart/bar-chart-theme';
@import '../app/layout/card/card-theme';
@mixin dsh-theme($theme) {
body.#{map-get($theme, name)} {
@include angular-material-theme($theme);
@include dsh-body-theme($theme);
@include dsh-dropdown-theme($theme);
@include dsh-table-theme($theme);
@include dsh-bar-chart-theme($theme);
@include dsh-card-theme($theme);
}
}

1
src/themes/themes.json Normal file
View File

@ -0,0 +1 @@
["light", "dark"]