From 047f3a2798d89ea3d5e0454c56f52d12ee4fb07f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 1 Oct 2026 20:35:39 +0900 Subject: [PATCH] =?UTF-8?q?fix(m02-df):=20=EC=9E=AC=EB=A3=8C=20=EC=9E=90?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=92=20=EB=AC=BC=EC=9D=8C=20=C2=B7=20=EC=83=81?= =?UTF-8?q?=EC=9E=90=20=EC=9D=B4=EB=A6=84=20=EC=B9=B8=20=C2=B7=20=EC=9E=91?= =?UTF-8?q?=EC=9D=80=20=EC=B9=B8=20=C2=B7=20=EC=8B=9C=EC=9E=91=20=EB=8B=A8?= =?UTF-8?q?=EC=B6=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 재료 선택 자리 · 조건 찾기를 서버 /values 에 slot(방식 · 기본 품목 · 범위 · 찾기 규칙) · value(이어진 알약 지금 값)로 물음 — 물음 서명에 들어가 값이 바뀌면 다시 받음(관경 600 → 400 = 116,600) - 상세 내용 설정에 「상자 이름」(입력 · 함수 상자 · 빈 이름 · 같은 이름 안 받음 · 출력은 출력 판 이름 칸) - 머리 로직 이름 · 결과 단위 칸 공용 작은 크기(compact) - 시작 화면 「새로 만들기」 단추가 칸 폭으로 늘어나지 않게 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- M02_MasterTemplete/DataFlow/M02_DF_Entry.ts | 30 ++++++++++++++--- .../DataFlow/M02_DF_Entry_Detail.ts | 32 +++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts b/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts index 3bec34b5a..c6f845eeb 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts @@ -89,6 +89,8 @@ const TEXT = { Diagram: ["다이어그램", "Diagram"], Summary: ["원가", "Cost"], Color: ["상자 색", "Box colour"], + BoxName: ["상자 이름", "Box name"], + NameTaken: ["「{v}」 — 같은 이름의 상자가 있음", "“{v}” — another box has this name"], Standards: ["표준 표", "Standards"], Logics: ["다른 로직을 못 읽음 — {v}", "Couldn't read other logics — {v}"], NoName: ["로직 이름을 넣어 주세요", "Enter a logic name"], @@ -307,6 +309,17 @@ function openDataFlow(): void { className: "m02-df__color", children: [el("span", { text: te("Color") }), colorBox], }); + // 상자 이름 — 입력 · 함수 상자(출력 상자는 출력 판의 이름 칸) · 빈 이름 · 같은 이름은 안 받음 + const boxName = createInputField({ label: te("BoxName"), compact: true }); + boxName.input.addEventListener("change", () => { + const box = lg.boxes.find((b) => b.id === pickedBox()); + if (!box) return; + const next = boxName.input.value.trim(); + const taken = lg.boxes.some((b) => b.id !== box.id && b.name === next); + if (taken) note.textContent = te("NameTaken", { v: next }); + if (!next || taken || next === box.name) return void (boxName.input.value = box.name); + edit(M.model.updateBox(lg, box.id, { name: next })); + }); // 판마다 제 상태를 쥠 — 종류마다 따로 두고 숨기기만(갈아끼우면 편집기가 있다고 여김) const funcHost = el("div"); const outHost = el("div"); @@ -315,7 +328,7 @@ function openDataFlow(): void { const linkHost = el("div"); const pillHost = el("div"); const detail = detailFrame(); - detail.settings.append(colorRow); + detail.settings.append(boxName.root, colorRow); detail.body.append(inHost, funcHost, outHost, linkHost, pillHost); const evalNow = (): CfEvalResult => { @@ -341,10 +354,12 @@ function openDataFlow(): void { /** 입력 값 다시 받기 — 물음이 바뀐 때만 · 같은 조작의 뒤끝이라 기록을 더 쌓지 않음 */ const refreshValues = (): void => { - const sig = JSON.stringify(queriesOf(lg).map((a) => a.q)); + // 조건 찾기 재료는 이어진 알약 값(지금 셈)을 함께 물음 — 값이 바뀌면 다시 받음 + const pill = (p: CfPort): unknown => result?.ports[p.box]?.[p.name] ?? null; + const sig = JSON.stringify(queriesOf(lg, pill, M.model.elementPort).map((a) => a.q)); if (sig === valueSig) return; valueSig = sig; - void fetchValues(M.api.cfValues, lg).then( + void fetchValues(M.api.cfValues, lg, pill, M.model.elementPort).then( (got) => { if (sig !== valueSig) return; values = got.values; @@ -410,6 +425,7 @@ function openDataFlow(): void { label: te("Name"), placeholder: te("NamePlaceholder"), value: lg.name, + compact: true, onInput: (v) => edit({ ...lg, name: v }), }); name.root.classList.add("m02-df__name"); @@ -418,6 +434,7 @@ function openDataFlow(): void { label: te("Unit"), options: unitList.map((u) => ({ value: u, text: u.replace(/^원\//, "") })), value: lg.resultUnit, + compact: true, onChange: (v) => edit({ ...lg, resultUnit: v }), }); @@ -458,6 +475,8 @@ function openDataFlow(): void { const box = lg.boxes.find((b) => b.id === pickedBox()); drawHead(detail, lg, pick); colorRow.hidden = !box; + boxName.root.hidden = !box || box.kind === "output"; + if (box && document.activeElement !== boxName.input) boxName.input.value = box.name; linkHost.hidden = pick?.kind !== "link"; pillHost.hidden = pick?.kind !== "pill"; if (pick?.kind === "link") @@ -638,6 +657,7 @@ function part(title: string, className: string, children: HTMLElement[]): HTMLEl }); } -function bigButton(label: string, onClick: () => void): HTMLButtonElement { - return createButton({ label, variant: "filled", size: "sm", onClick }); +/** 보통 크기 단추 — 칸 폭으로 늘어나지 않게 감쌈 */ +function bigButton(label: string, onClick: () => void): HTMLElement { + return el("div", { children: [createButton({ label, variant: "filled", size: "sm", onClick })] }); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts b/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts index fc27df74b..4cd321278 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Entry_Detail.ts @@ -10,7 +10,7 @@ import { el } from "@ui/ui_template_elements"; import { fetchLogics } from "../../M01_MasterData/M01_MasterData_UI_Logic_Api"; import type { CfAnswer, CfQuery } from "./M02_DF_Api"; import type { CfBreak, CfValues } from "./M02_DF_Eval"; -import type { CfLogic } from "./M02_DF_Model"; +import type { CfElementRef, CfLogic, CfPort } from "./M02_DF_Model"; const TEXT = { Detail: ["상세 내용", "Details"], @@ -169,14 +169,32 @@ export async function unitChoices(): Promise { return units.length ? units : ["원/㎡"]; } -/** 입력 값 물음 — 그룹 입력 상자의 요소(알약 = 요소 이름) · 표(알약 「표」) · 사용자 요소 · 변수는 Eval 이 스스로 봄 */ -export function queriesOf(lg: CfLogic): { q: CfQuery; box: string; port: string }[] { +/** 입력 값 물음 — 그룹 입력 상자의 요소(알약 = 요소 이름) · 재료 자리(알약 = 자리 이름 · 조건 찾기는 이어진 알약 값을 + * pill 로 읽어 넘김) · 표(알약 「표」) · 사용자 요소 · 변수는 Eval 이 스스로 봄 */ +export function queriesOf( + lg: CfLogic, + pill: (p: CfPort) => unknown = () => null, + elementPort: (e: CfElementRef) => string = (e) => e.name, +): { q: CfQuery; box: string; port: string }[] { return lg.boxes.flatMap((b) => { const inp = b.input; if (b.kind !== "input" || !inp || inp.mode !== "group" || !inp.group) return []; const group = inp.group; + const slots = inp.elements + .filter((e) => group === "재료" && e.mode && e.mode !== "fixed") + .map((e) => { + const port = elementPort(e); + const q: CfQuery = { + id: `${b.id}\u0001${port}`, + group, + unit: e.unit, + slot: { ...e, field: e.rule?.field, op: e.rule?.op }, + }; + if (e.mode === "rule" && e.rule) q.value = pill(e.rule.from); + return { q, box: b.id, port }; + }); const els = inp.elements - .filter((e) => e.ref && !e.ref.startsWith("UE-")) + .filter((e) => e.ref && !e.ref.startsWith("UE-") && !slots.some((s) => s.port === e.name)) .map((e) => { // 기계 요소는 「키#값 칸」 — 서버에는 키와 칸(field)을 따로 넘김 const [ref, field] = group === "기계" ? e.ref.split("#") : [e.ref, undefined]; @@ -187,7 +205,7 @@ export function queriesOf(lg: CfLogic): { q: CfQuery; box: string; port: string const table = inp.table ? [{ q: { id: `${b.id}\u0001표`, group, table: inp.table }, box: b.id, port: "표" }] : []; - return [...els, ...table]; + return [...slots, ...els, ...table]; }); } @@ -195,8 +213,10 @@ export function queriesOf(lg: CfLogic): { q: CfQuery; box: string; port: string export async function fetchValues( ask: (qs: CfQuery[], els: never) => Promise, lg: CfLogic, + pill?: (p: CfPort) => unknown, + elementPort?: (e: CfElementRef) => string, ): Promise<{ values: CfValues; errors: string[] }> { - const asks = queriesOf(lg); + const asks = queriesOf(lg, pill, elementPort); if (!asks.length) return { values: {}, errors: [] }; const answers = await ask( asks.map((a) => a.q),