feat(지도): 634-2 · 634-3 아이콘 표 두 장 · 묶음 메뉴 · 한 행 배선 · 호버 = 표(요구 546)

- 겹 표(map_layer_list) = 그룹1 22 줄 표 차례 · title = 호버제목 · group(수계망도 · 도로 · 토지·건물 · 제한·재해) · pages · 숨김 표 걷음 · 도로300m 한 줄(「도로」 묶음 · 기능 남김)
- 겹 줄(map_layer_bar) = 단독 넷 | 묶음 넷(여럿 고르기 메뉴 · 켠 수 배지 · 안 닫힘) | 그래픽 카드 · sync 가 단추 · 메뉴 줄 막힘 · 글을 맞춤(why = 페이지 까닭)
- 도구 표(map_toolbar) = 그룹2 17 줄 title · mapToolGroups 가 글을 표로 맞춤 · 기본 단추 생성 · 지형 칩 막힘 「수치지형도/라이다 — 까닭」
- 띠 = 한 자료 · 겹 둘(GL fillEdge 거름 · 옛 캔버스 · 범례 줄 따로) · B05 띠 받기(D03 과 같은 서버 길 · bandCollection 공용)
- D03 = 겹 줄 · 도구줄 한 행(createMapToolRow) · 222px 기댐 · 줄바꿈 CSS 걷음 · 측점 · 기울기 호버 덮어쓰기 걷음 · LAS 올리기 호버 걷음
- B05 = 세로 두 줄 → 한 행 · 호버 덮어쓰기 걷음(도구줄 · 1m 등고선 · 3D · 추천) · 겹 막힘 querySelector 덮어쓰기 → 공용 sync · 임도(업로드) 보임 + 「받은 임도 자료 없음」 막힘 · 스냅 간격 날 select → 고르기 메뉴(하나)
- 켬끔 기억 열쇠 · 측점 · 기울기 처음 값 · 그래픽 카드 취향 열쇠 둘 그대로
- 낡은 시험 고침(568_1 · 622_2 · 624_2 · 624_3 · 553_4 · 625_draw · b05_routeedit_initial · 626_3 · 390_4 · 346_2 · 353_1 · 581_2 · 584_1 · 584_3) — 표 꼴 row() · 호버 글 = 표 · 한 행 · 고르기 메뉴로 바뀐 글을 잡음 · 새 지킴 634_2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 11:40:26 +09:00
co-authored by Claude Opus 5.5
parent 2265ac4f7c
commit 61733d064d
34 changed files with 527 additions and 428 deletions
@@ -104,7 +104,8 @@ 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 'mapToolButton("footprint"' in chrome and "bindFootprintToggle(" in chrome
# 634-2 — 단추 = 공용 표 기본 단추(`mapToolGroups`) · 켬끔 배선은 페이지
assert 'bindFootprintToggle(mapTools.button("footprint")!)' 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,6 @@ def test_깎기_쌓기_바뀜_자리는_도로_끝(pieces) -> None:
def test_단추_이름은_절성토선() -> None:
chrome = (ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text("utf-8")
at = chrome.index('mapToolButton("footprint"')
assert 'title: "절/성토선 — ' in chrome[at : at + 200] and "발자국" not in chrome
# 634-2 — 호버 = 공용 표 「절성토선」(요구 546 · 페이지 덮어쓰기 없음)
table = (ROOT / "A00_Common" / "map" / "map_toolbar.ts").read_text("utf-8")
assert 'key: "footprint", title: "절성토선"' in table and "발자국" not in chrome
@@ -59,15 +59,16 @@ def test_편집기는_고른_간격_글을_씀() -> None:
assert edit.count("applyEdit(snap.text())") == 1 # 켬 · 간격 바꿈
assert 'applyEdit(stationSnap ? snap.text() : "옮겼습니다.")' in edit # 놓을 때(430)
snap = SNAP.read_text(encoding="utf-8")
assert "text: () => snapFitText(select.value)" in snap
assert "text: () => snapFitText(value())" in snap # 634-3 고르기 메뉴 값
def test_고르기는_10_m_측점_No_둘() -> None:
"""280-4 — 고르기 option 둘 · STEPS 둘(옛 pref 0.1 · 1 = 측점 No.)."""
"""280-4 — 고르기 줄 둘 · STEPS 둘(옛 pref 0.1 · 1 = 측점 No.) · 634-3 날 <select> → 고르기 메뉴 하나."""
import re
chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8")
block = chrome[chrome.index('data-act="snap-step"') :]
block = block[: block.index("</select>")]
assert re.findall(r'<option value="([^"]+)"', block) == ["10", "station"]
block = chrome[chrome.index('icon: "spacing"') :]
block = block[: block.index("onPick")]
assert re.findall(r'value: "([^"]+)"', block) == ["10", "station"]
assert "<select" not in chrome
assert 'const STEPS = ["10", "station"];' in SNAP.read_text(encoding="utf-8")
+9 -26
View File
@@ -70,32 +70,15 @@ def test_d03_parts():
)
assert "createToolbar(" not in layers and "createToolbar(" not in bar
assert '"aislo:pref:d03.layers"' in defs and "writeByKey(MAP_LAYER_PAGES[page].pref" in bar
keys = re.findall(r'key: "(\w+)",\s+icon: "\w+",\s+title:\s+"[^"]+",\s+on: (true|false)', defs)
assert keys == [
("satellite", "true"),
("hillshade", "false"),
("contour", "true"),
("slope", "false"),
("stream", "true"),
("water", "false"),
("road", "false"),
("nationalRoad", "false"),
("forestRoadFile", "false"),
("cadastral", "false"),
("zones", "false"),
("soils", "false"),
("ecoForest", "false"),
("ecology", "false"),
("landslide", "false"),
("landslideZones", "false"),
("faults", "false"),
("lineaments", "false"),
("zeroOrder", "false"),
("bands", "false"),
("building", "false"),
("place", "false"),
], keys
assert ".d03-plan__layers" in css and ".d03-plan__legend" in css
# 634-2 — 표 차례(요구 546 그룹1 22 + 도로300m) · row(열쇠, 아이콘, 호버제목, 묶음[, true])
rows = re.findall(r'row\("(\w+)", "\w+", "[^"]+", (?:"\w+"|null)(, true)?\)', defs)
keys = [(key, "true" if first else "false") for key, first in rows]
on = {"satellite", "contour", "stream"}
order = "satellite hillshade contour slope stream water zeroOrder bands road nationalRoad"
order += " forestRoadFile roadBands cadastral building place zones soils ecoForest ecology"
order += " landslide landslideZones faults lineaments"
assert keys == [(key, str(key in on).lower()) for key in order.split()], keys
assert ".d03-plan__row" in css and ".d03-plan__legend" in css # 634-3 한 행
# 599-1 — 방식 칸 = 마디 고름 2 × 2 · 길이 · 기울기 두 열 · 노드 판(598-4)과 같은 함수
# 626-3 — 방식 칸은 공용 `A00_Common/map/map_draw_kind`(B05 평면도 씀) · D03 칸은 다시 내보냄
kind = _src("A00_Common/map/map_draw_kind.ts")
+3 -2
View File
@@ -86,8 +86,9 @@ def test_soils_fail_raises(tmp_path, monkeypatch) -> None:
def test_screen_def_and_colors() -> None:
defs = (ROOT / "A00_Common" / "map" / "map_layer_list.ts").read_text("utf-8") # 624-2
entry = re.search(r'key: "soils",\s*icon: "(\w+)",[^}]*on: (\w+)', defs)
assert entry and entry.group(2) == "false" # 처음 값 끔
# 634-2 표 한 줄 = row(열쇠, 아이콘, 호버제목, 묶음[, true]) · true 없음 = 처음 끔
entry = re.search(r'row\("soils", "(\w+)", "[^"]+", (?:"\w+"|null)(, true)?\)', defs)
assert entry and not entry.group(2) # 처음 값 끔
icons = (ROOT / "ui_template" / "ui_template_icons.ts").read_text("utf-8")
assert re.search(rf"^\s+{entry.group(1)}:", icons, re.M)
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
@@ -199,8 +199,9 @@ def test_screen_defs_icons_colors() -> None:
("lineaments", "lineament"),
("zeroOrder", "zero-order"),
):
entry = re.search(rf'key: "{key}",\s*icon: "(\w+)",[^}}]*on: (\w+)', defs)
assert entry and entry.group(2) == "false", key # 처음 값 끔
# 634-2 표 한 줄 = row(열쇠, 아이콘, 호버제목, 묶음[, true]) · true 없음 = 처음 끔
entry = re.search(rf'row\("{key}", "(\w+)", "[^"]+", (?:"\w+"|null)(, true)?\)', defs)
assert entry and not entry.group(2), key # 처음 값 끔
assert re.search(rf"^\s+{entry.group(1)}:", icons, re.M), key
assert f"--draw-map-{color}:" in theme and f'"--draw-map-{color}"' in defs
for kind in layers.LINE_KINDS:
@@ -339,8 +339,9 @@ def test_screen_defs() -> None:
("forestRoadFile", "forest-road-file"),
("landslideZones", "landslide-zone"),
):
entry = re.search(rf'key: "{key}",\s*icon: "(\w+)",[^}}]*on: (\w+)', defs)
assert entry and entry.group(2) == "false", key # 처음 값 끔
# 634-2 표 한 줄 = row(열쇠, 아이콘, 호버제목, 묶음[, true]) · true 없음 = 처음 끔
entry = re.search(rf'row\("{key}", "(\w+)", "[^"]+", (?:"\w+"|null)(, true)?\)', defs)
assert entry and not entry.group(2), key # 처음 값 끔
assert re.search(rf"^\s+{entry.group(1)}:", icons, re.M), key
assert f"--draw-map-{color}:" in theme and f'"--draw-map-{color}"' in page
site = (folder / "D03_PlanAnalysis_UI_Site.ts").read_text("utf-8") # 596-2 올리기 = 위치 칸
+6 -5
View File
@@ -36,13 +36,14 @@ def test_fixed_sources_follow_plan_622_routes() -> None:
def test_layer_list_five_off_by_default() -> None:
defs = _read(MAP / "map_layer_list.ts")
icons = _read(ROOT / "ui_template" / "ui_template_icons.ts")
# 634-2 — 표 한 줄 = row(열쇠, 아이콘, 호버제목, 묶음[, 처음 켬]) · 처음 켬 빠짐 = 끔
for key in ("nationalRoad", "soils", "ecoForest", "ecology", "landslide"):
entry = re.search(rf'key: "{key}",\s*icon: "(\w+)",[^}}]*on: (\w+)', defs)
assert entry and entry.group(2) == "false", key
entry = re.search(rf'row\("{key}", "(\w+)", "[^"]+", "\w+"(, true)?\)', defs)
assert entry and not entry.group(2), key
assert re.search(rf"^\s+{entry.group(1)}:", icons, re.M), entry.group(1)
# 630 — 임도망도 = 짐작 자리 · route 아이콘 · 짐작 겹 걷음
assert re.search(r'key: "nationalRoad",\s*icon: "route",\s*title:\s*"임도망도\(', defs)
assert 'key: "forestRoad",' not in defs
# 630 — 임도망도 = 짐작 자리 · route 아이콘 · 짐작 겹 걷음 · 호버 = 표 「임도망도」
assert 'row("nationalRoad", "route", "임도망도", "road")' in defs
assert 'row("forestRoad",' not in defs
assert "item(`임도망도 ${label}`" in _read(MAP / "map_forest_layers.ts")
forest = _read(MAP / "map_forest_layers.ts")
assert '"nationalRoad-label"' in forest and "minzoom: 14" in forest # 534 줌인 글
+12 -5
View File
@@ -30,8 +30,9 @@ def _toolbar_rows() -> list[list[tuple[str, set[str]]]]:
def test_toolbar_table_d03_same_as_before_plus_terrain() -> None:
d03 = [[k for k, p in g if "d03" in p] for g in _toolbar_rows()]
d03 = [g for g in d03 if g]
# 옛 D03 도구줄 세 묶음 그대로 + 끝에 지형 칩
# 옛 D03 도구줄 세 묶음 그대로 + 끝에 지형 칩 · 맨 앞 [?] 도움말 줄(634-2 표 17 줄 · 단추는 `hint`)
assert d03 == [
["hint-toggle"],
["undo", "redo"],
["view3d", "rotate-ccw", "rotate-cw"],
["stations", "slopes", "measure"],
@@ -47,17 +48,23 @@ def test_toolbar_table_d03_same_as_before_plus_terrain() -> None:
"terrain",
} <= b05
tools = _read(ROOT / "ui_template" / "ui_template_map_tools.ts")
for key in {k for g in _toolbar_rows() for k, _ in g} - {"snap-step", "recommend", "terrain"}:
for key in {k for g in _toolbar_rows() for k, _ in g} - {
"hint-toggle",
"snap-step",
"recommend",
"terrain",
}:
assert f'"{key}": {{' in tools or f"{key}: {{" in tools, key # 아이콘 · 이름 = MAP_TOOLS
def test_layer_pages() -> None:
src = _read(MAP / "map_layer_list.ts")
assert 'd03: { hide: [], pref: "aislo:pref:d03.layers" }' in src
assert 'd03: { pref: "aislo:pref:d03.layers" }' in src
b05 = re.search(r"b05: \{(.*?)\},", src, re.S).group(1)
assert 'hide: ["bands", "forestRoadFile"]' in b05 # A⑨
# 634-2 — 숨김 표 걷음(요구 546 표 = 22 겹 두 페이지 사용) · 보일 페이지 = 겹 줄 `pages`
assert "hide" not in b05
assert 'on: ["contour", "stream"]' in b05 # A② 지금 그대로
assert "for (const key of hide) on[key] = false;" in src
assert "if (!def.pages.includes(page)) on[def.key] = false;" in src
def test_common_parts_use_kit_and_no_page_imports() -> None:
+4 -2
View File
@@ -16,7 +16,8 @@ read = lambda path: path.read_text(encoding="utf-8") # noqa: E731
def test_도구줄_표_겹_줄_범례() -> None:
chrome = read(B05 / "B05_Profile_UI_RouteEdit_Chrome.ts")
assert 'mapToolGroups("b05", {' in chrome
assert "toolRows.append(mapTools.root, layers.bar)" in chrome
# 634-3 — 한 행 두 영역(왼쪽 겹 줄 · 오른쪽 도구줄)
assert "createMapToolRow(layers.bar, mapTools.root)" in chrome
assert "info.prepend(layers.legend)" in chrome
layers = read(B05 / "B05_Profile_UI_RouteEdit_Layers.ts")
assert 'page: "b05"' in layers and "legendRows(" in layers and "mapLayerSpecs(" in layers
@@ -36,7 +37,8 @@ def test_띠_자르기는_등고선_하천만() -> None:
def test_처음_겹_숨긴_겹() -> None:
src = read(ROOT / "A00_Common" / "map" / "map_layer_list.ts")
b05 = re.search(r"b05: \{(.*?)\},", src, re.S).group(1)
assert 'hide: ["bands", "forestRoadFile"]' in b05 and 'on: ["contour", "stream"]' in b05
# 634-2 — 숨김 걷음(띠 · 임도(업로드) 보임 · 임도(업로드) = 받은 자료 없으면 막힘 + 까닭)
assert "hide" not in b05 and 'on: ["contour", "stream"]' in b05
def test_D03_안_부름_파일_한도() -> None:
+2 -1
View File
@@ -186,4 +186,5 @@ def test_놓인_노드_미리보기_부채꼴_원_위에_그림():
body = body[: body.index("\n}\n")]
assert body.index("extra();") < body.index("paintNodes(")
css = (ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_UI_Style.css").read_text(encoding="utf-8")
assert re.search(r"\.d03-plan__tools > \.ui-toolbar \{[^}]*flex-wrap: wrap", css)
# 634-3 — 줄바꿈 걷음 · 한 행(공용 `ui-map-row`) · 영역마다 가로 밀기
assert ".d03-plan__row {" in css and "flex-wrap: wrap" not in css
+7 -2
View File
@@ -98,8 +98,13 @@ def test_D03_긋기_고르기_메뉴_속성판_그대로_한_선():
props = _src(D03 / "D03_PlanAnalysis_UI_Props.ts")
assert "!!params.oneLine ||" in props and "...(params.oneLine ? [] :" in props
tools = _src(ROOT / "A00_Common" / "map" / "map_toolbar.ts")
assert re.search(r'key: "expected-draw",\s+does: "[^"]+",\s+pages: B05', tools)
assert re.search(r'key: "expected-original", does: "[^"]+", pages: B05', tools)
# 634-2 — 표 줄에 호버제목(title) · 걷음은 635-3
assert re.search(
r'key: "expected-draw",\s+title: "[^"]+",\s+does: "[^"]+",\s+pages: B05', tools
)
assert re.search(
r'key: "expected-original",\s+title: "[^"]+",\s+does: "[^"]+",\s+pages: B05', tools
)
render = _src(B05 / "B05_Profile_UI_RouteEdit_Render.ts")
# 맨 위 = 구조물 마커 뒤 · 회전 끝 앞
assert render.index("scene.overAll?.(context);") < render.index("context.restore(); // 회전 끝")
+111
View File
@@ -0,0 +1,111 @@
"""634-2 표 두 장(요구 546) — 그룹1 22 겹 호버제목 · 묶음 · 그룹2 17 줄 호버제목 · 페이지 덮어쓰기 없음 · 계류 · 도로 띠 둘."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
MAP = ROOT / "A00_Common" / "map"
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")
# 요구 546 그룹1 표(호버제목 · 아이콘 그룹명) + 도로300m(표 밖 · 「도로」 묶음)
GROUP1 = [
("satellite", "위성사진", None),
("hillshade", "음영기복도", None),
("contour", "등고선", None),
("slope", "급경사", None),
("stream", "세류", "water"),
("water", "하천", "water"),
("zeroOrder", "0차곡", "water"),
("bands", "계류100m", "water"),
("road", "도로", "road"),
("nationalRoad", "임도망도", "road"),
("forestRoadFile", "임도(업로드)", "road"),
("roadBands", "도로300m", "road"),
("cadastral", "지적도", "land"),
("building", "건물", "land"),
("place", "지명", "land"),
("zones", "보호규제구역", "limit"),
("soils", "산림입지도", "limit"),
("ecoForest", "국유림·사유림", "limit"),
("ecology", "생태자연도", "limit"),
("landslide", "산사태위험지도", "limit"),
("landslideZones", "산사태취약지역", "limit"),
("faults", "단층", "limit"),
("lineaments", "지질구조선", "limit"),
]
GROUP2 = [
("hint-toggle", "도움말"),
("undo", "되돌리기"),
("redo", "다시하기"),
("view3d", "3D"),
("rotate-ccw", "좌 회전"),
("rotate-cw", "우 회전"),
("contour-minor", "등고선 1m"),
("stations", "측점"),
("slopes", "길이·기울기"),
("footprint", "절성토선"),
("measure", "길이측정"),
("station-snap", "스냅"),
("snap-step", "스냅 간격"),
("expected-draw", "예정노선 변경"),
("expected-original", "예정노선 원본"),
("recommend", "추천노선"),
("terrain", "수치지형도/라이다"),
]
def test_그룹1_표_차례_호버_묶음():
src = _read(MAP / "map_layer_list.ts")
rows = re.findall(r'row\("(\w+)", "\w+", "([^"]+)", (?:"(\w+)"|null)', src)
assert [(k, t, g or None) for k, t, g in rows] == GROUP1
for name in ("수계망도", "도로", "토지·건물", "제한·재해"):
assert f'name: "{name}"' in src
# 표 「사용」 = 두 페이지 모두 · 숨김 표 없음
assert "pages: BOTH" in src and "hide" not in src[src.index("MAP_LAYER_PAGES") :]
def test_그룹2_표_호버():
src = _read(MAP / "map_toolbar.ts")
rows = re.findall(r'key: "([\w-]+)",\s+title: "([^"]+)"', src)
assert rows == GROUP2
tools = _read(ROOT / "ui_template" / "ui_template_map_tools.ts")
for key, title in GROUP2[1:-4]:
if key != "snap-step":
assert re.search(rf'"?{key}"?: \{{ icon: "\w+", title: "{title}"', tools), key
def test_페이지_호버_덮어쓰기_없음():
files = [
ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Chrome.ts",
ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_UI_Map.ts",
ROOT / "A00_Common" / "design" / "design_view_profile_3d_modal.ts",
]
for path in files:
for call in re.findall(r"mapToolButton\([^)]*\)", _read(path), re.S):
assert "title" not in call, (path.name, call)
layers = _read(ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Layers.ts")
assert (
"layerBar.root.querySelector" not in layers
and "layerBar.sync({ ready: !!meta, glOn, why })" in layers
)
assert 'forestRoadFile: "받은 임도 자료 없음"' in layers # B05 임도(업로드) 막힘 + 까닭
edit = _read(ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit.ts")
assert 'mapToolTip(minorContourButton, contours?.minor ? null : "LAS 등고선 없음")' in edit
bar = _read(MAP / "map_layer_bar.ts")
assert 'const GL_TITLE = "그래픽 카드";' in bar and "multi: true" in bar
def test_계류_도로_띠_둘():
specs = _read(MAP / "map_layer_specs.ts")
assert 'fillEdge("roadBands", "bands"' in specs and 'fillEdge("bands", "bands"' in specs
assert specs.index('fillEdge("roadBands"') < specs.index('fillEdge("bands", "bands"') # 옛 차례
legend = _read(MAP / "map_legend.ts")
assert "...(on.bands" in legend and "...(on.roadBands" in legend
paint = _read(ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_UI_LayerPaint.ts")
assert "if (on.roadBands)" in paint and "if (on.bands)" in paint
b05 = _read(ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Layers.ts")
assert 'key === "bands" || key === "roadBands"' in b05 and "fetchPlanBands(projectId)" in b05
@@ -211,12 +211,7 @@ def test_노선_추천_다듬기_23_3():
'mapToolButton("redo"',
"view3d: tools[0]", # 부른 쪽 단추([3D])
'mapToolButton("contour-minor"',
'mapToolButton("stations"',
'mapToolButton("slopes"',
'mapToolButton("footprint"',
'mapToolButton("measure"',
'mapToolButton("station-snap"',
'"snap-step": snapStep,',
'"snap-step": snapStep.root,', # 634-3 고르기 메뉴 · 측점 · 기울기 · 절성토선 · 재기 · 스냅 = 표 기본 단추
"recommend: preview.root,",
"terrain: terrain.root,",
]