This commit is contained in:
2026-07-24 17:52:01 +09:00
parent 5c8c0462f9
commit 505f28a30b
10 changed files with 109 additions and 39 deletions
@@ -135,6 +135,8 @@ def generate_sections(
long_z, long_valid = sampler.sample_xy(long_xy)
station_chainage = _chainages(total, options.station_interval_m, options.include_endpoint)
# 규칙(격자) 측점 chainage 집합 — 비정규와 겹칠 때 규칙 측점을 우선 판정하는 데 쓴다.
regular_rounded = {round(float(value), 6) for value in station_chainage}
# 비정규 측점(구조물)을 격자 측점 배열에 병합한다. 정렬·중복 제거만 하면 이후 접선·횡단
# 샘플링이 전부 이 배열을 따라 자동으로 확장된다. 어느 측점이 비정규인지는 chainage로 판별.
extras = {
@@ -183,7 +185,8 @@ def generate_sections(
kind = "bp"
elif abs(float(value) - total) <= 1e-6:
kind = "ep"
elif rounded in extras:
elif rounded in extras and rounded not in regular_rounded:
# 격자와 겹치지 않는 비정규 측점만 irregular. 규칙 격자와 겹치면 규칙 측점 우선.
kind = "irregular"
else:
kind = "regular"
@@ -213,7 +216,8 @@ def generate_sections(
"azimuth_deg": round(azimuth, 6),
"frame": frame,
}
if kind == "irregular":
# 구조물은 kind와 무관하게 부착한다(규칙 격자와 겹쳐 regular가 돼도 구조물 정보는 유지).
if rounded in extras:
station["structure"] = extras[rounded]
stations.append(station)
@@ -72,8 +72,9 @@ export function createIrregularStationsSection(
callbacks: IrregularStationsCallbacks,
): IrregularStationsSection {
const root = document.createElement("section");
root.className = "b05-route__panel-section";
root.className = "b05-route__panel-section ui-collapsible";
const heading = document.createElement("h3");
heading.className = "ui-collapsible__title";
heading.textContent = "비정규 측점 (구조물)";
const body = document.createElement("div");
body.className = "b05-route__panel-body";
+6 -10
View File
@@ -5,6 +5,7 @@ import {
type IrregularStationsSection,
} from "./B05_wf2_Route_UI_IrregularStations";
import { type ButtonVariant, createButton } from "@ui/ui_template_elements";
import { attachCollapsible } from "@ui/ui_template_collapsible";
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
export interface RoutePanelValues {
@@ -71,8 +72,9 @@ function L(key: keyof typeof ui_locales): string {
function section(title: string): { root: HTMLElement; body: HTMLElement } {
const root = document.createElement("section");
root.className = "b05-route__panel-section";
root.className = "b05-route__panel-section ui-collapsible";
const heading = document.createElement("h3");
heading.className = "ui-collapsible__title";
heading.textContent = title;
const body = document.createElement("div");
body.className = "b05-route__panel-body";
@@ -355,15 +357,9 @@ export function createRoutePanel(callbacks: PanelCallbacks) {
actionRow,
);
// 컨테이너 제목(h3) 클릭 시 본문을 접거나 편다(제목은 남긴다). 본문 안의 details 등 별도
// 접힘 항목은 손대지 않으므로 열려 있으면 그대로 보인다.
root.addEventListener("click", (event) => {
const heading = (event.target as HTMLElement).closest("h3");
const container = heading?.parentElement;
if (heading && container?.classList.contains("b05-route__panel-section")) {
container.classList.toggle("is-collapsed");
}
});
// 컨테이너 제목 행 전체 클릭 시 본문을 접거나 편다(공용 collapsible). 내부 details 등 별도
// 접힘 항목은 손대지 않다.
attachCollapsible(root);
return {
root,
@@ -292,6 +292,11 @@ function buildCurveRows(options: ProfileTableOptions, centers: number[]): HTMLEl
if (curve.omitted) cell.classList.add("is-omitted");
});
}
// 선택된 규칙 측점 열의 곡선 셀도 함께 하이라이트한다.
if (station.station_id && station.station_id === options.selectedStationId) {
lengthCell.classList.add("is-selected");
radiusCell.classList.add("is-selected");
}
placeCell(lengthRow, centers[index], lengthCell);
placeCell(radiusRow, centers[index], radiusCell);
});
@@ -458,8 +463,12 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement {
spec.unit,
);
// 값이 없는 측점(절토고/성토고 중 한쪽)도 빈 칸을 만들어야 세로 구분선이 끊기지 않는다.
alignment.stations.forEach((_station, stationIndex) => {
alignment.stations.forEach((station, stationIndex) => {
const cell = element("span", "b05-profile-table__cell", spec.cell(stationIndex));
// 선택된 규칙 측점은 그 열의 값 셀을 하이라이트한다(비정규 측점의 값 열 강조와 통일).
if (station.station_id && station.station_id === options.selectedStationId) {
cell.classList.add("is-selected");
}
placeCell(row, centers[stationIndex], cell);
});
table.append(row);
+11 -23
View File
@@ -211,33 +211,11 @@
background: var(--color-surface-raised);
}
/* 제목 행 클릭 접기/펼치기는 공용 collapsible(ui_template_theme.css)에서 처리한다. */
.b05-route__panel-section h3 {
margin: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8);
color: var(--color-text);
font-size: var(--text-body-sm);
cursor: pointer;
user-select: none;
}
/* 접기/펼침 상태를 제목 우측 캐럿으로 표시(이 기능이 있음을 알린다). 펼침 ▾ / 접힘 ▸. */
.b05-route__panel-section h3::after {
content: "▾";
flex: 0 0 auto;
color: var(--color-text-muted);
font-size: 0.8em;
}
.b05-route__panel-section.is-collapsed h3::after {
content: "▸";
}
/* 제목 클릭으로 접힌 컨테이너: 본문만 감추고 제목은 남긴다. */
.b05-route__panel-section.is-collapsed .b05-route__panel-body {
display: none;
}
.b05-route__panel-body,
@@ -577,6 +555,16 @@
border-right: 1px solid var(--color-border);
}
/* 선택된 규칙 측점 열의 값·곡선 셀 하이라이트(비정규 측점 값 열 강조와 같은 색). */
.b05-profile-table__cell.is-selected,
.b05-profile-table__curve.is-selected {
background: color-mix(
in srgb,
var(--color-royal-amethyst, rgb(109 40 217)) 14%,
var(--color-surface)
);
}
.b05-profile-table__row.is-cut .b05-profile-table__cell {
color: rgb(220 38 38);
}
@@ -141,7 +141,18 @@ export function createCrossSectionCard(
: section.kind === "bp"
? L("B06_Profile_View_Kind_BP")
: L("B06_Profile_View_Kind_Station");
header.append(title, kind);
// kind 라벨(일반측점/BP/EP) 좌측에 구조물 정보를 표기(비정규 측점이 확정 시 실어온 값).
const meta = document.createElement("div");
meta.className = "b06-cross-card__meta";
if (section.structure) {
const structure = document.createElement("span");
structure.className = "b06-cross-card__structure";
structure.textContent = section.structure;
structure.title = `구조물: ${section.structure}`;
meta.append(structure);
}
meta.append(kind);
header.append(title, meta);
card.append(header);
if (onDesignChange) card.append(buildDesignControls(section, onDesignChange));
@@ -240,6 +240,16 @@
font-size: var(--text-body-sm);
}
/* 비정규 측점의 구조물 라벨 — kind(일반측점/BP/EP) 좌측에 자수정색 pill로 표기. */
.b06-cross-card__structure {
padding: 1px 6px;
border-radius: var(--radius-pills);
background: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 16%, transparent);
color: var(--color-royal-amethyst, rgb(109 40 217));
font-size: var(--text-caption);
white-space: nowrap;
}
.b06-cross-card > footer {
border-top: 1px solid var(--color-border);
}
@@ -11,6 +11,7 @@
* ========================================================================== */
import "./B07_wf4_DesignDetail_UI_Style.css";
import { attachCollapsible } from "@ui/ui_template_collapsible";
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import {
createButton,
@@ -136,9 +137,10 @@ function buildDrawingSidePanel(
for (const [label, kind, items] of groups) {
if (!items.length) continue;
const section = document.createElement("section");
section.className = "b07-drawing-group";
section.className = "b07-drawing-group ui-collapsible";
section.dataset.kind = kind;
const sectionTitle = document.createElement("h3");
sectionTitle.className = "ui-collapsible__title";
sectionTitle.textContent = `${label} ${items.length}`;
section.append(sectionTitle);
for (const drawing of items) {
@@ -159,6 +161,7 @@ function buildDrawingSidePanel(
}
panel.append(section);
}
attachCollapsible(panel);
return panel;
}
+21
View File
@@ -0,0 +1,21 @@
/* =============================================================================
* ui_template_collapsible.ts
* "접기 컨테이너" . (B05 릿 )
*
* :
* - `ui-collapsible`, `ui-collapsible__title` .
* - ** ** `is-collapsed` ,
* ( / CSS가 ).
* - (`<details>` ) .
*
* `ui_template_theme.css` .
* ========================================================================== */
/** root 아래 `.ui-collapsible__title` 클릭을 위임 처리해 해당 컨테이너를 접거나 편다. */
export function attachCollapsible(root: HTMLElement): void {
root.addEventListener("click", (event) => {
const title = (event.target as HTMLElement).closest<HTMLElement>(".ui-collapsible__title");
const container = title?.closest<HTMLElement>(".ui-collapsible");
if (title && container) container.classList.toggle("is-collapsed");
});
}
+27
View File
@@ -331,6 +331,33 @@
}
}
/* 공용 접기 컨테이너 (ui_template_collapsible.ts)
컨테이너에 `ui-collapsible`, 제목에 `ui-collapsible__title` 주면 제목 전체가
클릭 영역이 되고, 접히면 제목을 직속 자식이 숨겨진다. 펼침 / 접힘 캐럿 표시. */
.ui-collapsible__title {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8, 8px);
cursor: pointer;
user-select: none;
}
.ui-collapsible__title::after {
content: "▾";
flex: 0 0 auto;
color: var(--color-text-muted);
font-size: 0.8em;
}
.ui-collapsible.is-collapsed > .ui-collapsible__title::after {
content: "▸";
}
.ui-collapsible.is-collapsed > :not(.ui-collapsible__title) {
display: none;
}
html,
body {
margin: 0;