Changed it so that tasks can be dragged and reordered, as well as having tasks breathe to leave a space where the task will go when you let go
This commit is contained in:
+89
-23
@@ -212,29 +212,59 @@ function HeadsUp({ items, onDismiss, onOpenTask }) {
|
||||
function OverviewScreen({ tasks, onOpen, onAddFor, density, onMoveTask, dbUsers = [] }) {
|
||||
const byUser = Object.fromEntries(dbUsers.map(u => [u.id, []]));
|
||||
tasks.forEach(t => { if (byUser[t.assignee] && t.status !== 'closed') byUser[t.assignee].push(t); });
|
||||
|
||||
const [draggingTask, setDraggingTask] = React.useState(null);
|
||||
const [dragOverCol, setDragOverCol] = React.useState(null);
|
||||
const [dragOverTaskId, setDragOverTaskId] = React.useState(null);
|
||||
const [dropSide, setDropSide] = React.useState('bottom'); // 'top' or 'bottom'
|
||||
|
||||
return (
|
||||
<div className="board">
|
||||
{dbUsers.map(u => (
|
||||
<Column
|
||||
key={u.id}
|
||||
user={u}
|
||||
tasks={byUser[u.id]}
|
||||
// Filter out the dragging task from its current column to make it "disappear" from origin
|
||||
tasks={byUser[u.id].filter(t => !draggingTask || t.id !== draggingTask.id)}
|
||||
onOpen={onOpen}
|
||||
onAdd={() => onAddFor(u.id)}
|
||||
density={density}
|
||||
dragOver={dragOverCol === u.id && draggingTask && draggingTask.assignee !== u.id}
|
||||
onDragOver={(uid) => setDragOverCol(uid)}
|
||||
onDragLeave={() => setDragOverCol(prev => prev === u.id ? null : prev)}
|
||||
onDragStartCard={(t) => setDraggingTask(t)}
|
||||
onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); }}
|
||||
onDragLeave={() => { setDragOverCol(null); setDragOverTaskId(null); }}
|
||||
onDragStartCard={(t) => {
|
||||
// Use a tiny timeout to ensure the drag image is captured before we hide the element
|
||||
setTimeout(() => setDraggingTask(t), 0);
|
||||
}}
|
||||
onDragEndCard={() => { setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null); }}
|
||||
draggingId={draggingTask && draggingTask.id}
|
||||
dragOverTaskId={dragOverTaskId}
|
||||
dropSide={dropSide}
|
||||
onDragOverTask={(tid, side) => { setDragOverTaskId(tid); setDropSide(side); }}
|
||||
onDropTask={(toId) => {
|
||||
if (draggingTask && draggingTask.assignee !== toId) {
|
||||
onMoveTask && onMoveTask(draggingTask.id, toId);
|
||||
if (!draggingTask) return;
|
||||
|
||||
// Calculate position using the list that DOES NOT include the dragging task
|
||||
const colTasks = byUser[toId].filter(t => t.id !== draggingTask.id);
|
||||
let newPos = 0;
|
||||
|
||||
if (dragOverTaskId) {
|
||||
const idx = colTasks.findIndex(t => t.id === dragOverTaskId);
|
||||
if (dropSide === 'top') {
|
||||
const prev = colTasks[idx - 1];
|
||||
newPos = prev ? (colTasks[idx].position + prev.position) / 2 : colTasks[idx].position / 2;
|
||||
} else {
|
||||
const next = colTasks[idx + 1];
|
||||
newPos = next ? (colTasks[idx].position + next.position) / 2 : colTasks[idx].position + 1000;
|
||||
}
|
||||
} else {
|
||||
// Dropped on empty area or no specific task
|
||||
const last = colTasks[colTasks.length - 1];
|
||||
newPos = last ? last.position + 1000 : 1000;
|
||||
}
|
||||
setDraggingTask(null); setDragOverCol(null);
|
||||
|
||||
onMoveTask && onMoveTask(draggingTask.id, toId, newPos);
|
||||
setDraggingTask(null); setDragOverCol(null); setDragOverTaskId(null);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -242,13 +272,17 @@ function OverviewScreen({ tasks, onOpen, onAddFor, density, onMoveTask, dbUsers
|
||||
);
|
||||
}
|
||||
|
||||
function Column({ user, tasks, onOpen, onAdd, density, onDropTask, dragOver, onDragOver, onDragLeave, onDragStartCard, onDragEndCard, draggingId }) {
|
||||
function Column({ user, tasks, onOpen, onAdd, density, onDropTask, dragOver, onDragOver, onDragLeave, onDragStartCard, onDragEndCard, draggingId, dragOverTaskId, dropSide, onDragOverTask }) {
|
||||
return (
|
||||
<section
|
||||
className={"column" + (dragOver ? " column--over" : "")}
|
||||
className={"column" + (dragOver && tasks.length === 0 ? " column--over" : "")}
|
||||
data-comment-anchor={"col-" + user.id}
|
||||
onDragOver={(e) => { e.preventDefault(); onDragOver && onDragOver(user.id); }}
|
||||
onDragLeave={(e) => { onDragLeave && onDragLeave(user.id); }}
|
||||
onDragLeave={(e) => {
|
||||
if (e.relatedTarget && !e.currentTarget.contains(e.relatedTarget)) {
|
||||
onDragLeave && onDragLeave(user.id);
|
||||
}
|
||||
}}
|
||||
onDrop={(e) => { e.preventDefault(); onDropTask && onDropTask(user.id); }}
|
||||
>
|
||||
<header className="column__head">
|
||||
@@ -266,23 +300,55 @@ function Column({ user, tasks, onOpen, onAdd, density, onDropTask, dragOver, onD
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div className="column__list">
|
||||
{tasks.length === 0 && (
|
||||
|
||||
<div className="column__list" style={{ flex: 1, position: 'relative' }}>
|
||||
{tasks.length === 0 && !dragOver && (
|
||||
<div className="column__empty">
|
||||
<span className="mono">— inbox zero —</span>
|
||||
</div>
|
||||
)}
|
||||
{tasks.map(t => (
|
||||
<TaskCard
|
||||
key={t.id}
|
||||
task={t}
|
||||
onOpen={onOpen}
|
||||
density={density}
|
||||
dragging={draggingId === t.id}
|
||||
onDragStart={onDragStartCard}
|
||||
onDragEnd={onDragEndCard}
|
||||
/>
|
||||
))}
|
||||
|
||||
{tasks.length === 0 && dragOver && (
|
||||
<div className="drop-placeholder" />
|
||||
)}
|
||||
|
||||
{tasks.map(t => {
|
||||
const isOver = dragOverTaskId === t.id;
|
||||
const isTop = isOver && dropSide === 'top';
|
||||
const isBottom = isOver && dropSide === 'bottom';
|
||||
|
||||
return (
|
||||
<div key={t.id} style={{
|
||||
position: 'relative',
|
||||
transition: 'padding 0.18s cubic-bezier(0.2, 0.8, 0.2, 1)',
|
||||
paddingTop: isTop ? '60px' : '0px',
|
||||
paddingBottom: isBottom ? '60px' : '0px'
|
||||
}}>
|
||||
{isOver && (
|
||||
<div className="drop-placeholder" style={{
|
||||
position: 'absolute',
|
||||
left: 0, right: 0,
|
||||
top: isTop ? '4px' : 'auto',
|
||||
bottom: isBottom ? '4px' : 'auto',
|
||||
margin: 0
|
||||
}} />
|
||||
)}
|
||||
<TaskCard
|
||||
task={t}
|
||||
onOpen={onOpen}
|
||||
density={density}
|
||||
dragging={draggingId === t.id}
|
||||
onDragStart={onDragStartCard}
|
||||
onDragEnd={onDragEndCard}
|
||||
onDragOver={(e) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const mid = rect.top + rect.height / 2;
|
||||
onDragOverTask(t.id, e.clientY < mid ? 'top' : 'bottom');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user