무슨 일이 있었나 랩탑 줄의 병합 `20ba886c`(Merge origin/main_desktop_1·main_laptop_1·sub_desktop_1 into sub_laptop_1)가 우리 파일 22개를 떨구고 35개 파일의 내용을 옛것으로 되돌림. 손으로 지운 커밋은 없고 **병합 자체가 떨군 것**임. 그것이 `origin/dev`·`main_laptop_1`·`sub_laptop_1`· `CODEX` 까지 퍼졌고(데스크탑 둘만 무사), 이 창의 병합 `d92c1f2b` 로 들어옴. 잃었던 것 - 공용 — `common_util_provenance.py` · `ui_template_provenance.ts` - B08 — 근거 사전 · 좌측 패널 상자 모듈 · 토량환산계수 칸 - B09 — 근거 사전 셋 - B05 — 계획노선 편집 모듈 아홉 · 지형 라우터 · B04 지도 모듈 - 시험 셋과, 35개 파일 안의 최근 작업(환산계수 고르기 · 근거 호버 배선 등) 어떻게 되살렸나 `611a2b40`(병합 직전, 전부 온전)에서 `git show <커밋>:<경로>` 로 내용만 꺼내 되돌림. 이력은 안 건드림. ⚠ HEAD 에만 있던 「추가 816줄」은 랩탑의 새 작업이 아니라 **되살아난 옛 코드**였음(B05 편집은 모듈로 쪼개기 전 덩어리 · B08 라우터는 환산계수 고르기 전 옛 상수판). 되돌릴 시점 이후의 **진짜 새 커밋은 둘뿐**이라 그 둘만 패치로 다시 얹음 — `9f827bf6`(리로드 빌드 고리 끊기, 데스크탑 보조) · `b9bca6b3`(B06 조정창 1px, 랩탑). 위키 여덟은 코덱스 몫이라 손대지 않음. 자체검증 — 양쪽 작업이 다 살아 있음을 짚어 확인: `main.py` 의 「개발 서버는 살려 둔다」 · `B05_Profile_Engine_Grade.py` 의 `plan_curve_length_limit_m` · `B08_..._EarthworkGrid.ts` 의 `attachProvenance`. `tsc --noEmit` 통과 · `pytest -q` **1317 passed, 28 skipped** (되살리기 전에는 시험 둘이 수집 단계에서 깨져 있었음). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RANEBHns1S4tkmsYwewtk
99 lines
5.8 KiB
TypeScript
99 lines
5.8 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_Chrome.ts
|
|
* 계획노선 편집 모달의 **뼈대** — 창·단추·오버레이 판을 만들고 자주 쓰는 요소를 집어 준다.
|
|
*
|
|
* `B05_Profile_UI_RouteEdit.ts` 가 700줄을 넘겨 떼어낸 조각이다(2026-09-12). 생김새만 있고
|
|
* 동작은 없다 — 배선은 본체와 각 조각(`_Apply`·`_Rotate`·`_History`…)이 한다.
|
|
*
|
|
* **배치**(2026-09-12 사용자 지시 ⑩~⑬·⑱) — 아래 정보행을 없애고 지도 위 오버레이로 옮겼다.
|
|
* 단추는 제목행 오른쪽, 조작 설명은 지도 왼쪽 위 2열, 상태·범례는 왼쪽 아래, 잰 값은 오른쪽
|
|
* 아래. 메인 창은 왼쪽으로 밀고 오른쪽 세로 칸에 횡단 두 판이 앉는다.
|
|
* ========================================================================== */
|
|
|
|
/** 회전 단추 아이콘 — **반만 도는 화살표**(2026-09-12 사용자 지시 ㉙). 한 바퀴를 다 그린
|
|
* 기호(`↺`·`↻`)는 「한 바퀴 돈다」로 읽혀 한 칸씩 도는 동작과 안 맞았다. */
|
|
const HALF_TURN_ICON = {
|
|
ccw: `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="none"
|
|
stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M13 8a5 5 0 0 0-10 0" /><path d="M3 8 1.2 5.6" /><path d="M3 8 5.4 6.6" /></svg>`,
|
|
cw: `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="none"
|
|
stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M3 8a5 5 0 0 1 10 0" /><path d="M13 8 14.8 5.6" /><path d="M13 8 10.6 6.6" /></svg>`,
|
|
};
|
|
|
|
export interface RouteEditChrome {
|
|
overlay: HTMLElement;
|
|
canvas: HTMLCanvasElement;
|
|
status: HTMLElement;
|
|
busy: HTMLElement;
|
|
measureBox: HTMLElement;
|
|
measureText: HTMLElement;
|
|
measureButton: HTMLButtonElement;
|
|
}
|
|
|
|
/** 모달을 만들어 `document.body` 에 붙이고, 자주 쓰는 요소를 집어 돌려준다. */
|
|
export function createRouteEditChrome(): RouteEditChrome {
|
|
const overlay = document.createElement("div");
|
|
overlay.className = "b05-routeedit";
|
|
overlay.innerHTML = `
|
|
<div class="b05-routeedit__box" role="dialog" aria-label="계획노선 편집">
|
|
<div class="b05-routeedit__head">
|
|
<strong>계획노선 편집</strong>
|
|
<span class="b05-routeedit__actions">
|
|
<button type="button" class="ui-btn ui-btn--ghost" data-act="measure"
|
|
title="노선 위 두 점을 눌러 거리·기울기를 잽니다 (Shift+클릭도 같음)">거리 재기</button>
|
|
<i class="b05-routeedit__divider" aria-hidden="true"></i>
|
|
<button type="button" class="ui-btn ui-btn--ghost" data-act="undo"
|
|
title="되돌리기 (Ctrl+Z)" disabled>↶ 되돌리기</button>
|
|
<button type="button" class="ui-btn ui-btn--ghost" data-act="redo"
|
|
title="다시하기 (Ctrl+Y)" disabled>↷ 다시하기</button>
|
|
<button type="button" class="ui-btn ui-btn--ghost" data-act="history-reset"
|
|
title="이 창을 연 상태로 되돌립니다 (재계산 없음)" disabled>초기화</button>
|
|
<button type="button" class="ui-btn ui-btn--ghost" data-act="reset">예상노선으로</button>
|
|
<button type="button" class="ui-btn ui-btn--ghost" data-act="cancel">취소</button>
|
|
<button type="button" class="ui-btn ui-btn--filled" data-act="apply">확인</button>
|
|
</span>
|
|
<button type="button" class="b05-routeedit__close" aria-label="닫기">✕</button>
|
|
</div>
|
|
<div class="b05-routeedit__canvas-wrap">
|
|
<canvas class="b05-routeedit__canvas"></canvas>
|
|
<div class="b05-routeedit__hint">
|
|
<span>노드 끌기 = 옮기기</span><span>노드 클릭 = R 라벨</span>
|
|
<span>선 두 번 클릭 = 노드 추가</span><span>노드 오른쪽 클릭 = 삭제</span>
|
|
<span>측점 눈금 클릭 = 횡단 미리보기</span><span>Shift+클릭 = 거리·기울기</span>
|
|
<span>가운데(휠) 버튼 끌기 = 지도 이동</span><span>휠 = 확대</span>
|
|
</div>
|
|
<div class="b05-routeedit__spin">
|
|
<button type="button" class="ui-btn ui-btn--glass" data-act="rotate-ccw"
|
|
title="반시계로 돌리기" aria-label="반시계로 돌리기">${HALF_TURN_ICON.ccw}</button>
|
|
<button type="button" class="ui-btn ui-btn--glass" data-act="rotate-cw"
|
|
title="시계로 돌리기" aria-label="시계로 돌리기">${HALF_TURN_ICON.cw}</button>
|
|
</div>
|
|
<div class="b05-routeedit__measure" hidden>
|
|
<span class="b05-routeedit__measure-text"></span>
|
|
<button type="button" class="b05-routeedit__measure-close" aria-label="닫기"
|
|
title="닫기">✕</button>
|
|
</div>
|
|
<div class="b05-routeedit__info">
|
|
<span class="b05-routeedit__status">노선을 읽는 중…</span>
|
|
<span class="b05-routeedit__legend">
|
|
<i class="is-expected"></i> 예상노선(원본)
|
|
<i class="is-planned"></i> 계획노선
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="b05-routeedit__busy" hidden><span></span></div>
|
|
</div>
|
|
<div class="b05-routeedit__side"></div>`;
|
|
document.body.append(overlay);
|
|
|
|
const canvas = overlay.querySelector<HTMLCanvasElement>(".b05-routeedit__canvas")!;
|
|
const status = overlay.querySelector<HTMLElement>(".b05-routeedit__status")!;
|
|
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 busy = overlay.querySelector<HTMLElement>(".b05-routeedit__busy")!;
|
|
|
|
return { overlay, canvas, status, busy, measureBox, measureText, measureButton };
|
|
}
|