create additional refs and playground

This commit is contained in:
Nate Kelley 2025-07-29 17:34:49 -06:00
parent 14e1680f43
commit 8a5ee5b417
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 93 additions and 63 deletions

View File

@ -7,6 +7,8 @@ import type { ReportElements } from '@buster/server-shared/reports';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { mainApiV2 } from '@/api/buster_rest/instances'; import { mainApiV2 } from '@/api/buster_rest/instances';
import { useDebounceEffect } from '@/hooks'; import { useDebounceEffect } from '@/hooks';
import { useThemesConfig } from '@/components/ui/report/ThemeWrapper/useThemesConfig';
import { cn } from '@/lib/utils';
const ReportEditor = dynamic( const ReportEditor = dynamic(
() => import('@/components/ui/report/ReportEditor').then((mod) => mod.ReportEditor), () => import('@/components/ui/report/ReportEditor').then((mod) => mod.ReportEditor),
@ -14,6 +16,66 @@ const ReportEditor = dynamic(
); );
// Status indicator component with dynamic backgrounds // Status indicator component with dynamic backgrounds
export const ReportPlayground: React.FC = () => {
const [markdown, setMarkdown] = useState<string>('');
const [hasBeenSuccessFullAtLeastOnce, setHasBeenSuccessFullAtLeastOnce] = useState(false);
const { data, refetch, isLoading, isFetched, error } = useQuery({
queryKey: ['report-playground', markdown],
queryFn: () => {
return mainApiV2
.post<{ elements: ReportElements }>('/temp/validate-markdown', { markdown })
.then((res) => {
setHasBeenSuccessFullAtLeastOnce(true);
return res.data;
});
},
enabled: false
});
useDebounceEffect(
() => {
if (markdown.length > 0) {
refetch();
}
},
[markdown, refetch],
{ wait: 150 }
);
const usedValue: ReportElements = hasBeenSuccessFullAtLeastOnce ? data?.elements || [] : value;
return (
<div className="grid max-h-screen min-h-screen grid-cols-[270px_1fr] gap-5 rounded border p-7">
<div className="flex h-full flex-col space-y-5">
<InputTextArea
className="flex-1 resize-none"
placeholder="Put markdown here"
value={markdown}
onChange={(e) => setMarkdown(e.target.value)}
/>
<ValidationStatus isLoading={isLoading} error={error} isFetched={isFetched} data={data} />
{data && (
<div className="max-h-[28vh] min-h-0 flex-1">
<div className="flex h-full flex-col">
<h3 className="mb-2 text-sm font-medium text-gray-700">Successful Response:</h3>
<pre className="flex-1 overflow-auto rounded border bg-gray-50 p-3 font-mono text-xs">
{JSON.stringify(data.elements, null, 2)}
</pre>
</div>
</div>
)}
<ThemePicker />
</div>
<div className="bg-background h-full max-h-[calc(100vh-56px)] overflow-y-auto rounded border shadow">
<ReportEditor value={usedValue} readOnly={false} />
</div>
</div>
);
};
interface ValidationStatusProps { interface ValidationStatusProps {
isLoading: boolean; isLoading: boolean;
error: unknown; error: unknown;
@ -92,62 +154,37 @@ const ValidationStatus: React.FC<ValidationStatusProps> = ({
); );
}; };
export const ReportPlayground: React.FC = () => { const ThemePicker = React.memo(() => {
const [markdown, setMarkdown] = useState<string>(''); const { activeTheme, setActiveTheme, allThemes } = useThemesConfig();
const [hasBeenSuccessFullAtLeastOnce, setHasBeenSuccessFullAtLeastOnce] = useState(false);
const { data, refetch, isLoading, isFetched, error } = useQuery({ const themesList = Object.values(allThemes);
queryKey: ['report-playground', markdown],
queryFn: () => {
return mainApiV2
.post<{ elements: ReportElements }>('/temp/validate-markdown', { markdown })
.then((res) => {
setHasBeenSuccessFullAtLeastOnce(true);
return res.data;
});
},
enabled: false
});
useDebounceEffect(
() => {
if (markdown.length > 0) {
refetch();
}
},
[markdown, refetch],
{ wait: 150 }
);
const usedValue: ReportElements = hasBeenSuccessFullAtLeastOnce ? data?.elements || [] : value;
return ( return (
<div className="grid max-h-screen min-h-screen grid-cols-[400px_1fr] gap-5 rounded border p-7"> <div className="bg-background flex gap-x-2 overflow-x-auto rounded border p-2">
<div className="flex h-full flex-col space-y-5"> {themesList.map((theme) => {
<InputTextArea const firstThreeColors = Object.values(theme.light).slice(0, 3);
className="flex-1 resize-none"
placeholder="Put markdown here" const isActive = activeTheme.id === theme.id;
value={markdown} return (
onChange={(e) => setMarkdown(e.target.value)} <div
/> key={theme.id}
<ValidationStatus isLoading={isLoading} error={error} isFetched={isFetched} data={data} /> className={cn(
{data && ( 'min-h-7 min-w-7 cursor-pointer rounded-full border transition-all duration-200 hover:scale-110',
<div className="max-h-[28vh] min-h-0 flex-1"> isActive && 'border-primary shadow-2xl'
<div className="flex h-full flex-col"> )}
<h3 className="mb-2 text-sm font-medium text-gray-700">Successful Response:</h3> style={{
<pre className="flex-1 overflow-auto rounded border bg-gray-50 p-3 font-mono text-xs"> background: `linear-gradient(0deg, hsl(${firstThreeColors[0]}) 0%, hsl(${firstThreeColors[0]}) 33%, hsl(${firstThreeColors[1]}) 33%, hsl(${firstThreeColors[1]}) 66%, hsl(${firstThreeColors[2]}) 66%, hsl(${firstThreeColors[2]}) 100%)`
{JSON.stringify(data.elements, null, 2)} }}
</pre> onClick={() => setActiveTheme(theme)}
</div> title={theme.id}
</div> />
)} );
</div> })}
<div className="bg-background h-full max-h-[calc(100vh-56px)] overflow-y-auto rounded border shadow">
<ReportEditor value={usedValue} readOnly={false} />
</div>
</div> </div>
); );
}; });
ThemePicker.displayName = 'ThemePicker';
const value: ReportElements = [ const value: ReportElements = [
{ {

View File

@ -59,7 +59,7 @@ export const ReportEditor = React.memo(
variant={variant} variant={variant}
readonly={readOnly} readonly={readOnly}
disabled={disabled} disabled={disabled}
className={cn('pb-[20vh]', className)}> className={cn('pb-[15vh]', className)}>
<Editor style={style} placeholder={placeholder} disabled={disabled} /> <Editor style={style} placeholder={placeholder} disabled={disabled} />
</EditorContainer> </EditorContainer>
</Plate> </Plate>

View File

@ -13,5 +13,5 @@ export function useThemesConfig() {
const activeTheme = useThemesConfigStore((state) => state.activeTheme); const activeTheme = useThemesConfigStore((state) => state.activeTheme);
const setActiveTheme = useThemesConfigStore((state) => state.setActiveTheme); const setActiveTheme = useThemesConfigStore((state) => state.setActiveTheme);
return { activeTheme, setActiveTheme }; return { activeTheme, setActiveTheme, allThemes: THEMES };
} }

View File

@ -275,7 +275,7 @@ function withTooltip<T extends React.ElementType>(Component: T) {
tooltip, tooltip,
tooltipContentProps, tooltipContentProps,
tooltipProps, tooltipProps,
tooltipTriggerProps, ref,
...props ...props
}: TooltipProps<T>) { }: TooltipProps<T>) {
const [mounted, setMounted] = React.useState(false); const [mounted, setMounted] = React.useState(false);
@ -287,15 +287,7 @@ function withTooltip<T extends React.ElementType>(Component: T) {
const component = <Component {...(props as React.ComponentProps<T>)} />; const component = <Component {...(props as React.ComponentProps<T>)} />;
if (tooltip && mounted) { if (tooltip && mounted) {
return ( return <Tooltip title={tooltip}>{component}</Tooltip>;
<Tooltip {...tooltipProps}>
<TooltipTrigger asChild {...tooltipTriggerProps}>
{component}
</TooltipTrigger>
<TooltipContent {...tooltipContentProps}>{tooltip}</TooltipContent>
</Tooltip>
);
} }
return component; return component;

View File

@ -2,6 +2,7 @@
@import './tailwindAnimations.css'; @import './tailwindAnimations.css';
@plugin 'tailwind-scrollbar'; @plugin 'tailwind-scrollbar';
@plugin "tailwindcss-animate"; @plugin "tailwindcss-animate";
@plugin 'tailwind-scrollbar-hide';
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));