From e18753416fdea27cd859b9a2597462059ea16f92 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 19 Jul 2026 11:36:25 +0900 Subject: [PATCH] 260719_5 --- B03_FileInput/B03_FileInput_UI_Style.css | 21 +++++-- .../B05_wf2_Route_UI_Profile_Panel.ts | 10 ++-- .../B06_wf3_ProfileCross_UI_Section_View.ts | 16 +++++- .../B06_wf3_ProfileCross_UI_Style.css | 4 ++ B10_Payment/B10_Payment_UI_Page.ts | 2 +- scratch/test_crs_verification.py | 56 +++++++++++++------ scratch/wiki_linter.py | 43 ++++++++------ 7 files changed, 106 insertions(+), 46 deletions(-) diff --git a/B03_FileInput/B03_FileInput_UI_Style.css b/B03_FileInput/B03_FileInput_UI_Style.css index bb795b76..2726521f 100644 --- a/B03_FileInput/B03_FileInput_UI_Style.css +++ b/B03_FileInput/B03_FileInput_UI_Style.css @@ -1,6 +1,10 @@ .b03-file { min-height: calc(100vh - var(--app-header-height, 0px)); - background: linear-gradient(180deg, var(--color-canvas, #ffffff) 0%, var(--color-mist-violet, #edecff) 100%); + background: linear-gradient( + 180deg, + var(--color-canvas, #ffffff) 0%, + var(--color-mist-violet, #edecff) 100% + ); padding: 0 var(--spacing-24) var(--spacing-48) var(--spacing-24); /* 상단 여백은 main-layout 마진으로 조정하므로 padding-top은 0 */ } @@ -52,7 +56,10 @@ gap: var(--spacing-8); cursor: pointer; text-align: center; - transition: background var(--transition-base, 0.2s), border-color var(--transition-base, 0.2s), box-shadow var(--transition-base, 0.2s); + transition: + background var(--transition-base, 0.2s), + border-color var(--transition-base, 0.2s), + box-shadow var(--transition-base, 0.2s); } .b03-file__dropzone:hover { @@ -140,7 +147,10 @@ display: flex; flex-direction: column; gap: var(--spacing-20); - transition: transform var(--transition-base, 0.2s), border-color var(--transition-base, 0.2s), box-shadow var(--transition-base, 0.2s); + transition: + transform var(--transition-base, 0.2s), + border-color var(--transition-base, 0.2s), + box-shadow var(--transition-base, 0.2s); } .b03-file__card:hover { @@ -258,7 +268,9 @@ font-size: var(--text-body-sm, 14px); cursor: pointer; margin-bottom: var(--spacing-8); /* 파일 선택 버튼 하단 마진 추가 */ - transition: background var(--transition-base, 0.2s), border-color var(--transition-base, 0.2s); + transition: + background var(--transition-base, 0.2s), + border-color var(--transition-base, 0.2s); } .b03-file__file-info { @@ -373,4 +385,3 @@ grid-template-columns: 1fr; /* 모바일에서는 1행 1열 구조 */ } } - diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts index 0cf0fb85..7026ce4a 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts @@ -10,6 +10,7 @@ import { createWorkflowPanelHandle } from "@ui/ui_template_overlay"; import "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css"; const COLLAPSED_KEY = "b05-route-profile-collapsed"; +const HORIZONTAL_SCROLLBAR_HEIGHT = 16; function normalizedLongitudinal(data: LongitudinalSection): LongitudinalSection { return { @@ -43,11 +44,9 @@ export function createRouteProfilePanel(onSelectStation: (stationId: string) => function draw(): void { if (!detail || body.clientWidth <= 0 || body.clientHeight <= 0) return; const availableWidth = Math.max(1, body.clientWidth - 30); - const height = body.clientHeight; - const width = Math.max( - availableWidth, - longitudinalMinimumWidth(detail.longitudinal, stationInterval), - ); + const height = Math.max(1, body.clientHeight - HORIZONTAL_SCROLLBAR_HEIGHT); + const minimumWidth = longitudinalMinimumWidth(detail.longitudinal, stationInterval); + const width = Math.max(availableWidth, minimumWidth); lastWidth = body.clientWidth; lastHeight = height; body.replaceChildren( @@ -60,6 +59,7 @@ export function createRouteProfilePanel(onSelectStation: (stationId: string) => stationInterval, width, height, + minimumWidth, ), ); } diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts index 870e0186..85c4fed2 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts @@ -11,6 +11,8 @@ const LONG_WIDTH = 1200; const LONG_HEIGHT = 220; const CROSS_WIDTH = 560; const CROSS_HEIGHT = 250; +const CROSS_GRID_MIN_WIDTH = 480; +const CROSS_GRID_GAP = 16; const LONG_PAD = { left: 62, right: 24, top: 30, bottom: 52 }; const CROSS_PAD = { left: 58, right: 20, top: 20, bottom: 52 }; @@ -119,6 +121,7 @@ export function createLongitudinalProfile( configuredStationInterval?: number, widthPx = LONG_WIDTH, heightPx = LONG_HEIGHT, + minimumWidthPx = widthPx, ): HTMLElement { const samples = data.samples.filter(validElevation); if (samples.length < 2) return emptyView(L("B06_Profile_View_NoLongitudinal")); @@ -133,6 +136,8 @@ export function createLongitudinalProfile( role: "img", "aria-label": L("B06_Profile_View_Longitudinal"), }); + svg.style.width = "100%"; + svg.style.minWidth = `${minimumWidthPx}px`; svg.append(svgElement("rect", { width: widthPx, height: heightPx, class: "b06-chart__bg" })); const maxChainage = Math.max(data.length_m, samples[samples.length - 1]?.chainage_m ?? 1, 1); @@ -499,6 +504,7 @@ export function createSectionView(): SectionViewController { const longitudinalPanel = document.createElement("section"); longitudinalPanel.className = "b06-section__panel"; + const longitudinalMinWidth = longitudinalMinimumWidth(detail.longitudinal, stationInterval); longitudinalPanel.append( createLongitudinalProfile( detail.longitudinal, @@ -507,8 +513,9 @@ export function createSectionView(): SectionViewController { yScale, (stationId) => selectStation(stationId, true), stationInterval, - Math.max(renderWidth, longitudinalMinimumWidth(detail.longitudinal, stationInterval)), + Math.max(renderWidth, longitudinalMinWidth), LONG_HEIGHT, + longitudinalMinWidth, ), ); @@ -521,8 +528,11 @@ export function createSectionView(): SectionViewController { crossHeading.append(crossTitle, crossCount); const grid = document.createElement("div"); grid.className = "b06-section__grid"; - const columnCount = renderWidth >= 976 ? 2 : 1; - const cardWidth = (renderWidth - (columnCount - 1) * 16) / columnCount; + const columnCount = Math.max( + 1, + Math.floor((renderWidth + CROSS_GRID_GAP) / (CROSS_GRID_MIN_WIDTH + CROSS_GRID_GAP)), + ); + const cardWidth = (renderWidth - (columnCount - 1) * CROSS_GRID_GAP) / columnCount; if (detail.cross_sections.length) { detail.cross_sections.forEach((section) => grid.append( diff --git a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css index f11f013c..7d85caca 100644 --- a/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css +++ b/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css @@ -182,6 +182,10 @@ max-width: none; } +.b06-cross-card > .b06-section__chart { + width: 100%; +} + .b06-section__heading { display: flex; align-items: end; diff --git a/B10_Payment/B10_Payment_UI_Page.ts b/B10_Payment/B10_Payment_UI_Page.ts index 957ead6f..3a6ddb7e 100644 --- a/B10_Payment/B10_Payment_UI_Page.ts +++ b/B10_Payment/B10_Payment_UI_Page.ts @@ -18,7 +18,7 @@ export function renderB10Payment(root: HTMLElement): void { const invoiceSection = section(L("B10_Payment_Invoice_Title"), invoiceBody, true, [ createButton({ label: L("B10_Payment_Invoice_Request"), disabled: true }), ]); - + const depositNote = document.createElement("p"); depositNote.className = "b10-payment__note"; depositNote.textContent = L("B10_Payment_Deposit_Note"); diff --git a/scratch/test_crs_verification.py b/scratch/test_crs_verification.py index 6fdb188f..66acc689 100644 --- a/scratch/test_crs_verification.py +++ b/scratch/test_crs_verification.py @@ -1,20 +1,27 @@ import sys -import os from pathlib import Path # Add project root to sys.path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from B03_FileInput.B03_FileInput_Engine_Analyze import ( + analyze_las_metadata, analyze_prj_metadata, analyze_tif_metadata, - analyze_las_metadata, ) BASE_DIR = Path("D:/02_Software_Prog/임도설계 및 견적자동화 프로그램 개발") -PRJ_PATH = BASE_DIR / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/prj/result.prj" -TIF_PATH = BASE_DIR / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/tif/result.tif" -LAS_PATH = BASE_DIR / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/las/cloud_merged.las" +PRJ_PATH = ( + BASE_DIR / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/prj/result.prj" +) +TIF_PATH = ( + BASE_DIR / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/tif/result.tif" +) +LAS_PATH = ( + BASE_DIR + / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/las/cloud_merged.las" +) + def run_tests(): print("--- 1. Testing PRJ Metadata Extraction ---") @@ -23,12 +30,20 @@ def run_tests(): print("PRJ Metadata:") for k, v in prj_meta.items(): print(f" {k}: {v}") - + # Assertions based on PLAN.md expectations - assert prj_meta.get("epsg") == 5187, f"Expected horizontal EPSG to be 5187, got {prj_meta.get('epsg')}" - assert prj_meta.get("crs_status") == "custom_vertical_crs", f"Expected custom_vertical_crs, got {prj_meta.get('crs_status')}" - assert prj_meta.get("vertical_crs") is not None, "Expected vertical_crs metadata to be present" - assert "KNGeoid24" in prj_meta["vertical_crs"]["name"], f"Expected KNGeoid24 in vertical crs name, got {prj_meta['vertical_crs']['name']}" + assert prj_meta.get("epsg") == 5187, ( + f"Expected horizontal EPSG to be 5187, got {prj_meta.get('epsg')}" + ) + assert prj_meta.get("crs_status") == "custom_vertical_crs", ( + f"Expected custom_vertical_crs, got {prj_meta.get('crs_status')}" + ) + assert prj_meta.get("vertical_crs") is not None, ( + "Expected vertical_crs metadata to be present" + ) + assert "KNGeoid24" in prj_meta["vertical_crs"]["name"], ( + f"Expected KNGeoid24 in vertical crs name, got {prj_meta['vertical_crs']['name']}" + ) print("PRJ test passed successfully.") else: print(f"PRJ file not found at {PRJ_PATH}") @@ -39,9 +54,11 @@ def run_tests(): print("TIF Metadata:") for k, v in tif_meta.items(): print(f" {k}: {v}") - + assert tif_meta.get("epsg") == 5187, f"Expected EPSG to be 5187, got {tif_meta.get('epsg')}" - assert tif_meta.get("crs_status") == "identified", f"Expected identified, got {tif_meta.get('crs_status')}" + assert tif_meta.get("crs_status") == "identified", ( + f"Expected identified, got {tif_meta.get('crs_status')}" + ) assert tif_meta.get("vertical_crs") is None, "Expected no vertical crs for TIF file" print("TIF test passed successfully.") else: @@ -57,14 +74,21 @@ def run_tests(): print(f" {k}: id={v.get('id')}, num_dimensions={len(v.get('dimensions', []))}") else: print(f" {k}: {v}") - + assert las_meta.get("epsg") == 5187, f"Expected EPSG to be 5187, got {las_meta.get('epsg')}" - assert las_meta.get("crs_status") == "custom_vertical_crs", f"Expected custom_vertical_crs, got {las_meta.get('crs_status')}" - assert las_meta.get("vertical_crs") is not None, "Expected vertical_crs metadata to be present" - assert "KNGeoid24" in las_meta["vertical_crs"]["name"], f"Expected KNGeoid24 in vertical crs name, got {las_meta['vertical_crs']['name']}" + assert las_meta.get("crs_status") == "custom_vertical_crs", ( + f"Expected custom_vertical_crs, got {las_meta.get('crs_status')}" + ) + assert las_meta.get("vertical_crs") is not None, ( + "Expected vertical_crs metadata to be present" + ) + assert "KNGeoid24" in las_meta["vertical_crs"]["name"], ( + f"Expected KNGeoid24 in vertical crs name, got {las_meta['vertical_crs']['name']}" + ) print("LAS test passed successfully.") else: print(f"LAS file not found at {LAS_PATH}") + if __name__ == "__main__": run_tests() diff --git a/scratch/wiki_linter.py b/scratch/wiki_linter.py index aee6382d..263b502c 100644 --- a/scratch/wiki_linter.py +++ b/scratch/wiki_linter.py @@ -35,51 +35,57 @@ for rel_path, abs_path in list(all_wiki_files.items()): continue if rel_path in ["index", "log", "index.md", "log.md"]: continue - + with open(abs_path, "r", encoding="utf-8") as f: lines = f.readlines() - + line_count = len(lines) content = "".join(lines) - + # Rule 14: Max 100 lines if line_count > 100: warnings.append(f"[Line Count] `{rel_path}.md` exceeds 100 lines ({line_count} lines).") - + # Check YAML Frontmatter via simple regex frontmatter_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL) if not frontmatter_match: errors.append(f"[Frontmatter] `{rel_path}.md` has no valid YAML frontmatter.") continue - + fm_text = frontmatter_match.group(1) - + # Simple regex parsing for status & page_id status_match = re.search(r"^status:\s*(\w+)", fm_text, re.MULTILINE) page_id_match = re.search(r"^page_id:\s*([^\n\r]+)", fm_text, re.MULTILINE) - + if not status_match: errors.append(f"[Status] `{rel_path}.md` has no status field in frontmatter.") else: status = status_match.group(1).strip() if status not in ["draft", "stable", "stale"]: - errors.append(f"[Status] `{rel_path}.md` has invalid status '{status}'. Must be draft, stable, or stale.") + errors.append( + f"[Status] `{rel_path}.md` has invalid status '{status}'. Must be draft, stable, or stale." + ) elif status == "stale": - warnings.append(f"[Stale Page] `{rel_path}.md` is marked as stale and needs updates from raw inputs.") - + warnings.append( + f"[Stale Page] `{rel_path}.md` is marked as stale and needs updates from raw inputs." + ) + # Rule 5: page_id required for pages/ if "pages/" in rel_path: if not page_id_match: errors.append(f"[page_id] `{rel_path}.md` is in pages/ but lacks a 'page_id' field.") - + # Check for broken wikilinks in content links = wikilink_pat.findall(content) for link in links: - link_clean = link.strip().replace("\\", "/").split("#")[0] # ignore anchor for file existence check + link_clean = ( + link.strip().replace("\\", "/").split("#")[0] + ) # ignore anchor for file existence check if not link_clean: continue found = False - + # 1. Absolute link from vault root (e.g. concepts/storage_paths) if link_clean in all_wiki_files: found = True @@ -98,7 +104,7 @@ for rel_path, abs_path in list(all_wiki_files.items()): resolved = os.path.normpath(os.path.join(curr_dir, link_clean)).replace("\\", "/") if resolved in all_wiki_files or resolved.replace("pages/", "") in all_wiki_files: found = True - + if not found: errors.append(f"[Broken Link] `{rel_path}.md` contains broken wikilink: [[{link}]]") @@ -108,8 +114,13 @@ for rel_path, abs_path in list(all_wiki_files.items()): if file_name_no_ext not in index_content: # check if relative path is in index rel_path_no_ext = rel_path.replace(".md", "") - if rel_path_no_ext not in index_content and rel_path_no_ext.split("/")[-1] not in index_content: - warnings.append(f"[Orphan Page] `{rel_path}.md` is not linked or mentioned in index.md.") + if ( + rel_path_no_ext not in index_content + and rel_path_no_ext.split("/")[-1] not in index_content + ): + warnings.append( + f"[Orphan Page] `{rel_path}.md` is not linked or mentioned in index.md." + ) print("=== LINT ERRORS ===") for e in sorted(list(set(errors))):