mirror of
https://github.com/booklore-app/booklore.git
synced 2026-02-18 00:17:53 +01:00
Improve Aesthetics of Login and Admin Setup Views
This commit is contained in:
committed by
Aditya Chandel
parent
8edf589f3d
commit
897a3c2fb6
@@ -8,6 +8,7 @@ import {ConfirmDialog} from 'primeng/confirmdialog';
|
||||
import {Toast} from 'primeng/toast';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {AuthInitializationService} from './auth-initialization-service';
|
||||
import {AppConfigService} from './core/service/app-config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -23,6 +24,7 @@ export class AppComponent implements OnInit {
|
||||
private bookService = inject(BookService);
|
||||
private rxStompService = inject(RxStompService);
|
||||
private eventService = inject(EventService);
|
||||
private appConfigService = inject(AppConfigService);
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
|
||||
@@ -1,41 +1,79 @@
|
||||
<div class="login-container" >
|
||||
<div class="login-wrapper">
|
||||
<h1 class="app-title">BookLore</h1>
|
||||
<p-card class="w-96">
|
||||
<ng-template pTemplate="title">
|
||||
<div class="text-center pb-2">Login</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden"
|
||||
style="background: linear-gradient(60deg, var(--primary-color) 0%, #1e3a8a 100%);">
|
||||
<div class="flex flex-col items-center justify-center -mt-48">
|
||||
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||
<div class="w-full bg-surface-0 dark:bg-surface-900 py-16 px-8 sm:px-16" style="border-radius: 53px">
|
||||
<div class="text-center mb-8">
|
||||
<svg class="mb-8 w-16 h-16 mx-auto" viewBox="0 0 126 126" fill="var(--primary-color)" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M59 4.79297C71.5051 11.5557 80 24.7854 80 40C80 40.5959 79.987 41.1888 79.9609 41.7783C79.8609 44.0406 81.7355 46 84 46C106.091 46 124 63.9086 124 86C124 108.091 106.091 126 84 126H10C4.47715 126 0 121.523 0 116V39.0068L0.0126953 38.9941C0.357624 25.0252 7.86506 12.8347 19 5.95215V63.832C19 64.8345 20.0676 65.4391 20.9121 64.9902L21.0771 64.8867L38.2227 52.3428C38.6819 52.0068 39.3064 52.0068 39.7656 52.3428L56.9229 64.8945L57.0879 64.998C57.9324 65.447 59 64.8423 59 63.8398V4.79297Z"/>
|
||||
<path
|
||||
d="M40 0C43.8745 0 47.6199 0.552381 51.1631 1.58008V50.9697L44.3926 46.0176L44.0879 45.8037C40.9061 43.6679 36.7098 43.7393 33.5957 46.0176L26.8369 50.9619V2.21875C30.9593 0.782634 35.3881 0 40 0Z"
|
||||
fill="white"/>
|
||||
</svg>
|
||||
<div class="text-3xl font-medium mb-4">Welcome to Booklore</div>
|
||||
<span class="text-muted-color font-medium">Sign in to continue</span>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-4" #loginForm="ngForm" (ngSubmit)="login()">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="username">Username</label>
|
||||
<input fluid pInputText id="username" [(ngModel)]="username" name="username" required/>
|
||||
<label for="username" class="text-xl font-medium">Username</label>
|
||||
<input
|
||||
pInputText
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
[(ngModel)]="username"
|
||||
placeholder="Enter your username"
|
||||
class="w-full md:w-[30rem]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="password">Password</label>
|
||||
<p-password fluid id="password" [(ngModel)]="password" name="password" [feedback]="false" required></p-password>
|
||||
<label for="password" class="text-xl font-medium">Password</label>
|
||||
<p-password
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
[(ngModel)]="password"
|
||||
[feedback]="false"
|
||||
placeholder="Enter your password"
|
||||
[toggleMask]="true"
|
||||
styleClass="mb-4"
|
||||
[fluid]="true"
|
||||
></p-password>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 mt-4">
|
||||
|
||||
<p-button
|
||||
type="submit"
|
||||
label="Login"
|
||||
icon="pi pi-sign-in"
|
||||
[disabled]="!loginForm.valid || !username || !password"
|
||||
styleClass="w-full"
|
||||
></p-button>
|
||||
|
||||
@if (errorMessage) {
|
||||
<p-message severity="error" text="{{ errorMessage }}"></p-message>
|
||||
}
|
||||
|
||||
@if (oidcEnabled) {
|
||||
<div class="flex items-center gap-2 my-2">
|
||||
<hr class="flex-grow" style="border: none; border-top: 1px solid var(--border-color);"/>
|
||||
<span class="text-gray-300">or</span>
|
||||
<hr class="flex-grow" style="border: none; border-top: 1px solid var(--border-color);"/>
|
||||
</div>
|
||||
|
||||
<p-button
|
||||
fluid
|
||||
severity="primary"
|
||||
type="submit"
|
||||
label="Login"
|
||||
icon="pi pi-sign-in"
|
||||
[disabled]="!loginForm.valid || !username || !password">
|
||||
</p-button>
|
||||
@if (errorMessage) {
|
||||
<p-message severity="error" text="{{ errorMessage }}"></p-message>
|
||||
}
|
||||
@if (oidcEnabled) {
|
||||
<div>
|
||||
<p-divider></p-divider>
|
||||
<p-button fluid severity="info" type="button" label="Login with {{oidcName}}" icon="pi pi-id-card" (click)="loginWithOidc()"></p-button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
severity="info"
|
||||
label="Login with {{ oidcName }}"
|
||||
icon="pi pi-id-card"
|
||||
(click)="loginWithOidc()"
|
||||
styleClass="w-full bg-gray-800 text-white hover:bg-gray-700"
|
||||
/>
|
||||
}
|
||||
</form>
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(to bottom right, #1e3a8a, #06b6d4, #3b82f6);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
transform: translateY(-25%);
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
text-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@ import {Component, inject, OnInit} from '@angular/core';
|
||||
import {AuthService} from '../../service/auth.service';
|
||||
import {Router} from '@angular/router';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
import {Card} from 'primeng/card';
|
||||
import {Password} from 'primeng/password';
|
||||
import {Button} from 'primeng/button';
|
||||
import {Message} from 'primeng/message';
|
||||
import {PrimeTemplate} from 'primeng/api';
|
||||
import {InputText} from 'primeng/inputtext';
|
||||
import {OAuthService} from 'angular-oauth2-oidc';
|
||||
import {Divider} from 'primeng/divider';
|
||||
import {AppSettingsService} from '../../service/app-settings.service';
|
||||
import {AppSettings} from '../../model/app-settings.model';
|
||||
import {Observable} from 'rxjs';
|
||||
@@ -20,14 +16,11 @@ import {filter, take} from 'rxjs/operators';
|
||||
selector: 'app-login',
|
||||
imports: [
|
||||
FormsModule,
|
||||
Card,
|
||||
Password,
|
||||
Button,
|
||||
Message,
|
||||
PrimeTemplate,
|
||||
InputText,
|
||||
Divider
|
||||
],
|
||||
InputText
|
||||
],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import {DOCUMENT, isPlatformBrowser} from '@angular/common';
|
||||
import {effect, inject, Injectable, PLATFORM_ID, signal} from '@angular/core';
|
||||
import {$t, updatePreset, updateSurfacePalette} from '@primeng/themes';
|
||||
import Aura from '@primeng/themes/aura';
|
||||
import {AppState} from '../model/app-state.model';
|
||||
|
||||
type ColorPalette = Record<string, string>;
|
||||
|
||||
interface Palette {
|
||||
name: string;
|
||||
palette: ColorPalette;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@@ -12,21 +21,129 @@ export class AppConfigService {
|
||||
platformId = inject(PLATFORM_ID);
|
||||
private initialized = false;
|
||||
|
||||
readonly surfaces: Palette[] = [
|
||||
{
|
||||
name: 'neutral',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fafafa',
|
||||
100: '#f5f5f5',
|
||||
200: '#e5e5e5',
|
||||
300: '#d4d4d4',
|
||||
400: '#a3a3a3',
|
||||
500: '#737373',
|
||||
600: '#525252',
|
||||
700: '#404040',
|
||||
800: '#262626',
|
||||
900: '#171717',
|
||||
950: '#0a0a0a'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'zinc',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fafafa',
|
||||
100: '#f4f4f5',
|
||||
200: '#e4e4e7',
|
||||
300: '#d4d4d8',
|
||||
400: '#a1a1aa',
|
||||
500: '#71717a',
|
||||
600: '#52525b',
|
||||
700: '#3f3f46',
|
||||
800: '#27272a',
|
||||
900: '#18181b',
|
||||
950: '#09090b'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'stone',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fafaf9',
|
||||
100: '#f5f5f4',
|
||||
200: '#e7e5e4',
|
||||
300: '#d6d3d1',
|
||||
400: '#a8a29e',
|
||||
500: '#78716c',
|
||||
600: '#57534e',
|
||||
700: '#44403c',
|
||||
800: '#292524',
|
||||
900: '#1c1917',
|
||||
950: '#0c0a09'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'soho',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#ececec',
|
||||
100: '#dedfdf',
|
||||
200: '#c4c4c6',
|
||||
300: '#adaeb0',
|
||||
400: '#97979b',
|
||||
500: '#7f8084',
|
||||
600: '#6a6b70',
|
||||
700: '#55565b',
|
||||
800: '#3f4046',
|
||||
900: '#2c2c34',
|
||||
950: '#16161d'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'viva',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#f3f3f3',
|
||||
100: '#e7e7e8',
|
||||
200: '#cfd0d0',
|
||||
300: '#b7b8b9',
|
||||
400: '#9fa1a1',
|
||||
500: '#87898a',
|
||||
600: '#6e7173',
|
||||
700: '#565a5b',
|
||||
800: '#3e4244',
|
||||
900: '#262b2c',
|
||||
950: '#0e1315'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ocean',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fbfcfc',
|
||||
100: '#F7F9F8',
|
||||
200: '#EFF3F2',
|
||||
300: '#DADEDD',
|
||||
400: '#B1B7B6',
|
||||
500: '#828787',
|
||||
600: '#5F7274',
|
||||
700: '#415B61',
|
||||
800: '#29444E',
|
||||
900: '#183240',
|
||||
950: '#0c1920'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
constructor() {
|
||||
const initialState = this.loadAppState();
|
||||
this.appState.set({...initialState});
|
||||
this.document.documentElement.classList.add('p-dark');
|
||||
effect(
|
||||
() => {
|
||||
const state = this.appState();
|
||||
if (!this.initialized || !state) {
|
||||
this.initialized = true;
|
||||
return;
|
||||
}
|
||||
this.saveAppState(state);
|
||||
},
|
||||
{allowSignalWrites: true}
|
||||
);
|
||||
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
this.onPresetChange();
|
||||
}
|
||||
|
||||
effect(() => {
|
||||
const state = this.appState();
|
||||
if (!this.initialized || !state) {
|
||||
this.initialized = true;
|
||||
return;
|
||||
}
|
||||
this.saveAppState(state);
|
||||
this.onPresetChange();
|
||||
}, {allowSignalWrites: true});
|
||||
}
|
||||
|
||||
private loadAppState(): AppState {
|
||||
@@ -48,4 +165,67 @@ export class AppConfigService {
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
|
||||
}
|
||||
}
|
||||
|
||||
private getSurfacePalette(surface: string): ColorPalette {
|
||||
return this.surfaces.find(s => s.name === surface)?.palette ?? {};
|
||||
}
|
||||
|
||||
getPresetExt(): object {
|
||||
const surfacePalette = this.getSurfacePalette(this.appState().surface ?? 'neutral');
|
||||
const primaryName = this.appState().primary ?? 'green';
|
||||
const presetPalette = (Aura.primitive ?? {}) as Record<string, ColorPalette>;
|
||||
const color = presetPalette[primaryName] ?? {};
|
||||
|
||||
if (primaryName === 'noir') {
|
||||
return {
|
||||
semantic: {
|
||||
primary: {...surfacePalette},
|
||||
colorScheme: {
|
||||
dark: {
|
||||
primary: {
|
||||
color: '{primary.50}',
|
||||
contrastColor: '{primary.950}',
|
||||
hoverColor: '{primary.200}',
|
||||
activeColor: '{primary.300}'
|
||||
},
|
||||
highlight: {
|
||||
background: '{primary.50}',
|
||||
focusBackground: '{primary.300}',
|
||||
color: '{primary.950}',
|
||||
focusColor: '{primary.950}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
semantic: {
|
||||
primary: color,
|
||||
colorScheme: {
|
||||
dark: {
|
||||
primary: {
|
||||
color: '{primary.400}',
|
||||
contrastColor: '{surface.900}',
|
||||
hoverColor: '{primary.300}',
|
||||
activeColor: '{primary.200}'
|
||||
},
|
||||
highlight: {
|
||||
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||
color: 'rgba(255,255,255,.87)',
|
||||
focusColor: 'rgba(255,255,255,.87)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onPresetChange(): void {
|
||||
const surfacePalette = this.getSurfacePalette(this.appState().surface ?? 'neutral');
|
||||
const preset = this.getPresetExt();
|
||||
$t().preset(Aura).preset(preset).surfacePalette(surfacePalette).use({useDefaultOptions: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
<div class="setup-container">
|
||||
<div class="setup-wrapper">
|
||||
<h1 class="app-title">BookLore</h1>
|
||||
<p-card class="w-96">
|
||||
<ng-template pTemplate="title">
|
||||
<div class="text-center pb-2">Initial Admin Setup</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden"
|
||||
style="background: linear-gradient(60deg, var(--primary-color) 0%, #1e3a8a 100%);">
|
||||
<div class="flex flex-col items-center justify-center -mt-48">
|
||||
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||
<div class="w-full bg-surface-0 dark:bg-surface-900 py-16 px-8 sm:px-16" style="border-radius: 53px">
|
||||
<div class="text-center mb-8">
|
||||
<svg class="mb-8 w-16 h-16 mx-auto" viewBox="0 0 126 126" fill="var(--primary-color)" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M59 4.79297C71.5051 11.5557 80 24.7854 80 40C80 40.5959 79.987 41.1888 79.9609 41.7783C79.8609 44.0406 81.7355 46 84 46C106.091 46 124 63.9086 124 86C124 108.091 106.091 126 84 126H10C4.47715 126 0 121.523 0 116V39.0068L0.0126953 38.9941C0.357624 25.0252 7.86506 12.8347 19 5.95215V63.832C19 64.8345 20.0676 65.4391 20.9121 64.9902L21.0771 64.8867L38.2227 52.3428C38.6819 52.0068 39.3064 52.0068 39.7656 52.3428L56.9229 64.8945L57.0879 64.998C57.9324 65.447 59 64.8423 59 63.8398V4.79297Z"/>
|
||||
<path
|
||||
d="M40 0C43.8745 0 47.6199 0.552381 51.1631 1.58008V50.9697L44.3926 46.0176L44.0879 45.8037C40.9061 43.6679 36.7098 43.7393 33.5957 46.0176L26.8369 50.9619V2.21875C30.9593 0.782634 35.3881 0 40 0Z"
|
||||
fill="white"/>
|
||||
</svg>
|
||||
<div class="text-3xl font-medium mb-4">Welcome to Booklore</div>
|
||||
<span class="text-muted-color text-xl">Setup Initial Admin Account</span>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-4" [formGroup]="setupForm" (ngSubmit)="onSubmit()">
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="name">Username</label>
|
||||
<input pInputText id="name" type="text" formControlName="username" required />
|
||||
<label for="username" class="text-xl font-medium">Username</label>
|
||||
<input
|
||||
pInputText
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
formControlName="username"
|
||||
placeholder="Enter your username"
|
||||
class="w-full md:w-[30rem]"
|
||||
/>
|
||||
@if (setupForm.get('username')?.invalid && setupForm.get('username')?.touched) {
|
||||
<small class="text-red-500">
|
||||
Username is required.
|
||||
@@ -19,8 +36,16 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="name">Name</label>
|
||||
<input pInputText id="name" type="text" formControlName="name" required />
|
||||
<label for="name" class="text-xl font-medium">Name</label>
|
||||
<input
|
||||
pInputText
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
formControlName="name"
|
||||
placeholder="Enter your full name"
|
||||
class="w-full md:w-[30rem]"
|
||||
/>
|
||||
@if (setupForm.get('name')?.invalid && setupForm.get('name')?.touched) {
|
||||
<small class="text-red-500">
|
||||
Name is required.
|
||||
@@ -29,8 +54,16 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="email">Email</label>
|
||||
<input pInputText id="email" type="email" formControlName="email" required />
|
||||
<label for="email" class="text-xl font-medium">Email</label>
|
||||
<input
|
||||
pInputText
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
formControlName="email"
|
||||
placeholder="Enter your email"
|
||||
class="w-full md:w-[30rem]"
|
||||
/>
|
||||
@if (setupForm.get('email')?.invalid && setupForm.get('email')?.touched) {
|
||||
<small class="text-red-500">
|
||||
Valid email is required.
|
||||
@@ -39,8 +72,16 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="password">Password</label>
|
||||
<p-password fluid id="password" formControlName="password" [feedback]="false" required></p-password>
|
||||
<label for="password" class="text-xl font-medium">Password</label>
|
||||
<input
|
||||
pInputText
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
formControlName="password"
|
||||
placeholder="Enter new password"
|
||||
class="w-full md:w-[30rem]"
|
||||
/>
|
||||
@if (setupForm.get('password')?.invalid && setupForm.get('password')?.touched) {
|
||||
<small class="text-red-500">
|
||||
Minimum 6 characters required.
|
||||
@@ -49,11 +90,15 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 mt-4">
|
||||
<p-button fluid severity="success" type="submit"
|
||||
<p-button
|
||||
fluid
|
||||
type="submit"
|
||||
[label]="loading ? 'Creating…' : 'Create Admin Account'"
|
||||
[disabled]="loading || setupForm.invalid"
|
||||
icon="pi pi-user-plus">
|
||||
icon="pi pi-user-plus"
|
||||
>
|
||||
</p-button>
|
||||
|
||||
@if (error) {
|
||||
<p-message severity="error" text="{{ error }}"></p-message>
|
||||
}
|
||||
@@ -62,7 +107,7 @@
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
.setup-container {
|
||||
@apply flex items-center justify-center min-h-screen bg-gradient-to-br from-blue-900 via-cyan-600 to-blue-400 relative;
|
||||
}
|
||||
|
||||
.setup-wrapper {
|
||||
@apply flex flex-col items-center gap-6 absolute top-1/4 transform -translate-y-1/4;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
@apply text-5xl font-extrabold text-white drop-shadow-lg;
|
||||
text-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {CommonModule, isPlatformBrowser} from '@angular/common';
|
||||
import {Component, computed, effect, inject, OnInit, PLATFORM_ID} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, computed, effect, inject} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {$t, updatePreset, updateSurfacePalette} from '@primeng/themes';
|
||||
import {$t} from '@primeng/themes';
|
||||
import Aura from '@primeng/themes/aura';
|
||||
import {ButtonModule} from 'primeng/button';
|
||||
import {PrimeNG} from 'primeng/config';
|
||||
import {InputSwitchModule} from 'primeng/inputswitch';
|
||||
import {RadioButtonModule} from 'primeng/radiobutton';
|
||||
import {ToggleSwitchModule} from 'primeng/toggleswitch';
|
||||
@@ -25,155 +24,24 @@ interface Palette {
|
||||
host: {
|
||||
class: 'config-panel hidden'
|
||||
},
|
||||
imports: [CommonModule, FormsModule, InputSwitchModule, ButtonModule, RadioButtonModule, ToggleSwitchModule]
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
InputSwitchModule,
|
||||
ButtonModule,
|
||||
RadioButtonModule,
|
||||
ToggleSwitchModule
|
||||
]
|
||||
})
|
||||
export class ThemeConfiguratorComponent implements OnInit {
|
||||
readonly config = inject(PrimeNG);
|
||||
export class ThemeConfiguratorComponent {
|
||||
readonly configService = inject(AppConfigService);
|
||||
readonly platformId = inject(PLATFORM_ID);
|
||||
readonly faviconService = inject(FaviconService);
|
||||
|
||||
readonly surfaces: Palette[] = [
|
||||
{
|
||||
name: 'slate',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#f8fafc',
|
||||
100: '#f1f5f9',
|
||||
200: '#e2e8f0',
|
||||
300: '#cbd5e1',
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#334155',
|
||||
800: '#1e293b',
|
||||
900: '#0f172a',
|
||||
950: '#020617'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'gray',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#f9fafb',
|
||||
100: '#f3f4f6',
|
||||
200: '#e5e7eb',
|
||||
300: '#d1d5db',
|
||||
400: '#9ca3af',
|
||||
500: '#6b7280',
|
||||
600: '#4b5563',
|
||||
700: '#374151',
|
||||
800: '#1f2937',
|
||||
900: '#111827',
|
||||
950: '#030712'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'zinc',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fafafa',
|
||||
100: '#f4f4f5',
|
||||
200: '#e4e4e7',
|
||||
300: '#d4d4d8',
|
||||
400: '#a1a1aa',
|
||||
500: '#71717a',
|
||||
600: '#52525b',
|
||||
700: '#3f3f46',
|
||||
800: '#27272a',
|
||||
900: '#18181b',
|
||||
950: '#09090b'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'neutral',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fafafa',
|
||||
100: '#f5f5f5',
|
||||
200: '#e5e5e5',
|
||||
300: '#d4d4d4',
|
||||
400: '#a3a3a3',
|
||||
500: '#737373',
|
||||
600: '#525252',
|
||||
700: '#404040',
|
||||
800: '#262626',
|
||||
900: '#171717',
|
||||
950: '#0a0a0a'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'stone',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fafaf9',
|
||||
100: '#f5f5f4',
|
||||
200: '#e7e5e4',
|
||||
300: '#d6d3d1',
|
||||
400: '#a8a29e',
|
||||
500: '#78716c',
|
||||
600: '#57534e',
|
||||
700: '#44403c',
|
||||
800: '#292524',
|
||||
900: '#1c1917',
|
||||
950: '#0c0a09'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'soho',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#ececec',
|
||||
100: '#dedfdf',
|
||||
200: '#c4c4c6',
|
||||
300: '#adaeb0',
|
||||
400: '#97979b',
|
||||
500: '#7f8084',
|
||||
600: '#6a6b70',
|
||||
700: '#55565b',
|
||||
800: '#3f4046',
|
||||
900: '#2c2c34',
|
||||
950: '#16161d'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'viva',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#f3f3f3',
|
||||
100: '#e7e7e8',
|
||||
200: '#cfd0d0',
|
||||
300: '#b7b8b9',
|
||||
400: '#9fa1a1',
|
||||
500: '#87898a',
|
||||
600: '#6e7173',
|
||||
700: '#565a5b',
|
||||
800: '#3e4244',
|
||||
900: '#262b2c',
|
||||
950: '#0e1315'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ocean',
|
||||
palette: {
|
||||
0: '#ffffff',
|
||||
50: '#fbfcfc',
|
||||
100: '#F7F9F8',
|
||||
200: '#EFF3F2',
|
||||
300: '#DADEDD',
|
||||
400: '#B1B7B6',
|
||||
500: '#828787',
|
||||
600: '#5F7274',
|
||||
700: '#415B61',
|
||||
800: '#29444E',
|
||||
900: '#183240',
|
||||
950: '#0c1920'
|
||||
}
|
||||
}
|
||||
];
|
||||
readonly surfaces = this.configService.surfaces;
|
||||
|
||||
readonly selectedPrimaryColor = computed(() => this.configService.appState().primary);
|
||||
readonly selectedSurfaceColor = computed(() => this.configService.appState().surface);
|
||||
|
||||
readonly faviconColor = computed(() => {
|
||||
const name = this.selectedPrimaryColor() ?? 'green';
|
||||
const presetPalette = (Aura.primitive ?? {}) as Record<string, ColorPalette>;
|
||||
@@ -196,84 +64,11 @@ export class ThemeConfiguratorComponent implements OnInit {
|
||||
);
|
||||
});
|
||||
|
||||
ngOnInit(): void {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
this.onPresetChange();
|
||||
}
|
||||
}
|
||||
|
||||
getPresetExt(): object {
|
||||
const color = this.primaryColors().find(c => c.name === this.selectedPrimaryColor());
|
||||
const surface = this.surfaces.find(s => s.name === this.selectedSurfaceColor());
|
||||
|
||||
if (!color) return {};
|
||||
if (color.name === 'noir') {
|
||||
return {
|
||||
semantic: {
|
||||
primary: {...(surface?.palette ?? {})},
|
||||
colorScheme: {
|
||||
dark: {
|
||||
primary: {
|
||||
color: '{primary.50}',
|
||||
contrastColor: '{primary.950}',
|
||||
hoverColor: '{primary.200}',
|
||||
activeColor: '{primary.300}'
|
||||
},
|
||||
highlight: {
|
||||
background: '{primary.50}',
|
||||
focusBackground: '{primary.300}',
|
||||
color: '{primary.950}',
|
||||
focusColor: '{primary.950}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
semantic: {
|
||||
primary: color.palette,
|
||||
colorScheme: {
|
||||
dark: {
|
||||
primary: {
|
||||
color: '{primary.400}',
|
||||
contrastColor: '{surface.900}',
|
||||
hoverColor: '{primary.300}',
|
||||
activeColor: '{primary.200}'
|
||||
},
|
||||
highlight: {
|
||||
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||
color: 'rgba(255,255,255,.87)',
|
||||
focusColor: 'rgba(255,255,255,.87)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
updateColors(event: Event, type: 'primary' | 'surface', color: { name: string; palette?: ColorPalette }) {
|
||||
this.configService.appState.update((state) => ({
|
||||
...state,
|
||||
[type]: color.name
|
||||
}));
|
||||
this.applyTheme(type, color);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
applyTheme(type: 'primary' | 'surface', color: { name: string; palette?: ColorPalette }) {
|
||||
if (type === 'primary') {
|
||||
updatePreset(this.getPresetExt());
|
||||
} else if (type === 'surface') {
|
||||
updateSurfacePalette(color.palette ?? {});
|
||||
}
|
||||
}
|
||||
|
||||
onPresetChange(): void {
|
||||
const surface = this.surfaces.find(s => s.name === this.selectedSurfaceColor());
|
||||
const surfacePalette = surface?.palette ?? {};
|
||||
$t().preset(Aura).preset(this.getPresetExt()).surfacePalette(surfacePalette).use({useDefaultOptions: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@ import {initializeAuthFactory} from './app/auth-initializer';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: themeInitializer,
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: websocketInitializer,
|
||||
@@ -57,10 +52,3 @@ bootstrapApplication(AppComponent, {
|
||||
})
|
||||
]
|
||||
}).catch(err => console.error(err));
|
||||
|
||||
export function themeInitializer(): () => void {
|
||||
return () => {
|
||||
document.documentElement.classList.add('p-dark');
|
||||
document.body.classList.add('p-dark');
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user