To make it universal for everyone, I have modified the original app.config.component.ts to save and load the user profile from a cookie. No private services are used anymore. Please run the following:
with my code (you can compare it to original to see, there are only some "minor" differences). If you could include this "saving" functionality into original theme, that would be really great... or at least post the changes in the function
.
Code: Select all
import {Component, OnInit} from '@angular/core';
import {AppComponent} from '../app.component';
import {AppMainComponent} from './app.main.component';
import {CookieService} from "ngx-cookie-service";
@Component({
selector: 'app-config',
template: `
<p-sidebar #sidebar [(visible)]="configActive" [position]="app.isRTL ? 'left' : 'right'" [blockScroll]="true" [showCloseIcon]="false" [baseZIndex]="1000" styleClass="layout-config p-sidebar-sm fs-small p-0">
<div class="layout-config-panel flex flex-column">
<div class="px-3 pt-3">
<h5>Theme Customization</h5>
<span>Ultima offers different themes for layout, topbar, menu etc.</span>
</div>
<hr class="mb-0" />
<div class="layout-config-options p-3">
<h6>Layout/Theme Scale</h6>
<div class="flex align-items-center">
<button pButton pRipple type="button" icon="pi pi-minus" (click)="decrementScale();saveLayoutAndDesign()" class="p-button-rounded p-button-text" [disabled]="scale === scales[0]"></button>
<i class="pi pi-circle-on m-1 scale-icon" *ngFor="let s of scales" [ngClass]="{'scale-active': s === scale}"></i>
<button pButton pRipple type="button" icon="pi pi-plus" (click)="incrementScale();saveLayoutAndDesign()" class="p-button-rounded p-button-text" [disabled]="scale === scales[scales.length - 1]"></button>
</div>
<h6>Layout Mode</h6>
<div class="flex">
<div class="flex align-items-center">
<p-radioButton name="layoutMode" value="light" [(ngModel)]="app.layoutMode" inputId="layoutMode1" (onClick)="onLayoutModeChange($event, 'light');saveLayoutAndDesign()"></p-radioButton>
<label for="layoutMode1" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Light</label>
</div>
<div class="flex align-items-center" [ngClass]="{'ml-4': !app.isRTL, 'mr-4': app.isRTL}">
<p-radioButton name="layoutMode" value="dark" [(ngModel)]="app.layoutMode" inputId="layoutMode2" (onClick)="onLayoutModeChange($event, 'dark');saveLayoutAndDesign()"></p-radioButton>
<label for="layoutMode2" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Dark</label>
</div>
</div>
<h6>Menu Mode</h6>
<div class="flex">
<div class="flex flex-column">
<div class="flex align-items-center">
<p-radioButton name="menuMode" value="static" [(ngModel)]="app.menuMode" inputId="menuMode1" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="menuMode1" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Static</label>
</div>
<div class="flex align-items-center mt-3">
<p-radioButton name="menuMode" value="horizontal" [(ngModel)]="app.menuMode" inputId="menuMode2" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="menuMode2" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Horizontal</label>
</div>
</div>
<div class="flex flex-column" [ngClass]="{'ml-4': !app.isRTL, 'mr-4': app.isRTL}">
<div class="flex align-items-center">
<p-radioButton name="menuMode" value="overlay" [(ngModel)]="app.menuMode" inputId="menuMode4" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="menuMode4" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Overlay</label>
</div>
<div class="flex align-items-center mt-3">
<p-radioButton name="menuMode" value="slim" [(ngModel)]="app.menuMode" inputId="menuMode3" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="menuMode3" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Slim</label>
</div>
</div>
</div>
<h6>Inline Menu Position</h6>
<div class="flex">
<div class="flex align-items-center">
<p-radioButton name="inlineMenuPosition" value="top" [(ngModel)]="app.inlineMenuPosition" inputId="inlineMenuPosition1" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="inlineMenuPosition1" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Top</label>
</div>
<div class="flex align-items-center" [ngClass]="{'ml-4': !app.isRTL, 'mr-4': app.isRTL}">
<p-radioButton name="inlineMenuPosition" value="bottom" [(ngModel)]="app.inlineMenuPosition" inputId="inlineMenuPosition2" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="inlineMenuPosition2" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Bottom</label>
</div>
<div class="flex align-items-center" [ngClass]="{'ml-4': !app.isRTL, 'mr-4': app.isRTL}">
<p-radioButton name="inlineMenuPosition" value="both" [(ngModel)]="app.inlineMenuPosition" inputId="inlineMenuPosition3" (onClick)="saveLayoutAndDesign()"></p-radioButton>
<label for="inlineMenuPosition3" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Both</label>
</div>
</div>
<h6>Input Background</h6>
<div class="flex">
<div class="flex align-items-center">
<p-radioButton name="inputStyle" value="outlined" [(ngModel)]="app.inputStyle" inputId="inputStyle1" (onClick)="onInputStyleClick();saveLayoutAndDesign();"></p-radioButton>
<label for="inputStyle1" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Outlined</label>
</div>
<div class="flex align-items-center" [ngClass]="{'ml-4': !app.isRTL, 'mr-4': app.isRTL}">
<p-radioButton name="inputStyle" value="filled" [(ngModel)]="app.inputStyle" inputId="inputStyle2" (onClick)="onInputStyleClick();saveLayoutAndDesign();"></p-radioButton>
<label for="inputStyle2" [ngClass]="{'ml-2': !app.isRTL, 'mr-2': app.isRTL}">Filled</label>
</div>
</div>
<h6>Ripple Effect</h6>
<p-inputSwitch [ngModel]="app.ripple" (onChange)="appMain.onRippleChange($event);saveLayoutAndDesign();"></p-inputSwitch>
<h6>RTL</h6>
<p-inputSwitch [ngModel]="app.isRTL" (onChange)="appMain.onRTLChange($event);saveLayoutAndDesign();" styleClass="block"></p-inputSwitch>
<h6>Menu Themes</h6>
<div *ngIf="app.layoutMode!=='dark'" class="grid">
<div *ngFor="let t of menuThemes" class="col col-fixed">
<a style="cursor: pointer" (click)="changeMenuTheme(t);saveLayoutAndDesign();" class="layout-config-color-option" [title]="t.name">
<span class="color" [ngStyle]="{'background-color': t.color}"></span>
<span class="check flex align-items-center justify-content-center" *ngIf="app.menuTheme === t.name">
<i class="pi pi-check" style="color: var(--menu-text-color)"></i>
</span>
</a>
</div>
</div>
<p *ngIf="app.layoutMode==='dark'">Menu themes are only available in light mode by design as large surfaces can emit too much brightness in dark mode.</p>
<h6>Topbar Themes</h6>
<div class="grid">
<div *ngFor="let t of topbarThemes" class="col col-fixed">
<a style="cursor: pointer" (click)="changeTopbarTheme(t);saveLayoutAndDesign();" class="layout-config-color-option" [title]="t.name">
<span class="color" [ngStyle]="{'background-color': t.color}"></span>
<span class="check flex align-items-center justify-content-center" *ngIf="app.topbarTheme === t.name">
<i class="pi pi-check" style="color: var(--topbar-text-color)"></i>
</span>
</a>
</div>
</div>
<h6>Component Themes</h6>
<div class="grid">
<div *ngFor="let t of themes" class="col col-fixed">
<a style="cursor: pointer" (click)="changeTheme(t.name);saveLayoutAndDesign();" class="layout-config-color-option" [title]="t.name">
<span class="color" [ngStyle]="{'background-color': t.color}"></span>
<span class="check flex align-items-center justify-content-center" *ngIf="theme === t.name">
<i class="pi pi-check" style="color: var(--primary-color-text)"></i>
</span>
</a>
</div>
</div>
</div>
</div>
</p-sidebar>
<p-button type="button" (click)="configActive = true" icon="pi pi-cog" *ngIf="!configActive" styleClass="layout-config-button"></p-button>
`
})
export class AppConfigComponent implements OnInit {
scale = 14;
scales: number[] = [12, 13, 14, 15, 16, 17, 18, 19];
themes: any[];
menuThemes: any[];
menuTheme = 'light';
topbarThemes: any[];
topbarTheme = 'blue';
theme = 'indigo';
matchingMenuTheme = false;
matchingTopbarTheme = false;
selectedMenuTheme: any;
selectedTopbarTheme: any;
configActive = false;
isInputBackgroundChanged = false;
constructor(public appMain: AppMainComponent, public app: AppComponent
, private cookieService: CookieService ) {}
ngOnInit() {
this.themes = [
{name: 'indigo', color: '#3F51B5'},
{name: 'pink', color: '#E91E63'},
{name: 'purple', color: '#9C27B0'},
{name: 'deeppurple', color: '#673AB7'},
{name: 'blue', color: '#2196F3'},
{name: 'lightblue', color: '#03A9F4'},
{name: 'cyan', color: '#00BCD4'},
{name: 'teal', color: '#009688'},
{name: 'green', color: '#4CAF50'},
{name: 'lightgreen', color: '#8BC34A'},
{name: 'lime', color: '#CDDC39'},
{name: 'yellow', color: '#FFEB3B'},
{name: 'amber', color: '#FFC107'},
{name: 'orange', color: '#FF9800'},
{name: 'deeporange', color: '#FF5722'},
{name: 'brown', color: '#795548'},
{name: 'bluegrey', color: '#607D8B'}
];
this.menuThemes = [
{name: 'light', color: '#FDFEFF'},
{name: 'dark', color: '#434B54'},
{name: 'indigo', color: '#1A237E'},
{name: 'bluegrey', color: '#37474F'},
{name: 'brown', color: '#4E342E'},
{name: 'cyan', color: '#006064'},
{name: 'green', color: '#2E7D32'},
{name: 'deeppurple', color: '#4527A0'},
{name: 'deeporange', color: '#BF360C'},
{name: 'pink', color: '#880E4F'},
{name: 'purple', color: '#6A1B9A'},
{name: 'teal', color: '#00695C'}
];
this.topbarThemes = [
{name: 'lightblue', color: '#2E88FF'},
{name: 'dark', color: '#363636'},
{name: 'white', color: '#FDFEFF'},
{name: 'blue', color: '#1565C0'},
{name: 'deeppurple', color: '#4527A0'},
{name: 'purple', color: '#6A1B9A'},
{name: 'pink', color: '#AD1457'},
{name: 'cyan', color: '#0097A7'},
{name: 'teal', color: '#00796B'},
{name: 'green', color: '#43A047'},
{name: 'lightgreen', color: '#689F38'},
{name: 'lime', color: '#AFB42B'},
{name: 'yellow', color: '#FBC02D'},
{name: 'amber', color: '#FFA000'},
{name: 'orange', color: '#FB8C00'},
{name: 'deeporange', color: '#D84315'},
{name: 'brown', color: '#5D4037'},
{name: 'grey', color: '#616161'},
{name: 'bluegrey', color: '#546E7A'},
{name: 'indigo', color: '#3F51B5'}
];
this.loadLayoutAndDesign();
}
decrementScale() {
this.scale--;
this.applyScale();
}
incrementScale() {
this.scale++;
this.applyScale();
}
applyScale() {
document.documentElement.style.fontSize = this.scale + 'px';
}
onInputStyleClick() {
this.isInputBackgroundChanged = true;
}
onLayoutModeChange(event, mode) {
this.app.layoutMode = mode;
if (!this.isInputBackgroundChanged) {
this.app.inputStyle = mode === 'dark' ? 'filled' : 'outlined';
}
if (mode === 'dark') {
this.app.menuTheme = 'dark';
this.app.topbarTheme = 'dark';
}
else {
this.app.menuTheme = 'light';
this.app.topbarTheme = 'blue';
}
this.setCorrectLogo(mode);
const layoutLink: HTMLLinkElement = document.getElementById('layout-css') as HTMLLinkElement;
const layoutHref = 'assets/layout/css/layout-' + this.app.layoutMode + '.css';
this.replaceLink(layoutLink, layoutHref);
const themeLink = document.getElementById('theme-css');
const urlTokens = themeLink.getAttribute('href').split('/');
urlTokens[urlTokens.length - 1] = 'theme-' + this.app.layoutMode + '.css';
const newURL = urlTokens.join('/');
this.replaceLink(themeLink, newURL, this.appMain['refreshChart']);
}
changeTheme(theme) {
this.theme = theme;
const themeLink: HTMLLinkElement = document.getElementById('theme-css') as HTMLLinkElement;
const themeHref = 'assets/theme/' + theme + '/theme-' + this.app.layoutMode + '.css';
this.replaceLink(themeLink, themeHref);
}
changeMenuTheme(theme) {
this.selectedMenuTheme = theme;
this.app.menuTheme = theme.name;
}
changeTopbarTheme(theme) {
this.selectedTopbarTheme = theme;
this.app.topbarTheme = theme.name;
this.setCorrectLogo(theme.name);
}
isIE() {
return /(MSIE|Trident\/|Edge\/)/i.test(window.navigator.userAgent);
}
replaceLink(linkElement, href, callback?) {
if (this.isIE()) {
linkElement.setAttribute('href', href);
if (callback) {
callback();
}
} else {
const id = linkElement.getAttribute('id');
const cloneLinkElement = linkElement.cloneNode(true);
cloneLinkElement.setAttribute('href', href);
cloneLinkElement.setAttribute('id', id + '-clone');
linkElement.parentNode.insertBefore(cloneLinkElement, linkElement.nextSibling);
cloneLinkElement.addEventListener('load', () => {
linkElement.remove();
cloneLinkElement.setAttribute('id', id);
if (callback) {
callback();
}
});
}
}
loadLayoutAndDesign() {
const designTmp = this.cookieService.get('layoutAndDesign');
if (!designTmp) {
return;
}
const design = JSON.parse(designTmp);
console.log('AppConfigComponent.loadLayoutAndDesign()', design);
if (design.hasOwnProperty('scale')) {
this.scale = design.scale;
this.applyScale();
}
if (design.hasOwnProperty('menuMode')) {
this.app.menuMode = design.menuMode;
}
if (design.hasOwnProperty('ripple')) {
this.appMain.onRippleChange({checked: design.ripple});
}
if (design.hasOwnProperty('isRTL')) {
this.appMain.onRTLChange({checked: design.isRTL});
}
if (design.hasOwnProperty('inlineMenuPosition')) {
this.app.inlineMenuPosition = design.inlineMenuPosition;
}
if (design.hasOwnProperty('inputStyle')) {
this.app.inputStyle = design.inputStyle;
this.onInputStyleClick();
}
if (design.hasOwnProperty('layoutMode')) { // dark vs. light
if (design.layoutMode === 'dark') {
} else {
this.onLayoutModeChange(null, design.layoutMode);
}
}
if (design.hasOwnProperty('menuTheme')) {
this.menuTheme = design.menuTheme;
const theme = this.menuThemes.find(t => t.name === this.menuTheme);
this.changeMenuTheme(theme);
} else {
this.selectedMenuTheme = this.menuThemes.find(theme => theme.name === this.menuTheme);
}
if (design.hasOwnProperty('topbarTheme')) {
const theme = this.topbarThemes.find(t => t.name === design.topbarTheme);
this.app.topbarTheme = theme.name;
this.topbarTheme = design.topbarTheme;
this.changeTopbarTheme(theme);
} else {
this.selectedTopbarTheme = this.topbarThemes.find(theme => theme.name === this.topbarTheme);
}
if (design.hasOwnProperty('theme')) {
const theme = this.themes.find(t => t.name === design.theme);
this.changeTheme(theme ? theme.name : 'indigo');
}
}
saveLayoutAndDesign() {
const design: any = {};
design.scale = this.scale;
design.theme = this.theme;
design.menuTheme = this.app.menuTheme;
design.menuMode = this.app.menuMode;
design.topbarTheme = this.app.topbarTheme;
design.ripple = this.app.ripple;
design.isRTL = this.app.isRTL;
design.layoutMode = this.app.layoutMode;
design.inlineMenuPosition = this.app.inlineMenuPosition;
design.inputStyle = this.app.inputStyle;
console.log('AppConfigComponent.saveLayoutAndDesign()', design);
const tmp = JSON.stringify(design);
this.cookieService.set('layoutAndDesign', tmp, 1000);
}
private setCorrectLogo(theme: string) {
const appLogoLink: HTMLImageElement = document.getElementById('app-logo') as HTMLImageElement;
if (theme === 'white' ||
theme === 'yellow' ||
theme === 'amber' ||
theme === 'orange' ||
theme === 'lime' ||
theme === 'light') {
appLogoLink.src = 'assets/layout/images/logo-dark.svg';
} else {
appLogoLink.src = 'assets/layout/images/logo-light.svg';
}
}
}