Merge pull request #518 from kubet/fix/pointer-native-event

Fix/pointer native event
This commit is contained in:
kubet 2025-05-26 19:52:34 +02:00 committed by GitHub
commit 606bff01f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 92 additions and 75 deletions

View File

@ -61,38 +61,38 @@ export function NavAgents() {
const { performDelete } = useDeleteOperation();
const isPerformingActionRef = useRef(false);
const queryClient = useQueryClient();
const [selectedThreads, setSelectedThreads] = useState<Set<string>>(new Set());
const [deleteProgress, setDeleteProgress] = useState(0);
const [totalToDelete, setTotalToDelete] = useState(0);
const {
data: projects = [],
const {
data: projects = [],
isLoading: isProjectsLoading,
error: projectsError
error: projectsError
} = useProjects();
const {
data: threads = [],
const {
data: threads = [],
isLoading: isThreadsLoading,
error: threadsError
error: threadsError
} = useThreads();
const { mutate: deleteThreadMutation, isPending: isDeletingSingle } = useDeleteThread();
const {
mutate: deleteMultipleThreadsMutation,
isPending: isDeletingMultiple
const {
mutate: deleteMultipleThreadsMutation,
isPending: isDeletingMultiple
} = useDeleteMultipleThreads();
const combinedThreads: ThreadWithProject[] =
!isProjectsLoading && !isThreadsLoading ?
processThreadsWithProjects(threads, projects) : [];
const combinedThreads: ThreadWithProject[] =
!isProjectsLoading && !isThreadsLoading ?
processThreadsWithProjects(threads, projects) : [];
const handleDeletionProgress = (completed: number, total: number) => {
const percentage = (completed / total) * 100;
setDeleteProgress(percentage);
};
useEffect(() => {
const handleProjectUpdate = (event: Event) => {
const customEvent = event as CustomEvent;
@ -145,7 +145,7 @@ export function NavAgents() {
e.preventDefault();
return;
}
e.preventDefault()
setLoadingThreadId(threadId)
router.push(url)
@ -157,7 +157,7 @@ export function NavAgents() {
e.preventDefault();
e.stopPropagation();
}
setSelectedThreads(prev => {
const newSelection = new Set(prev);
if (newSelection.has(threadId)) {
@ -189,18 +189,18 @@ export function NavAgents() {
// Function to handle multi-delete
const handleMultiDelete = () => {
if (selectedThreads.size === 0) return;
// Get thread names for confirmation dialog
const threadsToDelete = combinedThreads.filter(t => selectedThreads.has(t.threadId));
const threadNames = threadsToDelete.map(t => t.projectName).join(", ");
setThreadToDelete({
id: "multiple",
name: selectedThreads.size > 3
? `${selectedThreads.size} conversations`
: threadNames
setThreadToDelete({
id: "multiple",
name: selectedThreads.size > 3
? `${selectedThreads.size} conversations`
: threadNames
});
setTotalToDelete(selectedThreads.size);
setDeleteProgress(0);
setIsDeleteDialogOpen(true);
@ -261,10 +261,10 @@ export function NavAgents() {
// Multi-thread deletion
const threadIdsToDelete = Array.from(selectedThreads);
const isActiveThreadIncluded = threadIdsToDelete.some(id => pathname?.includes(id));
// Show initial toast
toast.info(`Deleting ${threadIdsToDelete.length} conversations...`);
try {
// If the active thread is included, handle navigation first
if (isActiveThreadIncluded) {
@ -272,14 +272,14 @@ export function NavAgents() {
isNavigatingRef.current = true;
document.body.style.pointerEvents = 'none';
router.push('/dashboard');
// Wait a moment for navigation to start
await new Promise(resolve => setTimeout(resolve, 100));
}
// Use the mutation for bulk deletion
deleteMultipleThreadsMutation(
{
{
threadIds: threadIdsToDelete,
onProgress: handleDeletionProgress
},
@ -287,15 +287,15 @@ export function NavAgents() {
onSuccess: (data) => {
// Invalidate queries to refresh the list
queryClient.invalidateQueries({ queryKey: threadKeys.lists() });
// Show success message
toast.success(`Successfully deleted ${data.successful.length} conversations`);
// If some deletions failed, show warning
if (data.failed.length > 0) {
toast.warning(`Failed to delete ${data.failed.length} conversations`);
}
// Reset states
setSelectedThreads(new Set());
setDeleteProgress(0);
@ -316,7 +316,7 @@ export function NavAgents() {
} catch (err) {
console.error('Error initiating bulk deletion:', err);
toast.error('Error initiating deletion process');
// Reset states
setSelectedThreads(new Set());
setThreadToDelete(null);
@ -330,7 +330,7 @@ export function NavAgents() {
// Loading state or error handling
const isLoading = isProjectsLoading || isThreadsLoading;
const hasError = projectsError || threadsError;
if (hasError) {
console.error('Error loading data:', { projectsError, threadsError });
}
@ -343,16 +343,16 @@ export function NavAgents() {
<div className="flex items-center space-x-1">
{selectedThreads.size > 0 ? (
<>
<Button
variant="ghost"
<Button
variant="ghost"
size="icon"
onClick={deselectAllThreads}
className="h-7 w-7"
>
<X className="h-4 w-4" />
</Button>
<Button
variant="ghost"
<Button
variant="ghost"
size="icon"
onClick={selectAllThreads}
disabled={selectedThreads.size === combinedThreads.length}
@ -360,8 +360,8 @@ export function NavAgents() {
>
<Check className="h-4 w-4" />
</Button>
<Button
variant="ghost"
<Button
variant="ghost"
size="icon"
onClick={handleMultiDelete}
className="h-7 w-7 text-destructive"
@ -436,8 +436,8 @@ export function NavAgents() {
<SidebarMenuButton
asChild
className={
isActive ? 'bg-accent text-accent-foreground' :
isSelected ? 'bg-primary/10' : ''
isActive ? 'bg-accent text-accent-foreground' :
isSelected ? 'bg-primary/10' : ''
}
>
<Link
@ -462,13 +462,12 @@ export function NavAgents() {
<div className="relative">
<SidebarMenuButton
asChild
className={`relative ${
isActive
? 'bg-accent text-accent-foreground font-medium'
: isSelected
? 'bg-primary/10'
className={`relative ${isActive
? 'bg-accent text-accent-foreground font-medium'
: isSelected
? 'bg-primary/10'
: ''
}`}
}`}
>
<Link
href={thread.url}
@ -484,27 +483,24 @@ export function NavAgents() {
) : (
<>
{/* MessagesSquare icon - hidden on hover if not selected */}
<MessagesSquare
className={`h-4 w-4 transition-opacity duration-150 ${
isSelected ? 'opacity-0' : 'opacity-100 group-hover/icon:opacity-0'
}`}
<MessagesSquare
className={`h-4 w-4 transition-opacity duration-150 ${isSelected ? 'opacity-0' : 'opacity-100 group-hover/icon:opacity-0'
}`}
/>
{/* Checkbox - appears on hover or when selected */}
<div
className={`absolute inset-0 flex items-center justify-center transition-opacity duration-150 ${
isSelected
? 'opacity-100'
: 'opacity-0 group-hover/icon:opacity-100'
}`}
<div
className={`absolute inset-0 flex items-center justify-center transition-opacity duration-150 ${isSelected
? 'opacity-100'
: 'opacity-0 group-hover/icon:opacity-100'
}`}
onClick={(e) => toggleThreadSelection(thread.threadId, e)}
>
<div
className={`h-4 w-4 border rounded cursor-pointer hover:bg-muted/50 transition-colors flex items-center justify-center ${
isSelected
? 'bg-primary border-primary'
: 'border-muted-foreground/30 bg-background'
}`}
<div
className={`h-4 w-4 border rounded cursor-pointer hover:bg-muted/50 transition-colors flex items-center justify-center ${isSelected
? 'bg-primary border-primary'
: 'border-muted-foreground/30 bg-background'
}`}
>
{isSelected && <Check className="h-3 w-3 text-primary-foreground" />}
</div>
@ -520,7 +516,14 @@ export function NavAgents() {
{state !== 'collapsed' && !isSelected && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover className="group-hover:opacity-100">
<SidebarMenuAction
showOnHover
className="group-hover:opacity-100"
onClick={() => {
// Ensure pointer events are enabled when dropdown opens
document.body.style.pointerEvents = 'auto';
}}
>
<MoreHorizontal />
<span className="sr-only">More</span>
</SidebarMenuAction>
@ -575,21 +578,21 @@ export function NavAgents() {
</SidebarMenuItem>
)}
</SidebarMenu>
{(isDeletingSingle || isDeletingMultiple) && totalToDelete > 0 && (
<div className="mt-2 px-2">
<div className="text-xs text-muted-foreground mb-1">
Deleting {deleteProgress > 0 ? `(${Math.floor(deleteProgress)}%)` : '...'}
</div>
<div className="w-full bg-secondary h-1 rounded-full overflow-hidden">
<div
className="bg-primary h-1 transition-all duration-300 ease-in-out"
<div
className="bg-primary h-1 transition-all duration-300 ease-in-out"
style={{ width: `${deleteProgress}%` }}
/>
</div>
</div>
)}
<ShareModal
isOpen={showShareModal}
onClose={() => setShowShareModal(false)}

View File

@ -28,7 +28,14 @@ export function ShareModal({ isOpen, onClose, threadId, projectId }: ShareModalP
const [isLoading, setIsLoading] = useState(false);
const [isChecking, setIsChecking] = useState(false);
const [isCopying, setIsCopying] = useState(false);
// Reset pointer events when modal opens
useEffect(() => {
if (isOpen) {
document.body.style.pointerEvents = 'auto';
}
}, [isOpen]);
const updateThreadMutation = useUpdateThreadMutation();
useEffect(() => {
@ -105,7 +112,7 @@ export function ShareModal({ isOpen, onClose, threadId, projectId }: ShareModalP
const updatePublicStatus = async (isPublic: boolean) => {
console.log("Updating public status for thread:", threadId, "and project:", projectId, "to", isPublic);
if (!threadId) return;
await updateProject(projectId, { is_public: isPublic });
await updateThreadMutation.mutateAsync({
threadId,

View File

@ -1,6 +1,6 @@
'use client';
import React from 'react';
import React, { useEffect } from 'react';
import { Loader2 } from 'lucide-react';
import {
@ -32,6 +32,13 @@ export function DeleteConfirmationDialog({
threadName,
isDeleting,
}: DeleteConfirmationDialogProps) {
// Reset pointer events when dialog opens
useEffect(() => {
if (isOpen) {
document.body.style.pointerEvents = 'auto';
}
}, [isOpen]);
return (
<AlertDialog open={isOpen} onOpenChange={onClose}>
<AlertDialogContent>

View File

@ -96,7 +96,7 @@ export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
}, [value, ref]);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
e.preventDefault();
if (
(value.trim() || uploadedFiles.length > 0) &&