fix(B05·B06): 54-30 추가 흙막이 강조 유지 · 관 보호공 [연동] [경사] · 칸 고침 강조 · 흙막이 칸 일원화
- 폼이 상자를 새로 세울 때마다(단 수 · 유입 형식 · 벽 조작 · 저장 · 넘김) 고른 상자 강조 다시(specpaint 한 곳) · 54-29 의 두 군데 호출 대체 - 관 보호공(유입 · 유출) 상자 안 [연동] [경사] 반 폭 두 단추 — 옛 연동 제어 값(revet_link_detached · revet_follow_grade) 그대로 · 쓸 수 있을 때만 활성 · 경사 끄면 연동 잠김 · 누르면 횡단 그림 바로 - 상자 칸을 고치거나 고르면 그 벽을 고름(횡단에서 누른 것과 같은 길 · 카드 다시 그림) · 단 수 칸은 고른 단을 지킴 - 추가 흙막이 상자 = 사면안정 흙막이와 같은 칸(형태 · 길이 · 높이 · 전 · 후) · 구조 고르기 칸과 정의 이름 두 줄 뺌 · 저장 키 그대로 - 시험 54-29 · 54-27 · 52 명세 · 54-14 말풍선 기대값 맞춤 화면 확인(sub5_검증 12+18): 흙막이 1 강조 = 단 2→1 · 넘김 · 저장 뒤 유지 · 형태 고침 → 상자 · 벽 강조 · 경사 끔 → 13+0 벽 이동 · 두 줄 함께 · 목록 대 횡단 같은 글(칸 54) · 단 0 저장으로 되돌림 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
@@ -201,6 +201,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
const title = containerTitle(c, names, groupNames[c.link]);
|
||||
const box = group(c.required ? title : `${title} ${index + 1}`);
|
||||
box.root.dataset.specContainer = c.id;
|
||||
box.root.dataset.specIndex = String(index); // 칸을 고친 뒤 다시 그려도 몇째 상자인지(54-30)
|
||||
// 보이는 값 = 고른 값 + 그림이 쓰는 값(54-13) · 위 칸이 다른 갈래를 보이면 아래 칸은 빈 채로
|
||||
const view: Instance = { ...values };
|
||||
const tags = new Map<string, string>();
|
||||
@@ -309,6 +310,8 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
root.replaceChildren(...boxes);
|
||||
// 개수가 밖에서 바뀌었으면 값 변경으로 알림(불러오기 때는 안 함)
|
||||
if (!quiet) fitted.forEach((c) => hooks.onChange?.(c, c.items[0]));
|
||||
// 상자를 새로 세움 — 고른 상자 강조를 다시 얹을 곳(B06 · 54-30 · 시험 흉내 DOM 은 없음)
|
||||
root.dispatchEvent?.(new Event("specpaint", { bubbles: true }));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -152,15 +152,8 @@ export const PIPE_SPEC: StructureSpec = {
|
||||
required: false,
|
||||
prefix: "soil_add",
|
||||
link: "gsoiladd",
|
||||
items: [
|
||||
{
|
||||
id: "structure",
|
||||
label: "구조",
|
||||
kind: "select",
|
||||
choices: [{ value: SOIL, label: "흙막이" }],
|
||||
},
|
||||
...guardItems(),
|
||||
],
|
||||
// 사면안정 흙막이(표 09 · 54-26)와 같은 칸 — 형태 6 · 길이 · 높이 · 전 · 후 · 구조 고르기 없음(54-30)
|
||||
items: guardItems(),
|
||||
},
|
||||
{
|
||||
id: "chute_add",
|
||||
|
||||
@@ -122,3 +122,40 @@ export function guardHeightHint(diameterMm: string): string {
|
||||
const d = Number(diameterMm || "1000") / 1000;
|
||||
return `비우면 관경 기준 ${pipeWallMinHeightM(d)}`;
|
||||
}
|
||||
|
||||
/** 관 보호공 [연동] · [경사](54-30) — null = 그 쪽을 쓸 수 없음(벽이 서는 옆 측점 없음 · 구간 없음). */
|
||||
export interface PipeRevetChoice {
|
||||
linked: boolean | null;
|
||||
grade: boolean | null;
|
||||
}
|
||||
/** 읽고 쓰는 곳 — 횡단 보기의 연동 제어(`revetLink` · 옛 저장 자리)가 걸어 둠. */
|
||||
interface RevetChoiceBridge {
|
||||
read: (owner: CrossSection, sections: readonly CrossSection[]) => PipeRevetChoice;
|
||||
write: (
|
||||
owner: CrossSection,
|
||||
sections: readonly CrossSection[],
|
||||
kind: "link" | "grade",
|
||||
on: boolean,
|
||||
) => void;
|
||||
}
|
||||
let revetChoice: RevetChoiceBridge | null = null;
|
||||
export function setPipeRevetChoiceBridge(bridge: RevetChoiceBridge): void {
|
||||
revetChoice = bridge;
|
||||
}
|
||||
|
||||
/** 그 관의 [연동] · [경사] 지금 값 — 횡단 자료가 없으면 null(모름). */
|
||||
export function pipeRevetChoice(chainageM: number | null): PipeRevetChoice | null {
|
||||
const found = ownerSection(chainageM);
|
||||
return found && revetChoice ? revetChoice.read(found.owner, found.sections) : null;
|
||||
}
|
||||
|
||||
/** 좌측 [연동] · [경사] → 횡단 연동 제어(종단 보기는 횡단 보기를 숨긴 채 세워 씀 · 54-27 과 같은 길). */
|
||||
export async function setPipeRevetChoice(
|
||||
chainageM: number | null,
|
||||
kind: "link" | "grade",
|
||||
on: boolean,
|
||||
): Promise<void> {
|
||||
if (!revetChoice) await crossOpener?.();
|
||||
const found = ownerSection(chainageM);
|
||||
if (found) revetChoice?.write(found.owner, found.sections, kind, on);
|
||||
}
|
||||
|
||||
@@ -78,8 +78,10 @@ import {
|
||||
setPipeExtraCountSink,
|
||||
setPipeExtraCountSource,
|
||||
setPipeInletSource,
|
||||
setPipeRevetChoiceBridge,
|
||||
setPipeSectionsSource,
|
||||
} from "./design_structure_pipe_calc";
|
||||
import { pipeRevetChoiceBridge } from "../../B06_Section/B06_Section_UI_Page_Link_Session";
|
||||
import {
|
||||
bindDesignView,
|
||||
bindPipeOptions,
|
||||
@@ -313,6 +315,8 @@ export async function mountCrossView(
|
||||
});
|
||||
// 폼이 보이는 유입 그림 값(54-13) — 세션 형식 우선
|
||||
setPipeInletSource((section) => stationControls.inletStructure.valueFor(section));
|
||||
// 좌측 관 보호공 [연동] · [경사] = 옛 연동 제어 그대로(54-30)
|
||||
setPipeRevetChoiceBridge(pipeRevetChoiceBridge(stationControls.revetLink));
|
||||
|
||||
const sectionView = createSectionView(
|
||||
(chainageM, change) => {
|
||||
@@ -372,6 +376,7 @@ export async function mountCrossView(
|
||||
partKey: key,
|
||||
from: "b06",
|
||||
}),
|
||||
(chainageM) => sectionView.refreshCard(chainageM),
|
||||
);
|
||||
// 횡단도(카드) 자체를 골라도 그 측점 구조물 정보를 폼에 올린다(2026-08-29 사용자).
|
||||
// 연동으로 옆에서 넘어온 카드는 **소유 측점** 시설을 보여 준다.
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
WING_IN_KEYS,
|
||||
WING_OUT_KEYS,
|
||||
} from "./B05_Profile_UI_Drainage_Facility_Fields";
|
||||
import { revetChoiceRow } from "./B05_Profile_UI_Drainage_Revet_Choice";
|
||||
import { createSpecForm, type SpecLinks } from "../A00_Common/design/design_structure_form";
|
||||
import { branchOf } from "../A00_Common/design/design_structure_spec";
|
||||
import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe";
|
||||
@@ -321,8 +322,22 @@ export function createFacilityOptionsForm(
|
||||
// 지형이 못 세운 단은 횡단 기하가 자름 — 끝난 뒤 세운 만큼으로 다시 그림
|
||||
void setPipeExtraCount(chainage, count).then(() => pipeForm.recount());
|
||||
});
|
||||
const inletChoice = revetChoiceRow(
|
||||
() => chainage,
|
||||
() => pipeForm.refresh(),
|
||||
);
|
||||
const outletChoice = revetChoiceRow(
|
||||
() => chainage,
|
||||
() => pipeForm.refresh(),
|
||||
);
|
||||
const pipeForm = createSpecForm(specLinks, {
|
||||
fields: (c, values) => (c.id === "outlet" ? [paintTiers(values)] : []),
|
||||
// 관 보호공 [연동] · [경사](54-30) — 유입 · 유출 상자 각각 한 줄(같은 값) · 집수정이면 숨김
|
||||
fields: (c, values) =>
|
||||
c.id === "outlet"
|
||||
? [paintTiers(values), outletChoice.paint(false)]
|
||||
: c.id === "inlet"
|
||||
? [inletChoice.paint(values.structure === "집수정")]
|
||||
: [],
|
||||
shown: (c, item, values, index) => {
|
||||
if (c.id === "inlet" && item.id === "structure" && (!values.structure || inletIsAuto())) {
|
||||
const drawn = drawnNow();
|
||||
@@ -331,8 +346,6 @@ export function createFacilityOptionsForm(
|
||||
: { value: "", tag: "자동(지형 규칙)" };
|
||||
}
|
||||
if (values[item.id]) return null;
|
||||
// 흙막이 상자 구조 = 표 01 흙막이 하나(54-27)
|
||||
if (c.id === "soil_add" && item.id === "structure") return { value: "기슭막이", tag: "기본" };
|
||||
if (item.id !== "form") return byDefault(branchOf(item, values)?.key);
|
||||
const basin = c.id === "inlet" && values.structure === "집수정";
|
||||
const drawn = drawnNow();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile_UI_Drainage_Revet_Choice.ts
|
||||
* 관 보호공 상자 안 [연동] · [경사] 한 줄(54-30) — 반 폭 단추 둘 · 쓸 수 있는 쪽만 누를 수 있음.
|
||||
* 값 = 횡단 연동 제어 옛 자리(`revet_link_detached` · `revet_follow_grade` · `design_structure_pipe_calc`) ·
|
||||
* 고르면 횡단 그림이 바로 따라감. 경사를 끄면(수평) 연동은 풀 수 없음(옛 규칙 그대로).
|
||||
* ========================================================================== */
|
||||
|
||||
import { toggleButton } from "./B05_Profile_UI_Panel_Fields";
|
||||
import {
|
||||
pipeRevetChoice,
|
||||
setPipeRevetChoice,
|
||||
} from "../A00_Common/design/design_structure_pipe_calc";
|
||||
|
||||
const LINK_TITLE = "옆 측점 흙막이와 횡단 자리를 함께 씀 — 끄면 측점마다 따로 잡음";
|
||||
const LINK_LOCKED = "경사를 끈(수평) 동안은 연동을 풀 수 없음";
|
||||
const GRADE_TITLE = "종단경사 반영 — 끄면 연장 구간 내내 같은 높이(수평)";
|
||||
|
||||
/** 한 줄 — `paint` 가 지금 값 · 쓸 수 있는지를 다시 칠하고 줄을 돌려줌(폼 다시 그림마다). */
|
||||
export function revetChoiceRow(
|
||||
chainageOf: () => number | null,
|
||||
/** 쓴 뒤 — 폼을 다시 그려 유입 · 유출 두 줄을 함께 칠함. */
|
||||
written: () => void,
|
||||
): {
|
||||
paint: (hidden: boolean) => HTMLElement;
|
||||
} {
|
||||
const row = document.createElement("div"); // label 이 아님 — 이름표를 눌러 첫 단추가 눌리지 않게
|
||||
row.className = "b05-route__field";
|
||||
const caption = document.createElement("span");
|
||||
caption.textContent = "옵션";
|
||||
const pair = document.createElement("div");
|
||||
pair.className = "b05-structure__half-pair";
|
||||
const write = (kind: "link" | "grade", on: boolean): void =>
|
||||
void setPipeRevetChoice(chainageOf(), kind, on).then(written);
|
||||
const link = toggleButton("연동", true, (on) => write("link", on));
|
||||
const grade = toggleButton("경사", true, (on) => write("grade", on));
|
||||
grade.title = GRADE_TITLE;
|
||||
pair.append(link, grade);
|
||||
row.append(caption, pair);
|
||||
|
||||
const set = (button: HTMLButtonElement, value: boolean | null, locked: boolean): void => {
|
||||
button.disabled = value === null || locked;
|
||||
button.classList.toggle("is-active", value === true);
|
||||
button.setAttribute("aria-pressed", String(value === true));
|
||||
};
|
||||
function paint(hidden: boolean): HTMLElement {
|
||||
const now = pipeRevetChoice(chainageOf());
|
||||
const locked = now?.grade === false;
|
||||
set(link, now?.linked ?? null, locked);
|
||||
link.title = locked ? LINK_LOCKED : LINK_TITLE;
|
||||
set(grade, now?.grade ?? null, false);
|
||||
row.hidden = hidden;
|
||||
return row;
|
||||
}
|
||||
return { paint };
|
||||
}
|
||||
@@ -547,3 +547,10 @@
|
||||
.b05-structure__spec-add {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* 반 폭 단추 둘 한 줄 — 관 보호공 [연동] · [경사](54-30). */
|
||||
.b05-structure__half-pair {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ function moveRows(panelRoot: HTMLElement, selector: string, target: HTMLElement)
|
||||
* 조정창 사본의 단 수·옵션 행을 고른 벽 쪽(유입/유출) 그룹으로 옮긴다. 행 DOM을
|
||||
* 그대로 옮기므로 배선·값 동기는 손대지 않는다. 자리가 없으면 사본에 그대로 둔다.
|
||||
*/
|
||||
function relocateSideRows(panelRoot: HTMLElement): void {
|
||||
function relocateSideRows(panelRoot: HTMLElement, scroll = true): void {
|
||||
clearSideSlots();
|
||||
const side = panelRoot.dataset.side;
|
||||
if (!sideSlots || (side !== "inlet" && side !== "outlet" && side !== "extra")) return;
|
||||
@@ -173,7 +173,7 @@ function relocateSideRows(panelRoot: HTMLElement): void {
|
||||
moveRows(panelRoot, PIPE_OUTLET_ROWS, ownerSlot);
|
||||
setGroupLabel(soil, "");
|
||||
soil.classList.add("is-active");
|
||||
soil.scrollIntoView({ block: "nearest" });
|
||||
if (scroll) soil.scrollIntoView({ block: "nearest" });
|
||||
return;
|
||||
}
|
||||
const owner = ownerSlot.closest("fieldset");
|
||||
@@ -197,7 +197,7 @@ function relocateSideRows(panelRoot: HTMLElement): void {
|
||||
const pipe = target === sideSlots.inlet || target === sideSlots.outlet;
|
||||
setGroupLabel(group, pipe ? "" : (panelRoot.dataset.panelTitle ?? ""));
|
||||
// 고른 칸이 화면 밖일 수 있다 — 그 자리로 스크롤한다(지시 2 · 유입 · 유출도 53-1).
|
||||
group.scrollIntoView({ block: "nearest" });
|
||||
if (scroll) group.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
function ensureDock(): void {
|
||||
@@ -248,9 +248,9 @@ export function adoptAdjustPanel(panelRoot: HTMLElement): void {
|
||||
* 칸(배관 유입구·유출구 / 독립 좌·우)이 아직 이전 시설 것이다(2026-08-30 사용자:
|
||||
* 다른 기슭막이를 고른 뒤 배관 유출을 고르면 강조가 없다).
|
||||
*/
|
||||
export function refreshAdjustSlots(): void {
|
||||
export function refreshAdjustSlots(scroll = true): void {
|
||||
const panel = slot?.firstElementChild as HTMLElement | null;
|
||||
if (panel) relocateSideRows(panel);
|
||||
if (panel) relocateSideRows(panel, scroll);
|
||||
}
|
||||
|
||||
/** 이 사본이 슬롯에 서 있으면 내린다 — 다른 사본이 이미 대신 서 있으면 손대지 않는다. */
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
* 검증 → 담기 / 쓰기 → 통째 직렬화)이라 제어기마다 짝을 두던 것을 하나로 모았다.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import {
|
||||
culvertOwnerFor,
|
||||
culvertReach,
|
||||
culvertWallsStandAt,
|
||||
type RevetLinkControl,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import type { PipeRevetChoice } from "../A00_Common/design/design_structure_pipe_calc";
|
||||
|
||||
/** 세션에 담기는 측점별 조작값 한 겹 맵. */
|
||||
export interface SessionMap<V> {
|
||||
values: Map<string, V>;
|
||||
@@ -101,3 +110,31 @@ export function createLinkFlagSession(sessionKey: () => string | null): LinkFlag
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 좌측 관 보호공 [연동] · [경사](54-30) — 옛 연동 제어 그대로 읽고 씀. 연동 = 벽이 서는 옆 측점이
|
||||
* 있을 때만 · 켬 = 그 측점이 모두 연동 · 끄면 모두 따로. 경사 = 보호공 구간이 있을 때만.
|
||||
* ponytail: 연동을 풀 때 카드 단추가 하던 높이 굳히기는 안 함 — 필요하면 카드 높이 셈을 여기로.
|
||||
*/
|
||||
export function pipeRevetChoiceBridge(revetLink: RevetLinkControl) {
|
||||
const linkCards = (owner: CrossSection, sections: readonly CrossSection[]) =>
|
||||
sections.filter((s) => culvertOwnerFor(s, sections) === owner && culvertWallsStandAt(owner, s));
|
||||
return {
|
||||
read: (owner: CrossSection, sections: readonly CrossSection[]): PipeRevetChoice => {
|
||||
const cards = linkCards(owner, sections);
|
||||
return {
|
||||
linked: cards.length ? cards.every((s) => revetLink.linkedFor(s)) : null,
|
||||
grade: culvertReach(owner) ? revetLink.followGradeFor(owner) : null,
|
||||
};
|
||||
},
|
||||
write: (
|
||||
owner: CrossSection,
|
||||
sections: readonly CrossSection[],
|
||||
kind: "link" | "grade",
|
||||
on: boolean,
|
||||
): void => {
|
||||
if (kind === "grade") revetLink.setFollowGrade(owner, on);
|
||||
else linkCards(owner, sections).forEach((s) => revetLink.setLinked(s.chainage_m, on));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
} from "./B06_Section_UI_Adjust_Dock";
|
||||
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import type { StationControls } from "./B06_Section_UI_Page_Station_Controls";
|
||||
import type { RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { withSpecDefaults } from "./B06_Section_UI_Page_Structures_Spec";
|
||||
|
||||
const PIPE_ADD_GUIDE = "계곡 통과 시설을 넣고 빼려면 프로젝트를 먼저 여세요.";
|
||||
@@ -317,6 +318,8 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
resetAdjustDock();
|
||||
section.root.querySelector(".b05-route__panel-body")?.append(adjustDockRoot());
|
||||
// 단 수·옵션 행은 유입구·유출구 그룹으로 옮겨 붙는다(2026-08-29 지시 4).
|
||||
// 상자를 새로 세우면(단 수 · 유입 형식 · 벽 조작 · 저장 · 넘김) 고른 상자 강조를 다시 얹음(54-30)
|
||||
section.facility.root.addEventListener("specpaint", () => refreshAdjustSlots(false));
|
||||
setAdjustSideSlots({
|
||||
inlet: section.facility.inletSlot,
|
||||
outlet: section.facility.outletSlot,
|
||||
@@ -496,8 +499,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
facility: {
|
||||
setInletStructure: (value) => section.facility.setInletStructure(value),
|
||||
onInletStructureChange: (handler) => section.facility.onInletStructureChange(handler),
|
||||
// 다시 그린 상자에 강조를 다시 얹음(흙막이 상자 · 54-29)
|
||||
recount: () => (section.facility.recount(), refreshAdjustSlots()),
|
||||
recount: () => section.facility.recount(),
|
||||
},
|
||||
overrideOptions: (chainageM, patch) => {
|
||||
const hit = pipeFacilities.find(
|
||||
@@ -567,6 +569,8 @@ export function wireStructureSelection(
|
||||
/** 고른 부재를 세션에 남긴다(2026-09-04). 해제(null)는 부르지 않는다 — 조정창 닫기가
|
||||
* 카드(측점) 선택까지 지우면 안 된다. 측점 해제는 카드 선택 리스너가 따로 적는다. */
|
||||
remember?: (chainageM: number, key: string) => void,
|
||||
/** 폼에서 고른 벽을 카드 조정창에도 세움(54-30 · 목록 넘김 `applyStructurePick` 과 같은 차례). */
|
||||
refreshCard?: (chainageM: number) => void,
|
||||
): { syncInletStructure: () => void } {
|
||||
const ownerChainageOf = (chainageM: number): number => {
|
||||
const sectionAt = detail()?.cross_sections.find(
|
||||
@@ -588,9 +592,7 @@ export function wireStructureSelection(
|
||||
const syncInletStructure = (): void => {
|
||||
const chainageM = panel.currentChainage();
|
||||
const target = chainageM === null ? undefined : sectionAt(chainageM);
|
||||
if (!target) return;
|
||||
panel.facility.setInletStructure(stationControls.inletStructure.valueFor(target));
|
||||
refreshAdjustSlots(); // 폼을 다시 그림 — 고른 상자 강조를 다시 얹음(54-29)
|
||||
if (target) panel.facility.setInletStructure(stationControls.inletStructure.valueFor(target));
|
||||
};
|
||||
|
||||
const origRevet = stationControls.revetOffset.select;
|
||||
@@ -611,6 +613,26 @@ export function wireStructureSelection(
|
||||
refreshAdjustSlots();
|
||||
};
|
||||
|
||||
// 관 상자 칸을 고치거나 고르면 그 부분을 고름 — 횡단에서 누른 것과 같은 길(54-30)
|
||||
panel.root.addEventListener("change", (event) => {
|
||||
// 단 수 칸은 고른 단을 지킴(단 수 바꿈 뒤 강조 유지)
|
||||
if ((event.target as HTMLElement).closest('[data-spec-item="outlet.tiers"]')) return;
|
||||
const box = (event.target as HTMLElement).closest<HTMLElement>("fieldset[data-spec-container]");
|
||||
const chainageM = panel.currentChainage();
|
||||
const target = chainageM === null ? undefined : sectionAt(chainageM);
|
||||
if (!box || !target?.culvert) return;
|
||||
const id = box.dataset.specContainer;
|
||||
const key: RevetKey | null =
|
||||
id === "inlet" || id === "outlet"
|
||||
? id
|
||||
: id === "soil_add"
|
||||
? `extra${Number(box.dataset.specIndex)}`
|
||||
: null;
|
||||
if (!key || stationControls.revetOffset.highlightFor(target) === key) return;
|
||||
stationControls.revetOffset.select(target.chainage_m, key);
|
||||
refreshCard?.(target.chainage_m);
|
||||
});
|
||||
|
||||
const origFord = stationControls.ford.select;
|
||||
stationControls.ford.select = (chainageM, role) => {
|
||||
const nested = wallSelecting;
|
||||
|
||||
@@ -3331,8 +3331,6 @@
|
||||
"outlet.before": "기준측점 전 (m)",
|
||||
"outlet.after": "기준측점 후 (m)",
|
||||
"soil_add": "흙막이 추가",
|
||||
"soil_add.structure": "구조",
|
||||
"soil_add.structure=기슭막이": "흙막이",
|
||||
"soil_add.form": "형태",
|
||||
"soil_add.form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"soil_add.form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
|
||||
@@ -127,6 +127,10 @@ const PIPE_CALC = {
|
||||
const STUBS = {
|
||||
"@config/config_frontend": { FORD_MANNING_N: 0.03, FORD_SLOPE: 0.02 },
|
||||
"../A00_Common/design/design_structure_pipe_calc": PIPE_CALC,
|
||||
// 관 보호공 [연동] · [경사] 줄(54-30) — 여기서는 자리만
|
||||
"./B05_Profile_UI_Drainage_Revet_Choice": {
|
||||
revetChoiceRow: () => ({ paint: () => new FakeNode("div") }),
|
||||
},
|
||||
"../../B05_Profile/B05_Profile_UI_Structures_Summary_Row": {
|
||||
buildSummaryRow: () => {
|
||||
const root = new FakeNode("div");
|
||||
@@ -174,7 +178,10 @@ const tierInput = () => tierRow()?.querySelector("input");
|
||||
const snap = () => ({
|
||||
value: tierInput()?.value ?? null,
|
||||
boxes: boxesOf("soil_add").length,
|
||||
structures: boxesOf("soil_add").map((b) => b.querySelector("select")?.children[0]?.textContent),
|
||||
// 상자 첫 칸 이름 — 구조 고르기 없이 형태부터(54-30 · 사면안정 흙막이와 같은 칸)
|
||||
structures: boxesOf("soil_add").map(
|
||||
(b) => b.querySelectorAll("label")[0]?.children[0]?.textContent,
|
||||
),
|
||||
});
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -66,9 +66,10 @@ def test_옛_키로_읽고_같은_키로_씀_옛_칸은_안_씀(out):
|
||||
|
||||
|
||||
def test_선택_컨테이너는_인스턴스마다_차례대로_다시_매김(out):
|
||||
assert [i["structure"] for i in out["state"]["soil_add"]] == ["기슭막이", ""]
|
||||
# 흙막이 상자 = 구조 고르기 없음(54-30) — 옛 구조 키만 든 상자도 자리는 지킴
|
||||
assert [i["form"] for i in out["state"]["soil_add"]] == ["바자", ""]
|
||||
assert out["back"]["soil_add_1_form"] == "바자"
|
||||
assert "soil_add_2_form" not in out["back"]
|
||||
assert out["back"]["soil_add_2_form"] == "" # 빈 상자 자리 표식 = 첫 칸(형태) 빈 글
|
||||
assert out["back"]["chute_add_1_structure"] == "도수로"
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ def test_배관_컨테이너마다_첫_값_한_줄(out):
|
||||
"관: 관경 1200",
|
||||
"관 보호공(유입): 구조 집수정",
|
||||
"관 보호공(유출): 구조 흙막이",
|
||||
"흙막이 1: 구조 흙막이",
|
||||
"흙막이 1: 형태 돌망태", # 구조 칸 없음(54-30)
|
||||
"도수로/돌붙임 1: 구조 도수로",
|
||||
]
|
||||
|
||||
@@ -56,7 +56,7 @@ def test_빈_값은_등록부_기본값에_기본_표시(out):
|
||||
|
||||
|
||||
def test_흙막이_상자_수는_밖에서_정한_단_수(out):
|
||||
assert out["pipeCount"][-2:] == ["흙막이 1: 구조 흙막이", "흙막이 2: 구조 —"]
|
||||
assert out["pipeCount"][-2:] == ["흙막이 1: 형태 —", "흙막이 2: 형태 —"]
|
||||
assert len(out["pipeCount"]) == 2 + 3 + 2
|
||||
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ def test_횡단에서_바꾸면_좌측_칸이_따라감(out) -> None:
|
||||
assert (out["fromCross"]["value"], out["fromCross"]["boxes"]) == ("2", 2)
|
||||
|
||||
|
||||
def test_늘어난_흙막이_상자_구조는_흙막이_기본(out) -> None:
|
||||
assert out["set2"]["structures"] == ["흙막이 (기본)", "흙막이 (기본)"]
|
||||
def test_늘어난_흙막이_상자는_구조_칸_없이_형태부터(out) -> None:
|
||||
"""54-30 — 사면안정 흙막이와 같은 칸(형태 · 길이 · 높이 · 전 · 후) · 구조 고르기 없음."""
|
||||
assert out["set2"]["structures"] == ["형태", "형태"]
|
||||
|
||||
|
||||
def test_흙막이_아닌_구조면_칸을_가림(out) -> None:
|
||||
|
||||
@@ -40,7 +40,31 @@ def test_배관_유입_단_수_줄은_선_단이_있을_때만() -> None:
|
||||
|
||||
|
||||
def test_폼을_다시_그리면_강조_다시() -> None:
|
||||
"""54-30 — 상자를 새로 세울 때마다(단 수 · 유입 형식 · 벽 조작 · 저장 · 넘김) 한 곳에서 강조를 다시 얹음."""
|
||||
form = _src("A00_Common/design/design_structure_form.ts")
|
||||
assert 'root.dispatchEvent?.(new Event("specpaint", { bubbles: true }));' in form
|
||||
page = _src("B06_Section/B06_Section_UI_Page_Structures_Panel.ts")
|
||||
assert "recount: () => (section.facility.recount(), refreshAdjustSlots())" in page
|
||||
sync = page[page.index("const syncInletStructure") :]
|
||||
assert sync.index("refreshAdjustSlots()") < sync.index("};")
|
||||
assert 'addEventListener("specpaint", () => refreshAdjustSlots(false))' in page
|
||||
|
||||
|
||||
def test_상자_칸을_고치면_그_부분을_고름() -> None:
|
||||
"""54-30 — 칸 고침 = 횡단에서 그 벽을 누른 것과 같은 길 · 단 수 칸은 고른 단을 지킴."""
|
||||
page = _src("B06_Section/B06_Section_UI_Page_Structures_Panel.ts")
|
||||
assert '[data-spec-item="outlet.tiers"]' in page
|
||||
assert "`extra${Number(box.dataset.specIndex)}`" in page
|
||||
assert "refreshCard?.(target.chainage_m);" in page
|
||||
|
||||
|
||||
def test_흙막이_상자는_사면안정_흙막이와_같은_칸() -> None:
|
||||
pipe = _src("A00_Common/design/design_structure_pipe.ts")
|
||||
soil = pipe[pipe.index('id: "soil_add"') :]
|
||||
assert soil[: soil.index("},")].count("items: guardItems(),") == 1
|
||||
|
||||
|
||||
def test_관_보호공_연동_경사_단추() -> None:
|
||||
row = _src("B05_Profile/B05_Profile_UI_Drainage_Revet_Choice.ts")
|
||||
assert 'toggleButton("연동"' in row and 'toggleButton("경사"' in row
|
||||
assert "b05-structure__half-pair" in row
|
||||
session = _src("B06_Section/B06_Section_UI_Page_Link_Session.ts")
|
||||
assert "revetLink.setFollowGrade(owner, on)" in session
|
||||
assert "revetLink.setLinked(s.chainage_m, on)" in session
|
||||
|
||||
Reference in New Issue
Block a user