Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/components/Chat/ChatImageMessage/ChatImageMessage.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import styled from 'styled-components';

type Orientation = 'landscape' | 'portrait' | 'square';

const IMAGE_CARD_SIZE: Record<Orientation, { width: string; height: string }> = {
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)};
`;
53 changes: 53 additions & 0 deletions src/components/Chat/ChatImageMessage/ChatImageMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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<Orientation>('portrait');

const handleImageLoad = (event: React.SyntheticEvent<HTMLImageElement>) => {
const { naturalWidth, naturalHeight } = event.currentTarget;

if (naturalWidth > naturalHeight) setOrientation('landscape');
else if (naturalWidth < naturalHeight) setOrientation('portrait');
else setOrientation('square');

setIsLoaded(true);
};

const handleImageClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (event.ctrlKey || event.metaKey || event.shiftKey) return;

event.preventDefault();
openImagePreviewWindow({ src, alt });
};

return (
<S.ImageLink
href={src}
target='_blank'
rel='noopener noreferrer'
aria-label={`${alt} 이미지 새 탭에서 보기`}
onClick={handleImageClick}
$isLoaded={isLoaded}
$orientation={orientation}>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<S.PreviewImage
src={src}
alt={alt}
loading='lazy'
onLoad={handleImageLoad}
$isLoaded={isLoaded}
/>
</S.ImageLink>
);
};

export default ChatImageMessage;
10 changes: 2 additions & 8 deletions src/components/Chat/MyMessageBox/MyMessageBox.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand Down Expand Up @@ -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;
}
`;
13 changes: 3 additions & 10 deletions src/components/Chat/MyMessageBox/MyMessageBox.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -33,15 +34,7 @@ const MyMessageBox = (props: MyMessageBoxProps) => {
)}
{type === 'IMAGE' && (
<S.ImageWrapper>
{file?.map((img) => (
<a
key={img.id}
href={img.url}
target='_blank'
rel='noopener noreferrer'>
<img src={img.url} alt={img.filename} />
</a>
))}
{file?.map((img) => <ChatImageMessage key={img.id} src={img.url} alt={img.filename} />)}
</S.ImageWrapper>
)}
{type === 'FILE' && (
Expand Down
14 changes: 3 additions & 11 deletions src/components/Chat/OtherMessageBox/OtherMessageBox.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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};
`;

Expand All @@ -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}`
Expand Down Expand Up @@ -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;
}
`;
11 changes: 3 additions & 8 deletions src/components/Chat/OtherMessageBox/OtherMessageBox.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -43,13 +44,7 @@ const OtherMessageBox = (props: OtherMessageBoxProps) => {
{type === 'IMAGE' && (
<S.ImageWrapper>
{file?.map((img) => (
<a
key={img.id}
href={img.url}
target='_blank'
rel='noopener noreferrer'>
<img src={img.url} alt={img.filename} />
</a>
<ChatImageMessage key={img.id} src={img.url} alt={img.filename} />
))}
</S.ImageWrapper>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/chatting/useChatMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 29 additions & 0 deletions src/utils/openImagePreviewWindow.ts
Original file line number Diff line number Diff line change
@@ -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);
};