@arronqzy/react-view 1.0.13 → 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.
- package/dist/styles.css +96 -4
- package/package.json +7 -6
- package/src/panel/ReactViewOnlinePreview.tsx +31 -2
- package/src/panel/ReactViewPanel.tsx +186 -124
- package/src/panel/ai/applyAssistantAction.ts +1 -0
- package/src/panel/components/BusyOverlay.tsx +25 -0
- package/src/panel/components/ElementsLayer.tsx +110 -68
- package/src/panel/components/MaterialSidebar.tsx +15 -0
- package/src/panel/components/MoveableLayer.tsx +8 -0
- package/src/panel/components/PanelCanvas.tsx +3 -0
- package/src/panel/components/PanelConfigSidebar.tsx +140 -91
- package/src/panel/components/SelectLayer.tsx +5 -0
- package/src/panel/components/WorkspaceConfigSidebar.tsx +1 -0
- package/src/panel/components/WorkspaceProjectNav.tsx +27 -4
- package/src/panel/components/config/PanelConfigScene3dSection.tsx +874 -0
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.tsx +1 -2
- package/src/panel/components/table/PanelConfigTableSection.tsx +82 -50
- package/src/panel/components/table/TableNodeContent.tsx +40 -15
- package/src/panel/hooks/usePanelElements.ts +13 -1
- package/src/panel/hooks/useWorkspaceProjects.ts +133 -66
- package/src/panel/library/workspace-project-cache.ts +8 -2
- package/src/panel/library/workspace-project-db.ts +29 -11
- package/src/panel/library/workspace-snapshot.ts +21 -4
- package/src/panel/scope/view-scope-store.ts +8 -0
- package/src/panel/types.ts +4 -3
- package/src/panel/utils/async-work.ts +178 -0
- package/src/panel/utils/gridPlacement.ts +29 -0
- package/src/panel/utils/panelElementDefaults.ts +37 -1
- package/src/panel/utils/scope-autocomplete.ts +0 -114
|
@@ -7,6 +7,7 @@ import { usePanelElements } from "./hooks/usePanelElements";
|
|
|
7
7
|
import { useWorkspaceProjects } from "./hooks/useWorkspaceProjects";
|
|
8
8
|
import type { WorkspaceProjectRecord } from "./library/workspace-project-db";
|
|
9
9
|
import { WorkspaceProjectNav } from "./components/WorkspaceProjectNav";
|
|
10
|
+
import { BusyOverlay } from "./components/BusyOverlay";
|
|
10
11
|
import { PanelCanvas } from "./components/PanelCanvas";
|
|
11
12
|
import { PanelRulers } from "./components/PanelRulers";
|
|
12
13
|
import { type ViewportZoom } from "./viewportZoom";
|
|
@@ -15,6 +16,12 @@ import { SelectLayer } from "./components/SelectLayer";
|
|
|
15
16
|
import { MoveableLayer } from "./components/MoveableLayer";
|
|
16
17
|
import { buildChartOption, CHART_TYPES } from "./utils/chartOptionBuilder";
|
|
17
18
|
import { readOutputScale, writeOutputScale } from "./utils/outputScale";
|
|
19
|
+
import {
|
|
20
|
+
assertFileSize,
|
|
21
|
+
parseJsonText,
|
|
22
|
+
readFileAsDataUrl,
|
|
23
|
+
runBusyTask,
|
|
24
|
+
} from "./utils/async-work";
|
|
18
25
|
import { MaterialSidebar } from "./components/MaterialSidebar";
|
|
19
26
|
import { AssistantChatPanel } from "./ai/AssistantChatPanel";
|
|
20
27
|
import {
|
|
@@ -45,13 +52,15 @@ import {
|
|
|
45
52
|
useBlueprintNodeSelectionGuard,
|
|
46
53
|
useBlueprintPageLifecycle,
|
|
47
54
|
stopAllClockSchedules,
|
|
55
|
+
resolveRunnableNodeType,
|
|
56
|
+
resolveViewElementIds,
|
|
48
57
|
type BlueprintDocument,
|
|
49
58
|
type BlueprintGraphNode,
|
|
50
59
|
type BlueprintLibraryListItem,
|
|
51
60
|
type BlueprintMetaDraft,
|
|
52
61
|
} from "@arronqzy/react-blueprint";
|
|
53
62
|
import type { LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
54
|
-
import { abortClockNode } from "@arronqzy/blueprint-dsl";
|
|
63
|
+
import { abortClockNode, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
|
|
55
64
|
import { WorkspaceStageSplit } from "./components/WorkspaceStageSplit";
|
|
56
65
|
import {
|
|
57
66
|
WorkspaceConfigSidebar,
|
|
@@ -736,6 +745,7 @@ export function ReactViewPanel({
|
|
|
736
745
|
| "jsonConfig"
|
|
737
746
|
| "logicConfig"
|
|
738
747
|
| "clockConfig"
|
|
748
|
+
| "eventConfig"
|
|
739
749
|
>
|
|
740
750
|
>
|
|
741
751
|
) => {
|
|
@@ -987,18 +997,20 @@ export function ReactViewPanel({
|
|
|
987
997
|
void refreshBlueprintLibrary();
|
|
988
998
|
return;
|
|
989
999
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1000
|
+
await runBusyTask(t("common.loadingBlueprint"), async () => {
|
|
1001
|
+
setBlueprintGraph(BlueprintGraph.fromDocument(record.document));
|
|
1002
|
+
setBlueprintMeta({
|
|
1003
|
+
name: record.name,
|
|
1004
|
+
remark: record.remark ?? "",
|
|
1005
|
+
});
|
|
1006
|
+
setActiveBlueprintLibraryId(record.id);
|
|
1007
|
+
setBlueprintSyncedDocument(record.document);
|
|
1008
|
+
setSelectedBlueprintNodeId(null);
|
|
1009
|
+
setConfigFocus("blueprint");
|
|
1010
|
+
setBlueprintOpen(true);
|
|
994
1011
|
});
|
|
995
|
-
setActiveBlueprintLibraryId(record.id);
|
|
996
|
-
setBlueprintSyncedDocument(record.document);
|
|
997
|
-
setSelectedBlueprintNodeId(null);
|
|
998
|
-
setConfigFocus("blueprint");
|
|
999
|
-
setBlueprintOpen(true);
|
|
1000
1012
|
},
|
|
1001
|
-
[refreshBlueprintLibrary]
|
|
1013
|
+
[refreshBlueprintLibrary, t]
|
|
1002
1014
|
);
|
|
1003
1015
|
|
|
1004
1016
|
const snapshotWorkspaceBlueprint = useCallback(() => {
|
|
@@ -1052,66 +1064,70 @@ export function ReactViewPanel({
|
|
|
1052
1064
|
|
|
1053
1065
|
const syncBlueprintToLibrary = useCallback(async () => {
|
|
1054
1066
|
if (!activeBlueprintLibraryId) return;
|
|
1067
|
+
await runBusyTask(t("common.syncingBlueprint"), async () => {
|
|
1068
|
+
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
1069
|
+
if (!existing) {
|
|
1070
|
+
toast({ title: t("panel.messages.blueprintNotFound") });
|
|
1071
|
+
void refreshBlueprintLibrary();
|
|
1072
|
+
setActiveBlueprintLibraryId(null);
|
|
1073
|
+
setBlueprintSyncedDocument(null);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1055
1076
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
createdAt: existing.createdAt,
|
|
1068
|
-
document: blueprintGraph.document,
|
|
1069
|
-
meta: blueprintMeta,
|
|
1070
|
-
source: existing.source,
|
|
1077
|
+
const record = buildLibraryRecord({
|
|
1078
|
+
id: existing.id,
|
|
1079
|
+
createdAt: existing.createdAt,
|
|
1080
|
+
document: blueprintGraph.document,
|
|
1081
|
+
meta: blueprintMeta,
|
|
1082
|
+
source: existing.source,
|
|
1083
|
+
});
|
|
1084
|
+
await putBlueprintLibraryRecord(record);
|
|
1085
|
+
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1086
|
+
await refreshBlueprintLibrary();
|
|
1087
|
+
toast({ title: messages.blueprintSynced(record.name) });
|
|
1071
1088
|
});
|
|
1072
|
-
await putBlueprintLibraryRecord(record);
|
|
1073
|
-
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1074
|
-
await refreshBlueprintLibrary();
|
|
1075
|
-
toast({ title: messages.blueprintSynced(record.name) });
|
|
1076
1089
|
}, [
|
|
1077
1090
|
activeBlueprintLibraryId,
|
|
1078
1091
|
blueprintGraph.document,
|
|
1079
1092
|
blueprintMeta,
|
|
1080
1093
|
refreshBlueprintLibrary,
|
|
1094
|
+
t,
|
|
1081
1095
|
]);
|
|
1082
1096
|
|
|
1083
1097
|
const saveBlueprintToLibrary = useCallback(
|
|
1084
1098
|
async (meta: BlueprintMetaDraft) => {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1099
|
+
await runBusyTask(t("common.savingBlueprint"), async () => {
|
|
1100
|
+
setBlueprintMeta(meta);
|
|
1101
|
+
|
|
1102
|
+
let recordId = activeBlueprintLibraryId ?? undefined;
|
|
1103
|
+
let createdAt: number | undefined;
|
|
1104
|
+
if (activeBlueprintLibraryId) {
|
|
1105
|
+
const existing = await getBlueprintLibraryRecord(activeBlueprintLibraryId);
|
|
1106
|
+
if (existing?.source === "saved") {
|
|
1107
|
+
recordId = existing.id;
|
|
1108
|
+
createdAt = existing.createdAt;
|
|
1109
|
+
} else {
|
|
1110
|
+
recordId = createLibraryBlueprintId();
|
|
1111
|
+
}
|
|
1094
1112
|
} else {
|
|
1095
1113
|
recordId = createLibraryBlueprintId();
|
|
1096
1114
|
}
|
|
1097
|
-
} else {
|
|
1098
|
-
recordId = createLibraryBlueprintId();
|
|
1099
|
-
}
|
|
1100
1115
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1116
|
+
const record = buildLibraryRecord({
|
|
1117
|
+
id: recordId,
|
|
1118
|
+
createdAt,
|
|
1119
|
+
document: blueprintGraph.document,
|
|
1120
|
+
meta,
|
|
1121
|
+
source: "saved",
|
|
1122
|
+
});
|
|
1123
|
+
await putBlueprintLibraryRecord(record);
|
|
1124
|
+
setActiveBlueprintLibraryId(record.id);
|
|
1125
|
+
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1126
|
+
await refreshBlueprintLibrary();
|
|
1127
|
+
toast({ title: t("panel.messages.blueprintSavedLocal") });
|
|
1107
1128
|
});
|
|
1108
|
-
await putBlueprintLibraryRecord(record);
|
|
1109
|
-
setActiveBlueprintLibraryId(record.id);
|
|
1110
|
-
setBlueprintSyncedDocument(blueprintGraph.document);
|
|
1111
|
-
await refreshBlueprintLibrary();
|
|
1112
|
-
toast({ title: t("panel.messages.blueprintSavedLocal") });
|
|
1113
1129
|
},
|
|
1114
|
-
[activeBlueprintLibraryId, blueprintGraph.document, refreshBlueprintLibrary]
|
|
1130
|
+
[activeBlueprintLibraryId, blueprintGraph.document, refreshBlueprintLibrary, t]
|
|
1115
1131
|
);
|
|
1116
1132
|
|
|
1117
1133
|
const openBlueprintMetaDialog = useCallback((mode: "export" | "save") => {
|
|
@@ -1122,16 +1138,18 @@ export function ReactViewPanel({
|
|
|
1122
1138
|
const handleBlueprintMetaConfirm = useCallback(
|
|
1123
1139
|
async (meta: BlueprintMetaDraft) => {
|
|
1124
1140
|
if (blueprintMetaDialogMode === "export") {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1141
|
+
await runBusyTask(t("common.exportingPanel"), async () => {
|
|
1142
|
+
setBlueprintMeta(meta);
|
|
1143
|
+
downloadBlueprintExport(
|
|
1144
|
+
buildBlueprintExportPayload(blueprintGraph.document, meta)
|
|
1145
|
+
);
|
|
1146
|
+
toast({ title: t("panel.messages.blueprintExported") });
|
|
1147
|
+
});
|
|
1130
1148
|
return;
|
|
1131
1149
|
}
|
|
1132
1150
|
await saveBlueprintToLibrary(meta);
|
|
1133
1151
|
},
|
|
1134
|
-
[blueprintGraph.document, blueprintMetaDialogMode, saveBlueprintToLibrary]
|
|
1152
|
+
[blueprintGraph.document, blueprintMetaDialogMode, saveBlueprintToLibrary, t]
|
|
1135
1153
|
);
|
|
1136
1154
|
|
|
1137
1155
|
const handleRenameBlueprintLibraryItem = useCallback(
|
|
@@ -1170,22 +1188,32 @@ export function ReactViewPanel({
|
|
|
1170
1188
|
|
|
1171
1189
|
const handleBlueprintImportFile = useCallback(
|
|
1172
1190
|
async (file: File) => {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1191
|
+
try {
|
|
1192
|
+
await runBusyTask(t("common.importingBlueprint"), async () => {
|
|
1193
|
+
assertFileSize(file, "json");
|
|
1194
|
+
const text = await file.text();
|
|
1195
|
+
const payload = parseBlueprintImportFile(await parseJsonText(text));
|
|
1196
|
+
const record = libraryRecordFromImport(payload);
|
|
1197
|
+
await putBlueprintLibraryRecord(record);
|
|
1198
|
+
await refreshBlueprintLibrary();
|
|
1199
|
+
if (!activeBlueprintLibraryId) {
|
|
1200
|
+
snapshotWorkspaceBlueprint();
|
|
1201
|
+
}
|
|
1202
|
+
await loadBlueprintFromLibrary(record.id);
|
|
1203
|
+
toast({ title: t("panel.messages.blueprintImported") });
|
|
1204
|
+
});
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
toast({
|
|
1207
|
+
title: error instanceof Error ? error.message : t("panel.messages.importInvalidFormat"),
|
|
1208
|
+
});
|
|
1180
1209
|
}
|
|
1181
|
-
await loadBlueprintFromLibrary(record.id);
|
|
1182
|
-
toast({ title: t("panel.messages.blueprintImported") });
|
|
1183
1210
|
},
|
|
1184
1211
|
[
|
|
1185
1212
|
activeBlueprintLibraryId,
|
|
1186
1213
|
loadBlueprintFromLibrary,
|
|
1187
1214
|
refreshBlueprintLibrary,
|
|
1188
1215
|
snapshotWorkspaceBlueprint,
|
|
1216
|
+
t,
|
|
1189
1217
|
]
|
|
1190
1218
|
);
|
|
1191
1219
|
|
|
@@ -1227,7 +1255,7 @@ export function ReactViewPanel({
|
|
|
1227
1255
|
onProjectApplied: handleWorkspaceProjectApplied,
|
|
1228
1256
|
});
|
|
1229
1257
|
|
|
1230
|
-
const { triggerBlueprintNode } = useBlueprintPageLifecycle({
|
|
1258
|
+
const { triggerBlueprintNode, emitViewEvent, firedLifecyclePhases } = useBlueprintPageLifecycle({
|
|
1231
1259
|
graph: blueprintGraph,
|
|
1232
1260
|
active: blueprintOpen,
|
|
1233
1261
|
bootKey: workspaceProjects.activeProjectId ?? undefined,
|
|
@@ -1239,6 +1267,24 @@ export function ReactViewPanel({
|
|
|
1239
1267
|
onViewScopeUpdate: handleViewScopeUpdate,
|
|
1240
1268
|
});
|
|
1241
1269
|
|
|
1270
|
+
const boundViewEventTypes = useMemo(
|
|
1271
|
+
() =>
|
|
1272
|
+
collectArmedViewEventBindings(
|
|
1273
|
+
blueprintGraph.document.nodes.map((node) => ({
|
|
1274
|
+
id: node.id,
|
|
1275
|
+
nodeType: resolveRunnableNodeType(node),
|
|
1276
|
+
lifecyclePhase: node.lifecyclePhase,
|
|
1277
|
+
viewElementIds: resolveViewElementIds(node),
|
|
1278
|
+
eventConfig: node.eventConfig,
|
|
1279
|
+
})),
|
|
1280
|
+
blueprintGraph.document.edges,
|
|
1281
|
+
EVENT_NODE_TYPE,
|
|
1282
|
+
LIFECYCLE_NODE_TYPE,
|
|
1283
|
+
firedLifecyclePhases
|
|
1284
|
+
),
|
|
1285
|
+
[blueprintGraph.document.nodes, blueprintGraph.document.edges, firedLifecyclePhases]
|
|
1286
|
+
);
|
|
1287
|
+
|
|
1242
1288
|
const handleWorkspaceCreateProject = useCallback(async () => {
|
|
1243
1289
|
const result = await workspaceProjects.handleCreateProject();
|
|
1244
1290
|
if (result?.name) {
|
|
@@ -1250,6 +1296,7 @@ export function ReactViewPanel({
|
|
|
1250
1296
|
const handleWorkspaceOpenProject = useCallback(
|
|
1251
1297
|
async (id: string) => {
|
|
1252
1298
|
try {
|
|
1299
|
+
stopAllClockSchedules();
|
|
1253
1300
|
clearViewElementScopes();
|
|
1254
1301
|
await workspaceProjects.handleOpenProject(id);
|
|
1255
1302
|
setSelectedIds([]);
|
|
@@ -1563,70 +1610,72 @@ export function ReactViewPanel({
|
|
|
1563
1610
|
);
|
|
1564
1611
|
|
|
1565
1612
|
const handleExport = useCallback(() => {
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1613
|
+
void runBusyTask(t("common.exportingPanel"), async () => {
|
|
1614
|
+
const data = exportPanelData();
|
|
1615
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
|
1616
|
+
type: "application/json",
|
|
1617
|
+
});
|
|
1618
|
+
const url = URL.createObjectURL(blob);
|
|
1619
|
+
const a = document.createElement("a");
|
|
1620
|
+
a.href = url;
|
|
1621
|
+
const safeName = (productName.trim() || "panel")
|
|
1622
|
+
.replace(/[\\/:*?"<>|]/g, "-")
|
|
1623
|
+
.replace(/\s+/g, "-");
|
|
1624
|
+
a.download = `${safeName}-${Date.now()}.json`;
|
|
1625
|
+
a.click();
|
|
1626
|
+
URL.revokeObjectURL(url);
|
|
1569
1627
|
});
|
|
1570
|
-
|
|
1571
|
-
const a = document.createElement("a");
|
|
1572
|
-
a.href = url;
|
|
1573
|
-
const safeName = (productName.trim() || "panel")
|
|
1574
|
-
.replace(/[\\/:*?"<>|]/g, "-")
|
|
1575
|
-
.replace(/\s+/g, "-");
|
|
1576
|
-
a.download = `${safeName}-${Date.now()}.json`;
|
|
1577
|
-
a.click();
|
|
1578
|
-
URL.revokeObjectURL(url);
|
|
1579
|
-
}, [exportPanelData, productName]);
|
|
1628
|
+
}, [exportPanelData, productName, t]);
|
|
1580
1629
|
|
|
1581
1630
|
const handleImportFile = useCallback(
|
|
1582
1631
|
async (file: File) => {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1632
|
+
try {
|
|
1633
|
+
await runBusyTask(t("common.importingPanel"), async () => {
|
|
1634
|
+
assertFileSize(file, "json");
|
|
1635
|
+
const text = await file.text();
|
|
1636
|
+
const parsed = await parseJsonText<State>(text);
|
|
1637
|
+
const ok = importPanelData(parsed);
|
|
1638
|
+
if (!ok) {
|
|
1639
|
+
window.alert(messages.importInvalidFormat);
|
|
1640
|
+
} else {
|
|
1641
|
+
setSelectedIds([]);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
} catch (error) {
|
|
1645
|
+
window.alert(
|
|
1646
|
+
error instanceof Error ? error.message : messages.importInvalidFormat
|
|
1647
|
+
);
|
|
1590
1648
|
}
|
|
1591
1649
|
},
|
|
1592
|
-
[importPanelData]
|
|
1650
|
+
[importPanelData, t]
|
|
1593
1651
|
);
|
|
1594
1652
|
|
|
1595
|
-
const normalizeTitleIconFile = useCallback((file: File) => {
|
|
1653
|
+
const normalizeTitleIconFile = useCallback(async (file: File) => {
|
|
1654
|
+
const src = await readFileAsDataUrl(file, "read-failed", "image");
|
|
1596
1655
|
return new Promise<string>((resolve, reject) => {
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
const
|
|
1601
|
-
|
|
1602
|
-
|
|
1656
|
+
const img = new Image();
|
|
1657
|
+
img.onload = () => {
|
|
1658
|
+
const size = 64;
|
|
1659
|
+
const canvas = document.createElement("canvas");
|
|
1660
|
+
canvas.width = size;
|
|
1661
|
+
canvas.height = size;
|
|
1662
|
+
const ctx = canvas.getContext("2d");
|
|
1663
|
+
if (!ctx) {
|
|
1664
|
+
reject(new Error("canvas-context-failed"));
|
|
1603
1665
|
return;
|
|
1604
1666
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
return;
|
|
1615
|
-
}
|
|
1616
|
-
ctx.clearRect(0, 0, size, size);
|
|
1617
|
-
// 保持长宽比居中绘制,统一导出 PNG,提升 favicon 兼容性
|
|
1618
|
-
const scale = Math.min(size / img.width, size / img.height);
|
|
1619
|
-
const drawW = img.width * scale;
|
|
1620
|
-
const drawH = img.height * scale;
|
|
1621
|
-
const dx = (size - drawW) / 2;
|
|
1622
|
-
const dy = (size - drawH) / 2;
|
|
1623
|
-
ctx.drawImage(img, dx, dy, drawW, drawH);
|
|
1624
|
-
resolve(canvas.toDataURL("image/png"));
|
|
1625
|
-
};
|
|
1626
|
-
img.onerror = () => reject(new Error("image-decode-failed"));
|
|
1627
|
-
img.src = src;
|
|
1667
|
+
ctx.clearRect(0, 0, size, size);
|
|
1668
|
+
const scale = Math.min(size / img.width, size / img.height);
|
|
1669
|
+
const drawW = img.width * scale;
|
|
1670
|
+
const drawH = img.height * scale;
|
|
1671
|
+
const dx = (size - drawW) / 2;
|
|
1672
|
+
const dy = (size - drawH) / 2;
|
|
1673
|
+
ctx.drawImage(img, dx, dy, drawW, drawH);
|
|
1674
|
+
img.src = "";
|
|
1675
|
+
resolve(canvas.toDataURL("image/png"));
|
|
1628
1676
|
};
|
|
1629
|
-
|
|
1677
|
+
img.onerror = () => reject(new Error("image-decode-failed"));
|
|
1678
|
+
img.src = src;
|
|
1630
1679
|
});
|
|
1631
1680
|
}, []);
|
|
1632
1681
|
|
|
@@ -1935,6 +1984,7 @@ export function ReactViewPanel({
|
|
|
1935
1984
|
].join(" ")}
|
|
1936
1985
|
style={{ ["--panel-font-px" as string]: `${panelFontPx}px` }}
|
|
1937
1986
|
>
|
|
1987
|
+
<BusyOverlay />
|
|
1938
1988
|
<TooltipProvider delayDuration={120}>
|
|
1939
1989
|
<Tooltip open={ellipsisTooltip.open}>
|
|
1940
1990
|
<TooltipTrigger asChild>
|
|
@@ -1953,6 +2003,11 @@ export function ReactViewPanel({
|
|
|
1953
2003
|
.panel-font-root :is(span, label, p, strong, button, input, textarea, select, option, a, li) {
|
|
1954
2004
|
font-size: var(--panel-font-px) !important;
|
|
1955
2005
|
}
|
|
2006
|
+
/* 画布节点、富文本内容使用各自的字号,不被编辑器 UI 字号强制覆盖 */
|
|
2007
|
+
.panel-font-root [data-workspace-region="view"] :is(span, label, p, strong, button, input, textarea, select, option, a, li),
|
|
2008
|
+
.panel-font-root [data-panel-user-text] :is(span, label, p, strong, button, input, textarea, select, option, a, li) {
|
|
2009
|
+
font-size: inherit !important;
|
|
2010
|
+
}
|
|
1956
2011
|
.panel-font-root :is(input, textarea):focus,
|
|
1957
2012
|
.panel-font-root :is(input, textarea):focus-visible {
|
|
1958
2013
|
outline: none !important;
|
|
@@ -2069,6 +2124,7 @@ export function ReactViewPanel({
|
|
|
2069
2124
|
activeProjectId={workspaceProjects.activeProjectId}
|
|
2070
2125
|
activeProjectName={workspaceProjects.activeProjectName}
|
|
2071
2126
|
dirty={workspaceProjects.dirty}
|
|
2127
|
+
previewingProjectIds={workspaceProjects.previewingProjectIds}
|
|
2072
2128
|
onCreateProject={handleWorkspaceCreateProject}
|
|
2073
2129
|
onOpenProject={handleWorkspaceOpenProject}
|
|
2074
2130
|
onSyncProject={handleWorkspaceSyncProject}
|
|
@@ -2558,8 +2614,10 @@ export function ReactViewPanel({
|
|
|
2558
2614
|
try {
|
|
2559
2615
|
const normalizedDataUrl = await normalizeTitleIconFile(file);
|
|
2560
2616
|
setTitleIconDataUrl(normalizedDataUrl);
|
|
2561
|
-
} catch {
|
|
2562
|
-
window.alert(
|
|
2617
|
+
} catch (error) {
|
|
2618
|
+
window.alert(
|
|
2619
|
+
error instanceof Error ? error.message : messages.iconReadFailed
|
|
2620
|
+
);
|
|
2563
2621
|
}
|
|
2564
2622
|
}}
|
|
2565
2623
|
/>
|
|
@@ -2692,6 +2750,10 @@ export function ReactViewPanel({
|
|
|
2692
2750
|
onTableCellAction={(payload) => {
|
|
2693
2751
|
void triggerBlueprintNode(payload.blueprintNodeId, payload);
|
|
2694
2752
|
}}
|
|
2753
|
+
boundViewEventTypes={boundViewEventTypes}
|
|
2754
|
+
onViewUiEvent={(payload) => {
|
|
2755
|
+
void emitViewEvent(payload);
|
|
2756
|
+
}}
|
|
2695
2757
|
/>
|
|
2696
2758
|
|
|
2697
2759
|
<SelectLayer
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createPortal } from "react-dom";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
+
import { useBusyOverlayMessage } from "../utils/async-work";
|
|
4
|
+
|
|
5
|
+
export function BusyOverlay() {
|
|
6
|
+
const { t } = useI18n();
|
|
7
|
+
const message = useBusyOverlayMessage();
|
|
8
|
+
if (!message || typeof document === "undefined") return null;
|
|
9
|
+
|
|
10
|
+
return createPortal(
|
|
11
|
+
<div
|
|
12
|
+
className="fixed inset-0 flex flex-col items-center justify-center gap-3 bg-black/45 px-6"
|
|
13
|
+
style={{ zIndex: 40000 }}
|
|
14
|
+
role="status"
|
|
15
|
+
aria-live="polite"
|
|
16
|
+
aria-busy="true"
|
|
17
|
+
>
|
|
18
|
+
<div className="h-9 w-9 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
|
19
|
+
<div className="max-w-sm text-center text-sm text-white">
|
|
20
|
+
{message || t("common.loading")}
|
|
21
|
+
</div>
|
|
22
|
+
</div>,
|
|
23
|
+
document.body
|
|
24
|
+
);
|
|
25
|
+
}
|