- 공용 모달 바깥 누르기 = 누름 · 뗌 둘 다 배경일 때만 닫음(숫자 칸 끌기로 안 닫힘 · 평면도 ⚙ 포함 모든 openModal) - ⚙ 설정 모달(종단 · 횡단 · 토적 · 구조물 · 전개도) 폭 720 → 360 px · 그룹 상자(legend) · 맨 아래 단추 줄 하나([안내선 숨기기] [기본값] [취소] [확인]) - [확인] = 바뀐 것만 저장(축척 → 배치) 뒤 다시 그림 · 붉은 칸이면 꺼짐 · 횡단 [이 장] [모든 장] [배율 적용] 단추 → 「적용」 고르기 + [확인] - 축척 「자동」 체크 걷음 — 기본 = 지정값(종단 평면도 1/5,000 · 토적 X 1/2,000 · 전개도 X · Y 1/50) · 옛 null = 기본으로 읽음 - 낡은 시험 고침: 153-1(지운 buildCrossLayoutControls 대신 ⚙ 횡단 모달) · 212-3 · 180-3(안내선 단추 자리) · 280-5(자동 체크 · 단추 판정) · 485 축척 · 토적 · 종단 평면 칸(옛 자동 축척 값 → 지정 기본값) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
210 lines
9.0 KiB
TypeScript
210 lines
9.0 KiB
TypeScript
/* =============================================================================
|
|
* B07_DesignDetail_UI_Settings.ts
|
|
* 좌측 도면 묶음 ⚙ 설정 모달(요구 280-5) — 공용 `openModal` · 머리 [원본에서 다시 그리기](281).
|
|
*
|
|
* 평면도 = 평면도 관리 모달 그대로(`openPlanManager`)
|
|
* 그 밖 = 그룹 상자(523-1) + 맨 아래 단추 줄 하나 — [안내선 숨기기](횡단) · [기본값](축척) · [취소] · [확인]
|
|
* 횡단도 = 「장 배치」(열 · 행 · 적용 이 장 / 모든 장) · 「표」(표 배율) · 축척
|
|
* 구조물 도면 = 「M02 그림 — 축척 없음」 한 줄 · 종단 · 횡단 · 토적 · 전개도 = X · Y 축척 칸(`_UI_Settings_Scale`)
|
|
* [확인] = 바뀐 것만 저장(축척 → 배치) 뒤 닫고 다시 그림 · 바뀐 것 없으면 닫기만 · 붉은 칸 있으면 꺼짐
|
|
* [원본에서 다시 그리기] = 지금 연 도면이 그 묶음일 때만 켬
|
|
* ========================================================================== */
|
|
|
|
import { createButton, createInputField, createSelectField } from "@ui/ui_template_elements";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import type { CrossLayout, DesignDrawingItem } from "./B07_DesignDetail_Api_Fetch";
|
|
import { DRAWING_GROUPS, isCrossSheet } from "./B07_DesignDetail_UI_Panels";
|
|
import { openPlanManager, type PlanManageControls } from "./B07_DesignDetail_UI_PlanManage";
|
|
import { mountDrawingScale, settingsGroup } from "./B07_DesignDetail_UI_Settings_Scale";
|
|
|
|
type Kind = DesignDrawingItem["kind"];
|
|
|
|
export interface DrawingSettingsDeps {
|
|
drawings: DesignDrawingItem[];
|
|
current: () => DesignDrawingItem | undefined;
|
|
/** 지금 도면에 버릴 것(저장본 · 고친 것)이 있나 */
|
|
canRedraw: () => boolean;
|
|
/** 원본에서 다시 그리기 — 해냈으면 참(모달 닫음) */
|
|
redraw: () => Promise<boolean>;
|
|
plan?: PlanManageControls;
|
|
cross: {
|
|
layout: () => Promise<CrossLayout>;
|
|
/** 배치 저장 — 모달은 닫힌 뒤 · `sheet` = 지금 장 번호(장 아니면 0) */
|
|
save: (next: CrossLayout, sheet: number) => void;
|
|
guideToggle: HTMLButtonElement;
|
|
};
|
|
}
|
|
|
|
/** ⚙ 묶음에 드는 도면 종류 — 횡단도 카드엔 표준 횡단면도 줄이 같이 있음. */
|
|
const memberOf = (kind: Kind): Kind[] => (kind === "cross" ? ["cross", "cross_standard"] : [kind]);
|
|
|
|
/** 표 · 글 배율 범위(180-2) — 서버 `CROSS_SCALE_RANGE` 와 같음. */
|
|
const CROSS_SCALE_MIN = 0.5;
|
|
const CROSS_SCALE_MAX = 2;
|
|
|
|
const note = (text: string): HTMLElement => {
|
|
const line = document.createElement("p");
|
|
line.className = "b07-settings__note";
|
|
line.textContent = text;
|
|
return line;
|
|
};
|
|
|
|
/** 묶음 `kind` 의 ⚙ 모달을 엶. */
|
|
export function openDrawingSettings(kind: Kind, deps: DrawingSettingsDeps): void {
|
|
const current = deps.current();
|
|
const mine = !!current && memberOf(kind).includes(current.kind);
|
|
let closeModal = (): void => undefined;
|
|
const redraw = createButton({
|
|
label: "원본에서 다시 그리기",
|
|
variant: "ghost",
|
|
disabled: !(mine && deps.canRedraw()),
|
|
onClick: () => {
|
|
void deps.redraw().then((done) => done && closeModal());
|
|
},
|
|
});
|
|
redraw.title = !mine
|
|
? "이 묶음 도면을 연 뒤 쓸 수 있음"
|
|
: redraw.disabled
|
|
? "이미 원본 그대로"
|
|
: "저장본 · 고친 것을 버리고 B06 원본에서 새로 그림";
|
|
redraw.classList.add("b07-settings__redraw");
|
|
|
|
if (kind === "plan" && deps.plan) {
|
|
const manager = openPlanManager({ ...deps.plan, drawings: deps.drawings, actions: [redraw] });
|
|
closeModal = manager.close;
|
|
return;
|
|
}
|
|
const label = DRAWING_GROUPS.find((group) => group.kind === kind)?.label ?? "도면";
|
|
openModal({
|
|
title: `${label} 설정`,
|
|
closeLabel: "닫기",
|
|
dialogClass: "b07-settings",
|
|
actions: [redraw],
|
|
mount: (body, close) => {
|
|
closeModal = close;
|
|
if (mine) body.append(note(`지금 도면: ${current?.label}`));
|
|
if (kind === "structure") body.append(settingsGroup("축척", note("M02 그림 — 축척 없음")));
|
|
const confirm = createButton({ label: "확인", onClick: () => void apply() });
|
|
const refresh = (): void => {
|
|
confirm.disabled = scale?.read() === "invalid" || cross?.read() === "invalid";
|
|
};
|
|
const cross = kind === "cross" ? mountCross(body, deps, refresh) : undefined;
|
|
const scale = mountDrawingScale(body, kind, deps, refresh);
|
|
|
|
const apply = async (): Promise<void> => {
|
|
const nextScale = scale?.read() ?? null;
|
|
const nextLayout = cross?.read() ?? null;
|
|
if (nextScale === "invalid" || nextLayout === "invalid") return;
|
|
if (nextScale && !(await scale!.save(nextScale))) return;
|
|
close();
|
|
if (nextLayout) deps.cross.save(nextLayout, cross!.sheet);
|
|
else if (nextScale) deps.plan?.onClosed(true, deps.current()?.id); // 목록 · 도면 다시 받음
|
|
};
|
|
const cancel = createButton({ label: "취소", variant: "ghost", onClick: close });
|
|
cancel.classList.add("b07-settings__cancel");
|
|
const footer = document.createElement("div");
|
|
footer.className = "b07-settings__actions";
|
|
if (cross) footer.append(deps.cross.guideToggle);
|
|
if (scale) {
|
|
const reset = createButton({ label: "기본값", variant: "ghost", onClick: scale.reset });
|
|
reset.title = "축척 칸을 기본으로 채움 — [확인]으로 씀";
|
|
footer.append(reset);
|
|
}
|
|
footer.append(cancel, confirm);
|
|
body.append(footer);
|
|
},
|
|
});
|
|
}
|
|
|
|
interface CrossFields {
|
|
sheet: number;
|
|
/** null = 그대로(못 받음 포함) · "invalid" = 붉은 칸 · 그 밖 = 저장할 배치 */
|
|
read: () => CrossLayout | null | "invalid";
|
|
}
|
|
|
|
/** 횡단 「장 배치」 · 「표」 그룹 — 칸은 배치를 받은 뒤 채움. */
|
|
function mountCross(
|
|
body: HTMLElement,
|
|
deps: DrawingSettingsDeps,
|
|
onCheck: () => void,
|
|
): CrossFields {
|
|
const current = deps.current();
|
|
const sheet =
|
|
current && isCrossSheet(current)
|
|
? deps.drawings.filter(isCrossSheet).findIndex((item) => item.id === current.id) + 1
|
|
: 0;
|
|
const number = (label: string, min: number, max: number) => {
|
|
const field = createInputField({ label, type: "number", min, max, compact: true });
|
|
field.input.disabled = true; // 받기 전
|
|
field.input.addEventListener("input", onCheck);
|
|
return field;
|
|
};
|
|
const cols = number("배치 열", 1, 10);
|
|
const rows = number("행", 1, 10);
|
|
const scope = createSelectField({
|
|
label: "적용",
|
|
compact: true,
|
|
options: [
|
|
...(sheet ? [{ value: "sheet", text: "이 장만" }] : []),
|
|
{ value: "all", text: "모든 장" },
|
|
],
|
|
value: sheet ? "sheet" : "all",
|
|
});
|
|
scope.select.title = "이 장만 = 지금 장 · 모든 장 = 모든 장을 이 배치로(장마다 값 지움)";
|
|
const table = number("표 배율", CROSS_SCALE_MIN, CROSS_SCALE_MAX);
|
|
table.input.setAttribute("step", "0.1");
|
|
table.root.title = "모든 장의 표 · 표 위 글 크기(기본 1)";
|
|
const line = (...items: HTMLElement[]): HTMLElement => {
|
|
const row = document.createElement("div");
|
|
row.className = "b07-cross-layout__row";
|
|
row.append(...items);
|
|
return row;
|
|
};
|
|
const layoutBox = settingsGroup("장 배치", line(cols.root, rows.root), line(scope.root));
|
|
const tableBox = settingsGroup("표", line(table.root));
|
|
layoutBox.classList.add("b07-cross-layout");
|
|
tableBox.classList.add("b07-cross-layout");
|
|
body.append(layoutBox, tableBox);
|
|
|
|
let layout: CrossLayout | undefined;
|
|
let shown: [number, number] = [0, 0];
|
|
void deps.cross
|
|
.layout()
|
|
.then((got) => {
|
|
layout = got;
|
|
shown = (sheet && got.sheets[String(sheet)]) || got.default;
|
|
cols.input.value = String(shown[0]);
|
|
rows.input.value = String(shown[1]);
|
|
table.input.value = String(got.table_scale ?? 1);
|
|
for (const field of [cols, rows, table]) field.input.disabled = false;
|
|
})
|
|
.catch(() => layoutBox.append(note("배치를 받지 못했습니다.")));
|
|
|
|
return {
|
|
sheet,
|
|
read: () => {
|
|
if (!layout) return null;
|
|
const grid: [number, number] = [Number(cols.input.value), Number(rows.input.value)];
|
|
const tableScale = Number(table.input.value);
|
|
const badGrid = !grid.every((n) => Number.isInteger(n) && n >= 1 && n <= 10);
|
|
const badTable = !(tableScale >= CROSS_SCALE_MIN && tableScale <= CROSS_SCALE_MAX);
|
|
cols.setError(badGrid ? "1~10 정수" : undefined);
|
|
rows.setError(badGrid ? "1~10 정수" : undefined);
|
|
table.setError(badTable ? `${CROSS_SCALE_MIN}~${CROSS_SCALE_MAX}` : undefined);
|
|
if (badGrid || badTable) return "invalid";
|
|
let next = layout;
|
|
const touched =
|
|
grid[0] !== shown[0] ||
|
|
grid[1] !== shown[1] ||
|
|
scope.select.value !== (sheet ? "sheet" : "all");
|
|
if (touched && scope.select.value === "sheet") {
|
|
next = { ...next, sheets: { ...next.sheets, [String(sheet)]: grid } };
|
|
} else if (touched) {
|
|
next = { ...next, default: grid, sheets: {} };
|
|
}
|
|
if (tableScale !== (layout.table_scale ?? 1)) next = { ...next, table_scale: tableScale };
|
|
return next === layout ? null : next;
|
|
},
|
|
};
|
|
}
|