feat(structure): B05 · B06 좌측 패널 배관 1차 요청 반영(54-1)
- 잇기 단추 글 「고르기」 → 「선택」(명세 · 정의 그룹 · 종류 단위 줄 공용) - 「지난 정의 값」 상자 모든 종류에서 숨김 — 값은 파일에 그대로 · 코드 남김 - 관 길이 = 보여 주는 칸(손 입력 칸 아님 · 정수 · 횡단 전 「횡단 설계 뒤 셈」) - 유입 집수정 = 길이 · 기준측점 전 · 후 칸 숨김 · 옛 값은 병합이 지킴 - 배관 유출 칸에 읽기 줄(관 길이 · 좌우 자동 자리 안내) 안 옮김 - [흙막이 추가] 없앰 — 흙막이 컨테이너 수 = 유출 「추가 기슭막이(단)」 N(늘 보임 · 줄면 끝에서 빠짐 · 잇기 함께 풂 · 이름 흙막이) - 단 수 소스 = 횡단 세션 값 → 캐시 patch · 종단 보기도 같은 횡단 자료(B05 = B06) - 지형이 단을 못 세워 되돌릴 때 캐시 patch 도 맞춤(옛 수로 돌아가던 흠) - 집계표 선택 컨테이너 이름 짝(서버) 흙막이 · 시험 기대값 · 52-1 · 52-13 화면 시나리오 고침 · 54-1 화면 점검 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
@@ -55,6 +55,9 @@ export interface SpecFormHooks {
|
||||
/** 숫자 칸 빈 칸 안내. */
|
||||
hint?: (c: SpecContainer, item: SpecItem, state: SpecState) => string;
|
||||
onChange?: (c: SpecContainer, item: SpecItem) => void;
|
||||
/** 선택 컨테이너 개수를 밖에서 정함(54-1 배관 흙막이 = 추가 기슭막이(단)) — 수면 그만큼 늘 보임 ·
|
||||
* [추가] · [빼기] 없음 · 줄면 끝에서부터 빠짐 · null = 모름(저장된 것 그대로) · undefined = 손 [추가]. */
|
||||
count?: (c: SpecContainer) => number | null | undefined;
|
||||
}
|
||||
|
||||
export interface SpecForm {
|
||||
@@ -75,6 +78,8 @@ export interface SpecForm {
|
||||
/** 잇기 값이 바뀌었을 때 줄을 다시 그림. */
|
||||
paintLinks: () => void;
|
||||
refresh: () => void;
|
||||
/** 밖에서 정한 개수(`hooks.count`)가 바뀌었을 때 — 맞춰 그리고 바뀌었으면 change 알림. */
|
||||
recount: () => void;
|
||||
}
|
||||
|
||||
export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {}): SpecForm {
|
||||
@@ -122,11 +127,12 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
}
|
||||
const input = numberInput(String(item.step ?? 0.1), "0", hooks.hint?.(c, item, state) ?? "");
|
||||
if (item.kind === "auto") {
|
||||
input.type = "text";
|
||||
input.readOnly = true;
|
||||
input.value = hooks.auto?.(c, item, state) || "—";
|
||||
input.title = "자동 계산 — 고치지 않음";
|
||||
return input;
|
||||
// 보여 주는 칸(54-1) — 손 입력 칸 아님
|
||||
const shown = document.createElement("output");
|
||||
shown.className = "b05-structure__auto";
|
||||
shown.textContent = hooks.auto?.(c, item, state) || "—";
|
||||
shown.title = "자동 계산";
|
||||
return shown;
|
||||
}
|
||||
input.value = values[item.id] ?? "";
|
||||
return stepper(input, item.step ?? 0.1);
|
||||
@@ -187,7 +193,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
rows.push({ key, set: row.set });
|
||||
box.body.append(row.root);
|
||||
}
|
||||
if (!c.required) {
|
||||
if (!c.required && hooks.count?.(c) === undefined) {
|
||||
const remove = document.createElement("button");
|
||||
remove.type = "button";
|
||||
remove.className = "b05-route__irregular-btn";
|
||||
@@ -211,13 +217,25 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
return box.root;
|
||||
};
|
||||
|
||||
function paint(): void {
|
||||
/** 밖에서 정한 개수로 맞춤 — 줄면 끝 인스턴스 · 잇기를 뺌. 바뀌었으면 true. */
|
||||
const fit = (c: SpecContainer, list: Instance[]): boolean => {
|
||||
const n = hooks.count?.(c);
|
||||
if (c.required || n === undefined || n === null || n === list.length) return false;
|
||||
for (let k = n + 1; k <= list.length; k++) links?.set(linkOf(c, k), null);
|
||||
list.splice(n);
|
||||
while (list.length < n) list.push({});
|
||||
return true;
|
||||
};
|
||||
|
||||
function paint(quiet = true): void {
|
||||
rows.length = 0;
|
||||
const boxes: HTMLElement[] = [];
|
||||
const fitted: SpecContainer[] = [];
|
||||
for (const c of spec?.containers ?? []) {
|
||||
const list = (state[c.id] ??= c.required ? [{}] : []);
|
||||
if (fit(c, list)) fitted.push(c);
|
||||
list.forEach((values, index) => boxes.push(instanceBox(c, values, index)));
|
||||
if (c.required || (c.max && list.length >= c.max)) continue;
|
||||
if (c.required || (c.max && list.length >= c.max) || hooks.count?.(c) !== undefined) continue;
|
||||
const add = document.createElement("button");
|
||||
add.type = "button";
|
||||
add.className = "b05-route__irregular-btn b05-structure__spec-add";
|
||||
@@ -232,6 +250,8 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
boxes.push(add);
|
||||
}
|
||||
root.replaceChildren(...boxes);
|
||||
// 개수가 밖에서 바뀌었으면 값 변경으로 알림(불러오기 때는 안 함)
|
||||
if (!quiet) fitted.forEach((c) => hooks.onChange?.(c, c.items[0]));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -257,6 +277,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
||||
const now = links?.get() ?? {};
|
||||
rows.forEach((row) => row.set(now[row.key] ?? null));
|
||||
},
|
||||
refresh: paint,
|
||||
refresh: () => paint(),
|
||||
recount: () => paint(false),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* design_structure_pipe.ts
|
||||
* 횡단배수 › 배관 명세 — 사용자 구조물 표 01(`docs/raw/구조물정의_표/01_횡단배수_배관.md`) 그대로.
|
||||
*
|
||||
* 컨테이너 다섯: 관(필수) · 관 보호공(유입)(필수) · 관 보호공(유출)(필수) · 흙막이 추가(선택 · 여러 개) ·
|
||||
* 도수로/돌붙임(선택 · 여러 개). 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
|
||||
* 컨테이너 다섯: 관(필수) · 관 보호공(유입)(필수) · 관 보호공(유출)(필수) · 흙막이(선택 · 개수 = 유출
|
||||
* 「추가 기슭막이(단)」 · 54-1) · 도수로/돌붙임(선택 · 여러 개). 집수정 = 길이 · 전 · 후 없음(54-1).
|
||||
* 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
|
||||
* 저장 키 = pipe_points.json 옛 키 그대로(B06 횡단 · B07 · B08 이 읽는 자리) — 흙막이 값은 옛 「기슭막이」
|
||||
* (`inlet_type` · `*_revet_*`)에 둠. 새 키 = 집수정 높이 · 선택 컨테이너(`soil_add_{n}_*` · `chute_add_{n}_*`).
|
||||
* 정함(PLAN 52): 유출 형태 = 흙막이 6가지 · 도수로/돌붙임 = 구조만 · 길이 = 전 + 후 맞물림.
|
||||
@@ -40,10 +41,13 @@ function guardItems(keys?: Record<string, Record<string, string>>): SpecItem[] {
|
||||
? {
|
||||
item: "structure",
|
||||
map: Object.fromEntries(
|
||||
Object.entries(keys).map(([structure, k]) => [
|
||||
structure,
|
||||
{ key: k[field], choices: choices?.[structure] },
|
||||
]),
|
||||
// 키 없는 갈래 = 그 구조에 없는 칸(집수정 길이 · 전 · 후) — 가림
|
||||
Object.entries(keys)
|
||||
.filter(([, k]) => k[field])
|
||||
.map(([structure, k]) => [
|
||||
structure,
|
||||
{ key: k[field], choices: choices?.[structure] },
|
||||
]),
|
||||
),
|
||||
}
|
||||
: undefined;
|
||||
@@ -121,13 +125,7 @@ export const PIPE_SPEC: StructureSpec = {
|
||||
],
|
||||
},
|
||||
...guardItems({
|
||||
[BASIN]: {
|
||||
form: "inlet_structure",
|
||||
length: "inlet_basin_length_m",
|
||||
height: "inlet_basin_height_m",
|
||||
before: "inlet_basin_before_m",
|
||||
after: "inlet_basin_after_m",
|
||||
},
|
||||
[BASIN]: { form: "inlet_structure", height: "inlet_basin_height_m" },
|
||||
[SOIL]: revetKeys("inlet"),
|
||||
}),
|
||||
],
|
||||
@@ -150,7 +148,7 @@ export const PIPE_SPEC: StructureSpec = {
|
||||
},
|
||||
{
|
||||
id: "soil_add",
|
||||
title: "흙막이 추가",
|
||||
title: "흙막이",
|
||||
required: false,
|
||||
prefix: "soil_add",
|
||||
link: "gsoiladd",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* 셈 · 시험 test_b06_pipe_length) 그대로 — 여기서 새로 셈하지 않음 · 화면에 보이기만(저장 안 함).
|
||||
* 높이 = 손 입력 · 횡단도 조정 높이는 B06 `withSpecDefaults` 가 같은 키(`*_revet_height_m`)에 실음 ·
|
||||
* 빈 칸 안내 = 관경 기준 최소 높이(`pipeWallMinHeightM` ↔ 파이썬 `pipe_wall_min_height_m` 짝).
|
||||
* 흙막이 개수 = 주인 측점 유출 「추가 기슭막이(단)」(횡단 설계 patch `extra_wall_counts` · 54-1) — 읽기만.
|
||||
* ========================================================================== */
|
||||
|
||||
import { pipeWallMinHeightM } from "../../common_util/common_util_culvert_sets";
|
||||
@@ -18,9 +19,16 @@ export function setPipeSectionsSource(source: () => readonly CrossSection[] | nu
|
||||
sectionsSource = source;
|
||||
}
|
||||
|
||||
/** 관 길이(m) — 그 관의 주인 측점(관 자리에서 가장 가까운 측점) 횡단 기하 값 · 못 세우면 저장된
|
||||
* `pipe_length_m` · 그것도 없으면 null. */
|
||||
export function pipeLengthM(chainageM: number | null): number | null {
|
||||
/** 유출 단 수를 대는 곳 — 횡단 보기의 세션 값 우선(`countFor`) · 없으면 정본 patch. */
|
||||
let extraCountSource: ((section: CrossSection) => number) | null = null;
|
||||
export function setPipeExtraCountSource(source: (section: CrossSection) => number): void {
|
||||
extraCountSource = source;
|
||||
}
|
||||
|
||||
/** 그 관의 주인 측점(관 자리에서 가장 가까운 측점) — 횡단 자료가 없으면 null. */
|
||||
function ownerSection(
|
||||
chainageM: number | null,
|
||||
): { owner: CrossSection; sections: readonly CrossSection[] } | null {
|
||||
const sections = sectionsSource?.() ?? null;
|
||||
if (chainageM === null || !sections?.length) return null;
|
||||
const holders = sections.filter(
|
||||
@@ -30,11 +38,26 @@ export function pipeLengthM(chainageM: number | null): number | null {
|
||||
const owner = holders.reduce((a, b) =>
|
||||
Math.abs(b.chainage_m - chainageM) < Math.abs(a.chainage_m - chainageM) ? b : a,
|
||||
);
|
||||
return { owner, sections };
|
||||
}
|
||||
|
||||
/** 관 길이(m) — 주인 측점 횡단 기하 값 · 못 세우면 저장된 `pipe_length_m` · 그것도 없으면 null. */
|
||||
export function pipeLengthM(chainageM: number | null): number | null {
|
||||
const found = ownerSection(chainageM);
|
||||
if (!found) return null;
|
||||
const { owner, sections } = found;
|
||||
const drawn = computeStoredLayouts(owner, sections)?.culvert?.pipe?.lengthM;
|
||||
const length = typeof drawn === "number" && drawn > 0 ? drawn : owner.design?.pipe_length_m;
|
||||
return typeof length === "number" && length > 0 ? length : null;
|
||||
}
|
||||
|
||||
/** 흙막이 개수 = 주인 측점 유출 「추가 기슭막이(단)」 — 횡단 자료가 없으면 null(모름). */
|
||||
export function pipeExtraCount(chainageM: number | null): number | null {
|
||||
const owner = ownerSection(chainageM)?.owner;
|
||||
if (!owner) return null;
|
||||
return extraCountSource?.(owner) ?? owner.design?.extra_wall_counts?.outlet ?? 0;
|
||||
}
|
||||
|
||||
/** 흙막이 높이 빈 칸 안내 — 관경 기준 최소 높이(엔진이 빈 칸을 이 값으로 그림). */
|
||||
export function guardHeightHint(diameterMm: string): string {
|
||||
const d = Number(diameterMm || "1000") / 1000;
|
||||
|
||||
@@ -73,7 +73,7 @@ import {
|
||||
createStandardSlot,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Panel";
|
||||
import type { DesignPageHost } from "./design_page";
|
||||
import { setPipeSectionsSource } from "./design_structure_pipe_calc";
|
||||
import { setPipeExtraCountSource, setPipeSectionsSource } from "./design_structure_pipe_calc";
|
||||
import {
|
||||
bindDesignView,
|
||||
bindPipeOptions,
|
||||
@@ -281,7 +281,10 @@ export async function mountCrossView(
|
||||
ensureSampledWidth,
|
||||
projectId: () => projectId,
|
||||
onSaveError: (message) => showToast(`배수관 구간값 저장 실패 — ${message}`, "error"),
|
||||
onExtraCount: () => structuresPanel.facility.recount(),
|
||||
});
|
||||
// 배관 흙막이 개수 = 유출 단 수(54-1) — 세션 값 우선
|
||||
setPipeExtraCountSource((section) => stationControls.extraWalls.countFor(section, "outlet"));
|
||||
|
||||
const sectionView = createSectionView(
|
||||
(chainageM, change) => {
|
||||
|
||||
@@ -70,6 +70,7 @@ import {
|
||||
} from "../../B05_Profile/B05_Profile_UI_Page_Actions";
|
||||
import { createLatestSource, panelValuesOf } from "../../B05_Profile/B05_Profile_UI_Page_Latest";
|
||||
import { stationLinesOf } from "../../B05_Profile/B05_Profile_UI_Page_Stations";
|
||||
import { setPipeSectionsSource } from "./design_structure_pipe_calc";
|
||||
import type { RouteProfilePanelCallbacks } from "../../B05_Profile/B05_Profile_UI_Profile_Panel_Types";
|
||||
import "../../B05_Profile/B05_Profile_UI_Style.css";
|
||||
// 700줄 제한으로 잘라낸 조각 — 본체 **다음에** 불러야 캐스케이드 순서가 같다(2026-09-04).
|
||||
@@ -194,6 +195,8 @@ export async function mountProfileView(
|
||||
let latest: RouteLatestResponse | null = null;
|
||||
let roadWidths = DEFAULT_ROAD_WIDTHS;
|
||||
let currentSectionDetail: SectionDetailResponse | null = null;
|
||||
// 배관 관 길이 · 흙막이 개수(54-1) — 종단 보기도 같은 횡단 자료로(B05 = B06)
|
||||
setPipeSectionsSource(() => currentSectionDetail?.cross_sections ?? null);
|
||||
/** [예상형상] 표시 상태 — 측점 바를 계획고 위로 올릴지 판단한다(패널 기본 ON). */
|
||||
let corridorVisible = true;
|
||||
let routeReady = false;
|
||||
|
||||
@@ -55,7 +55,11 @@ import {
|
||||
} from "../A00_Common/design/design_structure_box";
|
||||
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||
import { fordHeightHint, fordHeightM } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import { guardHeightHint, pipeLengthM } from "../A00_Common/design/design_structure_pipe_calc";
|
||||
import {
|
||||
guardHeightHint,
|
||||
pipeExtraCount,
|
||||
pipeLengthM,
|
||||
} from "../A00_Common/design/design_structure_pipe_calc";
|
||||
import {
|
||||
hasLinks,
|
||||
linkFields,
|
||||
@@ -215,6 +219,8 @@ export interface FacilityOptionsForm {
|
||||
paintLinks: () => void;
|
||||
/** 고른 관 측점 — 관 길이(자동) 계산에 씀. */
|
||||
setChainage: (chainageM: number | null) => void;
|
||||
/** 횡단 「추가 기슭막이(단)」이 바뀜 — 배관 흙막이 컨테이너 수를 다시 맞춤(54-1 · B06 이 부름). */
|
||||
recount: () => void;
|
||||
/** 현재 필드 값을 시설 옵션으로 읽는다(빈 값 생략). 숨긴 칸은 폼이 받은 옛 값 그대로. */
|
||||
readOptions: () => Record<string, string | number>;
|
||||
/** 「구조물 옵션 보이기」에서 끈 레지스트리 옵션 키 — 그 칸을 가린다(값은 옛 값 보존).
|
||||
@@ -265,8 +271,10 @@ export function createFacilityOptionsForm(
|
||||
auto: (_c, item) => {
|
||||
if (item.id !== "length") return "";
|
||||
const length = pipeLengthM(chainage);
|
||||
return length === null ? "횡단 설계 뒤 셈" : length.toFixed(2);
|
||||
return length === null ? "횡단 설계 뒤 셈" : String(Math.round(length));
|
||||
},
|
||||
// 흙막이 개수 = 유출 「추가 기슭막이(단)」(54-1)
|
||||
count: (c) => (c.id === "soil_add" ? pipeExtraCount(chainage) : undefined),
|
||||
hint: (c, item, state) => {
|
||||
const soil = c.id !== "inlet" || state.inlet?.[0]?.structure === "기슭막이";
|
||||
return item.id === "height" && soil ? guardHeightHint(state.pipe?.[0]?.diameter ?? "") : "";
|
||||
@@ -480,6 +488,9 @@ export function createFacilityOptionsForm(
|
||||
chainage = chainageM;
|
||||
if (current === "pipe") pipeForm.refresh();
|
||||
},
|
||||
recount() {
|
||||
if (current === "pipe") pipeForm.recount();
|
||||
},
|
||||
onInletStructureChange(handler) {
|
||||
inletStructureHandler = handler;
|
||||
},
|
||||
@@ -499,7 +510,11 @@ export function createFacilityOptionsForm(
|
||||
// 받은 값만 칸에 — 없으면 「안 정함」(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7).
|
||||
const groups = Object.fromEntries((definition?.groups ?? []).map((g) => [g.id, g.name]));
|
||||
const names = definition?.spec_names ?? {};
|
||||
if (facility === "pipe") pipeForm.write(PIPE_SPEC, options, names, groups);
|
||||
if (facility === "pipe") {
|
||||
// 정의 파일의 옛 이름 「흙막이 추가」 — 명세 이름 「흙막이」가 앞섬(54-1)
|
||||
const { soil_add: _old, ...pipeNames } = names;
|
||||
pipeForm.write(PIPE_SPEC, options, pipeNames, groups);
|
||||
}
|
||||
if (facility === "box_culvert") {
|
||||
// 본체 규격은 저장 안 함 — 폭 · 높이로 되찾음 · 옛 설치 「있음」 = 날개벽
|
||||
boxForm.write(BOX_SPEC, boxOptionsIn(options), names, groups);
|
||||
|
||||
@@ -26,10 +26,8 @@ export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||
"pipe_diameter_mm",
|
||||
"inlet_type",
|
||||
"inlet_structure",
|
||||
"inlet_basin_length_m",
|
||||
// 집수정 길이 · 전 · 후는 폼에서 빠짐(54-1) — 옛 값 그대로 둠
|
||||
"inlet_basin_height_m",
|
||||
"inlet_basin_before_m",
|
||||
"inlet_basin_after_m",
|
||||
...revetKeys("inlet"),
|
||||
"outlet_type",
|
||||
...revetKeys("outlet"),
|
||||
|
||||
@@ -246,7 +246,8 @@ export function paintStale(
|
||||
? staleValues(values, known).filter(([key]) => !own.prefixes.some((p) => key.startsWith(p)))
|
||||
: [];
|
||||
box.replaceChildren();
|
||||
box.hidden = !stale.length;
|
||||
// 상자는 모든 종류에서 숨김(54-1) — 값은 파일에 그대로
|
||||
box.hidden = true;
|
||||
if (!stale.length) return;
|
||||
const legend = document.createElement("legend");
|
||||
legend.textContent = "지난 정의 값";
|
||||
|
||||
@@ -34,7 +34,7 @@ export function buildSummaryRow(): SummaryRow {
|
||||
const pick = document.createElement("button");
|
||||
pick.type = "button";
|
||||
pick.className = "b05-route__irregular-btn";
|
||||
pick.textContent = "고르기";
|
||||
pick.textContent = "선택";
|
||||
pick.dataset.b05Summary = "pick";
|
||||
const clear = document.createElement("button");
|
||||
clear.type = "button";
|
||||
|
||||
@@ -147,7 +147,12 @@ function relocateSideRows(panelRoot: HTMLElement): void {
|
||||
if (slotUsable(ownerSlot)) moveRows(panelRoot, SIDE_ROWS, ownerSlot);
|
||||
} else {
|
||||
if (!slotUsable(target)) return;
|
||||
moveRows(panelRoot, SIDE_ROWS, target);
|
||||
// 배관 유출 칸은 읽기 줄(관 길이 · 좌우 · 상하 안내)을 안 받음(54-1)
|
||||
const rows =
|
||||
target === sideSlots.outlet
|
||||
? SIDE_ROWS.replace(".b06-structure-panel__struct--readout, ", "")
|
||||
: SIDE_ROWS;
|
||||
moveRows(panelRoot, rows, target);
|
||||
}
|
||||
// 고른 구조물이 어느 쪽인지 그룹 테두리로 알린다(2026-08-29 사용자 지시 3).
|
||||
const group = target.closest<HTMLElement>("fieldset");
|
||||
|
||||
@@ -61,6 +61,8 @@ export interface StationControlDeps {
|
||||
projectId: () => string | null;
|
||||
/** 구간값 저장 실패 알림 — 화면 토스트는 페이지가 띄운다. */
|
||||
onSaveError?: (message: string) => void;
|
||||
/** 다단 단 수가 바뀜 — 좌측 배관 흙막이 컨테이너 수를 맞춤(54-1). */
|
||||
onExtraCount?: () => void;
|
||||
}
|
||||
|
||||
/** 반폭·기슭막이·유입 구조물 제어 묶음. `load`는 경로가 바뀔 때 세션 값을 다시 읽는다. */
|
||||
@@ -366,6 +368,7 @@ export function createStationControls(deps: StationControlDeps): StationControls
|
||||
},
|
||||
});
|
||||
refreshWithLinked(chainageM);
|
||||
deps.onExtraCount?.();
|
||||
},
|
||||
syncCount: (chainageM, built, side = "outlet") => {
|
||||
const key = extraKey(chainageM, side);
|
||||
@@ -381,6 +384,14 @@ export function createStationControls(deps: StationControlDeps): StationControls
|
||||
persistRevetShifts();
|
||||
persistExtraSpans();
|
||||
persistExtraCounts();
|
||||
// 캐시 patch 도 세운 만큼으로 — 세션 키를 지우면 `countFor` 가 옛 patch 수로 돌아감(54-1)
|
||||
patchCachedDesign(chainageM, {
|
||||
extra_wall_counts: {
|
||||
outlet: extraCounts.get(extraKey(chainageM, "outlet")) ?? 0,
|
||||
basin: extraCounts.get(extraKey(chainageM, "basin")) ?? 0,
|
||||
},
|
||||
});
|
||||
deps.onExtraCount?.();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -137,6 +137,8 @@ export interface B06StructuresPanel {
|
||||
facility: {
|
||||
setInletStructure: (value: "auto" | "revet" | "I" | "L" | "U") => void;
|
||||
onInletStructureChange: (handler: (value: "auto" | "revet" | "I" | "L" | "U") => void) => void;
|
||||
/** 횡단 단 수가 바뀜 — 배관 흙막이 컨테이너 수를 맞춤(54-1). */
|
||||
recount: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -463,6 +465,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
facility: {
|
||||
setInletStructure: (value) => section.facility.setInletStructure(value),
|
||||
onInletStructureChange: (handler) => section.facility.onInletStructureChange(handler),
|
||||
recount: () => section.facility.recount(),
|
||||
},
|
||||
overrideOptions: (chainageM, patch) => {
|
||||
const hit = pipeFacilities.find(
|
||||
|
||||
@@ -19,7 +19,7 @@ from common_util.common_util_summary_item import GROUP_JOIN
|
||||
#: 종류 → 선택 컨테이너 (머리, 잇기, 제목)
|
||||
SPEC_CONTAINERS: dict[str, tuple[tuple[str, str, str], ...]] = {
|
||||
"pipe": (
|
||||
("soil_add", "gsoiladd", "흙막이 추가"),
|
||||
("soil_add", "gsoiladd", "흙막이"),
|
||||
("chute_add", "gchuteadd", "도수로/돌붙임"),
|
||||
),
|
||||
"ford_pavement": (("ford_chute", "gfordpavechute", "도수로/돌붙임"),),
|
||||
|
||||
@@ -39,7 +39,7 @@ SOIL = {
|
||||
def test_컨테이너마다_한_건_잇기와_그_값():
|
||||
item = {"type_id": "pipe", "options": {"pipe_diameter_mm": 1000, **SOIL}, "복사본": "pipe@40.0"}
|
||||
rows = container_rows(item, {"gsoiladd1": RV, "gchuteadd1": MS})
|
||||
assert [r["그룹"] for r in rows] == ["흙막이 추가 1", "흙막이 추가 2", "도수로/돌붙임 1"]
|
||||
assert [r["그룹"] for r in rows] == ["흙막이 1", "흙막이 2", "도수로/돌붙임 1"]
|
||||
assert [r["복사본"] for r in rows] == [
|
||||
"pipe@40.0__gsoiladd1",
|
||||
"pipe@40.0__gsoiladd2",
|
||||
|
||||
@@ -36,7 +36,7 @@ def test_컨테이너_다섯_필수_셋_선택_둘(out):
|
||||
["pipe", "관", True],
|
||||
["inlet", "관 보호공 (유입)", True],
|
||||
["outlet", "관 보호공 (유출)", True],
|
||||
["soil_add", "흙막이 추가", False],
|
||||
["soil_add", "흙막이", False],
|
||||
["chute_add", "도수로/돌붙임", False],
|
||||
]
|
||||
|
||||
@@ -58,7 +58,8 @@ def test_형태_선택지는_구조를_따름(out):
|
||||
def test_옛_키로_읽고_같은_키로_씀_옛_칸은_안_씀(out):
|
||||
back = out["back"]
|
||||
assert back["pipe_diameter_mm"] == 1000
|
||||
assert back["inlet_structure"] == "L" and back["inlet_basin_length_m"] == 2
|
||||
# 집수정 = 길이 · 전 · 후 칸 없음(54-1) — 폼이 안 적음(병합이 옛 값을 지킴)
|
||||
assert back["inlet_structure"] == "L" and "inlet_basin_length_m" not in back
|
||||
assert back["outlet_revet_height_m"] == 2.5
|
||||
# 표 01 에 없는 옛 칸은 폼이 안 적음(병합이 이어 붙임 = 지난 정의 값)
|
||||
assert "pipe_kind" not in back and "wing_wall_type" not in back
|
||||
|
||||
@@ -94,7 +94,7 @@ def merged(tmp_path_factory) -> dict:
|
||||
def test_폼이_모르는_칸은_남고_폼이_아는_칸은_폼대로(merged) -> None:
|
||||
options = merged["out"][0]["options"]
|
||||
assert options["revet_foundation"] == "기초유" # 집계표 값이 살아남음
|
||||
assert "inlet_basin_before_m" not in options # 폼 칸(24-4a) — 집수정을 걷으면 함께 지움
|
||||
assert options["inlet_basin_before_m"] == 1.5 # 집수정 전 — 폼에서 빠짐(54-1) · 옛 값 남음
|
||||
assert options["pipe_diameter_mm"] == 800 # 폼 값이 이김
|
||||
# 표 01 명세(PLAN 52-1)가 뺀 옛 칸 = 폼이 모르는 칸 → 남음(지난 정의 값)
|
||||
assert options["inlet_basin_form"] == "□형(기본형)"
|
||||
|
||||
@@ -80,7 +80,7 @@ def test_집계_항목_줄(out):
|
||||
True,
|
||||
None,
|
||||
)
|
||||
assert (empty["label"], empty["pick"], empty["clear"]) == ("집계 항목", "고르기", "해제")
|
||||
assert (empty["label"], empty["pick"], empty["clear"]) == ("집계 항목", "선택", "해제")
|
||||
row = out["rowSet"]
|
||||
assert row["assigned"] is True and row["clearDisabled"] is False
|
||||
assert row["name"] == "돌기슭막이 메쌓기 1.2m" and A_KEY in row["title"]
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
* 시작 화면: B05 종단설계(#/b05-profile) · 검증 프로젝트 · 목록에 「배관 1」 「세월교 1」 · 로그인 상태
|
||||
* 부르는 법: 단계마다 한 번씩 — 앞에 `window.__m5213 = "단계";` 를 붙여
|
||||
* cache → 저장 안 하고 B05 → B06 → B05 오가기(캐시 값 그대로) · 종류 바꾸기(배관 → BOX → 배관)
|
||||
* pipe → 배관 길이 · 전 · 후 비움 + 흙막이 추가 셋 + 도수로/돌붙임 하나 → [저장]
|
||||
* (orca reload) pipe_v → 다시 열기 · 흙막이 수 3 · 비운 칸 그대로
|
||||
* pipe → 배관 길이 · 전 · 후 비움 + 도수로/돌붙임 하나 → [저장](흙막이 = 유출 단 수 · [추가] 없음 · 54-1)
|
||||
* (orca reload) pipe_v → 다시 열기 · 흙막이 수 = 저장 전 그대로 · 비운 칸 그대로
|
||||
* bf → 258.12 를 BOX암거로(PUT) · 가로 비움 · 세월교 1 월류 폭 비움 → [저장]
|
||||
* (orca reload) bf_v → 다시 열기 · 오류 없음
|
||||
* restore → 원래 관 지점으로 되돌림(PUT) · 초안 비움
|
||||
* 쓰는 것: 검증 프로젝트 pipe_points.json(되돌림 단계가 원래 값으로 다시 씀)
|
||||
* 기대 수치: errors 0 · cache.same true · kind.leak 0 · soilCount 3 · inletBlank 빈 칸 셋 · restored true
|
||||
* 기대 수치: errors 0 · cache.same true · kind.leak 0 · soilCount pipe = pipe_v · soilAdd 0 · inletBlank 빈 칸 셋 · restored true
|
||||
* (배관 도수로/돌붙임 [추가] 는 하나 뒤에도 보임 — 「하나만」은 물넘이 포장 · 기타만 · 52-3 · 52-10)
|
||||
*/
|
||||
(async () => {
|
||||
@@ -66,7 +66,8 @@
|
||||
await w(500);
|
||||
return until(() => !document.querySelector(".ui-overlay.is-active"), 60000);
|
||||
};
|
||||
const items = () => [...spec().querySelectorAll("[data-spec-item]")].map((n) => n.dataset.specItem);
|
||||
const items = () =>
|
||||
[...spec().querySelectorAll("[data-spec-item]")].map((n) => n.dataset.specItem);
|
||||
const point = async (lo, hi) =>
|
||||
(await fetchApi.fetchDetailPipePoints(projectId)).pipe_points.find(
|
||||
(p) => p.chainage_m > lo && p.chainage_m < hi,
|
||||
@@ -89,21 +90,17 @@
|
||||
}
|
||||
|
||||
if (MODE === "pipe") {
|
||||
sessionStorage.setItem("__5213_original", JSON.stringify((await fetchApi.fetchDetailPipePoints(projectId)).pipe_points));
|
||||
sessionStorage.setItem(
|
||||
"__5213_original",
|
||||
JSON.stringify((await fetchApi.fetchDetailPipePoints(projectId)).pipe_points),
|
||||
);
|
||||
await pick(/배관 1$/);
|
||||
await set("inlet", "structure", "기슭막이");
|
||||
await set("inlet", "form", "돌망태");
|
||||
await set("inlet", "length", "");
|
||||
await set("inlet", "before", "");
|
||||
await set("inlet", "after", "");
|
||||
for (let i = 0; i < 3; i++) {
|
||||
spec().querySelector('[data-spec-add="soil_add"]').click();
|
||||
await w(200);
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await set("soil_add", "structure", "기슭막이", i);
|
||||
await set("soil_add", "form", "콘크리트", i);
|
||||
}
|
||||
out.soilAdd = spec().querySelectorAll('[data-spec-add="soil_add"]').length;
|
||||
spec().querySelector('[data-spec-add="chute_add"]').click();
|
||||
await w(200);
|
||||
await set("chute_add", "structure", "돌붙임");
|
||||
@@ -180,7 +177,8 @@
|
||||
clearDrafts();
|
||||
sessionStorage.removeItem("__5213_original");
|
||||
const now = (await fetchApi.fetchDetailPipePoints(projectId)).pipe_points;
|
||||
out.restored = JSON.stringify(now.map((p) => [p.chainage_m, p.facility ?? null, p.options ?? null])) ===
|
||||
out.restored =
|
||||
JSON.stringify(now.map((p) => [p.chainage_m, p.facility ?? null, p.options ?? null])) ===
|
||||
JSON.stringify(original.map((p) => [p.chainage_m, p.facility ?? null, p.options ?? null]));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* orca eval --expression "window.__m521='verify'; $(cat 이 파일)" → 다시 연 값 · B06 같은 그리기 · 관 길이
|
||||
* orca eval --expression "window.__m521='restore'; $(cat 이 파일)" → 원래 관 지점으로 되돌림(PUT) · 초안 비움
|
||||
* 쓰는 것: 검증 프로젝트 pipe_points.json(되돌림 단계가 원래 값으로 다시 씀)
|
||||
* 기대 수치(edit): kinds 5 · required 3 늘 보임 · formBasin I형/L형/ㄷ형 · formSoil 6 · split 길이 7 ·
|
||||
* soilAdd 둘 → 하나 빼면 하나(콘크리트) · chute 하나(돌붙임) · 잇기 유입 지정 · 유출 지정 뒤 풂
|
||||
* 기대 수치(edit): kinds 4(흙막이 = 유출 단 수 · [추가] 없음 · 54-1) · required 3 늘 보임 · formBasin I형/L형/ㄷ형 ·
|
||||
* formSoil 6 · split 길이 7 · soilAddButtons 0 · chute 하나(돌붙임) · 잇기 유입 지정 · 유출 지정 뒤 풂
|
||||
* 기대 수치(verify): 저장 값 그대로 · 같은 그리기(B05 = B06) · 관 길이 수(B06)
|
||||
* 52-1b: edit 가 흙막이 추가 · 도수로/돌붙임 컨테이너도 이음 → verify 집계표 줄 = 처음 + 3
|
||||
* (유입 잇기 1 · 컨테이너마다 1)
|
||||
@@ -120,33 +120,16 @@
|
||||
out.splitAfter = ctl("inlet", "after").value;
|
||||
await set("inlet", "after", "4");
|
||||
out.splitLength = ctl("inlet", "length").value;
|
||||
// 선택 컨테이너 — 흙막이 추가 둘 → 첫째 뺌 · 도수로/돌붙임 하나
|
||||
spec().querySelector('[data-spec-add="soil_add"]').click();
|
||||
await w(200);
|
||||
spec().querySelector('[data-spec-add="soil_add"]').click();
|
||||
await w(200);
|
||||
await set("soil_add", "structure", "기슭막이", 0);
|
||||
await set("soil_add", "form", "바자", 0);
|
||||
await set("soil_add", "structure", "기슭막이", 1);
|
||||
await set("soil_add", "form", "콘크리트", 1);
|
||||
out.soilAddTwo = spec().querySelectorAll('[data-spec-container="soil_add"]').length;
|
||||
out.soilFormsSoilAdd = labels("soil_add", "form").length;
|
||||
box("soil_add", 0).querySelector('[data-spec-remove="soil_add"]').click();
|
||||
await w(300);
|
||||
out.soilAddAfterRemove = [...spec().querySelectorAll('[data-spec-container="soil_add"]')].map(
|
||||
(b, i) => [
|
||||
b.querySelector("legend").firstChild.textContent,
|
||||
ctl("soil_add", "form", i).value,
|
||||
],
|
||||
);
|
||||
// 흙막이 = 유출 「추가 기슭막이(단)」 수만큼(54-1) — [추가] · [빼기] 없음 · 도수로/돌붙임 하나
|
||||
out.soilAddButtons = spec().querySelectorAll('[data-spec-add="soil_add"]').length;
|
||||
out.soilRemoveButtons = spec().querySelectorAll('[data-spec-remove="soil_add"]').length;
|
||||
spec().querySelector('[data-spec-add="chute_add"]').click();
|
||||
await w(200);
|
||||
await set("chute_add", "structure", "돌붙임");
|
||||
out.chuteChoices = labels("chute_add", "structure");
|
||||
// 52-1b — 컨테이너마다 구조물 조합 잇기(집계표 한 줄씩)
|
||||
await pickLink("soil_add");
|
||||
await pickLink("chute_add");
|
||||
out.linkContainers = [linkName("soil_add"), linkName("chute_add")];
|
||||
out.linkContainers = [linkName("chute_add")];
|
||||
// 구조물 조합 잇기 — 유입 지정 · 유출 지정 뒤 풂
|
||||
await pickLink("inlet");
|
||||
out.linkInlet = linkName("inlet");
|
||||
@@ -179,7 +162,7 @@
|
||||
linkOutlet: linkName("outlet"),
|
||||
};
|
||||
const b05 = shape();
|
||||
out.b05Length = ctl("pipe", "length").value;
|
||||
out.b05Length = box("pipe").querySelector("output")?.textContent; // 보여 주는 칸(54-1)
|
||||
location.hash = "#/b06-section";
|
||||
await until(() => location.hash.includes("b06"), 5000);
|
||||
await w(6000);
|
||||
@@ -187,7 +170,8 @@
|
||||
const b06 = shape();
|
||||
out.sameDrawing = JSON.stringify(b05) === JSON.stringify(b06);
|
||||
if (!out.sameDrawing) out.diff = { b05, b06 };
|
||||
out.b06Length = ctl("pipe", "length").value;
|
||||
out.b06Length = box("pipe").querySelector("output")?.textContent;
|
||||
out.lengthInputs = box("pipe").querySelectorAll('[data-spec-item="pipe.length"] input').length;
|
||||
out.baseRows = Number(sessionStorage.getItem("__521_baseRows"));
|
||||
out.rows = await filledRows();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/* 54-1 — B05 · B06 좌측 패널 배관: 「선택」 글 · 지난 정의 값 숨김 · 관 길이 보여 주는 칸(정수) ·
|
||||
* 유입 집수정 = 길이 · 전 · 후 숨김 · 유출 읽기 줄(관길이 · 좌우 자동 자리) 없음 · [흙막이 추가] 없음 ·
|
||||
* 흙막이 컨테이너 수 = 유출 「추가 기슭막이(단)」 N
|
||||
* 시작 화면: B05(#/b05-profile) 또는 B06(#/b06-section) · 검증 프로젝트(sub5_검증) · 목록에 「배관 1」(4+5)
|
||||
* 부르는 법: orca eval --expression "window.__m541='단계'; $(cat 이 파일)"
|
||||
* look → 지금 화면 배관 폼 수치(아무것도 안 바꿈)
|
||||
* basin → 유입 구조 집수정 · 흙막이 번갈아 보이는 칸(되돌림 · 저장 안 함)
|
||||
* tier+ / tier- → B06 유출 벽을 골라 「추가 기슭막이(단)」 + / - 한 번 · 흙막이 컨테이너 수
|
||||
* 기대: pick 글 전부 「선택」 · 고르기 0 · stale 보임 0 · 관 길이 input 0 · 값 정수 ·
|
||||
* 집수정 칸 = structure · form · height · 흙막이 칸 = + length · before · after ·
|
||||
* soil_add 추가 단추 0 · 유출 읽기 줄 0 · soil_add 수 = 단 수
|
||||
*/
|
||||
(async () => {
|
||||
const MODE = window.__m541 || "look";
|
||||
// 고를 관(목록 이름) — 기본 「배관 1」 · 단이 서는 지형이 아니면 다른 관(window.__m541pipe)
|
||||
const PIPE = window.__m541pipe || "배관 1";
|
||||
let station = "";
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const until = async (fn, ms = 20000) => {
|
||||
for (let t = 0; t < ms; t += 200) {
|
||||
if (fn()) return true;
|
||||
await w(200);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const spec = () =>
|
||||
[...document.querySelectorAll(".b05-structure__spec")].find((n) => n.offsetParent !== null);
|
||||
const box = (c, i = 0) => spec()?.querySelectorAll(`[data-spec-container="${c}"]`)[i];
|
||||
const items = (c) =>
|
||||
[...(box(c)?.querySelectorAll("[data-spec-item]") ?? [])].map(
|
||||
(n) => n.dataset.specItem.split(".")[1],
|
||||
);
|
||||
const set = async (c, item, v) => {
|
||||
const el = box(c).querySelector(`[data-spec-item="${c}.${item}"]`).querySelector("select");
|
||||
el.value = v;
|
||||
el.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await w(300);
|
||||
};
|
||||
const pickPipe = async () => {
|
||||
const chosen = document.querySelector(".b05-route__irregular-list li.is-selected");
|
||||
if (spec() && chosen?.textContent.trim().endsWith(PIPE)) return;
|
||||
await until(() => document.querySelectorAll(".b05-route__irregular-list li").length > 0);
|
||||
const li = [...document.querySelectorAll(".b05-route__irregular-list li")].find((l) =>
|
||||
l.textContent.trim().endsWith(PIPE),
|
||||
);
|
||||
(li.querySelector("button") ?? li).click();
|
||||
await until(() => spec(), 5000);
|
||||
await w(500);
|
||||
};
|
||||
const visible = (n) => n.offsetParent !== null && !n.closest("[hidden]");
|
||||
const tierRow = () =>
|
||||
box("outlet")?.querySelector(".b06-structure-panel__struct--tier:not(.is-hidden)");
|
||||
const look = () => {
|
||||
const lengthField = box("pipe")?.querySelector('[data-spec-item="pipe.length"]');
|
||||
const picks = [...document.querySelectorAll('[data-b05-summary="pick"]')].filter(visible);
|
||||
return {
|
||||
view: location.hash,
|
||||
pickTexts: [...new Set(picks.map((b) => b.textContent))],
|
||||
pickCount: picks.length,
|
||||
gorugi: [...document.querySelectorAll("button")].filter(
|
||||
(b) => visible(b) && b.textContent.trim() === "고르기",
|
||||
).length,
|
||||
staleShown: [...document.querySelectorAll(".b05-structure__stale")].filter(visible).length,
|
||||
staleLegend: [...document.querySelectorAll("legend")].filter(
|
||||
(l) => visible(l) && l.textContent.includes("지난 정의 값"),
|
||||
).length,
|
||||
lengthInputs: lengthField?.querySelectorAll("input").length,
|
||||
lengthText: lengthField?.querySelector("output")?.textContent,
|
||||
inletStructure: box("inlet")?.querySelector('[data-spec-item="inlet.structure"] select')
|
||||
?.value,
|
||||
inletItems: items("inlet"),
|
||||
soilAddButton: spec()?.querySelectorAll('[data-spec-add="soil_add"]').length,
|
||||
soilRemove: spec()?.querySelectorAll('[data-spec-remove="soil_add"]').length,
|
||||
chuteAddButton: spec()?.querySelectorAll('[data-spec-add="chute_add"]').length,
|
||||
soilBoxes: [...(spec()?.querySelectorAll('[data-spec-container="soil_add"]') ?? [])].map(
|
||||
(b) => ({
|
||||
legend: b.querySelector("legend").firstChild.textContent,
|
||||
items: [...b.querySelectorAll("[data-spec-item]")].map(
|
||||
(n) => n.dataset.specItem.split(".")[1],
|
||||
),
|
||||
link: !!b.querySelector('[data-b05-summary="pick"]'),
|
||||
}),
|
||||
),
|
||||
outletReadout: box("outlet")?.querySelectorAll(".b06-structure-panel__struct--readout")
|
||||
.length,
|
||||
outletText: box("outlet")?.textContent.includes("자동 자리"),
|
||||
tier: tierRow()?.querySelector(".b06-structure-panel__hval")?.textContent ?? null,
|
||||
extraGroupShown: [...document.querySelectorAll("legend")].filter(
|
||||
(l) => visible(l) && l.textContent.startsWith("추가 기슭막이"),
|
||||
).length,
|
||||
};
|
||||
};
|
||||
await pickPipe();
|
||||
station = [...document.querySelectorAll(".b05-route__irregular-list li")]
|
||||
.find((l) => l.textContent.trim().endsWith(PIPE))
|
||||
.textContent.trim()
|
||||
.split(PIPE)[0];
|
||||
const out = { mode: MODE, pipe: PIPE, station };
|
||||
if (MODE === "look") Object.assign(out, look());
|
||||
if (MODE === "basin") {
|
||||
const was = box("inlet").querySelector('[data-spec-item="inlet.structure"] select').value;
|
||||
await set("inlet", "structure", "집수정");
|
||||
out.basinItems = items("inlet");
|
||||
await set("inlet", "structure", "기슭막이");
|
||||
out.soilItems = items("inlet");
|
||||
await set("inlet", "structure", was);
|
||||
out.back = items("inlet");
|
||||
out.was = was;
|
||||
}
|
||||
if (MODE === "tier+" || MODE === "tier-") {
|
||||
// 유출 벽 고르기(이미 골라져 단 수 줄이 서 있으면 그대로)
|
||||
if (!tierRow()) {
|
||||
// 판정면(관 위로 올린 것) — 4+5 카드의 유출 벽
|
||||
const wall = [...document.querySelectorAll(".b06-chart__culvert-revet-hit")].find(
|
||||
(e) =>
|
||||
e.closest(".b06-cross-card")?.textContent.startsWith(station) &&
|
||||
e.querySelector("title")?.textContent.startsWith("유출"),
|
||||
);
|
||||
out.wallFound = !!wall;
|
||||
wall?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await until(() => tierRow(), 5000);
|
||||
await w(500);
|
||||
}
|
||||
out.before = tierRow()?.querySelector(".b06-structure-panel__hval")?.textContent;
|
||||
const btns = [...(tierRow()?.querySelectorAll("button") ?? [])];
|
||||
(MODE === "tier+"
|
||||
? btns.find((b) => b.textContent === "+")
|
||||
: btns.find((b) => b.textContent === "-")
|
||||
)?.click();
|
||||
await w(1500);
|
||||
Object.assign(out, look());
|
||||
}
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user