feat(755-3): 예정노선 수정 켠 채 판 고침 — [저장] · [확정] 막고 알림 · 떠남 물음 · 탭 닫기 브라우저 물음

- design_save 저장 막음 자리(holdDesignSave · 화면에서 빠지면 풂) · 막음 = 저장 줄 · 가림막 앞
- ExpectedDraw = 켠 채 판 고침(사진첩 한 걸음 이상)이면 막음 글 · 공용 떠남 물음(guardProjectLeave) · 되돌려 고침 없으면 안 물음
- 시험 test_755 지킴 한 건 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 23:28:47 +09:00
co-authored by Claude Opus 5.5
parent 8b3560c4ce
commit 9d5778bf57
3 changed files with 45 additions and 0 deletions
+18
View File
@@ -52,6 +52,22 @@ import { designSignature, markDesignClean } from "./design_leave";
import { routeGradeTouch } from "./design_pipeline_page";
import type { GradeTouch } from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade";
/** [저장] · [확정] 막음(755-3) — 글을 돌려주면 알림하고 안 냄 · `root` 가 화면에서 빠지면 풂. */
const saveHolds: Array<{ root: HTMLElement; hold: () => string | null }> = [];
export function holdDesignSave(root: HTMLElement, hold: () => string | null): void {
saveHolds.push({ root, hold });
}
function heldSave(): string | null {
for (let index = saveHolds.length - 1; index >= 0; index -= 1) {
if (!saveHolds[index].root.isConnected) saveHolds.splice(index, 1);
}
for (const { hold } of saveHolds) {
const why = hold();
if (why) return why;
}
return null;
}
export interface DesignSaveContext {
projectId: string;
routeId: () => number | null;
@@ -249,6 +265,8 @@ export async function saveDesign(
const confirm = options.confirm ?? false;
const routeId = ctx.routeId();
if (routeId === null) return false;
const held = heldSave();
if (held) return (showToast(held, "warning"), false);
saving.add(ctx.projectId);
let locked = confirm;
if (locked) showLoadingOverlay();
@@ -11,11 +11,14 @@
* 끌 때 한 번 = 끊겨 있으면 알림하고 안 끔 · 바뀌었으면 확인창 → `POST /route/follow`(저장 0 · 폴리라인 두 단계) →
* `onFollow(평면, 예정노선)` = 편집기 한 걸음 → 페이지 [저장] = 손 편집과 같은 길 + 초안 `expected`(`expected_route_edit.csv`).
* [받은 노선으로] = 받은 꼭짓점으로 다시 추적 · 초안 `expected` = [](고친 예정노선 지움) — 한 걸음 · 페이지 [초기화] 는 그대로.
* 켠 채 판을 고쳤으면(755-3) = [저장] · [확정] 막고 알림(`holdDesignSave`) · 떠남 = 공용 떠남 물음 · 탭 닫기 = 브라우저 물음.
* 예정노선 세 벌 = 받은 노선(`expected_route.csv` · 안 덮음 · 고쳤으면 옅은 점선) · 저장본(`expected_edited`) · 이번 고침(`edit`).
* 같은 예정노선 다른 세트(635-4) = 흐린 겹 보기만(`B05_Profile_UI_PlanContext`) · 그 끝 = 긋기 · 끌기 붙을 자리 · 벗어나면 안내 한 줄.
* ========================================================================== */
import { keyIs } from "../A00_Common/key_registry";
import { guardProjectLeave } from "../A00_Common/b_project_switch";
import { holdDesignSave } from "../A00_Common/design/design_save";
import { listenWhileAlive } from "@ui/ui_template_window_watch";
import { createButton, showToast } from "@ui/ui_template_elements";
import { askConfirm } from "@ui/ui_template_modal";
@@ -42,6 +45,8 @@ import { flattenServerPlan, type FlattenedPlan } from "./logic/B05_Profile_UI_Ro
type Vertex = [number, number];
const OFF_HINT = "「예정노선 수정」을 켜면 지도에서 예정노선을 고칩니다.";
const HELD =
"예정노선 수정 중 — 「예정노선 수정」을 끈 뒤 [저장]합니다(켠 동안 고친 판은 끌 때 평면선에 들어감).";
/** `POST /route/follow` 응답 — GET `/route/plan` 칸 이름(626-2). */
type FollowResponse = Pick<RoutePlanResponse, "planned" | "nodes" | "curves" | "node_edits">;
@@ -132,6 +137,14 @@ export function createExpectedDraw(params: ExpectedDrawParams): ExpectedDraw {
return { lines: base.length > 1 ? [{ id: "main", vertices, branch: null }] : [], pieces: [] };
};
const measuring = (): boolean => open && params.measuring();
/** 켠 채 판을 고침 — [저장] · 떠남이면 잃음(755-3). */
const pending = (): boolean => open && undoStack.length > 0;
holdDesignSave(canvas, () => (pending() ? HELD : null));
guardProjectLeave(canvas, {
dirty: pending,
save: async () => (showToast(HELD, "warning"), false),
message: HELD,
});
// 속성 판 경위도 = 서버 변환(635-5)
const coords = createPlanCoords(params.projectId, () => kit.props.render());
@@ -3,6 +3,7 @@
- 755-1 켠 동안 = 판만 고침 · 끊김 허용(가운데 묶음 지움) · 이 판 사진첩(Ctrl+Z · 도구줄 단추) ·
끌 때 한 번 = 끊겼으면 알림하고 안 끔 · 바뀌었으면 `/route/follow` 한 걸음 · [잇기] = B05 사용.
- 755-2 켠 동안 재기 = D03 재기 겹 · 받은 노선 = 분홍 점선 + 검은 테 · 켬 설명 걷음 · 위치 칸 걷음.
- 755-3 켠 채 판 고침 = [저장] · [확정] 막고 알림 · 떠남 = 공용 떠남 물음(탭 닫기 = 브라우저 물음).
./venv/Scripts/python.exe -m pytest resources/tester/test_755_expected_flow.py -q
"""
@@ -82,3 +83,16 @@ def test_받은_노선_색_설명_위치_걷음():
assert re.search(r'key: "site",[^}]*d03: "use"', table, re.S) # D03 그대로
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
assert "--draw-map-route-3:" in theme
def test_켠_채_저장_떠남_알림():
body = _body().replace("\r\n", "\n")
assert "const pending = (): boolean => open && undoStack.length > 0;" in body
assert "holdDesignSave(canvas, () => (pending() ? HELD : null));" in body
assert "guardProjectLeave(canvas, {\n dirty: pending," in body
save = (ROOT / "A00_Common" / "design" / "design_save.ts").read_text("utf-8")
save = save.replace("\r\n", "\n")
head = save[save.index("export async function saveDesign(") :]
held = head.index("const held = heldSave();")
assert held < head.index("saving.add(ctx.projectId);") # 막음 = 저장 줄 앞 · 가림막 없음
assert held < head.index("showLoadingOverlay")