@arronqzy/react-view 1.0.12 → 1.0.14

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.
Files changed (31) hide show
  1. package/dist/styles.css +96 -4
  2. package/package.json +8 -7
  3. package/src/panel/ReactViewOnlinePreview.tsx +72 -20
  4. package/src/panel/ReactViewPanel.tsx +238 -126
  5. package/src/panel/ai/applyAssistantAction.ts +1 -0
  6. package/src/panel/components/BusyOverlay.tsx +25 -0
  7. package/src/panel/components/ElementsLayer.tsx +110 -68
  8. package/src/panel/components/MaterialSidebar.tsx +15 -0
  9. package/src/panel/components/MoveableLayer.tsx +8 -0
  10. package/src/panel/components/PanelCanvas.tsx +3 -0
  11. package/src/panel/components/PanelConfigSidebar.tsx +582 -443
  12. package/src/panel/components/SelectLayer.tsx +5 -0
  13. package/src/panel/components/WorkspaceConfigSidebar.tsx +9 -0
  14. package/src/panel/components/WorkspaceProjectNav.tsx +27 -4
  15. package/src/panel/components/config/PanelConfigScene3dSection.tsx +874 -0
  16. package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.tsx +1 -2
  17. package/src/panel/components/table/PanelConfigTableSection.tsx +82 -50
  18. package/src/panel/components/table/TableNodeContent.tsx +40 -15
  19. package/src/panel/hooks/usePanelElements.ts +13 -1
  20. package/src/panel/hooks/useWorkspaceProjects.ts +133 -66
  21. package/src/panel/library/workspace-project-cache.ts +8 -2
  22. package/src/panel/library/workspace-project-db.ts +29 -11
  23. package/src/panel/library/workspace-snapshot.ts +21 -4
  24. package/src/panel/scope/view-scope-store.ts +8 -0
  25. package/src/panel/types.ts +4 -3
  26. package/src/panel/utils/async-work.ts +178 -0
  27. package/src/panel/utils/chartOptionBuilder.ts +7 -1
  28. package/src/panel/utils/gridPlacement.ts +29 -0
  29. package/src/panel/utils/outputScale.ts +69 -0
  30. package/src/panel/utils/panelElementDefaults.ts +37 -2
  31. package/src/panel/utils/scope-autocomplete.ts +0 -114
@@ -187,3 +187,32 @@ export function computeSnapPatchForNewElementOnLayer(
187
187
  height: nearest.height,
188
188
  };
189
189
  }
190
+
191
+ function isGridAncestorOf(
192
+ ancestorId: string,
193
+ el: PanelElement,
194
+ byId: Map<string, PanelElement>
195
+ ): boolean {
196
+ let current: PanelElement | undefined = el;
197
+ const seen = new Set<string>();
198
+ while (current?.parentGridId && !seen.has(current.id)) {
199
+ if (current.parentGridId === ancestorId) return true;
200
+ seen.add(current.id);
201
+ current = byId.get(current.parentGridId);
202
+ }
203
+ return false;
204
+ }
205
+
206
+ /** 父网格先于格子内子节点绘制,避免网格占位层盖住内容 */
207
+ export function comparePanelElementsPaintOrder(
208
+ a: PanelElement,
209
+ b: PanelElement,
210
+ byId: Map<string, PanelElement>
211
+ ): number {
212
+ if (isGridAncestorOf(a.id, b, byId)) return -1;
213
+ if (isGridAncestorOf(b.id, a, byId)) return 1;
214
+ const za = a.zIndex ?? 1;
215
+ const zb = b.zIndex ?? 1;
216
+ if (za !== zb) return za - zb;
217
+ return a.id.localeCompare(b.id);
218
+ }
@@ -0,0 +1,69 @@
1
+ /** 产物预览是否缩放内部内容(字体等)。默认 false:块仍按视口撑满,内容保持原尺寸以免失真。 */
2
+ export const OUTPUT_SCALE_STORAGE_KEY = "panel:outputScale";
3
+
4
+ export function readOutputScale(): boolean {
5
+ try {
6
+ return localStorage.getItem(OUTPUT_SCALE_STORAGE_KEY) === "1";
7
+ } catch {
8
+ return false;
9
+ }
10
+ }
11
+
12
+ export function writeOutputScale(enabled: boolean) {
13
+ try {
14
+ localStorage.setItem(OUTPUT_SCALE_STORAGE_KEY, enabled ? "1" : "0");
15
+ } catch {
16
+ // ignore storage errors
17
+ }
18
+ }
19
+
20
+ export type PreviewFillScale = {
21
+ vw: number;
22
+ vh: number;
23
+ scaleX: number;
24
+ scaleY: number;
25
+ };
26
+
27
+ export function getPreviewFillScale(
28
+ sceneWidth: number,
29
+ sceneHeight: number
30
+ ): PreviewFillScale {
31
+ const vw = window.innerWidth || 1;
32
+ const vh = window.innerHeight || 1;
33
+ const sw = Math.max(1, sceneWidth);
34
+ const sh = Math.max(1, sceneHeight);
35
+ return {
36
+ vw,
37
+ vh,
38
+ scaleX: vw / sw,
39
+ scaleY: vh / sh,
40
+ };
41
+ }
42
+
43
+ /**
44
+ * 预览始终按视口宽高比把场景块撑满。
45
+ * outputScale=true:CSS transform 缩放整棵内容(含字体)。
46
+ * outputScale=false:只把场景盒拉到视口大小,节点几何由调用方按 scale 映射,字体不跟着拉伸。
47
+ */
48
+ export function applyPreviewSceneFill(
49
+ scene: HTMLElement | null,
50
+ sceneWidth: number,
51
+ sceneHeight: number,
52
+ outputScale: boolean
53
+ ): PreviewFillScale {
54
+ const fill = getPreviewFillScale(sceneWidth, sceneHeight);
55
+ if (!Number.isFinite(fill.scaleX) || !Number.isFinite(fill.scaleY)) return fill;
56
+ if (!scene) return fill;
57
+ const root = scene.parentElement;
58
+ if (root) root.style.overflow = "hidden";
59
+ if (outputScale) {
60
+ scene.style.width = `${Math.max(1, sceneWidth)}px`;
61
+ scene.style.height = `${Math.max(1, sceneHeight)}px`;
62
+ scene.style.transform = `translate(0px,0px) scale(${fill.scaleX},${fill.scaleY})`;
63
+ } else {
64
+ scene.style.width = `${fill.vw}px`;
65
+ scene.style.height = `${fill.vh}px`;
66
+ scene.style.transform = "none";
67
+ }
68
+ return fill;
69
+ }
@@ -1,6 +1,7 @@
1
1
  import type { TranslateFn } from "@arronqzy/i18n";
2
2
  import { tForLocale } from "@arronqzy/i18n";
3
- import type { PanelChartConfig, PanelLayer } from "../types";
3
+ import { createDefaultScene3dConfig } from "@arronqzy/view-scene3d";
4
+ import type { PanelChartConfig, PanelLayer, PanelElement } from "../types";
4
5
  import { createDefaultTableConfig, type PanelTableConfig } from "@arronqzy/view-table";
5
6
 
6
7
  const tFallback = tForLocale("zh-CN");
@@ -21,6 +22,7 @@ const DEFAULT_NODE_NAME_KEYS: Record<string, string> = {
21
22
  audio: "panel.defaults.audio",
22
23
  reference: "panel.defaults.reference",
23
24
  geometry: "panel.defaults.geometry",
25
+ scene3d: "panel.defaults.scene3d",
24
26
  table: "panel.defaults.table",
25
27
  };
26
28
 
@@ -80,6 +82,8 @@ export function getDefaultSizeByMaterial(materialType: string) {
80
82
  return { width: 280, height: 180 };
81
83
  case "geometry":
82
84
  return { width: 220, height: 220 };
85
+ case "scene3d":
86
+ return { width: 480, height: 320 };
83
87
  case "table":
84
88
  return { width: 480, height: 280 };
85
89
  default:
@@ -87,6 +91,33 @@ export function getDefaultSizeByMaterial(materialType: string) {
87
91
  }
88
92
  }
89
93
 
94
+ export function cssTextLineHeight(
95
+ value: number | "auto" | undefined
96
+ ): string | undefined {
97
+ if (value === "auto") return "normal";
98
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
99
+ return String(value);
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+ export function cssTextAlignStyle(align: PanelElement["textAlign"]): {
105
+ textAlign: "left" | "center" | "right" | "justify";
106
+ display?: "flex";
107
+ flexDirection?: "column";
108
+ justifyContent?: "center";
109
+ } {
110
+ if (align === "middle") {
111
+ return {
112
+ textAlign: "center",
113
+ display: "flex",
114
+ flexDirection: "column",
115
+ justifyContent: "center",
116
+ };
117
+ }
118
+ return { textAlign: align ?? "left" };
119
+ }
120
+
90
121
  export function getDefaultTextContent(materialType: string, t: TranslateFn = tFallback) {
91
122
  if (materialType !== "text") return {};
92
123
  return {
@@ -126,7 +157,6 @@ export function getDefaultChartConfig(
126
157
  return {
127
158
  title: t("panel.defaults.bar"),
128
159
  ...common,
129
- barWidth: 24,
130
160
  };
131
161
  }
132
162
  if (materialType === "line") {
@@ -189,3 +219,8 @@ export function getDefaultTableConfig(
189
219
  if (cfg.columns?.[2]) cfg.columns[2].title = t("panel.config.tableColScore");
190
220
  return cfg;
191
221
  }
222
+
223
+ export function getDefaultScene3dConfigForMaterial(materialType: string) {
224
+ if (materialType !== "scene3d") return {};
225
+ return { scene3d: createDefaultScene3dConfig() };
226
+ }
@@ -1,114 +0,0 @@
1
- export type ScopeAutocompleteState = {
2
- templateStart: number;
3
- cursor: number;
4
- path: string[];
5
- partialKey: string;
6
- };
7
-
8
- function getValueAtPath(scope: unknown, path: string[]): unknown {
9
- let current: unknown = scope;
10
- for (const part of path) {
11
- if (current === null || typeof current !== "object") return undefined;
12
- current = (current as Record<string, unknown>)[part];
13
- }
14
- return current;
15
- }
16
-
17
- function listObjectKeys(value: unknown): string[] {
18
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
19
- return [];
20
- }
21
- return Object.keys(value as Record<string, unknown>);
22
- }
23
-
24
- /** 解析光标处未闭合的 scope 模版表达式 */
25
- export function parseScopeAutocomplete(
26
- text: string,
27
- cursor: number,
28
- scope: unknown
29
- ): ScopeAutocompleteState | null {
30
- const before = text.slice(0, cursor);
31
- const templateStart = before.lastIndexOf("{");
32
- if (templateStart < 0) return null;
33
-
34
- const inner = before.slice(templateStart + 1);
35
- if (inner.includes("}")) return null;
36
- if (!inner.startsWith("scope")) return null;
37
-
38
- let body = inner.slice("scope".length);
39
- if (body === "" || body === "?") {
40
- return { templateStart, cursor, path: [], partialKey: "" };
41
- }
42
- if (!body.startsWith("?.")) return null;
43
-
44
- body = body.slice(2);
45
- if (body === "") {
46
- return { templateStart, cursor, path: [], partialKey: "" };
47
- }
48
-
49
- const segments = body.split("?.");
50
- const endsWithSeparator = inner.endsWith("?.");
51
-
52
- if (endsWithSeparator) {
53
- const path = segments.filter((segment) => segment.length > 0);
54
- return { templateStart, cursor, path, partialKey: "" };
55
- }
56
-
57
- const lastSegment = segments[segments.length - 1] ?? "";
58
- const prefixSegments = segments.slice(0, -1).filter((segment) => segment.length > 0);
59
- const parent = getValueAtPath(scope, prefixSegments);
60
- const keys = listObjectKeys(parent);
61
-
62
- if (lastSegment.length === 0) {
63
- return { templateStart, cursor, path: prefixSegments, partialKey: "" };
64
- }
65
-
66
- if (keys.includes(lastSegment)) {
67
- return {
68
- templateStart,
69
- cursor,
70
- path: [...prefixSegments, lastSegment],
71
- partialKey: "",
72
- };
73
- }
74
-
75
- return {
76
- templateStart,
77
- cursor,
78
- path: prefixSegments,
79
- partialKey: lastSegment,
80
- };
81
- }
82
-
83
- export function getScopeAutocompleteSuggestions(
84
- scope: unknown,
85
- state: ScopeAutocompleteState
86
- ): string[] {
87
- const parent =
88
- state.partialKey.length > 0
89
- ? getValueAtPath(scope, state.path)
90
- : state.path.length > 0
91
- ? getValueAtPath(scope, state.path)
92
- : scope;
93
-
94
- const keys = listObjectKeys(parent);
95
- if (state.partialKey.length === 0) return keys;
96
- return keys.filter((key) => key.startsWith(state.partialKey));
97
- }
98
-
99
- export function buildScopeExpression(path: string[], selectedKey: string): string {
100
- return `scope?.${[...path, selectedKey].join("?.")}`;
101
- }
102
-
103
- export function applyScopeAutocompleteSelection(
104
- text: string,
105
- state: ScopeAutocompleteState,
106
- selectedKey: string
107
- ): { value: string; cursor: number } {
108
- const expr = buildScopeExpression(state.path, selectedKey);
109
- const before = text.slice(0, state.templateStart + 1);
110
- const after = text.slice(state.cursor);
111
- const value = `${before}${expr}${after}`;
112
- const cursor = before.length + expr.length;
113
- return { value, cursor };
114
- }