From 46dd8887cc58f5076e26f1c4a5ba4d7ddab62418 Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Thu, 4 Sep 2025 21:55:28 +0300 Subject: [PATCH] Fix dialog click closing the context menu --- frontend/src/resources/css/_chat.scss | 68 ---------- .../src/resources/css/common/_components.scss | 9 +- .../src/resources/css/dialogs/_reply.scss | 31 +++++ frontend/src/resources/css/style.scss | 1 + .../src/ui/components/chat/ChatMessages.tsx | 64 ++------- .../ui/components/chat/MessageContextMenu.tsx | 127 +++++++++++++----- .../ui/components/chat/ReplyMessageDialog.tsx | 4 +- 7 files changed, 142 insertions(+), 162 deletions(-) create mode 100644 frontend/src/resources/css/dialogs/_reply.scss diff --git a/frontend/src/resources/css/_chat.scss b/frontend/src/resources/css/_chat.scss index af0e905..e687c1e 100644 --- a/frontend/src/resources/css/_chat.scss +++ b/frontend/src/resources/css/_chat.scss @@ -321,25 +321,6 @@ } } -// Reply preview styles -.reply-preview { - background-color: $color-dark-surface; - border-radius: 8px; - padding: 0.75rem; - margin-bottom: 1rem; - border-left: 3px solid $color-dark-primary; - - .reply-preview-content { - color: $color-dark-on-surface-variant; - font-size: 0.9rem; - line-height: 1.4; - - strong { - color: $color-dark-primary; - } - } -} - .message-profile-pic { img { width: 40px; @@ -362,55 +343,6 @@ } } -.dialog-content { - padding: 1.5rem; - - h3 { - margin: 0 0 1rem 0; - color: $color-dark-on-surface; - font-size: 1.2rem; - font-weight: 600; - } - - mdui-text-field { - width: 100%; - margin-bottom: 1.5rem; - } - - .reply-preview { - margin-bottom: 1rem; - padding: 0.75rem; - background-color: $color-dark-surface-container; - border-radius: 8px; - border: 1px solid $color-dark-outline; - - .reply-content { - display: flex; - flex-direction: column; - gap: 0.25rem; - - .reply-username { - font-weight: 600; - color: $color-dark-on-surface; - font-size: 0.85rem; - } - - .reply-text { - color: $color-dark-on-surface-variant; - font-size: 0.9rem; - line-height: 1.4; - } - } - } - - .dialog-actions { - display: flex; - gap: 0.75rem; - justify-content: flex-end; - margin-top: 1rem; - } -} - .context-menu { position: fixed; background: $color-dark-surface; diff --git a/frontend/src/resources/css/common/_components.scss b/frontend/src/resources/css/common/_components.scss index cf4e87e..8fd5ee7 100644 --- a/frontend/src/resources/css/common/_components.scss +++ b/frontend/src/resources/css/common/_components.scss @@ -101,12 +101,17 @@ button, input { margin: 0 0 1rem 0; color: $color-dark-on-surface; font-size: 1.2rem; + font-weight: 600; } - + + mdui-text-field { + width: 100%; + } + .dialog-actions { display: flex; gap: 0.75rem; justify-content: flex-end; - margin-top: 1.5rem; + margin-top: 1rem; } } \ No newline at end of file diff --git a/frontend/src/resources/css/dialogs/_reply.scss b/frontend/src/resources/css/dialogs/_reply.scss new file mode 100644 index 0000000..420d389 --- /dev/null +++ b/frontend/src/resources/css/dialogs/_reply.scss @@ -0,0 +1,31 @@ +@use "../common/material" as *; + +.reply-dialog .dialog-content { + width: 300px; + overflow-x:hidden; + + .reply-preview-dialog { + margin-bottom: 1rem; + padding: 16px; + background-color: $color-dark-surface-container; + border-radius: 16px; + + .reply-content { + display: flex; + flex-direction: column; + gap: 0.25rem; + + .reply-username { + font-weight: 600; + color: $color-dark-on-surface; + font-size: 0.85rem; + } + + .reply-text { + color: $color-dark-on-surface-variant; + font-size: 0.9rem; + line-height: 1.4; + } + } + } +} \ No newline at end of file diff --git a/frontend/src/resources/css/style.scss b/frontend/src/resources/css/style.scss index e052db4..c380a7b 100644 --- a/frontend/src/resources/css/style.scss +++ b/frontend/src/resources/css/style.scss @@ -8,6 +8,7 @@ @use "common/colors" as *; @use "common/material" as *; @use "electron"; +@use "dialogs/reply"; @use "lib/fonts/montserrat"; @use "lib/fonts/material-symbols"; diff --git a/frontend/src/ui/components/chat/ChatMessages.tsx b/frontend/src/ui/components/chat/ChatMessages.tsx index de1703e..603aa9b 100644 --- a/frontend/src/ui/components/chat/ChatMessages.tsx +++ b/frontend/src/ui/components/chat/ChatMessages.tsx @@ -6,7 +6,7 @@ import type { UserProfile } from "../../../core/types"; import { UserProfileDialog } from "./UserProfileDialog"; import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu"; import { fetchUserProfile } from "../../api/profileApi"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { delay } from "../../../utils/utils"; import { request } from "../../../websocket"; @@ -23,45 +23,6 @@ export function ChatMessages() { message: null, position: { x: 0, y: 0 } }); - const [isContextMenuClosing, setIsContextMenuClosing] = useState(false); - - // Effect to handle clicks outside the context menu - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (contextMenu.isOpen && !isContextMenuClosing) { - // Check if the click is on a context menu element - const target = event.target as Element; - if (!target.closest('.context-menu')) { - handleContextMenuClose(); - } - } - }; - - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Escape' && contextMenu.isOpen && !isContextMenuClosing) { - handleContextMenuClose(); - } - }; - - const handleWindowBlur = () => { - // Close context menu when browser window loses focus - if (contextMenu.isOpen && !isContextMenuClosing) { - handleContextMenuClose(); - } - }; - - // Add event listeners - document.addEventListener('mousedown', handleClickOutside); - document.addEventListener('keydown', handleKeyDown); - window.addEventListener('blur', handleWindowBlur); - - // Cleanup - return () => { - document.removeEventListener('mousedown', handleClickOutside); - document.removeEventListener('keydown', handleKeyDown); - window.removeEventListener('blur', handleWindowBlur); - }; - }, [contextMenu.isOpen, isContextMenuClosing]); const handleProfileClick = async (username: string) => { if (!user.authToken) return; @@ -83,7 +44,6 @@ export function ChatMessages() { const handleContextMenu = (e: React.MouseEvent, message: MessageType) => { e.preventDefault(); console.log("Context menu triggered for message:", message.id, "at position:", e.clientX, e.clientY); - setIsContextMenuClosing(false); setContextMenu({ isOpen: true, message, @@ -91,17 +51,11 @@ export function ChatMessages() { }); }; - const handleContextMenuClose = () => { - setIsContextMenuClosing(true); - // Wait for animation to complete before removing from DOM - setTimeout(() => { - setContextMenu({ - isOpen: false, - message: null, - position: { x: 0, y: 0 } - }); - setIsContextMenuClosing(false); - }, 200); // Match the animation duration from _animations.scss + const handleContextMenuOpenChange = (isOpen: boolean) => { + setContextMenu(prev => ({ + ...prev, + isOpen + })); }; const handleEdit = async (message: MessageType) => { @@ -191,16 +145,16 @@ export function ChatMessages() { /> {/* Context Menu */} - {contextMenu.isOpen && contextMenu.message && ( + {contextMenu.message && ( )} diff --git a/frontend/src/ui/components/chat/MessageContextMenu.tsx b/frontend/src/ui/components/chat/MessageContextMenu.tsx index 01329ad..57e711d 100644 --- a/frontend/src/ui/components/chat/MessageContextMenu.tsx +++ b/frontend/src/ui/components/chat/MessageContextMenu.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import type { Message } from "../../../core/types"; import { EditMessageDialog } from "./EditMessageDialog"; import { ReplyMessageDialog } from "./ReplyMessageDialog"; @@ -9,9 +9,9 @@ interface MessageContextMenuProps { onEdit: (message: Message) => void; onReply: (message: Message) => void; onDelete: (message: Message) => void; - onClose: () => void; position: { x: number; y: number }; - isClosing: boolean; + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; } export interface ContextMenuState { @@ -26,15 +26,54 @@ export function MessageContextMenu({ onEdit, onReply, onDelete, - onClose, position, - isClosing + isOpen, + onOpenChange }: MessageContextMenuProps) { console.log("MessageContextMenu rendered with position:", position, "message:", message.id); - // Internal state for dialogs + // Internal state for dialogs and closing animation const [editDialogOpen, setEditDialogOpen] = useState(false); const [replyDialogOpen, setReplyDialogOpen] = useState(false); + const [isClosing, setIsClosing] = useState(false); + + // Effect to handle clicks outside the context menu + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (isOpen && !isClosing && !editDialogOpen && !replyDialogOpen) { + // Check if the click is on a context menu element + const target = event.target as Element; + if (!target.closest('.context-menu')) { + handleClose(); + } + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isOpen && !isClosing && !editDialogOpen && !replyDialogOpen) { + handleClose(); + } + }; + + const handleWindowBlur = () => { + // Close context menu when browser window loses focus + if (isOpen && !isClosing && !editDialogOpen && !replyDialogOpen) { + handleClose(); + } + }; + + // Add event listeners + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleKeyDown); + window.addEventListener('blur', handleWindowBlur); + + // Cleanup + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('blur', handleWindowBlur); + }; + }, [isOpen, isClosing, editDialogOpen, replyDialogOpen]); const handleAction = (action: string) => { console.log("Context menu action triggered:", action); @@ -50,12 +89,21 @@ export function MessageContextMenu({ case "delete": if (isAuthor) { onDelete(message); - onClose(); + handleClose(); } break; } }; + const handleClose = () => { + setIsClosing(true); + // Wait for animation to complete before calling onOpenChange + setTimeout(() => { + onOpenChange(false); + setIsClosing(false); + }, 200); // Match the animation duration from _animations.scss + }; + const handleEditSave = (messageId: number, newContent: string) => { // Create a temporary message object with the updated content const updatedMessage = { ...message, content: newContent }; @@ -70,36 +118,45 @@ export function MessageContextMenu({ setReplyDialogOpen(false); }; + + + const content = ( +
e.stopPropagation()} + > +
handleAction("reply")}> + reply + Reply +
+ {isAuthor && ( + <> +
handleAction("edit")}> + edit + Edit +
+
handleAction("delete")}> + delete + Delete +
+ + )} +
+ ) + + // Don't render if not open + if (!isOpen && !editDialogOpen && !replyDialogOpen) return null; + return ( <> -
e.stopPropagation()} - > -
handleAction("reply")}> - reply - Reply -
- {isAuthor && ( - <> -
handleAction("edit")}> - edit - Edit -
-
handleAction("delete")}> - delete - Delete -
- - )} -
+ {isOpen ? content : null} {/* Edit Dialog */} +

Reply to Message

-
+
{replyToMessage.username} {replyToMessage.content}