@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 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.20",
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.7",
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.11",
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.9"
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
 
@@ -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({ children, locale, onLocaleChange }: I18nRootProps) {
15
+ export function I18nRoot({
16
+ children,
17
+ locale,
18
+ onLocaleChange,
19
+ nameSpace,
20
+ }: I18nRootProps) {
13
21
  return (
14
- <I18nProvider locale={locale} onLocaleChange={onLocaleChange}>
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(projectId: string): Promise<WorkspaceProjectRecord | null> {
72
- const fromDb = await getWorkspaceProject(projectId);
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.trim() || record.name || t("panel.workspace.previewDocTitle");
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("panel:fontSize");
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("panel:fontSize", panelFontSize);
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
- try {
603
- const stored = localStorage.getItem(TITLE_ICON_STORAGE_KEY) ?? "";
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
- nodes.forEach(function (node) {
1893
- rememberBox(node);
1894
- node.style.left = node.dataset.origLeft + "px";
1895
- node.style.top = node.dataset.origTop + "px";
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
- rememberBox(node);
1907
- node.style.left = (Number(node.dataset.origLeft) * scaleX) + "px";
1908
- node.style.top = (Number(node.dataset.origTop) * scaleY) + "px";
1909
- node.style.width = (Number(node.dataset.origWidth) * scaleX) + "px";
1910
- node.style.height = (Number(node.dataset.origHeight) * scaleY) + "px";
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
  }