202 lines
5.7 KiB
Plaintext
202 lines
5.7 KiB
Plaintext
---
|
|
import Layout from "../layouts/Layout.astro";
|
|
import Sidebar from "../components/Sidebar.astro";
|
|
import Tooltip from "../components/Tooltip.astro";
|
|
import ControlPanel from "../components/ControlPanel.astro";
|
|
---
|
|
|
|
<Layout title="Syntaxbullet - Digital Wizard">
|
|
<div class="split-layout">
|
|
<Sidebar />
|
|
|
|
<!-- RIGHT MAIN: ASCII & Controls -->
|
|
<main class="ascii-workspace hero-wrapper">
|
|
<!-- Canvas Layer -->
|
|
<div class="canvas-layer">
|
|
<div id="loading">Loading...</div>
|
|
<pre id="ascii-result">Preparing art...</pre>
|
|
<canvas id="ascii-canvas"></canvas>
|
|
</div>
|
|
|
|
<ControlPanel />
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
import { AsciiController } from "../scripts/ascii-controller";
|
|
import { ImageQueue } from "../scripts/image-queue";
|
|
import { UIBindings } from "../scripts/ui-bindings";
|
|
|
|
// ============= Global Cleanup Protocol =============
|
|
// Fix for accumulating event listeners and render loops during HMR/Navigation
|
|
if (window.__ASCII_APP__) {
|
|
console.log("♻️ Disposing previous application instance...");
|
|
try {
|
|
window.__ASCII_APP__.dispose();
|
|
} catch (e) {
|
|
console.error("Failed to dispose previous instance:", e);
|
|
}
|
|
}
|
|
|
|
// ============= DOM Elements =============
|
|
const canvas = document.getElementById(
|
|
"ascii-canvas",
|
|
) as HTMLCanvasElement;
|
|
const asciiResult = document.getElementById(
|
|
"ascii-result",
|
|
) as HTMLPreElement;
|
|
const loadingIndicator = document.getElementById(
|
|
"loading",
|
|
) as HTMLDivElement;
|
|
|
|
if (!canvas || !asciiResult || !loadingIndicator) {
|
|
throw new Error("Critical UI elements missing");
|
|
}
|
|
|
|
// ============= Initialize =============
|
|
const controller = new AsciiController(
|
|
canvas,
|
|
asciiResult,
|
|
loadingIndicator,
|
|
);
|
|
const queue = new ImageQueue(2);
|
|
const ui = new UIBindings(controller, queue, loadNewImage);
|
|
|
|
// Store instances globally for cleanup
|
|
window.__ASCII_APP__ = {
|
|
controller,
|
|
queue,
|
|
ui,
|
|
dispose: () => {
|
|
controller.dispose();
|
|
ui.dispose();
|
|
queue.dispose();
|
|
window.__ASCII_APP__ = undefined;
|
|
},
|
|
};
|
|
|
|
// Link settings updates to UI sync
|
|
controller.onSettingsChanged(() => ui.updateUI());
|
|
|
|
let retryCount = 0;
|
|
const MAX_RETRIES = 3;
|
|
|
|
// ============= Image Loading =============
|
|
async function loadNewImage(): Promise<void> {
|
|
try {
|
|
let item;
|
|
|
|
if (queue.getLength() === 0) {
|
|
controller.showLoading("FETCHING...");
|
|
item = await queue.fetchDirect();
|
|
} else {
|
|
item = queue.pop()!;
|
|
queue.ensureFilled(); // Background refill
|
|
}
|
|
|
|
controller.setCurrentImage(item.url, item.suggestions);
|
|
retryCount = 0;
|
|
|
|
ui.updateUI();
|
|
await controller.generate();
|
|
controller.hideLoading();
|
|
} catch (e) {
|
|
console.error(e);
|
|
if (retryCount < MAX_RETRIES) {
|
|
retryCount++;
|
|
asciiResult.textContent = `SIGNAL LOST. RETRYING (${retryCount}/${MAX_RETRIES})...`;
|
|
setTimeout(loadNewImage, 2000);
|
|
} else {
|
|
asciiResult.textContent = "SIGNAL LOST. PLEASE REFRESH.";
|
|
controller.hideLoading();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============= Initialize UI and Load First Image =============
|
|
ui.init();
|
|
loadNewImage().then(() => {
|
|
queue.ensureFilled();
|
|
});
|
|
</script>
|
|
<Tooltip />
|
|
</Layout>
|
|
|
|
<style>
|
|
/* Split Layout */
|
|
.split-layout {
|
|
display: flex;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: var(--bg-color);
|
|
}
|
|
|
|
/* Workspace (Right 75%) */
|
|
|
|
.ascii-workspace {
|
|
flex-grow: 1;
|
|
height: 100vh;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #050505;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.canvas-layer {
|
|
flex-grow: 1;
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
background: radial-gradient(circle at center, #111 0%, #000 100%);
|
|
}
|
|
|
|
#ascii-result {
|
|
font-size: 8px;
|
|
line-height: 1;
|
|
white-space: pre;
|
|
color: var(--text-color);
|
|
transform-origin: center;
|
|
}
|
|
|
|
#ascii-canvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
display: none;
|
|
image-rendering: pixelated;
|
|
opacity: 0;
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
|
|
#loading {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-family: var(--font-mono);
|
|
color: #fff;
|
|
font-size: 1.5rem;
|
|
display: none;
|
|
z-index: 10;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
padding: 1rem 2rem;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 1024px) {
|
|
.split-layout {
|
|
flex-direction: column;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.ascii-workspace {
|
|
height: 80vh; /* Give space for scroll */
|
|
}
|
|
}
|
|
</style>
|