feat(view): ②·③ 모델과 입력창/철근표를 상하·좌우로 전환하고 분할선으로 크기 조절

- 중앙 도구줄 '상하 | 좌우' 전환(② 도구줄 신설), 분할선 드래그·키보드(←→↑↓, Shift×5, Home/End)·더블클릭 기본값
- 상하: 모델 높이 180–560 px, 좌우: 모델 폭 30–70 %(모델 왼쪽), 모델 접기 시 창 전체 사용
- 중앙 열 900 px 미만·1100 px 이하 화면은 자동 상하 + 안내, 넓어지면 복귀
- 단계별 표시 설정을 localStorage에 저장(기존 pierzainer.review-layout 이관), 프로젝트 파일 미포함
- 오른쪽 창 묶음(.workspace-pane) 도입에 맞춰 기존 검증 스크립트 선택자 갱신
- 단위시험 4개, verify-workspace-layout.cjs Chrome 27개 검사

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-09 18:01:20 +09:00
1 parent ef596650cc
commit 7d36c4b64a
8 files changed
+389 -44

No files matched your search

+4 -33
View File
@@ -145,6 +145,7 @@ import type { Issue, PierInput, PierType, StrataRow } from './core/types';
import { buildPierScene, setRebarView, setRebarViewOn, viewPosition, type PartName, type PierScene, type ViewKind } from './view/scene';
import { buildAllPiers, type AllPiers } from './view/all-piers';
import { buildTerrainMapGroup, disposeTerrainMapGroup, setTerrainMapMode } from './view/terrain-map';
import { initWorkspaceLayout } from './view/workspace-layout';
/** One pier as shown in the list. `checked` is the multi-select state (시안 ①). */
interface Pier extends PierValues {
@@ -6383,28 +6384,10 @@ function restorePanel(id: string): void {
restorePanel('parts-info');
restorePanel('color-info');
/** Viewer sizing is a local display preference, separate from project/design data. */
/** Viewer sizing/arrangement is a local display preference, separate from project/design data. */
function initReviewWorkspace(): void {
const workspace = $('review-workspace');
const slider = $<HTMLInputElement>('review-model-height');
const toggle = $<HTMLButtonElement>('review-model-toggle');
let collapsed = false;
try {
const saved = JSON.parse(localStorage.getItem('pierzainer.review-layout') ?? 'null');
if (saved && Number.isFinite(saved.height) && saved.height >= 180 && saved.height <= 560) slider.value = String(saved.height);
collapsed = saved?.collapsed === true;
} catch { /* A blocked store must not prevent resizing. */ }
const apply = () => {
workspace.style.setProperty('--review-model-height', `${slider.value}px`);
workspace.classList.toggle('model-collapsed', collapsed);
$('review-model-height-value').textContent = `${slider.value} px`;
toggle.textContent = collapsed ? '모델 펼치기' : '모델 접기';
toggle.setAttribute('aria-expanded', String(!collapsed));
slider.disabled = collapsed;
try { localStorage.setItem('pierzainer.review-layout', JSON.stringify({ height: Number(slider.value), collapsed })); } catch { /* Display preference only. */ }
};
slider.addEventListener('input', apply);
toggle.addEventListener('click', () => { collapsed = !collapsed; apply(); });
initWorkspaceLayout($('plan-workspace'));
initWorkspaceLayout($('review-workspace'));
const jump = (selector: string) => {
const node = document.querySelector<HTMLElement>(selector);
if (!node) return;
@@ -6414,18 +6397,6 @@ function initReviewWorkspace(): void {
};
$('review-inputs').addEventListener('click', () => jump('#rebar-table .abut-rebar-inputs input, #rebar-table input[type="number"], #rebar-table select'));
$('review-table').addEventListener('click', () => jump('#rebar-table table.rebar-table'));
apply();
new ResizeObserver(() => {
if (workspace.clientHeight === 0 || !window.matchMedia('(min-width: 1101px)').matches) return;
const available = workspace.clientHeight - workspace.querySelector<HTMLElement>('.workspace-tools')!.offsetHeight - 264;
const max = Math.max(180, Math.min(560, Math.floor(available / 20) * 20));
const next = Math.min(Number(slider.value), max);
if (slider.max !== String(max) || slider.value !== String(next)) {
slider.max = String(max);
slider.value = String(next);
apply();
}
}).observe(workspace);
}
initReviewWorkspace();