; Please enter a commit message to explain why this merge is necessary,
; especially if it merges an updated upstream into a topic branch.
;
; Lines starting with ';' will be ignored, and an empty message aborts
; the commit.
This commit is contained in:
leeheejin
2025-10-22 10:10:37 +09:00
4 changed files with 362 additions and 21 deletions

View File

@@ -13,6 +13,7 @@ import { GRID_CONFIG, snapToGrid, snapSizeToGrid, calculateCellSize } from "./gr
import { Resolution, RESOLUTIONS, detectScreenResolution } from "./ResolutionSelector";
import { DashboardProvider } from "@/contexts/DashboardContext";
import { useMenu } from "@/contexts/MenuContext";
import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import {
AlertDialog,
@@ -58,6 +59,9 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
const [successModalOpen, setSuccessModalOpen] = useState(false);
const [clearConfirmOpen, setClearConfirmOpen] = useState(false);
// 클립보드 (복사/붙여넣기용)
const [clipboard, setClipboard] = useState<DashboardElement | null>(null);
// 화면 해상도 자동 감지
const [screenResolution] = useState<Resolution>(() => detectScreenResolution());
const [resolution, setResolution] = useState<Resolution>(screenResolution);
@@ -290,6 +294,51 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
[selectedElement],
);
// 키보드 단축키 핸들러들
const handleCopyElement = useCallback(() => {
if (!selectedElement) return;
const element = elements.find((el) => el.id === selectedElement);
if (element) {
setClipboard(element);
}
}, [selectedElement, elements]);
const handlePasteElement = useCallback(() => {
if (!clipboard) return;
// 새 ID 생성
const newId = `element-${elementCounter + 1}`;
setElementCounter((prev) => prev + 1);
// 위치를 약간 오프셋 (오른쪽 아래로 20px씩)
const newElement: DashboardElement = {
...clipboard,
id: newId,
position: {
x: clipboard.position.x + 20,
y: clipboard.position.y + 20,
},
};
setElements((prev) => [...prev, newElement]);
setSelectedElement(newId);
}, [clipboard, elementCounter]);
const handleDeleteSelected = useCallback(() => {
if (selectedElement) {
removeElement(selectedElement);
}
}, [selectedElement, removeElement]);
// 키보드 단축키 활성화
useKeyboardShortcuts({
selectedElementId: selectedElement,
onDelete: handleDeleteSelected,
onCopy: handleCopyElement,
onPaste: handlePasteElement,
enabled: !saveModalOpen && !successModalOpen && !clearConfirmOpen,
});
// 전체 삭제 확인 모달 열기
const clearCanvas = useCallback(() => {
setClearConfirmOpen(true);