fix(B05 배수유역): 349-0i 되돌리기로 저장 노선에 돌아오면 저장분 다시 받기 — 낡음 그대로 머묾 없앰
- 노선 초안이 없어지면(되돌리기 · 버림) 저장 배수유역을 열 때와 같은 길로 조용히 다시 받음(계산 아님) · 그 답의 낡음대로 흐림 풂 또는 그대로 · [적용] 따라감 - 떠 있던 초안용 live 요청 끊음 · 자동도 3 s 셈 대신 저장분 · 답 올 때까지 흐림 유지 - 패널 받기 줄 — loadSaved 를 요청 함수로(열 때 · reload · 되돌리기가 같은 길) · 700줄 맞춤 - test_349_drainage_live_flow 되돌리기 장면(자동 · 수동 요청 중 · 저장분 낡음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
* 흐림 + 가운데 [업데이트]. 낡은 동안 제안 [적용]은 끔(두 모드 · 새 답이 오면 다시 켬).
|
||||
* - 다시 조작하면 처음으로 — 떠 있는 요청을 끊고(AbortController) 다시 흐림 또는 다시 3 s.
|
||||
* 묶음이 같은 노선 · 관으로 다시 돌면(입력 지문 같음) 그대로 둠 — 떠 있는 답을 받음.
|
||||
* - 초안이 없어지면(되돌리기 · 버림) 저장 배수유역을 다시 받아(열 때와 같은 길 · 계산 아님) 그 답의
|
||||
* 낡음대로 — 떠 있던 초안용 답은 버림 · 자동도 3 s 셈 대신 저장분.
|
||||
* - 서버가 「낡음」으로 답하면(노선 [저장] 뒤 등) 같은 흐림 + [업데이트] · 자동이면 3 s 뒤.
|
||||
* - 답은 패널 요청 감싸기(`DetailRunner`, quiet)로 올림 — 옛 차례 · 서버가 그만둔 답은 버림.
|
||||
* - 덮개 · 잠금 없음 — 다른 칸 조작은 그대로.
|
||||
@@ -47,6 +49,8 @@ export interface DrainageLiveDeps {
|
||||
runner: DetailRunner;
|
||||
/** [적용] 답이 화면에 오른 뒤 — 세션 초안에 담음. */
|
||||
rememberPipes: () => void;
|
||||
/** 저장 배수유역 다시 받기(열 때와 같은 길 · 조용히) — 답을 화면에 올렸으면 true. */
|
||||
loadSaved: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface DrainageLive {
|
||||
@@ -98,6 +102,10 @@ export function createDrainageLive(deps: DrainageLiveDeps): DrainageLive {
|
||||
let mode = readDrainageMode();
|
||||
let route: unknown = null;
|
||||
let stale = false;
|
||||
/** 마지막 단계에 노선 초안이 있었나 — 없어지면 저장분으로 돌아옴. */
|
||||
let drafted = false;
|
||||
/** 저장분 다시 받기 답이 「낡음」이었나(`markStale` 이 세움). */
|
||||
let savedStale = false;
|
||||
let generation = 0;
|
||||
/** 마지막 단계 입력 지문 — 같으면 묶음이 다시 돈 것뿐. */
|
||||
let key = "";
|
||||
@@ -172,6 +180,20 @@ export function createDrainageLive(deps: DrainageLiveDeps): DrainageLive {
|
||||
paint();
|
||||
}
|
||||
|
||||
/** 초안이 없어짐 — 저장분을 다시 받아 그 답의 낡음대로(흐림은 답이 올 때까지 그대로). */
|
||||
function backToSaved(): void {
|
||||
cancel();
|
||||
const sent = ++generation;
|
||||
savedStale = false; // 답이 「낡음」이면 apply 가 markStale 로 세움
|
||||
paint();
|
||||
void deps.loadSaved().then((applied) => {
|
||||
if (sent !== generation) return;
|
||||
stale = !applied || savedStale;
|
||||
if (stale && mode === "auto") timer = window.setTimeout(refresh, QUIET_MS);
|
||||
paint();
|
||||
});
|
||||
}
|
||||
|
||||
function openSettings(): void {
|
||||
openModal({
|
||||
title: "배수유역 계산",
|
||||
@@ -219,12 +241,16 @@ export function createDrainageLive(deps: DrainageLiveDeps): DrainageLive {
|
||||
const next = inputKey(route, ctx.inputs.pipes);
|
||||
if (next === key) return null;
|
||||
key = next;
|
||||
if (draftMakesStale(route) || stale) invalidate();
|
||||
const wasDraft = drafted;
|
||||
drafted = draftMakesStale(route);
|
||||
if (drafted || (stale && !wasDraft)) invalidate();
|
||||
else if (wasDraft) backToSaved();
|
||||
return null;
|
||||
},
|
||||
refresh,
|
||||
suggest,
|
||||
markStale() {
|
||||
savedStale = true;
|
||||
if (stale) return;
|
||||
stale = true;
|
||||
if (mode === "auto" && !inflight) timer = window.setTimeout(refresh, QUIET_MS);
|
||||
|
||||
@@ -409,13 +409,12 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
|
||||
/** 저장된 관 지점(없으면 자동 배치)을 불러온다. 화면에 들어올 때 1회. 저장 전 조작분(세션 초안)이
|
||||
* 있으면 그 관 목록으로 세운다 — B06 을 다녀와도 지정 · 옵션이 옛값으로 안 돌아감(21-10 흠 3). */
|
||||
const loadSaved = (): Promise<boolean> =>
|
||||
run(() => {
|
||||
const pending = readPendingPipes(projectId as string);
|
||||
return pending !== null // 빈 목록 = 다 지움(관 0개 · 312) — 저장분으로 안 넘어감
|
||||
? computeDetailBasins(projectId as string, pending)
|
||||
: fetchDetailPipePoints(projectId as string);
|
||||
});
|
||||
const loadSaved = () => {
|
||||
const pending = readPendingPipes(projectId as string);
|
||||
return pending !== null // 빈 목록 = 다 지움(관 0개 · 312) — 저장분으로 안 넘어감
|
||||
? computeDetailBasins(projectId as string, pending)
|
||||
: fetchDetailPipePoints(projectId as string);
|
||||
};
|
||||
|
||||
/** 저장분까지 버리고 자동 배치로 되돌린다("초기화"). 화면만 되돌리면 다시 들어왔을 때
|
||||
* 옛 관이 살아난다(2026-08-02 사용자 보고). */
|
||||
@@ -426,7 +425,14 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
const analyze = batched(() => run(() => computeDetailBasins(projectId as string, pipeRequest())));
|
||||
|
||||
// 자동/수동 다시 셈(347-2) · 제안 목록(347-3 · [적용] = 관 초안으로 세부 유역 다시 나눔).
|
||||
const live = createDrainageLive({ root, basinList, pipeRequest, runner, rememberPipes });
|
||||
const live = createDrainageLive({
|
||||
root,
|
||||
basinList,
|
||||
pipeRequest,
|
||||
runner,
|
||||
rememberPipes,
|
||||
loadSaved: () => run(loadSaved, true), // 초안 버림 = 저장분 조용히(349-0i)
|
||||
});
|
||||
analyzeButton.addEventListener("click", () => void analyze());
|
||||
deleteButton.addEventListener("click", () => void pipeEditor.deleteSelected());
|
||||
autoButton.addEventListener("click", () => void resetPipes());
|
||||
@@ -493,7 +499,7 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
showProgress(2 / 3, L("B05_Drainage_Status_Analyzing"));
|
||||
// B04가 확정해 둔 관 지점을 그대로 불러온다 — 저장분이 없으면 백엔드가 자동 배치를 준다.
|
||||
// 두 화면이 같은 파일을 보므로 B04에서 옮긴 관이 여기서도 같은 자리에 있다.
|
||||
void loadSaved();
|
||||
void run(loadSaved);
|
||||
} catch (error) {
|
||||
if (sequence !== loadSequence) return;
|
||||
status.hidden = false;
|
||||
@@ -565,7 +571,7 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra
|
||||
void loadLayers();
|
||||
},
|
||||
reload() {
|
||||
if (projectId && meta) void loadSaved();
|
||||
if (projectId && meta) void run(loadSaved);
|
||||
},
|
||||
pipeChainages: () => pipeEditor.chainages(),
|
||||
movePipe(fromChainage, toChainage) {
|
||||
|
||||
@@ -86,6 +86,7 @@ const flush = async () => {
|
||||
/* ---- 모듈 읽기(그 자리 트랜스파일 · 바깥 모듈은 가짜) ---- */
|
||||
const state = new Map();
|
||||
const detailCalls = [];
|
||||
let modeRow = 1; // ⚙ 창에서 고를 줄 — 0 수동 · 1 자동
|
||||
const stubs = {
|
||||
"@config/config_frontend": { API_BASE_URL: "/api" },
|
||||
"@ui/ui_template_progress": { createProgressCircle: () => ({ root: el("progress") }) },
|
||||
@@ -94,7 +95,7 @@ const stubs = {
|
||||
openModal: (options) => {
|
||||
const body = el("modal");
|
||||
options.mount(body, () => undefined);
|
||||
body.children[1].children[0].fire("change");
|
||||
body.children[modeRow].children[0].fire("change");
|
||||
},
|
||||
},
|
||||
"../A00_Common/b_page_state": {
|
||||
@@ -144,6 +145,8 @@ async function main() {
|
||||
{ chainage_m: 100, id: "a1", source: "stream", options: { pipe_diameter_mm: 1000 } },
|
||||
];
|
||||
let applied = 0;
|
||||
let savedLoads = 0;
|
||||
let savedAnswer = null;
|
||||
let remembered = 0;
|
||||
let live;
|
||||
const status = el("status");
|
||||
@@ -167,6 +170,11 @@ async function main() {
|
||||
pipeRequest: () => pipes,
|
||||
runner,
|
||||
rememberPipes: () => (remembered += 1),
|
||||
// 저장분 다시 받기(349-0i) — 답은 시험이 정할 때 옴
|
||||
loadSaved: () => {
|
||||
savedLoads += 1;
|
||||
return runner.run(() => new Promise((resolve) => (savedAnswer = resolve)), true);
|
||||
},
|
||||
});
|
||||
const [veil, busy] = viewport.children;
|
||||
const update = veil.children[0];
|
||||
@@ -270,6 +278,36 @@ async function main() {
|
||||
answer(calls[5]);
|
||||
await flush();
|
||||
out.autoSavedDone = look();
|
||||
|
||||
// 349-0i 되돌리기 — 자동: 끌기 → 낡음 → Ctrl+Z(초안 없어짐) = 3 s 셈 대신 저장분
|
||||
const savedOk = { basins: [], pipe_points: pipes, stale: null };
|
||||
stage(draft(20, 300));
|
||||
advance(1000);
|
||||
stage(null);
|
||||
out.autoUndoWaiting = { ...look(), savedLoads };
|
||||
advance(5000);
|
||||
out.autoUndoNoFetch = look();
|
||||
savedAnswer(savedOk);
|
||||
await flush();
|
||||
out.autoUndo = { ...look(), savedLoads };
|
||||
|
||||
// 수동: 끌기 → [업데이트] 요청 중 → Ctrl+Z = 초안용 답 끊고 저장분 · 흐림은 답 올 때까지
|
||||
modeRow = 0;
|
||||
gear.fire("click");
|
||||
stage(draft(21, 310));
|
||||
update.fire("click");
|
||||
const draftCall = calls[calls.length - 1];
|
||||
stage(null);
|
||||
out.undoWaiting = { ...look(), aborted: draftCall.aborted, savedLoads };
|
||||
savedAnswer(savedOk);
|
||||
await flush();
|
||||
out.undo = { ...look(), savedLoads };
|
||||
// 저장분 자체가 낡음 = 흐림 · [업데이트] 그대로
|
||||
stage(draft(22, 320));
|
||||
stage(null);
|
||||
savedAnswer({ basins: [], pipe_points: pipes, stale: "배수유역 낡음" });
|
||||
await flush();
|
||||
out.undoSavedStale = { ...look(), savedLoads, mode: state.get("drainage-mode") };
|
||||
// 입력 지문 — 판 번호 · 관 차례는 안 봄 · 꼭짓점 · 관 자리(cm) · id 는 봄
|
||||
const { inputKey } = load(path.join(B05, "logic", "B05_Profile_Drainage_Live.ts"));
|
||||
const a = { id: "a1", chainage_m: 100 };
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- 노선 [저장] 뒤 서버 답이 「낡음」 = 수동은 흐림 + [업데이트] · 자동은 3 s 뒤 조용히.
|
||||
- ⚙ 자동: 흐림 없음 · 3 s 조용한 뒤 셈 · 그 사이 또 옮기면 처음부터 · [업데이트] 막 없음(349-0e).
|
||||
- 낡은 동안 제안 [적용] 끔(두 모드) · 새 답이 오면 다시 켬(349-0e).
|
||||
- 되돌리기로 초안이 없어지면 저장분 다시 받기 · 그 답의 낡음대로 · 초안용 답 끊음 · 자동도 3 s 셈 대신(349-0i).
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -103,6 +104,23 @@ def test_auto_mode(r):
|
||||
assert look(r["autoSavedDone"]) == state(False, False, False, 6)
|
||||
|
||||
|
||||
def test_undo_back_to_saved(r):
|
||||
# 자동 — 끌기 뒤 1 s 에 되돌리기: 3 s 셈 없음 · 저장분 하나 · 답 오면 낡음 풂
|
||||
assert look(r["autoUndoWaiting"]) == {**state(True, False, False, 6, True), "savedLoads": 2}
|
||||
assert r["autoUndoNoFetch"]["fetches"] == 6
|
||||
assert look(r["autoUndo"]) == {**state(False, False, False, 6), "savedLoads": 2}
|
||||
# 수동 — [업데이트] 요청 중 되돌리기: 초안용 요청 끊음 · 저장분 답 올 때까지 흐림 그대로
|
||||
waiting = r["undoWaiting"]
|
||||
assert waiting["aborted"] and waiting["stale"] and waiting["blur"] and not waiting["busy"]
|
||||
assert look(r["undo"]) == {**state(False, False, False, 7), "savedLoads": 3}
|
||||
# 저장분 자체가 낡음 = 흐림 · [업데이트] · [적용] 꺼짐 그대로
|
||||
assert look(r["undoSavedStale"]) == {
|
||||
**state(True, True, False, 7),
|
||||
"savedLoads": 4,
|
||||
"mode": "manual",
|
||||
}
|
||||
|
||||
|
||||
def test_input_key(r):
|
||||
assert look(r["keys"]) == {
|
||||
"rev": True,
|
||||
|
||||
Reference in New Issue
Block a user