mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
- Added a new package `@photo-gallery/data` to manage photo data and metadata. - Updated various components to utilize the new photoLoader from the data package. - Adjusted dependencies in `pnpm-lock.yaml` and `package.json` files to include the new package. - Refactored imports across the application to streamline photo data access. Signed-off-by: Innei <tukon479@gmail.com>
122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import { photoLoader } from '@photo-gallery/data'
|
|
import { atom, useAtom, useAtomValue } from 'jotai'
|
|
import { useCallback, useEffect, useMemo } from 'react'
|
|
import { useLocation, useNavigate } from 'react-router'
|
|
|
|
import { gallerySettingAtom } from '~/atoms/app'
|
|
|
|
const openAtom = atom(false)
|
|
const currentIndexAtom = atom(0)
|
|
const triggerElementAtom = atom<HTMLElement | null>(null)
|
|
const data = photoLoader.getPhotos()
|
|
export const usePhotos = () => {
|
|
const { sortOrder, selectedTags } = useAtomValue(gallerySettingAtom)
|
|
|
|
const masonryItems = useMemo(() => {
|
|
// 首先根据 tags 筛选
|
|
let filteredPhotos = data
|
|
if (selectedTags.length > 0) {
|
|
filteredPhotos = data.filter((photo) =>
|
|
selectedTags.some((tag) => photo.tags.includes(tag)),
|
|
)
|
|
}
|
|
|
|
// 然后排序
|
|
const sortedPhotos = filteredPhotos.toSorted((a, b) => {
|
|
let aDateStr = ''
|
|
let bDateStr = ''
|
|
|
|
if (a.exif && a.exif.Photo && a.exif.Photo.DateTimeOriginal) {
|
|
aDateStr = a.exif.Photo.DateTimeOriginal as unknown as string
|
|
} else {
|
|
aDateStr = a.lastModified
|
|
}
|
|
|
|
if (b.exif && b.exif.Photo && b.exif.Photo.DateTimeOriginal) {
|
|
bDateStr = b.exif.Photo.DateTimeOriginal as unknown as string
|
|
} else {
|
|
bDateStr = b.lastModified
|
|
}
|
|
|
|
return sortOrder === 'asc'
|
|
? aDateStr.localeCompare(bDateStr)
|
|
: bDateStr.localeCompare(aDateStr)
|
|
})
|
|
|
|
return sortedPhotos
|
|
}, [sortOrder, selectedTags])
|
|
return masonryItems
|
|
}
|
|
export const usePhotoViewer = () => {
|
|
const photos = usePhotos()
|
|
const [isOpen, setIsOpen] = useAtom(openAtom)
|
|
const [currentIndex, setCurrentIndex] = useAtom(currentIndexAtom)
|
|
const [triggerElement, setTriggerElement] = useAtom(triggerElementAtom)
|
|
|
|
const navigate = useNavigate()
|
|
|
|
const openViewer = useCallback(
|
|
(index: number, element?: HTMLElement) => {
|
|
setCurrentIndex(index)
|
|
setTriggerElement(element || null)
|
|
setIsOpen(true)
|
|
// 防止背景滚动
|
|
document.body.style.overflow = 'hidden'
|
|
},
|
|
[setCurrentIndex, setIsOpen, setTriggerElement],
|
|
)
|
|
|
|
const closeViewer = useCallback(() => {
|
|
setIsOpen(false)
|
|
setTriggerElement(null)
|
|
// 恢复背景滚动
|
|
document.body.style.overflow = ''
|
|
}, [setIsOpen, setTriggerElement])
|
|
|
|
const goToNext = useCallback(() => {
|
|
if (currentIndex < photos.length - 1) {
|
|
setCurrentIndex(currentIndex + 1)
|
|
}
|
|
}, [currentIndex, photos.length, setCurrentIndex])
|
|
|
|
const location = useLocation()
|
|
useEffect(() => {
|
|
if (!isOpen) {
|
|
const timer = setTimeout(() => {
|
|
navigate('/')
|
|
}, 500)
|
|
return () => clearTimeout(timer)
|
|
}
|
|
const targetPathname = `/${photos[currentIndex].id}`
|
|
if (location.pathname !== targetPathname) {
|
|
navigate(targetPathname)
|
|
}
|
|
}, [currentIndex, isOpen, location.pathname, navigate, photos])
|
|
|
|
const goToPrevious = useCallback(() => {
|
|
if (currentIndex > 0) {
|
|
setCurrentIndex(currentIndex - 1)
|
|
}
|
|
}, [currentIndex, setCurrentIndex])
|
|
|
|
const goToIndex = useCallback(
|
|
(index: number) => {
|
|
if (index >= 0 && index < photos.length) {
|
|
setCurrentIndex(index)
|
|
}
|
|
},
|
|
[photos.length, setCurrentIndex],
|
|
)
|
|
|
|
return {
|
|
isOpen,
|
|
currentIndex,
|
|
triggerElement,
|
|
openViewer,
|
|
closeViewer,
|
|
goToNext,
|
|
goToPrevious,
|
|
goToIndex,
|
|
}
|
|
}
|