diff --git a/index.html b/index.html index 46989d3..4772efc 100644 --- a/index.html +++ b/index.html @@ -74,6 +74,7 @@
+ diff --git a/src/core/active-piers.ts b/src/core/active-piers.ts new file mode 100644 index 0000000..2fca62e --- /dev/null +++ b/src/core/active-piers.ts @@ -0,0 +1,29 @@ +import { overrideKeys, type EditableKey, type PierValues } from './changes'; +import type { PierInput } from './types'; + +/** One list row as the selection rules see it. */ +export interface PierSelection { + name: string; + checked: boolean; + hasModel: boolean; +} + +/** + * Piers shown in the 3D view: every checked pier that has a model, plus the 기준 pier (the one whose card is open) + * even when it is unchecked. Order follows the list (승인 2026-10-27, 체크 = 활성). + */ +export function shownNames(list: PierSelection[], active: string | null): string[] { + return list.filter((p) => p.hasModel && (p.checked || p.name === active)).map((p) => p.name); +} + +/** Piers that receive the 기준 pier's overrides: the checked piers with a model, other than the 기준 pier. */ +export function overrideTargets(list: PierSelection[], active: string | null): string[] { + return list.filter((p) => p.checked && p.hasModel && p.name !== active).map((p) => p.name); +} + +/** The target input with the source's overridden values copied in; every other value of the target is kept. */ +export function applyOverrides(source: PierValues, target: PierInput): PierInput { + const next: PierInput = { ...target }; + for (const key of overrideKeys(source) as EditableKey[]) next[key] = source.draft[key] as never; + return next; +} diff --git a/src/main.ts b/src/main.ts index fb45f3b..89c2bbf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ import { buildPier, type PierModel } from './core/geometry'; import { NOT_IMPLEMENTED_RULES, validateInput, validatePier } from './core/validate'; import { buildTemplateWorkbook } from './core/template'; import { EDITABLE_FIELDS, FIELD_LABELS, overrideKeys, pendingKeys, revertDraft, type EditableKey, type PierValues } from './core/changes'; +import { applyOverrides, overrideTargets, shownNames, type PierSelection } from './core/active-piers'; import { adjustRow, layoutText, resolveRowCounts, rowLayout, totalPiles } from './core/piles'; import { applyCopy, DEFAULT_COPY_ITEMS, planCopy, type CopyItem } from './core/copy'; import { pileGeometry } from './core/geometry'; @@ -350,13 +351,26 @@ function setView(kind: ViewKind): void { } /** 전체 보기 applies on ① 형상 생성 only; ③ and the others keep the single-pier scene. */ -function allViewOn(): boolean { - return state.allView && state.stage === 'shape'; +function selectionOf(p: Pier): PierSelection { + return { name: p.input.name, checked: p.checked, hasModel: p.model !== null }; } -/** Builds 전체 보기 from every pier that has a model. Returns false when none does. */ +/** The piers the 3D view shows together: the checked ones plus the 기준 pier (체크 = 활성, 승인 2026-10-27). */ +function shownPiers(): Pier[] { + const names = new Set(shownNames(state.piers.map(selectionOf), state.active)); + return state.piers.filter((p) => names.has(p.input.name)); +} + +/** Several piers in one frame: ① 전체 보기 (button), or ② when two or more piers are shown. */ +function multiSceneOn(): boolean { + if (state.stage === 'shape') return state.allView; + if (state.stage === 'plan') return shownPiers().length >= 2; + return false; +} + +/** Builds the shown piers in one frame. Returns false when none has a model. */ function buildAllScene(): boolean { - const entries = state.piers.flatMap((p) => (p.model ? [{ name: p.input.name, model: p.model }] : [])); + const entries = shownPiers().flatMap((p) => (p.model ? [{ name: p.input.name, model: p.model }] : [])); if (entries.length === 0) return false; allPiers = buildAllPiers(entries, groundModel?.terrainGrid); world.add(allPiers.piers); @@ -375,7 +389,7 @@ function setupScene(): void { } if (rebar3d) world.remove(rebar3d.group); rebar3d = null; - if (allViewOn() && buildAllScene()) { + if (multiSceneOn() && buildAllScene()) { setView(state.view); return; } @@ -430,6 +444,7 @@ function renderPierRows(list: HTMLUListElement, mode: Stage): void { check.setAttribute('aria-label', `${pier.input.name} 선택`); check.addEventListener('change', () => { pier.checked = check.checked; + setupScene(); renderAll(); }); // The pier name and type are shown here, since the 3D title band is gone on wide screens (승인 2026-10-23). @@ -485,7 +500,8 @@ function renderInputTable(): void { const inp = p.input; const front = inp.bearings.filter((b) => b.row === '전열').length; const back = inp.bearings.filter((b) => b.row === '후열').length; - const tr = el('tr', undefined, p.input.name === state.active ? 'active' : undefined); + const classes = [p.checked ? 'shown' : '', p.input.name === state.active ? 'active' : ''].filter(Boolean).join(' '); + const tr = el('tr', undefined, classes || undefined); tr.tabIndex = 0; tr.addEventListener('click', () => selectPier(p.input.name)); tr.addEventListener('keydown', (e) => { @@ -533,7 +549,7 @@ function renderPanels(): void { renderInputTable(); $('inputs-active').textContent = pier ? `활성 교각 ${pier.input.name} 상세 (표의 행을 누르면 바뀜)` : ''; - $('edit-title').textContent = pier ? `코핑·말뚝 입력 수정 · ${pier.input.name}` : '코핑·말뚝 입력 수정'; + $('edit-title').textContent = pier ? `코핑·말뚝 입력 수정 · 기준 ${pier.input.name}` : '코핑·말뚝 입력 수정'; if (!pier) { fillKv($('inputs'), []); @@ -796,6 +812,12 @@ function renderEditCard(): void { : '기본값과 같습니다.'; btn.textContent = `다시 생성 · 변경 ${status.pending.length}`; btn.disabled = status.pending.length === 0; + // The 기준 pier's overrides can go to the other checked piers; they take effect on 선택 교각 다시 생성. + const targets = overrideTargets(state.piers.map(selectionOf), state.active); + const applyBtn = $