diff --git a/apps/web/components/dashboard/tags/AllTagsView.tsx b/apps/web/components/dashboard/tags/AllTagsView.tsx index ac139e23..7b81ed72 100644 --- a/apps/web/components/dashboard/tags/AllTagsView.tsx +++ b/apps/web/components/dashboard/tags/AllTagsView.tsx @@ -13,16 +13,11 @@ import InfoTooltip from "@/components/ui/info-tooltip"; import { Separator } from "@/components/ui/separator"; import { Toggle } from "@/components/ui/toggle"; import { toast } from "@/components/ui/use-toast"; -import { useDragAndDrop } from "@/lib/drag-and-drop"; import { api } from "@/lib/trpc"; import { ArrowDownAZ, Combine } from "lucide-react"; -import Draggable from "react-draggable"; import type { ZGetTagResponse } from "@hoarder/shared/types/tags"; -import { - useDeleteUnusedTags, - useMergeTag, -} from "@hoarder/shared-react/hooks/tags"; +import { useDeleteUnusedTags } from "@hoarder/shared-react/hooks/tags"; import { TagPill } from "./TagPill"; @@ -79,17 +74,6 @@ export default function AllTagsView({ const [draggingEnabled, setDraggingEnabled] = React.useState(false); const [sortByName, setSortByName] = React.useState(false); - const { handleDragStart, handleDragEnd } = useDragAndDrop( - "data-id", - "data-id", - (dragSourceId: string, dragTargetId: string) => { - mergeTag({ - fromTagIds: [dragSourceId], - intoTagId: dragTargetId, - }); - }, - ); - function toggleSortByName(): void { setSortByName(!sortByName); } @@ -98,40 +82,9 @@ export default function AllTagsView({ setDraggingEnabled(!draggingEnabled); } - const { mutate: mergeTag } = useMergeTag({ - onSuccess: () => { - toast({ - description: "Tags have been merged!", - }); - }, - onError: (e) => { - if (e.data?.code == "BAD_REQUEST") { - if (e.data.zodError) { - toast({ - variant: "destructive", - description: Object.values(e.data.zodError.fieldErrors) - .flat() - .join("\n"), - }); - } else { - toast({ - variant: "destructive", - description: e.message, - }); - } - } else { - toast({ - variant: "destructive", - title: "Something went wrong", - }); - } - }, - }); - const { data } = api.tags.list.useQuery(undefined, { initialData: { tags: initialData }, }); - // Sort tags by usage desc const allTags = data.tags.sort(sortByName ? byNameSorter : byUsageSorter); @@ -145,21 +98,13 @@ export default function AllTagsView({ tagPill = (
{tags.map((t) => ( - -
- -
-
+ id={t.id} + name={t.name} + count={t.count} + isDraggable={draggingEnabled} + /> ))}
); @@ -173,6 +118,7 @@ export default function AllTagsView({
@@ -184,6 +130,7 @@ export default function AllTagsView({ @@ -196,22 +143,16 @@ export default function AllTagsView({

Tags that were attached at least once by you

- {tagsToPill(humanTags)} - -

AI Tags

Tags that were only attached automatically (by AI)

- {tagsToPill(aiTags)} - -

Unused Tags

diff --git a/apps/web/components/dashboard/tags/TagPill.tsx b/apps/web/components/dashboard/tags/TagPill.tsx index f1c99d70..88b88b52 100644 --- a/apps/web/components/dashboard/tags/TagPill.tsx +++ b/apps/web/components/dashboard/tags/TagPill.tsx @@ -1,7 +1,13 @@ +import React from "react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; +import { toast } from "@/components/ui/use-toast"; +import { useDragAndDrop } from "@/lib/drag-and-drop"; import { X } from "lucide-react"; +import Draggable from "react-draggable"; + +import { useMergeTag } from "@hoarder/shared-react/hooks/tags"; import DeleteTagConfirmationDialog from "./DeleteTagConfirmationDialog"; @@ -9,32 +15,84 @@ export function TagPill({ id, name, count, + isDraggable, }: { id: string; name: string; count: number; + isDraggable: boolean; }) { - return ( -
- { + toast({ + description: "Tags have been merged!", + }); + }, + onError: (e) => { + if (e.data?.code == "BAD_REQUEST") { + if (e.data.zodError) { + toast({ + variant: "destructive", + description: Object.values(e.data.zodError.fieldErrors) + .flat() + .join("\n"), + }); + } else { + toast({ + variant: "destructive", + description: e.message, + }); } - href={`/dashboard/tags/${id}`} - data-id={id} - > - {name} {count} - + } else { + toast({ + variant: "destructive", + title: "Something went wrong", + }); + } + }, + }); + + const dragAndDropFunction = useDragAndDrop( + "data-id", + (dragTargetId: string) => { + mergeTag({ + fromTagIds: [id], + intoTagId: dragTargetId, + }); + }, + ); - - - -
+ {name} {count} + + + + + +
+ ); } diff --git a/apps/web/lib/drag-and-drop.ts b/apps/web/lib/drag-and-drop.ts index e005a6d0..ec37e810 100644 --- a/apps/web/lib/drag-and-drop.ts +++ b/apps/web/lib/drag-and-drop.ts @@ -1,31 +1,48 @@ import React from "react"; -import { DraggableData, DraggableEvent } from "react-draggable"; +import { DraggableEvent } from "react-draggable"; + +export interface DraggingState { + isDragging: boolean; + initialX: number; + initialY: number; +} export function useDragAndDrop( - dragSourceIdAttribute: string, dragTargetIdAttribute: string, - onDragOver: (dragSourceId: string, dragTargetId: string) => void, + onDragOver: (dragTargetId: string) => void, + setDraggingState?: React.Dispatch>, ) { - const [dragSourceId, setDragSourceId] = React.useState(null); + function findTargetId(element: HTMLElement): string | null { + let currentElement: HTMLElement | null = element; + while (currentElement) { + const listId = currentElement.getAttribute(dragTargetIdAttribute); + if (listId) { + return listId; + } + currentElement = currentElement.parentElement; + } + return null; + } const handleDragStart = React.useCallback( - (_e: DraggableEvent, { node }: DraggableData) => { - const id = node.getAttribute(dragSourceIdAttribute); - setDragSourceId(id); + (e: DraggableEvent) => { + const rect = (e.target as HTMLElement).getBoundingClientRect(); + setDraggingState?.({ + isDragging: true, + initialX: rect.x, + initialY: rect.y, + }); }, - [], + [setDraggingState], ); const handleDragEnd = React.useCallback( (e: DraggableEvent) => { const { target } = e; - const dragTargetId = (target as HTMLElement).getAttribute( - dragTargetIdAttribute, - ); + const dragTargetId = findTargetId(target as HTMLElement); - if (dragSourceId && dragTargetId && dragSourceId !== dragTargetId) { - /* - As Draggable tries to setState when the + if (dragTargetId) { + /* As Draggable tries to setState when the component is unmounted, it is needed to push onCombine to the event loop queue. onCombine would be run after setState on @@ -33,12 +50,16 @@ export function useDragAndDrop( they fix it on their end. */ setTimeout(() => { - onDragOver(dragSourceId, dragTargetId); + onDragOver(dragTargetId); }, 0); } - setDragSourceId(null); + setDraggingState?.({ + isDragging: false, + initialX: 0, + initialY: 0, + }); }, - [dragSourceId, onDragOver], + [onDragOver], ); return {