Merge remote-tracking branch 'origin/v19' into v19
# Conflicts: # src/layout/appconfigurator.ts # src/layout/applayout.ts
This commit is contained in:
@@ -158,45 +158,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-panel {
|
||||
.config-panel-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary-color);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.config-panel-colors {
|
||||
> div {
|
||||
padding-top: 0.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
outline-color: transparent;
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-offset: 1px;
|
||||
|
||||
&.active-color {
|
||||
outline-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-panel-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LayoutService } from '@/src/service/layout/layout.service';
|
||||
selector: 'floating-configurator',
|
||||
imports: [ButtonModule, StyleClassModule, AppConfigurator],
|
||||
template: `
|
||||
<div class="fixed flex gap-6 top-8 right-8">
|
||||
<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
|
||||
@@ -17,7 +17,7 @@ import { LayoutService } from '@/src/service/layout/layout.service';
|
||||
enterFromClass="hidden"
|
||||
enterActiveClass="animate-scalein"
|
||||
leaveToClass="hidden"
|
||||
leaveActiveClass="animate-scalein"
|
||||
leaveActiveClass="animate-fadeout"
|
||||
[hideOnOutsideClick]="true"
|
||||
type="button"
|
||||
rounded
|
||||
|
||||
@@ -4,7 +4,6 @@ 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 { SelectButtonModule } from 'primeng/selectbutton';
|
||||
import { LayoutService } from '@/src/service/layout/layout.service';
|
||||
@@ -17,7 +16,7 @@ const presets = {
|
||||
@Component({
|
||||
selector: 'app-configurator',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, ButtonModule, SelectButtonModule],
|
||||
imports: [CommonModule, FormsModule, SelectButtonModule],
|
||||
template: `
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
@@ -56,16 +55,16 @@ const presets = {
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-sm text-muted-color font-semibold">Presets</span>
|
||||
<p-selectbutton [options]="presets" [ngModel]="selectedPreset()" (ngModelChange)="onPresetChange($event)" [allowEmpty]="false" />
|
||||
<p-selectbutton [options]="presets" [ngModel]="selectedPreset()" (ngModelChange)="onPresetChange($event)" [allowEmpty]="false" size="small" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-sm text-muted-color font-semibold">Menu Mode</span>
|
||||
<p-selectbutton [ngModel]="menuMode()" (ngModelChange)="onMenuModeChange($event)" [options]="menuModeOptions" [allowEmpty]="false" />
|
||||
<p-selectbutton [ngModel]="menuMode()" (ngModelChange)="onMenuModeChange($event)" [options]="menuModeOptions" [allowEmpty]="false" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
host: {
|
||||
class: 'hidden absolute top-[3.25rem] right-0 w-64 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]',
|
||||
class: 'hidden absolute top-[3.25rem] right-0 w-64 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]'
|
||||
}
|
||||
})
|
||||
export class AppConfigurator {
|
||||
|
||||
@@ -4,21 +4,15 @@ import { CommonModule } from '@angular/common';
|
||||
import { AppTopBar } from '@/src/layout/apptopbar';
|
||||
import { AppSidebar } from '@/src/layout/appsidebar';
|
||||
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
||||
import { AppConfigurator } from '@/src/layout/appconfigurator';
|
||||
import { AppFooter } from '@/src/layout/appfooter';
|
||||
import { filter, Subscription } from 'rxjs';
|
||||
import { LayoutService } from '@/src/service/layout/layout.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-layout',
|
||||
standalone:true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
ToastModule,
|
||||
AppTopBar,
|
||||
AppSidebar,
|
||||
RouterModule,
|
||||
AppFooter,
|
||||
],
|
||||
standalone: true,
|
||||
imports: [CommonModule, ToastModule, AppTopBar, AppSidebar, RouterModule, AppFooter, AppConfigurator],
|
||||
template: `<div class="layout-wrapper" [ngClass]="containerClass">
|
||||
<app-topbar></app-topbar>
|
||||
<div class="layout-sidebar">
|
||||
@@ -30,9 +24,9 @@ import { LayoutService } from '@/src/service/layout/layout.service';
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
<app-configurator></app-configurator>
|
||||
<div class="layout-mask animate-fadein"></div>
|
||||
</div>
|
||||
`,
|
||||
</div> `
|
||||
})
|
||||
export class AppLayout {
|
||||
overlayMenuOpenSubscription: Subscription;
|
||||
@@ -45,23 +39,28 @@ export class AppLayout {
|
||||
|
||||
@ViewChild(AppTopBar) appTopBar!: AppTopBar;
|
||||
|
||||
constructor(public layoutService: LayoutService, public renderer: Renderer2, public router: Router) {
|
||||
constructor(
|
||||
public layoutService: LayoutService,
|
||||
public renderer: Renderer2,
|
||||
public router: Router
|
||||
) {
|
||||
this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => {
|
||||
if (!this.menuOutsideClickListener) {
|
||||
this.menuOutsideClickListener = this.renderer.listen('document', 'click', event => {
|
||||
const isOutsideClicked = !(this.appSidebar.el.nativeElement.isSameNode(event.target) || this.appSidebar.el.nativeElement.contains(event.target)
|
||||
|| this.appTopBar.menuButton.nativeElement.isSameNode(event.target) || this.appTopBar.menuButton.nativeElement.contains(event.target));
|
||||
|
||||
if (isOutsideClicked) {
|
||||
this.menuOutsideClickListener = this.renderer.listen('document', 'click', (event) => {
|
||||
if (this.isOutsideClicked(event)) {
|
||||
this.hideMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.profileMenuOutsideClickListener) {
|
||||
this.profileMenuOutsideClickListener = this.renderer.listen('document', 'click', event => {
|
||||
const isOutsideClicked = !(this.appTopBar.menu.nativeElement.isSameNode(event.target) || this.appTopBar.menu.nativeElement.contains(event.target)
|
||||
|| this.appTopBar.topbarMenuButton.nativeElement.isSameNode(event.target) || this.appTopBar.topbarMenuButton.nativeElement.contains(event.target));
|
||||
this.profileMenuOutsideClickListener = this.renderer.listen('document', 'click', (event) => {
|
||||
const isOutsideClicked = !(
|
||||
this.appTopBar.menu.nativeElement.isSameNode(event.target) ||
|
||||
this.appTopBar.menu.nativeElement.contains(event.target) ||
|
||||
this.appTopBar.topbarMenuButton.nativeElement.isSameNode(event.target) ||
|
||||
this.appTopBar.topbarMenuButton.nativeElement.contains(event.target)
|
||||
);
|
||||
|
||||
if (isOutsideClicked) {
|
||||
}
|
||||
@@ -73,14 +72,20 @@ export class AppLayout {
|
||||
}
|
||||
});
|
||||
|
||||
this.router.events.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(() => {
|
||||
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => {
|
||||
this.hideMenu();
|
||||
});
|
||||
}
|
||||
|
||||
isOutsideClicked(event) {
|
||||
const sidebarEl = document.querySelector('.layout-sidebar');
|
||||
const topbarEl = document.querySelector('.layout-menu-button');
|
||||
|
||||
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
|
||||
}
|
||||
|
||||
hideMenu() {
|
||||
this.layoutService.layoutState.update((prev) => ({...prev, overlayMenuActive:false, staticMenuMobileActive: false, menuHoverActive: false}))
|
||||
this.layoutService.layoutState.update((prev) => ({ ...prev, overlayMenuActive: false, staticMenuMobileActive: false, menuHoverActive: false }));
|
||||
if (this.menuOutsideClickListener) {
|
||||
this.menuOutsideClickListener();
|
||||
this.menuOutsideClickListener = null;
|
||||
@@ -91,8 +96,7 @@ export class AppLayout {
|
||||
blockBodyScroll(): void {
|
||||
if (document.body.classList) {
|
||||
document.body.classList.add('blocked-scroll');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
document.body.className += ' blocked-scroll';
|
||||
}
|
||||
}
|
||||
@@ -100,10 +104,8 @@ export class AppLayout {
|
||||
unblockBodyScroll(): void {
|
||||
if (document.body.classList) {
|
||||
document.body.classList.remove('blocked-scroll');
|
||||
}
|
||||
else {
|
||||
document.body.className = document.body.className.replace(new RegExp('(^|\\b)' +
|
||||
'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||
} else {
|
||||
document.body.className = document.body.className.replace(new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ export class AppLayout {
|
||||
'layout-static-inactive': this.layoutService.layoutState().staticMenuDesktopInactive && this.layoutService.layoutConfig().menuMode === 'static',
|
||||
'layout-overlay-active': this.layoutService.layoutState().overlayMenuActive,
|
||||
'layout-mobile-active': this.layoutService.layoutState().staticMenuMobileActive
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable, effect, signal, computed } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export interface layoutConfig {
|
||||
preset?: string,
|
||||
preset?: string;
|
||||
primary?: string;
|
||||
surface?: string;
|
||||
darkTheme?: boolean;
|
||||
@@ -23,7 +23,7 @@ interface MenuChangeEvent {
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LayoutService {
|
||||
_config: layoutConfig = {
|
||||
@@ -39,12 +39,12 @@ export class LayoutService {
|
||||
overlayMenuActive: false,
|
||||
configSidebarVisible: false,
|
||||
staticMenuMobileActive: false,
|
||||
menuHoverActive: false,
|
||||
menuHoverActive: false
|
||||
};
|
||||
|
||||
layoutConfig = signal<layoutConfig>(this._config);
|
||||
|
||||
layoutState = signal<LayoutState>(this._state)
|
||||
layoutState = signal<LayoutState>(this._state);
|
||||
|
||||
private configUpdate = new Subject<layoutConfig>();
|
||||
|
||||
@@ -62,7 +62,7 @@ export class LayoutService {
|
||||
|
||||
overlayOpen$ = this.overlayOpen.asObservable();
|
||||
|
||||
theme = computed(() => this.layoutConfig()?.darkTheme ? 'light' : 'dark');
|
||||
theme = computed(() => (this.layoutConfig()?.darkTheme ? 'light' : 'dark'));
|
||||
|
||||
isSidebarActive = computed(() => this.layoutState().overlayMenuActive || this.layoutState().staticMenuMobileActive);
|
||||
|
||||
@@ -72,7 +72,7 @@ export class LayoutService {
|
||||
|
||||
getSurface = computed(() => this.layoutConfig().surface);
|
||||
|
||||
isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay')
|
||||
isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay');
|
||||
|
||||
transitionComplete = signal<boolean>(false);
|
||||
|
||||
@@ -81,7 +81,7 @@ export class LayoutService {
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const config = this.layoutConfig();
|
||||
if(config) {
|
||||
if (config) {
|
||||
this.onConfigUpdate();
|
||||
}
|
||||
});
|
||||
@@ -95,7 +95,7 @@ export class LayoutService {
|
||||
}
|
||||
|
||||
this.handleDarkModeTransition(config);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private handleDarkModeTransition(config: layoutConfig): void {
|
||||
@@ -112,11 +112,15 @@ export class LayoutService {
|
||||
this.toggleDarkMode(config);
|
||||
});
|
||||
|
||||
transition.ready.then(() => this.onTransitionEnd());
|
||||
transition.ready
|
||||
.then(() => {
|
||||
this.onTransitionEnd();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
toggleDarkMode(config?: layoutConfig): void {
|
||||
const _config = config || this.layoutConfig()
|
||||
const _config = config || this.layoutConfig();
|
||||
if (_config.darkTheme) {
|
||||
document.documentElement.classList.add('app-dark');
|
||||
} else {
|
||||
@@ -131,10 +135,9 @@ export class LayoutService {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onMenuToggle() {
|
||||
if (this.isOverlay()) {
|
||||
this.layoutState.update((prev) => ({...prev, overlayMenuActive: !this.layoutState().overlayMenuActive}));
|
||||
this.layoutState.update((prev) => ({ ...prev, overlayMenuActive: !this.layoutState().overlayMenuActive }));
|
||||
|
||||
if (this.layoutState().overlayMenuActive) {
|
||||
this.overlayOpen.next(null);
|
||||
@@ -142,9 +145,9 @@ export class LayoutService {
|
||||
}
|
||||
|
||||
if (this.isDesktop()) {
|
||||
this.layoutState.update((prev) => ({...prev, staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive}));
|
||||
this.layoutState.update((prev) => ({ ...prev, staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive }));
|
||||
} else {
|
||||
this.layoutState.update((prev) => ({...prev, staticMenuMobileActive: !this.layoutState().staticMenuMobileActive}));
|
||||
this.layoutState.update((prev) => ({ ...prev, staticMenuMobileActive: !this.layoutState().staticMenuMobileActive }));
|
||||
|
||||
if (this.layoutState().staticMenuMobileActive) {
|
||||
this.overlayOpen.next(null);
|
||||
|
||||
@@ -100,7 +100,6 @@ import { OverlayBadgeModule } from 'primeng/overlaybadge';
|
||||
etiam sit amet nisl purus. Cursus sit amet dictum sit amet. Tristique senectus et netus et malesuada fames ac turpis egestas. Et tortor consequat id porta nibh venenatis cras sed. Diam maecenas ultricies mi eget mauris.
|
||||
Eget egestas purus viverra accumsan in nisl nisi. Suscipit adipiscing bibendum est ultricies integer. Mattis aliquam faucibus purus in massa tempor nec.
|
||||
</p>
|
||||
<p-scrolltop target="parent" [threshold]="100" icon="pi pi-arrow-up"></p-scrolltop>
|
||||
</p-scrollpanel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user