feat: enhance asset browser panel with improved asset selection and loading states

- Added state management for asset selection to prevent multiple simultaneous selections.
- Implemented request sequence tracking to ensure accurate loading state handling during asset searches.
- Enhanced error handling and user feedback for asset loading failures.
- Updated UI elements to improve accessibility and user experience during asset browsing.
This commit is contained in:
Matthias
2026-03-27 21:26:29 +01:00
parent bc3bbf9d69
commit 8e4e2fcac1
9 changed files with 278 additions and 155 deletions

View File

@@ -1,8 +1,37 @@
"use client";
import type { ReactNode } from "react";
import { useCallback, useRef, type ReactNode } from "react";
import { NodeResizeControl, type ShouldResize } from "@xyflow/react";
import { NodeErrorBoundary } from "./node-error-boundary";
interface ResizeConfig {
minWidth: number;
minHeight: number;
keepAspectRatio?: boolean;
contentAware?: boolean;
}
const RESIZE_CONFIGS: Record<string, ResizeConfig> = {
frame: { minWidth: 200, minHeight: 150 },
group: { minWidth: 150, minHeight: 100 },
image: { minWidth: 100, minHeight: 80, keepAspectRatio: true },
asset: { minWidth: 100, minHeight: 80, keepAspectRatio: true },
"ai-image": { minWidth: 200, minHeight: 200 },
compare: { minWidth: 300, minHeight: 200 },
prompt: { minWidth: 240, minHeight: 200, contentAware: true },
text: { minWidth: 180, minHeight: 80, contentAware: true },
note: { minWidth: 160, minHeight: 80, contentAware: true },
};
const DEFAULT_CONFIG: ResizeConfig = { minWidth: 80, minHeight: 50, contentAware: true };
const CORNERS = [
"top-left",
"top-right",
"bottom-left",
"bottom-right",
] as const;
interface BaseNodeWrapperProps {
nodeType: string;
selected?: boolean;
@@ -20,6 +49,9 @@ export default function BaseNodeWrapper({
children,
className = "",
}: BaseNodeWrapperProps) {
const wrapperRef = useRef<HTMLDivElement | null>(null);
const config = RESIZE_CONFIGS[nodeType] ?? DEFAULT_CONFIG;
const statusStyles: Record<string, string> = {
idle: "",
analyzing: "border-yellow-400 animate-pulse",
@@ -29,8 +61,35 @@ export default function BaseNodeWrapper({
error: "border-red-500",
};
const shouldResize: ShouldResize = useCallback(
(event, params) => {
if (!wrapperRef.current || !config.contentAware) return true;
const contentEl = wrapperRef.current;
const paddingX =
parseFloat(getComputedStyle(contentEl).paddingLeft) +
parseFloat(getComputedStyle(contentEl).paddingRight);
const paddingY =
parseFloat(getComputedStyle(contentEl).paddingTop) +
parseFloat(getComputedStyle(contentEl).paddingBottom);
const minW = Math.max(
config.minWidth,
contentEl.scrollWidth - paddingX + paddingX * 0.5,
);
const minH = Math.max(
config.minHeight,
contentEl.scrollHeight - paddingY + paddingY * 0.5,
);
return params.width >= minW && params.height >= minH;
},
[config],
);
return (
<div
ref={wrapperRef}
className={`
rounded-xl border bg-card shadow-sm transition-shadow
${selected ? "ring-2 ring-primary shadow-md" : ""}
@@ -38,6 +97,61 @@ export default function BaseNodeWrapper({
${className}
`}
>
{selected &&
CORNERS.map((corner) => (
<NodeResizeControl
key={corner}
position={corner}
minWidth={config.minWidth}
minHeight={config.minHeight}
keepAspectRatio={config.keepAspectRatio}
shouldResize={shouldResize}
style={{
background: "none",
border: "none",
width: 12,
height: 12,
}}
>
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
className="text-primary/70"
>
<path
d={
corner === "bottom-right"
? "M11 5V11H5"
: corner === "bottom-left"
? "M1 5V11H7"
: corner === "top-right"
? "M11 7V1H5"
: "M1 7V1H7"
}
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<circle
cx={
corner === "bottom-right" || corner === "top-right"
? "11"
: "1"
}
cy={
corner === "bottom-right" || corner === "bottom-left"
? "11"
: "1"
}
r="1.5"
fill="currentColor"
/>
</svg>
</NodeResizeControl>
))}
<NodeErrorBoundary nodeType={nodeType}>{children}</NodeErrorBoundary>
{status === "error" && statusMessage && (
<div className="px-3 pb-2 text-xs text-red-500 truncate">