fix(b06): 안 골라진 카드 첫 누름 = 눌린 대상 — 카드 고름이 연동 관으로 폼을 옮기지 않음 · 폼 여는 동안 선 도수로 상자를 새 상자로 안 고름 (294-8 · 295-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
@@ -57,6 +57,7 @@ import {
|
||||
import { createB06StructuresPanel } from "../../B06_Section/B06_Section_UI_Page_Structures_Panel";
|
||||
import {
|
||||
isWallSelecting,
|
||||
wallOwnerM,
|
||||
wireStructureSelection,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Structures_Selection";
|
||||
import "../../B06_Section/B06_Section_UI_Style.css";
|
||||
@@ -392,9 +393,12 @@ export async function mountCrossView(
|
||||
releaseCross(); // 카드 다시 누름 = 풀기 한 길(범위 테두리 · 형상까지 · 233-2)
|
||||
return;
|
||||
}
|
||||
const owner = isWallSelecting() ? stationControls.structureSpan.ownerOf(target) : null;
|
||||
// 찍은 부재가 이미 연 시설이 먼저 — 물넘이 콘크리트가 걸친 연동 관으로 안 감(294-8)
|
||||
const owner = isWallSelecting()
|
||||
? (wallOwnerM() ?? stationControls.structureSpan.ownerOf(target)?.chainage_m)
|
||||
: undefined;
|
||||
const preferred = structuresPanel.preferred() !== null;
|
||||
const opened = structuresPanel.showAtChainage(owner?.chainage_m ?? target.chainage_m);
|
||||
const opened = structuresPanel.showAtChainage(owner ?? target.chainage_m);
|
||||
// 카드 하나 고름 = 그 측점(구조물 id 는 목록 · 알약 · 넘김이 고른 것만 · 189-1)
|
||||
const picked = preferred ? opened : { chainageM: opened.chainageM, structureId: null };
|
||||
if (!isWallSelecting()) selectDesign(projectId, picked, { from: "b06" });
|
||||
|
||||
@@ -21,15 +21,23 @@ import { BERM_TYPE_ID } from "./B06_Section_UI_Page_Persist";
|
||||
* 이 값으로 "카드만 고른 경우"를 가려낸다(2026-08-29 사용자: 측점 선택 때 구조물이
|
||||
* 자동으로 골라지면 안 된다). 같은 턴에서만 참이다. */
|
||||
let wallSelecting = false;
|
||||
/** 이번 턴에 찍은 부재가 폼에 올린 시설 자리 — 안 골라진 카드 첫 누름에 카드 고름이 연동 관으로 다시 옮기지 않게(294-8). */
|
||||
let wallOwner: number | null = null;
|
||||
|
||||
export function isWallSelecting(): boolean {
|
||||
return wallSelecting;
|
||||
}
|
||||
|
||||
function markWallSelecting(): void {
|
||||
export function wallOwnerM(): number | null {
|
||||
return wallSelecting ? wallOwner : null;
|
||||
}
|
||||
|
||||
function markWallSelecting(owner: number | null = null): void {
|
||||
wallSelecting = true;
|
||||
wallOwner = owner;
|
||||
queueMicrotask(() => {
|
||||
wallSelecting = false;
|
||||
wallOwner = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,12 +89,12 @@ export function wireStructureSelection(
|
||||
stationControls.revetOffset.select = (chainageM, key, ownerM) => {
|
||||
// 다른 카드 부재를 고르는 중에 옛 카드가 제 선택을 푸는 것 — 폼 · 선택 칸은 새 것 몫(53-1)
|
||||
const nested = wallSelecting;
|
||||
// 푸는 것은 고름이 아님 — 표시를 켜면 뒤이은 카드 고름이 선택 칸에 안 적힘(53-1)
|
||||
if (key) markWallSelecting();
|
||||
origRevet(chainageM, key);
|
||||
// 도수로 띠는 자기 측점 시설 — 이웃 관 연장 안이어도 그 관 폼을 안 엶(152-4)
|
||||
const owner =
|
||||
ownerM ?? (key === "chute" ? chainageM : ownerChainageOf(chainageM, key ?? undefined));
|
||||
// 푸는 것은 고름이 아님 — 표시를 켜면 뒤이은 카드 고름이 선택 칸에 안 적힘(53-1)
|
||||
if (key) markWallSelecting(owner);
|
||||
origRevet(chainageM, key);
|
||||
if (key) remember?.(owner, key);
|
||||
if (key) panel.showAtChainage(owner);
|
||||
else if (!nested) {
|
||||
@@ -163,6 +171,18 @@ export function wireStructureSelection(
|
||||
if (!pointing) pressAt(event.target as HTMLElement);
|
||||
});
|
||||
panel.root.addEventListener("change", ({ target: el }) => pickAt(el as HTMLElement));
|
||||
// 시설을 폼에 여는 동안의 그림(종류 칸 → 값 채움 두 번)은 새 상자가 아님 — 마지막 상자(도수로)를
|
||||
// 고르면 누른 선 · 형상을 덮음(294-8 · 295-4)
|
||||
let opening = false;
|
||||
const origShow = panel.showAtChainage;
|
||||
panel.showAtChainage = (chainageM) => {
|
||||
opening = true;
|
||||
try {
|
||||
return origShow(chainageM);
|
||||
} finally {
|
||||
opening = false;
|
||||
}
|
||||
};
|
||||
// 상자가 새로 서면(단 늘림 · [추가]) 그 상자를 고름 — 같은 시설을 다시 그린 때만
|
||||
let painted: { chainageM: number | null; keys: Set<string> } | null = null;
|
||||
panel.root.addEventListener("specpaint", () => {
|
||||
@@ -171,7 +191,7 @@ export function wireStructureSelection(
|
||||
const before = painted?.chainageM === chainageM ? painted.keys : null;
|
||||
painted = { chainageM, keys: new Set(boxes.map(fieldOf)) };
|
||||
const born = before ? boxes.filter((box) => !before.has(fieldOf(box))).at(-1) : undefined;
|
||||
if (!born || chainageM === null) return;
|
||||
if (!born || chainageM === null || opening) return;
|
||||
revealAt(chainageM, null);
|
||||
pickBox(born);
|
||||
});
|
||||
|
||||
@@ -191,8 +191,120 @@ DS.selectDesign(P, { chainageM: 110, structureId: null }, { partKey: "", from: "
|
||||
DS.selectDesign(P, { chainageM: 110, structureId: null }, { from: "b06" }); // 그다음 카드 고름
|
||||
out.firstLine = { oldKey, newKey: DS.readSelection(P)?.partKey ?? null };
|
||||
|
||||
// 다시 누름 = 풂 — 고름 표시(마이크로태스크)가 풀린 뒤
|
||||
queueMicrotask(() => {
|
||||
/* ── ⑤ 안 골라진 카드 첫 누름(294-8 · 295-4) — 페이지 흐름: 부재 고름 → 카드 고름 리스너 → 폼 엶 ──
|
||||
* 폼은 열 때 두 번 그림(종류 칸 = 기본값 → 시설 값) — 둘째 그림에 도수로 상자가 섬. */
|
||||
const tick = () => new Promise((done) => setTimeout(done, 0));
|
||||
const flowSections = [
|
||||
{ chainage_m: 90, station_id: "s90", ford_pavement: { chainage_m: 90, chute: {} } },
|
||||
{ chainage_m: 95, station_id: "s95", culvert: { c: 95, chute: {} } },
|
||||
{ chainage_m: 110, station_id: "s110", ford_pavement: { chainage_m: 110, chute: {} } },
|
||||
];
|
||||
const flowLog = [];
|
||||
const box = (id) => {
|
||||
const el = {
|
||||
dataset: { specContainer: id, specIndex: "0" },
|
||||
closest: (sel) => (sel.startsWith("fieldset") ? el : null),
|
||||
};
|
||||
return el;
|
||||
};
|
||||
let shown = null;
|
||||
let boxes = [];
|
||||
const handlers = {};
|
||||
let flowPicked = null;
|
||||
const flowControls = {
|
||||
revetOffset: {
|
||||
select: (at, key) => {
|
||||
flowLog.push(["select", at, key]);
|
||||
flowPicked = key ? { at, key } : null;
|
||||
},
|
||||
highlightFor: (s) => (flowPicked?.at === s.chainage_m ? flowPicked.key : null),
|
||||
},
|
||||
ford: { select: () => {} },
|
||||
box: { select: () => {} },
|
||||
structureSpan: {
|
||||
// 연동 관 95 m 의 유출 흙막이가 4+10(90 m) 카드까지 옴(299)
|
||||
ownerOf: (s) => (s.culvert ? s : s.chainage_m === 90 ? flowSections[1] : null),
|
||||
},
|
||||
inletStructure: { set: () => {}, valueFor: () => "auto" },
|
||||
};
|
||||
const paint = (ids) => {
|
||||
boxes = ids.map(box);
|
||||
handlers.specpaint?.forEach((fn) => fn());
|
||||
};
|
||||
const flowPanel = {
|
||||
root: {
|
||||
addEventListener: (type, fn) => (handlers[type] ??= []).push(fn),
|
||||
querySelectorAll: () => boxes,
|
||||
contains: () => false,
|
||||
},
|
||||
facility: { onInletStructureChange: () => {}, setInletStructure: () => {} },
|
||||
currentChainage: () => shown,
|
||||
showAtChainage: (at) => {
|
||||
flowLog.push(["form", at]);
|
||||
shown = at;
|
||||
if (at === null) return { chainageM: null, structureId: null };
|
||||
paint(at === 95 ? ["pipe"] : ["slope"]); // 종류 칸 = 기본값
|
||||
paint(at === 95 ? ["pipe", "chute_add"] : ["slope", "chute"]); // 시설 값 = 도수로 상자 섬
|
||||
return { chainageM: at, structureId: null };
|
||||
},
|
||||
releasePipe: () => flowLog.push(["release"]),
|
||||
getStructures: () => [],
|
||||
selectStructure: () => {},
|
||||
};
|
||||
SEL.wireStructureSelection(
|
||||
flowControls,
|
||||
() => ({ cross_sections: flowSections }),
|
||||
flowPanel,
|
||||
(at, key) => flowLog.push(["remember", at, key]),
|
||||
);
|
||||
// 카드 고름 리스너(`design_view_cross` setStationSelectListener) 의 폼 자리 고르기 그대로
|
||||
const cardSelected = (target) => {
|
||||
const owner = SEL.isWallSelecting()
|
||||
? (SEL.wallOwnerM() ?? flowControls.structureSpan.ownerOf(target)?.chainage_m)
|
||||
: undefined;
|
||||
flowControls.revetOffset.highlightFor(target);
|
||||
flowPanel.showAtChainage(owner ?? target.chainage_m);
|
||||
};
|
||||
const esc = () => {
|
||||
flowPicked = null;
|
||||
shown = null;
|
||||
boxes = [];
|
||||
};
|
||||
const flow = async (before, press) => {
|
||||
esc();
|
||||
before?.();
|
||||
await tick();
|
||||
flowLog.length = 0;
|
||||
press();
|
||||
const got = flowLog.map((entry) => [...entry]);
|
||||
await tick();
|
||||
return got;
|
||||
};
|
||||
// 형상 누름 = toggleRevet("pipe", 90) — 안 골라진 카드면 부재 고름 → onSelect
|
||||
const slabPress = () => {
|
||||
flowControls.revetOffset.select(90, "pipe", 90);
|
||||
cardSelected(flowSections[0]);
|
||||
};
|
||||
// 보라 선 첫 누름 = pickLine — 남은 부재 풂 → onSelect
|
||||
const linePress = () => {
|
||||
flowControls.revetOffset.select(110, null);
|
||||
cardSelected(flowSections[2]);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
// 다시 누름 = 풂 — 고름 표시(마이크로태스크)가 풀린 뒤
|
||||
await Promise.resolve();
|
||||
out.owner.release = run(90, null, 90);
|
||||
out.first = {
|
||||
slabAfterEsc: await flow(null, slabPress),
|
||||
slabAfterOther: await flow(() => flowPanel.showAtChainage(95), slabPress),
|
||||
lineAfterEsc: await flow(null, linePress),
|
||||
lineAfterOther: await flow(() => flowPanel.showAtChainage(95), linePress),
|
||||
};
|
||||
// 폼 안 [추가] · 단 늘림 = 그 상자 고름 그대로(54-30)
|
||||
flowPanel.showAtChainage(95);
|
||||
flowLog.length = 0;
|
||||
paint(["pipe", "chute_add", "soil_add"]);
|
||||
out.first.addBox = flowLog.map((entry) => [...entry]);
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
- 콘크리트 누름 = 그 물넘이(자기 id 누가거리) 고름 — 카드에 걸친 연동 관(95 m) · 자기 다른 관으로 안 감 ·
|
||||
자기 관 없는 카드의 「관」 되살림도 물넘이 · 관 몸통 누름은 자기 관 그대로 · 다시 누름 = 풂
|
||||
- 맹암거 형상 = 알림(목록이 id 로 고름) 그대로
|
||||
- 294-8 · 295-4 안 골라진 카드 첫 누름 = 눌린 대상 — 카드 고름이 연동 관으로 폼을 옮기지 않음 · 폼을 열 때
|
||||
두 번 그림(기본값 → 시설 값)에 선 도수로 상자를 「새 상자」로 골라 덮지 않음(Esc 뒤 · 다른 카드 고른 뒤)
|
||||
|
||||
실제 TS 를 `helper_294_7_ford_slab_pick.cjs` 가 Node 로 돌린다.
|
||||
"""
|
||||
@@ -84,3 +86,39 @@ def test_배선_경사각도_상자():
|
||||
# 폼에 물넘이(관 상자 없음) = 「경사각도」 상자(294-2 그대로)
|
||||
dock = _read("B06_Section_UI_Adjust_Dock.ts")
|
||||
assert 'side === "pipe" && (!own || own.closest("[hidden]")) ? visibleBox(SLOPE_BOX)' in dock
|
||||
|
||||
|
||||
def test_안_골라진_카드_콘크리트_첫_누름_294_8(out):
|
||||
f = out["first"]
|
||||
want = [["select", 90, "pipe"], ["remember", 90, "pipe"], ["form", 90], ["form", 90]]
|
||||
assert f["slabAfterEsc"] == want # 옛 = 폼 95 · {at:95, key:"chute"}
|
||||
assert f["slabAfterOther"] == want # 4+15 배관 고른 뒤도
|
||||
|
||||
|
||||
def test_안_골라진_카드_보라_선_첫_누름_295_4(out):
|
||||
f = out["first"]
|
||||
want = [["select", 110, None], ["remember", 110, ""], ["release"], ["form", 110]]
|
||||
assert f["lineAfterEsc"] == want # 옛 = {at:110, key:"chute"} · 선 풀림
|
||||
assert f["lineAfterOther"] == want
|
||||
|
||||
|
||||
def test_폼_안_새_상자는_그대로_고름(out):
|
||||
assert out["first"]["addBox"] == [
|
||||
["select", 95, "extra0"],
|
||||
["remember", 95, "extra0"],
|
||||
["form", 95],
|
||||
]
|
||||
|
||||
|
||||
def test_배선_카드_고름_리스너():
|
||||
with open(
|
||||
os.path.join(ROOT, "A00_Common", "design", "design_view_cross.ts"), encoding="utf-8"
|
||||
) as f:
|
||||
cross = " ".join(f.read().split())
|
||||
assert (
|
||||
"const owner = isWallSelecting() ? (wallOwnerM() ?? stationControls.structureSpan.ownerOf(target)?.chainage_m) : undefined;"
|
||||
in cross
|
||||
)
|
||||
assert "structuresPanel.showAtChainage(owner ?? target.chainage_m)" in cross
|
||||
sel = _read("B06_Section_UI_Page_Structures_Selection.ts")
|
||||
assert "if (!born || chainageM === null || opening) return;" in sel
|
||||
|
||||
Reference in New Issue
Block a user