Merge pull request #110 from primefaces/nextgen

Nextgen
This commit is contained in:
Çetin
2026-01-30 17:30:52 +03:00
committed by GitHub
114 changed files with 4932 additions and 4333 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "src/assets"]
path = src/assets
url = https://github.com/cetincakiroglu/sakai-assets

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2018-2022 PrimeTek Copyright (c) 2018-2026 PrimeTek
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
# Sakai19 # Sakai19
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.5. This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.
## Development server ## Development server

View File

@@ -20,9 +20,6 @@
"outputPath": "dist/sakai-ng", "outputPath": "dist/sakai-ng",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": [
@@ -32,12 +29,25 @@
} }
], ],
"styles": [ "styles": [
"src/assets/styles.scss" "src/assets/styles.scss",
"src/assets/tailwind.css"
], ],
"scripts": [] "scripts": []
}, },
"configurations": { "configurations": {
"production": { "production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "1mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all" "outputHashing": "all"
}, },
"development": { "development": {
@@ -66,17 +76,11 @@
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": [
{ "src/favicon.ico",
"glob": "**/*", "src/assets"
"input": "public"
}
], ],
"styles": [ "styles": [
"src/assets/styles.scss" "src/assets/styles.scss"
@@ -88,6 +92,6 @@
} }
}, },
"cli": { "cli": {
"analytics": false "analytics": false
} }
} }

7306
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "sakai-ng", "name": "sakai-ng",
"version": "20.0.0", "version": "21.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
@@ -11,45 +11,44 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^20", "@angular/common": "^21",
"@angular/common": "^20", "@angular/compiler": "^21",
"@angular/compiler": "^20", "@angular/core": "^21",
"@angular/core": "^20", "@angular/forms": "^21",
"@angular/forms": "^20", "@angular/platform-browser": "^21",
"@angular/platform-browser": "^20", "@angular/platform-browser-dynamic": "^21",
"@angular/platform-browser-dynamic": "^20", "@angular/router": "^21",
"@angular/router": "^20", "@primeuix/themes": "^2.0.0",
"@primeuix/themes": "^1.2.1",
"@tailwindcss/postcss": "^4.1.11", "@tailwindcss/postcss": "^4.1.11",
"chart.js": "4.4.2", "chart.js": "4.4.2",
"primeclt": "^0.1.5", "primeclt": "^0.1.5",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primeng": "^20", "primeng": "^21.0.2",
"rxjs": "~7.8.2", "quill": "^2.0.3",
"rxjs": "~7.8.0",
"tailwindcss-primeui": "^0.6.1", "tailwindcss-primeui": "^0.6.1",
"tslib": "^2.8.1", "tslib": "^2.8.1"
"zone.js": "~0.15.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^20", "@angular-devkit/build-angular": "^21",
"@angular/cli": "^20", "@angular/cli": "^21",
"@angular/compiler-cli": "^20", "@angular/compiler-cli": "^21",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.30.1", "eslint": "^9.14.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^5.5.1", "eslint-plugin-prettier": "^4.2.1",
"jasmine-core": "~5.8.0", "jasmine-core": "~5.4.0",
"karma": "~6.4.4", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.1", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine-html-reporter": "~2.1.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"prettier": "^3.6.2", "prettier": "^3.0.0",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"typescript": "~5.8.3" "typescript": "~5.9.3"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="300px" height="200px" viewBox="0 0 300 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Artboard</title>
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="#F8F9FA" x="0" y="0" width="300" height="200"></rect>
<g id="image" transform="translate(110.000000, 70.000000)" fill="#BABABC" fill-rule="nonzero">
<path d="M75,0 L5,0 C2.23857625,0 0,2.23857625 0,5 L0,55 C0,57.7614237 2.23857625,60 5,60 L75,60 C77.7614237,60 80,57.7614237 80,55 L80,5 C80,2.23857625 77.7614237,0 75,0 Z M20,10 C25.5228475,10 30,14.4771525 30,20 C30,25.5228475 25.5228475,30 20,30 C14.4771525,30 10,25.5228475 10,20 C10,14.4771525 14.4771525,10 20,10 Z M70,40 L70,50 L10,50 L10,40 L18.55,35.7 C19.4648753,35.2524957 20.5351247,35.2524957 21.45,35.7 L30,40 L53.65,21.1 C54.4866298,20.4991452 55.6133702,20.4991452 56.45,21.1 L70,30 L70,40 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,6 +1,5 @@
import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideHttpClient, withFetch } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core'; import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router'; import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router';
import Aura from '@primeuix/themes/aura'; import Aura from '@primeuix/themes/aura';
import { providePrimeNG } from 'primeng/config'; import { providePrimeNG } from 'primeng/config';
@@ -10,7 +9,7 @@ export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideRouter(appRoutes, withInMemoryScrolling({ anchorScrolling: 'enabled', scrollPositionRestoration: 'enabled' }), withEnabledBlockingInitialNavigation()), provideRouter(appRoutes, withInMemoryScrolling({ anchorScrolling: 'enabled', scrollPositionRestoration: 'enabled' }), withEnabledBlockingInitialNavigation()),
provideHttpClient(withFetch()), provideHttpClient(withFetch()),
provideAnimationsAsync(), provideZonelessChangeDetection(),
providePrimeNG({ theme: { preset: Aura, options: { darkModeSelector: '.app-dark' } } }) providePrimeNG({ theme: { preset: Aura, options: { darkModeSelector: '.app-dark' } } })
] ]
}; };

View File

@@ -8,7 +8,7 @@ import Lara from '@primeuix/themes/lara';
import Nora from '@primeuix/themes/nora'; import Nora from '@primeuix/themes/nora';
import { PrimeNG } from 'primeng/config'; import { PrimeNG } from 'primeng/config';
import { SelectButtonModule } from 'primeng/selectbutton'; import { SelectButtonModule } from 'primeng/selectbutton';
import { LayoutService } from '../service/layout.service'; import { LayoutService } from '@/app/layout/service/layout.service';
const presets = { const presets = {
Aura, Aura,

View File

@@ -2,7 +2,7 @@ import {Component, computed, inject, input} from '@angular/core';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { StyleClassModule } from 'primeng/styleclass'; import { StyleClassModule } from 'primeng/styleclass';
import { AppConfigurator } from './app.configurator'; import { AppConfigurator } from './app.configurator';
import { LayoutService } from '../service/layout.service'; import { LayoutService } from '@/app/layout/service/layout.service';
import {CommonModule} from "@angular/common"; import {CommonModule} from "@angular/common";
@Component({ @Component({

View File

@@ -1,17 +1,16 @@
import { Component, Renderer2, ViewChild } from '@angular/core'; import { Component, computed, effect, inject } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { filter, Subscription } from 'rxjs';
import { AppTopbar } from './app.topbar'; import { AppTopbar } from './app.topbar';
import { AppSidebar } from './app.sidebar'; import { AppSidebar } from './app.sidebar';
import { AppFooter } from './app.footer'; import { AppFooter } from './app.footer';
import { LayoutService } from '../service/layout.service'; import { LayoutService } from '@/app/layout/service/layout.service';
@Component({ @Component({
selector: 'app-layout', selector: 'app-layout',
standalone: true, standalone: true,
imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter], imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter],
template: `<div class="layout-wrapper" [ngClass]="containerClass"> template: `<div class="layout-wrapper" [ngClass]="containerClass()">
<app-topbar></app-topbar> <app-topbar></app-topbar>
<app-sidebar></app-sidebar> <app-sidebar></app-sidebar>
<div class="layout-main-container"> <div class="layout-main-container">
@@ -20,92 +19,32 @@ import { LayoutService } from '../service/layout.service';
</div> </div>
<app-footer></app-footer> <app-footer></app-footer>
</div> </div>
<div class="layout-mask animate-fadein"></div> <div class="layout-mask"></div>
</div> ` </div> `
}) })
export class AppLayout { export class AppLayout {
overlayMenuOpenSubscription: Subscription; layoutService = inject(LayoutService);
menuOutsideClickListener: any; constructor() {
effect(() => {
@ViewChild(AppSidebar) appSidebar!: AppSidebar; const state = this.layoutService.layoutState();
if (state.mobileMenuActive) {
@ViewChild(AppTopbar) appTopBar!: AppTopbar; document.body.classList.add('blocked-scroll');
} else {
constructor( document.body.classList.remove('blocked-scroll');
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) => {
if (this.isOutsideClicked(event)) {
this.hideMenu();
}
});
}
if (this.layoutService.layoutState().staticMenuMobileActive) {
this.blockBodyScroll();
} }
}); });
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => {
this.hideMenu();
});
} }
isOutsideClicked(event: MouseEvent) { containerClass = computed(() => {
const sidebarEl = document.querySelector('.layout-sidebar'); const config = this.layoutService.layoutConfig();
const topbarEl = document.querySelector('.layout-menu-button'); const state = this.layoutService.layoutState();
const eventTarget = event.target as Node;
return !(sidebarEl?.isSameNode(eventTarget) || sidebarEl?.contains(eventTarget) || topbarEl?.isSameNode(eventTarget) || topbarEl?.contains(eventTarget));
}
hideMenu() {
this.layoutService.layoutState.update((prev) => ({ ...prev, overlayMenuActive: false, staticMenuMobileActive: false, menuHoverActive: false }));
if (this.menuOutsideClickListener) {
this.menuOutsideClickListener();
this.menuOutsideClickListener = null;
}
this.unblockBodyScroll();
}
blockBodyScroll(): void {
if (document.body.classList) {
document.body.classList.add('blocked-scroll');
} else {
document.body.className += ' blocked-scroll';
}
}
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'), ' ');
}
}
get containerClass() {
return { return {
'layout-overlay': this.layoutService.layoutConfig().menuMode === 'overlay', 'layout-overlay': config.menuMode === 'overlay',
'layout-static': this.layoutService.layoutConfig().menuMode === 'static', 'layout-static': config.menuMode === 'static',
'layout-static-inactive': this.layoutService.layoutState().staticMenuDesktopInactive && this.layoutService.layoutConfig().menuMode === 'static', 'layout-static-inactive': state.staticMenuDesktopInactive && config.menuMode === 'static',
'layout-overlay-active': this.layoutService.layoutState().overlayMenuActive, 'layout-overlay-active': state.overlayMenuActive,
'layout-mobile-active': this.layoutService.layoutState().staticMenuMobileActive 'layout-mobile-active': state.mobileMenuActive
}; };
} })
ngOnDestroy() {
if (this.overlayMenuOpenSubscription) {
this.overlayMenuOpenSubscription.unsubscribe();
}
if (this.menuOutsideClickListener) {
this.menuOutsideClickListener();
}
}
} }

View File

@@ -9,11 +9,14 @@ import { AppMenuitem } from './app.menuitem';
standalone: true, standalone: true,
imports: [CommonModule, AppMenuitem, RouterModule], imports: [CommonModule, AppMenuitem, RouterModule],
template: `<ul class="layout-menu"> template: `<ul class="layout-menu">
<ng-container *ngFor="let item of model; let i = index"> @for (item of model; track item.label) {
<li app-menuitem *ngIf="!item.separator" [item]="item" [index]="i" [root]="true"></li> @if (!item.separator) {
<li *ngIf="item.separator" class="menu-separator"></li> <li app-menuitem [item]="item" [root]="true"></li>
</ng-container> } @else {
</ul> ` <li class="menu-separator"></li>
}
}
</ul> `,
}) })
export class AppMenu { export class AppMenu {
model: MenuItem[] = []; model: MenuItem[] = [];
@@ -47,7 +50,7 @@ export class AppMenu {
{ {
label: 'Pages', label: 'Pages',
icon: 'pi pi-fw pi-briefcase', icon: 'pi pi-fw pi-briefcase',
routerLink: ['/pages'], path: '/pages',
items: [ items: [
{ {
label: 'Landing', label: 'Landing',
@@ -57,6 +60,7 @@ export class AppMenu {
{ {
label: 'Auth', label: 'Auth',
icon: 'pi pi-fw pi-user', icon: 'pi pi-fw pi-user',
path: '/auth',
items: [ items: [
{ {
label: 'Login', label: 'Login',
@@ -94,14 +98,17 @@ export class AppMenu {
}, },
{ {
label: 'Hierarchy', label: 'Hierarchy',
path: '/hierarchy',
items: [ items: [
{ {
label: 'Submenu 1', label: 'Submenu 1',
icon: 'pi pi-fw pi-bookmark', icon: 'pi pi-fw pi-bookmark',
path: '/hierarchy/submenu_1',
items: [ items: [
{ {
label: 'Submenu 1.1', label: 'Submenu 1.1',
icon: 'pi pi-fw pi-bookmark', icon: 'pi pi-fw pi-bookmark',
path: '/hierarchy/submenu_1/submenu_1_1',
items: [ items: [
{ label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' }, { label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' },
{ label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' }, { label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' },
@@ -111,6 +118,7 @@ export class AppMenu {
{ {
label: 'Submenu 1.2', label: 'Submenu 1.2',
icon: 'pi pi-fw pi-bookmark', icon: 'pi pi-fw pi-bookmark',
path: '/hierarchy/submenu_1/submenu_1_2',
items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }] items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }]
} }
] ]
@@ -118,10 +126,12 @@ export class AppMenu {
{ {
label: 'Submenu 2', label: 'Submenu 2',
icon: 'pi pi-fw pi-bookmark', icon: 'pi pi-fw pi-bookmark',
path: '/hierarchy/submenu_2',
items: [ items: [
{ {
label: 'Submenu 2.1', label: 'Submenu 2.1',
icon: 'pi pi-fw pi-bookmark', icon: 'pi pi-fw pi-bookmark',
path: '/hierarchy/submenu_2/submenu_2_1',
items: [ items: [
{ label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' }, { label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' },
{ label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' } { label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' }
@@ -130,6 +140,7 @@ export class AppMenu {
{ {
label: 'Submenu 2.2', label: 'Submenu 2.2',
icon: 'pi pi-fw pi-bookmark', icon: 'pi pi-fw pi-bookmark',
path: '/hierarchy/submenu_2/submenu_2_2',
items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }] items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }]
} }
] ]

View File

@@ -1,170 +1,209 @@
import { Component, HostBinding, Input } from '@angular/core'; import { Component, computed, inject, input, signal } from '@angular/core';
import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RippleModule } from 'primeng/ripple'; import { RippleModule } from 'primeng/ripple';
import { MenuItem } from 'primeng/api'; import { LayoutService } from '@/app/layout/service/layout.service';
import { LayoutService } from '../service/layout.service'; import { filter } from 'rxjs/operators';
@Component({ @Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: '[app-menuitem]', selector: '[app-menuitem]',
imports: [CommonModule, RouterModule, RippleModule], imports: [CommonModule, RouterModule, RippleModule],
template: ` template: `
<ng-container> @if (root() && isVisible()) {
<div *ngIf="root && item.visible !== false" class="layout-menuitem-root-text">{{ item.label }}</div> <div class="layout-menuitem-root-text">{{ item().label }}</div>
<a *ngIf="(!item.routerLink || item.items) && item.visible !== false" [attr.href]="item.url" (click)="itemClick($event)" [ngClass]="item.styleClass" [attr.target]="item.target" tabindex="0" pRipple> }
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i> @if ((!hasRouterLink() || hasChildren()) && isVisible()) {
<span class="layout-menuitem-text">{{ item.label }}</span> <a [attr.href]="item().url" (click)="itemClick($event)" [ngClass]="item().class" [attr.target]="item().target" tabindex="0" pRipple>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" *ngIf="item.items"></i> <i [ngClass]="item().icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">{{ item().label }}</span>
@if (hasChildren()) {
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
}
</a> </a>
}
@if (hasRouterLink() && !hasChildren() && isVisible()) {
<a <a
*ngIf="item.routerLink && !item.items && item.visible !== false"
(click)="itemClick($event)" (click)="itemClick($event)"
[ngClass]="item.styleClass" [ngClass]="item().class"
[routerLink]="item.routerLink" [routerLink]="item().routerLink"
routerLinkActive="active-route" routerLinkActive="active-route"
[routerLinkActiveOptions]="item.routerLinkActiveOptions || { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }" [routerLinkActiveOptions]="item().routerLinkActiveOptions || { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }"
[fragment]="item.fragment" [fragment]="item().fragment"
[queryParamsHandling]="item.queryParamsHandling" [queryParamsHandling]="item().queryParamsHandling"
[preserveFragment]="item.preserveFragment" [preserveFragment]="item().preserveFragment"
[skipLocationChange]="item.skipLocationChange" [skipLocationChange]="item().skipLocationChange"
[replaceUrl]="item.replaceUrl" [replaceUrl]="item().replaceUrl"
[state]="item.state" [state]="item().state"
[queryParams]="item.queryParams" [queryParams]="item().queryParams"
[attr.target]="item.target" [attr.target]="item().target"
tabindex="0" tabindex="0"
pRipple pRipple
> >
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i> <i [ngClass]="item().icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">{{ item.label }}</span> <span class="layout-menuitem-text">{{ item().label }}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" *ngIf="item.items"></i> @if (hasChildren()) {
<i class="pi pi-fw pi-angle-down layout-submenu-toggler"></i>
}
</a> </a>
}
<ul *ngIf="item.items && item.visible !== false" [@children]="submenuAnimation"> @if (hasChildren() && isVisible() && (root() || isActive())) {
<ng-template ngFor let-child let-i="index" [ngForOf]="item.items"> <ul [animate.enter]="initialized() ? 'p-submenu-enter' : null" [animate.leave]="'p-submenu-leave'" [class.layout-root-submenulist]="root()">
<li app-menuitem [item]="child" [index]="i" [parentKey]="key" [class]="child['badgeClass']"></li> @for (child of item().items; track child?.label) {
</ng-template> <li app-menuitem [item]="child" [parentPath]="fullPath()" [root]="false" [class]="child['badgeClass']"></li>
}
</ul> </ul>
</ng-container> }
`, `,
animations: [ host: {
trigger('children', [ '[class.active-menuitem]': 'isActive()',
state( '[class.layout-root-menuitem]': 'root()'
'collapsed', },
style({ styles: [
height: '0' `
}) .p-submenu-enter {
), animation: p-animate-submenu-expand 450ms cubic-bezier(0.86, 0, 0.07, 1) forwards;
state( }
'expanded',
style({ .p-submenu-leave {
height: '*' animation: p-animate-submenu-collapse 450ms cubic-bezier(0.86, 0, 0.07, 1) forwards;
}) }
),
transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')) @keyframes p-animate-submenu-expand {
]) from {
], max-height: 0;
providers: [LayoutService] overflow: hidden;
}
to {
max-height: 1000px;
overflow: visible;
}
}
@keyframes p-animate-submenu-collapse {
from {
max-height: 1000px;
overflow: hidden;
}
to {
max-height: 0;
overflow: hidden;
}
}
`
]
}) })
export class AppMenuitem { export class AppMenuitem {
@Input() item!: MenuItem; layoutService = inject(LayoutService);
@Input() index!: number; router = inject(Router);
@Input() @HostBinding('class.layout-root-menuitem') root!: boolean; item = input<any>(null);
@Input() parentKey!: string; root = input<boolean>(false);
active = false; parentPath = input<string | null>(null);
menuSourceSubscription: Subscription; isVisible = computed(() => this.item()?.visible !== false);
menuResetSubscription: Subscription; hasChildren = computed(() => this.item()?.items && this.item()?.items.length > 0);
key: string = ''; hasRouterLink = computed(() => !!this.item()?.routerLink);
constructor( fullPath = computed(() => {
public router: Router, const itemPath = this.item()?.path;
private layoutService: LayoutService if (!itemPath) return this.parentPath();
) { const parent = this.parentPath();
this.menuSourceSubscription = this.layoutService.menuSource$.subscribe((value) => { if (parent && !itemPath.startsWith(parent)) {
Promise.resolve(null).then(() => { return parent + itemPath;
if (value.routeEvent) { }
this.active = value.key === this.key || value.key.startsWith(this.key + '-') ? true : false; return itemPath;
} else { });
if (value.key !== this.key && !value.key.startsWith(this.key + '-')) {
this.active = false;
}
}
});
});
this.menuResetSubscription = this.layoutService.resetSource$.subscribe(() => { isActive = computed(() => {
this.active = false; const activePath = this.layoutService.layoutState().activePath;
}); if (this.item()?.path) {
return activePath?.startsWith(this.fullPath() ?? '') ?? false;
}
return false;
});
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((params) => { initialized = signal<boolean>(false);
if (this.item.routerLink) {
constructor() {
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => {
if (this.item()?.routerLink) {
this.updateActiveStateFromRoute(); this.updateActiveStateFromRoute();
} }
}); });
} }
ngOnInit() { ngOnInit() {
this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index); if (this.item()?.routerLink) {
if (this.item.routerLink) {
this.updateActiveStateFromRoute(); this.updateActiveStateFromRoute();
} }
} }
updateActiveStateFromRoute() { ngAfterViewInit() {
let activeRoute = this.router.isActive(this.item.routerLink[0], { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }); setTimeout(() => {
this.initialized.set(true);
});
}
if (activeRoute) { updateActiveStateFromRoute() {
this.layoutService.onMenuStateChange({ key: this.key, routeEvent: true }); const item = this.item();
if (!item?.routerLink) return;
const isRouteActive = this.router.isActive(item.routerLink[0], {
paths: 'exact',
queryParams: 'ignored',
matrixParams: 'ignored',
fragment: 'ignored'
});
if (isRouteActive) {
const parentPath = this.parentPath();
if (parentPath) {
this.layoutService.layoutState.update((val) => ({
...val,
activePath: parentPath
}));
}
} }
} }
itemClick(event: Event) { itemClick(event: Event) {
// avoid processing disabled items const item = this.item();
if (this.item.disabled) {
if (item?.disabled) {
event.preventDefault(); event.preventDefault();
return; return;
} }
// execute command if (item?.command) {
if (this.item.command) { item.command({ originalEvent: event, item: item });
this.item.command({ originalEvent: event, item: this.item });
} }
// toggle active state if (this.hasChildren()) {
if (this.item.items) { if (this.isActive()) {
this.active = !this.active; this.layoutService.layoutState.update((val) => ({
} ...val,
activePath: this.parentPath()
this.layoutService.onMenuStateChange({ key: this.key }); }));
} } else {
this.layoutService.layoutState.update((val) => ({
get submenuAnimation() { ...val,
return this.root ? 'expanded' : this.active ? 'expanded' : 'collapsed'; activePath: this.fullPath(),
} menuHoverActive: true
}));
@HostBinding('class.active-menuitem') }
get activeClass() { } else {
return this.active && !this.root; this.layoutService.layoutState.update((val) => ({
} ...val,
overlayMenuActive: false,
ngOnDestroy() { staticMenuMobileActive: false,
if (this.menuSourceSubscription) { mobileMenuActive: false,
this.menuSourceSubscription.unsubscribe(); menuHoverActive: false
} }));
if (this.menuResetSubscription) {
this.menuResetSubscription.unsubscribe();
} }
} }
} }

View File

@@ -1,14 +1,115 @@
import { Component, ElementRef } from '@angular/core'; import { Component, computed, effect, ElementRef, inject, OnDestroy, OnInit } from '@angular/core';
import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { filter, Subject, takeUntil } from 'rxjs';
import { AppMenu } from './app.menu'; import { AppMenu } from './app.menu';
import { LayoutService } from '@/app/layout/service/layout.service';
@Component({ @Component({
selector: 'app-sidebar', selector: 'app-sidebar',
standalone: true, standalone: true,
imports: [AppMenu], imports: [AppMenu, RouterModule],
template: ` <div class="layout-sidebar"> template: `
<app-menu></app-menu> <div class="layout-sidebar">
</div>` <app-menu></app-menu>
</div>
`
}) })
export class AppSidebar { export class AppSidebar implements OnInit, OnDestroy {
constructor(public el: ElementRef) {} layoutService = inject(LayoutService);
router = inject(Router);
el = inject(ElementRef);
private outsideClickListener: ((event: MouseEvent) => void) | null = null;
private destroy$ = new Subject<void>();
constructor() {
effect(() => {
const state = this.layoutService.layoutState();
if (this.layoutService.isDesktop()) {
if (state.overlayMenuActive) {
this.bindOutsideClickListener();
} else {
this.unbindOutsideClickListener();
}
} else {
if (state.mobileMenuActive) {
this.bindOutsideClickListener();
} else {
this.unbindOutsideClickListener();
}
}
});
}
ngOnInit() {
this.router.events
.pipe(
filter((event) => event instanceof NavigationEnd),
takeUntil(this.destroy$)
)
.subscribe((event) => {
const navEvent = event as NavigationEnd;
this.onRouteChange(navEvent.urlAfterRedirects);
});
this.onRouteChange(this.router.url);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.unbindOutsideClickListener();
}
private onRouteChange(path: string) {
this.layoutService.layoutState.update((val) => ({
...val,
activePath: path,
overlayMenuActive: false,
staticMenuMobileActive: false,
mobileMenuActive: false,
menuHoverActive: false
}));
}
private bindOutsideClickListener() {
if (!this.outsideClickListener) {
this.outsideClickListener = (event: MouseEvent) => {
if (this.isOutsideClicked(event)) {
this.layoutService.layoutState.update((val) => ({
...val,
overlayMenuActive: false,
staticMenuMobileActive: false,
mobileMenuActive: false,
menuHoverActive: false
}));
}
};
document.addEventListener('click', this.outsideClickListener);
}
}
private unbindOutsideClickListener() {
if (this.outsideClickListener) {
document.removeEventListener('click', this.outsideClickListener);
this.outsideClickListener = null;
}
}
private isOutsideClicked(event: MouseEvent): boolean {
const topbarButtonEl = document.querySelector('.topbar-start > button');
const sidebarEl = this.el.nativeElement;
return !(
sidebarEl?.isSameNode(event.target as Node) ||
sidebarEl?.contains(event.target as Node) ||
topbarButtonEl?.isSameNode(event.target as Node) ||
topbarButtonEl?.contains(event.target as Node)
);
}
} }

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core'; import { Component, inject } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { StyleClassModule } from 'primeng/styleclass'; import { StyleClassModule } from 'primeng/styleclass';
import { AppConfigurator } from './app.configurator'; import { AppConfigurator } from './app.configurator';
import { LayoutService } from '../service/layout.service'; import { LayoutService } from '@/app/layout/service/layout.service';
@Component({ @Component({
selector: 'app-topbar', selector: 'app-topbar',
@@ -84,9 +84,12 @@ import { LayoutService } from '../service/layout.service';
export class AppTopbar { export class AppTopbar {
items!: MenuItem[]; items!: MenuItem[];
constructor(public layoutService: LayoutService) {} layoutService = inject(LayoutService);
toggleDarkMode() { toggleDarkMode() {
this.layoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme })); this.layoutService.layoutConfig.update((state) => ({
...state,
darkTheme: !state.darkTheme
}));
} }
} }

View File

@@ -1,70 +1,46 @@
import { Injectable, effect, signal, computed } from '@angular/core'; import { Injectable, effect, signal, computed } from '@angular/core';
import { Subject } from 'rxjs';
export interface layoutConfig { export interface LayoutConfig {
preset?: string; preset: string;
primary?: string; primary: string;
surface?: string | undefined | null; surface: string | undefined | null;
darkTheme?: boolean; darkTheme: boolean;
menuMode?: string; menuMode: string;
} }
interface LayoutState { interface LayoutState {
staticMenuDesktopInactive?: boolean; staticMenuDesktopInactive: boolean;
overlayMenuActive?: boolean; overlayMenuActive: boolean;
configSidebarVisible?: boolean; configSidebarVisible: boolean;
staticMenuMobileActive?: boolean; mobileMenuActive: boolean;
menuHoverActive?: boolean; menuHoverActive: boolean;
} activePath: string | null;
interface MenuChangeEvent {
key: string;
routeEvent?: boolean;
} }
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class LayoutService { export class LayoutService {
_config: layoutConfig = { layoutConfig = signal<LayoutConfig>({
preset: 'Aura', preset: 'Aura',
primary: 'emerald', primary: 'emerald',
surface: null, surface: null,
darkTheme: false, darkTheme: false,
menuMode: 'static' menuMode: 'static'
}; });
_state: LayoutState = { layoutState = signal<LayoutState>({
staticMenuDesktopInactive: false, staticMenuDesktopInactive: false,
overlayMenuActive: false, overlayMenuActive: false,
configSidebarVisible: false, configSidebarVisible: false,
staticMenuMobileActive: false, mobileMenuActive: false,
menuHoverActive: false menuHoverActive: false,
}; activePath: null
});
layoutConfig = signal<layoutConfig>(this._config); theme = computed(() => (this.layoutConfig().darkTheme ? 'light' : 'dark'));
layoutState = signal<LayoutState>(this._state); isSidebarActive = computed(() => this.layoutState().overlayMenuActive || this.layoutState().mobileMenuActive);
private configUpdate = new Subject<layoutConfig>();
private overlayOpen = new Subject<any>();
private menuSource = new Subject<MenuChangeEvent>();
private resetSource = new Subject();
menuSource$ = this.menuSource.asObservable();
resetSource$ = this.resetSource.asObservable();
configUpdate$ = this.configUpdate.asObservable();
overlayOpen$ = this.overlayOpen.asObservable();
theme = computed(() => (this.layoutConfig()?.darkTheme ? 'light' : 'dark'));
isSidebarActive = computed(() => this.layoutState().overlayMenuActive || this.layoutState().staticMenuMobileActive);
isDarkTheme = computed(() => this.layoutConfig().darkTheme); isDarkTheme = computed(() => this.layoutConfig().darkTheme);
@@ -79,13 +55,6 @@ export class LayoutService {
private initialized = false; private initialized = false;
constructor() { constructor() {
effect(() => {
const config = this.layoutConfig();
if (config) {
this.onConfigUpdate();
}
});
effect(() => { effect(() => {
const config = this.layoutConfig(); const config = this.layoutConfig();
@@ -98,28 +67,23 @@ export class LayoutService {
}); });
} }
private handleDarkModeTransition(config: layoutConfig): void { private handleDarkModeTransition(config: LayoutConfig): void {
if ((document as any).startViewTransition) { const supportsViewTransition = 'startViewTransition' in document;
if (supportsViewTransition) {
this.startViewTransition(config); this.startViewTransition(config);
} else { } else {
this.toggleDarkMode(config); this.toggleDarkMode(config);
this.onTransitionEnd();
} }
} }
private startViewTransition(config: layoutConfig): void { private startViewTransition(config: LayoutConfig): void {
const transition = (document as any).startViewTransition(() => { document.startViewTransition(() => {
this.toggleDarkMode(config); this.toggleDarkMode(config);
}); });
transition.ready
.then(() => {
this.onTransitionEnd();
})
.catch(() => {});
} }
toggleDarkMode(config?: layoutConfig): void { toggleDarkMode(config?: LayoutConfig): void {
const _config = config || this.layoutConfig(); const _config = config || this.layoutConfig();
if (_config.darkTheme) { if (_config.darkTheme) {
document.documentElement.classList.add('app-dark'); document.documentElement.classList.add('app-dark');
@@ -128,33 +92,26 @@ export class LayoutService {
} }
} }
private onTransitionEnd() {
this.transitionComplete.set(true);
setTimeout(() => {
this.transitionComplete.set(false);
});
}
onMenuToggle() { onMenuToggle() {
if (this.isOverlay()) { 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);
}
} }
if (this.isDesktop()) { if (this.isDesktop()) {
this.layoutState.update((prev) => ({ ...prev, staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive })); this.layoutState.update((prev) => ({ ...prev, staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive }));
} else { } else {
this.layoutState.update((prev) => ({ ...prev, staticMenuMobileActive: !this.layoutState().staticMenuMobileActive })); this.layoutState.update((prev) => ({ ...prev, mobileMenuActive: !this.layoutState().mobileMenuActive }));
if (this.layoutState().staticMenuMobileActive) {
this.overlayOpen.next(null);
}
} }
} }
showConfigSidebar() {
this.layoutState.update((prev) => ({ ...prev, configSidebarVisible: true }));
}
hideConfigSidebar() {
this.layoutState.update((prev) => ({ ...prev, configSidebarVisible: false }));
}
isDesktop() { isDesktop() {
return window.innerWidth > 991; return window.innerWidth > 991;
} }
@@ -162,17 +119,4 @@ export class LayoutService {
isMobile() { isMobile() {
return !this.isDesktop(); return !this.isDesktop();
} }
onConfigUpdate() {
this._config = { ...this.layoutConfig() };
this.configUpdate.next(this.layoutConfig());
}
onMenuStateChange(event: MenuChangeEvent) {
this.menuSource.next(event);
}
reset() {
this.resetSource.next(true);
}
} }

View File

@@ -18,7 +18,7 @@ import { TagModule } from 'primeng/tag';
import { InputIconModule } from 'primeng/inputicon'; import { InputIconModule } from 'primeng/inputicon';
import { IconFieldModule } from 'primeng/iconfield'; import { IconFieldModule } from 'primeng/iconfield';
import { ConfirmDialogModule } from 'primeng/confirmdialog'; import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { Product, ProductService } from '../service/product.service'; import { Product, ProductService } from '@/app/pages/service/product.service';
interface Column { interface Column {
field: string; field: string;

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core'; import { Component, inject, signal } from '@angular/core';
import { RippleModule } from 'primeng/ripple'; import { RippleModule } from 'primeng/ripple';
import { TableModule } from 'primeng/table'; import { TableModule } from 'primeng/table';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Product, ProductService } from '../../service/product.service'; import { Product, ProductService } from '@/app/pages/service/product.service';
@Component({ @Component({
standalone: true, standalone: true,
@@ -11,7 +11,7 @@ import { Product, ProductService } from '../../service/product.service';
imports: [CommonModule, TableModule, ButtonModule, RippleModule], imports: [CommonModule, TableModule, ButtonModule, RippleModule],
template: `<div class="card mb-8!"> template: `<div class="card mb-8!">
<div class="font-semibold text-xl mb-4">Recent Sales</div> <div class="font-semibold text-xl mb-4">Recent Sales</div>
<p-table [value]="products" [paginator]="true" [rows]="5" responsiveLayout="scroll"> <p-table [value]="products()" [paginator]="true" [rows]="5" responsiveLayout="scroll">
<ng-template #header> <ng-template #header>
<tr> <tr>
<th>Image</th> <th>Image</th>
@@ -37,11 +37,11 @@ import { Product, ProductService } from '../../service/product.service';
providers: [ProductService] providers: [ProductService]
}) })
export class RecentSalesWidget { export class RecentSalesWidget {
products!: Product[]; products = signal<Product[]>([]);
constructor(private productService: ProductService) {} productService = inject(ProductService);
ngOnInit() { ngOnInit() {
this.productService.getProductsSmall().then((data) => (this.products = data)); this.productService.getProductsSmall().then((data) => (this.products.set(data)));
} }
} }

View File

@@ -1,7 +1,6 @@
import { Component } from '@angular/core'; import { afterNextRender, Component, effect, inject, signal } from '@angular/core';
import { ChartModule } from 'primeng/chart'; import { ChartModule } from 'primeng/chart';
import { debounceTime, Subscription } from 'rxjs'; import { LayoutService } from '@/app/layout/service/layout.service';
import { LayoutService } from '../../../layout/service/layout.service';
@Component({ @Component({
standalone: true, standalone: true,
@@ -9,24 +8,29 @@ import { LayoutService } from '../../../layout/service/layout.service';
imports: [ChartModule], imports: [ChartModule],
template: `<div class="card mb-8!"> template: `<div class="card mb-8!">
<div class="font-semibold text-xl mb-4">Revenue Stream</div> <div class="font-semibold text-xl mb-4">Revenue Stream</div>
<p-chart type="bar" [data]="chartData" [options]="chartOptions" class="h-100" /> <p-chart type="bar" [data]="chartData()" [options]="chartOptions()" class="h-100" />
</div>` </div>`
}) })
export class RevenueStreamWidget { export class RevenueStreamWidget {
chartData: any; layoutService = inject(LayoutService);
chartOptions: any; chartData = signal<any>(null);
subscription!: Subscription; chartOptions = signal<any>(null);
constructor(public layoutService: LayoutService) { constructor() {
this.subscription = this.layoutService.configUpdate$.pipe(debounceTime(25)).subscribe(() => { afterNextRender(() => {
this.initChart(); setTimeout(() => {
this.initChart();
}, 150);
}); });
}
ngOnInit() { effect(() => {
this.initChart(); this.layoutService.layoutConfig().darkTheme;
setTimeout(() => {
this.initChart();
}, 150);
});
} }
initChart() { initChart() {
@@ -35,7 +39,7 @@ export class RevenueStreamWidget {
const borderColor = documentStyle.getPropertyValue('--surface-border'); const borderColor = documentStyle.getPropertyValue('--surface-border');
const textMutedColor = documentStyle.getPropertyValue('--text-color-secondary'); const textMutedColor = documentStyle.getPropertyValue('--text-color-secondary');
this.chartData = { this.chartData.set({
labels: ['Q1', 'Q2', 'Q3', 'Q4'], labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [ datasets: [
{ {
@@ -67,9 +71,9 @@ export class RevenueStreamWidget {
barThickness: 32 barThickness: 32
} }
] ]
}; });
this.chartOptions = { this.chartOptions.set({
maintainAspectRatio: false, maintainAspectRatio: false,
aspectRatio: 0.8, aspectRatio: 0.8,
plugins: { plugins: {
@@ -102,12 +106,6 @@ export class RevenueStreamWidget {
} }
} }
} }
}; });
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
} }
} }

View File

@@ -9,7 +9,7 @@ import { Component } from '@angular/core';
<div class="card"> <div class="card">
<div class="font-semibold text-2xl mb-4">Documentation</div> <div class="font-semibold text-2xl mb-4">Documentation</div>
<div class="font-semibold text-xl mb-4">Get Started</div> <div class="font-semibold text-xl mb-4">Get Started</div>
<p class="text-lg mb-4">Sakai is an application template for Angular and is distributed as a CLI project. Current versions is Angular v20 with PrimeNG v20. In case CLI is not installed already, use the command below to set it up.</p> <p class="text-lg mb-4">Sakai is an application template for Angular and is distributed as a CLI project. Current versions is Angular v21 with PrimeNG v21. In case CLI is not installed already, use the command below to set it up.</p>
<pre class="app-code"> <pre class="app-code">
<code>npm install -g &#64;angular/cli</code></pre> <code>npm install -g &#64;angular/cli</code></pre>
<p class="text-lg mb-4"> <p class="text-lg mb-4">

View File

@@ -3,7 +3,7 @@ import { StyleClassModule } from 'primeng/styleclass';
import { Router, RouterModule } from '@angular/router'; import { Router, RouterModule } from '@angular/router';
import { RippleModule } from 'primeng/ripple'; import { RippleModule } from 'primeng/ripple';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import {AppFloatingConfigurator} from "@/layout/component/app.floatingconfigurator"; import {AppFloatingConfigurator} from "@/app/layout/component/app.floatingconfigurator";
@Component({ @Component({
selector: 'topbar-widget', selector: 'topbar-widget',

View File

@@ -1,96 +1,96 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
@Injectable() @Injectable()
export class PhotoService { export class PhotoService {
getData() { getData() {
return [ return [
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria1.jpg', itemImageSrc: '/demo/images/galleria/galleria1.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria1s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria1s.jpg',
alt: 'Description for Image 1', alt: 'Description for Image 1',
title: 'Title 1' title: 'Title 1'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria2.jpg', itemImageSrc: '/demo/images/galleria/galleria2.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria2s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria2s.jpg',
alt: 'Description for Image 2', alt: 'Description for Image 2',
title: 'Title 2' title: 'Title 2'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria3.jpg', itemImageSrc: '/demo/images/galleria/galleria3.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria3s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria3s.jpg',
alt: 'Description for Image 3', alt: 'Description for Image 3',
title: 'Title 3' title: 'Title 3'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria4.jpg', itemImageSrc: '/demo/images/galleria/galleria4.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria4s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria4s.jpg',
alt: 'Description for Image 4', alt: 'Description for Image 4',
title: 'Title 4' title: 'Title 4'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria5.jpg', itemImageSrc: '/demo/images/galleria/galleria5.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria5s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria5s.jpg',
alt: 'Description for Image 5', alt: 'Description for Image 5',
title: 'Title 5' title: 'Title 5'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria6.jpg', itemImageSrc: '/demo/images/galleria/galleria6.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria6s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria6s.jpg',
alt: 'Description for Image 6', alt: 'Description for Image 6',
title: 'Title 6' title: 'Title 6'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria7.jpg', itemImageSrc: '/demo/images/galleria/galleria7.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria7s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria7s.jpg',
alt: 'Description for Image 7', alt: 'Description for Image 7',
title: 'Title 7' title: 'Title 7'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria8.jpg', itemImageSrc: '/demo/images/galleria/galleria8.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria8s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria8s.jpg',
alt: 'Description for Image 8', alt: 'Description for Image 8',
title: 'Title 8' title: 'Title 8'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria9.jpg', itemImageSrc: '/demo/images/galleria/galleria9.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria9s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria9s.jpg',
alt: 'Description for Image 9', alt: 'Description for Image 9',
title: 'Title 9' title: 'Title 9'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria10.jpg', itemImageSrc: '/demo/images/galleria/galleria10.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria10s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria10s.jpg',
alt: 'Description for Image 10', alt: 'Description for Image 10',
title: 'Title 10' title: 'Title 10'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria11.jpg', itemImageSrc: '/demo/images/galleria/galleria11.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria11s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria11s.jpg',
alt: 'Description for Image 11', alt: 'Description for Image 11',
title: 'Title 11' title: 'Title 11'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria12.jpg', itemImageSrc: '/demo/images/galleria/galleria12.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria12s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria12s.jpg',
alt: 'Description for Image 12', alt: 'Description for Image 12',
title: 'Title 12' title: 'Title 12'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria13.jpg', itemImageSrc: '/demo/images/galleria/galleria13.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria13s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria13s.jpg',
alt: 'Description for Image 13', alt: 'Description for Image 13',
title: 'Title 13' title: 'Title 13'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria14.jpg', itemImageSrc: '/demo/images/galleria/galleria14.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria14s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria14s.jpg',
alt: 'Description for Image 14', alt: 'Description for Image 14',
title: 'Title 14' title: 'Title 14'
}, },
{ {
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria15.jpg', itemImageSrc: '/demo/images/galleria/galleria15.jpg',
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria15s.jpg', thumbnailImageSrc: '/demo/images/galleria/galleria15s.jpg',
alt: 'Description for Image 15', alt: 'Description for Image 15',
title: 'Title 15' title: 'Title 15'
} }

View File

@@ -1,86 +1,80 @@
import { CommonModule } from '@angular/common'; import {Component, effect, inject, signal} from '@angular/core';
import { Component } from '@angular/core'; import {ChartModule} from 'primeng/chart';
import { ChartModule } from 'primeng/chart'; import {FluidModule} from 'primeng/fluid';
import { FluidModule } from 'primeng/fluid'; import {LayoutService} from '@/app/layout/service/layout.service';
import { debounceTime, Subscription } from 'rxjs';
import { LayoutService } from '../../layout/service/layout.service';
@Component({ @Component({
selector: 'app-chart-demo', selector: 'app-chart-demo',
standalone: true, standalone: true,
imports: [CommonModule, ChartModule, FluidModule], imports: [ChartModule, FluidModule],
template: ` template: `
<p-fluid class="grid grid-cols-12 gap-8"> <p-fluid class="grid grid-cols-12 gap-8">
<div class="col-span-12 xl:col-span-6"> <div class="col-span-12 xl:col-span-6">
<div class="card"> <div class="card">
<div class="font-semibold text-xl mb-4">Linear</div> <div class="font-semibold text-xl mb-6">Linear</div>
<p-chart type="line" [data]="lineData" [options]="lineOptions"></p-chart> <p-chart type="line" [data]="lineData()" [options]="lineOptions()"></p-chart>
</div> </div>
</div> </div>
<div class="col-span-12 xl:col-span-6"> <div class="col-span-12 xl:col-span-6">
<div class="card"> <div class="card">
<div class="font-semibold text-xl mb-4">Bar</div> <div class="font-semibold text-xl mb-6">Bar</div>
<p-chart type="bar" [data]="barData" [options]="barOptions"></p-chart> <p-chart type="bar" [data]="barData()" [options]="barOptions()"></p-chart>
</div> </div>
</div> </div>
<div class="col-span-12 xl:col-span-6"> <div class="col-span-12 xl:col-span-6">
<div class="card flex flex-col items-center"> <div class="card flex flex-col items-center">
<div class="font-semibold text-xl mb-4">Pie</div> <div class="font-semibold text-xl mb-6">Pie</div>
<p-chart type="pie" [data]="pieData" [options]="pieOptions"></p-chart> <p-chart type="pie" [data]="pieData()" [options]="pieOptions()"></p-chart>
</div> </div>
</div> </div>
<div class="col-span-12 xl:col-span-6"> <div class="col-span-12 xl:col-span-6">
<div class="card flex flex-col items-center"> <div class="card flex flex-col items-center">
<div class="font-semibold text-xl mb-4">Doughnut</div> <div class="font-semibold text-xl mb-6">Doughnut</div>
<p-chart type="doughnut" [data]="pieData" [options]="pieOptions"></p-chart> <p-chart type="doughnut" [data]="pieData()" [options]="pieOptions()"></p-chart>
</div> </div>
</div> </div>
<div class="col-span-12 xl:col-span-6"> <div class="col-span-12 xl:col-span-6">
<div class="card flex flex-col items-center"> <div class="card flex flex-col items-center">
<div class="font-semibold text-xl mb-4">Polar Area</div> <div class="font-semibold text-xl mb-6">Polar Area</div>
<p-chart type="polarArea" [data]="polarData" [options]="polarOptions"></p-chart> <p-chart type="polarArea" [data]="polarData()" [options]="polarOptions()"></p-chart>
</div> </div>
</div> </div>
<div class="col-span-12 xl:col-span-6"> <div class="col-span-12 xl:col-span-6">
<div class="card flex flex-col items-center"> <div class="card flex flex-col items-center">
<div class="font-semibold text-xl mb-4">Radar</div> <div class="font-semibold text-xl mb-6">Radar</div>
<p-chart type="radar" [data]="radarData" [options]="radarOptions"></p-chart> <p-chart type="radar" [data]="radarData()" [options]="radarOptions()"></p-chart>
</div> </div>
</div> </div>
</p-fluid> </p-fluid>
` `
}) })
export class ChartDemo { export class ChartDemo {
lineData: any; layoutService = inject(LayoutService);
barData: any; lineData = signal<any>(null);
pieData: any; barData = signal<any>(null);
polarData: any; pieData = signal<any>(null);
radarData: any; polarData = signal<any>(null);
lineOptions: any; radarData = signal<any>(null);
barOptions: any; lineOptions = signal<any>(null);
pieOptions: any; barOptions = signal<any>(null);
polarOptions: any; pieOptions = signal<any>(null);
radarOptions: any; polarOptions = signal<any>(null);
subscription: Subscription; radarOptions = signal<any>(null);
constructor(private layoutService: LayoutService) {
this.subscription = this.layoutService.configUpdate$.pipe(debounceTime(25)).subscribe(() => {
this.initCharts();
});
}
ngOnInit() { chartEffect = effect(() => {
this.initCharts(); this.layoutService.layoutConfig().darkTheme;
} setTimeout(() => this.initCharts(), 150);
})
initCharts() { initCharts() {
const documentStyle = getComputedStyle(document.documentElement); const documentStyle = getComputedStyle(document.documentElement);
@@ -88,7 +82,7 @@ export class ChartDemo {
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary'); const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
const surfaceBorder = documentStyle.getPropertyValue('--surface-border'); const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
this.barData = { this.barData.set({
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [ datasets: [
{ {
@@ -104,9 +98,9 @@ export class ChartDemo {
data: [28, 48, 40, 19, 86, 27, 90] data: [28, 48, 40, 19, 86, 27, 90]
} }
] ]
}; });
this.barOptions = { this.barOptions.set({
maintainAspectRatio: false, maintainAspectRatio: false,
aspectRatio: 0.8, aspectRatio: 0.8,
plugins: { plugins: {
@@ -139,9 +133,9 @@ export class ChartDemo {
} }
} }
} }
}; });
this.pieData = { this.pieData.set({
labels: ['A', 'B', 'C'], labels: ['A', 'B', 'C'],
datasets: [ datasets: [
{ {
@@ -150,9 +144,9 @@ export class ChartDemo {
hoverBackgroundColor: [documentStyle.getPropertyValue('--p-indigo-400'), documentStyle.getPropertyValue('--p-purple-400'), documentStyle.getPropertyValue('--p-teal-400')] hoverBackgroundColor: [documentStyle.getPropertyValue('--p-indigo-400'), documentStyle.getPropertyValue('--p-purple-400'), documentStyle.getPropertyValue('--p-teal-400')]
} }
] ]
}; });
this.pieOptions = { this.pieOptions.set({
plugins: { plugins: {
legend: { legend: {
labels: { labels: {
@@ -161,9 +155,9 @@ export class ChartDemo {
} }
} }
} }
}; });
this.lineData = { this.lineData.set({
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [ datasets: [
{ {
@@ -183,9 +177,9 @@ export class ChartDemo {
tension: 0.4 tension: 0.4
} }
] ]
}; });
this.lineOptions = { this.lineOptions.set({
maintainAspectRatio: false, maintainAspectRatio: false,
aspectRatio: 0.8, aspectRatio: 0.8,
plugins: { plugins: {
@@ -215,9 +209,9 @@ export class ChartDemo {
} }
} }
} }
}; });
this.polarData = { this.polarData.set({
datasets: [ datasets: [
{ {
data: [11, 16, 7, 3], data: [11, 16, 7, 3],
@@ -226,9 +220,9 @@ export class ChartDemo {
} }
], ],
labels: ['Indigo', 'Purple', 'Teal', 'Orange'] labels: ['Indigo', 'Purple', 'Teal', 'Orange']
}; });
this.polarOptions = { this.polarOptions.set({
plugins: { plugins: {
legend: { legend: {
labels: { labels: {
@@ -239,17 +233,17 @@ export class ChartDemo {
scales: { scales: {
r: { r: {
grid: { grid: {
color: surfaceBorder, color: surfaceBorder
}, },
ticks: { ticks: {
display: false, display: false,
color: textColorSecondary color: textColorSecondary
}, }
}, }
}, }
}; });
this.radarData = { this.radarData.set({
labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'], labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'],
datasets: [ datasets: [
{ {
@@ -271,9 +265,9 @@ export class ChartDemo {
data: [28, 48, 40, 19, 96, 27, 100] data: [28, 48, 40, 19, 96, 27, 100]
} }
] ]
}; });
this.radarOptions = { this.radarOptions.set({
plugins: { plugins: {
legend: { legend: {
labels: { labels: {
@@ -291,12 +285,6 @@ export class ChartDemo {
} }
} }
} }
}; });
}
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
} }
} }

View File

@@ -26,10 +26,10 @@ import { ListboxModule } from 'primeng/listbox';
import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
import { TextareaModule } from 'primeng/textarea'; import { TextareaModule } from 'primeng/textarea';
import { ToggleButtonModule } from 'primeng/togglebutton'; import { ToggleButtonModule } from 'primeng/togglebutton';
import { CountryService } from '../service/country.service'; import { CountryService } from '@/app/pages/service/country.service';
import { NodeService } from '../service/node.service'; import { NodeService } from '@/app/pages/service/node.service';
import { TreeNode } from 'primeng/api'; import { TreeNode } from 'primeng/api';
import { Country } from '../service/customer.service'; import { Country } from '@/app/pages/service/customer.service';
@Component({ @Component({
selector: 'app-input-demo', selector: 'app-input-demo',

View File

@@ -7,7 +7,7 @@ import { OrderListModule } from 'primeng/orderlist';
import { PickListModule } from 'primeng/picklist'; import { PickListModule } from 'primeng/picklist';
import { SelectButtonModule } from 'primeng/selectbutton'; import { SelectButtonModule } from 'primeng/selectbutton';
import { TagModule } from 'primeng/tag'; import { TagModule } from 'primeng/tag';
import { Product, ProductService } from '../service/product.service'; import { Product, ProductService } from '@/app/pages/service/product.service';
@Component({ @Component({
selector: 'app-list-demo', selector: 'app-list-demo',

View File

@@ -1,12 +1,12 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core'; import { Component, inject, OnInit, signal } from '@angular/core';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { CarouselModule } from 'primeng/carousel'; import { CarouselModule } from 'primeng/carousel';
import { GalleriaModule } from 'primeng/galleria'; import { GalleriaModule } from 'primeng/galleria';
import { ImageModule } from 'primeng/image'; import { ImageModule } from 'primeng/image';
import { TagModule } from 'primeng/tag'; import { TagModule } from 'primeng/tag';
import { PhotoService } from '../service/photo.service'; import { PhotoService } from '@/app/pages/service/photo.service';
import { Product, ProductService } from '../service/product.service'; import { Product, ProductService } from '@/app/pages/service/product.service';
@Component({ @Component({
selector: 'app-media-demo', selector: 'app-media-demo',
@@ -14,7 +14,7 @@ import { Product, ProductService } from '../service/product.service';
imports: [CommonModule, CarouselModule, ButtonModule, GalleriaModule, ImageModule, TagModule], imports: [CommonModule, CarouselModule, ButtonModule, GalleriaModule, ImageModule, TagModule],
template: `<div class="card"> template: `<div class="card">
<div class="font-semibold text-xl mb-4">Carousel</div> <div class="font-semibold text-xl mb-4">Carousel</div>
<p-carousel [value]="products" [numVisible]="3" [numScroll]="3" [circular]="false" [responsiveOptions]="carouselResponsiveOptions"> <p-carousel [value]="products()" [numVisible]="3" [numScroll]="3" [circular]="false" [responsiveOptions]="carouselResponsiveOptions">
<ng-template let-product #item> <ng-template let-product #item>
<div class="border border-surface rounded-border m-2 p-4"> <div class="border border-surface rounded-border m-2 p-4">
<div class="mb-4"> <div class="mb-4">
@@ -45,7 +45,7 @@ import { Product, ProductService } from '../service/product.service';
<div class="card"> <div class="card">
<div class="font-semibold text-xl mb-4">Galleria</div> <div class="font-semibold text-xl mb-4">Galleria</div>
<p-galleria [value]="images" [responsiveOptions]="galleriaResponsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5"> <p-galleria [value]="images()" [responsiveOptions]="galleriaResponsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
<ng-template #item let-item> <ng-template #item let-item>
<img [src]="item.itemImageSrc" style="width:100%" /> <img [src]="item.itemImageSrc" style="width:100%" />
</ng-template> </ng-template>
@@ -57,9 +57,13 @@ import { Product, ProductService } from '../service/product.service';
providers: [ProductService, PhotoService] providers: [ProductService, PhotoService]
}) })
export class MediaDemo implements OnInit { export class MediaDemo implements OnInit {
products!: Product[]; productService = inject(ProductService);
images!: any[]; photoService = inject(PhotoService);
products = signal<Product[]>([]);
images = signal<any[]>([]);
galleriaResponsiveOptions: any[] = [ galleriaResponsiveOptions: any[] = [
{ {
@@ -98,19 +102,9 @@ export class MediaDemo implements OnInit {
} }
]; ];
constructor(
private productService: ProductService,
private photoService: PhotoService
) {}
ngOnInit() { ngOnInit() {
this.productService.getProductsSmall().then((products) => { this.productService.getProductsSmall().then((products) => this.products.set(products));
this.products = products; this.photoService.getImages().then((images) => this.images.set(images));
});
this.photoService.getImages().then((images) => {
this.images = images;
});
} }
getSeverity(status: string) { getSeverity(status: string) {

View File

@@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common'; import {CommonModule} from '@angular/common';
import { Component } from '@angular/core'; import {Component} from '@angular/core';
import { FormsModule } from '@angular/forms'; import {FormsModule} from '@angular/forms';
import { MessageService, ToastMessageOptions } from 'primeng/api'; import {MessageService, ToastMessageOptions} from 'primeng/api';
import { ButtonModule } from 'primeng/button'; import {ButtonModule} from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext'; import {InputTextModule} from 'primeng/inputtext';
import { MessageModule } from 'primeng/message'; import {MessageModule} from 'primeng/message';
import { ToastModule } from 'primeng/toast'; import {ToastModule} from 'primeng/toast';
@Component({ @Component({
selector: 'app-messages-demo', selector: 'app-messages-demo',
@@ -25,13 +25,15 @@ import { ToastModule } from 'primeng/toast';
</div> </div>
<div class="font-semibold text-xl mt-4 mb-4">Inline</div> <div class="font-semibold text-xl mt-4 mb-4">Inline</div>
<div class="flex flex-col mb-4 gap-1"> <div class="flex mb-4 gap-1">
<input pInputText [(ngModel)]="username" placeholder="Username" aria-label="username" class="ng-dirty ng-invalid" /> <input pInputText [(ngModel)]="username" placeholder="Username" aria-label="username" class="ng-dirty ng-invalid" />
<p-message severity="error" variant="simple" size="small">Username is required</p-message> <p-message severity="error" size="small" styleClass="h-auto w-full " [pt]="pt">Username is required</p-message>
</div> </div>
<div class="flex flex-col flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
<input pInputText [(ngModel)]="email" placeholder="Email" aria-label="email" class="ng-dirty ng-invalid" /> <input pInputText [(ngModel)]="email" placeholder="Email" aria-label="email" class="ng-dirty ng-invalid" />
<p-message severity="error" variant="simple" size="small">Email is required</p-message> <p-message severity="error" size="small" styleClass="flex items-center text-center justify-center h-auto w-11" [pt]="pt">
<i class="pi pi-times-circle"></i>
</p-message>
</div> </div>
</div> </div>
</div> </div>
@@ -61,19 +63,39 @@ export class MessagesDemo {
constructor(private service: MessageService) {} constructor(private service: MessageService) {}
pt: any = {
contentWrapper: 'flex items-center'
};
showInfoViaToast() { showInfoViaToast() {
this.service.add({ severity: 'info', summary: 'Info Message', detail: 'PrimeNG rocks' }); this.service.add({
severity: 'info',
summary: 'Info Message',
detail: 'PrimeNG rocks'
});
} }
showWarnViaToast() { showWarnViaToast() {
this.service.add({ severity: 'warn', summary: 'Warn Message', detail: 'There are unsaved changes' }); this.service.add({
severity: 'warn',
summary: 'Warn Message',
detail: 'There are unsaved changes'
});
} }
showErrorViaToast() { showErrorViaToast() {
this.service.add({ severity: 'error', summary: 'Error Message', detail: 'Validation failed' }); this.service.add({
severity: 'error',
summary: 'Error Message',
detail: 'Validation failed'
});
} }
showSuccessViaToast() { showSuccessViaToast() {
this.service.add({ severity: 'success', summary: 'Success Message', detail: 'Message sent' }); this.service.add({
severity: 'success',
summary: 'Success Message',
detail: 'Message sent'
});
} }
} }

View File

@@ -1,22 +1,22 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import { ConfirmationService, MessageService } from 'primeng/api'; import {ConfirmationService, MessageService} from 'primeng/api';
import { ButtonModule } from 'primeng/button'; import {ButtonModule} from 'primeng/button';
import { DialogModule } from 'primeng/dialog'; import {DialogModule} from 'primeng/dialog';
import { ToastModule } from 'primeng/toast'; import {ToastModule} from 'primeng/toast';
import { DrawerModule } from 'primeng/drawer'; import {DrawerModule} from 'primeng/drawer';
import { Popover, PopoverModule } from 'primeng/popover'; import {Popover, PopoverModule} from 'primeng/popover';
import { ConfirmPopupModule } from 'primeng/confirmpopup'; import {ConfirmPopupModule} from 'primeng/confirmpopup';
import { InputTextModule } from 'primeng/inputtext'; import {InputTextModule} from 'primeng/inputtext';
import { FormsModule } from '@angular/forms'; import {FormsModule} from '@angular/forms';
import { TooltipModule } from 'primeng/tooltip'; import {TooltipModule} from 'primeng/tooltip';
import { TableModule } from 'primeng/table'; import {TableModule} from 'primeng/table';
import { Product, ProductService } from '../service/product.service'; import {Product, ProductService} from '@/app/pages/service/product.service';
@Component({ @Component({
selector: 'app-overlay-demo', selector: 'app-overlay-demo',
standalone: true, standalone: true,
imports: [ToastModule, DialogModule, ButtonModule, DrawerModule, PopoverModule, ConfirmPopupModule, InputTextModule, FormsModule, TooltipModule, TableModule, ToastModule], imports: [ToastModule, DialogModule, ButtonModule, DrawerModule, PopoverModule, ConfirmPopupModule, InputTextModule, FormsModule, TooltipModule, TableModule, ToastModule],
template: ` <div class="flex flex-col md:flex-row gap-8"> template: `<div class="flex flex-col md:flex-row gap-8">
<div class="md:w-1/2"> <div class="md:w-1/2">
<div class="card"> <div class="card">
<div class="font-semibold text-xl mb-4">Dialog</div> <div class="font-semibold text-xl mb-4">Dialog</div>
@@ -40,15 +40,23 @@ import { Product, ProductService } from '../service/product.service';
<p-table [value]="products" selectionMode="single" [(selection)]="selectedProduct" dataKey="id" [rows]="5" [paginator]="true" (onRowSelect)="onProductSelect(op2, $event)"> <p-table [value]="products" selectionMode="single" [(selection)]="selectedProduct" dataKey="id" [rows]="5" [paginator]="true" (onRowSelect)="onProductSelect(op2, $event)">
<ng-template #header> <ng-template #header>
<tr> <tr>
<th>Name</th> <th pSortableColumn="name" style="width: 33%;">
<th>Image</th> Name
<th>Price</th> <p-sortIcon field="name" />
</th>
<th style="width: 33%;">Image</th>
<th pSortableColumn="price" style="width: 33%;">
Price
<p-sortIcon field="price" />
</th>
</tr> </tr>
</ng-template> </ng-template>
<ng-template #body let-product> <ng-template #body let-product>
<tr [pSelectableRow]="product"> <tr [pSelectableRow]="product">
<td>{{ product.name }}</td> <td>{{ product.name }}</td>
<td><img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.name" class="w-16 shadow-sm" /></td> <td>
<img [src]="'/demo/images/product/' + product.image" [alt]="product.name" class="w-16 shadow-sm" />
</td>
<td>{{ product.price }}</td> <td>{{ product.price }}</td>
</tr> </tr>
</ng-template> </ng-template>
@@ -113,7 +121,7 @@ import { Product, ProductService } from '../service/product.service';
<div class="card"> <div class="card">
<div class="font-semibold text-xl mb-4">ConfirmPopup</div> <div class="font-semibold text-xl mb-4">ConfirmPopup</div>
<p-confirmpopup></p-confirmpopup> <p-confirmpopup key="confirm2"></p-confirmpopup>
<p-button #popup (click)="confirm($event)" icon="pi pi-check" label="Confirm" class="mr-2"></p-button> <p-button #popup (click)="confirm($event)" icon="pi pi-check" label="Confirm" class="mr-2"></p-button>
</div> </div>
@@ -122,7 +130,7 @@ import { Product, ProductService } from '../service/product.service';
<p-button label="Delete" icon="pi pi-trash" severity="danger" [style]="{ width: 'auto' }" (click)="openConfirmation()" /> <p-button label="Delete" icon="pi pi-trash" severity="danger" [style]="{ width: 'auto' }" (click)="openConfirmation()" />
<p-dialog header="Confirmation" [(visible)]="displayConfirmation" [style]="{ width: '350px' }" [modal]="true"> <p-dialog header="Confirmation" [(visible)]="displayConfirmation" [style]="{ width: '350px' }" [modal]="true">
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<i class="pi pi-exclamation-triangle mr-4" style="font-size: 2rem"> </i> <i class="pi pi-exclamation-triangle mr-6" style="font-size: 2rem"> </i>
<span>Are you sure you want to proceed?</span> <span>Are you sure you want to proceed?</span>
</div> </div>
<ng-template #footer> <ng-template #footer>
@@ -136,8 +144,6 @@ import { Product, ProductService } from '../service/product.service';
providers: [ConfirmationService, MessageService, ProductService] providers: [ConfirmationService, MessageService, ProductService]
}) })
export class OverlayDemo implements OnInit { export class OverlayDemo implements OnInit {
images: any[] = [];
display: boolean = false; display: boolean = false;
products: Product[] = []; products: Product[] = [];
@@ -164,28 +170,6 @@ export class OverlayDemo implements OnInit {
ngOnInit() { ngOnInit() {
this.productService.getProductsSmall().then((products) => (this.products = products)); this.productService.getProductsSmall().then((products) => (this.products = products));
this.images = [];
this.images.push({
source: 'assets/demo/images/sopranos/sopranos1.jpg',
thumbnail: 'assets/demo/images/sopranos/sopranos1_small.jpg',
title: 'Sopranos 1'
});
this.images.push({
source: 'assets/demo/images/sopranos/sopranos2.jpg',
thumbnail: 'assets/demo/images/sopranos/sopranos2_small.jpg',
title: 'Sopranos 2'
});
this.images.push({
source: 'assets/demo/images/sopranos/sopranos3.jpg',
thumbnail: 'assets/demo/images/sopranos/sopranos3_small.jpg',
title: 'Sopranos 3'
});
this.images.push({
source: 'assets/demo/images/sopranos/sopranos4.jpg',
thumbnail: 'assets/demo/images/sopranos/sopranos4_small.jpg',
title: 'Sopranos 4'
});
} }
confirm(event: Event) { confirm(event: Event) {
@@ -194,11 +178,27 @@ export class OverlayDemo implements OnInit {
target: event.target || new EventTarget(), target: event.target || new EventTarget(),
message: 'Are you sure that you want to proceed?', message: 'Are you sure that you want to proceed?',
icon: 'pi pi-exclamation-triangle', icon: 'pi pi-exclamation-triangle',
rejectButtonProps: {
label: 'Cancel',
severity: 'secondary',
outlined: true
},
acceptButtonProps: {
label: 'Save'
},
accept: () => { accept: () => {
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'You have accepted' }); this.messageService.add({
severity: 'info',
summary: 'Confirmed',
detail: 'You have accepted'
});
}, },
reject: () => { reject: () => {
this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' }); this.messageService.add({
severity: 'error',
summary: 'Rejected',
detail: 'You have rejected'
});
} }
}); });
} }
@@ -217,7 +217,12 @@ export class OverlayDemo implements OnInit {
onProductSelect(op: Popover, event: any) { onProductSelect(op: Popover, event: any) {
op.hide(); op.hide();
this.messageService.add({ severity: 'info', summary: 'Product Selected', detail: event?.data.name, life: 3000 }); this.messageService.add({
severity: 'info',
summary: 'Product Selected',
detail: event?.data.name,
life: 3000
});
} }
openConfirmation() { openConfirmation() {

View File

@@ -16,8 +16,8 @@ import { RippleModule } from 'primeng/ripple';
import { InputIconModule } from 'primeng/inputicon'; import { InputIconModule } from 'primeng/inputicon';
import { IconFieldModule } from 'primeng/iconfield'; import { IconFieldModule } from 'primeng/iconfield';
import { TagModule } from 'primeng/tag'; import { TagModule } from 'primeng/tag';
import { Customer, CustomerService, Representative } from '../service/customer.service'; import { Customer, CustomerService, Representative } from '@/app/pages/service/customer.service';
import { Product, ProductService } from '../service/product.service'; import { Product, ProductService } from '@/app/pages/service/product.service';
import {ObjectUtils} from "primeng/utils"; import {ObjectUtils} from "primeng/utils";
interface expandedRows { interface expandedRows {

View File

@@ -63,7 +63,7 @@ import {ButtonModule} from 'primeng/button';
</ng-template> </ng-template>
<ng-template #content let-event> <ng-template #content let-event>
<p-card [header]="event.status" [subheader]="event.date"> <p-card [header]="event.status" [subheader]="event.date">
<img *ngIf="event.image" [src]="'/images/product/' + event.image" [alt]="event.name" width="200" class="shadow" /> <img *ngIf="event.image" [src]="'/demo/images/product/' + event.image" [alt]="event.name" width="200" class="shadow" />
<p> <p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore sed consequuntur error repudiandae numquam deserunt quisquam repellat libero asperiores earum nam nobis, culpa ratione quam perferendis esse, Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore sed consequuntur error repudiandae numquam deserunt quisquam repellat libero asperiores earum nam nobis, culpa ratione quam perferendis esse,
cupiditate neque quas! cupiditate neque quas!

View File

@@ -1,10 +1,10 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, inject, OnInit, signal } from '@angular/core';
import { TreeNode } from 'primeng/api'; import { TreeNode } from 'primeng/api';
import { TreeModule } from 'primeng/tree'; import { TreeModule } from 'primeng/tree';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { TreeTableModule } from 'primeng/treetable'; import { TreeTableModule } from 'primeng/treetable';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NodeService } from '../service/node.service'; import { NodeService } from '@/app/pages/service/node.service';
@Component({ @Component({
selector: 'app-tree-demo', selector: 'app-tree-demo',
@@ -13,12 +13,12 @@ import { NodeService } from '../service/node.service';
template: ` template: `
<div class="card"> <div class="card">
<div class="font-semibold text-xl">Tree</div> <div class="font-semibold text-xl">Tree</div>
<p-tree [value]="treeValue" selectionMode="checkbox" [(selection)]="selectedTreeValue"></p-tree> <p-tree [value]="treeValue()" selectionMode="checkbox" [(selection)]="selectedTreeValue"></p-tree>
</div> </div>
<div class="card"> <div class="card">
<div class="font-semibold text-xl mb-4">TreeTable</div> <div class="font-semibold text-xl mb-4">TreeTable</div>
<p-treetable [value]="treeTableValue" [columns]="cols" selectionMode="checkbox" [(selectionKeys)]="selectedTreeTableValue" dataKey="key" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }"> <p-treetable [value]="treeTableValue()" [columns]="cols" selectionMode="checkbox" [(selectionKeys)]="selectedTreeTableValue" dataKey="key" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns> <ng-template #header let-columns>
<tr> <tr>
<th *ngFor="let col of columns"> <th *ngFor="let col of columns">
@@ -43,9 +43,9 @@ import { NodeService } from '../service/node.service';
providers: [NodeService] providers: [NodeService]
}) })
export class TreeDemo implements OnInit { export class TreeDemo implements OnInit {
treeValue: TreeNode[] = []; treeValue = signal<TreeNode[]>([]);
treeTableValue: TreeNode[] = []; treeTableValue = signal<TreeNode[]>([]);
selectedTreeValue: TreeNode[] = []; selectedTreeValue: TreeNode[] = [];
@@ -56,8 +56,8 @@ export class TreeDemo implements OnInit {
nodeService = inject(NodeService); nodeService = inject(NodeService);
ngOnInit() { ngOnInit() {
this.nodeService.getFiles().then((files) => (this.treeValue = files)); this.nodeService.getFiles().then((files) => this.treeValue.set(files));
this.nodeService.getTreeTableNodes().then((files: any) => (this.treeTableValue = files)); this.nodeService.getTreeTableNodes().then((files: any) => this.treeTableValue.set(files));
this.cols = [ this.cols = [
{ field: 'name', header: 'Name' }, { field: 'name', header: 'Name' },

1
src/assets Submodule

Submodule src/assets added at eaa70ece4c

View File

@@ -1,17 +0,0 @@
pre.app-code {
background-color: var(--code-background);
margin: 0 0 1rem 0;
padding: 0;
border-radius: var(--content-border-radius);
overflow: auto;
code {
color: var(--code-color);
padding: 1rem;
margin: 0;
line-height: 1.5;
display: block;
font-weight: semibold;
font-family: monaco, Consolas, monospace;
}
}

View File

@@ -1,2 +0,0 @@
@use './code.scss';
@use './flags/flags';

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -1,24 +0,0 @@
html {
height: 100%;
font-size: 14px;
}
body {
font-family: 'Lato', sans-serif;
color: var(--text-color);
background-color: var(--surface-ground);
margin: 0;
padding: 0;
min-height: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.2;
}
a {
text-decoration: none;
}
.layout-wrapper {
min-height: 100vh;
}

View File

@@ -1,8 +0,0 @@
.layout-footer {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 0 1rem 0;
gap: 0.5rem;
border-top: 1px solid var(--surface-border);
}

View File

@@ -1,17 +0,0 @@
.layout-main-container {
display: flex;
flex-direction: column;
min-height: 100vh;
justify-content: space-between;
padding: 6rem 2rem 0 2rem;
transition: margin-left var(--layout-section-transition-duration);
}
.layout-main {
flex: 1 1 auto;
padding-bottom: 2rem;
}
img {
max-width: none !important;
}

Some files were not shown because too many files have changed in this diff Show More