fix(B07 CAD·M02): 16장 점검 — 테마 어긋남 · 첫 맞춤 · 불러온 양식 떠나기 · 작도 영역 안내

- CAD 테마: 저장값이 없으면 부모 문서 테마(호스트 기본 밝음)를 따름 — 시스템 설정을 따라 CAD 만 어둡던 것
- 맞춤 뒤 화면을 안 옮겼으면 캔버스 크기가 바뀔 때 다시 맞춤 — 첫 표시 때 iframe 이 늦게 커져 어긋나던 것 · 빈 도면은 원점을 가운데로
- 도면 양식 📂 불러온 뒤 저장 안 하고 떠나면 물음
- 작도 영역 칸: 빈 칸 · 숫자 아님은 그 칸만 짚음
- 화면 검증용 창구에 기준점 · 보기 전용 상태 추가

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qWfH6MMhLwjhtLYnDVUXv
This commit is contained in:
2026-09-28 22:18:59 +09:00
co-authored by Claude Opus 5.5
parent 527e68db85
commit 26772b87bd
5 changed files with 70 additions and 6 deletions
@@ -41,6 +41,8 @@ export class ScreenCanvasDrawController implements DrawController {
private screenScale = 1;
private screenMouseLocation: Point;
private canvasSize: Point = new Point(100, 100);
/** 맞춤 뒤 사용자가 화면을 안 옮겼음 — 캔버스 크기가 바뀌면 다시 맞춘다(첫 표시 때 iframe 이 늦게 커짐). */
private viewFitted = false;
// Style-run batching (static scene rendering): consecutive stroke calls
// with the same style are collected into one Path2D and stroked once.
@@ -61,7 +63,9 @@ export class ScreenCanvasDrawController implements DrawController {
}
public setCanvasSize(newCanvasSize: Point) {
const changed = newCanvasSize.x !== this.canvasSize.x || newCanvasSize.y !== this.canvasSize.y;
this.canvasSize = newCanvasSize;
if (changed && this.viewFitted) this.zoomToFitScreen();
}
public getScreenScale() {
@@ -70,6 +74,7 @@ export class ScreenCanvasDrawController implements DrawController {
public setScreenScale(newScreenScale: number) {
this.screenScale = newScreenScale;
this.viewFitted = false;
triggerReactUpdate(StateVariable.screenZoom);
}
@@ -79,6 +84,7 @@ export class ScreenCanvasDrawController implements DrawController {
public setScreenOffset(newScreenOffset: Point) {
this.screenOffset = newScreenOffset;
this.viewFitted = false;
triggerReactUpdate(StateVariable.screenOffset);
}
@@ -96,6 +102,7 @@ export class ScreenCanvasDrawController implements DrawController {
}
public panScreen(screenOffsetX: number, screenOffsetY: number) {
this.viewFitted = false;
this.screenOffset = new Point(
this.screenOffset.x - screenOffsetX / this.screenScale,
this.screenOffset.y - screenOffsetY / this.screenScale
@@ -145,7 +152,14 @@ export class ScreenCanvasDrawController implements DrawController {
// 확정). 도각까지 넣으면 A1 한 장 전체가 잡혀 화면의 40%가 여백으로 간다.
const pickable = getPickableEntities();
const entities = pickable.length ? pickable : getEntities();
if (!entities.length) return;
if (!entities.length) {
// 빈 도면 — 원점(0,0)을 화면 가운데로. 안 옮기면 기준점 십자가 왼쪽 아래 귀퉁이에 잘린다.
const canvasSize = this.getCanvasSize();
const zoom = this.getScreenScale();
this.setScreenOffset(new Point(-canvasSize.x / 2 / zoom, -canvasSize.y / 2 / zoom));
this.viewFitted = true;
return;
}
const boundingBox = getBoundingBoxOfMultipleEntities(entities);
const boundingWidth = boundingBox.maxX - boundingBox.minX;
const boundingHeight = boundingBox.maxY - boundingBox.minY;
@@ -171,6 +185,7 @@ export class ScreenCanvasDrawController implements DrawController {
worldCenterY - canvasSize.y / 2 / zoomLevel
)
);
this.viewFitted = true;
}
/**
@@ -12,6 +12,8 @@ import {
getSnapPoint,
isDrawingDirty,
isDrawingReadOnly,
isOriginMarker,
isViewOnly,
} from '../state';
import { Point } from '@flatten-js/core';
import { isEntityHidden } from './visibility';
@@ -47,6 +49,9 @@ export function registerCadDebugHook(): void {
readOnly: () => isDrawingReadOnly(),
dirty: () => isDrawingDirty(),
meta: () => getDesignMeta(),
// 기준점 십자 · 미리보기(보기 전용) 켜짐 — 그림이 안 도는 탭에서도 수치로 본다.
originMarker: () => isOriginMarker(),
viewOnly: () => isViewOnly(),
// 세계 좌표 → 화면 좌표. 그림·글자가 "제 자리에 그려졌나"를 픽셀로 판정할 때 쓴다.
screen: (x: number, y: number) => {
const point = getScreenCanvasDrawController().worldToTarget(new Point(x, y));
+16 -4
View File
@@ -3,8 +3,9 @@
*
* iframe은 호스트와 **같은 오리진**이라 `localStorage["frd_theme"]`를 공유한다.
* 부모가 토글하며 값을 쓰면 이 문서에는 `storage` 이벤트가 오므로, 부모 쪽에
* 별도 전달 코드(postMessage)를 넣지 않는다. 값이 없으면 미지정 상태로 두어
* `ui_template_theme.css`의 `prefers-color-scheme` 폴백이 그대로 먹는다.
* 별도 전달 코드(postMessage)를 넣지 않는다. 값이 없으면 부모 문서의 `data-theme`
* (호스트 index.html 기본값 light)를 따른다 — 시스템 설정을 따로 따르면 호스트와 어긋난다.
* 부모가 없을 때(단독 실행)만 `prefers-color-scheme` 폴백이 먹는다.
*/
import { bumpSceneVersion } from './helpers/scene-version.ts';
@@ -27,11 +28,22 @@ function applyTheme(value: string | null): void {
bumpSceneVersion();
}
/** 부모(호스트) 문서의 테마 — 부모가 없거나 못 읽으면 null. */
function parentTheme(): string | null {
try {
return window.parent === window
? null
: window.parent.document.documentElement.getAttribute('data-theme');
} catch {
return null;
}
}
/** 부팅 시 1회 호출 — 현재 테마 반영 + 이후 변경 구독. */
export function syncThemeFromHost(): void {
applyTheme(localStorage.getItem(THEME_STORAGE_KEY));
applyTheme(localStorage.getItem(THEME_STORAGE_KEY) ?? parentTheme());
window.addEventListener('storage', (event) => {
if (event.key === THEME_STORAGE_KEY) applyTheme(event.newValue);
if (event.key === THEME_STORAGE_KEY) applyTheme(event.newValue ?? parentTheme());
});
// 시스템 설정 폴백으로 도는 중이면 OS 전환도 따라간다.
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {