Functionnal UI with All Files

This commit is contained in:
2025-07-15 02:29:44 +02:00
parent 9d7b1d3b76
commit 5929c758d3
52 changed files with 11979 additions and 0 deletions

View File

@ -0,0 +1,132 @@
// src/app/components/cued-title/cued-title.component.ts
import { Component, input, computed, signal, effect } from '@angular/core';
import { DatePipe } from '@angular/common';
import { TitleCasePipe } from '@angular/common';
import { TitleSheet, TitleSheetListStatus } from '../../../models/title-sheet.model';
@Component({
selector: 'app-cued-title',
standalone: true,
imports: [TitleCasePipe],
templateUrl: './cued-title.component.html',
styleUrl: './cued-title.component.scss'
})
export class CuedTitleComponent {
// === INPUT SIGNALS (ANGULAR 19) ===
unrealStatus = input<TitleSheetListStatus | null>(null);
// === INTERNAL SIGNALS ===
private playStartTime = signal<Date | null>(null);
private elapsedSeconds = signal<number>(0);
private intervalId : any = signal<number | null>(null);
// Keep track of the last attempted title (even if it failed)
private lastAttemptedTitle = signal<TitleSheet | null>(null);
// === COMPUTED SIGNALS ===
statusClass = computed(() => {
const status = this.unrealStatus();
if (!status) return 'stopped';
return status.isPlaying ? 'playing' : 'stopped';
});
hasCurrentTitle = computed(() => {
const status = this.unrealStatus();
const lastAttempted = this.lastAttemptedTitle();
// Show title if:
// 1. There's a current title, OR
// 2. There's an error/stopped state but we have a last attempted title
return status?.currentTitleSheet !== null ||
(lastAttempted !== null && (status?.errorMessage));
});
// Get the title to display (current or last attempted)
displayedTitle = computed(() => {
const status = this.unrealStatus();
// Prefer current title, fallback to last attempted title
return status?.currentTitleSheet || this.lastAttemptedTitle();
});
isPlaying = computed(() => {
const status = this.unrealStatus();
return status?.isPlaying || false;
});
// Format du timer : MM:SS
formattedTimer = computed(() => {
const seconds = this.elapsedSeconds();
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
});
// Couleur du timer selon les règles
timerColorClass = computed(() => {
if (!this.isPlaying()) return 'timer-stopped';
const seconds = this.elapsedSeconds();
if (seconds < 10) return 'timer-white';
return 'timer-red';
});
constructor() {
// Track the last attempted title for error display
effect(() => {
const status = this.unrealStatus();
if (status?.currentTitleSheet) {
// Update last attempted title when a new title is set
this.lastAttemptedTitle.set(status.currentTitleSheet);
} else if (!status?.isPlaying && !status?.errorMessage) {
// Clear last attempted title when voluntarily stopped (no error)
this.lastAttemptedTitle.set(null);
}
});
// Effect pour gérer le démarrage/arrêt du timer
effect(() => {
const isPlaying = this.isPlaying();
if (isPlaying) {
this.startTimer();
} else {
this.stopTimer();
this.resetTimer();
}
});
}
ngOnDestroy(): void {
this.stopTimer();
}
private startTimer(): void {
this.stopTimer();
this.playStartTime.set(new Date());
this.elapsedSeconds.set(0);
this.intervalId = setInterval(() => {
const startTime = this.playStartTime();
if (startTime) {
const now = new Date();
const elapsed = Math.floor((now.getTime() - startTime.getTime()) / 1000);
this.elapsedSeconds.set(elapsed);
}
}, 1000);
}
private stopTimer(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
private resetTimer(): void {
this.playStartTime.set(null);
this.elapsedSeconds.set(0);
}
}