@arronqzy/react-view 1.0.21 → 1.0.23

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 (34) hide show
  1. package/package.json +11 -11
  2. package/readme.md +6 -1
  3. package/src/panel/I18nRoot.tsx +14 -2
  4. package/src/panel/ReactViewOnlinePreview.tsx +29 -18
  5. package/src/panel/ReactViewPanel.tsx +154 -1006
  6. package/src/panel/components/PanelConfigSidebar.tsx +297 -3712
  7. package/src/panel/components/PanelHistoryPopover.tsx +163 -0
  8. package/src/panel/components/PanelLayerDock.tsx +412 -0
  9. package/src/panel/components/PanelMenubar.tsx +149 -0
  10. package/src/panel/components/PanelWorkspaceDialogs.tsx +271 -0
  11. package/src/panel/components/config/PanelConfigAudioSection.tsx +334 -0
  12. package/src/panel/components/config/PanelConfigChartSection.tsx +799 -0
  13. package/src/panel/components/config/PanelConfigGeometrySection.tsx +233 -0
  14. package/src/panel/components/config/PanelConfigGridChildSpan.tsx +70 -0
  15. package/src/panel/components/config/PanelConfigGridSection.tsx +118 -0
  16. package/src/panel/components/config/PanelConfigMultiSelect.tsx +1291 -0
  17. package/src/panel/components/config/PanelConfigNodeInfo.tsx +169 -0
  18. package/src/panel/components/config/PanelConfigReferenceSection.tsx +99 -0
  19. package/src/panel/components/config/PanelConfigScene3dSection.tsx +6 -2
  20. package/src/panel/components/config/PanelConfigStyleSections.tsx +249 -0
  21. package/src/panel/components/config/PanelConfigTextSection.tsx +268 -0
  22. package/src/panel/components/config/PanelConfigVideoSection.tsx +133 -0
  23. package/src/panel/components/config/PanelConfigViewportSection.tsx +139 -0
  24. package/src/panel/components/config/helpers.ts +85 -0
  25. package/src/panel/hooks/useWorkspaceProjects.ts +112 -34
  26. package/src/panel/icons.tsx +160 -0
  27. package/src/panel/index.tsx +15 -1
  28. package/src/panel/library/parse-workspace-data.ts +142 -0
  29. package/src/panel/library/workspace-project-cache.ts +13 -4
  30. package/src/panel/library/workspace-project-db.ts +24 -13
  31. package/src/panel/library/workspace-project-sync.ts +17 -6
  32. package/src/panel/utils/outputScale.ts +9 -4
  33. package/src/panel/utils/panelSelection.ts +57 -0
  34. package/src/panel/utils/panelStateIO.ts +18 -0
@@ -1,6 +1,7 @@
1
1
  import type { BlueprintDocument } from "@arronqzy/react-blueprint";
2
2
  import type { BlueprintMetaDraft } from "@arronqzy/react-blueprint";
3
3
  import type { State } from "@arronqzy/rx-store";
4
+ import { appStorageKey } from "@arronqzy/blueprint-dsl";
4
5
 
5
6
  const DB_NAME = "arronqzy-workspace-projects";
6
7
  const DB_VERSION = 1;
@@ -25,9 +26,9 @@ export type WorkspaceProjectRecord = {
25
26
  titleIconDataUrl?: string;
26
27
  };
27
28
 
28
- function openDb(): Promise<IDBDatabase> {
29
+ function openDb(nameSpace?: string | null): Promise<IDBDatabase> {
29
30
  return new Promise((resolve, reject) => {
30
- const request = indexedDB.open(DB_NAME, DB_VERSION);
31
+ const request = indexedDB.open(appStorageKey(DB_NAME, nameSpace), DB_VERSION);
31
32
  request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
32
33
  request.onupgradeneeded = () => {
33
34
  const db = request.result;
@@ -42,9 +43,10 @@ function openDb(): Promise<IDBDatabase> {
42
43
 
43
44
  function runTransaction<T>(
44
45
  mode: IDBTransactionMode,
45
- runner: (store: IDBObjectStore) => IDBRequest<T>
46
+ runner: (store: IDBObjectStore) => IDBRequest<T>,
47
+ nameSpace?: string | null
46
48
  ): Promise<T> {
47
- return openDb().then(
49
+ return openDb(nameSpace).then(
48
50
  (db) =>
49
51
  new Promise<T>((resolve, reject) => {
50
52
  const tx = db.transaction(STORE_NAME, mode);
@@ -77,8 +79,10 @@ export function createWorkspaceProjectId(): string {
77
79
  return `ws-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
78
80
  }
79
81
 
80
- export async function listWorkspaceProjects(): Promise<WorkspaceProjectListItem[]> {
81
- const db = await openDb();
82
+ export async function listWorkspaceProjects(
83
+ nameSpace?: string | null
84
+ ): Promise<WorkspaceProjectListItem[]> {
85
+ const db = await openDb(nameSpace);
82
86
  return new Promise((resolve, reject) => {
83
87
  const tx = db.transaction(STORE_NAME, "readonly");
84
88
  const store = tx.objectStore(STORE_NAME);
@@ -110,21 +114,28 @@ export async function listWorkspaceProjects(): Promise<WorkspaceProjectListItem[
110
114
  }
111
115
 
112
116
  export async function getWorkspaceProject(
113
- id: string
117
+ id: string,
118
+ nameSpace?: string | null
114
119
  ): Promise<WorkspaceProjectRecord | null> {
115
- const record = await runTransaction<WorkspaceProjectRecord | undefined>("readonly", (store) =>
116
- store.get(id)
120
+ const record = await runTransaction<WorkspaceProjectRecord | undefined>(
121
+ "readonly",
122
+ (store) => store.get(id),
123
+ nameSpace
117
124
  );
118
125
  return record ?? null;
119
126
  }
120
127
 
121
128
  export async function putWorkspaceProject(
122
- record: WorkspaceProjectRecord
129
+ record: WorkspaceProjectRecord,
130
+ nameSpace?: string | null
123
131
  ): Promise<WorkspaceProjectRecord> {
124
- await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
132
+ await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record), nameSpace);
125
133
  return record;
126
134
  }
127
135
 
128
- export async function deleteWorkspaceProject(id: string): Promise<void> {
129
- await runTransaction<undefined>("readwrite", (store) => store.delete(id));
136
+ export async function deleteWorkspaceProject(
137
+ id: string,
138
+ nameSpace?: string | null
139
+ ): Promise<void> {
140
+ await runTransaction<undefined>("readwrite", (store) => store.delete(id), nameSpace);
130
141
  }
@@ -1,3 +1,5 @@
1
+ import { appStorageKey } from "@arronqzy/blueprint-dsl";
2
+
1
3
  const CHANNEL_PREFIX = "arronqzy-workspace-project";
2
4
 
3
5
  export type WorkspaceProjectSyncMessage = {
@@ -6,16 +8,22 @@ export type WorkspaceProjectSyncMessage = {
6
8
  updatedAt: number;
7
9
  };
8
10
 
9
- export function getWorkspaceProjectChannelName(projectId: string): string {
10
- return `${CHANNEL_PREFIX}:${projectId}`;
11
+ export function getWorkspaceProjectChannelName(
12
+ projectId: string,
13
+ nameSpace?: string | null
14
+ ): string {
15
+ return appStorageKey(`${CHANNEL_PREFIX}:${projectId}`, nameSpace);
11
16
  }
12
17
 
13
18
  export function broadcastWorkspaceProjectUpdate(
14
19
  projectId: string,
15
- updatedAt: number
20
+ updatedAt: number,
21
+ nameSpace?: string | null
16
22
  ): void {
17
23
  if (typeof BroadcastChannel === "undefined") return;
18
- const channel = new BroadcastChannel(getWorkspaceProjectChannelName(projectId));
24
+ const channel = new BroadcastChannel(
25
+ getWorkspaceProjectChannelName(projectId, nameSpace)
26
+ );
19
27
  channel.postMessage({
20
28
  type: "updated",
21
29
  projectId,
@@ -26,12 +34,15 @@ export function broadcastWorkspaceProjectUpdate(
26
34
 
27
35
  export function subscribeWorkspaceProjectUpdates(
28
36
  projectId: string,
29
- onUpdate: (message: WorkspaceProjectSyncMessage) => void
37
+ onUpdate: (message: WorkspaceProjectSyncMessage) => void,
38
+ nameSpace?: string | null
30
39
  ): () => void {
31
40
  if (typeof BroadcastChannel === "undefined") {
32
41
  return () => {};
33
42
  }
34
- const channel = new BroadcastChannel(getWorkspaceProjectChannelName(projectId));
43
+ const channel = new BroadcastChannel(
44
+ getWorkspaceProjectChannelName(projectId, nameSpace)
45
+ );
35
46
  channel.onmessage = (event: MessageEvent<WorkspaceProjectSyncMessage>) => {
36
47
  const data = event.data;
37
48
  if (!data || data.type !== "updated" || data.projectId !== projectId) return;
@@ -1,17 +1,22 @@
1
1
  /** 产物预览是否缩放内部内容(字体等)。默认 false:块仍按视口撑满,内容保持原尺寸以免失真。 */
2
+ import { appStorageKey } from "@arronqzy/blueprint-dsl";
3
+
2
4
  export const OUTPUT_SCALE_STORAGE_KEY = "panel:outputScale";
3
5
 
4
- export function readOutputScale(): boolean {
6
+ export function readOutputScale(nameSpace?: string | null): boolean {
5
7
  try {
6
- return localStorage.getItem(OUTPUT_SCALE_STORAGE_KEY) === "1";
8
+ return localStorage.getItem(appStorageKey(OUTPUT_SCALE_STORAGE_KEY, nameSpace)) === "1";
7
9
  } catch {
8
10
  return false;
9
11
  }
10
12
  }
11
13
 
12
- export function writeOutputScale(enabled: boolean) {
14
+ export function writeOutputScale(enabled: boolean, nameSpace?: string | null) {
13
15
  try {
14
- localStorage.setItem(OUTPUT_SCALE_STORAGE_KEY, enabled ? "1" : "0");
16
+ localStorage.setItem(
17
+ appStorageKey(OUTPUT_SCALE_STORAGE_KEY, nameSpace),
18
+ enabled ? "1" : "0"
19
+ );
15
20
  } catch {
16
21
  // ignore storage errors
17
22
  }
@@ -0,0 +1,57 @@
1
+ export function formatRelativeTime(
2
+ timestamp: number,
3
+ now: number,
4
+ t: (key: string, params?: Record<string, string | number>) => string
5
+ ): string {
6
+ const diff = Math.max(0, now - timestamp);
7
+ const sec = Math.floor(diff / 1000);
8
+ if (sec < 60) return t("common.secondsAgo", { sec });
9
+ const min = Math.floor(sec / 60);
10
+ if (min < 60) return t("common.minutesAgo", { min });
11
+ const hour = Math.floor(min / 60);
12
+ if (hour < 24) return t("common.hoursAgo", { hour });
13
+ const day = Math.floor(hour / 24);
14
+ return t("common.daysAgo", { day });
15
+ }
16
+
17
+ export function getSelectedTargetsFromIds(
18
+ container: HTMLElement | null,
19
+ ids: string[],
20
+ ) {
21
+ if (!container) return [];
22
+ const targets: HTMLElement[] = [];
23
+ for (const id of ids) {
24
+ const el = container.querySelector<HTMLElement>(
25
+ `[data-element-id="${id}"]`,
26
+ );
27
+ if (el) targets.push(el);
28
+ }
29
+ return targets;
30
+ }
31
+
32
+ export function shouldClearSelectionOnBlank(target: HTMLElement | null) {
33
+ if (!target) return false;
34
+ if (
35
+ target.closest(".rv-selectable") ||
36
+ target.closest(".moveable-control-box") ||
37
+ target.closest(".moveable-group") ||
38
+ target.closest(".moveable-line") ||
39
+ target.closest(".moveable-control") ||
40
+ target.closest(".moveable-direction")
41
+ ) {
42
+ return false;
43
+ }
44
+ if (
45
+ target.closest("button") ||
46
+ target.closest("input") ||
47
+ target.closest("select") ||
48
+ target.closest("textarea") ||
49
+ target.closest("label") ||
50
+ target.closest("a") ||
51
+ target.closest("[role='menuitem']") ||
52
+ target.closest("[data-radix-popper-content-wrapper]")
53
+ ) {
54
+ return false;
55
+ }
56
+ return true;
57
+ }
@@ -26,6 +26,24 @@ export function normalizeImportedPanelState(state: unknown): State | null {
26
26
  return next;
27
27
  }
28
28
 
29
+ export function createEmptyPanelState(): State {
30
+ return {
31
+ root: {
32
+ id: "root",
33
+ type: "root",
34
+ props: {},
35
+ children: [],
36
+ },
37
+ selectedIds: [],
38
+ variables: {
39
+ counter: 0,
40
+ isOpen: false,
41
+ layers: [DEFAULT_LAYER],
42
+ activeLayerId: DEFAULT_LAYER_ID,
43
+ },
44
+ };
45
+ }
46
+
29
47
  export function parseAllPanelElements(state: State): PanelElement[] {
30
48
  return (state.root.children ?? [])
31
49
  .filter((node) => {