fix: reorder skill tags while dragging

This commit is contained in:
134 2026-07-03 00:41:38 +08:00
parent 273379b18d
commit 18035d4272

View File

@ -436,6 +436,17 @@ function reorderCanvasSkillTags(
return nextTags;
}
function applyCanvasSkillTagOrder(tags: CanvasSkillTag[], order: string[]) {
if (tags.length < 2 || order.length === 0) return tags;
const tagById = new Map(tags.map((tag) => [tag.id, tag]));
const orderedTags = order
.map((id) => tagById.get(id))
.filter((tag): tag is CanvasSkillTag => Boolean(tag));
const orderedIds = new Set(orderedTags.map((tag) => tag.id));
return [...orderedTags, ...tags.filter((tag) => !orderedIds.has(tag.id))];
}
type ClawdComposerSubmissionSnapshot = {
chatId: string;
canvasId?: string;
@ -2185,8 +2196,13 @@ export function CanvasChatPanel({
const [selectedSkillTagId, setSelectedSkillTagId] = useState<string | null>(null);
const [areCanvasSkillTagsExpanded, setAreCanvasSkillTagsExpanded] = useState(false);
const [draggingSkillTagId, setDraggingSkillTagId] = useState<string | null>(null);
const [canvasSkillTagOrderOverride, setCanvasSkillTagOrderOverride] = useState<string[] | null>(
null
);
const pendingSelectedClawdSkillTagRef = useRef<CanvasSkillTag | null>(null);
const draggingSkillTagIdRef = useRef<string | null>(null);
const latestCanvasSkillTagsRef = useRef<CanvasSkillTag[]>([]);
const pendingCanvasSkillTagOrderSaveRef = useRef<string[] | null>(null);
const suppressSkillTagClickRef = useRef(false);
const [messageViewportKey, setMessageViewportKey] = useState(0);
const messagesEndRef = useRef<HTMLDivElement>(null);
@ -4164,16 +4180,34 @@ export function CanvasChatPanel({
const canvasSkillTagsEnabled = canvasSkillTagsData?.enabled !== false;
const canvasSkillTagsTitle =
String(canvasSkillTagsData?.title || "").trim() || DEFAULT_CANVAS_SKILL_TAGS_TITLE;
const canvasSkillTags = useMemo(
const baseCanvasSkillTags = useMemo(
() => (canvasSkillTagsEnabled ? normalizeCanvasSkillTags(canvasSkillTagsData?.tags) : []),
[canvasSkillTagsData?.tags, canvasSkillTagsEnabled]
);
const handleReorderCanvasSkillTag = useCallback(
async (draggedId: string, targetId: string) => {
const nextTags = reorderCanvasSkillTags(canvasSkillTags, draggedId, targetId);
if (nextTags === canvasSkillTags) return;
const canvasSkillTags = useMemo(
() =>
canvasSkillTagOrderOverride
? applyCanvasSkillTagOrder(baseCanvasSkillTags, canvasSkillTagOrderOverride)
: baseCanvasSkillTags,
[baseCanvasSkillTags, canvasSkillTagOrderOverride]
);
useEffect(() => {
latestCanvasSkillTagsRef.current = canvasSkillTags;
}, [canvasSkillTags]);
const userOrder = nextTags.map((tag) => tag.id);
const persistCanvasSkillTagOrder = useCallback(
async (nextOrder: string[]) => {
const knownIds = new Set(baseCanvasSkillTags.map((tag) => tag.id));
const userOrder = Array.from(
new Set(
nextOrder
.map((id) => String(id || "").trim())
.filter((id) => id && knownIds.has(id))
)
);
if (userOrder.length < 2) return;
const nextTags = applyCanvasSkillTagOrder(baseCanvasSkillTags, userOrder);
const optimisticData = {
...(canvasSkillTagsData || {}),
enabled: canvasSkillTagsData?.enabled,
@ -4198,11 +4232,12 @@ export function CanvasChatPanel({
toast.success("快捷标签顺序已保存");
} catch (error) {
console.error("[CanvasChat] 保存快捷标签顺序失败:", error);
setCanvasSkillTagOrderOverride(null);
toast.error("快捷标签顺序保存失败");
void mutate("/api/canvas-skill-tags");
}
},
[canvasSkillTags, canvasSkillTagsData, mutate]
[baseCanvasSkillTags, canvasSkillTagsData, mutate]
);
const selectedSkillTag = useMemo(
() => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null,
@ -5429,6 +5464,7 @@ export function CanvasChatPanel({
const handleCanvasSkillTagDragStart = useCallback(
(event: ReactDragEvent<HTMLButtonElement>, tagId: string) => {
draggingSkillTagIdRef.current = tagId;
pendingCanvasSkillTagOrderSaveRef.current = null;
setDraggingSkillTagId(tagId);
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", tagId);
@ -5436,6 +5472,24 @@ export function CanvasChatPanel({
[]
);
const handleCanvasSkillTagDragEnter = useCallback(
(event: ReactDragEvent<HTMLButtonElement>, targetId: string) => {
const draggedId = draggingSkillTagIdRef.current;
if (!draggedId || draggedId === targetId) return;
event.preventDefault();
const currentTags = latestCanvasSkillTagsRef.current;
const nextTags = reorderCanvasSkillTags(currentTags, draggedId, targetId);
if (nextTags === currentTags) return;
const nextOrder = nextTags.map((tag) => tag.id);
pendingCanvasSkillTagOrderSaveRef.current = nextOrder;
latestCanvasSkillTagsRef.current = nextTags;
setCanvasSkillTagOrderOverride(nextOrder);
},
[]
);
const handleCanvasSkillTagDragOver = useCallback(
(event: ReactDragEvent<HTMLButtonElement>, targetId: string) => {
const draggedId = draggingSkillTagIdRef.current;
@ -5451,23 +5505,41 @@ export function CanvasChatPanel({
event.preventDefault();
const draggedId =
event.dataTransfer.getData("text/plain") || draggingSkillTagIdRef.current;
draggingSkillTagIdRef.current = null;
setDraggingSkillTagId(null);
if (!draggedId || draggedId === targetId) return;
if (draggedId && draggedId !== targetId && !pendingCanvasSkillTagOrderSaveRef.current) {
const nextTags = reorderCanvasSkillTags(
latestCanvasSkillTagsRef.current,
draggedId,
targetId
);
if (nextTags !== latestCanvasSkillTagsRef.current) {
const nextOrder = nextTags.map((tag) => tag.id);
pendingCanvasSkillTagOrderSaveRef.current = nextOrder;
latestCanvasSkillTagsRef.current = nextTags;
setCanvasSkillTagOrderOverride(nextOrder);
}
}
suppressSkillTagClickRef.current = true;
window.setTimeout(() => {
suppressSkillTagClickRef.current = false;
}, 0);
void handleReorderCanvasSkillTag(draggedId, targetId);
},
[handleReorderCanvasSkillTag]
[]
);
const handleCanvasSkillTagDragEnd = useCallback(() => {
const pendingOrder = pendingCanvasSkillTagOrderSaveRef.current;
pendingCanvasSkillTagOrderSaveRef.current = null;
draggingSkillTagIdRef.current = null;
setDraggingSkillTagId(null);
}, []);
if (pendingOrder?.length) {
suppressSkillTagClickRef.current = true;
window.setTimeout(() => {
suppressSkillTagClickRef.current = false;
}, 0);
void persistCanvasSkillTagOrder(pendingOrder);
}
}, [persistCanvasSkillTagOrder]);
useEffect(() => {
if (!effectiveAgentEnabled || typeof window === "undefined") return;
@ -12760,6 +12832,9 @@ export function CanvasChatPanel({
onDragStart={(event) =>
handleCanvasSkillTagDragStart(event, tag.id)
}
onDragEnter={(event) =>
handleCanvasSkillTagDragEnter(event, tag.id)
}
onDragOver={(event) =>
handleCanvasSkillTagDragOver(event, tag.id)
}