diff --git a/apps/client/src/widgets/view_widgets/board_view/index.ts b/apps/client/src/widgets/view_widgets/board_view/index.ts index 11cfbbd57..8bbe443a2 100644 --- a/apps/client/src/widgets/view_widgets/board_view/index.ts +++ b/apps/client/src/widgets/view_widgets/board_view/index.ts @@ -1,6 +1,7 @@ import { setupHorizontalScrollViaWheel } from "../../widget_utils"; import ViewMode, { ViewModeArgs } from "../view_mode"; import { getBoardData } from "./data"; +import attributeService from "../../../services/attributes"; const TPL = /*html*/`
@@ -22,10 +23,24 @@ const TPL = /*html*/` .board-view-container .board-column { width: 250px; flex-shrink: 0; + min-height: 200px; + border: 2px solid transparent; + border-radius: 8px; + padding: 0.5em; + background-color: var(--accented-background-color); + transition: border-color 0.2s ease; + } + + .board-view-container .board-column.drag-over { + border-color: var(--main-text-color); + background-color: var(--hover-item-background-color); } .board-view-container .board-column h3 { font-size: 1em; + margin-bottom: 0.75em; + padding-bottom: 0.5em; + border-bottom: 1px solid var(--main-border-color); } .board-view-container .board-note { @@ -33,11 +48,41 @@ const TPL = /*html*/` margin: 0.65em 0; padding: 0.5em; border-radius: 5px; + cursor: move; + position: relative; + background-color: var(--main-background-color); + border: 1px solid var(--main-border-color); + transition: transform 0.2s ease, box-shadow 0.2s ease; + } + + .board-view-container .board-note:hover { + transform: translateY(-2px); + box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.35); + } + + .board-view-container .board-note.dragging { + opacity: 0.8; + transform: rotate(5deg); + z-index: 1000; + box-shadow: 4px 8px 16px rgba(0, 0, 0, 0.5); } .board-view-container .board-note .icon { margin-right: 0.25em; } + + .board-drop-indicator { + height: 3px; + background-color: var(--main-text-color); + border-radius: 2px; + margin: 0.25em 0; + opacity: 0; + transition: opacity 0.2s ease; + } + + .board-drop-indicator.show { + opacity: 1; + }
@@ -52,6 +97,8 @@ export default class BoardView extends ViewMode { private $root: JQuery; private $container: JQuery; + private draggedNote: any = null; + private draggedNoteElement: JQuery | null = null; constructor(args: ViewModeArgs) { super(args, "board"); @@ -65,7 +112,7 @@ export default class BoardView extends ViewMode { async renderList(): Promise | undefined> { this.$container.empty(); - this.renderBoard(this.$container[0]); + await this.renderBoard(this.$container[0]); return this.$root; } @@ -81,8 +128,12 @@ export default class BoardView extends ViewMode { const $columnEl = $("
") .addClass("board-column") + .attr("data-column", column) .append($("

").text(column)); + // Setup drop zone for the column + this.setupColumnDropZone($columnEl, column); + for (const note of columnNotes) { const $iconEl = $("") .addClass("icon") @@ -90,8 +141,15 @@ export default class BoardView extends ViewMode { const $noteEl = $("
") .addClass("board-note") - .text(note.title); // Assuming FNote has a title property + .attr("data-note-id", note.noteId) + .attr("data-current-column", column) + .text(note.title); + $noteEl.prepend($iconEl); + + // Setup drag functionality for the note + this.setupNoteDrag($noteEl, note); + $columnEl.append($noteEl); } @@ -99,4 +157,132 @@ export default class BoardView extends ViewMode { } } + private setupNoteDrag($noteEl: JQuery, note: any) { + $noteEl.attr("draggable", "true"); + + $noteEl.on("dragstart", (e) => { + this.draggedNote = note; + this.draggedNoteElement = $noteEl; + $noteEl.addClass("dragging"); + + // Set drag data + const originalEvent = e.originalEvent as DragEvent; + if (originalEvent.dataTransfer) { + originalEvent.dataTransfer.effectAllowed = "move"; + originalEvent.dataTransfer.setData("text/plain", note.noteId); + } + }); + + $noteEl.on("dragend", () => { + $noteEl.removeClass("dragging"); + this.draggedNote = null; + this.draggedNoteElement = null; + + // Remove all drop indicators + this.$container.find(".board-drop-indicator").removeClass("show"); + }); + } + + private setupColumnDropZone($columnEl: JQuery, column: string) { + $columnEl.on("dragover", (e) => { + e.preventDefault(); + const originalEvent = e.originalEvent as DragEvent; + if (originalEvent.dataTransfer) { + originalEvent.dataTransfer.dropEffect = "move"; + } + + if (this.draggedNote) { + $columnEl.addClass("drag-over"); + this.showDropIndicator($columnEl, e); + } + }); + + $columnEl.on("dragleave", (e) => { + // Only remove drag-over if we're leaving the column entirely + const rect = $columnEl[0].getBoundingClientRect(); + const originalEvent = e.originalEvent as DragEvent; + const x = originalEvent.clientX; + const y = originalEvent.clientY; + + if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { + $columnEl.removeClass("drag-over"); + $columnEl.find(".board-drop-indicator").removeClass("show"); + } + }); + + $columnEl.on("drop", async (e) => { + e.preventDefault(); + $columnEl.removeClass("drag-over"); + $columnEl.find(".board-drop-indicator").removeClass("show"); + + if (this.draggedNote && this.draggedNoteElement) { + const currentColumn = this.draggedNoteElement.attr("data-current-column"); + + if (currentColumn !== column) { + try { + // Update the note's status label + await attributeService.setLabel(this.draggedNote.noteId, "status", column); + + // Move the note element to the new column + const dropIndicator = $columnEl.find(".board-drop-indicator.show"); + if (dropIndicator.length > 0) { + dropIndicator.after(this.draggedNoteElement); + } else { + $columnEl.append(this.draggedNoteElement); + } + + // Update the data attribute + this.draggedNoteElement.attr("data-current-column", column); + + // Show success feedback (optional) + console.log(`Moved note "${this.draggedNote.title}" from "${currentColumn}" to "${column}"`); + } catch (error) { + console.error("Failed to update note status:", error); + // Optionally show user-facing error message + } + } + } + }); + } + + private showDropIndicator($columnEl: JQuery, e: JQuery.DragOverEvent) { + const originalEvent = e.originalEvent as DragEvent; + const mouseY = originalEvent.clientY; + const columnRect = $columnEl[0].getBoundingClientRect(); + const relativeY = mouseY - columnRect.top; + + // Find existing drop indicator or create one + let $dropIndicator = $columnEl.find(".board-drop-indicator"); + if ($dropIndicator.length === 0) { + $dropIndicator = $("
").addClass("board-drop-indicator"); + $columnEl.append($dropIndicator); + } + + // Find the best position to insert the note + const $notes = this.draggedNoteElement ? + $columnEl.find(".board-note").not(this.draggedNoteElement) : + $columnEl.find(".board-note"); + let insertAfterElement: HTMLElement | null = null; + + $notes.each((_, noteEl) => { + const noteRect = noteEl.getBoundingClientRect(); + const noteMiddle = noteRect.top + noteRect.height / 2 - columnRect.top; + + if (relativeY > noteMiddle) { + insertAfterElement = noteEl; + } + }); + + // Position the drop indicator + if (insertAfterElement) { + $(insertAfterElement).after($dropIndicator); + } else { + // Insert at the beginning (after the header) + const $header = $columnEl.find("h3"); + $header.after($dropIndicator); + } + + $dropIndicator.addClass("show"); + } + }