Save and restore user settings (colors, layout)

Forum rules
Please note that response time for technical support is within 3-5 business days.
Post Reply
MickeyMiner
Posts: 11
Joined: 04 Jul 2017, 13:05

14 Apr 2022, 10:40

Hi,

I would like to give the user the opportunity to customize his workspace and save it for everyday use.

I have saved all variables used in AppConfigComponent using the function below.
When I try to load it again, and set the variables of the component, I don't really succeed in restoring his design. There is always some color missing or elements are simply wrong.
I have noticed, the order in which I set the variables and call their respective functions influences the final result.

What is the correct order of the variables and called functions?

I think a straight answer on this could be useful also for other users.

Thanx,

Michael

Code: Select all

  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);
    this.userService.layoutAndDesign = design;
    this.userService.saveProfile();
  }

Code: Select all

  parseLayoutAndDesign(design) {
    console.log('AppConfigComponent.parseLayoutAndDesign()', design);
      this.scale = design.scale;
      this.applyScale();
      this.app.menuMode = design.menuMode;
      this.appMain.onRippleChange({checked: design.ripple});
      this.appMain.onRTLChange({checked: design.isRTL});
      this.app.inlineMenuPosition = design.inlineMenuPosition;
      this.app.inputStyle = design.inputStyle;
      this.onInputStyleClick();
      if (design.layoutMode!=='dark'){
        this.onLayoutModeChange(null, design.layoutMode);
      }
      this.menuTheme = design.menuTheme;
      const mTheme = this.menuThemes.find(t => t.name === this.menuTheme);
      this.changeMenuTheme(mTheme);
      const tTheme = this.topbarThemes.find(t => t.name === design.topbarTheme);
      this.app.topbarTheme = tTheme.name;
      this.topbarTheme = design.topbarTheme;
      this.changeTopbarTheme(tTheme);
      const theme = this.themes.find(t => t.name === design.theme);
      this.changeTheme(theme ? theme.name : 'indigo');
  }

cetincakiroglu
Posts: 130
Joined: 17 Dec 2021, 09:33

18 Apr 2022, 09:25

Hi,

Could you please share code examples of AppConfig and userService?

Regards.

MickeyMiner
Posts: 11
Joined: 04 Jul 2017, 13:05

27 Apr 2022, 17:26

Hi,

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:

Code: Select all

npm i ngx-cookie-service
Then replace original app.config.component.ts 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 loadLayoutAndDesign().

Thank you for your support,

Michael

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';
    }
  }

}

cetincakiroglu
Posts: 130
Joined: 17 Dec 2021, 09:33

09 May 2022, 13:11

Hi,

This happens because you trying to change an expression after it was set at the top-level (app.component.ts). You need to separate your logic to prevent it. loadLayoutAndDesign() function should be moved to the top level, into app.component.ts. For more info regarding the error please visit https://angular.io/errors/NG0100

Regards

Post Reply

Return to “Ultima - PrimeNG”

  • Information
  • Who is online

    Users browsing this forum: No registered users and 8 guests