This commit is contained in:
Robbie Davis 2025-08-08 08:14:08 -04:00
parent edf123a550
commit be914f36cf
5 changed files with 3941 additions and 342 deletions

View File

@ -33,7 +33,15 @@
<ng-template #noData>
<app-no-data
variant="empty-library"
[isDarkTheme]="isDarkTheme">
size="medium"
animationType="pulse"
backgroundTheme="random"
[showActionButton]="true"
[title]="'Your Library is Empty'"
[subtitle]=""
[message]="'Start your collection by adding your books, manga, or comics. Configure your library settings to get started.'"
[actionButtonText]="'Configure Library'"
(actionButtonClick)="handleNoDataActionClick()">
</app-no-data>
</ng-template>
</app-card-detail-layout>

View File

@ -353,4 +353,11 @@ export class LibraryDetailComponent implements OnInit {
}
trackByIdentity = (index: number, item: Series) => `${item.id}_${item.name}_${item.localizedName}_${item.pagesRead}`;
handleNoDataActionClick(): void {
// Get the current library and open the edit modal
this.libraryService.getLibrary(this.libraryId).subscribe(library => {
this.actionService.editLibrary(library);
});
}
}

View File

@ -1,35 +1,340 @@
<ng-container *transloco="let t">
<div [class]="containerClasses" [ngStyle]="containerStyles">
<!-- Main illustration container -->
<div class="illustration-container">
<!-- Background decorative elements -->
<div class="background-elements">
<div class="floating-shapes">
<div class="shape shape-1"></div>
<div class="shape shape-2"></div>
<div class="shape shape-3"></div>
<div class="shape shape-4"></div>
<div class="shape shape-5"></div>
</div>
</div>
<!-- Main content container -->
<div class="content-wrapper">
<!-- Central empty library visualization -->
<div class="empty-library-visual">
<!-- Library shelf -->
<div class="library-shelf">
<div class="shelf-interior">
<div class="empty-books-placeholder">
<div class="book-spine"></div>
<div class="book-spine"></div>
<div class="book-spine"></div>
<div class="book-spine"></div>
<div class="book-spine"></div>
<!-- Icon/Illustration section -->
<div class="icon-section">
<div class="icon-container">
<i [class]="iconClasses"></i>
<!-- Background elements for empty-library variant -->
<div class="background-elements" *ngIf="effectiveConfig.variant === 'empty-library' && getCurrentBackgroundTheme !== 'none'">
<!-- Fantasy background elements -->
<div class="fantasy-background" *ngIf="getCurrentBackgroundTheme === 'fantasy'">
<div class="magical-orb orb-1"></div>
<div class="magical-orb orb-2"></div>
<div class="magical-orb orb-3"></div>
<div class="magical-orb orb-4"></div>
<div class="magical-orb orb-5"></div>
<div class="floating-rune rune-1"></div>
<div class="floating-rune rune-2"></div>
<div class="floating-rune rune-3"></div>
<div class="floating-rune rune-4"></div>
<div class="floating-rune rune-5"></div>
<div class="magical-dust dust-1"></div>
<div class="magical-dust dust-2"></div>
<div class="magical-dust dust-3"></div>
<div class="magical-dust dust-4"></div>
<div class="magical-dust dust-5"></div>
</div>
<!-- Sci-Fi background elements -->
<div class="scifi-background" *ngIf="getCurrentBackgroundTheme === 'scifi'">
<div class="energy-field field-1"></div>
<div class="energy-field field-2"></div>
<div class="energy-field field-3"></div>
<div class="energy-field field-4"></div>
<div class="energy-field field-5"></div>
<div class="floating-circuit circuit-1"></div>
<div class="floating-circuit circuit-2"></div>
<div class="floating-circuit circuit-3"></div>
<div class="floating-circuit circuit-4"></div>
<div class="floating-circuit circuit-5"></div>
<div class="data-particle particle-1"></div>
<div class="data-particle particle-2"></div>
<div class="data-particle particle-3"></div>
<div class="data-particle particle-4"></div>
<div class="data-particle particle-5"></div>
</div>
<!-- Comic Book background elements -->
<div class="comic-background" *ngIf="getCurrentBackgroundTheme === 'comic-book'">
<div class="speech-bubble bubble-1">"</div>
<div class="speech-bubble bubble-2">"</div>
<div class="speech-bubble bubble-3">"</div>
<div class="speech-bubble bubble-4">"</div>
<div class="speech-bubble bubble-5">"</div>
<div class="comic-star star-1"></div>
<div class="comic-star star-2"></div>
<div class="comic-star star-3"></div>
<div class="comic-star star-4"></div>
<div class="comic-star star-5"></div>
<div class="pow-effect pow-1">!</div>
<div class="pow-effect pow-2">!</div>
<div class="pow-effect pow-3">!</div>
<div class="comic-dot dot-1"></div>
<div class="comic-dot dot-2"></div>
<div class="comic-dot dot-3"></div>
<div class="comic-dot dot-4"></div>
<div class="comic-dot dot-5"></div>
</div>
<!-- Manga background elements -->
<div class="manga-background" *ngIf="getCurrentBackgroundTheme === 'manga'">
<div class="cherry-blossom blossom-1"></div>
<div class="cherry-blossom blossom-2"></div>
<div class="cherry-blossom blossom-3"></div>
<div class="cherry-blossom blossom-4"></div>
<div class="cherry-blossom blossom-5"></div>
<div class="manga-panel panel-1"></div>
<div class="manga-panel panel-2"></div>
<div class="manga-panel panel-3"></div>
<div class="manga-panel panel-4"></div>
<div class="manga-panel panel-5"></div>
<div class="manga-speech-bubble manga-bubble-1">"</div>
<div class="manga-speech-bubble manga-bubble-2">"</div>
<div class="manga-speech-bubble manga-bubble-3">"</div>
<div class="manga-star manga-star-1"></div>
<div class="manga-star manga-star-2"></div>
<div class="manga-star manga-star-3"></div>
<div class="manga-line line-1"></div>
<div class="manga-line line-2"></div>
<div class="manga-line line-3"></div>
<div class="manga-line line-4"></div>
<div class="manga-line line-5"></div>
</div>
<!-- Thriller background elements -->
<div class="thriller-background" *ngIf="getCurrentBackgroundTheme === 'thriller'">
<div class="shadow-figure shadow-1"></div>
<div class="shadow-figure shadow-2"></div>
<div class="shadow-figure shadow-3"></div>
<div class="shadow-figure shadow-4"></div>
<div class="shadow-figure shadow-5"></div>
<div class="fog-cloud fog-1"></div>
<div class="fog-cloud fog-2"></div>
<div class="fog-cloud fog-3"></div>
<div class="fog-cloud fog-4"></div>
<div class="fog-cloud fog-5"></div>
<div class="lightning-bolt bolt-1"></div>
<div class="lightning-bolt bolt-2"></div>
<div class="lightning-bolt bolt-3"></div>
<div class="mysterious-eye eye-1"></div>
<div class="mysterious-eye eye-2"></div>
<div class="mysterious-eye eye-3"></div>
<div class="dark-whisper whisper-1">~</div>
<div class="dark-whisper whisper-2">~</div>
<div class="dark-whisper whisper-3">~</div>
<div class="thriller-line line-1"></div>
<div class="thriller-line line-2"></div>
<div class="thriller-line line-3"></div>
<div class="thriller-line line-4"></div>
<div class="thriller-line line-5"></div>
</div>
<!-- Mystery background elements -->
<div class="mystery-background" *ngIf="getCurrentBackgroundTheme === 'mystery'">
<div class="magnifying-glass glass-1">🔍</div>
<div class="magnifying-glass glass-2">🔍</div>
<div class="magnifying-glass glass-3">🔍</div>
<div class="footprint footprint-1"></div>
<div class="footprint footprint-2"></div>
<div class="footprint footprint-3"></div>
<div class="question-mark mark-1">?</div>
<div class="question-mark mark-2">?</div>
<div class="question-mark mark-3">?</div>
<div class="mystery-clue clue-1"></div>
<div class="mystery-clue clue-2"></div>
<div class="mystery-clue clue-3"></div>
<div class="detective-hat hat-1"></div>
<div class="detective-hat hat-2"></div>
<div class="mystery-smoke smoke-1">~</div>
<div class="mystery-smoke smoke-2">~</div>
<div class="mystery-smoke smoke-3">~</div>
<div class="mystery-line line-1"></div>
<div class="mystery-line line-2"></div>
<div class="mystery-line line-3"></div>
<div class="mystery-line line-4"></div>
<div class="mystery-line line-5"></div>
</div>
<!-- Romance background elements -->
<div class="romance-background" *ngIf="getCurrentBackgroundTheme === 'romance'">
<div class="rose rose-1"></div>
<div class="rose rose-2"></div>
<div class="rose rose-3"></div>
<div class="rose rose-4"></div>
<div class="rose rose-5"></div>
<div class="heart heart-1"></div>
<div class="heart heart-2"></div>
<div class="heart heart-3"></div>
<div class="heart heart-4"></div>
<div class="heart heart-5"></div>
<div class="cupid-arrow arrow-1"></div>
<div class="cupid-arrow arrow-2"></div>
<div class="cupid-arrow arrow-3"></div>
<div class="romance-star star-1"></div>
<div class="romance-star star-2"></div>
<div class="romance-star star-3"></div>
<div class="romance-star star-4"></div>
<div class="romance-star star-5"></div>
<div class="romance-sparkle sparkle-1"></div>
<div class="romance-sparkle sparkle-2"></div>
<div class="romance-sparkle sparkle-3"></div>
<div class="romance-sparkle sparkle-4"></div>
<div class="romance-sparkle sparkle-5"></div>
<div class="romance-line line-1"></div>
<div class="romance-line line-2"></div>
<div class="romance-line line-3"></div>
<div class="romance-line line-4"></div>
<div class="romance-line line-5"></div>
</div>
<!-- Adventure background elements -->
<div class="adventure-background" *ngIf="getCurrentBackgroundTheme === 'adventure'">
<div class="compass compass-1"></div>
<div class="compass compass-2"></div>
<div class="compass compass-3"></div>
<div class="map-icon map-1"></div>
<div class="map-icon map-2"></div>
<div class="map-icon map-3"></div>
<div class="treasure-chest chest-1"></div>
<div class="treasure-chest chest-2"></div>
<div class="treasure-chest chest-3"></div>
<div class="mountain-icon mountain-1"></div>
<div class="mountain-icon mountain-2"></div>
<div class="mountain-icon mountain-3"></div>
<div class="adventure-star star-1"></div>
<div class="adventure-star star-2"></div>
<div class="adventure-star star-3"></div>
<div class="adventure-star star-4"></div>
<div class="adventure-star star-5"></div>
<div class="adventure-sparkle sparkle-1"></div>
<div class="adventure-sparkle sparkle-2"></div>
<div class="adventure-sparkle sparkle-3"></div>
<div class="adventure-sparkle sparkle-4"></div>
<div class="adventure-sparkle sparkle-5"></div>
<div class="adventure-line line-1"></div>
<div class="adventure-line line-2"></div>
<div class="adventure-line line-3"></div>
<div class="adventure-line line-4"></div>
<div class="adventure-line line-5"></div>
</div>
<!-- Horror background elements -->
<div class="horror-background" *ngIf="getCurrentBackgroundTheme === 'horror'">
<div class="ghost ghost-1">~</div>
<div class="ghost ghost-2">~</div>
<div class="ghost ghost-3">~</div>
<div class="skull skull-1"></div>
<div class="skull skull-2"></div>
<div class="skull skull-3"></div>
<div class="spider spider-1">×</div>
<div class="spider spider-2">×</div>
<div class="spider spider-3">×</div>
<div class="bat bat-1"></div>
<div class="bat bat-2"></div>
<div class="bat bat-3"></div>
<div class="horror-star star-1"></div>
<div class="horror-star star-2"></div>
<div class="horror-star star-3"></div>
<div class="horror-star star-4"></div>
<div class="horror-star star-5"></div>
<div class="horror-sparkle sparkle-1"></div>
<div class="horror-sparkle sparkle-2"></div>
<div class="horror-sparkle sparkle-3"></div>
<div class="horror-sparkle sparkle-4"></div>
<div class="horror-sparkle sparkle-5"></div>
<div class="horror-line line-1"></div>
<div class="horror-line line-2"></div>
<div class="horror-line line-3"></div>
<div class="horror-line line-4"></div>
<div class="horror-line line-5"></div>
</div>
</div>
</div>
<!-- Animated elements based on variant -->
<div class="variant-specific-elements">
<!-- Empty Library Animation -->
<div class="library-animation" *ngIf="effectiveConfig.variant === 'empty-library'">
<div class="bookshelf" #bookshelf>
<div class="shelf shelf-1">
<div class="book-placeholder book-1"></div>
<div class="book-placeholder book-2"></div>
<div class="book-placeholder book-3"></div>
</div>
</div>
</div>
<!-- Search Animation -->
<div class="search-animation" *ngIf="effectiveConfig.variant === 'search-no-results'">
<div class="search-magnifier">
<div class="magnifier-glass"></div>
<div class="magnifier-handle"></div>
</div>
<div class="search-dots">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div>
<!-- Loading Animation -->
<div class="loading-animation" *ngIf="effectiveConfig.variant === 'loading'">
<div class="loading-spinner">
<div class="spinner-ring"></div>
<div class="spinner-ring"></div>
<div class="spinner-ring"></div>
</div>
</div>
<!-- Achievement Animation -->
<div class="achievement-animation" *ngIf="effectiveConfig.variant === 'achievement'">
<div class="confetti-container">
<div class="confetti-piece" *ngFor="let i of [1,2,3,4,5,6,7,8]"></div>
</div>
</div>
<!-- Error Animation -->
<div class="error-animation" *ngIf="effectiveConfig.variant === 'error'">
<div class="error-waves">
<div class="wave"></div>
<div class="wave"></div>
<div class="wave"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Text content section -->
<div class="text-section">
<h2 class="title" *ngIf="shouldShowTitle">{{ effectiveTitle }}</h2>
<p class="subtitle" *ngIf="shouldShowSubtitle">{{ effectiveSubtitle }}</p>
<p class="message" *ngIf="shouldShowMessage">{{ effectiveMessage }}</p>
</div>
<!-- Action button section -->
<div class="action-section" *ngIf="effectiveConfig.showActionButton">
<button
class="btn btn-outline-primary action-button"
(click)="onActionButtonClick()">
<i *ngIf="effectiveActionButtonIcon" [class]="effectiveActionButtonIcon"></i>
{{ effectiveActionButtonText }}
</button>
</div>
</div>
<!-- Text content -->
<div class="content-text">
<h2 class="main-title">{{ effectiveTitle }}</h2>
<p class="subtitle">{{ effectiveSubtitle }}</p>
<div class="action-hint">
<span class="hint-text">{{ effectiveMessage }}</span>
<!-- Interactive elements -->
<div class="interactive-elements">
<div class="particle-field">
<div class="particle" *ngFor="let i of [1,2,3,4,5,6,7,8,9,10]"></div>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslocoDirective } from '@jsverse/transloco';
@ -6,11 +6,32 @@ export interface NoDataConfig {
title?: string;
subtitle?: string;
message?: string;
variant?: 'default' | 'search' | 'filter' | 'empty-library' | 'maintenance';
variant?: NoDataVariant;
size?: NoDataSize;
showActionButton?: boolean;
actionButtonText?: string;
actionButtonIcon?: string;
customIcon?: string;
size?: 'small' | 'medium' | 'large';
animationType?: AnimationType;
theme?: 'light' | 'dark' | 'auto';
backgroundTheme?: 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror' | 'random' | 'none';
}
export type NoDataVariant =
| 'empty-library'
| 'search-no-results'
| 'filter-no-results'
| 'maintenance'
| 'error'
| 'loading'
| 'welcome'
| 'achievement'
| 'custom';
export type NoDataSize = 'small' | 'medium' | 'large' | 'fullscreen';
export type AnimationType = 'floating' | 'pulse' | 'wave' | 'bounce' | 'fade' | 'none';
@Component({
selector: 'app-no-data',
standalone: true,
@ -18,92 +39,210 @@ export interface NoDataConfig {
templateUrl: './no-data.component.html',
styleUrls: ['./no-data.component.scss']
})
export class NoDataComponent {
// Basic customization inputs
export class NoDataComponent implements OnInit {
// Basic inputs
@Input() title?: string;
@Input() subtitle?: string;
@Input() message?: string;
@Input() variant: 'default' | 'search' | 'filter' | 'empty-library' | 'maintenance' = 'default';
@Input() size: 'small' | 'medium' | 'large' = 'medium';
@Input() isDarkTheme: boolean = false;
@Input() variant: NoDataVariant = 'empty-library';
@Input() size: NoDataSize = 'medium';
@Input() animationType: AnimationType = 'floating';
@Input() theme: 'light' | 'dark' | 'auto' = 'auto';
@Input() showActionButton: boolean = false;
@Input() actionButtonText?: string;
@Input() actionButtonIcon?: string;
@Input() customIcon?: string;
@Input() customClass?: string;
@Input() minHeight?: number;
@Input() backgroundTheme?: 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror' | 'random' | 'none';
// Output for action button click
@Output() actionButtonClick = new EventEmitter<void>();
// Background theme randomization
private currentBackgroundTheme: 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror' | 'none' = 'fantasy';
// Available background themes for randomization
private readonly availableBackgroundThemes: Array<'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror'> = [
'fantasy',
'scifi',
'comic-book',
'manga',
'thriller',
'mystery',
'romance',
'adventure',
'horror'
];
// Configuration object (alternative to individual inputs)
@Input() config?: NoDataConfig;
// Computed properties based on variant and config
// Computed properties
get effectiveConfig(): NoDataConfig {
return {
title: this.config?.title || this.title,
subtitle: this.config?.subtitle || this.subtitle,
message: this.config?.message || this.message,
variant: this.config?.variant || this.variant,
size: this.config?.size || this.size,
showActionButton: this.config?.showActionButton ?? this.showActionButton,
actionButtonText: this.config?.actionButtonText || this.actionButtonText,
actionButtonIcon: this.config?.actionButtonIcon || this.actionButtonIcon,
customIcon: this.config?.customIcon || this.customIcon,
animationType: this.config?.animationType || this.animationType,
theme: this.config?.theme || this.theme,
backgroundTheme: this.config?.backgroundTheme || this.backgroundTheme || 'random'
};
}
get effectiveTitle(): string {
if (this.config?.title || this.title) {
return this.config?.title || this.title || '';
if (this.effectiveConfig.title !== undefined && this.effectiveConfig.title !== '') {
return this.effectiveConfig.title;
}
switch (this.variant) {
case 'search':
switch (this.effectiveConfig.variant) {
case 'search-no-results':
return 'No Results Found';
case 'filter':
case 'filter-no-results':
return 'No Items Match Your Filters';
case 'empty-library':
return 'Your Library Awaits';
case 'maintenance':
return 'Under Maintenance';
case 'error':
return 'Something Went Wrong';
case 'loading':
return 'Loading...';
case 'welcome':
return 'Welcome to Your Library';
case 'achievement':
return 'Achievement Unlocked!';
default:
return 'Nothing Here Yet';
}
}
get shouldShowTitle(): boolean {
return this.effectiveConfig.title !== undefined && this.effectiveConfig.title !== '';
}
get effectiveSubtitle(): string {
if (this.config?.subtitle || this.subtitle) {
return this.config?.subtitle || this.subtitle || '';
if (this.effectiveConfig.subtitle !== undefined && this.effectiveConfig.subtitle !== '') {
return this.effectiveConfig.subtitle;
}
switch (this.variant) {
case 'search':
switch (this.effectiveConfig.variant) {
case 'search-no-results':
return 'Try different search terms or browse categories';
case 'filter':
case 'filter-no-results':
return 'Adjust your filters to discover more content';
case 'empty-library':
return 'Ready to be filled with amazing stories and knowledge';
case 'maintenance':
return 'We\'ll be back shortly with improvements';
case 'error':
return 'Don\'t worry, we\'re here to help';
case 'loading':
return 'Please wait while we prepare your content';
case 'welcome':
return 'Start your journey of discovery today';
case 'achievement':
return 'You\'ve reached a milestone!';
default:
return 'This space is ready for your content';
}
}
get shouldShowSubtitle(): boolean {
return this.effectiveConfig.subtitle !== undefined && this.effectiveConfig.subtitle !== '';
}
get effectiveMessage(): string {
if (this.config?.message || this.message) {
return this.config?.message || this.message || '';
if (this.effectiveConfig.message !== undefined && this.effectiveConfig.message !== '') {
return this.effectiveConfig.message;
}
switch (this.variant) {
case 'search':
switch (this.effectiveConfig.variant) {
case 'search-no-results':
return 'Refine your search or explore different categories to find what you\'re looking for.';
case 'filter':
case 'filter-no-results':
return 'Try removing some filters or broadening your criteria to see more results.';
case 'empty-library':
return 'Start your collection by adding books, series, or documents to this library.';
case 'maintenance':
return 'Our team is working hard to improve your experience. Thank you for your patience.';
case 'error':
return 'We encountered an unexpected issue. Please try refreshing the page or contact support if the problem persists.';
case 'loading':
return 'We\'re gathering your content and preparing everything for you.';
case 'welcome':
return 'Add your first item to get started and bring this space to life.';
case 'achievement':
return 'Congratulations on reaching this milestone! Keep exploring to discover more.';
default:
return 'Add your first item to get started and bring this space to life.';
}
}
get shouldShowMessage(): boolean {
return this.effectiveConfig.message !== undefined && this.effectiveConfig.message !== '';
}
get effectiveActionButtonText(): string {
if (this.effectiveConfig.actionButtonText) {
return this.effectiveConfig.actionButtonText;
}
switch (this.effectiveConfig.variant) {
case 'search-no-results':
return 'Browse Categories';
case 'filter-no-results':
return 'Clear Filters';
case 'empty-library':
return 'Add Content';
case 'error':
return 'Try Again';
case 'welcome':
return 'Get Started';
default:
return 'Take Action';
}
}
get effectiveActionButtonIcon(): string {
if (this.effectiveConfig.actionButtonIcon) {
return this.effectiveConfig.actionButtonIcon;
}
switch (this.effectiveConfig.variant) {
case 'search-no-results':
return 'fas fa-search';
case 'filter-no-results':
return 'fas fa-filter';
case 'empty-library':
return 'fas fa-plus';
case 'error':
return 'fas fa-redo';
case 'welcome':
return 'fas fa-rocket';
default:
return 'fas fa-arrow-right';
}
}
get containerClasses(): string {
const classes = ['no-data-container'];
if (this.isDarkTheme) {
classes.push('dark-theme');
}
classes.push(`variant-${this.effectiveConfig.variant}`);
classes.push(`size-${this.effectiveConfig.size}`);
classes.push(`animation-${this.effectiveConfig.animationType}`);
classes.push(`theme-${this.effectiveConfig.theme}`);
if (this.customClass) {
classes.push(this.customClass);
}
classes.push(`variant-${this.variant}`);
classes.push(`size-${this.config?.size || this.size}`);
return classes.join(' ');
}
@ -116,4 +255,125 @@ export class NoDataComponent {
return styles;
}
get iconClasses(): string {
const classes = ['no-data-icon'];
if (this.effectiveConfig.customIcon) {
classes.push(this.effectiveConfig.customIcon);
} else {
classes.push(this.getDefaultIcon());
}
return classes.join(' ');
}
get getCurrentBackgroundTheme(): 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror' | 'none' {
return this.currentBackgroundTheme;
}
ngOnInit(): void {
// Handle background theme for empty-library variant
if (this.effectiveConfig.variant === 'empty-library') {
const themeInput = this.effectiveConfig.backgroundTheme;
// If backgroundTheme is missing or "none", don't use any background theme
if (!themeInput || themeInput === 'none') {
this.currentBackgroundTheme = 'none';
} else if (themeInput !== 'random') {
// Use the specified theme
this.currentBackgroundTheme = themeInput;
} else {
// Truly random background theme selection
this.currentBackgroundTheme = this.getRandomBackgroundTheme();
}
}
}
/**
* Get a truly random background theme from available themes
* @returns A random background theme
*/
private getRandomBackgroundTheme(): 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror' {
// Use crypto.getRandomValues for better randomness if available
let randomIndex: number;
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
const array = new Uint32Array(1);
crypto.getRandomValues(array);
randomIndex = array[0] % this.availableBackgroundThemes.length;
} else {
// Fallback to Math.random() if crypto is not available
randomIndex = Math.floor(Math.random() * this.availableBackgroundThemes.length);
}
return this.availableBackgroundThemes[randomIndex];
}
/**
* Add a new background theme to the available themes array
* This makes the randomization scalable
* @param theme The new theme to add
*/
public addBackgroundTheme(theme: 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror'): void {
if (!this.availableBackgroundThemes.includes(theme)) {
this.availableBackgroundThemes.push(theme);
}
}
/**
* Remove a background theme from the available themes array
* @param theme The theme to remove
*/
public removeBackgroundTheme(theme: 'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror'): void {
const index = this.availableBackgroundThemes.indexOf(theme);
if (index > -1) {
this.availableBackgroundThemes.splice(index, 1);
}
}
/**
* Get all available background themes
* @returns Array of available background themes
*/
public getAvailableBackgroundThemes(): Array<'fantasy' | 'scifi' | 'comic-book' | 'manga' | 'thriller' | 'mystery' | 'romance' | 'adventure' | 'horror'> {
return [...this.availableBackgroundThemes];
}
/**
* Force a new random background theme (useful for testing or dynamic changes)
*/
public randomizeBackgroundTheme(): void {
if (this.effectiveConfig.variant === 'empty-library' && this.effectiveConfig.backgroundTheme === 'random') {
this.currentBackgroundTheme = this.getRandomBackgroundTheme();
}
}
private getDefaultIcon(): string {
switch (this.effectiveConfig.variant) {
case 'search-no-results':
return 'fas fa-search';
case 'filter-no-results':
return 'fas fa-filter';
case 'empty-library':
return 'fas fa-books';
case 'maintenance':
return 'fas fa-tools';
case 'error':
return 'fas fa-exclamation-triangle';
case 'loading':
return 'fas fa-spinner';
case 'welcome':
return 'fas fa-star';
case 'achievement':
return 'fas fa-trophy';
default:
return 'fas fa-inbox';
}
}
onActionButtonClick(): void {
// Emit event for action button click
this.actionButtonClick.emit();
}
}