Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
'use client'

import { memo } from 'react'
import { memo, useState } from 'react'
Comment thread
waleedlatif1 marked this conversation as resolved.
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, {
version: Number(new Date(file.updatedAt)) || file.size,
})
Comment thread
waleedlatif1 marked this conversation as resolved.

return (
Comment thread
waleedlatif1 marked this conversation as resolved.
<ZoomablePreview className='flex flex-1' contentClassName='h-full w-full'>
<img
src={serveUrl}
alt={file.name}
className='max-h-full max-w-full select-none rounded-md object-contain'
draggable={false}
loading='eager'
/>
</ZoomablePreview>
<div className='relative flex min-h-0 flex-1 flex-col'>
<ZoomablePreview className='flex flex-1' contentClassName='h-full w-full'>
<img
src={serveUrl}
alt={file.name}
className='max-h-full max-w-full select-none rounded-md object-contain'
draggable={false}
loading='eager'
onLoad={() => setHasSettled(true)}
onError={() => setHasSettled(true)}
/>
</ZoomablePreview>
{!hasSettled && PREVIEW_LOADING_OVERLAY}
Comment thread
waleedlatif1 marked this conversation as resolved.
</div>
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -87,13 +88,20 @@ export function resolvePreviewError(
return renderError
}

/** Canonical content-area loading spinner, matching the rest of the app. */
const PREVIEW_LOADING_SPINNER = (
<Loader className='size-[20px] text-[var(--text-secondary)]' animate />
)

/**
* 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 = (
<div className='absolute inset-0 z-10 bg-[var(--surface-1)]' />
<div className='absolute inset-0 z-10 flex items-center justify-center bg-[var(--surface-1)]'>
{PREVIEW_LOADING_SPINNER}
</div>
)

interface PreviewLoadingFrameProps {
Expand All @@ -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 (
<div
className={cn(tone === 'surface' ? 'bg-[var(--surface-1)]' : 'bg-[var(--bg)]', className)}
/>
className={cn(
'flex items-center justify-center',
tone === 'surface' ? 'bg-[var(--surface-1)]' : 'bg-[var(--bg)]',
className
)}
>
{PREVIEW_LOADING_SPINNER}
</div>
)
}
Loading