@world-engines/blocks-editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +46 -0
  2. package/README.md +40 -0
  3. package/dist/adapters/trigger.d.ts +72 -0
  4. package/dist/adapters/trigger.js +206 -0
  5. package/dist/blockly-config.d.ts +3 -0
  6. package/dist/blockly-config.js +7 -0
  7. package/dist/blocks/ctx.d.ts +67 -0
  8. package/dist/blocks/ctx.js +96 -0
  9. package/dist/blocks/registry-taxonomy.d.ts +12 -0
  10. package/dist/blocks/registry-taxonomy.js +79 -0
  11. package/dist/blocks/registry.d.ts +39 -0
  12. package/dist/blocks/registry.js +1000 -0
  13. package/dist/blocks/time-fields.d.ts +16 -0
  14. package/dist/blocks/time-fields.js +45 -0
  15. package/dist/blocks/toolbox.d.ts +53 -0
  16. package/dist/blocks/toolbox.js +195 -0
  17. package/dist/fields/AttrPickerField.d.ts +13 -0
  18. package/dist/fields/AttrPickerField.js +48 -0
  19. package/dist/fields/EdgePickerField.d.ts +10 -0
  20. package/dist/fields/EdgePickerField.js +36 -0
  21. package/dist/fields/EntityPickerField.d.ts +10 -0
  22. package/dist/fields/EntityPickerField.js +36 -0
  23. package/dist/fields/StatePickerField.d.ts +13 -0
  24. package/dist/fields/StatePickerField.js +47 -0
  25. package/dist/fields/TimePickerField.d.ts +10 -0
  26. package/dist/fields/TimePickerField.js +81 -0
  27. package/dist/fields/picker-events.d.ts +21 -0
  28. package/dist/fields/picker-events.js +28 -0
  29. package/dist/fields/register.d.ts +6 -0
  30. package/dist/fields/register.js +22 -0
  31. package/dist/i18n/messages-zh.d.ts +6 -0
  32. package/dist/i18n/messages-zh.js +34 -0
  33. package/dist/index.d.ts +21 -0
  34. package/dist/index.js +11 -0
  35. package/dist/migration/index.d.ts +1 -0
  36. package/dist/migration/index.js +1 -0
  37. package/dist/migration/types.d.ts +91 -0
  38. package/dist/migration/types.js +1 -0
  39. package/dist/react/PickerHost.d.ts +6 -0
  40. package/dist/react/PickerHost.js +110 -0
  41. package/dist/sdk-to-block/index.d.ts +4 -0
  42. package/dist/sdk-to-block/index.js +4 -0
  43. package/dist/sdk-to-block/parser.d.ts +2 -0
  44. package/dist/sdk-to-block/parser.js +101 -0
  45. package/dist/sdk-to-block/register.d.ts +3 -0
  46. package/dist/sdk-to-block/register.js +27 -0
  47. package/dist/sdk-to-block/signature.d.ts +31 -0
  48. package/dist/sdk-to-block/signature.js +12 -0
  49. package/dist/sdk-to-block/to-block-def.d.ts +25 -0
  50. package/dist/sdk-to-block/to-block-def.js +71 -0
  51. package/dist/themes/index.d.ts +4 -0
  52. package/dist/themes/index.js +12 -0
  53. package/dist/themes/liquid-glass-dark.d.ts +57 -0
  54. package/dist/themes/liquid-glass-dark.js +187 -0
  55. package/dist/themes/liquid-glass.css +195 -0
  56. package/dist/view-to-block/index.d.ts +10 -0
  57. package/dist/view-to-block/index.js +67 -0
  58. package/dist/workspace.d.ts +33 -0
  59. package/dist/workspace.js +306 -0
  60. package/package.json +65 -0
  61. package/public/blockly-media/1x1.gif +0 -0
  62. package/public/blockly-media/click.mp3 +0 -0
  63. package/public/blockly-media/delete-icon.svg +1 -0
  64. package/public/blockly-media/delete.mp3 +0 -0
  65. package/public/blockly-media/disconnect.mp3 +0 -0
  66. package/public/blockly-media/drop.mp3 +0 -0
  67. package/public/blockly-media/dropdown-arrow.svg +1 -0
  68. package/public/blockly-media/foldout-icon.svg +1 -0
  69. package/public/blockly-media/handclosed.cur +0 -0
  70. package/public/blockly-media/handdelete.cur +0 -0
  71. package/public/blockly-media/handopen.cur +0 -0
  72. package/public/blockly-media/pilcrow.png +0 -0
  73. package/public/blockly-media/quote0.png +0 -0
  74. package/public/blockly-media/quote1.png +0 -0
  75. package/public/blockly-media/resize-handle.svg +3 -0
  76. package/public/blockly-media/sprites.svg +74 -0
package/LICENSE ADDED
@@ -0,0 +1,46 @@
1
+ WorldEngine 官方作者工具许可证
2
+
3
+ 版权所有 (c) 2026 Nixdorfer。保留所有权利。
4
+
5
+ 本仓库的源代码、构建材料及附带资源(统称“本作品”)不是开源软件,
6
+ 不适用任何 OSI 认证的开源许可证。
7
+
8
+ 一、官方作者工具分发许可
9
+
10
+ 版权所有者可以通过其官方网站、npm registry、签名安装包或其他官方渠道,
11
+ 复制并分发由本作品构建的 WorldEngine 作者工具及其必要运行资源
12
+ (统称“官方作者工具”)。仅版权所有者或其书面指定的发布者享有此分发权。
13
+
14
+ 二、作者用户许可
15
+
16
+ 从官方渠道取得官方作者工具的作者用户,可以:
17
+
18
+ 1. 安装和运行官方作者工具;
19
+ 2. 使用官方作者工具创作、编辑、预览、导入、导出和提交其有权处理的内容;
20
+ 3. 为上述使用目的制作合理必要的本地备份副本。
21
+
22
+ 三、未授予的权利
23
+
24
+ 除第二条明确允许的行为外,本许可证不授予作者用户或其他第三方以下权利:
25
+
26
+ 1. 修改、改编、反编译、反汇编或制作官方作者工具的派生作品;
27
+ 2. 复制、镜像、转发、再上传、出售、出租、再分发或再许可官方作者工具;
28
+ 3. 使用本作品的源代码、构建材料或任何部分开发、提供或训练其他产品或服务;
29
+ 4. 删除或规避版权、许可、签名、访问控制或其他权利管理信息。
30
+
31
+ 法律强制允许且合同不得排除的权利不受上述限制影响。
32
+
33
+ 四、源代码查看
34
+
35
+ 第三方可以在已获合法访问权限的范围内查看本作品,用于审查、安全研究或学习参考;
36
+ 查看不授予运行、复制、修改、分发、再许可或用于其他产品与服务的权利。
37
+
38
+ 五、无担保与责任限制
39
+
40
+ 本作品与官方作者工具均按“现状”提供,不附带任何明示或暗示的担保。
41
+ 在适用法律允许的最大范围内,版权所有者不对因访问或使用本作品或官方作者工具
42
+ 造成的任何损失承担责任。
43
+
44
+ 六、终止
45
+
46
+ 违反本许可证将立即终止相应的访问与使用权;终止不影响版权所有者已经产生的权利和救济。
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @world-engines/blocks-editor
2
+
3
+ Scratch 风格积木编辑器共享包 用户铁律 平台中所有"类代码"输入面统一用积木拼装 底层 ts/js。
4
+
5
+ 本包基于 Blockly 13 + zelos renderer + react-blockly 提供通用积木运行时
6
+ 首位消费者是 creator-editor 的 TriggerEditor 后续 advanced-mode 视图脚本 LLM 路由 经济触发 审核红线 任务条件 等"类代码"面陆续接入 不再各自造轮。
7
+
8
+ ## 导出契约
9
+
10
+ `src/index.ts` 为 barrel export 后续按 T2-T7 任务依次填充
11
+
12
+ - `BlocksWorkspace` React 组件 props {schema value onChange expertMode ctx}
13
+ - `triggerAdapter` TriggerTree 适配器 toAst/fromAst/registerBlocks/toolbox
14
+ - `liquidGlassDarkTheme` Blockly Theme 项目深色液态玻璃皮
15
+ - `toTsExpression` / `fromTsExpression` Monaco 专家模式互转
16
+ - `upgradeLegacyTrigger` / `upgradeScenarioTriggers` / `SCHEMA_VERSION` v1 → v2 升级器
17
+ - `Adapter` interface 后续 ast 适配器范式
18
+
19
+ ## 8 个块名 触发器特化 见 ADR-0104 决策 5 6 7 8
20
+
21
+ | 块 ID | 形状 | 颜色族 |
22
+ | :--- | :--- | :--- |
23
+ | `trigger_root` | cap 顶层容器 | 深青 |
24
+ | `op_and` | C 形多 child | 橙 |
25
+ | `op_or` | C 形多 child | 橙 |
26
+ | `op_not` | cap 单 child | 橙 |
27
+ | `leaf_time` | stack | 紫 |
28
+ | `leaf_event` | stack | 绿 |
29
+ | `leaf_nl` | stack | 绿 |
30
+ | `leaf_attr_ref` | stack | 蓝 |
31
+
32
+ ## 与 ADR / PLAN 的关系
33
+
34
+ - ADR-0104 `docs/adr/0104-scratch-block-editor-trigger-ui.md` 决策依据
35
+ - PLAN `.scratch/editor-workbench-overhaul/scratch-block-trigger-ui.md` 实施清单
36
+ - ADR-0096 引擎不动 本包仅做 UI 层与持久化升级
37
+
38
+ ## media 资源
39
+
40
+ `public/blockly-media/` 自托管 Blockly 内置 svg + sound 离线 隐私 CSP 四角度禁止走 google CDN 见 ADR-0104 决策 11。
@@ -0,0 +1,72 @@
1
+ export interface BlockNodeInputSlot {
2
+ readonly block?: BlockNode;
3
+ }
4
+ export interface BlockNodeNextSlot {
5
+ readonly block?: BlockNode;
6
+ }
7
+ export interface BlockNode {
8
+ readonly type: string;
9
+ readonly id?: string;
10
+ readonly x?: number;
11
+ readonly y?: number;
12
+ readonly fields?: {
13
+ readonly [name: string]: unknown;
14
+ };
15
+ readonly inputs?: {
16
+ readonly [name: string]: BlockNodeInputSlot;
17
+ };
18
+ readonly next?: BlockNodeNextSlot;
19
+ readonly extraState?: {
20
+ readonly [key: string]: unknown;
21
+ };
22
+ }
23
+ export interface BlocklyWorkspaceJson {
24
+ readonly blocks?: {
25
+ readonly languageVersion?: number;
26
+ readonly blocks?: ReadonlyArray<BlockNode>;
27
+ };
28
+ }
29
+ import type { SelectorAstV3, TriggerConditionNodeV3 } from "@world-engines/tmw";
30
+ export type BlocksConditionExpressionV3 = {
31
+ readonly op: "literal";
32
+ readonly value: boolean;
33
+ } | {
34
+ readonly op: "event_field";
35
+ readonly path: readonly string[];
36
+ readonly comparison: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "contains";
37
+ readonly value: string | number | boolean | null;
38
+ } | {
39
+ readonly op: "selector_exists";
40
+ readonly selector: SelectorAstV3;
41
+ } | {
42
+ readonly op: "nl_condition";
43
+ readonly stable_id: string;
44
+ readonly text: string;
45
+ readonly locale: string;
46
+ readonly anchor?: SelectorAstV3;
47
+ } | {
48
+ readonly op: "not";
49
+ readonly value: BlocksConditionExpressionV3;
50
+ } | {
51
+ readonly op: "all" | "any";
52
+ readonly values: readonly BlocksConditionExpressionV3[];
53
+ };
54
+ export type BlocksTriggerConditionNodeV3 = Omit<TriggerConditionNodeV3, "expression"> & {
55
+ readonly expression: BlocksConditionExpressionV3;
56
+ };
57
+ export declare const TMW_CONDITION_BLOCK_TYPES: Readonly<{
58
+ readonly root: "tmw_v3_condition_root";
59
+ readonly all: "tmw_v3_condition_all";
60
+ readonly any: "tmw_v3_condition_any";
61
+ readonly not: "tmw_v3_condition_not";
62
+ readonly literal: "tmw_v3_condition_literal";
63
+ readonly eventField: "tmw_v3_condition_event_field";
64
+ readonly selectorExists: "tmw_v3_condition_selector_exists";
65
+ readonly naturalLanguage: "tmw_v3_condition_natural_language";
66
+ }>;
67
+ /** 将一个现行 TMW3 condition node 投影为 Blockly JSON;不创建第二份运行时 authority。 */
68
+ export declare const tmwConditionNodeToWorkspace: (condition: BlocksTriggerConditionNodeV3) => BlocklyWorkspaceJson;
69
+ /** 从 Blockly 草稿严格重建同一个 TMW3 condition node。 */
70
+ export declare const tmwConditionNodeFromWorkspace: (value: unknown) => BlocksTriggerConditionNodeV3;
71
+ /** Fresh-only workspace parser;不迁移、不归一化旧 block。 */
72
+ export declare const parseTmwWorkspaceV3: (value: unknown) => BlocklyWorkspaceJson;
@@ -0,0 +1,206 @@
1
+ export const TMW_CONDITION_BLOCK_TYPES = Object.freeze({
2
+ root: "tmw_v3_condition_root",
3
+ all: "tmw_v3_condition_all",
4
+ any: "tmw_v3_condition_any",
5
+ not: "tmw_v3_condition_not",
6
+ literal: "tmw_v3_condition_literal",
7
+ eventField: "tmw_v3_condition_event_field",
8
+ selectorExists: "tmw_v3_condition_selector_exists",
9
+ naturalLanguage: "tmw_v3_condition_natural_language",
10
+ });
11
+ const primitive = (value) => {
12
+ if (value === null || typeof value === "string" || typeof value === "boolean")
13
+ return value;
14
+ if (typeof value === "number" && Number.isFinite(value))
15
+ return value;
16
+ throw new Error("tmw_condition_primitive_invalid");
17
+ };
18
+ const expressionToBlock = (expression) => {
19
+ switch (expression.op) {
20
+ case "literal": return { type: TMW_CONDITION_BLOCK_TYPES.literal, fields: { VALUE: expression.value ? "TRUE" : "FALSE" } };
21
+ case "event_field": return {
22
+ type: TMW_CONDITION_BLOCK_TYPES.eventField,
23
+ fields: {
24
+ PATH: expression.path.join("."),
25
+ COMPARISON: expression.comparison,
26
+ VALUE_JSON: JSON.stringify(expression.value),
27
+ },
28
+ };
29
+ case "selector_exists": {
30
+ return { type: TMW_CONDITION_BLOCK_TYPES.selectorExists, fields: { SELECTOR_JSON: JSON.stringify(expression.selector) } };
31
+ }
32
+ case "nl_condition": return {
33
+ type: TMW_CONDITION_BLOCK_TYPES.naturalLanguage,
34
+ fields: {
35
+ STABLE_ID: expression.stable_id,
36
+ TEXT: expression.text,
37
+ LOCALE: expression.locale,
38
+ ANCHOR_JSON: expression.anchor === undefined ? "" : JSON.stringify(expression.anchor),
39
+ },
40
+ };
41
+ case "not": return { type: TMW_CONDITION_BLOCK_TYPES.not, inputs: { VALUE: { block: expressionToBlock(expression.value) } } };
42
+ case "all":
43
+ case "any": return {
44
+ type: expression.op === "all" ? TMW_CONDITION_BLOCK_TYPES.all : TMW_CONDITION_BLOCK_TYPES.any,
45
+ extraState: { itemCount: expression.values.length },
46
+ inputs: Object.fromEntries(expression.values.map((value, index) => [`ITEM${index}`, { block: expressionToBlock(value) }])),
47
+ };
48
+ }
49
+ };
50
+ const fieldString = (node, name) => {
51
+ const value = node.fields?.[name];
52
+ if (typeof value !== "string")
53
+ throw new Error(`tmw_condition_field_required:${name}`);
54
+ return value;
55
+ };
56
+ const inputBlock = (node, name) => {
57
+ const value = node.inputs?.[name]?.block;
58
+ if (value === undefined)
59
+ throw new Error(`tmw_condition_input_required:${name}`);
60
+ return value;
61
+ };
62
+ const blockToExpression = (node, depth = 0) => {
63
+ if (depth > 32)
64
+ throw new Error("tmw_condition_depth_exceeded");
65
+ switch (node.type) {
66
+ case TMW_CONDITION_BLOCK_TYPES.literal:
67
+ return { op: "literal", value: fieldString(node, "VALUE") === "TRUE" };
68
+ case TMW_CONDITION_BLOCK_TYPES.eventField: {
69
+ const path = fieldString(node, "PATH").split(".").map((part) => part.trim()).filter(Boolean);
70
+ if (path.length === 0)
71
+ throw new Error("tmw_condition_event_path_required");
72
+ const comparison = fieldString(node, "COMPARISON");
73
+ if (!["eq", "neq", "gt", "gte", "lt", "lte", "contains"].includes(comparison))
74
+ throw new Error("tmw_condition_comparison_invalid");
75
+ return {
76
+ op: "event_field",
77
+ path,
78
+ comparison: comparison,
79
+ value: primitive(JSON.parse(fieldString(node, "VALUE_JSON"))),
80
+ };
81
+ }
82
+ case TMW_CONDITION_BLOCK_TYPES.selectorExists:
83
+ return { op: "selector_exists", selector: JSON.parse(fieldString(node, "SELECTOR_JSON")) };
84
+ case TMW_CONDITION_BLOCK_TYPES.naturalLanguage: {
85
+ const anchorJson = fieldString(node, "ANCHOR_JSON").trim();
86
+ return {
87
+ op: "nl_condition",
88
+ stable_id: fieldString(node, "STABLE_ID"),
89
+ text: fieldString(node, "TEXT"),
90
+ locale: fieldString(node, "LOCALE"),
91
+ ...(anchorJson.length === 0 ? {} : { anchor: JSON.parse(anchorJson) }),
92
+ };
93
+ }
94
+ case TMW_CONDITION_BLOCK_TYPES.not:
95
+ return { op: "not", value: blockToExpression(inputBlock(node, "VALUE"), depth + 1) };
96
+ case TMW_CONDITION_BLOCK_TYPES.all:
97
+ case TMW_CONDITION_BLOCK_TYPES.any: {
98
+ const rawCount = node.extraState?.itemCount;
99
+ if (!Number.isSafeInteger(rawCount) || rawCount < 1 || rawCount > 16)
100
+ throw new Error("tmw_condition_item_count_invalid");
101
+ const values = Array.from({ length: rawCount }, (_, index) => blockToExpression(inputBlock(node, `ITEM${index}`), depth + 1));
102
+ return { op: node.type === TMW_CONDITION_BLOCK_TYPES.all ? "all" : "any", values };
103
+ }
104
+ default: throw new Error(`tmw_condition_block_unsupported:${node.type}`);
105
+ }
106
+ };
107
+ /** 将一个现行 TMW3 condition node 投影为 Blockly JSON;不创建第二份运行时 authority。 */
108
+ export const tmwConditionNodeToWorkspace = (condition) => ({
109
+ blocks: {
110
+ languageVersion: 0,
111
+ blocks: [{
112
+ type: TMW_CONDITION_BLOCK_TYPES.root,
113
+ id: condition.stable_id,
114
+ x: 32,
115
+ y: 32,
116
+ fields: {
117
+ STABLE_ID: condition.stable_id,
118
+ DOMAIN: condition.effective_time.domain,
119
+ DUE_VALUE: condition.effective_time.due_value,
120
+ EFFECTIVE_VALUE: condition.effective_time.effective_value,
121
+ CROSSING_POLICY: condition.effective_time.crossing_policy,
122
+ REAL_TIME_CLOCK: condition.effective_time.real_time_clock ?? "active_play",
123
+ WALL_CLOCK_POLICY: condition.effective_time.wall_clock_policy ?? "active_session_only",
124
+ },
125
+ inputs: { EXPRESSION: { block: expressionToBlock(condition.expression) } },
126
+ }],
127
+ },
128
+ });
129
+ /** 从 Blockly 草稿严格重建同一个 TMW3 condition node。 */
130
+ export const tmwConditionNodeFromWorkspace = (value) => {
131
+ const workspace = parseTmwWorkspaceV3(value);
132
+ const roots = workspace.blocks?.blocks?.filter((block) => block.type === TMW_CONDITION_BLOCK_TYPES.root) ?? [];
133
+ if (roots.length !== 1)
134
+ throw new Error(`tmw_condition_root_count_invalid:${roots.length}`);
135
+ const root = roots[0];
136
+ const domain = fieldString(root, "DOMAIN");
137
+ if (!["logical_seq", "world_time", "presentation_seq", "real_time"].includes(domain))
138
+ throw new Error("tmw_condition_domain_invalid");
139
+ const numberField = (name) => {
140
+ const value = root.fields?.[name];
141
+ const parsed = typeof value === "number" ? value : Number(value);
142
+ if (!Number.isFinite(parsed))
143
+ throw new Error(`tmw_condition_number_invalid:${name}`);
144
+ return parsed;
145
+ };
146
+ const realTime = domain === "real_time";
147
+ return {
148
+ kind: "condition",
149
+ stable_id: fieldString(root, "STABLE_ID"),
150
+ effective_time: {
151
+ domain: domain,
152
+ due_value: numberField("DUE_VALUE"),
153
+ effective_value: numberField("EFFECTIVE_VALUE"),
154
+ crossing_policy: fieldString(root, "CROSSING_POLICY"),
155
+ real_time_clock: realTime ? fieldString(root, "REAL_TIME_CLOCK") : null,
156
+ wall_clock_policy: realTime ? fieldString(root, "WALL_CLOCK_POLICY") : null,
157
+ },
158
+ expression: blockToExpression(inputBlock(root, "EXPRESSION")),
159
+ };
160
+ };
161
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
162
+ const parseNode = (value, active) => {
163
+ if (!isRecord(value) || typeof value.type !== "string" || value.type.length === 0) {
164
+ throw new Error("tmw_workspace_node_invalid");
165
+ }
166
+ if (active.has(value))
167
+ throw new Error("tmw_workspace_cycle_invalid");
168
+ active.add(value);
169
+ if (value.inputs !== undefined) {
170
+ if (!isRecord(value.inputs))
171
+ throw new Error("tmw_workspace_inputs_invalid");
172
+ for (const slot of Object.values(value.inputs)) {
173
+ if (!isRecord(slot))
174
+ throw new Error("tmw_workspace_input_slot_invalid");
175
+ if (slot.block !== undefined)
176
+ parseNode(slot.block, active);
177
+ }
178
+ }
179
+ if (value.next !== undefined) {
180
+ if (!isRecord(value.next))
181
+ throw new Error("tmw_workspace_next_invalid");
182
+ if (value.next.block !== undefined)
183
+ parseNode(value.next.block, active);
184
+ }
185
+ active.delete(value);
186
+ return value;
187
+ };
188
+ /** Fresh-only workspace parser;不迁移、不归一化旧 block。 */
189
+ export const parseTmwWorkspaceV3 = (value) => {
190
+ if (!isRecord(value))
191
+ throw new Error("tmw_workspace_invalid");
192
+ if (value.blocks === undefined)
193
+ return value;
194
+ if (!isRecord(value.blocks))
195
+ throw new Error("tmw_workspace_blocks_invalid");
196
+ if (value.blocks.languageVersion !== undefined
197
+ && (!Number.isSafeInteger(value.blocks.languageVersion) || value.blocks.languageVersion !== 0)) {
198
+ throw new Error("tmw_workspace_language_version_invalid");
199
+ }
200
+ if (value.blocks.blocks !== undefined) {
201
+ if (!Array.isArray(value.blocks.blocks))
202
+ throw new Error("tmw_workspace_roots_invalid");
203
+ value.blocks.blocks.forEach((node) => parseNode(node, new Set()));
204
+ }
205
+ return value;
206
+ };
@@ -0,0 +1,3 @@
1
+ export declare const DRAG_SNAP_RADIUS = 40;
2
+ export declare const DRAG_CONNECT_SNAP_RADIUS = 40;
3
+ export declare function applyDragTolerance(): void;
@@ -0,0 +1,7 @@
1
+ import { config as blocklyConfig } from "blockly";
2
+ export const DRAG_SNAP_RADIUS = 40;
3
+ export const DRAG_CONNECT_SNAP_RADIUS = 40;
4
+ export function applyDragTolerance() {
5
+ blocklyConfig.snapRadius = DRAG_SNAP_RADIUS;
6
+ blocklyConfig.connectingSnapRadius = DRAG_CONNECT_SNAP_RADIUS;
7
+ }
@@ -0,0 +1,67 @@
1
+ import type * as Blockly from "blockly";
2
+ import type { WorldTimeBounds } from "./time-fields.js";
3
+ import type { SdkCatalog } from "../sdk-to-block/signature.js";
4
+ import type { ViewBlockCatalog } from "../view-to-block/index.js";
5
+ export interface CtxEvent {
6
+ readonly id: string;
7
+ readonly name: string;
8
+ }
9
+ export interface CtxEntityAttr {
10
+ readonly key: string;
11
+ readonly label: string;
12
+ }
13
+ export interface CtxEntity {
14
+ readonly id: string;
15
+ readonly name: string;
16
+ readonly attrs: ReadonlyArray<CtxEntityAttr>;
17
+ readonly states?: ReadonlyArray<CtxEntityState>;
18
+ }
19
+ export interface CtxEntityState {
20
+ readonly key: string;
21
+ readonly label: string;
22
+ }
23
+ export interface CtxEdge {
24
+ readonly id: string;
25
+ readonly src_id: string;
26
+ readonly dst_id: string;
27
+ readonly kind: string;
28
+ readonly label: string;
29
+ }
30
+ export interface CtxVariable {
31
+ readonly name: string;
32
+ readonly type: "string" | "number" | "boolean";
33
+ }
34
+ export interface BlocksEditorCtx {
35
+ readonly event_list: ReadonlyArray<CtxEvent>;
36
+ readonly entity_list: ReadonlyArray<CtxEntity>;
37
+ readonly edge_list?: ReadonlyArray<CtxEdge>;
38
+ readonly variable_list?: ReadonlyArray<CtxVariable>;
39
+ readonly sdk_catalog?: SdkCatalog;
40
+ readonly view_catalog?: ViewBlockCatalog;
41
+ readonly worldTime?: WorldTimeBounds;
42
+ }
43
+ export declare const EMPTY_CTX: BlocksEditorCtx;
44
+ export declare const BlocksEditorCtxContext: import("react").Context<BlocksEditorCtx>;
45
+ export declare function useBlocksEditorCtx(): BlocksEditorCtx;
46
+ export declare const WORKSPACE_CTX_PROPERTY: "__blocksEditorCtx__";
47
+ export interface WorkspaceWithCtx {
48
+ [WORKSPACE_CTX_PROPERTY]?: BlocksEditorCtx;
49
+ }
50
+ export declare function attachCtxToWorkspace(workspace: Blockly.WorkspaceSvg, ctx: BlocksEditorCtx): void;
51
+ export declare function readCtxFromWorkspace(workspace: Blockly.Workspace | null | undefined): BlocksEditorCtx;
52
+ export declare const PLACEHOLDER_EVENT_OPTION: readonly [string, string];
53
+ export declare const PLACEHOLDER_ENTITY_OPTION: readonly [string, string];
54
+ export declare const PLACEHOLDER_ATTR_OPTION: readonly [string, string];
55
+ export declare const PLACEHOLDER_EDGE_OPTION: readonly [string, string];
56
+ export declare const PLACEHOLDER_VARIABLE_OPTION: readonly [string, string];
57
+ export declare const PLACEHOLDER_STATE_OPTION: readonly [string, string];
58
+ export declare function eventOptions(ctx: BlocksEditorCtx): ReadonlyArray<readonly [string, string]>;
59
+ export declare function entityOptions(ctx: BlocksEditorCtx): ReadonlyArray<readonly [string, string]>;
60
+ export declare function attrOptionsForEntity(ctx: BlocksEditorCtx, entityId: string): ReadonlyArray<readonly [string, string]>;
61
+ export declare function edgeOptions(ctx: BlocksEditorCtx): ReadonlyArray<readonly [string, string]>;
62
+ export declare function variableOptions(ctx: BlocksEditorCtx): ReadonlyArray<readonly [string, string]>;
63
+ export declare function stateOptionsForEntity(ctx: BlocksEditorCtx, entityId: string): ReadonlyArray<readonly [string, string]>;
64
+ export declare function findEntityName(ctx: BlocksEditorCtx, entityId: string): string;
65
+ export declare function findAttrLabel(ctx: BlocksEditorCtx, entityId: string, attrKey: string): string;
66
+ export declare function findEdgeLabel(ctx: BlocksEditorCtx, edgeId: string): string;
67
+ export declare function findStateLabel(ctx: BlocksEditorCtx, entityId: string, stateKey: string): string;
@@ -0,0 +1,96 @@
1
+ import { createContext, useContext } from "react";
2
+ export const EMPTY_CTX = {
3
+ event_list: [],
4
+ entity_list: []
5
+ };
6
+ export const BlocksEditorCtxContext = createContext(EMPTY_CTX);
7
+ export function useBlocksEditorCtx() {
8
+ return useContext(BlocksEditorCtxContext);
9
+ }
10
+ export const WORKSPACE_CTX_PROPERTY = "__blocksEditorCtx__";
11
+ export function attachCtxToWorkspace(workspace, ctx) {
12
+ workspace[WORKSPACE_CTX_PROPERTY] = ctx;
13
+ }
14
+ export function readCtxFromWorkspace(workspace) {
15
+ if (!workspace)
16
+ return EMPTY_CTX;
17
+ const carrier = workspace;
18
+ return carrier[WORKSPACE_CTX_PROPERTY] ?? EMPTY_CTX;
19
+ }
20
+ export const PLACEHOLDER_EVENT_OPTION = [
21
+ "(no events declared)",
22
+ "__none__"
23
+ ];
24
+ export const PLACEHOLDER_ENTITY_OPTION = [
25
+ "(no entities declared)",
26
+ "__none__"
27
+ ];
28
+ export const PLACEHOLDER_ATTR_OPTION = [
29
+ "(no attrs)",
30
+ "__none__"
31
+ ];
32
+ export const PLACEHOLDER_EDGE_OPTION = [
33
+ "(no edges declared)",
34
+ "__none__"
35
+ ];
36
+ export const PLACEHOLDER_VARIABLE_OPTION = [
37
+ "(no variables)",
38
+ "__none__"
39
+ ];
40
+ export const PLACEHOLDER_STATE_OPTION = [
41
+ "(no states)",
42
+ "__none__"
43
+ ];
44
+ export function eventOptions(ctx) {
45
+ if (ctx.event_list.length === 0)
46
+ return [PLACEHOLDER_EVENT_OPTION];
47
+ return ctx.event_list.map((e) => [e.name, e.id]);
48
+ }
49
+ export function entityOptions(ctx) {
50
+ if (ctx.entity_list.length === 0)
51
+ return [PLACEHOLDER_ENTITY_OPTION];
52
+ return ctx.entity_list.map((e) => [e.name, e.id]);
53
+ }
54
+ export function attrOptionsForEntity(ctx, entityId) {
55
+ const entity = ctx.entity_list.find((e) => e.id === entityId);
56
+ if (!entity || entity.attrs.length === 0)
57
+ return [PLACEHOLDER_ATTR_OPTION];
58
+ return entity.attrs.map((a) => [a.label, a.key]);
59
+ }
60
+ export function edgeOptions(ctx) {
61
+ const edges = ctx.edge_list ?? [];
62
+ if (edges.length === 0)
63
+ return [PLACEHOLDER_EDGE_OPTION];
64
+ return edges.map((e) => [e.label, e.id]);
65
+ }
66
+ export function variableOptions(ctx) {
67
+ const vars = ctx.variable_list ?? [];
68
+ if (vars.length === 0)
69
+ return [PLACEHOLDER_VARIABLE_OPTION];
70
+ return vars.map((v) => [v.name, v.name]);
71
+ }
72
+ export function stateOptionsForEntity(ctx, entityId) {
73
+ const entity = ctx.entity_list.find((e) => e.id === entityId);
74
+ const states = entity?.states ?? [];
75
+ if (states.length === 0)
76
+ return [PLACEHOLDER_STATE_OPTION];
77
+ return states.map((s) => [s.label, s.key]);
78
+ }
79
+ export function findEntityName(ctx, entityId) {
80
+ const entity = ctx.entity_list.find((e) => e.id === entityId);
81
+ return entity?.name ?? entityId;
82
+ }
83
+ export function findAttrLabel(ctx, entityId, attrKey) {
84
+ const entity = ctx.entity_list.find((e) => e.id === entityId);
85
+ const attr = entity?.attrs.find((a) => a.key === attrKey);
86
+ return attr?.label ?? attrKey;
87
+ }
88
+ export function findEdgeLabel(ctx, edgeId) {
89
+ const edge = (ctx.edge_list ?? []).find((e) => e.id === edgeId);
90
+ return edge?.label ?? edgeId;
91
+ }
92
+ export function findStateLabel(ctx, entityId, stateKey) {
93
+ const entity = ctx.entity_list.find((e) => e.id === entityId);
94
+ const state = (entity?.states ?? []).find((s) => s.key === stateKey);
95
+ return state?.label ?? stateKey;
96
+ }
@@ -0,0 +1,12 @@
1
+ export declare const HAT_BLOCK_TYPES: readonly ["when_scenario_start", "when_value_changed", "when_time_changed", "when_entity_state_changed", "when_var_changed", "when_time_reached", "when_recall_success"];
2
+ export type HatBlockType = (typeof HAT_BLOCK_TYPES)[number];
3
+ export declare const HAT_TYPES: ReadonlySet<string>;
4
+ export declare function isHatType(type: string): type is HatBlockType;
5
+ export declare const CONTROL_STATEMENT_TYPES: readonly ["if", "if_else", "repeat", "repeat_until", "wait_seconds", "stop_this_script"];
6
+ export declare const SENSING_REPORTER_TYPES: readonly ["sense_time_year", "sense_time_month", "sense_time_day", "sense_time_hour", "sense_time_minute", "sense_time_second", "sense_object_state", "sense_object_attr", "sense_edge_relation", "sense_relation_ref", "sense_object_frozen"];
7
+ export declare const OPERATOR_BOOLEAN_TYPES: readonly ["op_and", "op_or", "op_not", "op_equals", "op_greater", "op_less", "op_contains", "op_time_crossed"];
8
+ export declare const OPERATOR_NUMBER_TYPES: readonly ["op_add", "op_subtract", "op_multiply", "op_divide", "op_mod", "op_round", "op_abs"];
9
+ export declare const OPERATOR_STRING_TYPES: readonly ["op_join", "op_letter_of", "op_length_of"];
10
+ export declare const VARIABLE_TYPES: readonly ["var_read", "var_set", "var_change", "var_const_boolean", "var_const_value", "var_const_time"];
11
+ export declare const ENTITY_OP_TYPES: readonly ["entity_freeze", "entity_toggle", "entity_enable", "entity_disable", "attr_set"];
12
+ export declare const TIME_OP_TYPES: readonly ["time_advance", "time_set"];
@@ -0,0 +1,79 @@
1
+ // 独立 taxonomy 描述所有新 block 的形状分类 (hat/statement/reporter/boolean)
2
+ // 与 registry.ts 解耦 避免 generator/adapter 依赖 registry 造成循环
3
+ export const HAT_BLOCK_TYPES = [
4
+ "when_scenario_start",
5
+ "when_value_changed",
6
+ "when_time_changed",
7
+ "when_entity_state_changed",
8
+ "when_var_changed",
9
+ "when_time_reached",
10
+ "when_recall_success"
11
+ ];
12
+ export const HAT_TYPES = new Set(HAT_BLOCK_TYPES);
13
+ export function isHatType(type) {
14
+ return HAT_TYPES.has(type);
15
+ }
16
+ export const CONTROL_STATEMENT_TYPES = [
17
+ "if",
18
+ "if_else",
19
+ "repeat",
20
+ "repeat_until",
21
+ "wait_seconds",
22
+ "stop_this_script"
23
+ ];
24
+ export const SENSING_REPORTER_TYPES = [
25
+ "sense_time_year",
26
+ "sense_time_month",
27
+ "sense_time_day",
28
+ "sense_time_hour",
29
+ "sense_time_minute",
30
+ "sense_time_second",
31
+ "sense_object_state",
32
+ "sense_object_attr",
33
+ "sense_edge_relation",
34
+ "sense_relation_ref",
35
+ "sense_object_frozen"
36
+ ];
37
+ export const OPERATOR_BOOLEAN_TYPES = [
38
+ "op_and",
39
+ "op_or",
40
+ "op_not",
41
+ "op_equals",
42
+ "op_greater",
43
+ "op_less",
44
+ "op_contains",
45
+ "op_time_crossed"
46
+ ];
47
+ export const OPERATOR_NUMBER_TYPES = [
48
+ "op_add",
49
+ "op_subtract",
50
+ "op_multiply",
51
+ "op_divide",
52
+ "op_mod",
53
+ "op_round",
54
+ "op_abs"
55
+ ];
56
+ export const OPERATOR_STRING_TYPES = [
57
+ "op_join",
58
+ "op_letter_of",
59
+ "op_length_of"
60
+ ];
61
+ export const VARIABLE_TYPES = [
62
+ "var_read",
63
+ "var_set",
64
+ "var_change",
65
+ "var_const_boolean",
66
+ "var_const_value",
67
+ "var_const_time"
68
+ ];
69
+ export const ENTITY_OP_TYPES = [
70
+ "entity_freeze",
71
+ "entity_toggle",
72
+ "entity_enable",
73
+ "entity_disable",
74
+ "attr_set"
75
+ ];
76
+ export const TIME_OP_TYPES = [
77
+ "time_advance",
78
+ "time_set"
79
+ ];
@@ -0,0 +1,39 @@
1
+ export declare const BLOCK_TYPES: {
2
+ readonly triggerRoot: "trigger_def_root";
3
+ readonly opAnd: "trigger_def_and";
4
+ readonly opOr: "trigger_def_or";
5
+ readonly opNot: "trigger_def_not";
6
+ readonly leafTime: "trigger_def_time";
7
+ readonly leafEvent: "trigger_def_event";
8
+ readonly leafNl: "trigger_def_nl";
9
+ readonly leafAttrRef: "trigger_def_attr_ref";
10
+ readonly leafRelationRef: "trigger_def_relation_ref";
11
+ readonly leafDaySegment: "trigger_def_day_segment";
12
+ };
13
+ export type BlockTypeId = (typeof BLOCK_TYPES)[keyof typeof BLOCK_TYPES];
14
+ export declare const FIELD_NAMES: {
15
+ readonly closeMode: "close_mode";
16
+ readonly eventId: "event_id";
17
+ readonly delayMs: "delay_ms";
18
+ readonly delaySeconds: "delay_seconds";
19
+ readonly text: "text";
20
+ readonly milestone: "milestone";
21
+ readonly entityId: "entity_id";
22
+ readonly attrKey: "attr_key";
23
+ readonly srcId: "src_id";
24
+ readonly dstId: "dst_id";
25
+ readonly kind: "kind";
26
+ readonly segment: "segment";
27
+ readonly expectedValue: "expected_value";
28
+ };
29
+ export declare const INPUT_NAMES: {
30
+ readonly tree: "tree";
31
+ readonly closeWhen: "close_when";
32
+ readonly children: "children";
33
+ readonly child: "child";
34
+ };
35
+ export declare const CLOSE_MODE_OPTIONS: ReadonlyArray<readonly [string, string]>;
36
+ export declare function registerTriggerBlocks(): void;
37
+ export declare function listTriggerBlockTypes(): ReadonlyArray<BlockTypeId>;
38
+ export declare function isTriggerBlockRegistered(type: BlockTypeId): boolean;
39
+ export { HAT_BLOCK_TYPES, HAT_TYPES, isHatType, CONTROL_STATEMENT_TYPES, SENSING_REPORTER_TYPES, OPERATOR_BOOLEAN_TYPES, OPERATOR_NUMBER_TYPES, OPERATOR_STRING_TYPES, VARIABLE_TYPES, ENTITY_OP_TYPES, TIME_OP_TYPES } from "./registry-taxonomy.js";