diff --git a/CHANGELOG.md b/CHANGELOG.md index 00882c8..305f866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.css b/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.css index 5efc52e..c582433 100644 --- a/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.css +++ b/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.css @@ -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; diff --git a/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.html b/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.html index 8f37679..db0d93c 100644 --- a/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.html +++ b/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.html @@ -52,10 +52,35 @@ - -
- -
+ + @if (safeUrl || isEditingHtml) { +
+ + +
+ @if (isEditingHtml) { + + + } @else if (safeUrl) { + + } +
+ + + @if (isEditingHtml) { + + } @else if (safeUrl) { + + } + +
+ } diff --git a/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.ts b/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.ts index 41cd393..94bccd7 100644 --- a/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.ts +++ b/ogWebconsole/src/app/components/menus/create-menu/create-menu.component.ts @@ -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 = ''; + } + 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()