mirror of
https://github.com/Afilmory/afilmory
synced 2026-04-24 23:05:05 +00:00
@@ -19,7 +19,6 @@ import {
|
||||
StreamlineImageAccessoriesLensesPhotosCameraShutterPicturePhotographyPicturesPhotoLens,
|
||||
TablerAperture,
|
||||
} from '~/icons'
|
||||
import { getImageFormat } from '~/lib/image-utils'
|
||||
import { convertExifGPSToDecimal } from '~/lib/map-utils'
|
||||
|
||||
import { formatExifData, Row } from './formatExifData'
|
||||
@@ -45,8 +44,6 @@ export const ExifPanel: FC<{
|
||||
const decimalLatitude = gpsData?.latitude || null
|
||||
const decimalLongitude = gpsData?.longitude || null
|
||||
|
||||
// 使用通用的图片格式提取函数
|
||||
const imageFormat = getImageFormat(currentPhoto.originalUrl || currentPhoto.s3Key || '')
|
||||
const megaPixels = (((currentPhoto.height * currentPhoto.width) / 1000000) | 0).toString()
|
||||
|
||||
return (
|
||||
@@ -109,7 +106,7 @@ export const ExifPanel: FC<{
|
||||
<h4 className="mb-2 text-sm font-medium text-white/80">{t('exif.basic.info')}</h4>
|
||||
<div className="space-y-1 text-sm">
|
||||
<Row label={t('exif.filename')} value={currentPhoto.title} ellipsis={true} />
|
||||
<Row label={t('exif.format')} value={imageFormat} />
|
||||
<Row label={t('exif.format')} value={currentPhoto.format} />
|
||||
<Row label={t('exif.dimensions')} value={`${currentPhoto.width} × ${currentPhoto.height}`} />
|
||||
<Row label={t('exif.file.size')} value={`${(currentPhoto.size / 1024 / 1024).toFixed(1)}MB`} />
|
||||
{megaPixels && <Row label={t('exif.pixels')} value={`${megaPixels} MP`} />}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clsxm } from '@afilmory/utils'
|
||||
import { WebGLImageViewer } from '@afilmory/webgl-viewer'
|
||||
import { AnimatePresence, m } from 'motion/react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ReactZoomPanPinchRef } from 'react-zoom-pan-pinch'
|
||||
import { useMediaQuery } from 'usehooks-ts'
|
||||
@@ -70,9 +70,16 @@ export const ProgressiveImage = ({
|
||||
const domImageViewerRef = useRef<ReactZoomPanPinchRef>(null)
|
||||
const livePhotoRef = useRef<any>(null)
|
||||
|
||||
const resolvedSrc = useMemo(() => {
|
||||
if (src.startsWith('/')) {
|
||||
return new URL(src, window.location.origin).toString()
|
||||
}
|
||||
return src
|
||||
}, [src])
|
||||
|
||||
// Hooks
|
||||
const imageLoaderManagerRef = useImageLoader(
|
||||
src,
|
||||
resolvedSrc,
|
||||
isCurrentImage,
|
||||
highResLoaded,
|
||||
error,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Thumbhash } from '@afilmory/ui'
|
||||
import clsx from 'clsx'
|
||||
import { m } from 'motion/react'
|
||||
import { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Fragment, memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useContextPhotos, usePhotoViewer } from '~/hooks/usePhotoViewer'
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
} from '~/icons'
|
||||
import { isMobileDevice } from '~/lib/device-viewport'
|
||||
import { ImageLoaderManager } from '~/lib/image-loader-manager'
|
||||
import { getImageFormat } from '~/lib/image-utils'
|
||||
import type { PhotoManifest } from '~/types/photo'
|
||||
|
||||
export const MasonryPhotoItem = ({ data, width, index: _ }: { data: PhotoManifest; width: number; index: number }) => {
|
||||
export const MasonryPhotoItem = memo(({ data, width }: { data: PhotoManifest; width: number }) => {
|
||||
const photos = useContextPhotos()
|
||||
const photoViewer = usePhotoViewer()
|
||||
const { t } = useTranslation()
|
||||
@@ -96,9 +95,6 @@ export const MasonryPhotoItem = ({ data, width, index: _ }: { data: PhotoManifes
|
||||
|
||||
const exifData = formatExifData()
|
||||
|
||||
// 使用通用的图片格式提取函数
|
||||
const imageFormat = getImageFormat(data.originalUrl || data.s3Key || '')
|
||||
|
||||
// 检查是否有视频内容(Live Photo 或 Motion Photo)
|
||||
const hasVideo = data.video !== undefined
|
||||
|
||||
@@ -302,7 +298,7 @@ export const MasonryPhotoItem = ({ data, width, index: _ }: { data: PhotoManifes
|
||||
{/* 内容层 - 独立的层以支持 backdrop-filter */}
|
||||
<div className="absolute inset-x-0 bottom-0 p-4 pb-0 text-white">
|
||||
{/* 基本信息和标签 section */}
|
||||
<div className="mb-3 [&_*]:duration-300">
|
||||
<div className="mb-3 **:duration-300">
|
||||
<h3 className="mb-2 truncate text-sm font-medium opacity-0 group-hover:opacity-100">{data.title}</h3>
|
||||
{data.description && (
|
||||
<p className="mb-2 line-clamp-2 text-sm text-white/80 opacity-0 group-hover:opacity-100">
|
||||
@@ -312,7 +308,7 @@ export const MasonryPhotoItem = ({ data, width, index: _ }: { data: PhotoManifes
|
||||
|
||||
{/* 基本信息 */}
|
||||
<div className="mb-2 flex flex-wrap gap-2 text-xs text-white/80 opacity-0 group-hover:opacity-100">
|
||||
<span>{imageFormat}</span>
|
||||
<span>{data.format}</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{data.width} × {data.height}
|
||||
@@ -373,4 +369,4 @@ export const MasonryPhotoItem = ({ data, width, index: _ }: { data: PhotoManifes
|
||||
)}
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -250,7 +250,7 @@ export const MasonryItem = memo(
|
||||
animate="visible"
|
||||
onAnimationComplete={shouldAnimate ? onAnimationComplete : undefined}
|
||||
>
|
||||
<MasonryPhotoItem data={data as PhotoManifest} width={width} index={index} />
|
||||
<MasonryPhotoItem data={data as PhotoManifest} width={width} />
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user