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,
+ });
+ },
+ );
-
-
+
);
}
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 {