@djangocfg/ui-tools 2.1.91
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/LottiePlayer.client-LBEC2JKY.mjs +161 -0
- package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
- package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
- package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
- package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
- package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
- package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
- package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
- package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
- package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
- package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
- package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
- package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
- package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
- package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
- package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
- package/dist/chunk-37ZI6VD4.mjs +12 -0
- package/dist/chunk-37ZI6VD4.mjs.map +1 -0
- package/dist/chunk-3HK2OE62.cjs +81 -0
- package/dist/chunk-3HK2OE62.cjs.map +1 -0
- package/dist/chunk-7DGDQVQW.cjs +591 -0
- package/dist/chunk-7DGDQVQW.cjs.map +1 -0
- package/dist/chunk-M6P2FU7L.mjs +572 -0
- package/dist/chunk-M6P2FU7L.mjs.map +1 -0
- package/dist/chunk-UQ3XI5MY.cjs +15 -0
- package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
- package/dist/chunk-YFRNE2IR.mjs +79 -0
- package/dist/chunk-YFRNE2IR.mjs.map +1 -0
- package/dist/index.cjs +5042 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1591 -0
- package/dist/index.d.ts +1591 -0
- package/dist/index.mjs +4941 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +340 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/index.ts +26 -0
- package/src/stores/index.ts +9 -0
- package/src/stores/mediaCache.ts +534 -0
- package/src/tools/AudioPlayer/README.md +206 -0
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
- package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
- package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
- package/src/tools/AudioPlayer/components/index.ts +22 -0
- package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
- package/src/tools/AudioPlayer/context/index.ts +16 -0
- package/src/tools/AudioPlayer/effects/index.ts +412 -0
- package/src/tools/AudioPlayer/hooks/index.ts +35 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
- package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
- package/src/tools/AudioPlayer/index.ts +133 -0
- package/src/tools/AudioPlayer/types/effects.ts +73 -0
- package/src/tools/AudioPlayer/types/index.ts +27 -0
- package/src/tools/AudioPlayer/utils/debug.ts +14 -0
- package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
- package/src/tools/AudioPlayer/utils/index.ts +6 -0
- package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
- package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
- package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
- package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
- package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
- package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
- package/src/tools/ImageViewer/README.md +200 -0
- package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
- package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
- package/src/tools/ImageViewer/components/index.ts +7 -0
- package/src/tools/ImageViewer/hooks/index.ts +9 -0
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
- package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
- package/src/tools/ImageViewer/index.ts +60 -0
- package/src/tools/ImageViewer/types.ts +81 -0
- package/src/tools/ImageViewer/utils/constants.ts +59 -0
- package/src/tools/ImageViewer/utils/debug.ts +14 -0
- package/src/tools/ImageViewer/utils/index.ts +17 -0
- package/src/tools/ImageViewer/utils/lqip.ts +47 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +213 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
- package/src/tools/JsonForm/widgets/index.ts +14 -0
- package/src/tools/JsonTree/index.tsx +243 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
- package/src/tools/LottiePlayer/index.tsx +56 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +164 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
- package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
- package/src/tools/Mermaid/hooks/index.ts +4 -0
- package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
- package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
- package/src/tools/Mermaid/index.tsx +44 -0
- package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
- package/src/tools/OpenapiViewer/components/index.ts +14 -0
- package/src/tools/OpenapiViewer/constants.ts +39 -0
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
- package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
- package/src/tools/OpenapiViewer/index.tsx +37 -0
- package/src/tools/OpenapiViewer/types.ts +151 -0
- package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
- package/src/tools/OpenapiViewer/utils/index.ts +9 -0
- package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
- package/src/tools/PrettyCode/index.tsx +47 -0
- package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
- package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
- package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
- package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
- package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
- package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
- package/src/tools/VideoPlayer/README.md +264 -0
- package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
- package/src/tools/VideoPlayer/components/index.ts +14 -0
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
- package/src/tools/VideoPlayer/context/index.ts +8 -0
- package/src/tools/VideoPlayer/hooks/index.ts +12 -0
- package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
- package/src/tools/VideoPlayer/index.ts +77 -0
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
- package/src/tools/VideoPlayer/providers/index.ts +8 -0
- package/src/tools/VideoPlayer/types/index.ts +38 -0
- package/src/tools/VideoPlayer/types/player.ts +116 -0
- package/src/tools/VideoPlayer/types/provider.ts +93 -0
- package/src/tools/VideoPlayer/types/sources.ts +97 -0
- package/src/tools/VideoPlayer/utils/debug.ts +14 -0
- package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
- package/src/tools/VideoPlayer/utils/index.ts +12 -0
- package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
- package/src/tools/_shared.ts +29 -0
- package/src/tools/index.ts +172 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LottiePlayer Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the Lottie animation player component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type LottieSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
8
|
+
|
|
9
|
+
export type LottieSpeed = 0.5 | 1 | 1.5 | 2;
|
|
10
|
+
|
|
11
|
+
export type LottieDirection = 1 | -1;
|
|
12
|
+
|
|
13
|
+
export interface LottiePlayerProps {
|
|
14
|
+
/**
|
|
15
|
+
* Animation data (JSON object) or URL to load from
|
|
16
|
+
*/
|
|
17
|
+
src: string | object;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Size preset for the player
|
|
21
|
+
* @default 'md'
|
|
22
|
+
*/
|
|
23
|
+
size?: LottieSize;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Custom width (overrides size preset)
|
|
27
|
+
*/
|
|
28
|
+
width?: number | string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Custom height (overrides size preset)
|
|
32
|
+
*/
|
|
33
|
+
height?: number | string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Autoplay animation
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
autoplay?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Loop animation
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
loop?: boolean | number;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Playback speed
|
|
49
|
+
* @default 1
|
|
50
|
+
*/
|
|
51
|
+
speed?: LottieSpeed;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Direction (1 = forward, -1 = reverse)
|
|
55
|
+
* @default 1
|
|
56
|
+
*/
|
|
57
|
+
direction?: LottieDirection;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Show playback controls
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
controls?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Background color
|
|
67
|
+
*/
|
|
68
|
+
background?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* CSS class name
|
|
72
|
+
*/
|
|
73
|
+
className?: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show loading state
|
|
77
|
+
* @default true
|
|
78
|
+
*/
|
|
79
|
+
showLoading?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Callback when animation completes
|
|
83
|
+
*/
|
|
84
|
+
onComplete?: () => void;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Callback when animation loads
|
|
88
|
+
*/
|
|
89
|
+
onLoad?: () => void;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Callback on error
|
|
93
|
+
*/
|
|
94
|
+
onError?: (error: Error) => void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LottieAnimationData {
|
|
98
|
+
v: string;
|
|
99
|
+
fr: number;
|
|
100
|
+
ip: number;
|
|
101
|
+
op: number;
|
|
102
|
+
w: number;
|
|
103
|
+
h: number;
|
|
104
|
+
nm: string;
|
|
105
|
+
ddd: number;
|
|
106
|
+
assets: any[];
|
|
107
|
+
layers: any[];
|
|
108
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLottie Hook
|
|
3
|
+
*
|
|
4
|
+
* Hook for loading and managing Lottie animation data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import { useEffect, useRef, useState } from 'react';
|
|
10
|
+
|
|
11
|
+
import { LottieAnimationData } from './types';
|
|
12
|
+
|
|
13
|
+
export interface UseLottieOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Animation data (JSON object) or URL to load from
|
|
16
|
+
*/
|
|
17
|
+
src: string | object;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enable caching of loaded animations
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
cache?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseLottieReturn {
|
|
27
|
+
/**
|
|
28
|
+
* Loaded animation data
|
|
29
|
+
*/
|
|
30
|
+
animationData: object | null;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loading state
|
|
34
|
+
*/
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Error state
|
|
39
|
+
*/
|
|
40
|
+
error: Error | null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Retry loading the animation
|
|
44
|
+
*/
|
|
45
|
+
retry: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Simple in-memory cache for loaded animations
|
|
49
|
+
const animationCache = new Map<string, object>();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Hook for loading Lottie animations from URLs or objects
|
|
53
|
+
*
|
|
54
|
+
* Features:
|
|
55
|
+
* - Loads animations from URLs or accepts animation objects directly
|
|
56
|
+
* - Caching support to prevent re-fetching the same animation
|
|
57
|
+
* - Error handling with retry capability
|
|
58
|
+
* - Loading states
|
|
59
|
+
*
|
|
60
|
+
* Usage:
|
|
61
|
+
* ```tsx
|
|
62
|
+
* const { animationData, isLoading, error, retry } = useLottie({
|
|
63
|
+
* src: 'https://example.com/animation.json'
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
67
|
+
* if (error) return <div>Error: {error.message} <button onClick={retry}>Retry</button></div>;
|
|
68
|
+
* if (!animationData) return null;
|
|
69
|
+
*
|
|
70
|
+
* return <LottiePlayer animationData={animationData} />;
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function useLottie(options: UseLottieOptions): UseLottieReturn {
|
|
74
|
+
const { src, cache = true } = options;
|
|
75
|
+
|
|
76
|
+
const [animationData, setAnimationData] = useState<object | null>(null);
|
|
77
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
78
|
+
const [error, setError] = useState<Error | null>(null);
|
|
79
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
80
|
+
|
|
81
|
+
// Track if component is mounted to prevent state updates on unmounted component
|
|
82
|
+
const isMountedRef = useRef(true);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
isMountedRef.current = true;
|
|
86
|
+
return () => {
|
|
87
|
+
isMountedRef.current = false;
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
// If src is already an object, use it directly
|
|
93
|
+
if (typeof src === 'object' && src !== null) {
|
|
94
|
+
setAnimationData(src);
|
|
95
|
+
setIsLoading(false);
|
|
96
|
+
setError(null);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If src is a string (URL), fetch it
|
|
101
|
+
if (typeof src === 'string') {
|
|
102
|
+
const loadAnimation = async () => {
|
|
103
|
+
// Check cache first
|
|
104
|
+
if (cache && animationCache.has(src)) {
|
|
105
|
+
if (isMountedRef.current) {
|
|
106
|
+
setAnimationData(animationCache.get(src)!);
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
setError(null);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Load from URL
|
|
114
|
+
if (isMountedRef.current) {
|
|
115
|
+
setIsLoading(true);
|
|
116
|
+
setError(null);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(src);
|
|
121
|
+
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(`Failed to load animation: ${response.status} ${response.statusText}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
|
|
128
|
+
// Validate that it's a valid Lottie animation
|
|
129
|
+
if (!data || typeof data !== 'object' || !data.v || !data.layers) {
|
|
130
|
+
throw new Error('Invalid Lottie animation data');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Cache the loaded animation
|
|
134
|
+
if (cache) {
|
|
135
|
+
animationCache.set(src, data);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isMountedRef.current) {
|
|
139
|
+
setAnimationData(data);
|
|
140
|
+
setIsLoading(false);
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (isMountedRef.current) {
|
|
144
|
+
setError(err instanceof Error ? err : new Error('Failed to load animation'));
|
|
145
|
+
setIsLoading(false);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
loadAnimation();
|
|
151
|
+
}
|
|
152
|
+
}, [src, cache, retryCount]);
|
|
153
|
+
|
|
154
|
+
const retry = () => {
|
|
155
|
+
setRetryCount((prev) => prev + 1);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
animationData,
|
|
160
|
+
isLoading,
|
|
161
|
+
error,
|
|
162
|
+
retry,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
|
|
6
|
+
import { MermaidFullscreenModal } from './components/MermaidFullscreenModal';
|
|
7
|
+
import { useMermaidFullscreen } from './hooks/useMermaidFullscreen';
|
|
8
|
+
import { useMermaidRenderer } from './hooks/useMermaidRenderer';
|
|
9
|
+
|
|
10
|
+
interface MermaidProps {
|
|
11
|
+
chart: string;
|
|
12
|
+
className?: string;
|
|
13
|
+
isCompact?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Mermaid: React.FC<MermaidProps> = ({ chart, className = '', isCompact = false }) => {
|
|
17
|
+
const theme = useResolvedTheme();
|
|
18
|
+
|
|
19
|
+
// Rendering logic
|
|
20
|
+
const { mermaidRef, svgContent, isVertical, isRendering } = useMermaidRenderer({
|
|
21
|
+
chart,
|
|
22
|
+
theme,
|
|
23
|
+
isCompact,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Fullscreen modal logic
|
|
27
|
+
const {
|
|
28
|
+
isFullscreen,
|
|
29
|
+
fullscreenRef,
|
|
30
|
+
openFullscreen,
|
|
31
|
+
closeFullscreen,
|
|
32
|
+
handleBackdropClick,
|
|
33
|
+
} = useMermaidFullscreen();
|
|
34
|
+
|
|
35
|
+
const handleClick = () => {
|
|
36
|
+
if (svgContent) {
|
|
37
|
+
openFullscreen();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<div
|
|
44
|
+
className={`relative bg-card rounded-sm border border-border overflow-hidden cursor-pointer hover:shadow-sm transition-shadow ${className}`}
|
|
45
|
+
onClick={handleClick}
|
|
46
|
+
>
|
|
47
|
+
<div className="p-4 border-b border-border bg-muted/50">
|
|
48
|
+
<h6 className="text-sm font-semibold text-foreground">Diagram</h6>
|
|
49
|
+
<p className="text-xs text-muted-foreground mt-1">Click to view fullscreen</p>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="relative p-4 overflow-hidden">
|
|
52
|
+
<div
|
|
53
|
+
ref={mermaidRef}
|
|
54
|
+
className="flex justify-center items-center min-h-[200px]"
|
|
55
|
+
style={{ isolation: 'isolate' }}
|
|
56
|
+
/>
|
|
57
|
+
{isRendering && (
|
|
58
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm">
|
|
59
|
+
<div className="flex flex-col items-center gap-2">
|
|
60
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
61
|
+
<p className="text-xs text-muted-foreground">Rendering diagram...</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<MermaidFullscreenModal
|
|
69
|
+
isOpen={isFullscreen}
|
|
70
|
+
svgContent={svgContent}
|
|
71
|
+
isVertical={isVertical}
|
|
72
|
+
theme={theme}
|
|
73
|
+
chart={chart}
|
|
74
|
+
fullscreenRef={fullscreenRef}
|
|
75
|
+
onClose={closeFullscreen}
|
|
76
|
+
onBackdropClick={handleBackdropClick}
|
|
77
|
+
/>
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default Mermaid;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
interface MermaidCodeViewerProps {
|
|
6
|
+
chart: string;
|
|
7
|
+
renderPreview: () => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const MermaidCodeViewer: React.FC<MermaidCodeViewerProps> = ({
|
|
11
|
+
chart,
|
|
12
|
+
renderPreview,
|
|
13
|
+
}) => {
|
|
14
|
+
const [activeTab, setActiveTab] = useState<'preview' | 'code'>('preview');
|
|
15
|
+
const [copied, setCopied] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleCopy = async () => {
|
|
18
|
+
await navigator.clipboard.writeText(chart);
|
|
19
|
+
setCopied(true);
|
|
20
|
+
setTimeout(() => setCopied(false), 2000);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex flex-col h-full">
|
|
25
|
+
{/* Tabs */}
|
|
26
|
+
<div className="flex items-center justify-between border-b border-border px-4">
|
|
27
|
+
<div className="flex">
|
|
28
|
+
<button
|
|
29
|
+
onClick={() => setActiveTab('preview')}
|
|
30
|
+
className={`px-4 py-3 text-sm font-medium transition-colors relative ${
|
|
31
|
+
activeTab === 'preview'
|
|
32
|
+
? 'text-foreground'
|
|
33
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
34
|
+
}`}
|
|
35
|
+
>
|
|
36
|
+
Preview
|
|
37
|
+
{activeTab === 'preview' && (
|
|
38
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
|
39
|
+
)}
|
|
40
|
+
</button>
|
|
41
|
+
<button
|
|
42
|
+
onClick={() => setActiveTab('code')}
|
|
43
|
+
className={`px-4 py-3 text-sm font-medium transition-colors relative ${
|
|
44
|
+
activeTab === 'code'
|
|
45
|
+
? 'text-foreground'
|
|
46
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
47
|
+
}`}
|
|
48
|
+
>
|
|
49
|
+
Code
|
|
50
|
+
{activeTab === 'code' && (
|
|
51
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
|
|
52
|
+
)}
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Copy button - show only on Code tab */}
|
|
57
|
+
{activeTab === 'code' && (
|
|
58
|
+
<button
|
|
59
|
+
onClick={handleCopy}
|
|
60
|
+
className="flex items-center gap-2 px-3 py-1.5 text-xs font-medium bg-primary/10 hover:bg-primary/20 text-primary rounded transition-colors"
|
|
61
|
+
>
|
|
62
|
+
{copied ? (
|
|
63
|
+
<>
|
|
64
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
65
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
66
|
+
</svg>
|
|
67
|
+
Copied!
|
|
68
|
+
</>
|
|
69
|
+
) : (
|
|
70
|
+
<>
|
|
71
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
72
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
73
|
+
</svg>
|
|
74
|
+
Copy
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
</button>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* Content */}
|
|
82
|
+
<div className="flex-1 overflow-auto">
|
|
83
|
+
{activeTab === 'preview' ? (
|
|
84
|
+
<div className="p-6 flex items-center justify-center min-h-full">
|
|
85
|
+
{renderPreview()}
|
|
86
|
+
</div>
|
|
87
|
+
) : (
|
|
88
|
+
<pre className="p-6 text-sm font-mono text-foreground bg-muted/30 h-full overflow-auto">
|
|
89
|
+
<code>{chart}</code>
|
|
90
|
+
</pre>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useEffect } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
|
|
6
|
+
import { applyMermaidTextColors } from '../utils/mermaid-helpers';
|
|
7
|
+
import { MermaidCodeViewer } from './MermaidCodeViewer';
|
|
8
|
+
|
|
9
|
+
interface MermaidFullscreenModalProps {
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
svgContent: string;
|
|
12
|
+
isVertical: boolean;
|
|
13
|
+
theme: string;
|
|
14
|
+
chart: string;
|
|
15
|
+
fullscreenRef: React.RefObject<HTMLDivElement>;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
onBackdropClick: (e: React.MouseEvent) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const MermaidFullscreenModal: React.FC<MermaidFullscreenModalProps> = ({
|
|
21
|
+
isOpen,
|
|
22
|
+
svgContent,
|
|
23
|
+
isVertical,
|
|
24
|
+
theme,
|
|
25
|
+
chart,
|
|
26
|
+
fullscreenRef,
|
|
27
|
+
onClose,
|
|
28
|
+
onBackdropClick,
|
|
29
|
+
}) => {
|
|
30
|
+
// Apply text colors to fullscreen modal after render
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (isOpen && fullscreenRef.current) {
|
|
33
|
+
const getCSSVariable = (variable: string) => {
|
|
34
|
+
if (typeof document === 'undefined') return '';
|
|
35
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
|
|
36
|
+
return value ? `hsl(${value})` : '';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const textColor = theme === 'dark'
|
|
40
|
+
? getCSSVariable('--foreground') || 'hsl(0 0% 90%)'
|
|
41
|
+
: getCSSVariable('--foreground') || 'hsl(222.2 84% 4.9%)';
|
|
42
|
+
|
|
43
|
+
applyMermaidTextColors(fullscreenRef.current, textColor);
|
|
44
|
+
|
|
45
|
+
// Make SVG responsive
|
|
46
|
+
const svgElement = fullscreenRef.current.querySelector('svg');
|
|
47
|
+
if (svgElement) {
|
|
48
|
+
svgElement.style.display = 'block';
|
|
49
|
+
svgElement.style.height = 'auto';
|
|
50
|
+
svgElement.style.maxWidth = '100%';
|
|
51
|
+
|
|
52
|
+
// For vertical diagrams, limit width
|
|
53
|
+
if (isVertical) {
|
|
54
|
+
svgElement.style.maxWidth = '600px';
|
|
55
|
+
svgElement.style.margin = '0 auto';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, [isOpen, theme, isVertical, fullscreenRef]);
|
|
60
|
+
|
|
61
|
+
if (!isOpen || typeof document === 'undefined') return null;
|
|
62
|
+
|
|
63
|
+
return createPortal(
|
|
64
|
+
<div
|
|
65
|
+
className="fixed inset-0 z-9999 flex items-center justify-center p-4"
|
|
66
|
+
style={{ backgroundColor: 'rgb(0 0 0 / 0.75)' }}
|
|
67
|
+
onClick={onBackdropClick}
|
|
68
|
+
>
|
|
69
|
+
<div className={`relative bg-card rounded-sm shadow-xl max-h-[95vh] flex flex-col border border-border ${
|
|
70
|
+
isVertical
|
|
71
|
+
? 'w-auto min-w-[400px] max-w-[600px]'
|
|
72
|
+
: 'min-w-[600px] max-w-[90vw] w-auto'
|
|
73
|
+
}`}>
|
|
74
|
+
{/* Header */}
|
|
75
|
+
<div className="flex items-center justify-between py-4 px-6 border-b border-border">
|
|
76
|
+
<h3 className="text-sm font-medium text-foreground py-0 my-0">Diagram</h3>
|
|
77
|
+
<button
|
|
78
|
+
onClick={onClose}
|
|
79
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
80
|
+
>
|
|
81
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
82
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* Content - Code Viewer with tabs */}
|
|
88
|
+
<div className="flex-1 overflow-hidden">
|
|
89
|
+
<MermaidCodeViewer
|
|
90
|
+
chart={chart}
|
|
91
|
+
renderPreview={() => (
|
|
92
|
+
<div
|
|
93
|
+
ref={fullscreenRef}
|
|
94
|
+
dangerouslySetInnerHTML={{ __html: svgContent }}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>,
|
|
101
|
+
document.body
|
|
102
|
+
);
|
|
103
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for cleaning up orphaned Mermaid DOM nodes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
export function useMermaidCleanup() {
|
|
8
|
+
const cleanupMermaidErrors = useCallback(() => {
|
|
9
|
+
if (typeof document === 'undefined') return;
|
|
10
|
+
|
|
11
|
+
// Remove all orphaned mermaid elements from body
|
|
12
|
+
// Mermaid can append: SVGs, divs with errors, text nodes
|
|
13
|
+
|
|
14
|
+
// 1. Remove elements with mermaid-* IDs directly in body
|
|
15
|
+
document.querySelectorAll('[id^="mermaid-"]').forEach((node) => {
|
|
16
|
+
if (node.parentNode === document.body) {
|
|
17
|
+
node.remove();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 2. Remove elements with d prefix (mermaid diagram IDs) directly in body
|
|
22
|
+
document.querySelectorAll('[id^="d"]').forEach((node) => {
|
|
23
|
+
if (node.parentNode === document.body && node.id.match(/^d\d+$/)) {
|
|
24
|
+
node.remove();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// 3. Remove orphaned SVG elements that mermaid creates in body
|
|
29
|
+
document.querySelectorAll('body > svg').forEach((node) => {
|
|
30
|
+
// Check if it's a mermaid SVG (has mermaid classes or aria-roledescription)
|
|
31
|
+
if (node.getAttribute('aria-roledescription') ||
|
|
32
|
+
node.classList.contains('mermaid') ||
|
|
33
|
+
node.querySelector('.mermaid') ||
|
|
34
|
+
node.id?.includes('mermaid')) {
|
|
35
|
+
node.remove();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 4. Remove any orphaned error divs with "Syntax error" text
|
|
40
|
+
document.querySelectorAll('body > div').forEach((node) => {
|
|
41
|
+
const text = node.textContent || '';
|
|
42
|
+
if (text.includes('Syntax error in text') ||
|
|
43
|
+
text.includes('mermaid version') ||
|
|
44
|
+
node.id?.startsWith('mermaid-') ||
|
|
45
|
+
node.id?.startsWith('d') && node.id.match(/^d\d+$/)) {
|
|
46
|
+
node.remove();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 5. Remove orphaned pre elements with error info
|
|
51
|
+
document.querySelectorAll('body > pre').forEach((node) => {
|
|
52
|
+
const text = node.textContent || '';
|
|
53
|
+
if (text.includes('Syntax error') || text.includes('mermaid')) {
|
|
54
|
+
node.remove();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
// Cleanup on unmount
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
return () => {
|
|
62
|
+
cleanupMermaidErrors();
|
|
63
|
+
};
|
|
64
|
+
}, [cleanupMermaidErrors]);
|
|
65
|
+
|
|
66
|
+
// Also run cleanup periodically to catch any stray elements
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const interval = setInterval(cleanupMermaidErrors, 1000);
|
|
69
|
+
return () => clearInterval(interval);
|
|
70
|
+
}, [cleanupMermaidErrors]);
|
|
71
|
+
|
|
72
|
+
return { cleanupMermaidErrors };
|
|
73
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for managing Mermaid fullscreen modal
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
export function useMermaidFullscreen() {
|
|
8
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
9
|
+
const fullscreenRef = useRef<HTMLDivElement>(null);
|
|
10
|
+
|
|
11
|
+
const openFullscreen = () => setIsFullscreen(true);
|
|
12
|
+
const closeFullscreen = () => setIsFullscreen(false);
|
|
13
|
+
|
|
14
|
+
const handleBackdropClick = (e: React.MouseEvent) => {
|
|
15
|
+
if (e.target === e.currentTarget) {
|
|
16
|
+
closeFullscreen();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Handle ESC key
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const handleEscKey = (event: KeyboardEvent) => {
|
|
23
|
+
if (event.key === 'Escape' && isFullscreen) {
|
|
24
|
+
closeFullscreen();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (isFullscreen) {
|
|
29
|
+
document.addEventListener('keydown', handleEscKey);
|
|
30
|
+
document.body.style.overflow = 'hidden';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
document.removeEventListener('keydown', handleEscKey);
|
|
35
|
+
document.body.style.overflow = 'unset';
|
|
36
|
+
};
|
|
37
|
+
}, [isFullscreen]);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
isFullscreen,
|
|
41
|
+
fullscreenRef,
|
|
42
|
+
openFullscreen,
|
|
43
|
+
closeFullscreen,
|
|
44
|
+
handleBackdropClick,
|
|
45
|
+
};
|
|
46
|
+
}
|