From f6f933bc1984cabe02b7d2b6b922fbb731fb6644 Mon Sep 17 00:00:00 2001 From: 0Jaemin0 Date: Wed, 1 Jul 2026 13:53:27 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20util=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/openImagePreviewWindow.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/utils/openImagePreviewWindow.ts diff --git a/src/utils/openImagePreviewWindow.ts b/src/utils/openImagePreviewWindow.ts new file mode 100644 index 0000000..b6c3031 --- /dev/null +++ b/src/utils/openImagePreviewWindow.ts @@ -0,0 +1,29 @@ +interface OpenImagePreviewWindowParams { + src: string; + alt: string; +} + +export const openImagePreviewWindow = ({ src, alt }: OpenImagePreviewWindowParams) => { + const imageWindow = window.open('', '_blank'); + + if (!imageWindow) return; + + imageWindow.opener = null; + imageWindow.document.title = alt; + imageWindow.document.body.style.margin = '0'; + imageWindow.document.body.style.backgroundColor = '#0f1115'; + imageWindow.document.body.style.display = 'flex'; + imageWindow.document.body.style.alignItems = 'center'; + imageWindow.document.body.style.justifyContent = 'center'; + imageWindow.document.body.style.minHeight = '100vh'; + + const previewImage = imageWindow.document.createElement('img'); + + previewImage.src = src; + previewImage.alt = alt; + previewImage.style.maxWidth = '100vw'; + previewImage.style.maxHeight = '100vh'; + previewImage.style.objectFit = 'contain'; + + imageWindow.document.body.replaceChildren(previewImage); +}; From 4a2f66732682f48f7cddac2a568311e52394ae3e Mon Sep 17 00:00:00 2001 From: 0Jaemin0 Date: Wed, 1 Jul 2026 13:54:00 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatImageMessage.styled.ts | 42 ++++++++++++++++ .../ChatImageMessage/ChatImageMessage.tsx | 49 +++++++++++++++++++ .../Chat/MyMessageBox/MyMessageBox.styled.ts | 10 +--- .../Chat/MyMessageBox/MyMessageBox.tsx | 13 ++--- .../OtherMessageBox/OtherMessageBox.styled.ts | 14 ++---- .../Chat/OtherMessageBox/OtherMessageBox.tsx | 11 ++--- 6 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 src/components/Chat/ChatImageMessage/ChatImageMessage.styled.ts create mode 100644 src/components/Chat/ChatImageMessage/ChatImageMessage.tsx diff --git a/src/components/Chat/ChatImageMessage/ChatImageMessage.styled.ts b/src/components/Chat/ChatImageMessage/ChatImageMessage.styled.ts new file mode 100644 index 0000000..86e2376 --- /dev/null +++ b/src/components/Chat/ChatImageMessage/ChatImageMessage.styled.ts @@ -0,0 +1,42 @@ +import styled from 'styled-components'; + +type Orientation = 'landscape' | 'portrait' | 'square'; + +const IMAGE_CARD_SIZE: Record = { + landscape: { + width: '260px', + height: '174px', + }, + portrait: { + width: '260px', + height: '325px', + }, + square: { + width: '260px', + height: '260px', + }, +}; + +export const ImageLink = styled.a<{ + $isLoaded: boolean; + $orientation: Orientation; +}>` + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + + width: ${(props) => IMAGE_CARD_SIZE[props.$orientation].width}; + height: ${(props) => IMAGE_CARD_SIZE[props.$orientation].height}; +`; + +export const PreviewImage = styled.img<{ $isLoaded: boolean }>` + display: block; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + object-fit: contain; + opacity: ${(props) => (props.$isLoaded ? 1 : 0)}; +`; diff --git a/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx b/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx new file mode 100644 index 0000000..e22b626 --- /dev/null +++ b/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx @@ -0,0 +1,49 @@ +import { useState } from 'react'; +import { openImagePreviewWindow } from '@utils/openImagePreviewWindow'; +import * as S from './ChatImageMessage.styled'; + +type Orientation = 'landscape' | 'portrait' | 'square'; + +interface ChatImageMessageProps { + src: string; + alt: string; +} + +const ChatImageMessage = ({ src, alt }: ChatImageMessageProps) => { + const [isLoaded, setIsLoaded] = useState(false); + const [orientation, setOrientation] = useState('portrait'); + + const updateImageMeta = (event: React.SyntheticEvent) => { + const { naturalWidth, naturalHeight } = event.currentTarget; + + if (naturalWidth > naturalHeight) setOrientation('landscape'); + else if (naturalWidth < naturalHeight) setOrientation('portrait'); + else setOrientation('square'); + + setIsLoaded(true); + }; + + return ( + { + event.preventDefault(); + openImagePreviewWindow({ src, alt }); + }} + $isLoaded={isLoaded} + $orientation={orientation}> + + + ); +}; + +export default ChatImageMessage; diff --git a/src/components/Chat/MyMessageBox/MyMessageBox.styled.ts b/src/components/Chat/MyMessageBox/MyMessageBox.styled.ts index 7a19bc5..b7d75df 100644 --- a/src/components/Chat/MyMessageBox/MyMessageBox.styled.ts +++ b/src/components/Chat/MyMessageBox/MyMessageBox.styled.ts @@ -17,8 +17,7 @@ export const MyMessageBoxWrapper = styled.div<{ state: boolean; type: string }>` props.type === 'TEXT' ? `${theme.units.spacing.space10} ${theme.units.spacing.space14}` : `${theme.units.spacing.space10} 0 0 0`}; - background-color: ${(props) => - props.type === 'TEXT' ? theme.color.bg.iPrimary : 'none'}; + background-color: ${(props) => (props.type === 'TEXT' ? theme.color.bg.iPrimary : 'none')}; border-radius: ${(props) => props.state ? `${theme.units.radius.radius20} 0 ${theme.units.radius.radius20} ${theme.units.radius.radius20}` @@ -47,11 +46,6 @@ export const TimeWrapper = styled.div` export const ImageWrapper = styled.div` display: flex; + flex-wrap: wrap; gap: ${theme.units.spacing.space10}; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } `; diff --git a/src/components/Chat/MyMessageBox/MyMessageBox.tsx b/src/components/Chat/MyMessageBox/MyMessageBox.tsx index ce6b807..1bb3427 100644 --- a/src/components/Chat/MyMessageBox/MyMessageBox.tsx +++ b/src/components/Chat/MyMessageBox/MyMessageBox.tsx @@ -1,7 +1,8 @@ +import ChatAttachments from '@components/Chat/ChatAttachment/ChatAttachments'; +import ChatImageMessage from '@components/Chat/ChatImageMessage/ChatImageMessage'; import Flex from '@components/common/Flex/Flex'; import Text from '@components/common/Text/Text'; import type { Attachment } from '@type/chat'; -import ChatAttachments from '../ChatAttachment/ChatAttachments'; import * as S from './MyMessageBox.styled'; export interface MyMessageBoxProps { @@ -33,15 +34,7 @@ const MyMessageBox = (props: MyMessageBoxProps) => { )} {type === 'IMAGE' && ( - {file?.map((img) => ( - - {img.filename} - - ))} + {file?.map((img) => )} )} {type === 'FILE' && ( diff --git a/src/components/Chat/OtherMessageBox/OtherMessageBox.styled.ts b/src/components/Chat/OtherMessageBox/OtherMessageBox.styled.ts index 48c01b4..fdbbaed 100644 --- a/src/components/Chat/OtherMessageBox/OtherMessageBox.styled.ts +++ b/src/components/Chat/OtherMessageBox/OtherMessageBox.styled.ts @@ -7,9 +7,7 @@ export const OtherMessageBoxContainer = styled.div<{ display: flex; width: 100%; padding-top: ${(props) => - props.state - ? `${theme.units.spacing.space16}` - : `${theme.units.spacing.space6}`}; + props.state ? `${theme.units.spacing.space16}` : `${theme.units.spacing.space6}`}; gap: ${theme.units.spacing.space8}; `; @@ -25,8 +23,7 @@ export const OtherMessageBoxWrapper = styled.div<{ props.type === 'TEXT' ? `${theme.units.spacing.space10} ${theme.units.spacing.space14}` : `${theme.units.spacing.space10} 0 0 0`}; - background-color: ${(props) => - props.type === 'TEXT' ? theme.color.bg.secondary : 'none'}; + background-color: ${(props) => (props.type === 'TEXT' ? theme.color.bg.secondary : 'none')}; border-radius: ${(props) => props.state ? `0 ${theme.units.radius.radius20} ${theme.units.radius.radius20} ${theme.units.radius.radius20}` @@ -62,11 +59,6 @@ export const AvatarSpacer = styled.div` export const ImageWrapper = styled.div` display: flex; + flex-wrap: wrap; gap: ${theme.units.spacing.space10}; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } `; diff --git a/src/components/Chat/OtherMessageBox/OtherMessageBox.tsx b/src/components/Chat/OtherMessageBox/OtherMessageBox.tsx index 5092b58..128f2fa 100644 --- a/src/components/Chat/OtherMessageBox/OtherMessageBox.tsx +++ b/src/components/Chat/OtherMessageBox/OtherMessageBox.tsx @@ -1,8 +1,9 @@ +import ChatAttachments from '@components/Chat/ChatAttachment/ChatAttachments'; +import ChatImageMessage from '@components/Chat/ChatImageMessage/ChatImageMessage'; import Avatar from '@components/common/Avatar/Avatar'; import Flex from '@components/common/Flex/Flex'; import Text from '@components/common/Text/Text'; import type { Attachment } from '@type/chat'; -import ChatAttachments from '../ChatAttachment/ChatAttachments'; import * as S from './OtherMessageBox.styled'; export interface OtherMessageBoxProps { @@ -43,13 +44,7 @@ const OtherMessageBox = (props: OtherMessageBoxProps) => { {type === 'IMAGE' && ( {file?.map((img) => ( - - {img.filename} - + ))} )} From 34a9b42a1c178c76d1fb23a8ce5c7cd7931d00b0 Mon Sep 17 00:00:00 2001 From: 0Jaemin0 Date: Sat, 4 Jul 2026 16:12:32 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EA=B8=B0=EB=B3=B8=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chat/ChatImageMessage/ChatImageMessage.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx b/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx index e22b626..ede9912 100644 --- a/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx +++ b/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx @@ -23,16 +23,20 @@ const ChatImageMessage = ({ src, alt }: ChatImageMessageProps) => { setIsLoaded(true); }; + const handleImageClick = (event: React.MouseEvent) => { + if (event.ctrlKey || event.metaKey || event.shiftKey) return; + + event.preventDefault(); + openImagePreviewWindow({ src, alt }); + }; + return ( { - event.preventDefault(); - openImagePreviewWindow({ src, alt }); - }} + onClick={handleImageClick} $isLoaded={isLoaded} $orientation={orientation}> Date: Sat, 4 Jul 2026 16:16:02 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=AA=85?= =?UTF-8?q?=EC=B9=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Chat/ChatImageMessage/ChatImageMessage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx b/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx index ede9912..69655d9 100644 --- a/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx +++ b/src/components/Chat/ChatImageMessage/ChatImageMessage.tsx @@ -13,7 +13,7 @@ const ChatImageMessage = ({ src, alt }: ChatImageMessageProps) => { const [isLoaded, setIsLoaded] = useState(false); const [orientation, setOrientation] = useState('portrait'); - const updateImageMeta = (event: React.SyntheticEvent) => { + const handleImageLoad = (event: React.SyntheticEvent) => { const { naturalWidth, naturalHeight } = event.currentTarget; if (naturalWidth > naturalHeight) setOrientation('landscape'); @@ -43,7 +43,7 @@ const ChatImageMessage = ({ src, alt }: ChatImageMessageProps) => { src={src} alt={alt} loading='lazy' - onLoad={updateImageMeta} + onLoad={handleImageLoad} $isLoaded={isLoaded} /> From a7304b77dccfd6f8fb1137429468d72f948d6b94 Mon Sep 17 00:00:00 2001 From: 0Jaemin0 Date: Sun, 5 Jul 2026 15:04:18 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=EC=B1=84=ED=8C=85=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=8C=20=EC=B2=98=EB=A6=AC=20=EC=A0=84=EC=86=A1=20=EC=95=88?= =?UTF-8?q?=EC=A0=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/chatting/useChatMessages.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/chatting/useChatMessages.ts b/src/hooks/chatting/useChatMessages.ts index d1faccd..b14fb2a 100644 --- a/src/hooks/chatting/useChatMessages.ts +++ b/src/hooks/chatting/useChatMessages.ts @@ -28,10 +28,10 @@ const useChatMessages = (props: useChatMessagesProps) => { const latestMessages = messagePages[0]?.chatChannelMessages ?? []; const latestMessageId = latestMessages[0]?.id; - if (!latestMessageId || !userStatus) return; + if (!latestMessageId || !userStatus || !stompClient) return; if (lastReadMessageIdRef.current === latestMessageId) return; - stompClient?.send( + stompClient.send( END_POINTS.READ_MESSAGE(userStatus.profile.lastSeenTeamspaceId, selectedChat, latestMessageId) ); lastReadMessageIdRef.current = latestMessageId;