diff --git a/src/components/WellShow/Attachments.tsx b/src/components/WellShow/Attachments.tsx index bb4414af..a5fd140f 100644 --- a/src/components/WellShow/Attachments.tsx +++ b/src/components/WellShow/Attachments.tsx @@ -3,13 +3,12 @@ import { Box, ButtonBase, IconButton, - Link, Paper, Stack, Typography, Tooltip, + Button, } from '@mui/material' -import { DataGrid, GridColDef } from '@mui/x-data-grid' import { ChevronLeft, ChevronRight, @@ -18,68 +17,40 @@ import { ViewCarousel, } from '@mui/icons-material' import { Masonry } from '@mui/lab' -import { settings } from '@/settings' import type { IAsset } from '@/interfaces/ocotillo' +import { QueryObserverResult } from '@tanstack/react-query' +import { GetListResponse, HttpError } from '@refinedev/core' -type ImageViewMode = 'grid' | 'slideshow' +type PreviewViewMode = 'grid' | 'slideshow' + +const isImage = (asset: IAsset) => asset.mime_type?.startsWith('image/') + +const isPdf = (asset: IAsset) => asset.mime_type === 'application/pdf' + +const isText = (asset: IAsset) => asset.mime_type === 'text/plain' + +const canPreview = (asset: IAsset) => + Boolean(asset.signed_url) && (isImage(asset) || isPdf(asset) || isText(asset)) export const AttachmentsCard = ({ assets, isLoading, + refetchAssets, }: { assets: IAsset[] isLoading: boolean + refetchAssets: () => Promise< + QueryObserverResult, HttpError> + > }) => { - const [imageViewMode, setImageViewMode] = useState('grid') + const [previewViewMode, setPreviewViewMode] = + useState('grid') const [slideshowIndex, setSlideshowIndex] = useState(0) - const imageAssets = useMemo( - () => assets.filter((a: { signed_url?: string }) => a?.signed_url), - [assets] - ) - - const columns = useMemo[]>( - () => [ - { field: 'name', headerName: 'Name', minWidth: 150 }, - { - field: 'uri', - headerName: 'URL', - flex: 1, - minWidth: 200, - renderCell: ({ value }) => { - const href = typeof value === 'string' ? value : '' - if (!href) { - return ( - - N/A - - ) - } - return ( - - {href} - - ) - }, - }, - ], - [] - ) + const previewAssets = useMemo(() => assets.filter(canPreview), [assets]) - const currentImage = imageAssets[slideshowIndex] - const hasImages = imageAssets.length > 0 + const currentAsset = previewAssets[slideshowIndex] + const hasAssets = previewAssets.length > 0 return ( @@ -108,7 +79,7 @@ export const AttachmentsCard = ({ ) : ( {/* Images section (above table) with view toggle */} - {hasImages && ( + {hasAssets && ( setImageViewMode('grid')} - aria-pressed={imageViewMode === 'grid'} + color={previewViewMode === 'grid' ? 'primary' : 'default'} + onClick={() => setPreviewViewMode('grid')} + aria-pressed={previewViewMode === 'grid'} > @@ -131,29 +102,29 @@ export const AttachmentsCard = ({ { - setImageViewMode('slideshow') + setPreviewViewMode('slideshow') setSlideshowIndex(0) }} - aria-pressed={imageViewMode === 'slideshow'} + aria-pressed={previewViewMode === 'slideshow'} > - {imageViewMode === 'grid' ? ( + {previewViewMode === 'grid' ? ( - {imageAssets.map((img, idx) => ( + {previewAssets.map((asset, idx) => ( { setSlideshowIndex(idx) - setImageViewMode('slideshow') + setPreviewViewMode('slideshow') }} sx={{ display: 'block', @@ -164,15 +135,10 @@ export const AttachmentsCard = ({ textAlign: 'left', }} > - ))} @@ -191,23 +157,20 @@ export const AttachmentsCard = ({ justifyContent: 'center', }} > - - {imageAssets.length > 1 && ( + {currentAsset && ( + + )} + {previewAssets.length > 1 && ( <> setSlideshowIndex((i) => - i === 0 ? imageAssets.length - 1 : i - 1 + i === 0 ? previewAssets.length - 1 : i - 1 ) } sx={{ @@ -225,7 +188,7 @@ export const AttachmentsCard = ({ aria-label="Next image" onClick={() => setSlideshowIndex((i) => - i === imageAssets.length - 1 ? 0 : i + 1 + i === previewAssets.length - 1 ? 0 : i + 1 ) } sx={{ @@ -253,7 +216,7 @@ export const AttachmentsCard = ({ borderRadius: 1, }} > - {slideshowIndex + 1} / {imageAssets.length} + {slideshowIndex + 1} / {previewAssets.length} )} @@ -261,21 +224,212 @@ export const AttachmentsCard = ({ )} )} - - )} ) } + +const previewStyles = { + grid: { + image: { + width: '100%', + display: 'block', + verticalAlign: 'bottom', + }, + frame: { + width: '100%', + height: 220, + border: 0, + bgcolor: 'background.paper', + }, + }, + slideshow: { + image: { + width: '100%', + maxWidth: '100%', + maxHeight: 400, + objectFit: 'contain', + display: 'block', + }, + frame: { + width: '100%', + height: 400, + border: 0, + bgcolor: 'background.paper', + }, + }, +} as const + +const AssetPreview = ({ + asset, + variant, +}: { + asset: IAsset + variant: 'grid' | 'slideshow' +}) => { + if (isImage(asset)) { + return ( + + ) + } + + if (isPdf(asset) || isText(asset)) { + return ( + + ) + } + + return ( + + Preview not available. + + ) +} + +const AssetPreviewWithOverlay = ({ + asset, + variant, + refetchAssets, +}: { + asset: IAsset + variant: 'grid' | 'slideshow' + refetchAssets: () => Promise< + QueryObserverResult, HttpError> + > +}) => { + const isSlideshow = variant === 'slideshow' + + const downloadAsset = async ( + asset: IAsset, + refetchAssets: () => Promise< + QueryObserverResult, HttpError> + > + ) => { + const downloadFromUrl = async (url: string, fileName: string) => { + const response = await fetch(url) + + if (!response.ok) { + throw response + } + + const blob = await response.blob() + const objectUrl = URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = objectUrl + link.download = fileName || 'download' + document.body.appendChild(link) + link.click() + link.remove() + + URL.revokeObjectURL(objectUrl) + } + + try { + await downloadFromUrl(asset.signed_url, asset.name) + } catch (error) { + if (!(error instanceof Response) || error.status !== 403) { + throw error + } + + const refetchResult = await refetchAssets() + const refreshedAssets = + refetchResult.data?.data ?? refetchResult.data ?? [] + + const refreshedAsset = refreshedAssets.find( + (item: IAsset) => item.id === asset.id + ) + + if (!refreshedAsset?.signed_url) { + throw new Error('Could not refresh signed URL') + } + + await downloadFromUrl(refreshedAsset.signed_url, refreshedAsset.name) + } + } + + return ( + + + + + + + {asset.name} + + + {asset.signed_url && ( + + )} + + ) +} diff --git a/src/pages/ocotillo/thing/well-show.tsx b/src/pages/ocotillo/thing/well-show.tsx index c039d512..d7049681 100644 --- a/src/pages/ocotillo/thing/well-show.tsx +++ b/src/pages/ocotillo/thing/well-show.tsx @@ -3,7 +3,6 @@ import { useDataProvider, useList, useResourceParams } from '@refinedev/core' import { captureEvent } from '@/analytics/posthog' import { useQuery } from '@tanstack/react-query' import { Show, useDataGrid } from '@refinedev/mui' -import { AppBreadcrumb } from '@/components/AppBreadcrumb' import { TransducerObservationWithBlockResponse } from '@/generated/types.gen' import { IAsset, @@ -402,6 +401,7 @@ export const WellShow = () => {