Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1

This commit is contained in:
2026-10-04 23:34:56 +09:00
3 changed files with 49 additions and 10 deletions
@@ -66,6 +66,7 @@ const TEXT = {
saveLabel: "저장하고 보내기",
failed: "표를 못 만듦 — {value}",
noRange: "보낼 범위를 시트에서 먼저 고름",
empty: "보낼 칸에 내용이 없음",
place: "도면에서 표를 놓을 자리를 누름(Esc = 그만)",
noPlace: "이 도면 화면은 표 놓기를 아직 못 함",
stale: "면적산출 값이 바뀐 표 {n}개",
@@ -129,6 +130,10 @@ export interface AreaToDrawing {
onArea: (at: Selection, readOnly: boolean, slot: HTMLElement) => void;
/** 도면 상세가 섰음 — 넘겨받은 표 놓기 · 연결표 다름 알림 */
onDrawing: (handle: unknown, at: Selection, doc: StructureDoc) => void;
/** 보낸 표 놓기 기다리는 중 — 편집 칸 「저장 안 한 고침」 에 듦 */
waiting: () => boolean;
/** 떠나기로 함(물음 [확인]) — 기다리던 표 버림 */
drop: () => void;
/** 도면 저장 문서 — 맨 위 `연결표` 를 도면의 표에 맞춤 */
withLinks: (doc: StructureDoc) => StructureDoc;
}
@@ -141,6 +146,8 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
let fresh = new Map<string, TableLink>();
let latest = new Map<string, Record<string, string>>();
let shown: string | null = null;
/** 보낸 표가 도면에 놓이길 기다림 — 떠날 때 「저장 안 한 고침」 물음(`waiting`) */
let waiting = false;
/** 글자 크기 작은 창 — 보내기면 크기 · 닫으면 null */
const askSize = (spec: { 범위: string }): Promise<number | null> =>
@@ -183,15 +190,20 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
if (opt.isDirty()) return; // 저장 실패 — 알림은 저장 길이 띄움
}
const picked = range;
const get = (size: number): Promise<AreaTable | null> =>
fetchTable(at.layer, at.name, { 시트: picked.시트, 범위: picked.범위, 글자크기: size }).catch(
(error: unknown) => (showToast(fill(TEXT.failed, "value", why(error)), "error"), null),
);
// 보이는 글이 하나도 없는 범위(빈 칸만)는 크기를 묻기 전에 막음
let got = await get(SIZE);
if (!got) return;
if (!Object.values(got.값 ?? {}).some((v) => String(v ?? "").trim())) {
return showToast(TEXT.empty, "error");
}
const size = await askSize(picked);
if (size === null) return;
const spec: LinkSpec = { 시트: picked.시트, 범위: picked.범위, 글자크기: size };
let got: AreaTable;
try {
got = await fetchTable(at.layer, at.name, spec);
} catch (error) {
return showToast(fill(TEXT.failed, "value", why(error)), "error");
}
if (size !== SIZE) got = await get(size);
if (!got) return;
pending = { layer: at.layer, column: at.name, got, 글자크기: size };
await opt.open({
layer: at.layer,
@@ -247,7 +259,10 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
return {
select: (s) => (range = { 시트: s.시트, 범위: s.범위 }),
waiting: () => waiting,
drop: () => (waiting = false),
onArea: (at, readOnly, slot) => {
waiting = false;
// 범위는 그대로 — 시트가 처음 고름(A1)을 이 갈고리보다 먼저 알림
if (readOnly || at.layer === "project") return;
const button = createButton({ label: TEXT.send, variant: "ghost", size: "sm" });
@@ -257,6 +272,7 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
slot.append(button);
},
onDrawing: (raw, at, doc) => {
waiting = false;
const handle = raw as TableHandle;
shown = `${at.layer}:${at.name}`;
range = null; // 면적 상세를 떠남
@@ -273,12 +289,14 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
const style = { ...(표.shapeData.style ?? {}), [LINK]: id };
fresh.set(id, { 연결: id, 시트, 범위, 글자크기: give.글자크기, 값 });
handle.placeTable({ ...표, shapeData: { ...표.shapeData, style } }, id);
waiting = true; // 놓기 전 떠나도 물음(놓으면 도면 고침 · [저장] 때 풂)
showToast(TEXT.place, "info");
}
}
void checkStale(handle, at, doc);
},
withLinks: (doc) => {
waiting = false;
const old = new Map((doc.연결표 ?? []).map((row) => [row.연결, row]));
const rows: TableLink[] = [];
for (const e of doc.도면?.entities ?? []) {
@@ -185,7 +185,7 @@ export function createMain(
let seq = 0;
let dirty = false;
/** 저장 안 한 고침 — 이 화면 + 카드(도면 31-4 · 면적산출 31-3) */
const busy = (): boolean => dirty || !!panel?.dirty();
const busy = (): boolean => dirty || !!panel?.dirty() || toDrawing.waiting(); // + 놓기 기다리는 표(41)
const guard = (event: BeforeUnloadEvent): void => {
if (!root.isConnected) return window.removeEventListener("beforeunload", guard);
@@ -204,6 +204,7 @@ export function createMain(
return false;
}
dirty = false;
toDrawing.drop();
return true;
};
setLeaveGuard(confirmLeave); // 머리 메뉴 · 뒤로 가기로 떠날 때도 물음
+22 -2
View File
@@ -94,6 +94,9 @@ globalThis.fetch = async (url, init) => {
const old = { 연결: "T1", 시트: "s1", 범위: "A1:A1", 글자크기: 3, 값: { "s1!A1": "옛" } };
const drawing = (ents, links) => ({ 양식: "구조물도면", 종류: "structure", 판: 1, 열: "item1", 도번: "SD-1", 도면: { entities: ents }, ...(links ? { 연결표: links } : {}) });
ad.onDrawing(handle, { ...area, kind: "structure" }, drawing([table([["옛"]], { 연결: "T1" })], [old]));
const waitPlaced = ad.waiting(); // 놓기 기다림 = 저장 안 한 고침
ad.drop();
const waitDropped = ad.waiting();
await new Promise((r) => setTimeout(r, 20));
const notice = notices.at(-1) ?? [];
const upd = notice.find((x) => x && x.on);
@@ -109,7 +112,14 @@ globalThis.fetch = async (url, init) => {
await slot.kids[0].on.click();
await new Promise((r) => setTimeout(r, 20));
ad.onDrawing(handle, { ...area, kind: "structure", name: "item2" }, drawing([]));
console.log(JSON.stringify({ ro, buttons: slot.kids.length, sent, placed, notice: notice.map((x) => (typeof x === "string" ? x : x.label)), saved: saved.연결표, plain: "연결표" in plain, other: placed.length - before }));
// 빈 칸만 고른 범위 — 크기를 묻기 전에 막고 알림 · 도면 안 엶
now = [["", " "]];
const openedBefore = opened.length;
ad.select({ 시트: "산출", 칸: "E1", 범위: "E1:F1" });
await slot.kids[0].on.click();
await new Promise((r) => setTimeout(r, 20));
const empty = { opened: opened.length - openedBefore, toast: toasts.at(-1) };
console.log(JSON.stringify({ ro, buttons: slot.kids.length, sent, placed, notice: notice.map((x) => (typeof x === "string" ? x : x.label)), saved: saved.연결표, plain: "연결표" in plain, other: placed.length - before, waitPlaced, waitDropped, waitSaved: ad.waiting(), empty }));
})();
"""
@@ -163,4 +173,14 @@ def test_다른_열_도면에는_안_놓음(got):
def test_편집_칸은_갈고리만():
main = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts").read_text("utf-8")
assert main.count("toDrawing.") == 4 # select · onArea · onDrawing · withLinks
# select · onArea · onDrawing · withLinks · waiting(저장 안 한 고침) · drop(떠나기 확인)
assert main.count("toDrawing.") == 6
assert "|| toDrawing.waiting()" in main.split("const busy = ")[1].splitlines()[0]
def test_놓기_기다림은_저장_안_한_고침(got):
assert got["waitPlaced"] is True and got["waitDropped"] is False and got["waitSaved"] is False
def test_빈_범위는_막고_알림(got):
assert got["empty"] == {"opened": 0, "toast": ["error", "보낼 칸에 내용이 없음"]}