menu-edition #53

Merged
jmbardallo merged 2 commits from menu-edition into main 2026-03-17 09:55:12 +01:00
4 changed files with 117 additions and 6 deletions

View File

@ -1,4 +1,8 @@
# Changelog
## [1.2.2] - 2026-03-17
### Added
- Se ha integrado un editor interactivo en texto plano para los menús HTML directamente en la pantalla de edición, permitiendo previsualizar y modificar el código en vivo sin necesidad de resubir archivos. Utiliza la sintaxis moderna `@if/@else` de Angular 17+ para su lógica de renderizado.
## [1.2.1] - 2026-03-11
### Added
- Se ha añadido la funcionalidad para poder adjuntar un fichero HTML local en la creación/edición de menús, codificándolo y enviándolo en Base64 de forma transparente.

View File

@ -26,9 +26,10 @@
.iframe-container {
flex: 1;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
align-items: flex-end;
max-width: 50%;
gap: 10px;
}
.iframe {
@ -39,6 +40,26 @@
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
}
.editor-controls {
display: flex;
gap: 10px;
width: 100%;
justify-content: flex-end;
}
.html-editor {
width: 100%;
height: 600px;
font-family: 'Courier New', Courier, monospace;
font-size: 13px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 10px;
resize: vertical;
background-color: #fcfcfc;
box-shadow: inset 0px 2px 5px rgba(0, 0, 0, 0.05);
}
.action-container {
display: flex;
justify-content: flex-end;

View File

@ -52,10 +52,35 @@
</form>
<!-- Iframe -->
<div class="iframe-container" *ngIf="safeUrl">
<iframe [src]="safeUrl" class="iframe"></iframe>
</div>
<!-- Iframe o Editor HTML -->
@if (safeUrl || isEditingHtml) {
<div class="iframe-container">
<!-- Zona de Controles del Editor -->
<div class="editor-controls">
@if (isEditingHtml) {
<button type="button" class="ordinary-button edit-btn" (click)="cancelHtmlChanges()">
Cancelar Edición
</button>
<button type="button" class="submit-button apply-btn" (click)="applyHtmlChanges()">
Aplicar Cambios
</button>
} @else if (safeUrl) {
<button type="button" class="ordinary-button edit-btn" (click)="editHtml()">
<mat-icon>edit</mat-icon> Editar HTML
</button>
}
</div>
<!-- Reproductor / Previsualizador o Editor -->
@if (isEditingHtml) {
<textarea class="html-editor" [(ngModel)]="htmlEditorContent" spellcheck="false"></textarea>
} @else if (safeUrl) {
<iframe [src]="safeUrl" class="iframe"></iframe>
}
</div>
}
</div>
</mat-dialog-content>

View File

@ -20,6 +20,8 @@ export class CreateMenuComponent implements OnInit {
safeUrl: SafeResourceUrl | null = null;
selectedFile: File | null = null;
fileContent: string | null = null;
isEditingHtml: boolean = false;
htmlEditorContent: string = '';
constructor(
private fb: FormBuilder,
@ -69,6 +71,65 @@ export class CreateMenuComponent implements OnInit {
}
}
editHtml(): void {
if (this.fileContent) {
// Decode Base64 content to text
try {
// Modern approach to decode Base64 ensuring UTF-8
const binaryStr = window.atob(this.fileContent);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
this.htmlEditorContent = new TextDecoder('utf-8').decode(bytes);
} catch (e) {
console.error('Failed to decode previous fileContent Base64', e);
this.htmlEditorContent = '<!-- Error decodificando HTML anterior -->';
}
this.isEditingHtml = true;
} else if (this.menuForm.get('publicUrl')?.value) {
// Fetch the actual HTML from the public URL if it exists and no file was manually uploaded yet
const url = `${this.baseUrl}/menu/${this.menuForm.get('publicUrl')?.value}`;
this.http.get(url, { responseType: 'text' }).subscribe({
next: (htmlStr) => {
this.htmlEditorContent = htmlStr;
this.isEditingHtml = true;
},
error: (err) => {
this.toastService.error('No se pudo descargar el HTML actual del menú para editarlo.');
console.error(err);
}
});
}
}
applyHtmlChanges(): void {
if (this.htmlEditorContent) {
// Encode the typed text back into Base64 enforcing UTF-8 compatibility
try {
// Modern approach to encode UTF-8 string to Base64
const bytes = new TextEncoder().encode(this.htmlEditorContent);
const binaryStr = Array.from(bytes).map(byte => String.fromCharCode(byte)).join('');
this.fileContent = window.btoa(binaryStr);
// Switch to preview mode and update the iframe source to use the fresh Base64
this.isEditingHtml = false;
const dataUrl = `data:text/html;base64,${this.fileContent}`;
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(dataUrl);
this.toastService.success('Cambios aplicados temporalmente. Pulsa Guardar para subirlos al servidor.');
} catch (e) {
console.error('Failed to encode updated HTML to Base64', e);
this.toastService.error('Hubo un problema procesando los caracteres especiales del HTML.');
// En caso de error, nos mantenemos en la vista de edición para que el usuario pueda corregirlo
}
}
}
cancelHtmlChanges(): void {
this.isEditingHtml = false;
this.htmlEditorContent = '';
}
ngOnInit() {
if (this.data) {
this.load()