merge: sub_laptop_2 52-8 — 충돌(Panel_Options 종류 단위 잇기 가림) = 두 조건 합침(명세 · 정의 그룹 · 종단배수)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV
This commit is contained in:
2026-10-05 23:48:08 +09:00
co-authored by Claude Opus 5.5
6 changed files with 126 additions and 9 deletions
@@ -17,6 +17,7 @@ import {
} from "./B05_Profile_UI_IrregularStations"; } from "./B05_Profile_UI_IrregularStations";
import { structureAnchorM, type StructureInstance } from "./B05_Profile_Api_Structures"; import { structureAnchorM, type StructureInstance } from "./B05_Profile_Api_Structures";
import { GROUP_LABELS } from "./B05_Profile_UI_Structures_Panel"; import { GROUP_LABELS } from "./B05_Profile_UI_Structures_Panel";
import { HIDDEN_PICK_GROUPS } from "./B05_Profile_UI_Structures_Panel_Types";
/** 우클릭 메뉴용 최소 타입 정보 — 사이드 「구조물 배치」 종류 목록과 같은 원천. */ /** 우클릭 메뉴용 최소 타입 정보 — 사이드 「구조물 배치」 종류 목록과 같은 원천. */
export interface StructureMenuType { export interface StructureMenuType {
@@ -32,7 +33,9 @@ export function structureGroupMenuItems(
types: ReadonlyArray<StructureMenuType>, types: ReadonlyArray<StructureMenuType>,
onPick: (typeId: string) => void, onPick: (typeId: string) => void,
): MapContextMenuItem[] { ): MapContextMenuItem[] {
const groups = [...new Set(types.map((type) => type.group))]; const groups = [...new Set(types.map((type) => type.group))].filter(
(group) => !HIDDEN_PICK_GROUPS.has(group),
);
return groups.map((group) => ({ return groups.map((group) => ({
label: GROUP_LABELS[group] ?? group, label: GROUP_LABELS[group] ?? group,
children: types children: types
@@ -78,7 +78,9 @@ export function createFacilityDefinition(root: HTMLElement): FacilityDefinition
root root
.querySelectorAll(".is-rule-hidden") .querySelectorAll(".is-rule-hidden")
.forEach((node) => node.classList.remove("is-rule-hidden")); .forEach((node) => node.classList.remove("is-rule-hidden"));
root.querySelectorAll<HTMLOptionElement>("option[hidden]").forEach((o) => (o.hidden = false)); root
.querySelectorAll<HTMLOptionElement>("option[hidden]:not([data-pick-hidden])")
.forEach((o) => (o.hidden = false));
paint(); paint();
}, },
}; };
@@ -24,7 +24,7 @@ import type { PipeFacility } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
import { type FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility"; import { type FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility";
import { field, optionDefaults } from "./B05_Profile_UI_Structures_Fields"; import { field, optionDefaults } from "./B05_Profile_UI_Structures_Fields";
import { import {
hasSpecContainers, hidesTypeSummary,
paintStale, paintStale,
renderOptionFields as renderOptionFieldsInto, renderOptionFields as renderOptionFieldsInto,
} from "./B05_Profile_UI_Structures_Panel_Options"; } from "./B05_Profile_UI_Structures_Panel_Options";
@@ -54,6 +54,7 @@ export type { PipeFacilityItem, StructuresSection } from "./B05_Profile_UI_Struc
import { import {
DEFAULT_INTERVAL_LENGTH_M, DEFAULT_INTERVAL_LENGTH_M,
GROUP_LABELS, GROUP_LABELS,
groupOption,
hasComputedRange, hasComputedRange,
readBeforeOption, readBeforeOption,
readLengthOption, readLengthOption,
@@ -227,7 +228,7 @@ export function createStructuresSection(
!!type && !managed && type.placement === "interval" && !hasComputedRange(type); !!type && !managed && type.placement === "interval" && !hasComputedRange(type);
positionRow.hidden = !type; positionRow.hidden = !type;
positionDivider.hidden = !type; positionDivider.hidden = !type;
summary.root.hidden = !type || hasSpecContainers(type); // 컨테이너 종류는 컨테이너마다 잇기(52-1) summary.root.hidden = !type || hidesTypeSummary(type); // 컨테이너 종류는 컨테이너마다 잇기(52-1)
startFields.wrap.hidden = !isInterval; startFields.wrap.hidden = !isInterval;
endFields.wrap.hidden = !isInterval; endFields.wrap.hidden = !isInterval;
primary.disabled = !type; primary.disabled = !type;
@@ -544,7 +545,7 @@ export function createStructuresSection(
// 첫 항목은 빈칸 — 진입·리셋 상태에서 군·종류를 강제로 고르게 두지 않는다. 이름은 정의(48) 먼저. // 첫 항목은 빈칸 — 진입·리셋 상태에서 군·종류를 강제로 고르게 두지 않는다. 이름은 정의(48) 먼저.
groupSelect.replaceChildren( groupSelect.replaceChildren(
new Option("— 선택 —", ""), new Option("— 선택 —", ""),
...[...groups].map(([g, name]) => new Option(name ?? GROUP_LABELS[g] ?? g, g)), ...[...groups].map(([g, name]) => groupOption(g, name ?? GROUP_LABELS[g] ?? g)),
); );
groupSelect.value = ""; groupSelect.value = "";
syncTypeOptions(); syncTypeOptions();
@@ -23,10 +23,10 @@ import { FORM_OPTION_KEYS } from "./B05_Profile_UI_Drainage_Facility_Merge";
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe"; import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
import { specKeys } from "../A00_Common/design/design_structure_spec"; import { specKeys } from "../A00_Common/design/design_structure_spec";
/** 구조물 명세 컨테이너 · 정의 그룹 상자(그룹마다 잇기 줄)로 그리는 종류인지 — 그때 종류 단위 /** 종류 단위 「구조물 조합」 줄을 가릴 종류인지 — 구조물 명세 컨테이너 · 정의 그룹 상자(그룹마다 잇기 줄)로
* 「구조물 조합」 줄은 가림(PLAN 52-1 · 52-6b). */ * 그리는 종류(PLAN 52-1 · 52-6b) · 종단배수(B군 · 조합 잇기 없음 · 표 07). */
export const hasSpecContainers = (type: StructureType): boolean => export const hidesTypeSummary = (type: StructureType): boolean =>
!!STRUCTURE_SPECS[type.type_id]?.containers.length || !!type.groups?.length; !!STRUCTURE_SPECS[type.type_id]?.containers.length || !!type.groups?.length || type.group === "B";
/** 패널이 쥔 값 중 옵션 그리기에 필요한 것들. 이름은 분리 전 지역변수와 같다. */ /** 패널이 쥔 값 중 옵션 그리기에 필요한 것들. 이름은 분리 전 지역변수와 같다. */
export interface OptionRenderContext { export interface OptionRenderContext {
@@ -34,6 +34,18 @@ export const GROUP_LABELS: Record<string, string> = {
호환: "기타", 호환: "기타",
}; };
/** 새로 놓을 때 고르기 목록(군 드롭다운 · 우클릭 메뉴)에서 뺄 군 — 종단배수·계류사방(표 07).
* 옛 값 · 도면 · 집계는 그대로이고 데이터도 지우지 않는다. */
export const HIDDEN_PICK_GROUPS: ReadonlySet<string> = new Set(["B", "D"]);
/** 군 드롭다운 항목 — 뺄 군은 가림(옛 항목을 열 때는 값으로 그대로 고를 수 있음). */
export function groupOption(group: string, name: string): HTMLOptionElement {
const option = new Option(name, group);
option.hidden = HIDDEN_PICK_GROUPS.has(group);
if (option.hidden) option.dataset.pickHidden = ""; // 정의 폼의 「가림 풀기」가 건드리지 않게
return option;
}
/** 구간형 신규 추가 시 기본 구간 길이(m). 사용자가 종점을 바로 고칠 수 있게 짧게 잡는다. */ /** 구간형 신규 추가 시 기본 구간 길이(m). 사용자가 종점을 바로 고칠 수 있게 짧게 잡는다. */
export const DEFAULT_INTERVAL_LENGTH_M = 15; export const DEFAULT_INTERVAL_LENGTH_M = 15;
@@ -0,0 +1,99 @@
/* 52-8 — B05 · B06 좌측 「구조물 배치」 종단배수(B) · 계류사방(D) 고르기 목록에서 숨김
* 시작 화면: B05 종단설계(#/b05-profile) · 검증 프로젝트(sub2_검증)
* 부르는 법: 앞에 `window.__m528 = "단계";` 를 붙여
* orca eval --expression "window.__m528='seed'; $(cat 이 파일)" → 옛 값 둘(골막이 D · 측구 B)을 세션 초안에만 둠
* (orca reload)
* orca eval --expression "window.__m528='verify'; $(cat 이 파일)" → 군 목록 · 우클릭 메뉴 · 옛 값 열기 (B05)
* (#/b06-section 으로 가서 reload 후 verify 한 번 더)
* orca eval --expression "window.__m528='clear'; $(cat 이 파일)" → 세션 초안 비움(서버 저장 안 함)
* 기대 수치(verify): visibleGroups 에 B · D 없음(0) · hiddenGroups 가림만 · menuHasBD 0 ·
* 옛 값 목록 둘 그대로 · 옛 값을 열면 군 = D / B 로 열림(editGroup) · 잇기 칸 pickLinks 골막이(D) 1 · 측구(B) 0
*/
(async () => {
const MODE = window.__m528 || "verify";
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const projectId = localStorage.getItem("frd_current_project_id");
const modUrl = (part) =>
performance
.getEntriesByType("resource")
.map((e) => e.name)
.filter((n) => n.includes(part))
.pop();
const api = await import(modUrl("/B05_Profile/B05_Profile_Api_Structures.ts"));
const out = { mode: MODE, hash: location.hash };
if (MODE === "seed") {
const base = {
placement_source: "manual",
status: "draft",
revision: 0,
geometry: null,
memo: "",
options: {},
};
api.writePendingStructures(projectId, [
{
...base,
structure_id: "m528d",
type_id: "erosion_check",
placement: "point",
chainage_m: 40,
},
{
...base,
structure_id: "m528b",
type_id: "ditch_side",
placement: "interval",
start_m: 60,
end_m: 80,
},
]);
out.seeded = api.readPendingStructures(projectId).length;
return JSON.stringify(out);
}
if (MODE === "clear") {
api.writePendingStructures(projectId, null);
out.cleared = api.readPendingStructures(projectId) === null;
return JSON.stringify(out);
}
const groupSel = [...document.querySelectorAll("select")].find((s) =>
[...s.options].some((o) => o.text.includes("A 횡단배수")),
);
const isBD = (o) => /^(B|D) /.test(o.text);
out.visibleGroups = [...groupSel.options].filter((o) => !o.hidden && o.value).map((o) => o.text);
out.visibleBD = [...groupSel.options].filter((o) => !o.hidden && isBD(o)).length;
out.hiddenGroups = [...groupSel.options].filter((o) => o.hidden).map((o) => o.text);
// 우클릭 메뉴 목록(공용 만들기) — 종류 전부를 줘도 B · D 군이 안 나옴
const menuMod = await import(modUrl("/B05_Profile/B05_Profile_UI_Profile_Structures.ts"));
const types = (await api.fetchStructureTypes(true)).map((t) => ({
type_id: t.type_id,
group: t.group,
name: t.name,
}));
const menu = menuMod.structureGroupMenuItems(types, () => {});
out.menuGroups = menu.map((m) => m.label);
out.menuHasBD = menu.filter((m) => /^(B|D) /.test(m.label)).length;
// 옛 값 — 목록에 그대로 · 열면 그 군으로 열림
const rows = [...document.querySelectorAll(".b05-route__irregular-list li")];
out.rows = rows.map((r) => r.textContent.trim().replace(/\s+/g, " ").slice(0, 40));
const old = rows.find((r) =>
(window.__m528row ? new RegExp(window.__m528row) : /골막이/).test(r.textContent),
);
if (old) {
(old.querySelector("button") ?? old).click();
await w(1500);
const sels = [...document.querySelectorAll("select")].filter((s) =>
[...s.options].some((o) => o.text.includes("A 횡단배수")),
);
// 다시 열면 고른 것이 복원돼 있음 — 누르면 풀리므로 풀렸으면 한 번 더
if (!sels[0]?.value) {
(old.querySelector("button") ?? old).click();
await w(1500);
}
out.editGroup = sels[0]?.value;
// 종단배수(B)는 조합 잇기 칸이 없음(0) · 계류사방(D)은 옛 그대로(1)
out.pickLinks = [...document.querySelectorAll('[data-b05-summary="pick"]')].filter(
(n) => n.offsetParent,
).length;
}
return JSON.stringify(out);
})();