Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
This commit is contained in:
@@ -27,6 +27,54 @@ export function fordHeightM(designFlowM3s: number | null, widthM: number): numbe
|
||||
return section ? Math.ceil(section.depthM * 100 - 1e-9) / 100 : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 월류 폭 ↔ 높이 연동 — 세월교 · 물넘이 포장 한 벌(52-4 · 65-4 · 65-5 · 287-3).
|
||||
* · 폭을 바꾸면 높이 = 필요 수심 · 높이를 비우거나 그 아래로 넣으면 필요 수심으로.
|
||||
* · 새 시설(`arm(true)`) 빈 높이 = 유량이 오면 필요 수심(사용자가 넣은 것처럼 — [추가] · [수정]이 읽음).
|
||||
* · 상자 끝 안내 — 「필요 수심 n 이상」 · 「설계유량 없음 — 수기 입력」(유량이 없으면 손 입력).
|
||||
* `box` = 폭 · 높이 칸이 든 상자 id(세월교 `ford` · 물넘이 `slope`) · `flow` = 담당 유역 설계유량.
|
||||
*/
|
||||
export function overflowSync(
|
||||
form: () => Pick<SpecForm, "state" | "setValue" | "refresh">,
|
||||
box: string,
|
||||
flow: () => number | null,
|
||||
emit: () => void,
|
||||
) {
|
||||
let pending = false;
|
||||
const values = () => form().state()[box]?.[0] ?? {};
|
||||
const width = () => Number(values().width ?? "");
|
||||
const need = (): number | null => fordHeightM(flow(), width());
|
||||
return {
|
||||
arm: (fresh: boolean): void => {
|
||||
pending = fresh;
|
||||
},
|
||||
onChange: (c: SpecContainer, item: SpecItem): void => {
|
||||
if (c.id === box && item.id === "height") pending = false;
|
||||
const want = c.id === box ? need() : null;
|
||||
const height = values().height ?? "";
|
||||
const low = height === "" || !(Number(height) >= (want ?? 0));
|
||||
if (want !== null && (item.id === "width" || (item.id === "height" && low)))
|
||||
form().setValue(box, "height", want.toFixed(2));
|
||||
emit();
|
||||
},
|
||||
/** 유량이 왔거나 시설을 올림 — 새 시설 빈 높이를 채우고 안내를 다시 그림. */
|
||||
fill: (): void => {
|
||||
const want = pending && !values().height ? need() : null;
|
||||
if (want === null) return form().refresh();
|
||||
form().setValue(box, "height", want.toFixed(2));
|
||||
pending = false;
|
||||
},
|
||||
/** 상자 끝 안내 글(폼 `fields` 훅). */
|
||||
fields: (c: SpecContainer): HTMLElement[] => {
|
||||
if (c.id !== box) return [];
|
||||
const hint = document.createElement("p");
|
||||
hint.className = "b05-structure__flow-hint";
|
||||
hint.textContent = fordHeightHint(flow(), width());
|
||||
return [hint];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 높이 빈 칸 안내. */
|
||||
export function fordHeightHint(designFlowM3s: number | null, widthM: number): string {
|
||||
const need = fordHeightM(designFlowM3s, widthM);
|
||||
|
||||
@@ -56,7 +56,6 @@ import {
|
||||
type SpecForm,
|
||||
type SpecLinks,
|
||||
} from "../A00_Common/design/design_structure_form";
|
||||
import type { SpecContainer, SpecItem } from "../A00_Common/design/design_structure_spec";
|
||||
import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe";
|
||||
import {
|
||||
BOX_SPEC,
|
||||
@@ -65,7 +64,7 @@ import {
|
||||
boxOptionsIn,
|
||||
} from "../A00_Common/design/design_structure_box";
|
||||
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||
import { fordHeightM, syncChuteArea } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import { overflowSync, syncChuteArea } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import {
|
||||
pipeDrawn,
|
||||
pipeExtraCount,
|
||||
@@ -372,38 +371,21 @@ export function createFacilityOptionsForm(
|
||||
// 월류 높이 = 월류 폭과 연동(폭을 바꾸면 필요 수심으로 채움 · 그 아래는 안 받음 · `design_structure_ford_calc`) —
|
||||
// 물넘이 포장도 같은 키 · 같은 계산(65-5) · 새 시설은 유량이 오면 빈 높이를 필요 수심으로(65-4)
|
||||
let fordFlow: number | null = null;
|
||||
let heightPending = false;
|
||||
const overflow = (form: () => SpecForm, box: string) => {
|
||||
const values = () => form().state()[box]?.[0] ?? {};
|
||||
const need = (): number | null => fordHeightM(fordFlow, Number(values().width ?? ""));
|
||||
return {
|
||||
onChange: (c: SpecContainer, item: SpecItem) => {
|
||||
if (c.id === box && item.id === "height") heightPending = false;
|
||||
const want = c.id === box ? need() : null;
|
||||
const height = values().height ?? "";
|
||||
const low = height !== "" && !(Number(height) >= (want ?? 0));
|
||||
if (want !== null && (item.id === "width" || (item.id === "height" && low)))
|
||||
form().setValue(box, "height", want.toFixed(2));
|
||||
emit();
|
||||
},
|
||||
/** 새 시설 빈 높이 = 필요 수심(사용자가 넣은 것처럼 · 알림 없음 — [추가] · [수정]이 읽음). */
|
||||
fill: () => {
|
||||
const want = heightPending && !values().height ? need() : null;
|
||||
if (want === null) return;
|
||||
form().setValue(box, "height", want.toFixed(2));
|
||||
heightPending = false;
|
||||
},
|
||||
};
|
||||
};
|
||||
const fordFlowBox = overflow(() => fordForm, "ford");
|
||||
const fordFlowBox = overflowSync(
|
||||
() => fordForm,
|
||||
"ford",
|
||||
() => fordFlow,
|
||||
emit,
|
||||
);
|
||||
const guardFields = guardChoiceFields(
|
||||
() => chainage,
|
||||
() => fordForm.refresh(),
|
||||
() => fordForm.recount(),
|
||||
);
|
||||
// 유출 흙막이 단 수 = 흙막이 상자 수(관보호공과 같은 방향 · 「+ 흙막이」 없음 · 90-1)
|
||||
const fordForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: fordFlowBox.onChange,
|
||||
fields: guardChoiceFields(
|
||||
() => chainage,
|
||||
() => fordForm.refresh(),
|
||||
() => fordForm.recount(),
|
||||
),
|
||||
fields: (c, values) => [...guardFields(c, values), ...fordFlowBox.fields(c)],
|
||||
count: guardSoilCount(
|
||||
() => chainage,
|
||||
() => fordForm.state().outlet?.[0]?.structure,
|
||||
@@ -485,9 +467,15 @@ export function createFacilityOptionsForm(
|
||||
|
||||
// ── 물넘이 포장 = 사용자 구조물 표 03 명세(PLAN 52-3) — 경사각도(+ 월류 폭 · 높이 · 65-5) · 도수로/돌붙임 ·
|
||||
// 옛 바닥 경사 · 두께 · 길이 칸은 뺌(지난 정의 값) ──
|
||||
const paveFlowBox = overflow(() => paveForm, "slope");
|
||||
const paveFlowBox = overflowSync(
|
||||
() => paveForm,
|
||||
"slope",
|
||||
() => fordFlow,
|
||||
emit,
|
||||
);
|
||||
const paveForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: (c, item) => (syncChuteArea(paveForm, c, item), paveFlowBox.onChange(c, item)),
|
||||
fields: paveFlowBox.fields,
|
||||
});
|
||||
|
||||
// 10-A ⑲ 저장 규칙 글은 [?] 설명서 `구조물_<시설>.md` 로 옮김(54-2) — 병합은 `_Drainage_Facility_Merge`.
|
||||
@@ -605,7 +593,7 @@ export function createFacilityOptionsForm(
|
||||
},
|
||||
setFacility(facility, options = {}, designFlow = null, definition = null, fresh = false) {
|
||||
current = facility;
|
||||
heightPending = fresh;
|
||||
[fordFlowBox, paveFlowBox].forEach((box) => box.arm(fresh));
|
||||
hiddenOptions.remember(options);
|
||||
fordFlow = designFlow ?? null;
|
||||
if (facility === null) {
|
||||
|
||||
@@ -657,3 +657,11 @@
|
||||
.b05-structure__widening [aria-pressed="false"] .b05-structure__widening-area {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 월류 높이 안내(287-3) — 필요 수심 · 설계유량 없음 */
|
||||
.b05-structure__flow-hint {
|
||||
grid-column: 1 / -1;
|
||||
margin: 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,41 @@ pave.appendFordPavementOverlay(
|
||||
(e) => e,
|
||||
);
|
||||
|
||||
// 287-3 화면 길 — 폼 onChange 훅 그대로(세월교 · 물넘이 한 벌 overflowSync)
|
||||
globalThis.document.createElement = (tag) => new Node(tag);
|
||||
const overflowRun = (flowValue) => {
|
||||
const state = { slope: [{ width: "10", height: "0.05" }] };
|
||||
const form = {
|
||||
state: () => state,
|
||||
setValue: (c, i, v) => (state[c][0][i] = v),
|
||||
refresh: () => undefined,
|
||||
};
|
||||
let emitted = 0;
|
||||
const sync = calc.overflowSync(
|
||||
() => form,
|
||||
"slope",
|
||||
() => flowValue,
|
||||
() => (emitted += 1),
|
||||
);
|
||||
const edit = (item, value) => {
|
||||
state.slope[0][item] = value; // 폼이 값을 적고 훅을 부름(design_structure_form onEdit)
|
||||
sync.onChange({ id: "slope" }, { id: item });
|
||||
return state.slope[0].height;
|
||||
};
|
||||
return {
|
||||
afterWidth8: edit("width", "8"),
|
||||
afterClear: edit("height", ""),
|
||||
afterLow: edit("height", "0.01"),
|
||||
afterHigh: edit("height", "0.9"),
|
||||
hint: sync.fields({ id: "slope" }).map((el) => el.textContent),
|
||||
otherBoxHint: sync.fields({ id: "chute" }).length,
|
||||
emitted,
|
||||
};
|
||||
};
|
||||
|
||||
const out = {
|
||||
overflowFlow: overflowRun(14.5776),
|
||||
overflowNoFlow: overflowRun(null),
|
||||
shapeOrder: layer.children.map((k) =>
|
||||
(k.attrs.class ?? k.attrs["data-structure-id"]).split(" ").pop(),
|
||||
),
|
||||
|
||||
@@ -92,7 +92,6 @@ def test_월류_폭_넣으면_높이_자동_세월교_한_벌(ts):
|
||||
assert ts["heightByWidth"] and ts["heightByWidth"] > 0
|
||||
assert ts["heightNoFlow"] is None
|
||||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||||
assert 'const paveFlowBox = overflow(() => paveForm, "slope");' in form
|
||||
assert 'if (facility === "ford_pavement") paveFlowBox.fill();' in form
|
||||
|
||||
|
||||
@@ -135,3 +134,27 @@ def test_보라_손잡이는_계획고만_형상_누름은_고름만():
|
||||
assert "highlightShapes(card, pickedMarkId);" in section_view
|
||||
selection = _read("B06_Section", "B06_Section_UI_Page_Structures_Selection.ts")
|
||||
assert "sectionAt.ford_pavement?.chainage_m" in selection
|
||||
|
||||
|
||||
def test_월류_폭_높이_화면_길_유량_있음(ts):
|
||||
got = ts["overflowFlow"]
|
||||
assert got["afterWidth8"] == "0.26" # 폭 8 → 필요 수심(유량 14.58 ㎥/s)
|
||||
assert got["afterClear"] == "0.26" # 높이 비움 → 다시 채움
|
||||
assert got["afterLow"] == "0.26" # 필요 수심 아래 → 끌어올림
|
||||
assert got["afterHigh"] == "0.9" # 올려 적은 값은 그대로
|
||||
assert got["hint"] == ["필요 수심 0.26 이상"] and got["otherBoxHint"] == 0
|
||||
assert got["emitted"] == 4
|
||||
|
||||
|
||||
def test_월류_폭_높이_화면_길_유량_없음(ts):
|
||||
got = ts["overflowNoFlow"]
|
||||
assert got["afterWidth8"] == "0.05" and got["afterClear"] == "" # 손 입력 그대로
|
||||
assert got["hint"] == ["설계유량 없음 — 수기 입력"]
|
||||
|
||||
|
||||
def test_세월교_물넘이_한_벌_안내_훅():
|
||||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||||
flat = " ".join(form.split())
|
||||
assert 'overflowSync( () => fordForm, "ford", () => fordFlow, emit, )' in flat
|
||||
assert 'overflowSync( () => paveForm, "slope", () => fordFlow, emit, )' in flat
|
||||
assert "...fordFlowBox.fields(c)" in form and "fields: paveFlowBox.fields," in form
|
||||
|
||||
Reference in New Issue
Block a user