canti primo tag

This commit is contained in:
David Frassi
2026-05-16 17:24:59 +02:00
commit 0c33fc6fcf
106 changed files with 28210 additions and 0 deletions
@@ -0,0 +1,254 @@
import { Component, OnInit, ViewChild, ElementRef, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule, ToastController, IonTextarea, PopoverController, NavController } from '@ionic/angular';
import { createWorker } from 'tesseract.js';
import { CantiService } from '../../services/canti.service';
import { MyCantiService } from '../../services/my-canti.service';
@Component({
selector: 'app-propose-canto',
templateUrl: './propose-canto.page.html',
styleUrls: ['./propose-canto.page.scss'],
standalone: true,
imports: [CommonModule, FormsModule, IonicModule]
})
export class ProposeCantoPage implements OnInit {
@ViewChild('contentTextarea', { static: false }) contentTextarea!: IonTextarea;
@ViewChild('cameraInput', { static: false }) cameraInput!: ElementRef;
@ViewChild('docInput', { static: false }) docInput!: ElementRef;
public cantiService = inject(CantiService);
private myCantiService = inject(MyCantiService);
private navCtrl = inject(NavController);
title: string = '';
author: string = '';
youtubeLink: string = '';
selectedLiturgico: number[] = [];
selectedTematico: number[] = [];
private _content: string = '';
get content(): string { return this._content; }
set content(val: string) {
if (this._content !== val) {
this.saveToUndoStack(this._content);
this._content = val;
}
}
undoStack: string[] = [];
isProcessingOCR: boolean = false;
ocrProgress: number = 0;
isHighContrast: boolean = false;
commonChords = [
'DO', 'RE', 'MI', 'FA', 'SOL', 'LA', 'SI',
'DO-', 'RE-', 'MI-', 'FA-', 'SOL-', 'LA-', 'SI-',
'DO#', 'RE#', 'FA#', 'SOL#', 'LA#', 'DO#-', 'RE#-', 'FA#-', 'SOL#-', 'LA#-',
'DO7', 'RE7', 'MI7', 'FA7', 'SOL7', 'LA7', 'SI7', 'DO-7', 'RE-7', 'MI-7', 'LA-7', 'SI-7',
'DOmaj7', 'REmaj7', 'MImaj7', 'FAmaj7', 'SOLmaj7', 'LAmaj7', 'SImaj7',
'DO4', 'RE4', 'MI4', 'FA4', 'SOL4', 'LA4', 'SI4',
'DOdim', 'REdim', 'MIdim', 'FAdim', 'SOLdim', 'LAdim', 'SIdim',
'DOaug', 'REaug', 'MIaug', 'FAaug', 'SOLaug', 'LAaug', 'SIaug',
'DOm7', 'REm7', 'FAm7', 'SOLm7', 'LAm7'
];
commonTags = [
{ label: 'Ritornello', start: '{start_chorus}', end: '{end_chorus}' },
{ label: 'Strofa', start: '{start_verse}', end: '{end_verse}' },
{ label: 'Strofa Num.', start: '{start_verse_num}', end: '{end_verse_num}' },
{ label: 'Inciso', start: '{start_bridge}', end: '{end_bridge}' },
{ label: 'Intro Accordi', start: '{start_chord}', end: '{end_chord}' },
{ label: 'Commento', start: '{c: ', end: '}' },
{ label: 'ChordPro Strofa', start: '{sov}', end: '{eov}' },
{ label: 'ChordPro Rit.', start: '{soc}', end: '{eoc}' }
];
isChordPopoverOpen = false;
constructor(private toastController: ToastController, private popoverController: PopoverController) { }
ngOnInit() {
}
async insertText(tag: string) {
const input = await this.contentTextarea.getInputElement();
const start = input.selectionStart || 0;
const end = input.selectionEnd || 0;
this.content = this.content.substring(0, start) + tag + this.content.substring(end);
setTimeout(() => {
input.focus();
input.setSelectionRange(start + tag.length, start + tag.length);
}, 10);
}
insertChord(chord: string) {
this.insertText(`[${chord}]`);
}
undo() {
if (this.undoStack.length > 0) {
const previous = this.undoStack.pop();
if (previous !== undefined) {
this._content = previous;
}
}
}
private saveToUndoStack(val: string) {
if (this.undoStack.length >= 30) {
this.undoStack.shift();
}
this.undoStack.push(val);
}
takePhoto() {
this.cameraInput.nativeElement.click();
}
uploadDoc() {
this.docInput.nativeElement.click();
}
async onFileSelected(event: any, isCamera: boolean) {
const file = event.target.files[0];
if (!file) return;
this.isProcessingOCR = true;
this.ocrProgress = 0;
try {
const extension = file.name.split('.').pop().toLowerCase();
let extractedText = '';
if (extension === 'pdf') {
extractedText = await this.processPdf(file);
} else if (extension === 'docx') {
const mammoth = await import('mammoth');
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
extractedText = result.value;
} else if (['jpg', 'jpeg', 'png', 'webp'].includes(extension) || isCamera) {
extractedText = await this.processImageOCR(file);
} else {
extractedText = await file.text();
}
if (extractedText) {
const processed = this.smartProcessOCR(extractedText);
this.content += (this.content ? '\n\n' : '') + processed;
const toast = await this.toastController.create({
message: 'Documento elaborato!',
duration: 2000,
color: 'success'
});
toast.present();
}
} catch (error) {
console.error('File Processing Error:', error);
} finally {
this.isProcessingOCR = false;
this.ocrProgress = 0;
event.target.value = '';
}
}
async processImageOCR(file: File): Promise<string> {
const worker = await createWorker('ita', 1, {
logger: m => {
if (m.status === 'recognizing text') this.ocrProgress = m.progress;
}
});
const { data: { text } } = await worker.recognize(file);
await worker.terminate();
return text;
}
async processPdf(file: File): Promise<string> {
const pdfjsLib = await import('pdfjs-dist');
// Set worker src from CDN for PWA compatibility
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.mjs`;
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
const pdf = await loadingTask.promise;
let fullText = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items.map((item: any) => item.str).join(' ');
fullText += pageText + '\n';
}
return fullText;
}
smartProcessOCR(text: string): string {
let lines = text.split('\n');
let processedLines: string[] = [];
const chordRegex = /\b(DO|RE|MI|FA|SOL|LA|SI|C|D|E|F|G|A|B)(#|b)?(m|-|min|maj|aug|dim)?(7|9|11|13)?\b/gi;
let inChorus = false;
let inVerse = false;
lines.forEach((line) => {
let trimmed = line.trim();
if (!trimmed) {
if (inChorus) { processedLines.push('{end_chorus}'); inChorus = false; }
if (inVerse) { processedLines.push('{end_verse}'); inVerse = false; }
processedLines.push('');
return;
}
const isChorusMarker = /^(Rit|Ritornello|Chorus|CORO)/i.test(trimmed);
if (isChorusMarker && !inChorus) {
if (inVerse) { processedLines.push('{end_verse}'); inVerse = false; }
processedLines.push('{start_chorus}');
inChorus = true;
if (trimmed.length > 12) processedLines.push(this.wrapChords(trimmed, chordRegex));
} else {
if (!inVerse && !inChorus && trimmed.length > 5) {
processedLines.push('{start_verse}');
inVerse = true;
}
processedLines.push(this.wrapChords(trimmed, chordRegex));
}
});
if (inChorus) processedLines.push('{end_chorus}');
if (inVerse) processedLines.push('{end_verse}');
return processedLines.join('\n');
}
private wrapChords(line: string, regex: RegExp): string {
const chordsInLine = line.match(regex);
if (chordsInLine && chordsInLine.length > 0) {
return line.replace(regex, (match) => `[${match.toUpperCase()}]`);
}
return line;
}
async openQuickMenu(event: any) {
this.isChordPopoverOpen = true;
}
async saveToMyCanti() {
if (!this.title || !this.content) return;
// Combine lit and tematico for id_momenti
const id_momenti = [...this.selectedLiturgico, ...this.selectedTematico];
await this.myCantiService.saveCanto({
titolo: this.title,
autore: this.author,
link_youtube: this.youtubeLink,
testo: this.content,
accordi: this.content, // Save to both fields for compatibility
id_momenti: id_momenti
});
this.navCtrl.back();
}
}