@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.
- package/LICENSE +46 -0
- package/README.md +40 -0
- package/dist/adapters/trigger.d.ts +72 -0
- package/dist/adapters/trigger.js +206 -0
- package/dist/blockly-config.d.ts +3 -0
- package/dist/blockly-config.js +7 -0
- package/dist/blocks/ctx.d.ts +67 -0
- package/dist/blocks/ctx.js +96 -0
- package/dist/blocks/registry-taxonomy.d.ts +12 -0
- package/dist/blocks/registry-taxonomy.js +79 -0
- package/dist/blocks/registry.d.ts +39 -0
- package/dist/blocks/registry.js +1000 -0
- package/dist/blocks/time-fields.d.ts +16 -0
- package/dist/blocks/time-fields.js +45 -0
- package/dist/blocks/toolbox.d.ts +53 -0
- package/dist/blocks/toolbox.js +195 -0
- package/dist/fields/AttrPickerField.d.ts +13 -0
- package/dist/fields/AttrPickerField.js +48 -0
- package/dist/fields/EdgePickerField.d.ts +10 -0
- package/dist/fields/EdgePickerField.js +36 -0
- package/dist/fields/EntityPickerField.d.ts +10 -0
- package/dist/fields/EntityPickerField.js +36 -0
- package/dist/fields/StatePickerField.d.ts +13 -0
- package/dist/fields/StatePickerField.js +47 -0
- package/dist/fields/TimePickerField.d.ts +10 -0
- package/dist/fields/TimePickerField.js +81 -0
- package/dist/fields/picker-events.d.ts +21 -0
- package/dist/fields/picker-events.js +28 -0
- package/dist/fields/register.d.ts +6 -0
- package/dist/fields/register.js +22 -0
- package/dist/i18n/messages-zh.d.ts +6 -0
- package/dist/i18n/messages-zh.js +34 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +11 -0
- package/dist/migration/index.d.ts +1 -0
- package/dist/migration/index.js +1 -0
- package/dist/migration/types.d.ts +91 -0
- package/dist/migration/types.js +1 -0
- package/dist/react/PickerHost.d.ts +6 -0
- package/dist/react/PickerHost.js +110 -0
- package/dist/sdk-to-block/index.d.ts +4 -0
- package/dist/sdk-to-block/index.js +4 -0
- package/dist/sdk-to-block/parser.d.ts +2 -0
- package/dist/sdk-to-block/parser.js +101 -0
- package/dist/sdk-to-block/register.d.ts +3 -0
- package/dist/sdk-to-block/register.js +27 -0
- package/dist/sdk-to-block/signature.d.ts +31 -0
- package/dist/sdk-to-block/signature.js +12 -0
- package/dist/sdk-to-block/to-block-def.d.ts +25 -0
- package/dist/sdk-to-block/to-block-def.js +71 -0
- package/dist/themes/index.d.ts +4 -0
- package/dist/themes/index.js +12 -0
- package/dist/themes/liquid-glass-dark.d.ts +57 -0
- package/dist/themes/liquid-glass-dark.js +187 -0
- package/dist/themes/liquid-glass.css +195 -0
- package/dist/view-to-block/index.d.ts +10 -0
- package/dist/view-to-block/index.js +67 -0
- package/dist/workspace.d.ts +33 -0
- package/dist/workspace.js +306 -0
- package/package.json +65 -0
- package/public/blockly-media/1x1.gif +0 -0
- package/public/blockly-media/click.mp3 +0 -0
- package/public/blockly-media/delete-icon.svg +1 -0
- package/public/blockly-media/delete.mp3 +0 -0
- package/public/blockly-media/disconnect.mp3 +0 -0
- package/public/blockly-media/drop.mp3 +0 -0
- package/public/blockly-media/dropdown-arrow.svg +1 -0
- package/public/blockly-media/foldout-icon.svg +1 -0
- package/public/blockly-media/handclosed.cur +0 -0
- package/public/blockly-media/handdelete.cur +0 -0
- package/public/blockly-media/handopen.cur +0 -0
- package/public/blockly-media/pilcrow.png +0 -0
- package/public/blockly-media/quote0.png +0 -0
- package/public/blockly-media/quote1.png +0 -0
- package/public/blockly-media/resize-handle.svg +3 -0
- package/public/blockly-media/sprites.svg +74 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from "react";
|
|
3
|
+
import * as Blockly from "blockly";
|
|
4
|
+
import { BlocklyWorkspace } from "react-blockly";
|
|
5
|
+
import { BlocksEditorCtxContext, EMPTY_CTX, attachCtxToWorkspace } from "./blocks/ctx.js";
|
|
6
|
+
import { BLOCK_TYPES, registerTriggerBlocks } from "./blocks/registry.js";
|
|
7
|
+
import { buildToolbox, CATEGORY_IDS, categoryDisplayNameOf } from "./blocks/toolbox.js";
|
|
8
|
+
import { registerPickerFieldsOnce } from "./fields/register.js";
|
|
9
|
+
import { PickerHost } from "./react/PickerHost.js";
|
|
10
|
+
import { applyLiquidGlassCss, liquidGlassDarkTheme } from "./themes/index.js";
|
|
11
|
+
import { GRID_COLOUR } from "./themes/liquid-glass-dark.js";
|
|
12
|
+
import { applyDragTolerance } from "./blockly-config.js";
|
|
13
|
+
import { applyZhMessages } from "./i18n/messages-zh.js";
|
|
14
|
+
import { parseTmwWorkspaceV3, TMW_CONDITION_BLOCK_TYPES } from "./adapters/trigger.js";
|
|
15
|
+
import { isViewActionBlockType, registerViewBlocks } from "./view-to-block/index.js";
|
|
16
|
+
applyZhMessages(Blockly);
|
|
17
|
+
applyDragTolerance();
|
|
18
|
+
const DEFAULT_MEDIA_BASE = "/blockly-media/";
|
|
19
|
+
const EMPTY_WORKSPACE_JSON = '{"blocks":{"languageVersion":0,"blocks":[]}}';
|
|
20
|
+
const TMW_CONDITION_MUTATOR = "tmw_v3_condition_items";
|
|
21
|
+
let tmwConditionBlocksRegistered = false;
|
|
22
|
+
const registerTmwConditionBlocks = () => {
|
|
23
|
+
if (tmwConditionBlocksRegistered)
|
|
24
|
+
return;
|
|
25
|
+
if (!Blockly.Extensions.isRegistered(TMW_CONDITION_MUTATOR)) {
|
|
26
|
+
Blockly.Extensions.registerMutator(TMW_CONDITION_MUTATOR, {
|
|
27
|
+
itemCount_: 2,
|
|
28
|
+
saveExtraState() { return { itemCount: this.itemCount_ }; },
|
|
29
|
+
loadExtraState(state) {
|
|
30
|
+
const count = typeof state.itemCount === "number" ? state.itemCount : 2;
|
|
31
|
+
this.itemCount_ = Math.max(1, Math.min(16, Math.floor(count)));
|
|
32
|
+
this.updateShape_();
|
|
33
|
+
},
|
|
34
|
+
updateShape_() {
|
|
35
|
+
let index = 0;
|
|
36
|
+
while (this.getInput(`ITEM${index}`) !== null)
|
|
37
|
+
this.removeInput(`ITEM${index++}`);
|
|
38
|
+
for (let item = 0; item < this.itemCount_; item += 1) {
|
|
39
|
+
this.appendValueInput(`ITEM${item}`).setCheck("Boolean").appendField(item === 0 ? "条件" : "以及");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
Blockly.common.defineBlocksWithJsonArray([
|
|
45
|
+
{
|
|
46
|
+
type: TMW_CONDITION_BLOCK_TYPES.root,
|
|
47
|
+
message0: "条件节点 %1",
|
|
48
|
+
args0: [{ type: "field_input", name: "STABLE_ID", text: "condition.new" }],
|
|
49
|
+
message1: "时钟 %1 到期 %2 生效 %3 穿越 %4",
|
|
50
|
+
args1: [
|
|
51
|
+
{ type: "field_dropdown", name: "DOMAIN", options: [["逻辑序", "logical_seq"], ["世界时间", "world_time"], ["演出序", "presentation_seq"], ["真实时间", "real_time"]] },
|
|
52
|
+
{ type: "field_number", name: "DUE_VALUE", value: 0 },
|
|
53
|
+
{ type: "field_number", name: "EFFECTIVE_VALUE", value: 0 },
|
|
54
|
+
{ type: "field_dropdown", name: "CROSSING_POLICY", options: [["仅正向", "forward_only"], ["仅反向", "backward_only"], ["双向", "both_directions"]] },
|
|
55
|
+
],
|
|
56
|
+
message2: "真实时钟 %1 挂起策略 %2",
|
|
57
|
+
args2: [
|
|
58
|
+
{ type: "field_dropdown", name: "REAL_TIME_CLOCK", options: [["活跃游玩", "active_play"], ["会话单调钟", "monotonic_session"], ["墙上时间", "wall_clock"]] },
|
|
59
|
+
{ type: "field_dropdown", name: "WALL_CLOCK_POLICY", options: [["仅活跃会话", "active_session_only"], ["挂起仍流逝", "elapse_while_suspended"], ["拒绝挂起", "reject"]] },
|
|
60
|
+
],
|
|
61
|
+
message3: "满足 %1",
|
|
62
|
+
args3: [{ type: "input_value", name: "EXPRESSION", check: "Boolean" }],
|
|
63
|
+
colour: 174,
|
|
64
|
+
tooltip: "现行 TriggerDocumentV3 condition;时间坐标和表达式一起无损写回",
|
|
65
|
+
},
|
|
66
|
+
{ type: TMW_CONDITION_BLOCK_TYPES.all, message0: "全部满足", output: "Boolean", colour: 118, mutator: TMW_CONDITION_MUTATOR },
|
|
67
|
+
{ type: TMW_CONDITION_BLOCK_TYPES.any, message0: "任一满足", output: "Boolean", colour: 118, mutator: TMW_CONDITION_MUTATOR },
|
|
68
|
+
{ type: TMW_CONDITION_BLOCK_TYPES.not, message0: "不满足 %1", args0: [{ type: "input_value", name: "VALUE", check: "Boolean" }], output: "Boolean", colour: 118 },
|
|
69
|
+
{ type: TMW_CONDITION_BLOCK_TYPES.literal, message0: "常量 %1", args0: [{ type: "field_dropdown", name: "VALUE", options: [["真", "TRUE"], ["假", "FALSE"]] }], output: "Boolean", colour: 210 },
|
|
70
|
+
{
|
|
71
|
+
type: TMW_CONDITION_BLOCK_TYPES.eventField,
|
|
72
|
+
message0: "事件属性 %1 %2 JSON %3",
|
|
73
|
+
args0: [
|
|
74
|
+
{ type: "field_input", name: "PATH", text: "timestamp" },
|
|
75
|
+
{ type: "field_dropdown", name: "COMPARISON", options: [["=", "eq"], ["≠", "neq"], [">", "gt"], ["≥", "gte"], ["<", "lt"], ["≤", "lte"], ["包含", "contains"]] },
|
|
76
|
+
{ type: "field_input", name: "VALUE_JSON", text: "0" },
|
|
77
|
+
],
|
|
78
|
+
output: "Boolean",
|
|
79
|
+
colour: 210,
|
|
80
|
+
tooltip: "读取本次 committed event payload 的属性;timestamp 也是普通事件属性",
|
|
81
|
+
},
|
|
82
|
+
{ type: TMW_CONDITION_BLOCK_TYPES.selectorExists, message0: "存在选择器 JSON %1", args0: [{ type: "field_input", name: "SELECTOR_JSON", text: "{\"op\":\"entity_kind\",\"entity_kind\":\"npc\"}" }], output: "Boolean", colour: 210 },
|
|
83
|
+
{
|
|
84
|
+
type: TMW_CONDITION_BLOCK_TYPES.naturalLanguage,
|
|
85
|
+
message0: "请 Judge 判断 %1",
|
|
86
|
+
args0: [{ type: "field_input", name: "TEXT", text: "自然语言条件" }],
|
|
87
|
+
message1: "叶子 ID %1 locale %2",
|
|
88
|
+
args1: [{ type: "field_input", name: "STABLE_ID", text: "nl.condition" }, { type: "field_input", name: "LOCALE", text: "zh-CN" }],
|
|
89
|
+
message2: "可选 anchor JSON %1",
|
|
90
|
+
args2: [{ type: "field_input", name: "ANCHOR_JSON", text: "" }],
|
|
91
|
+
output: "Boolean",
|
|
92
|
+
colour: 285,
|
|
93
|
+
tooltip: "现行 TMW3 nl_condition;运行时产生 unknown 并请求语义 Judge,不直接视为 true",
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
tmwConditionBlocksRegistered = true;
|
|
97
|
+
};
|
|
98
|
+
const TMW_CONDITION_TOOLBOX = {
|
|
99
|
+
kind: "categoryToolbox",
|
|
100
|
+
contents: [
|
|
101
|
+
{ kind: "category", name: "条件节点", colour: "#16897b", contents: [{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.root }] },
|
|
102
|
+
{ kind: "category", name: "逻辑", colour: "#59a83b", contents: [
|
|
103
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.all },
|
|
104
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.any },
|
|
105
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.not },
|
|
106
|
+
] },
|
|
107
|
+
{ kind: "category", name: "条件", colour: "#3373cc", contents: [
|
|
108
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.eventField },
|
|
109
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.selectorExists },
|
|
110
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.naturalLanguage },
|
|
111
|
+
{ kind: "block", type: TMW_CONDITION_BLOCK_TYPES.literal },
|
|
112
|
+
] },
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
const parseWorkspaceJson = (raw, schema, authorizedViewBlockTypes) => {
|
|
116
|
+
const parsed = parseTmwWorkspaceV3(JSON.parse(raw));
|
|
117
|
+
if (schema === "trigger-definition") {
|
|
118
|
+
const roots = parsed.blocks?.blocks?.filter((block) => block.type === BLOCK_TYPES.triggerRoot) ?? [];
|
|
119
|
+
if (roots.length > 1)
|
|
120
|
+
throw new Error(`trigger_definition_workspace_multiple_roots:${roots.length}`);
|
|
121
|
+
}
|
|
122
|
+
const persistedViewBlockTypes = new Set();
|
|
123
|
+
const collectViewBlockTypes = (node) => {
|
|
124
|
+
if (node === undefined)
|
|
125
|
+
return;
|
|
126
|
+
if (isViewActionBlockType(node.type))
|
|
127
|
+
persistedViewBlockTypes.add(node.type);
|
|
128
|
+
for (const slot of Object.values(node.inputs ?? {}))
|
|
129
|
+
collectViewBlockTypes(slot.block);
|
|
130
|
+
collectViewBlockTypes(node.next?.block);
|
|
131
|
+
};
|
|
132
|
+
for (const root of parsed.blocks?.blocks ?? [])
|
|
133
|
+
collectViewBlockTypes(root);
|
|
134
|
+
const unauthorized = [...persistedViewBlockTypes]
|
|
135
|
+
.filter((type) => !authorizedViewBlockTypes.has(type));
|
|
136
|
+
if (unauthorized.length > 0) {
|
|
137
|
+
throw new Error(`tmw_workspace_view_capability_revoked:${unauthorized.sort().join(",")}`);
|
|
138
|
+
}
|
|
139
|
+
return { parsed, serialized: JSON.stringify(parsed) };
|
|
140
|
+
};
|
|
141
|
+
const disableUnauthorizedViewBlocks = (workspace, authorizedViewBlockTypes) => {
|
|
142
|
+
for (const block of workspace.getAllBlocks(false)) {
|
|
143
|
+
if (!isViewActionBlockType(block.type) || authorizedViewBlockTypes.has(block.type))
|
|
144
|
+
continue;
|
|
145
|
+
block.setDisabledReason(true, "unauthorized_view_action");
|
|
146
|
+
block.setWarningText("此视图操作不属于当前 manifest 版本,已禁用且不会编译执行。");
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const SHELL_STYLE = {
|
|
150
|
+
display: "flex",
|
|
151
|
+
flexDirection: "column",
|
|
152
|
+
gap: "0.5rem",
|
|
153
|
+
height: "100%",
|
|
154
|
+
flex: 1,
|
|
155
|
+
minHeight: 0
|
|
156
|
+
};
|
|
157
|
+
const WORKSPACE_HOST_STYLE = { flex: 1, minHeight: "20rem" };
|
|
158
|
+
const TriggerWorkspace = ({ schema, workspaceJson, ctx, onWorkspaceJsonChange, mediaBaseUrl, toolbox, authorizedViewBlockTypes, className, workspaceRefOut }) => {
|
|
159
|
+
// Registration must precede the child BlocklyWorkspace render, but it must not run
|
|
160
|
+
// as an import side effect because Rollup may evaluate this workspace module in
|
|
161
|
+
// multiple chunks that share Blockly's global block registry.
|
|
162
|
+
registerTriggerBlocks();
|
|
163
|
+
if (schema === "tmw-condition-v3")
|
|
164
|
+
registerTmwConditionBlocks();
|
|
165
|
+
registerPickerFieldsOnce();
|
|
166
|
+
const containerRef = useRef(null);
|
|
167
|
+
const onChangeRef = useRef(onWorkspaceJsonChange);
|
|
168
|
+
onChangeRef.current = onWorkspaceJsonChange;
|
|
169
|
+
const initialJsonRef = useRef(workspaceJson);
|
|
170
|
+
initialJsonRef.current = workspaceJson;
|
|
171
|
+
const lastEmittedRef = useRef("");
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
const workspace = workspaceRefOut.current;
|
|
174
|
+
if (workspace === null || workspaceJson.length === 0)
|
|
175
|
+
return;
|
|
176
|
+
const current = JSON.stringify(Blockly.serialization.workspaces.save(workspace));
|
|
177
|
+
const target = parseWorkspaceJson(workspaceJson, schema, authorizedViewBlockTypes);
|
|
178
|
+
Blockly.Events.disable();
|
|
179
|
+
try {
|
|
180
|
+
if (current === target.serialized) {
|
|
181
|
+
disableUnauthorizedViewBlocks(workspace, authorizedViewBlockTypes);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
lastEmittedRef.current = target.serialized;
|
|
185
|
+
Blockly.serialization.workspaces.load(target.parsed, workspace);
|
|
186
|
+
disableUnauthorizedViewBlocks(workspace, authorizedViewBlockTypes);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
Blockly.Events.enable();
|
|
190
|
+
}
|
|
191
|
+
if (target.serialized !== workspaceJson)
|
|
192
|
+
onChangeRef.current(target.serialized);
|
|
193
|
+
}, [authorizedViewBlockTypes, schema, workspaceJson, workspaceRefOut]);
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
const workspace = workspaceRefOut.current;
|
|
196
|
+
if (workspace !== null) {
|
|
197
|
+
attachCtxToWorkspace(workspace, ctx);
|
|
198
|
+
}
|
|
199
|
+
}, [ctx, workspaceRefOut]);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const host = containerRef.current;
|
|
202
|
+
if (host === null)
|
|
203
|
+
return;
|
|
204
|
+
return applyLiquidGlassCss(host);
|
|
205
|
+
}, []);
|
|
206
|
+
const workspaceConfiguration = useMemo(() => ({
|
|
207
|
+
theme: liquidGlassDarkTheme,
|
|
208
|
+
media: mediaBaseUrl,
|
|
209
|
+
renderer: "zelos",
|
|
210
|
+
grid: { spacing: 24, length: 2, snap: true, colour: GRID_COLOUR },
|
|
211
|
+
...(schema === "trigger-definition" || schema === "tmw-condition-v3"
|
|
212
|
+
? { maxInstances: { [schema === "tmw-condition-v3" ? TMW_CONDITION_BLOCK_TYPES.root : BLOCK_TYPES.triggerRoot]: 1 } }
|
|
213
|
+
: {}),
|
|
214
|
+
trashcan: false,
|
|
215
|
+
zoom: { controls: false, wheel: true, startScale: 0.9, maxScale: 1.5, minScale: 0.5 }
|
|
216
|
+
}), [mediaBaseUrl, schema]);
|
|
217
|
+
const handleWorkspaceChange = useCallback((workspace) => {
|
|
218
|
+
const workspaceJsonNext = Blockly.serialization.workspaces.save(workspace);
|
|
219
|
+
const serialized = JSON.stringify(workspaceJsonNext);
|
|
220
|
+
if (lastEmittedRef.current === serialized)
|
|
221
|
+
return;
|
|
222
|
+
lastEmittedRef.current = serialized;
|
|
223
|
+
onChangeRef.current(serialized);
|
|
224
|
+
}, []);
|
|
225
|
+
const handleInject = useCallback((workspace) => {
|
|
226
|
+
workspaceRefOut.current = workspace;
|
|
227
|
+
attachCtxToWorkspace(workspace, ctx);
|
|
228
|
+
const seed = initialJsonRef.current;
|
|
229
|
+
if (seed.length === 0)
|
|
230
|
+
return;
|
|
231
|
+
try {
|
|
232
|
+
const target = parseWorkspaceJson(seed, schema, authorizedViewBlockTypes);
|
|
233
|
+
const parsed = target.parsed;
|
|
234
|
+
lastEmittedRef.current = target.serialized;
|
|
235
|
+
Blockly.serialization.workspaces.load(parsed, workspace);
|
|
236
|
+
disableUnauthorizedViewBlocks(workspace, authorizedViewBlockTypes);
|
|
237
|
+
if (target.serialized !== seed)
|
|
238
|
+
onChangeRef.current(target.serialized);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
console.warn("blocks-editor: failed to load initial workspaceJson", err);
|
|
242
|
+
}
|
|
243
|
+
}, [authorizedViewBlockTypes, ctx, schema, workspaceRefOut]);
|
|
244
|
+
const handleDispose = useCallback(() => {
|
|
245
|
+
workspaceRefOut.current = null;
|
|
246
|
+
}, [workspaceRefOut]);
|
|
247
|
+
return (_jsx("div", { ref: containerRef, className: className, style: WORKSPACE_HOST_STYLE, children: _jsx(BlocklyWorkspace, { className: "blocks-editor__blockly-host", toolboxConfiguration: toolbox, workspaceConfiguration: workspaceConfiguration, onWorkspaceChange: handleWorkspaceChange, onInject: handleInject, onDispose: handleDispose }) }));
|
|
248
|
+
};
|
|
249
|
+
export const BlocksWorkspace = forwardRef(function BlocksWorkspace(props, ref) {
|
|
250
|
+
const { schema, workspaceJson, ctx, onWorkspaceJsonChange, className, mediaBaseUrl } = props;
|
|
251
|
+
if (schema !== "trigger" && schema !== "trigger-definition" && schema !== "tmw-condition-v3") {
|
|
252
|
+
throw new Error(`BlocksWorkspace schema=${schema} not supported yet`);
|
|
253
|
+
}
|
|
254
|
+
const resolvedMedia = mediaBaseUrl ?? DEFAULT_MEDIA_BASE;
|
|
255
|
+
const resolvedJson = workspaceJson.length === 0 ? EMPTY_WORKSPACE_JSON : workspaceJson;
|
|
256
|
+
const workspaceRef = useRef(null);
|
|
257
|
+
const triggerCtx = useMemo(() => {
|
|
258
|
+
const next = {
|
|
259
|
+
event_list: ctx.event_list,
|
|
260
|
+
entity_list: ctx.entity_list
|
|
261
|
+
};
|
|
262
|
+
if (ctx.edge_list !== undefined)
|
|
263
|
+
next.edge_list = ctx.edge_list;
|
|
264
|
+
if (ctx.variable_list !== undefined)
|
|
265
|
+
next.variable_list = ctx.variable_list;
|
|
266
|
+
if (ctx.sdk_catalog !== undefined)
|
|
267
|
+
next.sdk_catalog = ctx.sdk_catalog;
|
|
268
|
+
if (ctx.worldTime !== undefined)
|
|
269
|
+
next.worldTime = ctx.worldTime;
|
|
270
|
+
if (ctx.view_catalog !== undefined)
|
|
271
|
+
next.view_catalog = ctx.view_catalog;
|
|
272
|
+
return next;
|
|
273
|
+
}, [ctx]);
|
|
274
|
+
const viewBlockTypes = useMemo(() => schema === "trigger" && triggerCtx.view_catalog !== undefined
|
|
275
|
+
? registerViewBlocks(triggerCtx.view_catalog)
|
|
276
|
+
: [], [schema, triggerCtx.view_catalog]);
|
|
277
|
+
const authorizedViewBlockTypes = useMemo(() => new Set(viewBlockTypes), [viewBlockTypes]);
|
|
278
|
+
const toolboxConfiguration = useMemo(() => schema === "tmw-condition-v3"
|
|
279
|
+
? TMW_CONDITION_TOOLBOX
|
|
280
|
+
: JSON.parse(JSON.stringify(buildToolbox({ schema, viewBlockTypes }))), [schema, viewBlockTypes]);
|
|
281
|
+
useImperativeHandle(ref, () => ({
|
|
282
|
+
selectCategory(id) {
|
|
283
|
+
const ws = workspaceRef.current;
|
|
284
|
+
if (!ws)
|
|
285
|
+
return;
|
|
286
|
+
const toolbox = ws.getToolbox();
|
|
287
|
+
if (!toolbox)
|
|
288
|
+
return;
|
|
289
|
+
const displayName = categoryDisplayNameOf(id);
|
|
290
|
+
const items = toolbox.getToolboxItems?.() ?? [];
|
|
291
|
+
for (let i = 0; i < items.length; i++) {
|
|
292
|
+
const it = items[i];
|
|
293
|
+
const name = it.getName?.();
|
|
294
|
+
if (name === displayName) {
|
|
295
|
+
toolbox.selectItemByPosition?.(i);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
getWorkspace() {
|
|
301
|
+
return workspaceRef.current;
|
|
302
|
+
}
|
|
303
|
+
}), []);
|
|
304
|
+
return (_jsx(BlocksEditorCtxContext.Provider, { value: triggerCtx ?? EMPTY_CTX, children: _jsxs("div", { className: className, style: SHELL_STYLE, children: [_jsx(TriggerWorkspace, { schema: schema, workspaceJson: resolvedJson, ctx: triggerCtx, onWorkspaceJsonChange: onWorkspaceJsonChange, mediaBaseUrl: resolvedMedia, toolbox: toolboxConfiguration, authorizedViewBlockTypes: authorizedViewBlockTypes, workspaceRefOut: workspaceRef, ...(className ? { className } : {}) }), _jsx(PickerHost, {})] }) }));
|
|
305
|
+
});
|
|
306
|
+
export { CATEGORY_IDS };
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@world-engines/blocks-editor",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "面向浏览器与 Vite 的 Blockly 场景编辑器组件",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"browser": "./dist/index.js",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"public",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": "^18.3.1",
|
|
22
|
+
"react-dom": "^18.3.1"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@world-engines/tmw": "0.1.0-alpha.0",
|
|
26
|
+
"@world-engines/protocol-ts": "0.1.0-alpha.0",
|
|
27
|
+
"@world-engines/view-exposure": "0.0.0",
|
|
28
|
+
"@blockly/field-multilineinput": "^6.0.9",
|
|
29
|
+
"@world-engines/monaco-host": "0.1.0",
|
|
30
|
+
"blockly": "^13.1.0",
|
|
31
|
+
"react-blockly": "^9.0.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"worldengine_source_sha256": "3d10b3aa183316e67c98fb03db2660a41e8d5e18906e04ea5c685b84cec8c633",
|
|
37
|
+
"worldengine_source_identity_provenance": {
|
|
38
|
+
"schema": "worldengine-public-namespace-projection/v1",
|
|
39
|
+
"kind": "composite-source-identity; namespace projection; not-full-source-recompile",
|
|
40
|
+
"original_name": "@chat/blocks-editor",
|
|
41
|
+
"original_archive_sha256": "78128a459e6d08da84115c261d8bfe775c5f3f763b2cddc3149c038e90bdc99b",
|
|
42
|
+
"original_source_sha256": "375e73a1788e6e4d8966c7342ea9e4e203cd777655d7ee714dcc6c95e0371356",
|
|
43
|
+
"projected_name": "@world-engines/blocks-editor",
|
|
44
|
+
"package_name_mapping": {
|
|
45
|
+
"@chat/blocks-editor": "@world-engines/blocks-editor",
|
|
46
|
+
"@chat/ladybug-bridge": "@world-engines/ladybug-bridge",
|
|
47
|
+
"@chat/monaco-host": "@world-engines/monaco-host",
|
|
48
|
+
"@chat/protocol-ts": "@world-engines/protocol-ts",
|
|
49
|
+
"@chat/scenario-author-source": "@world-engines/scenario-author-source",
|
|
50
|
+
"@chat/scenario-review-snapshot": "@world-engines/scenario-review-snapshot",
|
|
51
|
+
"@chat/tmw": "@world-engines/tmw",
|
|
52
|
+
"@chat/view-exposure": "@world-engines/view-exposure",
|
|
53
|
+
"@world-engines/chatplay-vite-plugin": "@world-engines/chatplay-vite-plugin",
|
|
54
|
+
"@worldengine/agent-kit": "@world-engines/agent-kit",
|
|
55
|
+
"@worldengine/authoring-bridge": "@world-engines/authoring-bridge",
|
|
56
|
+
"@worldengine/authoring-ui": "@world-engines/authoring-ui",
|
|
57
|
+
"@worldengine/create-project": "@world-engines/create-project",
|
|
58
|
+
"@worldengine/project-format": "@world-engines/project-format",
|
|
59
|
+
"@worldengine/project-host": "@world-engines/project-host",
|
|
60
|
+
"@worldengine/project-setup": "@world-engines/project-setup",
|
|
61
|
+
"@worldengine/spatial-authoring": "@world-engines/spatial-authoring",
|
|
62
|
+
"@worldengine/view-sdk": "@world-engines/view-sdk"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#041E49"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="12.71" height="8.79" viewBox="0 0 12.71 8.79"><title>dropdown-arrow</title><g opacity="0.1"><path d="M12.71,2.44A2.41,2.41,0,0,1,12,4.16L8.08,8.08a2.45,2.45,0,0,1-3.45,0L0.72,4.16A2.42,2.42,0,0,1,0,2.44,2.48,2.48,0,0,1,.71.71C1,0.47,1.43,0,6.36,0S11.75,0.46,12,.71A2.44,2.44,0,0,1,12.71,2.44Z" fill="#231f20"/></g><path d="M6.36,7.79a1.43,1.43,0,0,1-1-.42L1.42,3.45a1.44,1.44,0,0,1,0-2c0.56-.56,9.31-0.56,9.87,0a1.44,1.44,0,0,1,0,2L7.37,7.37A1.43,1.43,0,0,1,6.36,7.79Z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#041E49"><path d="M480-360 280-560h400L480-360Z"/></svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="96px" height="124px">
|
|
3
|
+
<style type="text/css">
|
|
4
|
+
#background {
|
|
5
|
+
fill: none;
|
|
6
|
+
}
|
|
7
|
+
.arrows {
|
|
8
|
+
fill: #000;
|
|
9
|
+
stroke: none;
|
|
10
|
+
}
|
|
11
|
+
.selected>.arrows {
|
|
12
|
+
fill: #fff;
|
|
13
|
+
}
|
|
14
|
+
.checkmark {
|
|
15
|
+
fill: #000;
|
|
16
|
+
font-family: sans-serif;
|
|
17
|
+
font-size: 10pt;
|
|
18
|
+
text-anchor: middle;
|
|
19
|
+
}
|
|
20
|
+
.trash {
|
|
21
|
+
fill: #888;
|
|
22
|
+
}
|
|
23
|
+
.zoom {
|
|
24
|
+
fill: none;
|
|
25
|
+
stroke: #888;
|
|
26
|
+
stroke-width: 2;
|
|
27
|
+
stroke-linecap: round;
|
|
28
|
+
}
|
|
29
|
+
.zoom>.center {
|
|
30
|
+
fill: #888;
|
|
31
|
+
stroke-width: 0;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
<rect id="background" width="96" height="124" x="0" y="0" />
|
|
35
|
+
|
|
36
|
+
<g>
|
|
37
|
+
<path class="arrows" d="M 13,1.5 13,14.5 1.74,8 z" />
|
|
38
|
+
<path class="arrows" d="M 17.5,3 30.5,3 24,14.26 z" />
|
|
39
|
+
<path class="arrows" d="M 35,1.5 35,14.5 46.26,8 z" />
|
|
40
|
+
</g>
|
|
41
|
+
<g class="selected" transform="translate(0, 16)">
|
|
42
|
+
<path class="arrows" d="M 13,1.5 13,14.5 1.74,8 z" />
|
|
43
|
+
<path class="arrows" d="M 17.5,3 30.5,3 24,14.26 z" />
|
|
44
|
+
<path class="arrows" d="M 35,1.5 35,14.5 46.26,8 z" />
|
|
45
|
+
</g>
|
|
46
|
+
|
|
47
|
+
<text class="checkmark" x="55.5" y="28">✓</text>
|
|
48
|
+
|
|
49
|
+
<g class="trash">
|
|
50
|
+
<path d="M 2,41 v 6 h 42 v -6 h -10.5 l -3,-3 h -15 l -3,3 z" />
|
|
51
|
+
<rect width="36" height="20" x="5" y="50" />
|
|
52
|
+
<rect width="36" height="42" x="5" y="50" rx="4" ry="4" />
|
|
53
|
+
</g>
|
|
54
|
+
|
|
55
|
+
<g class="zoom">
|
|
56
|
+
<circle r="11.5" cx="16" cy="108" />
|
|
57
|
+
<circle r="4.3" cx="16" cy="108" class="center" />
|
|
58
|
+
<path d="m 28,108 h3" />
|
|
59
|
+
<path d="m 1,108 h3" />
|
|
60
|
+
<path d="m 16,120 v3" />
|
|
61
|
+
<path d="m 16,93 v3" />
|
|
62
|
+
</g>
|
|
63
|
+
|
|
64
|
+
<g class="zoom">
|
|
65
|
+
<circle r="15" cx="48" cy="108" />
|
|
66
|
+
<path d="m 48,101.6 v12.8" />
|
|
67
|
+
<path d="m 41.6,108 h12.8" />
|
|
68
|
+
</g>
|
|
69
|
+
|
|
70
|
+
<g class="zoom">
|
|
71
|
+
<circle r="15" cx="80" cy="108" />
|
|
72
|
+
<path d="m 73.6,108 h12.8" />
|
|
73
|
+
</g>
|
|
74
|
+
</svg>
|