@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,6 @@
|
|
|
1
|
+
export declare const FIELD_ENTITY_PICKER: "field_entity_picker";
|
|
2
|
+
export declare const FIELD_ATTR_PICKER: "field_attr_picker";
|
|
3
|
+
export declare const FIELD_EDGE_PICKER: "field_edge_picker";
|
|
4
|
+
export declare const FIELD_STATE_PICKER: "field_state_picker";
|
|
5
|
+
export declare const FIELD_TIME_PICKER: "field_time_picker";
|
|
6
|
+
export declare function registerPickerFieldsOnce(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as Blockly from "blockly";
|
|
2
|
+
import { AttrPickerField } from "./AttrPickerField.js";
|
|
3
|
+
import { EdgePickerField } from "./EdgePickerField.js";
|
|
4
|
+
import { EntityPickerField } from "./EntityPickerField.js";
|
|
5
|
+
import { StatePickerField } from "./StatePickerField.js";
|
|
6
|
+
import { TimePickerField } from "./TimePickerField.js";
|
|
7
|
+
export const FIELD_ENTITY_PICKER = "field_entity_picker";
|
|
8
|
+
export const FIELD_ATTR_PICKER = "field_attr_picker";
|
|
9
|
+
export const FIELD_EDGE_PICKER = "field_edge_picker";
|
|
10
|
+
export const FIELD_STATE_PICKER = "field_state_picker";
|
|
11
|
+
export const FIELD_TIME_PICKER = "field_time_picker";
|
|
12
|
+
let registered = false;
|
|
13
|
+
export function registerPickerFieldsOnce() {
|
|
14
|
+
if (registered)
|
|
15
|
+
return;
|
|
16
|
+
Blockly.fieldRegistry.register(FIELD_ENTITY_PICKER, EntityPickerField);
|
|
17
|
+
Blockly.fieldRegistry.register(FIELD_ATTR_PICKER, AttrPickerField);
|
|
18
|
+
Blockly.fieldRegistry.register(FIELD_EDGE_PICKER, EdgePickerField);
|
|
19
|
+
Blockly.fieldRegistry.register(FIELD_STATE_PICKER, StatePickerField);
|
|
20
|
+
Blockly.fieldRegistry.register(FIELD_TIME_PICKER, TimePickerField);
|
|
21
|
+
registered = true;
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Blockly 发布了该运行时子路径 但未在 exports 中提供类型声明。
|
|
2
|
+
// @ts-ignore 上游不同安装形态对 blockly/msg/zh-hans 的声明可用性不一致
|
|
3
|
+
import * as blocklyZhHans from "blockly/msg/zh-hans";
|
|
4
|
+
const TERM_OVERRIDES = {
|
|
5
|
+
DUPLICATE_BLOCK: "复制积木",
|
|
6
|
+
DELETE_BLOCK: "删除积木",
|
|
7
|
+
DELETE_X_BLOCKS: "删除 %1 个积木",
|
|
8
|
+
DELETE_ALL_BLOCKS: "删除所有积木 (%1 个)?",
|
|
9
|
+
CLEAN_UP: "整理积木",
|
|
10
|
+
COLLAPSE_BLOCK: "折叠积木",
|
|
11
|
+
COLLAPSE_ALL: "折叠所有积木",
|
|
12
|
+
EXPAND_BLOCK: "展开积木",
|
|
13
|
+
EXPAND_ALL: "展开所有积木",
|
|
14
|
+
DISABLE_BLOCK: "禁用积木",
|
|
15
|
+
ENABLE_BLOCK: "启用积木",
|
|
16
|
+
INLINE_INPUTS: "内联输入",
|
|
17
|
+
EXTERNAL_INPUTS: "外置输入",
|
|
18
|
+
HELP: "帮助",
|
|
19
|
+
UNDO: "撤销",
|
|
20
|
+
REDO: "重做"
|
|
21
|
+
};
|
|
22
|
+
export function buildZhMessages() {
|
|
23
|
+
const source = blocklyZhHans;
|
|
24
|
+
const base = {};
|
|
25
|
+
for (const key of Object.keys(source)) {
|
|
26
|
+
const value = source[key];
|
|
27
|
+
if (typeof value === "string")
|
|
28
|
+
base[key] = value;
|
|
29
|
+
}
|
|
30
|
+
return { ...base, ...TERM_OVERRIDES };
|
|
31
|
+
}
|
|
32
|
+
export function applyZhMessages(target) {
|
|
33
|
+
target.setLocale(buildZhMessages());
|
|
34
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type { AttrRefLeaf, CloseMode, DaySegment, DaySegmentLeaf, EventLeaf, LeafId, NlConditionLeaf, RelationRefLeaf, ScenarioEntityMinimal, ScenarioPropMinimal, ScenarioPropValueMinimal, ScenarioRootMinimal, TimeLeaf, TriggerDef, TriggerId, TriggerLeaf, TriggerTree, WorldTimePoint } from "./migration/types.js";
|
|
2
|
+
export { liquidGlassDarkTheme } from "./themes/liquid-glass-dark.js";
|
|
3
|
+
export { parseTmwWorkspaceV3, TMW_CONDITION_BLOCK_TYPES, tmwConditionNodeFromWorkspace, tmwConditionNodeToWorkspace } from "./adapters/trigger.js";
|
|
4
|
+
export type { BlockNode, BlockNodeInputSlot, BlockNodeNextSlot, BlocklyWorkspaceJson } from "./adapters/trigger.js";
|
|
5
|
+
export type { BlocksConditionExpressionV3, BlocksTriggerConditionNodeV3 } from "./adapters/trigger.js";
|
|
6
|
+
export { BlocksWorkspace, CATEGORY_IDS } from "./workspace.js";
|
|
7
|
+
export type { BlocksWorkspaceHandle, BlocksWorkspaceProps, BlocksWorkspaceSchema, BlocksWorkspaceTriggerCtx, CategoryId } from "./workspace.js";
|
|
8
|
+
export type { BlocksEditorCtx, CtxEdge, CtxEntity, CtxEntityAttr, CtxEntityState, CtxEvent, CtxVariable } from "./blocks/ctx.js";
|
|
9
|
+
export { buildToolbox, getToolbox, listToolboxCategoryIds, listToolboxCategoryNames, categoryDisplayNameOf } from "./blocks/toolbox.js";
|
|
10
|
+
export type { ToolboxJson, ToolboxCategoryEntry } from "./blocks/toolbox.js";
|
|
11
|
+
export { HAT_TYPES, HAT_BLOCK_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 "./blocks/registry-taxonomy.js";
|
|
12
|
+
export { EMPTY_SDK_CATALOG, parseSdkCatalogFromSource, registerSdkBlocks, SDK_BLOCK_PREFIX } from "./sdk-to-block/index.js";
|
|
13
|
+
export type { SdkArg, SdkArgType, SdkBlockCategory, SdkBlockKind, SdkCatalog, SdkEnumType, SdkFunctionSignature, SdkPickerType, SdkPrimitiveType, SdkReturnType } from "./sdk-to-block/index.js";
|
|
14
|
+
export { PickerHost } from "./react/PickerHost.js";
|
|
15
|
+
export { PICKER_EVENT_NAME } from "./fields/picker-events.js";
|
|
16
|
+
export type { PickerCandidate, PickerKind, PickerRequestDetail } from "./fields/picker-events.js";
|
|
17
|
+
export { FIELD_ATTR_PICKER, FIELD_EDGE_PICKER, FIELD_ENTITY_PICKER, FIELD_STATE_PICKER, FIELD_TIME_PICKER } from "./fields/register.js";
|
|
18
|
+
export { registerViewBlocks, viewBlockType } from "./view-to-block/index.js";
|
|
19
|
+
export type { ViewBlockCatalog } from "./view-to-block/index.js";
|
|
20
|
+
export { beginTmwBuilder, TmwBuilder } from "@world-engines/tmw/builder";
|
|
21
|
+
export type { TriggerDocumentV3, TriggerNodeV3 } from "@world-engines/tmw";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { liquidGlassDarkTheme } from "./themes/liquid-glass-dark.js";
|
|
2
|
+
export { parseTmwWorkspaceV3, TMW_CONDITION_BLOCK_TYPES, tmwConditionNodeFromWorkspace, tmwConditionNodeToWorkspace } from "./adapters/trigger.js";
|
|
3
|
+
export { BlocksWorkspace, CATEGORY_IDS } from "./workspace.js";
|
|
4
|
+
export { buildToolbox, getToolbox, listToolboxCategoryIds, listToolboxCategoryNames, categoryDisplayNameOf } from "./blocks/toolbox.js";
|
|
5
|
+
export { HAT_TYPES, HAT_BLOCK_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 "./blocks/registry-taxonomy.js";
|
|
6
|
+
export { EMPTY_SDK_CATALOG, parseSdkCatalogFromSource, registerSdkBlocks, SDK_BLOCK_PREFIX } from "./sdk-to-block/index.js";
|
|
7
|
+
export { PickerHost } from "./react/PickerHost.js";
|
|
8
|
+
export { PICKER_EVENT_NAME } from "./fields/picker-events.js";
|
|
9
|
+
export { FIELD_ATTR_PICKER, FIELD_EDGE_PICKER, FIELD_ENTITY_PICKER, FIELD_STATE_PICKER, FIELD_TIME_PICKER } from "./fields/register.js";
|
|
10
|
+
export { registerViewBlocks, viewBlockType } from "./view-to-block/index.js";
|
|
11
|
+
export { beginTmwBuilder, TmwBuilder } from "@world-engines/tmw/builder";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { AttrRefLeaf, CloseMode, DaySegment, DaySegmentLeaf, EventLeaf, LeafId, NlConditionLeaf, RelationRefLeaf, ScenarioEntityMinimal, ScenarioPropMinimal, ScenarioPropValueMinimal, ScenarioRootMinimal, TimeLeaf, TriggerDef, TriggerId, TriggerLeaf, TriggerTree, WorldTimePoint, } from "./types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export interface WorldTimePoint {
|
|
2
|
+
readonly year: number;
|
|
3
|
+
readonly month: number;
|
|
4
|
+
readonly day: number;
|
|
5
|
+
readonly hour: number;
|
|
6
|
+
readonly minute: number;
|
|
7
|
+
readonly second: number;
|
|
8
|
+
}
|
|
9
|
+
export type LeafId = string;
|
|
10
|
+
export type TriggerId = string;
|
|
11
|
+
export type CloseMode = "permanent" | "temporary";
|
|
12
|
+
export interface TimeLeaf {
|
|
13
|
+
readonly leaf: "time";
|
|
14
|
+
readonly leaf_id: LeafId;
|
|
15
|
+
readonly at: WorldTimePoint;
|
|
16
|
+
}
|
|
17
|
+
export interface EventLeaf {
|
|
18
|
+
readonly leaf: "event";
|
|
19
|
+
readonly leaf_id: LeafId;
|
|
20
|
+
readonly event_id: string;
|
|
21
|
+
readonly delay_ms?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface NlConditionLeaf {
|
|
24
|
+
readonly leaf: "nl_condition";
|
|
25
|
+
readonly leaf_id: LeafId;
|
|
26
|
+
readonly text: string;
|
|
27
|
+
readonly milestone?: boolean;
|
|
28
|
+
readonly delay_ms?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AttrRefLeaf {
|
|
31
|
+
readonly leaf: "attr_ref";
|
|
32
|
+
readonly leaf_id: LeafId;
|
|
33
|
+
readonly entity_id: string;
|
|
34
|
+
readonly attr_key: string;
|
|
35
|
+
readonly expected_value?: string;
|
|
36
|
+
readonly delay_ms?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RelationRefLeaf {
|
|
39
|
+
readonly leaf: "relation_ref";
|
|
40
|
+
readonly leaf_id: LeafId;
|
|
41
|
+
readonly src_id: string;
|
|
42
|
+
readonly dst_id: string;
|
|
43
|
+
readonly kind: string;
|
|
44
|
+
readonly attr_key: string;
|
|
45
|
+
readonly expected_value?: string;
|
|
46
|
+
readonly delay_ms?: number;
|
|
47
|
+
}
|
|
48
|
+
export type DaySegment = "morning" | "forenoon" | "afternoon" | "night";
|
|
49
|
+
export interface DaySegmentLeaf {
|
|
50
|
+
readonly leaf: "day_segment";
|
|
51
|
+
readonly leaf_id: LeafId;
|
|
52
|
+
readonly segment: DaySegment;
|
|
53
|
+
}
|
|
54
|
+
export type TriggerLeaf = TimeLeaf | EventLeaf | NlConditionLeaf | AttrRefLeaf | RelationRefLeaf | DaySegmentLeaf;
|
|
55
|
+
export type TriggerTree = {
|
|
56
|
+
readonly op: "and";
|
|
57
|
+
readonly children: ReadonlyArray<TriggerTree>;
|
|
58
|
+
} | {
|
|
59
|
+
readonly op: "or";
|
|
60
|
+
readonly children: ReadonlyArray<TriggerTree>;
|
|
61
|
+
} | {
|
|
62
|
+
readonly op: "not";
|
|
63
|
+
readonly child: TriggerTree;
|
|
64
|
+
} | TriggerLeaf;
|
|
65
|
+
export interface TriggerDef {
|
|
66
|
+
readonly id: TriggerId;
|
|
67
|
+
readonly name?: string;
|
|
68
|
+
readonly description?: string;
|
|
69
|
+
readonly tree: TriggerTree;
|
|
70
|
+
readonly close_when?: TriggerTree;
|
|
71
|
+
readonly close_mode?: CloseMode;
|
|
72
|
+
}
|
|
73
|
+
export type ScenarioPropValueMinimal = string | ReadonlyArray<string> | {
|
|
74
|
+
readonly [subKey: string]: ScenarioPropMinimal;
|
|
75
|
+
};
|
|
76
|
+
export interface ScenarioPropMinimal {
|
|
77
|
+
readonly value: ScenarioPropValueMinimal;
|
|
78
|
+
readonly trigger?: TriggerDef;
|
|
79
|
+
readonly states?: ReadonlyArray<unknown>;
|
|
80
|
+
}
|
|
81
|
+
export interface ScenarioEntityMinimal {
|
|
82
|
+
readonly id: string;
|
|
83
|
+
readonly trigger?: TriggerDef;
|
|
84
|
+
readonly props?: {
|
|
85
|
+
readonly [key: string]: ScenarioPropMinimal;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export interface ScenarioRootMinimal {
|
|
89
|
+
readonly schemaVersion?: number;
|
|
90
|
+
readonly entities: ReadonlyArray<ScenarioEntityMinimal>;
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { PICKER_EVENT_NAME } from "../fields/picker-events.js";
|
|
5
|
+
const OVERLAY_STYLE = {
|
|
6
|
+
position: "fixed",
|
|
7
|
+
inset: 0,
|
|
8
|
+
background: "rgba(0, 0, 0, 0.5)",
|
|
9
|
+
zIndex: 9999,
|
|
10
|
+
display: "flex",
|
|
11
|
+
alignItems: "center",
|
|
12
|
+
justifyContent: "center"
|
|
13
|
+
};
|
|
14
|
+
const MODAL_STYLE = {
|
|
15
|
+
background: "rgba(20, 20, 30, 0.95)",
|
|
16
|
+
color: "rgba(232, 236, 246, 0.92)",
|
|
17
|
+
borderRadius: "12px",
|
|
18
|
+
padding: "1rem",
|
|
19
|
+
minWidth: "20rem",
|
|
20
|
+
maxWidth: "min(90vw, 32rem)",
|
|
21
|
+
maxHeight: "70vh",
|
|
22
|
+
overflow: "hidden",
|
|
23
|
+
display: "flex",
|
|
24
|
+
flexDirection: "column",
|
|
25
|
+
gap: "0.75rem",
|
|
26
|
+
boxShadow: "0 1rem 3rem rgba(0, 0, 0, 0.6)"
|
|
27
|
+
};
|
|
28
|
+
const LIST_STYLE = {
|
|
29
|
+
listStyle: "none",
|
|
30
|
+
margin: 0,
|
|
31
|
+
padding: 0,
|
|
32
|
+
overflowY: "auto",
|
|
33
|
+
maxHeight: "50vh",
|
|
34
|
+
display: "flex",
|
|
35
|
+
flexDirection: "column",
|
|
36
|
+
gap: "0.25rem"
|
|
37
|
+
};
|
|
38
|
+
const ITEM_STYLE = {
|
|
39
|
+
padding: "0.5rem 0.75rem",
|
|
40
|
+
borderRadius: "6px",
|
|
41
|
+
cursor: "pointer",
|
|
42
|
+
background: "rgba(255, 255, 255, 0.04)",
|
|
43
|
+
border: "1px solid transparent"
|
|
44
|
+
};
|
|
45
|
+
const ITEM_ACTIVE_STYLE = {
|
|
46
|
+
...ITEM_STYLE,
|
|
47
|
+
background: "rgba(106, 227, 251, 0.15)",
|
|
48
|
+
borderColor: "rgba(106, 227, 251, 0.5)"
|
|
49
|
+
};
|
|
50
|
+
const HEADER_STYLE = {
|
|
51
|
+
fontSize: "0.95rem",
|
|
52
|
+
fontWeight: 600,
|
|
53
|
+
margin: 0
|
|
54
|
+
};
|
|
55
|
+
const FOOTER_STYLE = {
|
|
56
|
+
display: "flex",
|
|
57
|
+
justifyContent: "flex-end",
|
|
58
|
+
gap: "0.5rem"
|
|
59
|
+
};
|
|
60
|
+
const BUTTON_STYLE = {
|
|
61
|
+
padding: "0.375rem 0.75rem",
|
|
62
|
+
borderRadius: "6px",
|
|
63
|
+
border: "1px solid rgba(255,255,255,0.15)",
|
|
64
|
+
background: "transparent",
|
|
65
|
+
color: "inherit",
|
|
66
|
+
cursor: "pointer"
|
|
67
|
+
};
|
|
68
|
+
const EMPTY_STYLE = {
|
|
69
|
+
padding: "1rem",
|
|
70
|
+
textAlign: "center",
|
|
71
|
+
color: "rgba(232, 236, 246, 0.5)",
|
|
72
|
+
fontSize: "0.85rem"
|
|
73
|
+
};
|
|
74
|
+
export function PickerHost(_props = {}) {
|
|
75
|
+
const [request, setRequest] = useState(null);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const handler = (ev) => {
|
|
78
|
+
const detail = ev.detail;
|
|
79
|
+
if (!detail)
|
|
80
|
+
return;
|
|
81
|
+
setRequest(detail);
|
|
82
|
+
};
|
|
83
|
+
window.addEventListener(PICKER_EVENT_NAME, handler);
|
|
84
|
+
return () => window.removeEventListener(PICKER_EVENT_NAME, handler);
|
|
85
|
+
}, []);
|
|
86
|
+
if (request === null)
|
|
87
|
+
return null;
|
|
88
|
+
if (typeof document === "undefined")
|
|
89
|
+
return null;
|
|
90
|
+
const handleCancel = () => {
|
|
91
|
+
request.onCancel?.();
|
|
92
|
+
setRequest(null);
|
|
93
|
+
};
|
|
94
|
+
const handleSelect = (value) => {
|
|
95
|
+
request.onSelect(value);
|
|
96
|
+
setRequest(null);
|
|
97
|
+
};
|
|
98
|
+
return createPortal(_jsx("div", { style: OVERLAY_STYLE, onClick: (ev) => {
|
|
99
|
+
if (ev.target === ev.currentTarget)
|
|
100
|
+
handleCancel();
|
|
101
|
+
}, role: "presentation", children: _jsxs("div", { style: MODAL_STYLE, role: "dialog", "aria-modal": "true", "aria-label": request.title, children: [_jsx("h3", { style: HEADER_STYLE, children: request.title }), request.candidates.length === 0 ? (_jsx("div", { style: EMPTY_STYLE, children: "\u6682\u65E0\u53EF\u9009\u9879 \u8BF7\u5148\u5728\u56FE\u8C31\u5185\u521B\u5EFA" })) : (_jsx("ul", { style: LIST_STYLE, children: request.candidates.map((c) => {
|
|
102
|
+
const active = c.id === request.currentValue;
|
|
103
|
+
return (_jsxs("li", { style: active ? ITEM_ACTIVE_STYLE : ITEM_STYLE, onClick: () => handleSelect(c.id), role: "button", tabIndex: 0, onKeyDown: (ev) => {
|
|
104
|
+
if (ev.key === "Enter" || ev.key === " ") {
|
|
105
|
+
ev.preventDefault();
|
|
106
|
+
handleSelect(c.id);
|
|
107
|
+
}
|
|
108
|
+
}, children: [_jsx("div", { style: { fontWeight: 500 }, children: c.name }), c.subtitle !== undefined && (_jsx("div", { style: { fontSize: "0.75rem", opacity: 0.6 }, children: c.subtitle }))] }, c.id));
|
|
109
|
+
}) })), _jsx("div", { style: FOOTER_STYLE, children: _jsx("button", { type: "button", style: BUTTON_STYLE, onClick: handleCancel, children: "\u53D6\u6D88" }) })] }) }), document.body);
|
|
110
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { EMPTY_SDK_CATALOG, isEnumType, isPickerType, type SdkArg, type SdkArgType, type SdkBlockCategory, type SdkBlockKind, type SdkCatalog, type SdkEnumType, type SdkFunctionSignature, type SdkPickerType, type SdkPrimitiveType, type SdkReturnType } from "./signature.js";
|
|
2
|
+
export { toBlockDef, SDK_BLOCK_PREFIX, type BlockDefJson } from "./to-block-def.js";
|
|
3
|
+
export { listRegisteredSdkBlockTypes, registerSdkBlocks } from "./register.js";
|
|
4
|
+
export { parseSdkCatalogFromSource } from "./parser.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { EMPTY_SDK_CATALOG, isEnumType, isPickerType } from "./signature.js";
|
|
2
|
+
export { toBlockDef, SDK_BLOCK_PREFIX } from "./to-block-def.js";
|
|
3
|
+
export { listRegisteredSdkBlockTypes, registerSdkBlocks } from "./register.js";
|
|
4
|
+
export { parseSdkCatalogFromSource } from "./parser.js";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const FUNC_REGEX = /export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*:\s*([^\{;]+)/g;
|
|
2
|
+
const stripParens = (s) => s.trim().replace(/^\(|\)$/g, "");
|
|
3
|
+
const parseReturnType = (raw) => {
|
|
4
|
+
const trim = raw.trim().replace(/\s+/g, "");
|
|
5
|
+
if (trim.startsWith("void") || trim.startsWith("Promise<void>"))
|
|
6
|
+
return "void";
|
|
7
|
+
const inner = trim.startsWith("Promise<")
|
|
8
|
+
? trim.slice("Promise<".length, -1)
|
|
9
|
+
: trim;
|
|
10
|
+
if (inner === "boolean")
|
|
11
|
+
return "boolean";
|
|
12
|
+
if (inner === "number")
|
|
13
|
+
return "number";
|
|
14
|
+
if (inner === "string")
|
|
15
|
+
return "string";
|
|
16
|
+
if (inner === "object" || inner === "unknown")
|
|
17
|
+
return "object";
|
|
18
|
+
return "any";
|
|
19
|
+
};
|
|
20
|
+
const mapArgType = (raw) => {
|
|
21
|
+
const trim = raw.trim();
|
|
22
|
+
if (trim === "string")
|
|
23
|
+
return "string";
|
|
24
|
+
if (trim === "number")
|
|
25
|
+
return "number";
|
|
26
|
+
if (trim === "boolean")
|
|
27
|
+
return "boolean";
|
|
28
|
+
if (trim === "object" || trim === "unknown")
|
|
29
|
+
return "object";
|
|
30
|
+
if (trim === "EntityId" || trim === "entity_id")
|
|
31
|
+
return "entity_id";
|
|
32
|
+
if (trim === "AttrRef" || trim === "attr_ref")
|
|
33
|
+
return "attr_ref";
|
|
34
|
+
if (trim === "EdgeRef" || trim === "edge_ref")
|
|
35
|
+
return "edge_ref";
|
|
36
|
+
if (trim === "TimeRef" || trim === "time")
|
|
37
|
+
return "time";
|
|
38
|
+
if (trim === "VarRef" || trim === "variable")
|
|
39
|
+
return "variable";
|
|
40
|
+
const enumMatch = trim.match(/^"([^"]+)"(?:\s*\|\s*"([^"]+)")*$/);
|
|
41
|
+
if (enumMatch !== null) {
|
|
42
|
+
const values = Array.from(trim.matchAll(/"([^"]+)"/g)).map((m) => m[1] ?? "");
|
|
43
|
+
return { enum: values };
|
|
44
|
+
}
|
|
45
|
+
return "string";
|
|
46
|
+
};
|
|
47
|
+
const parseArgs = (raw) => {
|
|
48
|
+
const cleaned = stripParens(raw);
|
|
49
|
+
if (cleaned.length === 0)
|
|
50
|
+
return [];
|
|
51
|
+
const parts = [];
|
|
52
|
+
let depth = 0;
|
|
53
|
+
let cur = "";
|
|
54
|
+
for (const ch of cleaned) {
|
|
55
|
+
if (ch === "<" || ch === "(" || ch === "{")
|
|
56
|
+
depth++;
|
|
57
|
+
else if (ch === ">" || ch === ")" || ch === "}")
|
|
58
|
+
depth--;
|
|
59
|
+
if (ch === "," && depth === 0) {
|
|
60
|
+
parts.push(cur);
|
|
61
|
+
cur = "";
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
cur += ch;
|
|
65
|
+
}
|
|
66
|
+
if (cur.trim().length > 0)
|
|
67
|
+
parts.push(cur);
|
|
68
|
+
const args = [];
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
const colonIdx = part.indexOf(":");
|
|
71
|
+
if (colonIdx < 0)
|
|
72
|
+
continue;
|
|
73
|
+
const name = part.slice(0, colonIdx).trim().replace(/[?]$/, "");
|
|
74
|
+
const typeRaw = part.slice(colonIdx + 1).trim();
|
|
75
|
+
if (name.length === 0)
|
|
76
|
+
continue;
|
|
77
|
+
args.push({ name, label: name, type: mapArgType(typeRaw) });
|
|
78
|
+
}
|
|
79
|
+
return args;
|
|
80
|
+
};
|
|
81
|
+
export function parseSdkCatalogFromSource(source) {
|
|
82
|
+
const functions = [];
|
|
83
|
+
let m;
|
|
84
|
+
const regex = new RegExp(FUNC_REGEX.source, "g");
|
|
85
|
+
while ((m = regex.exec(source)) !== null) {
|
|
86
|
+
const name = m[1] ?? "";
|
|
87
|
+
const argsRaw = m[2] ?? "";
|
|
88
|
+
const retRaw = m[3] ?? "";
|
|
89
|
+
if (name.length === 0)
|
|
90
|
+
continue;
|
|
91
|
+
functions.push({
|
|
92
|
+
name,
|
|
93
|
+
label: name,
|
|
94
|
+
description: "",
|
|
95
|
+
args: parseArgs(argsRaw),
|
|
96
|
+
returnType: parseReturnType(retRaw),
|
|
97
|
+
category: "my_blocks"
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { version: 1, functions };
|
|
101
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as Blockly from "blockly";
|
|
2
|
+
import { registerPickerFieldsOnce } from "../fields/register.js";
|
|
3
|
+
import { SDK_BLOCK_PREFIX, toBlockDef } from "./to-block-def.js";
|
|
4
|
+
const registeredTypes = new Set();
|
|
5
|
+
export function registerSdkBlocks(catalog) {
|
|
6
|
+
registerPickerFieldsOnce();
|
|
7
|
+
const types = [];
|
|
8
|
+
const fresh = catalog.functions.filter((f) => {
|
|
9
|
+
const type = `${SDK_BLOCK_PREFIX}${f.name}`;
|
|
10
|
+
if (registeredTypes.has(type))
|
|
11
|
+
return false;
|
|
12
|
+
return true;
|
|
13
|
+
});
|
|
14
|
+
if (fresh.length > 0) {
|
|
15
|
+
const defs = fresh.map((sig) => toBlockDef(sig));
|
|
16
|
+
Blockly.common.defineBlocksWithJsonArray(defs);
|
|
17
|
+
for (const def of defs)
|
|
18
|
+
registeredTypes.add(def.type);
|
|
19
|
+
}
|
|
20
|
+
for (const sig of catalog.functions) {
|
|
21
|
+
types.push(`${SDK_BLOCK_PREFIX}${sig.name}`);
|
|
22
|
+
}
|
|
23
|
+
return types;
|
|
24
|
+
}
|
|
25
|
+
export function listRegisteredSdkBlockTypes() {
|
|
26
|
+
return Array.from(registeredTypes);
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type SdkPrimitiveType = "string" | "number" | "boolean" | "object";
|
|
2
|
+
export type SdkPickerType = "entity_id" | "attr_ref" | "edge_ref" | "time" | "variable";
|
|
3
|
+
export interface SdkEnumType {
|
|
4
|
+
readonly enum: ReadonlyArray<string>;
|
|
5
|
+
}
|
|
6
|
+
export type SdkArgType = SdkPrimitiveType | SdkPickerType | SdkEnumType;
|
|
7
|
+
export type SdkReturnType = "void" | "string" | "number" | "boolean" | "object" | "any";
|
|
8
|
+
export type SdkBlockKind = "statement" | "reporter" | "boolean";
|
|
9
|
+
export type SdkBlockCategory = "my_blocks" | "sensing" | "variables";
|
|
10
|
+
export interface SdkArg {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly type: SdkArgType;
|
|
14
|
+
readonly description?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SdkFunctionSignature {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly args: ReadonlyArray<SdkArg>;
|
|
21
|
+
readonly returnType: SdkReturnType;
|
|
22
|
+
readonly kind?: SdkBlockKind;
|
|
23
|
+
readonly category: SdkBlockCategory;
|
|
24
|
+
}
|
|
25
|
+
export interface SdkCatalog {
|
|
26
|
+
readonly version: number;
|
|
27
|
+
readonly functions: ReadonlyArray<SdkFunctionSignature>;
|
|
28
|
+
}
|
|
29
|
+
export declare const EMPTY_SDK_CATALOG: SdkCatalog;
|
|
30
|
+
export declare function isEnumType(t: SdkArgType): t is SdkEnumType;
|
|
31
|
+
export declare function isPickerType(t: SdkArgType): t is SdkPickerType;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const EMPTY_SDK_CATALOG = {
|
|
2
|
+
version: 1,
|
|
3
|
+
functions: []
|
|
4
|
+
};
|
|
5
|
+
export function isEnumType(t) {
|
|
6
|
+
return typeof t === "object" && t !== null && "enum" in t;
|
|
7
|
+
}
|
|
8
|
+
export function isPickerType(t) {
|
|
9
|
+
if (typeof t !== "string")
|
|
10
|
+
return false;
|
|
11
|
+
return t === "entity_id" || t === "attr_ref" || t === "edge_ref" || t === "time" || t === "variable";
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SdkFunctionSignature } from "./signature.js";
|
|
2
|
+
export declare const SDK_BLOCK_PREFIX: "sdk_";
|
|
3
|
+
interface JsonFieldSpec {
|
|
4
|
+
readonly type: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly [key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface JsonInputSpec {
|
|
9
|
+
readonly type: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly [key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
type JsonArg = JsonFieldSpec | JsonInputSpec;
|
|
14
|
+
export interface BlockDefJson {
|
|
15
|
+
readonly type: string;
|
|
16
|
+
readonly message0: string;
|
|
17
|
+
readonly args0?: ReadonlyArray<JsonArg>;
|
|
18
|
+
readonly colour: string;
|
|
19
|
+
readonly tooltip?: string;
|
|
20
|
+
readonly output?: string | null;
|
|
21
|
+
readonly previousStatement?: null;
|
|
22
|
+
readonly nextStatement?: null;
|
|
23
|
+
}
|
|
24
|
+
export declare function toBlockDef(sig: SdkFunctionSignature): BlockDefJson;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { FIELD_ATTR_PICKER, FIELD_EDGE_PICKER, FIELD_ENTITY_PICKER, FIELD_TIME_PICKER } from "../fields/register.js";
|
|
2
|
+
import { isEnumType } from "./signature.js";
|
|
3
|
+
import { BLOCK_CATEGORY_PALETTE } from "../themes/liquid-glass-dark.js";
|
|
4
|
+
export const SDK_BLOCK_PREFIX = "sdk_";
|
|
5
|
+
function argToField(a) {
|
|
6
|
+
const upperName = a.name.toUpperCase();
|
|
7
|
+
const type = a.type;
|
|
8
|
+
if (isEnumType(type)) {
|
|
9
|
+
return {
|
|
10
|
+
type: "field_dropdown",
|
|
11
|
+
name: upperName,
|
|
12
|
+
options: type.enum.map((v) => [v, v])
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
switch (type) {
|
|
16
|
+
case "entity_id":
|
|
17
|
+
return { type: FIELD_ENTITY_PICKER, name: upperName };
|
|
18
|
+
case "attr_ref":
|
|
19
|
+
return { type: FIELD_ATTR_PICKER, name: upperName };
|
|
20
|
+
case "edge_ref":
|
|
21
|
+
return { type: FIELD_EDGE_PICKER, name: upperName };
|
|
22
|
+
case "time":
|
|
23
|
+
return { type: FIELD_TIME_PICKER, name: upperName };
|
|
24
|
+
case "variable":
|
|
25
|
+
return { type: "field_variable", name: upperName };
|
|
26
|
+
case "string":
|
|
27
|
+
return { type: "input_value", name: upperName, check: "String" };
|
|
28
|
+
case "number":
|
|
29
|
+
return { type: "input_value", name: upperName, check: "Number" };
|
|
30
|
+
case "boolean":
|
|
31
|
+
return { type: "input_value", name: upperName, check: "Boolean" };
|
|
32
|
+
case "object":
|
|
33
|
+
return { type: "input_value", name: upperName, check: "Object" };
|
|
34
|
+
default: {
|
|
35
|
+
const exhaustiveCheck = type;
|
|
36
|
+
void exhaustiveCheck;
|
|
37
|
+
return { type: "input_value", name: upperName };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function toBlockDef(sig) {
|
|
42
|
+
const args0 = sig.args.map((a) => argToField(a));
|
|
43
|
+
const parts = sig.args.map((_, i) => `%${i + 1}`);
|
|
44
|
+
const message0 = parts.length > 0 ? `${sig.label} ${parts.join(" ")}` : sig.label;
|
|
45
|
+
const type = `${SDK_BLOCK_PREFIX}${sig.name}`;
|
|
46
|
+
const base = {
|
|
47
|
+
type,
|
|
48
|
+
message0,
|
|
49
|
+
args0,
|
|
50
|
+
colour: BLOCK_CATEGORY_PALETTE.sdkInterfaces.primary,
|
|
51
|
+
tooltip: sig.description
|
|
52
|
+
};
|
|
53
|
+
const explicitKind = sig.kind;
|
|
54
|
+
if (explicitKind === "statement") {
|
|
55
|
+
return { ...base, previousStatement: null, nextStatement: null };
|
|
56
|
+
}
|
|
57
|
+
if (explicitKind === "boolean" || sig.returnType === "boolean") {
|
|
58
|
+
return { ...base, output: "Boolean" };
|
|
59
|
+
}
|
|
60
|
+
if (sig.returnType === "object") {
|
|
61
|
+
return { ...base, output: "Object" };
|
|
62
|
+
}
|
|
63
|
+
if (explicitKind === "reporter" || (sig.returnType !== "void" && sig.returnType !== "any")) {
|
|
64
|
+
const outputType = sig.returnType === "number" ? "Number" : "String";
|
|
65
|
+
return { ...base, output: outputType };
|
|
66
|
+
}
|
|
67
|
+
if (sig.returnType === "any") {
|
|
68
|
+
return { ...base, output: null };
|
|
69
|
+
}
|
|
70
|
+
return { ...base, previousStatement: null, nextStatement: null };
|
|
71
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./liquid-glass.css";
|
|
2
|
+
export { liquidGlassDarkTheme, LIQUID_GLASS_DARK_THEME_NAME, LIQUID_GLASS_BLOCK_STYLE_KEYS, BLOCK_STYLE_KEYS, GRID_COLOUR, } from "./liquid-glass-dark.js";
|
|
3
|
+
export type { LiquidGlassBlockStyleKey, BlockStyleKey } from "./liquid-glass-dark.js";
|
|
4
|
+
export declare function applyLiquidGlassCss(container: HTMLElement): () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./liquid-glass.css";
|
|
2
|
+
export { liquidGlassDarkTheme, LIQUID_GLASS_DARK_THEME_NAME, LIQUID_GLASS_BLOCK_STYLE_KEYS, BLOCK_STYLE_KEYS, GRID_COLOUR, } from "./liquid-glass-dark.js";
|
|
3
|
+
const BLOCKS_EDITOR_CLASS = "blocks-editor";
|
|
4
|
+
const BLOCKS_EDITOR_LIQUID_GLASS_MOD = "blocks-editor--liquid-glass-dark";
|
|
5
|
+
export function applyLiquidGlassCss(container) {
|
|
6
|
+
container.classList.add(BLOCKS_EDITOR_CLASS);
|
|
7
|
+
container.classList.add(BLOCKS_EDITOR_LIQUID_GLASS_MOD);
|
|
8
|
+
return () => {
|
|
9
|
+
container.classList.remove(BLOCKS_EDITOR_CLASS);
|
|
10
|
+
container.classList.remove(BLOCKS_EDITOR_LIQUID_GLASS_MOD);
|
|
11
|
+
};
|
|
12
|
+
}
|