Add configservice, configurator & floatingconfigurator
This commit is contained in:
38
src/components/floatingconfigurator.ts
Normal file
38
src/components/floatingconfigurator.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Component, computed, inject } from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { AppConfigurator } from '@/src/layout/appconfigurator';
|
||||||
|
import { AppConfigService } from '@/src/service/appconfigservice';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'floating-configurator',
|
||||||
|
imports: [ButtonModule, StyleClassModule, AppConfigurator],
|
||||||
|
template: `
|
||||||
|
<div class="fixed flex gap-4 top-8 right-8">
|
||||||
|
<p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
|
||||||
|
<div class="relative">
|
||||||
|
<p-button
|
||||||
|
icon="pi pi-palette"
|
||||||
|
pStyleClass="@next"
|
||||||
|
enterFromClass="hidden"
|
||||||
|
enterActiveClass="animate-scalein"
|
||||||
|
leaveToClass="hidden"
|
||||||
|
leaveActiveClass="animate-scalein"
|
||||||
|
[hideOnOutsideClick]="true"
|
||||||
|
type="button"
|
||||||
|
rounded
|
||||||
|
/>
|
||||||
|
<app-configurator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class FloatingConfigurator {
|
||||||
|
configService = inject(AppConfigService);
|
||||||
|
|
||||||
|
isDarkTheme = computed(() => this.configService.appState().darkTheme);
|
||||||
|
|
||||||
|
toggleDarkMode() {
|
||||||
|
this.configService.appState.update((state) => ({ ...state, darkTheme: !state.darkTheme }));
|
||||||
|
}
|
||||||
|
}
|
||||||
491
src/layout/appconfigurator.ts
Normal file
491
src/layout/appconfigurator.ts
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
|
||||||
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
|
import { Component, computed, inject, PLATFORM_ID } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { $t, updatePreset, updateSurfacePalette } from '@primeng/themes';
|
||||||
|
import Aura from '@primeng/themes/aura';
|
||||||
|
import Lara from '@primeng/themes/lara';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { PrimeNG } from 'primeng/config';
|
||||||
|
import { InputSwitchModule } from 'primeng/inputswitch';
|
||||||
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
||||||
|
import { SelectButton } from 'primeng/selectbutton';
|
||||||
|
import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
||||||
|
import { AppConfigService } from '@/src/service/appconfigservice';
|
||||||
|
|
||||||
|
const presets = {
|
||||||
|
Aura,
|
||||||
|
Lara
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-configurator',
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<div class="config-panel-content">
|
||||||
|
<div class="config-panel-colors">
|
||||||
|
<span class="config-panel-label">Primary</span>
|
||||||
|
<div>
|
||||||
|
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[title]="primaryColor.name"
|
||||||
|
(click)="updateColors($event, 'primary', primaryColor)"
|
||||||
|
[ngClass]="{ 'active-color': primaryColor.name === selectedPrimaryColor() }"
|
||||||
|
[style]="{
|
||||||
|
'background-color': primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette['500']
|
||||||
|
}"
|
||||||
|
></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-panel-colors">
|
||||||
|
<span class="config-panel-label">Surface</span>
|
||||||
|
<div>
|
||||||
|
@for (surface of surfaces; track surface.name) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[title]="surface.name"
|
||||||
|
(click)="updateColors($event, 'surface', surface)"
|
||||||
|
[ngClass]="{ 'active-color': selectedSurfaceColor() ? selectedSurfaceColor() === surface.name : configService.appState().darkTheme ? surface.name === 'zinc' : surface.name === 'slate' }"
|
||||||
|
[style]="{
|
||||||
|
'background-color': surface.name === 'noir' ? 'var(--text-color)' : surface?.palette['500']
|
||||||
|
}"
|
||||||
|
></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-panel-settings">
|
||||||
|
<span class="config-panel-label">Presets</span>
|
||||||
|
<p-selectbutton [options]="presets" [ngModel]="selectedPreset()" (ngModelChange)="onPresetChange($event)" [allowEmpty]="false" size="small" />
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="config-panel-settings">
|
||||||
|
<span class="config-panel-label">Ripple</span>
|
||||||
|
<p-toggleswitch [(ngModel)]="ripple" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="config-panel-settings items-end">
|
||||||
|
<span class="config-panel-label">RTL</span>
|
||||||
|
<p-toggleswitch [ngModel]="isRTL" (ngModelChange)="onRTLChange($event)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
host: {
|
||||||
|
class: 'config-panel hidden'
|
||||||
|
},
|
||||||
|
imports: [CommonModule, FormsModule, InputSwitchModule, ButtonModule, RadioButtonModule, SelectButton, ToggleSwitchModule]
|
||||||
|
})
|
||||||
|
export class AppConfigurator {
|
||||||
|
get ripple() {
|
||||||
|
return this.config.ripple();
|
||||||
|
}
|
||||||
|
|
||||||
|
set ripple(value: boolean) {
|
||||||
|
this.config.ripple.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRTL() {
|
||||||
|
return this.configService.appState().RTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
config: PrimeNG = inject(PrimeNG);
|
||||||
|
|
||||||
|
configService: AppConfigService = inject(AppConfigService);
|
||||||
|
|
||||||
|
platformId = inject(PLATFORM_ID);
|
||||||
|
|
||||||
|
presets = Object.keys(presets);
|
||||||
|
|
||||||
|
onRTLChange(value: boolean) {
|
||||||
|
this.configService.appState.update((state) => ({ ...state, RTL: value }));
|
||||||
|
if (!(document as any).startViewTransition) {
|
||||||
|
this.toggleRTL(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(document as any).startViewTransition(() => this.toggleRTL(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleRTL(value: boolean) {
|
||||||
|
const htmlElement = document.documentElement;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
htmlElement.setAttribute('dir', 'rtl');
|
||||||
|
} else {
|
||||||
|
htmlElement.removeAttribute('dir');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
this.onPresetChange(this.configService.appState().preset);
|
||||||
|
this.toggleRTL(this.configService.appState().RTL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
surfaces = [
|
||||||
|
{
|
||||||
|
name: 'slate',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f8fafc',
|
||||||
|
100: '#f1f5f9',
|
||||||
|
200: '#e2e8f0',
|
||||||
|
300: '#cbd5e1',
|
||||||
|
400: '#94a3b8',
|
||||||
|
500: '#64748b',
|
||||||
|
600: '#475569',
|
||||||
|
700: '#334155',
|
||||||
|
800: '#1e293b',
|
||||||
|
900: '#0f172a',
|
||||||
|
950: '#020617'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gray',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f9fafb',
|
||||||
|
100: '#f3f4f6',
|
||||||
|
200: '#e5e7eb',
|
||||||
|
300: '#d1d5db',
|
||||||
|
400: '#9ca3af',
|
||||||
|
500: '#6b7280',
|
||||||
|
600: '#4b5563',
|
||||||
|
700: '#374151',
|
||||||
|
800: '#1f2937',
|
||||||
|
900: '#111827',
|
||||||
|
950: '#030712'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zinc',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafafa',
|
||||||
|
100: '#f4f4f5',
|
||||||
|
200: '#e4e4e7',
|
||||||
|
300: '#d4d4d8',
|
||||||
|
400: '#a1a1aa',
|
||||||
|
500: '#71717a',
|
||||||
|
600: '#52525b',
|
||||||
|
700: '#3f3f46',
|
||||||
|
800: '#27272a',
|
||||||
|
900: '#18181b',
|
||||||
|
950: '#09090b'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'neutral',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafafa',
|
||||||
|
100: '#f5f5f5',
|
||||||
|
200: '#e5e5e5',
|
||||||
|
300: '#d4d4d4',
|
||||||
|
400: '#a3a3a3',
|
||||||
|
500: '#737373',
|
||||||
|
600: '#525252',
|
||||||
|
700: '#404040',
|
||||||
|
800: '#262626',
|
||||||
|
900: '#171717',
|
||||||
|
950: '#0a0a0a'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stone',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafaf9',
|
||||||
|
100: '#f5f5f4',
|
||||||
|
200: '#e7e5e4',
|
||||||
|
300: '#d6d3d1',
|
||||||
|
400: '#a8a29e',
|
||||||
|
500: '#78716c',
|
||||||
|
600: '#57534e',
|
||||||
|
700: '#44403c',
|
||||||
|
800: '#292524',
|
||||||
|
900: '#1c1917',
|
||||||
|
950: '#0c0a09'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'soho',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#ececec',
|
||||||
|
100: '#dedfdf',
|
||||||
|
200: '#c4c4c6',
|
||||||
|
300: '#adaeb0',
|
||||||
|
400: '#97979b',
|
||||||
|
500: '#7f8084',
|
||||||
|
600: '#6a6b70',
|
||||||
|
700: '#55565b',
|
||||||
|
800: '#3f4046',
|
||||||
|
900: '#2c2c34',
|
||||||
|
950: '#16161d'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'viva',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f3f3f3',
|
||||||
|
100: '#e7e7e8',
|
||||||
|
200: '#cfd0d0',
|
||||||
|
300: '#b7b8b9',
|
||||||
|
400: '#9fa1a1',
|
||||||
|
500: '#87898a',
|
||||||
|
600: '#6e7173',
|
||||||
|
700: '#565a5b',
|
||||||
|
800: '#3e4244',
|
||||||
|
900: '#262b2c',
|
||||||
|
950: '#0e1315'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ocean',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fbfcfc',
|
||||||
|
100: '#F7F9F8',
|
||||||
|
200: '#EFF3F2',
|
||||||
|
300: '#DADEDD',
|
||||||
|
400: '#B1B7B6',
|
||||||
|
500: '#828787',
|
||||||
|
600: '#5F7274',
|
||||||
|
700: '#415B61',
|
||||||
|
800: '#29444E',
|
||||||
|
900: '#183240',
|
||||||
|
950: '#0c1920'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
selectedPrimaryColor = computed(() => {
|
||||||
|
return this.configService.appState().primary;
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedSurfaceColor = computed(() => this.configService.appState().surface);
|
||||||
|
|
||||||
|
selectedPreset = computed(() => this.configService.appState().preset);
|
||||||
|
|
||||||
|
primaryColors = computed(() => {
|
||||||
|
const presetPalette = presets[this.configService.appState().preset].primitive;
|
||||||
|
const colors = ['emerald', 'green', 'lime', 'orange', 'amber', 'yellow', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'];
|
||||||
|
const palettes = [{ name: 'noir', palette: {} }];
|
||||||
|
|
||||||
|
colors.forEach((color) => {
|
||||||
|
palettes.push({
|
||||||
|
name: color,
|
||||||
|
palette: presetPalette[color]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return palettes;
|
||||||
|
});
|
||||||
|
|
||||||
|
getPresetExt() {
|
||||||
|
const color = this.primaryColors().find((c) => c.name === this.selectedPrimaryColor());
|
||||||
|
|
||||||
|
if (color.name === 'noir') {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: {
|
||||||
|
50: '{surface.50}',
|
||||||
|
100: '{surface.100}',
|
||||||
|
200: '{surface.200}',
|
||||||
|
300: '{surface.300}',
|
||||||
|
400: '{surface.400}',
|
||||||
|
500: '{surface.500}',
|
||||||
|
600: '{surface.600}',
|
||||||
|
700: '{surface.700}',
|
||||||
|
800: '{surface.800}',
|
||||||
|
900: '{surface.900}',
|
||||||
|
950: '{surface.950}'
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.950}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.800}',
|
||||||
|
activeColor: '{primary.700}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.950}',
|
||||||
|
focusBackground: '{primary.700}',
|
||||||
|
color: '#ffffff',
|
||||||
|
focusColor: '#ffffff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.50}',
|
||||||
|
contrastColor: '{primary.950}',
|
||||||
|
hoverColor: '{primary.200}',
|
||||||
|
activeColor: '{primary.300}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.50}',
|
||||||
|
focusBackground: '{primary.300}',
|
||||||
|
color: '{primary.950}',
|
||||||
|
focusColor: '{primary.950}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (this.configService.appState().preset === 'Nora') {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: color.palette,
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.600}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.700}',
|
||||||
|
activeColor: '{primary.800}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.600}',
|
||||||
|
focusBackground: '{primary.700}',
|
||||||
|
color: '#ffffff',
|
||||||
|
focusColor: '#ffffff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.500}',
|
||||||
|
contrastColor: '{surface.900}',
|
||||||
|
hoverColor: '{primary.400}',
|
||||||
|
activeColor: '{primary.300}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.500}',
|
||||||
|
focusBackground: '{primary.400}',
|
||||||
|
color: '{surface.900}',
|
||||||
|
focusColor: '{surface.900}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (this.configService.appState().preset === 'Material') {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: color.palette,
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.500}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.400}',
|
||||||
|
activeColor: '{primary.300}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: 'color-mix(in srgb, {primary.color}, transparent 88%)',
|
||||||
|
focusBackground: 'color-mix(in srgb, {primary.color}, transparent 76%)',
|
||||||
|
color: '{primary.700}',
|
||||||
|
focusColor: '{primary.800}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.400}',
|
||||||
|
contrastColor: '{surface.900}',
|
||||||
|
hoverColor: '{primary.300}',
|
||||||
|
activeColor: '{primary.200}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||||
|
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||||
|
color: 'rgba(255,255,255,.87)',
|
||||||
|
focusColor: 'rgba(255,255,255,.87)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: color.palette,
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.500}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.600}',
|
||||||
|
activeColor: '{primary.700}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.50}',
|
||||||
|
focusBackground: '{primary.100}',
|
||||||
|
color: '{primary.700}',
|
||||||
|
focusColor: '{primary.800}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.400}',
|
||||||
|
contrastColor: '{surface.900}',
|
||||||
|
hoverColor: '{primary.300}',
|
||||||
|
activeColor: '{primary.200}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||||
|
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||||
|
color: 'rgba(255,255,255,.87)',
|
||||||
|
focusColor: 'rgba(255,255,255,.87)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColors(event: any, type: string, color: any) {
|
||||||
|
if (type === 'primary') {
|
||||||
|
this.configService.appState.update((state) => ({ ...state, primary: color.name }));
|
||||||
|
} else if (type === 'surface') {
|
||||||
|
this.configService.appState.update((state) => ({ ...state, surface: color.name }));
|
||||||
|
}
|
||||||
|
this.applyTheme(type, color);
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTheme(type: string, color: any) {
|
||||||
|
if (type === 'primary') {
|
||||||
|
updatePreset(this.getPresetExt());
|
||||||
|
} else if (type === 'surface') {
|
||||||
|
updateSurfacePalette(color.palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPresetChange(event: any) {
|
||||||
|
this.configService.appState.update((state) => ({ ...state, preset: event }));
|
||||||
|
const preset = presets[event];
|
||||||
|
const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette;
|
||||||
|
if (this.configService.appState().preset === 'Material') {
|
||||||
|
document.body.classList.add('material');
|
||||||
|
this.config.ripple.set(true);
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('material');
|
||||||
|
this.config.ripple.set(false);
|
||||||
|
}
|
||||||
|
$t().preset(preset).preset(this.getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/service/appconfigservice.ts
Normal file
135
src/service/appconfigservice.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
|
import { computed, effect, inject, Injectable, PLATFORM_ID, signal } from '@angular/core';
|
||||||
|
|
||||||
|
interface AppState {
|
||||||
|
preset?: string;
|
||||||
|
primary?: string;
|
||||||
|
surface?: string;
|
||||||
|
darkTheme?: boolean;
|
||||||
|
menuActive?: boolean;
|
||||||
|
designerKey?: string;
|
||||||
|
RTL?: boolean;
|
||||||
|
overlayMenuActive?: boolean;
|
||||||
|
menuMode?: string;
|
||||||
|
staticMenuDesktopInactive?: boolean;
|
||||||
|
staticMenuMobileActive?: boolean;
|
||||||
|
profileSidebarVisible?: boolean;
|
||||||
|
configSidebarVisible?: boolean;
|
||||||
|
menuHoverActive?: boolean;
|
||||||
|
activeMenuItem?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AppConfigService {
|
||||||
|
private readonly STORAGE_KEY = 'appConfigState';
|
||||||
|
|
||||||
|
appState = signal<AppState>(null);
|
||||||
|
|
||||||
|
document = inject(DOCUMENT);
|
||||||
|
|
||||||
|
platformId = inject(PLATFORM_ID);
|
||||||
|
|
||||||
|
theme = computed(() => (this.appState()?.darkTheme ? 'dark' : 'light'));
|
||||||
|
|
||||||
|
transitionComplete = signal<boolean>(false);
|
||||||
|
|
||||||
|
private initialized = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect(() => {
|
||||||
|
this.appState.set({ ...this.loadAppState() });
|
||||||
|
const state = this.appState();
|
||||||
|
|
||||||
|
if (!this.initialized || !state) {
|
||||||
|
this.initialized = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveAppState(state);
|
||||||
|
this.handleDarkModeTransition(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDarkModeTransition(state: AppState): void {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
if ((document as any).startViewTransition) {
|
||||||
|
this.startViewTransition(state);
|
||||||
|
} else {
|
||||||
|
this.toggleDarkMode(state);
|
||||||
|
this.onTransitionEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startViewTransition(state: AppState): void {
|
||||||
|
const transition = (document as any).startViewTransition(() => {
|
||||||
|
this.toggleDarkMode(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
transition.ready.then(() => this.onTransitionEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleDarkMode(state: AppState): void {
|
||||||
|
if (state.darkTheme) {
|
||||||
|
this.document.documentElement.classList.add('app-dark');
|
||||||
|
} else {
|
||||||
|
this.document.documentElement.classList.remove('app-dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleMenu () {
|
||||||
|
const {menuMode, overlayMenuActive, staticMenuDesktopInactive, staticMenuMobileActive} = this.appState();
|
||||||
|
|
||||||
|
if (menuMode === 'overlay') {
|
||||||
|
this.appState.update((prev) => ({...prev, overlayMenuActive: !overlayMenuActive}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.innerWidth > 991) {
|
||||||
|
this.appState.update((prev) => ({...prev, staticMenuDesktopInactive: !staticMenuDesktopInactive}));
|
||||||
|
} else {
|
||||||
|
this.appState.update((prev) => ({...prev, staticMenuMobileActive: !staticMenuMobileActive}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
isSidebarActive = computed(() => this.appState().overlayMenuActive || this.appState().staticMenuMobileActive);
|
||||||
|
|
||||||
|
isDarkTheme = computed(() => this.appState().darkTheme);
|
||||||
|
|
||||||
|
getPrimary = computed(() => this.appState().primary);
|
||||||
|
|
||||||
|
getSurface = computed(() => this.appState().surface);
|
||||||
|
|
||||||
|
private onTransitionEnd() {
|
||||||
|
this.transitionComplete.set(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.transitionComplete.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadAppState(): any {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
const storedState = localStorage.getItem(this.STORAGE_KEY);
|
||||||
|
if (storedState) {
|
||||||
|
return JSON.parse(storedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
preset: 'Aura',
|
||||||
|
primary: 'noir',
|
||||||
|
surface: null,
|
||||||
|
darkTheme: false,
|
||||||
|
menuActive: false,
|
||||||
|
designerKey: 'primeng-designer-theme',
|
||||||
|
RTL: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveAppState(state: any): void {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user