canti primo tag
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user