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;
const viewStorage = new ViewModeStorage<T>(note, viewType);
viewStorage.restore().then(config => {
const storeFn = (config: T) => viewStorage.store(config);
const storeFn = (config: T) => {
setViewConfig([ config, storeFn ]);
viewStorage.store(config);
};
setViewConfig([ config, storeFn ]);
});
}, [ note, viewType ]);

View File

@ -66,9 +66,10 @@
}
.board-view-container .board-column.column-dragging {
opacity: 0.6;
transform: scale(0.98);
opacity: 0.5;
transform: scale(0.98) rotate(2deg);
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 {
@ -185,20 +186,19 @@
opacity: 0.6;
}
.column-drop-indicator {
width: 4px;
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);
.column-drop-placeholder {
width: 250px;
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 {
opacity: 1;
.column-drop-placeholder.show {
opacity: 0.6;
}
.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 [ dropTarget, setDropTarget ] = useState<string | 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() {
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 ]);
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 }) => {
// Check if any changes affect our board
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 (
<div className="board-view">
<div className="board-view-container">
{byColumn && columns?.map(column => (
<Column
column={column}
columnItems={byColumn.get(column)}
parentNote={parentNote}
statusAttribute={statusAttribute ?? "status"}
draggedCard={draggedCard}
setDraggedCard={setDraggedCard}
dropTarget={dropTarget}
setDropTarget={setDropTarget}
dropPosition={dropPosition}
setDropPosition={setDropPosition}
onCardDrop={refresh}
/>
<div
className="board-view-container"
onDragOver={handleColumnDragOver}
onDrop={handleContainerDrop}
>
{byColumn && columns?.map((column, index) => (
<>
{columnDropPosition === index && draggedColumn?.column !== column && (
<div className="column-drop-placeholder show" />
)}
<Column
column={column}
columnIndex={index}
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} />
</div>
@ -95,6 +163,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
function Column({
parentNote,
column,
columnIndex,
columnItems,
statusAttribute,
draggedCard,
@ -103,10 +172,14 @@ function Column({
setDropTarget,
dropPosition,
setDropPosition,
onCardDrop
onCardDrop,
draggedColumn,
setDraggedColumn,
isDraggingColumn
}: {
parentNote: FNote,
column: string,
columnIndex: number,
columnItems?: { note: FNote, branch: FBranch }[],
statusAttribute: string,
draggedCard: { noteId: string, branchId: string, fromColumn: string, index: number } | null,
@ -115,9 +188,24 @@ function Column({
setDropTarget: (target: string | null) => void,
dropPosition: { column: string, index: number } | null,
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) => {
if (draggedColumn) return; // Don't handle card drops when dragging columns
e.preventDefault();
setDropTarget(column);
@ -151,6 +239,7 @@ function Column({
}, [setDropTarget, setDropPosition]);
const handleDrop = useCallback(async (e: DragEvent) => {
if (draggedColumn) return; // Don't handle card drops when dragging columns
e.preventDefault();
setDropTarget(null);
setDropPosition(null);
@ -189,15 +278,20 @@ function Column({
onCardDrop();
}
setDraggedCard(null);
}, [draggedCard, dropPosition, columnItems, column, statusAttribute, setDraggedCard, setDropTarget, setDropPosition, onCardDrop]);
}, [draggedCard, draggedColumn, dropPosition, columnItems, column, statusAttribute, setDraggedCard, setDropTarget, setDropPosition, onCardDrop]);
return (
<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}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<h3>
<h3
draggable="true"
onDragStart={handleColumnDragStart}
onDragEnd={handleColumnDragEnd}
>
<span>{column}</span>
<span
className="edit-icon icon bx bx-edit-alt"