mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
kanban dashboard: multi-card drag visual feedback
- When dragging a selected card while multiple cards are selected, the browser ghost image now shows a 'N cards' badge instead of a single card. - All selected cards in the original column are dimmed (opacity 0.45 + grayscale) during the drag so the user sees the whole set is in-flight. - Uses React state for the dragged task id; event delegation on the board columns container to avoid deep prop threading.
This commit is contained in:
parent
98c499b235
commit
a88f201cd4
2 changed files with 59 additions and 1 deletions
35
plugins/kanban/dashboard/dist/index.js
vendored
35
plugins/kanban/dashboard/dist/index.js
vendored
|
|
@ -441,6 +441,9 @@
|
|||
const [selectedIds, setSelectedIds] = useState(() => new Set());
|
||||
const [lastSelectedId, setLastSelectedId] = useState(null);
|
||||
const [failedIds, setFailedIds] = useState(() => new Set());
|
||||
const [draggingTaskId, setDraggingTaskId] = useState(null);
|
||||
const handleDragStart = useCallback(function (taskId) { setDraggingTaskId(taskId); }, []);
|
||||
const handleDragEnd = useCallback(function () { setDraggingTaskId(null); }, []);
|
||||
// Per-task event counter incremented whenever the WS stream reports
|
||||
// a new event for that task id. TaskDrawer useEffect-depends on its
|
||||
// own task's counter so it reloads itself on live events instead of
|
||||
|
|
@ -911,6 +914,9 @@
|
|||
laneByProfile,
|
||||
selectedIds,
|
||||
failedIds,
|
||||
draggingTaskId,
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd,
|
||||
toggleSelected,
|
||||
toggleRange,
|
||||
selectAllInColumn,
|
||||
|
|
@ -1762,7 +1768,16 @@
|
|||
// -------------------------------------------------------------------------
|
||||
|
||||
function BoardColumns(props) {
|
||||
return h("div", { className: "hermes-kanban-columns" },
|
||||
const handleDragStart = useCallback(function (e) {
|
||||
const card = e.target.closest && e.target.closest(".hermes-kanban-card");
|
||||
if (!card) return;
|
||||
const taskId = card.getAttribute("data-task-id");
|
||||
if (taskId && props.onDragStart) props.onDragStart(taskId);
|
||||
}, [props.onDragStart]);
|
||||
const handleDragEnd = useCallback(function () {
|
||||
if (props.onDragEnd) props.onDragEnd();
|
||||
}, [props.onDragEnd]);
|
||||
return h("div", { className: "hermes-kanban-columns", onDragStart: handleDragStart, onDragEnd: handleDragEnd },
|
||||
props.board.columns.map(function (col) {
|
||||
return h(Column, {
|
||||
key: col.name,
|
||||
|
|
@ -1770,6 +1785,7 @@
|
|||
laneByProfile: props.laneByProfile,
|
||||
selectedIds: props.selectedIds,
|
||||
failedIds: props.failedIds,
|
||||
draggingTaskId: props.draggingTaskId,
|
||||
toggleSelected: props.toggleSelected,
|
||||
toggleRange: props.toggleRange,
|
||||
selectAllInColumn: props.selectAllInColumn,
|
||||
|
|
@ -1903,6 +1919,8 @@
|
|||
key: t.id, task: t,
|
||||
selected: props.selectedIds.has(t.id),
|
||||
failed: props.failedIds && props.failedIds.has(t.id),
|
||||
draggingTaskId: props.draggingTaskId,
|
||||
draggingSource: props.draggingTaskId && props.selectedIds.has(props.draggingTaskId) && props.selectedIds.size > 1 && props.selectedIds.has(t.id),
|
||||
toggleSelected: props.toggleSelected,
|
||||
toggleRange: props.toggleRange,
|
||||
onOpen: props.onOpen,
|
||||
|
|
@ -1915,6 +1933,8 @@
|
|||
key: t.id, task: t,
|
||||
selected: props.selectedIds.has(t.id),
|
||||
failed: props.failedIds && props.failedIds.has(t.id),
|
||||
draggingTaskId: props.draggingTaskId,
|
||||
draggingSource: props.draggingTaskId && props.selectedIds.has(props.draggingTaskId) && props.selectedIds.size > 1 && props.selectedIds.has(t.id),
|
||||
toggleSelected: props.toggleSelected,
|
||||
toggleRange: props.toggleRange,
|
||||
onOpen: props.onOpen,
|
||||
|
|
@ -1961,6 +1981,17 @@
|
|||
const handleDragStart = function (e) {
|
||||
e.dataTransfer.setData(MIME_TASK, t.id);
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
const selectedCards = document.querySelectorAll(".hermes-kanban-card--selected");
|
||||
if (selectedCards.length > 1 && props.selected) {
|
||||
const ghost = document.createElement("div");
|
||||
ghost.className = "hermes-kanban-drag-ghost";
|
||||
ghost.textContent = selectedCards.length + " cards";
|
||||
document.body.appendChild(ghost);
|
||||
e.dataTransfer.setDragImage(ghost, 0, 0);
|
||||
requestAnimationFrame(function () {
|
||||
if (ghost.parentNode) document.body.removeChild(ghost);
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleClick = function (e) {
|
||||
if (e.shiftKey) {
|
||||
|
|
@ -1995,10 +2026,12 @@
|
|||
|
||||
return h("div", {
|
||||
ref: cardRef,
|
||||
"data-task-id": t.id,
|
||||
className: cn(
|
||||
"hermes-kanban-card",
|
||||
props.selected ? "hermes-kanban-card--selected" : "",
|
||||
props.failed ? "hermes-kanban-card--failed" : "",
|
||||
props.draggingSource ? "hermes-kanban-card--dragging-source" : "",
|
||||
stalenessClass(t),
|
||||
),
|
||||
draggable: true,
|
||||
|
|
|
|||
25
plugins/kanban/dashboard/dist/style.css
vendored
25
plugins/kanban/dashboard/dist/style.css
vendored
|
|
@ -538,6 +538,15 @@
|
|||
background: color-mix(in srgb, var(--color-ring) 6%, var(--color-card));
|
||||
}
|
||||
|
||||
/* Batch drag source styling — cards that are part of the current multi-drag.
|
||||
The browser ghost image floats; we dim the original DOM nodes so the user
|
||||
sees the whole set is in-flight. */
|
||||
.hermes-kanban-card--dragging-source :where(.hermes-kanban-card-content) {
|
||||
opacity: 0.45;
|
||||
filter: grayscale(0.6);
|
||||
transition: opacity 120ms ease, filter 120ms ease;
|
||||
}
|
||||
|
||||
.hermes-kanban-card-check {
|
||||
width: 0.85rem;
|
||||
height: 0.85rem;
|
||||
|
|
@ -776,6 +785,22 @@
|
|||
transition: none;
|
||||
}
|
||||
|
||||
/* ---- Multi-drag ghost ----------------------------------------------- */
|
||||
|
||||
.hermes-kanban-drag-ghost {
|
||||
position: fixed;
|
||||
left: -9999px;
|
||||
padding: 0.45rem 0.8rem;
|
||||
background: var(--color-card);
|
||||
border: 2px solid var(--color-ring);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-foreground);
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
||||
pointer-events: none;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
/* ---- Staleness tiers ------------------------------------------------ */
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue