diff --git a/angular.json b/angular.json index c6a1cd1f1..05add5dd7 100644 --- a/angular.json +++ b/angular.json @@ -22,6 +22,7 @@ "main": "src/main.ts", "tsConfig": "src/tsconfig.app.json", "polyfills": "src/polyfills.ts", + "extractCss": true, "assets": [ "src/favicon.ico", "src/robots.txt", @@ -29,7 +30,27 @@ "src/assets" ], "styles": [ - "src/main.scss" + "src/main.scss", + { + "input": "src/theme/deeppurple-amber.scss", + "bundleName": "theme/deeppurple-amber", + "lazy": true + }, + { + "input": "src/theme/indigo-pink.scss", + "bundleName": "theme/indigo-pink", + "lazy": true + }, + { + "input": "src/theme/pink-bluegrey.scss", + "bundleName": "theme/pink-bluegrey", + "lazy": true + }, + { + "input": "src/theme/purple-green.scss", + "bundleName": "theme/purple-green", + "lazy": true + } ], "scripts": [] }, @@ -80,7 +101,27 @@ "tsConfig": "src/tsconfig.spec.json", "scripts": [], "styles": [ - "src/main.scss" + "src/main.scss", + { + "input": "src/theme/deeppurple-amber.scss", + "bundleName": "theme/deeppurple-amber", + "lazy": true + }, + { + "input": "src/theme/indigo-pink.scss", + "bundleName": "theme/indigo-pink", + "lazy": true + }, + { + "input": "src/theme/pink-bluegrey.scss", + "bundleName": "theme/pink-bluegrey", + "lazy": true + }, + { + "input": "src/theme/purple-green.scss", + "bundleName": "theme/purple-green", + "lazy": true + } ], "assets": [ "src/favicon.ico", diff --git a/package-lock.json b/package-lock.json index 5ed762012..5c65776ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -685,6 +685,11 @@ "webpack-sources": "1.1.0" } }, + "@ngx-pwa/local-storage": { + "version": "6.0.0-rc.0", + "resolved": "https://registry.npmjs.org/@ngx-pwa/local-storage/-/local-storage-6.0.0-rc.0.tgz", + "integrity": "sha512-ks+5nSjSbErv8u22jsu4zKUDcQuXTum9k5qVptq2+mytzxXfNZLMNz3NT91Ww72axgsoDKBNEYNaLuY4ST5pLg==" + }, "@ngx-rocket/ascii-logo": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@ngx-rocket/ascii-logo/-/ascii-logo-1.1.0.tgz", diff --git a/package.json b/package.json index b9b38177c..8ecb98e4d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@angular/platform-browser-dynamic": "^6.0.6", "@angular/router": "^6.0.6", "@angular/service-worker": "^6.0.6", + "@ngx-pwa/local-storage": "^6.0.0-rc.0", "@ngx-translate/core": "^10.0.2", "@swimlane/ngx-charts": "^8.1.0", "angular-archwizard": "^3.0.0", diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 158f21544..be2230005 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -53,6 +53,7 @@ import { CacheInterceptor } from './http/cache.interceptor'; import { ShellComponent } from './shell/shell.component'; import { SidenavComponent } from './shell/sidenav/sidenav.component'; +import { ThemePickerComponent } from './shell/toolbar/theme-picker/theme-picker.component'; import { ToolbarComponent } from './shell/toolbar/toolbar.component'; import { ContentComponent } from './shell/content/content.component'; import { BreadcrumbComponent } from './shell/breadcrumb/breadcrumb.component'; @@ -102,6 +103,7 @@ import { BreadcrumbComponent } from './shell/breadcrumb/breadcrumb.component'; declarations: [ ShellComponent, SidenavComponent, + ThemePickerComponent, ToolbarComponent, ContentComponent, BreadcrumbComponent diff --git a/src/app/core/shell/toolbar/theme-picker/theme-manager.service.spec.ts b/src/app/core/shell/toolbar/theme-picker/theme-manager.service.spec.ts new file mode 100644 index 000000000..7bfde5b85 --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-manager.service.spec.ts @@ -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(); + })); +}); diff --git a/src/app/core/shell/toolbar/theme-picker/theme-manager.service.ts b/src/app/core/shell/toolbar/theme-picker/theme-manager.service.ts new file mode 100644 index 000000000..cb28501ab --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-manager.service.ts @@ -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; + } + +} diff --git a/src/app/core/shell/toolbar/theme-picker/theme-picker.component.html b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.html new file mode 100644 index 000000000..45e3b413d --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.html @@ -0,0 +1,16 @@ + + + + + +
+
+ check_circle +
+
+
+
+
+
diff --git a/src/app/core/shell/toolbar/theme-picker/theme-picker.component.scss b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.scss new file mode 100644 index 000000000..b2a98433d --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.scss @@ -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; + } +} diff --git a/src/app/core/shell/toolbar/theme-picker/theme-picker.component.spec.ts b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.spec.ts new file mode 100644 index 000000000..87b8ba21c --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ThemePickerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ThemePickerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/shell/toolbar/theme-picker/theme-picker.component.ts b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.ts new file mode 100644 index 000000000..6d1fbf545 --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-picker.component.ts @@ -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); + } + +} diff --git a/src/app/core/shell/toolbar/theme-picker/theme-storage.service.spec.ts b/src/app/core/shell/toolbar/theme-picker/theme-storage.service.spec.ts new file mode 100644 index 000000000..e18a1826a --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-storage.service.spec.ts @@ -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(); + })); +}); diff --git a/src/app/core/shell/toolbar/theme-picker/theme-storage.service.ts b/src/app/core/shell/toolbar/theme-picker/theme-storage.service.ts new file mode 100644 index 000000000..795b1dd43 --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme-storage.service.ts @@ -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; + + constructor(private localStorage: LocalStorage) { + this.onThemeUpdate = new EventEmitter(); + } + + storeTheme(mifosXTheme: Theme) { + this.localStorage.setItem(this.themeStorageKey, mifosXTheme) + .subscribe(() => this.onThemeUpdate.emit(mifosXTheme)); + } + + getTheme(): Observable { + return this.localStorage.getItem(this.themeStorageKey); + } + + clearTheme() { + this.localStorage.removeItem(this.themeStorageKey); + } + +} diff --git a/src/app/core/shell/toolbar/theme-picker/theme.model.ts b/src/app/core/shell/toolbar/theme-picker/theme.model.ts new file mode 100644 index 000000000..453853a70 --- /dev/null +++ b/src/app/core/shell/toolbar/theme-picker/theme.model.ts @@ -0,0 +1,7 @@ +export interface Theme { + href: string; + primary: string; + accent: string; + isDark?: boolean; + isDefault?: boolean; +} diff --git a/src/app/core/shell/toolbar/toolbar.component.html b/src/app/core/shell/toolbar/toolbar.component.html index 1654a7b7e..db9fed5d4 100644 --- a/src/app/core/shell/toolbar/toolbar.component.html +++ b/src/app/core/shell/toolbar/toolbar.component.html @@ -56,9 +56,7 @@ Hindi - +