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