feat(b06): 배관 횡단도 관 고름 강조 · 좌측 「관」 상자 강조(118-1)
- 관 판정면(벽 판정면 아래 — 겹친 자리는 벽) · 누르면 관 두 겹 is-active - 고름 전용 키 RevetPick(RevetKey | "pipe") — 조정 함수는 RevetKey 그대로 - 조정창 없음(show "pipe" = 숨김 · side "pipe") · 좌측 fieldset[data-spec-container="pipe"] 강조 + 스크롤 - 좌측 「관」 상자 칸 고침 = 관 고름 · B05 3D pipe 키 → B06 관 고름 - 107-1 벽 · 본체 동시 고름 금지 그대로 · 관 숨긴 카드(링크 · 세월교 · 박스)엔 판정면 없음 - 시험 test_118_pipe_pick(helper_118_pipe_pick.cjs) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
This commit is contained in:
@@ -154,11 +154,14 @@ function relocateSideRows(panelRoot: HTMLElement, scroll = true): void {
|
||||
clearSideSlots();
|
||||
const side = panelRoot.dataset.side;
|
||||
// 세월교 · BOX 본체 = 옮길 줄 없이 그 명세의 본체 상자만 강조(66-1) — 날개벽 칸이 든 명세 폼에서 찾음
|
||||
if (side === "body") {
|
||||
const body = sideSlots?.wingInlet
|
||||
.closest(".b05-structure__spec")
|
||||
// 관 = 배관 명세 폼의 「관」 상자만 강조(118-1)
|
||||
if (side === "body" || side === "pipe") {
|
||||
const body = (side === "pipe" ? sideSlots?.inlet : sideSlots?.wingInlet)
|
||||
?.closest(".b05-structure__spec")
|
||||
?.querySelector<HTMLElement>(
|
||||
'fieldset[data-spec-container="ford"], fieldset[data-spec-container="body"]',
|
||||
side === "pipe"
|
||||
? 'fieldset[data-spec-container="pipe"]'
|
||||
: 'fieldset[data-spec-container="ford"], fieldset[data-spec-container="body"]',
|
||||
);
|
||||
if (!body || body.closest("[hidden]")) return;
|
||||
setGroupLabel(body, "");
|
||||
|
||||
@@ -56,9 +56,11 @@ function polygon(
|
||||
// "own" = 배관과 무관한 **독립 기슭막이**(구조물 정본 D군). 조정창·조작값 저장은
|
||||
// 배관 벽과 같은 체계를 그대로 쓴다(2026-08-28 사용자: 배관측점처럼 이동).
|
||||
export type RevetKey = "inlet" | "outlet" | "own" | `extra${number}` | `bextra${number}`;
|
||||
/** 고름 전용 키 — 벽 키 + 관(118-1 · 조정창 없음 · 강조만). 조정 함수에는 `RevetKey` 만. */
|
||||
export type RevetPick = RevetKey | "pipe";
|
||||
|
||||
/** 벽 강조를 카드 재생성 없이 갈아 끼우는 setter. */
|
||||
export type RevetHighlightSetter = (key: RevetKey | null) => void;
|
||||
export type RevetHighlightSetter = (key: RevetPick | null) => void;
|
||||
|
||||
/**
|
||||
* 배수관 세트 오버레이 — computeCulvertLayout 결과를 그린다.
|
||||
@@ -72,7 +74,7 @@ export function appendCulvertOverlay(
|
||||
layout: CulvertLayout,
|
||||
x: (offset: number) => number,
|
||||
toDisplayY: (elevation: number) => number,
|
||||
onSelectRevet?: (key: RevetKey) => void,
|
||||
onSelectRevet?: (key: RevetPick) => void,
|
||||
/** 관을 그리지 않는다 — 관은 한 측점에만 있고, 연장이 넘어온 옆 측점 카드에는
|
||||
* 기슭막이만 링크로 보인다(2026-08-24 사용자). */
|
||||
hidePipe?: boolean,
|
||||
@@ -311,6 +313,8 @@ export function appendCulvertOverlay(
|
||||
elevation: corner.top.elevation + normal.elevation * delta,
|
||||
},
|
||||
});
|
||||
const pipeShapes: SVGPolygonElement[] = []; // 관 두 겹 — 관 고름 강조(118-1)
|
||||
let pipeHitPoints: Array<[number, number]> = [];
|
||||
const pipeLayers = hidePipe
|
||||
? []
|
||||
: ([
|
||||
@@ -320,18 +324,16 @@ export function appendCulvertOverlay(
|
||||
for (const [delta, cls] of pipeLayers) {
|
||||
const a = shifted(inletFinal, delta);
|
||||
const b = shifted(outletFinal, delta);
|
||||
layer.append(
|
||||
polygon(
|
||||
[
|
||||
[x(a.bottom.offset), toDisplayY(a.bottom.elevation)],
|
||||
[x(b.bottom.offset), toDisplayY(b.bottom.elevation)],
|
||||
[x(b.top.offset), toDisplayY(b.top.elevation)],
|
||||
[x(a.top.offset), toDisplayY(a.top.elevation)],
|
||||
],
|
||||
cls,
|
||||
tip,
|
||||
),
|
||||
);
|
||||
const points: Array<[number, number]> = [
|
||||
[x(a.bottom.offset), toDisplayY(a.bottom.elevation)],
|
||||
[x(b.bottom.offset), toDisplayY(b.bottom.elevation)],
|
||||
[x(b.top.offset), toDisplayY(b.top.elevation)],
|
||||
[x(a.top.offset), toDisplayY(a.top.elevation)],
|
||||
];
|
||||
if (!pipeHitPoints.length) pipeHitPoints = points; // 외경 = 판정면
|
||||
const shape = polygon(points, cls, tip);
|
||||
pipeShapes.push(shape);
|
||||
layer.append(shape);
|
||||
}
|
||||
drawOutletFill();
|
||||
|
||||
@@ -340,6 +342,15 @@ export function appendCulvertOverlay(
|
||||
// 먼저 먹는다(2026-08-21 화면 확인). 그리기 순서(벽 → 관 → 보호공)는 그대로 두고
|
||||
// 판정면만 위로 올린다. 관 자체의 툴팁은 벽 밖 구간에서 그대로 뜬다.
|
||||
if (onSelectRevet) {
|
||||
// 관 판정면은 벽 판정면 **아래** — 겹친 자리는 벽이 이김(118-1).
|
||||
if (pipeHitPoints.length) {
|
||||
const hit = polygon(pipeHitPoints, "b06-chart__culvert-revet-hit", `${tip} — 관 선택`);
|
||||
hit.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
onSelectRevet("pipe");
|
||||
});
|
||||
layer.append(hit);
|
||||
}
|
||||
for (const { wall, key: wallKey } of wallsInOrder) {
|
||||
const hit = polygon(
|
||||
wall.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]),
|
||||
@@ -375,5 +386,6 @@ export function appendCulvertOverlay(
|
||||
for (const [role, shape] of revetShapes) shape.classList.toggle("is-active", role === key);
|
||||
// 유입이 집수정이면 부재 전체를 함께 강조한다(2026-08-22 사용자 ③).
|
||||
for (const shape of basinShapes) shape.classList.toggle("is-active", key === "inlet");
|
||||
for (const shape of pipeShapes) shape.classList.toggle("is-active", key === "pipe");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
import { bodyAbsolute } from "./B06_Section_UI_Cross_Body_Guard";
|
||||
import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch";
|
||||
import { computeCulvertLayout } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { CulvertLayout, InletStructureChoice, RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type {
|
||||
CulvertLayout,
|
||||
InletStructureChoice,
|
||||
RevetKey,
|
||||
RevetPick,
|
||||
} from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { BasinAdjust, CulvertDesignTrim } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import {
|
||||
basinSpanOfSpec,
|
||||
@@ -31,12 +36,12 @@ export interface RevetOffsetControl {
|
||||
adjustFor: (section: CrossSection, role: RevetKey) => WallAdjust;
|
||||
/** 이 측점이 **실제로 손댄** 값만 — 없으면 null(자동·상속과 구분해야 한다). */
|
||||
storedAdjustFor: (section: CrossSection, role: RevetKey) => WallAdjust | null;
|
||||
selectedFor: (section: CrossSection) => RevetKey | null;
|
||||
selectedFor: (section: CrossSection) => RevetPick | null;
|
||||
/** 이 카드에서 **강조**할 벽 — 연동으로 이어진 옆 측점이 고른 벽도 켠다
|
||||
* (2026-08-30 사용자: 한 기슭막이는 그 연장 안 모든 횡단도에서 같이 강조된다).
|
||||
* 조정창은 고른 카드 하나에만 뜨므로 `selectedFor`와 따로 둔다. */
|
||||
highlightFor: (section: CrossSection) => RevetKey | null;
|
||||
select: (chainageM: number, key: RevetKey | null) => void;
|
||||
highlightFor: (section: CrossSection) => RevetPick | null;
|
||||
select: (chainageM: number, key: RevetPick | null) => void;
|
||||
/** 선택이 바뀌면 화면이 연동 카드 강조를 다시 칠한다 — 목록 쪽이 꽂는다. */
|
||||
onSelectionChange?: () => void;
|
||||
/** 기하가 실제로 적용한 조작값을 되받아 담는다(한계에 걸린 요청값을 잘라 낸다). */
|
||||
@@ -382,7 +387,7 @@ export function restrictToSide(layout: CulvertLayout, side: string | undefined):
|
||||
export function computeCardCulvert(
|
||||
section: CrossSection,
|
||||
sourceSamples: SectionSample[],
|
||||
activeRevet: RevetKey | null,
|
||||
activeRevet: RevetPick | null,
|
||||
revetOffset?: RevetOffsetControl,
|
||||
inletStructure?: InletStructureControl,
|
||||
extraWalls?: ExtraWallControl,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* 폼 칸에 옮겨 붙인다(`_Adjust_Dock`). 폼과 같은 값(형태 · 높이 · 길이 · 전 · 후)은 만들지 않는다.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { InletStructureChoice, RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { InletStructureChoice, RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { REVET_FORMS } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { BasinAdjust, WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type { SpanValues } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
@@ -97,7 +97,7 @@ export interface StructurePanelDeps {
|
||||
export interface StructurePanelHandle {
|
||||
root: HTMLElement;
|
||||
/** null이면 숨긴다. 값이 오면 그 구조물 기준으로 다시 그린다. */
|
||||
show: (key: RevetKey | null) => void;
|
||||
show: (key: RevetPick | null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,9 +494,12 @@ export function buildStructurePanel(
|
||||
|
||||
return {
|
||||
root,
|
||||
show: (key) => {
|
||||
show: (pick) => {
|
||||
// 관 = 조정창 없음 · 좌측 「관」 상자만 강조(118-1 · 세월교 본체 꼴)
|
||||
const key = pick === "pipe" ? null : pick;
|
||||
current = key;
|
||||
root.classList.toggle("is-hidden", key === null);
|
||||
root.dataset.side = pick === "pipe" ? "pipe" : "";
|
||||
document.removeEventListener("keydown", onArrowKey);
|
||||
drag.detach();
|
||||
if (!key) return;
|
||||
|
||||
@@ -55,7 +55,7 @@ import { attachZoomPan, buildZoomControls, cardZoomStates } from "./B06_Section_
|
||||
import type { CrossWidthActions } from "./B06_Section_UI_Cross_View_Zoom";
|
||||
import type { CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope";
|
||||
import { createCrossAxes, IDENTITY_VIEW } from "./B06_Section_UI_Cross_Axes";
|
||||
import type { RevetHighlightSetter, RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { RevetHighlightSetter, RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { REVET_FORM_DEFAULT } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import {
|
||||
@@ -82,7 +82,7 @@ export interface CrossCardElement extends HTMLElement {
|
||||
applySelection?: (selected: boolean, areaKey: CrossAreaKey | null) => void;
|
||||
/** 벽 강조만 갈아 끼운다 — 연동으로 이어진 카드도 같이 켜진다(2026-08-30 사용자).
|
||||
* 카드를 다시 만들지 않으므로 줌·팬이 살아남는다. */
|
||||
applyRevetHighlight?: (key: RevetKey | null) => void;
|
||||
applyRevetHighlight?: (key: RevetPick | null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,10 +180,10 @@ export function createCrossSectionCard(
|
||||
let activeArea: CrossAreaKey | null = selected ? (initialAreaKey ?? null) : null;
|
||||
let setBandActive: AreaHighlightSetter = () => undefined;
|
||||
let setChipActive: AreaHighlightSetter = () => undefined;
|
||||
let activeRevet: RevetKey | null = revetOffset?.selectedFor(section) ?? null;
|
||||
let activeRevet: RevetPick | null = revetOffset?.selectedFor(section) ?? null;
|
||||
/** 이 카드에서 **강조**할 벽. 연동으로 이어진 옆 측점이 고른 벽도 켠다 —
|
||||
* 조정창(`activeRevet`)은 고른 카드 하나에만 뜬다(2026-08-30 사용자). */
|
||||
let highlightRevet: RevetKey | null = revetOffset?.highlightFor(section) ?? activeRevet;
|
||||
let highlightRevet: RevetPick | null = revetOffset?.highlightFor(section) ?? activeRevet;
|
||||
let setRevetActive: RevetHighlightSetter = () => undefined;
|
||||
let showRevetControl: (visible: boolean) => void = () => undefined;
|
||||
let culvertInletIsBasin = false; // 유입이 집수정인가 — 조정창 표시·제목 분기
|
||||
@@ -222,7 +222,7 @@ export function createCrossSectionCard(
|
||||
},
|
||||
selectCard: () => onSelect(section.station_id),
|
||||
});
|
||||
const toggleRevet = (key: RevetKey): void => {
|
||||
const toggleRevet = (key: RevetPick): void => {
|
||||
const wasSelected = isSelected;
|
||||
activeRevet = activeRevet === key ? null : key;
|
||||
showRevetControl(activeRevet !== null);
|
||||
@@ -654,7 +654,7 @@ export function createCrossSectionCard(
|
||||
extraState: () => culvertExtraState,
|
||||
basinExtraState: () => culvertBasinExtraState,
|
||||
activeRevet: () => activeRevet,
|
||||
setActiveRevet: (key: RevetKey | null) => {
|
||||
setActiveRevet: (key: RevetPick | null) => {
|
||||
activeRevet = key;
|
||||
},
|
||||
toggleRevet,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import type { RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import {
|
||||
FILL_SLOPE_RATIO_MIN,
|
||||
PIPE_WALL_DEFAULT_RUN_M,
|
||||
@@ -81,9 +81,9 @@ export interface StructurePanelContext {
|
||||
inletOptions: () => { revetAllowed: boolean; basinLUAllowed: boolean };
|
||||
extraState: () => { canAdd: boolean; count: number };
|
||||
basinExtraState: () => { canAdd: boolean; count: number };
|
||||
activeRevet: () => RevetKey | null;
|
||||
setActiveRevet: (key: RevetKey | null) => void;
|
||||
toggleRevet: (key: RevetKey) => void;
|
||||
activeRevet: () => RevetPick | null;
|
||||
setActiveRevet: (key: RevetPick | null) => void;
|
||||
toggleRevet: (key: RevetPick) => void;
|
||||
/** 창 스크롤 자리를 기억할 카드 키(측점 id). */
|
||||
scrollKey: string;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
SectionDetailResponse,
|
||||
StoredWallSpan,
|
||||
} from "./B06_Section_Api_Fetch";
|
||||
import type { InletStructureChoice, RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { InletStructureChoice, RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type { BasinAdjust, WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type {
|
||||
@@ -180,7 +180,7 @@ export function createStationControls(deps: StationControlDeps): StationControls
|
||||
const revetShifts = revetSession.values;
|
||||
/** 지금 고른 벽 **하나** — 조정창이 뜬 측점(누가거리 키)과 벽 키(2026-08-30 개편:
|
||||
* 강조는 연동으로 이어진 카드 전부, 조정창은 고른 카드 하나). */
|
||||
let revetSelection: { at: string; key: RevetKey } | null = null;
|
||||
let revetSelection: { at: string; key: RevetPick } | null = null;
|
||||
const revetKey = (chainageM: number, role: RevetKey): string => `${chainageM.toFixed(2)}:${role}`;
|
||||
|
||||
function loadRevetShifts(): void {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import type { RevetKey } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import type { StructurePickHandoff } from "../B05_Profile/B05_Profile_UI_Structure_Pick_Session";
|
||||
import {
|
||||
coversChainage,
|
||||
@@ -23,13 +23,13 @@ import {
|
||||
type SelectableStructure,
|
||||
} from "../A00_Common/design/design_selection";
|
||||
|
||||
/** 기슭막이 조정창이 받는 부재키 — 집수정·배관·본체는 여기서 빠진다. */
|
||||
const REVET_KEYS = /^(inlet|outlet|own|extra\d+|bextra\d+)$/;
|
||||
/** 벽 고름이 받는 부재키 — 관은 강조만(118-1) · 집수정·본체는 여기서 빠진다. */
|
||||
const REVET_KEYS = /^(inlet|outlet|own|extra\d+|bextra\d+|pipe)$/;
|
||||
|
||||
interface PickTargets {
|
||||
focusStation: (stationId: string) => void;
|
||||
refreshCard: (chainageM: number) => void;
|
||||
revetSelect: (chainageM: number, key: RevetKey) => void;
|
||||
revetSelect: (chainageM: number, key: RevetPick) => void;
|
||||
fordSelect: (chainageM: number, role: "inlet" | "outlet" | "body") => void;
|
||||
boxSelect: (chainageM: number, role: "left" | "right" | "body") => void;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export function applyStructurePick(
|
||||
targets.boxSelect(target.chainage_m, key);
|
||||
} else {
|
||||
if (!REVET_KEYS.test(key)) return;
|
||||
targets.revetSelect(target.chainage_m, key as RevetKey);
|
||||
targets.revetSelect(target.chainage_m, key as RevetPick);
|
||||
}
|
||||
targets.refreshCard(target.chainage_m);
|
||||
});
|
||||
|
||||
@@ -55,7 +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 type { RevetPick } from "./B06_Section_UI_Cross_Culvert";
|
||||
import { withSpecDefaults } from "./B06_Section_UI_Page_Structures_Spec";
|
||||
|
||||
const PIPE_ADD_GUIDE = "계곡 통과 시설을 넣고 빼려면 프로젝트를 먼저 여세요.";
|
||||
@@ -616,9 +616,9 @@ export function wireStructureSelection(
|
||||
};
|
||||
|
||||
// 관 상자 칸을 고치거나 고르면 그 부분을 고름 — 횡단에서 누른 것과 같은 길(54-30)
|
||||
const keyOf = (box: HTMLElement): RevetKey | null => {
|
||||
const keyOf = (box: HTMLElement): RevetPick | null => {
|
||||
const id = box.dataset.specContainer;
|
||||
if (id === "inlet" || id === "outlet") return id;
|
||||
if (id === "inlet" || id === "outlet" || id === "pipe") return id;
|
||||
return id === "soil_add" ? `extra${Number(box.dataset.specIndex)}` : null;
|
||||
};
|
||||
const pickBox = (box: HTMLElement): void => {
|
||||
|
||||
Reference in New Issue
Block a user