- 머리 단추 차례 … [새로고침] [초기화] [삭제] [저장] — 새 Main_Actions(쓰기 권한 있는 층만 보임) - [삭제] = 735 경고 창(쓰는 곳 표 · 키) → 나무 지우기 → 목록으로 · 쓰는 곳 없음 = 한 줄 물음 - [초기화] = 서버 -reset 미리 보기(걷힐 고침 수 · 뺀 줄 되살림 수) → 물음 → 그 층에 씀 → 다시 그림 · 저장 안 한 고침은 먼저 물음 · 옵션 · 기본정보 그대로 - 출처 꼬리표(본뜬) — 순수 logic/M02_Born_Tag · 단가산출 = 이름 옆 글 꼬리표(공용 Ho 꼬리표 칸을 이름 줄 안으로 · 첫 줄 보임 · 둘째 줄 풍선) · 일위대가표 격자 = 비고 칸 글 + 풍선 · 줄 표 = 이름 끝 - 시험: 새 test_740_2 · 낡은 시험 4 고침(test_727 · 717_2b · 704_3 · 704_4 — 모델 · 격자 파일을 이어 붙여 도는 시험이라 새 꼬리표 글 파일을 목록에 더함) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
157 lines
6.4 KiB
TypeScript
157 lines
6.4 KiB
TypeScript
/* =============================================================================
|
|
* 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, string | number>): 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<boolean>;
|
|
open: (sel: Selection | null) => Promise<void>;
|
|
/** 좌측 목록을 다시 읽음 */
|
|
changed: () => void;
|
|
}
|
|
|
|
/** 문서 안 모든 `뺀줄` 길이 합(단가산출 · 일위대가 줄 안 덮개까지) */
|
|
function removedCount(node: unknown): number {
|
|
if (Array.isArray(node)) return node.reduce<number>((n, x) => n + removedCount(x), 0);
|
|
if (!node || typeof node !== "object") return 0;
|
|
return Object.entries(node as Record<string, unknown>).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<typeof sangeunList>[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;
|
|
},
|
|
};
|
|
}
|