@djvlc/runtime-host-react 1.1.1 → 1.1.3
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/index.cjs +570 -73
- package/dist/index.d.cts +188 -11
- package/dist/index.d.ts +188 -11
- package/dist/index.js +566 -76
- package/package.json +4 -5
package/dist/index.cjs
CHANGED
|
@@ -25,21 +25,36 @@ __export(index_exports, {
|
|
|
25
25
|
RuntimeContext: () => RuntimeContext,
|
|
26
26
|
RuntimeProvider: () => RuntimeProvider,
|
|
27
27
|
useAction: () => useAction,
|
|
28
|
+
useAsync: () => useAsync,
|
|
29
|
+
useComponentState: () => useComponentState,
|
|
28
30
|
useDJVRuntime: () => useDJVRuntime,
|
|
29
31
|
useData: () => useData,
|
|
32
|
+
useDebouncedAction: () => useDebouncedAction,
|
|
30
33
|
useHostApi: () => useHostApi,
|
|
34
|
+
useLifecycle: () => useLifecycle,
|
|
35
|
+
usePageInfo: () => usePageInfo,
|
|
36
|
+
usePrevious: () => usePrevious,
|
|
31
37
|
useQuery: () => useQuery,
|
|
32
38
|
useReactRuntime: () => useReactRuntime,
|
|
33
39
|
useRuntimeContext: () => useRuntimeContext,
|
|
34
40
|
useRuntimeEvent: () => useRuntimeEvent,
|
|
35
41
|
useRuntimeState: () => useRuntimeState,
|
|
36
|
-
useRuntimeStateWritable: () => useRuntimeStateWritable
|
|
42
|
+
useRuntimeStateWritable: () => useRuntimeStateWritable,
|
|
43
|
+
useWhen: () => useWhen
|
|
37
44
|
});
|
|
38
45
|
module.exports = __toCommonJS(index_exports);
|
|
39
46
|
|
|
40
47
|
// src/react-runtime.ts
|
|
41
48
|
var import_react = require("react");
|
|
42
49
|
var import_runtime_core = require("@djvlc/runtime-core");
|
|
50
|
+
var defaultMetrics = {
|
|
51
|
+
initTime: 0,
|
|
52
|
+
loadTime: 0,
|
|
53
|
+
renderTime: 0,
|
|
54
|
+
totalTime: 0,
|
|
55
|
+
initTimestamp: null,
|
|
56
|
+
readyTimestamp: null
|
|
57
|
+
};
|
|
43
58
|
function useReactRuntime(options) {
|
|
44
59
|
const [runtime, setRuntime] = (0, import_react.useState)(null);
|
|
45
60
|
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
@@ -47,12 +62,29 @@ function useReactRuntime(options) {
|
|
|
47
62
|
const [page, setPage] = (0, import_react.useState)(null);
|
|
48
63
|
const [error, setError] = (0, import_react.useState)(null);
|
|
49
64
|
const [hostApi, setHostApi] = (0, import_react.useState)(null);
|
|
65
|
+
const [metrics, setMetrics] = (0, import_react.useState)(defaultMetrics);
|
|
50
66
|
const runtimeRef = (0, import_react.useRef)(null);
|
|
67
|
+
const timingRef = (0, import_react.useRef)({
|
|
68
|
+
startTime: 0,
|
|
69
|
+
initStartTime: 0,
|
|
70
|
+
loadStartTime: 0,
|
|
71
|
+
renderStartTime: 0
|
|
72
|
+
});
|
|
73
|
+
const isReady = (0, import_react.useMemo)(() => phase === "ready", [phase]);
|
|
74
|
+
const hasError = (0, import_react.useMemo)(() => phase === "error" || error !== null, [phase, error]);
|
|
51
75
|
const init = (0, import_react.useCallback)(async () => {
|
|
52
76
|
const container = options.containerRef?.current;
|
|
53
77
|
if (!container) {
|
|
54
78
|
throw new Error("Container element not found");
|
|
55
79
|
}
|
|
80
|
+
timingRef.current.startTime = performance.now();
|
|
81
|
+
timingRef.current.initStartTime = timingRef.current.startTime;
|
|
82
|
+
if (options.enableMetrics) {
|
|
83
|
+
setMetrics((prev) => ({
|
|
84
|
+
...prev,
|
|
85
|
+
initTimestamp: Date.now()
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
56
88
|
const runtimeInstance = (0, import_runtime_core.createRuntime)({
|
|
57
89
|
...options,
|
|
58
90
|
container,
|
|
@@ -78,23 +110,46 @@ function useReactRuntime(options) {
|
|
|
78
110
|
});
|
|
79
111
|
await runtimeInstance.init();
|
|
80
112
|
setHostApi(runtimeInstance.getHostApi());
|
|
113
|
+
if (options.enableMetrics) {
|
|
114
|
+
setMetrics((prev) => ({
|
|
115
|
+
...prev,
|
|
116
|
+
initTime: performance.now() - timingRef.current.initStartTime
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
81
119
|
}, [options]);
|
|
82
120
|
const load = (0, import_react.useCallback)(async () => {
|
|
83
121
|
if (!runtimeRef.current) {
|
|
84
122
|
throw new Error("Runtime not initialized");
|
|
85
123
|
}
|
|
124
|
+
timingRef.current.loadStartTime = performance.now();
|
|
86
125
|
const result = await runtimeRef.current.load();
|
|
87
126
|
setPage(result);
|
|
88
127
|
setHostApi(runtimeRef.current.getHostApi());
|
|
128
|
+
if (options.enableMetrics) {
|
|
129
|
+
setMetrics((prev) => ({
|
|
130
|
+
...prev,
|
|
131
|
+
loadTime: performance.now() - timingRef.current.loadStartTime
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
89
134
|
return result;
|
|
90
|
-
}, []);
|
|
135
|
+
}, [options.enableMetrics]);
|
|
91
136
|
const render = (0, import_react.useCallback)(async () => {
|
|
92
137
|
if (!runtimeRef.current) {
|
|
93
138
|
throw new Error("Runtime not initialized");
|
|
94
139
|
}
|
|
140
|
+
timingRef.current.renderStartTime = performance.now();
|
|
95
141
|
await runtimeRef.current.render();
|
|
96
142
|
setLoading(false);
|
|
97
|
-
|
|
143
|
+
if (options.enableMetrics) {
|
|
144
|
+
const now = performance.now();
|
|
145
|
+
setMetrics((prev) => ({
|
|
146
|
+
...prev,
|
|
147
|
+
renderTime: now - timingRef.current.renderStartTime,
|
|
148
|
+
totalTime: now - timingRef.current.startTime,
|
|
149
|
+
readyTimestamp: Date.now()
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
}, [options.enableMetrics]);
|
|
98
153
|
const destroy = (0, import_react.useCallback)(() => {
|
|
99
154
|
runtimeRef.current?.destroy();
|
|
100
155
|
runtimeRef.current = null;
|
|
@@ -104,13 +159,43 @@ function useReactRuntime(options) {
|
|
|
104
159
|
setError(null);
|
|
105
160
|
setPhase("idle");
|
|
106
161
|
setLoading(true);
|
|
162
|
+
setMetrics(defaultMetrics);
|
|
107
163
|
}, []);
|
|
164
|
+
const reload = (0, import_react.useCallback)(async () => {
|
|
165
|
+
if (!runtimeRef.current) {
|
|
166
|
+
throw new Error("Runtime not initialized");
|
|
167
|
+
}
|
|
168
|
+
setError(null);
|
|
169
|
+
setLoading(true);
|
|
170
|
+
await load();
|
|
171
|
+
await render();
|
|
172
|
+
}, [load, render]);
|
|
108
173
|
const setVariable = (0, import_react.useCallback)((key, value) => {
|
|
109
174
|
runtimeRef.current?.setVariable(key, value);
|
|
110
175
|
}, []);
|
|
176
|
+
const setVariables = (0, import_react.useCallback)((variables) => {
|
|
177
|
+
if (!runtimeRef.current) return;
|
|
178
|
+
Object.entries(variables).forEach(([key, value]) => {
|
|
179
|
+
runtimeRef.current?.setVariable(key, value);
|
|
180
|
+
});
|
|
181
|
+
}, []);
|
|
182
|
+
const getVariable = (0, import_react.useCallback)((key) => {
|
|
183
|
+
return runtimeRef.current?.getState().variables[key];
|
|
184
|
+
}, []);
|
|
111
185
|
const refreshData = (0, import_react.useCallback)(async (queryId) => {
|
|
112
186
|
await runtimeRef.current?.refreshData(queryId);
|
|
113
187
|
}, []);
|
|
188
|
+
const executeAction = (0, import_react.useCallback)(async (actionType, params) => {
|
|
189
|
+
const api = hostApi;
|
|
190
|
+
if (!api) {
|
|
191
|
+
throw new Error("HostAPI not available");
|
|
192
|
+
}
|
|
193
|
+
const response = await api.executeAction(actionType, params || {});
|
|
194
|
+
if (response.success) {
|
|
195
|
+
return response.data;
|
|
196
|
+
}
|
|
197
|
+
throw new Error(response.errorMessage || "Action failed");
|
|
198
|
+
}, [hostApi]);
|
|
114
199
|
return {
|
|
115
200
|
runtime,
|
|
116
201
|
loading,
|
|
@@ -118,12 +203,19 @@ function useReactRuntime(options) {
|
|
|
118
203
|
page,
|
|
119
204
|
error,
|
|
120
205
|
hostApi,
|
|
206
|
+
isReady,
|
|
207
|
+
hasError,
|
|
208
|
+
metrics,
|
|
121
209
|
init,
|
|
122
210
|
load,
|
|
123
211
|
render,
|
|
124
212
|
destroy,
|
|
213
|
+
reload,
|
|
125
214
|
setVariable,
|
|
126
|
-
|
|
215
|
+
setVariables,
|
|
216
|
+
getVariable,
|
|
217
|
+
refreshData,
|
|
218
|
+
executeAction
|
|
127
219
|
};
|
|
128
220
|
}
|
|
129
221
|
|
|
@@ -179,13 +271,20 @@ function DJVRenderer({
|
|
|
179
271
|
onLoad,
|
|
180
272
|
onError,
|
|
181
273
|
onReady,
|
|
274
|
+
onPhaseChange,
|
|
182
275
|
loadingComponent,
|
|
183
276
|
errorComponent,
|
|
277
|
+
emptyComponent,
|
|
184
278
|
children,
|
|
185
279
|
className,
|
|
186
|
-
style
|
|
280
|
+
style,
|
|
281
|
+
retryCount = 3,
|
|
282
|
+
retryDelay = 1e3,
|
|
283
|
+
timeout = 3e4
|
|
187
284
|
}) {
|
|
188
285
|
const containerRef = (0, import_react3.useRef)(null);
|
|
286
|
+
const mountedRef = (0, import_react3.useRef)(true);
|
|
287
|
+
const retryCountRef = (0, import_react3.useRef)(0);
|
|
189
288
|
const [state, setState] = (0, import_react3.useState)({
|
|
190
289
|
phase: "idle",
|
|
191
290
|
page: null,
|
|
@@ -214,8 +313,8 @@ function DJVRenderer({
|
|
|
214
313
|
const {
|
|
215
314
|
runtime,
|
|
216
315
|
loading,
|
|
217
|
-
phase
|
|
218
|
-
page
|
|
316
|
+
phase,
|
|
317
|
+
page,
|
|
219
318
|
error,
|
|
220
319
|
hostApi,
|
|
221
320
|
init,
|
|
@@ -223,27 +322,61 @@ function DJVRenderer({
|
|
|
223
322
|
render,
|
|
224
323
|
destroy
|
|
225
324
|
} = useReactRuntime(options);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
325
|
+
(0, import_react3.useEffect)(() => {
|
|
326
|
+
onPhaseChange?.(phase);
|
|
327
|
+
}, [phase, onPhaseChange]);
|
|
328
|
+
const initWithRetry = (0, import_react3.useCallback)(async () => {
|
|
329
|
+
if (!containerRef.current || !mountedRef.current) return;
|
|
330
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
331
|
+
setTimeout(() => reject(new Error("\u52A0\u8F7D\u8D85\u65F6")), timeout);
|
|
332
|
+
});
|
|
230
333
|
try {
|
|
231
|
-
await
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
334
|
+
await Promise.race([
|
|
335
|
+
(async () => {
|
|
336
|
+
await init();
|
|
337
|
+
const pageData = await load();
|
|
338
|
+
if (mountedRef.current) {
|
|
339
|
+
onLoad?.(pageData);
|
|
340
|
+
}
|
|
341
|
+
await render();
|
|
342
|
+
if (mountedRef.current) {
|
|
343
|
+
onReady?.();
|
|
344
|
+
retryCountRef.current = 0;
|
|
345
|
+
if (runtime) {
|
|
346
|
+
runtime.onStateChange((newState) => {
|
|
347
|
+
if (mountedRef.current) {
|
|
348
|
+
setState(newState);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
})(),
|
|
354
|
+
timeoutPromise
|
|
355
|
+
]);
|
|
241
356
|
} catch (err) {
|
|
357
|
+
if (!mountedRef.current) return;
|
|
358
|
+
if (retryCountRef.current < retryCount) {
|
|
359
|
+
retryCountRef.current++;
|
|
360
|
+
if (debug) {
|
|
361
|
+
console.log(`[DJVRenderer] \u91CD\u8BD5 ${retryCountRef.current}/${retryCount}...`);
|
|
362
|
+
}
|
|
363
|
+
setTimeout(() => {
|
|
364
|
+
if (mountedRef.current) {
|
|
365
|
+
initWithRetry();
|
|
366
|
+
}
|
|
367
|
+
}, retryDelay);
|
|
368
|
+
}
|
|
242
369
|
}
|
|
243
|
-
}, [init, load, render, runtime, onLoad, onReady]);
|
|
370
|
+
}, [init, load, render, runtime, onLoad, onReady, timeout, retryCount, retryDelay, debug]);
|
|
371
|
+
const handleRetry = (0, import_react3.useCallback)(() => {
|
|
372
|
+
retryCountRef.current = 0;
|
|
373
|
+
initWithRetry();
|
|
374
|
+
}, [initWithRetry]);
|
|
244
375
|
(0, import_react3.useEffect)(() => {
|
|
245
|
-
|
|
376
|
+
mountedRef.current = true;
|
|
377
|
+
initWithRetry();
|
|
246
378
|
return () => {
|
|
379
|
+
mountedRef.current = false;
|
|
247
380
|
destroy();
|
|
248
381
|
};
|
|
249
382
|
}, [pageUid]);
|
|
@@ -251,18 +384,43 @@ function DJVRenderer({
|
|
|
251
384
|
if (!loading) return null;
|
|
252
385
|
return loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "djvlc-loading", children: [
|
|
253
386
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "djvlc-loading-spinner" }),
|
|
254
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u52A0\u8F7D\u4E2D..." })
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u52A0\u8F7D\u4E2D..." }),
|
|
388
|
+
retryCountRef.current > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "djvlc-loading-retry", children: [
|
|
389
|
+
"\u91CD\u8BD5 ",
|
|
390
|
+
retryCountRef.current,
|
|
391
|
+
"/",
|
|
392
|
+
retryCount
|
|
393
|
+
] })
|
|
255
394
|
] });
|
|
256
395
|
};
|
|
257
396
|
const renderError = () => {
|
|
258
397
|
if (!error) return null;
|
|
259
398
|
if (typeof errorComponent === "function") {
|
|
260
|
-
return errorComponent(error);
|
|
399
|
+
return errorComponent(error, handleRetry);
|
|
400
|
+
}
|
|
401
|
+
return errorComponent || /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "djvlc-error", children: [
|
|
402
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "djvlc-error-icon", children: "\u26A0\uFE0F" }),
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "djvlc-error-message", children: [
|
|
404
|
+
"\u52A0\u8F7D\u5931\u8D25\uFF1A",
|
|
405
|
+
error.message
|
|
406
|
+
] }),
|
|
407
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
408
|
+
"button",
|
|
409
|
+
{
|
|
410
|
+
className: "djvlc-error-retry-btn",
|
|
411
|
+
onClick: handleRetry,
|
|
412
|
+
type: "button",
|
|
413
|
+
children: "\u91CD\u8BD5"
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] });
|
|
417
|
+
};
|
|
418
|
+
const renderEmpty = () => {
|
|
419
|
+
if (loading || error || page) return null;
|
|
420
|
+
if (phase === "ready" && !page) {
|
|
421
|
+
return emptyComponent || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "djvlc-empty", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u6682\u65E0\u5185\u5BB9" }) });
|
|
261
422
|
}
|
|
262
|
-
return
|
|
263
|
-
"\u52A0\u8F7D\u5931\u8D25\uFF1A",
|
|
264
|
-
error.message
|
|
265
|
-
] }) });
|
|
423
|
+
return null;
|
|
266
424
|
};
|
|
267
425
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RuntimeProvider, { runtime, state, hostApi, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
268
426
|
"div",
|
|
@@ -270,9 +428,12 @@ function DJVRenderer({
|
|
|
270
428
|
ref: containerRef,
|
|
271
429
|
className: `djvlc-renderer ${className || ""}`,
|
|
272
430
|
style,
|
|
431
|
+
"data-phase": phase,
|
|
432
|
+
"data-page-uid": pageUid,
|
|
273
433
|
children: [
|
|
274
434
|
renderLoading(),
|
|
275
435
|
renderError(),
|
|
436
|
+
renderEmpty(),
|
|
276
437
|
children
|
|
277
438
|
]
|
|
278
439
|
}
|
|
@@ -282,32 +443,63 @@ function DJVRenderer({
|
|
|
282
443
|
// src/components/DJVProvider.tsx
|
|
283
444
|
var import_react4 = require("react");
|
|
284
445
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
446
|
+
var defaultState2 = {
|
|
447
|
+
phase: "idle",
|
|
448
|
+
page: null,
|
|
449
|
+
variables: {},
|
|
450
|
+
queries: {},
|
|
451
|
+
components: /* @__PURE__ */ new Map(),
|
|
452
|
+
error: null,
|
|
453
|
+
destroyed: false
|
|
454
|
+
};
|
|
285
455
|
function DJVProvider({
|
|
286
456
|
runtime,
|
|
287
457
|
hostApi,
|
|
288
|
-
children
|
|
458
|
+
children,
|
|
459
|
+
className,
|
|
460
|
+
debug = false,
|
|
461
|
+
onStateChange,
|
|
462
|
+
onPhaseChange,
|
|
463
|
+
onError
|
|
289
464
|
}) {
|
|
290
465
|
const [state, setState] = (0, import_react4.useState)(
|
|
291
|
-
runtime?.getState() ||
|
|
292
|
-
phase: "idle",
|
|
293
|
-
page: null,
|
|
294
|
-
variables: {},
|
|
295
|
-
queries: {},
|
|
296
|
-
components: /* @__PURE__ */ new Map(),
|
|
297
|
-
error: null,
|
|
298
|
-
destroyed: false
|
|
299
|
-
}
|
|
466
|
+
runtime?.getState() || defaultState2
|
|
300
467
|
);
|
|
468
|
+
const prevPhaseRef = (0, import_react4.useRef)(state.phase);
|
|
301
469
|
(0, import_react4.useEffect)(() => {
|
|
302
|
-
if (!runtime)
|
|
470
|
+
if (!runtime) {
|
|
471
|
+
setState(defaultState2);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
setState(runtime.getState());
|
|
303
475
|
const unsubscribe = runtime.onStateChange((newState) => {
|
|
304
476
|
setState(newState);
|
|
477
|
+
onStateChange?.(newState);
|
|
478
|
+
if (newState.phase !== prevPhaseRef.current) {
|
|
479
|
+
onPhaseChange?.(newState.phase);
|
|
480
|
+
if (debug) {
|
|
481
|
+
console.log(`[DJVProvider] Phase changed: ${prevPhaseRef.current} -> ${newState.phase}`);
|
|
482
|
+
}
|
|
483
|
+
prevPhaseRef.current = newState.phase;
|
|
484
|
+
}
|
|
485
|
+
if (newState.error) {
|
|
486
|
+
onError?.(newState.error);
|
|
487
|
+
}
|
|
305
488
|
});
|
|
306
489
|
return () => {
|
|
307
490
|
unsubscribe();
|
|
308
491
|
};
|
|
309
|
-
}, [runtime]);
|
|
310
|
-
|
|
492
|
+
}, [runtime, onStateChange, onPhaseChange, onError, debug]);
|
|
493
|
+
(0, import_react4.useEffect)(() => {
|
|
494
|
+
}, [hostApi]);
|
|
495
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RuntimeProvider, { runtime, state, hostApi, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
496
|
+
"div",
|
|
497
|
+
{
|
|
498
|
+
className: ["djvlc-provider", className].filter(Boolean).join(" "),
|
|
499
|
+
"data-phase": state.phase,
|
|
500
|
+
children
|
|
501
|
+
}
|
|
502
|
+
) });
|
|
311
503
|
}
|
|
312
504
|
|
|
313
505
|
// src/hooks/useDJVRuntime.ts
|
|
@@ -318,13 +510,25 @@ function useDJVRuntime() {
|
|
|
318
510
|
const phase = context.state.phase;
|
|
319
511
|
return phase !== "ready" && phase !== "error";
|
|
320
512
|
}, [context.state.phase]);
|
|
513
|
+
const isReady = context.state.phase === "ready";
|
|
514
|
+
const hasError = context.state.phase === "error" || context.state.error !== null;
|
|
515
|
+
const reload = (0, import_react5.useCallback)(async () => {
|
|
516
|
+
if (!context.runtime) {
|
|
517
|
+
throw new Error("Runtime not available");
|
|
518
|
+
}
|
|
519
|
+
await context.runtime.load();
|
|
520
|
+
await context.runtime.render();
|
|
521
|
+
}, [context.runtime]);
|
|
321
522
|
return {
|
|
322
523
|
runtime: context.runtime,
|
|
323
524
|
state: context.state,
|
|
324
525
|
loading,
|
|
325
526
|
phase: context.state.phase,
|
|
326
527
|
error: context.state.error,
|
|
327
|
-
page: context.state.page
|
|
528
|
+
page: context.state.page,
|
|
529
|
+
isReady,
|
|
530
|
+
hasError,
|
|
531
|
+
reload
|
|
328
532
|
};
|
|
329
533
|
}
|
|
330
534
|
function useHostApi() {
|
|
@@ -349,67 +553,143 @@ function useRuntimeStateWritable(key, defaultValue) {
|
|
|
349
553
|
);
|
|
350
554
|
return [value, setValue];
|
|
351
555
|
}
|
|
352
|
-
function useQuery(queryId) {
|
|
556
|
+
function useQuery(queryId, options) {
|
|
353
557
|
const context = useRuntimeContext();
|
|
354
558
|
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
355
559
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
560
|
+
const [lastUpdated, setLastUpdated] = (0, import_react5.useState)(null);
|
|
561
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
356
562
|
const data = context.state.queries[queryId];
|
|
357
563
|
const refetch = (0, import_react5.useCallback)(async () => {
|
|
564
|
+
if (!mountedRef.current) return;
|
|
358
565
|
setLoading(true);
|
|
359
566
|
setError(null);
|
|
360
567
|
try {
|
|
361
568
|
await context.runtime?.refreshData(queryId);
|
|
569
|
+
if (mountedRef.current) {
|
|
570
|
+
setLastUpdated(Date.now());
|
|
571
|
+
}
|
|
362
572
|
} catch (e) {
|
|
363
|
-
|
|
573
|
+
if (mountedRef.current) {
|
|
574
|
+
setError(e);
|
|
575
|
+
}
|
|
364
576
|
} finally {
|
|
365
|
-
|
|
577
|
+
if (mountedRef.current) {
|
|
578
|
+
setLoading(false);
|
|
579
|
+
}
|
|
366
580
|
}
|
|
367
581
|
}, [context.runtime, queryId]);
|
|
582
|
+
(0, import_react5.useEffect)(() => {
|
|
583
|
+
mountedRef.current = true;
|
|
584
|
+
if (options?.refreshOnMount && context.runtime) {
|
|
585
|
+
refetch();
|
|
586
|
+
}
|
|
587
|
+
return () => {
|
|
588
|
+
mountedRef.current = false;
|
|
589
|
+
};
|
|
590
|
+
}, []);
|
|
591
|
+
(0, import_react5.useEffect)(() => {
|
|
592
|
+
if (!options?.refreshInterval || options.refreshInterval <= 0) return;
|
|
593
|
+
const timer = setInterval(() => {
|
|
594
|
+
refetch();
|
|
595
|
+
}, options.refreshInterval);
|
|
596
|
+
return () => clearInterval(timer);
|
|
597
|
+
}, [options?.refreshInterval, refetch]);
|
|
598
|
+
(0, import_react5.useEffect)(() => {
|
|
599
|
+
if (!options?.refreshOnFocus) return;
|
|
600
|
+
const handleFocus = () => {
|
|
601
|
+
refetch();
|
|
602
|
+
};
|
|
603
|
+
window.addEventListener("focus", handleFocus);
|
|
604
|
+
return () => window.removeEventListener("focus", handleFocus);
|
|
605
|
+
}, [options?.refreshOnFocus, refetch]);
|
|
368
606
|
return {
|
|
369
607
|
data,
|
|
370
608
|
loading,
|
|
371
609
|
error,
|
|
372
|
-
refetch
|
|
610
|
+
refetch,
|
|
611
|
+
lastUpdated
|
|
373
612
|
};
|
|
374
613
|
}
|
|
375
|
-
function useAction(actionType) {
|
|
614
|
+
function useAction(actionType, options) {
|
|
376
615
|
const context = useRuntimeContext();
|
|
377
616
|
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
378
617
|
const [result, setResult] = (0, import_react5.useState)();
|
|
379
618
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
380
|
-
const
|
|
381
|
-
|
|
619
|
+
const [executionCount, setExecutionCount] = (0, import_react5.useState)(0);
|
|
620
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
621
|
+
(0, import_react5.useEffect)(() => {
|
|
622
|
+
mountedRef.current = true;
|
|
623
|
+
return () => {
|
|
624
|
+
mountedRef.current = false;
|
|
625
|
+
};
|
|
626
|
+
}, []);
|
|
627
|
+
const reset = (0, import_react5.useCallback)(() => {
|
|
628
|
+
setResult(void 0);
|
|
629
|
+
setError(null);
|
|
630
|
+
setExecutionCount(0);
|
|
631
|
+
}, []);
|
|
632
|
+
const executeWithRetry = (0, import_react5.useCallback)(
|
|
633
|
+
async (params, retriesLeft) => {
|
|
382
634
|
const hostApi = context.hostApi;
|
|
383
635
|
if (!hostApi) {
|
|
384
636
|
throw new Error("HostAPI not available");
|
|
385
637
|
}
|
|
386
|
-
setLoading(true);
|
|
387
|
-
setError(null);
|
|
388
638
|
try {
|
|
389
639
|
const response = await hostApi.executeAction(
|
|
390
640
|
actionType,
|
|
391
641
|
params
|
|
392
642
|
);
|
|
393
643
|
if (response.success) {
|
|
394
|
-
setResult(response.data);
|
|
395
644
|
return response.data;
|
|
396
645
|
} else {
|
|
397
|
-
throw new Error(response.
|
|
646
|
+
throw new Error(response.errorMessage || "Action failed");
|
|
398
647
|
}
|
|
399
648
|
} catch (e) {
|
|
400
|
-
|
|
649
|
+
if (retriesLeft > 0) {
|
|
650
|
+
await new Promise((resolve) => setTimeout(resolve, options?.retryDelay || 1e3));
|
|
651
|
+
return executeWithRetry(params, retriesLeft - 1);
|
|
652
|
+
}
|
|
653
|
+
throw e;
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
[context.hostApi, actionType, options?.retryDelay]
|
|
657
|
+
);
|
|
658
|
+
const execute = (0, import_react5.useCallback)(
|
|
659
|
+
async (params) => {
|
|
660
|
+
if (!mountedRef.current) return void 0;
|
|
661
|
+
setLoading(true);
|
|
662
|
+
setError(null);
|
|
663
|
+
setExecutionCount((c) => c + 1);
|
|
664
|
+
try {
|
|
665
|
+
const data = await executeWithRetry(params, options?.retryCount || 0);
|
|
666
|
+
if (mountedRef.current) {
|
|
667
|
+
setResult(data);
|
|
668
|
+
options?.onSuccess?.(data);
|
|
669
|
+
}
|
|
670
|
+
return data;
|
|
671
|
+
} catch (e) {
|
|
672
|
+
const err = e;
|
|
673
|
+
if (mountedRef.current) {
|
|
674
|
+
setError(err);
|
|
675
|
+
options?.onError?.(err);
|
|
676
|
+
}
|
|
401
677
|
throw e;
|
|
402
678
|
} finally {
|
|
403
|
-
|
|
679
|
+
if (mountedRef.current) {
|
|
680
|
+
setLoading(false);
|
|
681
|
+
}
|
|
404
682
|
}
|
|
405
683
|
},
|
|
406
|
-
[
|
|
684
|
+
[executeWithRetry, options]
|
|
407
685
|
);
|
|
408
686
|
return {
|
|
409
687
|
execute,
|
|
410
688
|
loading,
|
|
411
689
|
result,
|
|
412
|
-
error
|
|
690
|
+
error,
|
|
691
|
+
reset,
|
|
692
|
+
executionCount
|
|
413
693
|
};
|
|
414
694
|
}
|
|
415
695
|
function useData(queryId, params, options) {
|
|
@@ -417,42 +697,69 @@ function useData(queryId, params, options) {
|
|
|
417
697
|
const [data, setData] = (0, import_react5.useState)();
|
|
418
698
|
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
419
699
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
700
|
+
const [isFetched, setIsFetched] = (0, import_react5.useState)(false);
|
|
701
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
702
|
+
const paramsRef = (0, import_react5.useRef)(params);
|
|
703
|
+
(0, import_react5.useEffect)(() => {
|
|
704
|
+
mountedRef.current = true;
|
|
705
|
+
return () => {
|
|
706
|
+
mountedRef.current = false;
|
|
707
|
+
};
|
|
708
|
+
}, []);
|
|
420
709
|
const refetch = (0, import_react5.useCallback)(
|
|
421
710
|
async (newParams) => {
|
|
422
711
|
const hostApi = context.hostApi;
|
|
423
|
-
if (!hostApi)
|
|
424
|
-
throw new Error("HostAPI not available");
|
|
425
|
-
}
|
|
712
|
+
if (!hostApi || !mountedRef.current) return;
|
|
426
713
|
setLoading(true);
|
|
427
714
|
setError(null);
|
|
428
715
|
try {
|
|
429
|
-
const
|
|
716
|
+
const data2 = await hostApi.requestData(
|
|
430
717
|
queryId,
|
|
431
|
-
newParams ||
|
|
718
|
+
newParams || paramsRef.current
|
|
432
719
|
);
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
720
|
+
if (!mountedRef.current) return;
|
|
721
|
+
setData(data2);
|
|
722
|
+
setIsFetched(true);
|
|
723
|
+
options?.onSuccess?.(data2);
|
|
438
724
|
} catch (e) {
|
|
439
|
-
|
|
725
|
+
if (mountedRef.current) {
|
|
726
|
+
const err = e;
|
|
727
|
+
setError(err);
|
|
728
|
+
options?.onError?.(err);
|
|
729
|
+
}
|
|
440
730
|
} finally {
|
|
441
|
-
|
|
731
|
+
if (mountedRef.current) {
|
|
732
|
+
setLoading(false);
|
|
733
|
+
}
|
|
442
734
|
}
|
|
443
735
|
},
|
|
444
|
-
[context.hostApi, queryId,
|
|
736
|
+
[context.hostApi, queryId, options]
|
|
445
737
|
);
|
|
738
|
+
(0, import_react5.useEffect)(() => {
|
|
739
|
+
const prevParams = paramsRef.current;
|
|
740
|
+
paramsRef.current = params;
|
|
741
|
+
if (options?.refreshOnParamsChange !== false && context.hostApi && isFetched && JSON.stringify(prevParams) !== JSON.stringify(params)) {
|
|
742
|
+
refetch();
|
|
743
|
+
}
|
|
744
|
+
}, [params, context.hostApi, options?.refreshOnParamsChange, isFetched, refetch]);
|
|
446
745
|
(0, import_react5.useEffect)(() => {
|
|
447
746
|
if (options?.immediate !== false && context.hostApi) {
|
|
448
747
|
refetch();
|
|
449
748
|
}
|
|
450
749
|
}, [context.hostApi]);
|
|
750
|
+
(0, import_react5.useEffect)(() => {
|
|
751
|
+
if (!options?.refreshInterval || options.refreshInterval <= 0) return;
|
|
752
|
+
const timer = setInterval(() => {
|
|
753
|
+
refetch();
|
|
754
|
+
}, options.refreshInterval);
|
|
755
|
+
return () => clearInterval(timer);
|
|
756
|
+
}, [options?.refreshInterval, refetch]);
|
|
451
757
|
return {
|
|
452
758
|
data,
|
|
453
759
|
loading,
|
|
454
760
|
error,
|
|
455
|
-
refetch
|
|
761
|
+
refetch,
|
|
762
|
+
isFetched
|
|
456
763
|
};
|
|
457
764
|
}
|
|
458
765
|
function useRuntimeEvent(eventType, handler) {
|
|
@@ -466,6 +773,189 @@ function useRuntimeEvent(eventType, handler) {
|
|
|
466
773
|
};
|
|
467
774
|
}, [context.runtime, eventType, handler]);
|
|
468
775
|
}
|
|
776
|
+
function usePageInfo() {
|
|
777
|
+
const context = useRuntimeContext();
|
|
778
|
+
const page = context.state.page;
|
|
779
|
+
return (0, import_react5.useMemo)(() => ({
|
|
780
|
+
/** 页面 UID */
|
|
781
|
+
pageUid: page?.pageUid,
|
|
782
|
+
/** 页面版本 ID */
|
|
783
|
+
pageVersionId: page?.pageVersionId,
|
|
784
|
+
/** Schema 版本 */
|
|
785
|
+
schemaVersion: page?.pageJson?.schemaVersion,
|
|
786
|
+
/** 页面标题 */
|
|
787
|
+
title: page?.title,
|
|
788
|
+
/** 页面配置 */
|
|
789
|
+
config: page?.config,
|
|
790
|
+
/** 页面是否已加载 */
|
|
791
|
+
isLoaded: page !== null
|
|
792
|
+
}), [page]);
|
|
793
|
+
}
|
|
794
|
+
function useComponentState(componentId) {
|
|
795
|
+
const context = useRuntimeContext();
|
|
796
|
+
const componentStatus = context.state.components.get(componentId);
|
|
797
|
+
return (0, import_react5.useMemo)(() => ({
|
|
798
|
+
/** 组件是否已加载 */
|
|
799
|
+
isLoaded: componentStatus?.status === "loaded",
|
|
800
|
+
/** 组件是否加载中 */
|
|
801
|
+
isLoading: componentStatus?.status === "loading",
|
|
802
|
+
/** 组件是否加载失败 */
|
|
803
|
+
hasError: componentStatus?.status === "failed",
|
|
804
|
+
/** 加载耗时 */
|
|
805
|
+
loadTime: componentStatus?.loadTime,
|
|
806
|
+
/** 组件信息 */
|
|
807
|
+
info: componentStatus
|
|
808
|
+
}), [componentStatus]);
|
|
809
|
+
}
|
|
810
|
+
function useLifecycle(options) {
|
|
811
|
+
const context = useRuntimeContext();
|
|
812
|
+
const [hasMounted, setHasMounted] = (0, import_react5.useState)(false);
|
|
813
|
+
const [hasReady, setHasReady] = (0, import_react5.useState)(false);
|
|
814
|
+
const prevPhaseRef = (0, import_react5.useRef)(context.state.phase);
|
|
815
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
816
|
+
(0, import_react5.useEffect)(() => {
|
|
817
|
+
mountedRef.current = true;
|
|
818
|
+
return () => {
|
|
819
|
+
mountedRef.current = false;
|
|
820
|
+
};
|
|
821
|
+
}, []);
|
|
822
|
+
(0, import_react5.useEffect)(() => {
|
|
823
|
+
const currentPhase = context.state.phase;
|
|
824
|
+
if (currentPhase !== prevPhaseRef.current) {
|
|
825
|
+
options?.onPhaseChange?.(currentPhase);
|
|
826
|
+
prevPhaseRef.current = currentPhase;
|
|
827
|
+
}
|
|
828
|
+
if (!hasMounted && currentPhase !== "idle") {
|
|
829
|
+
setHasMounted(true);
|
|
830
|
+
Promise.resolve(options?.onMounted?.()).catch((error) => {
|
|
831
|
+
options?.onError?.(error);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (!hasReady && currentPhase === "ready") {
|
|
835
|
+
setHasReady(true);
|
|
836
|
+
Promise.resolve(options?.onReady?.()).catch((error) => {
|
|
837
|
+
options?.onError?.(error);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
if (currentPhase === "error" && context.state.error) {
|
|
841
|
+
options?.onError?.(context.state.error);
|
|
842
|
+
}
|
|
843
|
+
}, [context.state.phase, context.state.error, hasMounted, hasReady, options]);
|
|
844
|
+
return {
|
|
845
|
+
/** 当前阶段 */
|
|
846
|
+
phase: context.state.phase,
|
|
847
|
+
/** 是否已 mounted */
|
|
848
|
+
hasMounted,
|
|
849
|
+
/** 是否已 ready */
|
|
850
|
+
hasReady
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function useWhen(condition, callback, options) {
|
|
854
|
+
const [executed, setExecuted] = (0, import_react5.useState)(false);
|
|
855
|
+
const { once = true } = options || {};
|
|
856
|
+
(0, import_react5.useEffect)(() => {
|
|
857
|
+
if (condition && (!once || !executed)) {
|
|
858
|
+
setExecuted(true);
|
|
859
|
+
callback();
|
|
860
|
+
}
|
|
861
|
+
}, [condition, callback, once, executed]);
|
|
862
|
+
return {
|
|
863
|
+
/** 是否已执行 */
|
|
864
|
+
executed
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function useDebouncedAction(actionType, delay = 300) {
|
|
868
|
+
const { execute: rawExecute, loading, result, error } = useAction(actionType);
|
|
869
|
+
const timeoutRef = (0, import_react5.useRef)(null);
|
|
870
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
871
|
+
(0, import_react5.useEffect)(() => {
|
|
872
|
+
mountedRef.current = true;
|
|
873
|
+
return () => {
|
|
874
|
+
mountedRef.current = false;
|
|
875
|
+
if (timeoutRef.current) {
|
|
876
|
+
clearTimeout(timeoutRef.current);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
}, []);
|
|
880
|
+
const execute = (0, import_react5.useCallback)((params) => {
|
|
881
|
+
if (timeoutRef.current) {
|
|
882
|
+
clearTimeout(timeoutRef.current);
|
|
883
|
+
}
|
|
884
|
+
timeoutRef.current = setTimeout(() => {
|
|
885
|
+
if (mountedRef.current) {
|
|
886
|
+
rawExecute(params).catch(() => {
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}, delay);
|
|
890
|
+
}, [rawExecute, delay]);
|
|
891
|
+
const cancel = (0, import_react5.useCallback)(() => {
|
|
892
|
+
if (timeoutRef.current) {
|
|
893
|
+
clearTimeout(timeoutRef.current);
|
|
894
|
+
timeoutRef.current = null;
|
|
895
|
+
}
|
|
896
|
+
}, []);
|
|
897
|
+
return {
|
|
898
|
+
execute,
|
|
899
|
+
loading,
|
|
900
|
+
result,
|
|
901
|
+
error,
|
|
902
|
+
cancel
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function useAsync(asyncFn, deps = [], options) {
|
|
906
|
+
const [data, setData] = (0, import_react5.useState)();
|
|
907
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
908
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
909
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
910
|
+
(0, import_react5.useEffect)(() => {
|
|
911
|
+
mountedRef.current = true;
|
|
912
|
+
return () => {
|
|
913
|
+
mountedRef.current = false;
|
|
914
|
+
};
|
|
915
|
+
}, []);
|
|
916
|
+
const execute = (0, import_react5.useCallback)(async () => {
|
|
917
|
+
if (!mountedRef.current) return void 0;
|
|
918
|
+
setLoading(true);
|
|
919
|
+
setError(null);
|
|
920
|
+
try {
|
|
921
|
+
const result = await asyncFn();
|
|
922
|
+
if (mountedRef.current) {
|
|
923
|
+
setData(result);
|
|
924
|
+
options?.onSuccess?.(result);
|
|
925
|
+
}
|
|
926
|
+
return result;
|
|
927
|
+
} catch (e) {
|
|
928
|
+
const err = e;
|
|
929
|
+
if (mountedRef.current) {
|
|
930
|
+
setError(err);
|
|
931
|
+
options?.onError?.(err);
|
|
932
|
+
}
|
|
933
|
+
return void 0;
|
|
934
|
+
} finally {
|
|
935
|
+
if (mountedRef.current) {
|
|
936
|
+
setLoading(false);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}, [asyncFn, ...deps]);
|
|
940
|
+
(0, import_react5.useEffect)(() => {
|
|
941
|
+
if (options?.immediate !== false) {
|
|
942
|
+
execute();
|
|
943
|
+
}
|
|
944
|
+
}, []);
|
|
945
|
+
return {
|
|
946
|
+
data,
|
|
947
|
+
loading,
|
|
948
|
+
error,
|
|
949
|
+
execute
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function usePrevious(value) {
|
|
953
|
+
const ref = (0, import_react5.useRef)();
|
|
954
|
+
(0, import_react5.useEffect)(() => {
|
|
955
|
+
ref.current = value;
|
|
956
|
+
}, [value]);
|
|
957
|
+
return ref.current;
|
|
958
|
+
}
|
|
469
959
|
// Annotate the CommonJS export names for ESM import in node:
|
|
470
960
|
0 && (module.exports = {
|
|
471
961
|
DJVProvider,
|
|
@@ -473,13 +963,20 @@ function useRuntimeEvent(eventType, handler) {
|
|
|
473
963
|
RuntimeContext,
|
|
474
964
|
RuntimeProvider,
|
|
475
965
|
useAction,
|
|
966
|
+
useAsync,
|
|
967
|
+
useComponentState,
|
|
476
968
|
useDJVRuntime,
|
|
477
969
|
useData,
|
|
970
|
+
useDebouncedAction,
|
|
478
971
|
useHostApi,
|
|
972
|
+
useLifecycle,
|
|
973
|
+
usePageInfo,
|
|
974
|
+
usePrevious,
|
|
479
975
|
useQuery,
|
|
480
976
|
useReactRuntime,
|
|
481
977
|
useRuntimeContext,
|
|
482
978
|
useRuntimeEvent,
|
|
483
979
|
useRuntimeState,
|
|
484
|
-
useRuntimeStateWritable
|
|
980
|
+
useRuntimeStateWritable,
|
|
981
|
+
useWhen
|
|
485
982
|
});
|