diff --git a/A00_Common/design/design_save.ts b/A00_Common/design/design_save.ts index ebf731acc..013e61792 100644 --- a/A00_Common/design/design_save.ts +++ b/A00_Common/design/design_save.ts @@ -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(); diff --git a/B05_Profile/B05_Profile_UI_ExpectedDraw.ts b/B05_Profile/B05_Profile_UI_ExpectedDraw.ts index 6b36b2c80..0adcb7599 100644 --- a/B05_Profile/B05_Profile_UI_ExpectedDraw.ts +++ b/B05_Profile/B05_Profile_UI_ExpectedDraw.ts @@ -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; @@ -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()); diff --git a/resources/tester/test_755_expected_flow.py b/resources/tester/test_755_expected_flow.py index 95f888405..26311c0a3 100644 --- a/resources/tester/test_755_expected_flow.py +++ b/resources/tester/test_755_expected_flow.py @@ -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")