/* ============================================================================= * M02_MasterTemplete_Main_Actions.ts * 단가산출 · 일위대가 상세 머리 단추 [초기화] [삭제](740-2 · 738-2 · 요구 579-3 · 581) — 차례 … [초기화] [삭제] [저장]. * [삭제] = 735 경고 창(쓰는 곳 표 · 키) → 나무 지우기 → 목록으로 · 쓰는 곳 없으면 한 줄 물음. * [초기화] = 서버 `-reset` 미리 보기(걷힐 고침 수) → 물음 → 그 층에 씀 → 다시 읽음. * 덮개 · 뺀줄 · 더한줄 단가를 M01 키 값으로 되돌림 · 옵션(입력값) · 기본정보는 그대로(서버 740-1). * 둘 다 쓰기 권한이 있는 층에서만 보임(`update`) · 상세에 저장 안 한 고침이 있으면 먼저 물음. * ========================================================================== */ import { createButton, showToast } from "@ui/ui_template_elements"; import { currentLanguageIndex } from "@ui/ui_template_locale"; import { askConfirm } from "@ui/ui_template_modal"; import { editPrecomboTree, fetchPrecomboTree, resetLibrary, type LibraryKind, } from "./M02_MasterTemplete_Api_Fetch"; import { askLibraryDelete } from "./M02_MasterTemplete_Library_Delete"; import { precomboList } from "./M02_MasterTemplete_UI_Side_Precombo"; import { sangeunList } from "./M02_MasterTemplete_UI_Side_Sangeun"; import type { Selection } from "./M02_MasterTemplete_UI_Main"; const TEXT = { Reset: ["초기화", "Reset"], ResetTip: [ "고친 값을 연결된 M01 키 값으로 되돌림 — 옵션 · 기본정보는 그대로", "Revert edited values to their linked M01 key values — options and basic info stay", ], Delete: ["삭제", "Delete"], DeleteTip: [ "이 항목을 지움 — 쓰는 곳이 있으면 먼저 알림", "Delete this item — warns first if used", ], ResetAsk: [ "고친 값 {n} 개를 M01 값으로 되돌림{k} — 옵션 · 기본정보는 그대로", "Revert {n} edited value(s) to M01 values{k} — options and basic info stay", ], Restored: [" (빼 둔 줄 {k} 개 되살림)", " ({k} removed line(s) restored)"], Nothing: ["되돌릴 고침이 없음", "Nothing to revert"], Done: ["M01 값으로 되돌렸음", "Reverted to M01 values"], DeleteAsk: ["「{name}」 을(를) 지울까?", "Delete 「{name}」?"], Deleted: ["지웠음", "Deleted"], Failed: ["못 했음 — {why}", "Failed — {why}"], } as const; const tx = (key: keyof typeof TEXT): string => String(TEXT[key][currentLanguageIndex as 0 | 1] ?? TEXT[key][0]); const fill = (text: string, vars: Record): string => Object.entries(vars).reduce((s, [k, v]) => s.replace(`{${k}}`, String(v)), text); export interface MainActionsCtx { sel: () => Selection | null; /** 지금 읽은 문서의 판 */ version: () => string; /** 지금 읽은 문서(저장본) — 뺀 줄 수를 셈 */ loaded: () => unknown; confirmLeave: () => Promise; open: (sel: Selection | null) => Promise; /** 좌측 목록을 다시 읽음 */ changed: () => void; } /** 문서 안 모든 `뺀줄` 길이 합(단가산출 · 일위대가 줄 안 덮개까지) */ function removedCount(node: unknown): number { if (Array.isArray(node)) return node.reduce((n, x) => n + removedCount(x), 0); if (!node || typeof node !== "object") return 0; return Object.entries(node as Record).reduce( (n, [k, v]) => n + (k === "뺀줄" && Array.isArray(v) ? v.length : removedCount(v)), 0, ); } const why = (error: unknown): string => (error instanceof Error ? error.message : ""); export function createMainActions(ctx: MainActionsCtx): { reset: HTMLButtonElement; del: HTMLButtonElement; update: (writable: boolean) => void; } { const reset = createButton({ label: tx("Reset"), variant: "ghost" }); const del = createButton({ label: tx("Delete"), variant: "danger-ghost" }); reset.title = tx("ResetTip"); del.title = tx("DeleteTip"); reset.dataset.mainReset = ""; del.dataset.mainDelete = ""; const target = (): { at: Selection; kind: LibraryKind } | null => { const at = ctx.sel(); const kind = at?.kind; return at && at.name && (kind === "sangeun" || kind === "precombo") ? { at, kind } : null; }; reset.addEventListener("click", async () => { const got = target(); if (!got || !(await ctx.confirmLeave())) return; const { at, kind } = got; try { const look = await resetLibrary(kind, at.layer, at.owner ?? null, { 키: at.name, 미리보기: true, }); if (!look.걷힘) return void showToast(tx("Nothing"), "success"); const k = removedCount(ctx.loaded()); const message = fill(tx("ResetAsk"), { n: look.걷힘, k: k ? fill(tx("Restored"), { k }) : "", }); if (!(await askConfirm({ message, okLabel: tx("Reset"), danger: true }))) return; await resetLibrary(kind, at.layer, at.owner ?? null, { 키: at.name, 미리보기: false, 판: ctx.version(), }); await ctx.open(at); // 다시 그림 ctx.changed(); showToast(tx("Done"), "success"); } catch (error) { showToast(fill(tx("Failed"), { why: why(error) }), "error"); } }); del.addEventListener("click", async () => { const got = target(); if (!got || !(await ctx.confirmLeave())) return; const { at, kind } = got; const owner = at.owner ?? null; try { const tree = await fetchPrecomboTree(at.layer, owner, kind); const pick = { 경로: tree.자리?.[at.name] ?? [], 키: at.name }; const ask = fill(tx("DeleteAsk"), { name: at.label ?? at.name }); const ans = await askLibraryDelete(kind, at.layer, owner, [pick], ask, tx("Delete")); if (!ans.ok) return; await editPrecomboTree( at.layer, { op: "delete", 대상: [pick], ...(ans.sure ? { 확인: true } : {}) }, tree.판, owner, kind, ); const scope = { layer: at.layer, owner, canWrite: true } as Parameters[0]; await ctx.open(kind === "sangeun" ? sangeunList(scope) : precomboList(scope)); // 목록으로 ctx.changed(); showToast(tx("Deleted"), "success"); } catch (error) { showToast(fill(tx("Failed"), { why: why(error) }), "error"); } }); return { reset, del, update: (writable) => { const on = writable && !!target(); reset.hidden = del.hidden = !on; }, }; }