Upload 15 files
Browse files- static/app.js +33 -13
- static/index.html +7 -4
- static/style.css +64 -2
static/app.js
CHANGED
|
@@ -75,18 +75,36 @@ function toast(message, type = "info") {
|
|
| 75 |
// ── Zone Management ──────────────────────────
|
| 76 |
async function loadZones() {
|
| 77 |
const zones = await api("/api/zones");
|
|
|
|
|
|
|
| 78 |
const list = document.getElementById("zone-list");
|
| 79 |
if (zones.length === 0) {
|
| 80 |
list.innerHTML = `<li class="empty-hint" style="color:var(--text-3);font-size:12px;padding:8px 10px;cursor:default;opacity:0.6">No zones yet</li>`;
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
}
|
| 83 |
-
list.innerHTML = zones.map(z => `
|
| 84 |
-
<li data-zone="${escapeAttr(z.name)}" class="${currentZone === z.name ? 'active' : ''}" onclick="openZone('${escapeAttr(z.name)}')">
|
| 85 |
-
<span class="zone-icon"><i data-lucide="box"></i></span>
|
| 86 |
-
<span class="zone-name">${escapeHtml(z.name)}</span>
|
| 87 |
-
</li>
|
| 88 |
-
`).join("");
|
| 89 |
-
lucide.createIcons({ nodes: [list] });
|
| 90 |
}
|
| 91 |
|
| 92 |
async function openZone(name) {
|
|
@@ -100,7 +118,7 @@ async function openZone(name) {
|
|
| 100 |
|
| 101 |
// Reset editor
|
| 102 |
const editorContainer = document.getElementById("editor-container");
|
| 103 |
-
editorContainer.innerHTML = `<div class="editor-empty"><i data-lucide="mouse-pointer-click"></i><p>
|
| 104 |
document.getElementById("editor-tabs").innerHTML = `<span class="tab-placeholder"><i data-lucide="code-2"></i> No file open</span>`;
|
| 105 |
lucide.createIcons({ nodes: [editorContainer, document.getElementById("editor-tabs")] });
|
| 106 |
cmEditor = null;
|
|
@@ -191,7 +209,7 @@ function renderFiles(files) {
|
|
| 191 |
}
|
| 192 |
let html = "";
|
| 193 |
if (currentPath) {
|
| 194 |
-
html += `<div class="file-item"
|
| 195 |
<span class="fi-icon fi-icon-back"><i data-lucide="corner-left-up"></i></span>
|
| 196 |
<span class="fi-name">..</span>
|
| 197 |
</div>`;
|
|
@@ -205,10 +223,9 @@ function renderFiles(files) {
|
|
| 205 |
const iconClass = f.is_dir ? "fi-icon-folder" : fileIconClass(f.name);
|
| 206 |
const iconName = f.is_dir ? "folder" : "file-text";
|
| 207 |
const size = f.is_dir ? "" : formatSize(f.size);
|
| 208 |
-
const
|
| 209 |
-
const tapAction = f.is_dir ? `navigateTo('${escapeAttr(relPath)}')` : `editFile('${escapeAttr(relPath)}')`;
|
| 210 |
|
| 211 |
-
html += `<div class="file-item"
|
| 212 |
<span class="fi-icon ${iconClass}"><i data-lucide="${iconName}"></i></span>
|
| 213 |
<span class="fi-name">${escapeHtml(f.name)}</span>
|
| 214 |
<span class="fi-size">${size}</span>
|
|
@@ -724,12 +741,15 @@ function fileIconClass(name) {
|
|
| 724 |
function toggleSidebar(open) {
|
| 725 |
const sidebar = document.getElementById("sidebar");
|
| 726 |
const backdrop = document.getElementById("sidebar-backdrop");
|
|
|
|
| 727 |
if (open) {
|
| 728 |
sidebar.classList.add("open");
|
| 729 |
backdrop.classList.add("open");
|
|
|
|
| 730 |
} else {
|
| 731 |
sidebar.classList.remove("open");
|
| 732 |
backdrop.classList.remove("open");
|
|
|
|
| 733 |
}
|
| 734 |
}
|
| 735 |
|
|
|
|
| 75 |
// ── Zone Management ──────────────────────────
|
| 76 |
async function loadZones() {
|
| 77 |
const zones = await api("/api/zones");
|
| 78 |
+
|
| 79 |
+
// Sidebar zone list
|
| 80 |
const list = document.getElementById("zone-list");
|
| 81 |
if (zones.length === 0) {
|
| 82 |
list.innerHTML = `<li class="empty-hint" style="color:var(--text-3);font-size:12px;padding:8px 10px;cursor:default;opacity:0.6">No zones yet</li>`;
|
| 83 |
+
} else {
|
| 84 |
+
list.innerHTML = zones.map(z => `
|
| 85 |
+
<li data-zone="${escapeAttr(z.name)}" class="${currentZone === z.name ? 'active' : ''}" onclick="openZone('${escapeAttr(z.name)}')">
|
| 86 |
+
<span class="zone-icon"><i data-lucide="box"></i></span>
|
| 87 |
+
<span class="zone-name">${escapeHtml(z.name)}</span>
|
| 88 |
+
</li>
|
| 89 |
+
`).join("");
|
| 90 |
+
lucide.createIcons({ nodes: [list] });
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// Welcome page zone cards (so users can pick zone without sidebar)
|
| 94 |
+
const welcomeZones = document.getElementById("welcome-zones");
|
| 95 |
+
if (welcomeZones) {
|
| 96 |
+
if (zones.length === 0) {
|
| 97 |
+
welcomeZones.innerHTML = `<p class="welcome-hint">No zones yet — create one to get started</p>`;
|
| 98 |
+
} else {
|
| 99 |
+
welcomeZones.innerHTML = `<div class="zone-grid">${zones.map(z => `
|
| 100 |
+
<button class="zone-card" onclick="openZone('${escapeAttr(z.name)}')">
|
| 101 |
+
<i data-lucide="box"></i>
|
| 102 |
+
<span>${escapeHtml(z.name)}</span>
|
| 103 |
+
</button>
|
| 104 |
+
`).join("")}</div>`;
|
| 105 |
+
lucide.createIcons({ nodes: [welcomeZones] });
|
| 106 |
+
}
|
| 107 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
}
|
| 109 |
|
| 110 |
async function openZone(name) {
|
|
|
|
| 118 |
|
| 119 |
// Reset editor
|
| 120 |
const editorContainer = document.getElementById("editor-container");
|
| 121 |
+
editorContainer.innerHTML = `<div class="editor-empty"><i data-lucide="mouse-pointer-click"></i><p>Tap a file to open</p></div>`;
|
| 122 |
document.getElementById("editor-tabs").innerHTML = `<span class="tab-placeholder"><i data-lucide="code-2"></i> No file open</span>`;
|
| 123 |
lucide.createIcons({ nodes: [editorContainer, document.getElementById("editor-tabs")] });
|
| 124 |
cmEditor = null;
|
|
|
|
| 209 |
}
|
| 210 |
let html = "";
|
| 211 |
if (currentPath) {
|
| 212 |
+
html += `<div class="file-item" onclick="navigateUp()">
|
| 213 |
<span class="fi-icon fi-icon-back"><i data-lucide="corner-left-up"></i></span>
|
| 214 |
<span class="fi-name">..</span>
|
| 215 |
</div>`;
|
|
|
|
| 223 |
const iconClass = f.is_dir ? "fi-icon-folder" : fileIconClass(f.name);
|
| 224 |
const iconName = f.is_dir ? "folder" : "file-text";
|
| 225 |
const size = f.is_dir ? "" : formatSize(f.size);
|
| 226 |
+
const action = f.is_dir ? `navigateTo('${escapeAttr(relPath)}')` : `editFile('${escapeAttr(relPath)}')`;
|
|
|
|
| 227 |
|
| 228 |
+
html += `<div class="file-item" onclick="${action}">
|
| 229 |
<span class="fi-icon ${iconClass}"><i data-lucide="${iconName}"></i></span>
|
| 230 |
<span class="fi-name">${escapeHtml(f.name)}</span>
|
| 231 |
<span class="fi-size">${size}</span>
|
|
|
|
| 741 |
function toggleSidebar(open) {
|
| 742 |
const sidebar = document.getElementById("sidebar");
|
| 743 |
const backdrop = document.getElementById("sidebar-backdrop");
|
| 744 |
+
const hamburger = document.getElementById("btn-hamburger");
|
| 745 |
if (open) {
|
| 746 |
sidebar.classList.add("open");
|
| 747 |
backdrop.classList.add("open");
|
| 748 |
+
if (hamburger) hamburger.style.display = "none";
|
| 749 |
} else {
|
| 750 |
sidebar.classList.remove("open");
|
| 751 |
backdrop.classList.remove("open");
|
| 752 |
+
if (hamburger && isMobile) hamburger.style.display = "";
|
| 753 |
}
|
| 754 |
}
|
| 755 |
|
static/index.html
CHANGED
|
@@ -72,6 +72,11 @@
|
|
| 72 |
|
| 73 |
<!-- Main -->
|
| 74 |
<main id="main">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
<!-- Welcome -->
|
| 76 |
<div id="welcome" class="view active">
|
| 77 |
<div class="welcome-hero">
|
|
@@ -79,6 +84,7 @@
|
|
| 79 |
<div class="welcome-icon"><i data-lucide="server"></i></div>
|
| 80 |
<h1>HugPanel</h1>
|
| 81 |
<p>Multi-zone workspace with file manager, code editor & terminal.</p>
|
|
|
|
| 82 |
<button class="btn btn-accent btn-lg" onclick="document.getElementById('btn-add-zone').click()">
|
| 83 |
<i data-lucide="plus"></i> Create Zone
|
| 84 |
</button>
|
|
@@ -89,9 +95,6 @@
|
|
| 89 |
<div id="workspace" class="view">
|
| 90 |
<div class="topbar">
|
| 91 |
<div class="topbar-left">
|
| 92 |
-
<button id="btn-hamburger" class="icon-btn-sm hamburger-btn" onclick="toggleSidebar(true)" title="Menu">
|
| 93 |
-
<i data-lucide="menu"></i>
|
| 94 |
-
</button>
|
| 95 |
<span class="zone-indicator" id="zone-badge">
|
| 96 |
<i data-lucide="box"></i>
|
| 97 |
<span id="zone-title"></span>
|
|
@@ -145,7 +148,7 @@
|
|
| 145 |
<div id="editor-container" class="editor-container">
|
| 146 |
<div class="editor-empty">
|
| 147 |
<i data-lucide="mouse-pointer-click"></i>
|
| 148 |
-
<p>
|
| 149 |
</div>
|
| 150 |
</div>
|
| 151 |
</div>
|
|
|
|
| 72 |
|
| 73 |
<!-- Main -->
|
| 74 |
<main id="main">
|
| 75 |
+
<!-- Global hamburger (visible on mobile for both views) -->
|
| 76 |
+
<button id="btn-hamburger" class="icon-btn-sm hamburger-global" onclick="toggleSidebar(true)" title="Menu">
|
| 77 |
+
<i data-lucide="menu"></i>
|
| 78 |
+
</button>
|
| 79 |
+
|
| 80 |
<!-- Welcome -->
|
| 81 |
<div id="welcome" class="view active">
|
| 82 |
<div class="welcome-hero">
|
|
|
|
| 84 |
<div class="welcome-icon"><i data-lucide="server"></i></div>
|
| 85 |
<h1>HugPanel</h1>
|
| 86 |
<p>Multi-zone workspace with file manager, code editor & terminal.</p>
|
| 87 |
+
<div id="welcome-zones" class="welcome-zones"></div>
|
| 88 |
<button class="btn btn-accent btn-lg" onclick="document.getElementById('btn-add-zone').click()">
|
| 89 |
<i data-lucide="plus"></i> Create Zone
|
| 90 |
</button>
|
|
|
|
| 95 |
<div id="workspace" class="view">
|
| 96 |
<div class="topbar">
|
| 97 |
<div class="topbar-left">
|
|
|
|
|
|
|
|
|
|
| 98 |
<span class="zone-indicator" id="zone-badge">
|
| 99 |
<i data-lucide="box"></i>
|
| 100 |
<span id="zone-title"></span>
|
|
|
|
| 148 |
<div id="editor-container" class="editor-container">
|
| 149 |
<div class="editor-empty">
|
| 150 |
<i data-lucide="mouse-pointer-click"></i>
|
| 151 |
+
<p>Tap a file to open</p>
|
| 152 |
</div>
|
| 153 |
</div>
|
| 154 |
</div>
|
static/style.css
CHANGED
|
@@ -284,6 +284,64 @@ body {
|
|
| 284 |
text-align: center;
|
| 285 |
}
|
| 286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
/* ── Topbar ────────────────── */
|
| 288 |
.topbar {
|
| 289 |
height: var(--topbar-h);
|
|
@@ -399,6 +457,7 @@ body {
|
|
| 399 |
}
|
| 400 |
|
| 401 |
.file-item:hover { background: var(--bg-hover); }
|
|
|
|
| 402 |
|
| 403 |
.file-item .fi-icon {
|
| 404 |
width: 16px; height: 16px;
|
|
@@ -845,7 +904,6 @@ body {
|
|
| 845 |
}
|
| 846 |
|
| 847 |
.sidebar-close-btn { display: none; }
|
| 848 |
-
.hamburger-btn { display: none; }
|
| 849 |
.mobile-only { display: none !important; }
|
| 850 |
.mobile-tabs { display: none; }
|
| 851 |
|
|
@@ -886,7 +944,7 @@ body {
|
|
| 886 |
margin-left: auto;
|
| 887 |
}
|
| 888 |
|
| 889 |
-
.hamburger-
|
| 890 |
|
| 891 |
/* Sidebar brand spacing */
|
| 892 |
.sidebar-brand { padding: 12px 14px 10px; }
|
|
@@ -901,6 +959,10 @@ body {
|
|
| 901 |
|
| 902 |
.zone-list li .zone-icon { width: 20px; height: 20px; }
|
| 903 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 904 |
/* Main takes full width */
|
| 905 |
#main { width: 100%; }
|
| 906 |
|
|
|
|
| 284 |
text-align: center;
|
| 285 |
}
|
| 286 |
|
| 287 |
+
/* ── Welcome Zone Grid ─────── */
|
| 288 |
+
.welcome-zones {
|
| 289 |
+
max-width: 480px;
|
| 290 |
+
width: 100%;
|
| 291 |
+
padding: 0 16px;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.welcome-hint {
|
| 295 |
+
color: var(--text-3);
|
| 296 |
+
font-size: 13px;
|
| 297 |
+
text-align: center;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.zone-grid {
|
| 301 |
+
display: flex;
|
| 302 |
+
flex-wrap: wrap;
|
| 303 |
+
gap: 8px;
|
| 304 |
+
justify-content: center;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.zone-card {
|
| 308 |
+
display: flex;
|
| 309 |
+
align-items: center;
|
| 310 |
+
gap: 8px;
|
| 311 |
+
padding: 10px 18px;
|
| 312 |
+
background: var(--bg-2);
|
| 313 |
+
border: 1px solid var(--border);
|
| 314 |
+
border-radius: var(--radius);
|
| 315 |
+
color: var(--text);
|
| 316 |
+
font-size: 14px;
|
| 317 |
+
font-weight: 500;
|
| 318 |
+
font-family: var(--font);
|
| 319 |
+
cursor: pointer;
|
| 320 |
+
transition: all var(--transition);
|
| 321 |
+
-webkit-tap-highlight-color: transparent;
|
| 322 |
+
min-height: 44px;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
.zone-card svg { width: 16px; height: 16px; color: var(--accent); flex-shrink: 0; }
|
| 326 |
+
.zone-card:hover { background: var(--bg-hover); border-color: var(--accent); }
|
| 327 |
+
.zone-card:active { background: var(--bg-active); transform: scale(0.97); }
|
| 328 |
+
|
| 329 |
+
/* ── Global Hamburger ──────── */
|
| 330 |
+
.hamburger-global {
|
| 331 |
+
display: none;
|
| 332 |
+
position: fixed;
|
| 333 |
+
top: 10px;
|
| 334 |
+
left: 10px;
|
| 335 |
+
z-index: 40;
|
| 336 |
+
width: 40px;
|
| 337 |
+
height: 40px;
|
| 338 |
+
background: var(--bg-2);
|
| 339 |
+
border: 1px solid var(--border);
|
| 340 |
+
border-radius: var(--radius);
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.hamburger-global svg { width: 20px; height: 20px; }
|
| 344 |
+
|
| 345 |
/* ── Topbar ────────────────── */
|
| 346 |
.topbar {
|
| 347 |
height: var(--topbar-h);
|
|
|
|
| 457 |
}
|
| 458 |
|
| 459 |
.file-item:hover { background: var(--bg-hover); }
|
| 460 |
+
.file-item:active { background: var(--bg-active); }
|
| 461 |
|
| 462 |
.file-item .fi-icon {
|
| 463 |
width: 16px; height: 16px;
|
|
|
|
| 904 |
}
|
| 905 |
|
| 906 |
.sidebar-close-btn { display: none; }
|
|
|
|
| 907 |
.mobile-only { display: none !important; }
|
| 908 |
.mobile-tabs { display: none; }
|
| 909 |
|
|
|
|
| 944 |
margin-left: auto;
|
| 945 |
}
|
| 946 |
|
| 947 |
+
.hamburger-global { display: inline-flex; }
|
| 948 |
|
| 949 |
/* Sidebar brand spacing */
|
| 950 |
.sidebar-brand { padding: 12px 14px 10px; }
|
|
|
|
| 959 |
|
| 960 |
.zone-list li .zone-icon { width: 20px; height: 20px; }
|
| 961 |
|
| 962 |
+
/* Welcome zones on mobile */
|
| 963 |
+
.welcome-zones { max-width: 100%; padding: 0 12px; }
|
| 964 |
+
.zone-card { flex: 1; min-width: 120px; justify-content: center; }
|
| 965 |
+
|
| 966 |
/* Main takes full width */
|
| 967 |
#main { width: 100%; }
|
| 968 |
|