feat(M01): 단가산출 로직 상세 차례 · 오른쪽 흐름도/원문 두 패널
- 좌측 차례 = 기본정보 › 시험 계산 › 단가산출 상세 › 텍스트 수식 - 오른쪽 패널 둘(흐름도 · 원문) — 하나만 열림 · 둘 다 접기 · 머리 토글 단추 · 열린 패널 계정 취향(m01-logic-panel) - 흐름도 패널 접힘을 부르는 쪽이 정하는 길(folded · onFold · fold) - 원문 패널 = 출처 글로 품셈 본문 절을 찾아 보임(GET /api/m01/logic/source) · 못 찾으면 「원문 연결 없음」 + 출처 - 시험 test_m01_logic_source.py · 화면 시나리오 ui_checks/44-4_로직상세_패널.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
@@ -190,6 +190,12 @@ def get_logic(key: str) -> dict:
|
||||
return _call(store.logic, key)
|
||||
|
||||
|
||||
@router.get("/logic/source")
|
||||
def get_logic_source(src: str) -> dict:
|
||||
"""로직 원문 — 출처 글로 찾은 품셈 본문 절(읽기만)."""
|
||||
return _call(make.source, src)
|
||||
|
||||
|
||||
@router.get("/logic/auto")
|
||||
def get_logic_auto(key: str) -> dict:
|
||||
"""견본 입력 — 마스터 보기용 자동값(프로젝트 기본값 아님) · 흐름 로직은 설계 예시값."""
|
||||
|
||||
@@ -103,6 +103,79 @@ def sections(book: str, division: str = "", chapter: str = "", q: str = "", limi
|
||||
return {"total": len(hits), "sections": hits[: min(max(limit, 1), 1000)]}
|
||||
|
||||
|
||||
# ── 로직 원문(출처 글 → 품셈 본문 절) ──────────────────────────────────
|
||||
_NUMBER = re.compile(r"(?<![\d-])\d+(?:-\d+)+(?![\d-])")
|
||||
|
||||
|
||||
def _blocks(lines: list[str]) -> list[dict]:
|
||||
"""md 줄 → 화면 조각 — 제목 {h} · 표 {table} · 글 {p} · 주석 줄은 뺌."""
|
||||
out: list[dict] = []
|
||||
for line in lines:
|
||||
text = line.strip()
|
||||
if not text or text.startswith("<!--"):
|
||||
continue
|
||||
if text.startswith("#"):
|
||||
out.append({"h": len(text) - len(text.lstrip("#")), "text": text.lstrip("#").strip()})
|
||||
elif text.startswith("|"):
|
||||
cells = [c.strip() for c in text.strip("|").split("|")]
|
||||
if all(re.fullmatch(r":?-{3,}:?", c) for c in cells):
|
||||
continue # 머리 · 몸 가름 줄
|
||||
if not out or "table" not in out[-1]:
|
||||
out.append({"table": []})
|
||||
out[-1]["table"].append(cells)
|
||||
else:
|
||||
out.append({"p": text})
|
||||
return out
|
||||
|
||||
|
||||
def _section(book: str, division: str, number: str) -> dict | None:
|
||||
"""번호 절을 품셈 본문에서 찾음 — 없으면 끝 마디를 하나씩 떼며 위 절로(두 마디까지)."""
|
||||
parts = number.split("-")
|
||||
folders = [f for d, no, _t, f in _chapters(book) if d == division and int(no) == int(parts[0])]
|
||||
for upto in range(len(parts), 1, -1):
|
||||
want = "-".join(parts[:upto])
|
||||
for folder in folders:
|
||||
for md in sorted(folder.glob("*.md")):
|
||||
lines = md.read_text(encoding="utf-8").split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
m = _HEAD.match(line)
|
||||
if not m or m.group(1) != want:
|
||||
continue
|
||||
level = len(line) - len(line.lstrip("#"))
|
||||
end = next(
|
||||
(
|
||||
j
|
||||
for j in range(i + 1, len(lines))
|
||||
if _HEAD.match(lines[j])
|
||||
and len(lines[j]) - len(lines[j].lstrip("#")) <= level
|
||||
),
|
||||
len(lines),
|
||||
)
|
||||
return {
|
||||
"number": number,
|
||||
"matched": want,
|
||||
"file": md.relative_to(BODY.parent).as_posix(),
|
||||
"blocks": _blocks(lines[i:end]),
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def source(src: str) -> dict:
|
||||
"""로직 출처 글(예 「건설품셈 토목 1-1-2」 · 「산림품셈 3-4-2 1」)로 찾은 품셈 원문 절 —
|
||||
번호가 여럿이면(「8-1-5 · 8-1-6」) 절마다 · 못 찾으면 found 빈 목록(화면 「원문 연결 없음」)."""
|
||||
words = src.split()
|
||||
book = words[0] if words else ""
|
||||
if book not in BOOKS:
|
||||
return {"source": src, "found": []}
|
||||
division = (
|
||||
next((d for d in cm.DIVISIONS if src.startswith(f"{book} {d} ")), "")
|
||||
if book == "건설품셈"
|
||||
else ""
|
||||
)
|
||||
found = [_section(book, division, n) for n in dict.fromkeys(_NUMBER.findall(src))]
|
||||
return {"source": src, "found": [x for x in found if x]}
|
||||
|
||||
|
||||
# ── 표 조건 값 ─────────────────────────────────────────────────────────
|
||||
def table_options(file: str, key: str) -> dict:
|
||||
"""표 조건 칸의 값만 — 표 몸을 통째로 받지 않으려는 자리(만들기 입력 제안)."""
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
type LogicFlow,
|
||||
} from "./M01_MasterData_UI_LogicLab_Flow";
|
||||
import { askBasicInfo } from "./M01_MasterData_UI_LogicLab_Basic";
|
||||
import { buildLogicSide } from "./M01_MasterData_UI_LogicLab_Side";
|
||||
import { buildHeadBar } from "./M01_MasterData_UI_Head";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
import type { CfLogic } from "../M02_MasterTemplete/DataFlow/M02_DF_Model";
|
||||
@@ -129,6 +130,8 @@ export async function mountM01LogicLab(
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
// 로직 상세 두 칸 — 왼쪽 지금 내용 · 오른쪽 흐름도(패널 자리) · 너비 비율 기억
|
||||
const split = buildSplit(editor);
|
||||
// 오른쪽 두 패널(흐름도 · 원문) — 하나만 열림 · 흐름도는 이 칸 안에 띄움
|
||||
const panes = buildLogicSide(split);
|
||||
const errors = el("div", { className: "m01-logic__reasons", attrs: { hidden: "" } });
|
||||
const calc = el("div", { className: "m01lab__calc" });
|
||||
const filterHost = el("div");
|
||||
@@ -273,7 +276,9 @@ export async function mountM01LogicLab(
|
||||
const startFlow = async (o: Opened): Promise<void> => {
|
||||
const row = o.row;
|
||||
if (!row) return;
|
||||
split.right.replaceChildren(el("p", { className: "m01lab-flow__wait", text: tf("Waiting") }));
|
||||
panes.flowSplit.right.replaceChildren(
|
||||
el("p", { className: "m01lab-flow__wait", text: tf("Waiting") }),
|
||||
);
|
||||
const own = flowOf(row);
|
||||
const got = own || o.origKey === null ? null : await fetchFlow(o.origKey, o.file);
|
||||
// 시험 입력 한 벌 — 열 때 왼쪽 시험 계산 입력을 흐름 변수 예시값으로
|
||||
@@ -284,7 +289,9 @@ export async function mountM01LogicLab(
|
||||
if (opened !== o) return;
|
||||
try {
|
||||
const made = await mountLogicFlow({
|
||||
split,
|
||||
split: panes.flowSplit,
|
||||
folded: panes.flowFolded(),
|
||||
onFold: (on) => panes.pick(on ? "none" : "flow"),
|
||||
flow: clone(o.flowBase),
|
||||
title: row.이름 || row.키,
|
||||
readOnly: !admin,
|
||||
@@ -307,9 +314,10 @@ export async function mountM01LogicLab(
|
||||
});
|
||||
if (opened !== o) return made.destroy();
|
||||
flowNow = made;
|
||||
panes.bindFlow(made.fold);
|
||||
made.setExamples(o.values); // 띄우는 사이에 온 자동값까지
|
||||
} catch (e) {
|
||||
split.right.replaceChildren(
|
||||
panes.flowSplit.right.replaceChildren(
|
||||
el("p", { className: "m01-logic__muted", text: tf("Failed", String(e)) }),
|
||||
);
|
||||
}
|
||||
@@ -344,7 +352,9 @@ export async function mountM01LogicLab(
|
||||
const show = (next: Opened | null): void => {
|
||||
flowNow?.destroy();
|
||||
flowNow = null;
|
||||
panes.bindFlow(null);
|
||||
opened = next;
|
||||
panes.showSource(next?.row?.출처 ?? "");
|
||||
errors.hidden = true;
|
||||
drawEditor();
|
||||
drawCalc();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab_Detail.ts
|
||||
* 「단가산출 로직」 상세 화면 — 컨테이너 넷을 위에서 아래로
|
||||
* ① 기본정보(정본은 읽기 전용 · 비고만 고침) ② 단가산출 상세(인력·자재·경비·그 밖 묶음 + 소계)
|
||||
* ③ 텍스트 수식(자리 — `_LogicLab_Formula.ts`) ④ 시험 계산(입력값 + 결과값 — `Logic_Calc` 그대로)
|
||||
* ① 기본정보(정본은 읽기 전용 · 비고만 고침) ② 시험 계산(입력값 + 결과값 — `Logic_Calc` 그대로)
|
||||
* ③ 단가산출 상세(인력·자재·경비·그 밖 묶음 + 소계) ④ 텍스트 수식(자리 — `_LogicLab_Formula.ts`)
|
||||
* 호표 줄을 누르면 그 줄이 쓰는 자료 모달(`_LogicLab_Modal.ts`)
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -70,7 +70,7 @@ export interface DetailContext {
|
||||
/** 자체 로직 편집기 자리 — 다시 그려도 같은 칸을 씀(고치던 값이 안 날아감) */
|
||||
ownHost: HTMLElement;
|
||||
onChange: () => void;
|
||||
/** 시험 계산 칸(`buildCalc` 가 채움) — 넷째 컨테이너 안에 놓음 */
|
||||
/** 시험 계산 칸(`buildCalc` 가 채움) — 둘째 컨테이너 안에 놓음 */
|
||||
calcHost: HTMLElement;
|
||||
/** 흐름이 있는 로직 — 이 칸에 왼쪽 「지금 내용」(단가산출 상세 · 텍스트 수식 = `buildFlowView`)을 그림 ·
|
||||
* 있으면 호표 · 읽는 식 두 묶음 대신 이 칸을 놓음(기본정보 · 시험 계산은 그대로) */
|
||||
@@ -487,8 +487,8 @@ export function buildLabDetail(host: HTMLElement, ctx: DetailContext): void {
|
||||
: null;
|
||||
host.replaceChildren(
|
||||
infoBox(ctx),
|
||||
...(ctx.flowHost ? [ctx.flowHost] : [hoBox(ctx), section(tl("Formula_Title"), [formula])]),
|
||||
section(tl("Calc_Title"), [ctx.calcHost]),
|
||||
...(ctx.flowHost ? [ctx.flowHost] : [hoBox(ctx), section(tl("Formula_Title"), [formula])]),
|
||||
);
|
||||
const own = kept?.node.dataset.own; // 기본정보 칸은 새로 만들어지므로 이름표로 다시 찾음
|
||||
const back = own ? host.querySelector<HTMLInputElement>(`[data-own="${own}"]`) : kept?.node;
|
||||
|
||||
@@ -235,6 +235,9 @@ export interface LogicFlowOpts {
|
||||
/** 흐름 로직이면 왼쪽 「지금 내용」 자리(LogicLab_Detail 의 flowHost) */
|
||||
leftHost(): HTMLElement | null;
|
||||
onChange(lg: CfLogic): void;
|
||||
/** 패널 접힘 — 부르는 쪽(오른쪽 두 패널)이 정함 */
|
||||
folded?: boolean;
|
||||
onFold?(on: boolean): void;
|
||||
}
|
||||
|
||||
export interface LogicFlow {
|
||||
@@ -244,6 +247,8 @@ export interface LogicFlow {
|
||||
redrawLeft(): void;
|
||||
/** 왼쪽 시험 계산 입력이 바뀜 — 같은 이름 변수의 예시값으로(고친 것 아님) */
|
||||
setExamples(vals: Record<string, string>): void;
|
||||
/** 패널 접기 · 펴기 */
|
||||
fold(on: boolean): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
@@ -312,6 +317,8 @@ export async function mountLogicFlow(opts: LogicFlowOpts): Promise<LogicFlow> {
|
||||
readOnly: opts.readOnly,
|
||||
logics: getLogic,
|
||||
onResize: (px) => opts.split.setRight(px),
|
||||
folded: opts.folded,
|
||||
onFold: opts.onFold,
|
||||
onChange: (next) => {
|
||||
lg = next;
|
||||
opts.onChange(next);
|
||||
@@ -329,6 +336,7 @@ export async function mountLogicFlow(opts: LogicFlowOpts): Promise<LogicFlow> {
|
||||
refresh();
|
||||
},
|
||||
redrawLeft: left,
|
||||
fold: (on) => panel.fold(on),
|
||||
setExamples: (vals) => {
|
||||
const next = withExamples(lg, vals);
|
||||
if (JSON.stringify(next) === JSON.stringify(lg)) return;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/* 로직 상세 오른쪽 두 패널 — 흐름도 · 원문 · 접힌 패널은 얇은 띠 */
|
||||
.m01lab-side {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m01lab-side__slot {
|
||||
flex: 0 0 40px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m01lab-side__slot.is-open {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* 접힌 흐름도 자리 — 띠만(기다림 글 · 변환 알림은 숨김) */
|
||||
.m01lab-side__slot:not(.is-open) > :not(.m01lab-flow__panel, .m01lab-src) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.m01lab-src__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
min-width: 0;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.m01lab-src__head {
|
||||
margin: var(--spacing-8) 0 0;
|
||||
}
|
||||
|
||||
.m01lab-src__p {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.m01lab-src__file {
|
||||
margin: 0;
|
||||
font-size: var(--text-body-sm);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.m01lab-src__none {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.m01lab-src__table td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab_Side.ts
|
||||
* 로직 상세 오른쪽 두 패널 — 「흐름도」(다이어그램 · 상세 내용 = M02_DF_Panel) · 「원문」(품셈 본문 절)
|
||||
* 둘 중 하나만 열림(하나 열면 다른 하나 접힘) · 둘 다 접기 됨 · 접힌 패널 = 얇은 띠(머리 단추 그대로)
|
||||
* 열린 패널은 계정 취향(`m01-logic-panel`)으로 기억 · 원문은 출처 글로 서버가 찾음(`GET /m01/logic/source`)
|
||||
* ========================================================================== */
|
||||
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import { readStateRaw, writeStateRaw } from "../A00_Common/b_page_state";
|
||||
import type { SplitHandle } from "./M01_MasterData_UI_LogicLab_Flow";
|
||||
import "../M02_MasterTemplete/DataFlow/M02_DF_Panel.css";
|
||||
import "./M01_MasterData_UI_LogicLab_Side.css";
|
||||
|
||||
const TEXT = {
|
||||
Toggle: ["원문", "Source"],
|
||||
Open: ["원문 펴기", "Open the source panel"],
|
||||
Close: ["원문 접기", "Collapse the source panel"],
|
||||
Loading: ["원문 찾는 중", "Looking up the source"],
|
||||
None: ["원문 연결 없음", "No linked source"],
|
||||
From: ["출처", "Source"],
|
||||
NoSource: ["출처 없음", "No source"],
|
||||
Near: ["{v} 절이 없어 위 절", "No {v} section — showing the parent"],
|
||||
} as const;
|
||||
|
||||
const ts = (key: keyof typeof TEXT, v = ""): string =>
|
||||
TEXT[key][currentLanguageIndex === 1 ? 1 : 0].replace("{v}", v);
|
||||
|
||||
/** 접힌 띠 너비 — M02_DF_Panel 의 STRIP_WIDTH 와 같음(패널 모듈은 늦게 받으므로 따로 둠) */
|
||||
const STRIP = 40;
|
||||
|
||||
export type PanelPick = "flow" | "source" | "none";
|
||||
|
||||
interface Block {
|
||||
h?: number;
|
||||
text?: string;
|
||||
p?: string;
|
||||
table?: string[][];
|
||||
}
|
||||
interface Found {
|
||||
number: string;
|
||||
matched: string;
|
||||
file: string;
|
||||
blocks: Block[];
|
||||
}
|
||||
|
||||
export interface LogicSide {
|
||||
/** 흐름도 패널에 넘길 칸 — right = 흐름도 자리 · setRight = 흐름도 패널이 알린 너비 */
|
||||
flowSplit: SplitHandle;
|
||||
flowFolded(): boolean;
|
||||
pick(next: PanelPick): void;
|
||||
/** 흐름도 패널이 뜨면 접기 함수 · 내리면 null */
|
||||
bindFlow(fold: ((on: boolean) => void) | null): void;
|
||||
/** 연 로직의 출처 글 — 원문 패널이 열려 있으면 찾음 */
|
||||
showSource(src: string): void;
|
||||
}
|
||||
|
||||
const readPick = (): PanelPick => {
|
||||
const got = readStateRaw("m01-logic-panel");
|
||||
return got === "source" || got === "none" ? got : "flow";
|
||||
};
|
||||
|
||||
function blockNode(b: Block): HTMLElement {
|
||||
if (b.table)
|
||||
return el("div", {
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-logic__grid m01lab-src__table",
|
||||
children: [
|
||||
el("tbody", {
|
||||
children: b.table.map((r) =>
|
||||
el("tr", { children: r.map((c) => el("td", { text: c })) }),
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
if (b.h) return el("h4", { className: "m01lab-src__head", text: b.text ?? "" });
|
||||
return el("p", { className: "m01lab-src__p", text: b.p ?? "" });
|
||||
}
|
||||
|
||||
export function buildLogicSide(split: SplitHandle): LogicSide {
|
||||
const flowSlot = el("div", { className: "m01lab-side__slot" });
|
||||
const body = el("div", { className: "m01lab-src__body" });
|
||||
const title = el("strong", { className: "m02-dfp__title" });
|
||||
const toggle = createButton({
|
||||
label: ts("Toggle"),
|
||||
size: "sm",
|
||||
variant: "ghost",
|
||||
onClick: () => pick(now === "source" ? "none" : "source"),
|
||||
});
|
||||
toggle.classList.add("m02-dfp__toggle");
|
||||
const head = el("header", { className: "m02-dfp__head", children: [toggle, title] });
|
||||
const open = el("div", { className: "m02-dfp__open", children: [head, body] });
|
||||
const strip = el("div", { className: "m02-dfp__strip" });
|
||||
const source = el("aside", { className: "m02-dfp m01lab-src", children: [open, strip] });
|
||||
const sourceSlot = el("div", { className: "m01lab-side__slot", children: [source] });
|
||||
split.right.replaceChildren(
|
||||
el("div", { className: "m01lab-side", children: [flowSlot, sourceSlot] }),
|
||||
);
|
||||
|
||||
let now = readPick();
|
||||
let flowPx: number | null = null;
|
||||
let flowFold: ((on: boolean) => void) | null = null;
|
||||
let want = "";
|
||||
let shown: string | null = null;
|
||||
|
||||
/** 칸 너비 — 원문(또는 흐름도가 아직 안 뜸) = 두 칸 비율 · 흐름도 = 패널 너비 + 띠 · 둘 다 접힘 = 띠 둘 */
|
||||
const width = (): void =>
|
||||
split.setRight(
|
||||
now === "source" || (now === "flow" && flowPx === null)
|
||||
? null
|
||||
: now === "flow"
|
||||
? (flowPx ?? 0) + STRIP
|
||||
: STRIP * 2,
|
||||
);
|
||||
|
||||
const load = (): void => {
|
||||
if (now !== "source" || shown === want) return;
|
||||
const src = (shown = want);
|
||||
title.textContent = src || ts("NoSource");
|
||||
body.replaceChildren(el("p", { className: "m01-logic__muted", text: ts("Loading") }));
|
||||
const none = (): void =>
|
||||
body.replaceChildren(
|
||||
el("p", { className: "m01lab-src__none", text: ts("None") }),
|
||||
el("p", { className: "m01-logic__muted", text: `${ts("From")} · ${src || "—"}` }),
|
||||
);
|
||||
if (!src.trim()) return none();
|
||||
void fetch(`${API_BASE_URL}/m01/logic/source?${new URLSearchParams({ src })}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((r) => (r.ok ? (r.json() as Promise<{ found: Found[] }>) : { found: [] }))
|
||||
.catch(() => ({ found: [] as Found[] }))
|
||||
.then(({ found }) => {
|
||||
if (shown !== src) return;
|
||||
if (!found.length) return none();
|
||||
body.replaceChildren(
|
||||
...found.flatMap((f) => [
|
||||
el("p", {
|
||||
className: "m01-logic__muted m01lab-src__file",
|
||||
text: f.matched === f.number ? f.file : `${f.file} · ${ts("Near", f.number)}`,
|
||||
}),
|
||||
...f.blocks.map(blockNode),
|
||||
]),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const fold = (on: boolean): void => {
|
||||
open.hidden = on;
|
||||
strip.hidden = !on;
|
||||
(on ? strip : head).prepend(toggle);
|
||||
toggle.setAttribute("aria-expanded", String(!on));
|
||||
toggle.title = ts(on ? "Open" : "Close");
|
||||
};
|
||||
|
||||
const pick = (next: PanelPick): void => {
|
||||
if (next !== now) writeStateRaw("m01-logic-panel", next);
|
||||
now = next;
|
||||
flowFold?.(now !== "flow"); // 칸을 줄이기 전에 — 흐름도 패널이 접기 전 너비를 재어 둠
|
||||
flowSlot.classList.toggle("is-open", now === "flow");
|
||||
sourceSlot.classList.toggle("is-open", now === "source");
|
||||
fold(now !== "source");
|
||||
width();
|
||||
load();
|
||||
};
|
||||
|
||||
pick(now);
|
||||
return {
|
||||
flowSplit: {
|
||||
root: split.root,
|
||||
right: flowSlot,
|
||||
setRight: (px) => {
|
||||
flowPx = px;
|
||||
width();
|
||||
},
|
||||
},
|
||||
flowFolded: () => now !== "flow",
|
||||
pick,
|
||||
bindFlow: (f) => {
|
||||
flowFold = f;
|
||||
},
|
||||
showSource: (src) => {
|
||||
want = src;
|
||||
load();
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user