- 배수유역 판 켬끔(setVisible · drainage-visible 값) 지움 — 판은 늘 보임 · 접기 · 펼치기는 판 손잡이 하나로 · 옛 값 false 무시 - [새 창] 단추 = 도구 묶음 맨 앞(toolLead) — 다시 그릴 때마다 [초기화] 바로 왼쪽에 옮겨 붙임 · 열림 · 닫힘 동작 그대로 - 시험 test_282_1 추가 · 옛 단추를 찾던 시험(187-1 · drainage_side · profile_single) 새 꼴로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
186 lines
6.3 KiB
JavaScript
186 lines
6.3 KiB
JavaScript
/* 282 종단 도구줄 — 진짜 TS(도구 묶음 render · withToolLead · attachProfilePopout · 배수유역 판)를
|
|
* 가짜 DOM 위에서 돌려 JSON 으로 냄.
|
|
* · [새 창] = 도구 묶음 맨 앞([초기화] 바로 왼쪽) · 다시 그려도 그 자리
|
|
* · 새 창 열림 · 닫힘 = 글 바뀜 · 제자리 돌림
|
|
* · 옛 값 drainage-visible=false 여도 판(손잡이) 보임 */
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), "utf8");
|
|
const load = (code, scope) => {
|
|
const js = ts.transpileModule(code, {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const exports = {};
|
|
const names = Object.keys(scope);
|
|
new Function("exports", ...names, js)(exports, ...names.map((n) => scope[n]));
|
|
return exports;
|
|
};
|
|
|
|
// ---- 가짜 DOM ----
|
|
function el(tag) {
|
|
const node = {
|
|
tagName: tag.toUpperCase(),
|
|
className: "",
|
|
textContent: "",
|
|
title: "",
|
|
hidden: false,
|
|
disabled: false,
|
|
attrs: {},
|
|
listeners: {},
|
|
children: [],
|
|
parent: null,
|
|
dataset: {},
|
|
style: {},
|
|
classList: {
|
|
list: () => node.className.split(/\s+/).filter(Boolean),
|
|
contains: (c) => node.classList.list().includes(c),
|
|
add: (c) =>
|
|
!node.classList.contains(c) && (node.className = [...node.classList.list(), c].join(" ")),
|
|
remove: (c) =>
|
|
(node.className = node.classList
|
|
.list()
|
|
.filter((x) => x !== c)
|
|
.join(" ")),
|
|
toggle: (c, on) =>
|
|
(on ?? !node.classList.contains(c)) ? node.classList.add(c) : node.classList.remove(c),
|
|
},
|
|
detach() {
|
|
if (node.parent) node.parent.children = node.parent.children.filter((c) => c !== node);
|
|
node.parent = null;
|
|
},
|
|
append(...kids) {
|
|
kids.forEach((k) => (k.detach(), (k.parent = node), node.children.push(k)));
|
|
},
|
|
prepend(...kids) {
|
|
kids.reverse().forEach((k) => (k.detach(), (k.parent = node), node.children.unshift(k)));
|
|
},
|
|
replaceChildren(...kids) {
|
|
node.children.slice().forEach((c) => c.detach());
|
|
node.append(...kids);
|
|
},
|
|
setAttribute: (k, v) => (node.attrs[k] = v),
|
|
addEventListener: (type, fn) => (node.listeners[type] ??= []).push(fn),
|
|
click: () => (node.listeners.click ?? []).forEach((fn) => fn({ stopPropagation() {} })),
|
|
};
|
|
return node;
|
|
}
|
|
const fakeDoc = () => {
|
|
const doc = { title: "" };
|
|
doc.createElement = el;
|
|
doc.head = el("head");
|
|
doc.body = el("body");
|
|
doc.documentElement = Object.assign(el("html"), { attributes: [] });
|
|
doc.querySelectorAll = () => [];
|
|
doc.importNode = (n) => n;
|
|
return doc;
|
|
};
|
|
const document = fakeDoc();
|
|
const frames = [];
|
|
const window = {
|
|
listeners: {},
|
|
addEventListener: (t, fn) => (window.listeners[t] ??= []).push(fn),
|
|
removeEventListener: () => {},
|
|
open: () => popup,
|
|
};
|
|
const popupListeners = {};
|
|
const popup = {
|
|
closed: false,
|
|
document: fakeDoc(),
|
|
close: () => (popup.closed = true),
|
|
addEventListener: (t, fn) => (popupListeners[t] ??= []).push(fn),
|
|
ResizeObserver: class {
|
|
observe() {}
|
|
},
|
|
requestAnimationFrame: () => 0,
|
|
cancelAnimationFrame: () => {},
|
|
};
|
|
|
|
// ---- 진짜 도구 묶음 ----
|
|
const { createProfileTools } = load(read("B05_Profile", "B05_Profile_UI_Profile_Tools.ts"), {
|
|
require: () => ({ sameRun: () => false }),
|
|
document,
|
|
});
|
|
const tools = createProfileTools({
|
|
onStraighten() {},
|
|
onShift() {},
|
|
onTilt() {},
|
|
onResetToSaved() {},
|
|
canResetToSaved: () => true,
|
|
onUndo() {},
|
|
onRedo() {},
|
|
canUndo: () => false,
|
|
canRedo: () => false,
|
|
hasStraightRuns: () => false,
|
|
onChanged() {},
|
|
onModeChange() {},
|
|
freeHint: () => "",
|
|
mountOverlay() {},
|
|
});
|
|
|
|
// ---- 진짜 withToolLead(design_profile.ts) ----
|
|
const profileSrc = read("A00_Common", "design", "design_profile.ts");
|
|
const leadAt = profileSrc.indexOf("const withToolLead");
|
|
const leadSrc = profileSrc.slice(leadAt, profileSrc.indexOf("};", leadAt) + 2);
|
|
const button = el("button");
|
|
const { withToolLead } = load(`${leadSrc}\nexports.withToolLead = withToolLead;`, {
|
|
ctx: { toolLead: button },
|
|
});
|
|
|
|
// ---- 진짜 attachProfilePopout ----
|
|
const { attachProfilePopout } = load(read("A00_Common", "design", "design_profile_popout.ts"), {
|
|
require: () => ({ showToast() {} }),
|
|
document,
|
|
window,
|
|
getComputedStyle: () => ({ backgroundColor: "" }),
|
|
requestAnimationFrame: (fn) => frames.push(fn),
|
|
});
|
|
const slot = el("div");
|
|
const root = el("div");
|
|
slot.append(root);
|
|
const balanceBar = el("div");
|
|
root.append(balanceBar);
|
|
const profile = { root, redraw() {}, dispose() {} };
|
|
attachProfilePopout(profile, slot, button);
|
|
|
|
const render = () => {
|
|
const wrap = withToolLead(tools.render());
|
|
balanceBar.replaceChildren(wrap);
|
|
return wrap.children.map((c) => c.textContent);
|
|
};
|
|
const out = { first: render(), buttonClass: button.className };
|
|
out.again = render();
|
|
button.click();
|
|
out.opened = { text: button.textContent, inPopup: popup.document.body.children[0] === root };
|
|
out.openedRender = render();
|
|
button.click();
|
|
out.closed = { text: button.textContent, inSlot: root.parent === slot, popupClosed: popup.closed };
|
|
out.closedRender = render();
|
|
|
|
// ---- 옛 값 drainage-visible=false — 판 · 손잡이 보임 ----
|
|
const store = { "drainage-visible": "false", "drainage-collapsed": "false" };
|
|
const sideSrc = read("B05_Profile", "B05_Profile_UI_Drainage_Side.ts");
|
|
const panelRoot = Object.assign(el("aside"), { className: "b05-drainage" });
|
|
const handle = el("button");
|
|
panelRoot.append(handle);
|
|
const fakePanel = { root: panelRoot, setView() {}, dispose() {} };
|
|
const side = load(sideSrc, {
|
|
require: (id) => (id.endsWith(".css") ? {} : { createDrainagePanel: () => fakePanel }),
|
|
});
|
|
const sideToolSrc = read("B05_Profile", "B05_Profile_UI_Profile_SideTool.ts");
|
|
const { drainageSide } = load(sideToolSrc, {
|
|
require: (id) =>
|
|
id.includes("b_page_state")
|
|
? { readStateRaw: (k) => store[k] ?? null, writeStateRaw: (k, v) => (store[k] = v) }
|
|
: side,
|
|
});
|
|
const board = drainageSide({ projectId: "p", view: "profile" });
|
|
out.side = {
|
|
rootHidden: board.root.hidden,
|
|
handleHidden: handle.hidden,
|
|
hasSetVisible: "setVisible" in board,
|
|
};
|
|
process.stdout.write(JSON.stringify(out));
|