| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- import { useParams } from 'common'
- import { AlertCircle, LoaderCircle, X } from 'lucide-react'
- import SVG from 'react-inlinesvg'
- import { Button } from 'ui'
- import { StorageItemWithColumn } from '../Storage.types'
- import { useFetchFileUrlQuery } from '../StorageExplorer/useFetchFileUrlQuery'
- import { useBucketFilePickerStateSnapshot } from './BucketFilePickerState'
- import { BASE_PATH } from '@/lib/constants'
- import { formatBytes } from '@/lib/helpers'
- const PREVIEW_SIZE_LIMIT = 10 * 1024 * 1024 // 10MB
- const PreviewFile = ({ item }: { item: StorageItemWithColumn }) => {
- const { ref: projectRef } = useParams()
- const { bucket, columns } = useBucketFilePickerStateSnapshot()
- const path = columns.slice(0, item.columnIndex).concat(item.name).join('/')
- const { data: previewUrl, isPending: isLoading } = useFetchFileUrlQuery({
- path,
- projectRef: projectRef!,
- bucket,
- })
- // if the size is not available, we set it to be greater than the max size
- const size = +(item.metadata?.size ?? PREVIEW_SIZE_LIMIT + 1)
- const mimeType = item.metadata?.mimetype
- const isSkipped = !!mimeType && !!size && size > PREVIEW_SIZE_LIMIT
- if (isLoading) {
- return (
- <div className="flex h-full w-full items-center justify-center text-foreground-lighter">
- <LoaderCircle size={14} strokeWidth={2} className="animate-spin text-foreground-lighter" />
- </div>
- )
- }
- if (isSkipped) {
- return (
- <div className="flex h-full w-full flex-col items-center justify-center">
- <SVG
- src={`${BASE_PATH}/img/file-filled.svg`}
- preProcessor={(code) =>
- code.replace(/svg/, 'svg class="mx-auto w-32 h-32 text-color-inherit opacity-75"')
- }
- />
- <p className="mt-2 w-2/5 text-center text-sm">
- File size is too large to preview in the explorer
- </p>
- </div>
- )
- }
- if (!mimeType || !previewUrl) {
- return (
- <SVG
- src={`${BASE_PATH}/img/file-filled.svg`}
- preProcessor={(code) =>
- code.replace(/svg/, 'svg class="mx-auto w-32 h-32 text-color-inherit opacity-75"')
- }
- />
- )
- }
- if (mimeType.includes('image')) {
- return (
- <div
- className="flex h-full w-full items-center justify-center bg-contain bg-center bg-no-repeat"
- style={{ backgroundImage: `url('${previewUrl}')` }}
- />
- )
- }
- if (mimeType.includes('audio')) {
- return (
- <div className="flex h-full w-full items-center justify-center px-10">
- <audio key={previewUrl} controls style={{ width: 'inherit' }}>
- <source src={previewUrl} type="audio/mpeg" />
- <p className="text-sm text-foreground-light">
- Your browser does not support the audio element.
- </p>
- </audio>
- </div>
- )
- }
- if (mimeType.includes('video')) {
- return (
- <div className="flex h-full w-full items-center justify-center">
- <video key={previewUrl} controls style={{ maxHeight: '100%' }}>
- <source src={previewUrl} type="video/mp4" />
- <p className="text-sm text-foreground-light">
- Your browser does not support the video tag.
- </p>
- </video>
- </div>
- )
- }
- return (
- <SVG
- src={`${BASE_PATH}/img/file-filled.svg`}
- preProcessor={(code) =>
- code.replace(/svg/, 'svg class="mx-auto w-32 h-32 text-color-inherit opacity-75"')
- }
- />
- )
- }
- export const PreviewPane = ({ onSelect }: { onSelect: (url: string) => void }) => {
- const { ref: projectRef } = useParams()
- const {
- selectedFilePreview: file,
- setSelectedFilePreview,
- bucket,
- columns,
- } = useBucketFilePickerStateSnapshot()
- const path = file ? columns.slice(0, file.columnIndex).concat(file.name).join('/') : ''
- const { data: previewUrl, isLoading } = useFetchFileUrlQuery(
- { path, projectRef: projectRef!, bucket },
- { enabled: !!file }
- )
- if (!file) return null
- const size = file.metadata ? formatBytes(file.metadata.size) : null
- const mimeType = file.metadata ? file.metadata.mimetype : undefined
- const createdAt = file.created_at ? new Date(file.created_at).toLocaleString() : 'Unknown'
- const updatedAt = file.updated_at ? new Date(file.updated_at).toLocaleString() : 'Unknown'
- return (
- <div className="h-full border-l border-overlay bg-surface-100 overflow-y-auto w-[450px] pb-4">
- {/* Preview Header */}
- <div className="flex w-full justify-end items-center gap-2 sticky top-0 bg-surface-100 p-4 border-b">
- <Button
- size="tiny"
- onClick={() => onSelect(previewUrl!)}
- disabled={!previewUrl}
- loading={isLoading}
- >
- Select
- </Button>
- <div className="text-foreground-lighter transition-colors hover:text-foreground">
- <X
- className="cursor-pointer"
- size={14}
- strokeWidth={2}
- onClick={() => setSelectedFilePreview(undefined)}
- />
- </div>
- </div>
- {/* Preview Thumbnail*/}
- <div className="my-4 border border-overlay mx-4">
- <div className="flex h-56 w-full items-center 2xl:h-72">
- <PreviewFile item={file} />
- </div>
- </div>
- <div className="w-full space-y-6 px-4">
- {/* Preview Information */}
- <div className="space-y-1">
- <h5 className="wrap-break-word text-base text-foreground">{file.name}</h5>
- {file.isCorrupted && (
- <div className="flex items-center space-x-2">
- <AlertCircle size={14} strokeWidth={2} className="text-foreground-light" />
- <p className="text-sm text-foreground-light">
- File is corrupted, please delete and reupload this file again
- </p>
- </div>
- )}
- {mimeType && (
- <p className="text-sm text-foreground-light">
- {mimeType}
- {size && <span> - {size}</span>}
- </p>
- )}
- </div>
- {/* Preview Metadata */}
- <div className="space-y-2">
- <div>
- <label className="mb-1 text-xs text-foreground-lighter">Added on</label>
- <p className="text-sm text-foreground-light">{createdAt}</p>
- </div>
- <div>
- <label className="mb-1 text-xs text-foreground-lighter">Last modified</label>
- <p className="text-sm text-foreground-light">{updatedAt}</p>
- </div>
- </div>
- </div>
- </div>
- )
- }
|