@arronqzy/react-view 1.0.20 → 1.0.22
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 +45 -0
- package/package.json +5 -5
- package/readme.md +6 -1
- package/src/panel/I18nRoot.tsx +14 -2
- package/src/panel/ReactViewOnlinePreview.tsx +29 -18
- package/src/panel/ReactViewPanel.tsx +65 -59
- package/src/panel/ai/applyAssistantAction.ts +1 -0
- package/src/panel/components/ElementsLayer.tsx +67 -16
- package/src/panel/components/MaterialSidebar.tsx +18 -11
- package/src/panel/components/PanelConfigSidebar.tsx +139 -1
- package/src/panel/components/WorkspaceConfigSidebar.tsx +1 -0
- package/src/panel/hooks/usePanelElements.ts +10 -4
- package/src/panel/hooks/useWorkspaceProjects.ts +112 -34
- package/src/panel/index.tsx +15 -1
- package/src/panel/library/capture-preview-snapshot.ts +4 -2
- package/src/panel/library/parse-workspace-data.ts +142 -0
- package/src/panel/library/workspace-project-cache.ts +13 -4
- package/src/panel/library/workspace-project-db.ts +24 -13
- package/src/panel/library/workspace-project-sync.ts +17 -6
- package/src/panel/types.ts +4 -0
- package/src/panel/utils/outputScale.ts +9 -4
- package/src/panel/utils/panelElementDefaults.ts +10 -0
- package/src/panel/utils/panelElementNodes.ts +5 -3
- package/src/panel/utils/panelStateIO.ts +18 -0
- package/src/panel/utils/viewportPlacement.ts +73 -0
package/dist/styles.css
CHANGED
|
@@ -616,6 +616,10 @@ video {
|
|
|
616
616
|
inset: 0px;
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
+
.inset-1\.5 {
|
|
620
|
+
inset: 0.375rem;
|
|
621
|
+
}
|
|
622
|
+
|
|
619
623
|
.inset-2 {
|
|
620
624
|
inset: 0.5rem;
|
|
621
625
|
}
|
|
@@ -645,6 +649,10 @@ video {
|
|
|
645
649
|
left: 0.125rem;
|
|
646
650
|
}
|
|
647
651
|
|
|
652
|
+
.left-1 {
|
|
653
|
+
left: 0.25rem;
|
|
654
|
+
}
|
|
655
|
+
|
|
648
656
|
.left-1\/2 {
|
|
649
657
|
left: 50%;
|
|
650
658
|
}
|
|
@@ -653,6 +661,10 @@ video {
|
|
|
653
661
|
left: 0.5rem;
|
|
654
662
|
}
|
|
655
663
|
|
|
664
|
+
.left-6 {
|
|
665
|
+
left: 1.5rem;
|
|
666
|
+
}
|
|
667
|
+
|
|
656
668
|
.left-\[var\(--rv-ruler-size\)\] {
|
|
657
669
|
left: var(--rv-ruler-size);
|
|
658
670
|
}
|
|
@@ -677,10 +689,18 @@ video {
|
|
|
677
689
|
top: 0.125rem;
|
|
678
690
|
}
|
|
679
691
|
|
|
692
|
+
.top-1 {
|
|
693
|
+
top: 0.25rem;
|
|
694
|
+
}
|
|
695
|
+
|
|
680
696
|
.top-2 {
|
|
681
697
|
top: 0.5rem;
|
|
682
698
|
}
|
|
683
699
|
|
|
700
|
+
.top-4 {
|
|
701
|
+
top: 1rem;
|
|
702
|
+
}
|
|
703
|
+
|
|
684
704
|
.top-\[calc\(100\%\+6px\)\] {
|
|
685
705
|
top: calc(100% + 6px);
|
|
686
706
|
}
|
|
@@ -1513,6 +1533,10 @@ video {
|
|
|
1513
1533
|
border-radius: 1rem;
|
|
1514
1534
|
}
|
|
1515
1535
|
|
|
1536
|
+
.rounded-\[2px\] {
|
|
1537
|
+
border-radius: 2px;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1516
1540
|
.rounded-full {
|
|
1517
1541
|
border-radius: 9999px;
|
|
1518
1542
|
}
|
|
@@ -1533,6 +1557,15 @@ video {
|
|
|
1533
1557
|
border-radius: 0.75rem;
|
|
1534
1558
|
}
|
|
1535
1559
|
|
|
1560
|
+
.rounded-l {
|
|
1561
|
+
border-top-left-radius: 0.25rem;
|
|
1562
|
+
border-bottom-left-radius: 0.25rem;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.rounded-tl {
|
|
1566
|
+
border-top-left-radius: 0.25rem;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1536
1569
|
.border {
|
|
1537
1570
|
border-width: 1px;
|
|
1538
1571
|
}
|
|
@@ -1890,10 +1923,22 @@ video {
|
|
|
1890
1923
|
background-color: hsl(var(--primary) / 0.15);
|
|
1891
1924
|
}
|
|
1892
1925
|
|
|
1926
|
+
.bg-primary\/30 {
|
|
1927
|
+
background-color: hsl(var(--primary) / 0.3);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1893
1930
|
.bg-primary\/35 {
|
|
1894
1931
|
background-color: hsl(var(--primary) / 0.35);
|
|
1895
1932
|
}
|
|
1896
1933
|
|
|
1934
|
+
.bg-primary\/40 {
|
|
1935
|
+
background-color: hsl(var(--primary) / 0.4);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
.bg-primary\/45 {
|
|
1939
|
+
background-color: hsl(var(--primary) / 0.45);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1897
1942
|
.bg-primary\/5 {
|
|
1898
1943
|
background-color: hsl(var(--primary) / 0.05);
|
|
1899
1944
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/react-view",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.22",
|
|
4
4
|
"files": [
|
|
5
5
|
"src",
|
|
6
6
|
"dist",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"@codemirror/lang-json": "^6.0.1",
|
|
31
31
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
32
32
|
"@codemirror/view": "^6.36.2",
|
|
33
|
-
"@arronqzy/i18n": "0.1.
|
|
34
|
-
"@arronqzy/blueprint-dsl": "1.0.9",
|
|
33
|
+
"@arronqzy/i18n": "0.1.8",
|
|
35
34
|
"@arronqzy/ui": "1.0.6",
|
|
36
35
|
"@arronqzy/rx-store": "1.0.5",
|
|
37
|
-
"@arronqzy/react-blueprint": "1.0.
|
|
36
|
+
"@arronqzy/react-blueprint": "1.0.13",
|
|
37
|
+
"@arronqzy/blueprint-dsl": "1.0.11",
|
|
38
38
|
"@arronqzy/view-scene3d": "0.1.2",
|
|
39
39
|
"@arronqzy/view-table": "0.1.5",
|
|
40
|
-
"@arronqzy/webllm-assistant": "0.1.
|
|
40
|
+
"@arronqzy/webllm-assistant": "0.1.10"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/react": "^19.0.1",
|
package/readme.md
CHANGED
|
@@ -147,7 +147,12 @@ const dataUrl = await getPreviewSnapshot({
|
|
|
147
147
|
| `parseOnlinePreviewSearchParams` | 解析预览 URL 参数 |
|
|
148
148
|
| `addEventSubscription` | 订阅工作区事件,返回 `{ unsubscribe }` |
|
|
149
149
|
| `AbuilderEvents` | 工作区事件名常量 |
|
|
150
|
-
| `getPreviewSnapshot` | 获取预览画面 base64
|
|
150
|
+
| `getPreviewSnapshot` | 获取预览画面 base64 缩略图(仅本包 / `@arronqzy/abuilder`) |
|
|
151
|
+
| `parseWorkspaceData` / `validateWorkspaceData` | 解析或校验完整工作区;`parse` 的 `value` 可作 `initialWorkspace` |
|
|
152
|
+
| `parseViewData` / `validateViewData` | 解析或校验视图 `panelState` |
|
|
153
|
+
| `parseBlueprintData` / `validateBlueprintData` | 解析或校验蓝图文档 |
|
|
154
|
+
| `createEmptyWorkspace` | 构造空工作区记录 |
|
|
155
|
+
| `createWorkspaceProjectId` | 生成工作区 ID |
|
|
151
156
|
| `WorkspaceData` / `WorkspaceProjectRecord` | 完整工作区数据类型 |
|
|
152
157
|
| `createView` / `createAction` 等 | 视图 DSL 与指令扩展(`src/core`) |
|
|
153
158
|
|
package/src/panel/I18nRoot.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { I18nProvider } from "@arronqzy/i18n/react";
|
|
2
|
+
import { LOCALE_STORAGE_KEY } from "@arronqzy/i18n";
|
|
3
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
2
4
|
import type { Locale } from "@arronqzy/i18n";
|
|
3
5
|
import type { ReactNode } from "react";
|
|
4
6
|
|
|
@@ -6,12 +8,22 @@ export type I18nRootProps = {
|
|
|
6
8
|
children: ReactNode;
|
|
7
9
|
locale?: Locale | null;
|
|
8
10
|
onLocaleChange?: (locale: Locale) => void;
|
|
11
|
+
nameSpace?: string | null;
|
|
9
12
|
};
|
|
10
13
|
|
|
11
14
|
/** Ensures panel / preview subtree always has i18n context (host apps, duplicate package graphs). */
|
|
12
|
-
export function I18nRoot({
|
|
15
|
+
export function I18nRoot({
|
|
16
|
+
children,
|
|
17
|
+
locale,
|
|
18
|
+
onLocaleChange,
|
|
19
|
+
nameSpace,
|
|
20
|
+
}: I18nRootProps) {
|
|
13
21
|
return (
|
|
14
|
-
<I18nProvider
|
|
22
|
+
<I18nProvider
|
|
23
|
+
locale={locale}
|
|
24
|
+
onLocaleChange={onLocaleChange}
|
|
25
|
+
storageKey={appStorageKey(LOCALE_STORAGE_KEY, nameSpace)}
|
|
26
|
+
>
|
|
15
27
|
{children}
|
|
16
28
|
</I18nProvider>
|
|
17
29
|
);
|
|
@@ -54,6 +54,7 @@ export type ReactViewOnlinePreviewProps = {
|
|
|
54
54
|
workspace?: WorkspaceProjectRecord | null;
|
|
55
55
|
locale?: Locale | null;
|
|
56
56
|
onLocaleChange?: (locale: Locale) => void;
|
|
57
|
+
nameSpace?: string | null;
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
function applyTitleIcon(titleIconDataUrl?: string) {
|
|
@@ -68,20 +69,24 @@ function applyTitleIcon(titleIconDataUrl?: string) {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
async function loadWorkspaceRecord(
|
|
72
|
-
|
|
72
|
+
async function loadWorkspaceRecord(
|
|
73
|
+
projectId: string,
|
|
74
|
+
nameSpace?: string | null
|
|
75
|
+
): Promise<WorkspaceProjectRecord | null> {
|
|
76
|
+
const fromDb = await getWorkspaceProject(projectId, nameSpace);
|
|
73
77
|
if (fromDb) return fromDb;
|
|
74
|
-
return readWorkspacePreviewCache(projectId);
|
|
78
|
+
return readWorkspacePreviewCache(projectId, nameSpace);
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
export function ReactViewOnlinePreview({
|
|
78
82
|
locale,
|
|
79
83
|
onLocaleChange,
|
|
84
|
+
nameSpace,
|
|
80
85
|
...rest
|
|
81
86
|
}: ReactViewOnlinePreviewProps) {
|
|
82
87
|
return (
|
|
83
|
-
<I18nRoot locale={locale} onLocaleChange={onLocaleChange}>
|
|
84
|
-
<ReactViewOnlinePreviewInner {...rest} />
|
|
88
|
+
<I18nRoot locale={locale} onLocaleChange={onLocaleChange} nameSpace={nameSpace}>
|
|
89
|
+
<ReactViewOnlinePreviewInner nameSpace={nameSpace} {...rest} />
|
|
85
90
|
</I18nRoot>
|
|
86
91
|
);
|
|
87
92
|
}
|
|
@@ -90,6 +95,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
90
95
|
projectId,
|
|
91
96
|
previewInstanceId,
|
|
92
97
|
workspace = null,
|
|
98
|
+
nameSpace = null,
|
|
93
99
|
}: ReactViewOnlinePreviewProps) {
|
|
94
100
|
const { t } = useI18n();
|
|
95
101
|
|
|
@@ -102,7 +108,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
102
108
|
>([]);
|
|
103
109
|
const [layoutRevision, setLayoutRevision] = useState(0);
|
|
104
110
|
const [layoutReady, setLayoutReady] = useState(false);
|
|
105
|
-
const [outputScale, setOutputScale] = useState(() => readOutputScale());
|
|
111
|
+
const [outputScale, setOutputScale] = useState(() => readOutputScale(nameSpace));
|
|
106
112
|
const [fillScale, setFillScale] = useState({ scaleX: 1, scaleY: 1 });
|
|
107
113
|
|
|
108
114
|
const sceneRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -122,7 +128,9 @@ function ReactViewOnlinePreviewInner({
|
|
|
122
128
|
setPanelState(normalized);
|
|
123
129
|
setBlueprintGraph(BlueprintGraph.fromDocument(record.blueprintDocument));
|
|
124
130
|
document.title =
|
|
125
|
-
record.productName
|
|
131
|
+
(typeof record.productName === "string" ? record.productName : "").trim() ||
|
|
132
|
+
record.name ||
|
|
133
|
+
t("panel.workspace.previewDocTitle");
|
|
126
134
|
applyTitleIcon(record.titleIconDataUrl);
|
|
127
135
|
setProjectRevision((v) => v + 1);
|
|
128
136
|
return true;
|
|
@@ -141,7 +149,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
141
149
|
return;
|
|
142
150
|
}
|
|
143
151
|
|
|
144
|
-
const record = await loadWorkspaceRecord(projectId);
|
|
152
|
+
const record = await loadWorkspaceRecord(projectId, nameSpace);
|
|
145
153
|
if (!record) {
|
|
146
154
|
setLoadError(t("panel.messages.workspaceNotFound"));
|
|
147
155
|
setPanelState(null);
|
|
@@ -149,7 +157,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
applyWorkspaceRecord(record);
|
|
152
|
-
}, [applyWorkspaceRecord, projectId, t, workspace]);
|
|
160
|
+
}, [applyWorkspaceRecord, nameSpace, projectId, t, workspace]);
|
|
153
161
|
|
|
154
162
|
useEffect(() => {
|
|
155
163
|
void loadProject();
|
|
@@ -159,8 +167,8 @@ function ReactViewOnlinePreviewInner({
|
|
|
159
167
|
if (workspace || !projectId) return;
|
|
160
168
|
return subscribeWorkspaceProjectUpdates(projectId, () => {
|
|
161
169
|
void loadProject();
|
|
162
|
-
});
|
|
163
|
-
}, [loadProject, projectId, workspace]);
|
|
170
|
+
}, nameSpace);
|
|
171
|
+
}, [loadProject, nameSpace, projectId, workspace]);
|
|
164
172
|
|
|
165
173
|
const layers = useMemo(
|
|
166
174
|
() => (panelState ? parsePanelLayers(panelState) : []),
|
|
@@ -177,13 +185,13 @@ function ReactViewOnlinePreviewInner({
|
|
|
177
185
|
|
|
178
186
|
useEffect(() => {
|
|
179
187
|
let cancelled = false;
|
|
180
|
-
void listBlueprintLibrary().then((items) => {
|
|
188
|
+
void listBlueprintLibrary(nameSpace).then((items) => {
|
|
181
189
|
if (!cancelled) setBlueprintLibraryItems(items);
|
|
182
190
|
});
|
|
183
191
|
return () => {
|
|
184
192
|
cancelled = true;
|
|
185
193
|
};
|
|
186
|
-
}, [projectRevision]);
|
|
194
|
+
}, [nameSpace, projectRevision]);
|
|
187
195
|
|
|
188
196
|
const blueprintLibraryNameById = useMemo(
|
|
189
197
|
() => new Map(blueprintLibraryItems.map((item) => [item.id, item.name])),
|
|
@@ -192,13 +200,13 @@ function ReactViewOnlinePreviewInner({
|
|
|
192
200
|
|
|
193
201
|
const resolveLibraryBlueprint = useCallback<LibraryBlueprintResolver>(
|
|
194
202
|
async (libraryBlueprintId) => {
|
|
195
|
-
const record = await getBlueprintLibraryRecord(libraryBlueprintId);
|
|
203
|
+
const record = await getBlueprintLibraryRecord(libraryBlueprintId, nameSpace);
|
|
196
204
|
if (!record) return null;
|
|
197
|
-
const items = await listBlueprintLibrary();
|
|
205
|
+
const items = await listBlueprintLibrary(nameSpace);
|
|
198
206
|
const nameById = new Map(items.map((item) => [item.id, item.name]));
|
|
199
207
|
return documentToRunnableGraph(record.document, { libraryNameById: nameById });
|
|
200
208
|
},
|
|
201
|
-
[]
|
|
209
|
+
[nameSpace]
|
|
202
210
|
);
|
|
203
211
|
|
|
204
212
|
const handleViewScopeUpdate = useCallback(
|
|
@@ -251,7 +259,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
251
259
|
);
|
|
252
260
|
|
|
253
261
|
const applySceneFit = useCallback(() => {
|
|
254
|
-
const enabled = readOutputScale();
|
|
262
|
+
const enabled = readOutputScale(nameSpace);
|
|
255
263
|
setOutputScale(enabled);
|
|
256
264
|
const fill = applyPreviewSceneFill(
|
|
257
265
|
sceneRef.current,
|
|
@@ -262,7 +270,7 @@ function ReactViewOnlinePreviewInner({
|
|
|
262
270
|
setFillScale({ scaleX: fill.scaleX, scaleY: fill.scaleY });
|
|
263
271
|
setLayoutRevision((v) => v + 1);
|
|
264
272
|
notifyPreviewLayoutChanged();
|
|
265
|
-
}, [sceneBounds.height, sceneBounds.width]);
|
|
273
|
+
}, [nameSpace, sceneBounds.height, sceneBounds.width]);
|
|
266
274
|
|
|
267
275
|
useLayoutEffect(() => {
|
|
268
276
|
if (!panelState || layerElements.length === 0) return;
|
|
@@ -451,13 +459,16 @@ function ReactViewOnlinePreviewInner({
|
|
|
451
459
|
export function parseOnlinePreviewSearchParams(search: string): {
|
|
452
460
|
projectId: string;
|
|
453
461
|
previewInstanceId?: string;
|
|
462
|
+
nameSpace?: string;
|
|
454
463
|
} | null {
|
|
455
464
|
const params = new URLSearchParams(search);
|
|
456
465
|
if (params.get("preview") !== "online") return null;
|
|
457
466
|
const projectId = params.get("projectId") ?? params.get("id");
|
|
458
467
|
if (!projectId) return null;
|
|
468
|
+
const nameSpace = (params.get("ns") ?? params.get("nameSpace") ?? "").trim();
|
|
459
469
|
return {
|
|
460
470
|
projectId,
|
|
461
471
|
previewInstanceId: params.get("pid") ?? undefined,
|
|
472
|
+
nameSpace: nameSpace || undefined,
|
|
462
473
|
};
|
|
463
474
|
}
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
type BlueprintMetaDraft,
|
|
66
66
|
} from "@arronqzy/react-blueprint";
|
|
67
67
|
import type { LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
68
|
-
import { abortClockNode, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
68
|
+
import { abortClockNode, appStorageKey, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
69
69
|
import { WorkspaceStageSplit } from "./components/WorkspaceStageSplit";
|
|
70
70
|
import {
|
|
71
71
|
WorkspaceConfigSidebar,
|
|
@@ -362,22 +362,30 @@ function shouldClearSelectionOnBlank(target: HTMLElement | null) {
|
|
|
362
362
|
export type ReactViewPanelProps = {
|
|
363
363
|
initialZoom?: number;
|
|
364
364
|
className?: string;
|
|
365
|
-
/**
|
|
365
|
+
/** 外部传入的完整工作区数据,挂载后按这份数据完整渲染;空则显示空白,不自动打开 IndexedDB 记录 */
|
|
366
366
|
initialWorkspace?: WorkspaceProjectRecord | null;
|
|
367
367
|
/** 界面语言;省略时按 localStorage / 浏览器语言解析 */
|
|
368
368
|
locale?: Locale | null;
|
|
369
369
|
onLocaleChange?: (locale: Locale) => void;
|
|
370
|
+
/**
|
|
371
|
+
* 隔离本实例的 IndexedDB / localStorage / BroadcastChannel。
|
|
372
|
+
* 同一页面挂多个 App 时传入不同值,避免工作区、蓝图库、预览缓存互相覆盖。
|
|
373
|
+
* 省略或空字符串保持历史全局库名。
|
|
374
|
+
*/
|
|
375
|
+
nameSpace?: string | null;
|
|
370
376
|
};
|
|
371
377
|
|
|
372
378
|
function ReactViewPanelInner({
|
|
373
379
|
initialZoom = 1,
|
|
374
380
|
className,
|
|
375
381
|
initialWorkspace = null,
|
|
382
|
+
nameSpace = null,
|
|
376
383
|
}: ReactViewPanelProps) {
|
|
377
384
|
const { t, locale, setLocale } = useI18n();
|
|
378
385
|
const messages = useMemo(() => getPanelMessages(t), [t]);
|
|
379
|
-
const THEME_STORAGE_KEY = "panel:theme";
|
|
380
|
-
const TITLE_ICON_STORAGE_KEY = "panel:titleIconDataUrl";
|
|
386
|
+
const THEME_STORAGE_KEY = appStorageKey("panel:theme", nameSpace);
|
|
387
|
+
const TITLE_ICON_STORAGE_KEY = appStorageKey("panel:titleIconDataUrl", nameSpace);
|
|
388
|
+
const FONT_SIZE_STORAGE_KEY = appStorageKey("panel:fontSize", nameSpace);
|
|
381
389
|
const { setTheme } = useTheme();
|
|
382
390
|
const themedScrollbarClass =
|
|
383
391
|
"scrollbar-thin [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-muted/40 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/80 [&::-webkit-scrollbar-thumb]:hover:bg-border";
|
|
@@ -483,7 +491,7 @@ function ReactViewPanelInner({
|
|
|
483
491
|
const [titleIconZoom, setTitleIconZoom] = useState(1.6);
|
|
484
492
|
const [pendingPreviewLayerId, setPendingPreviewLayerId] = useState<string | null>(null);
|
|
485
493
|
const [panelFontSize, setPanelFontSize] = useState<"sm" | "md" | "lg">("md");
|
|
486
|
-
const [outputScale, setOutputScale] = useState(() => readOutputScale());
|
|
494
|
+
const [outputScale, setOutputScale] = useState(() => readOutputScale(nameSpace));
|
|
487
495
|
const [blueprintOpen, setBlueprintOpen] = useState(false);
|
|
488
496
|
const [assistantOpen, setAssistantOpen] = useState(false);
|
|
489
497
|
const [blueprintGraph, setBlueprintGraph] = useState(() => BlueprintGraph.empty());
|
|
@@ -577,35 +585,26 @@ function ReactViewPanelInner({
|
|
|
577
585
|
|
|
578
586
|
useEffect(() => {
|
|
579
587
|
try {
|
|
580
|
-
const stored = localStorage.getItem(
|
|
588
|
+
const stored = localStorage.getItem(FONT_SIZE_STORAGE_KEY);
|
|
581
589
|
if (stored === "sm" || stored === "md" || stored === "lg") {
|
|
582
590
|
setPanelFontSize(stored);
|
|
583
591
|
}
|
|
584
592
|
} catch {
|
|
585
593
|
// ignore storage errors
|
|
586
594
|
}
|
|
587
|
-
}, []);
|
|
595
|
+
}, [FONT_SIZE_STORAGE_KEY]);
|
|
588
596
|
|
|
589
597
|
useEffect(() => {
|
|
590
598
|
try {
|
|
591
|
-
localStorage.setItem(
|
|
599
|
+
localStorage.setItem(FONT_SIZE_STORAGE_KEY, panelFontSize);
|
|
592
600
|
} catch {
|
|
593
601
|
// ignore storage errors
|
|
594
602
|
}
|
|
595
|
-
}, [panelFontSize]);
|
|
596
|
-
|
|
597
|
-
useEffect(() => {
|
|
598
|
-
writeOutputScale(outputScale);
|
|
599
|
-
}, [outputScale]);
|
|
603
|
+
}, [FONT_SIZE_STORAGE_KEY, panelFontSize]);
|
|
600
604
|
|
|
601
605
|
useEffect(() => {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (stored) setTitleIconDataUrl(stored);
|
|
605
|
-
} catch {
|
|
606
|
-
// ignore storage errors
|
|
607
|
-
}
|
|
608
|
-
}, [TITLE_ICON_STORAGE_KEY]);
|
|
606
|
+
writeOutputScale(outputScale, nameSpace);
|
|
607
|
+
}, [nameSpace, outputScale]);
|
|
609
608
|
|
|
610
609
|
useEffect(() => {
|
|
611
610
|
try {
|
|
@@ -755,6 +754,7 @@ function ReactViewPanelInner({
|
|
|
755
754
|
| "lifecyclePhase"
|
|
756
755
|
| "fetchConfig"
|
|
757
756
|
| "jsonConfig"
|
|
757
|
+
| "storageConfig"
|
|
758
758
|
| "logicConfig"
|
|
759
759
|
| "clockConfig"
|
|
760
760
|
| "eventConfig"
|
|
@@ -864,13 +864,13 @@ function ReactViewPanelInner({
|
|
|
864
864
|
|
|
865
865
|
const resolveLibraryBlueprint = useCallback<LibraryBlueprintResolver>(
|
|
866
866
|
async (libraryBlueprintId) => {
|
|
867
|
-
const record = await getBlueprintLibraryRecord(libraryBlueprintId);
|
|
867
|
+
const record = await getBlueprintLibraryRecord(libraryBlueprintId, nameSpace);
|
|
868
868
|
if (!record) return null;
|
|
869
|
-
const items = await listBlueprintLibrary();
|
|
869
|
+
const items = await listBlueprintLibrary(nameSpace);
|
|
870
870
|
const nameById = new Map(items.map((item) => [item.id, item.name]));
|
|
871
871
|
return documentToRunnableGraph(record.document, { libraryNameById: nameById });
|
|
872
872
|
},
|
|
873
|
-
[]
|
|
873
|
+
[nameSpace]
|
|
874
874
|
);
|
|
875
875
|
|
|
876
876
|
const handleBlueprintExecutionBlocked = useCallback((message: string) => {
|
|
@@ -892,6 +892,7 @@ function ReactViewPanelInner({
|
|
|
892
892
|
libraryNameById: blueprintLibraryNameById,
|
|
893
893
|
onExecutionBlocked: handleBlueprintExecutionBlocked,
|
|
894
894
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
895
|
+
nameSpace,
|
|
895
896
|
});
|
|
896
897
|
|
|
897
898
|
const { selectLifecycleNode, selectedLifecycleNodeId: debugLifecycleNodeId } =
|
|
@@ -993,9 +994,9 @@ function ReactViewPanelInner({
|
|
|
993
994
|
);
|
|
994
995
|
|
|
995
996
|
const refreshBlueprintLibrary = useCallback(async () => {
|
|
996
|
-
const items = await listBlueprintLibrary();
|
|
997
|
+
const items = await listBlueprintLibrary(nameSpace);
|
|
997
998
|
setBlueprintLibraryItems(items);
|
|
998
|
-
}, []);
|
|
999
|
+
}, [nameSpace]);
|
|
999
1000
|
|
|
1000
1001
|
useEffect(() => {
|
|
1001
1002
|
void refreshBlueprintLibrary();
|
|
@@ -1003,7 +1004,7 @@ function ReactViewPanelInner({
|
|
|
1003
1004
|
|
|
1004
1005
|
const loadBlueprintFromLibrary = useCallback(
|
|
1005
1006
|
async (id: string) => {
|
|
1006
|
-
const record = await getBlueprintLibraryRecord(id);
|
|
1007
|
+
const record = await getBlueprintLibraryRecord(id, nameSpace);
|
|
1007
1008
|
if (!record) {
|
|
1008
1009
|
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
1009
1010
|
void refreshBlueprintLibrary();
|
|
@@ -1022,7 +1023,7 @@ function ReactViewPanelInner({
|
|
|
1022
1023
|
setBlueprintOpen(true);
|
|
1023
1024
|
});
|
|
1024
1025
|
},
|
|
1025
|
-
[refreshBlueprintLibrary, t]
|
|
1026
|
+
[nameSpace, refreshBlueprintLibrary, t]
|
|
1026
1027
|
);
|
|
1027
1028
|
|
|
1028
1029
|
const snapshotWorkspaceBlueprint = useCallback(() => {
|
|
@@ -1077,7 +1078,7 @@ function ReactViewPanelInner({
|
|
|
1077
1078
|
const syncBlueprintToLibrary = useCallback(async () => {
|
|
1078
1079
|
if (!activeBlueprintLibraryId) return;
|
|
1079
1080
|
await runBusyTask(t("common.syncingBlueprint"), async () => {
|
|
1080
|
-
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
1081
|
+
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId, nameSpace);
|
|
1081
1082
|
if (!existing) {
|
|
1082
1083
|
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
1083
1084
|
void refreshBlueprintLibrary();
|
|
@@ -1093,7 +1094,7 @@ function ReactViewPanelInner({
|
|
|
1093
1094
|
meta: blueprintMeta,
|
|
1094
1095
|
source: existing.source,
|
|
1095
1096
|
});
|
|
1096
|
-
await putBlueprintLibraryRecord(record);
|
|
1097
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
1097
1098
|
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1098
1099
|
await refreshBlueprintLibrary();
|
|
1099
1100
|
toast({ title: messages.blueprintSynced(record.name) });
|
|
@@ -1104,6 +1105,7 @@ function ReactViewPanelInner({
|
|
|
1104
1105
|
blueprintMeta,
|
|
1105
1106
|
refreshBlueprintLibrary,
|
|
1106
1107
|
t,
|
|
1108
|
+
nameSpace,
|
|
1107
1109
|
]);
|
|
1108
1110
|
|
|
1109
1111
|
const saveBlueprintToLibrary = useCallback(
|
|
@@ -1114,7 +1116,7 @@ function ReactViewPanelInner({
|
|
|
1114
1116
|
let recordId = activeBlueprintLibraryId ?? undefined;
|
|
1115
1117
|
let createdAt: number | undefined;
|
|
1116
1118
|
if (activeBlueprintLibraryId) {
|
|
1117
|
-
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
1119
|
+
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId, nameSpace);
|
|
1118
1120
|
if (existing?.source === "saved") {
|
|
1119
1121
|
recordId = existing.id;
|
|
1120
1122
|
createdAt = existing.createdAt;
|
|
@@ -1132,14 +1134,14 @@ function ReactViewPanelInner({
|
|
|
1132
1134
|
meta,
|
|
1133
1135
|
source: "saved",
|
|
1134
1136
|
});
|
|
1135
|
-
await putBlueprintLibraryRecord(record);
|
|
1137
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
1136
1138
|
setActiveBlueprintLibraryId(record.id);
|
|
1137
1139
|
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1138
1140
|
await refreshBlueprintLibrary();
|
|
1139
1141
|
toast({ title: t("panel.messages.blueprintSavedLocal") });
|
|
1140
1142
|
});
|
|
1141
1143
|
},
|
|
1142
|
-
[activeBlueprintLibraryId, blueprintGraph.document, refreshBlueprintLibrary, t]
|
|
1144
|
+
[activeBlueprintLibraryId, blueprintGraph.document, nameSpace, refreshBlueprintLibrary, t]
|
|
1143
1145
|
);
|
|
1144
1146
|
|
|
1145
1147
|
const openBlueprintMetaDialog = useCallback((mode: "export" | "save") => {
|
|
@@ -1166,7 +1168,7 @@ function ReactViewPanelInner({
|
|
|
1166
1168
|
|
|
1167
1169
|
const handleRenameBlueprintLibraryItem = useCallback(
|
|
1168
1170
|
async (id: string, name: string) => {
|
|
1169
|
-
const updated = await updateBlueprintLibraryMeta(id, { name });
|
|
1171
|
+
const updated = await updateBlueprintLibraryMeta(id, { name }, nameSpace);
|
|
1170
1172
|
if (!updated) {
|
|
1171
1173
|
toast({ title: t("panel.messages.renameFailed") });
|
|
1172
1174
|
void refreshBlueprintLibrary();
|
|
@@ -1178,12 +1180,12 @@ function ReactViewPanelInner({
|
|
|
1178
1180
|
}
|
|
1179
1181
|
toast({ title: t("panel.messages.blueprintRenamed") });
|
|
1180
1182
|
},
|
|
1181
|
-
[activeBlueprintLibraryId, refreshBlueprintLibrary]
|
|
1183
|
+
[activeBlueprintLibraryId, nameSpace, refreshBlueprintLibrary]
|
|
1182
1184
|
);
|
|
1183
1185
|
|
|
1184
1186
|
const handleDeleteBlueprintLibraryItem = useCallback(
|
|
1185
1187
|
async (id: string) => {
|
|
1186
|
-
await deleteBlueprintLibraryRecord(id);
|
|
1188
|
+
await deleteBlueprintLibraryRecord(id, nameSpace);
|
|
1187
1189
|
await refreshBlueprintLibrary();
|
|
1188
1190
|
if (activeBlueprintLibraryId === id) {
|
|
1189
1191
|
setActiveBlueprintLibraryId(null);
|
|
@@ -1191,7 +1193,7 @@ function ReactViewPanelInner({
|
|
|
1191
1193
|
}
|
|
1192
1194
|
toast({ title: t("panel.messages.blueprintDeletedFromLibrary") });
|
|
1193
1195
|
},
|
|
1194
|
-
[activeBlueprintLibraryId, refreshBlueprintLibrary]
|
|
1196
|
+
[activeBlueprintLibraryId, nameSpace, refreshBlueprintLibrary]
|
|
1195
1197
|
);
|
|
1196
1198
|
|
|
1197
1199
|
const openBlueprintExportDialog = useCallback(() => {
|
|
@@ -1206,7 +1208,7 @@ function ReactViewPanelInner({
|
|
|
1206
1208
|
const text = await file.text();
|
|
1207
1209
|
const payload = parseBlueprintImportFile(await parseJsonText(text));
|
|
1208
1210
|
const record = libraryRecordFromImport(payload);
|
|
1209
|
-
await putBlueprintLibraryRecord(record);
|
|
1211
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
1210
1212
|
await refreshBlueprintLibrary();
|
|
1211
1213
|
if (!activeBlueprintLibraryId) {
|
|
1212
1214
|
snapshotWorkspaceBlueprint();
|
|
@@ -1223,6 +1225,7 @@ function ReactViewPanelInner({
|
|
|
1223
1225
|
[
|
|
1224
1226
|
activeBlueprintLibraryId,
|
|
1225
1227
|
loadBlueprintFromLibrary,
|
|
1228
|
+
nameSpace,
|
|
1226
1229
|
refreshBlueprintLibrary,
|
|
1227
1230
|
snapshotWorkspaceBlueprint,
|
|
1228
1231
|
t,
|
|
@@ -1266,6 +1269,7 @@ function ReactViewPanelInner({
|
|
|
1266
1269
|
panelRevision: `${historyCursor}|${allElements.length}`,
|
|
1267
1270
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
1268
1271
|
initialWorkspace,
|
|
1272
|
+
nameSpace,
|
|
1269
1273
|
});
|
|
1270
1274
|
|
|
1271
1275
|
useEffect(() => {
|
|
@@ -1275,11 +1279,11 @@ function ReactViewPanelInner({
|
|
|
1275
1279
|
allElements,
|
|
1276
1280
|
layers,
|
|
1277
1281
|
activeLayerId,
|
|
1278
|
-
outputScale: readOutputScale(),
|
|
1282
|
+
outputScale: readOutputScale(nameSpace),
|
|
1279
1283
|
});
|
|
1280
1284
|
});
|
|
1281
1285
|
return () => registerPreviewSnapshotProvider(null);
|
|
1282
|
-
}, [activeLayerId, allElements, canvasEl, layers]);
|
|
1286
|
+
}, [activeLayerId, allElements, canvasEl, layers, nameSpace]);
|
|
1283
1287
|
|
|
1284
1288
|
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
1285
1289
|
graph: blueprintGraph,
|
|
@@ -1602,7 +1606,7 @@ function ReactViewPanelInner({
|
|
|
1602
1606
|
if (targetLayer.isMapping) return null;
|
|
1603
1607
|
const hasBlockingRef = allElements.some((el) => {
|
|
1604
1608
|
if (el.layerId === layerId) return false;
|
|
1605
|
-
if (el.materialType !== "reference") return false;
|
|
1609
|
+
if (el.materialType !== "reference" && el.materialType !== "viewport") return false;
|
|
1606
1610
|
if (el.refLayerId !== layerId) return false;
|
|
1607
1611
|
return (el.refCopyMode ?? "shallow") !== "deep";
|
|
1608
1612
|
});
|
|
@@ -1875,6 +1879,13 @@ function ReactViewPanelInner({
|
|
|
1875
1879
|
node.dataset.origWidth = String(parseFloat(node.style.width) || node.offsetWidth || 0);
|
|
1876
1880
|
node.dataset.origHeight = String(parseFloat(node.style.height) || node.offsetHeight || 0);
|
|
1877
1881
|
}
|
|
1882
|
+
function applyBoxScale(node, sx, sy) {
|
|
1883
|
+
rememberBox(node);
|
|
1884
|
+
node.style.left = (Number(node.dataset.origLeft) * sx) + "px";
|
|
1885
|
+
node.style.top = (Number(node.dataset.origTop) * sy) + "px";
|
|
1886
|
+
node.style.width = (Number(node.dataset.origWidth) * sx) + "px";
|
|
1887
|
+
node.style.height = (Number(node.dataset.origHeight) * sy) + "px";
|
|
1888
|
+
}
|
|
1878
1889
|
function fitScene() {
|
|
1879
1890
|
if (!scene) return;
|
|
1880
1891
|
var vw = window.innerWidth || 1;
|
|
@@ -1885,29 +1896,23 @@ function ReactViewPanelInner({
|
|
|
1885
1896
|
var scaleY = vh / sh;
|
|
1886
1897
|
if (root) root.style.overflow = "hidden";
|
|
1887
1898
|
var nodes = scene.querySelectorAll("[data-element-id]");
|
|
1899
|
+
var sx = outputScale ? 1 : scaleX;
|
|
1900
|
+
var sy = outputScale ? 1 : scaleY;
|
|
1888
1901
|
if (outputScale) {
|
|
1889
1902
|
scene.style.width = sw + "px";
|
|
1890
1903
|
scene.style.height = sh + "px";
|
|
1891
1904
|
scene.style.transform = "translate(0px,0px) scale(" + scaleX + "," + scaleY + ")";
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
node.style.width = node.dataset.origWidth + "px";
|
|
1897
|
-
node.style.height = node.dataset.origHeight + "px";
|
|
1898
|
-
});
|
|
1899
|
-
return;
|
|
1905
|
+
} else {
|
|
1906
|
+
scene.style.width = vw + "px";
|
|
1907
|
+
scene.style.height = vh + "px";
|
|
1908
|
+
scene.style.transform = "none";
|
|
1900
1909
|
}
|
|
1901
|
-
// 块仍按视口撑满,但不对整棵内容做 transform,字体保持原尺寸
|
|
1902
|
-
scene.style.width = vw + "px";
|
|
1903
|
-
scene.style.height = vh + "px";
|
|
1904
|
-
scene.style.transform = "none";
|
|
1905
1910
|
nodes.forEach(function (node) {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
+
applyBoxScale(node, sx, sy);
|
|
1912
|
+
var inner = node.querySelectorAll("[data-viewport-sizer], [data-viewport-item]");
|
|
1913
|
+
inner.forEach(function (el) {
|
|
1914
|
+
applyBoxScale(el, sx, sy);
|
|
1915
|
+
});
|
|
1911
1916
|
});
|
|
1912
1917
|
}
|
|
1913
1918
|
fitScene();
|
|
@@ -3587,11 +3592,12 @@ function ReactViewPanelInner({
|
|
|
3587
3592
|
export function ReactViewPanel({
|
|
3588
3593
|
locale,
|
|
3589
3594
|
onLocaleChange,
|
|
3595
|
+
nameSpace,
|
|
3590
3596
|
...rest
|
|
3591
3597
|
}: ReactViewPanelProps) {
|
|
3592
3598
|
return (
|
|
3593
|
-
<I18nRoot locale={locale} onLocaleChange={onLocaleChange}>
|
|
3594
|
-
<ReactViewPanelInner {...rest} />
|
|
3599
|
+
<I18nRoot locale={locale} onLocaleChange={onLocaleChange} nameSpace={nameSpace}>
|
|
3600
|
+
<ReactViewPanelInner nameSpace={nameSpace} {...rest} />
|
|
3595
3601
|
</I18nRoot>
|
|
3596
3602
|
);
|
|
3597
3603
|
}
|