@therealtinhtute/baroiplayer 1.2.0 → 2.0.0-beta.1
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/README.md +91 -73
- package/dist/components/icons/index.d.ts +36 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/ArrowLeftIcon.d.ts +4 -0
- package/dist/components/icons/svg-icons/ArrowLeftIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/ArrowRightIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/ArrowRightIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/AudioIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/AudioIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/BackwardIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/BackwardIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/CameraIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/CameraIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/CheckIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/CheckIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/EnablePiPIcon.d.ts +4 -0
- package/dist/components/icons/svg-icons/EnablePiPIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/ForwardIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/ForwardIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/FullscreenEnterIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/FullscreenEnterIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/FullscreenExitIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/FullscreenExitIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/LoadingIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/LoadingIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/PauseIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/PauseIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/PlayIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/PlayIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/PlaybackSpeedIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/PlaybackSpeedIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/QualityIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/QualityIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SettingsIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SettingsIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SliderIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SliderIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SubtitleIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SubtitleIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SubtitleOffIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SubtitleOffIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/UnPiPIcon.d.ts +4 -0
- package/dist/components/icons/svg-icons/UnPiPIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeMutedIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeMutedIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeOneIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeOneIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeThreeIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeThreeIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeTwoIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeTwoIcon.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-audio.d.ts +13 -0
- package/dist/components/variants/gradient/gradient-audio.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-controls.d.ts +4 -0
- package/dist/components/variants/gradient/gradient-controls.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-glyphs.d.ts +30 -0
- package/dist/components/variants/gradient/gradient-glyphs.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-video.d.ts +16 -0
- package/dist/components/variants/gradient/gradient-video.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +786 -6271
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +3 -0
- package/package.json +110 -108
- package/src/components/variants/gradient/gradient-audio.tsx +47 -0
- package/src/components/variants/gradient/gradient-controls.tsx +254 -0
- package/src/components/variants/gradient/gradient-glyphs.test.tsx +69 -0
- package/src/components/variants/gradient/gradient-glyphs.tsx +129 -0
- package/src/components/variants/gradient/gradient-skin.css +211 -0
- package/src/components/variants/gradient/gradient-video.tsx +70 -0
- package/src/index.ts +5 -198
- package/src/test/accessibility-utils.ts +0 -59
- package/dist/context/media.js +0 -2
- package/dist/context/media.js.map +0 -1
- package/dist/context/media.mjs +0 -15
- package/dist/context/media.mjs.map +0 -1
- package/dist/context/store.js +0 -2
- package/dist/context/store.js.map +0 -1
- package/dist/context/store.mjs +0 -5
- package/dist/context/store.mjs.map +0 -1
- package/dist/context-BYwDoSRX.js +0 -1172
- package/dist/context-BYwDoSRX.js.map +0 -1
- package/dist/context-eNS62_ac.cjs +0 -23
- package/dist/context-eNS62_ac.cjs.map +0 -1
- package/dist/core-events-B1iM2F46.cjs +0 -2
- package/dist/core-events-B1iM2F46.cjs.map +0 -1
- package/dist/core-events-DunPHKRE.js +0 -484
- package/dist/core-events-DunPHKRE.js.map +0 -1
- package/dist/core-plugins-By-4-kGs.cjs +0 -2
- package/dist/core-plugins-By-4-kGs.cjs.map +0 -1
- package/dist/core-plugins-DSBWcTuX.js +0 -821
- package/dist/core-plugins-DSBWcTuX.js.map +0 -1
- package/dist/core.js +0 -37
- package/dist/core.js.map +0 -1
- package/dist/core.mjs +0 -1233
- package/dist/core.mjs.map +0 -1
- package/dist/enhanced-components-B_l3cist.js +0 -1142
- package/dist/enhanced-components-B_l3cist.js.map +0 -1
- package/dist/enhanced-components-BspFs9OO.cjs +0 -2
- package/dist/enhanced-components-BspFs9OO.cjs.map +0 -1
- package/dist/hooks-BXwI8QG6.cjs +0 -4
- package/dist/hooks-BXwI8QG6.cjs.map +0 -1
- package/dist/hooks-Bpi-jDiO.js +0 -1816
- package/dist/hooks-Bpi-jDiO.js.map +0 -1
- package/dist/minimal-components-C4z900Ro.js +0 -1926
- package/dist/minimal-components-C4z900Ro.js.map +0 -1
- package/dist/minimal-components-v4qBp6jv.cjs +0 -2
- package/dist/minimal-components-v4qBp6jv.cjs.map +0 -1
- package/dist/minimal-components.css +0 -1
- package/dist/mona-loading.gif +0 -0
- package/dist/performance-monitor-C5k6FDXw.js +0 -301
- package/dist/performance-monitor-C5k6FDXw.js.map +0 -1
- package/dist/performance-monitor-lJmuUjQQ.cjs +0 -2
- package/dist/performance-monitor-lJmuUjQQ.cjs.map +0 -1
- package/dist/utils-CFOue_9K.cjs +0 -9
- package/dist/utils-CFOue_9K.cjs.map +0 -1
- package/dist/utils-Dzpl2ArK.js +0 -2844
- package/dist/utils-Dzpl2ArK.js.map +0 -1
- package/dist/utils.js +0 -2
- package/dist/utils.js.map +0 -1
- package/dist/utils.mjs +0 -11
- package/dist/utils.mjs.map +0 -1
- package/src/adapters/videojs-adapter.tsx +0 -470
- package/src/assets/mona-loading.gif +0 -0
- package/src/components/AnimateLoading.tsx +0 -107
- package/src/components/LoadingState.tsx +0 -239
- package/src/components/__tests__/media-player-accessibility.test.tsx +0 -510
- package/src/components/__tests__/media-player-react19.test.tsx +0 -457
- package/src/components/accessibility/keyboard-help-overlay.tsx +0 -207
- package/src/components/audio-player.tsx +0 -85
- package/src/components/basic/index.ts +0 -37
- package/src/components/enhanced/advanced-features.tsx +0 -311
- package/src/components/enhanced/debug-panel.tsx +0 -448
- package/src/components/enhanced/enhanced-controls.tsx +0 -597
- package/src/components/enhanced/enhanced-media-player.tsx +0 -566
- package/src/components/enhanced/error-handling.tsx +0 -338
- package/src/components/enhanced/index.ts +0 -39
- package/src/components/enhanced/media-player-enhanced.tsx +0 -383
- package/src/components/examples/basic-video-player.tsx +0 -50
- package/src/components/examples/custom-controls-example.tsx +0 -63
- package/src/components/examples/enhanced-controls-example.tsx +0 -100
- package/src/components/examples/enhanced-media-player-example.tsx +0 -302
- package/src/components/examples/loading-states-demo.tsx +0 -142
- package/src/components/examples/react-19-demo.tsx +0 -353
- package/src/components/examples/volume-orientation-demo.tsx +0 -106
- package/src/components/icon-demo.tsx +0 -196
- package/src/components/legacy.tsx +0 -87
- package/src/components/media-metadata-display.tsx +0 -99
- package/src/components/media-player-audio.tsx +0 -39
- package/src/components/media-player-controls.tsx +0 -223
- package/src/components/media-player-error.tsx +0 -163
- package/src/components/media-player-fullscreen.tsx +0 -109
- package/src/components/media-player-hls.tsx +0 -111
- package/src/components/media-player-loading.tsx +0 -76
- package/src/components/media-player-play.tsx +0 -75
- package/src/components/media-player-root.tsx +0 -338
- package/src/components/media-player-seek.tsx +0 -168
- package/src/components/media-player-time.tsx +0 -99
- package/src/components/media-player-tooltip.tsx +0 -58
- package/src/components/media-player-video.tsx +0 -54
- package/src/components/media-player-volume.tsx +0 -155
- package/src/components/media-player.tsx +0 -292
- package/src/components/media-suspense.tsx +0 -344
- package/src/components/player-settings-form.tsx +0 -158
- package/src/components/react-player-wrapper.tsx +0 -651
- package/src/components/simple-video-player.tsx +0 -62
- package/src/components/ui/blur-poster.tsx +0 -93
- package/src/components/ui/empty-state.tsx +0 -91
- package/src/components/ui/enhanced-seek-bar.tsx +0 -254
- package/src/components/ui/error-state.tsx +0 -124
- package/src/components/ui/fullscreen-button.tsx +0 -81
- package/src/components/ui/loading-skeleton.tsx +0 -107
- package/src/components/ui/media-resource-preloader.tsx +0 -458
- package/src/components/ui/offline-message.tsx +0 -98
- package/src/components/ui/play-button.tsx +0 -99
- package/src/components/ui/seek-bar.tsx +0 -191
- package/src/components/ui/time-display.tsx +0 -61
- package/src/components/ui/volume-control.tsx +0 -190
- package/src/components/unified/base-media-player.tsx +0 -252
- package/src/components/unified/media-player.tsx +0 -75
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +0 -587
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +0 -573
- package/src/components/variants/minimal/MinimalControls.tsx +0 -124
- package/src/components/variants/minimal/MinimalLoading.tsx +0 -28
- package/src/components/variants/minimal/MinimalMediaPlayer.tsx +0 -318
- package/src/components/variants/minimal/MinimalOverlay.tsx +0 -58
- package/src/components/variants/minimal/MinimalPlayButton.tsx +0 -56
- package/src/components/variants/minimal/MinimalSeekBar.tsx +0 -140
- package/src/components/variants/minimal/MinimalVolumeControl.tsx +0 -75
- package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +0 -206
- package/src/components/variants/minimal/components/CenterPlayButton.tsx +0 -22
- package/src/components/variants/minimal/components/ControlBar.tsx +0 -142
- package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +0 -412
- package/src/components/variants/minimal/components/PlaybackControls.tsx +0 -46
- package/src/components/variants/minimal/components/RightControls.tsx +0 -164
- package/src/components/variants/minimal/components/SeekBar.tsx +0 -127
- package/src/components/variants/minimal/components/SettingsDropdown.tsx +0 -68
- package/src/components/variants/minimal/components/SubtitleDisplay.tsx +0 -21
- package/src/components/variants/minimal/components/TimeDisplay.tsx +0 -15
- package/src/components/variants/minimal/components/VideoElement.tsx +0 -33
- package/src/components/variants/minimal/components/VolumeControl.tsx +0 -220
- package/src/components/variants/minimal/index.ts +0 -23
- package/src/components/video-player.tsx +0 -100
- package/src/context/__tests__/media-store.test.ts +0 -509
- package/src/context/error-handling.ts +0 -398
- package/src/context/media-context.tsx +0 -254
- package/src/context/media-provider.tsx +0 -264
- package/src/context/media-store.ts +0 -387
- package/src/context/persistence.ts +0 -462
- package/src/core/events/event-system.ts +0 -448
- package/src/core/events/hooks.ts +0 -325
- package/src/core/events/index.ts +0 -22
- package/src/core/events/middleware.ts +0 -286
- package/src/core/index.ts +0 -22
- package/src/core/optimization/bundle-optimizer.tsx +0 -312
- package/src/core/performance/cicd.ts +0 -98
- package/src/core/performance/index.ts +0 -38
- package/src/core/performance/media-loading-optimizer.ts +0 -544
- package/src/core/performance/performance-budgets.ts +0 -437
- package/src/core/performance/performance-monitor.tsx +0 -488
- package/src/core/performance/performance-testing.ts +0 -461
- package/src/core/plugins/__tests__/registry.test.ts +0 -595
- package/src/core/plugins/base-plugin.ts +0 -225
- package/src/core/plugins/enhanced-plugin-system.ts +0 -435
- package/src/core/plugins/examples/analytics-plugin.ts +0 -241
- package/src/core/plugins/index.ts +0 -39
- package/src/core/plugins/registry.ts +0 -546
- package/src/core/plugins/types.ts +0 -169
- package/src/core/plugins/utils.ts +0 -317
- package/src/core/progressive-loading.tsx +0 -331
- package/src/core/providers/enhanced-hls-provider.ts +0 -307
- package/src/core/providers/lazy-provider.tsx +0 -14
- package/src/core/providers/service-provider.tsx +0 -164
- package/src/core/utils.tsx +0 -34
- package/src/features/media-session/media-session-manager.ts +0 -411
- package/src/features/picture-in-picture/pip-manager.ts +0 -428
- package/src/features/playlist/playlist-manager.ts +0 -626
- package/src/features/subtitles/subtitle-manager.ts +0 -435
- package/src/features/subtitles/subtitle-parser.ts +0 -284
- package/src/features/thumbnails/thumbnail-generator.ts +0 -528
- package/src/hooks/__tests__/currentTime-fix.test.ts +0 -149
- package/src/hooks/__tests__/use-media-player.test.ts +0 -712
- package/src/hooks/__tests__/use-media-state.test.tsx +0 -316
- package/src/hooks/accessibility/index.ts +0 -27
- package/src/hooks/accessibility/use-accessibility-preferences.ts +0 -190
- package/src/hooks/accessibility/use-focus-management.ts +0 -183
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +0 -152
- package/src/hooks/accessibility/use-screen-reader.ts +0 -212
- package/src/hooks/core/index.ts +0 -47
- package/src/hooks/legacy.ts +0 -21
- package/src/hooks/use-buffer-health.ts +0 -218
- package/src/hooks/use-enhanced-media-player.ts +0 -575
- package/src/hooks/use-focus-trap.tsx +0 -162
- package/src/hooks/use-intelligent-preloading.ts +0 -387
- package/src/hooks/use-keyboard-controls.ts +0 -115
- package/src/hooks/use-keyboard-shortcuts.ts +0 -255
- package/src/hooks/use-media-actions.ts +0 -184
- package/src/hooks/use-media-metadata.ts +0 -91
- package/src/hooks/use-media-optimistic.ts +0 -264
- package/src/hooks/use-media-player.ts +0 -343
- package/src/hooks/use-media-state.ts +0 -194
- package/src/hooks/use-memory-optimization.ts +0 -360
- package/src/hooks/use-network-status.ts +0 -124
- package/src/hooks/use-optimistic-controls.ts +0 -95
- package/src/hooks/use-optimized-media.ts +0 -283
- package/src/hooks/use-smart-preload.ts +0 -229
- package/src/hooks/use-touch-gestures.ts +0 -461
- package/src/hooks/useEnhancedSubtitles.ts +0 -258
- package/src/hooks/useKeyboardShortcuts.ts +0 -106
- package/src/hooks/useMediaControls.ts +0 -52
- package/src/hooks/useScreenshot.ts +0 -61
- package/src/hooks/useSubtitles.ts +0 -91
- package/src/hooks/useVideoPlayer.ts +0 -278
- package/src/icons/components/FullscreenIcon.tsx +0 -36
- package/src/icons/components/SettingsIcon.tsx +0 -37
- package/src/icons/components/animated/LoadingIcon.tsx +0 -27
- package/src/icons/components/animated/PlayPauseIcon.tsx +0 -42
- package/src/icons/components/media/PauseIcon.tsx +0 -38
- package/src/icons/components/media/PlayIcon.tsx +0 -37
- package/src/icons/components/volume/VolumeIcon.tsx +0 -38
- package/src/icons/components/volume/VolumeOffIcon.tsx +0 -38
- package/src/icons/index.ts +0 -56
- package/src/icons/provider.tsx +0 -58
- package/src/icons/types.ts +0 -60
- package/src/index-enhanced.ts +0 -35
- package/src/index-legacy.ts +0 -324
- package/src/index.css +0 -102
- package/src/lib/__tests__/async-utils.test.ts +0 -291
- package/src/lib/async-utils.ts +0 -194
- package/src/lib/deprecation.ts +0 -51
- package/src/lib/format-time.ts +0 -37
- package/src/lib/index.ts +0 -4
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -462
- package/src/styles/minimal-player.css +0 -621
- package/src/styles/pixel-art.css +0 -41
- package/src/styles/videojs.css +0 -100
- package/src/types/events.ts +0 -47
- package/src/types/formats.ts +0 -79
- package/src/types/index.ts +0 -203
- package/src/types/videojs-plugins.d.ts +0 -14
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { memo } from "react"
|
|
2
|
-
import { cn } from "../../lib/utils"
|
|
3
|
-
|
|
4
|
-
interface LoadingSkeletonProps {
|
|
5
|
-
className?: string
|
|
6
|
-
variant?: "text" | "circular" | "rectangular" | "rounded"
|
|
7
|
-
animation?: "pulse" | "wave" | "none"
|
|
8
|
-
width?: number | string
|
|
9
|
-
height?: number | string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const variantStyles = {
|
|
13
|
-
text: "rounded",
|
|
14
|
-
circular: "rounded-full",
|
|
15
|
-
rectangular: "rounded-none",
|
|
16
|
-
rounded: "rounded-lg"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const animationStyles = {
|
|
20
|
-
pulse: "animate-pulse",
|
|
21
|
-
wave: "animate-pulse",
|
|
22
|
-
none: ""
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const LoadingSkeletonComponent = memo(function LoadingSkeleton({
|
|
26
|
-
className,
|
|
27
|
-
variant = "text",
|
|
28
|
-
animation = "pulse",
|
|
29
|
-
width,
|
|
30
|
-
height
|
|
31
|
-
}: LoadingSkeletonProps) {
|
|
32
|
-
return (
|
|
33
|
-
<div
|
|
34
|
-
className={cn(
|
|
35
|
-
"bg-[color:var(--media-skeleton)]",
|
|
36
|
-
variantStyles[variant],
|
|
37
|
-
animation === "wave" &&
|
|
38
|
-
"bg-[length:200%_100%] bg-gradient-to-r from-[color:var(--media-skeleton)] via-[color:var(--media-skeleton-light)] to-[color:var(--media-skeleton)]",
|
|
39
|
-
animationStyles[animation],
|
|
40
|
-
className
|
|
41
|
-
)}
|
|
42
|
-
style={{ width, height }}
|
|
43
|
-
role="status"
|
|
44
|
-
aria-label="Loading content"
|
|
45
|
-
/>
|
|
46
|
-
)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
interface LoadingSkeletonListProps {
|
|
50
|
-
count?: number
|
|
51
|
-
className?: string
|
|
52
|
-
itemClassName?: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const LoadingSkeletonList = memo(function LoadingSkeletonList({
|
|
56
|
-
count = 3,
|
|
57
|
-
className,
|
|
58
|
-
itemClassName
|
|
59
|
-
}: LoadingSkeletonListProps) {
|
|
60
|
-
return (
|
|
61
|
-
<div className={cn("space-y-4", className)}>
|
|
62
|
-
{Array.from({ length: count }).map((_, i) => (
|
|
63
|
-
<LoadingSkeletonComponent
|
|
64
|
-
key={i}
|
|
65
|
-
variant="rounded"
|
|
66
|
-
width="100%"
|
|
67
|
-
height={60}
|
|
68
|
-
className={itemClassName}
|
|
69
|
-
/>
|
|
70
|
-
))}
|
|
71
|
-
</div>
|
|
72
|
-
)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
interface LoadingPlayerSkeletonProps {
|
|
76
|
-
className?: string
|
|
77
|
-
showTimeDisplay?: boolean
|
|
78
|
-
showControls?: boolean
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const LoadingPlayerSkeleton = memo(function LoadingPlayerSkeleton({
|
|
82
|
-
className,
|
|
83
|
-
showTimeDisplay = true,
|
|
84
|
-
showControls = true
|
|
85
|
-
}: LoadingPlayerSkeletonProps) {
|
|
86
|
-
return (
|
|
87
|
-
<div className={cn("relative overflow-hidden rounded-lg bg-black", className)}>
|
|
88
|
-
<LoadingSkeletonComponent
|
|
89
|
-
variant="rectangular"
|
|
90
|
-
width="100%"
|
|
91
|
-
height="100%"
|
|
92
|
-
className="aspect-video"
|
|
93
|
-
/>
|
|
94
|
-
<div className="absolute right-0 bottom-0 left-0 p-4">
|
|
95
|
-
{showTimeDisplay && (
|
|
96
|
-
<div className="mb-2 flex justify-between">
|
|
97
|
-
<LoadingSkeletonComponent variant="text" width={60} height={16} />
|
|
98
|
-
<LoadingSkeletonComponent variant="text" width={60} height={16} />
|
|
99
|
-
</div>
|
|
100
|
-
)}
|
|
101
|
-
{showControls && <LoadingSkeletonComponent variant="rounded" width="100%" height={8} />}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
export { LoadingSkeletonComponent as LoadingSkeleton }
|
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Resource Preloader using React 19's resource preloading APIs
|
|
3
|
-
* Implements intelligent preloading strategies based on user behavior and network conditions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { useEffect, useRef, useMemo } from "react"
|
|
7
|
-
import { preconnect, prefetchDNS, preinit, preload } from "react-dom"
|
|
8
|
-
import { MediaSource } from "../../types"
|
|
9
|
-
|
|
10
|
-
interface MediaResourcePreloaderProps {
|
|
11
|
-
/** Media sources to preload */
|
|
12
|
-
sources: MediaSource[]
|
|
13
|
-
/** Enable intelligent preloading based on user behavior */
|
|
14
|
-
enableIntelligentPreloading?: boolean
|
|
15
|
-
/** Network-aware preloading */
|
|
16
|
-
adaptToNetworkConditions?: boolean
|
|
17
|
-
/** Preload priority level */
|
|
18
|
-
priority?: "high" | "low" | "auto"
|
|
19
|
-
/** Custom preloading strategy */
|
|
20
|
-
strategy?: "conservative" | "aggressive" | "adaptive"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface PreloadingConfig {
|
|
24
|
-
dnsPrefetch: string[]
|
|
25
|
-
preconnect: string[]
|
|
26
|
-
preload: Array<{ url: string; type: string }>
|
|
27
|
-
preinit: Array<{ url: string; type: string }>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
|
|
31
|
-
sources,
|
|
32
|
-
enableIntelligentPreloading = true,
|
|
33
|
-
adaptToNetworkConditions = true,
|
|
34
|
-
priority = "auto",
|
|
35
|
-
strategy = "adaptive"
|
|
36
|
-
}) => {
|
|
37
|
-
const networkInfoRef = useRef<any>(null)
|
|
38
|
-
const userBehaviorRef = useRef<UserBehaviorTracker | null>(null)
|
|
39
|
-
const preloadingHistoryRef = useRef<Map<string, number>>(new Map())
|
|
40
|
-
|
|
41
|
-
// Helper functions (must be declared before use)
|
|
42
|
-
function shouldUseCrossOrigin(url: string): boolean {
|
|
43
|
-
return !url.includes(window.location.origin)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getAssetIntegrity(url: string): string | undefined {
|
|
47
|
-
// Return integrity hash if available for the asset
|
|
48
|
-
// This would typically come from a build process or asset manifest
|
|
49
|
-
return undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function preloadLikelyContent(sources: string[]): void {
|
|
53
|
-
sources.forEach((url) => {
|
|
54
|
-
// Mark as preloaded
|
|
55
|
-
preloadingHistoryRef.current.set(url, Date.now())
|
|
56
|
-
|
|
57
|
-
// Preload the content
|
|
58
|
-
preload(url, {
|
|
59
|
-
as: "fetch" as any,
|
|
60
|
-
crossOrigin: "anonymous"
|
|
61
|
-
} as any)
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function adjustPreloadingStrategy(): void {
|
|
66
|
-
// Adjust preloading based on network conditions
|
|
67
|
-
if (!networkInfoRef.current) return
|
|
68
|
-
|
|
69
|
-
const { effectiveType, downlink } = networkInfoRef.current
|
|
70
|
-
|
|
71
|
-
// This would update the preloading configuration
|
|
72
|
-
// Implementation depends on specific requirements
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Analyze sources and determine preloading configuration
|
|
76
|
-
const preloadingConfig = useMemo((): PreloadingConfig => {
|
|
77
|
-
const config: PreloadingConfig = {
|
|
78
|
-
dnsPrefetch: [],
|
|
79
|
-
preconnect: [],
|
|
80
|
-
preload: [],
|
|
81
|
-
preinit: []
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
sources.forEach((source) => {
|
|
85
|
-
const url = new URL(source.src)
|
|
86
|
-
const domain = url.origin
|
|
87
|
-
|
|
88
|
-
// DNS prefetch for CDN domains
|
|
89
|
-
if (isCDNDomain(domain)) {
|
|
90
|
-
config.dnsPrefetch.push(domain)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Preconnect to media domains
|
|
94
|
-
if (shouldPreconnect(source.type || "", strategy)) {
|
|
95
|
-
config.preconnect.push(domain)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Preload media files based on strategy (basic check without refs)
|
|
99
|
-
if (shouldPreloadBasic(source, priority, strategy)) {
|
|
100
|
-
config.preload.push({
|
|
101
|
-
url: source.src,
|
|
102
|
-
type: getPreloadType(source.type || "")
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Preinit critical resources
|
|
107
|
-
if (shouldPreinit(source, strategy)) {
|
|
108
|
-
config.preinit.push({
|
|
109
|
-
url: source.src,
|
|
110
|
-
type: "script"
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
return config
|
|
116
|
-
}, [sources, priority, strategy])
|
|
117
|
-
|
|
118
|
-
// Store the filtered preloading config
|
|
119
|
-
const [filteredPreloadingConfig, setFilteredPreloadingConfig] = React.useState(preloadingConfig)
|
|
120
|
-
|
|
121
|
-
// Update filtered config when preloading config or refs change (safe ref access in effect)
|
|
122
|
-
React.useEffect(() => {
|
|
123
|
-
const filtered = { ...preloadingConfig }
|
|
124
|
-
filtered.preload = preloadingConfig.preload.filter(({ url }) => {
|
|
125
|
-
const source = sources.find((s) => s.src === url)
|
|
126
|
-
if (!source) return false
|
|
127
|
-
|
|
128
|
-
// Inline the full shouldPreload logic to avoid function hoisting issues
|
|
129
|
-
// Check if already preloaded recently
|
|
130
|
-
const lastPreload = preloadingHistoryRef.current.get(source.src)
|
|
131
|
-
if (lastPreload && Date.now() - lastPreload < 300000) return false // 5 minutes
|
|
132
|
-
|
|
133
|
-
// Strategy-based decision
|
|
134
|
-
if (strategy === "conservative") {
|
|
135
|
-
return priority === "high" && (source.type as string) === "hls"
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (strategy === "aggressive") {
|
|
139
|
-
return true
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Adaptive strategy (simplified version)
|
|
143
|
-
if (networkInfoRef.current) {
|
|
144
|
-
const { effectiveType, saveData } = networkInfoRef.current
|
|
145
|
-
|
|
146
|
-
if (saveData) return false
|
|
147
|
-
if (effectiveType === "slow-2g" || effectiveType === "2g") {
|
|
148
|
-
return priority === "high" && (source.type as string) === "hls"
|
|
149
|
-
}
|
|
150
|
-
if (effectiveType === "3g") {
|
|
151
|
-
return ["hls", "m3u8"].includes(source.type || "")
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return priority !== "low"
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
setFilteredPreloadingConfig(filtered)
|
|
159
|
-
}, [preloadingConfig, sources, priority, strategy])
|
|
160
|
-
|
|
161
|
-
// Initialize network monitoring
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
if (adaptToNetworkConditions && "connection" in navigator) {
|
|
164
|
-
networkInfoRef.current = (navigator as any).connection
|
|
165
|
-
|
|
166
|
-
const handleNetworkChange = () => {
|
|
167
|
-
adjustPreloadingStrategy()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
networkInfoRef.current.addEventListener("change", handleNetworkChange)
|
|
171
|
-
|
|
172
|
-
return () => {
|
|
173
|
-
networkInfoRef.current?.removeEventListener("change", handleNetworkChange)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}, [adaptToNetworkConditions])
|
|
177
|
-
|
|
178
|
-
// Initialize user behavior tracking
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
if (enableIntelligentPreloading) {
|
|
181
|
-
userBehaviorRef.current = new UserBehaviorTracker()
|
|
182
|
-
userBehaviorRef.current.startTracking()
|
|
183
|
-
|
|
184
|
-
return () => {
|
|
185
|
-
userBehaviorRef.current?.stopTracking()
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}, [enableIntelligentPreloading])
|
|
189
|
-
|
|
190
|
-
// Apply DNS prefetching
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
filteredPreloadingConfig.dnsPrefetch.forEach((domain) => {
|
|
193
|
-
prefetchDNS(domain)
|
|
194
|
-
})
|
|
195
|
-
}, [filteredPreloadingConfig.dnsPrefetch])
|
|
196
|
-
|
|
197
|
-
// Apply preconnections
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
filteredPreloadingConfig.preconnect.forEach((origin) => {
|
|
200
|
-
preconnect(origin, { crossOrigin: "anonymous" })
|
|
201
|
-
})
|
|
202
|
-
}, [filteredPreloadingConfig.preconnect])
|
|
203
|
-
|
|
204
|
-
// Apply preloading
|
|
205
|
-
useEffect(() => {
|
|
206
|
-
filteredPreloadingConfig.preload.forEach(({ url, type }) => {
|
|
207
|
-
const crossOrigin = shouldUseCrossOrigin(url) ? "anonymous" : undefined
|
|
208
|
-
|
|
209
|
-
preload(url, {
|
|
210
|
-
as: type as any,
|
|
211
|
-
crossOrigin,
|
|
212
|
-
integrity: getAssetIntegrity(url)
|
|
213
|
-
} as any)
|
|
214
|
-
})
|
|
215
|
-
}, [filteredPreloadingConfig.preload, priority])
|
|
216
|
-
|
|
217
|
-
// Apply preinit for critical resources
|
|
218
|
-
useEffect(() => {
|
|
219
|
-
filteredPreloadingConfig.preinit.forEach(({ url, type }) => {
|
|
220
|
-
const crossOrigin = shouldUseCrossOrigin(url) ? "anonymous" : undefined
|
|
221
|
-
|
|
222
|
-
preinit(url, {
|
|
223
|
-
as: type as any,
|
|
224
|
-
crossOrigin
|
|
225
|
-
} as any)
|
|
226
|
-
})
|
|
227
|
-
}, [filteredPreloadingConfig.preinit])
|
|
228
|
-
|
|
229
|
-
// Intelligent preloading based on user behavior
|
|
230
|
-
useEffect(() => {
|
|
231
|
-
if (!enableIntelligentPreloading || !userBehaviorRef.current) return
|
|
232
|
-
|
|
233
|
-
const handleUserBehavior = (behavior: UserBehaviorData) => {
|
|
234
|
-
if (behavior.isLikelyToInteract) {
|
|
235
|
-
preloadLikelyContent(behavior.predictedSources)
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
userBehaviorRef.current.onBehaviorChange(handleUserBehavior)
|
|
240
|
-
}, [enableIntelligentPreloading])
|
|
241
|
-
|
|
242
|
-
// Helper functions
|
|
243
|
-
function isCDNDomain(domain: string): boolean {
|
|
244
|
-
const cdnDomains = [
|
|
245
|
-
"cdn.jsdelivr.net",
|
|
246
|
-
"cdnjs.cloudflare.com",
|
|
247
|
-
"unpkg.com",
|
|
248
|
-
"cloudflare.com",
|
|
249
|
-
"fastly.net",
|
|
250
|
-
"akamaized.net"
|
|
251
|
-
]
|
|
252
|
-
|
|
253
|
-
return cdnDomains.some((cdn) => domain.includes(cdn))
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function shouldPreconnect(mediaType: string, strategy: string): boolean {
|
|
257
|
-
if (strategy === "aggressive") return true
|
|
258
|
-
if (strategy === "conservative") return ["hls", "m3u8"].includes(mediaType)
|
|
259
|
-
return ["hls", "m3u8", "mp4"].includes(mediaType)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function shouldPreloadBasic(source: MediaSource, priority: string, strategy: string): boolean {
|
|
263
|
-
// Basic strategy-based decision without ref access
|
|
264
|
-
if (strategy === "conservative") {
|
|
265
|
-
return priority === "high" && (source.type as string) === "hls"
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (strategy === "aggressive") {
|
|
269
|
-
return true
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Adaptive strategy (basic version)
|
|
273
|
-
return priority !== "low"
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function shouldPreload(
|
|
277
|
-
source: MediaSource,
|
|
278
|
-
priority: string,
|
|
279
|
-
strategy: string,
|
|
280
|
-
preloadingHistory: Map<string, number>,
|
|
281
|
-
networkInfo: any
|
|
282
|
-
): boolean {
|
|
283
|
-
// Check if already preloaded recently
|
|
284
|
-
const lastPreload = preloadingHistory.get(source.src)
|
|
285
|
-
if (lastPreload && Date.now() - lastPreload < 300000) return false // 5 minutes
|
|
286
|
-
|
|
287
|
-
// Strategy-based decision
|
|
288
|
-
if (strategy === "conservative") {
|
|
289
|
-
return priority === "high" && (source.type as string) === "hls"
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (strategy === "aggressive") {
|
|
293
|
-
return true
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Adaptive strategy
|
|
297
|
-
return shouldAdaptivePreload(source, priority, networkInfo)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function shouldAdaptivePreload(
|
|
301
|
-
source: MediaSource,
|
|
302
|
-
priority: string,
|
|
303
|
-
networkInfo: any
|
|
304
|
-
): boolean {
|
|
305
|
-
// Consider network conditions
|
|
306
|
-
if (networkInfo) {
|
|
307
|
-
const { effectiveType, saveData } = networkInfo
|
|
308
|
-
|
|
309
|
-
if (saveData) return false
|
|
310
|
-
if (effectiveType === "slow-2g" || effectiveType === "2g") {
|
|
311
|
-
return priority === "high" && (source.type as string) === "hls"
|
|
312
|
-
}
|
|
313
|
-
if (effectiveType === "3g") {
|
|
314
|
-
return ["hls", "m3u8"].includes(source.type || "")
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Consider user behavior
|
|
319
|
-
if (userBehaviorRef.current) {
|
|
320
|
-
const behavior = userBehaviorRef.current.getCurrentBehavior()
|
|
321
|
-
if (behavior.engagementScore > 0.7) {
|
|
322
|
-
return true
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return priority !== "low"
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function shouldPreinit(source: MediaSource, strategy: string): boolean {
|
|
330
|
-
return strategy === "aggressive" && (source.type as string) === "hls"
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function getPreloadType(mediaType: string): string {
|
|
334
|
-
switch (mediaType) {
|
|
335
|
-
case "hls":
|
|
336
|
-
case "m3u8":
|
|
337
|
-
return "fetch"
|
|
338
|
-
case "mp4":
|
|
339
|
-
case "webm":
|
|
340
|
-
return "video"
|
|
341
|
-
case "mp3":
|
|
342
|
-
case "wav":
|
|
343
|
-
return "audio"
|
|
344
|
-
default:
|
|
345
|
-
return "fetch"
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// This component doesn't render anything visible
|
|
350
|
-
return null
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* User behavior tracker for intelligent preloading
|
|
355
|
-
*/
|
|
356
|
-
class UserBehaviorTracker {
|
|
357
|
-
private behaviorData: UserBehaviorData = {
|
|
358
|
-
isLikelyToInteract: false,
|
|
359
|
-
engagementScore: 0,
|
|
360
|
-
predictedSources: [],
|
|
361
|
-
interactionHistory: []
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private listeners: ((behavior: UserBehaviorData) => void)[] = []
|
|
365
|
-
private mouseMoveTimer: number | null = null
|
|
366
|
-
|
|
367
|
-
public startTracking(): void {
|
|
368
|
-
// Track mouse movements as interaction indicators
|
|
369
|
-
document.addEventListener("mousemove", this.handleMouseMove)
|
|
370
|
-
document.addEventListener("scroll", this.handleScroll)
|
|
371
|
-
document.addEventListener("keydown", this.handleKeydown)
|
|
372
|
-
|
|
373
|
-
// Track focus events
|
|
374
|
-
document.addEventListener("focusin", this.handleFocus)
|
|
375
|
-
document.addEventListener("visibilitychange", this.handleVisibilityChange)
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
public stopTracking(): void {
|
|
379
|
-
document.removeEventListener("mousemove", this.handleMouseMove)
|
|
380
|
-
document.removeEventListener("scroll", this.handleScroll)
|
|
381
|
-
document.removeEventListener("keydown", this.handleKeydown)
|
|
382
|
-
document.removeEventListener("focusin", this.handleFocus)
|
|
383
|
-
document.removeEventListener("visibilitychange", this.handleVisibilityChange)
|
|
384
|
-
|
|
385
|
-
if (this.mouseMoveTimer) {
|
|
386
|
-
clearTimeout(this.mouseMoveTimer)
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
public onBehaviorChange(listener: (behavior: UserBehaviorData) => void): void {
|
|
391
|
-
this.listeners.push(listener)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
public getCurrentBehavior(): UserBehaviorData {
|
|
395
|
-
return this.behaviorData
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
private handleMouseMove = (): void => {
|
|
399
|
-
if (this.mouseMoveTimer) return
|
|
400
|
-
|
|
401
|
-
this.mouseMoveTimer = window.setTimeout(() => {
|
|
402
|
-
this.updateEngagementScore(0.1)
|
|
403
|
-
this.mouseMoveTimer = null
|
|
404
|
-
}, 100)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
private handleScroll = (): void => {
|
|
408
|
-
this.updateEngagementScore(0.2)
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private handleKeydown = (): void => {
|
|
412
|
-
this.updateEngagementScore(0.3)
|
|
413
|
-
this.behaviorData.isLikelyToInteract = true
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private handleFocus = (): void => {
|
|
417
|
-
this.updateEngagementScore(0.4)
|
|
418
|
-
this.behaviorData.isLikelyToInteract = true
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private handleVisibilityChange = (): void => {
|
|
422
|
-
if (!document.hidden) {
|
|
423
|
-
this.updateEngagementScore(0.5)
|
|
424
|
-
this.behaviorData.isLikelyToInteract = true
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private updateEngagementScore(increment: number): void {
|
|
429
|
-
this.behaviorData.engagementScore = Math.min(1, this.behaviorData.engagementScore + increment)
|
|
430
|
-
|
|
431
|
-
// Decay engagement score over time
|
|
432
|
-
setTimeout(() => {
|
|
433
|
-
this.behaviorData.engagementScore = Math.max(
|
|
434
|
-
0,
|
|
435
|
-
this.behaviorData.engagementScore - increment * 0.5
|
|
436
|
-
)
|
|
437
|
-
this.notifyListeners()
|
|
438
|
-
}, 30000) // 30 seconds decay
|
|
439
|
-
|
|
440
|
-
this.notifyListeners()
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
private notifyListeners(): void {
|
|
444
|
-
this.listeners.forEach((listener) => listener(this.behaviorData))
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
interface UserBehaviorData {
|
|
449
|
-
isLikelyToInteract: boolean
|
|
450
|
-
engagementScore: number
|
|
451
|
-
predictedSources: string[]
|
|
452
|
-
interactionHistory: Array<{
|
|
453
|
-
type: string
|
|
454
|
-
timestamp: number
|
|
455
|
-
}>
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
export default MediaResourcePreloader
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { RefreshCw, WifiOff } from "lucide-react"
|
|
2
|
-
import { memo, useCallback } from "react"
|
|
3
|
-
import { cn } from "../../lib/utils"
|
|
4
|
-
|
|
5
|
-
interface OfflineMessageProps {
|
|
6
|
-
className?: string
|
|
7
|
-
title?: string
|
|
8
|
-
message?: string
|
|
9
|
-
onRetry?: () => void
|
|
10
|
-
retryLabel?: string
|
|
11
|
-
showRetry?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const OfflineMessageComponent = memo(function OfflineMessage({
|
|
15
|
-
className,
|
|
16
|
-
title = "You're offline",
|
|
17
|
-
message = "Check your internet connection and try again.",
|
|
18
|
-
onRetry,
|
|
19
|
-
retryLabel = "Retry",
|
|
20
|
-
showRetry = true
|
|
21
|
-
}: OfflineMessageProps) {
|
|
22
|
-
const handleRetry = useCallback(() => {
|
|
23
|
-
onRetry?.()
|
|
24
|
-
}, [onRetry])
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div
|
|
28
|
-
className={cn(
|
|
29
|
-
"flex flex-col items-center justify-center p-8 text-center",
|
|
30
|
-
"rounded-lg bg-[color:var(--media-bg)]",
|
|
31
|
-
className
|
|
32
|
-
)}
|
|
33
|
-
role="alert"
|
|
34
|
-
aria-live="polite">
|
|
35
|
-
<div className="mb-4 rounded-full bg-[color:var(--media-control-bg)] p-4" aria-hidden="true">
|
|
36
|
-
<WifiOff size={48} className="text-[color:var(--media-text-secondary)]" />
|
|
37
|
-
</div>
|
|
38
|
-
<h3 className="mb-2 font-semibold text-[color:var(--media-text)] text-lg">{title}</h3>
|
|
39
|
-
<p className="mb-4 max-w-sm text-[color:var(--media-text-secondary)] text-sm">{message}</p>
|
|
40
|
-
{showRetry && onRetry && (
|
|
41
|
-
<button
|
|
42
|
-
onClick={handleRetry}
|
|
43
|
-
className={cn(
|
|
44
|
-
"inline-flex items-center gap-2 rounded-lg px-4 py-2",
|
|
45
|
-
"bg-[color:var(--media-primary)] text-white",
|
|
46
|
-
"transition-opacity hover:opacity-90",
|
|
47
|
-
"focus:outline-none focus:ring-2 focus:ring-[color:var(--media-primary)] focus:ring-offset-2"
|
|
48
|
-
)}
|
|
49
|
-
aria-label={retryLabel}>
|
|
50
|
-
<RefreshCw size={16} />
|
|
51
|
-
{retryLabel}
|
|
52
|
-
</button>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
interface OfflineBannerProps {
|
|
59
|
-
className?: string
|
|
60
|
-
onRetry?: () => void
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const OfflineBanner = memo(function OfflineBanner({
|
|
64
|
-
className,
|
|
65
|
-
onRetry
|
|
66
|
-
}: OfflineBannerProps) {
|
|
67
|
-
return (
|
|
68
|
-
<div
|
|
69
|
-
className={cn(
|
|
70
|
-
"flex items-center justify-between gap-4 p-3 px-4",
|
|
71
|
-
"rounded-lg border border-amber-500/20 bg-amber-500/10",
|
|
72
|
-
className
|
|
73
|
-
)}
|
|
74
|
-
role="alert"
|
|
75
|
-
aria-live="polite">
|
|
76
|
-
<div className="flex items-center gap-3">
|
|
77
|
-
<WifiOff size={20} className="text-amber-500" aria-hidden="true" />
|
|
78
|
-
<span className="text-amber-500 text-sm">
|
|
79
|
-
You're offline. Some features may be limited.
|
|
80
|
-
</span>
|
|
81
|
-
</div>
|
|
82
|
-
{onRetry && (
|
|
83
|
-
<button
|
|
84
|
-
onClick={onRetry}
|
|
85
|
-
className={cn(
|
|
86
|
-
"rounded px-3 py-1 text-sm",
|
|
87
|
-
"bg-amber-500/20 text-amber-500",
|
|
88
|
-
"transition-colors hover:bg-amber-500/30"
|
|
89
|
-
)}
|
|
90
|
-
aria-label="Retry connection check">
|
|
91
|
-
Retry
|
|
92
|
-
</button>
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
export { OfflineMessageComponent as OfflineMessage }
|