Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
This commit is contained in:
@@ -58,26 +58,36 @@ export function pipeChainageOf(id: string, projectId: string | null = active): n
|
||||
/** 관을 옮김 — 옛 자리 id 가 새 자리로(새 자리에 남은 옛 기록은 뺌) · 옮긴 관의 id. */
|
||||
export function movePipeId(fromM: number, toM: number, projectId: string | null = active): string {
|
||||
if (!projectId) return `pipe-${toM.toFixed(2)}`;
|
||||
return movePipeIds([[fromM, toM]], projectId)[0] ?? pipeIdAt(toM, projectId);
|
||||
const book = bookOf(projectId);
|
||||
const moving = nearestWithin(book.ids, fromM, (entry) => entry.at);
|
||||
if (!moving) return pipeIdAt(toM, projectId);
|
||||
const stale = nearestWithin(
|
||||
book.ids.filter((entry) => entry !== moving),
|
||||
toM,
|
||||
(entry) => entry.at,
|
||||
);
|
||||
const ids = book.ids
|
||||
.filter((entry) => entry !== stale)
|
||||
.map((entry) => (entry === moving ? { ...entry, at: toM } : entry));
|
||||
writeState("pipe-ids", { next: book.next, ids }, projectId);
|
||||
return moving.id;
|
||||
}
|
||||
|
||||
/** 관 여럿을 한 번에 옮김(노선 따라감 · 411-B) — 옮길 기록을 먼저 다 찾고 옮김(앞 관이 뒤 관 옛 자리로 가도
|
||||
* 뒤 관 기록을 안 지움) · 새 자리에 남은 옮기지 않는 옛 기록은 뺌 · 옮긴 관마다 id(못 찾으면 null). */
|
||||
export function movePipeIds(
|
||||
moves: ReadonlyArray<readonly [number, number]>,
|
||||
/** 관 세션 id 를 그 자리로(화면 목록 맞춤 · 411-B) — 짝 = 관 고정 id 가 쥔 세션 id(노선 따라감 · 끌기 · 되돌리기
|
||||
* 어느 길로 옮겨도 그 id) · 그 자리 근처의 다른 기록(옛 측점 열쇠가 지은 헛 id)은 뺌 · 장부에 없는 id 는 적음. */
|
||||
export function placePipeIds(
|
||||
places: ReadonlyArray<readonly [string, number]>,
|
||||
projectId: string | null = active,
|
||||
): Array<string | null> {
|
||||
if (!projectId) return moves.map(() => null);
|
||||
): void {
|
||||
if (!projectId || !places.length) return;
|
||||
const book = bookOf(projectId);
|
||||
const moving = moves.map(([fromM]) => nearestWithin(book.ids, fromM, (entry) => entry.at));
|
||||
const to = new Map(moving.flatMap((entry, i) => (entry ? [[entry, moves[i][1]]] : [])));
|
||||
const still = book.ids.filter((entry) => !to.has(entry));
|
||||
const stale = new Set([...to.values()].map((at) => nearestWithin(still, at, (e) => e.at)));
|
||||
const to = new Map(places);
|
||||
const ids = book.ids
|
||||
.filter((entry) => !stale.has(entry))
|
||||
.map((entry) => (to.has(entry) ? { ...entry, at: to.get(entry)! } : entry));
|
||||
if (to.size) writeState("pipe-ids", { next: book.next, ids }, projectId);
|
||||
return moving.map((entry) => entry?.id ?? null);
|
||||
.filter((entry) => to.has(entry.id) || !places.some(([, at]) => isNear(entry.at, at)))
|
||||
.map((entry) => (to.has(entry.id) ? { ...entry, at: to.get(entry.id)! } : entry));
|
||||
for (const [id, at] of places) if (!ids.some((entry) => entry.id === id)) ids.push({ id, at });
|
||||
if (JSON.stringify(ids) !== JSON.stringify(book.ids))
|
||||
writeState("pipe-ids", { next: book.next, ids }, projectId);
|
||||
}
|
||||
|
||||
/** 관 목록에 세션 id 를 얹은 사본 — 측점 계획의 덮는 구조물 id(종단 측점 id · 알약 짝)가 세션 id. */
|
||||
|
||||
@@ -317,13 +317,26 @@ function createPagePipeline(
|
||||
replay = false;
|
||||
if (kept) return { alignment: kept }; // 관 · 구조물도 세션 그대로(가게 · 쓰기 그대로)
|
||||
const line = ctx.before.route?.line ?? savedLine() ?? [];
|
||||
// 자리를 읽을 선 — 초안 둔 채 열기(앞 노선 없음)의 가게 값 = 이미 초안 노선 자리(앞 따라감이 씀 · 410)
|
||||
const frame = (list: unknown, saved: unknown) =>
|
||||
!ctx.before.route && ctx.inputs.route && list !== saved
|
||||
? (ctx.out.route?.line ?? line)
|
||||
: line;
|
||||
const { pipes, structures } = ctx.inputs;
|
||||
// 좌표 없는 관 = 앞 따라감(같은 id · 같은 자리) → 정본 좌표 · 옮긴 것 = 그 자리의 앞 노선 위(410)
|
||||
const followed = (ctx.before.follow?.pipes ?? []) as FollowingStructure[];
|
||||
const inputs = {
|
||||
...ctx.inputs,
|
||||
pipes: placed(pipes, line, [...(savedPipes as FollowingStructure[]), ...followed]),
|
||||
structures: placed(structures, line, [], SECTION_STATION_INTERVAL_M),
|
||||
pipes: placed(pipes, frame(pipes, savedPipes), [
|
||||
...(savedPipes as FollowingStructure[]),
|
||||
...followed,
|
||||
]),
|
||||
structures: placed(
|
||||
structures,
|
||||
frame(structures, savedStructures),
|
||||
[],
|
||||
SECTION_STATION_INTERVAL_M,
|
||||
),
|
||||
};
|
||||
return followStage({ ...ctx, inputs });
|
||||
},
|
||||
|
||||
@@ -66,7 +66,7 @@ import "../../B05_Profile/B05_Profile_UI_Style.css";
|
||||
import "../../B05_Profile/B05_Profile_UI_Style_Table.css";
|
||||
import "../../B05_Profile/B05_Profile_UI_Style_Drainage.css";
|
||||
import "../../B05_Profile/B05_Profile_UI_Style_Structures.css";
|
||||
import { movePipeIds, pipeIdAt } from "./design_pipe_ids";
|
||||
import { pipeIdAt, placePipeIds } from "./design_pipe_ids";
|
||||
import { useDesignStore } from "./design_store";
|
||||
import { setPlusStations } from "../../common_util/common_util_structure_station";
|
||||
import {
|
||||
@@ -114,21 +114,24 @@ export async function mountProfileView(
|
||||
let lastPipes: PipeReport | null = shared.lastPipes ?? null;
|
||||
const designStore = useDesignStore(activeProjectId, null);
|
||||
/** 화면 관 목록 = 배수유역도 알림을 가게 관 초안 측점으로(노선 따라 다시 매김 · 349-0d) · 관 세션 id 도
|
||||
* 옮김 — 짝 = 관 고정 id(411-B · 앞 관을 지나 옮겨도 그 관) · id 없는 관은 안 옮김(막 놓은 관). */
|
||||
let lastShown: PipeReport = [];
|
||||
* 그 자리로 — 짝 = 관 고정 id(411-B · 앞 관을 지나 옮겨도 · 되돌려도 그 관) · id 없는 관(막 놓은 관)은 자리로. */
|
||||
let sessionIds = new Map<string, string>(); // 관 고정 id → 세션 id(알약 id)
|
||||
const shownPipes = (pipes: PipeReport): PipeReport => {
|
||||
const shown = followPipeReports(pipes, designStore.get("pipes")).sort(
|
||||
(x, y) => x.chainage_m - y.chainage_m,
|
||||
);
|
||||
const was = new Map(lastShown.flatMap((pipe) => (pipe.id ? [[pipe.id, pipe.chainage_m]] : [])));
|
||||
const moves = shown.flatMap((pipe) => {
|
||||
const from = pipe.id ? was.get(pipe.id) : undefined;
|
||||
return from === undefined || Math.abs(pipe.chainage_m - from) <= 0.005
|
||||
? []
|
||||
: [[from, pipe.chainage_m] as const];
|
||||
});
|
||||
if (moves.length) movePipeIds(moves, activeProjectId);
|
||||
lastShown = shown;
|
||||
placePipeIds(
|
||||
shown.flatMap((pipe) => {
|
||||
const sid = pipe.id && sessionIds.get(pipe.id);
|
||||
return sid ? [[sid, pipe.chainage_m] as const] : [];
|
||||
}),
|
||||
activeProjectId,
|
||||
);
|
||||
sessionIds = new Map(
|
||||
shown.flatMap((pipe) =>
|
||||
pipe.id ? [[pipe.id, pipeIdAt(pipe.chainage_m, activeProjectId)] as const] : [],
|
||||
),
|
||||
);
|
||||
return shown;
|
||||
};
|
||||
const applyPipes = (reported: PipeReport, carryEdits = true): void => {
|
||||
|
||||
@@ -342,17 +342,26 @@ export function mountDrainageToggles(
|
||||
);
|
||||
}
|
||||
|
||||
/** 노선 초안 follow 가 다시 매긴 관 자리(가게 관 초안)를 편집기에 얹음(410) — 짝 = 관 id · 시설 속성 ·
|
||||
* 고름도 따라감 · 재계산 · 초안 쓰기 없음(가게가 이미 그 값). 편집기가 옛 노선 누가거리로 초안을 덮지 않게. */
|
||||
/** 노선 초안 follow 가 다시 매긴 관 자리(가게 관 초안)를 편집기에 얹음(410) — 짝 = 관 id(막 놓아 초안에 id
|
||||
* 없는 관끼리는 누가거리 차례 · 수가 같을 때만) · 시설 속성 · 고름도 따라감 · 재계산 · 초안 쓰기 없음(가게가
|
||||
* 이미 그 값). 편집기가 옛 노선 누가거리로 초안을 덮지 않게. */
|
||||
export function followEditorPipes(
|
||||
editor: PipeEditor,
|
||||
facilities: FacilityStore,
|
||||
followed: ReadonlyArray<{ id?: string | null; chainage_m: number }>,
|
||||
): void {
|
||||
const to = new Map(followed.flatMap((pipe) => (pipe.id ? [[pipe.id, pipe.chainage_m]] : [])));
|
||||
const to = new Map<PipePoint | string, number>(
|
||||
followed.flatMap((pipe) => (pipe.id ? [[pipe.id, pipe.chainage_m]] : [])),
|
||||
);
|
||||
const before = editor.pipes();
|
||||
const byOrder = (a: { chainage_m: number }, b: { chainage_m: number }) =>
|
||||
a.chainage_m - b.chainage_m;
|
||||
const looseEditor = before.filter((pipe) => !(pipe.id && to.has(pipe.id))).sort(byOrder);
|
||||
const looseFollowed = followed.filter((pipe) => !pipe.id).sort(byOrder);
|
||||
if (looseEditor.length === looseFollowed.length)
|
||||
looseEditor.forEach((pipe, i) => to.set(pipe, looseFollowed[i].chainage_m));
|
||||
const moves = before.flatMap((pipe) => {
|
||||
const at = pipe.id ? to.get(pipe.id) : undefined;
|
||||
const at = (pipe.id ? to.get(pipe.id) : undefined) ?? to.get(pipe);
|
||||
return at === undefined || Math.abs(at - pipe.chainage_m) < 0.005
|
||||
? []
|
||||
: [{ pipe, at, attributes: facilities.get(pipe.chainage_m) }];
|
||||
|
||||
@@ -5,7 +5,7 @@ export { followPipeReports } from "../../B05_Profile/logic/B05_Profile_Pipeline_
|
||||
export { followedMarks } from "../../B05_Profile/logic/B05_Profile_Structure_Follow";
|
||||
export {
|
||||
movePipeId,
|
||||
movePipeIds,
|
||||
placePipeIds,
|
||||
pipeChainageOf,
|
||||
pipeIdAt,
|
||||
usePipeIds,
|
||||
|
||||
@@ -46,28 +46,42 @@ out.reports = m
|
||||
.followPipeReports(reports, draft)
|
||||
.map((r) => [r.id, r.chainage_m, r.start_m ?? null]);
|
||||
|
||||
/* 2. 세션 id 장부 — 끌어 옮김(followMovedPipe 길) 뒤 화면 목록 짝(shownPipes 규칙 = id) */
|
||||
/* 2. 세션 id 장부 — 끌어 옮김(followMovedPipe 길) · 그 사이 옛 측점 열쇠가 옛 자리에 헛 id 를 지음 →
|
||||
* 화면 목록 맞춤(shownPipes = 관 고정 id 가 쥔 세션 id 를 새 자리로 · placePipeIds) */
|
||||
const idA = m.pipeIdAt(100, P);
|
||||
const idB = m.pipeIdAt(150, P);
|
||||
const sessionIds = new Map([
|
||||
["p10000", idA],
|
||||
["p15000", idB],
|
||||
]);
|
||||
m.movePipeId(150, 80, P); // 끌기 = 옮긴 관 id 그대로
|
||||
const was = new Map(reports.map((r) => [r.id, r.chainage_m]));
|
||||
const shown = m.followPipeReports(reports, draft);
|
||||
const moves = shown.flatMap((r) => {
|
||||
const from = was.get(r.id);
|
||||
return from === undefined || Math.abs(from - r.chainage_m) <= 0.005 ? [] : [[from, r.chainage_m]];
|
||||
});
|
||||
m.movePipeIds(moves, P);
|
||||
out.ids = { idA, idB, at80: m.pipeIdAt(80, P), at100: m.pipeIdAt(100, P) };
|
||||
out.bookNext = m.pipeIdAt(999, P); // 새 관 = 다음 차례(앞에서 헛 id 가 안 생겼으면 s3)
|
||||
const stray = m.pipeIdAt(150, P); // 옛 측점 열쇠가 옛 자리를 물음
|
||||
const place = (shown, project) =>
|
||||
m.placePipeIds(
|
||||
shown.map((r) => [sessionIds.get(r.id), r.chainage_m]),
|
||||
project,
|
||||
);
|
||||
place(m.followPipeReports(reports, draft), P);
|
||||
out.ids = { idA, idB, stray, at80: m.pipeIdAt(80, P), at100: m.pipeIdAt(100, P) };
|
||||
|
||||
/* 3. 노선 따라감이 두 관을 함께 밂(앞 관이 뒤 관 옛 자리로) — 한 번에 옮김 · 기록 안 잃음 */
|
||||
/* 2-1. 되돌림 — 관이 옛 자리로 돌아감 · 그 자리에 헛 id 가 먼저 있어도 그 관 id(헛 기록은 뺌) */
|
||||
place(
|
||||
[
|
||||
{ id: "p15000", chainage_m: 150 },
|
||||
{ id: "p10000", chainage_m: 100 },
|
||||
],
|
||||
P,
|
||||
);
|
||||
out.back = { at150: m.pipeIdAt(150, P), strayAt: m.pipeChainageOf(stray, P) };
|
||||
|
||||
/* 3. 노선 따라감이 두 관을 함께 밂(앞 관이 뒤 관 옛 자리로) — 기록 안 잃음 · 헛 id 안 생김 */
|
||||
const Q = "q411";
|
||||
const q1 = m.pipeIdAt(100, Q);
|
||||
const q2 = m.pipeIdAt(103, Q);
|
||||
m.movePipeIds(
|
||||
m.placePipeIds(
|
||||
[
|
||||
[100, 103],
|
||||
[103, 106],
|
||||
[q1, 103],
|
||||
[q2, 106],
|
||||
],
|
||||
Q,
|
||||
);
|
||||
@@ -111,6 +125,27 @@ out.editor = {
|
||||
m.followEditorPipes(editor, facilities, [{ id: "p10000", chainage_m: 103 }]); // 같은 값 = 그대로
|
||||
out.editorSame = editor.pipes().map((p) => p.chainage_m);
|
||||
|
||||
/* 5-1. 막 놓은 관 — 초안엔 id 없음 · 편집기는 서버 id(재계산 응답) → id 없는 것끼리 차례 짝 */
|
||||
const fresh = m.createPipeEditor(() => {});
|
||||
fresh.setPipes([
|
||||
{ chainage_m: 120, reason: "confirmed", id: "p12000" },
|
||||
{ chainage_m: 351, reason: "stream", id: "p35100" },
|
||||
]);
|
||||
m.followEditorPipes(fresh, m.createFacilityStore(), [
|
||||
{ chainage_m: 120 },
|
||||
{ id: "p35100", chainage_m: 353 },
|
||||
]);
|
||||
const fresh2 = m.createPipeEditor(() => {});
|
||||
fresh2.setPipes([
|
||||
{ chainage_m: 200, reason: "confirmed", id: "p20000" },
|
||||
{ chainage_m: 351, reason: "stream", id: "p35100" },
|
||||
]);
|
||||
m.followEditorPipes(fresh2, m.createFacilityStore(), [
|
||||
{ chainage_m: 204 },
|
||||
{ id: "p35100", chainage_m: 353 },
|
||||
]);
|
||||
out.fresh = [fresh.pipes(), fresh2.pipes()].map((list) => list.map((p) => [p.id, p.chainage_m]));
|
||||
|
||||
/* 6. B06 측점 열쇠 — 서버 관 id 도 관(계산 묶음 `pipe:` 와 같음) */
|
||||
const planned = [
|
||||
{ chainage_m: 80.17, owners: ["p8017"], role: "anchor" },
|
||||
|
||||
@@ -6,7 +6,7 @@ B06 열쇠 = 계산 묶음과 같음.
|
||||
새 `pipe-sN` 을 지어 새 측점 · 계획고 0 이 생기던 흠. 진짜 TS 를 vite ssr 로
|
||||
묶어(`helper_411_b_entry.ts`) Node 로 잰다:
|
||||
① `followPipeReports` 짝 = 관 고정 id
|
||||
② 세션 id 장부(`movePipeId` · `movePipeIds`)
|
||||
② 세션 id 장부(`movePipeId` 끌기 · `placePipeIds` 화면 맞춤)
|
||||
③ `followedMarks`
|
||||
④ 배수 편집기 `followEditorPipes`(410 · 따라간 자리 받음)
|
||||
⑤ B06 `useStationOwners` · `stationKeysOf`.
|
||||
@@ -75,15 +75,19 @@ def test_알림_목록_짝은_id(out):
|
||||
|
||||
def test_앞_관_지나_옮겨도_세션_id_그대로(out):
|
||||
ids = out["ids"]
|
||||
assert ids["at80"] == ids["idB"] # 옮긴 관 = 제 id
|
||||
assert ids["at80"] == ids["idB"] # 옮긴 관 = 제 id(옛 자리 헛 id 를 안 받음)
|
||||
assert ids["at100"] == ids["idA"] # 이웃 관 = 제 id(새 pipe-sN 아님)
|
||||
assert out["bookNext"] == "pipe-s3" # 헛 id 없음 = 알약 · 측점 수 그대로
|
||||
|
||||
|
||||
def test_되돌려_옛_자리로_가도_그_관_id(out):
|
||||
assert out["back"]["at150"] == out["ids"]["idB"]
|
||||
assert out["back"]["strayAt"] is None # 그 자리 헛 기록은 장부에서 빠짐
|
||||
|
||||
|
||||
def test_따라감이_여럿을_함께_밀어도_기록_안_잃음(out):
|
||||
batch = out["batch"]
|
||||
assert batch["at103"] == batch["q1"] and batch["at106"] == batch["q2"]
|
||||
assert batch["next"] == "pipe-s3"
|
||||
assert batch["next"] == "pipe-s3" # 헛 id 없음 = 알약 · 측점 수 그대로
|
||||
|
||||
|
||||
def test_알약은_그_관_자리(out):
|
||||
@@ -98,6 +102,13 @@ def test_배수_편집기는_따라간_자리를_받음(out):
|
||||
assert out["editorSame"] == [98, 103]
|
||||
|
||||
|
||||
def test_막_놓은_관은_id_없는_것끼리_차례로_따라감(out):
|
||||
assert out["fresh"] == [
|
||||
[["p12000", 120], ["p35100", 353]],
|
||||
[["p20000", 204], ["p35100", 353]],
|
||||
]
|
||||
|
||||
|
||||
def test_B06_열쇠는_서버_관_id_도_pipe(out):
|
||||
assert out["keys"] == ["structure:st-1", "pipe:p8017"]
|
||||
assert out["keys"][1] == out["pipelineKey"] # 계산 묶음 측점 열쇠와 같음
|
||||
|
||||
Reference in New Issue
Block a user