fix(export): enable proper file downloads and fix empty PNG export

This commit is contained in:
syntaxbullet
2026-02-09 22:50:25 +01:00
parent ea05b814b4
commit 8dae3578b1
5 changed files with 40 additions and 13 deletions

View File

@@ -223,14 +223,14 @@ import Tooltip from "../components/Tooltip.astro";
<TuiButton <TuiButton
id="btn-copy-text" id="btn-copy-text"
label="TXT" label="TXT"
title="Copy Text" title="Save as Text"
description="Copy raw ASCII text to clipboard." description="Download raw ASCII text file."
/> />
<TuiButton <TuiButton
id="btn-copy-html" id="btn-copy-html"
label="HTML" label="HTML"
title="Copy HTML" title="Save as HTML"
description="Copy colored HTML span elements to clipboard." description="Download colored HTML file."
/> />
</div> </div>
</div> </div>

View File

@@ -417,11 +417,27 @@ export class AsciiController {
// ============= Export ============= // ============= Export =============
savePNG(): void { savePNG(): void {
const wasShowing = this.zoomState.showMagnifier;
// 1. Force hide magnifier for clean export
if (wasShowing) {
this.zoomState.showMagnifier = false;
this.requestRender('uniforms');
// Force synchronous render
this.renderFrame();
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
AsciiExporter.downloadPNG(this.canvas, `ascii-art-${timestamp}.png`); AsciiExporter.downloadPNG(this.canvas, `ascii-art-${timestamp}.png`);
// 2. Restore state
if (wasShowing) {
this.zoomState.showMagnifier = true;
this.requestRender('uniforms');
}
} }
async copyText(): Promise<void> { saveText(): void {
if (!this.cachedGrid.imgEl) return; if (!this.cachedGrid.imgEl) return;
const text = AsciiExporter.generateText( const text = AsciiExporter.generateText(
this.cachedGrid.imgEl, this.cachedGrid.imgEl,
@@ -429,10 +445,11 @@ export class AsciiController {
this.cachedGrid.widthCols, this.cachedGrid.widthCols,
Math.floor(this.cachedGrid.heightRows) Math.floor(this.cachedGrid.heightRows)
); );
await AsciiExporter.copyToClipboard(text); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
AsciiExporter.downloadText(text, `ascii-art-${timestamp}.txt`);
} }
async copyHTML(): Promise<void> { saveHTML(): void {
if (!this.cachedGrid.imgEl) return; if (!this.cachedGrid.imgEl) return;
const html = AsciiExporter.generateHTML( const html = AsciiExporter.generateHTML(
this.cachedGrid.imgEl, this.cachedGrid.imgEl,
@@ -440,7 +457,8 @@ export class AsciiController {
this.cachedGrid.widthCols, this.cachedGrid.widthCols,
Math.floor(this.cachedGrid.heightRows) Math.floor(this.cachedGrid.heightRows)
); );
await AsciiExporter.copyToClipboard(html); const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
AsciiExporter.downloadText(html, `ascii-art-${timestamp}.html`);
} }
// ============= Utilities ============= // ============= Utilities =============

View File

@@ -8,6 +8,15 @@ export class AsciiExporter {
link.click(); link.click();
} }
static downloadText(content: string, filename: string) {
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
static async copyToClipboard(text: string): Promise<void> { static async copyToClipboard(text: string): Promise<void> {
if (navigator.clipboard && navigator.clipboard.writeText) { if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);

View File

@@ -308,9 +308,9 @@ export class UIBindings {
const btnCopyText = document.getElementById('btn-copy-text'); const btnCopyText = document.getElementById('btn-copy-text');
if (btnCopyText) { if (btnCopyText) {
const handler = async (e: Event) => { const handler = (e: Event) => {
e.stopPropagation(); e.stopPropagation();
await this.controller.copyText(); this.controller.saveText();
}; };
this.buttonHandlers.set('btn-copy-text', handler); this.buttonHandlers.set('btn-copy-text', handler);
btnCopyText.addEventListener('click', handler); btnCopyText.addEventListener('click', handler);
@@ -318,9 +318,9 @@ export class UIBindings {
const btnCopyHtml = document.getElementById('btn-copy-html'); const btnCopyHtml = document.getElementById('btn-copy-html');
if (btnCopyHtml) { if (btnCopyHtml) {
const handler = async (e: Event) => { const handler = (e: Event) => {
e.stopPropagation(); e.stopPropagation();
await this.controller.copyHTML(); this.controller.saveHTML();
}; };
this.buttonHandlers.set('btn-copy-html', handler); this.buttonHandlers.set('btn-copy-html', handler);
btnCopyHtml.addEventListener('click', handler); btnCopyHtml.addEventListener('click', handler);

View File

@@ -46,7 +46,7 @@ export class WebGLAsciiRenderer {
private lastImage: HTMLImageElement | null; private lastImage: HTMLImageElement | null;
constructor(_canvas: HTMLCanvasElement) { constructor(_canvas: HTMLCanvasElement) {
const gl = _canvas.getContext('webgl', { antialias: false }); const gl = _canvas.getContext('webgl', { antialias: false, preserveDrawingBuffer: true });
if (!gl) { if (!gl) {
throw new Error('WebGL not supported'); throw new Error('WebGL not supported');
} }