/* ============================================================================= * M02_MasterTemplete_TreePanel_Dialogs.ts * 구조물 라이브러리 오른쪽 클릭 메뉴가 띄우는 작은 창 — 글 받기 · 옮길 폴더 고르기 · 저장/버림 물음. * 모두 약속(Promise)으로 답함 — 닫기 · Esc · 바깥 누르기 = 취소(null / "cancel"). * ========================================================================== */ import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; import { openModal } from "@ui/ui_template_modal"; export interface TextField { key: string; label: string; value?: string; /** 비워도 됨 — 아니면 비운 채 [확인] 하면 그 칸에 안내 글 */ optional?: boolean; } /** 글 칸 하나 이상을 받음 — 확인이면 칸 이름 → 글(앞뒤 공백 뺌) */ export function askText( title: string, fields: TextField[], okLabel = L("M02_TreeOk"), ): Promise | null> { return new Promise((resolve) => { let done = false; const finish = (value: Record | null): void => { if (done) return; done = true; resolve(value); }; openModal({ title, closeLabel: L("M02_Close"), dialogClass: "m02-modal m02-treedlg", beforeClose: () => (finish(null), true), mount: (body, close) => { const inputs = fields.map((f) => ({ f, field: createInputField({ label: f.label, value: f.value }), })); const ok = createButton({ label: okLabel, variant: "filled" }); const submit = (): void => { // 빈 칸은 그 칸에 빨간 테두리 + 한 줄(칸마다) — 토스트는 창 뒤에 가려 안 보였음 const out: Record = {}; let bad = false; for (const { f, field } of inputs) { out[f.key] = field.input.value.trim(); const empty = !out[f.key] && !f.optional; field.setError(empty ? L("M02_FieldNeeded").replace("{label}", f.label) : undefined); bad ||= empty; } if (bad) return; finish(out); close(); }; ok.addEventListener("click", submit); for (const { field } of inputs) { field.input.addEventListener("input", () => field.setError(undefined)); field.input.addEventListener("keydown", (event) => { if (event.key === "Enter" && !event.isComposing) submit(); }); } body.append(...inputs.map(({ field }) => field.root), ok); inputs[0]?.field.input.select(); }, }); }); } /** 옮길 폴더 고르기 — `folders` 에서 `blocked` 인 것은 뺌 · 맨 위(폴더 밖)는 늘 있음 */ export function askFolder( title: string, folders: string[][], blocked: (path: string[]) => boolean, current: string[], ): Promise { return new Promise((resolve) => { let done = false; const finish = (value: string[] | null): void => { if (done) return; done = true; resolve(value); }; const choices = [[], ...folders.filter((path) => !blocked(path))]; openModal({ title, closeLabel: L("M02_Close"), dialogClass: "m02-modal m02-treedlg", beforeClose: () => (finish(null), true), mount: (body, close) => { const field = createSelectField({ label: L("M02_TreeMoveTo"), options: choices.map((path, i) => ({ value: String(i), text: path.length ? path.join(" / ") : L("M02_TreeTop"), })), value: String(choices.findIndex((path) => path.join("|") === current.join("|"))), }); const ok = createButton({ label: L("M02_TreeOk"), variant: "filled" }); ok.addEventListener("click", () => { finish(choices[Number(field.select.value)] ?? []); close(); }); body.append(field.root, ok); }, }); }); } export type Settle = "save" | "discard" | "cancel"; /** 저장 안 한 표 고침 — 저장하고 계속 · 버리고 계속 · 취소 */ export function askSettle(): Promise { return new Promise((resolve) => { let done = false; const finish = (value: Settle): void => { if (done) return; done = true; resolve(value); }; openModal({ title: L("M02_TreeUnsaved"), closeLabel: L("M02_TreeCancel"), dialogClass: "m02-modal m02-treedlg", beforeClose: () => (finish("cancel"), true), mount: (body, close) => { const choose = (value: Settle) => () => { finish(value); close(); }; const save = createButton({ label: L("M02_TreeSaveGo"), variant: "filled" }); const discard = createButton({ label: L("M02_TreeDiscardGo"), variant: "danger" }); const cancel = createButton({ label: L("M02_TreeCancel"), variant: "ghost" }); save.addEventListener("click", choose("save")); discard.addEventListener("click", choose("discard")); cancel.addEventListener("click", choose("cancel")); body.append(el("div", { className: "m02-side__row", children: [save, discard, cancel] })); }, }); }); }