d83a5db65a
- Aggiunto il caricamento reattivo del canto tramite un 'effect' in PlayerPage e DisplayPage. - Ora l'applicazione attende che la lista asincrona dei canti sia popolata per caricare la canzone richiesta da URL.
134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import { Component, OnInit, OnDestroy, signal, computed, effect, inject } from '@angular/core';
|
|
import { ActivatedRoute } from '@angular/router';
|
|
import { CantiService, Canto } from '../../services/canti.service';
|
|
import { LyricsParserService, ParsedSection } from '../../services/lyrics-parser.service';
|
|
|
|
@Component({
|
|
selector: 'app-display',
|
|
templateUrl: './display.page.html',
|
|
styleUrls: ['./display.page.scss'],
|
|
standalone: false
|
|
})
|
|
export class DisplayPage implements OnInit, OnDestroy {
|
|
public canto = signal<Canto | null>(null);
|
|
public activeSongId = signal<string | null>(null);
|
|
public showChords = signal<boolean>(false);
|
|
public fontSize = signal<number>(1.0);
|
|
public currentLineIndex = signal<number>(0);
|
|
|
|
public parsedSections = computed<ParsedSection[]>(() => {
|
|
const c = this.canto();
|
|
if (!c) return [];
|
|
if (this.showChords() && c.accordi) {
|
|
return this.lyricsParser.parseAccordi(c.accordi);
|
|
}
|
|
return this.lyricsParser.parseText(c.testo);
|
|
});
|
|
|
|
/** Get the current line text and surrounding lines from flat index */
|
|
public displayLines = computed(() => {
|
|
const sections = this.parsedSections();
|
|
const idx = this.currentLineIndex();
|
|
const allLines: { text: string; segments: any[]; sectionType: string }[] = [];
|
|
|
|
for (const section of sections) {
|
|
for (const line of section.lines) {
|
|
allLines.push({ text: line.text, segments: line.segments, sectionType: section.type });
|
|
}
|
|
}
|
|
|
|
return {
|
|
prev: idx > 0 ? allLines[idx - 1] : null,
|
|
current: allLines[idx] || null,
|
|
next: idx < allLines.length - 1 ? allLines[idx + 1] : null
|
|
};
|
|
});
|
|
|
|
private channel = new BroadcastChannel('karaoke_sync');
|
|
|
|
private readonly MIN_FONT = 0.6;
|
|
private readonly MAX_FONT = 5.0;
|
|
private initialPinchDistance: number | null = null;
|
|
private initialFontSize: number = 1.0;
|
|
|
|
private route = inject(ActivatedRoute);
|
|
private cantiService = inject(CantiService);
|
|
private lyricsParser = inject(LyricsParserService);
|
|
|
|
constructor() {
|
|
effect(() => {
|
|
const id = this.activeSongId();
|
|
const allCanti = this.cantiService.canti();
|
|
if (id) {
|
|
const found = allCanti.find(c => c.id === id);
|
|
if (found) {
|
|
this.canto.set(found);
|
|
}
|
|
}
|
|
}, { allowSignalWrites: true });
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.route.queryParams.subscribe(params => {
|
|
const id = params['id'];
|
|
if (id) {
|
|
this.activeSongId.set(id);
|
|
}
|
|
});
|
|
|
|
this.channel.onmessage = (event) => {
|
|
if (event.data.type === 'SYNC_INDEX') {
|
|
this.currentLineIndex.set(event.data.index);
|
|
}
|
|
if (event.data.type === 'SYNC_CANTO') {
|
|
this.activeSongId.set(event.data.id);
|
|
}
|
|
if (event.data.type === 'SYNC_CHORDS') {
|
|
this.showChords.set(event.data.showChords);
|
|
}
|
|
if (event.data.type === 'SYNC_FONT') {
|
|
this.fontSize.set(event.data.fontSize);
|
|
}
|
|
};
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.channel.close();
|
|
}
|
|
|
|
// --- Touch Gestures for Pinch-to-Zoom ---
|
|
onTouchStart(event: TouchEvent) {
|
|
if (event.touches.length === 2) {
|
|
this.initialPinchDistance = this.getDistance(event.touches[0], event.touches[1]);
|
|
this.initialFontSize = this.fontSize();
|
|
}
|
|
}
|
|
|
|
onTouchMove(event: TouchEvent) {
|
|
if (event.touches.length === 2 && this.initialPinchDistance !== null) {
|
|
event.preventDefault(); // Prevent browser zoom/scroll
|
|
const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
|
|
const ratio = currentDistance / this.initialPinchDistance;
|
|
let newSize = this.initialFontSize * ratio;
|
|
|
|
// Clamp values
|
|
newSize = Math.max(this.MIN_FONT, Math.min(this.MAX_FONT, newSize));
|
|
|
|
if (Math.abs(newSize - this.fontSize()) > 0.01) {
|
|
this.fontSize.set(newSize);
|
|
// Sync back to player if display is touched
|
|
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.fontSize() });
|
|
}
|
|
}
|
|
}
|
|
|
|
onTouchEnd() {
|
|
this.initialPinchDistance = null;
|
|
}
|
|
|
|
private getDistance(t1: Touch, t2: Touch): number {
|
|
return Math.sqrt(Math.pow(t1.clientX - t2.clientX, 2) + Math.pow(t1.clientY - t2.clientY, 2));
|
|
}
|
|
// ----------------------------------------
|
|
}
|