fix(472-5 남음): 암선 · 암 경사 · B05 상단측 메모리 맵 걷음 · 노선 저장 뒤 옛 노선 칸 · hold 비움
- 암 경계 · 암 절토 경사 저장소(`B06_Section_UI_Page_Persist`) = 세션 칸이 주인 — 읽을 때마다 측점 열쇠 칸(`readCells`) · 고칠 때 지금 칸을 읽어 그 측점만 바꿔 씀(`putCell`) · 메모리 맵 · load · 갈아끼움 다시 읽기(keyedStores · reloadKeyedStores) 걷음 · ↶ 뒤 다음 조작이 되돌림을 덮던 흠(N5) 고침 - 저장 재료 = `offsets()` · `ratios()`(새 Map) · ↶ ↷ 가 rockb · cutslope 칸을 바꾸면 조정창과 같은 onRestore 로 카드 · 면적 다시 - B05 상단측 메모리 맵(uphillOverrides · load/saveUphillOverrides) 걷음 — 읽는 곳 없이 저장 뒤 비우기만 하던 사본 · 값 주인 = 세션 칸 `uphill` · 상단측 차례 = 기본 조각 그대로 - 노선 바뀐 [저장] 성공 뒤(페이지 다시 세운 뒤) 옛 노선 번호의 노선 범위 초안(`clearRouteDrafts`) · `route-draft-hold` 비움 · 넘김 규칙 그대로 · 같은 번호면 노선 칸 안 지움 · 실패면 그대로 - 시험 test_472_5r_rock_owner 7(옛 코드 빨강 5) · 옛 단언 고침(176_3 · 271 · 443 상단측 조각 · 344_3 저장소 load) - 위키 암선 · 암 경사 · ↶ ↷ 규칙 · 노선 저장 뒤 정리 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,6 @@ import {
|
||||
type SectionContextResponse,
|
||||
type SectionDetailResponse,
|
||||
} from "./B06_Section_Api_Fetch";
|
||||
import { onAnySwap } from "./B06_Section_Swap_Bus";
|
||||
import type { DesignViewSave } from "../A00_Common/design/design_shared";
|
||||
import { buildCrossPatches, type CrossPatchSources } from "./logic/B06_Section_UI_Page_Patches";
|
||||
import {
|
||||
@@ -72,21 +71,6 @@ export function readCutSlopeSession(projectId: string, routeId: number): Record<
|
||||
return Object.fromEntries(readKeyed<number>("cutslope", projectId, routeId));
|
||||
}
|
||||
|
||||
/** 지금 사는 암 경계 · 암 경사 저장소 — 측점 목록을 갈아끼우면 새 목록 누가거리로 다시 읽음(346-2 4파). */
|
||||
const keyedStores = new Set<{ load: () => void }>();
|
||||
function keepAlive<T extends { load: () => void }>(store: T): T {
|
||||
keyedStores.add(store);
|
||||
return store;
|
||||
}
|
||||
|
||||
/** 측점 목록이 갈아끼워짐 — 암 경계 · 암 경사 값을 측점 열쇠 세션에서 다시 읽음. */
|
||||
export function reloadKeyedStores(): void {
|
||||
keyedStores.forEach((store) => store.load());
|
||||
}
|
||||
|
||||
// 상세 갈아끼움(어느 보기가 했든)마다 한 번 — 암 경계 · 암 경사를 새 목록 누가거리로 다시 읽음
|
||||
onAnySwap(() => reloadKeyedStores());
|
||||
|
||||
/** 세션 열쇠 글(`aislo:draft:rockb:{p}:{r}`)의 프로젝트 · 노선 — 저장소가 측점 열쇠 창구로 읽고 씀. */
|
||||
function keyedScope(storageKey: string): { projectId: string; routeId: number } | null {
|
||||
const [projectId, route] = storageKey.split(":").slice(-2);
|
||||
@@ -109,6 +93,43 @@ function persistKeyed(name: KeyedState, storageKey: string, values: Map<string,
|
||||
else writeByKey(storageKey, JSON.stringify(Object.fromEntries(values)));
|
||||
}
|
||||
|
||||
/** 지금 세션 칸(읽을 때마다 · 메모리 사본 없음 · 472-5) — ↶ ↷ · 측점 갈아끼움이 칸을 되써도 다음 조작이 옛 값으로
|
||||
* 안 덮음(N5). 손상된 값은 빈 칸(기본값으로). */
|
||||
function readCells(
|
||||
name: KeyedState,
|
||||
sessionKey: () => string | null,
|
||||
accept: (value: number) => boolean,
|
||||
): Map<string, number> {
|
||||
const values = new Map<string, number>();
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return values;
|
||||
try {
|
||||
for (const [chainage, value] of loadKeyed(name, storageKey))
|
||||
if (Number.isFinite(value) && accept(value)) values.set(chainage, value);
|
||||
} catch {
|
||||
/* 손상된 세션 값은 무시 — 기본값으로 재시작. */
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/** 한 측점 값 바꿈 — 지금 칸을 읽어 그 칸만 바꿔 씀. 세션 저장 실패(용량 초과 등)는 그 조작만 안 남음. */
|
||||
function putCell(
|
||||
name: KeyedState,
|
||||
sessionKey: () => string | null,
|
||||
values: Map<string, number>,
|
||||
chainageM: number,
|
||||
value: number,
|
||||
): void {
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
values.set(chainageM.toFixed(2), value);
|
||||
try {
|
||||
persistKeyed(name, storageKey, values);
|
||||
} catch {
|
||||
/* 무시 */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션에 쌓인 소단 구간 목록. 없거나 손상되면 빈 목록.
|
||||
*
|
||||
@@ -124,13 +145,11 @@ export function writeBermSpans(projectId: string, routeId: number, spans: BermSp
|
||||
writeState("berm", spans, projectId, routeId);
|
||||
}
|
||||
|
||||
/** 암 경계선 오프셋 저장소 — 값(Map)과 조정창 제어기를 함께 낸다. */
|
||||
/** 암 경계선 오프셋 저장소 — 값(세션 칸)과 조정창 제어기를 함께 낸다. */
|
||||
export interface RockBoundaryStore {
|
||||
/** 측점키(누가거리 2자리) → 오프셋(m). `buildCrossPatches` 가 그대로 읽는다. */
|
||||
offsets: Map<string, number>;
|
||||
/** 지금 세션 칸 — 측점키(누가거리 2자리) → 오프셋(m). `buildCrossPatches` 재료(새 Map). */
|
||||
offsets: () => Map<string, number>;
|
||||
control: RockBoundaryControl;
|
||||
/** 세션값 읽기 — 프로젝트·노선이 정해진 뒤에 부른다. */
|
||||
load: () => void;
|
||||
/** context(config) 기본값·스텝 반영. */
|
||||
setDefaults: (defaultOffsetM: number, stepM: number) => void;
|
||||
}
|
||||
@@ -157,33 +176,13 @@ export function createRockBoundaryStore(options: {
|
||||
* DB에 옛 양수값이 남아 있어도 읽는 즉시 0으로 눌러 계산이 뒤집히지 않게 한다.
|
||||
*/
|
||||
const clamp = (value: number): number => Math.min(value, 0);
|
||||
const offsets = new Map<string, number>();
|
||||
const offsets = (): Map<string, number> => readCells("rockb", sessionKey, () => true);
|
||||
const key = (chainageM: number): string => chainageM.toFixed(2);
|
||||
const put = (chainageM: number, value: number): void =>
|
||||
putCell("rockb", sessionKey, offsets(), chainageM, value);
|
||||
|
||||
function persist(): void {
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
try {
|
||||
persistKeyed("rockb", storageKey, offsets);
|
||||
} catch {
|
||||
/* 세션 저장 실패는 무시(용량 초과 등) — 값은 메모리에 유지된다. */
|
||||
}
|
||||
}
|
||||
|
||||
return keepAlive({
|
||||
return {
|
||||
offsets,
|
||||
load(): void {
|
||||
offsets.clear();
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
try {
|
||||
loadKeyed("rockb", storageKey).forEach(([chainage, offset]) => {
|
||||
if (Number.isFinite(offset)) offsets.set(chainage, offset);
|
||||
});
|
||||
} catch {
|
||||
/* 손상된 세션 값은 무시 — 기본값으로 재시작. */
|
||||
}
|
||||
},
|
||||
setDefaults(defaultOffsetM: number, stepM: number): void {
|
||||
rockBoundaryDefault = defaultOffsetM;
|
||||
rockBoundaryStep = stepM;
|
||||
@@ -197,7 +196,7 @@ export function createRockBoundaryStore(options: {
|
||||
},
|
||||
offsetFor: (section) =>
|
||||
clamp(
|
||||
offsets.get(key(section.chainage_m)) ??
|
||||
offsets().get(key(section.chainage_m)) ??
|
||||
section.design?.rock_boundary_offset_m ??
|
||||
rockBoundaryDefault,
|
||||
),
|
||||
@@ -205,30 +204,27 @@ export function createRockBoundaryStore(options: {
|
||||
const stored = detail()?.cross_sections.find(
|
||||
(section) => Math.abs(section.chainage_m - chainageM) < 0.01,
|
||||
)?.design?.rock_boundary_offset_m;
|
||||
const current = clamp(offsets.get(key(chainageM)) ?? stored ?? rockBoundaryDefault);
|
||||
offsets.set(key(chainageM), clamp(Math.round((current + deltaM) * 100) / 100));
|
||||
persist();
|
||||
const current = clamp(offsets().get(key(chainageM)) ?? stored ?? rockBoundaryDefault);
|
||||
put(chainageM, clamp(Math.round((current + deltaM) * 100) / 100));
|
||||
refreshCard(chainageM);
|
||||
recompute(chainageM);
|
||||
},
|
||||
reset: (chainageM) => {
|
||||
offsets.set(key(chainageM), rockBoundaryDefault);
|
||||
persist();
|
||||
put(chainageM, rockBoundaryDefault);
|
||||
refreshCard(chainageM);
|
||||
recompute(chainageM);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** 측점별 암 절토 경사비 저장소 — 값(Map)과 카드 제어기를 함께 낸다(2026-09-07). */
|
||||
/** 측점별 암 절토 경사비 저장소 — 값(세션 칸)과 카드 제어기를 함께 낸다(2026-09-07). */
|
||||
export interface CutSlopeStore {
|
||||
/** 측점키(누가거리 2자리) → 경사비(1:n 의 n). `buildCrossPatches` 가 그대로 읽는다.
|
||||
/** 지금 세션 칸 — 측점키(누가거리 2자리) → 경사비(1:n 의 n). `buildCrossPatches` 재료(새 Map).
|
||||
* **0 은 「표준값을 씀」**이다 — 되돌리기(↺)가 남기는 값이라, 저장분에 옛 사용자 값이
|
||||
* 남아 있어도 0 이 그것을 덮어 표준으로 돌려놓는다(지우기만 하면 옛 값이 되살아난다). */
|
||||
ratios: Map<string, number>;
|
||||
ratios: () => Map<string, number>;
|
||||
control: CutSlopeControl;
|
||||
load: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,8 +245,12 @@ export function createCutSlopeStore(options: {
|
||||
recompute: (chainageM: number) => void;
|
||||
}): CutSlopeStore {
|
||||
const { sessionKey, standard, refreshCard, recompute } = options;
|
||||
const ratios = new Map<string, number>();
|
||||
// 0(되돌림 표시)도 그대로 싣는다 — 거르면 저장분 옛 값이 되살아난다.
|
||||
const ratios = (): Map<string, number> =>
|
||||
readCells("cutslope", sessionKey, (ratio) => ratio >= 0);
|
||||
const key = (chainageM: number): string => chainageM.toFixed(2);
|
||||
const put = (chainageM: number, value: number): void =>
|
||||
putCell("cutslope", sessionKey, ratios(), chainageM, value);
|
||||
|
||||
/** 이 측점이 쓰는 표준 경사비 — 지반 프리셋(암/토사)의 값. */
|
||||
const standardRatio = (section: { design?: { geometry_preset?: string } | null }): number => {
|
||||
@@ -260,35 +260,12 @@ export function createCutSlopeStore(options: {
|
||||
return typeof value === "number" && value > 0 ? value : 0.4;
|
||||
};
|
||||
|
||||
function persist(): void {
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
try {
|
||||
persistKeyed("cutslope", storageKey, ratios);
|
||||
} catch {
|
||||
/* 세션 저장 실패는 무시(용량 초과 등) — 값은 메모리에 유지된다. */
|
||||
}
|
||||
}
|
||||
|
||||
return keepAlive({
|
||||
return {
|
||||
ratios,
|
||||
load(): void {
|
||||
ratios.clear();
|
||||
const storageKey = sessionKey();
|
||||
if (!storageKey) return;
|
||||
try {
|
||||
loadKeyed("cutslope", storageKey).forEach(([chainage, ratio]) => {
|
||||
// 0(되돌림 표시)도 그대로 싣는다 — 거르면 저장분 옛 값이 되살아난다.
|
||||
if (Number.isFinite(ratio) && ratio >= 0) ratios.set(chainage, ratio);
|
||||
});
|
||||
} catch {
|
||||
/* 손상된 세션 값은 무시 — 표준값으로 재시작. */
|
||||
}
|
||||
},
|
||||
control: {
|
||||
standardRatioFor: (section) => standardRatio(section),
|
||||
ratioFor: (section) => {
|
||||
const session = ratios.get(key(section.chainage_m));
|
||||
const session = ratios().get(key(section.chainage_m));
|
||||
if (session === 0) return standardRatio(section); // 되돌림 — 저장분보다 세션이 먼저다.
|
||||
if (typeof session === "number" && session > 0) return session;
|
||||
const stored = (section.design as { cut_slope_ratio_user?: number } | undefined)
|
||||
@@ -298,14 +275,13 @@ export function createCutSlopeStore(options: {
|
||||
},
|
||||
set: (chainageM, ratio) => {
|
||||
// null = 되돌리기. 지우지 않고 0 을 남겨야 저장분에 있던 옛 값까지 표준으로 돌아간다.
|
||||
if (ratio === null || !Number.isFinite(ratio) || ratio <= 0) ratios.set(key(chainageM), 0);
|
||||
else ratios.set(key(chainageM), Math.round(ratio * 10000) / 10000);
|
||||
persist();
|
||||
if (ratio === null || !Number.isFinite(ratio) || ratio <= 0) put(chainageM, 0);
|
||||
else put(chainageM, Math.round(ratio * 10000) / 10000);
|
||||
refreshCard(chainageM);
|
||||
recompute(chainageM);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** [저장]·[확정]이 함께 쓰는 페이지 상태 창구. */
|
||||
|
||||
@@ -56,7 +56,9 @@ export interface StationControlDeps {
|
||||
| "culvertopt"
|
||||
| "revetlink"
|
||||
| "fordadjust"
|
||||
| "boxadjust",
|
||||
| "boxadjust"
|
||||
| "rockb"
|
||||
| "cutslope",
|
||||
) => string | null;
|
||||
refreshCard: (chainageM: number) => void;
|
||||
detail: () => SectionDetailResponse | null;
|
||||
@@ -606,6 +608,9 @@ export function createStationControls(deps: StationControlDeps): StationControls
|
||||
"revetlink",
|
||||
"fordadjust",
|
||||
"boxadjust",
|
||||
// 암 경계 · 암 경사(카드 칸 · `_UI_Page_Persist` 저장소) — 되돌린 칸으로 카드 · 면적 다시(472-5)
|
||||
"rockb",
|
||||
"cutslope",
|
||||
] as const;
|
||||
const print = (): string =>
|
||||
JSON.stringify(
|
||||
|
||||
Reference in New Issue
Block a user