Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_3' into main_laptop_1

This commit is contained in:
2026-10-28 07:10:58 +09:00
16 changed files with 162 additions and 185 deletions
@@ -8,7 +8,7 @@
* 그리기 루프 · 포인터 듣기만 멈추고 WebGL 문맥 · 지형은 살린다. 버림은 페이지를 떠날 때만.
* ========================================================================== */
import { createButton } from "@ui/ui_template_elements";
import { mapToolButton } from "@ui/ui_template_map_tools";
import { openModal } from "@ui/ui_template_modal";
import { createProgressCircle } from "@ui/ui_template_progress";
@@ -73,9 +73,12 @@ export function createViewer3dModal(deps: Viewer3dModalDeps) {
shown.close();
}
const button = createButton({ label: "3D", variant: "ghost", onClick: open });
// 평면 도구줄 아이콘 단추(568-2) — 클래스는 가이드 촬영이 잡음.
const button = mapToolButton("view3d", {
title: "3D 화면을 큰 창으로 엽니다 — 닫으면 그리기를 멈춥니다",
onClick: open,
});
button.classList.add("b05-route__3d-open");
button.title = "3D 화면을 큰 창으로 엽니다 — 닫으면 그리기를 멈춥니다";
return {
park,
+74 -76
View File
@@ -1,4 +1,5 @@
import { createIconButton } from "@ui/ui_template_elements";
import { createMapTools, mapToolButton } from "@ui/ui_template_map_tools";
import { createSidePanels } from "@ui/ui_template_side_panels";
import { bindFootprintToggle } from "./B05_Profile_UI_RouteEdit_Footprint";
import { bindMarkToggle } from "./B05_Profile_UI_RouteEdit_Marks";
@@ -15,7 +16,7 @@ import { bottomRowNarrow, infoLiftPx } from "./logic/B05_Profile_RouteEdit_InfoL
* (지도 왼쪽 위 · ☰ 비킴), [?] 조작 설명은 도구줄 아래 2열, 상태·범례는 왼쪽 아래, 잰 값은 오른쪽
* 아래. 횡단 두 판은 지도 오른쪽을 덮는 판(아래 종단 판이 그 위).
* 도구줄 차례(366-2 · 381-2) — [?] | ↶ ↷ | 3D | ⟲ ⟳ · 1m 등고선 · 측점 · 기울기 · 절/성토선 · 거리 재기 |
* 측점 스냅 · 분석 지형.
* 측점 스냅 · 간격 | 분석 지형. 단추는 공용 지도 도구줄(568-2 · `ui_template_map_tools`) 아이콘 — 긴 설명은 마우스 글.
*
* 자리(345-3) — 모달이 아니라 종단 보기 가운데 위 본문(옛 3D 자리)에 붙는다. 덮개 · 전체 화면 ·
* 닫기 단추는 걷었다 · [취소] · [확인] · [노선 반영] · [저장 전으로]는 없앰(346-3 · 352-2 — 노선은 페이지
@@ -67,6 +68,17 @@ const SIDE_HANDLE_PX = 32;
// 글판 접기 기억(345-5 덧 3) — 페이지 동안만(모듈 값) · 다시 열어도 그대로.
let hintOpen = false;
const HINT_LINES = [
"노드 끌기 = 옮기기",
"노드 클릭 = R 라벨",
"선 두 번 클릭 = 노드 추가",
"노드 오른쪽 클릭 = 삭제",
"측점 눈금 클릭 = 횡단 미리보기",
"Shift+클릭 = 거리·기울기",
"가운데(휠) 버튼 끌기 = 지도 이동",
"휠 = 확대",
"Ctrl+휠 = 돌리기(한 칸 5°)",
];
let infoCollapsed = false;
/** 뼈대를 만들어 `parent` 에 붙이고, 자주 쓰는 요소를 집어 돌려준다. */
@@ -79,56 +91,7 @@ export function createRouteEditChrome(
overlay.innerHTML = `
<div class="b05-routeedit__canvas-wrap" aria-label="계획노선 편집">
<canvas class="b05-routeedit__canvas"></canvas>
<div class="b05-route__view-controls b05-routeedit__tools">
<button type="button" data-size="32" class="ui-btn ui-btn--glass b05-routeedit__hint-toggle"
data-act="hint-toggle" aria-expanded="false" title="조작 도움말 펼치기 · 접기">?</button>
<i class="b05-route__view-separator" aria-hidden="true"></i>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="undo"
title="되돌리기 (Ctrl+Z)" aria-label="되돌리기" disabled>${HISTORY_ICON.undo}</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="redo"
title="다시하기 (Ctrl+Y)" aria-label="다시하기" disabled>${HISTORY_ICON.redo}</button>
<i class="b05-route__view-separator" aria-hidden="true"></i>
<span data-slot="tools"></span>
<i class="b05-route__view-separator" aria-hidden="true"></i>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="rotate-ccw"
title="반시계로 돌리기" aria-label="반시계로 돌리기">${HALF_TURN_ICON.ccw}</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="rotate-cw"
title="시계로 돌리기" aria-label="시계로 돌리기">${HALF_TURN_ICON.cw}</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="contour-minor"
aria-pressed="false" disabled
title="1m 간격 등고선을 얇고 옅게 겹칩니다 (확대하면 보임)">1m 등고선</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="stations"
title="측점 눈금 · 번호를 평면에 보이기 · 숨기기 (시점 · 종점 이름표는 늘 보임)">측점</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="slopes"
title="측점 사이 기울기(%)를 평면에 보이기 · 숨기기 (확대해야 보임)">기울기</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="footprint"
title="도로 끝 · 비탈 끝(깎기 붉음 · 쌓기 청록)을 평면에 겹칩니다">절/성토선</button>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="measure"
title="노선 위 두 점을 눌러 거리·기울기를 잽니다 (Shift+클릭도 같음)">거리 재기</button>
<i class="b05-route__view-separator" aria-hidden="true"></i>
<button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="station-snap"
aria-pressed="false"
title="켜면 고친 곡선의 가운데(MC)를 고른 간격 배수에 맞춥니다 — 일반측점(20 m)은 그대로 · MC 가 일반측점 위가 아니면 「No.N+10 (MC)」 특별측점을 더함 · 손대지 않은 곡선은 그 자리 유지 · 고정 교각점은 그대로 · 못 맞춘 곡선은 상태줄 「측점 밖 곡선」 · [저장] 때 저장">측점 스냅</button>
<label class="b05-routeedit__terrain b05-routeedit__snap-step"
title="측점 스냅 간격 — 곡선 가운데(MC)를 이 간격 배수에 맞춥니다(측점 No. = 측점 간격)">간격
<select class="ui-select ui-select--sm" data-size="32" data-act="snap-step">
<option value="10">10 m</option>
<option value="station">측점 No.</option>
</select>
</label>
<i class="b05-route__view-separator" aria-hidden="true"></i>
<span class="b05-routeedit__terrain" data-slot="terrain-name" hidden
title="설계 지형 — 바꾸기는 [노선 추천] 창에서 지형을 골라 [적용]"></span>
</div>
<div class="b05-routeedit__hint">
<div class="b05-routeedit__hint-body">
<span>노드 끌기 = 옮기기</span><span>노드 클릭 = R 라벨</span>
<span>선 두 번 클릭 = 노드 추가</span><span>노드 오른쪽 클릭 = 삭제</span>
<span>측점 눈금 클릭 = 횡단 미리보기</span><span>Shift+클릭 = 거리·기울기</span>
<span>가운데(휠) 버튼 끌기 = 지도 이동</span><span>휠 = 확대</span>
<span>Ctrl+휠 = 돌리기(한 칸 5°)</span>
</div>
</div>
<div data-slot="map-tools"></div>
<div class="b05-routeedit__measure" hidden>
<span class="b05-routeedit__measure-text"></span>
<span data-slot="measure-close"></span>
@@ -149,12 +112,61 @@ export function createRouteEditChrome(
<div class="b05-routeedit__busy" hidden><span></span></div>
<aside class="b05-routeedit__side-wrap"></aside>`;
parent.append(overlay);
// 부른 쪽 단추([3D])도 같은 유리 단추로 — 지도 위에 떠 있는 도구줄.
tools.forEach((tool) => {
tool.classList.replace("ui-btn--ghost", "ui-btn--glass");
tool.setAttribute("data-size", "32"); // 도구줄 단 32(553-2)
// 스냅 간격 고르기 · 설계 지형 이름 칩 — 단추 아님(도구줄에 요소로 끼움).
const extras = document.createElement("div");
extras.innerHTML = `
<label class="b05-routeedit__terrain b05-routeedit__snap-step"
title="측점 스냅 간격 — 곡선 가운데(MC)를 이 간격 배수에 맞춥니다(측점 No. = 측점 간격)">간격
<select class="ui-select ui-select--sm" data-size="32" data-act="snap-step">
<option value="10">10 m</option>
<option value="station">측점 No.</option>
</select>
</label>
<span class="b05-routeedit__terrain" data-slot="terrain-name" hidden
title="설계 지형 — 바꾸기는 [노선 추천] 창에서 지형을 골라 [적용]"></span>`;
const [snapStep, terrainName] = extras.children as unknown as [HTMLElement, HTMLElement];
const mapTools = createMapTools({
label: "평면 도구",
hint: { lines: HINT_LINES, open: hintOpen, onToggle: (open) => (hintOpen = open) },
groups: [
[
mapToolButton("undo", { title: "되돌리기 (Ctrl+Z)", disabled: true }),
mapToolButton("redo", { title: "다시하기 (Ctrl+Y)", disabled: true }),
],
tools, // 부른 쪽 단추([3D])
[
"rotate-ccw",
"rotate-cw",
mapToolButton("contour-minor", {
title: "1m 등고선 — 1m 간격 등고선을 얇고 옅게 겹칩니다 (확대하면 보임)",
disabled: true,
}),
mapToolButton("stations", {
title: "측점 — 측점 눈금 · 번호를 평면에 보이기 · 숨기기 (시점 · 종점 이름표는 늘 보임)",
}),
mapToolButton("slopes", {
title: "기울기 — 측점 사이 기울기(%)를 평면에 보이기 · 숨기기 (확대해야 보임)",
}),
mapToolButton("footprint", {
title: "절/성토선 — 도로 끝 · 비탈 끝(깎기 붉음 · 쌓기 청록)을 평면에 겹칩니다",
}),
mapToolButton("measure", {
title: "거리 재기 — 노선 위 두 점을 눌러 거리·기울기를 잽니다 (Shift+클릭도 같음)",
}),
],
[
mapToolButton("station-snap", {
title:
"측점 스냅 — 켜면 고친 곡선의 가운데(MC)를 고른 간격 배수에 맞춥니다 — 일반측점(20 m)은 그대로 · MC 가 일반측점 위가 아니면 「No.N+10 (MC)」 특별측점을 더함 · 손대지 않은 곡선은 그 자리 유지 · 고정 교각점은 그대로 · 못 맞춘 곡선은 상태줄 「측점 밖 곡선」 · [저장] 때 저장",
}),
snapStep,
],
[terrainName],
],
});
overlay.querySelector('[data-slot="tools"]')!.replaceWith(...tools);
mapTools.root.classList.add("b05-routeedit__tools");
mapTools.hint!.classList.add("b05-routeedit__hint"); // 가이드 촬영 · 엿보기 숨김이 잡음
overlay.querySelector('[data-slot="map-tools"]')!.replaceWith(mapTools.root);
// 횡단 미리보기 = 패널 양식(382-1 · 손잡이 · 머리 · 폭 끌기 · 열림 · 폭 기억) · 자리는 지도 위 덮개 그대로 ·
// 기본 접힘(393-1 · 키 -v2 = 옛 열림 기억 한 번 버림) · 열림 · 닫힘은 손잡이만(513-2) · 잰 값 창이 판을 비킴(--b05-side-open).
@@ -177,17 +189,6 @@ export function createRouteEditChrome(
overlay.querySelector(".b05-routeedit__side-wrap")!.append(panels.root);
setSideOpen();
// 도움말 = 기본 접힘 · [?] 한 번에 펼침(345-5 덧 3).
const hint = overlay.querySelector<HTMLElement>(".b05-routeedit__hint")!;
const hintToggle = overlay.querySelector<HTMLButtonElement>('[data-act="hint-toggle"]')!;
const setHint = (open: boolean): void => {
hintOpen = open;
hint.classList.toggle("is-open", open);
hintToggle.setAttribute("aria-expanded", String(open));
};
setHint(hintOpen);
hintToggle.addEventListener("click", () => setHint(!hintOpen));
// 상태 · 범례 접기 — 접으면 한 줄 「계획노선 N m」(상태 글에서 읽음 · 345-5 덧 3).
const info = overlay.querySelector<HTMLElement>(".b05-routeedit__info")!;
const infoToggle = info.querySelector<HTMLButtonElement>('[data-act="info-toggle"]')!;
@@ -243,16 +244,13 @@ export function createRouteEditChrome(
syncSummary();
const measureBox = overlay.querySelector<HTMLElement>(".b05-routeedit__measure")!;
const measureText = overlay.querySelector<HTMLElement>(".b05-routeedit__measure-text")!;
const measureButton = overlay.querySelector<HTMLButtonElement>('[data-act="measure"]')!;
const measureButton = mapTools.button("measure")!;
const busy = overlay.querySelector<HTMLElement>(".b05-routeedit__busy")!;
const minorContourButton = overlay.querySelector<HTMLButtonElement>(
'[data-act="contour-minor"]',
)!;
const terrainName = overlay.querySelector<HTMLElement>('[data-slot="terrain-name"]')!;
const stationSnapButton = overlay.querySelector<HTMLButtonElement>('[data-act="station-snap"]')!;
bindFootprintToggle(overlay.querySelector<HTMLButtonElement>('[data-act="footprint"]')!);
bindMarkToggle(overlay.querySelector<HTMLButtonElement>('[data-act="stations"]')!, "stations");
bindMarkToggle(overlay.querySelector<HTMLButtonElement>('[data-act="slopes"]')!, "slopes");
const minorContourButton = mapTools.button("contour-minor")!;
const stationSnapButton = mapTools.button("station-snap")!;
bindFootprintToggle(mapTools.button("footprint")!);
bindMarkToggle(mapTools.button("stations")!, "stations");
bindMarkToggle(mapTools.button("slopes")!, "slopes");
return {
overlay,
@@ -169,12 +169,14 @@ export function bindMeasureControls(params: MeasureControlsParams): {
button.addEventListener("click", () => {
mode = !mode;
button.classList.toggle("is-active", mode);
button.setAttribute("aria-pressed", String(mode)); // 아이콘 단추 눌림 꼴(568-2)
if (!mode) measure.clear();
});
params.closeButton.addEventListener("click", () => {
measure.clear(); // 닫으면 잰 것이 지워진다(㉔).
mode = false;
button.classList.remove("is-active");
button.setAttribute("aria-pressed", "false");
sync();
params.redraw();
});
+13 -44
View File
@@ -1,8 +1,8 @@
/* 계획노선 편집(평면) — 종단 보기 가운데 위 본문(옛 3D 자리 · 345-3). 덮개 · 전체 화면은 걷음 —
재계산 대기 막만 화면을 덮는다(노선 재탐색 · CLAUDE.md 5장). 색은 전부 테마 토큰을 쓴다. */
/* 평면 = 가운데 칸 전체(352-1) — 머리 줄 · 테두리 없음. 도구줄은 3D 와 같은 떠 있는 유리 단추
(`.b05-route__view-controls` · ☰ 비킴 `--wf-burger-pad`) · 횡단 판은 오른쪽을 덮는 판. */
/* 평면 = 가운데 칸 전체(352-1) — 머리 줄 · 테두리 없음. 도구줄은 공용 지도 도구줄(568-2 · 유리 아이콘 줄 ·
☰ 비킴 `--wf-burger-pad`) · 횡단 판은 오른쪽을 덮는 판. */
.b05-routeedit {
--b05-side-open: 0px; /* 횡단 판이 지도를 덮는 폭(손잡이 포함 · `_Chrome` 이 적음) */
/* 아래 줄 반 폭(408-4) — 횡단 판 왼쪽 빈 폭을 상태 글판 · 잰 값 창이 반씩(안 겹침 · 좁으면 줄바꿈) ·
@@ -14,15 +14,18 @@
min-height: 0;
}
/* 도구줄 — 3D 도구줄 규격 그대로 · 회전 단추도 이 줄(366-2) · 넘치면 줄바꿈. */
/* 도구줄 — 지도 왼쪽 위(공용 `ui-map-tools` · 넘치면 줄바꿈 · 빈 자리는 지도 조작 통과) · [?] 안내 판은 그 아래. */
.b05-routeedit__tools {
position: absolute;
z-index: 2;
top: var(--spacing-16);
right: var(--spacing-12);
flex-wrap: wrap;
pointer-events: none; /* 빈 자리는 지도 조작을 통과 */
left: var(--spacing-16);
}
.b05-routeedit__tools > * {
pointer-events: auto;
/* ☰ 들여쓰기(7-2) */
html.is-bm-route .b05-routeedit__tools {
left: max(var(--spacing-16), var(--wf-burger-pad, 0px));
}
.b05-routeedit__canvas-wrap {
@@ -43,8 +46,9 @@
/* 지도 위에 얹는 판들 — 아래 정보행을 없애고 여기로 옮겼다(2026-09-12 사용자 지시 ⑩·⑫·⑬).
글판은 **클릭을 통과시킨다** — 지도 조작을 가리면 안 된다(접기 단추만 받음 · 345-5 덧 3). */
.b05-routeedit__hint-body,
.b05-routeedit__info {
position: absolute;
z-index: 1;
border: 1px solid color-mix(in srgb, var(--color-border) 65%, transparent);
border-radius: var(--radius-cards);
background: color-mix(in srgb, var(--color-surface-raised) 72%, transparent);
@@ -55,41 +59,6 @@
pointer-events: none;
}
.b05-routeedit__hint,
.b05-routeedit__info {
position: absolute;
z-index: 1;
}
/* 조작 설명 — 도구줄 아래 왼쪽 · 기본 접힘 · [?] 로 펼침(345-5 덧 3 · 352-1) · 펼치면 **2열**(사용자 지시 ⑩). */
.b05-routeedit__hint {
top: calc(var(--spacing-16) + 32px + var(--spacing-8));
left: var(--spacing-16);
display: flex;
align-items: flex-start;
gap: var(--spacing-8);
max-width: 60%;
pointer-events: none;
}
.b05-routeedit__hint-toggle.ui-btn[data-size] {
flex: none;
padding-inline: var(--spacing-8);
font-weight: 600;
pointer-events: auto;
}
.b05-routeedit__hint-body {
display: none;
grid-template-columns: auto auto;
gap: 2px var(--spacing-16);
padding: var(--spacing-8) var(--spacing-12);
}
.b05-routeedit__hint.is-open > .b05-routeedit__hint-body {
display: grid;
}
/* 상태 · 범례 — 지도 왼쪽 아래(사용자 지시 ⑫) · 뜬 종단 판이 덮으면 그 위로(408-2 · `_Chrome` 이 적음).
두 줄 안 · 좁은 폭(345-5 덧 3) — 왼쪽 상태 글(두 줄에서 자름) · 가운데 범례 두 줄 · 오른쪽 ▾. */
.b05-routeedit__info {
@@ -365,7 +334,7 @@
}
/* 분석 지형이 숨으면 앞 구분선도(390-1) · 스냅 끔이면 간격 고르기 흐림(390-2). */
.b05-route__view-separator:has(+ .b05-routeedit__terrain[hidden]) {
.ui-toolbar__divider:has(+ .b05-routeedit__terrain[hidden]) {
display: none;
}
+2 -1
View File
@@ -135,10 +135,11 @@ export async function fetchPlanSurface(projectId: string): Promise<PlanSurface |
return { modelId: model.id, grid: parseDtmGrid(await fetchCachedBytes(projectId, url)) };
}
/** 지도 겹 그림(569-1) — 작업 좌표계 m 상자 · data URL. */
/** 지도 겹 그림(569-1) — 작업 좌표계 m 상자(칸 가장자리) · data URL(위 = 북) · 단계 색 · 넓이 비율. */
export interface PlanRaster {
box_m: [number, number, number, number];
image: string;
bands: Array<{ min_deg: number; max_deg: number; color: string; ratio: number }>;
}
export const fetchPlanCadastral = (projectId: string): Promise<GeoJsonCollection> =>
+13 -10
View File
@@ -60,11 +60,6 @@ const LAYERS: readonly LayerDef[] = [
const PREF_KEY = "aislo:pref:d03.layers";
/** 등고선을 받아 올 그은 선 둘레(m). */
const CONTOUR_PAD_M = 600;
const SLOPE_STEPS = [
{ label: "15°", token: "--draw-slope-15" },
{ label: "35°", token: "--draw-slope-35" },
{ label: "40°", token: "--draw-slope-40" },
];
interface Prepared {
contour: PreparedLayer;
@@ -142,10 +137,7 @@ export function createPlanLayers(opts: {
bar.classList.add("d03-plan__layers");
const legend = document.createElement("div");
legend.className = "d03-plan__legend";
legend.title = "급경사 색 — 그 값 이상";
legend.append(
createLegend(SLOPE_STEPS.map((step) => ({ label: step.label, color: `var(${step.token})` }))),
);
legend.title = "급경사 색 — 단계 · 받은 지형 안 넓이 비율";
function sync(): void {
for (const def of LAYERS) {
@@ -159,6 +151,8 @@ export function createPlanLayers(opts: {
legend.hidden = !meta || !on.slope || !data.slope;
}
sync();
function toggle(key: PlanLayerKey): void {
on[key] = !on[key];
writeByKey(PREF_KEY, JSON.stringify(on));
@@ -239,6 +233,15 @@ export function createPlanLayers(opts: {
} else {
const raster = await fetchPlanRaster(projectId, "slope");
data.slope = { raster, image: await loadImage(raster.image) };
// 범례 = 서버 단계 색 그대로(색 한 곳)
legend.replaceChildren(
createLegend(
raster.bands.map((band) => ({
label: `${band.min_deg}${band.max_deg < 90 ? `~${band.max_deg}°` : "°↑"} ${(band.ratio * 100).toFixed(1)} %`,
color: band.color,
})),
),
);
}
}
@@ -271,7 +274,7 @@ export function createPlanLayers(opts: {
draw: (context, map) => {
if (!on.cadastral || !data.cadastral) return;
context.globalAlpha = 0.8;
stroke(context, map, data.cadastral, themeColor("--draw-map-parcel", "#facc15"), 0.8);
stroke(context, map, data.cadastral, themeColor("--draw-map-parcel", "#f0abfc"), 0.8);
},
},
{
@@ -29,7 +29,6 @@
position: absolute;
top: var(--spacing-8);
right: var(--spacing-8);
align-items: flex-end;
}
/* 569-2 바탕 겹 단추 — 왼쪽 위 · 급경사 범례 — 왼쪽 아래 */
+4 -3
View File
@@ -52,10 +52,11 @@ global.document = { createElement: (tag) => new FakeNode(tag) };
const modals = [];
const STUBS = {
"@ui/ui_template_elements": {
createButton: ({ label, onClick }) => {
"@ui/ui_template_map_tools": {
mapToolButton: (act, { title, onClick }) => {
const button = new FakeNode("button");
button.label = label;
button.act = act;
button.title = title;
if (onClick) button.addEventListener("click", onClick);
return button;
},
@@ -28,15 +28,13 @@ def _rule(selector: str) -> str:
def test_도움말_기본_접힘_펼침_기억() -> None:
assert "let hintOpen = false;" in CHROME # 기본 접힘 · 모듈 값 = 페이지 동안 기억
assert 'data-act="hint-toggle" aria-expanded="false"' in CHROME and ">?</button>" in CHROME
assert "hintOpen = open;" in CHROME and 'hint.classList.toggle("is-open", open);' in CHROME
assert "setHint(hintOpen);" in CHROME
assert 'hintToggle.addEventListener("click", () => setHint(!hintOpen));' in CHROME
# 접힘 = 몸통 없음 · 펼침 = 2열 · 단추만 클릭 받음
assert "display: none;" in _rule(".b05-routeedit__hint-body")
assert "display: grid;" in _rule(".b05-routeedit__hint.is-open > .b05-routeedit__hint-body")
assert "pointer-events: auto;" in _rule(".b05-routeedit__hint-toggle.ui-btn[data-size]")
assert "pointer-events: none;" in _rule(".b05-routeedit__hint")
# 568-2 — [?] 단추 · 안내 판 = 공용 지도 도구줄(접힘 · 2열 · 클릭 통과는 test_568_1)
assert (
"hint: { lines: HINT_LINES, open: hintOpen, onToggle: (open) => (hintOpen = open) }"
in CHROME
)
assert 'mapTools.hint!.classList.add("b05-routeedit__hint")' in CHROME # 가이드 촬영 선택자
assert '"Ctrl+휠 = 돌리기(한 칸 5°)",' in CHROME
def test_상태_범례_접기_한_줄_기억() -> None:
@@ -63,7 +61,8 @@ def test_상태_글판_작게_반투명_자리() -> None:
# 상태 글 두 줄에서 자름 · 범례는 옆 칸 두 줄
assert "-webkit-line-clamp: 2;" in _rule(".b05-routeedit__status")
assert "flex-direction: column;" in _rule(".b05-routeedit__legend")
glass = _rule(".b05-routeedit__hint-body,\n.b05-routeedit__info")
glass = CSS[CSS.index("\n.b05-routeedit__info {") :]
glass = glass[: glass.index("}")]
assert "color-mix(in srgb, var(--color-surface-raised) 72%, transparent)" in glass
assert not re.search(r"#[0-9a-fA-F]{3,6}\b", glass) # 바탕 · 글 색은 토큰
@@ -104,7 +104,7 @@ def test_편집기가_발자국을_그리고_단추로_켬끔() -> None:
# 등고선 뒤 · 노선 앞(노선이 발자국 위에 보임)
assert render.index("drawFootprint(context") < render.index("scene.expected,")
chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8")
assert 'data-act="footprint"' in chrome and "bindFootprintToggle(" in chrome
assert 'mapToolButton("footprint"' in chrome and "bindFootprintToggle(" in chrome
footprint = (B05 / "B05_Profile_UI_RouteEdit_Footprint.ts").read_text(encoding="utf-8")
assert 'subscribe("footprint"' in footprint and "let shown = true;" in footprint
css = (B05 / "B05_Profile_UI_Style_RouteEdit_Footprint.css").read_text(encoding="utf-8")
@@ -195,5 +195,5 @@ def test_깎기_쌓기_바뀜_자리는_도로_끝(pieces) -> None:
def test_단추_이름은_절성토선() -> None:
chrome = (ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text("utf-8")
at = chrome.index('data-act="footprint"')
assert ">절/성토선</button>" in chrome[at : at + 200] and ">발자국<" not in chrome
at = chrome.index('mapToolButton("footprint"')
assert 'title: "절/성토선 — ' in chrome[at : at + 200] and "발자국" not in chrome
+1 -1
View File
@@ -124,7 +124,7 @@ def test_draw_values_are_literal_hex():
assert draws, mode
for name, value in draws.items():
assert HEX.fullmatch(value), (mode, name, value)
assert len([k for k in blocks["light"] if k.startswith("--draw-")]) == 24
assert len([k for k in blocks["light"] if k.startswith("--draw-")]) == 21
def test_drawing_css_uses_no_screen_tokens():
+25 -26
View File
@@ -209,40 +209,39 @@ def test_노선_추천_다듬기_23_3():
return open(os.path.join(page, name), encoding="utf-8").read()
chrome = read("B05_Profile_UI_RouteEdit_Chrome.ts")
# 366-2 — [?] | ↶ ↷ | 3D | ⟲ ⟳ · 1m 등고선 · 절/성토선 · 거리 재기 | 측점 스냅 · 간격 | 분석 지형(맨 오른쪽)
# 366-2 · 568-2 — [?] | ↶ ↷ | 3D | ⟲ ⟳ · 1m 등고선 · 측점 · 기울기 · 절/성토선 · 거리 재기 | 측점 스냅 · 간격 |
# 설계 지형 이름(맨 오른쪽) — 공용 지도 도구줄 묶음 차례([?] 는 hint 가 맨 앞에 붙임)
bar = chrome[
chrome.index("b05-routeedit__tools") : chrome.index('<div class="b05-routeedit__hint">')
chrome.index("createMapTools({") : chrome.index('classList.add("b05-routeedit__tools")')
]
order = [
'data-act="hint-toggle"',
"|",
'data-act="undo"',
'data-act="redo"',
"|",
'data-slot="tools"',
"|",
'data-act="rotate-ccw"',
'data-act="rotate-cw"',
'data-act="contour-minor"',
'data-act="stations"', # 381-2 [측점] [기울기] = 절/성토선 왼쪽
'data-act="slopes"',
'data-act="footprint"',
'data-act="measure"',
"|",
'data-act="station-snap"',
'data-act="snap-step"', # 390-2 스냅 간격 = [측점 스냅] 오른쪽
"|", # 390-1 [측점 스냅] | 설계 지형 이름(509-5)
'class="b05-routeedit__terrain" data-slot="terrain-name" hidden',
"hint: { lines: HINT_LINES",
"groups: [",
'mapToolButton("undo"',
'mapToolButton("redo"',
"],",
"tools, // 부른 쪽 단추([3D])",
'"rotate-ccw",',
'"rotate-cw",',
'mapToolButton("contour-minor"',
'mapToolButton("stations"', # 381-2 [측점] [기울기] = 절/성토선 왼쪽
'mapToolButton("slopes"',
'mapToolButton("footprint"',
'mapToolButton("measure"',
"],",
'mapToolButton("station-snap"',
"snapStep,", # 390-2 스냅 간격 = [측점 스냅] 오른쪽
"],", # 390-1 [측점 스냅] | 설계 지형 이름(509-5)
"[terrainName],",
]
at = 0
for mark in order:
key = "b05-route__view-separator" if mark == "|" else mark
at = bar.index(key, at) + 1
assert bar.count("b05-route__view-separator") == 5
assert chrome.count("b05-route__view-separator") == 5
at = bar.index(mark, at) + 1
assert 'class="b05-routeedit__terrain" data-slot="terrain-name" hidden' in chrome
assert "b05-route__view-separator" not in chrome # 나눔선 = 공용 도구줄
# 분석 지형이 숨으면 앞 구분선도 숨김 · 스냅 끔이면 간격 고르기 흐림
style = read("B05_Profile_UI_Style_RouteEdit.css")
assert ".b05-route__view-separator:has(+ .b05-routeedit__terrain[hidden])" in style
assert ".ui-toolbar__divider:has(+ .b05-routeedit__terrain[hidden])" in style
assert '[data-act="station-snap"][aria-pressed="false"] + .b05-routeedit__snap-step' in style
assert "b05-routeedit__spin" not in chrome # 회전 = 도구줄(추천 모달 CSS 는 남음)
shared = read("B05_Profile_UI_RouteEdit_Recommend_Map.ts")
+6
View File
@@ -31,3 +31,9 @@
.ui-map-tools__hint.is-open {
display: grid;
}
/* 숨김 — display 가 [hidden] 을 덮지 않게 */
.ui-map-tools[hidden],
.ui-map-tools__hint[hidden] {
display: none !important;
}
+3 -3
View File
@@ -77,9 +77,9 @@ export interface MapTools {
export function createMapTools(opts: MapToolsOptions): MapTools {
const root = document.createElement("div");
root.className = "ui-map-tools";
const groups = opts.groups.map((group) =>
group.map((item) => (typeof item === "string" ? mapToolButton(item) : item)),
);
const groups = opts.groups
.filter((group) => group.length)
.map((group) => group.map((item) => (typeof item === "string" ? mapToolButton(item) : item)));
let hint: HTMLElement | null = null;
if (opts.hint) {
const { lines, onToggle } = opts.hint;
+1 -4
View File
@@ -554,10 +554,7 @@ html.is-bm-route .m01lab-src__body * {
--draw-map-contour: #ffd68c; /* 위성 위 겹(노선분석) — 도엽 등고선 */
--draw-map-stream: #38bdf8; /* 세류 · 하천 */
--draw-map-road: #f8fafc; /* 기존 도로 */
--draw-map-parcel: #facc15; /* 지적 경계 */
--draw-slope-15: #fde047; /* 급경사 색 15° 이상 */
--draw-slope-35: #f97316; /* 35° 이상 */
--draw-slope-40: #dc2626; /* 40° 이상 */
--draw-map-parcel: #f0abfc; /* 지적 경계 */
}
:root[data-theme="dark"] {