fix(B06·B05): 노면공 고름 세 갈래 · 형상 누름 = 목록 고름 · 카드 하나 = 그 측점(189-1)
- ② 카드 안 노면공 포장층 누름 = 목록 고름과 같은 길(범위 카드 전부 · 포장층 강조) — 카드 고름보다 먼저 가로챔 - ③ 카드 하나 누름 = 연 노면공 · 범위 강조 풂 · 그 카드 하나(165-1 노면공 지킴 · 167-3 재누름 그대로 없앰) - 카드 고름 세션 = 그 측점(구조물 id 는 목록 · 알약 · 넘김이 고른 것만) - 종단: 구조물 넘김이면 측점선 · 3D 띠 안 켬 - 새 시험 test_189_1_pave_pick · test_165 · test_188_1 기대값 41번 규칙으로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
@@ -379,6 +379,8 @@ export async function mountCrossView(
|
|||||||
);
|
);
|
||||||
// 횡단도(카드) 자체를 골라도 그 측점 구조물 정보를 폼에 올린다(2026-08-29 사용자).
|
// 횡단도(카드) 자체를 골라도 그 측점 구조물 정보를 폼에 올린다(2026-08-29 사용자).
|
||||||
// 연동으로 옆에서 넘어온 카드는 벽을 찍었을 때만 **소유 측점** 시설 · 카드만 고르면 일반 측점(79-6).
|
// 연동으로 옆에서 넘어온 카드는 벽을 찍었을 때만 **소유 측점** 시설 · 카드만 고르면 일반 측점(79-6).
|
||||||
|
// 카드 안 노면공 형상 누름 = 목록 고름과 같은 길(189-1)
|
||||||
|
sectionView.setPavementSelectListener((id) => structuresPanel.selectStructure(id));
|
||||||
sectionView.setStationSelectListener((stationId) => {
|
sectionView.setStationSelectListener((stationId) => {
|
||||||
const target = stationId
|
const target = stationId
|
||||||
? sectionDetail?.cross_sections.find((section) => section.station_id === stationId)
|
? sectionDetail?.cross_sections.find((section) => section.station_id === stationId)
|
||||||
@@ -389,9 +391,11 @@ export async function mountCrossView(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const owner = isWallSelecting() ? stationControls.structureSpan.ownerOf(target) : null;
|
const owner = isWallSelecting() ? stationControls.structureSpan.ownerOf(target) : null;
|
||||||
|
const preferred = structuresPanel.preferred() !== null;
|
||||||
const opened = structuresPanel.showAtChainage(owner?.chainage_m ?? target.chainage_m);
|
const opened = structuresPanel.showAtChainage(owner?.chainage_m ?? target.chainage_m);
|
||||||
// 카드만 고른 것도 남긴다 — 종단 보기로 가면 그 시설이 그대로 열린다.
|
// 카드 하나 고름 = 그 측점(구조물 id 는 목록 · 알약 · 넘김이 고른 것만 · 189-1)
|
||||||
if (!isWallSelecting()) selectDesign(projectId, opened, { from: "b06" });
|
const picked = preferred ? opened : { chainageM: opened.chainageM, structureId: null };
|
||||||
|
if (!isWallSelecting()) selectDesign(projectId, picked, { from: "b06" });
|
||||||
// 카드 고름은 그 카드 측점선으로 알약을 맞춤 — 일반 측점 구조물 · 옆 측점 이음 카드면
|
// 카드 고름은 그 카드 측점선으로 알약을 맞춤 — 일반 측점 구조물 · 옆 측점 이음 카드면
|
||||||
// 알약이 꺼지므로 연 것으로 다시 세움(관 알약 id = pipe-측점 · 53-1)
|
// 알약이 꺼지므로 연 것으로 다시 세움(관 알약 id = pipe-측점 · 53-1)
|
||||||
const pipeAt = structuresPanel.currentChainage();
|
const pipeAt = structuresPanel.currentChainage();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
restoreStructurePick,
|
restoreStructurePick,
|
||||||
wireStructurePick,
|
wireStructurePick,
|
||||||
} from "../../B05_Profile/B05_Profile_UI_Structure_Pick_Session";
|
} from "../../B05_Profile/B05_Profile_UI_Structure_Pick_Session";
|
||||||
import { NO_SELECTION, nearestWithin, selectDesign } from "./design_selection";
|
import { NO_SELECTION, nearestWithin, readSelection, selectDesign } from "./design_selection";
|
||||||
import type { DesignPageHost } from "./design_page";
|
import type { DesignPageHost } from "./design_page";
|
||||||
import {
|
import {
|
||||||
bindDesignView,
|
bindDesignView,
|
||||||
@@ -124,9 +124,10 @@ export async function mountProfileView(
|
|||||||
restorePick(); // 관 목록이 실린 뒤라야 폼이 그 시설을 찾는다.
|
restorePick(); // 관 목록이 실린 뒤라야 폼이 그 시설을 찾는다.
|
||||||
};
|
};
|
||||||
/** 측점선 강조 — 측점선이 없는 관 시설(물넘이포장 · 비정규 측점 아님)은 알약을 id 로 켬(53-2) ·
|
/** 측점선 강조 — 측점선이 없는 관 시설(물넘이포장 · 비정규 측점 아님)은 알약을 id 로 켬(53-2) ·
|
||||||
* 관도 없는 자리면 그 측점선 · 3D 띠(횡단에서 고른 일반 측점 · 188-1). */
|
* 관도 없는 자리면 그 측점선 · 3D 띠(횡단에서 고른 일반 측점 · 188-1) — 구조물 고름은 선 없음(189-1). */
|
||||||
const selectGraphAt = (id: string | null, chainageM: number | null): void => {
|
const selectGraphAt = (id: string | null, chainageM: number | null): void => {
|
||||||
const sections = currentSectionDetail?.cross_sections ?? [];
|
const structurePick = !!readSelection(activeProjectId)?.structureId;
|
||||||
|
const sections = structurePick ? [] : (currentSectionDetail?.cross_sections ?? []);
|
||||||
const target = graphTargetAt(id, chainageM, lastPipes ?? [], sections);
|
const target = graphTargetAt(id, chainageM, lastPipes ?? [], sections);
|
||||||
if ("pipeChainageM" in target) {
|
if ("pipeChainageM" in target) {
|
||||||
profilePanel.setSelectedStructure(`pipe-${target.pipeChainageM.toFixed(2)}`);
|
profilePanel.setSelectedStructure(`pipe-${target.pipeChainageM.toFixed(2)}`);
|
||||||
|
|||||||
@@ -64,26 +64,38 @@ function standardEdges(edges: { left: Edge; right: Edge }, design: CrossDesign)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PaveMark = { type_id: string; start_m?: number | null; end_m?: number | null };
|
||||||
|
|
||||||
|
/** 포장층 범위 표식(종류 · 시작 · 끝)이 그 노면공인가 — 겹친 노면공 가름. */
|
||||||
|
function isPaveLayerOf(data: DOMStringMap, mark: PaveMark): boolean {
|
||||||
|
const [low, high] = [mark.start_m ?? 0, mark.end_m ?? 0].sort((a, b) => a - b);
|
||||||
|
return (
|
||||||
|
data.paveType === mark.type_id &&
|
||||||
|
Math.abs(Number(data.paveStart) - low) < 0.01 &&
|
||||||
|
Math.abs(Number(data.paveEnd) - high) < 0.01
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 고른 노면공의 포장층만 `is-active`(152-5) — 범위 표식(종류 · 시작 · 끝)이 맞는 층. 겹친 노면공은 하나만.
|
* 고른 노면공의 포장층만 `is-active`(152-5) — 범위 표식(종류 · 시작 · 끝)이 맞는 층. 겹친 노면공은 하나만.
|
||||||
* `mark` 없음 = 모두 풂.
|
* `mark` 없음 = 모두 풂.
|
||||||
*/
|
*/
|
||||||
export function highlightPavement(
|
export function highlightPavement(root: ParentNode, mark?: PaveMark): void {
|
||||||
root: ParentNode,
|
|
||||||
mark?: { type_id: string; start_m?: number | null; end_m?: number | null },
|
|
||||||
): void {
|
|
||||||
const [low, high] = [mark?.start_m ?? 0, mark?.end_m ?? 0].sort((a, b) => a - b);
|
|
||||||
for (const layer of root.querySelectorAll<SVGElement>(".b06-chart__pavement")) {
|
for (const layer of root.querySelectorAll<SVGElement>(".b06-chart__pavement")) {
|
||||||
const { paveType, paveStart, paveEnd } = layer.dataset;
|
layer.classList.toggle("is-active", !!mark && isPaveLayerOf(layer.dataset, mark));
|
||||||
const same =
|
|
||||||
!!mark &&
|
|
||||||
paveType === mark.type_id &&
|
|
||||||
Math.abs(Number(paveStart) - low) < 0.01 &&
|
|
||||||
Math.abs(Number(paveEnd) - high) < 0.01;
|
|
||||||
layer.classList.toggle("is-active", same);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 누른 자리의 포장층 → 그 노면공(189-1 · 형상 누름 = 목록 고름) · 범위 표식 없는 층 · 밖 = undefined. */
|
||||||
|
export function pavementAt<T extends PaveMark>(
|
||||||
|
target: EventTarget | null,
|
||||||
|
marks: ReadonlyArray<T>,
|
||||||
|
): T | undefined {
|
||||||
|
const layer = (target as Element | null)?.closest?.<SVGElement>(".b06-chart__pavement");
|
||||||
|
if (!layer?.dataset.paveType) return undefined;
|
||||||
|
return marks.find((mark) => isPaveLayerOf(layer.dataset, mark));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 포장층 — 노면선(= 포장 윗면) 아래로 두께만큼. 노면공 범위(`section.pavement`)면 그 두께 · 무늬 ·
|
* 포장층 — 노면선(= 포장 윗면) 아래로 두께만큼. 노면공 범위(`section.pavement`)면 그 두께 · 무늬 ·
|
||||||
* 확폭 켬/끔(여러 건은 아래로 차례로 쌓음) · 없으면 카드 포장 켬일 때 표준 두께 채움만.
|
* 확폭 켬/끔(여러 건은 아래로 차례로 쌓음) · 없으면 카드 포장 켬일 때 표준 두께 채움만.
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ export interface B06StructuresPanel {
|
|||||||
selectionAt: (chainageM: number | null) => DesignSelection;
|
selectionAt: (chainageM: number | null) => DesignSelection;
|
||||||
/** 다음 카드 고름이 측점보다 먼저 열 구조물 — 알약 · 넘김값이 누른 것(null = 측점 규칙대로). */
|
/** 다음 카드 고름이 측점보다 먼저 열 구조물 — 알약 · 넘김값이 누른 것(null = 측점 규칙대로). */
|
||||||
prefer: (structureId: string | null) => void;
|
prefer: (structureId: string | null) => void;
|
||||||
|
/** 지금 카드 고름이 먼저 열 구조물(목록 · 알약 · 넘김) — 없으면 null(카드만 고름 · 189-1). */
|
||||||
|
preferred: () => string | null;
|
||||||
/** 구조물 하나를 폼에 올리고 그 카드로 간다 — 알약을 누른 길(목록 클릭과 같은 흐름). */
|
/** 구조물 하나를 폼에 올리고 그 카드로 간다 — 알약을 누른 길(목록 클릭과 같은 흐름). */
|
||||||
selectStructure: (structureId: string) => void;
|
selectStructure: (structureId: string) => void;
|
||||||
/** 지금 목록의 구조물 정본(미저장분 포함) — 카드 찾기에 쓴다. */
|
/** 지금 목록의 구조물 정본(미저장분 포함) — 카드 찾기에 쓴다. */
|
||||||
@@ -164,8 +166,6 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
|||||||
markTypes.find((type) => type.type_id === entry.type_id)?.group !== "G";
|
markTypes.find((type) => type.type_id === entry.type_id)?.group !== "G";
|
||||||
/** 다음 카드 고름이 열 구조물 — 같은 측점에 관이 있어도 누른 것이 먼저다(52-15). */
|
/** 다음 카드 고름이 열 구조물 — 같은 측점에 관이 있어도 누른 것이 먼저다(52-15). */
|
||||||
let preferredId: string | null = null;
|
let preferredId: string | null = null;
|
||||||
/** 폼에 열린 구조물 — 범위 안 빈 카드를 눌러도 연 노면공을 지킴(165-1). */
|
|
||||||
let openId: string | null = null;
|
|
||||||
/** 그 구조물로 갈 카드 측점 — 구간형은 범위 안 카드 중 기준점에 가까운 것(경계 기준점이 범위 밖 카드로 안 감 · 165-2). */
|
/** 그 구조물로 갈 카드 측점 — 구간형은 범위 안 카드 중 기준점에 가까운 것(경계 기준점이 범위 밖 카드로 안 감 · 165-2). */
|
||||||
const focusOf = (structure: StructureInstance): number => {
|
const focusOf = (structure: StructureInstance): number => {
|
||||||
const anchor = structureAnchorM(structure);
|
const anchor = structureAnchorM(structure);
|
||||||
@@ -209,7 +209,6 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSelect: (structure) => {
|
onSelect: (structure) => {
|
||||||
openId = structure?.structure_id ?? null;
|
|
||||||
if (!structure) {
|
if (!structure) {
|
||||||
deps.markStructure?.(null); // 풂 = 알약도 끔(53-2)
|
deps.markStructure?.(null); // 풂 = 알약도 끔(53-2)
|
||||||
return;
|
return;
|
||||||
@@ -547,24 +546,15 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
|||||||
prefer: (structureId) => {
|
prefer: (structureId) => {
|
||||||
preferredId = structureId;
|
preferredId = structureId;
|
||||||
},
|
},
|
||||||
|
preferred: () => preferredId,
|
||||||
selectStructure: (structureId) => section.selectById(structureId),
|
selectStructure: (structureId) => section.selectById(structureId),
|
||||||
showAtChainage: (chainageM) => {
|
showAtChainage: (chainageM) => {
|
||||||
currentChainageM = null;
|
currentChainageM = null;
|
||||||
const selection: DesignSelection = { chainageM, structureId: preferredId };
|
const selection: DesignSelection = { chainageM, structureId: preferredId };
|
||||||
// 계곡 통과 시설이 먼저다 — 같은 측점에 구조물 정본 항목이 겹쳐도 횡단도에
|
// 계곡 통과 시설이 먼저다 — 같은 측점에 구조물 정본 항목이 겹쳐도 횡단도에
|
||||||
// 그려지는 것은 이쪽이다(순서 · 거리는 `resolveSelection` 한 곳).
|
// 그려지는 것은 이쪽이다(순서 · 거리는 `resolveSelection` 한 곳).
|
||||||
|
// 카드 하나 고름 = 그 측점 기준 — 연 노면공은 풂(189-1 · 노면공은 덮어 열지 않음 152-5)
|
||||||
const hit = resolveSelection(selection, pipeFacilities, structures, coverable);
|
const hit = resolveSelection(selection, pipeFacilities, structures, coverable);
|
||||||
// 범위 안 빈 카드(관 · 덮는 구조물 없음) = 연 노면공 그대로 · 카드만 옮김(165-1)
|
|
||||||
const open = structures.find((entry) => entry.structure_id === openId);
|
|
||||||
if (
|
|
||||||
chainageM !== null &&
|
|
||||||
hit.pipeChainageM === null &&
|
|
||||||
hit.structureId === null &&
|
|
||||||
open &&
|
|
||||||
!coverable(open) &&
|
|
||||||
coversChainage(open, chainageM)
|
|
||||||
)
|
|
||||||
return { chainageM, structureId: openId };
|
|
||||||
// 폼은 위에서 푼 값으로 엶 — 공용 `selectAt` 은 노면공을 덮어 엶(152-5)
|
// 폼은 위에서 푼 값으로 엶 — 공용 `selectAt` 은 노면공을 덮어 엶(152-5)
|
||||||
if (hit.pipeChainageM !== null) {
|
if (hit.pipeChainageM !== null) {
|
||||||
currentChainageM = hit.pipeChainageM;
|
currentChainageM = hit.pipeChainageM;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ import {
|
|||||||
} from "./B06_Section_UI_Section_Common";
|
} from "./B06_Section_UI_Section_Common";
|
||||||
import type { SectionViewController } from "./B06_Section_UI_Section_View_Types";
|
import type { SectionViewController } from "./B06_Section_UI_Section_View_Types";
|
||||||
import { createFillSlopeNotice } from "./B06_Section_UI_Cross_FillSlope_Notice";
|
import { createFillSlopeNotice } from "./B06_Section_UI_Cross_FillSlope_Notice";
|
||||||
import { highlightPavement } from "./B06_Section_UI_Cross_Design_Surface";
|
import { highlightPavement, pavementAt } from "./B06_Section_UI_Cross_Design_Surface";
|
||||||
import { createSectionProfile } from "./B06_Section_UI_Section_View_Profile";
|
import { createSectionProfile } from "./B06_Section_UI_Section_View_Profile";
|
||||||
import type { DesignPageShared } from "../A00_Common/design/design_shared";
|
import type { DesignPageShared } from "../A00_Common/design/design_shared";
|
||||||
|
|
||||||
@@ -270,6 +270,8 @@ export function createSectionView(
|
|||||||
|
|
||||||
/** 카드 선택 변경 수신자(좌측 구조물 폼). 페이지가 등록한다. */
|
/** 카드 선택 변경 수신자(좌측 구조물 폼). 페이지가 등록한다. */
|
||||||
let stationSelectListener: ((stationId: string | null) => void) | null = null;
|
let stationSelectListener: ((stationId: string | null) => void) | null = null;
|
||||||
|
/** 카드 안 노면공 형상 누름 수신자 — 목록 고름과 같은 길(189-1). */
|
||||||
|
let pavementSelectListener: ((structureId: string) => void) | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 선택이 바뀐 흔적만 화면에 반영한다 — **재렌더 금지**.
|
* 선택이 바뀐 흔적만 화면에 반영한다 — **재렌더 금지**.
|
||||||
@@ -288,16 +290,6 @@ export function createSectionView(
|
|||||||
|
|
||||||
/** 같은 측점을 다시 고르면 선택을 푼다(2026-08-02 사용자 지시) — 카드·측점선 어느 쪽이든. */
|
/** 같은 측점을 다시 고르면 선택을 푼다(2026-08-02 사용자 지시) — 카드·측점선 어느 쪽이든. */
|
||||||
const selectStation = (stationId: string, scroll: boolean): void => {
|
const selectStation = (stationId: string, scroll: boolean): void => {
|
||||||
// 고른 노면공 범위 안 카드는 다시 눌러도 그대로 — 풂은 빈 자리 · 목록 · 알약(167-3)
|
|
||||||
const section = currentDetail?.cross_sections.find((entry) => entry.station_id === stationId);
|
|
||||||
const held =
|
|
||||||
section && isPavement(spanMarkId)
|
|
||||||
? inSpan(
|
|
||||||
section,
|
|
||||||
spanMarks.find((entry) => entry.structure_id === spanMarkId),
|
|
||||||
)
|
|
||||||
: false;
|
|
||||||
if (selectedStationId === stationId && held) return;
|
|
||||||
const previous = selectedStationId;
|
const previous = selectedStationId;
|
||||||
const deselect = selectedStationId === stationId;
|
const deselect = selectedStationId === stationId;
|
||||||
selectedStationId = deselect ? null : stationId;
|
selectedStationId = deselect ? null : stationId;
|
||||||
@@ -429,6 +421,17 @@ export function createSectionView(
|
|||||||
grid.addEventListener("click", (event) => {
|
grid.addEventListener("click", (event) => {
|
||||||
if (!(event.target as HTMLElement).closest(".b06-cross-card")) clearSelection();
|
if (!(event.target as HTMLElement).closest(".b06-cross-card")) clearSelection();
|
||||||
});
|
});
|
||||||
|
// 노면공 형상 누름 = 목록 고름(범위 카드 전부 · 형상 강조) — 카드 고름보다 먼저 가로챔(189-1)
|
||||||
|
grid.addEventListener(
|
||||||
|
"click",
|
||||||
|
(event) => {
|
||||||
|
const pave = pavementAt(event.target, spanMarks);
|
||||||
|
if (!pave?.structure_id || !isPavement(pave.structure_id)) return;
|
||||||
|
event.stopPropagation();
|
||||||
|
pavementSelectListener?.(pave.structure_id);
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
// 열 수 = 사용자 값 · 폭 = (칸 폭 − 간격) ÷ 열 수(`_Section_Columns` · 138-3).
|
// 열 수 = 사용자 값 · 폭 = (칸 폭 − 간격) ÷ 열 수(`_Section_Columns` · 138-3).
|
||||||
cachedCardWidth = crossCardWidth(renderWidth, columnCount, CROSS_GRID_GAP);
|
cachedCardWidth = crossCardWidth(renderWidth, columnCount, CROSS_GRID_GAP);
|
||||||
// CSS auto-fill의 자체 열 수와 JS columnCount가 어긋나면 같은 행 높이 그룹핑이 실제
|
// CSS auto-fill의 자체 열 수와 JS columnCount가 어긋나면 같은 행 높이 그룹핑이 실제
|
||||||
@@ -609,6 +612,9 @@ export function createSectionView(
|
|||||||
setStationSelectListener(listener) {
|
setStationSelectListener(listener) {
|
||||||
stationSelectListener = listener;
|
stationSelectListener = listener;
|
||||||
},
|
},
|
||||||
|
setPavementSelectListener(listener) {
|
||||||
|
pavementSelectListener = listener;
|
||||||
|
},
|
||||||
setStructureEdit(edit) {
|
setStructureEdit(edit) {
|
||||||
structureEdit = edit;
|
structureEdit = edit;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export interface SectionViewController {
|
|||||||
/** 카드(측점) 선택이 바뀔 때 알림 — 좌측 「구조물 배치」 폼이 그 측점 구조물을
|
/** 카드(측점) 선택이 바뀔 때 알림 — 좌측 「구조물 배치」 폼이 그 측점 구조물을
|
||||||
* 올린다(2026-08-29 일원화). null = 선택 해제. */
|
* 올린다(2026-08-29 일원화). null = 선택 해제. */
|
||||||
setStationSelectListener: (listener: (stationId: string | null) => void) => void;
|
setStationSelectListener: (listener: (stationId: string | null) => void) => void;
|
||||||
|
/** 카드 안 노면공 형상 누름(189-1) — 목록 고름과 같은 길로 보냄. */
|
||||||
|
setPavementSelectListener: (listener: (structureId: string) => void) => void;
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
/** 종단 아래 구조물 알약 레인에 쓸 목록 — B05 와 같은 부품·같은 표기(2026-09-07 사용자
|
/** 종단 아래 구조물 알약 레인에 쓸 목록 — B05 와 같은 부품·같은 표기(2026-09-07 사용자
|
||||||
* 지시 4 「구조물 표시 통일」). 좌측 「구조물 배치」가 목록을 받을 때마다 넘겨준다. */
|
* 지시 4 「구조물 표시 통일」). 좌측 「구조물 배치」가 목록을 받을 때마다 넘겨준다. */
|
||||||
|
|||||||
@@ -163,11 +163,16 @@ let marks = [];
|
|||||||
const profile = {
|
const profile = {
|
||||||
markedId: null,
|
markedId: null,
|
||||||
pill: null,
|
pill: null,
|
||||||
|
line: null, // 종단 측점선(design_profile selectedStationId)
|
||||||
selectMark(id) {
|
selectMark(id) {
|
||||||
this.markedId = id;
|
this.markedId = id;
|
||||||
this.pill = id;
|
this.pill = id;
|
||||||
|
// setSelectedStructure(id, keepStation) — 선 있는 구조물(관)만 그 선 · 아니면 카드 선 지킴(177-3)
|
||||||
|
const pipe = PIPES.find((entry) => `pipe-${entry.chainage_m.toFixed(2)}` === id);
|
||||||
|
if (pipe) this.line = cardAt(pipe.chainage_m).station_id;
|
||||||
},
|
},
|
||||||
showCard(card) {
|
showCard(card) {
|
||||||
|
this.line = card ? card.station_id : null;
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
const pipe = PIPES.find((entry) => Math.abs(entry.chainage_m - card.chainage_m) < 0.51);
|
const pipe = PIPES.find((entry) => Math.abs(entry.chainage_m - card.chainage_m) < 0.51);
|
||||||
// 관 측점 = 그 측점선 짝 알약 · 일반 측점 = 꺼짐 — 고른 구조물이 덮으면 지킴(53-1)
|
// 관 측점 = 그 측점선 짝 알약 · 일반 측점 = 꺼짐 — 고른 구조물이 덮으면 지킴(53-1)
|
||||||
@@ -189,14 +194,7 @@ const view = {
|
|||||||
return !!mark && markTypes.find((type) => type.type_id === mark.type_id)?.group === "G";
|
return !!mark && markTypes.find((type) => type.type_id === mark.type_id)?.group === "G";
|
||||||
},
|
},
|
||||||
selectStation(id) {
|
selectStation(id) {
|
||||||
// 고른 노면공 범위 안 카드 재누름 = 그대로(167-3 · Section_View `selectStation`)
|
// 카드 하나 고름 = 그 측점 · 같은 카드 재누름 = 풂(189-1 · Section_View `selectStation`)
|
||||||
const card = CARDS.find((entry) => entry.station_id === id);
|
|
||||||
const mark = marks.find((entry) => entry.structure_id === this.spanMarkId);
|
|
||||||
const held =
|
|
||||||
this.isPavement(this.spanMarkId) &&
|
|
||||||
card.chainage_m >= Math.min(mark.start_m, mark.end_m) - 0.01 &&
|
|
||||||
card.chainage_m <= Math.max(mark.start_m, mark.end_m) + 0.01;
|
|
||||||
if (this.selected === id && held) return;
|
|
||||||
const previous = this.selected;
|
const previous = this.selected;
|
||||||
this.selected = this.selected === id ? null : id;
|
this.selected = this.selected === id ? null : id;
|
||||||
profile.showCard(CARDS.find((card) => card.station_id === this.selected) ?? null);
|
profile.showCard(CARDS.find((card) => card.station_id === this.selected) ?? null);
|
||||||
@@ -243,8 +241,11 @@ view.listener = (stationId) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const owner = wallSelecting ? ownerOf(target) : null;
|
const owner = wallSelecting ? ownerOf(target) : null;
|
||||||
|
const preferred = panel.preferred() !== null;
|
||||||
const opened = panel.showAtChainage(owner?.chainage_m ?? target.chainage_m);
|
const opened = panel.showAtChainage(owner?.chainage_m ?? target.chainage_m);
|
||||||
if (!wallSelecting) selection.selectDesign(PID, opened, { from: "b06" });
|
// 카드 하나 고름 = 그 측점(구조물 id 는 목록 · 알약 · 넘김이 고른 것만 · 189-1)
|
||||||
|
const picked = preferred ? opened : { chainageM: opened.chainageM, structureId: null };
|
||||||
|
if (!wallSelecting) selection.selectDesign(PID, picked, { from: "b06" });
|
||||||
const pipeAt = panel.currentChainage();
|
const pipeAt = panel.currentChainage();
|
||||||
const mark = opened.structureId ?? (pipeAt === null ? null : `pipe-${pipeAt.toFixed(2)}`);
|
const mark = opened.structureId ?? (pipeAt === null ? null : `pipe-${pipeAt.toFixed(2)}`);
|
||||||
view.selectMark(mark, false);
|
view.selectMark(mark, false);
|
||||||
@@ -260,6 +261,7 @@ async function build(list) {
|
|||||||
view.spanMarkId = null;
|
view.spanMarkId = null;
|
||||||
profile.markedId = null;
|
profile.markedId = null;
|
||||||
profile.pill = null;
|
profile.pill = null;
|
||||||
|
profile.line = null;
|
||||||
panel = panelModule.createB06StructuresPanel({
|
panel = panelModule.createB06StructuresPanel({
|
||||||
projectId: PID,
|
projectId: PID,
|
||||||
shared: { view: () => "cross" },
|
shared: { view: () => "cross" },
|
||||||
@@ -292,6 +294,7 @@ function snap() {
|
|||||||
paveCards: pave ? CARDS.filter((entry) => inSpan(entry.chainage_m)).length : 0,
|
paveCards: pave ? CARDS.filter((entry) => inSpan(entry.chainage_m)).length : 0,
|
||||||
card: card ? card.chainage_m : null,
|
card: card ? card.chainage_m : null,
|
||||||
sid: selection.readSelection(PID)?.structureId ?? null,
|
sid: selection.readSelection(PID)?.structureId ?? null,
|
||||||
|
line: profile.line,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +305,8 @@ const pillPick = (id) => {
|
|||||||
panel.selectStructure(id);
|
panel.selectStructure(id);
|
||||||
};
|
};
|
||||||
const cardPick = (at) => view.selectStation(cardAt(at).station_id);
|
const cardPick = (at) => view.selectStation(cardAt(at).station_id);
|
||||||
|
/** 카드 안 노면공 형상 누름 — Section_View 가 카드 고름 전에 가로채 design_view_cross 가 목록 길로(189-1). */
|
||||||
|
const shapePick = (id) => panel.selectStructure(id);
|
||||||
/** 벽 누름 — SPS `revetOffset.select` 감싸개(소유 측점 폼) 뒤 카드 고름(벽 고르는 턴). */
|
/** 벽 누름 — SPS `revetOffset.select` 감싸개(소유 측점 폼) 뒤 카드 고름(벽 고르는 턴). */
|
||||||
const wallPick = (cardAtM, ownerM) => {
|
const wallPick = (cardAtM, ownerM) => {
|
||||||
wallOwner = { card: cardAtM, chainage_m: ownerM };
|
wallOwner = { card: cardAtM, chainage_m: ownerM };
|
||||||
@@ -340,6 +345,25 @@ const wallPick = (cardAtM, ownerM) => {
|
|||||||
cardPick(280); // 범위 밖 카드 재누름 = 카드 풂(옛 규칙 그대로)
|
cardPick(280); // 범위 밖 카드 재누름 = 카드 풂(옛 규칙 그대로)
|
||||||
out.outsideRe = snap();
|
out.outsideRe = snap();
|
||||||
|
|
||||||
|
// 189-1 세 갈래 — ① 목록 · ② 형상 · ③ 카드 하나 · 종단 측점선 · 세션
|
||||||
|
await build([PAVE, { structure_id: "sign", type_id: "hazard_sign", chainage_m: 127.86 }]);
|
||||||
|
listPick("pave");
|
||||||
|
out.r1 = snap();
|
||||||
|
cardPick(140); // ③ 범위 안 카드 = 목록 고름 · 범위 강조 풂 · 그 카드 하나
|
||||||
|
out.r3 = snap();
|
||||||
|
shapePick("pave"); // ② 고른 카드 안 형상 = ① 과 같음(옛 카드 풂)
|
||||||
|
out.r2 = snap();
|
||||||
|
out.r2reveal = view.revealed;
|
||||||
|
cardPick(100); // ③ 다시
|
||||||
|
out.r3b = snap();
|
||||||
|
cardPick(100); // 같은 카드 재누름 = 풂(옛 167-3 「그대로」 없앰)
|
||||||
|
out.r3re = snap();
|
||||||
|
await build([PAVE]);
|
||||||
|
shapePick("pave"); // ② 처음부터 형상 누름
|
||||||
|
out.r2first = snap();
|
||||||
|
cardPick(80); // ③ 관 자리 카드 = 관
|
||||||
|
out.r3pipe = snap();
|
||||||
|
|
||||||
// 5. B05 알약 → B06 복귀 — 넘김값 {범위 첫 카드 · sid} · `restoreStructurePick` 의 focusStation 차례
|
// 5. B05 알약 → B06 복귀 — 넘김값 {범위 첫 카드 · sid} · `restoreStructurePick` 의 focusStation 차례
|
||||||
await build([PAVE]);
|
await build([PAVE]);
|
||||||
selection.selectDesign(PID, { chainageM: 40, structureId: "pave" }, { from: "b05" });
|
selection.selectDesign(PID, { chainageM: 40, structureId: "pave" }, { from: "b05" });
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ const sb = sync.createSelectionSync({
|
|||||||
sb.syncIrregularSelection(id);
|
sb.syncIrregularSelection(id);
|
||||||
},
|
},
|
||||||
selectGraph: (id, c) => {
|
selectGraph: (id, c) => {
|
||||||
const target = sync.graphTargetAt(id, c ?? null, pipes, cards);
|
// 구조물 고름은 측점선 없음(189-1 · view 의 selectGraphAt 과 같음)
|
||||||
|
const sections = sel.readSelection(P)?.structureId ? [] : cards;
|
||||||
|
const target = sync.graphTargetAt(id, c ?? null, pipes, sections);
|
||||||
if ("pipeChainageM" in target) graph.mark = `pipe-${target.pipeChainageM.toFixed(2)}`;
|
if ("pipeChainageM" in target) graph.mark = `pipe-${target.pipeChainageM.toFixed(2)}`;
|
||||||
else {
|
else {
|
||||||
graph.station = target.stationId;
|
graph.station = target.stationId;
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/* PLAN 189-1 단위검증 헬퍼 — 카드 안 누른 포장층 → 그 노면공(`pavementAt`) · 범위 표식으로 겹친 노면공 가름.
|
||||||
|
* 화면 TS(`B06_Section_UI_Cross_Design_Surface.ts`)를 그 자리에서 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON.
|
||||||
|
* 짝 = `test_189_1_pave_pick.py`(세 갈래 상태는 `helper_165_pavement_flow.cjs` 가 냄).
|
||||||
|
*/
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, "..", "..");
|
||||||
|
const file = path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Surface.ts");
|
||||||
|
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||||
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||||
|
}).outputText;
|
||||||
|
const box = { exports: {} };
|
||||||
|
new Function("exports", "module", "require", js)(box.exports, box, () => ({}));
|
||||||
|
const { pavementAt } = box.exports;
|
||||||
|
|
||||||
|
const marks = [
|
||||||
|
{ structure_id: "pave", type_id: "pavement_concrete", start_m: 30, end_m: 250 },
|
||||||
|
{ structure_id: "pave2", type_id: "pavement_concrete", start_m: 100, end_m: 200 },
|
||||||
|
{ structure_id: "wall", type_id: "retaining_wall", start_m: 100, end_m: 200 },
|
||||||
|
];
|
||||||
|
/** 누른 요소 흉내 — `closest(".b06-chart__pavement")` 가 그 층(없으면 null). */
|
||||||
|
const hit = (dataset) => ({ closest: () => (dataset ? { dataset } : null) });
|
||||||
|
const layer = (type, start, end) => ({
|
||||||
|
paveType: type,
|
||||||
|
paveStart: String(start),
|
||||||
|
paveEnd: String(end),
|
||||||
|
});
|
||||||
|
|
||||||
|
const out = {
|
||||||
|
pave: pavementAt(hit(layer("pavement_concrete", 30, 250)), marks)?.structure_id ?? null,
|
||||||
|
pave2: pavementAt(hit(layer("pavement_concrete", 100, 200)), marks)?.structure_id ?? null,
|
||||||
|
plainLayer: pavementAt(hit({}), marks)?.structure_id ?? null, // 범위 표식 없는 카드 포장
|
||||||
|
outside: pavementAt(hit(null), marks)?.structure_id ?? null,
|
||||||
|
noTarget: pavementAt(null, marks)?.structure_id ?? null,
|
||||||
|
unknown: pavementAt(hit(layer("pavement_concrete", 0, 10)), marks)?.structure_id ?? null,
|
||||||
|
};
|
||||||
|
process.stdout.write(JSON.stringify(out));
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
못박는 것(단계마다 폼 · 알약 · 포장층 · 고른 카드 · 세션 sid 가 같은 것을 가리킴)
|
못박는 것(단계마다 폼 · 알약 · 포장층 · 고른 카드 · 세션 sid 가 같은 것을 가리킴)
|
||||||
1. 목록 노면공 = 포장층 · 알약 · 폼 · sid 모두 노면공 · 카드는 안 고름(스크롤만 · 177-4 · 기준점이 범위 경계여도 범위 밖 카드로 안 감).
|
1. 목록 노면공 = 포장층 · 알약 · 폼 · sid 모두 노면공 · 카드는 안 고름(스크롤만 · 177-4 · 기준점이 범위 경계여도 범위 밖 카드로 안 감).
|
||||||
2. 범위 안 빈 카드(관 · 덮는 구조물 없음) = 노면공 그대로 · 카드만 옮김(165-1).
|
2. 범위 안 빈 카드 = 카드 하나 고름 · 노면공 풂 · 세션 = 그 측점(189-1 이 165-1 · 167-3 을 바꿈).
|
||||||
3. 고른 카드의 벽 = 관 폼 + 관 알약(알약이 꺼진 채 폼만 남지 않음 · 165-2) · 포장층 풂.
|
3. 고른 카드의 벽 = 관 폼 + 관 알약(알약이 꺼진 채 폼만 남지 않음 · 165-2) · 포장층 풂.
|
||||||
4. 알약 다시 = 1 과 같음. 관 자리 카드 = 관 · 범위 밖 카드 = 모두 풂(지금처럼).
|
4. 알약 다시 = 1 과 같음. 관 자리 카드 = 관 · 범위 밖 카드 = 모두 풂(지금처럼).
|
||||||
5. B05 알약 → B06 복귀 = 1 과 같음 · 넘김 카드에서 안 튐.
|
5. B05 알약 → B06 복귀 = 1 과 같음 · 넘김 카드에서 안 튐.
|
||||||
6. 겹친 노면공 둘 = 고른 층만 · 범위 안 카드 눌러도 그 층 그대로.
|
6. 겹친 노면공 둘 = 고른 층만 · 범위 안 카드 누름 = 카드 하나(189-1).
|
||||||
|
|
||||||
Node 헬퍼 `helper_165_pavement_flow.cjs` 가 진짜 SPN 에 폼 · 카드 · 알약 흉내를 붙여 돌림. 화면은 사용자 확인.
|
Node 헬퍼 `helper_165_pavement_flow.cjs` 가 진짜 SPN 에 폼 · 카드 · 알약 흉내를 붙여 돌림. 화면은 사용자 확인.
|
||||||
"""
|
"""
|
||||||
@@ -41,6 +41,7 @@ def out():
|
|||||||
|
|
||||||
|
|
||||||
def _pave(card, layer="pave", cards=12):
|
def _pave(card, layer="pave", cards=12):
|
||||||
|
# 노면공 고름 = 카드 하나 안 고름 · 종단 측점선 없음(189-1)
|
||||||
return {
|
return {
|
||||||
"form": layer,
|
"form": layer,
|
||||||
"pill": layer,
|
"pill": layer,
|
||||||
@@ -48,10 +49,31 @@ def _pave(card, layer="pave", cards=12):
|
|||||||
"paveCards": cards,
|
"paveCards": cards,
|
||||||
"card": card,
|
"card": card,
|
||||||
"sid": layer,
|
"sid": layer,
|
||||||
|
"line": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PIPE = {"form": "pipe-80.17", "pill": "pipe-80.17", "paveLayer": None, "paveCards": 0}
|
def _card(at):
|
||||||
|
# 카드 하나 고름 = 그 측점 기준 · 노면공 · 범위 강조 풂 · 세션 sid 없음(189-1)
|
||||||
|
line = None if at is None else f"s{at}"
|
||||||
|
return {
|
||||||
|
"form": None,
|
||||||
|
"pill": None,
|
||||||
|
"paveLayer": None,
|
||||||
|
"paveCards": 0,
|
||||||
|
"card": at,
|
||||||
|
"sid": None,
|
||||||
|
"line": line,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PIPE = {
|
||||||
|
"form": "pipe-80.17",
|
||||||
|
"pill": "pipe-80.17",
|
||||||
|
"paveLayer": None,
|
||||||
|
"paveCards": 0,
|
||||||
|
"line": "s80.17",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_1_목록_노면공(out):
|
def test_1_목록_노면공(out):
|
||||||
@@ -59,11 +81,11 @@ def test_1_목록_노면공(out):
|
|||||||
assert out["reveal1"] == "s40" # 기준점 30 = 범위 경계 → 범위 안 첫 카드(20 아님)
|
assert out["reveal1"] == "s40" # 기준점 30 = 범위 경계 → 범위 안 첫 카드(20 아님)
|
||||||
|
|
||||||
|
|
||||||
def test_2_범위_안_빈_카드는_노면공_그대로(out):
|
def test_2_범위_안_빈_카드는_카드_하나(out):
|
||||||
assert out["step2first"] == _pave(40)
|
assert out["step2first"] == _card(40)
|
||||||
assert out["step2re"] == _pave(40) # 이미 고른 카드 재누름도 그대로(167-3)
|
assert out["step2re"] == _card(None) # 같은 카드 재누름 = 풂(189-1)
|
||||||
assert out["step2"] == _pave(140)
|
assert out["step2"] == _card(140)
|
||||||
assert out["step2b"] == _pave(100)
|
assert out["step2b"] == _card(100)
|
||||||
|
|
||||||
|
|
||||||
def test_3_벽은_관_폼과_관_알약_함께(out):
|
def test_3_벽은_관_폼과_관_알약_함께(out):
|
||||||
@@ -73,14 +95,7 @@ def test_3_벽은_관_폼과_관_알약_함께(out):
|
|||||||
def test_4_알약_다시_관_카드_범위_밖(out):
|
def test_4_알약_다시_관_카드_범위_밖(out):
|
||||||
assert out["step4"] == _pave(None)
|
assert out["step4"] == _pave(None)
|
||||||
assert out["pipeCard"] == {**PIPE, "card": 80, "sid": None}
|
assert out["pipeCard"] == {**PIPE, "card": 80, "sid": None}
|
||||||
assert out["outside"] == {
|
assert out["outside"] == _card(280)
|
||||||
"form": None,
|
|
||||||
"pill": None,
|
|
||||||
"paveLayer": None,
|
|
||||||
"paveCards": 0,
|
|
||||||
"card": 280,
|
|
||||||
"sid": None,
|
|
||||||
}
|
|
||||||
assert out["outsideRe"]["card"] is None
|
assert out["outsideRe"]["card"] is None
|
||||||
|
|
||||||
|
|
||||||
@@ -90,16 +105,16 @@ def test_5_B05_복귀(out):
|
|||||||
|
|
||||||
def test_6_겹친_노면공(out):
|
def test_6_겹친_노면공(out):
|
||||||
assert out["step6"] == _pave(None, "pave2", 6)
|
assert out["step6"] == _pave(None, "pave2", 6)
|
||||||
assert out["step6b"] == _pave(160, "pave2", 6)
|
assert out["step6b"] == _card(160)
|
||||||
|
|
||||||
|
|
||||||
def test_배선():
|
def test_배선():
|
||||||
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
||||||
assert "deps.focusChainage(focusOf(structure), pave);" in panel
|
assert "deps.focusChainage(focusOf(structure), pave);" in panel
|
||||||
assert "return { chainageM, structureId: openId };" in panel
|
assert "openId" not in panel # 범위 안 빈 카드 = 연 노면공 지킴(165-1) 없앰 · 189-1
|
||||||
# 관 알약은 폼 뒤(폼이 연 구조물을 닫으며 알약을 끔)
|
# 관 알약은 폼 뒤(폼이 연 구조물을 닫으며 알약을 끔)
|
||||||
at = panel.index("section.selectPipeByChainage(hit.pipeChainageM);")
|
at = panel.index("section.selectPipeByChainage(hit.pipeChainageM);")
|
||||||
assert at < panel.index("deps.markStructure?.(`pipe-${hit.pipeChainageM.toFixed(2)}`);", at)
|
assert at < panel.index("deps.markStructure?.(`pipe-${hit.pipeChainageM.toFixed(2)}`);", at)
|
||||||
view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||||||
assert "spanMarkId = span || isPavement(structureId) ? structureId : null;" in view
|
assert "spanMarkId = span || isPavement(structureId) ? structureId : null;" in view
|
||||||
assert "if (selectedStationId === stationId && held) return;" in view
|
assert "held" not in view # 범위 카드 재누름 = 그대로(167-3) 없앰 · 189-1
|
||||||
|
|||||||
@@ -74,9 +74,12 @@ def test_횡단_배관은_종단_배관_측점선_측점선_없는_관은_알약
|
|||||||
assert out["b06BarePipe"]["graph"] == {"station": None, "mark": "pipe-50.00"}
|
assert out["b06BarePipe"]["graph"] == {"station": None, "mark": "pipe-50.00"}
|
||||||
|
|
||||||
|
|
||||||
def test_횡단_구조물은_종단에서_그_구조물을_열고_고름_한_칸을_안_덮음(out) -> None:
|
def test_횡단_구조물은_종단에서_그_구조물을_열고_측점선_없이_고름_한_칸을_안_덮음(out) -> None:
|
||||||
structure = out["b06Structure"]
|
structure = out["b06Structure"]
|
||||||
assert structure["opened"] == ["wall"]
|
assert structure["opened"] == ["wall"]
|
||||||
|
# 구조물 고름 = 종단 측점선 · 3D 띠 없음(189-1 · 여러 측점이라 한 측점 기준 없음)
|
||||||
|
assert structure["graph"] == {"station": None, "mark": None}
|
||||||
|
assert structure["marker"] is None
|
||||||
# 되살리며 3D 띠 리스너가 되불러도(동기화 중) 구조물 id · 부재키가 남음
|
# 되살리며 3D 띠 리스너가 되불러도(동기화 중) 구조물 id · 부재키가 남음
|
||||||
assert structure["pick"] == {"chainageM": 60, "structureId": "wall", "partKey": "own"}
|
assert structure["pick"] == {"chainageM": 60, "structureId": "wall", "partKey": "own"}
|
||||||
|
|
||||||
@@ -99,3 +102,4 @@ def test_종단_보기_배선() -> None:
|
|||||||
assert "regularChainage:" in page
|
assert "regularChainage:" in page
|
||||||
assert "remember: (chainageM) => rememberPick(chainageM, null)" in page
|
assert "remember: (chainageM) => rememberPick(chainageM, null)" in page
|
||||||
assert "graphTargetAt(id, chainageM, lastPipes ?? [], sections)" in page
|
assert "graphTargetAt(id, chainageM, lastPipes ?? [], sections)" in page
|
||||||
|
assert "const sections = structurePick ? [] :" in page
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""PLAN 189-1 — 노면공(여러 측점 구간) 고름 세 갈래 · 종단 측점선(사용자 41번).
|
||||||
|
|
||||||
|
못박는 것
|
||||||
|
① 좌측 목록 노면공 = 범위 카드 전부 테두리 + 포장층 강조 · 카드 하나 안 고름 · 종단 측점선 없음.
|
||||||
|
② 카드 안 노면공 형상 누름 = ① 과 같음(고른 카드도 풂 · 카드 고름 리스너 안 돎).
|
||||||
|
③ 카드 하나 누름(형상 밖) = 목록 고름 · 범위 강조 풂 · 그 카드 하나 · 종단 측점선 = 그 카드 · 세션 sid 없음.
|
||||||
|
같은 카드 재누름 = 풂 · 관 자리 카드 = 관.
|
||||||
|
|
||||||
|
상태는 `helper_165_pavement_flow.cjs`(진짜 SPN) · 형상 → 노면공 찾기는 `helper_189_1_pave_shape.cjs`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(
|
||||||
|
not os.path.isdir(os.path.join(ROOT, "config", "node_modules")),
|
||||||
|
reason="프론트엔드 의존성 미설치",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _node(name: str) -> dict:
|
||||||
|
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||||
|
["node", os.path.join(HERE, name)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
assert proc.returncode == 0, proc.stderr
|
||||||
|
return json.loads(proc.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def flow() -> dict:
|
||||||
|
return _node("helper_165_pavement_flow.cjs")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def shape() -> dict:
|
||||||
|
return _node("helper_189_1_pave_shape.cjs")
|
||||||
|
|
||||||
|
|
||||||
|
def _read(*parts: str) -> str:
|
||||||
|
with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle:
|
||||||
|
return handle.read()
|
||||||
|
|
||||||
|
|
||||||
|
PAVE = {
|
||||||
|
"form": "pave",
|
||||||
|
"pill": "pave",
|
||||||
|
"paveLayer": "pave",
|
||||||
|
"paveCards": 12,
|
||||||
|
"card": None,
|
||||||
|
"sid": "pave",
|
||||||
|
"line": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _card(at):
|
||||||
|
return {
|
||||||
|
"form": None,
|
||||||
|
"pill": None,
|
||||||
|
"paveLayer": None,
|
||||||
|
"paveCards": 0,
|
||||||
|
"card": at,
|
||||||
|
"sid": None,
|
||||||
|
"line": None if at is None else f"s{at}",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_1_목록_고름은_범위_카드_전부_종단_선_없음(flow) -> None:
|
||||||
|
assert flow["r1"] == PAVE
|
||||||
|
|
||||||
|
|
||||||
|
def test_2_형상_누름은_목록_고름과_같음(flow) -> None:
|
||||||
|
assert flow["r2first"] == PAVE # 처음부터 형상
|
||||||
|
assert flow["r2"] == PAVE # 카드 하나 고른 뒤 형상 = 옛 카드 풂
|
||||||
|
assert flow["r2reveal"] == "s40"
|
||||||
|
|
||||||
|
|
||||||
|
def test_3_카드_하나는_목록_고름과_범위_강조를_풂(flow) -> None:
|
||||||
|
assert flow["r3"] == _card(140)
|
||||||
|
assert flow["r3b"] == _card(100)
|
||||||
|
assert flow["r3re"] == _card(None) # 같은 카드 재누름 = 풂
|
||||||
|
pipe = flow["r3pipe"]
|
||||||
|
assert (pipe["form"], pipe["pill"], pipe["paveCards"], pipe["sid"]) == (
|
||||||
|
"pipe-80.17",
|
||||||
|
"pipe-80.17",
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_누른_포장층은_범위_표식으로_그_노면공(shape) -> None:
|
||||||
|
assert shape["pave"] == "pave"
|
||||||
|
assert shape["pave2"] == "pave2" # 겹친 노면공 = 누른 층
|
||||||
|
for key in ("plainLayer", "outside", "noTarget", "unknown"):
|
||||||
|
assert shape[key] is None, key
|
||||||
|
|
||||||
|
|
||||||
|
def test_배선() -> None:
|
||||||
|
view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||||||
|
# 형상 누름은 카드 고름보다 먼저 가로챔(잡기 단계 · 전파 멈춤)
|
||||||
|
body = re.search(
|
||||||
|
r"const pave = pavementAt\(event\.target, spanMarks\);(.*?)true,\n", view, re.S
|
||||||
|
)
|
||||||
|
assert body, "형상 누름 배선 없음"
|
||||||
|
assert "event.stopPropagation();" in body.group(1)
|
||||||
|
assert "pavementSelectListener?.(pave.structure_id);" in body.group(1)
|
||||||
|
cross = _read("A00_Common", "design", "design_view_cross.ts")
|
||||||
|
assert (
|
||||||
|
"sectionView.setPavementSelectListener((id) => structuresPanel.selectStructure(id));"
|
||||||
|
in cross
|
||||||
|
)
|
||||||
|
# 카드 하나 고름 = 그 측점(구조물 id 는 목록 · 알약 · 넘김만)
|
||||||
|
assert "const preferred = structuresPanel.preferred() !== null;" in cross
|
||||||
|
assert (
|
||||||
|
"const picked = preferred ? opened : { chainageM: opened.chainageM, structureId: null };"
|
||||||
|
in cross
|
||||||
|
)
|
||||||
|
profile = _read("A00_Common", "design", "design_view_profile.ts")
|
||||||
|
assert "const structurePick = !!readSelection(activeProjectId)?.structureId;" in profile
|
||||||
Reference in New Issue
Block a user