Spaces:
Paused
Paused
Update public/index.html
Browse files- public/index.html +31 -7
public/index.html
CHANGED
|
@@ -26,7 +26,7 @@
|
|
| 26 |
.bread a { color: var(--link); text-decoration:none; }
|
| 27 |
.bread span.sep { margin: 0 6px; color: var(--muted); }
|
| 28 |
main { padding:18px 20px; max-width:1100px; margin:auto; }
|
| 29 |
-
.bar { display:flex; gap:8px; flex-wrap: wrap; margin-bottom:12px; }
|
| 30 |
.bar input[type="text"] { background:var(--panel-alt); color:var(--txt);
|
| 31 |
border:1px solid var(--border); border-radius:8px; padding:8px 10px; min-width: 220px; }
|
| 32 |
.btn { background:var(--btn-bg); color:var(--txt); border:1px solid var(--border2);
|
|
@@ -42,19 +42,19 @@
|
|
| 42 |
tbody tr:hover { background:var(--panel-alt); }
|
| 43 |
.name { display:flex; align-items:center; gap:10px; }
|
| 44 |
.name a { color: var(--txt); text-decoration:none; }
|
| 45 |
-
.muted { color:
|
| 46 |
.right { text-align:right; }
|
| 47 |
.actions { display:flex; gap:6px; }
|
| 48 |
.chip { font-size:12px; color:#0f172a; background:#a7f3d0; padding:2px 8px; border-radius:999px; }
|
| 49 |
footer { color: var(--muted); text-align:center; padding:12px; font-size:12px; }
|
| 50 |
-
.flex-gap { display:flex; gap:8px; align-items:center; flex-wrap: wrap; }
|
| 51 |
.spacer { flex:1; }
|
| 52 |
.hidden { display:none; }
|
| 53 |
.note { font-size:12px; color:var(--muted); }
|
| 54 |
-
.checkbox { width:18px; height:18px; }
|
| 55 |
.icon { width:18px; text-align:center; }
|
| 56 |
.toast { position:fixed; right:16px; bottom:16px; background:var(--panel); border:1px solid var(--border2);
|
| 57 |
padding:10px 12px; border-radius:8px; color:var(--txt); box-shadow:var(--shadow); }
|
|
|
|
| 58 |
</style>
|
| 59 |
</head>
|
| 60 |
<body class="theme-dark">
|
|
@@ -71,6 +71,8 @@
|
|
| 71 |
<input type="text" id="urlInput" placeholder="https://contoh.com/file.tgz" />
|
| 72 |
<input type="text" id="filenameInput" placeholder="Nama file (opsional)" />
|
| 73 |
<button class="btn pri" id="fetchBtn">Save dari URL</button>
|
|
|
|
|
|
|
| 74 |
|
| 75 |
<input type="file" id="fileInput" multiple />
|
| 76 |
<button class="btn" id="uploadBtn">Upload</button>
|
|
@@ -119,6 +121,8 @@
|
|
| 119 |
const urlInput = document.getElementById("urlInput");
|
| 120 |
const filenameInput = document.getElementById("filenameInput");
|
| 121 |
const fetchBtn = document.getElementById("fetchBtn");
|
|
|
|
|
|
|
| 122 |
const fileInput = document.getElementById("fileInput");
|
| 123 |
const uploadBtn = document.getElementById("uploadBtn");
|
| 124 |
const newFolderBtn = document.getElementById("newFolderBtn");
|
|
@@ -181,6 +185,14 @@
|
|
| 181 |
history.replaceState(null, "", url);
|
| 182 |
load();
|
| 183 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
async function load() {
|
| 186 |
const res = await fetch(`/api/list?path=${encodeURIComponent(currentPath)}`);
|
|
@@ -189,6 +201,7 @@
|
|
| 189 |
}
|
| 190 |
|
| 191 |
function render(data) {
|
|
|
|
| 192 |
breadcrumb.innerHTML = "";
|
| 193 |
data.breadcrumb.forEach((c, idx) => {
|
| 194 |
const a = document.createElement("a");
|
|
@@ -204,6 +217,7 @@
|
|
| 204 |
}
|
| 205 |
});
|
| 206 |
|
|
|
|
| 207 |
tbody.innerHTML = "";
|
| 208 |
if (!data.items.length) emptyState.classList.remove("hidden"); else emptyState.classList.add("hidden");
|
| 209 |
|
|
@@ -287,7 +301,7 @@
|
|
| 287 |
unzipBtn.className = "btn warn";
|
| 288 |
unzipBtn.textContent = "Unarchive";
|
| 289 |
unzipBtn.onclick = async () => {
|
| 290 |
-
const defaultName = item.name
|
| 291 |
const destDefault = `${currentPath ? currentPath + "/" : ""}${defaultName}`;
|
| 292 |
const dest = prompt("Extract ke folder (opsional):", destDefault);
|
| 293 |
const body = dest ? { zipPath: item.relPath, destDir: dest } : { zipPath: item.relPath };
|
|
@@ -352,8 +366,15 @@
|
|
| 352 |
const url = urlInput.value.trim();
|
| 353 |
const filename = filenameInput.value.trim();
|
| 354 |
if (!url) return showToast("URL kosong");
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
if (filename) body.filename = filename;
|
|
|
|
| 357 |
const res = await fetch("/api/fetch-url", {
|
| 358 |
method: "POST",
|
| 359 |
headers: { "Content-Type": "application/json" },
|
|
@@ -361,7 +382,10 @@
|
|
| 361 |
});
|
| 362 |
const j = await res.json();
|
| 363 |
if (!res.ok) return showToast(j.error || "Gagal download dari URL");
|
| 364 |
-
|
|
|
|
|
|
|
|
|
|
| 365 |
urlInput.value = ""; filenameInput.value = "";
|
| 366 |
load();
|
| 367 |
};
|
|
|
|
| 26 |
.bread a { color: var(--link); text-decoration:none; }
|
| 27 |
.bread span.sep { margin: 0 6px; color: var(--muted); }
|
| 28 |
main { padding:18px 20px; max-width:1100px; margin:auto; }
|
| 29 |
+
.bar { display:flex; gap:8px; flex-wrap: wrap; margin-bottom:12px; align-items:center; }
|
| 30 |
.bar input[type="text"] { background:var(--panel-alt); color:var(--txt);
|
| 31 |
border:1px solid var(--border); border-radius:8px; padding:8px 10px; min-width: 220px; }
|
| 32 |
.btn { background:var(--btn-bg); color:var(--txt); border:1px solid var(--border2);
|
|
|
|
| 42 |
tbody tr:hover { background:var(--panel-alt); }
|
| 43 |
.name { display:flex; align-items:center; gap:10px; }
|
| 44 |
.name a { color: var(--txt); text-decoration:none; }
|
| 45 |
+
.muted { color:var(--muted); }
|
| 46 |
.right { text-align:right; }
|
| 47 |
.actions { display:flex; gap:6px; }
|
| 48 |
.chip { font-size:12px; color:#0f172a; background:#a7f3d0; padding:2px 8px; border-radius:999px; }
|
| 49 |
footer { color: var(--muted); text-align:center; padding:12px; font-size:12px; }
|
|
|
|
| 50 |
.spacer { flex:1; }
|
| 51 |
.hidden { display:none; }
|
| 52 |
.note { font-size:12px; color:var(--muted); }
|
| 53 |
+
.checkbox { width:18px; height:18px; vertical-align: middle; }
|
| 54 |
.icon { width:18px; text-align:center; }
|
| 55 |
.toast { position:fixed; right:16px; bottom:16px; background:var(--panel); border:1px solid var(--border2);
|
| 56 |
padding:10px 12px; border-radius:8px; color:var(--txt); box-shadow:var(--shadow); }
|
| 57 |
+
label.inline { display:inline-flex; align-items:center; gap:6px; font-size:13px; color:var(--muted); }
|
| 58 |
</style>
|
| 59 |
</head>
|
| 60 |
<body class="theme-dark">
|
|
|
|
| 71 |
<input type="text" id="urlInput" placeholder="https://contoh.com/file.tgz" />
|
| 72 |
<input type="text" id="filenameInput" placeholder="Nama file (opsional)" />
|
| 73 |
<button class="btn pri" id="fetchBtn">Save dari URL</button>
|
| 74 |
+
<label class="inline"><input type="checkbox" id="autoExtract" class="checkbox" checked /> Auto extract</label>
|
| 75 |
+
<label class="inline"><input type="checkbox" id="removeArchive" class="checkbox" /> Hapus arsip</label>
|
| 76 |
|
| 77 |
<input type="file" id="fileInput" multiple />
|
| 78 |
<button class="btn" id="uploadBtn">Upload</button>
|
|
|
|
| 121 |
const urlInput = document.getElementById("urlInput");
|
| 122 |
const filenameInput = document.getElementById("filenameInput");
|
| 123 |
const fetchBtn = document.getElementById("fetchBtn");
|
| 124 |
+
const autoExtractEl = document.getElementById("autoExtract");
|
| 125 |
+
const removeArchiveEl = document.getElementById("removeArchive");
|
| 126 |
const fileInput = document.getElementById("fileInput");
|
| 127 |
const uploadBtn = document.getElementById("uploadBtn");
|
| 128 |
const newFolderBtn = document.getElementById("newFolderBtn");
|
|
|
|
| 185 |
history.replaceState(null, "", url);
|
| 186 |
load();
|
| 187 |
}
|
| 188 |
+
function stripArchiveExt(name) {
|
| 189 |
+
const lower = String(name).toLowerCase();
|
| 190 |
+
if (lower.endsWith(".tar.gz")) return name.slice(0, -7);
|
| 191 |
+
if (lower.endsWith(".tgz")) return name.slice(0, -4);
|
| 192 |
+
if (lower.endsWith(".zip")) return name.slice(0, -4);
|
| 193 |
+
if (lower.endsWith(".tar")) return name.slice(0, -4);
|
| 194 |
+
return name;
|
| 195 |
+
}
|
| 196 |
|
| 197 |
async function load() {
|
| 198 |
const res = await fetch(`/api/list?path=${encodeURIComponent(currentPath)}`);
|
|
|
|
| 201 |
}
|
| 202 |
|
| 203 |
function render(data) {
|
| 204 |
+
// Breadcrumb
|
| 205 |
breadcrumb.innerHTML = "";
|
| 206 |
data.breadcrumb.forEach((c, idx) => {
|
| 207 |
const a = document.createElement("a");
|
|
|
|
| 217 |
}
|
| 218 |
});
|
| 219 |
|
| 220 |
+
// Rows
|
| 221 |
tbody.innerHTML = "";
|
| 222 |
if (!data.items.length) emptyState.classList.remove("hidden"); else emptyState.classList.add("hidden");
|
| 223 |
|
|
|
|
| 301 |
unzipBtn.className = "btn warn";
|
| 302 |
unzipBtn.textContent = "Unarchive";
|
| 303 |
unzipBtn.onclick = async () => {
|
| 304 |
+
const defaultName = stripArchiveExt(item.name);
|
| 305 |
const destDefault = `${currentPath ? currentPath + "/" : ""}${defaultName}`;
|
| 306 |
const dest = prompt("Extract ke folder (opsional):", destDefault);
|
| 307 |
const body = dest ? { zipPath: item.relPath, destDir: dest } : { zipPath: item.relPath };
|
|
|
|
| 366 |
const url = urlInput.value.trim();
|
| 367 |
const filename = filenameInput.value.trim();
|
| 368 |
if (!url) return showToast("URL kosong");
|
| 369 |
+
|
| 370 |
+
const body = {
|
| 371 |
+
url,
|
| 372 |
+
destDir: currentPath,
|
| 373 |
+
autoExtract: autoExtractEl.checked,
|
| 374 |
+
removeArchive: removeArchiveEl.checked
|
| 375 |
+
};
|
| 376 |
if (filename) body.filename = filename;
|
| 377 |
+
|
| 378 |
const res = await fetch("/api/fetch-url", {
|
| 379 |
method: "POST",
|
| 380 |
headers: { "Content-Type": "application/json" },
|
|
|
|
| 382 |
});
|
| 383 |
const j = await res.json();
|
| 384 |
if (!res.ok) return showToast(j.error || "Gagal download dari URL");
|
| 385 |
+
|
| 386 |
+
if (j.extractedTo) showToast(`Diunduh & diextract ke: ${j.extractedTo}`);
|
| 387 |
+
else showToast("Berhasil diunduh ke folder ini");
|
| 388 |
+
|
| 389 |
urlInput.value = ""; filenameInput.value = "";
|
| 390 |
load();
|
| 391 |
};
|