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()