📝 docs(code-reader): useState 状态卡片原型的调用树挪进解构表每一行
This commit is contained in:
1 parent
785bfd4f78
commit
719453a28b
5 files changed
+2619
-2215
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 767 KiB After Width: | Height: | Size: 775 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 268 KiB |
+28
-18
@@ -503,9 +503,25 @@ function group(parent, segs, color, bg) {
|
||||
// ─── 卡片 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 卡片右上角:问 AI、编辑;给了 `calls` 再加调用树,同 webview 的 BlockTools:调用次数大于 0 时图标后面跟一个小圆标
|
||||
* (徽标底色、字色取 Dark Modern 的 badge),是 0 时只有图标。返回这一排的宽度。
|
||||
* 调用树按钮,同 webview 的 BlockTools:git-branch 图标,调用次数大于 0 时后面跟一个小圆标
|
||||
* (徽标底色、字色取 Dark Modern 的 badge),是 0 时只有图标。`tip` 是悬停文字,记在图层名里。返回按钮的宽度。
|
||||
*/
|
||||
function callTreeButton(parent, calls, tip) {
|
||||
const name = tip ? "git-branch / " + tip : "git-branch";
|
||||
if (calls === 0) {
|
||||
const tree = row(parent, name, { width: 22, height: 20, justify: "center", radius: 4 });
|
||||
icon(tree, "git-branch", C.desc, 14);
|
||||
return 22;
|
||||
}
|
||||
const tree = row(parent, name, { width: "fit_content", height: 20, padding: [0, 4], gap: 1, radius: 4 });
|
||||
icon(tree, "git-branch", C.desc, 14);
|
||||
const badge = row(tree, "Count", { width: 14, height: 14, justify: "center", radius: 7, fill: "#616161" });
|
||||
// 一两个字的数字按内容长会被导出时挤小,宽度定死
|
||||
text(badge, String(calls), { size: 9, weight: 700, color: "#f8f8f8", lh: 14, fixedW: Math.ceil(widthOf(String(calls), 9, false)) + 1 });
|
||||
return 4 + 14 + 1 + 14 + 4;
|
||||
}
|
||||
|
||||
/** 卡片右上角:问 AI、编辑;给了 `calls` 再加调用树按钮。返回这一排的宽度。 */
|
||||
function tools(parent, calls) {
|
||||
const bar = row(parent, "Tools", { width: "fit_content", height: 20, gap: 2 });
|
||||
for (const name of ["sparkles", "pencil"]) {
|
||||
@@ -514,28 +530,22 @@ function tools(parent, calls) {
|
||||
}
|
||||
if (calls == null)
|
||||
return 22 * 2 + 2;
|
||||
if (calls === 0) {
|
||||
const tree = row(bar, "git-branch", { width: 22, height: 20, justify: "center", radius: 4 });
|
||||
icon(tree, "git-branch", C.desc, 14);
|
||||
return 22 * 3 + 2 * 2;
|
||||
}
|
||||
const tree = row(bar, "git-branch", { width: "fit_content", height: 20, padding: [0, 4], gap: 1, radius: 4 });
|
||||
icon(tree, "git-branch", C.desc, 14);
|
||||
const badge = row(tree, "Count", { width: 14, height: 14, justify: "center", radius: 7, fill: "#616161" });
|
||||
text(badge, String(calls), { size: 9, weight: 700, color: "#f8f8f8", lh: 14 });
|
||||
return 22 * 2 + 2 + 2 + (4 + 14 + 1 + 14 + 4);
|
||||
return 22 * 2 + 2 + 2 + callTreeButton(bar, calls);
|
||||
}
|
||||
|
||||
/** 悬停提示气泡,样子同 webview 的 data-tooltip:悬停浮层的底色、边框、11px 字。`x`、`y` 是它在父节点里的位置。 */
|
||||
/**
|
||||
* 悬停提示气泡,样子同 webview 的 data-tooltip:悬停浮层的底色、边框、11px 字。
|
||||
* `x`、`y` 是它在自由摆放的父节点里的位置;不给时跟着自动排版排在前一个兄弟后面,高度压成一行的 20px。
|
||||
*/
|
||||
function tooltip(parent, label, x, y) {
|
||||
const bw = Math.ceil(widthOf(label, 11, false)) + 16;
|
||||
const tip = I(parent, {
|
||||
const placed = x != null ? { x: Math.round(x), y: Math.round(y) } : {};
|
||||
const tip = I(parent, Object.assign({
|
||||
type: "frame",
|
||||
name: "Tooltip",
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
}, placed, {
|
||||
width: bw,
|
||||
height: 22,
|
||||
height: x != null ? 22 : 20,
|
||||
layout: "horizontal",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
@@ -543,7 +553,7 @@ function tooltip(parent, label, x, y) {
|
||||
fill: solid("#202020"),
|
||||
stroke: { thickness: 1, fill: solid("#454545") },
|
||||
effects: [{ type: "shadow", offsetX: 0, offsetY: 2, blur: 8, spread: 0, color: "rgba(0,0,0,0.36)" }],
|
||||
});
|
||||
}));
|
||||
text(tip, label, { size: 11, color: C.fg, lh: 14 });
|
||||
return bw;
|
||||
}
|
||||
|
||||
+33
-31
@@ -1,6 +1,6 @@
|
||||
// 代码阅读 React 状态卡片:useState 不再写成「钩子 useState」,而是一张「状态」卡片,讲清楚五件事:
|
||||
// 它是普通变量也是 React 状态(左边变量、状态两个图标);它是数组解构(名称照源码写出解构,展开是每一项);
|
||||
// 它来自 React 内置的 useState(悬停说明);状态类型;默认值。修改函数在哪调用,用右上角的调用树查。
|
||||
// 它来自 React 内置的 useState(悬停说明);状态类型;默认值。解构出的每一项各有一个调用树按钮:当前值查谁用了它,修改函数查谁改了它。
|
||||
// 画板一照抄 luffy-agent-platform 的 frontend/client-nextjs/src/app/desktop/login/page.tsx 第 1–37 行,
|
||||
// 画板二是手写的三行,讲没标类型、函数形式的默认值、只取当前值三种写法。
|
||||
// 窗口、对齐、别的卡片的画法在同目录 react-shared.js,接在它后面跑:`cat react-shared.js react-state.js | op design - --script`。
|
||||
@@ -84,26 +84,36 @@ const VARIANTS = sampleFile({
|
||||
|
||||
// 种类「状态」:描边、淡底、两个图标都用变量色;左边先是变量卡片的图标,再是状态图标,悬停才出名字
|
||||
const STATE = { label: "状态", color: "#9cdcfe", icon: "variable" };
|
||||
const FROM_TIP = "React 内置钩子";
|
||||
|
||||
// 解构出的每一项:是什么、它那一行调用树按钮查什么(悬停文字)
|
||||
const ITEMS = [
|
||||
{ what: "当前值", tip: "谁用了这个状态" },
|
||||
{ what: "修改函数", tip: "谁改了这个状态" },
|
||||
];
|
||||
|
||||
/**
|
||||
* 「来自」那一行悬停时的气泡在卡片里的位置:在 `useState` 几个字正上方居中、隔 6px。
|
||||
* 内容区从卡片内边距(上 8、左 8)、折叠箭头一列(14)、两个图标(42)各隔 10px 往后排;
|
||||
* 「来自」前面是名称一行和解构表(表头一行、每项一行,各带 1px 分隔线、上下各 2px 留白)。
|
||||
* 「是什么」一格:说法后面跟这一项自己的调用树按钮,`calls` 是它在样例里出现几次;
|
||||
* `bubble` 时按钮右边画出它的悬停气泡(上方是上一行的按钮,放上面会挡住它)。
|
||||
*/
|
||||
function fromBubble(labelW, items) {
|
||||
const tableH = 2 + 24 + items * (24 + 1) + 2;
|
||||
const rowTop = 8 + 20 + 3 + tableH + 3;
|
||||
const valueX = 8 + 14 + 10 + 42 + 10 + labelW + 12;
|
||||
const center = valueX + widthOf("useState", 13, true) / 1.04 / 2;
|
||||
return { x: center - tooltipWidth(FROM_TIP) / 2, y: rowTop - 6 - 22 };
|
||||
function whatCell(item, calls, bubble) {
|
||||
return {
|
||||
est: item.what,
|
||||
extra: 6 + 37,
|
||||
draw: (frame) => {
|
||||
const line = row(frame, "What", { gap: 6, height: 20, width: "fit_content" });
|
||||
text(line, item.what, { size: 12.5, lh: 20 });
|
||||
callTreeButton(line, calls, item.tip);
|
||||
if (bubble)
|
||||
tooltip(line, item.tip);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 一处 useState:名称照源码写出解构(后面灰字「数组解构」,展开是「第几项 | 名称 | 是什么」)/ 来自 / 状态类型 / 默认值。
|
||||
* `names` 是解构出的名字,第二个是修改函数;有修改函数时右上角有调用树,`calls` 是它被调用几次。
|
||||
* `type` 是状态类型(没写时「未标注」),`initial` 是默认值的着色段。
|
||||
* `bubble` 画出「来自」的悬停气泡:卡片和气泡放进一个自由摆放的框,气泡在前,才画在卡片上层。
|
||||
* 一处 useState:名称照源码写出解构(后面灰字「数组解构」,展开是「第几项 | 名称 | 是什么」,每一项一个调用树按钮)/
|
||||
* 来自(悬停说是 React 内置钩子)/ 状态类型 / 默认值。
|
||||
* `names` 是解构出的名字,第二个是修改函数;`calls` 是各项在样例里出现几次。
|
||||
* `type` 是状态类型(没写时「未标注」),`initial` 是默认值的着色段;`bubble` 画出修改函数那一行调用树按钮的悬停气泡。
|
||||
*/
|
||||
function stateCard(s) {
|
||||
return (parent, w) => {
|
||||
@@ -111,15 +121,7 @@ function stateCard(s) {
|
||||
{ name: "variable", color: STATE.color, tip: "变量" },
|
||||
{ name: "toggle-right", color: STATE.color, tip: "React 状态" },
|
||||
];
|
||||
let holder = parent;
|
||||
let place = {};
|
||||
if (s.bubble) {
|
||||
holder = box(parent, "Hover / 来自", { layout: "none", width: w, height: "fit_content" });
|
||||
const spot = fromBubble(76, s.names.length);
|
||||
tooltip(holder, FROM_TIP, spot.x, spot.y);
|
||||
place = { x: 0, y: 0 };
|
||||
}
|
||||
const ctx = card(holder, STATE, w, Object.assign({ name: s.names[0], icons, calls: s.names.length > 1 ? s.calls : null }, place));
|
||||
const ctx = card(parent, STATE, w, { name: s.names[0], icons });
|
||||
prop(ctx, "名称", (frame) => {
|
||||
const line = row(frame, "Destructure", { gap: 8, height: 20, width: "fit_content" });
|
||||
const parts = [seg("[", C.fg)];
|
||||
@@ -133,8 +135,8 @@ function stateCard(s) {
|
||||
text(line, "数组解构", { size: 12.5, color: C.desc });
|
||||
}, { fold: "open" });
|
||||
table(ctx, [{ name: "第几项", mono: false }, { name: "名称", color: C.variable }, { name: "是什么", mono: false }],
|
||||
s.names.map((name, i) => [indexCell(i + 1), px(name, i === 0 ? C.variable : C.fn), i === 0 ? "当前值" : "修改函数"]));
|
||||
prop(ctx, "来自", px("useState", C.fn));
|
||||
s.names.map((name, i) => [indexCell(i + 1), px(name, i === 0 ? C.variable : C.fn), whatCell(ITEMS[i], s.calls[i], s.bubble && i === 1)]));
|
||||
prop(ctx, "来自", (frame) => text(frame, px("useState", C.fn), { mono: true, name: "useState(悬停:React 内置钩子)" }));
|
||||
prop(ctx, "状态类型", s.type || px("未标注", C.desc), { mono: Boolean(s.type) });
|
||||
prop(ctx, "默认值", s.initial);
|
||||
};
|
||||
@@ -175,7 +177,7 @@ function searchParamsHook(parent, w) {
|
||||
|
||||
const ACTIVE_TAB = stateCard({
|
||||
names: ["activeTab", "setActiveTab"],
|
||||
calls: 1,
|
||||
calls: [1, 1],
|
||||
type: [link("LoginTab", C.type)],
|
||||
initial: px("\"sms\"", C.string),
|
||||
bubble: true,
|
||||
@@ -183,7 +185,7 @@ const ACTIVE_TAB = stateCard({
|
||||
|
||||
const PASSWORD_VIEW = stateCard({
|
||||
names: ["passwordView", "setPasswordView"],
|
||||
calls: 1,
|
||||
calls: [1, 1],
|
||||
type: [link("PasswordView", C.type)],
|
||||
initial: px("\"login\"", C.string),
|
||||
});
|
||||
@@ -235,16 +237,16 @@ windowOf("Prototype / React 状态 · 其他写法", WIN_W + BOARD_GAP, VARIANTS
|
||||
{
|
||||
from: 1,
|
||||
to: 1,
|
||||
draw: stateCard({ names: ["open", "setOpen"], calls: 0, initial: cx("假") }),
|
||||
draw: stateCard({ names: ["open", "setOpen"], calls: [0, 0], initial: cx("假") }),
|
||||
},
|
||||
{
|
||||
from: 2,
|
||||
to: 2,
|
||||
draw: stateCard({ names: ["items", "setItems"], calls: 0, type: [link("Item", C.type), seg("[]", C.fg)], initial: [seg("首次渲染时调用 ", C.fg)].concat(cx("loadItems()")) }),
|
||||
draw: stateCard({ names: ["items", "setItems"], calls: [0, 0], type: [link("Item", C.type), seg("[]", C.fg)], initial: [seg("首次渲染时调用 ", C.fg)].concat(cx("loadItems()")) }),
|
||||
},
|
||||
{
|
||||
from: 3,
|
||||
to: 3,
|
||||
draw: stateCard({ names: ["count"], initial: cx("0") }),
|
||||
draw: stateCard({ names: ["count"], calls: [0], initial: cx("0") }),
|
||||
},
|
||||
]);
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user