Merge branch 'sub_laptop_1' of https://gitea.chemifactory.com/eomsangdon/Aislo into main_laptop_1
This commit is contained in:
@@ -30,6 +30,7 @@ import type {
|
||||
} from "../A00_Common/design/logic/design_pipeline_types";
|
||||
import {
|
||||
nearestWithin,
|
||||
readSelection,
|
||||
selectDesign,
|
||||
subscribeSelection,
|
||||
type DesignSelection,
|
||||
@@ -316,8 +317,13 @@ export function createCrossPreview(params: CrossPreviewParams): CrossPreviewWind
|
||||
stopPick();
|
||||
};
|
||||
|
||||
/** 그 측점 횡단을 위 판에 · 아래 판은 비움(전후 비교는 노선을 고쳤을 때만) · 접힌 판은 폄. */
|
||||
function showStation(key: string | null, chainageM: number, structure = false): void {
|
||||
/** 그 측점 횡단을 위 판에 · 아래 판은 비움(전후 비교는 노선을 고쳤을 때만) · 접힌 판은 폄(`open`). */
|
||||
function showStation(
|
||||
key: string | null,
|
||||
chainageM: number,
|
||||
structure = false,
|
||||
open = true,
|
||||
): void {
|
||||
// 아래 판은 **노선을 고쳤을 때만** 채운다(2026-09-12 사용자 지시 ⑤). 예전에는 같은
|
||||
// 측점을 두 번 누르면 위·아래에 **같은 그림 두 장**이 떠 전후 비교가 되지 않았다.
|
||||
previous.show(null, interval());
|
||||
@@ -326,11 +332,11 @@ export function createCrossPreview(params: CrossPreviewParams): CrossPreviewWind
|
||||
shown = null;
|
||||
redraw();
|
||||
const panels = sidePanelsOf.get(params.side);
|
||||
if (panels && panels.current() === null) panels.open("cross", false); // 열되 기억 안 함(393-1)
|
||||
if (open && panels && panels.current() === null) panels.open("cross", false); // 열되 기억 안 함(393-1)
|
||||
}
|
||||
|
||||
/** 다른 보기(종단 · 3D · 목록 · 횡단 카드 · 평면 마커) 고름 → 그 측점 횡단(구조물 · 관도 · 411-A). */
|
||||
function follow(selection: DesignSelection): void {
|
||||
function follow(selection: DesignSelection, open = true): void {
|
||||
attach();
|
||||
const at = selection.chainageM;
|
||||
if (at === null) {
|
||||
@@ -343,12 +349,15 @@ export function createCrossPreview(params: CrossPreviewParams): CrossPreviewWind
|
||||
}
|
||||
const station = nearestWithin(stationsNow(), at, (entry) => entry.chainageM);
|
||||
const structure = !!selection.structureId || (!!station && !isGridStation(station));
|
||||
showStation(station?.key ?? null, station?.chainageM ?? at, structure);
|
||||
showStation(station?.key ?? null, station?.chainageM ?? at, structure, open);
|
||||
}
|
||||
const stopPick = subscribeSelection(params.projectId, (selection, from) => {
|
||||
if (from !== PLAN_PICK_FROM) follow(selection);
|
||||
});
|
||||
attach();
|
||||
// 들어올 때 이미 고른 것 — 판에 그 측점 횡단(494-1 · 안 그리면 고른 채 빈 판 · 다시 누르면 고름이 풀림) · 판은 안 폄
|
||||
const picked = readSelection(params.projectId);
|
||||
if (picked) follow(picked, false);
|
||||
|
||||
/** 그 측점 발자국 기록(열쇠 · 없으면 누가거리) — 발자국 단계 전이면 없음. */
|
||||
const footprintAt = (key: string | null, chainageM: number): FootprintRecord | undefined => {
|
||||
|
||||
@@ -127,16 +127,24 @@ export function staleKey(
|
||||
.join("|");
|
||||
}
|
||||
|
||||
/** 화면 밖 카드 줄이 쉬는 틈을 기다리는 한도(ms). */
|
||||
const IDLE_TIMEOUT_MS = 200;
|
||||
|
||||
/**
|
||||
* 쉬는 틈 일거리 줄(346-2) — 화면 밖 횡단 카드를 몇 장씩 갈아 끼움(카드 하나 ≈ 8 ms · 0.54 s / 66).
|
||||
* 줄에 든 측점은 `run` 이 꺼내 그림 · 통째로 다시 그리면 `queue.clear()`.
|
||||
* 쉬는 틈이 안 오는 창(ORCA 내장 · 가려진 창)도 늦어도 `IDLE_TIMEOUT_MS` 에 그림 — 안 그러면 새 구조물 측점
|
||||
* 카드가 빈 설계 그대로 남음(494-1).
|
||||
*/
|
||||
export function createIdleQueue(run: (chainageM: number) => void, batch = 6) {
|
||||
const queue = new Set<number>();
|
||||
let waiting = 0;
|
||||
const drain = (): void => {
|
||||
if (waiting || !queue.size) return;
|
||||
const idle = window.requestIdleCallback ?? ((next: () => void) => window.setTimeout(next, 16));
|
||||
const idle = (next: () => void): number =>
|
||||
window.requestIdleCallback
|
||||
? window.requestIdleCallback(next, { timeout: IDLE_TIMEOUT_MS })
|
||||
: window.setTimeout(next, 16);
|
||||
waiting = idle(() => {
|
||||
waiting = 0;
|
||||
for (const chainageM of [...queue].slice(0, batch)) {
|
||||
|
||||
@@ -36,6 +36,8 @@ global.ResizeObserver = class {
|
||||
};
|
||||
|
||||
let listener = null;
|
||||
/** 들어올 때 이미 고른 것(`readSelection` · 494-1). */
|
||||
let stored = null;
|
||||
let stations = [];
|
||||
const route = { id: 1 };
|
||||
const subscribers = [];
|
||||
@@ -57,6 +59,7 @@ const STUBS = {
|
||||
if (Math.abs(of(item) - at) < gap) [best, gap] = [item, Math.abs(of(item) - at)];
|
||||
return best;
|
||||
},
|
||||
readSelection: () => stored,
|
||||
selectDesign() {},
|
||||
subscribeSelection: (_projectId, fn) => ((listener = fn), () => {}),
|
||||
},
|
||||
@@ -157,4 +160,19 @@ out.latchedMoved = shownNow();
|
||||
// ⑦ 다른 보기가 풂(null · b05) = 판 비움(382-2 그대로)
|
||||
pick(null, null, "b05");
|
||||
out.otherRelease = { ...shownNow(), watched: preview.watched() };
|
||||
// ⑧ 고른 구조물을 들고 들어옴(494-1) — 판을 만들자마자 그 측점 횡단(빈 판 아님)
|
||||
stored = { chainageM: 45, structureId: "A" };
|
||||
stations = [...grid, station("structure:A", 45, "structure", "A")];
|
||||
const entrySide = fakeElement();
|
||||
const entry = cross.createCrossPreview({
|
||||
projectId: "p1",
|
||||
side: entrySide,
|
||||
request: () => ({ vertices: [], min_radius_m: 15, station_interval_m: 20 }),
|
||||
});
|
||||
const entryPane = entrySide.children[0];
|
||||
out.entryPick = {
|
||||
station: entryPane.querySelector(".b05-routeedit__cross-station").textContent,
|
||||
foot: entryPane.querySelector(".b05-routeedit__cross-foot").textContent,
|
||||
structure: entry.watched()?.structure ?? null,
|
||||
};
|
||||
console.log(JSON.stringify(out));
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/* PLAN 494-1 단위검증 헬퍼 — 화면 밖 횡단 카드 줄(`createIdleQueue`)이 쉬는 틈이 안 와도 그려지는가.
|
||||
* `B06_Section_UI_Section_Common.ts` 에서 그 함수만 꺼내 트랜스파일해 Node 로 돌린다(다른 import 안 끎).
|
||||
* 가짜 창 = 쉬는 틈이 영영 안 오는 브라우저(ORCA 내장 · 가려진 창) — 시간 제한을 준 부탁만 그 시간에 불림.
|
||||
* 결과는 표준출력에 JSON 한 벌.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const file = path.join(__dirname, "..", "..", "B06_Section", "B06_Section_UI_Section_Common.ts");
|
||||
const src = fs.readFileSync(file, "utf-8").split("\r\n").join("\n");
|
||||
const body = src.match(/export function createIdleQueue[\s\S]*?\n}\n/);
|
||||
if (!body) throw new Error("createIdleQueue 못 찾음");
|
||||
// 함수가 쓰는 파일 머리 상수(대문자 이름 한 줄)
|
||||
const consts = [...src.matchAll(/^const [A-Z_]+ = [^;\n]+;$/gm)].map((m) => m[0]).join("\n");
|
||||
const js = ts.transpileModule(`${consts}\n${body[0]}`, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
|
||||
let now = 0;
|
||||
const timers = []; // { at, fn }
|
||||
const window = {
|
||||
requestIdleCallback: (fn, options) => {
|
||||
// 쉬는 틈 없음 — 시간 제한이 있을 때만 그 시간에 부름(didTimeout)
|
||||
if (options && Number.isFinite(options.timeout))
|
||||
timers.push({
|
||||
at: now + options.timeout,
|
||||
fn: () => fn({ didTimeout: true, timeRemaining: () => 0 }),
|
||||
});
|
||||
return timers.length + 1;
|
||||
},
|
||||
setTimeout: (fn, ms) => (timers.push({ at: now + (ms || 0), fn }), timers.length + 1),
|
||||
};
|
||||
const mod = { exports: {} };
|
||||
new Function("module", "exports", "window", js)(mod, mod.exports, window);
|
||||
const { createIdleQueue } = mod.exports;
|
||||
|
||||
const ran = [];
|
||||
const q = createIdleQueue((m) => ran.push(m), 2);
|
||||
[145, 150, 155, 160, 165].forEach((m) => q.queue.add(m));
|
||||
q.drain();
|
||||
// 2 초 흐름 — 그 사이 불릴 것 모두 부름
|
||||
for (;;) {
|
||||
timers.sort((a, b) => a.at - b.at);
|
||||
const next = timers[0];
|
||||
if (!next || next.at > 2000) break;
|
||||
timers.shift();
|
||||
now = next.at;
|
||||
next.fn();
|
||||
}
|
||||
process.stdout.write(JSON.stringify({ ran, left: [...q.queue], elapsedMs: now }));
|
||||
@@ -0,0 +1,53 @@
|
||||
"""PLAN 494-1 — B06 화면 밖 횡단 카드가 쉬는 틈이 안 와도 다시 그려지는가.
|
||||
|
||||
구조물을 놓으면 새 측점 카드가 빈 설계로 먼저 서고, 다시 셈 뒤 화면 밖 카드는 쉬는 틈 줄
|
||||
(`createIdleQueue`)에서 갈아 끼움. 쉬는 틈이 안 오는 창(ORCA 내장 · 가려진 창)에서는 줄이 영영 안
|
||||
비어 설계선 · 구조물 형상이 없는 카드가 남았음. 짝: `B06_Section/B06_Section_UI_Section_Common.ts`.
|
||||
실제 TS 를 `helper_494_idle_queue.cjs` 가 Node 로 돌린다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def _run() -> dict:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", os.path.join(HERE, "helper_494_idle_queue.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_쉬는_틈이_없어도_줄의_카드를_모두_그린다() -> None:
|
||||
out = _run()
|
||||
assert out["ran"] == [145, 150, 155, 160, 165]
|
||||
assert out["left"] == []
|
||||
|
||||
|
||||
def test_늦어도_1초_안에_다_그린다() -> None:
|
||||
"""카드 다섯 · 두 장씩 = 세 번 — 시간 제한 안에 끝남."""
|
||||
assert _run()["elapsedMs"] <= 1000
|
||||
|
||||
|
||||
def test_B05_평면_횡단판은_들어올_때_이미_고른_측점을_그린다() -> None:
|
||||
"""B05 「횡단」 판 — 고른 구조물을 들고 들어오면 빈 판이었음(다시 누르면 고름이 풀림).
|
||||
`_RouteEdit_Cross` 진짜를 `helper_411_a_cross_structure.cjs` 가 돌림(⑧)."""
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", os.path.join(HERE, "helper_411_a_cross_structure.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
got = json.loads(proc.stdout)["entryPick"]
|
||||
assert got == {"station": "45", "foot": "횡단 45", "structure": True}
|
||||
Reference in New Issue
Block a user