chore(react/collections/table): set up column dragging

This commit is contained in:
Elian Doran 2025-09-11 18:42:32 +03:00
parent ce0da3fb80
commit c30c9a7360
No known key found for this signature in database
3 changed files with 131 additions and 34 deletions

View File

@ -144,7 +144,10 @@ function useViewModeConfig<T extends object>(note: FNote | null | undefined, vie
if (!note || !viewType) return; if (!note || !viewType) return;
const viewStorage = new ViewModeStorage<T>(note, viewType); const viewStorage = new ViewModeStorage<T>(note, viewType);
viewStorage.restore().then(config => { viewStorage.restore().then(config => {
const storeFn = (config: T) => viewStorage.store(config); const storeFn = (config: T) => {
setViewConfig([ config, storeFn ]);
viewStorage.store(config);
};
setViewConfig([ config, storeFn ]); setViewConfig([ config, storeFn ]);
}); });
}, [ note, viewType ]); }, [ note, viewType ]);

View File

@ -66,9 +66,10 @@
} }
.board-view-container .board-column.column-dragging { .board-view-container .board-column.column-dragging {
opacity: 0.6; opacity: 0.5;
transform: scale(0.98); transform: scale(0.98) rotate(2deg);
transition: opacity 0.2s ease, transform 0.2s ease; transition: opacity 0.2s ease, transform 0.2s ease;
box-shadow: 4px 8px 16px rgba(0, 0, 0, 0.3);
} }
.board-view-container .board-column h3 input { .board-view-container .board-column h3 input {
@ -185,20 +186,19 @@
opacity: 0.6; opacity: 0.6;
} }
.column-drop-indicator { .column-drop-placeholder {
width: 4px; width: 250px;
background-color: var(--main-text-color);
border-radius: 2px;
opacity: 0;
transition: opacity 0.2s ease;
height: 100%;
z-index: 1000;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
flex-shrink: 0; flex-shrink: 0;
height: 200px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.1);
opacity: 0;
transition: opacity 0.15s ease;
margin: 0 0.5em;
} }
.column-drop-indicator.show { .column-drop-placeholder.show {
opacity: 1; opacity: 0.6;
} }
.board-new-item { .board-new-item {

View File

@ -26,6 +26,8 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
const [ draggedCard, setDraggedCard ] = useState<{ noteId: string, branchId: string, fromColumn: string, index: number } | null>(null); const [ draggedCard, setDraggedCard ] = useState<{ noteId: string, branchId: string, fromColumn: string, index: number } | null>(null);
const [ dropTarget, setDropTarget ] = useState<string | null>(null); const [ dropTarget, setDropTarget ] = useState<string | null>(null);
const [ dropPosition, setDropPosition ] = useState<{ column: string, index: number } | null>(null); const [ dropPosition, setDropPosition ] = useState<{ column: string, index: number } | null>(null);
const [ draggedColumn, setDraggedColumn ] = useState<{ column: string, index: number } | null>(null);
const [ columnDropPosition, setColumnDropPosition ] = useState<number | null>(null);
function refresh() { function refresh() {
getBoardData(parentNote, statusAttribute ?? "status", viewConfig ?? {}).then(({ byColumn, newPersistedData }) => { getBoardData(parentNote, statusAttribute ?? "status", viewConfig ?? {}).then(({ byColumn, newPersistedData }) => {
@ -46,6 +48,26 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
useEffect(refresh, [ parentNote, noteIds ]); useEffect(refresh, [ parentNote, noteIds ]);
const handleColumnDrop = useCallback((fromIndex: number, toIndex: number) => {
if (!columns || fromIndex === toIndex) return;
const newColumns = [...columns];
const [movedColumn] = newColumns.splice(fromIndex, 1);
newColumns.splice(toIndex, 0, movedColumn);
// Update view config with new column order
const newViewConfig = {
...viewConfig,
columns: newColumns.map(col => ({ value: col }))
};
saveConfig(newViewConfig);
setColumns(newColumns);
console.log("New columns are ", newColumns);
setDraggedColumn(null);
setColumnDropPosition(null);
}, [columns, viewConfig, saveConfig]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => { useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
// Check if any changes affect our board // Check if any changes affect our board
const hasRelevantChanges = const hasRelevantChanges =
@ -67,24 +89,70 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
} }
}); });
const handleColumnDragOver = useCallback((e: DragEvent) => {
if (!draggedColumn) return;
e.preventDefault();
const container = e.currentTarget as HTMLElement;
const columns = Array.from(container.querySelectorAll('.board-column'));
const mouseX = e.clientX;
let newIndex = columns.length;
for (let i = 0; i < columns.length; i++) {
const col = columns[i] as HTMLElement;
const rect = col.getBoundingClientRect();
const colMiddle = rect.left + rect.width / 2;
if (mouseX < colMiddle) {
newIndex = i;
break;
}
}
setColumnDropPosition(newIndex);
}, [draggedColumn]);
const handleContainerDrop = useCallback((e: DragEvent) => {
e.preventDefault();
if (draggedColumn && columnDropPosition !== null) {
handleColumnDrop(draggedColumn.index, columnDropPosition);
}
}, [draggedColumn, columnDropPosition, handleColumnDrop]);
return ( return (
<div className="board-view"> <div className="board-view">
<div className="board-view-container"> <div
{byColumn && columns?.map(column => ( className="board-view-container"
<Column onDragOver={handleColumnDragOver}
column={column} onDrop={handleContainerDrop}
columnItems={byColumn.get(column)} >
parentNote={parentNote} {byColumn && columns?.map((column, index) => (
statusAttribute={statusAttribute ?? "status"} <>
draggedCard={draggedCard} {columnDropPosition === index && draggedColumn?.column !== column && (
setDraggedCard={setDraggedCard} <div className="column-drop-placeholder show" />
dropTarget={dropTarget} )}
setDropTarget={setDropTarget} <Column
dropPosition={dropPosition} column={column}
setDropPosition={setDropPosition} columnIndex={index}
onCardDrop={refresh} columnItems={byColumn.get(column)}
/> parentNote={parentNote}
statusAttribute={statusAttribute ?? "status"}
draggedCard={draggedCard}
setDraggedCard={setDraggedCard}
dropTarget={dropTarget}
setDropTarget={setDropTarget}
dropPosition={dropPosition}
setDropPosition={setDropPosition}
onCardDrop={refresh}
draggedColumn={draggedColumn}
setDraggedColumn={setDraggedColumn}
isDraggingColumn={draggedColumn?.column === column}
/>
</>
))} ))}
{columnDropPosition === columns?.length && draggedColumn && (
<div className="column-drop-placeholder show" />
)}
<AddNewColumn viewConfig={viewConfig} saveConfig={saveConfig} /> <AddNewColumn viewConfig={viewConfig} saveConfig={saveConfig} />
</div> </div>
@ -95,6 +163,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
function Column({ function Column({
parentNote, parentNote,
column, column,
columnIndex,
columnItems, columnItems,
statusAttribute, statusAttribute,
draggedCard, draggedCard,
@ -103,10 +172,14 @@ function Column({
setDropTarget, setDropTarget,
dropPosition, dropPosition,
setDropPosition, setDropPosition,
onCardDrop onCardDrop,
draggedColumn,
setDraggedColumn,
isDraggingColumn
}: { }: {
parentNote: FNote, parentNote: FNote,
column: string, column: string,
columnIndex: number,
columnItems?: { note: FNote, branch: FBranch }[], columnItems?: { note: FNote, branch: FBranch }[],
statusAttribute: string, statusAttribute: string,
draggedCard: { noteId: string, branchId: string, fromColumn: string, index: number } | null, draggedCard: { noteId: string, branchId: string, fromColumn: string, index: number } | null,
@ -115,9 +188,24 @@ function Column({
setDropTarget: (target: string | null) => void, setDropTarget: (target: string | null) => void,
dropPosition: { column: string, index: number } | null, dropPosition: { column: string, index: number } | null,
setDropPosition: (position: { column: string, index: number } | null) => void, setDropPosition: (position: { column: string, index: number } | null) => void,
onCardDrop: () => void onCardDrop: () => void,
draggedColumn: { column: string, index: number } | null,
setDraggedColumn: (column: { column: string, index: number } | null) => void,
isDraggingColumn: boolean
}) { }) {
const handleColumnDragStart = useCallback((e: DragEvent) => {
e.dataTransfer!.effectAllowed = 'move';
e.dataTransfer!.setData('text/plain', column);
setDraggedColumn({ column, index: columnIndex });
e.stopPropagation(); // Prevent card drag from interfering
}, [column, columnIndex, setDraggedColumn]);
const handleColumnDragEnd = useCallback(() => {
setDraggedColumn(null);
}, [setDraggedColumn]);
const handleDragOver = useCallback((e: DragEvent) => { const handleDragOver = useCallback((e: DragEvent) => {
if (draggedColumn) return; // Don't handle card drops when dragging columns
e.preventDefault(); e.preventDefault();
setDropTarget(column); setDropTarget(column);
@ -151,6 +239,7 @@ function Column({
}, [setDropTarget, setDropPosition]); }, [setDropTarget, setDropPosition]);
const handleDrop = useCallback(async (e: DragEvent) => { const handleDrop = useCallback(async (e: DragEvent) => {
if (draggedColumn) return; // Don't handle card drops when dragging columns
e.preventDefault(); e.preventDefault();
setDropTarget(null); setDropTarget(null);
setDropPosition(null); setDropPosition(null);
@ -189,15 +278,20 @@ function Column({
onCardDrop(); onCardDrop();
} }
setDraggedCard(null); setDraggedCard(null);
}, [draggedCard, dropPosition, columnItems, column, statusAttribute, setDraggedCard, setDropTarget, setDropPosition, onCardDrop]); }, [draggedCard, draggedColumn, dropPosition, columnItems, column, statusAttribute, setDraggedCard, setDropTarget, setDropPosition, onCardDrop]);
return ( return (
<div <div
className={`board-column ${dropTarget === column && draggedCard?.fromColumn !== column ? 'drag-over' : ''}`} className={`board-column ${dropTarget === column && draggedCard?.fromColumn !== column ? 'drag-over' : ''} ${isDraggingColumn ? 'column-dragging' : ''}`}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onDrop={handleDrop} onDrop={handleDrop}
> >
<h3> <h3
draggable="true"
onDragStart={handleColumnDragStart}
onDragEnd={handleColumnDragEnd}
>
<span>{column}</span> <span>{column}</span>
<span <span
className="edit-icon icon bx bx-edit-alt" className="edit-icon icon bx bx-edit-alt"