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:
@@ -52,6 +52,22 @@ import { designSignature, markDesignClean } from "./design_leave";
|
|||||||
import { routeGradeTouch } from "./design_pipeline_page";
|
import { routeGradeTouch } from "./design_pipeline_page";
|
||||||
import type { GradeTouch } from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade";
|
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 {
|
export interface DesignSaveContext {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
routeId: () => number | null;
|
routeId: () => number | null;
|
||||||
@@ -249,6 +265,8 @@ export async function saveDesign(
|
|||||||
const confirm = options.confirm ?? false;
|
const confirm = options.confirm ?? false;
|
||||||
const routeId = ctx.routeId();
|
const routeId = ctx.routeId();
|
||||||
if (routeId === null) return false;
|
if (routeId === null) return false;
|
||||||
|
const held = heldSave();
|
||||||
|
if (held) return (showToast(held, "warning"), false);
|
||||||
saving.add(ctx.projectId);
|
saving.add(ctx.projectId);
|
||||||
let locked = confirm;
|
let locked = confirm;
|
||||||
if (locked) showLoadingOverlay();
|
if (locked) showLoadingOverlay();
|
||||||
|
|||||||
@@ -11,11 +11,14 @@
|
|||||||
* 끌 때 한 번 = 끊겨 있으면 알림하고 안 끔 · 바뀌었으면 확인창 → `POST /route/follow`(저장 0 · 폴리라인 두 단계) →
|
* 끌 때 한 번 = 끊겨 있으면 알림하고 안 끔 · 바뀌었으면 확인창 → `POST /route/follow`(저장 0 · 폴리라인 두 단계) →
|
||||||
* `onFollow(평면, 예정노선)` = 편집기 한 걸음 → 페이지 [저장] = 손 편집과 같은 길 + 초안 `expected`(`expected_route_edit.csv`).
|
* `onFollow(평면, 예정노선)` = 편집기 한 걸음 → 페이지 [저장] = 손 편집과 같은 길 + 초안 `expected`(`expected_route_edit.csv`).
|
||||||
* [받은 노선으로] = 받은 꼭짓점으로 다시 추적 · 초안 `expected` = [](고친 예정노선 지움) — 한 걸음 · 페이지 [초기화] 는 그대로.
|
* [받은 노선으로] = 받은 꼭짓점으로 다시 추적 · 초안 `expected` = [](고친 예정노선 지움) — 한 걸음 · 페이지 [초기화] 는 그대로.
|
||||||
|
* 켠 채 판을 고쳤으면(755-3) = [저장] · [확정] 막고 알림(`holdDesignSave`) · 떠남 = 공용 떠남 물음 · 탭 닫기 = 브라우저 물음.
|
||||||
* 예정노선 세 벌 = 받은 노선(`expected_route.csv` · 안 덮음 · 고쳤으면 옅은 점선) · 저장본(`expected_edited`) · 이번 고침(`edit`).
|
* 예정노선 세 벌 = 받은 노선(`expected_route.csv` · 안 덮음 · 고쳤으면 옅은 점선) · 저장본(`expected_edited`) · 이번 고침(`edit`).
|
||||||
* 같은 예정노선 다른 세트(635-4) = 흐린 겹 보기만(`B05_Profile_UI_PlanContext`) · 그 끝 = 긋기 · 끌기 붙을 자리 · 벗어나면 안내 한 줄.
|
* 같은 예정노선 다른 세트(635-4) = 흐린 겹 보기만(`B05_Profile_UI_PlanContext`) · 그 끝 = 긋기 · 끌기 붙을 자리 · 벗어나면 안내 한 줄.
|
||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|
||||||
import { keyIs } from "../A00_Common/key_registry";
|
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 { listenWhileAlive } from "@ui/ui_template_window_watch";
|
||||||
import { createButton, showToast } from "@ui/ui_template_elements";
|
import { createButton, showToast } from "@ui/ui_template_elements";
|
||||||
import { askConfirm } from "@ui/ui_template_modal";
|
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];
|
type Vertex = [number, number];
|
||||||
|
|
||||||
const OFF_HINT = "「예정노선 수정」을 켜면 지도에서 예정노선을 고칩니다.";
|
const OFF_HINT = "「예정노선 수정」을 켜면 지도에서 예정노선을 고칩니다.";
|
||||||
|
const HELD =
|
||||||
|
"예정노선 수정 중 — 「예정노선 수정」을 끈 뒤 [저장]합니다(켠 동안 고친 판은 끌 때 평면선에 들어감).";
|
||||||
|
|
||||||
/** `POST /route/follow` 응답 — GET `/route/plan` 칸 이름(626-2). */
|
/** `POST /route/follow` 응답 — GET `/route/plan` 칸 이름(626-2). */
|
||||||
type FollowResponse = Pick<RoutePlanResponse, "planned" | "nodes" | "curves" | "node_edits">;
|
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: [] };
|
return { lines: base.length > 1 ? [{ id: "main", vertices, branch: null }] : [], pieces: [] };
|
||||||
};
|
};
|
||||||
const measuring = (): boolean => open && params.measuring();
|
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)
|
// 속성 판 경위도 = 서버 변환(635-5)
|
||||||
const coords = createPlanCoords(params.projectId, () => kit.props.render());
|
const coords = createPlanCoords(params.projectId, () => kit.props.render());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- 755-1 켠 동안 = 판만 고침 · 끊김 허용(가운데 묶음 지움) · 이 판 사진첩(Ctrl+Z · 도구줄 단추) ·
|
- 755-1 켠 동안 = 판만 고침 · 끊김 허용(가운데 묶음 지움) · 이 판 사진첩(Ctrl+Z · 도구줄 단추) ·
|
||||||
끌 때 한 번 = 끊겼으면 알림하고 안 끔 · 바뀌었으면 `/route/follow` 한 걸음 · [잇기] = B05 사용.
|
끌 때 한 번 = 끊겼으면 알림하고 안 끔 · 바뀌었으면 `/route/follow` 한 걸음 · [잇기] = B05 사용.
|
||||||
- 755-2 켠 동안 재기 = D03 재기 겹 · 받은 노선 = 분홍 점선 + 검은 테 · 켬 설명 걷음 · 위치 칸 걷음.
|
- 755-2 켠 동안 재기 = D03 재기 겹 · 받은 노선 = 분홍 점선 + 검은 테 · 켬 설명 걷음 · 위치 칸 걷음.
|
||||||
|
- 755-3 켠 채 판 고침 = [저장] · [확정] 막고 알림 · 떠남 = 공용 떠남 물음(탭 닫기 = 브라우저 물음).
|
||||||
|
|
||||||
./venv/Scripts/python.exe -m pytest resources/tester/test_755_expected_flow.py -q
|
./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 그대로
|
assert re.search(r'key: "site",[^}]*d03: "use"', table, re.S) # D03 그대로
|
||||||
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
|
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
|
||||||
assert "--draw-map-route-3:" in theme
|
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")
|
||||||
|
|||||||
Reference in New Issue
Block a user