From 2d981c56e82e77a1465929ace874be53abb401a5 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 11 Jul 2026 17:24:21 -0700 Subject: [PATCH] improvement(files): show loading spinner in file preview content area --- .../components/file-viewer/image-preview.tsx | 27 +++++++++------ .../components/file-viewer/preview-shared.tsx | 33 ++++++++++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.tsx index e4ba8033261..d65fd1d928b 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.tsx @@ -1,12 +1,14 @@ 'use client' -import { memo } from 'react' +import { memo, useState } from 'react' import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace' import { useFileContentSource } from '@/hooks/use-file-content-source' +import { PREVIEW_LOADING_OVERLAY } from './preview-shared' import { ZoomablePreview } from './zoomable-preview' export const ImagePreview = memo(function ImagePreview({ file }: { file: WorkspaceFileRecord }) { const source = useFileContentSource() + const [hasSettled, setHasSettled] = useState(false) // Version the URL on updatedAt: overwrites keep the same storage key, so an unversioned // URL would resolve to a previously cached copy instead of the rewritten bytes. const serveUrl = source.buildUrl(file.key, { @@ -14,14 +16,19 @@ export const ImagePreview = memo(function ImagePreview({ file }: { file: Workspa }) return ( - - {file.name} - +
+ + {file.name} setHasSettled(true)} + onError={() => setHasSettled(true)} + /> + + {!hasSettled && PREVIEW_LOADING_OVERLAY} +
) }) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-shared.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-shared.tsx index 4796ae6bb1e..041a0225074 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-shared.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/preview-shared.tsx @@ -2,6 +2,7 @@ import { Component, type ErrorInfo, type ReactNode } from 'react' import { cn } from '@sim/emcn' +import { Loader } from '@sim/emcn/icons' import { createLogger } from '@sim/logger' const logger = createLogger('FilePreview') @@ -87,13 +88,20 @@ export function resolvePreviewError( return renderError } +/** Canonical content-area loading spinner, matching the rest of the app. */ +const PREVIEW_LOADING_SPINNER = ( + +) + /** - * Canonical blank loading overlay for previews that render into a - * `--surface-1` canvas. Absolutely covers the canvas (with `z-10` so it - * paints above in-flow render targets) until the preview is ready. + * Canonical loading overlay for previews that render into a `--surface-1` + * canvas. Absolutely covers the canvas (with `z-10` so it paints above + * in-flow render targets) with a centered spinner until the preview is ready. */ export const PREVIEW_LOADING_OVERLAY = ( -
+
+ {PREVIEW_LOADING_SPINNER} +
) interface PreviewLoadingFrameProps { @@ -104,14 +112,21 @@ interface PreviewLoadingFrameProps { } /** - * Canonical in-flow blank loading frame shown while a preview is fetching or - * rendering. The `tone` must match the background of the loaded state it is - * standing in for, so mount completion does not flash a different token. + * Canonical in-flow loading frame with a centered spinner, shown while a + * preview is fetching or rendering. The `tone` must match the background of + * the loaded state it is standing in for, so mount completion does not flash + * a different token. */ export function PreviewLoadingFrame({ className, tone = 'bg' }: PreviewLoadingFrameProps) { return (
+ className={cn( + 'flex items-center justify-center', + tone === 'surface' ? 'bg-[var(--surface-1)]' : 'bg-[var(--bg)]', + className + )} + > + {PREVIEW_LOADING_SPINNER} +
) }