refactor(B07): 자리표 팔레트를 CAD 내부 패널로 이동
- 부모 사이드바 팔레트(B07_DesignDetail_UI_FramePlaceholders.ts) 제거, CAD 앱에 FramePlaceholderPanel 신설 - 도면 로드 메시지에 frameEdit 플래그 추가 — 도각 편집으로 실은 도면에서만 패널 표시 - 자리표를 화면 중앙에 직접 생성 (부모 왕복 없이 CAD 상태에 추가, 실행취소 스택 반영) - 그림 자리표는 값이 없어도 테두리와 이름을 그려 배치 위치가 보이게 함 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { Point } from '@flatten-js/core';
|
||||
import { type FC, useCallback, useEffect, useState } from 'react';
|
||||
import { HtmlEvent } from '../App.types';
|
||||
import { ImageEntity } from '../entities/ImageEntity';
|
||||
import { TextEntity } from '../entities/TextEntity';
|
||||
import {
|
||||
getActiveLayerId,
|
||||
getEntities,
|
||||
getScreenCanvasDrawController,
|
||||
isFrameEditMode,
|
||||
setEntities,
|
||||
} from '../state';
|
||||
|
||||
/**
|
||||
* 도각 자리표 패널 — 프로그램 값이 들어갈 자리를 사용자가 직접 놓는다 (2026-09-06 사용자 확정).
|
||||
*
|
||||
* 값을 알아맞히는 규칙은 만들지 않는다. 여기서 놓은 `{{키}}` 토큰을 도면 출력 때 서버의
|
||||
* 치환 엔진이 채운다. 자리표를 안 놓은 값은 빈칸으로 남는다.
|
||||
*
|
||||
* 도각 편집으로 도면을 실었을 때만 뜬다. 놓은 자리표는 화면 한가운데에 서고, 그 뒤
|
||||
* 캐드의 이동·크기 도구로 자리를 잡는다.
|
||||
*/
|
||||
|
||||
/** 글자 자리표 — 출력 때 표제란 값으로 바뀐다. */
|
||||
const TEXT_TOKENS = [
|
||||
'도면명',
|
||||
'도면번호',
|
||||
'공사명',
|
||||
'위치',
|
||||
'시행청',
|
||||
'용역회사',
|
||||
'연도기번',
|
||||
'사업량',
|
||||
'과업책임자',
|
||||
'분야별책임자',
|
||||
'설계자',
|
||||
'설계일자',
|
||||
'축척_A1',
|
||||
'축척_A3',
|
||||
] as const;
|
||||
|
||||
/** 그림 자리표 — 회사 로고와 사람 서명. 값이 없으면 도면에서 그림째 빠진다. */
|
||||
const IMAGE_TOKENS = ['회사로고', '과업책임자서명', '분야별책임자서명', '설계자서명'] as const;
|
||||
|
||||
const TEXT_SIZE_MM = 5;
|
||||
const IMAGE_WIDTH_MM = 32;
|
||||
const IMAGE_HEIGHT_MM = 16;
|
||||
|
||||
/** 지금 보고 있는 화면의 한가운데 (도면 좌표). 자리표가 처음 서는 자리다. */
|
||||
function viewCenter(): Point {
|
||||
const drawController = getScreenCanvasDrawController();
|
||||
const size = drawController.getCanvasSize();
|
||||
return drawController.targetToWorld(new Point(size.x / 2, size.y / 2));
|
||||
}
|
||||
|
||||
function addTextPlaceholder(token: string): void {
|
||||
const center = viewCenter();
|
||||
const entity = new TextEntity(getActiveLayerId(), `{{${token}}}`, center, {
|
||||
fontSize: TEXT_SIZE_MM,
|
||||
textAlign: 'center',
|
||||
});
|
||||
setEntities([...getEntities(), entity], true);
|
||||
}
|
||||
|
||||
async function addImagePlaceholder(token: string): Promise<void> {
|
||||
const center = viewCenter();
|
||||
const halfWidth = IMAGE_WIDTH_MM / 2;
|
||||
const halfHeight = IMAGE_HEIGHT_MM / 2;
|
||||
const points = [
|
||||
{ x: center.x - halfWidth, y: center.y - halfHeight },
|
||||
{ x: center.x + halfWidth, y: center.y - halfHeight },
|
||||
{ x: center.x + halfWidth, y: center.y + halfHeight },
|
||||
{ x: center.x - halfWidth, y: center.y + halfHeight },
|
||||
];
|
||||
// 자리표는 그림 주소가 아니라 토큰이라 fromJson 으로 만든다 — 그래야 원본 문자열이
|
||||
// 그대로 보존돼 저장 한 번에 주소로 굳지 않는다.
|
||||
const entity = await ImageEntity.fromJson({
|
||||
id: crypto.randomUUID(),
|
||||
type: 'Image',
|
||||
lineColor: '#f5f7fa',
|
||||
lineWidth: 1,
|
||||
layerId: getActiveLayerId(),
|
||||
shapeData: { points, imageData: `{{${token}}}` },
|
||||
} as Parameters<typeof ImageEntity.fromJson>[0]);
|
||||
setEntities([...getEntities(), entity], true);
|
||||
}
|
||||
|
||||
export const FramePlaceholderPanel: FC = () => {
|
||||
const [visible, setVisible] = useState(isFrameEditMode());
|
||||
|
||||
const refresh = useCallback(() => setVisible(isFrameEditMode()), []);
|
||||
useEffect(() => {
|
||||
window.addEventListener(HtmlEvent.UPDATE_STATE, refresh);
|
||||
return () => window.removeEventListener(HtmlEvent.UPDATE_STATE, refresh);
|
||||
}, [refresh]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<section className="cad-frame-tokens controls">
|
||||
<header className="cad-frame-tokens__header">
|
||||
자리표 놓기 — 누르면 화면 가운데에 서고, 끌어서 자리를 잡습니다
|
||||
</header>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">글자</span>
|
||||
{TEXT_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="cad-frame-tokens__button"
|
||||
onClick={() => addTextPlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">그림</span>
|
||||
{IMAGE_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="cad-frame-tokens__button"
|
||||
onClick={() => void addImagePlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user