feat(499-1 좌측 패널 하이라이트): 닫힌 패널 안 엶 · 카드 폄 + 열 때 고른 줄 · 우클릭 [추가] 포장도 바로 · B06 평면 겹침 기준 main 전체 — 요구 298-4 · 298-6 · 300 E2 · E3 · 304 E1
- onReveal = 사용자 고름이면 「구조물 배치」 카드 폄만 · revealLeft 지움(부르는 열네 길 그대로) - 좌측 패널 열림 알림(workflow_layout onOptionsOpenChange) → 고른 목록 줄 · 강조 상자로 한 번 밂 - addsAtOnce = 등록부 기본값 뒤 필수 빈칸만 봄 · 포장 폼만 갈래 지움(물넘이 걸침 = 확인창 그대로) - design_page.css 횡단 보기 가운데 칸 position: static · 평면 z 4 그대로 - 시험 398_1 뒤집음 · 116 · 126 포장 바로 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,11 @@
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* 횡단 보기 평면 겹침 기준 = main 전체(위 칸 + 종단 판) — 종단 판 크기와 무관 · 넘침 없음(499-1) */
|
||||
.design-page__main[data-view="cross"] > .design-page__center {
|
||||
position: static;
|
||||
}
|
||||
|
||||
/* 새 창으로 뺀 종단 하단 패널(계획서 54-35) — 창 전체를 채움 · 접기 손잡이 · 높이 끌기는 감춤. */
|
||||
.b05-route-profile.is-popout {
|
||||
position: relative;
|
||||
|
||||
@@ -29,7 +29,12 @@ import {
|
||||
} from "../b_workflow_nav";
|
||||
import { currentRoute, navigateTo, type MountedPage } from "../router";
|
||||
import type { DesignView } from "./design_dock";
|
||||
import { confirmAndGo, saveSharedDesign, type DesignPageShared } from "./design_shared";
|
||||
import {
|
||||
confirmAndGo,
|
||||
saveSharedDesign,
|
||||
scrollToPick,
|
||||
type DesignPageShared,
|
||||
} from "./design_shared";
|
||||
import { guardDesignLeave } from "./design_leave";
|
||||
import { usePipeIds } from "./design_pipe_ids";
|
||||
import { createFacilityLane } from "./design_facility_lane";
|
||||
@@ -151,7 +156,6 @@ export function renderDesignPage(root: HTMLElement): MountedPage {
|
||||
structures: null,
|
||||
// 시설 줄 한 벌(462) — 보기가 숨어도 · 아직 안 섰어도 듣고 셈 · 종단도는 서면 받음
|
||||
lane: createFacilityLane(projectId, () => shared.profile),
|
||||
revealLeft: () => layout?.setOptionsOpen(true),
|
||||
ensureView: (view) => (slots.get(view) ?? mountView(view, false)).ready,
|
||||
refreshStages: () => {
|
||||
clearWorkflowStateCache();
|
||||
@@ -186,6 +190,7 @@ export function renderDesignPage(root: HTMLElement): MountedPage {
|
||||
stages: state?.stages,
|
||||
// 종단 · 횡단 · 상세설계 칸은 늘 켬 — B07 로 가는 길이 곧 확정(496-1 · 운영 잠금 흠)
|
||||
currentStage: [2, 3, 4],
|
||||
onOptionsOpenChange: (isOpen) => isOpen && scrollToPick(shared.structures), // 499-1
|
||||
routes: WORKFLOW_STEP_ROUTES,
|
||||
// 종단설계 · 횡단설계 두 칸은 같은 페이지 — 주소만 바꿔 보기만 바꾼다. B07~B09 = 확정 뒤 이동(496-1).
|
||||
onStepClick: (stepIndex) => {
|
||||
|
||||
@@ -117,8 +117,6 @@ export interface DesignPageShared {
|
||||
lastPipes?: Parameters<NonNullable<RouteProfilePanelCallbacks["onPipesChanged"]>>[0];
|
||||
/** 시설 줄 한 벌(462) — 알약 · 비정규 측점 · 흙덮기 관 · 관 id 장부 · 평면 마커 원천. 보기는 읽기만. */
|
||||
lane: FacilityLane;
|
||||
/** 접힌 좌측 패널을 편다(바깥에서 고른 것이 폼에 실릴 때). */
|
||||
revealLeft: () => void;
|
||||
/** 그 보기를 (숨긴 채라도) 세우고 자료가 설 때까지 기다린다. */
|
||||
ensureView: (view: DesignView) => Promise<void>;
|
||||
/** 자료가 섰다(`designReady`) — 그 전 확정 이동은 막는다. */
|
||||
@@ -322,7 +320,9 @@ export function sharedStructures(shared: DesignPageShared): StructuresSection {
|
||||
onPipeRemove: (chainageM) => on().onPipeRemove?.(chainageM),
|
||||
onPipeSelect: (chainageM) => on().onPipeSelect?.(chainageM),
|
||||
onListRelease: () => on().onListRelease?.(),
|
||||
onReveal: () => isUserPick(shared.projectId) && shared.revealLeft(), // 사용자 고름만(398-1)
|
||||
// 사용자 고름 = 「구조물 배치」 카드 폄 — 좌측 패널 열림 · 닫힘은 그대로(398-1 · 499-1)
|
||||
onReveal: () =>
|
||||
isUserPick(shared.projectId) && shared.structures?.root.classList.remove("is-collapsed"),
|
||||
},
|
||||
{ includeDetail: () => shared.view() === "cross", projectId: shared.projectId },
|
||||
);
|
||||
@@ -330,6 +330,12 @@ export function sharedStructures(shared: DesignPageShared): StructuresSection {
|
||||
return shared.structures;
|
||||
}
|
||||
|
||||
/** 닫힌 동안 고른 것 — 패널을 열 때 고른 줄 · 강조 상자로 한 번 밂(닫힌 몸통 = 스크롤 무효 · 499-1). */
|
||||
export function scrollToPick(section: StructuresSection | null): void {
|
||||
section?.listRoot.querySelector(".is-selected")?.scrollIntoView({ block: "nearest" });
|
||||
section?.root.querySelector("fieldset.is-active")?.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
/** 지금 두 보기가 아는 노선 — 횡단 보기가 먼저(확정 본문이 그쪽이다). */
|
||||
function routeIdOf(shared: DesignPageShared): number | null {
|
||||
return shared.bindings.cross?.save?.routeId() ?? shared.bindings.profile?.save?.routeId() ?? null;
|
||||
|
||||
@@ -637,8 +637,8 @@ export function createStructuresSection(
|
||||
const type = typeMap().get(typeId);
|
||||
if (!type) return;
|
||||
// 우클릭 = [추가]까지 끝난 상태(116-1 · 08-18 「즉시 추가 폐지」를 바꿈) — 새로 놓기에서
|
||||
// 시작해 옛 고름 · 집계 항목 · 그룹 잇기를 비우고 패널을 열어 「구조물 배치」를 폄.
|
||||
// 필수 빈칸이 없으면 바로 추가 + 새 항목 고름 · 남으면 폼만(A군은 임시 배치까지).
|
||||
// 시작해 옛 고름 · 집계 항목 · 그룹 잇기를 비우고 「구조물 배치」를 폄(좌측 패널은 안 엶 · 499-1).
|
||||
// 등록부 기본값 뒤 필수 빈칸이 없으면 바로 추가 + 새 항목 고름 · 남으면 폼만(A군은 임시 배치까지).
|
||||
startNew(type.group, typeId);
|
||||
callbacks.onReveal?.();
|
||||
root.classList.remove("is-collapsed");
|
||||
@@ -654,7 +654,7 @@ export function createStructuresSection(
|
||||
// 새 구조물 — 등록부 기본값을 칸에 실제 값으로(PLAN 46-7 · 사용자가 고치면 됨).
|
||||
renderOptionFields();
|
||||
syncFacilityForm();
|
||||
if (addsAtOnce(type, optionInputs))
|
||||
if (addsAtOnce(optionInputs))
|
||||
return finishAdd({ type, anchorM: chainageM, structures, commit, pick: this });
|
||||
// 임시 배치도 즉시 진행(A군).
|
||||
if (type.managed_by) commitTempPipe(true);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile_UI_Structures_Panel_AddAt.ts
|
||||
* 종단 우클릭 → 종류 고름 = 「[추가]까지 끝난 상태」(PLAN 116-1) — 세 길(B05 종단 · B06 종단 ·
|
||||
* 3D)이 함께 쓰는 패널 `addAt` 의 끝맺음. 필수 빈칸이 없는 종류는 그 측점에 바로 추가하고
|
||||
* 새 항목을 고른 채로 둠 · 남는 종류는 폼만. 세월교 · 물넘이포장도 바로 추가 — 빈 월류 높이는
|
||||
* 3D)이 함께 쓰는 패널 `addAt` 의 끝맺음. 등록부 기본값을 넣고도 필수 빈칸이 없는 종류는 그
|
||||
* 측점에 바로 추가하고 새 항목을 고른 채로 둠 · 남는 종류는 폼만(값 지어내지 않음). 포장도 바로
|
||||
* 추가(물넘이 걸침 = 확인창 · 499-1). 세월교 · 물넘이포장도 바로 추가 — 빈 월류 높이는
|
||||
* 배수유역 재계산 유량이 오면 그 항목에 채움(126-1 · `_Drainage_Ford_Fill`).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -10,18 +11,14 @@ import type { StructureInstance, StructureType } from "./B05_Profile_Api_Structu
|
||||
import type { StructuresSection } from "./B05_Profile_UI_Structures_Panel_Types";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
|
||||
/** 바로 추가하지 않는 동작 — 포장 · 물넘이 겹침(확인창 · 323-7 종류 키 대신 동작 이름). */
|
||||
const FORM_ONLY = "포장_물넘이_겹침";
|
||||
/** 월류 높이를 유량 뒤에 받는 동작 — 추가 때 알림 한 줄. */
|
||||
const FORD_KINDS = "월류단면";
|
||||
export const FORD_WAIT_NOTICE = "월류 높이는 배수유역 재계산(설계유량)이 오면 채워집니다.";
|
||||
|
||||
/** 우클릭 한 번으로 [추가]까지 끝낼 수 있는지 — [추가]가 막는 빈 필수 칸이 없어야 함. */
|
||||
export function addsAtOnce(
|
||||
type: StructureType,
|
||||
optionInputs: ReadonlyArray<{ required: boolean; isEmpty: () => boolean }>,
|
||||
): boolean {
|
||||
if (type.behaviors?.includes(FORM_ONLY)) return false;
|
||||
return !optionInputs.some((entry) => entry.required && entry.isEmpty());
|
||||
}
|
||||
|
||||
@@ -32,7 +29,7 @@ export async function finishAdd(ctx: {
|
||||
anchorM: number;
|
||||
structures: StructureInstance[];
|
||||
commit: () => Promise<void>;
|
||||
/** 패널 — 바깥에서 고를 때와 같은 길(좌측 패널 폄 · 폼 · 목록 고름). */
|
||||
/** 패널 — 바깥에서 고를 때와 같은 길(카드 폄 · 폼 · 목록 고름). */
|
||||
pick: Pick<StructuresSection, "selectById" | "selectPipeByChainage">;
|
||||
}): Promise<boolean> {
|
||||
if (ctx.type.managed_by) {
|
||||
|
||||
@@ -124,13 +124,13 @@ function clearedByNew() {
|
||||
(async () => {
|
||||
const out = {
|
||||
once: {
|
||||
noRequired: addsAtOnce(type("retaining_wall"), [entry(false, ""), entry(true, "L")]),
|
||||
requiredEmpty: addsAtOnce(type("masonry_wet"), [entry(true, ""), entry(false, "1")]),
|
||||
pavement: addsAtOnce(type("pavement_concrete"), []),
|
||||
fordBridge: addsAtOnce(type("ford_bridge", "pipe_points"), []),
|
||||
fordPavement: addsAtOnce(type("ford_pavement", "pipe_points"), []),
|
||||
pipe: addsAtOnce(type("pipe", "pipe_points"), []),
|
||||
box: addsAtOnce(type("box_culvert", "pipe_points"), []),
|
||||
noRequired: addsAtOnce([entry(false, ""), entry(true, "L")]),
|
||||
requiredEmpty: addsAtOnce([entry(true, ""), entry(false, "1")]),
|
||||
pavement: addsAtOnce([]),
|
||||
fordBridge: addsAtOnce([]),
|
||||
fordPavement: addsAtOnce([]),
|
||||
pipe: addsAtOnce([]),
|
||||
box: addsAtOnce([]),
|
||||
},
|
||||
structure: await finish(type("retaining_wall"), true),
|
||||
refused: await finish(type("retaining_wall"), false),
|
||||
|
||||
@@ -123,16 +123,9 @@ function respond(points, basins, pending) {
|
||||
|
||||
const out = {
|
||||
once: {
|
||||
fordBridge: addsAtOnce(type("ford_bridge"), []),
|
||||
fordPavement: addsAtOnce(type("ford_pavement"), []),
|
||||
pavement: addsAtOnce(
|
||||
{
|
||||
type_id: "pavement_concrete",
|
||||
managed_by: null,
|
||||
behaviors: DEFINED.종류?.pavement_concrete?.동작 ?? [],
|
||||
},
|
||||
[],
|
||||
),
|
||||
fordBridge: addsAtOnce([]),
|
||||
fordPavement: addsAtOnce([]),
|
||||
pavement: addsAtOnce([]), // 포장도 바로(499-1)
|
||||
},
|
||||
bridge: await rightClick("ford_bridge"),
|
||||
paveRc: await rightClick("ford_pavement"),
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
못박는 것
|
||||
- 필수 빈칸 없는 종류 = 그 측점에 바로 추가(캐시) + 새 항목을 고른 채(구조물 = 임시 id · A군 = 측점).
|
||||
- 필수 빈칸 남는 종류 · 노면공(확인창) = 폼만 · 세월교 · 물넘이포장은 바로 추가(126-1 · test_126).
|
||||
- 필수 빈칸 남는 종류 = 폼만 · 노면공(물넘이 걸침 = 확인창 · 499-1) · 세월교 · 물넘이포장은 바로 추가(126-1).
|
||||
- [추가]가 거절하면 아무것도 안 듦 · 고르지 않음.
|
||||
- 새로 놓기에서 시작 — 옛 집계 항목 · 그룹 잇기 · 메모가 새 항목에 안 실림.
|
||||
- 세 길(B05 종단 · B06 종단 · 3D)이 모두 패널 `addAt` 한 곳 · 패널을 열고 「구조물 배치」를 폄.
|
||||
- 세 길(B05 종단 · B06 종단 · 3D)이 모두 패널 `addAt` 한 곳 · 「구조물 배치」를 폄(좌측 패널은 안 엶 · 499-1).
|
||||
|
||||
Node 헬퍼 `helper_116_right_click_add.cjs` 가 판정 · 끝맺음 · `loadForm(null)` 을 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
@@ -47,9 +47,9 @@ def test_필수_빈칸_없는_종류만_바로_추가(out):
|
||||
assert once["requiredEmpty"] is False
|
||||
|
||||
|
||||
def test_노면공만_폼만_세월교_물넘이포장은_바로(out):
|
||||
def test_노면공_세월교_물넘이포장_모두_바로(out):
|
||||
once = out["once"]
|
||||
assert once["pavement"] is False
|
||||
assert once["pavement"] is True # 499-1 — 닫힌 패널에 폼만 남기지 않음
|
||||
assert once["fordBridge"] is True and once["fordPavement"] is True # 126-1
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ def test_addAt_은_새로_놓기_패널_폄_후_판정():
|
||||
body.index("startNew(type.group, typeId)"),
|
||||
body.index("callbacks.onReveal?.()"),
|
||||
body.index('root.classList.remove("is-collapsed")'),
|
||||
body.index("addsAtOnce(type, optionInputs)"),
|
||||
body.index("addsAtOnce(optionInputs)"),
|
||||
body.index("finishAdd("),
|
||||
body.index("commitTempPipe(true)"), # 폼만 갈래 — A군 임시 배치 그대로(우클릭만 · 291-1)
|
||||
]
|
||||
|
||||
@@ -41,7 +41,7 @@ def out():
|
||||
|
||||
|
||||
def test_우클릭_세월교_물넘이포장은_바로_추가(out):
|
||||
assert out["once"] == {"fordBridge": True, "fordPavement": True, "pavement": False}
|
||||
assert out["once"] == {"fordBridge": True, "fordPavement": True, "pavement": True}
|
||||
|
||||
|
||||
def test_추가_뒤_목록에_들고_그_관을_고른_채_알림_한_줄(out):
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
방향키 · 끌기 도중 · 가짜 누름 · 묶음 차례가 떠 있는 동안(교점 놓음 · 곡선 지우기 · 되돌리기).
|
||||
- 뿌리 한 곳 — 폼(`selectPipeByChainage` · `selectById` · `addAt`)은 그대로 펴기를 부르고 `design_shared`
|
||||
onReveal 하나가 `isUserPick` 으로 거름.
|
||||
- 499-1 뒤집음 — 펴기 = 「구조물 배치」 카드만 · 좌측 패널은 안 엶(`revealLeft` · `setOptionsOpen(` 없음) ·
|
||||
패널을 열 때 고른 줄 · 강조 상자로 한 번 밂 · B06 평면 겹침 기준 = main 전체.
|
||||
|
||||
짝: `helper_398_1_user_pick.cjs`(진짜 `design_user_pick` · `design_page_data`).
|
||||
"""
|
||||
@@ -53,7 +55,12 @@ def test_다시_칠하기는_안_폄(r, way):
|
||||
|
||||
def test_뿌리_한_곳():
|
||||
shared = _read("A00_Common/design/design_shared.ts")
|
||||
assert "onReveal: () => isUserPick(shared.projectId) && shared.revealLeft()" in shared
|
||||
assert (
|
||||
'isUserPick(shared.projectId) && shared.structures?.root.classList.remove("is-collapsed")'
|
||||
in shared
|
||||
)
|
||||
for rel in ("A00_Common/design/design_shared.ts", "A00_Common/design/design_page.ts"):
|
||||
assert "revealLeft" not in _read(rel) and "setOptionsOpen(" not in _read(rel) # 499-1
|
||||
panel = _read("B05_Profile/B05_Profile_UI_Structures_Panel.ts")
|
||||
assert panel.count("callbacks.onReveal?.()") == 3 # 고름 둘 · 우클릭 추가 — 부르는 쪽은 안 막음
|
||||
found = subprocess.run( # noqa: S603, S607 — 고정 실행 파일
|
||||
@@ -71,3 +78,24 @@ def test_뿌리_한_곳():
|
||||
"A00_Common/design/design_shared.ts",
|
||||
"A00_Common/design/design_user_pick.ts",
|
||||
]
|
||||
|
||||
|
||||
def test_열_때_고른_줄로_밂():
|
||||
page = _read("A00_Common/design/design_page.ts")
|
||||
assert "onOptionsOpenChange: (isOpen) => isOpen && scrollToPick(shared.structures)" in page
|
||||
shared = _read("A00_Common/design/design_shared.ts")
|
||||
body = shared[shared.index("export function scrollToPick(") :]
|
||||
body = body[: body.index("\n}\n")]
|
||||
assert 'listRoot.querySelector(".is-selected")' in body
|
||||
assert 'root.querySelector("fieldset.is-active")' in body
|
||||
layout = _read("ui_template/ui_template_workflow_layout.ts")
|
||||
assert "options.onOptionsOpenChange?.(isOpen);" in layout
|
||||
|
||||
|
||||
def test_B06_평면_겹침_기준_main_전체():
|
||||
css = _read("A00_Common/design/design_page.css")
|
||||
rule = css[css.index('.design-page__main[data-view="cross"] > .design-page__center {') :]
|
||||
assert "position: static;" in rule[: rule.index("}")]
|
||||
peek = css[css.index(".design-plan-peek {") :]
|
||||
z = int(peek[peek.index("z-index:") + 8 : peek.index(";", peek.index("z-index:"))])
|
||||
assert 3 < z < 199 # 종단 판(3) 위 · 서랍 덮개(199) 아래
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface WorkflowLayoutOptions {
|
||||
currentStage?: number | readonly number[];
|
||||
routes?: readonly string[];
|
||||
onStepClick?: (stepIndex: number, route: string) => void;
|
||||
/** 좌측 패널이 열리고 닫힐 때(처음 앉힘 포함) — 닫힌 동안 바뀐 안쪽을 열 때 맞춤(499-1). */
|
||||
onOptionsOpenChange?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export interface WorkflowLayoutHandle {
|
||||
@@ -219,7 +221,10 @@ export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLa
|
||||
// 좌측 패널이 없는 화면(B08 · B09)도 [?] 줄은 둠(54-2)
|
||||
optionsContent: options.leftPanel ?? helpOnlyRow(),
|
||||
progressContent,
|
||||
onOptionsOpenChange: (isOpen) => root.classList.toggle("is-options-open", isOpen),
|
||||
onOptionsOpenChange: (isOpen) => {
|
||||
root.classList.toggle("is-options-open", isOpen);
|
||||
options.onOptionsOpenChange?.(isOpen);
|
||||
},
|
||||
});
|
||||
|
||||
root.append(body, overlays.root);
|
||||
|
||||
Reference in New Issue
Block a user