From 7b94f6a5b0ad023a3c529f056e2666c0d0d82ee8 Mon Sep 17 00:00:00 2001 From: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:57:14 +0300 Subject: [PATCH] sheets: reorder by dragging instead of up/down buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move-up/move-down felt disconnected from the result — clicking a button and having two rows silently swap position doesn't read as motion. Dropped both buttons for native HTML5 drag-and-drop: drag a sheet, an accent line shows where it'll land, drop to reorder. Still respects the pinned/unpinned grouping — dragging across that boundary is a no-op, same as the old up/down buttons. --- src/i18n.ts | 4 ---- src/main.ts | 47 +++++++++++++++++++++++++++++++++++++---------- src/ui/app.css | 8 +++++--- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/i18n.ts b/src/i18n.ts index 38689f6..9ccc9a2 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -61,8 +61,6 @@ const dict: Record> = { cancel: "Cancel", pin: "Pin", unpin: "Unpin", - moveUp: "Move up", - moveDown: "Move down", marketUpdated: "Market data updated", exportImage: "Copy as image", imageCopied: "Image copied", @@ -139,8 +137,6 @@ const dict: Record> = { cancel: "Отмена", pin: "Закрепить", unpin: "Открепить", - moveUp: "Выше", - moveDown: "Ниже", marketUpdated: "Рыночные данные обновлены", exportImage: "Скопировать картинкой", imageCopied: "Картинка скопирована", diff --git a/src/main.ts b/src/main.ts index 6ea0ace..7b36140 100644 --- a/src/main.ts +++ b/src/main.ts @@ -81,24 +81,28 @@ function pinDoc(id: string): void { renderDocList(); } -function moveDoc(id: string, dir: -1 | 1): void { - const idx = data.docs.findIndex((d) => d.id === id); - if (idx < 0) return; - const doc = data.docs[idx]; - const target = data.docs[idx + dir]; - if (!target || !!target.pinned !== !!doc.pinned) return; // can't cross pin boundary - data.docs.splice(idx, 1); - data.docs.splice(idx + dir, 0, doc); +/** drag id -> drop-target id; no-op across the pinned/unpinned boundary */ +function reorderDoc(srcId: string, targetId: string): void { + if (srcId === targetId) return; + const src = data.docs.find((d) => d.id === srcId); + const target = data.docs.find((d) => d.id === targetId); + if (!src || !target || !!src.pinned !== !!target.pinned) return; + data.docs = data.docs.filter((d) => d.id !== srcId); + const targetIdx = data.docs.findIndex((d) => d.id === targetId); + data.docs.splice(targetIdx, 0, src); saveAppData(data); renderDocList(); } +let dragSrcId: string | null = null; + function renderDocList(): void { const list = $("#doc-list"); list.replaceChildren(); for (const doc of data.docs) { const el = document.createElement("div"); el.className = "doc-item" + (doc.id === data.activeId ? " active" : "") + (doc.pinned ? " pinned" : ""); + el.draggable = true; const name = document.createElement("span"); name.className = "doc-name"; name.textContent = doc.title || t("untitled"); @@ -114,8 +118,31 @@ function renderDocList(): void { }; el.appendChild(mkBtn("pin-btn" + (doc.pinned ? " active" : ""), "📌", doc.pinned ? t("unpin") : t("pin"), () => pinDoc(doc.id))); - el.appendChild(mkBtn("move-btn", "↑", t("moveUp"), () => moveDoc(doc.id, -1))); - el.appendChild(mkBtn("move-btn", "↓", t("moveDown"), () => moveDoc(doc.id, 1))); + + el.addEventListener("dragstart", (e) => { + dragSrcId = doc.id; + el.classList.add("dragging"); + e.dataTransfer!.effectAllowed = "move"; + e.dataTransfer?.setData("text/plain", doc.id); + }); + el.addEventListener("dragend", () => { + el.classList.remove("dragging"); + dragSrcId = null; + for (const other of list.querySelectorAll(".doc-item")) other.classList.remove("drag-over"); + }); + el.addEventListener("dragover", (e) => { + if (!dragSrcId || dragSrcId === doc.id) return; + const src = data.docs.find((d) => d.id === dragSrcId); + if (!src || !!src.pinned !== !!doc.pinned) return; // can't cross the pin boundary + e.preventDefault(); // allow drop + for (const other of list.querySelectorAll(".doc-item")) other.classList.remove("drag-over"); + el.classList.add("drag-over"); + }); + el.addEventListener("drop", (e) => { + e.preventDefault(); + el.classList.remove("drag-over"); + if (dragSrcId) reorderDoc(dragSrcId, doc.id); + }); const del = document.createElement("button"); del.className = "del"; diff --git a/src/ui/app.css b/src/ui/app.css index 9b9dbf9..4c9a9de 100644 --- a/src/ui/app.css +++ b/src/ui/app.css @@ -102,7 +102,7 @@ body { .doc-item { padding: 7px 10px; border-radius: 6px; - cursor: pointer; + cursor: grab; font-size: 13px; white-space: nowrap; overflow: hidden; @@ -110,12 +110,15 @@ body { display: flex; align-items: center; gap: 6px; + border-top: 2px solid transparent; + transition: background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease, border-top-color 0.15s ease; } .doc-item:hover { background: var(--hover); } .doc-item.active { background: var(--sel); } +.doc-item.dragging { opacity: 0.4; } +.doc-item.drag-over { border-top-color: var(--accent); } .doc-name { flex: 1; overflow: hidden; text-overflow: ellipsis; } .doc-item .pin-btn, -.doc-item .move-btn, .doc-item .del { flex-shrink: 0; opacity: 0; @@ -130,7 +133,6 @@ body { transition: opacity 0.15s ease, background-color 0.15s ease; } .doc-item:hover .pin-btn, -.doc-item:hover .move-btn, .doc-item:hover .del { opacity: 0.7; } .doc-item .pin-btn.active { opacity: 1; } .doc-item:hover .pin-btn.active { opacity: 1; }