@arronqzy/vue-view 0.1.4 → 0.1.7

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 (41) hide show
  1. package/package.json +6 -5
  2. package/src/panel/VueViewOnlinePreview.vue +55 -19
  3. package/src/panel/VueViewPanel.vue +123 -66
  4. package/src/panel/components/BusyOverlay.vue +24 -0
  5. package/src/panel/components/ConfigHintIcon.vue +10 -10
  6. package/src/panel/components/ElementsLayer.vue +51 -8
  7. package/src/panel/components/MaterialPreview.vue +8 -0
  8. package/src/panel/components/PanelConfigSidebar.vue +32 -20
  9. package/src/panel/components/WorkspaceConfigSidebar.vue +11 -0
  10. package/src/panel/components/WorkspaceProjectNav.vue +36 -8
  11. package/src/panel/components/config/ConfigColorField.vue +33 -9
  12. package/src/panel/components/config/ConfigFieldGroup.vue +35 -6
  13. package/src/panel/components/config/PanelConfigAudioSection.vue +37 -19
  14. package/src/panel/components/config/PanelConfigChartSection.vue +101 -84
  15. package/src/panel/components/config/PanelConfigGeometrySection.vue +10 -2
  16. package/src/panel/components/config/PanelConfigImageSection.vue +18 -10
  17. package/src/panel/components/config/PanelConfigNodeInfo.vue +21 -12
  18. package/src/panel/components/config/PanelConfigReferenceSection.vue +5 -1
  19. package/src/panel/components/config/PanelConfigScene3dSection.vue +505 -0
  20. package/src/panel/components/config/PanelConfigStyleSections.vue +19 -11
  21. package/src/panel/components/config/PanelConfigTextSection.vue +25 -7
  22. package/src/panel/components/config/PanelConfigVideoSection.vue +13 -9
  23. package/src/panel/components/config/shared.ts +16 -21
  24. package/src/panel/components/elementsLayerNodes.ts +64 -59
  25. package/src/panel/components/materialSidebarData.ts +2 -0
  26. package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +1 -1
  27. package/src/panel/components/table/PanelConfigTableSection.vue +39 -25
  28. package/src/panel/components/table/TableNodeContent.vue +36 -15
  29. package/src/panel/hooks/usePanelElements.ts +13 -1
  30. package/src/panel/hooks/useWorkspaceProjects.ts +128 -57
  31. package/src/panel/library/workspace-project-cache.ts +7 -2
  32. package/src/panel/library/workspace-project-db.ts +29 -11
  33. package/src/panel/library/workspace-snapshot.ts +21 -4
  34. package/src/panel/scope/view-scope-store.ts +8 -0
  35. package/src/panel/types.ts +4 -2
  36. package/src/panel/utils/async-work.ts +153 -0
  37. package/src/panel/utils/chartOptionBuilder.ts +7 -1
  38. package/src/panel/utils/gridPlacement.ts +29 -0
  39. package/src/panel/utils/outputScale.ts +56 -0
  40. package/src/panel/utils/panelElementDefaults.ts +37 -2
  41. 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,56 @@
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 type PreviewFillScale = {
13
+ vw: number;
14
+ vh: number;
15
+ scaleX: number;
16
+ scaleY: number;
17
+ };
18
+
19
+ export function getPreviewFillScale(
20
+ sceneWidth: number,
21
+ sceneHeight: number
22
+ ): PreviewFillScale {
23
+ const vw = window.innerWidth || 1;
24
+ const vh = window.innerHeight || 1;
25
+ const sw = Math.max(1, sceneWidth);
26
+ const sh = Math.max(1, sceneHeight);
27
+ return {
28
+ vw,
29
+ vh,
30
+ scaleX: vw / sw,
31
+ scaleY: vh / sh,
32
+ };
33
+ }
34
+
35
+ export function applyPreviewSceneFill(
36
+ scene: HTMLElement | null,
37
+ sceneWidth: number,
38
+ sceneHeight: number,
39
+ outputScale: boolean
40
+ ): PreviewFillScale {
41
+ const fill = getPreviewFillScale(sceneWidth, sceneHeight);
42
+ if (!Number.isFinite(fill.scaleX) || !Number.isFinite(fill.scaleY)) return fill;
43
+ if (!scene) return fill;
44
+ const root = scene.parentElement;
45
+ if (root) root.style.overflow = "hidden";
46
+ if (outputScale) {
47
+ scene.style.width = `${Math.max(1, sceneWidth)}px`;
48
+ scene.style.height = `${Math.max(1, sceneHeight)}px`;
49
+ scene.style.transform = `translate(0px,0px) scale(${fill.scaleX},${fill.scaleY})`;
50
+ } else {
51
+ scene.style.width = `${fill.vw}px`;
52
+ scene.style.height = `${fill.vh}px`;
53
+ scene.style.transform = "none";
54
+ }
55
+ return fill;
56
+ }
@@ -1,7 +1,8 @@
1
1
  import type { TranslateFn } from "@arronqzy/i18n";
2
2
  import { tForLocale } from "@arronqzy/i18n";
3
3
  import { createDefaultTableConfig, type PanelTableConfig } from "@arronqzy/view-table";
4
- import type { PanelChartConfig, PanelLayer } from "../types";
4
+ import { createDefaultScene3dConfig } from "@arronqzy/view-scene3d";
5
+ import type { PanelChartConfig, PanelLayer, PanelElement } from "../types";
5
6
 
6
7
  const DEFAULT_NODE_NAME_KEYS: Record<string, string> = {
7
8
  bar: "panel.defaults.bar",
@@ -19,6 +20,7 @@ const DEFAULT_NODE_NAME_KEYS: Record<string, string> = {
19
20
  audio: "panel.defaults.audio",
20
21
  reference: "panel.defaults.reference",
21
22
  geometry: "panel.defaults.geometry",
23
+ scene3d: "panel.defaults.scene3d",
22
24
  table: "panel.defaults.table",
23
25
  };
24
26
 
@@ -81,6 +83,8 @@ export function getDefaultSizeByMaterial(materialType: string) {
81
83
  return { width: 280, height: 180 };
82
84
  case "geometry":
83
85
  return { width: 220, height: 220 };
86
+ case "scene3d":
87
+ return { width: 480, height: 320 };
84
88
  case "table":
85
89
  return { width: 480, height: 280 };
86
90
  default:
@@ -88,6 +92,33 @@ export function getDefaultSizeByMaterial(materialType: string) {
88
92
  }
89
93
  }
90
94
 
95
+ export function cssTextLineHeight(
96
+ value: number | "auto" | undefined
97
+ ): string | undefined {
98
+ if (value === "auto") return "normal";
99
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
100
+ return String(value);
101
+ }
102
+ return undefined;
103
+ }
104
+
105
+ export function cssTextAlignStyle(align: PanelElement["textAlign"]): {
106
+ textAlign: "left" | "center" | "right" | "justify";
107
+ display?: "flex";
108
+ flexDirection?: "column";
109
+ justifyContent?: "center";
110
+ } {
111
+ if (align === "middle") {
112
+ return {
113
+ textAlign: "center",
114
+ display: "flex",
115
+ flexDirection: "column",
116
+ justifyContent: "center",
117
+ };
118
+ }
119
+ return { textAlign: align ?? "left" };
120
+ }
121
+
91
122
  export function getDefaultTextContent(
92
123
  materialType: string,
93
124
  t: TranslateFn = tForLocale("zh-CN")
@@ -130,7 +161,6 @@ export function getDefaultChartConfig(
130
161
  return {
131
162
  title: t("panel.defaults.bar"),
132
163
  ...common,
133
- barWidth: 24,
134
164
  };
135
165
  }
136
166
  if (materialType === "line") {
@@ -193,3 +223,8 @@ export function getDefaultTableConfig(
193
223
  if (cfg.columns?.[2]) cfg.columns[2].title = t("panel.config.tableColScore");
194
224
  return cfg;
195
225
  }
226
+
227
+ export function getDefaultScene3dConfigForMaterial(materialType: string) {
228
+ if (materialType !== "scene3d") return {};
229
+ return { scene3d: createDefaultScene3dConfig() };
230
+ }
@@ -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
- }