feat(m02-df): 단가산출 흐름도(시험) 들어가는 길 · 페이지 · 상세 내용 머리
- 시험판 ▾ 목록 「단가산출 흐름도(시험)」 · main.py DataFlow 서버 등록 줄 - 페이지 제목 「단가산출 흐름도」 · 단추 공용 작은 크기(size "sm") · 큰 단추 css 덮기 없앰 - 상세 내용(옛 「고른 상자」) — 머리 왼쪽 「상세 내용」 · 오른쪽 고른 상자 제목 · 알약이면 알약 이름 · 설정 · 본문 · 경고 차례 - 고름 = 상자 · 선 · 알약 — 종류마다 판을 바꿔 끼움(선 = 보내는 곳 · 받는 곳 · 선 규칙 글 · 알약 = 온 길 · 가는 길) - 경고 = 검사 · 계산 글 가운데 고른 것 몫만 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
/* =============================================================================
|
||||
* M02_DF_Entry.ts
|
||||
* 단가산출 다이어그램(시험) — 들어가는 단추 · 화면 전체를 덮는 시험판 페이지 · 시작 화면 · 고치는 화면 · 잇기
|
||||
* 모양은 `docs/raw/M01_쉬운단가/원가다이어그램_정함.md` 10 · 12 · 13 · 이 폴더째 지워도 됨
|
||||
* 단가산출 흐름도(시험) — 들어가는 단추 · 화면 전체를 덮는 시험판 페이지 · 시작 화면 · 고치는 화면 · 잇기
|
||||
* 모양은 `docs/raw/M01_쉬운단가/흐름도_정함.md`(1판 약속 = 원가다이어그램_정함.md) · 이 폴더째 지워도 됨
|
||||
* (폴더 밖 참조는 M01 LogicLab.ts 두 줄 + 서버 등록 한 줄). DataFlow 의 다른 파일은 import() 로 늦게.
|
||||
* 잇기: 열기(cfGet · 견본 sampleLoad) → 입력 값(cfValues) → renderDiagram · 고른 상자 판(종류별) · renderSummary
|
||||
* → 바뀔 때마다 기록(객체 그대로) · evalLogic → 저장 cfSave(서버 원가와 화면 대조) · 지우기 · 실행 취소 · Ctrl+Z · Ctrl+S.
|
||||
* 바깥 페이지 하나만 세로로 밀림(안쪽 스크롤 없음).
|
||||
* 잇기: 열기 → 입력 값(cfValues) → renderDiagram · 「상세 내용」(상자 · 알약 · 선 고름마다 판을 바꿔 끼움 —
|
||||
* M02_DF_Entry_Detail) · renderSummary → 바뀔 때마다 기록(객체 그대로) · evalLogic → 저장(서버 대조) · 지우기 ·
|
||||
* 실행 취소 · Ctrl+Z · Ctrl+S. 단추 · 칸은 공용 작은 크기. 바깥 페이지 하나만 세로로 밀림.
|
||||
* ========================================================================== */
|
||||
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
@@ -23,6 +23,14 @@ import { fetchLogics } from "../../M01_MasterData/M01_MasterData_UI_Logic_Api";
|
||||
import type { CfListItem, CfQuery } from "./M02_DF_Api";
|
||||
import type { CfEvalResult, CfValues } from "./M02_DF_Eval";
|
||||
import type { CfKind, CfLogic, CfPort } from "./M02_DF_Model";
|
||||
import {
|
||||
detailFrame,
|
||||
drawHead,
|
||||
drawWarnings,
|
||||
lineBody,
|
||||
pillBody,
|
||||
type DfPick,
|
||||
} from "./M02_DF_Entry_Detail";
|
||||
import "./M02_DF_Entry.css";
|
||||
|
||||
/** DataFlow 의 다른 파일 — 페이지를 열 때 한 번에 불러옴(엔진이 M01 첫 묶음에 안 들어가게) */
|
||||
@@ -37,7 +45,8 @@ const loadMods = () =>
|
||||
import("./M02_DF_Summary"),
|
||||
import("./M02_DF_Sample"),
|
||||
import("./M02_DF_InputPanel"),
|
||||
]).then(([model, ev, api, diagram, func, output, summary, sample, input]) => ({
|
||||
import("./M02_DF_Rule"),
|
||||
]).then(([model, ev, api, diagram, func, output, summary, sample, input, rule]) => ({
|
||||
model,
|
||||
ev,
|
||||
api,
|
||||
@@ -47,12 +56,13 @@ const loadMods = () =>
|
||||
summary,
|
||||
sample,
|
||||
input,
|
||||
rule,
|
||||
}));
|
||||
type Mods = Awaited<ReturnType<typeof loadMods>>;
|
||||
|
||||
const TEXT = {
|
||||
Open: ["단가산출 다이어그램(시험)", "Cost diagram (trial)"],
|
||||
Title: ["단가산출 다이어그램", "Cost diagram"],
|
||||
Open: ["단가산출 흐름도(시험)", "Cost flow (trial)"],
|
||||
Title: ["단가산출 흐름도", "Cost flow"],
|
||||
Trial: ["시험판 — 임시 폴더에 저장", "Trial — saved in a temporary folder"],
|
||||
Close: ["닫기", "Close"],
|
||||
Loading: ["불러오는 중", "Loading"],
|
||||
@@ -69,8 +79,6 @@ const TEXT = {
|
||||
Delete: ["지우기", "Delete"],
|
||||
Unsaved: ["저장 안 함", "Not saved"],
|
||||
Diagram: ["다이어그램", "Diagram"],
|
||||
Box: ["고른 상자", "Chosen box"],
|
||||
PickBox: ["다이어그램에서 상자를 고르면 여기에 그 상자 판이 보임", "Choose a box in the diagram"],
|
||||
Summary: ["원가", "Cost"],
|
||||
Color: ["상자 색", "Box colour"],
|
||||
NoName: ["로직 이름을 넣어 주세요", "Enter a logic name"],
|
||||
@@ -164,7 +172,7 @@ async function fetchValues(M: Mods, lg: CfLogic): Promise<{ values: CfValues; er
|
||||
|
||||
/** M01 단가산출 막대의 다른 시험판 단추 옆에 놓는 단추 — 누르면 시험판 페이지 */
|
||||
export function createDataFlowButton(): HTMLButtonElement {
|
||||
return createButton({ label: te("Open"), variant: "filled", onClick: openDataFlow });
|
||||
return createButton({ label: te("Open"), variant: "filled", size: "sm", onClick: openDataFlow });
|
||||
}
|
||||
|
||||
function openDataFlow(): void {
|
||||
@@ -181,6 +189,7 @@ function openDataFlow(): void {
|
||||
const close = createButton({
|
||||
label: te("Close"),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () =>
|
||||
void (async () => {
|
||||
if (await mayLeave()) paper ? void showStart() : shut();
|
||||
@@ -317,7 +326,8 @@ function openDataFlow(): void {
|
||||
let lg = first;
|
||||
let savedLg: CfLogic | null = saved ? first : null;
|
||||
let stack: CfLogic[] = [];
|
||||
let selected: string | null = null;
|
||||
let pick: DfPick = null;
|
||||
const pickedBox = (): string | null => (pick?.kind === "box" ? pick.id : null);
|
||||
let values: CfValues = {};
|
||||
let valueSig = "";
|
||||
let notes: string[] = [];
|
||||
@@ -327,14 +337,14 @@ function openDataFlow(): void {
|
||||
const unsaved = createTag(te("Unsaved"), "warning");
|
||||
const note = el("p", { className: "m02-df__note", attrs: { role: "status" } });
|
||||
const diagramHost = el("div");
|
||||
const pickHint = muted(te("PickBox"));
|
||||
// 상자마다 색 바꾸기(정함 점검 9) — 고른 상자 판 머리에 하나
|
||||
const colorBox = el("input", {
|
||||
className: "m02-df__color-input",
|
||||
attrs: { type: "color", "aria-label": te("Color") },
|
||||
});
|
||||
colorBox.addEventListener("change", () => {
|
||||
if (selected) edit(M.model.setColor(lg, selected, colorBox.value));
|
||||
const id = pickedBox();
|
||||
if (id) edit(M.model.setColor(lg, id, colorBox.value));
|
||||
});
|
||||
const colorRow = el("label", {
|
||||
className: "m02-df__color",
|
||||
@@ -345,6 +355,11 @@ function openDataFlow(): void {
|
||||
const outHost = el("div");
|
||||
const inHost = el("div");
|
||||
const summaryHost = el("div");
|
||||
const lineHost = el("div");
|
||||
const pillHost = el("div");
|
||||
const detail = detailFrame();
|
||||
detail.settings.append(colorRow);
|
||||
detail.body.append(inHost, funcHost, outHost, lineHost, pillHost);
|
||||
|
||||
const evalNow = (): CfEvalResult => {
|
||||
try {
|
||||
@@ -403,7 +418,16 @@ function openDataFlow(): void {
|
||||
|
||||
const diagramOn = {
|
||||
select: (id: string | null): void => {
|
||||
selected = id;
|
||||
pick = !id
|
||||
? null
|
||||
: lg.links.some((l) => l.id === id)
|
||||
? { kind: "link", id }
|
||||
: { kind: "box", id };
|
||||
drawPanel();
|
||||
},
|
||||
/** 알약 고름 — 다이어그램(sub8)이 알려 주면 상세 내용이 알약 판으로 */
|
||||
pill: (p: { box: string; name: string; dir: "in" | "out" } | null): void => {
|
||||
pick = p ? { kind: "pill", ...p } : null;
|
||||
drawPanel();
|
||||
},
|
||||
move: (id: string, x: number, y: number): void => edit(M.model.moveBox(lg, id, x, y)),
|
||||
@@ -416,19 +440,50 @@ function openDataFlow(): void {
|
||||
unlink: (id: string): void => editFromDiagram(M.model.unlink(lg, id)),
|
||||
add: (kind: CfKind, x: number, y: number): void => {
|
||||
const got = M.model.addBox(lg, kind, x, y);
|
||||
selected = got.box.id;
|
||||
pick = { kind: "box", id: got.box.id };
|
||||
editFromDiagram(got.lg);
|
||||
},
|
||||
remove: (id: string): void => {
|
||||
if (selected === id) selected = null;
|
||||
if (pickedBox() === id) pick = null;
|
||||
editFromDiagram(M.model.removeBox(lg, id));
|
||||
},
|
||||
};
|
||||
|
||||
const drawPanel = (): void => {
|
||||
const box = lg.boxes.find((b) => b.id === selected);
|
||||
pickHint.hidden = !!box;
|
||||
const boxId = pickedBox();
|
||||
if (boxId && !lg.boxes.some((b) => b.id === boxId)) pick = null;
|
||||
const box = lg.boxes.find((b) => b.id === pickedBox());
|
||||
drawHead(detail, lg, pick);
|
||||
colorRow.hidden = !box;
|
||||
lineHost.hidden = pick?.kind !== "link";
|
||||
pillHost.hidden = pick?.kind !== "pill";
|
||||
if (pick?.kind === "link") {
|
||||
const linkId = pick.id;
|
||||
const rule = lg.links.find((l) => l.id === linkId)?.rule;
|
||||
let text: string | null = null;
|
||||
try {
|
||||
text = rule ? M.rule.ruleLabel(rule) : null;
|
||||
} catch {
|
||||
text = rule ? rule.kind : null;
|
||||
}
|
||||
lineBody(lineHost, lg, pick.id, text);
|
||||
}
|
||||
if (pick?.kind === "pill") {
|
||||
const port = { box: pick.box, name: pick.name };
|
||||
pillBody(
|
||||
pillHost,
|
||||
lg,
|
||||
() => M.model.traceBack(lg, port),
|
||||
() => M.model.traceForward(lg, port),
|
||||
);
|
||||
}
|
||||
let checks: string[] = [];
|
||||
try {
|
||||
checks = M.ev.checkLogic(lg, values);
|
||||
} catch {
|
||||
checks = [];
|
||||
}
|
||||
drawWarnings(detail, lg, pick, [...checks, ...(result?.errors ?? [])]);
|
||||
if (box && /^#[0-9a-f]{6}$/i.test(box.color)) colorBox.value = box.color;
|
||||
funcHost.hidden = box?.kind !== "function";
|
||||
outHost.hidden = box?.kind !== "output";
|
||||
@@ -445,6 +500,8 @@ function openDataFlow(): void {
|
||||
unsaved.hidden = lg === savedLg;
|
||||
undo.disabled = !stack.length;
|
||||
deleteButton.hidden = !lg.key;
|
||||
const selected =
|
||||
pick?.kind === "box" || pick?.kind === "link" ? pick.id : (pick?.box ?? null);
|
||||
M.diagram.renderDiagram(diagramHost, lg, diagramOn, { values: result.ports, selected });
|
||||
drawPanel();
|
||||
// 「고칠 것」 검사가 받은 입력 값으로 하도록 값도 넘김 — Summary 가 opts.values 를 받으면 쓰임
|
||||
@@ -517,10 +574,11 @@ function openDataFlow(): void {
|
||||
}
|
||||
};
|
||||
|
||||
const undo = createButton({ label: te("Undo"), variant: "ghost", onClick: doUndo });
|
||||
const undo = createButton({ label: te("Undo"), variant: "ghost", size: "sm", onClick: doUndo });
|
||||
const deleteButton = createButton({
|
||||
label: te("Delete"),
|
||||
variant: "danger",
|
||||
size: "sm",
|
||||
onClick: () => void doDelete(),
|
||||
});
|
||||
strip.replaceChildren(
|
||||
@@ -531,7 +589,7 @@ function openDataFlow(): void {
|
||||
className: "m02-df__actions",
|
||||
children: [
|
||||
undo,
|
||||
createButton({ label: te("Save"), onClick: () => void save() }),
|
||||
createButton({ label: te("Save"), size: "sm", onClick: () => void save() }),
|
||||
deleteButton,
|
||||
],
|
||||
}),
|
||||
@@ -544,10 +602,7 @@ function openDataFlow(): void {
|
||||
part(te("Diagram"), "m02-df__diagram", [diagramHost]),
|
||||
el("div", {
|
||||
className: "m02-df__lower",
|
||||
children: [
|
||||
part(te("Box"), "m02-df__box", [pickHint, colorRow, inHost, funcHost, outHost]),
|
||||
part(te("Summary"), "m02-df__summary", [summaryHost]),
|
||||
],
|
||||
children: [detail.root, part(te("Summary"), "m02-df__summary", [summaryHost])],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@@ -573,7 +628,5 @@ function part(title: string, className: string, children: HTMLElement[]): HTMLEl
|
||||
}
|
||||
|
||||
function bigButton(label: string, onClick: () => void): HTMLButtonElement {
|
||||
const button = createButton({ label, variant: "filled", onClick });
|
||||
button.classList.add("m02-df__big");
|
||||
return button;
|
||||
return createButton({ label, variant: "filled", size: "sm", onClick });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user