menu-edition #53
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue