From 9994a47e547e9869eda22f6a404e3bd4d8b126a1 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 5 Feb 2026 14:07:15 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20=ED=99=95=EB=8C=80/=EC=B6=95=EC=86=8C?= =?UTF-8?q?=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파일 관리 모달에 이미지 미리보기 기능을 개선하여 사용자가 선택한 파일을 보다 직관적으로 확인할 수 있도록 하였습니다. - 확대/축소 기능을 추가하여 사용자가 이미지의 세부 사항을 쉽게 확인할 수 있도록 하였습니다. - 드래그 앤 드롭으로 이미지 위치를 조정할 수 있는 기능을 추가하여 사용자 경험을 향상시켰습니다. - 모달 열릴 때 확대/축소 레벨과 이미지 위치를 초기화하여 일관된 사용자 경험을 제공합니다. --- .../v2-file-upload/FileManagerModal.tsx | 158 +++++++++++++++--- 1 file changed, 133 insertions(+), 25 deletions(-) diff --git a/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx b/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx index de838fbf..3c00aeca 100644 --- a/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx +++ b/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx @@ -18,7 +18,10 @@ import { Archive, Presentation, X, - Star + Star, + ZoomIn, + ZoomOut, + RotateCcw, } from "lucide-react"; import { formatFileSize } from "@/lib/utils"; import { FileViewerModal } from "./FileViewerModal"; @@ -54,7 +57,12 @@ export const FileManagerModal: React.FC = ({ const [isViewerOpen, setIsViewerOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); // 선택된 파일 (좌측 미리보기용) const [previewImageUrl, setPreviewImageUrl] = useState(null); // 이미지 미리보기 URL + const [zoomLevel, setZoomLevel] = useState(1); // 🔍 확대/축소 레벨 + const [imagePosition, setImagePosition] = useState({ x: 0, y: 0 }); // 🖱️ 이미지 위치 + const [isDragging, setIsDragging] = useState(false); // 🖱️ 드래그 중 여부 + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); // 🖱️ 드래그 시작 위치 const fileInputRef = useRef(null); + const imageContainerRef = useRef(null); // 파일 아이콘 가져오기 const getFileIcon = (fileExt: string) => { @@ -146,6 +154,8 @@ export const FileManagerModal: React.FC = ({ // 파일 클릭 시 미리보기 로드 const handleFileClick = async (file: FileInfo) => { setSelectedFile(file); + setZoomLevel(1); // 🔍 파일 선택 시 확대/축소 레벨 초기화 + setImagePosition({ x: 0, y: 0 }); // 🖱️ 이미지 위치 초기화 // 이미지 파일인 경우 미리보기 로드 // 🔑 점(.)을 제거하고 확장자만 비교 @@ -195,18 +205,50 @@ export const FileManagerModal: React.FC = ({ }; }, [previewImageUrl]); - // 🔑 모달이 열릴 때 첫 번째 파일을 자동으로 선택 + // 🔑 모달이 열릴 때 첫 번째 파일을 자동으로 선택하고 확대/축소 레벨 초기화 React.useEffect(() => { - if (isOpen && uploadedFiles.length > 0 && !selectedFile) { - const firstFile = uploadedFiles[0]; - handleFileClick(firstFile); + if (isOpen) { + setZoomLevel(1); // 🔍 모달 열릴 때 확대/축소 레벨 초기화 + setImagePosition({ x: 0, y: 0 }); // 🖱️ 이미지 위치 초기화 + if (uploadedFiles.length > 0 && !selectedFile) { + const firstFile = uploadedFiles[0]; + handleFileClick(firstFile); + } } }, [isOpen, uploadedFiles, selectedFile]); + // 🖱️ 마우스 드래그 핸들러 + const handleMouseDown = (e: React.MouseEvent) => { + if (zoomLevel > 1) { + setIsDragging(true); + setDragStart({ x: e.clientX - imagePosition.x, y: e.clientY - imagePosition.y }); + } + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (isDragging && zoomLevel > 1) { + setImagePosition({ + x: e.clientX - dragStart.x, + y: e.clientY - dragStart.y, + }); + } + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + // 🔍 확대/축소 레벨이 1로 돌아가면 위치도 초기화 + React.useEffect(() => { + if (zoomLevel <= 1) { + setImagePosition({ x: 0, y: 0 }); + } + }, [zoomLevel]); + return ( <> {}}> - + 파일 관리 ({uploadedFiles.length}개) @@ -267,31 +309,97 @@ export const FileManagerModal: React.FC = ({ )} - {/* 좌우 분할 레이아웃 */} + {/* 좌우 분할 레이아웃 - 좌측 넓게, 우측 고정 너비 */}
- {/* 좌측: 이미지 미리보기 */} -
- {selectedFile && previewImageUrl ? ( - {selectedFile.realFileName} - ) : selectedFile ? ( -
- {getFileIcon(selectedFile.fileExt)} -

미리보기 불가능

+ {/* 좌측: 이미지 미리보기 (확대/축소 가능) */} +
+ {/* 확대/축소 컨트롤 */} + {selectedFile && previewImageUrl && ( +
+ + + {Math.round(zoomLevel * 100)}% + + +
- ) : ( -
- -

파일을 선택하면 미리보기가 표시됩니다

+ )} + + {/* 이미지 미리보기 영역 - 마우스 휠로 확대/축소, 드래그로 이동 */} +
1 ? (isDragging ? 'cursor-grabbing' : 'cursor-grab') : 'cursor-zoom-in' + }`} + onWheel={(e) => { + if (selectedFile && previewImageUrl) { + e.preventDefault(); + const delta = e.deltaY > 0 ? -0.1 : 0.1; + setZoomLevel(prev => Math.min(4, Math.max(0.25, prev + delta))); + } + }} + onMouseDown={handleMouseDown} + onMouseMove={handleMouseMove} + onMouseUp={handleMouseUp} + onMouseLeave={handleMouseUp} + > + {selectedFile && previewImageUrl ? ( + {selectedFile.realFileName} + ) : selectedFile ? ( +
+ {getFileIcon(selectedFile.fileExt)} +

미리보기 불가능

+
+ ) : ( +
+ +

파일을 선택하면 미리보기가 표시됩니다

+
+ )} +
+ + {/* 파일 정보 바 */} + {selectedFile && ( +
+ {selectedFile.realFileName}
)}
- {/* 우측: 파일 목록 */} -
+ {/* 우측: 파일 목록 (고정 너비) */} +