feat(B05): 352-2 되돌리기 · 다시하기 기호 단추 · [저장 전으로] 와 reset 삭제

- ↶ ↷ 글 없이 기호 + aria-label · title(단축키)
- 저장 전으로 단추 · history.reset · 초안 다리 reset 지움
- 시험 chrome_modal · 346_4 · 346_1 고쳐 씀

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
This commit is contained in:
2026-10-16 20:29:55 +09:00
co-authored by Claude Sonnet 5.5
parent ec02165954
commit fb93c14943
9 changed files with 21 additions and 62 deletions
-7
View File
@@ -284,13 +284,6 @@ export async function mountRouteEditor(
planMinRadiusM: () => minRadiusM,
apply: (snapshot, message) => restoreSnapshot(snapshot, message, true),
});
// [저장 전으로] — 마지막 저장(페이지를 세운 때) 노선 · 그 걸음의 「현재 추천」으로. 되돌리기로 다시 살림.
overlay.querySelector('[data-act="cancel"]')!.addEventListener("click", () => {
const snapshot = history?.reset();
if (!snapshot) return showToast("반영 뒤 고친 노선이 없습니다.", "info");
initial.restoreTag(snapshot.rec);
restoreSnapshot(snapshot, "반영 전 노선으로 되돌렸습니다.", false);
});
function resize(): void {
if (closed || !overlay.isConnected) return;
@@ -13,8 +13,8 @@ import { bindFootprintToggle } from "./B05_Profile_UI_RouteEdit_Footprint";
* 아래. 메인 창은 왼쪽으로 밀고 오른쪽 세로 칸에 횡단 두 판이 앉는다.
*
* 자리(345-3) — 모달이 아니라 종단 보기 가운데 위 본문(옛 3D 자리)에 붙는다. 덮개 · 전체 화면 ·
* 닫기 단추는 걷었다 · [취소] → [저장 전으로] · [확인] · [노선 반영]은 없앰(346-3 — 노선은 페이지 [저장]이
* 맨 앞에서 보냄) · 오른쪽 횡단 칸은 접는 칸.
* 닫기 단추는 걷었다 · [취소] · [확인] · [노선 반영] · [저장 전으로]는 없앰(346-3 · 352-2 — 노선은 페이지
* [저장]이 맨 앞에서 보냄) · 오른쪽 횡단 칸은 접는 칸.
* 머리 `tools` 자리에 부른 쪽 단추([3D])가 앉는다. [노선 추천] · 현재 추천 · [초기화]는 좌측
* 「노선 추천」 칸으로 옮겼다(345-4 · `_Panel_Recommend`).
* ========================================================================== */
@@ -84,11 +84,9 @@ export function createRouteEditChrome(
title="도로 끝 · 비탈 끝(깎기 붉음 · 쌓기 파랑)을 평면에 겹칩니다">발자국</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>
title="되돌리기 (Ctrl+Z)" aria-label="되돌리기" 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="cancel"
title="마지막 [저장] 뒤 고친 노선을 버리고 그때 노선으로 되돌립니다 — 되돌리기로 다시 살릴 수 있습니다">저장 전으로</button>
title="다시하기 (Ctrl+Y)" aria-label="다시하기" disabled>↷</button>
</span>
</div>
<div class="b05-routeedit__canvas-wrap">
@@ -1,14 +1,14 @@
/* =============================================================================
* B05_Profile_UI_RouteEdit_Draft.ts
* 노선 편집기 ↔ 계산 묶음 다리(PLAN 346-1) — 노선을 한 번 고칠 때마다(놓기 · 더함 · 지움 · 곡선 ·
* 되돌리기 · 다시하기 · [저장 전으로] · 추천 [적용]) 노선 초안을 넘김. 끄는 동안은 넘기지 않음
* 되돌리기 · 다시하기 · 추천 [적용]) 노선 초안을 넘김. 끄는 동안은 넘기지 않음
* (사진첩에 쌓이는 때 = 손을 놓은 때).
*
* 편집기는 사진첩(`_History`)을 `watch` 로 감싸기만 함 — 고친 줄 하나. 저장 안 한 초안은
* 다리가 캐시 칸(`route-draft`)에 두고, 편집기가 자료를 읽은 뒤 `restore` 로 한 번 되살림
* (새로고침 · 보기 전환에도 남음). 계산 · 가게는 `A00_Common/design/design_pipeline_page.ts` 몫.
*
* 되돌리기 줄(346-4) — 페이지 사진첩(`_Profile_History`)이 있으면 쌓기 · [저장 전으로] 뒤 사진첩에 한 걸음 ·
* 되돌리기 줄(346-4) — 페이지 사진첩(`_Profile_History`)이 있으면 쌓기 ·
* [되돌리기] · [다시하기]는 사진첩이 함(평면 · 종단 · 횡단 · 관 · 구조물 한 줄) · 사진첩이 되쓴 노선
* 초안은 `fromPage` 로 편집기 새 걸음이 됨. 사진첩이 없으면 편집기 사진 그대로.
* ========================================================================== */
@@ -99,11 +99,6 @@ export function watch(
album.redo();
return null;
},
reset: () => {
const snapshot = tell(history.reset());
if (snapshot) pageHistory()?.record();
return snapshot;
},
canUndo: () => pageHistory()?.canUndo() ?? history.canUndo(),
canRedo: () => pageHistory()?.canRedo() ?? history.canRedo(),
fromPage,
@@ -11,7 +11,7 @@
*
* 되돌리기 줄은 페이지 사진첩 하나(`_Profile_History` · 346-4) — 계산 묶음 다리가 있으면(`_Draft.watch`)
* [되돌리기] · [다시하기] · Ctrl+Z · Y 는 사진첩이 받고, 여기 사진은 고정 · 추천 꼬리표 · 고른 점을
* 되살리는 기록(`match`)과 [저장 전으로](`reset`) · 고친 것 있음(`isDirty`)에만 씀.
* 되살리는 기록(`match`) · 고친 것 있음(`isDirty`)에만 씀.
* ========================================================================== */
import { onPageHistory } from "./B05_Profile_UI_Profile_History";
@@ -44,8 +44,6 @@ export interface RouteEditHistory {
undo: () => RouteEditSnapshot | null;
/** 한 걸음 앞으로. 없으면 null. */
redo: () => RouteEditSnapshot | null;
/** 창을 연 상태로. 이미 그 상태면 null. */
reset: () => RouteEditSnapshot | null;
canUndo: () => boolean;
canRedo: () => boolean;
/** 창을 연 뒤로 고친 것이 있나 — [초기화]를 켤지 정한다. */
@@ -115,15 +113,6 @@ export function createRouteEditHistory(initial: RouteEditSnapshot): RouteEditHis
at += 1;
return clone(steps[at]);
},
reset() {
// 이미 연 상태 그대로면 할 일이 없다 — 눌러도 걸음만 늘어난다.
if (sameRoute(steps[at], steps[0])) return null;
// 초기화도 **되돌릴 수 있어야** 한다 — 첫 사진을 새 걸음으로 쌓는다.
steps.length = at + 1;
steps.push(clone(steps[0]));
at = steps.length - 1;
return clone(steps[at]);
},
canUndo: () => at > 0,
canRedo: () => at < steps.length - 1,
isDirty: () => !sameRoute(steps[at], steps[0]),
@@ -243,7 +243,7 @@ async function run() {
// ③ 편집기 다리 — 사진첩 감싸기 · 초안 되살림
{
const told = [];
let dirty = true;
const dirty = true;
const fake = {
commit: () => undefined,
undo: () => ({
@@ -259,15 +259,6 @@ async function run() {
picked: -1,
}),
redo: () => null,
reset: () => ({
planned: [[0, 0]],
curveOn: [true],
curveRadius: [null],
curveLock: [null],
curveArc: [null],
apexLock: [false],
picked: -1,
}),
canUndo: () => true,
canRedo: () => false,
isDirty: () => dirty,
@@ -287,8 +278,6 @@ async function run() {
});
watched.undo();
watched.redo();
dirty = false;
watched.reset();
const restored = [];
draftLink.restore(bridge, (snap, message, record) =>
restored.push([snap.planned.length, record]),
@@ -151,11 +151,6 @@ const snap = (nodes, lock = false) => ({
out.redo3 = state();
out.editorAfterRedo = editor.slice(out.editorAfterUndo.length);
out.profileReads = profileReads;
// [반영 전으로] — 사진첩 한 걸음 · 되돌리면 옮긴 노선
history.reset();
out.reset = state();
pageAlbum.undo();
out.resetUndo = state();
controls.dispose();
process.stdout.write(JSON.stringify(out));
})();
+1 -1
View File
@@ -173,7 +173,7 @@ def test_늦은_응답_버림(r):
def test_편집기_다리(r):
d = r["draftLink"]
assert d["told"] == [3, 2, None] # 쌓기 · 되돌리기 · 열 때로(null) — 다시하기 없음은 안 알림
assert d["told"] == [3, 2] # 쌓기 · 되돌리기 — 다시하기 없음은 안 알림
assert d["restored"] == [
[r["n"], True]
] # 저장 안 한 초안 = 새 걸음으로 되살림 · 없으면 안 부름
-5
View File
@@ -54,11 +54,6 @@ def test_다시하기는_앞으로(out) -> None:
assert out["profileReads"] == 6 # 종단도도 걸음마다 다시 읽음
def test_반영_전으로도_한_걸음(out) -> None:
assert out["reset"]["route"] is None and out["reset"]["cross"] # 노선만 열 때로
assert out["resetUndo"]["route"] == 70
def test_단축키는_사진첩_한_곳():
tools = (ROOT / "B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts").read_text(encoding="utf-8")
assert "undoOwner" not in tools
@@ -1,7 +1,7 @@
"""계획노선 편집 겉틀 — 345-3 부터 모달이 아니라 종단 보기 가운데 위 본문(PLAN 127-1 의 공용 모달 조각은 걷음).
· 덮개(`ui-modal__backdrop`) · 전체 화면 · 닫기 단추 없음 · 부모를 받아 붙음(`document.body` 아님)
· [취소] → [반영 전으로](이력 처음으로) · [확인] → [노선 반영] · 머리 끝 `tools` 자리([3D])
· [저장 전으로] 없음(352-2) · ↶ ↷ 는 기호 단추 · 머리 끝 `tools` 자리([3D])
· 오른쪽 횡단 미리보기 = 접는 칸 · 재계산 대기 막은 화면 전체(fixed) · R 라벨은 칸 안 절대 위치
· 바깥 누름 · Esc 로 안 닫힘(모달 아님)
"""
@@ -33,13 +33,16 @@ def test_겉틀은_본문_칸():
assert 'overlay.className = "b05-routeedit";' in chrome
assert "b05-routeedit__close" not in chrome and 'data-slot="close"' not in chrome
assert '"tools"' in chrome and "replaceWith(...tools)" in chrome
# 346-3 — [노선 반영] 없앰(노선은 페이지 [저장]이 맨 앞에서 보냄) · [취소] = [저장 전으로]
assert ">저장 전으로</button>" in chrome and 'data-act="apply"' not in chrome
assert "노선 반영</button>" not in chrome
# 346-3 — [노선 반영] 없앰(노선은 페이지 [저장]이 맨 앞에서 보냄) · 352-2 — [저장 전으로] 없앰
assert 'data-act="apply"' not in chrome and 'data-act="cancel"' not in chrome
assert ">저장 전으로</button>" not in chrome and "노선 반영</button>" not in chrome
assert ">취소</button>" not in chrome and ">확인</button>" not in chrome
# 352-2 — 되돌리기 · 다시하기 = 글 없이 기호 + aria-label · title(단축키)
assert 'aria-label="되돌리기" disabled>↶</button>' in chrome
assert 'aria-label="다시하기" disabled>↷</button>' in chrome
assert 'title="되돌리기 (Ctrl+Z)"' in chrome and 'title="다시하기 (Ctrl+Y)"' in chrome
# 시험 · 배선이 집는 자리는 남김
for hook in ('data-act="cancel"', "b05-routeedit__measure-close"):
assert hook in chrome
assert "b05-routeedit__measure-close" in chrome
assert 'data-act="side-toggle"' in chrome and "is-collapsed" in chrome
@@ -67,7 +70,9 @@ def test_페이지_수명_닫기_없음():
assert "new ResizeObserver(" in main
assert "host.onDispose(dispose);" in main
assert "historyControls.listen(host.visible());" in main
assert "history?.reset();" in main # [반영 전으로]
assert "history?.reset()" not in main # 352-2 — [저장 전으로] 지움
hist = _read("B05_Profile_UI_RouteEdit_History.ts")
assert "reset" not in hist.split("export interface RouteEditHistory", 1)[1].split("}", 1)[0]
assert len(main.splitlines()) <= 700
label = _read("B05_Profile_UI_RouteEdit_Label.ts")
assert "document.body.append" not in label and "container.append(root);" in label