diff --git a/M02_MasterTemplete/CostFlow/M02_CF_Diagram.css b/M02_MasterTemplete/CostFlow/M02_CF_Diagram.css index fc9c8ee8f..467e29d7a 100644 --- a/M02_MasterTemplete/CostFlow/M02_CF_Diagram.css +++ b/M02_MasterTemplete/CostFlow/M02_CF_Diagram.css @@ -256,3 +256,21 @@ stroke-width: 2; stroke-dasharray: 5 4; } + +/* 함수 상자 흐린 「+」 받는 자리 */ +.m02-cfdg__pill.is-plus { + justify-content: center; + min-width: 2rem; + border-style: dashed; + border-color: var(--color-text-muted); + background: transparent; + color: var(--color-text-muted); + opacity: 0.7; +} + +.m02-cfdg__pill.is-plus:hover, +.m02-cfdg.is-linking .m02-cfdg__pill.is-plus.is-target { + opacity: 1; + border-color: var(--cfdg-in); + color: var(--cfdg-in); +} diff --git a/M02_MasterTemplete/CostFlow/M02_CF_Diagram.ts b/M02_MasterTemplete/CostFlow/M02_CF_Diagram.ts index d8f55a7b5..e4febfee2 100644 --- a/M02_MasterTemplete/CostFlow/M02_CF_Diagram.ts +++ b/M02_MasterTemplete/CostFlow/M02_CF_Diagram.ts @@ -53,6 +53,10 @@ const TEXT = { From: ["어디서", "From"], To: ["어디로", "To"], Value: ["지금 값", "Now"], + PlusIn: [ + "받는 자리 — 내보내는 알약을 여기에 놓으면 받는 알약이 생김", + "Drop an output pill here to add an input", + ], LineOf: ["선 · {a} → {b}", "Line · {a} → {b}"], Hint: [ "상자 머리를 끌어 옮김 · 빈 곳 끌어 판 옮김 · 휠로 확대", @@ -124,6 +128,9 @@ export function resolveDrop( if (target.box === from.box) return { why: tt("SameBox") }; if (target.dir === "out") return { why: tt("NotIn") }; const box = lg.boxes.find((b) => b.id === target.box); + // 함수 상자 몸통 · 「+」 받는 자리 — 보낸 알약 이름으로 받는 알약을 새로(Model 이 만들고 이름이 겹치면 「이름 (2)」) + if (box?.kind === "function" && target.name === null) + return { to: { box: box.id, name: from.name } }; const ins = box ? pillsOf(box).ins : []; if (!box || !ins.length) return { why: tt("NoIn") }; const name = @@ -300,6 +307,28 @@ export function renderDiagram( return btn; }; + /** 함수 상자 왼쪽 흐린 「+」 — 여기(또는 몸통)에 놓으면 받는 알약이 새로 생김 */ + const plusSlot = (b: CfBox): HTMLElement => { + const slot = el("button", { + className: `m02-cfdg__pill is-in is-plus${st.linking && st.linking.box !== b.id ? " is-target" : ""}`, + text: "+", + attrs: { + type: "button", + "data-box": b.id, + "data-dir": "in", + "data-plus": "1", + title: tt("PlusIn"), + }, + }); + slot.addEventListener("click", (ev) => { + ev.stopPropagation(); + if (st.linking) return tryLink(st.linking, { box: b.id, name: null, dir: "in" }); + st.note = tt("PlusIn"); + redraw(); + }); + return slot; + }; + const boxEl = (b: CfBox): HTMLElement => { const { ins, outs } = pillsOf(b); const head = el("div", { @@ -323,7 +352,13 @@ export function renderDiagram( el("div", { className: "m02-cfdg__pills", children: [ - el("div", { className: "m02-cfdg__ins", children: ins.map((p) => pillEl(b, p, "in")) }), + el("div", { + className: "m02-cfdg__ins", + children: [ + ...ins.map((p) => pillEl(b, p, "in")), + ...(b.kind === "function" ? [plusSlot(b)] : []), + ], + }), el("div", { className: "m02-cfdg__outs", children: outs.map((p) => pillEl(b, p, "out")),