panels are all hideable/resizeable

main
Jake 2026-02-20 16:13:27 +08:00
parent 1e6fe41ddd
commit 3a040552bb
5 changed files with 439 additions and 302 deletions

View File

@ -34,47 +34,35 @@
<button id="btn-save" title="Save code to device as main.py" disabled> <button id="btn-save" title="Save code to device as main.py" disabled>
<span class="icon">&#128190;</span> Save <span class="icon">&#128190;</span> Save
</button> </button>
<button id="btn-projects" title="Open/save projects (browser or device)"> <button id="btn-projects" title="Toggle projects panel">
<span class="icon">&#128194;</span> Projects <span class="icon">&#128194;</span> Projects
</button> </button>
<span id="connection-status" class="status-disconnected">Disconnected</span> <span id="connection-status" class="status-disconnected">Disconnected</span>
</div> </div>
</header> </header>
<!-- Main workspace area -->
<main id="workspace-container"> <main id="workspace-container">
<!-- Blockly editor fills the top area --> <!-- Top row: Blockly + Projects sidebar -->
<div id="top-area">
<div id="blockly-area"> <div id="blockly-area">
<div id="blockly-div"></div> <div id="blockly-div"></div>
</div> </div>
<!-- Bottom panels: code preview + serial terminal --> <!-- Projects right sidebar -->
<div id="bottom-panels"> <div id="projects-panel" class="ide-panel">
<div id="resize-handle-h" class="resize-handle horizontal"></div> <div class="panel-header">
<div id="code-panel"> <span class="panel-title">Projects</span>
<div class="panel-header">Generated Code</div> <button class="panel-toggle" data-panel="projects-panel" title="Toggle panel">&#9666;</button>
<pre id="code-preview"><code id="code-output"></code></pre>
</div> </div>
<div id="terminal-panel"> <div class="panel-body">
<div class="panel-header">Serial Terminal</div> <!-- Tabs: Browser / Device -->
<div id="terminal-output"></div> <div class="proj-tabs">
<div id="terminal-input-row"> <button class="proj-tab active" data-tab="browser">Browser</button>
<input type="text" id="terminal-input" placeholder="Type command…" disabled /> <button class="proj-tab" data-tab="device">Device</button>
</div> </div>
</div>
</div>
</main>
<!-- Projects dialog --> <!-- Browser tab -->
<div id="projects-overlay" class="hidden"> <div class="proj-tab-content" id="proj-tab-browser">
<div id="projects-modal">
<div class="projects-header">
<h3>Projects</h3>
<button id="projects-close">&times;</button>
</div>
<div class="projects-columns">
<div class="projects-col">
<h4>Browser</h4>
<ul class="projects-list" id="browser-list"></ul> <ul class="projects-list" id="browser-list"></ul>
<div class="projects-actions"> <div class="projects-actions">
<div class="projects-name-row"> <div class="projects-name-row">
@ -87,8 +75,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="projects-col">
<h4>Device</h4> <!-- Device tab -->
<div class="proj-tab-content hidden" id="proj-tab-device">
<ul class="projects-list" id="device-list"></ul> <ul class="projects-list" id="device-list"></ul>
<div class="projects-actions"> <div class="projects-actions">
<div class="projects-name-row"> <div class="projects-name-row">
@ -106,6 +95,33 @@
</div> </div>
</div> </div>
<!-- Bottom panels: code preview + serial terminal -->
<div id="bottom-panels">
<div id="resize-handle-h" class="resize-handle horizontal"></div>
<div id="code-panel" class="ide-panel">
<div class="panel-header">
<span class="panel-title">Generated Code</span>
<button class="panel-toggle" data-panel="code-panel" title="Toggle panel">&#9662;</button>
</div>
<div class="panel-body">
<pre id="code-preview"><code id="code-output"></code></pre>
</div>
</div>
<div id="terminal-panel" class="ide-panel">
<div class="panel-header">
<span class="panel-title">Serial Terminal</span>
<button class="panel-toggle" data-panel="terminal-panel" title="Toggle panel">&#9662;</button>
</div>
<div class="panel-body">
<div id="terminal-output"></div>
<div id="terminal-input-row">
<input type="text" id="terminal-input" placeholder="Type command..." disabled />
</div>
</div>
</div>
</div>
</main>
<!-- Flash progress overlay --> <!-- Flash progress overlay -->
<div id="flash-overlay" class="hidden"> <div id="flash-overlay" class="hidden">
<div id="flash-modal"> <div id="flash-modal">

View File

@ -13,8 +13,8 @@ import { connect, disconnect, isConnected, onData, writeString } from './serial/
import { executeCode, stopExecution, saveToDevice, writeFileToDevice } from './serial/repl.js'; import { executeCode, stopExecution, saveToDevice, writeFileToDevice } from './serial/repl.js';
import { flashFirmware } from './serial/flasher.js'; import { flashFirmware } from './serial/flasher.js';
import { appendToTerminal, clearTerminal } from './ui/terminal.js'; import { appendToTerminal, clearTerminal } from './ui/terminal.js';
import { initResizablePanels } from './ui/panels.js'; import { initResizablePanels, initPanelToggles, initProjectTabs, setProjectsPanelCallbacks } from './ui/panels.js';
import { initProjectsDialog, open as openProjects } from './ui/projectsDialog.js'; import { initProjectsDialog, refreshAll as refreshProjects, refreshDeviceList } from './ui/projectsDialog.js';
import './style.css'; import './style.css';
// ─── Blockly Workspace ─────────────────────────────────── // ─── Blockly Workspace ───────────────────────────────────
@ -77,14 +77,22 @@ loadWorkspace();
function onResize() { function onResize() {
const blocklyArea = document.getElementById('blockly-area'); const blocklyArea = document.getElementById('blockly-area');
const blocklyDiv = document.getElementById('blockly-div'); const blocklyDiv = document.getElementById('blockly-div');
if (blocklyArea && blocklyDiv) {
blocklyDiv.style.width = blocklyArea.offsetWidth + 'px'; blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
blocklyDiv.style.height = blocklyArea.offsetHeight + 'px'; blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
Blockly.svgResize(workspace); Blockly.svgResize(workspace);
}
} }
window.addEventListener('resize', onResize); window.addEventListener('resize', onResize);
onResize(); onResize();
initResizablePanels(); initResizablePanels();
initPanelToggles();
initProjectTabs();
setProjectsPanelCallbacks({
onDeviceTab: () => refreshDeviceList(),
onExpand: () => refreshProjects(),
});
// ─── UI State Helpers ──────────────────────────────────── // ─── UI State Helpers ────────────────────────────────────
@ -330,7 +338,14 @@ initProjectsDialog({
isConnected, isConnected,
}); });
btnProjects.addEventListener('click', () => openProjects()); btnProjects.addEventListener('click', () => {
const panel = document.getElementById('projects-panel');
panel.classList.toggle('collapsed');
const btn = panel.querySelector('.panel-toggle');
if (btn) btn.innerHTML = panel.classList.contains('collapsed') ? '&#9666;' : '&#9656;';
refreshProjects();
window.dispatchEvent(new Event('resize'));
});
terminalInput.addEventListener('keydown', async (e) => { terminalInput.addEventListener('keydown', async (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {

View File

@ -21,6 +21,7 @@
--border: #313244; --border: #313244;
--radius: 6px; --radius: 6px;
--toolbar-height: 48px; --toolbar-height: 48px;
--panel-header-height: 30px;
} }
html, body { html, body {
@ -140,13 +141,21 @@ html, body {
height: calc(100vh - var(--toolbar-height)); height: calc(100vh - var(--toolbar-height));
} }
#blockly-area { /* --- Top Area (Blockly + Projects sidebar) --- */
flex: 1 1 60%; #top-area {
position: relative; flex: 1 1 65%;
display: flex;
min-height: 200px; min-height: 200px;
overflow: hidden; overflow: hidden;
} }
#blockly-area {
flex: 1;
position: relative;
min-width: 200px;
overflow: hidden;
}
#blockly-div { #blockly-div {
position: absolute; position: absolute;
inset: 0; inset: 0;
@ -155,12 +164,17 @@ html, body {
/* --- Bottom Panels --- */ /* --- Bottom Panels --- */
#bottom-panels { #bottom-panels {
flex: 0 0 35%; flex: 0 0 35%;
min-height: 120px; min-height: 0;
display: flex; display: flex;
position: relative; position: relative;
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
} }
#bottom-panels.all-collapsed {
flex: 0 0 var(--panel-header-height);
min-height: var(--panel-header-height);
}
.resize-handle.horizontal { .resize-handle.horizontal {
position: absolute; position: absolute;
top: -3px; top: -3px;
@ -171,27 +185,79 @@ html, body {
z-index: 10; z-index: 10;
} }
#code-panel, #terminal-panel { /* --- Generic Panel --- */
.ide-panel {
display: flex;
flex-direction: column;
overflow: hidden;
}
.ide-panel .panel-body {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
} }
.ide-panel.collapsed .panel-body {
display: none;
}
.panel-header {
background: var(--bg-secondary);
padding: 0 12px;
height: var(--panel-header-height);
min-height: var(--panel-header-height);
font-size: 11px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.8px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.panel-header .panel-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.panel-toggle {
background: none;
border: none;
color: var(--text-muted);
font-size: 12px;
cursor: pointer;
padding: 2px 4px;
line-height: 1;
border-radius: 3px;
transition: color 0.15s, background 0.15s;
flex-shrink: 0;
}
.panel-toggle:hover {
color: var(--text-primary);
background: var(--bg-surface);
}
/* --- Bottom panel specifics --- */
#code-panel, #terminal-panel {
flex: 1;
overflow: hidden;
}
#code-panel { #code-panel {
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
} }
.panel-header { #code-panel.collapsed,
background: var(--bg-secondary); #terminal-panel.collapsed {
padding: 6px 12px; flex: 0 0 auto;
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.8px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
} }
/* --- Code Preview --- */ /* --- Code Preview --- */
@ -206,10 +272,11 @@ html, body {
color: var(--text-primary); color: var(--text-primary);
white-space: pre; white-space: pre;
tab-size: 4; tab-size: 4;
margin: 0;
} }
/* --- Terminal --- */ /* --- Terminal --- */
#terminal-panel { #terminal-panel .panel-body {
background: var(--bg-secondary); background: var(--bg-secondary);
} }
@ -242,12 +309,181 @@ html, body {
outline: none; outline: none;
} }
#terminal-input:disabled { #terminal-input:disabled { opacity: 0.5; }
opacity: 0.5; #terminal-input::placeholder { color: var(--text-muted); }
/* --- Projects Sidebar Panel --- */
#projects-panel {
width: 280px;
min-width: 0;
border-left: 1px solid var(--border);
background: var(--bg-secondary);
transition: width 0.15s ease;
} }
#terminal-input::placeholder { #projects-panel.collapsed {
width: 30px;
min-width: 30px;
}
#projects-panel.collapsed .panel-header {
writing-mode: vertical-rl;
text-orientation: mixed;
height: 100%;
width: 30px;
padding: 10px 0;
justify-content: flex-start;
gap: 8px;
border-bottom: none;
border-left: none;
}
#projects-panel.collapsed .panel-toggle {
writing-mode: horizontal-tb;
}
/* Projects tabs */
.proj-tabs {
display: flex;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.proj-tab {
flex: 1;
background: none;
border: none;
border-bottom: 2px solid transparent;
color: var(--text-muted); color: var(--text-muted);
font-size: 12px;
font-weight: 600;
padding: 8px 4px;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: color 0.15s, border-color 0.15s;
}
.proj-tab:hover { color: var(--text-primary); }
.proj-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.proj-tab-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px;
overflow-y: auto;
}
.proj-tab-content.hidden { display: none; }
/* Projects list */
.projects-list {
list-style: none;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: var(--radius);
min-height: 100px;
max-height: 40vh;
overflow-y: auto;
padding: 4px;
}
.projects-list li {
padding: 6px 10px;
cursor: pointer;
border-radius: 4px;
font-size: 12px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.projects-list li:hover { background: var(--bg-surface); }
.projects-list li.selected {
background: var(--accent);
color: var(--bg-toolbar);
}
.projects-list .empty-msg {
padding: 6px 10px;
color: var(--text-muted);
font-size: 11px;
font-style: italic;
cursor: default;
}
/* Projects actions */
.projects-actions {
display: flex;
flex-direction: column;
gap: 6px;
}
.projects-name-row {
display: flex;
gap: 4px;
}
.projects-name-row input {
flex: 1;
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 5px 8px;
font-size: 12px;
outline: none;
min-width: 0;
}
.projects-name-row input:focus { border-color: var(--accent); }
.projects-btn-row {
display: flex;
gap: 4px;
}
.projects-actions button {
background: var(--bg-surface);
color: var(--text-primary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 5px 10px;
font-size: 12px;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
flex: 1;
}
.projects-actions button:hover:not(:disabled) {
background: var(--accent);
color: var(--bg-toolbar);
border-color: var(--accent);
}
.projects-actions button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.projects-actions button.btn-danger:hover:not(:disabled) {
background: var(--red);
border-color: var(--red);
}
.projects-note {
font-size: 11px;
color: var(--text-muted);
font-style: italic;
min-height: 16px;
} }
/* --- Blockly Overrides (dark theme) --- */ /* --- Blockly Overrides (dark theme) --- */
@ -273,206 +509,15 @@ html, body {
} }
/* Scrollbar styling */ /* Scrollbar styling */
::-webkit-scrollbar { ::-webkit-scrollbar { width: 8px; height: 8px; }
width: 8px; ::-webkit-scrollbar-track { background: var(--bg-secondary); }
height: 8px; ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
} ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
::-webkit-scrollbar-track { /* --- Utility --- */
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* --- Projects Dialog --- */
#projects-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
#projects-modal {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px 28px;
width: 680px;
max-width: 95vw;
max-height: 85vh;
display: flex;
flex-direction: column;
gap: 16px;
}
.projects-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.projects-header h3 {
margin: 0;
color: var(--accent);
font-size: 16px;
}
#projects-close {
background: none;
border: none;
color: var(--text-muted);
font-size: 22px;
cursor: pointer;
padding: 0 4px;
line-height: 1;
}
#projects-close:hover {
color: var(--text-primary);
}
.projects-columns {
display: flex;
gap: 16px;
}
.projects-col {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
}
.projects-col h4 {
margin: 0;
font-size: 13px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.6px;
}
.projects-list {
list-style: none;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
min-height: 180px;
max-height: 260px;
overflow-y: auto;
padding: 4px;
}
.projects-list li {
padding: 8px 12px;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.projects-list li:hover {
background: var(--bg-surface);
}
.projects-list li.selected {
background: var(--accent);
color: var(--bg-toolbar);
}
.projects-list .empty-msg {
padding: 8px 12px;
color: var(--text-muted);
font-size: 12px;
font-style: italic;
cursor: default;
}
.projects-actions {
display: flex;
flex-direction: column;
gap: 8px;
}
.projects-name-row {
display: flex;
gap: 6px;
}
.projects-name-row input {
flex: 1;
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 6px 10px;
font-size: 13px;
outline: none;
min-width: 0;
}
.projects-name-row input:focus {
border-color: var(--accent);
}
.projects-btn-row {
display: flex;
gap: 6px;
}
.projects-actions button {
background: var(--bg-surface);
color: var(--text-primary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
flex: 1;
}
.projects-actions button:hover:not(:disabled) {
background: var(--accent);
color: var(--bg-toolbar);
border-color: var(--accent);
}
.projects-actions button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.projects-actions button.btn-danger:hover:not(:disabled) {
background: var(--red);
border-color: var(--red);
}
.projects-note {
font-size: 12px;
color: var(--text-muted);
font-style: italic;
min-height: 18px;
}
/* --- Flash Progress Overlay --- */
.hidden { display: none !important; } .hidden { display: none !important; }
/* --- Flash Progress Overlay --- */
#flash-overlay { #flash-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;

View File

@ -1,10 +1,10 @@
export function initResizablePanels() { export function initResizablePanels() {
const handle = document.getElementById('resize-handle-h'); const handle = document.getElementById('resize-handle-h');
const container = document.getElementById('workspace-container'); const container = document.getElementById('workspace-container');
const blocklyArea = document.getElementById('blockly-area'); const topArea = document.getElementById('top-area');
const bottomPanels = document.getElementById('bottom-panels'); const bottomPanels = document.getElementById('bottom-panels');
if (!handle || !container || !blocklyArea || !bottomPanels) return; if (!handle || !container || !topArea || !bottomPanels) return;
let dragging = false; let dragging = false;
@ -15,18 +15,89 @@ export function initResizablePanels() {
document.addEventListener('mousemove', (e) => { document.addEventListener('mousemove', (e) => {
if (!dragging) return; if (!dragging) return;
const containerRect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
const offset = e.clientY - containerRect.top; const offset = e.clientY - rect.top;
const totalHeight = containerRect.height; const pct = Math.max(20, Math.min(85, (offset / rect.height) * 100));
const topPercent = Math.max(20, Math.min(80, (offset / totalHeight) * 100));
blocklyArea.style.flex = `0 0 ${topPercent}%`; topArea.style.flex = `0 0 ${pct}%`;
bottomPanels.style.flex = `0 0 ${100 - topPercent}%`; bottomPanels.style.flex = `0 0 ${100 - pct}%`;
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
}); });
document.addEventListener('mouseup', () => { document.addEventListener('mouseup', () => { dragging = false; });
dragging = false; }
export function initPanelToggles() {
const bottomPanels = document.getElementById('bottom-panels');
document.querySelectorAll('.panel-toggle').forEach(btn => {
btn.addEventListener('click', () => {
const panel = document.getElementById(btn.dataset.panel);
if (!panel) return;
panel.classList.toggle('collapsed');
updateToggleIcon(btn, panel);
if (bottomPanels) {
const code = document.getElementById('code-panel');
const term = document.getElementById('terminal-panel');
const allCollapsed = code?.classList.contains('collapsed') && term?.classList.contains('collapsed');
bottomPanels.classList.toggle('all-collapsed', allCollapsed);
}
if (btn.dataset.panel === 'projects-panel' && !panel.classList.contains('collapsed') && onProjectsPanelExpanded) {
onProjectsPanelExpanded();
}
window.dispatchEvent(new Event('resize'));
});
});
document.querySelectorAll('.panel-toggle').forEach(btn => {
const panel = document.getElementById(btn.dataset.panel);
if (panel) updateToggleIcon(btn, panel);
});
}
function updateToggleIcon(btn, panel) {
const collapsed = panel.classList.contains('collapsed');
const id = panel.id;
if (id === 'projects-panel') {
btn.innerHTML = collapsed ? '&#9666;' : '&#9656;';
} else {
btn.innerHTML = collapsed ? '&#9656;' : '&#9662;';
}
}
let onDeviceTabActivated = null;
let onProjectsPanelExpanded = null;
export function setProjectsPanelCallbacks({ onDeviceTab, onExpand }) {
onDeviceTabActivated = onDeviceTab;
onProjectsPanelExpanded = onExpand;
}
export function initProjectTabs() {
const tabs = document.querySelectorAll('.proj-tab');
const contents = {
browser: document.getElementById('proj-tab-browser'),
device: document.getElementById('proj-tab-device'),
};
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
Object.values(contents).forEach(c => c?.classList.add('hidden'));
const target = contents[tab.dataset.tab];
if (target) target.classList.remove('hidden');
if (tab.dataset.tab === 'device' && onDeviceTabActivated) {
onDeviceTabActivated();
}
});
}); });
} }

View File

@ -8,8 +8,7 @@ let execCode = null;
let writeFile = null; let writeFile = null;
let checkConnected = null; let checkConnected = null;
// DOM refs (cached on first open) let browserList, deviceList;
let overlay, browserList, deviceList;
let browserNameInput, deviceNameInput; let browserNameInput, deviceNameInput;
let browserSaveBtn, browserLoadBtn, browserDeleteBtn; let browserSaveBtn, browserLoadBtn, browserDeleteBtn;
let deviceSaveBtn, deviceLoadBtn, deviceDeleteBtn; let deviceSaveBtn, deviceLoadBtn, deviceDeleteBtn;
@ -25,7 +24,6 @@ export function initProjectsDialog(deps) {
writeFile = deps.writeFileToDevice; writeFile = deps.writeFileToDevice;
checkConnected = deps.isConnected; checkConnected = deps.isConnected;
overlay = document.getElementById('projects-overlay');
browserList = document.getElementById('browser-list'); browserList = document.getElementById('browser-list');
deviceList = document.getElementById('device-list'); deviceList = document.getElementById('device-list');
browserNameInput = document.getElementById('browser-save-name'); browserNameInput = document.getElementById('browser-save-name');
@ -38,27 +36,20 @@ export function initProjectsDialog(deps) {
deviceDeleteBtn = document.getElementById('device-delete-btn'); deviceDeleteBtn = document.getElementById('device-delete-btn');
deviceStatus = document.getElementById('device-status'); deviceStatus = document.getElementById('device-status');
document.getElementById('projects-close').addEventListener('click', close);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
browserSaveBtn.addEventListener('click', saveBrowser); browserSaveBtn.addEventListener('click', saveBrowser);
browserLoadBtn.addEventListener('click', loadBrowser); browserLoadBtn.addEventListener('click', loadBrowser);
browserDeleteBtn.addEventListener('click', deleteBrowser); browserDeleteBtn.addEventListener('click', deleteBrowser);
deviceSaveBtn.addEventListener('click', saveDevice); deviceSaveBtn.addEventListener('click', saveDevice);
deviceLoadBtn.addEventListener('click', loadDevice); deviceLoadBtn.addEventListener('click', loadDevice);
deviceDeleteBtn.addEventListener('click', deleteDevice); deviceDeleteBtn.addEventListener('click', deleteDevice);
}
export function open() {
browserSelected = null;
deviceSelected = null;
overlay.classList.remove('hidden');
refreshBrowserList(); refreshBrowserList();
refreshDeviceList(); refreshDeviceList();
} }
function close() { export function refreshAll() {
overlay.classList.add('hidden'); refreshBrowserList();
refreshDeviceList();
} }
// ─── Browser column ────────────────────────────────────── // ─── Browser column ──────────────────────────────────────
@ -123,7 +114,6 @@ function loadBrowser() {
const state = projects[browserSelected]; const state = projects[browserSelected];
if (!state) return; if (!state) return;
Blockly.serialization.workspaces.load(state, workspace); Blockly.serialization.workspaces.load(state, workspace);
close();
} }
function deleteBrowser() { function deleteBrowser() {
@ -136,7 +126,7 @@ function deleteBrowser() {
// ─── Device column ─────────────────────────────────────── // ─── Device column ───────────────────────────────────────
async function refreshDeviceList() { export async function refreshDeviceList() {
deviceList.innerHTML = ''; deviceList.innerHTML = '';
deviceSelected = null; deviceSelected = null;
updateDeviceButtons(); updateDeviceButtons();
@ -223,7 +213,7 @@ async function loadDevice() {
); );
const state = JSON.parse(raw.trim()); const state = JSON.parse(raw.trim());
Blockly.serialization.workspaces.load(state, workspace); Blockly.serialization.workspaces.load(state, workspace);
close(); deviceStatus.textContent = '';
} catch { } catch {
deviceStatus.textContent = 'Load failed'; deviceStatus.textContent = 'Load failed';
} }