Files
psp_panel/src/app/layout/service/layout.service.ts
T

259 lines
6.4 KiB
TypeScript
Raw Normal View History

2026-04-06 13:31:30 +03:30
import { computed, effect, Injectable, signal, TemplateRef } from '@angular/core';
import { MenuItem } from 'primeng/api';
2026-04-06 13:31:30 +03:30
import { BehaviorSubject, Subject } from 'rxjs';
export interface layoutConfig {
2025-12-04 21:07:18 +03:30
preset?: string;
primary?: string;
surface?: string | undefined | null;
darkTheme?: boolean;
menuMode?: string;
}
2026-03-18 13:35:57 +03:30
interface IPanelInfo {
title: string;
}
interface LayoutState {
2025-12-04 21:07:18 +03:30
staticMenuDesktopInactive?: boolean;
overlayMenuActive?: boolean;
configSidebarVisible?: boolean;
staticMenuMobileActive?: boolean;
menuHoverActive?: boolean;
isFixedContentSize?: boolean;
isFullPage?: boolean;
}
2025-01-07 10:25:40 +03:00
interface MenuChangeEvent {
2025-12-04 21:07:18 +03:30
key: string;
routeEvent?: boolean;
2025-01-07 10:25:40 +03:00
}
@Injectable({
2025-12-04 21:07:18 +03:30
providedIn: 'root',
})
export class LayoutService {
2025-12-04 21:07:18 +03:30
_config: layoutConfig = {
preset: 'Aura',
2026-03-18 13:35:57 +03:30
primary: 'surface',
2025-12-04 21:07:18 +03:30
surface: null,
darkTheme: localStorage.getItem('isDarkTheme') === 'true',
menuMode: 'static',
};
2025-12-04 21:07:18 +03:30
_state: LayoutState = {
staticMenuDesktopInactive: false,
overlayMenuActive: false,
configSidebarVisible: false,
staticMenuMobileActive: false,
menuHoverActive: false,
isFixedContentSize: true,
isFullPage: false,
2025-12-04 21:07:18 +03:30
};
2025-12-04 21:07:18 +03:30
layoutConfig = signal<layoutConfig>(this._config);
2025-12-04 21:07:18 +03:30
layoutState = signal<LayoutState>(this._state);
2025-12-04 21:07:18 +03:30
private configUpdate = new Subject<layoutConfig>();
2025-12-04 21:07:18 +03:30
private overlayOpen = new Subject<any>();
2025-12-04 21:07:18 +03:30
private menuSource = new Subject<MenuChangeEvent>();
2025-01-07 10:25:40 +03:00
2025-12-04 21:07:18 +03:30
private resetSource = new Subject();
2025-01-07 10:25:40 +03:00
2026-03-16 00:35:34 +03:30
public menuItems = signal<MenuItem[]>([]);
2026-03-18 13:35:57 +03:30
public panelInfo = signal<IPanelInfo>({
title: 'پنل مدیریت صورت‌حساب‌های مالیاتی',
2026-03-18 13:35:57 +03:30
});
2025-12-04 21:07:18 +03:30
menuSource$ = this.menuSource.asObservable();
2025-01-07 10:25:40 +03:00
2025-12-04 21:07:18 +03:30
resetSource$ = this.resetSource.asObservable();
2025-01-07 10:25:40 +03:00
2025-12-04 21:07:18 +03:30
configUpdate$ = this.configUpdate.asObservable();
2025-12-04 21:07:18 +03:30
overlayOpen$ = this.overlayOpen.asObservable();
2025-12-04 21:07:18 +03:30
theme = computed(() => (this.layoutConfig()?.darkTheme ? 'light' : 'dark'));
2025-12-04 21:07:18 +03:30
isSidebarActive = computed(
() => this.layoutState().overlayMenuActive || this.layoutState().staticMenuMobileActive,
);
2025-12-04 21:07:18 +03:30
isDarkTheme = computed(() => this.layoutConfig().darkTheme);
2025-12-04 21:07:18 +03:30
getPrimary = computed(() => this.layoutConfig().primary);
2025-12-04 21:07:18 +03:30
getSurface = computed(() => this.layoutConfig().surface);
2025-12-04 21:07:18 +03:30
isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay');
isFixedContentSize = computed(() => this.layoutState().isFixedContentSize);
isFullPage = computed(() => this.layoutState().isFullPage);
2025-12-04 21:07:18 +03:30
transitionComplete = signal<boolean>(false);
2025-12-04 21:07:18 +03:30
private initialized = false;
2025-12-04 21:07:18 +03:30
constructor() {
this.handleDarkModeTransition(this._config);
2025-12-04 21:07:18 +03:30
effect(() => {
const config = this.layoutConfig();
if (config) {
this.onConfigUpdate();
}
});
2025-12-04 21:07:18 +03:30
effect(() => {
const config = this.layoutConfig();
2025-12-04 21:07:18 +03:30
if (!this.initialized || !config) {
this.initialized = true;
return;
}
2025-12-04 21:07:18 +03:30
this.handleDarkModeTransition(config);
});
}
2025-12-04 21:07:18 +03:30
private handleDarkModeTransition(config: layoutConfig): void {
if ((document as any).startViewTransition) {
this.startViewTransition(config);
} else {
this.toggleDarkMode(config);
this.onTransitionEnd();
}
2025-12-04 21:07:18 +03:30
}
private startViewTransition(config: layoutConfig): void {
const transition = (document as any).startViewTransition(() => {
this.toggleDarkMode(config);
});
transition.ready
.then(() => {
this.onTransitionEnd();
})
.catch(() => {});
}
changeIsFixedContentSize(status: boolean) {
this.layoutState.update((prev) => ({
...prev,
isFixedContentSize: status,
}));
}
changeIsFullPage(status: boolean) {
this.layoutState.update((prev) => ({
...prev,
isFullPage: status,
}));
}
2025-12-04 21:07:18 +03:30
toggleDarkMode(config?: layoutConfig): void {
const _config = config || this.layoutConfig();
localStorage.setItem('isDarkTheme', JSON.stringify(_config.darkTheme));
2025-12-04 21:07:18 +03:30
if (_config.darkTheme) {
document.documentElement.classList.add('app-dark');
} else {
document.documentElement.classList.remove('app-dark');
}
2025-12-04 21:07:18 +03:30
}
private onTransitionEnd() {
this.transitionComplete.set(true);
setTimeout(() => {
this.transitionComplete.set(false);
});
}
onMenuToggle() {
if (this.isOverlay()) {
this.layoutState.update((prev) => ({
...prev,
overlayMenuActive: !this.layoutState().overlayMenuActive,
}));
if (this.layoutState().overlayMenuActive) {
this.overlayOpen.next(null);
}
}
2025-12-04 21:07:18 +03:30
if (this.isDesktop()) {
this.layoutState.update((prev) => ({
...prev,
staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive,
}));
} else {
this.layoutState.update((prev) => ({
...prev,
staticMenuMobileActive: !this.layoutState().staticMenuMobileActive,
}));
if (this.layoutState().staticMenuMobileActive) {
this.overlayOpen.next(null);
}
}
2025-12-04 21:07:18 +03:30
}
2025-12-04 21:07:18 +03:30
isDesktop() {
return window.innerWidth > 991;
}
2025-12-04 21:07:18 +03:30
isMobile() {
return !this.isDesktop();
}
2025-12-04 21:07:18 +03:30
onConfigUpdate() {
this._config = { ...this.layoutConfig() };
this.configUpdate.next(this.layoutConfig());
}
2025-01-07 10:25:40 +03:00
2025-12-04 21:07:18 +03:30
onMenuStateChange(event: MenuChangeEvent) {
this.menuSource.next(event);
}
2025-01-07 10:25:40 +03:00
2025-12-04 21:07:18 +03:30
reset() {
this.resetSource.next(true);
}
setMenuItems(items: MenuItem[]) {
this.menuItems.set(items);
}
2026-03-18 13:35:57 +03:30
setPanelInfo(info: IPanelInfo) {
this.panelInfo.set(info);
}
2026-04-06 13:31:30 +03:30
private headerSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
headerSlot$ = this.headerSlot.asObservable();
private topbarStartSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
topbarStartSlot$ = this.topbarStartSlot.asObservable();
private topbarCenterSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
topbarCenterSlot$ = this.topbarCenterSlot.asObservable();
private topbarEndSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
topbarEndSlot$ = this.topbarEndSlot.asObservable();
2026-04-06 13:31:30 +03:30
setHeaderSlot(tpl: TemplateRef<any> | null) {
// Backward-compatible alias for topbar end slot.
this.setTopbarEndSlot(tpl);
2026-04-06 13:31:30 +03:30
this.headerSlot.next(tpl);
}
setTopbarStartSlot(tpl: TemplateRef<any> | null) {
this.topbarStartSlot.next(tpl);
}
setTopbarCenterSlot(tpl: TemplateRef<any> | null) {
this.topbarCenterSlot.next(tpl);
}
setTopbarEndSlot(tpl: TemplateRef<any> | null) {
this.topbarEndSlot.next(tpl);
}
}