perf(522-2·522-3): B07 평면도 ⚙ 미리보기 첫 화면부터 캔버스만 · 받아 둔 도면 재사용
- cad_host bare 옵션 = iframe 주소 ?bare · openwebcad main.tsx 그리기 전 setBare(true) — 도면 오기 전에도 리본 · 패널 없음 - 평면도 관리 모달 · B06 표 위 CAD = bare - 평면도 관리 = 페이지 목록(deps.drawings) 그대로 · 장 받기 = Prefetch.openDrawing · 같은 목록 다시 열기 = 요청 없음 · 고친 뒤만 목록 새로 + syncDrawings - test_130 흉내 갱신(처음 = 목록 안 받음 · 고친 뒤 맞춤 · 다시 열기 요청 없음 · ?bare) - 첫 열기 1.23~1.34 s → 0.31 s · 다시 열기 0.14~0.16 s · 요청 3 → 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -333,6 +333,7 @@ export function mountNoteCad(
|
||||
let closed = false;
|
||||
const cad = createCadHost<CadHostDrawing>({
|
||||
title: "표 위 CAD",
|
||||
bare: true,
|
||||
onChanged: (dirty) => {
|
||||
if (!dirty || closed) return;
|
||||
cad
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
* 오른쪽 = 고른 장 미리보기(보기 전용 CAD · 캔버스만 `bare`) · 확정 배지 · [CAD 에서 열기]
|
||||
* 고치기는 지금 서버 길 그대로(종류 파일) — 고칠 때마다 모달 안을 다시 받고,
|
||||
* 닫을 때 `onClosed(changed)` 로 좌측 목록 · 도면을 다시 읽음.
|
||||
* 처음 열 때 = 페이지 목록 · 미리받은 장(`Prefetch.openDrawing`) 그대로 · 같은 목록으로 다시 열면 요청 없음(522-3).
|
||||
* ========================================================================== */
|
||||
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
import { createCadHost } from "@ui/cad_host/cad_host";
|
||||
import {
|
||||
fetchDesignDrawing,
|
||||
fetchDesignDrawingList,
|
||||
fetchPlanKinds,
|
||||
reorderPlanKinds,
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type DesignDrawingItem,
|
||||
type PlanKindsState,
|
||||
} from "./B07_DesignDetail_Api_Fetch";
|
||||
import { openDrawing, syncDrawings } from "./B07_DesignDetail_Prefetch";
|
||||
import {
|
||||
groupPlanSheets,
|
||||
kindForm,
|
||||
@@ -36,8 +37,15 @@ export interface PlanManageControls {
|
||||
onClosed: (changed: boolean, openId?: string) => void;
|
||||
/** 머리 단추 — ⚙ 로 열 때 [원본에서 다시 그리기](280-5). */
|
||||
actions?: HTMLElement[];
|
||||
/** 페이지가 받은 도면 목록 — 있으면 처음엔 목록을 다시 안 받음. */
|
||||
drawings?: DesignDrawingItem[];
|
||||
}
|
||||
|
||||
/** 마지막으로 세운 종류 · 목록 — 같은 페이지 목록(`source`)으로 다시 열면 그대로 씀. */
|
||||
let seen:
|
||||
| { source: DesignDrawingItem[] | undefined; state: PlanKindsState; list: DesignDrawingItem[] }
|
||||
| undefined;
|
||||
|
||||
function el<K extends keyof HTMLElementTagNameMap>(
|
||||
tag: K,
|
||||
className: string,
|
||||
@@ -66,7 +74,7 @@ export function openPlanManager(controls: PlanManageControls): { close: () => vo
|
||||
let pickedKind = "";
|
||||
let form: KindForm | undefined;
|
||||
let sheets: DesignDrawingItem[] = [];
|
||||
const preview = createCadHost<CadDrawing>({ title: "평면도 미리보기" });
|
||||
const preview = createCadHost<CadDrawing>({ title: "평면도 미리보기", bare: true });
|
||||
const side = el("div", "b07-plan-manage__side");
|
||||
const head = el("div", "b07-plan-manage__head");
|
||||
const view = el("div", "b07-plan-manage__view");
|
||||
@@ -77,7 +85,7 @@ export function openPlanManager(controls: PlanManageControls): { close: () => vo
|
||||
projectId,
|
||||
onChanged: () => {
|
||||
changed = true;
|
||||
void refresh();
|
||||
void refresh(true);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -171,7 +179,7 @@ export function openPlanManager(controls: PlanManageControls): { close: () => vo
|
||||
head.replaceChildren(el("strong", "", label));
|
||||
preview.beginLoading();
|
||||
try {
|
||||
const response = await fetchDesignDrawing(projectId, drawing.id);
|
||||
const response = await openDrawing(drawing);
|
||||
if (closed || picked !== drawing.id) return;
|
||||
const badge = el("span", "b07-plan-manage__badge", response.confirmed ? "확정" : "확정 전");
|
||||
badge.dataset.confirmed = String(response.confirmed);
|
||||
@@ -198,15 +206,23 @@ export function openPlanManager(controls: PlanManageControls): { close: () => vo
|
||||
}
|
||||
};
|
||||
|
||||
/** 종류 · 장 목록을 서버에서 다시 받아 왼쪽을 세우고 보던 장(없으면 첫 장)을 다시 봄. */
|
||||
const refresh = async (): Promise<void> => {
|
||||
/** 종류 · 장 목록으로 왼쪽을 세우고 보던 장(없으면 첫 장)을 다시 봄 ·
|
||||
* edited = 고친 뒤 — 서버에서 새로 받고 지문 바뀐 장은 미리받기에서 버림. */
|
||||
const refresh = async (edited = false): Promise<void> => {
|
||||
try {
|
||||
const [state, list] = await Promise.all([
|
||||
fetchPlanKinds(projectId),
|
||||
fetchDesignDrawingList(projectId),
|
||||
]);
|
||||
const source = controls.drawings;
|
||||
const fresh = edited || !source;
|
||||
const known = !fresh && seen?.source === source ? seen : undefined;
|
||||
const [state, list] = known
|
||||
? [known.state, known.list]
|
||||
: await Promise.all([
|
||||
fetchPlanKinds(projectId),
|
||||
fresh ? fetchDesignDrawingList(projectId).then((got) => got.drawings) : source,
|
||||
]);
|
||||
if (closed) return;
|
||||
sheets = list.drawings.filter((item) => item.kind === "plan");
|
||||
if (fresh) syncDrawings(projectId, list);
|
||||
seen = { source, state, list };
|
||||
sheets = list.filter((item) => item.kind === "plan");
|
||||
const all = groupPlanSheets(sheets).flatMap((group) => group.sheets);
|
||||
const keep = all.find((sheet) => sheet.drawing.id === picked) ?? all[0];
|
||||
if (keep) pickedKind = keep.drawing.plan_kind ?? "";
|
||||
|
||||
@@ -68,7 +68,7 @@ export function openDrawingSettings(kind: Kind, deps: DrawingSettingsDeps): void
|
||||
redraw.classList.add("b07-settings__redraw");
|
||||
|
||||
if (kind === "plan" && deps.plan) {
|
||||
const manager = openPlanManager({ ...deps.plan, actions: [redraw] });
|
||||
const manager = openPlanManager({ ...deps.plan, drawings: deps.drawings, actions: [redraw] });
|
||||
closeModal = manager.close;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
isViewOnly,
|
||||
setActiveLayerId,
|
||||
setActiveToolActor,
|
||||
setBare,
|
||||
setCanvas,
|
||||
setEntities,
|
||||
setHighlightedEntityIds,
|
||||
@@ -46,6 +47,8 @@ import { ActorEvent, type DrawEvent } from './tools/tool.types';
|
||||
|
||||
// 호스트 앱의 화이트/블랙 모드를 먼저 붙인 뒤 렌더한다.
|
||||
syncThemeFromHost();
|
||||
// 부모가 `?bare` 로 띄우면 첫 그림부터 캔버스만 — 도면을 넘길 때 다시 정함(522-2)
|
||||
if (new URLSearchParams(window.location.search).has('bare')) setBare(true);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLDivElement).render(
|
||||
<React.StrictMode>
|
||||
|
||||
@@ -187,8 +187,11 @@ const sheets = () =>
|
||||
);
|
||||
const copy = () => JSON.parse(JSON.stringify(state));
|
||||
const API = {
|
||||
fetchPlanKinds: async () => copy(),
|
||||
fetchDesignDrawingList: async () => ({ drawings: [{ id: "cover", kind: "cover" }, ...sheets()] }),
|
||||
fetchPlanKinds: async () => (calls.push(["kinds"]), copy()),
|
||||
fetchDesignDrawingList: async () => (
|
||||
calls.push(["list"]),
|
||||
{ drawings: [{ id: "cover", kind: "cover" }, ...sheets()] }
|
||||
),
|
||||
fetchDesignDrawing: async (_, id) => (
|
||||
calls.push(["fetchDrawing", id]),
|
||||
{ id, confirmed: confirmed.has(id), drawing: { entities: [{ id }], layers: [] } }
|
||||
@@ -232,6 +235,12 @@ const { loadTs } = makeLoader({
|
||||
if (request === "@ui/ui_template_collapsible")
|
||||
return loadTs(path.join(ROOT, "ui_template", "ui_template_collapsible.ts"));
|
||||
if (request === "./B07_DesignDetail_Api_Fetch") return API;
|
||||
// 522-3 미리받기 — 장 받기는 `openDrawing` · 고친 뒤 목록 맞춤은 `syncDrawings`
|
||||
if (request === "./B07_DesignDetail_Prefetch")
|
||||
return {
|
||||
openDrawing: (item) => API.fetchDesignDrawing("p1", item.id),
|
||||
syncDrawings: (_, list) => calls.push(["sync", list.length]),
|
||||
};
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
throw new Error(`stub 없음: ${request}`);
|
||||
},
|
||||
@@ -245,9 +254,15 @@ const { openPlanManager } = loadTs(path.join(B07, "B07_DesignDetail_UI_PlanManag
|
||||
const out = {};
|
||||
const closes = [];
|
||||
const selected = [];
|
||||
const plan = { projectId: "p1", onClosed: (changed, openId) => closes.push([changed, openId]) };
|
||||
// 페이지가 받은 목록을 넘김(`openDrawingSettings` 와 같은 길 · 522-3)
|
||||
const pageList = [{ id: "cover", kind: "cover", label: "표지", confirmed: false }, ...sheets()];
|
||||
const plan = {
|
||||
projectId: "p1",
|
||||
drawings: pageList,
|
||||
onClosed: (changed, openId) => closes.push([changed, openId]),
|
||||
};
|
||||
const panel = buildDrawingSidePanel(
|
||||
[{ id: "cover", kind: "cover", label: "표지", confirmed: false }, ...sheets()],
|
||||
pageList,
|
||||
(drawing) => selected.push(drawing.id),
|
||||
undefined,
|
||||
false,
|
||||
@@ -288,6 +303,8 @@ const { openPlanManager } = loadTs(path.join(B07, "B07_DesignDetail_UI_PlanManag
|
||||
hasMarks: all(modal.body).some((n) => n.textContent === "대피소 · 사토장 자리"),
|
||||
preview: cad.loads.at(-1).drawing.entities[0].id,
|
||||
extra: cad.loads.at(-1).extra,
|
||||
bare: cad.opts.bare,
|
||||
calls: calls.map((c) => c[0]),
|
||||
};
|
||||
|
||||
// 종류 추가 — 「새 종류」 · 이름 · 조각 하나
|
||||
@@ -297,7 +314,12 @@ const { openPlanManager } = loadTs(path.join(B07, "B07_DesignDetail_UI_PlanManag
|
||||
action().click();
|
||||
await settle();
|
||||
await settle();
|
||||
out.add = { kinds: kindRows(), sheets: sheetIds(), call: calls.find((c) => c[0] === "add") };
|
||||
out.add = {
|
||||
kinds: kindRows(),
|
||||
sheets: sheetIds(),
|
||||
call: calls.find((c) => c[0] === "add"),
|
||||
after: calls.slice(calls.findIndex((c) => c[0] === "add")).map((c) => c[0]),
|
||||
};
|
||||
|
||||
// 업데이트 — 새 종류를 골라 이름 · 축척 바꿈
|
||||
kindPick().change("k_new");
|
||||
@@ -393,10 +415,17 @@ const { openPlanManager } = loadTs(path.join(B07, "B07_DesignDetail_UI_PlanManag
|
||||
out.close = { closes: [...closes], destroyed: cad.destroyed, open: modal.open };
|
||||
|
||||
// 다시 열어 고치지 않고 [CAD 에서 열기] → changed = false · 그 장 id
|
||||
const callsBefore = calls.length;
|
||||
gear().click();
|
||||
await settle();
|
||||
await settle();
|
||||
const modal2 = modals[1];
|
||||
out.reopen = {
|
||||
calls: calls.slice(callsBefore).map((c) => c[0]),
|
||||
kinds: modal2.body
|
||||
.querySelectorAll(".b07-plan-manage__sheets .b07-plan-manage__row")
|
||||
.map((row) => row.dataset.planKind),
|
||||
};
|
||||
modal2.body
|
||||
.querySelectorAll(".b07-plan-manage__sheet")
|
||||
.find((b) => b.dataset.drawingId === "plan_k_route")
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
- 장 단추 → 드롭다운 따라감 · 드롭다운 → 그 종류 첫 장 · 줄 끌어 놓기 = 종류 순서(놓는 즉시 · 140-2).
|
||||
- 미리보기 장 바꾸기 · 캔버스만(bare) · 확정 배지만(확정 풀기 없음 140-1) · 닫으면 changed=True · [CAD 에서 열기] = 그 장 id.
|
||||
|
||||
- 522 처음 = 페이지 목록 · 미리받기 장 · 고친 뒤만 목록 새로 + 미리받기 맞춤 · 같은 목록 다시 열기 = 요청 없음 · iframe `?bare`.
|
||||
|
||||
Node 헬퍼 `helper_130_b07_plan_manage.cjs` 가 화면 그릇 흉내로 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
@@ -59,6 +61,21 @@ def test_첫_장_미리보기는_보기_전용(out):
|
||||
assert extra["readOnly"] is True and extra["viewOnly"] is True
|
||||
assert extra["bare"] is True # 140-1 캔버스만(제목줄 · 리본 · 상태막대 없음)
|
||||
assert extra["recoveryScope"] == "b07-plan-preview" # 본 CAD 자동백업 칸과 안 겹침
|
||||
assert out["open"]["bare"] is True # 522-2 iframe 을 `?bare` 로 — 도면 오기 전에도 리본 없음
|
||||
|
||||
|
||||
def test_처음_열기는_페이지_목록(out):
|
||||
# 522-3 목록은 페이지가 받은 것 · 장은 미리받기(`openDrawing`) — 목록 다시 안 받음
|
||||
assert out["open"]["calls"] == ["kinds", "fetchDrawing"]
|
||||
|
||||
|
||||
def test_고친_뒤엔_목록_새로_맞춤(out):
|
||||
assert out["add"]["after"][:4] == ["add", "kinds", "list", "sync"]
|
||||
|
||||
|
||||
def test_같은_목록으로_다시_열면_요청_없음(out):
|
||||
assert out["reopen"]["calls"] == ["fetchDrawing"] # 진짜 길은 미리받기 캐시
|
||||
assert out["reopen"]["kinds"] == ["k_route", "k_terrain"] # 고친 뒤 받은 순서 그대로
|
||||
|
||||
|
||||
def test_종류_추가(out):
|
||||
|
||||
@@ -64,6 +64,8 @@ export interface CadSaveResult<D, Q> {
|
||||
export interface CadHostOptions {
|
||||
/** iframe 제목(접근성). */
|
||||
title: string;
|
||||
/** 처음부터 캔버스만(`?bare`) — 도면이 오기 전에도 리본 · 패널을 안 그림(522-2). */
|
||||
bare?: boolean;
|
||||
/** CAD 편집 통지 — dirty 는 미저장 편집이 있는가. */
|
||||
onChanged?: (dirty: boolean) => void;
|
||||
/** CAD 리본의 DXF·DWG 내보내기 단추. */
|
||||
@@ -142,7 +144,7 @@ export function createCadHost<D extends CadHostDrawing, M = unknown, Q = unknown
|
||||
element.className = "cad-host";
|
||||
const frame = document.createElement("iframe");
|
||||
frame.className = "cad-host__frame";
|
||||
frame.src = CAD_APP_URL;
|
||||
frame.src = options.bare ? `${CAD_APP_URL}?bare` : CAD_APP_URL;
|
||||
frame.title = options.title;
|
||||
// CAD 안(리본 · 패널)도 브라우저 기본 메뉴를 막음 — 부모 문서의 듣기는 iframe 에 안 닿음(계획서 39-9a)
|
||||
frame.addEventListener("load", () => {
|
||||
|
||||
Reference in New Issue
Block a user