- 중심점 = 보라 선 고름 끝 조각(판정 원 · 보라 선 위 · 다른 고름과 하나만) · 십자는 누름 통과 - 좌우 = 노선 편집기가 등록한 host(B05_Profile_UI_RouteEdit_Shift) — 0.5 m 안 노드 옮김 · 곡선 안 = 교각점 · 아니면 끼움 · 고정 노드 안 옮김 - 끄는 동안 · 키를 잇는 동안 십자만 옮겨 보임 · 놓을 때 한 번 커밋 - 뒤 측점은 계산 묶음이 조용히 · 그 측점 계획고 = 옮기기 전 값(계획고 편집) · 노선 + 계획고 = 되돌리기 한 걸음 - B06 을 바로 열어 편집기가 없으면 종단 보기를 숨긴 채 세움(design_page 등록) - 시험 test_356 · helper_356(노드 고름 · 뒤 측점 범위 · 높이 그대로 · 되돌리기 한 걸음 · 바로 열기) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
91 lines
4.0 KiB
TypeScript
91 lines
4.0 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_Pick_Order.ts
|
|
* 횡단 카드 고르기 차례(295-1) — 고를 자리가 좁은 것이 위(모달 `pickAt` 과 같은 잣대).
|
|
*
|
|
* 판정면(구조물 · 띠 · 형상)을 다 꽂은 뒤 넓이 큰 것부터 아래로 다시 세움(작은 것 위) ·
|
|
* 보라 선 판정(넓이 0 · 요구 185 사면 손잡이)은 맨 위 · 면적 띠(절 · 성토)는 맨 아래 그대로.
|
|
* 끌고 뗀 누름(3 px 넘게 움직임)은 고름 아님.
|
|
* 맹암거(306-2) = 안쪽 판정면은 맨 아래(겹친 띠 · 구조물이 위) · 테두리 판정(투명 굵은 선)이 면 판정 맨 위.
|
|
* ========================================================================== */
|
|
|
|
import { thickenDown } from "./B06_Section_UI_Cross_Shape_Pick";
|
|
import { CENTER_HIT_CLASS } from "./B06_Section_UI_Cross_Center";
|
|
|
|
/** 면 판정 — 구조물(벽 · 관 · 박스 · 도수로 띠 등) · 물넘이포장 · 맹암거 형상. */
|
|
const SURFACE_HIT_CLASSES = ["b06-chart__culvert-revet-hit", "b06-chart__shape-hit"];
|
|
/** 선 판정 — 보라 설계선 조각(`_Cross_Design_Pick`). */
|
|
const LINE_HIT_CLASS = "b06-chart__design-hit";
|
|
/** 맹암거 몸(그림) · 테두리 판정 클래스(306-2). */
|
|
const UNDERDRAIN_GROUP = "b06-chart__underdrain";
|
|
const UNDERDRAIN_BODY = "b06-chart__underdrain-body";
|
|
const EDGE_CLASS = "b06-chart__shape-hit--edge";
|
|
/** 끌기로 볼 움직임(px) — 형상 고름(`SHAPE_DRAG_PX`)과 같은 값. */
|
|
const DRAG_PX = 3;
|
|
|
|
/** `points` 속성 다각형 넓이(px²) — 점이 셋 못 되면 0. */
|
|
export function pointsArea(points: string | null): number {
|
|
const pts = (points ?? "")
|
|
.trim()
|
|
.split(/\s+/)
|
|
.map((pair) => pair.split(",").map(Number))
|
|
.filter((p) => p.length === 2 && p.every(Number.isFinite));
|
|
if (pts.length < 3) return 0;
|
|
let twice = 0;
|
|
pts.forEach(([x0, y0], i) => {
|
|
const [x1, y1] = pts[(i + 1) % pts.length];
|
|
twice += x0 * y1 - x1 * y0;
|
|
});
|
|
return Math.abs(twice) / 2;
|
|
}
|
|
|
|
/** 판정면을 넓이 차례로 다시 세우고 보라 선 판정을 맨 위로 · 끌기 누름을 막음. */
|
|
export function stackPickTargets(layer: SVGElement): void {
|
|
const kids = [...layer.children];
|
|
// 맹암거 판정면 = 형상 고름이 몸 둘레(`thickenDown`)로 꽂은 것
|
|
const bodies = new Set(
|
|
kids
|
|
.filter((el) => el.classList.contains(UNDERDRAIN_GROUP))
|
|
.flatMap((group) => [...group.children])
|
|
.filter((el) => el.classList.contains(UNDERDRAIN_BODY))
|
|
.map((el) => thickenDown(el.getAttribute("points") ?? "")),
|
|
);
|
|
const inners = kids.filter(
|
|
(el) =>
|
|
el.classList.contains("b06-chart__shape-hit") && bodies.has(el.getAttribute("points") ?? ""),
|
|
);
|
|
const edges = inners.map((inner) => {
|
|
layer.prepend(inner);
|
|
const edge = inner.cloneNode() as SVGElement;
|
|
edge.classList.add(EDGE_CLASS);
|
|
edge.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
inner.dispatchEvent(new MouseEvent("click")); // 고름은 안쪽 판정면 창구 하나
|
|
});
|
|
return edge;
|
|
});
|
|
const surfaces = kids
|
|
.filter((el) => SURFACE_HIT_CLASSES.some((name) => el.classList.contains(name)))
|
|
.filter((el) => !inners.includes(el))
|
|
.map((el) => ({ el, area: pointsArea(el.getAttribute("points")) }))
|
|
.sort((a, b) => b.area - a.area)
|
|
.map(({ el }) => el);
|
|
const lines = kids.filter((el) => el.classList.contains(LINE_HIT_CLASS));
|
|
const centers = kids.filter((el) => el.classList.contains(CENTER_HIT_CLASS)); // 중심점 = 맨 위(356-3)
|
|
layer.append(...surfaces, ...edges, ...lines, ...centers);
|
|
let down: { x: number; y: number } | null = null;
|
|
layer.addEventListener(
|
|
"pointerdown",
|
|
(event) => (down = { x: (event as PointerEvent).clientX, y: (event as PointerEvent).clientY }),
|
|
true,
|
|
);
|
|
layer.addEventListener(
|
|
"click",
|
|
(event) => {
|
|
const { clientX, clientY } = event as MouseEvent;
|
|
if (down && Math.hypot(clientX - down.x, clientY - down.y) > DRAG_PX) event.stopPropagation();
|
|
down = null;
|
|
},
|
|
true,
|
|
);
|
|
}
|