diff --git a/A00_Common/design/design_pipeline_facilities.ts b/A00_Common/design/design_pipeline_facilities.ts index 8aa74f311..914939d7f 100644 --- a/A00_Common/design/design_pipeline_facilities.ts +++ b/A00_Common/design/design_pipeline_facilities.ts @@ -3,12 +3,12 @@ * 계산 묶음 관 · 구조물 · 추가 측점 먹이기(PLAN 415 · 429 ⑨) — 가게 초안이 바뀌면 묶음에 넣음(비면 저장분). * · [저장] 도중 = 안 넣음(H2) — 초안이 비는 사이 열 때 읽은 옛 저장분으로 돌지 않게 · 끝나면 `resync` * (저장 목록 다시 읽기 · 입력 맞춤) 한 번. - * · 되돌리기 · 다시하기 = 세션만 되쓰고 가게 알림이 없음 → 묶음 입력과 견줘 다르면 넣음(415-2 · 노선 초안은 - * 노선 길이 맡음). + * · 되돌리기 · 다시하기 = 아래 되살림 훅 한 길 — 묶음 입력과 견줘 다르면 넣음(415-2 · 노선 초안은 노선 길이 + * 맡음) · 그 사이 가게 알림(`announceDesignStore` · 472-5)은 구독이 받지 않음(두 번 안 넣음). * 저장 창구는 늦게 불러옴(묶음 시험이 화면 모듈을 안 끌게). * ========================================================================== */ -import { onPageHistory } from "../../B05_Profile/B05_Profile_UI_Profile_History"; +import { onPageHistory, pageReplaying } from "../../B05_Profile/B05_Profile_UI_Profile_History"; import type { useDesignStore } from "./design_store"; import type { ChangeSource } from "./logic/design_pipeline_types"; @@ -57,7 +57,13 @@ export function feedFacilities(o: { saving() ? afterSave() : o.feed(source, value); const stops = [ ...FACILITIES.map(({ source, key }) => - o.store.subscribe(key, (value) => o.ready() && put(source, value ?? o.saved(key))), + o.store.subscribe(key, (value) => { + const now = value ?? o.saved(key); + // 묶음 입력과 같은 값 = 이미 받음(되살림 뒤 추가 측점 다시 알림 등) — 한 번만 넣음(472-5) + if (!o.ready() || pageReplaying() || JSON.stringify(now) === JSON.stringify(o.input(key))) + return; + put(source, now); + }), ), onPageHistory({ restore: () => { diff --git a/A00_Common/design/design_store.ts b/A00_Common/design/design_store.ts index 5bd9af3df..c1b6e0540 100644 --- a/A00_Common/design/design_store.ts +++ b/A00_Common/design/design_store.ts @@ -241,11 +241,12 @@ const listeners = new Map void>>(); * 노선을 모르면 `routeId = null` — 노선 칸(`alignmentDraft` · `sectionDetail`)은 비어 보이고 * 쓰기는 버려진다. 프로젝트를 모르면 프로젝트 칸이 그렇다. */ -/** 가게를 안 거친 세션 되씀(되돌리기 · 다시하기) 뒤 — 프로젝트 목록 칸을 듣는 쪽을 지금 값으로 부름 - * (472-5 · 관 · 구조물 사본이 옛 값으로 남아 다음 조작이 되돌림을 덮던 흠). */ +/** 가게를 안 거친 세션 되씀(되돌리기 · 다시하기) 뒤 — 관 · 구조물 칸 듣는 쪽을 지금 값으로 한 번 부름 + * (472-5 · 사본이 옛 값으로 남아 다음 조작이 되돌림을 덮던 흠). 추가 측점 · 페이지 설정은 자기 되살림 훅이 + * 알림(`_Api_PlusStations` · `_Api_PageSettings`) · 계산 묶음은 되살림 훅 한 길(구독은 되돌리는 동안 안 받음). */ export function announceDesignStore(projectId: string): void { const store = useDesignStore(projectId, null); - (["structures", "pipes", "plusStations"] as const).forEach((key) => { + (["structures", "pipes"] as const).forEach((key) => { const id = slotOf(key, projectId, null).id; const value = store.get(key); if (id) listeners.get(`${key}|${id}`)?.forEach((fn) => fn(value)); diff --git a/resources/tester/helper_429_save_path.cjs b/resources/tester/helper_429_save_path.cjs index 94402bd31..941f5a7ed 100644 --- a/resources/tester/helper_429_save_path.cjs +++ b/resources/tester/helper_429_save_path.cjs @@ -105,6 +105,7 @@ async function run() { const feeder = loader({ "../../B05_Profile/B05_Profile_UI_Profile_History": { onPageHistory: (hooks) => ((restore = hooks.restore), () => undefined), + pageReplaying: () => false, // 묶음 먹이기가 되돌리는 동안 구독을 거름(472-5) }, "./design_save": { savingDesign: () => false }, })(path.join(DESIGN, "design_pipeline_facilities.ts")); @@ -116,7 +117,11 @@ async function run() { saved: (key) => (key === "plusStations" ? [12] : []), input: (key) => inputNow[key] ?? null, routed: () => false, - feed: (source, value) => fed.push([source, value]), + // 묶음처럼 받은 값이 입력이 됨 — 같은 값 다시 알림은 안 넣음(472-5) + feed: (source, value) => { + fed.push([source, value]); + if (source === "plus") inputNow.plusStations = value; + }, resync: async () => undefined, }); fakeStore.set("plusStations", [12, 30]); diff --git a/resources/tester/helper_472_5_shop.mjs b/resources/tester/helper_472_5_shop.mjs index 1f04eae81..59ae6b96f 100644 --- a/resources/tester/helper_472_5_shop.mjs +++ b/resources/tester/helper_472_5_shop.mjs @@ -43,19 +43,49 @@ const pipes2 = [ ]; store.set("structures", A); store.set("pipes", pipes1); +m.writePlusDraft(P, [60]); const history = m.createProfileHistory(() => undefined, 100); store.set("structures", AB); store.set("pipes", pipes2); +m.writePlusDraft(P, [60, 80]); history.record(); -const heard = { structures: [], pipes: [] }; +const heard = { structures: [], pipes: [], plusStations: [] }; store.subscribe("structures", (v) => heard.structures.push(v)); store.subscribe("pipes", (v) => heard.pipes.push(v)); +store.subscribe("plusStations", (v) => heard.plusStations.push(v)); +// 계산 묶음 먹이기 — 되돌리기 한 번에 출처마다 한 번만(되살림 훅 한 길 · 472-5 겹침 정리) +const fed = []; +const input = { pipes: pipes2, structures: AB, plusStations: [60, 80] }; +const keyOf = { pipe: "pipes", structure: "structures", plus: "plusStations" }; +m.feedFacilities({ + projectId: P, + store, + ready: () => true, + gone: () => false, + saved: () => null, + input: (key) => input[key], + routed: () => false, + feed: (source, value) => { + fed.push(source); + input[keyOf[source]] = value; + }, + resync: async () => undefined, +}); +const tick = () => new Promise((done) => setTimeout(done, 5)); history.undo(); +await tick(); const afterUndo = { structures: heard.structures.at(-1) ?? null, pipes: heard.pipes.at(-1) ?? null, }; +const counts = { + structures: heard.structures.length, + pipes: heard.pipes.length, + plusStations: heard.plusStations.length, + fed: [...fed].sort(), +}; history.redo(); +await tick(); const afterRedo = { structures: heard.structures.at(-1) ?? null, pipes: heard.pipes.at(-1) ?? null, @@ -85,6 +115,7 @@ console.log( JSON.stringify({ afterUndo, afterRedo, + counts, echo, adopted, picked, diff --git a/resources/tester/helper_472_5_shop_entry.ts b/resources/tester/helper_472_5_shop_entry.ts index f830f139a..7996e05fa 100644 --- a/resources/tester/helper_472_5_shop_entry.ts +++ b/resources/tester/helper_472_5_shop_entry.ts @@ -4,3 +4,5 @@ export { pinDraftProject, useDesignStore } from "../../A00_Common/design/design_ export { adoptEditorPipes } from "../../B05_Profile/B05_Profile_UI_Drainage_Parts"; export { createPipeEditor } from "../../B05_Profile/B05_Profile_UI_Drainage_Pipes"; export { createFacilityStore } from "../../B05_Profile/B05_Profile_UI_Drainage_Facility"; +export { feedFacilities } from "../../A00_Common/design/design_pipeline_facilities"; +export { writePlusDraft } from "../../B05_Profile/B05_Profile_Api_PlusStations"; diff --git a/resources/tester/test_472_5_shop_owner.py b/resources/tester/test_472_5_shop_owner.py index 02ac90f81..e288c3736 100644 --- a/resources/tester/test_472_5_shop_owner.py +++ b/resources/tester/test_472_5_shop_owner.py @@ -1,8 +1,9 @@ """PLAN 472-5 5-A (요구 273) — 관 · 구조물 값 주인 하나(설계 가게) · 사본 갈림 막음. 못박는 것 - - 되돌리기 · 다시하기(세션 직접 되씀)가 가게 듣는 쪽을 되돌린 값으로 부름 — 옛 코드는 안 불러 - 종단 목록 · 횡단 판 · 배수 편집기가 옛 값을 쥐고 다음 조작이 되돌림을 덮었음(옛 코드 실패). + - 되돌리기 · 다시하기(세션 직접 되씀)가 가게 듣는 쪽을 되돌린 값으로 한 번씩 부름(5-C 훅 · 묶음과 + 안 겹침) — 옛 코드는 안 불러 종단 목록 · 횡단 판 · 배수 편집기가 옛 값을 쥐고 다음 조작이 + 되돌림을 덮었음(옛 코드 실패). - 배수 편집기 받기 — 제 쓰기 메아리는 안 받음 · 남이 고친 옵션 · 차례도 받음 · 고른 관(id) 유지. - 배선 — 종단 보기 = 관(숨어도) · 구조물(보일 때) 구독 · 횡단 보기 = 남 고침(옵션만도) 다시 실음 · 횡단 판은 쓰기 전에 제 값을 적음(메아리 가름). @@ -75,6 +76,16 @@ def test_되돌리기가_가게_듣는_쪽을_부름(out) -> None: assert out["afterRedo"]["pipes"][0]["options"] == {"diameter_mm": 1000} +def test_되돌리기_알림은_한_번씩(out) -> None: + """겹침 정리 — 관 · 구조물 = 가게 알림 한 번 · 추가 측점 = 자기 훅 · 묶음 = 출처마다 한 번.""" + assert out["counts"] == { + "structures": 1, + "pipes": 1, + "plusStations": 1, + "fed": ["pipe", "plus", "structure"], + } + + def test_배수_편집기_남_고침만_받음(out) -> None: assert out["echo"] is False assert out["adopted"] is True