mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 02:12:00 +03:00
sheets: reorder by dragging instead of up/down buttons
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.
This commit is contained in:
parent
5205c9fb63
commit
7b94f6a5b0
3 changed files with 42 additions and 17 deletions
|
|
@ -61,8 +61,6 @@ const dict: Record<string, Record<string, string>> = {
|
||||||
cancel: "Cancel",
|
cancel: "Cancel",
|
||||||
pin: "Pin",
|
pin: "Pin",
|
||||||
unpin: "Unpin",
|
unpin: "Unpin",
|
||||||
moveUp: "Move up",
|
|
||||||
moveDown: "Move down",
|
|
||||||
marketUpdated: "Market data updated",
|
marketUpdated: "Market data updated",
|
||||||
exportImage: "Copy as image",
|
exportImage: "Copy as image",
|
||||||
imageCopied: "Image copied",
|
imageCopied: "Image copied",
|
||||||
|
|
@ -139,8 +137,6 @@ const dict: Record<string, Record<string, string>> = {
|
||||||
cancel: "Отмена",
|
cancel: "Отмена",
|
||||||
pin: "Закрепить",
|
pin: "Закрепить",
|
||||||
unpin: "Открепить",
|
unpin: "Открепить",
|
||||||
moveUp: "Выше",
|
|
||||||
moveDown: "Ниже",
|
|
||||||
marketUpdated: "Рыночные данные обновлены",
|
marketUpdated: "Рыночные данные обновлены",
|
||||||
exportImage: "Скопировать картинкой",
|
exportImage: "Скопировать картинкой",
|
||||||
imageCopied: "Картинка скопирована",
|
imageCopied: "Картинка скопирована",
|
||||||
|
|
|
||||||
47
src/main.ts
47
src/main.ts
|
|
@ -81,24 +81,28 @@ function pinDoc(id: string): void {
|
||||||
renderDocList();
|
renderDocList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveDoc(id: string, dir: -1 | 1): void {
|
/** drag id -> drop-target id; no-op across the pinned/unpinned boundary */
|
||||||
const idx = data.docs.findIndex((d) => d.id === id);
|
function reorderDoc(srcId: string, targetId: string): void {
|
||||||
if (idx < 0) return;
|
if (srcId === targetId) return;
|
||||||
const doc = data.docs[idx];
|
const src = data.docs.find((d) => d.id === srcId);
|
||||||
const target = data.docs[idx + dir];
|
const target = data.docs.find((d) => d.id === targetId);
|
||||||
if (!target || !!target.pinned !== !!doc.pinned) return; // can't cross pin boundary
|
if (!src || !target || !!src.pinned !== !!target.pinned) return;
|
||||||
data.docs.splice(idx, 1);
|
data.docs = data.docs.filter((d) => d.id !== srcId);
|
||||||
data.docs.splice(idx + dir, 0, doc);
|
const targetIdx = data.docs.findIndex((d) => d.id === targetId);
|
||||||
|
data.docs.splice(targetIdx, 0, src);
|
||||||
saveAppData(data);
|
saveAppData(data);
|
||||||
renderDocList();
|
renderDocList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dragSrcId: string | null = null;
|
||||||
|
|
||||||
function renderDocList(): void {
|
function renderDocList(): void {
|
||||||
const list = $("#doc-list");
|
const list = $("#doc-list");
|
||||||
list.replaceChildren();
|
list.replaceChildren();
|
||||||
for (const doc of data.docs) {
|
for (const doc of data.docs) {
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.className = "doc-item" + (doc.id === data.activeId ? " active" : "") + (doc.pinned ? " pinned" : "");
|
el.className = "doc-item" + (doc.id === data.activeId ? " active" : "") + (doc.pinned ? " pinned" : "");
|
||||||
|
el.draggable = true;
|
||||||
const name = document.createElement("span");
|
const name = document.createElement("span");
|
||||||
name.className = "doc-name";
|
name.className = "doc-name";
|
||||||
name.textContent = doc.title || t("untitled");
|
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("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");
|
const del = document.createElement("button");
|
||||||
del.className = "del";
|
del.className = "del";
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ body {
|
||||||
.doc-item {
|
.doc-item {
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: grab;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -110,12 +110,15 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
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:hover { background: var(--hover); }
|
||||||
.doc-item.active { background: var(--sel); }
|
.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-name { flex: 1; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.doc-item .pin-btn,
|
.doc-item .pin-btn,
|
||||||
.doc-item .move-btn,
|
|
||||||
.doc-item .del {
|
.doc-item .del {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
@ -130,7 +133,6 @@ body {
|
||||||
transition: opacity 0.15s ease, background-color 0.15s ease;
|
transition: opacity 0.15s ease, background-color 0.15s ease;
|
||||||
}
|
}
|
||||||
.doc-item:hover .pin-btn,
|
.doc-item:hover .pin-btn,
|
||||||
.doc-item:hover .move-btn,
|
|
||||||
.doc-item:hover .del { opacity: 0.7; }
|
.doc-item:hover .del { opacity: 0.7; }
|
||||||
.doc-item .pin-btn.active { opacity: 1; }
|
.doc-item .pin-btn.active { opacity: 1; }
|
||||||
.doc-item:hover .pin-btn.active { opacity: 1; }
|
.doc-item:hover .pin-btn.active { opacity: 1; }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue