feat(M01): 일위대가 로직 기본 보기를 흐름 그림으로 · 상자에서 보고 고치기

방식 C 를 테스트 컨테이너에서 꺼내 로직 화면 기본 보기로 올림(옛 호표 표는 「고급」).
상자를 누르면 값·출처에 더해 쉬운 말 풀이와 원문 표 미리보기(걸린 줄 강조)가 펼쳐짐.
자체 로직은 [고치기] 로 상자 안에서 고치고 줄을 더하거나 지움 — 정본은 「본떠 만들기」 뒤.

- UI_Test_C·_Model·_Style → UI_Logic_Flow·_Model·_Style
- UI_Test_Pick 의 재료·기계 바꿔 고르기를 UI_Logic_Pick 에 합침
- 방식 B 의 설명 카드 로직을 UI_Logic_Note 로 옮겨 흐름 그림과 같이 씀

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
2026-09-21 09:35:33 +09:00
co-authored by Claude Opus 5
parent 2d24cd358e
commit 98e40f4496
13 changed files with 1213 additions and 728 deletions
+45 -3
View File
@@ -32,6 +32,7 @@ import {
} from "./M01_MasterData_UI_Logic_Api";
import { buildCalc } from "./M01_MasterData_UI_Logic_Calc";
import { buildEditor } from "./M01_MasterData_UI_Logic_Edit";
import { isOwnLogic, mountFlow } from "./M01_MasterData_UI_Logic_Flow";
import { buildList, logicId, type ListItem, type ListMark } from "./M01_MasterData_UI_Logic_List";
import { openPicker } from "./M01_MasterData_UI_Logic_Pick";
import type { SideHandle } from "./M01_MasterData_UI_Side";
@@ -59,6 +60,8 @@ interface Opened extends Draft {
}
const CACHE_KEY = "m01_logic_drafts";
/** 기본 보기 = 흐름 그림 · 「고급」 = 옛 호표 표 + 시험 계산 칸 */
const VIEW_KEY = "m01.logic.view";
const clone = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
function loadDrafts(): Record<string, Draft> {
@@ -83,6 +86,8 @@ export async function mountM01Logic(
let files: LogicFile[] = [];
let opened: Opened | null = null;
let calcInputs = "";
let view: "flow" | "advanced" =
sessionStorage.getItem(VIEW_KEY) === "advanced" ? "advanced" : "flow";
const editor = el("div", { className: "m01-logic__editor" });
const errors = el("div", { className: "m01-logic__reasons", attrs: { hidden: "" } });
@@ -129,6 +134,7 @@ export async function mountM01Logic(
if (opened.origKey !== null && now === opened.original) delete drafts[opened.id];
else drafts[opened.id] = pick(opened, clone(opened.row));
persist();
if (view === "flow") return; // 흐름 그림은 제 안에서 다시 셈 — 다시 그리면 펼친 상자가 접힘
const inputs = JSON.stringify(opened.row.입력 ?? []);
if (inputs !== calcInputs) drawCalc();
};
@@ -142,7 +148,7 @@ export async function mountM01Logic(
});
const drawCalc = (): void => {
if (!opened?.row) {
if (!opened?.row || view === "flow") {
calc.replaceChildren();
return;
}
@@ -162,10 +168,11 @@ export async function mountM01Logic(
};
const drawEditor = (): void => {
// 고른 로직이 없으면 목록 · 있으면 편집 + 시험 계산
// 고른 로직이 없으면 목록 · 있으면 흐름 그림(기본) 또는 고급 = 호표 표 + 시험 계산
const listing = !opened;
list.root.hidden = !listing;
editor.hidden = calc.hidden = back.hidden = listing;
editor.hidden = back.hidden = viewButton.hidden = listing;
calc.hidden = listing || view === "flow";
if (!opened) return;
if (!opened.row) {
editor.replaceChildren(
@@ -174,6 +181,23 @@ export async function mountM01Logic(
return;
}
const current = opened;
const shown = current.row as LogicRow;
if (view === "flow") {
mountFlow(editor, {
one: {
file: current.file,
version: current.version,
logic: shown,
blocked: false,
reasons: current.reasons,
prices: current.prices,
},
row: shown,
editable: isOwnLogic(current.file, shown),
onChange: touch,
});
return;
}
buildEditor(editor, {
row: current.row as LogicRow,
file: current.file,
@@ -373,6 +397,23 @@ export async function mountM01Logic(
}
};
const viewButton = createButton({
label: tx("View_Advanced"),
variant: "ghost",
onClick: () => {
view = view === "flow" ? "advanced" : "flow";
try {
sessionStorage.setItem(VIEW_KEY, view);
} catch {
/* 캐시가 막혀도 이 판 안에서는 바뀐 보기가 남음 */
}
viewButton.textContent = view === "flow" ? tx("View_Advanced") : tx("View_Flow");
editor.replaceChildren();
drawEditor();
drawCalc();
},
});
viewButton.textContent = view === "flow" ? tx("View_Advanced") : tx("View_Flow");
const saveButton = createButton({ label: tx("Bar_Save"), onClick: () => void onSave() });
const discardButton = createButton({
label: tx("Bar_Discard"),
@@ -384,6 +425,7 @@ export async function mountM01Logic(
children: [
back,
el("h2", { text: tx("Title") }),
viewButton,
pending,
createButton({ label: tx("Bar_New"), variant: "ghost", onClick: onNew }),
createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }),