feat: Implement image import functionality, restructure control panel layout, and apply glassmorphism styling to controls.

This commit is contained in:
syntaxbullet
2026-02-09 23:16:26 +01:00
parent 8dae3578b1
commit 36cb793048
3 changed files with 252 additions and 98 deletions

View File

@@ -55,8 +55,11 @@ const { pathname } = Astro.url;
left: 0;
width: 100%;
height: 24px;
background: #000;
border-bottom: 1px solid var(--text-color);
background: rgba(10, 10, 10, 0.8);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
display: flex;
justify-content: space-between;
align-items: center;
@@ -81,30 +84,32 @@ const { pathname } = Astro.url;
align-items: center;
color: var(--text-color);
text-decoration: none;
border-right: 1px solid rgba(255, 103, 0, 0.2);
transition: all 0.1s;
border-right: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
}
.nav-link:hover {
background: var(--text-color);
color: #000;
background: rgba(255, 255, 255, 0.1);
color: #fff;
text-decoration: none;
}
.nav-link:hover .nav-index {
color: #000;
color: #fff;
opacity: 1;
}
.status-item.active {
background: var(--text-color);
color: #000;
background: rgba(255, 255, 255, 0.15);
color: #fff;
font-weight: bold;
box-shadow: inset 0 -2px 0 var(--text-color);
}
.status-item.brand {
background: rgba(255, 103, 0, 0.1);
background: rgba(255, 255, 255, 0.05);
font-weight: 900;
letter-spacing: 1px;
}
.nav-index {
@@ -122,7 +127,7 @@ const { pathname } = Astro.url;
.status-right .status-item {
border-right: none;
border-left: 1px solid rgba(255, 103, 0, 0.2);
border-left: 1px solid rgba(255, 255, 255, 0.1);
}
.prefix {
@@ -134,6 +139,7 @@ const { pathname } = Astro.url;
#system-status {
color: #0f0;
font-weight: bold;
text-shadow: 0 0 5px rgba(0, 255, 0, 0.5);
}
</style>

View File

@@ -174,64 +174,80 @@ import Tooltip from "../components/Tooltip.astro";
<!-- Divider -->
<div class="control-panel-divider"></div>
<!-- Actions Section -->
<div class="control-panel-section actions-section">
<div class="section-header">ACTIONS</div>
<div class="actions-row">
<TuiButton
id="btn-reset"
label="RESET"
shortcut="R"
title="Reset to Auto-detected Settings"
description="Resets all sliders and toggles to their default values."
/>
<!-- Right Column: Actions & Export -->
<div class="control-col-right">
<div class="control-panel-section actions-section">
<div class="section-header">ACTIONS</div>
<div class="actions-row">
<TuiButton
id="btn-reset"
label="RESET"
shortcut="R"
title="Reset to Auto-detected Settings"
description="Resets all sliders and toggles to their default values."
/>
<TuiButton
id="btn-next"
label="NEXT"
shortcut="N"
variant="primary"
title="Load Next Image"
description="Discards current image and loads a new one from the queue."
/>
<div
class="queue-display"
data-tooltip-title="Buffered Images"
data-tooltip-desc="Number of images pre-loaded in background queue."
>
<span class="queue-label">Q:</span>
<span id="val-queue" class="queue-value"
>0</span
<TuiButton
id="btn-next"
label="NEXT"
shortcut="N"
variant="primary"
title="Load Next Image"
description="Discards current image and loads a new one from the queue."
/>
<div
class="queue-display"
data-tooltip-title="Buffered Images"
data-tooltip-desc="Number of images pre-loaded in background queue."
>
<span class="queue-label">Q:</span>
<span id="val-queue" class="queue-value"
>0</span
>
</div>
</div>
</div>
</div>
<!-- Divider -->
<div class="control-panel-divider"></div>
<!-- Horizontal Divider for inside column -->
<div class="control-panel-divider-h"></div>
<!-- Export Section -->
<div class="control-panel-section export-section">
<div class="section-header">EXPORT</div>
<div class="actions-row">
<TuiButton
id="btn-save-png"
label="PNG"
title="Save as Image"
description="Download high-res PNG capture of the current view."
/>
<TuiButton
id="btn-copy-text"
label="TXT"
title="Save as Text"
description="Download raw ASCII text file."
/>
<TuiButton
id="btn-copy-html"
label="HTML"
title="Save as HTML"
description="Download colored HTML file."
/>
<div class="control-panel-section export-section">
<div class="section-header">
IMPORT / EXPORT
</div>
<div class="actions-row">
<!-- Hidden File Input -->
<input
type="file"
id="file-upload"
accept="image/*"
style="display: none;"
/>
<TuiButton
id="btn-import"
label="IMP"
title="Import Image"
description="Upload your own image from your device."
/>
<TuiButton
id="btn-save-png"
label="PNG"
title="Save as Image"
description="Download high-res PNG capture of the current view."
/>
<TuiButton
id="btn-copy-text"
label="TXT"
title="Save as Text"
description="Download raw ASCII text file."
/>
<TuiButton
id="btn-copy-html"
label="HTML"
title="Save as HTML"
description="Download colored HTML file."
/>
</div>
</div>
</div>
</div>
@@ -405,6 +421,12 @@ import Tooltip from "../components/Tooltip.astro";
font-size: 1.5rem;
display: none;
z-index: 10;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.6);
padding: 10px 20px;
border-radius: 4px;
backdrop-filter: blur(4px);
}
/* FOREGROUND LAYER */
@@ -474,6 +496,7 @@ import Tooltip from "../components/Tooltip.astro";
max-width: 400px;
background: rgba(0, 0, 0, 0.6);
padding: 5px;
border-radius: 2px;
}
/* Footer / Controls */
@@ -484,25 +507,40 @@ import Tooltip from "../components/Tooltip.astro";
align-items: center;
gap: 0.75rem;
position: relative;
z-index: 50; /* Ensure on top */
}
#tui-controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: flex-start;
align-items: stretch; /* Stretch to same height */
gap: 0;
background: rgba(0, 0, 0, 0.95);
border: 1px solid var(--text-color);
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
/* Glassmorphism update */
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
max-width: 100%;
border-radius: 8px; /* Slightly rounded for premium feel */
overflow: hidden; /* For border radius */
}
.control-panel-section {
padding: 12px 16px;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 10px;
flex-shrink: 0;
}
.control-col-right {
display: flex;
flex-direction: column;
gap: 8px;
}
.control-panel-divider {
@@ -510,32 +548,47 @@ import Tooltip from "../components/Tooltip.astro";
background: linear-gradient(
to bottom,
transparent 0%,
rgba(255, 103, 0, 0.3) 20%,
rgba(255, 103, 0, 0.3) 80%,
rgba(255, 255, 255, 0.1) 20%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 80%,
transparent 100%
);
align-self: stretch;
}
.control-panel-divider-h {
height: 1px;
width: 100%;
background: linear-gradient(
to right,
transparent 0%,
rgba(255, 255, 255, 0.1) 20%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 80%,
transparent 100%
);
}
.section-header {
font-size: 9px;
font-weight: bold;
opacity: 0.5;
font-size: 10px;
font-weight: 800;
opacity: 0.6;
letter-spacing: 2px;
margin-bottom: 4px;
margin-bottom: 2px;
text-transform: uppercase;
color: var(--text-color);
}
.sliders-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 6px 16px;
gap: 8px 20px;
}
.toggles-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
gap: 8px;
}
.segments-row {
@@ -547,7 +600,8 @@ import Tooltip from "../components/Tooltip.astro";
.actions-row {
display: flex;
align-items: center;
gap: 10px;
gap: 8px;
flex-wrap: wrap;
}
.queue-display {
@@ -557,7 +611,7 @@ import Tooltip from "../components/Tooltip.astro";
font-size: 11px;
opacity: 0.6;
padding: 0 8px;
border-left: 1px solid rgba(255, 103, 0, 0.2);
border-left: 1px solid rgba(255, 255, 255, 0.2);
}
.queue-label {
@@ -575,34 +629,34 @@ import Tooltip from "../components/Tooltip.astro";
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
font-size: 10px;
opacity: 0.4;
gap: 16px;
font-size: 11px;
opacity: 0.5;
transition: opacity 0.2s;
}
.shortcuts-hint:hover {
opacity: 0.7;
opacity: 0.8;
}
.shortcuts-hint span {
display: flex;
align-items: center;
gap: 4px;
gap: 6px;
}
.shortcuts-hint kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 16px;
height: 16px;
padding: 0 4px;
font-size: 9px;
min-width: 18px;
height: 18px;
padding: 0 5px;
font-size: 10px;
font-family: inherit;
border: 1px solid rgba(255, 103, 0, 0.4);
border-radius: 2px;
background: rgba(255, 103, 0, 0.05);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
background: rgba(255, 255, 255, 0.1);
}
/* Legacy styles kept for compatibility */
@@ -627,7 +681,7 @@ import Tooltip from "../components/Tooltip.astro";
.tui-btn:hover {
opacity: 1;
border-color: var(--text-color);
background: rgba(255, 103, 0, 0.1);
background: rgba(255, 255, 255, 0.1);
}
.tui-val {
@@ -648,20 +702,45 @@ import Tooltip from "../components/Tooltip.astro";
align-items: stretch;
}
.control-col-right {
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
}
.control-col-right > .control-panel-section {
flex: 1;
}
/* New vertical alignment for right col on mobile */
.control-panel-divider-h {
width: 1px;
height: auto;
align-self: stretch;
background: linear-gradient(
to bottom,
transparent 0%,
rgba(255, 255, 255, 0.2) 20%,
rgba(255, 255, 255, 0.2) 80%,
transparent 100%
);
}
.control-panel-divider {
width: 100%;
height: 1px;
background: linear-gradient(
to right,
transparent 0%,
rgba(255, 103, 0, 0.3) 20%,
rgba(255, 103, 0, 0.3) 80%,
rgba(255, 255, 255, 0.1) 20%,
rgba(255, 255, 255, 0.3) 50%,
rgba(255, 255, 255, 0.1) 80%,
transparent 100%
);
}
.control-panel-section {
padding: 10px 14px;
padding: 12px 16px;
}
}
@@ -670,6 +749,21 @@ import Tooltip from "../components/Tooltip.astro";
font-size: 3rem;
}
.control-col-right {
flex-direction: column;
}
.control-panel-divider-h {
width: 100%;
height: 1px;
background: linear-gradient(
to right,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
}
.controls-footer {
padding: 1rem;
}
@@ -691,11 +785,11 @@ import Tooltip from "../components/Tooltip.astro";
@media (max-width: 480px) {
.control-panel-section {
padding: 8px 10px;
padding: 10px 12px;
}
.section-header {
font-size: 8px;
font-size: 9px;
}
}
</style>

View File

@@ -67,6 +67,7 @@ export class UIBindings {
this.setupKeyboard();
this.setupZoom();
this.setupResize();
this.setupImport();
// Periodic queue update
this.queueInterval = window.setInterval(() => this.updateQueueDisplay(), 1000);
@@ -142,13 +143,20 @@ export class UIBindings {
}
// Cleanup Export Buttons
['btn-save-png', 'btn-copy-text', 'btn-copy-html'].forEach(id => {
['btn-save-png', 'btn-copy-text', 'btn-copy-html', 'btn-import'].forEach(id => {
const el = document.getElementById(id);
const handler = this.buttonHandlers.get(id);
if (el && handler) {
el.removeEventListener('click', handler);
}
});
// Cleanup File Input
const fileInput = document.getElementById('file-upload');
const fileHandler = this.buttonHandlers.get('file-upload');
if (fileInput && fileHandler) {
fileInput.removeEventListener('change', fileHandler);
}
}
// ============= Sliders =============
@@ -412,6 +420,52 @@ export class UIBindings {
window.addEventListener('resize', this.resizeHandler);
}
// ============= Import =============
private setupImport(): void {
const btnImport = document.getElementById('btn-import');
const fileInput = document.getElementById('file-upload') as HTMLInputElement;
if (btnImport && fileInput) {
// Button triggers file input
const btnHandler = (e: Event) => {
e.stopPropagation();
fileInput.click();
};
this.buttonHandlers.set('btn-import', btnHandler);
btnImport.addEventListener('click', btnHandler);
// File input change
const fileHandler = async (e: Event) => {
const files = (e.target as HTMLInputElement).files;
if (files && files.length > 0) {
const file = files[0];
const url = URL.createObjectURL(file);
// Reset value so same file can be selected again
fileInput.value = '';
try {
this.controller.showLoading("LOADING IMPORT...");
// Use empty suggestions for user imports unless we want to auto-detect?
// For now keep existing settings or use defaults.
// Let's pass empty object to respect current user settings or controller defaults.
this.controller.setCurrentImage(url, {});
this.updateUI();
await this.controller.generate();
this.controller.hideLoading();
} catch (err) {
console.error("Import failed:", err);
this.controller.hideLoading();
alert("Failed to load image. Please try another file.");
}
}
};
this.buttonHandlers.set('file-upload', fileHandler);
fileInput.addEventListener('change', fileHandler);
}
}
// ============= UI Sync =============
updateUI(): void {