From 406efc7192a91692db129f7a2b88266939e8ad41 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 01:38:21 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(structure):=20B05=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=C2=B7=20=ED=8F=BC=EC=9D=B4=20=EA=BA=BC=EC=A7=84=20=EC=A2=85?= =?UTF-8?q?=EB=A5=98(B=EA=B5=B0=20=EC=B8=A1=EA=B5=AC=20=EB=93=B1)=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=84=20=EC=A0=95=EC=9D=98=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9C=BC=EB=A1=9C=20=ED=92=82(52-20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B05 패널에 종류 전부를 넘기고 고르기 목록에서만 꺼진 종류 · 고를 종류 없는 군을 가림 · B06 그대로 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s --- B05_Profile/B05_Profile_UI_Page_Structures.ts | 9 +++--- .../B05_Profile_UI_Structures_Panel.ts | 18 +++++------ .../B05_Profile_UI_Structures_Panel_Types.ts | 30 ++++++++++++++----- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Page_Structures.ts b/B05_Profile/B05_Profile_UI_Page_Structures.ts index 49cc98731..ef571f0bc 100644 --- a/B05_Profile/B05_Profile_UI_Page_Structures.ts +++ b/B05_Profile/B05_Profile_UI_Page_Structures.ts @@ -313,15 +313,14 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) { async function loadStructures(): Promise { try { // 레지스트리는 **한 번만** 받고 쓰는 자리에 따라 나눈다(2026-09-07). - // · 추가 메뉴 — 켜진 종류만(B05 는 종전대로 여섯 군). - // · 알약 레인·군 판정 — **전부**. B06 에서 넣은 B군(측구·맹암거 등)은 레지스트리에서 - // 꺼져 있는데, 켜진 것만 주면 이름·색·군을 못 찾아 종단에 「?」 회색 알약으로 뜬다. + // · 고르기 목록 — 켜진 종류만(B05 는 종전대로 여섯 군). + // · 목록 이름 · 폼 · 알약 레인 · 군 판정 — **전부**. B06 에서 넣은 B군(측구·맹암거 등)은 + // 레지스트리에서 꺼져 있는데, 켜진 것만 주면 이름을 못 찾아 영문 키 · 「?」 알약으로 뜬다. const [allTypes, stored] = await Promise.all([ fetchStructureTypes(true), fetchStructures(deps.projectId), ]); - const menuTypes = allTypes.filter((type) => type.enabled); - deps.panel().structures.setTypes(menuTypes); + deps.panel().structures.setTypes(allTypes, (type) => type.enabled); deps.profilePanel().setStructureTypes(allTypes); // A군 판정에 쓸 타입 정보 — 횡단배수만 그래프 세로선·틸팅 대상이다. structureTypeMap = new Map( diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel.ts b/B05_Profile/B05_Profile_UI_Structures_Panel.ts index 2a4adbb4f..5eec636e4 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel.ts @@ -53,9 +53,9 @@ export { GROUP_LABELS } from "./B05_Profile_UI_Structures_Panel_Types"; export type { PipeFacilityItem, StructuresSection } from "./B05_Profile_UI_Structures_Panel_Types"; import { DEFAULT_INTERVAL_LENGTH_M, - GROUP_LABELS, - groupOption, + groupOptions, hasComputedRange, + pickOption, readBeforeOption, readLengthOption, type PipeFacilityItem, @@ -165,6 +165,7 @@ export function createStructuresSection( } let types: StructureType[] = []; + let canPick: (type: StructureType) => boolean = () => true; // 배열 자체를 바꾸지 않는다(조각들이 참조로 들고 있음) — 내용만 갈아끼운다. const structures: StructureInstance[] = []; let pipeFacilities: PipeFacilityItem[] = []; @@ -348,7 +349,7 @@ export function createStructuresSection( const candidates = group ? types.filter((t) => t.group === group && pickable(t)) : []; typeSelect.replaceChildren( new Option("— 선택 —", ""), - ...candidates.map((type) => new Option(type.name, type.type_id)), + ...candidates.map((type) => pickOption(type.name, type.type_id, !canPick(type))), ); typeSelect.value = keepTypeId && candidates.some((type) => type.type_id === keepTypeId) ? keepTypeId : ""; @@ -539,14 +540,11 @@ export function createStructuresSection( facility: facilityOptions, listRoot: list, hasSelection: () => editingId !== null || selectedPipeChainage !== null, - setTypes(next) { + setTypes(next, pick = () => true) { types = next; - const groups = new Map(next.map((t) => [t.group, t.group_name] as const)); - // 첫 항목은 빈칸 — 진입·리셋 상태에서 군·종류를 강제로 고르게 두지 않는다. 이름은 정의(48) 먼저. - groupSelect.replaceChildren( - new Option("— 선택 —", ""), - ...[...groups].map(([g, name]) => groupOption(g, name ?? GROUP_LABELS[g] ?? g)), - ); + canPick = pick; + // 첫 항목은 빈칸 — 진입·리셋 상태에서 군·종류를 강제로 고르게 두지 않는다. + groupSelect.replaceChildren(new Option("— 선택 —", ""), ...groupOptions(next, pick)); groupSelect.value = ""; syncTypeOptions(); }, diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel_Types.ts b/B05_Profile/B05_Profile_UI_Structures_Panel_Types.ts index 04fa037cd..86e12199c 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel_Types.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel_Types.ts @@ -38,14 +38,29 @@ export const GROUP_LABELS: Record = { * 옛 값 · 도면 · 집계는 그대로이고 데이터도 지우지 않는다. */ export const HIDDEN_PICK_GROUPS: ReadonlySet = new Set(["B", "D"]); -/** 군 드롭다운 항목 — 뺄 군은 가림(옛 항목을 열 때는 값으로 그대로 고를 수 있음). */ -export function groupOption(group: string, name: string): HTMLOptionElement { - const option = new Option(name, group); - option.hidden = HIDDEN_PICK_GROUPS.has(group); - if (option.hidden) option.dataset.pickHidden = ""; // 정의 폼의 「가림 풀기」가 건드리지 않게 +/** 고르기 목록 항목 — 가릴 항목은 가림(옛 항목을 열 때는 값으로 그대로 고를 수 있음). */ +export function pickOption(text: string, value: string, hidden: boolean): HTMLOptionElement { + const option = new Option(text, value); + option.hidden = hidden; + if (hidden) option.dataset.pickHidden = ""; // 정의 폼의 「가림 풀기」가 건드리지 않게 return option; } +/** 군 드롭다운 항목들 — 이름은 정의(48) 먼저 · 뺄 군과 고를 종류가 없는 군은 가림. */ +export function groupOptions( + types: StructureType[], + pick: (type: StructureType) => boolean, +): HTMLOptionElement[] { + const groups = new Map(types.map((t) => [t.group, t.group_name] as const)); + return [...groups].map(([g, name]) => + pickOption( + name ?? GROUP_LABELS[g] ?? g, + g, + HIDDEN_PICK_GROUPS.has(g) || !types.some((t) => t.group === g && pick(t)), + ), + ); +} + /** 구간형 신규 추가 시 기본 구간 길이(m). 사용자가 종점을 바로 고칠 수 있게 짧게 잡는다. */ export const DEFAULT_INTERVAL_LENGTH_M = 15; @@ -95,8 +110,9 @@ export interface StructuresSection { /** 배치된 구조물 목록(
    ) — 섹션 본문이 아니라 사이드 하단 고정 영역에 붙인다 * (2026-08-18 사용자 지시: 폼 길이에 밀려 목록이 화면 밖으로 나가던 문제). */ listRoot: HTMLElement; - /** 서버에서 받은 타입 목록을 채운다(최초 1회). */ - setTypes: (types: StructureType[]) => void; + /** 서버에서 받은 타입 목록을 채운다(최초 1회). 이름·폼은 넘긴 종류 전부로 풀고, + * 고르기 목록엔 `pick` 이 고른 종류만 보임(빼면 전부). */ + setTypes: (types: StructureType[], pick?: (type: StructureType) => boolean) => void; /** 서버 정본으로 목록을 교체한다(진입·복원·저장 후). */ setStructures: (structures: StructureInstance[]) => void; getStructures: () => StructureInstance[]; From 1a9055444f56e0fd27dfb62cb4d24c61d7515056 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 6 Oct 2026 01:43:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(box):=20=EB=B3=B8=EC=B2=B4=20=E3=80=8C?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9E=85=EB=A0=A5=E3=80=8D=20?= =?UTF-8?q?=EB=B9=88=20=EC=B9=B8=20=EC=A0=80=EC=9E=A5=20=EB=92=A4=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=97=B4=EC=96=B4=EB=8F=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=C2=B7=20B06=20?= =?UTF-8?q?=EB=8B=A4=EB=A6=AC=EA=B0=80=20=EC=9C=A0=EC=9E=85=C2=B7=EC=9C=A0?= =?UTF-8?q?=EC=B6=9C=20=EA=B5=AC=EC=A1=B0=20=EA=B8=80=20=EC=9C=A0=EC=A7=80?= =?UTF-8?q?(52-19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 규격 판정(bodyOf): 가로 · 세로 중 손 값 하나라도 있으면 사용자 입력 + 빈 칸은 빈 채 - 둘 다 비면 표시 키 body_custom 만 저장 · 폼 칸 목록에 넣어 숫자 채우면 지워짐 - B06 폼→조정창 다리: 흙막이 · 날개벽 글을 있음/없음으로 바꿔 적던 것 → 글 그대로 - 시험: 52-2 헬퍼 한 칸 · 두 칸 빈 경우 · ui_checks 52-19 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4 --- A00_Common/design/design_structure_box.ts | 23 +++-- .../B05_Profile_UI_Drainage_Facility_Merge.ts | 2 +- .../B06_Section_UI_Page_Ford_Controls.ts | 10 ++- .../tester/helper_52_structure_spec_box.cjs | 8 ++ .../tester/test_52_structure_spec_box.py | 18 ++++ resources/tester/ui_checks/52-19_박스_빈칸.js | 85 +++++++++++++++++++ 6 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 resources/tester/ui_checks/52-19_박스_빈칸.js diff --git a/A00_Common/design/design_structure_box.ts b/A00_Common/design/design_structure_box.ts index 639a01159..c76585021 100644 --- a/A00_Common/design/design_structure_box.ts +++ b/A00_Common/design/design_structure_box.ts @@ -5,6 +5,7 @@ * 컨테이너 셋(모두 필수): 본체 · 유입 구조 · 유출 구조. 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈. * 저장 키 = pipe_points.json 옛 키 그대로(B06 횡단 · B07 · B08 이 읽는 자리): * · 본체 = `body_width_m` · `body_height_m` 수 — 규격 칸은 저장 안 함(폭 · 높이로 되찾음 · `bodyOf`) + * · 사용자 입력에 두 칸 다 비면 `body_custom` = 「사용자 입력」 표시만 * · 유입 · 유출 구조 = `wing_in` · `wing_out`(없음 · 날개벽 · 흙막이 · 옛 「있음」 = 날개벽) * — 엔진은 날개벽만 세움(`_wing_spec` ↔ `wingSpec` 짝) * 표 02 에 없는 옛 날개벽 치수 칸은 화면에서 빼고 값은 남김(지난 정의 값). @@ -14,6 +15,8 @@ import type { Instance, SpecItem, StructureSpec } from "./design_structure_spec"; export const BOX_CUSTOM = "사용자 입력"; +/** 사용자 입력에 가로 · 세로 둘 다 빈 채 저장했다는 표시(폭 · 높이 수가 없을 때만 적음). */ +export const BOX_CUSTOM_KEY = "body_custom"; /** 본체 규격 → [가로, 세로](m) — 실무 관측 규격(2026-08-17 사용자 확정). */ const BOX_PRESETS: Record = { "2.0×2.0": [2, 2], @@ -97,21 +100,25 @@ export function boxOptionsIn( return out; } -/** 저장 폭 · 높이 → 본체 칸 값 — 규격과 맞으면 그 규격 · 아니면 사용자 입력 · 없으면 빈 값. */ +/** 저장 폭 · 높이 → 본체 칸 값 — 규격과 맞으면 그 규격 · 손 값이 하나라도 있거나 사용자 입력 표시면 + * 사용자 입력(빈 칸은 빈 채) · 아니면 빈 값. */ export function bodyOf(options: Record): Instance { const width = Number(options.body_width_m); const height = Number(options.body_height_m); - if (!(width > 0 && height > 0)) return { size: "" }; const preset = Object.entries(BOX_PRESETS).find( ([, [w, h]]) => Math.abs(w - width) < 0.05 && Math.abs(h - height) < 0.05, ); - return preset - ? { size: preset[0] } - : { size: BOX_CUSTOM, width: String(width), height: String(height) }; + if (preset) return { size: preset[0] }; + if (!(width > 0) && !(height > 0) && options[BOX_CUSTOM_KEY] !== BOX_CUSTOM) return { size: "" }; + const side = (n: number): string => (n > 0 ? String(n) : ""); + return { size: BOX_CUSTOM, width: side(width), height: side(height) }; } -/** 본체 칸 값 → 저장 폭 · 높이 — 규격이면 규격 수(사용자 입력은 명세 키가 적음). */ -export function bodyOptions(body: Instance | undefined): Record { +/** 본체 칸 값 → 저장 폭 · 높이 — 규격이면 규격 수(사용자 입력은 명세 키가 적음) · 사용자 입력에 두 칸 다 + * 비면 표시 키만(다시 열 때 「사용자 입력」). */ +export function bodyOptions(body: Instance | undefined): Record { const preset = BOX_PRESETS[body?.size ?? ""]; - return preset ? { body_width_m: preset[0], body_height_m: preset[1] } : {}; + if (preset) return { body_width_m: preset[0], body_height_m: preset[1] }; + const blank = !(body?.width ?? "").trim() && !(body?.height ?? "").trim(); + return body?.size === BOX_CUSTOM && blank ? { [BOX_CUSTOM_KEY]: BOX_CUSTOM } : {}; } diff --git a/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts b/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts index 50c414500..b2bdccdd4 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts @@ -35,7 +35,7 @@ export const FORM_OPTION_KEYS: Readonly> = { ...revetKeys("outlet"), ], // BOX = 표 02 명세 키(PLAN 52-2) — 옛 날개벽 치수는 폼에서 빠져 이어 붙임(지난 정의 값) - box_culvert: ["body_width_m", "body_height_m", "wing_in", "wing_out"], + box_culvert: ["body_width_m", "body_height_m", "body_custom", "wing_in", "wing_out"], // 물넘이 포장 = 표 03 명세 키(PLAN 52-3 · `design_structure_ford_pavement`) — 옛 월류 · 경사 · 두께 · 길이는 // 폼에서 빠져 이어 붙임 · 도수로/돌붙임 키는 `FORM_OPTION_PREFIXES` ford_pavement: ["ford_slope_deg"], diff --git a/B06_Section/B06_Section_UI_Page_Ford_Controls.ts b/B06_Section/B06_Section_UI_Page_Ford_Controls.ts index bec9877ef..a94fd89b5 100644 --- a/B06_Section/B06_Section_UI_Page_Ford_Controls.ts +++ b/B06_Section/B06_Section_UI_Page_Ford_Controls.ts @@ -25,6 +25,8 @@ import type { BoxControl } from "./B06_Section_UI_Cross_Box_Panel"; /** 날개벽 한 벌의 저장 키 — 세월교·BOX암거가 같은 옵션 이름을 쓴다(`wing_in*`/`wing_out*`). */ type WingPatch = Partial<{ installed: boolean; + /** 폼이 낸 설치 글(날개벽 · 흙막이 …) — 있으면 저장에 그 글 그대로(있음/없음으로 안 바꿈). */ + text: string; height_m: number; length_m: number; angle_deg: number; @@ -34,7 +36,8 @@ type WingPatch = Partial<{ function wingOptions(role: "inlet" | "outlet", patch: WingPatch): Record { const prefix = role === "inlet" ? "wing_in" : "wing_out"; const options: Record = {}; - if (patch.installed !== undefined) options[prefix] = patch.installed ? "있음" : "없음"; + if (patch.installed !== undefined) + options[prefix] = patch.text ?? (patch.installed ? "있음" : "없음"); if (patch.height_m !== undefined) options[`${prefix}_height_m`] = patch.height_m; if (patch.length_m !== undefined) options[`${prefix}_length_m`] = patch.length_m; if (patch.angle_deg !== undefined) options[`${prefix}_angle_deg`] = patch.angle_deg; @@ -82,7 +85,10 @@ function wingPatchFrom(patch: Record, prefix: string): }; const wing: WingPatch = {}; // 설치 값 글(있음 · 없음 · BOX 날개벽 · 흙막이) — 백엔드 `_wing_spec` 과 같은 판정. - if (patch[prefix] !== undefined) wing.installed = wingInstalled(patch[prefix]); + if (patch[prefix] !== undefined) { + wing.installed = wingInstalled(patch[prefix]); + wing.text = String(patch[prefix]); + } const heightM = num(`${prefix}_height_m`); if (heightM !== undefined) wing.height_m = heightM; const lengthM = num(`${prefix}_length_m`); diff --git a/resources/tester/helper_52_structure_spec_box.cjs b/resources/tester/helper_52_structure_spec_box.cjs index e86007804..ac77f9982 100644 --- a/resources/tester/helper_52_structure_spec_box.cjs +++ b/resources/tester/helper_52_structure_spec_box.cjs @@ -55,7 +55,15 @@ const out = { st.body[0].height = "1.8"; }), blank: roundTrip({}), + // 52-19 — 사용자 입력에서 한 칸 · 두 칸 비우고 저장 → 다시 열기 + oneBlank: roundTrip({ body_height_m: 2.5 }), + bothBlank: roundTrip({ body_width_m: 2, body_height_m: 2 }, (st) => { + st.body[0].size = "사용자 입력"; + st.body[0].width = ""; + st.body[0].height = ""; + }), keys: s.specKeys(SPEC), retired: SPEC.retired, }; +out.bothBlankAgain = roundTrip(out.bothBlank.out); process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_52_structure_spec_box.py b/resources/tester/test_52_structure_spec_box.py index 95a9307a9..2091a01a1 100644 --- a/resources/tester/test_52_structure_spec_box.py +++ b/resources/tester/test_52_structure_spec_box.py @@ -76,6 +76,24 @@ def test_빈_값은_빈_채로(out): assert out["blank"]["state"]["body"][0]["size"] == "" +def test_사용자_입력_빈_칸은_다시_열어도_사용자_입력(out): + # 52-19 — 가로만 비움: 세로 손 값이 남아 사용자 입력 + 빈 가로 + assert out["oneBlank"]["state"]["body"][0] == { + "size": "사용자 입력", + "width": "", + "height": "2.5", + } + assert out["oneBlank"]["out"] == {"body_height_m": 2.5} + # 둘 다 비움: 표시 키만 적고 다시 열면 사용자 입력 + 빈 칸 둘 + assert out["bothBlank"]["out"] == {"body_custom": "사용자 입력"} + assert out["bothBlankAgain"]["state"]["body"][0] == { + "size": "사용자 입력", + "width": "", + "height": "", + } + assert out["bothBlankAgain"]["out"] == {"body_custom": "사용자 입력"} + + def test_정의_파일에_이름_표_그룹_선택지가_올라_있음(): names = _node("names") with open( diff --git a/resources/tester/ui_checks/52-19_박스_빈칸.js b/resources/tester/ui_checks/52-19_박스_빈칸.js new file mode 100644 index 000000000..4048f6acb --- /dev/null +++ b/resources/tester/ui_checks/52-19_박스_빈칸.js @@ -0,0 +1,85 @@ +/* 52-19 — BOX암거 본체 「사용자 입력」 빈 칸 저장 → 다시 열기 + * 시작 화면: B05 종단설계(#/b05-profile) · 검증 프로젝트 · 목록에 「BOX암거」(52-2 edit 뒤 2.4 × 1.8 · 흙막이) + * 부르는 법: `window.__m5219 = "단계";` 를 붙여 — 단계 사이 orca reload + * one → 가로 비우고 [저장] (reload) view → 사용자 입력 · 가로 빈 칸 · 세로 1.8 + * both → 세로도 비우고 [저장] (reload) view → 사용자 입력 · 빈 칸 둘 · 파일 body_custom + * fill → 2.2 × 1.6 [저장] → 파일에 body_custom 없음 + * 기대: 유입 · 유출 구조 글(흙막이 · 없음) 그대로 · 되돌림은 52-2 restore + */ +(async () => { + const MODE = window.__m5219; + const w = (ms) => new Promise((r) => setTimeout(r, ms)); + const until = async (fn, ms = 20000) => { + for (let t = 0; t < ms; t += 200) { + if (fn()) return true; + await w(200); + } + return false; + }; + const pid = localStorage.getItem("frd_current_project_id"); + const api = await import( + performance + .getEntriesByType("resource") + .map((e) => e.name) + .filter((n) => n.includes("/B04_PreProcess/B04_PreProcess_Api_Fetch.ts")) + .pop() + ); + const spec = () => + [...document.querySelectorAll(".b05-structure__spec")].find((n) => n.offsetParent !== null); + const box = (c) => spec()?.querySelector(`[data-spec-container="${c}"]`); + const ctl = (c, i) => + box(c)?.querySelector(`[data-spec-item="${c}.${i}"]`)?.querySelector("select, input"); + const set = async (c, i, v) => { + const el = ctl(c, i); + el.value = v; + el.dispatchEvent(new Event("change", { bubbles: true })); + await w(200); + }; + await until(() => + [...document.querySelectorAll(".b05-route__irregular-list li")].some((l) => + /BOX암거$/.test(l.textContent.trim()), + ), + ); + const li = [...document.querySelectorAll(".b05-route__irregular-list li")].find((l) => + /BOX암거$/.test(l.textContent.trim()), + ); + (li.querySelector("button") ?? li).click(); + await w(800); + if (!box("body")) (li.querySelector("button") ?? li).click(); + await until(() => !!box("body")); + await w(500); + const read = () => ({ + size: ctl("body", "size")?.value, + width: ctl("body", "width")?.value ?? null, + height: ctl("body", "height")?.value ?? null, + }); + const file = async () => + (await api.fetchDetailPipePoints(pid)).pipe_points.find((p) => p.facility === "box_culvert") + ?.options; + const save = async () => { + document.querySelector('[data-dock-action="save"]').click(); + await w(500); + return until(() => !document.querySelector(".ui-overlay.is-active"), 60000); + }; + const wings = () => ["inlet", "outlet"].map((c) => ctl(c, "structure")?.value); + const out = { mode: MODE, reopened: read(), wings: wings() }; + if (MODE === "one") { + await set("body", "width", ""); + out.saved = await save(); + out.file = await file(); + } + if (MODE === "both") { + await set("body", "height", ""); + out.saved = await save(); + out.file = await file(); + } + if (MODE === "fill") { + await set("body", "width", "2.2"); + await set("body", "height", "1.6"); + out.saved = await save(); + out.file = await file(); + } + out.wingsAfter = wings(); + out.after = read(); + return JSON.stringify(out); +})();