@arronqzy/react-view 1.0.33 → 1.0.36
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/dist/styles.css +223 -0
- package/package.json +7 -7
- package/readme.md +70 -82
- package/src/core/instructions.tsx +1 -1
- package/src/core/template.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/panel/I18nRoot.tsx +2 -1
- package/src/panel/ReactViewOnlinePreview.tsx +4 -1
- package/src/panel/ReactViewPanel.tsx +64 -7
- package/src/panel/components/ConfigHintIcon.tsx +15 -2
- package/src/panel/components/PanelMenubar.tsx +6 -0
- package/src/panel/components/config/PanelConfigAudioSection.tsx +6 -4
- package/src/panel/components/config/PanelConfigChartSection.tsx +12 -8
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +16 -8
- package/src/panel/components/config/PanelConfigStyleSections.tsx +7 -4
- package/src/panel/components/config/PanelConfigTextSection.tsx +7 -4
- package/src/panel/components/config/PanelConfigVideoSection.tsx +7 -4
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.tsx +4 -8
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +1 -0
- package/src/panel/components/table/ConditionEditor.tsx +7 -2
- package/src/panel/components/table/PanelConfigTableSection.tsx +11 -5
- package/src/panel/hooks/usePanelElements.ts +20 -3
- package/src/panel/library/event-subscription.ts +20 -0
- package/src/panel/utils/panelElementDefaults.ts +47 -1
- package/src/panel/utils/scope-template.ts +20 -8
package/readme.md
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
# @arronqzy/react-view
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
视图编辑器。物料、无限画布、图层、配置侧栏、工作区和在线预览都在这个包。蓝图分屏通过依赖的 `@arronqzy/react-blueprint` 嵌进来。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
大多数 React 宿主应直接用 [`@arronqzy/abuilder`](../abuilder/README.md) 的 `<App />`。只有要把面板拆进已有布局时才引本包。
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 画布上有什么
|
|
10
|
+
|
|
11
|
+
- **无限画布**:平移、缩放、标尺、框选,Moveable 拖拽 / 缩放 / 旋转
|
|
12
|
+
- **物料**:ECharts 图表、可视化表格、文本、图片、音视频、几何、网格、视口、三维、引用节点
|
|
13
|
+
- **图层**:多图层、映射图层、主图层、锁定、显示、合并
|
|
14
|
+
- **配置**:按类型展开;可搜字段;展开状态按节点记住
|
|
15
|
+
- **Scope**:属性可写 `{scope.xxx}`,蓝图执行后展开
|
|
16
|
+
- **工作区**:IndexedDB 多项目、导入导出、跨标签同步
|
|
17
|
+
|
|
18
|
+
表格是物料里配置最多的一类,列、条件样式和外观都在右侧:
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
打开蓝图后,本包把画布拆成上下两块,下面交给蓝图编辑器:
|
|
23
|
+
|
|
24
|
+

|
|
14
25
|
|
|
15
26
|
## 安装
|
|
16
27
|
|
|
@@ -18,15 +29,13 @@ Abuilder 的**视图编辑器** React 包,提供可视化画布、图层、物
|
|
|
18
29
|
pnpm add @arronqzy/react-view
|
|
19
30
|
```
|
|
20
31
|
|
|
21
|
-
依赖 React 19,并需引入样式:
|
|
22
|
-
|
|
23
32
|
```ts
|
|
24
33
|
import "@arronqzy/react-view/styles.css";
|
|
25
34
|
```
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
Vite 宿主还要加 `@arronqzy/abuilder/vite` 的 `webllmAssistant()`,见 abuilder 的 README。否则打包 WebLLM 会失败。
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
## 编辑面板
|
|
30
39
|
|
|
31
40
|
```tsx
|
|
32
41
|
import { ReactViewPanel } from "@arronqzy/react-view";
|
|
@@ -44,15 +53,15 @@ export function Editor({ workspace }: { workspace?: WorkspaceProjectRecord | nul
|
|
|
44
53
|
}
|
|
45
54
|
```
|
|
46
55
|
|
|
47
|
-
|
|
|
48
|
-
|
|
56
|
+
| 参数 | 说明 | 默认 |
|
|
57
|
+
|------|------|------|
|
|
49
58
|
| `className` | 根节点 class | — |
|
|
50
59
|
| `initialZoom` | 初始缩放 | `1` |
|
|
51
|
-
| `initialWorkspace` |
|
|
60
|
+
| `initialWorkspace` | 挂载后加载的完整工作区。空则空白画布 | — |
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
## 在线预览
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
按 ID 从 IndexedDB / 缓存加载:
|
|
56
65
|
|
|
57
66
|
```tsx
|
|
58
67
|
import { ReactViewOnlinePreview } from "@arronqzy/react-view";
|
|
@@ -60,65 +69,62 @@ import { ReactViewOnlinePreview } from "@arronqzy/react-view";
|
|
|
60
69
|
<ReactViewOnlinePreview projectId="your-project-id" />
|
|
61
70
|
```
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
直接给数据(优先于 `projectId`):
|
|
64
73
|
|
|
65
74
|
```tsx
|
|
66
75
|
<ReactViewOnlinePreview workspace={savedWorkspace} />
|
|
67
76
|
```
|
|
68
77
|
|
|
69
|
-
URL 参数解析:
|
|
70
|
-
|
|
71
78
|
```ts
|
|
72
79
|
import { parseOnlinePreviewSearchParams } from "@arronqzy/react-view";
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
parseOnlinePreviewSearchParams("?preview=online&projectId=xxx");
|
|
75
82
|
```
|
|
76
83
|
|
|
77
|
-
|
|
|
78
|
-
|
|
79
|
-
| `projectId` | 从 IndexedDB
|
|
80
|
-
| `workspace` |
|
|
84
|
+
| 参数 | 说明 |
|
|
85
|
+
|------|------|
|
|
86
|
+
| `projectId` | 从 IndexedDB 加载 |
|
|
87
|
+
| `workspace` | 宿主传入的完整工作区,优先 |
|
|
81
88
|
| `previewInstanceId` | 预览实例 ID(可选) |
|
|
82
89
|
|
|
83
|
-
|
|
90
|
+
## 工作区事件
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
回调带完整工作区,用来写自己的后端。IndexedDB 的增删改查不导出。
|
|
86
93
|
|
|
87
94
|
```ts
|
|
88
95
|
import {
|
|
89
96
|
addEventSubscription,
|
|
90
97
|
AbuilderEvents,
|
|
98
|
+
parseWorkspaceData,
|
|
91
99
|
type WorkspaceData,
|
|
92
100
|
} from "@arronqzy/react-view";
|
|
93
101
|
|
|
94
|
-
const addSub = addEventSubscription(
|
|
95
|
-
AbuilderEvents.workspaceAdd,
|
|
96
|
-
async (workspace: WorkspaceData) => {
|
|
97
|
-
await saveToServer(workspace);
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
|
|
101
102
|
const syncSub = addEventSubscription(
|
|
102
103
|
AbuilderEvents.workspaceSync,
|
|
103
|
-
async (workspace) => {
|
|
104
|
+
async (workspace: WorkspaceData) => {
|
|
104
105
|
await saveToServer(workspace);
|
|
105
106
|
}
|
|
106
107
|
);
|
|
107
|
-
|
|
108
|
-
addSub.unsubscribe();
|
|
109
108
|
syncSub.unsubscribe();
|
|
110
109
|
```
|
|
111
110
|
|
|
112
|
-
| 事件 | 常量 |
|
|
111
|
+
| 事件 | 常量 | 何时触发 |
|
|
113
112
|
|------|------|----------|
|
|
114
|
-
| `workspace:add` | `AbuilderEvents.workspaceAdd` |
|
|
115
|
-
| `workspace:sync` | `AbuilderEvents.workspaceSync` |
|
|
113
|
+
| `workspace:add` | `AbuilderEvents.workspaceAdd` | 创建成功 |
|
|
114
|
+
| `workspace:sync` | `AbuilderEvents.workspaceSync` | 同步成功 |
|
|
115
|
+
|
|
116
|
+
字段:`id`、`name`、`createdAt`、`updatedAt`、`panelState`、`blueprintDocument`、`blueprintMeta`、`productName`、`titleIconDataUrl`。
|
|
117
|
+
|
|
118
|
+
校验:
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
- `validateViewData` / `parseViewData` — 视图 `panelState`
|
|
121
|
+
- `validateBlueprintData` / `parseBlueprintData` — 蓝图文档
|
|
122
|
+
- `validateWorkspaceData` / `parseWorkspaceData` — 两边都要合法;`parse` 的 `value` 可作 `initialWorkspace`
|
|
123
|
+
- `createEmptyWorkspace` / `createWorkspaceProjectId` — 空白记录和 ID
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
## 预览缩略图
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
编辑器或预览页已挂载且画布有内容时:
|
|
122
128
|
|
|
123
129
|
```ts
|
|
124
130
|
import { getPreviewSnapshot } from "@arronqzy/react-view";
|
|
@@ -131,56 +137,38 @@ const dataUrl = await getPreviewSnapshot({
|
|
|
131
137
|
});
|
|
132
138
|
```
|
|
133
139
|
|
|
134
|
-
| 参数 | 说明 |
|
|
135
|
-
|
|
136
|
-
| `maxWidth` / `maxHeight` |
|
|
137
|
-
| `mimeType` |
|
|
138
|
-
| `quality` | JPEG
|
|
140
|
+
| 参数 | 说明 | 默认 |
|
|
141
|
+
|------|------|------|
|
|
142
|
+
| `maxWidth` / `maxHeight` | 等比上限 | 不限制 |
|
|
143
|
+
| `mimeType` | png 或 jpeg | `"image/png"` |
|
|
144
|
+
| `quality` | JPEG 0–1 | `0.92` |
|
|
139
145
|
| `backgroundColor` | 背景色 | `"#ffffff"` |
|
|
140
146
|
|
|
141
|
-
##
|
|
147
|
+
## 主要导出
|
|
142
148
|
|
|
143
149
|
| 导出 | 说明 |
|
|
144
150
|
|------|------|
|
|
145
|
-
| `ReactViewPanel` |
|
|
146
|
-
| `ReactViewOnlinePreview` |
|
|
147
|
-
| `parseOnlinePreviewSearchParams` | 解析预览 URL
|
|
148
|
-
| `addEventSubscription`
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
| `WorkspaceData` / `WorkspaceProjectRecord` | 完整工作区数据类型 |
|
|
157
|
-
| `createView` / `createAction` 等 | 视图 DSL 与指令扩展(`src/core`) |
|
|
158
|
-
|
|
159
|
-
相关类型:`AbuilderEventName`、`AbuilderEventPayloadMap`、`WorkspaceAddEventPayload`、`WorkspaceSyncEventPayload`、`GetPreviewSnapshotOptions`。
|
|
160
|
-
|
|
161
|
-
## 样式与 Tailwind
|
|
162
|
-
|
|
163
|
-
本包内置 Tailwind 配置,构建产物为 `dist/styles.css`:
|
|
151
|
+
| `ReactViewPanel` | 主面板(画布 + 配置 + 蓝图分屏) |
|
|
152
|
+
| `ReactViewOnlinePreview` | 只读预览 |
|
|
153
|
+
| `parseOnlinePreviewSearchParams` | 解析预览 URL |
|
|
154
|
+
| `addEventSubscription` / `AbuilderEvents` | 工作区事件 |
|
|
155
|
+
| `getPreviewSnapshot` | 预览截图 data URL |
|
|
156
|
+
| `parseWorkspaceData` 等 | 校验 / 解析 / 空工作区 |
|
|
157
|
+
| `createView` / `createAction` | 视图 DSL 扩展点(`src/core`) |
|
|
158
|
+
|
|
159
|
+
## 样式
|
|
160
|
+
|
|
161
|
+
产物是 `dist/styles.css`。本仓库构建:
|
|
164
162
|
|
|
165
163
|
```bash
|
|
166
164
|
pnpm -C packages/react-view build:css
|
|
167
165
|
```
|
|
168
166
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
content: [
|
|
173
|
-
"./src/**/*.{ts,tsx}",
|
|
174
|
-
"./node_modules/@arronqzy/react-view/src/**/*.{ts,tsx}",
|
|
175
|
-
]
|
|
176
|
-
```
|
|
167
|
+
若应用自己的 Tailwind 要扫描本包源码,把 `node_modules/@arronqzy/react-view/src/**/*.{ts,tsx}` 加进 `content`。
|
|
177
168
|
|
|
178
|
-
##
|
|
169
|
+
## 依赖
|
|
179
170
|
|
|
180
|
-
|
|
181
|
-
- `@arronqzy/rx-store` — 画布状态与历史
|
|
182
|
-
- `@arronqzy/react-blueprint` — 蓝图编辑与执行
|
|
183
|
-
- `@arronqzy/blueprint-dsl` — 蓝图运行时
|
|
171
|
+
`@arronqzy/ui`、`@arronqzy/rx-store`、`@arronqzy/react-blueprint`、`@arronqzy/blueprint-dsl`、`@arronqzy/view-table`、`@arronqzy/view-scene3d`。
|
|
184
172
|
|
|
185
173
|
## 许可证
|
|
186
174
|
|
package/src/core/template.tsx
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type {
|
|
|
14
14
|
AbuilderEventPayloadMap,
|
|
15
15
|
WorkspaceAddEventPayload,
|
|
16
16
|
WorkspaceSyncEventPayload,
|
|
17
|
+
ApiCollectionUploadStartEventPayload,
|
|
18
|
+
ApiCollectionUploadSuccessEventPayload,
|
|
17
19
|
WorkspaceData,
|
|
18
20
|
} from "./panel/library/event-subscription";
|
|
19
21
|
export type { WorkspaceProjectRecord } from "./panel/library/workspace-project-db";
|
package/src/panel/I18nRoot.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { I18nProvider } from "@arronqzy/i18n/react";
|
|
2
2
|
import { LOCALE_STORAGE_KEY } from "@arronqzy/i18n";
|
|
3
3
|
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
4
|
+
import { BlueprintDslLocaleSync } from "@arronqzy/react-blueprint";
|
|
4
5
|
import type { Locale } from "@arronqzy/i18n";
|
|
5
6
|
import type { ReactNode } from "react";
|
|
6
7
|
|
|
@@ -24,7 +25,7 @@ export function I18nRoot({
|
|
|
24
25
|
onLocaleChange={onLocaleChange}
|
|
25
26
|
storageKey={appStorageKey(LOCALE_STORAGE_KEY, nameSpace)}
|
|
26
27
|
>
|
|
27
|
-
{children}
|
|
28
|
+
<BlueprintDslLocaleSync>{children}</BlueprintDslLocaleSync>
|
|
28
29
|
</I18nProvider>
|
|
29
30
|
);
|
|
30
31
|
}
|
|
@@ -17,6 +17,7 @@ import type { State } from "@arronqzy/rx-store";
|
|
|
17
17
|
import { ElementsLayer } from "./components/ElementsLayer";
|
|
18
18
|
import type { TableCellActionHandler } from "./components/table/TableNodeContent";
|
|
19
19
|
import type { PanelElement } from "./types";
|
|
20
|
+
import { localizePanelElement } from "./utils/panelElementDefaults";
|
|
20
21
|
import {
|
|
21
22
|
clearViewElementScopes,
|
|
22
23
|
getViewElementScope,
|
|
@@ -236,7 +237,8 @@ function ReactViewOnlinePreviewInner({
|
|
|
236
237
|
|
|
237
238
|
const displayElements = useMemo(
|
|
238
239
|
() =>
|
|
239
|
-
scopedLayerElements.map((
|
|
240
|
+
scopedLayerElements.map((source) => {
|
|
241
|
+
const el = localizePanelElement(source, t);
|
|
240
242
|
const x = el.x - sceneBounds.minX;
|
|
241
243
|
const y = el.y - sceneBounds.minY;
|
|
242
244
|
if (outputScale) return { ...el, x, y };
|
|
@@ -255,6 +257,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
255
257
|
sceneBounds.minX,
|
|
256
258
|
sceneBounds.minY,
|
|
257
259
|
scopedLayerElements,
|
|
260
|
+
t,
|
|
258
261
|
]
|
|
259
262
|
);
|
|
260
263
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
-
import type
|
|
3
|
+
import { relocalizeMessage, type Locale } from "@arronqzy/i18n";
|
|
4
4
|
import type { State } from "@arronqzy/rx-store";
|
|
5
5
|
import type { PanelElement } from "./types";
|
|
6
6
|
|
|
@@ -46,6 +46,7 @@ import { revealPanelConfigFromPatch } from "./ai/revealConfigField";
|
|
|
46
46
|
import {
|
|
47
47
|
BlueprintGraph,
|
|
48
48
|
BlueprintMetaDialog,
|
|
49
|
+
ApiCollectionManagerDialog,
|
|
49
50
|
BlueprintNodeSwitchTaskDialog,
|
|
50
51
|
blueprintDocumentsEqual,
|
|
51
52
|
buildBlueprintExportPayload,
|
|
@@ -55,6 +56,7 @@ import {
|
|
|
55
56
|
downloadBlueprintExport,
|
|
56
57
|
documentToRunnableGraph,
|
|
57
58
|
getBlueprintLibraryRecord,
|
|
59
|
+
hydrateApiCollections,
|
|
58
60
|
libraryRecordFromImport,
|
|
59
61
|
listBlueprintLibrary,
|
|
60
62
|
parseBlueprintImportFile,
|
|
@@ -72,12 +74,16 @@ import {
|
|
|
72
74
|
type BlueprintMetaDraft,
|
|
73
75
|
} from "@arronqzy/react-blueprint";
|
|
74
76
|
import type { LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
75
|
-
import { abortClockNode, appStorageKey, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
77
|
+
import { abortClockNode, appStorageKey, buildApiCollectionSyncPatches, toApiCollectionSyncResult, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
76
78
|
import { WorkspaceStageSplit } from "./components/WorkspaceStageSplit";
|
|
77
79
|
import {
|
|
78
80
|
WorkspaceConfigSidebar,
|
|
79
81
|
type WorkspaceConfigFocus,
|
|
80
82
|
} from "./components/WorkspaceConfigSidebar";
|
|
83
|
+
import {
|
|
84
|
+
AbuilderEvents,
|
|
85
|
+
emitAbuilderEvent,
|
|
86
|
+
} from "./library/event-subscription";
|
|
81
87
|
import {
|
|
82
88
|
getViewElementScope,
|
|
83
89
|
setViewElementScopes,
|
|
@@ -277,6 +283,12 @@ function ReactViewPanelInner({
|
|
|
277
283
|
name: t("panel.defaults.unnamedBlueprint"),
|
|
278
284
|
remark: "",
|
|
279
285
|
});
|
|
286
|
+
const displayProductName = relocalizeMessage(productName, "panel.defaults.unnamedProduct", t);
|
|
287
|
+
const displayBlueprintName = relocalizeMessage(
|
|
288
|
+
blueprintMeta.name,
|
|
289
|
+
["panel.defaults.unnamedBlueprint", "blueprint.dialog.unnamedBlueprint"],
|
|
290
|
+
t
|
|
291
|
+
);
|
|
280
292
|
const [blueprintLibraryItems, setBlueprintLibraryItems] = useState<
|
|
281
293
|
BlueprintLibraryListItem[]
|
|
282
294
|
>([]);
|
|
@@ -295,6 +307,7 @@ function ReactViewPanelInner({
|
|
|
295
307
|
const [blueprintMetaDialogMode, setBlueprintMetaDialogMode] = useState<
|
|
296
308
|
"export" | "save"
|
|
297
309
|
>("save");
|
|
310
|
+
const [apiCollectionManagerOpen, setApiCollectionManagerOpen] = useState(false);
|
|
298
311
|
const [selectedBlueprintNodeId, setSelectedBlueprintNodeId] = useState<string | null>(
|
|
299
312
|
null
|
|
300
313
|
);
|
|
@@ -655,6 +668,12 @@ function ReactViewPanelInner({
|
|
|
655
668
|
[nameSpace]
|
|
656
669
|
);
|
|
657
670
|
|
|
671
|
+
useEffect(() => {
|
|
672
|
+
void hydrateApiCollections(nameSpace).catch((error) => {
|
|
673
|
+
console.error("[abuilder] hydrate api collections failed", error);
|
|
674
|
+
});
|
|
675
|
+
}, [nameSpace]);
|
|
676
|
+
|
|
658
677
|
const handleBlueprintExecutionBlocked = useCallback((message: string) => {
|
|
659
678
|
toast({ title: message });
|
|
660
679
|
}, []);
|
|
@@ -669,7 +688,7 @@ function ReactViewPanelInner({
|
|
|
669
688
|
const blueprintDebugSession = useBlueprintDebugSession({
|
|
670
689
|
graph: blueprintGraph,
|
|
671
690
|
blueprintId: activeBlueprintLibraryId,
|
|
672
|
-
blueprintName:
|
|
691
|
+
blueprintName: displayBlueprintName || t("panel.defaults.unnamedBlueprint"),
|
|
673
692
|
resolveLibraryBlueprint,
|
|
674
693
|
libraryNameById: blueprintLibraryNameById,
|
|
675
694
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
@@ -1635,7 +1654,7 @@ function ReactViewPanelInner({
|
|
|
1635
1654
|
const previewFaviconLink = faviconHrefWithVersion
|
|
1636
1655
|
? `<link rel="icon" type="image/png" href="${faviconHrefWithVersion}" /><link rel="shortcut icon" type="image/png" href="${faviconHrefWithVersion}" />`
|
|
1637
1656
|
: "";
|
|
1638
|
-
const previewTitle =
|
|
1657
|
+
const previewTitle = displayProductName.trim() || t("panel.defaults.unnamedProduct");
|
|
1639
1658
|
const escapedPreviewTitle = previewTitle
|
|
1640
1659
|
.replace(/&/g, "&")
|
|
1641
1660
|
.replace(/</g, "<")
|
|
@@ -1774,7 +1793,7 @@ function ReactViewPanelInner({
|
|
|
1774
1793
|
win.document.head.appendChild(link);
|
|
1775
1794
|
});
|
|
1776
1795
|
}
|
|
1777
|
-
}, [allElements, layers,
|
|
1796
|
+
}, [allElements, layers, displayProductName, t, titleIconDataUrl]);
|
|
1778
1797
|
|
|
1779
1798
|
const handlePreviewLayer = useCallback(() => {
|
|
1780
1799
|
void (async () => {
|
|
@@ -1904,6 +1923,7 @@ function ReactViewPanelInner({
|
|
|
1904
1923
|
applyTheme={applyTheme}
|
|
1905
1924
|
isDark={isDark}
|
|
1906
1925
|
openBlueprintExportDialog={openBlueprintExportDialog}
|
|
1926
|
+
openApiCollectionManager={() => setApiCollectionManagerOpen(true)}
|
|
1907
1927
|
panelFontSize={panelFontSize}
|
|
1908
1928
|
setPanelFontSize={setPanelFontSize}
|
|
1909
1929
|
locale={locale}
|
|
@@ -2034,7 +2054,7 @@ function ReactViewPanelInner({
|
|
|
2034
2054
|
style={{ zIndex: PANEL_Z_INDEX.toolbar }}
|
|
2035
2055
|
>
|
|
2036
2056
|
<Input
|
|
2037
|
-
value={
|
|
2057
|
+
value={displayProductName}
|
|
2038
2058
|
onChange={(e) => setProductName(e.target.value)}
|
|
2039
2059
|
placeholder={t("panel.menubar.productNamePlaceholder")}
|
|
2040
2060
|
className="h-7 w-[220px] text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none"
|
|
@@ -2318,7 +2338,7 @@ function ReactViewPanelInner({
|
|
|
2318
2338
|
blueprintProps={blueprintCanvasProps}
|
|
2319
2339
|
blueprintLibraryItems={blueprintLibraryItems}
|
|
2320
2340
|
activeBlueprintLibraryId={activeBlueprintLibraryId}
|
|
2321
|
-
currentBlueprintLabel={
|
|
2341
|
+
currentBlueprintLabel={displayBlueprintName}
|
|
2322
2342
|
onSelectBlueprintLibraryItem={(id) => {
|
|
2323
2343
|
void handleSelectBlueprintLibraryItem(id);
|
|
2324
2344
|
}}
|
|
@@ -2768,6 +2788,43 @@ function ReactViewPanelInner({
|
|
|
2768
2788
|
void handleBlueprintMetaConfirm(meta);
|
|
2769
2789
|
}}
|
|
2770
2790
|
/>
|
|
2791
|
+
<ApiCollectionManagerDialog
|
|
2792
|
+
open={apiCollectionManagerOpen}
|
|
2793
|
+
onOpenChange={setApiCollectionManagerOpen}
|
|
2794
|
+
onUploadStart={(info) => {
|
|
2795
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart, info);
|
|
2796
|
+
}}
|
|
2797
|
+
onUploadSuccess={(info) => {
|
|
2798
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess, info);
|
|
2799
|
+
}}
|
|
2800
|
+
onSyncCollection={(record) => {
|
|
2801
|
+
const patches = buildApiCollectionSyncPatches({
|
|
2802
|
+
collectionId: record.id,
|
|
2803
|
+
document: record.document,
|
|
2804
|
+
nodes: blueprintGraph.document.nodes
|
|
2805
|
+
.filter((node) => node.role === "fetch" || Boolean(node.fetchConfig))
|
|
2806
|
+
.map((node) => ({
|
|
2807
|
+
id: node.id,
|
|
2808
|
+
label: node.label,
|
|
2809
|
+
fetchConfig: node.fetchConfig,
|
|
2810
|
+
})),
|
|
2811
|
+
});
|
|
2812
|
+
if (patches.length === 0) {
|
|
2813
|
+
return toApiCollectionSyncResult([]);
|
|
2814
|
+
}
|
|
2815
|
+
setBlueprintGraph((graph) => {
|
|
2816
|
+
let next = graph;
|
|
2817
|
+
for (const patch of patches) {
|
|
2818
|
+
next = next.updateNode(patch.nodeId, {
|
|
2819
|
+
fetchConfig:
|
|
2820
|
+
patch.fetchConfigPatch as import("@arronqzy/blueprint-dsl").FetchRequestConfig,
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
return next;
|
|
2824
|
+
});
|
|
2825
|
+
return toApiCollectionSyncResult(patches);
|
|
2826
|
+
}}
|
|
2827
|
+
/>
|
|
2771
2828
|
{pendingBlueprintNodeSwitch ? (
|
|
2772
2829
|
<BlueprintNodeSwitchTaskDialog
|
|
2773
2830
|
open
|
|
@@ -31,11 +31,24 @@ export function ConfigHintIcon({
|
|
|
31
31
|
type="button"
|
|
32
32
|
className={
|
|
33
33
|
className ??
|
|
34
|
-
"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full
|
|
34
|
+
"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
35
35
|
}
|
|
36
36
|
aria-label={t("common.hintAria", { label: resolvedLabel })}
|
|
37
37
|
>
|
|
38
|
-
|
|
38
|
+
<svg
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
strokeWidth={2}
|
|
43
|
+
strokeLinecap="round"
|
|
44
|
+
strokeLinejoin="round"
|
|
45
|
+
className="h-3.5 w-3.5"
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
>
|
|
48
|
+
<circle cx="12" cy="12" r="10" />
|
|
49
|
+
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
|
50
|
+
<path d="M12 17h.01" />
|
|
51
|
+
</svg>
|
|
39
52
|
</button>
|
|
40
53
|
</TooltipTrigger>
|
|
41
54
|
<TooltipContent
|
|
@@ -34,6 +34,7 @@ export type PanelMenubarProps = {
|
|
|
34
34
|
applyTheme: (dark: boolean) => void;
|
|
35
35
|
isDark: boolean;
|
|
36
36
|
openBlueprintExportDialog: () => void;
|
|
37
|
+
openApiCollectionManager: () => void;
|
|
37
38
|
panelFontSize: "sm" | "md" | "lg";
|
|
38
39
|
setPanelFontSize: (value: "sm" | "md" | "lg") => void;
|
|
39
40
|
locale: Locale;
|
|
@@ -65,6 +66,7 @@ export function PanelMenubar({
|
|
|
65
66
|
applyTheme,
|
|
66
67
|
isDark,
|
|
67
68
|
openBlueprintExportDialog,
|
|
69
|
+
openApiCollectionManager,
|
|
68
70
|
panelFontSize,
|
|
69
71
|
setPanelFontSize,
|
|
70
72
|
locale,
|
|
@@ -121,6 +123,10 @@ export function PanelMenubar({
|
|
|
121
123
|
<MenubarItem onClick={() => blueprintImportInputRef.current?.click()}>
|
|
122
124
|
{t("panel.menubar.import")}
|
|
123
125
|
</MenubarItem>
|
|
126
|
+
<MenubarSeparator />
|
|
127
|
+
<MenubarItem onClick={openApiCollectionManager}>
|
|
128
|
+
{t("panel.menubar.apiCollections")}
|
|
129
|
+
</MenubarItem>
|
|
124
130
|
</MenubarContent>
|
|
125
131
|
</MenubarMenu>
|
|
126
132
|
<MenubarMenu>
|
|
@@ -152,7 +152,12 @@ export function PanelConfigAudioSection({
|
|
|
152
152
|
t("panel.config.groupAudioSource"),
|
|
153
153
|
<>
|
|
154
154
|
<label className="block space-y-1" data-config-field="audioRemoteUrl">
|
|
155
|
-
<div
|
|
155
|
+
<div className="flex items-center gap-1">
|
|
156
|
+
{t("panel.config.audioUrl")}
|
|
157
|
+
<ConfigHintIcon label={t("panel.config.audioUrl")}>
|
|
158
|
+
{t("panel.config.urlScopeHint")}
|
|
159
|
+
</ConfigHintIcon>
|
|
160
|
+
</div>
|
|
156
161
|
<Input
|
|
157
162
|
value={selectedElement.audioRemoteUrl ?? ""}
|
|
158
163
|
onChange={(e) =>
|
|
@@ -164,9 +169,6 @@ export function PanelConfigAudioSection({
|
|
|
164
169
|
placeholder={t("panel.config.urlScopePlaceholder")}
|
|
165
170
|
className="h-7 font-mono text-[11px]"
|
|
166
171
|
/>
|
|
167
|
-
<p className="text-[10px] text-muted-foreground">
|
|
168
|
-
{t("panel.config.urlScopeHint")}
|
|
169
|
-
</p>
|
|
170
172
|
</label>
|
|
171
173
|
<div className="flex items-center gap-2">
|
|
172
174
|
<label className="inline-flex cursor-pointer items-center rounded border border-border px-2 py-1 text-[11px] hover:bg-accent">
|
|
@@ -118,7 +118,12 @@ export function PanelConfigChartSection({
|
|
|
118
118
|
t("panel.config.groupData"),
|
|
119
119
|
<>
|
|
120
120
|
<label className="block space-y-1" data-config-field="chart.labelsText">
|
|
121
|
-
<div
|
|
121
|
+
<div className="flex items-center gap-1">
|
|
122
|
+
{t("panel.config.labelsCsv")}
|
|
123
|
+
<ConfigHintIcon label={t("panel.config.labelsCsv")}>
|
|
124
|
+
{t("panel.config.scopeTemplateHint")}
|
|
125
|
+
</ConfigHintIcon>
|
|
126
|
+
</div>
|
|
122
127
|
<Input
|
|
123
128
|
value={getChartLabelsDisplayText(selectedElement.chart)}
|
|
124
129
|
onChange={(e) =>
|
|
@@ -129,13 +134,15 @@ export function PanelConfigChartSection({
|
|
|
129
134
|
placeholder={t("panel.config.labelsCsvPlaceholder")}
|
|
130
135
|
className="h-7"
|
|
131
136
|
/>
|
|
132
|
-
<p className="text-[10px] text-muted-foreground">
|
|
133
|
-
{t("panel.config.scopeTemplateHint")}
|
|
134
|
-
</p>
|
|
135
137
|
</label>
|
|
136
138
|
|
|
137
139
|
<label className="block space-y-1" data-config-field="chart.valuesText">
|
|
138
|
-
<div
|
|
140
|
+
<div className="flex items-center gap-1">
|
|
141
|
+
{t("panel.config.valuesCsv")}
|
|
142
|
+
<ConfigHintIcon label={t("panel.config.valuesCsv")}>
|
|
143
|
+
{t("panel.config.scopeTemplateHint")}
|
|
144
|
+
</ConfigHintIcon>
|
|
145
|
+
</div>
|
|
139
146
|
<Input
|
|
140
147
|
value={getChartValuesDisplayText(selectedElement.chart)}
|
|
141
148
|
onChange={(e) =>
|
|
@@ -146,9 +153,6 @@ export function PanelConfigChartSection({
|
|
|
146
153
|
placeholder={t("panel.config.valuesCsvPlaceholder")}
|
|
147
154
|
className="h-7"
|
|
148
155
|
/>
|
|
149
|
-
<p className="text-[10px] text-muted-foreground">
|
|
150
|
-
{t("panel.config.scopeTemplateHint")}
|
|
151
|
-
</p>
|
|
152
156
|
</label>
|
|
153
157
|
</>
|
|
154
158
|
)}
|