feat(B05): 노선 추천 — 되돌리기 · 다시하기가 「현재 추천」 글을 함께 되살림(걸음마다 추천을 실음) (PLAN 23-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019BVZd1M6WUf1YEy9gbAE1G
This commit is contained in:
@@ -368,7 +368,16 @@ export async function openRouteEditModal(
|
||||
|
||||
/** 지금 편집값을 사진 한 벌로 담는다 — 되돌리기가 쌓아 두는 것. */
|
||||
function snapshotNow(): RouteEditSnapshot {
|
||||
return { planned, curveOn, curveRadius, curveLock, curveArc, apexLock, picked };
|
||||
return {
|
||||
planned,
|
||||
curveOn,
|
||||
curveRadius,
|
||||
curveLock,
|
||||
curveArc,
|
||||
apexLock,
|
||||
picked,
|
||||
rec: initial.tag(),
|
||||
};
|
||||
}
|
||||
|
||||
/** 사진 한 벌을 편집값으로 되살린다. 되돌리기는 새 걸음이 아니고, 초기값 싣기는 새 걸음이다. */
|
||||
@@ -380,7 +389,10 @@ export async function openRouteEditModal(
|
||||
const historyControls = bindHistoryControls({
|
||||
overlay,
|
||||
getHistory: () => history,
|
||||
restore: (snapshot, message) => restoreSnapshot(snapshot, message, false),
|
||||
restore: (snapshot, message) => {
|
||||
initial.restoreTag(snapshot.rec); // 「현재 추천」도 그 걸음 것으로 — 노선과 짝.
|
||||
restoreSnapshot(snapshot, message, false);
|
||||
},
|
||||
});
|
||||
|
||||
// ── 조작 — 노드 끌기 · 집기는 `_Pointer` 몫 / 두 번 클릭 삽입 / 오른쪽 클릭 삭제 ──
|
||||
@@ -494,9 +506,9 @@ export async function openRouteEditModal(
|
||||
({ nodes: nodeInfo, curves: curveInfo } = flat);
|
||||
picked = -1;
|
||||
// 여기가 [연 상태로]가 돌아갈 자리다 — 창을 연 그대로.
|
||||
initial.ready(snapshotNow(), plan.edited, plan.current_initial ?? null);
|
||||
history = createRouteEditHistory(snapshotNow());
|
||||
historyControls.sync();
|
||||
initial.ready(snapshotNow(), plan.edited, plan.current_initial ?? null);
|
||||
syncCurveBar();
|
||||
if (!planned.length) planned = plannedLine.map((vertex) => [vertex[0], vertex[1]]);
|
||||
meta = drainage.meta;
|
||||
|
||||
@@ -24,6 +24,8 @@ export interface RouteEditSnapshot {
|
||||
/** 교각점(꺾임점) 자리를 못 박았나 — 켜진 자리는 끌어도 안 움직인다(2026-09-12 지시 ①). */
|
||||
apexLock: boolean[];
|
||||
picked: number;
|
||||
/** 이 걸음의 「현재 추천」 — `_Initial` 이 적고 되돌릴 때 되살린다(뜻은 그쪽만 안다). 노선과 짝. */
|
||||
rec?: unknown;
|
||||
}
|
||||
|
||||
/** 쌓아 둘 사진 수 상한 — 넘으면 오래된 것부터 버린다. */
|
||||
@@ -54,6 +56,7 @@ function clone(snapshot: RouteEditSnapshot): RouteEditSnapshot {
|
||||
curveArc: [...snapshot.curveArc],
|
||||
apexLock: [...snapshot.apexLock],
|
||||
picked: snapshot.picked,
|
||||
rec: snapshot.rec,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ export interface InitialPanel {
|
||||
weights: RouteInitialWeights | null;
|
||||
criteria: RouteInitialCriteria | null;
|
||||
};
|
||||
/** 지금 추천 — 편집 걸음(사진)에 실어 둔다. 되돌리기 · 다시하기가 `restoreTag` 로 되살린다. */
|
||||
tag: () => InitialChoice | null;
|
||||
restoreTag: (tag: unknown) => void;
|
||||
/** [취소] — 이 창에서 바꾼 추천을 창을 열 때 것으로 되돌린다. */
|
||||
cancel: () => void;
|
||||
/** [확인] 뒤 결과 캐시를 비웠다 — 추천은 새 노선의 출발점이라 다시 적는다. */
|
||||
@@ -199,6 +202,13 @@ export function createInitialPanel(params: InitialPanelParams): InitialPanel {
|
||||
const criteria = choice ? choice.criteriaOverrides : base.criteria;
|
||||
return { mode: now.logic, weights: now.weights, criteria };
|
||||
},
|
||||
tag: () => choice,
|
||||
restoreTag(tag) {
|
||||
if (tag === undefined) return; // 추천을 안 실은 옛 걸음 — 그대로 둔다.
|
||||
choice = tag as InitialChoice | null;
|
||||
writeInitialChoice(projectId, choice);
|
||||
render();
|
||||
},
|
||||
cancel: () => writeInitialChoice(projectId, openingChoice),
|
||||
keep: () => writeInitialChoice(projectId, choice),
|
||||
};
|
||||
|
||||
@@ -238,6 +238,23 @@ const mapLines = {
|
||||
oneDot: model.mapPaths([{ kind: "preview", points: [[5, 5]] }], 460, 340),
|
||||
};
|
||||
|
||||
// ⑦ 되돌리기 · 다시하기가 걸음마다 실은 「현재 추천」을 되살린다(노선과 짝).
|
||||
const history = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_History.ts"));
|
||||
const step = (x, rec) => ({
|
||||
planned: [[x, 0], [x + 1, 1]],
|
||||
curveOn: [true, true],
|
||||
curveRadius: [null, null],
|
||||
curveLock: [null, null],
|
||||
curveArc: [null, null],
|
||||
apexLock: [false, false],
|
||||
picked: -1,
|
||||
rec,
|
||||
});
|
||||
const trail = history.createRouteEditHistory(step(0, { logic: "follow" }));
|
||||
trail.commit(step(1, { logic: "earthwork" }));
|
||||
trail.commit(step(2, null));
|
||||
const historyRecs = [trail.undo(), trail.undo(), trail.redo(), trail.redo()].map((one) => one.rec);
|
||||
|
||||
// ④ 캐시 왕복 — 결과 통 프로젝트 칸. 꼴이 틀린 값은 안 읽는다.
|
||||
model.writeInitialChoice("p-1", plain);
|
||||
const readBack = model.readInitialChoice("p-1");
|
||||
@@ -278,5 +295,6 @@ process.stdout.write(
|
||||
sameAsServer,
|
||||
initialRoute,
|
||||
mapLines,
|
||||
historyRecs,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -182,3 +182,13 @@ def test_도구_막대는_노선_추천_하나만():
|
||||
).read()
|
||||
for name in ("follow", "earthwork", "gentle", "weighted"):
|
||||
assert f'"{name}"' in recommend
|
||||
|
||||
|
||||
def test_되돌리기_다시하기가_현재_추천을_함께_되살림(results):
|
||||
"""걸음마다 「현재 추천」이 노선과 짝 — 추천 없음(null)도 그대로 되돌아온다."""
|
||||
assert results["historyRecs"] == [
|
||||
{"logic": "earthwork"},
|
||||
{"logic": "follow"},
|
||||
{"logic": "earthwork"},
|
||||
None,
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user