fix(canvas): resolve hook rule violations in delete and image nodes

This commit is contained in:
2026-04-03 18:05:02 +02:00
parent c33e65b0f6
commit af3cb47247
2 changed files with 18 additions and 20 deletions

View File

@@ -99,7 +99,7 @@ export function useCanvasDeleteHandlers({
return true; return true;
}, },
[], [t],
); );
const onNodesDelete = useCallback( const onNodesDelete = useCallback(

View File

@@ -84,7 +84,7 @@ export default function ImageNode({
const generateUploadUrl = useMutation(api.storage.generateUploadUrl); const generateUploadUrl = useMutation(api.storage.generateUploadUrl);
const { queueNodeDataUpdate, queueNodeResize, status } = useCanvasSync(); const { queueNodeDataUpdate, queueNodeResize, status } = useCanvasSync();
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [isUploading, setIsUploading] = useState(false); const [uploadPhase, setUploadPhase] = useState<"idle" | "uploading" | "syncing">("idle");
const [uploadProgress, setUploadProgress] = useState(0); const [uploadProgress, setUploadProgress] = useState(0);
const [pendingUploadStorageId, setPendingUploadStorageId] = useState<string | null>( const [pendingUploadStorageId, setPendingUploadStorageId] = useState<string | null>(
null, null,
@@ -93,19 +93,13 @@ export default function ImageNode({
const [isFullscreenOpen, setIsFullscreenOpen] = useState(false); const [isFullscreenOpen, setIsFullscreenOpen] = useState(false);
const hasAutoSizedRef = useRef(false); const hasAutoSizedRef = useRef(false);
useEffect(() => { const isPendingUploadSynced =
if (!isUploading || !pendingUploadStorageId) return; pendingUploadStorageId !== null &&
if (
data.storageId === pendingUploadStorageId && data.storageId === pendingUploadStorageId &&
typeof data.url === "string" && typeof data.url === "string" &&
data.url.length > 0 data.url.length > 0;
) { const isWaitingForCanvasSync = pendingUploadStorageId !== null && !isPendingUploadSynced;
setIsUploading(false); const isUploading = uploadPhase !== "idle" || isWaitingForCanvasSync;
setPendingUploadStorageId(null);
setUploadProgress(0);
}
}, [data.storageId, data.url, isUploading, pendingUploadStorageId]);
useEffect(() => { useEffect(() => {
if (typeof id === "string" && id.startsWith(OPTIMISTIC_NODE_PREFIX)) { if (typeof id === "string" && id.startsWith(OPTIMISTIC_NODE_PREFIX)) {
@@ -170,7 +164,7 @@ export default function ImageNode({
return; return;
} }
setIsUploading(true); setUploadPhase("uploading");
setUploadProgress(0); setUploadProgress(0);
setPendingUploadStorageId(null); setPendingUploadStorageId(null);
@@ -216,6 +210,7 @@ export default function ImageNode({
setUploadProgress(100); setUploadProgress(100);
setPendingUploadStorageId(storageId); setPendingUploadStorageId(storageId);
setUploadPhase("syncing");
await queueNodeDataUpdate({ await queueNodeDataUpdate({
nodeId: id as Id<"nodes">, nodeId: id as Id<"nodes">,
@@ -241,6 +236,7 @@ export default function ImageNode({
} }
toast.success(t('canvas.imageUploaded')); toast.success(t('canvas.imageUploaded'));
setUploadPhase("idle");
} catch (err) { } catch (err) {
console.error("Upload failed:", err); console.error("Upload failed:", err);
setPendingUploadStorageId(null); setPendingUploadStorageId(null);
@@ -249,7 +245,7 @@ export default function ImageNode({
err instanceof Error ? err.message : undefined, err instanceof Error ? err.message : undefined,
); );
setUploadProgress(0); setUploadProgress(0);
setIsUploading(false); setUploadPhase("idle");
} }
}, },
[ [
@@ -259,6 +255,7 @@ export default function ImageNode({
queueNodeDataUpdate, queueNodeDataUpdate,
queueNodeResize, queueNodeResize,
status.isOffline, status.isOffline,
t,
], ],
); );
@@ -312,8 +309,9 @@ export default function ImageNode({
}, [isUploading]); }, [isUploading]);
const showFilename = Boolean(data.filename && data.url); const showFilename = Boolean(data.filename && data.url);
const effectiveUploadProgress = isWaitingForCanvasSync ? 100 : uploadProgress;
const uploadingLabel = const uploadingLabel =
uploadProgress === 100 && pendingUploadStorageId isWaitingForCanvasSync
? "100% — wird synchronisiert…" ? "100% — wird synchronisiert…"
: "Wird hochgeladen…"; : "Wird hochgeladen…";
@@ -365,10 +363,10 @@ export default function ImageNode({
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<span className="text-xs text-muted-foreground">{uploadingLabel}</span> <span className="text-xs text-muted-foreground">{uploadingLabel}</span>
<div className="w-40"> <div className="w-40">
<Progress value={uploadProgress} className="h-1.5" /> <Progress value={effectiveUploadProgress} className="h-1.5" />
</div> </div>
<span className="text-[11px] text-muted-foreground"> <span className="text-[11px] text-muted-foreground">
{uploadProgress}% {effectiveUploadProgress}%
</span> </span>
</div> </div>
</div> </div>