mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-08-30 23:00:06 -04:00
Updating
This commit is contained in:
parent
edf123a550
commit
be914f36cf
@ -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>
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user