diff --git a/B07_DesignDetail/openwebcad/src/helpers/draw.ts b/B07_DesignDetail/openwebcad/src/helpers/draw.ts index 771cd8f8..861ae433 100644 --- a/B07_DesignDetail/openwebcad/src/helpers/draw.ts +++ b/B07_DesignDetail/openwebcad/src/helpers/draw.ts @@ -13,6 +13,7 @@ import { getShouldDrawCursor, getSnapPoint, getSnapPointOnAngleGuide, + isViewOnly, } from '../state'; import { drawCursor, @@ -65,7 +66,8 @@ export function draw(drawController: ScreenCanvasDrawController) { ); drawSnapPoint(drawController, closestSnapPoint, isMarked); - if (getShouldDrawCursor()) { + // 미리보기는 십자 커서 · 명령 안내 · 좌표 풍선을 그리지 않는다(손 모양 커서). + if (getShouldDrawCursor() && !isViewOnly()) { drawCursor(drawController); getInputController().draw(drawController); } diff --git a/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts b/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts index 710ddf7e..ae5fcdc7 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts +++ b/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts @@ -42,6 +42,7 @@ import { getSnapPoint, getSnapPointOnAngleGuide, isHostSave, + isViewOnly, redo, setActiveToolActor, setAngleStep, @@ -111,6 +112,11 @@ export class InputController { canvas?.addEventListener('contextmenu', (evt) => { evt.preventDefault(); }); + // 가운데 단추 — 브라우저 자동 스크롤(페이지가 같이 움직임) · 붙여넣기를 막는다. + // 막는 자리는 mousedown(아래 handleMouseDown) — pointerdown 을 막으면 mousedown 이 안 와 팬이 끊긴다. + canvas?.addEventListener('auxclick', (evt: MouseEvent) => { + if (evt.button === MouseButton.Middle) evt.preventDefault(); + }); // 캔버스 밖(리본·패널) 위에서의 트랙패드 핀치도 브라우저를 확대시키지 않는다. // ctrl이 없는 휠은 그대로 둬서 패널 스크롤은 살린다. document.addEventListener( @@ -191,6 +197,10 @@ export class InputController { if (evt.button === MouseButton.Middle) { setPanStartLocation(null); } + if (evt.button === MouseButton.Left && isViewOnly()) { + setPanStartLocation(null); + return; + } if (evt.button === MouseButton.Left) { if (this.isSelectToolActive()) { // 선택 도구는 누를 때 첫 점을 이미 보냈다. 끌었으면 놓는 자리로 사각형을 닫는다. @@ -264,6 +274,9 @@ export class InputController { setPanStartLocation(newScreenMouseLocation); } + // 미리보기는 보기만 — 스냅 · 가까운 개체 강조를 하지 않는다. + if (isViewOnly()) return; + // Calculate angle guides and snap points if (getSnapEnabled()) { calculateAngleGuidesAndSnapPoints(); @@ -322,11 +335,18 @@ export class InputController { public handleMouseDown(evt: MouseEvent) { if (evt.button === MouseButton.Middle) { + evt.preventDefault(); // 자동 스크롤로 부모 페이지가 같이 굴러가던 것 setPanStartLocation(this.getCanvasPoint(evt)); return; } if (evt.button !== MouseButton.Left) return; if ((evt.target as HTMLElement | null)?.closest('.controls')) return; + if (isViewOnly()) { + // 미리보기 — 왼쪽 끌기는 선택 상자 대신 화면 옮기기. + evt.preventDefault(); + setPanStartLocation(this.getCanvasPoint(evt)); + return; + } // AutoCAD처럼 누른 자리에서 사각형이 시작되도록 선택 도구에만 첫 점을 미리 보낸다. // 다른 도구는 예전처럼 놓을 때 한 점을 받는다 (끌다가 점이 두 개 찍히지 않게). if (!this.isSelectToolActive()) return; diff --git a/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts b/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts index 6eef848b..4fd4ad21 100644 --- a/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts +++ b/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts @@ -15,6 +15,7 @@ import { getScreenCanvasDrawController, isDrawingDirty, isDrawingReadOnly, + isViewOnly, resetUndoBaseline, setActiveLayerId, setDesignMeta, @@ -24,6 +25,7 @@ import { setHostSave, setHostTitle, setLayers, + setViewOnly, } from '../state.ts'; import { setInspectorCollapsed, setRibbonCollapsed } from '../components/ui-state.ts'; import { toast } from 'react-toastify'; @@ -62,6 +64,8 @@ interface DrawingLoadMessage { ribbonCollapsed?: boolean; /** 좌측 패널(특성 · 도면층)을 접힌 채로 여는가 — 없으면(B07) 지금 상태 그대로. */ panelsCollapsed?: boolean; + /** 미리보기 — 왼쪽 끌기 = 화면 옮기기 · 두 번 누름 = 전체 맞춤 · 선택 · 명령 안내 없음. */ + viewOnly?: boolean; } interface DrawingSaveRequestMessage { @@ -112,6 +116,10 @@ function registerTextDoubleClickEdit() { const canvas = getCanvas(); if (!canvas) return; canvas.addEventListener('dblclick', (event: MouseEvent) => { + if (isViewOnly()) { + getScreenCanvasDrawController().zoomToFitScreen(); + return; + } if (isDrawingReadOnly()) { toast.info('확정한 도면입니다. 고치려면 [수정]을 먼저 누르세요.'); return; @@ -214,6 +222,7 @@ export function registerAisloDrawingBridge() { setHostSave(event.data.hostSave === true); setRibbonCollapsed(event.data.ribbonCollapsed === true); if (event.data.panelsCollapsed) setInspectorCollapsed(true); + setViewOnly(event.data.viewOnly === true); if (event.data.frameEdit) applyFramePreview(); // 앞 도면에서 켜 둔 그리기 도구를 내린다. 안 내리면 **확정한 도면 위에도** // 그 도구가 계속 그린다 — 읽기 전용은 새 명령만 막기 때문이다(2026-09-01 실측: diff --git a/B07_DesignDetail/openwebcad/src/main.tsx b/B07_DesignDetail/openwebcad/src/main.tsx index 795a65e8..9a42948d 100644 --- a/B07_DesignDetail/openwebcad/src/main.tsx +++ b/B07_DesignDetail/openwebcad/src/main.tsx @@ -26,6 +26,7 @@ import { getLastDrawTimestamp, getScreenCanvasDrawController, getSnapPoint, + isViewOnly, setActiveLayerId, setActiveToolActor, setCanvas, @@ -83,7 +84,7 @@ function startDrawLoop( /** * Highlight the entity closest to the mouse when the select tool is active */ - if (getActiveToolActor()?.getSnapshot()?.context?.type === Tool.SELECT) { + if (getActiveToolActor()?.getSnapshot()?.context?.type === Tool.SELECT && !isViewOnly()) { const screenCanvasDrawController = getScreenCanvasDrawController(); if (!screenCanvasDrawController) { throw new Error('getScreenCanvasDrawController() returned null'); diff --git a/B07_DesignDetail/openwebcad/src/state.ts b/B07_DesignDetail/openwebcad/src/state.ts index 3710d7b4..a9d9a0b7 100644 --- a/B07_DesignDetail/openwebcad/src/state.ts +++ b/B07_DesignDetail/openwebcad/src/state.ts @@ -192,6 +192,8 @@ let frameEditMode = false; let frameFields: Record = {}; /** 부모가 보기 전용으로 실었는가 — M02 에서 고칠 권한이 없는 양식을 볼 때. */ let hostReadOnly = false; +/** 미리보기(M02 집계표 카드) — 왼쪽 끌기 = 화면 옮기기 · 두 번 누름 = 전체 맞춤 · 선택 · 명령 안내 없음. */ +let viewOnly = false; let hostTitle = 'B07 상세 설계'; let hostSave = false; @@ -272,6 +274,7 @@ export const isDrawingDirty = () => drawingDirty; * 확정). 보기(확대·이동·도면층 켜기끄기)는 그대로 두고, 푸는 길은 부모의 [수정]뿐이다. */ export const isDrawingReadOnly = (): boolean => designMeta?.confirmed === true || hostReadOnly; +export const isViewOnly = (): boolean => viewOnly; // setters export const setCanvas = (newCanvas: HTMLCanvasElement) => { @@ -515,6 +518,13 @@ export const setHostReadOnly = (readOnly: boolean) => { hostReadOnly = readOnly; notifyWindow(HtmlEvent.UPDATE_STATE); }; +export const setViewOnly = (enabled: boolean) => { + viewOnly = enabled; + // 그리는 십자 커서 대신 손 모양 — 캔버스는 `cursor-none` 이라 안 두면 커서가 안 보임. + const canvas = getCanvas(); + if (canvas) canvas.style.cursor = enabled ? 'grab' : ''; + notifyWindow(HtmlEvent.UPDATE_STATE); +}; export const setFrameEditMode = (enabled: boolean, fields: Record = {}) => { frameEditMode = enabled; frameFields = enabled ? fields : {}; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts index 7bc676b3..63e1d9dd 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Drawing.ts @@ -41,6 +41,8 @@ export interface DrawingTemplateOptions { headerSlot?: HTMLElement; /** CAD 좌측 패널(특성 · 도면층)을 접힌 채로 연다 — 누르면 펼침(구조물 도면 미리보기). */ panelsCollapsed?: boolean; + /** 미리보기(집계표 카드) — 왼쪽 끌기 = 화면 옮기기 · 두 번 누름 = 전체 맞춤 · 선택 · 명령 안내 없음. */ + preview?: boolean; } const RECOVERY_PREFIX = "OPEN_WEB_CAD__RECOVERY__"; @@ -198,6 +200,7 @@ export function mountDrawingTemplate( hostSave: true, ribbonCollapsed: true, panelsCollapsed: options.panelsCollapsed, + viewOnly: options.preview === true, }, ); }; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Structure.ts b/M02_MasterTemplete/M02_MasterTemplete_Structure.ts index 76f5ce8d..c584702a 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Structure.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Structure.ts @@ -377,6 +377,7 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane name: `preview:${column ?? ""}`, layer: "system", panelsCollapsed: true, // 특성 · 도면층 패널은 접힌 채 — › 를 누르면 펼침 + preview: true, // 왼쪽 끌기 = 화면 옮기기 · 두 번 누름 = 전체 맞춤 }); const frame = previewHost.querySelector("iframe"); if (frame) hideToolbar(frame); diff --git a/ui_template/cad_host/cad_host.ts b/ui_template/cad_host/cad_host.ts index b9947190..ba4d3675 100644 --- a/ui_template/cad_host/cad_host.ts +++ b/ui_template/cad_host/cad_host.ts @@ -67,6 +67,8 @@ export interface CadLoadExtra { ribbonCollapsed?: boolean; /** 좌측 패널(특성 · 도면층)을 접힌 채로 연다 — 안 보내면(B07) 지금 상태 그대로. */ panelsCollapsed?: boolean; + /** 미리보기 — 왼쪽 끌기 = 화면 옮기기 · 두 번 누름 = 전체 맞춤 · 선택 · 명령 안내 없음. */ + viewOnly?: boolean; } export interface CadHost {