- ui_template_equation(.ts · _line.ts · .css): 분수 · 첨자 · 괄호 · % · 조건 칩 · 하위 로직 블록(접힘) · 결과명 = 이름 식 = 값 식 = 결과 + 변수 풀이 · 부품 모음 견본 한 줄 - M01 LogicLab_Equation: 옵션 가로(도구줄 + 값 칸 · 찾기형 · 말줄임) · 줄 고르면 식 칸 + 숨은 아이콘 줄 · 서버 미리 셈 다시 그림 · 고침 목록 캐시 → [수식 저장](개정 확인) · 떠나기 전 버림 물음 - 변수 창(이름 · 기호 · 위 · 아래첨자 · 단위 · 값 · 보이는 자리) · 변수 더함 · 지움(쓰는 곳 있으면 서버 까닭 그대로) - 보기 고르기 = 수식 첫 탭 · 단가산출 상세 · 시트 · 인력재료경비 · 시험계산 · 텍스트 수식은 뒤로(걷지 않음) - test_796_2_equation_screen 새로 · test_794_1 기대 갱신(첫 탭 수식 · LogicLab 700 줄 상한 — 수식 이음 14 줄) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
248 lines
8.5 KiB
TypeScript
248 lines
8.5 KiB
TypeScript
/* =============================================================================
|
|
* M01_MasterData_UI_LogicLab_EquationVar.ts
|
|
* 수식 탭 변수 창(PLAN 796-2 · 요구 657) — 변수 고침 · 변수 더함 · 변수 지움(쓰는 곳 있으면 막고 목록 보임).
|
|
* 고침 창 = 이름 · 기호 · 위첨자 · 아래첨자 · 단위 · 값(입력 변수만 · 시험 값 · 저장 안 함) · [적용] · [변수 지움]
|
|
* 더함 창 = 갈래(입력 · 중간) · 열쇠 · 이름 · 기호 · 첨자 · 단위 · 기본값(입력) | 식 · 자리(중간)
|
|
* 적용 = 부르는 쪽 `commit` 이 서버 미리 셈(`/logic/equation/preview`)을 거쳐 되면 [] · 안 되면 까닭 — 까닭은 창 안 띠에 그대로
|
|
* 부품 = 모달 · 입력 · 고르기 · 단추 · 띠 알림 · 열쇠 · 식 검사 · 이름 겹침은 서버 몫
|
|
* ========================================================================== */
|
|
|
|
import type { EqVarRow } from "@ui/ui_template_equation";
|
|
import { createBanner } from "@ui/ui_template_banner";
|
|
import { createButton } from "@ui/ui_template_button";
|
|
import { el } from "@ui/ui_template_elements_base";
|
|
import { createInputField, createSelectField } from "@ui/ui_template_input";
|
|
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import { addEdit, varEdits, type EqEdit, type VarForm } from "./logic/M01_Logic_Equation_Model";
|
|
|
|
const TEXT = {
|
|
VarTitle: ["변수 고침", "Edit variable"],
|
|
AddTitle: ["변수 더함", "Add variable"],
|
|
Close: ["닫기", "Close"],
|
|
Apply: ["적용", "Apply"],
|
|
Drop: ["변수 지움", "Delete variable"],
|
|
Key: ["열쇠 (식이 부르는 이름 · 안 바뀜)", "Key (used by formulas · fixed)"],
|
|
Name: ["이름", "Name"],
|
|
Sign: ["기호", "Symbol"],
|
|
Up: ["위첨자", "Superscript"],
|
|
Down: ["아래첨자", "Subscript"],
|
|
Unit: ["단위", "Unit"],
|
|
Value: ["값 (시험 값 · 저장 안 함)", "Value (trial · not saved)"],
|
|
Digits: ["보이는 자리 (0~6 · 빈 칸 = 전체 자리)", "Digits (0-6 · blank = full)"],
|
|
Kind: ["갈래", "Kind"],
|
|
Input: ["입력", "Input"],
|
|
Mid: ["중간", "Intermediate"],
|
|
Default: ["기본값", "Default"],
|
|
Formula: ["식", "Formula"],
|
|
After: ["자리", "Place"],
|
|
End: ["맨 끝", "End"],
|
|
Top: ["맨 위", "Top"],
|
|
Why: ["안 됨", "Not applied"],
|
|
Hint: ["이름 · 기호 · 첨자를 바꾸면 쓰는 모든 식에 함께 보임", "Renames show in every formula"],
|
|
} as const satisfies Record<string, readonly [string, string]>;
|
|
const t = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0];
|
|
|
|
export type Commit = (edits: EqEdit[]) => Promise<string[]>;
|
|
|
|
const grid = (...kids: HTMLElement[]): HTMLElement =>
|
|
el("div", { className: "m01-eq__form", children: kids });
|
|
|
|
/** 까닭 띠 — 있으면 보임 · 없으면 비움 */
|
|
function reasonBox(): { root: HTMLElement; set: (why: string[]) => void } {
|
|
const root = el("div", { className: "m01-eq__why" });
|
|
return {
|
|
root,
|
|
set: (why) =>
|
|
root.replaceChildren(
|
|
...(why.length
|
|
? [createBanner({ tone: "danger", title: t("Why"), text: why.join(" · ") })]
|
|
: []),
|
|
),
|
|
};
|
|
}
|
|
|
|
export interface VarDialog {
|
|
row: EqVarRow;
|
|
/** 입력 변수 시험 값(옵션 칸 글) */
|
|
value: string;
|
|
commit: Commit;
|
|
/** 시험 값을 바꿈(저장 안 함) */
|
|
onValue: (value: string) => void;
|
|
/** 변수를 지운 뒤 — 시험 값 비움 */
|
|
onDropped: (key: string) => void;
|
|
}
|
|
|
|
export function openVarDialog(o: VarDialog): void {
|
|
const v = o.row;
|
|
const field = (label: string, value: string): ReturnType<typeof createInputField> =>
|
|
createInputField({ label, value, size: 28 });
|
|
const name = field(t("Name"), v.이름);
|
|
const sign = field(t("Sign"), v.기호);
|
|
const up = field(t("Up"), v.위);
|
|
const down = field(t("Down"), v.아래);
|
|
const unit = field(t("Unit"), v.단위);
|
|
const val = field(t("Value"), o.value);
|
|
const shown = v.표시 === undefined ? "" : String(v.표시);
|
|
const digits = field(t("Digits"), shown);
|
|
const why = reasonBox();
|
|
openModal({
|
|
title: `${t("VarTitle")} · ${v.열쇠}`,
|
|
closeLabel: t("Close"),
|
|
width: "40rem",
|
|
mount: (body, close, actions) => {
|
|
body.append(
|
|
el("p", { className: "ui-text ui-text--muted", text: t("Hint") }),
|
|
grid(
|
|
name.root,
|
|
sign.root,
|
|
up.root,
|
|
down.root,
|
|
unit.root,
|
|
...(v.갈래 === "입력" ? [val.root] : [digits.root]),
|
|
),
|
|
why.root,
|
|
);
|
|
actions.append(
|
|
createButton({
|
|
label: t("Drop"),
|
|
variant: "danger-ghost",
|
|
onClick: async () => {
|
|
const bad = await o.commit([{ 할일: "지움", 열쇠: v.열쇠 }]);
|
|
why.set(bad);
|
|
if (bad.length) return;
|
|
o.onDropped(v.열쇠);
|
|
close();
|
|
},
|
|
}),
|
|
createButton({
|
|
label: t("Apply"),
|
|
onClick: async () => {
|
|
const now: VarForm = {
|
|
열쇠: v.열쇠,
|
|
이름: name.input.value.trim(),
|
|
기호: sign.input.value.trim(),
|
|
위: up.input.value.trim(),
|
|
아래: down.input.value.trim(),
|
|
단위: unit.input.value.trim(),
|
|
표시: digits.input.value.trim(),
|
|
};
|
|
const edits = varEdits(
|
|
{
|
|
열쇠: v.열쇠,
|
|
이름: v.이름,
|
|
기호: v.기호,
|
|
위: v.위,
|
|
아래: v.아래,
|
|
단위: v.단위,
|
|
표시: shown,
|
|
},
|
|
now,
|
|
);
|
|
const bad = edits.length ? await o.commit(edits) : [];
|
|
why.set(bad);
|
|
if (bad.length) return;
|
|
if (v.갈래 === "입력" && val.input.value.trim() !== o.value)
|
|
o.onValue(val.input.value.trim());
|
|
close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
}
|
|
|
|
export interface AddDialog {
|
|
/** 중간 줄 열쇠 차례(자리 고르기) */
|
|
mids: string[];
|
|
commit: Commit;
|
|
}
|
|
|
|
export function openAddDialog(o: AddDialog): void {
|
|
const field = (label: string, value = ""): ReturnType<typeof createInputField> =>
|
|
createInputField({ label, value, size: 28 });
|
|
const kind = createSelectField({
|
|
label: t("Kind"),
|
|
options: [
|
|
{ value: "입력", text: t("Input") },
|
|
{ value: "중간", text: t("Mid") },
|
|
],
|
|
value: "입력",
|
|
size: 28,
|
|
});
|
|
const key = field(t("Key"));
|
|
const name = field(t("Name"));
|
|
const sign = field(t("Sign"));
|
|
const up = field(t("Up"));
|
|
const down = field(t("Down"));
|
|
const unit = field(t("Unit"));
|
|
const dflt = field(t("Default"));
|
|
const expr = field(t("Formula"));
|
|
const place = createSelectField({
|
|
label: t("After"),
|
|
options: [
|
|
{ value: "", text: t("End") },
|
|
{ value: "\u0000top", text: t("Top") },
|
|
...o.mids.map((m) => ({ value: m, text: m })),
|
|
],
|
|
value: "",
|
|
size: 28,
|
|
});
|
|
const why = reasonBox();
|
|
const sync = (): void => {
|
|
const mid = kind.select.value === "중간";
|
|
dflt.root.hidden = mid;
|
|
expr.root.hidden = place.root.hidden = !mid;
|
|
};
|
|
kind.select.addEventListener("change", sync);
|
|
sync();
|
|
openModal({
|
|
title: t("AddTitle"),
|
|
closeLabel: t("Close"),
|
|
width: "40rem",
|
|
mount: (body, close, actions) => {
|
|
body.append(
|
|
grid(
|
|
kind.root,
|
|
key.root,
|
|
name.root,
|
|
sign.root,
|
|
up.root,
|
|
down.root,
|
|
unit.root,
|
|
dflt.root,
|
|
expr.root,
|
|
place.root,
|
|
),
|
|
why.root,
|
|
);
|
|
actions.append(
|
|
createButton({
|
|
label: t("Apply"),
|
|
onClick: async () => {
|
|
const mid = kind.select.value === "중간";
|
|
const at = place.select.value;
|
|
const edit = addEdit(
|
|
{
|
|
열쇠: key.input.value,
|
|
이름: name.input.value,
|
|
기호: sign.input.value,
|
|
위: up.input.value,
|
|
아래: down.input.value,
|
|
단위: unit.input.value,
|
|
갈래: mid ? "중간" : "입력",
|
|
기본값: dflt.input.value.trim(),
|
|
식: expr.input.value.trim(),
|
|
},
|
|
at === "\u0000top" ? "" : at || undefined,
|
|
);
|
|
const bad = await o.commit([edit]);
|
|
why.set(bad);
|
|
if (!bad.length) close();
|
|
},
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
}
|