@djvlc/runtime-host-react 1.1.3 → 1.1.4

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.js CHANGED
@@ -1,937 +1 @@
1
- // src/react-runtime.ts
2
- import { useState, useCallback, useRef, useMemo } from "react";
3
- import {
4
- createRuntime
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
- };
14
- function useReactRuntime(options) {
15
- const [runtime, setRuntime] = useState(null);
16
- const [loading, setLoading] = useState(true);
17
- const [phase, setPhase] = useState("idle");
18
- const [page, setPage] = useState(null);
19
- const [error, setError] = useState(null);
20
- const [hostApi, setHostApi] = useState(null);
21
- const [metrics, setMetrics] = useState(defaultMetrics);
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]);
31
- const init = useCallback(async () => {
32
- const container = options.containerRef?.current;
33
- if (!container) {
34
- throw new Error("Container element not found");
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
- }
44
- const runtimeInstance = createRuntime({
45
- ...options,
46
- container,
47
- onError: (err) => {
48
- setError(err);
49
- options.onError?.(err);
50
- },
51
- onEvent: (event) => {
52
- options.onEvent?.(event);
53
- }
54
- });
55
- runtimeRef.current = runtimeInstance;
56
- setRuntime(runtimeInstance);
57
- runtimeInstance.onStateChange((state) => {
58
- setPhase(state.phase);
59
- setLoading(state.phase !== "ready" && state.phase !== "error");
60
- if (state.page) {
61
- setPage(state.page);
62
- }
63
- if (state.error) {
64
- setError(state.error);
65
- }
66
- });
67
- await runtimeInstance.init();
68
- setHostApi(runtimeInstance.getHostApi());
69
- if (options.enableMetrics) {
70
- setMetrics((prev) => ({
71
- ...prev,
72
- initTime: performance.now() - timingRef.current.initStartTime
73
- }));
74
- }
75
- }, [options]);
76
- const load = useCallback(async () => {
77
- if (!runtimeRef.current) {
78
- throw new Error("Runtime not initialized");
79
- }
80
- timingRef.current.loadStartTime = performance.now();
81
- const result = await runtimeRef.current.load();
82
- setPage(result);
83
- setHostApi(runtimeRef.current.getHostApi());
84
- if (options.enableMetrics) {
85
- setMetrics((prev) => ({
86
- ...prev,
87
- loadTime: performance.now() - timingRef.current.loadStartTime
88
- }));
89
- }
90
- return result;
91
- }, [options.enableMetrics]);
92
- const render = useCallback(async () => {
93
- if (!runtimeRef.current) {
94
- throw new Error("Runtime not initialized");
95
- }
96
- timingRef.current.renderStartTime = performance.now();
97
- await runtimeRef.current.render();
98
- setLoading(false);
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]);
109
- const destroy = useCallback(() => {
110
- runtimeRef.current?.destroy();
111
- runtimeRef.current = null;
112
- setRuntime(null);
113
- setHostApi(null);
114
- setPage(null);
115
- setError(null);
116
- setPhase("idle");
117
- setLoading(true);
118
- setMetrics(defaultMetrics);
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]);
129
- const setVariable = useCallback((key, value) => {
130
- runtimeRef.current?.setVariable(key, value);
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
- }, []);
141
- const refreshData = useCallback(async (queryId) => {
142
- await runtimeRef.current?.refreshData(queryId);
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]);
155
- return {
156
- runtime,
157
- loading,
158
- phase,
159
- page,
160
- error,
161
- hostApi,
162
- isReady,
163
- hasError,
164
- metrics,
165
- init,
166
- load,
167
- render,
168
- destroy,
169
- reload,
170
- setVariable,
171
- setVariables,
172
- getVariable,
173
- refreshData,
174
- executeAction
175
- };
176
- }
177
-
178
- // src/context.tsx
179
- import { createContext, useContext } from "react";
180
- import { jsx } from "react/jsx-runtime";
181
- var defaultState = {
182
- phase: "idle",
183
- page: null,
184
- variables: {},
185
- queries: {},
186
- components: /* @__PURE__ */ new Map(),
187
- error: null,
188
- destroyed: false
189
- };
190
- var RuntimeContext = createContext({
191
- runtime: null,
192
- state: defaultState,
193
- hostApi: null
194
- });
195
- function RuntimeProvider({
196
- runtime,
197
- state,
198
- hostApi,
199
- children
200
- }) {
201
- const value = {
202
- runtime,
203
- state,
204
- hostApi
205
- };
206
- return /* @__PURE__ */ jsx(RuntimeContext.Provider, { value, children });
207
- }
208
- function useRuntimeContext() {
209
- const context = useContext(RuntimeContext);
210
- return context;
211
- }
212
-
213
- // src/components/DJVRenderer.tsx
214
- import { useRef as useRef2, useEffect, useState as useState2, useCallback as useCallback2 } from "react";
215
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
216
- function DJVRenderer({
217
- pageUid,
218
- apiBaseUrl,
219
- cdnBaseUrl,
220
- channel = "prod",
221
- userId,
222
- deviceId,
223
- authToken,
224
- previewToken,
225
- debug = false,
226
- enableSRI = true,
227
- onLoad,
228
- onError,
229
- onReady,
230
- onPhaseChange,
231
- loadingComponent,
232
- errorComponent,
233
- emptyComponent,
234
- children,
235
- className,
236
- style,
237
- retryCount = 3,
238
- retryDelay = 1e3,
239
- timeout = 3e4
240
- }) {
241
- const containerRef = useRef2(null);
242
- const mountedRef = useRef2(true);
243
- const retryCountRef = useRef2(0);
244
- const [state, setState] = useState2({
245
- phase: "idle",
246
- page: null,
247
- variables: {},
248
- queries: {},
249
- components: /* @__PURE__ */ new Map(),
250
- error: null,
251
- destroyed: false
252
- });
253
- const options = {
254
- pageUid,
255
- apiBaseUrl,
256
- cdnBaseUrl,
257
- channel,
258
- userId,
259
- deviceId,
260
- authToken,
261
- previewToken,
262
- debug,
263
- enableSRI,
264
- containerRef,
265
- onError: (error2) => {
266
- onError?.(error2);
267
- }
268
- };
269
- const {
270
- runtime,
271
- loading,
272
- phase,
273
- page,
274
- error,
275
- hostApi,
276
- init,
277
- load,
278
- render,
279
- destroy
280
- } = useReactRuntime(options);
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
- });
289
- try {
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
- ]);
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
- }
325
- }
326
- }, [init, load, render, runtime, onLoad, onReady, timeout, retryCount, retryDelay, debug]);
327
- const handleRetry = useCallback2(() => {
328
- retryCountRef.current = 0;
329
- initWithRetry();
330
- }, [initWithRetry]);
331
- useEffect(() => {
332
- mountedRef.current = true;
333
- initWithRetry();
334
- return () => {
335
- mountedRef.current = false;
336
- destroy();
337
- };
338
- }, [pageUid]);
339
- const renderLoading = () => {
340
- if (!loading) return null;
341
- return loadingComponent || /* @__PURE__ */ jsxs("div", { className: "djvlc-loading", children: [
342
- /* @__PURE__ */ jsx2("div", { className: "djvlc-loading-spinner" }),
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
- ] })
350
- ] });
351
- };
352
- const renderError = () => {
353
- if (!error) return null;
354
- if (typeof errorComponent === "function") {
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" }) });
378
- }
379
- return null;
380
- };
381
- return /* @__PURE__ */ jsx2(RuntimeProvider, { runtime, state, hostApi, children: /* @__PURE__ */ jsxs(
382
- "div",
383
- {
384
- ref: containerRef,
385
- className: `djvlc-renderer ${className || ""}`,
386
- style,
387
- "data-phase": phase,
388
- "data-page-uid": pageUid,
389
- children: [
390
- renderLoading(),
391
- renderError(),
392
- renderEmpty(),
393
- children
394
- ]
395
- }
396
- ) });
397
- }
398
-
399
- // src/components/DJVProvider.tsx
400
- import { useState as useState3, useEffect as useEffect2, useRef as useRef3 } from "react";
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
- };
411
- function DJVProvider({
412
- runtime,
413
- hostApi,
414
- children,
415
- className,
416
- debug = false,
417
- onStateChange,
418
- onPhaseChange,
419
- onError
420
- }) {
421
- const [state, setState] = useState3(
422
- runtime?.getState() || defaultState2
423
- );
424
- const prevPhaseRef = useRef3(state.phase);
425
- useEffect2(() => {
426
- if (!runtime) {
427
- setState(defaultState2);
428
- return;
429
- }
430
- setState(runtime.getState());
431
- const unsubscribe = runtime.onStateChange((newState) => {
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
- }
444
- });
445
- return () => {
446
- unsubscribe();
447
- };
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
- ) });
459
- }
460
-
461
- // src/hooks/useDJVRuntime.ts
462
- import { useCallback as useCallback3, useState as useState4, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef4 } from "react";
463
- function useDJVRuntime() {
464
- const context = useRuntimeContext();
465
- const loading = useMemo2(() => {
466
- const phase = context.state.phase;
467
- return phase !== "ready" && phase !== "error";
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]);
478
- return {
479
- runtime: context.runtime,
480
- state: context.state,
481
- loading,
482
- phase: context.state.phase,
483
- error: context.state.error,
484
- page: context.state.page,
485
- isReady,
486
- hasError,
487
- reload
488
- };
489
- }
490
- function useHostApi() {
491
- const context = useRuntimeContext();
492
- if (!context.hostApi) {
493
- throw new Error("HostAPI not available. Make sure runtime is initialized.");
494
- }
495
- return context.hostApi;
496
- }
497
- function useRuntimeState(key) {
498
- const context = useRuntimeContext();
499
- return context.state.variables[key];
500
- }
501
- function useRuntimeStateWritable(key, defaultValue) {
502
- const context = useRuntimeContext();
503
- const value = context.state.variables[key] ?? defaultValue;
504
- const setValue = useCallback3(
505
- (newValue) => {
506
- context.runtime?.setVariable(key, newValue);
507
- },
508
- [context.runtime, key]
509
- );
510
- return [value, setValue];
511
- }
512
- function useQuery(queryId, options) {
513
- const context = useRuntimeContext();
514
- const [loading, setLoading] = useState4(false);
515
- const [error, setError] = useState4(null);
516
- const [lastUpdated, setLastUpdated] = useState4(null);
517
- const mountedRef = useRef4(true);
518
- const data = context.state.queries[queryId];
519
- const refetch = useCallback3(async () => {
520
- if (!mountedRef.current) return;
521
- setLoading(true);
522
- setError(null);
523
- try {
524
- await context.runtime?.refreshData(queryId);
525
- if (mountedRef.current) {
526
- setLastUpdated(Date.now());
527
- }
528
- } catch (e) {
529
- if (mountedRef.current) {
530
- setError(e);
531
- }
532
- } finally {
533
- if (mountedRef.current) {
534
- setLoading(false);
535
- }
536
- }
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]);
562
- return {
563
- data,
564
- loading,
565
- error,
566
- refetch,
567
- lastUpdated
568
- };
569
- }
570
- function useAction(actionType, options) {
571
- const context = useRuntimeContext();
572
- const [loading, setLoading] = useState4(false);
573
- const [result, setResult] = useState4();
574
- const [error, setError] = useState4(null);
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) => {
590
- const hostApi = context.hostApi;
591
- if (!hostApi) {
592
- throw new Error("HostAPI not available");
593
- }
594
- try {
595
- const response = await hostApi.executeAction(
596
- actionType,
597
- params
598
- );
599
- if (response.success) {
600
- return response.data;
601
- } else {
602
- throw new Error(response.errorMessage || "Action failed");
603
- }
604
- } catch (e) {
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
- }
633
- throw e;
634
- } finally {
635
- if (mountedRef.current) {
636
- setLoading(false);
637
- }
638
- }
639
- },
640
- [executeWithRetry, options]
641
- );
642
- return {
643
- execute,
644
- loading,
645
- result,
646
- error,
647
- reset,
648
- executionCount
649
- };
650
- }
651
- function useData(queryId, params, options) {
652
- const context = useRuntimeContext();
653
- const [data, setData] = useState4();
654
- const [loading, setLoading] = useState4(false);
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
- }, []);
665
- const refetch = useCallback3(
666
- async (newParams) => {
667
- const hostApi = context.hostApi;
668
- if (!hostApi || !mountedRef.current) return;
669
- setLoading(true);
670
- setError(null);
671
- try {
672
- const data2 = await hostApi.requestData(
673
- queryId,
674
- newParams || paramsRef.current
675
- );
676
- if (!mountedRef.current) return;
677
- setData(data2);
678
- setIsFetched(true);
679
- options?.onSuccess?.(data2);
680
- } catch (e) {
681
- if (mountedRef.current) {
682
- const err = e;
683
- setError(err);
684
- options?.onError?.(err);
685
- }
686
- } finally {
687
- if (mountedRef.current) {
688
- setLoading(false);
689
- }
690
- }
691
- },
692
- [context.hostApi, queryId, options]
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]);
701
- useEffect3(() => {
702
- if (options?.immediate !== false && context.hostApi) {
703
- refetch();
704
- }
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]);
713
- return {
714
- data,
715
- loading,
716
- error,
717
- refetch,
718
- isFetched
719
- };
720
- }
721
- function useRuntimeEvent(eventType, handler) {
722
- const context = useRuntimeContext();
723
- useEffect3(() => {
724
- const unsubscribe = context.runtime?.on(eventType, (event) => {
725
- handler(event.data);
726
- });
727
- return () => {
728
- unsubscribe?.();
729
- };
730
- }, [context.runtime, eventType, handler]);
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
- }
915
- export {
916
- DJVProvider,
917
- DJVRenderer,
918
- RuntimeContext,
919
- RuntimeProvider,
920
- useAction,
921
- useAsync,
922
- useComponentState,
923
- useDJVRuntime,
924
- useData,
925
- useDebouncedAction,
926
- useHostApi,
927
- useLifecycle,
928
- usePageInfo,
929
- usePrevious,
930
- useQuery,
931
- useReactRuntime,
932
- useRuntimeContext,
933
- useRuntimeEvent,
934
- useRuntimeState,
935
- useRuntimeStateWritable,
936
- useWhen
937
- };
1
+ import{createContext as e,useState as r,useRef as n,useMemo as t,useCallback as a,useContext as o,useEffect as i}from"react";import{createRuntime as l}from"@djvlc/runtime-core";import{jsx as s,jsxs as c}from"react/jsx-runtime";var u={initTime:0,loadTime:0,renderTime:0,totalTime:0,initTimestamp:null,readyTimestamp:null};function d(e){const[o,i]=r(null),[s,c]=r(!0),[d,m]=r("idle"),[f,h]=r(null),[p,w]=r(null),[y,v]=r(null),[g,T]=r(u),b=n(null),E=n({startTime:0,initStartTime:0,loadStartTime:0,renderStartTime:0}),A=t(()=>"ready"===d,[d]),I=t(()=>"error"===d||null!==p,[d,p]),N=a(async()=>{const r=e.containerRef?.current;if(!r)throw new Error("Container element not found");E.current.startTime=performance.now(),E.current.initStartTime=E.current.startTime,e.enableMetrics&&T(e=>({...e,initTimestamp:Date.now()}));const n=l({...e,container:r,onError:r=>{w(r),e.onError?.(r)},onEvent:r=>{e.onEvent?.(r)}});b.current=n,i(n),n.onStateChange(e=>{m(e.phase),c("ready"!==e.phase&&"error"!==e.phase),e.page&&h(e.page),e.error&&w(e.error)}),await n.init(),v(n.getHostApi()),e.enableMetrics&&T(e=>({...e,initTime:performance.now()-E.current.initStartTime}))},[e]),j=a(async()=>{if(!b.current)throw new Error("Runtime not initialized");E.current.loadStartTime=performance.now();const r=await b.current.load();return h(r),v(b.current.getHostApi()),e.enableMetrics&&T(e=>({...e,loadTime:performance.now()-E.current.loadStartTime})),r},[e.enableMetrics]),R=a(async()=>{if(!b.current)throw new Error("Runtime not initialized");if(E.current.renderStartTime=performance.now(),await b.current.render(),c(!1),e.enableMetrics){const e=performance.now();T(r=>({...r,renderTime:e-E.current.renderStartTime,totalTime:e-E.current.startTime,readyTimestamp:Date.now()}))}},[e.enableMetrics]),C=a(()=>{b.current?.destroy(),b.current=null,i(null),v(null),h(null),w(null),m("idle"),c(!0),T(u)},[]),P=a(async()=>{if(!b.current)throw new Error("Runtime not initialized");w(null),c(!0),await j(),await R()},[j,R]),x=a((e,r)=>{b.current?.setVariable(e,r)},[]),S=a(e=>{b.current&&Object.entries(e).forEach(([e,r])=>{b.current?.setVariable(e,r)})},[]),U=a(e=>b.current?.getState().variables[e],[]),k=a(async e=>{await(b.current?.refreshData(e))},[]),B=a(async(e,r)=>{const n=y;if(!n)throw new Error("HostAPI not available");const t=await n.executeAction(e,r||{});if(t.success)return t.data;throw new Error(t.errorMessage||"Action failed")},[y]);return{runtime:o,loading:s,phase:d,page:f,error:p,hostApi:y,isReady:A,hasError:I,metrics:g,init:N,load:j,render:R,destroy:C,reload:P,setVariable:x,setVariables:S,getVariable:U,refreshData:k,executeAction:B}}var m=e({runtime:null,state:{phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1},hostApi:null});function f({runtime:e,state:r,hostApi:n,children:t}){const a={runtime:e,state:r,hostApi:n};return s(m.Provider,{value:a,children:t})}function h(){return o(m)}function p({pageUid:e,apiBaseUrl:t,cdnBaseUrl:o,channel:l="prod",userId:u,deviceId:m,authToken:h,previewToken:p,debug:w=!1,enableSRI:y=!0,onLoad:v,onError:g,onReady:T,onPhaseChange:b,loadingComponent:E,errorComponent:A,emptyComponent:I,children:N,className:j,style:R,retryCount:C=3,retryDelay:P=1e3,timeout:x=3e4}){const S=n(null),U=n(!0),k=n(0),[B,D]=r({phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1}),M={pageUid:e,apiBaseUrl:t,cdnBaseUrl:o,channel:l,userId:u,deviceId:m,authToken:h,previewToken:p,debug:w,enableSRI:y,containerRef:S,onError:e=>{g?.(e)}},{runtime:V,loading:z,phase:L,page:q,error:H,hostApi:O,init:J,load:F,render:$,destroy:G}=d(M);i(()=>{b?.(L)},[L,b]);const K=a(async()=>{if(!S.current||!U.current)return;const e=new Promise((e,r)=>{setTimeout(()=>r(new Error("加载超时")),x)});try{await Promise.race([(async()=>{await J();const e=await F();U.current&&v?.(e),await $(),U.current&&(T?.(),k.current=0,V&&V.onStateChange(e=>{U.current&&D(e)}))})(),e])}catch(e){if(!U.current)return;k.current<C&&(k.current++,setTimeout(()=>{U.current&&K()},P))}},[J,F,$,V,v,T,x,C,P,w]),Q=a(()=>{k.current=0,K()},[K]);return i(()=>(U.current=!0,K(),()=>{U.current=!1,G()}),[e]),s(f,{runtime:V,state:B,hostApi:O,children:c("div",{ref:S,className:`djvlc-renderer ${j||""}`,style:R,"data-phase":L,"data-page-uid":e,children:[z?E||c("div",{className:"djvlc-loading",children:[s("div",{className:"djvlc-loading-spinner"}),s("span",{children:"加载中..."}),k.current>0&&c("span",{className:"djvlc-loading-retry",children:["重试 ",k.current,"/",C]})]}):null,H?"function"==typeof A?A(H,Q):A||c("div",{className:"djvlc-error",children:[s("div",{className:"djvlc-error-icon",children:"⚠️"}),c("span",{className:"djvlc-error-message",children:["加载失败:",H.message]}),s("button",{className:"djvlc-error-retry-btn",onClick:Q,type:"button",children:"重试"})]}):null,z||H||q||"ready"!==L||q?null:I||s("div",{className:"djvlc-empty",children:s("span",{children:"暂无内容"})}),N]})})}var w={phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1};function y({runtime:e,hostApi:t,children:a,className:o,debug:l=!1,onStateChange:c,onPhaseChange:u,onError:d}){const[m,h]=r(e?.getState()||w),p=n(m.phase);return i(()=>{if(!e)return void h(w);h(e.getState());const r=e.onStateChange(e=>{h(e),c?.(e),e.phase!==p.current&&(u?.(e.phase),p.current=e.phase),e.error&&d?.(e.error)});return()=>{r()}},[e,c,u,d,l]),i(()=>{},[t]),s(f,{runtime:e,state:m,hostApi:t,children:s("div",{className:["djvlc-provider",o].filter(Boolean).join(" "),"data-phase":m.phase,children:a})})}function v(){const e=h(),r=t(()=>{const r=e.state.phase;return"ready"!==r&&"error"!==r},[e.state.phase]),n="ready"===e.state.phase,o="error"===e.state.phase||null!==e.state.error,i=a(async()=>{if(!e.runtime)throw new Error("Runtime not available");await e.runtime.load(),await e.runtime.render()},[e.runtime]);return{runtime:e.runtime,state:e.state,loading:r,phase:e.state.phase,error:e.state.error,page:e.state.page,isReady:n,hasError:o,reload:i}}function g(){const e=h();if(!e.hostApi)throw new Error("HostAPI not available. Make sure runtime is initialized.");return e.hostApi}function T(e){return h().state.variables[e]}function b(e,r){const n=h();return[n.state.variables[e]??r,a(r=>{n.runtime?.setVariable(e,r)},[n.runtime,e])]}function E(e,t){const o=h(),[l,s]=r(!1),[c,u]=r(null),[d,m]=r(null),f=n(!0),p=o.state.queries[e],w=a(async()=>{if(f.current){s(!0),u(null);try{await(o.runtime?.refreshData(e)),f.current&&m(Date.now())}catch(e){f.current&&u(e)}finally{f.current&&s(!1)}}},[o.runtime,e]);return i(()=>(f.current=!0,t?.refreshOnMount&&o.runtime&&w(),()=>{f.current=!1}),[]),i(()=>{if(!t?.refreshInterval||t.refreshInterval<=0)return;const e=setInterval(()=>{w()},t.refreshInterval);return()=>clearInterval(e)},[t?.refreshInterval,w]),i(()=>{if(!t?.refreshOnFocus)return;const e=()=>{w()};return window.addEventListener("focus",e),()=>window.removeEventListener("focus",e)},[t?.refreshOnFocus,w]),{data:p,loading:l,error:c,refetch:w,lastUpdated:d}}function A(e,t){const o=h(),[l,s]=r(!1),[c,u]=r(),[d,m]=r(null),[f,p]=r(0),w=n(!0);i(()=>(w.current=!0,()=>{w.current=!1}),[]);const y=a(()=>{u(void 0),m(null),p(0)},[]),v=a(async(r,n)=>{const a=o.hostApi;if(!a)throw new Error("HostAPI not available");try{const n=await a.executeAction(e,r);if(n.success)return n.data;throw new Error(n.errorMessage||"Action failed")}catch(e){if(n>0)return await new Promise(e=>setTimeout(e,t?.retryDelay||1e3)),v(r,n-1);throw e}},[o.hostApi,e,t?.retryDelay]);return{execute:a(async e=>{if(w.current){s(!0),m(null),p(e=>e+1);try{const r=await v(e,t?.retryCount||0);return w.current&&(u(r),t?.onSuccess?.(r)),r}catch(e){const r=e;throw w.current&&(m(r),t?.onError?.(r)),e}finally{w.current&&s(!1)}}},[v,t]),loading:l,result:c,error:d,reset:y,executionCount:f}}function I(e,t,o){const l=h(),[s,c]=r(),[u,d]=r(!1),[m,f]=r(null),[p,w]=r(!1),y=n(!0),v=n(t);i(()=>(y.current=!0,()=>{y.current=!1}),[]);const g=a(async r=>{const n=l.hostApi;if(n&&y.current){d(!0),f(null);try{const t=await n.requestData(e,r||v.current);if(!y.current)return;c(t),w(!0),o?.onSuccess?.(t)}catch(e){if(y.current){const r=e;f(r),o?.onError?.(r)}}finally{y.current&&d(!1)}}},[l.hostApi,e,o]);return i(()=>{const e=v.current;v.current=t,!1!==o?.refreshOnParamsChange&&l.hostApi&&p&&JSON.stringify(e)!==JSON.stringify(t)&&g()},[t,l.hostApi,o?.refreshOnParamsChange,p,g]),i(()=>{!1!==o?.immediate&&l.hostApi&&g()},[l.hostApi]),i(()=>{if(!o?.refreshInterval||o.refreshInterval<=0)return;const e=setInterval(()=>{g()},o.refreshInterval);return()=>clearInterval(e)},[o?.refreshInterval,g]),{data:s,loading:u,error:m,refetch:g,isFetched:p}}function N(e,r){const n=h();i(()=>{const t=n.runtime?.on(e,e=>{r(e.data)});return()=>{t?.()}},[n.runtime,e,r])}function j(){const e=h().state.page;return t(()=>({pageUid:e?.pageUid,pageVersionId:e?.pageVersionId,schemaVersion:e?.pageJson?.schemaVersion,title:e?.title,config:e?.config,isLoaded:null!==e}),[e])}function R(e){const r=h().state.components.get(e);return t(()=>({isLoaded:"loaded"===r?.status,isLoading:"loading"===r?.status,hasError:"failed"===r?.status,loadTime:r?.loadTime,info:r}),[r])}function C(e){const t=h(),[a,o]=r(!1),[l,s]=r(!1),c=n(t.state.phase),u=n(!0);return i(()=>(u.current=!0,()=>{u.current=!1}),[]),i(()=>{const r=t.state.phase;r!==c.current&&(e?.onPhaseChange?.(r),c.current=r),a||"idle"===r||(o(!0),Promise.resolve(e?.onMounted?.()).catch(r=>{e?.onError?.(r)})),l||"ready"!==r||(s(!0),Promise.resolve(e?.onReady?.()).catch(r=>{e?.onError?.(r)})),"error"===r&&t.state.error&&e?.onError?.(t.state.error)},[t.state.phase,t.state.error,a,l,e]),{phase:t.state.phase,hasMounted:a,hasReady:l}}function P(e,n,t){const[a,o]=r(!1),{once:l=!0}=t||{};return i(()=>{!e||l&&a||(o(!0),n())},[e,n,l,a]),{executed:a}}function x(e,r=300){const{execute:t,loading:o,result:l,error:s}=A(e),c=n(null),u=n(!0);return i(()=>(u.current=!0,()=>{u.current=!1,c.current&&clearTimeout(c.current)}),[]),{execute:a(e=>{c.current&&clearTimeout(c.current),c.current=setTimeout(()=>{u.current&&t(e).catch(()=>{})},r)},[t,r]),loading:o,result:l,error:s,cancel:a(()=>{c.current&&(clearTimeout(c.current),c.current=null)},[])}}function S(e,t=[],o){const[l,s]=r(),[c,u]=r(!1),[d,m]=r(null),f=n(!0);i(()=>(f.current=!0,()=>{f.current=!1}),[]);const h=a(async()=>{if(f.current){u(!0),m(null);try{const r=await e();return f.current&&(s(r),o?.onSuccess?.(r)),r}catch(e){const r=e;return void(f.current&&(m(r),o?.onError?.(r)))}finally{f.current&&u(!1)}}},[e,...t]);return i(()=>{!1!==o?.immediate&&h()},[]),{data:l,loading:c,error:d,execute:h}}function U(e){const r=n();return i(()=>{r.current=e},[e]),r.current}export{y as DJVProvider,p as DJVRenderer,m as RuntimeContext,f as RuntimeProvider,A as useAction,S as useAsync,R as useComponentState,v as useDJVRuntime,I as useData,x as useDebouncedAction,g as useHostApi,C as useLifecycle,j as usePageInfo,U as usePrevious,E as useQuery,d as useReactRuntime,h as useRuntimeContext,N as useRuntimeEvent,T as useRuntimeState,b as useRuntimeStateWritable,P as useWhen};