Skip to content
Merged
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
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"react-naver-maps": "^0.1.3",
"react-router-dom": "^7.1.1",
"react-table": "^7.8.0",
"react-zoom-pan-pinch": "^3.7.0",
"styled-components": "^6.1.14",
"zustand": "^5.0.3"
},
Expand Down
209 changes: 174 additions & 35 deletions src/components/ImgViewerModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useImgViewerStore } from '../stores/imgViewerStore';
import styled from 'styled-components';
import ArrowForwardIosRoundedIcon from '@mui/icons-material/ArrowForwardIosRounded';
import ArrowBackIosRoundedIcon from '@mui/icons-material/ArrowBackIosRounded';
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';
import { useState, useEffect } from 'react';
import { useImgViewerStore } from '../stores/imgViewerStore';

const ImgViewerModal = () => {
const {
Expand All @@ -18,14 +20,40 @@ const ImgViewerModal = () => {
images,
} = useImgViewerStore();

// 핀치줌 상태 관리
const [isZooming, setIsZooming] = useState<boolean>(false);

// 디버깅을 위한 WebView 메시지 전송
useEffect(() => {
const sendDebugMessage = (message: any) => {
if (typeof window !== 'undefined' && (window as any).ReactNativeWebView) {
(window as any).ReactNativeWebView.postMessage(JSON.stringify(message));
}
};

// 컴포넌트 마운트 시 디버그 메시지
sendDebugMessage({
type: 'modal_opened',
imageType: currentImageType,
timestamp: Date.now(),
});

return () => {
sendDebugMessage({
type: 'modal_closed',
timestamp: Date.now(),
});
};
}, [currentImageType]);

if (!isOpen || !currentImageType || !images) return null;

const { current, total } = getImagePosition();
const prevEnabled = canGoPrev();
const nextEnabled = canGoNext();

// 이미지 타입별 한국어 이름
const getImageTypeName = (type: string) => {
const getImageTypeName = (type: string): string => {
switch (type) {
case 'position':
return '위치도';
Expand All @@ -40,33 +68,53 @@ const ImgViewerModal = () => {
}
};

const handlePrev = () => {
const handlePrev = (): void => {
const prevType = getPrevImageType();
if (prevType && images[prevType]?.url) {
openImage(images[prevType].url, prevType);
}
};

const handleNext = () => {
const handleNext = (): void => {
const nextType = getNextImageType();
if (nextType && images[nextType]?.url) {
openImage(images[nextType].url, nextType);
}
};

// 키보드 이벤트 처리
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowLeft' && prevEnabled) {
const handleKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === 'ArrowLeft' && prevEnabled && !isZooming) {
handlePrev();
} else if (e.key === 'ArrowRight' && nextEnabled) {
} else if (e.key === 'ArrowRight' && nextEnabled && !isZooming) {
handleNext();
} else if (e.key === 'Escape') {
onClose();
}
};

// 배경 클릭 시 모달 닫기 (핀치줌 중이 아닐 때만)
const handleOverlayClick = (e: React.MouseEvent): void => {
if (e.target === e.currentTarget && !isZooming) {
onClose();
}
};

// 디버깅 메시지 전송 함수
const sendDebugMessage = (message: any): void => {
if (typeof window !== 'undefined' && (window as any).ReactNativeWebView) {
(window as any).ReactNativeWebView.postMessage(JSON.stringify(message));
}
};

return (
<ModalOverlay onClick={onClose} onKeyDown={handleKeyDown} tabIndex={0}>
<ModalOverlay
onClick={handleOverlayClick}
onKeyDown={handleKeyDown}
tabIndex={0}
onTouchStart={(e) => e.stopPropagation()}
onTouchMove={(e) => e.stopPropagation()}
>
<ModalContainer onClick={(e) => e.stopPropagation()}>
<ModalHeader>
<HeaderTitle>
Expand All @@ -76,34 +124,116 @@ const ImgViewerModal = () => {
</ModalHeader>

<ImageContainer>
{/* 이미지 */}
<MainImage
src={currentImageUrl || ''}
alt={getImageTypeName(currentImageType)}
onError={() => {
console.error('이미지 로드 실패:', currentImageUrl);
<TransformWrapper
initialScale={1}
minScale={0.5}
maxScale={4}
smooth={true}
wheel={{
disabled: true, // 마우스 휠 비활성화 (모바일 전용)
}}
pinch={{
disabled: false,
}}
panning={{
disabled: false,
}}
doubleClick={{
disabled: false,
mode: 'toggle',
}}
onPinchingStart={() => {
setIsZooming(true);
sendDebugMessage({
type: 'pinch_start',
timestamp: Date.now(),
});
}}
onPinchingStop={() => {
setTimeout(() => {
setIsZooming(false);
sendDebugMessage({
type: 'pinch_stop',
timestamp: Date.now(),
});
}, 100);
}}
onZoomStart={() => {
setIsZooming(true);
sendDebugMessage({
type: 'zoom_start',
timestamp: Date.now(),
});
}}
onZoomStop={() => {
setTimeout(() => {
setIsZooming(false);
sendDebugMessage({
type: 'zoom_stop',
timestamp: Date.now(),
});
}, 100);
}}
onPanningStart={() => {
sendDebugMessage({
type: 'pan_start',
timestamp: Date.now(),
});
}}
onPanningStop={() => {
sendDebugMessage({
type: 'pan_stop',
timestamp: Date.now(),
});
}}
/>

{/* 왼쪽 화살표 - MUI 아이콘 사용 */}
<ArrowButton
$position="left"
$enabled={prevEnabled}
onClick={handlePrev}
disabled={!prevEnabled}
>
<ArrowBackIosRoundedIcon sx={{ fontSize: '48px' }} />
</ArrowButton>

{/* 오른쪽 화살표 - MUI 아이콘 사용 */}
<ArrowButton
$position="right"
$enabled={nextEnabled}
onClick={handleNext}
disabled={!nextEnabled}
>
<ArrowForwardIosRoundedIcon sx={{ fontSize: '48px' }} />
</ArrowButton>
<TransformComponent
wrapperStyle={{
width: '100%',
height: '100%',
}}
contentStyle={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<MainImage
src={currentImageUrl || ''}
alt={getImageTypeName(currentImageType)}
onError={() => {
console.error('이미지 로드 실패:', currentImageUrl);
}}
draggable={false}
/>
</TransformComponent>
</TransformWrapper>

{/* 왼쪽 화살표 - 핀치줌 중이 아닐 때만 표시 */}
{!isZooming && (
<ArrowButton
$position="left"
$enabled={prevEnabled}
onClick={handlePrev}
disabled={!prevEnabled}
>
<ArrowBackIosRoundedIcon sx={{ fontSize: '48px' }} />
</ArrowButton>
)}

{/* 오른쪽 화살표 - 핀치줌 중이 아닐 때만 표시 */}
{!isZooming && (
<ArrowButton
$position="right"
$enabled={nextEnabled}
onClick={handleNext}
disabled={!nextEnabled}
>
<ArrowForwardIosRoundedIcon sx={{ fontSize: '48px' }} />
</ArrowButton>
)}
</ImageContainer>
</ModalContainer>
</ModalOverlay>
Expand All @@ -123,6 +253,9 @@ const ModalOverlay = styled.div`
display: flex;
flex-direction: column;
outline: none;

/* 터치 이벤트 처리 강화 */
touch-action: none;
`;

const ModalContainer = styled.div`
Expand Down Expand Up @@ -182,13 +315,19 @@ const ImageContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
/* overflow 제거 - 핀치줌을 위해 필요 */
`;

const MainImage = styled.img`
width: 100%;
height: 100%;
object-fit: contain;
user-select: none;
/* 드래그 방지 */
-webkit-user-drag: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
`;

const ArrowButton = styled.button<{
Expand Down
4 changes: 2 additions & 2 deletions src/pages/MapPage/components/InfoTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ const TitleWrapper = styled.div`
gap: 10px;
padding-bottom: 15px;
flex-grow: 1;
overflow: hidden;
`;

const Title = styled.div`
Expand All @@ -253,8 +254,7 @@ const UpperAddressValue = styled.div`
font-size: 14px;
color: #7e7e7e;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
overflow-x: scroll;
`;

const ContentSection = styled.div`
Expand Down
Loading
Loading