diff --git a/A05_EduDetail/guide/D03/_scenes.json b/A05_EduDetail/guide/D03/_scenes.json index eaf44181b..0afbda353 100644 --- a/A05_EduDetail/guide/D03/_scenes.json +++ b/A05_EduDetail/guide/D03/_scenes.json @@ -7,6 +7,6 @@ {"name":"D03_05_station_cross","route":"#/d03-plan-analysis","project":"plan","viewport":[1600,1250],"before":[{"wait":".d03-plan__map canvas"},{"wait":".d03-plan__mode"},{"wait_ms":4000},{"eval":"window.__d03Plan.pick(100)"},{"wait":".d03-plan__cross-body svg"},{"wait_ms":600}],"settle_ms":2000,"clip":".d03-plan__cross","callouts":[{"n":1,"sel":".d03-plan__cross-head"},{"n":2,"sel":".d03-plan__cross-body"}]}, {"name":"D03_06_cross_fold","route":"#/d03-plan-analysis","project":"plan","viewport":[1600,900],"before":[{"wait":".d03-plan__map canvas"},{"wait":".d03-plan__mode"},{"wait_ms":4000},{"eval":"window.__d03Plan.pick(400)"},{"wait":".d03-plan__cross-body svg"},{"wait_ms":600},{"click":".d03-plan__cross .ui-workflow-overlay__handle"},{"wait_ms":800}],"settle_ms":2000,"clip":null,"callouts":[{"n":1,"sel":".d03-plan__cross .ui-workflow-overlay__handle","pos":"tr"}]}, {"name":"D03_07_buttons","route":"#/d03-plan-analysis","project":"plan","viewport":[1600,1250],"before":[{"wait":".d03-plan__map canvas"},{"wait":".d03-plan__mode"},{"wait_ms":4000}],"settle_ms":2000,"clip":".ui-sidebar-actions","callouts":[{"n":1,"sel":".ui-sidebar-actions > button:nth-child(1)"},{"n":2,"sel":".ui-sidebar-actions > button:nth-child(2)"},{"n":3,"sel":".ui-sidebar-actions > button:nth-child(3)"},{"n":4,"sel":".ui-sidebar-actions > button:nth-child(4)"},{"n":5,"sel":".ui-sidebar-actions > button:nth-child(5)"},{"n":6,"sel":".ui-sidebar-actions > button:nth-child(6)"}]}, - {"name":"D03_08_3d_modal","route":"#/d03-plan-analysis","project":"plan","viewport":[1600,1250],"before":[{"wait":".d03-plan__map canvas"},{"wait":".d03-plan__mode"},{"wait_ms":4000},{"click":".ui-sidebar-actions > button:nth-child(6)"},{"wait":".b05-route__3d-modal"},{"wait_ms":9000},{"wait":".d03-plan__3d-corridor input:checked"}],"settle_ms":2000,"clip":null,"callouts":[{"n":1,"sel":".d03-plan__3d-year"},{"n":2,"sel":".d03-plan__3d-corridor"},{"n":3,"sel":".b05-route__3d-body .b05-route__viewport","pos":"br"}]}, + {"name":"D03_08_3d_modal","route":"#/d03-plan-analysis","project":"plan","viewport":[1600,1250],"before":[{"wait":".d03-plan__map canvas"},{"wait":".d03-plan__mode"},{"wait_ms":4000},{"click":".ui-sidebar-actions > button:nth-child(6)"},{"wait":".d03-plan__3d-modal"},{"wait_ms":9000},{"wait":".d03-plan__3d-corridor input:checked"}],"settle_ms":2000,"clip":null,"callouts":[{"n":1,"sel":".d03-plan__3d-year"},{"n":2,"sel":".d03-plan__3d-corridor"},{"n":3,"sel":".d03-plan__3d-body .b05-route__viewport","pos":"br"}]}, {"name":"D03_09_ip_edit","route":"#/d03-plan-analysis","project":"plan","viewport":[1600,1250],"before":[{"wait":".d03-plan__map canvas"},{"wait":".d03-plan__mode"},{"wait_ms":4000},{"eval":"window.__d03Plan.nudge(3, 18, 14)"},{"eval":"window.__d03Plan.pick(200)"},{"wait":".d03-plan__cross-body svg"},{"wait_ms":600}],"settle_ms":2000,"clip":null,"callouts":[{"n":1,"sel":".d03-plan__map","pos":"tr"},{"n":2,"sel":".d03-plan__year[data-year=\"2027\"] .d03-plan__year-head:nth-child(2)","pos":"tr"},{"n":3,"sel":".d03-plan__year[data-year=\"2027\"] .d03-plan__mode.is-picked","pos":"tr"},{"n":4,"sel":".ui-sidebar-actions > button:nth-child(7)","pos":"tr"}]} ] diff --git a/A05_EduDetail/guide/E06/_scenes.json b/A05_EduDetail/guide/E06/_scenes.json index 4d1546ad6..b2f42e8cb 100644 --- a/A05_EduDetail/guide/E06/_scenes.json +++ b/A05_EduDetail/guide/E06/_scenes.json @@ -85,11 +85,11 @@ "callouts": [ { "n": 1, - "sel": ".e06-cards > section:nth-child(2) .ui-field:nth-of-type(1), .e06-cards > section:nth-child(2) .e06-setting:nth-of-type(1)" + "sel": ".e06-cards > section:nth-child(2) .ui-field:nth-of-type(1), .e06-cards > section:nth-child(2) .ui-setting-row:nth-of-type(1)" }, { "n": 2, - "sel": ".e06-cards > section:nth-child(2) .e06-setting__ctl" + "sel": ".e06-cards > section:nth-child(2) .e06-ctl" } ] }, diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_3D.ts b/D03_PlanAnalysis/D03_PlanAnalysis_UI_3D.ts index 49a9d99f0..b1082a6fa 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_3D.ts +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_3D.ts @@ -136,9 +136,9 @@ export function openPlan3d(projectId: string, years: Plan3dYear[], firstYear: nu title: "3D 보기 — 지형 + 노선", closeLabel: "닫기", wide: true, - dialogClass: "b05-route__3d-modal", + dialogClass: "d03-plan__3d-modal", mount: (body) => { - body.classList.add("b05-route__3d-body"); + body.classList.add("d03-plan__3d-body"); body.append(top, viewer!.root); viewer!.setActive(true); }, diff --git a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css index 234fe200b..faf721e4d 100644 --- a/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css +++ b/D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css @@ -86,6 +86,17 @@ font-size: 0.85rem; } +/* 3D 큰 모달 — 3D 화면이 모달 몸을 꽉 채움 · 몸 스크롤 없음 */ +.ui-modal--wide > .ui-modal__body.d03-plan__3d-body { + overflow: hidden; +} + +.d03-plan__3d-body > .b05-route__viewport { + flex: 1 1 auto; + min-height: 0; + border-radius: var(--radius-md); +} + .d03-plan__3d-top { display: flex; gap: var(--spacing-8); diff --git a/E06_StabRock/E06_StabRock_UI_Joints.ts b/E06_StabRock/E06_StabRock_UI_Joints.ts index 447dfe8ab..6bbb1eb5a 100644 --- a/E06_StabRock/E06_StabRock_UI_Joints.ts +++ b/E06_StabRock/E06_StabRock_UI_Joints.ts @@ -8,6 +8,7 @@ import { createPanelCard } from "@ui/ui_template_collapsible"; import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements"; import { t, type LocaleKey } from "@ui/ui_template_locale"; +import { settingRow as settingRowPart } from "@ui/ui_template_setting_row"; import { e06Table, note } from "./E06_StabRock_UI_Parts"; export interface JointSet { @@ -169,11 +170,9 @@ function jointTable(view: JointsView): HTMLElement { return wrap; } -/** 좁은 패널 설정 줄 — 설정 줄 부품(줄 52 · 큰 글)은 이 폭에 안 맞아 옛 꼴 그대로 */ +/** 좁은 패널 설정 줄 — 설정 줄 부품 좁은 꼴 */ function settingRow(label: LocaleKey, control: HTMLElement): HTMLElement { - const row = el("label", { className: "e06-setting" }); - row.append(el("span", { text: t(label) }), control); - return row; + return settingRowPart(t(label), "", control, { compact: true }); } function select( @@ -211,7 +210,7 @@ function minBlockControl(view: JointsView, s: RockSettings): HTMLElement { }, }); reset.disabled = !view.editable; - const box = el("span", { className: "e06-setting__ctl" }); + const box = el("span", { className: "e06-ctl" }); box.append( numberInput(view, s.min_block_m3, "min_block_m3", (v) => (s.min_block_m3 = v ?? NaN), { step: "0.01", diff --git a/E06_StabRock/E06_StabRock_UI_Style.css b/E06_StabRock/E06_StabRock_UI_Style.css index 46c9f7516..2dc9ecde4 100644 --- a/E06_StabRock/E06_StabRock_UI_Style.css +++ b/E06_StabRock/E06_StabRock_UI_Style.css @@ -74,21 +74,11 @@ select.e06-input { margin-top: var(--spacing-4); } -.e06-setting { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--spacing-8); - margin: 2px 0; - font-size: 12px; - color: var(--color-text); -} - -.e06-setting .e06-input { +.ui-setting-row .e06-input { width: 5.5em; } -.e06-setting__ctl { +.e06-ctl { display: flex; align-items: center; gap: var(--spacing-4); diff --git a/resources/tester/test_552_3_setting_row.py b/resources/tester/test_552_3_setting_row.py index 46d877c99..bbf290806 100644 --- a/resources/tester/test_552_3_setting_row.py +++ b/resources/tester/test_552_3_setting_row.py @@ -13,6 +13,9 @@ def test_setting_row(): ) css = _text("ui_template_setting_row.css") assert "min-height: 52px" in css and "max-width: 55%" in css + ts = _text("ui_template_setting_row.ts") + assert "compact?: boolean" in ts and 'dataset.size = "compact"' in ts + assert '[data-size="compact"]' in css and "min-height: 28px" in css b01 = ROOT / "B01_Dashboard" kit = (b01 / "B01_Dashboard_UI_PageKit.ts").read_text(encoding="utf-8") assert ( diff --git a/resources/tester/test_553_4_de_parts.py b/resources/tester/test_553_4_de_parts.py index 46ba4c217..2a79b490f 100644 --- a/resources/tester/test_553_4_de_parts.py +++ b/resources/tester/test_553_4_de_parts.py @@ -46,6 +46,10 @@ def test_d03_parts(): assert 'createElement("input")' not in three and "createCheckbox" in three for dead in (".d03-plan__bar", ".d03-plan__mode", "is-picked"): assert dead not in css, dead + assert "b05-route__3d-modal" not in three and "b05-route__3d-body" not in three # 빌림 끊음 + assert 'dialogClass: "d03-plan__3d-modal"' in three and ".d03-plan__3d-body" in css + scenes = _src("A05_EduDetail/guide/D03/_scenes.json") + assert "b05-route__3d-modal" not in scenes # 촬영 장면이 `.d03-plan__3d-modal` 로 잡음 def test_d04_parts(): @@ -159,6 +163,7 @@ def test_e06_parts(): assert part in parts, part assert "createLegend(" in ts["Stereonet"] and "createBanner(" in ts["Page"] assert "createInputField(" in ts["Joints"] and "createSelectField(" in ts["Joints"] + assert "compact: true" in ts["Joints"] and ".e06-setting" not in css # 설정 줄 부품 좁은 꼴 assert ( 'classList.add("e06-toggle")' in parts ) # 촬영 장면이 `.e06-toggle button[data-value]` 로 잡음 diff --git a/ui_template/ui_template_setting_row.css b/ui_template/ui_template_setting_row.css index 4bbf3f7f5..9407aea1d 100644 --- a/ui_template/ui_template_setting_row.css +++ b/ui_template/ui_template_setting_row.css @@ -40,3 +40,19 @@ text-align: right; overflow-wrap: anywhere; } +/* 좁은 꼴 — 좁은 왼쪽 패널 · 줄 28 · 글 12 · 선 없음 */ +.ui-setting-row[data-size="compact"] { + gap: var(--spacing-8); + min-height: 28px; + padding: 2px 0; + border-bottom: 0; + font-size: var(--text-caption); + color: var(--color-text); +} +.ui-setting-row[data-size="compact"] .ui-setting-row__side { + max-width: none; + gap: var(--spacing-4); +} +.ui-setting-row[data-size="compact"] strong { + font-weight: inherit; +} diff --git a/ui_template/ui_template_setting_row.ts b/ui_template/ui_template_setting_row.ts index 0cfb25cd0..6849932b5 100644 --- a/ui_template/ui_template_setting_row.ts +++ b/ui_template/ui_template_setting_row.ts @@ -3,6 +3,7 @@ * 부품 「설정 줄」(552-3 · 목록 33) — 왼쪽 제목 · 설명 / 오른쪽 값 · 조작 한 줄(Linear 꼴) · 여러 줄은 둥근 상자 하나에. * 꼴 = 값 줄(`valueText` · 없으면 「-」) · 조작 줄(오른쪽에 입력 · 고르기 · 켜기 · 단추) · 설명 없는 줄 * 크기 = 줄 최소 52 · 여백 8 / 16 · 오른쪽 칸 최대 55 % + * 좁은 꼴 = `settingRow(제목, 설명, 오른쪽, { compact: true })` — 좁은 왼쪽 패널용 · 줄 최소 28 · 글 12 · 아래 선 없음 · 줄 통째가 label(`data-size="compact"`) * 상태 = 기본만(조작 부품이 저마다 상태를 가짐) * 쓰는 법 = `settingCard([settingRow("회사 이름", "보고서 머리에 들어감", valueText("산림설계"))])` * 대신하는 옛 변종 = B01 `settingRow` · `settingCard`(`.b01-pg-row` · `.b01-pg-settings`) · `.b01-dashboard__setting-card` · E02 `e02-field` · E06 `e06-setting` · B08 `b08-hydro__row` @@ -12,7 +13,12 @@ import { el } from "./ui_template_elements_base"; import "./ui_template_setting_row.css"; /** 한 줄 — 제목 · 설명 왼쪽 · 값 · 조작 오른쪽 */ -export function settingRow(title: string, desc: string, right: Node): HTMLElement { +export function settingRow( + title: string, + desc: string, + right: Node, + opts: { compact?: boolean } = {}, +): HTMLElement { const left = el("div", { className: "ui-setting-row__text", children: [el("strong", { text: title })], @@ -20,7 +26,12 @@ export function settingRow(title: string, desc: string, right: Node): HTMLElemen if (desc) left.append(el("span", { className: "ui-setting-row__desc", text: desc })); const side = el("div", { className: "ui-setting-row__side" }); side.append(right); - return el("div", { className: "ui-setting-row", children: [left, side] }); + const row = el(opts.compact ? "label" : "div", { + className: "ui-setting-row", + children: [left, side], + }); + if (opts.compact) row.dataset.size = "compact"; + return row; } /** 값 글 — 없으면 「-」 */ diff --git a/ui_template/ui_template_ui_kit_parts.ts b/ui_template/ui_template_ui_kit_parts.ts index 47660f9dc..421eb522c 100644 --- a/ui_template/ui_template_ui_kit_parts.ts +++ b/ui_template/ui_template_ui_kit_parts.ts @@ -280,8 +280,8 @@ export const KIT_PARTS: KitPart[] = [ demo: (s) => (s === "default" ? col(createBarChart([{ label: "산림설계", value: 12 }, { label: "녹색기술", value: 7 }, { label: "그 밖 3 곳", value: 4 }]), createDonut([{ label: "임도", value: 5 }, { label: "예정노선", value: 3 }, { label: "안정성", value: 2 }]), createLineChart({ series: [{ name: "CPU", values: [20, 35, 30, 50, 42, 60], colorIndex: 0 }], height: 120 })) : null) }, { key: "legend", name: "범례", group: "compound", kinds: "색 칩 · 선 · 점선 · 점 · 꺼짐 · 세로", file: "ui_template_flow_legend.ts", demo: (s) => (s === "default" ? col(createLegend([{ label: "임도", color: "var(--color-chart-0)" }, { label: "설계선", color: "var(--color-chart-2)", kind: "line" }, { label: "원지반선", color: "var(--color-chart-1)", kind: "dash" }, { label: "관", color: "var(--color-chart-3)", kind: "dot" }, { label: "꺼진 층", color: "var(--color-chart-0)", off: true }], { onToggle: noop }), createLegend([{ label: "절토", color: "var(--color-danger)" }, { label: "성토", color: "var(--color-info)" }], { vertical: true })) : null) }, - { key: "setting-row", name: "설정 줄", group: "compound", kinds: "제목 · 설명 왼쪽 · 오른쪽 값 · 조작 · 줄 52", file: "ui_template_setting_row.ts", - demo: (s) => (s === "default" ? settingCard([settingRow("회사 이름", "보고서 머리에 들어감", valueText("산림설계")), settingRow("단위", "", valueText(null)), settingRow("기본 노폭", "새 노선에 먼저 들어감", createInputField({ placeholder: "3.0 m" }).root), settingRow("글꼴 파일", "", createButton({ label: "바꾸기", variant: "ghost", size: 28, onClick: noop }))]) : null) }, + { key: "setting-row", name: "설정 줄", group: "compound", kinds: "제목 · 설명 왼쪽 · 오른쪽 값 · 조작 · 줄 52 · 좁은 꼴(줄 28)", file: "ui_template_setting_row.ts", + demo: (s) => (s === "default" ? col(settingCard([settingRow("회사 이름", "보고서 머리에 들어감", valueText("산림설계")), settingRow("단위", "", valueText(null)), settingRow("기본 노폭", "새 노선에 먼저 들어감", createInputField({ placeholder: "3.0 m" }).root), settingRow("글꼴 파일", "", createButton({ label: "바꾸기", variant: "ghost", size: 28, onClick: noop }))]), settingCard([settingRow("측면 한계각(°)", "", createInputField({ placeholder: "30", size: 24 }).root, { compact: true }), settingRow("균열 모드", "", valueText("없음"), { compact: true })])) : null) }, { key: "drop", name: "파일 놓기 칸", group: "compound", kinds: "끌어 놓기 · 고르기 단추 · 받는 꼴 안내 · 진행 · 오류", file: "ui_template_drop.ts", demo: (s) => { if (s === "active") return null; const d = createDrop({ accept: ".las,.laz", hint: "LAS · LAZ · 2 GB 까지", onFiles: noop, disabled: s === "disabled" }); if (s === "error") d.setError("LAS · LAZ 파일만 받음"); if (s === "hover" || s === "error") markState(d.root, s);