diff --git a/apps/web/src/atoms/app.ts b/apps/web/src/atoms/app.ts index 02db0907..60088b62 100644 --- a/apps/web/src/atoms/app.ts +++ b/apps/web/src/atoms/app.ts @@ -7,7 +7,11 @@ export const gallerySettingAtom = atom({ sortBy: 'date' as GallerySortBy, sortOrder: 'desc' as GallerySortOrder, selectedTags: [] as string[], + selectedCameras: [] as string[], // Selected camera display names + selectedLenses: [] as string[], // Selected lens display names tagSearchQuery: '' as string, + cameraSearchQuery: '' as string, // Camera search query + lensSearchQuery: '' as string, // Lens search query isTagsPanelOpen: false as boolean, columns: 'auto' as number | 'auto', // 自定义列数,auto 表示自动计算 }) diff --git a/apps/web/src/components/gallery/FilterPanel.tsx b/apps/web/src/components/gallery/FilterPanel.tsx new file mode 100644 index 00000000..eab5caae --- /dev/null +++ b/apps/web/src/components/gallery/FilterPanel.tsx @@ -0,0 +1,374 @@ +import { photoLoader } from '@afilmory/data' +import { useAtom } from 'jotai' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { gallerySettingAtom } from '~/atoms/app' +import { Button } from '~/components/ui/button' +import { clsxm } from '~/lib/cn' + +const allTags = photoLoader.getAllTags() +const allCameras = photoLoader.getAllCameras() +const allLenses = photoLoader.getAllLenses() + +export const FilterPanel = () => { + const { t } = useTranslation() + const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom) + const [activeTab, setActiveTab] = useState<'tags' | 'cameras' | 'lenses'>( + 'tags', + ) + const [tagSearchQuery, setTagSearchQuery] = useState( + gallerySetting.tagSearchQuery, + ) + const [cameraSearchQuery, setCameraSearchQuery] = useState( + gallerySetting.cameraSearchQuery, + ) + const [lensSearchQuery, setLensSearchQuery] = useState( + gallerySetting.lensSearchQuery, + ) + const inputRef = useRef(null) + + // Auto-focus input when panel opens + useEffect(() => { + if (gallerySetting.isTagsPanelOpen && inputRef.current) { + inputRef.current?.focus() + } + }, [gallerySetting.isTagsPanelOpen]) + + // Toggle handlers with useCallback to prevent re-creation + const toggleTag = useCallback( + (tag: string) => { + setGallerySetting((prev) => { + const newSelectedTags = prev.selectedTags.includes(tag) + ? prev.selectedTags.filter((t) => t !== tag) + : [...prev.selectedTags, tag] + + return { + ...prev, + selectedTags: newSelectedTags, + } + }) + }, + [setGallerySetting], + ) + + const toggleCamera = useCallback( + (camera: string) => { + setGallerySetting((prev) => { + const newSelectedCameras = prev.selectedCameras.includes(camera) + ? prev.selectedCameras.filter((c) => c !== camera) + : [...prev.selectedCameras, camera] + + return { + ...prev, + selectedCameras: newSelectedCameras, + } + }) + }, + [setGallerySetting], + ) + + const toggleLens = useCallback( + (lens: string) => { + setGallerySetting((prev) => { + const newSelectedLenses = prev.selectedLenses.includes(lens) + ? prev.selectedLenses.filter((l) => l !== lens) + : [...prev.selectedLenses, lens] + + return { + ...prev, + selectedLenses: newSelectedLenses, + } + }) + }, + [setGallerySetting], + ) + + // Clear handlers with useCallback + const clearTags = useCallback(() => { + setGallerySetting((prev) => ({ + ...prev, + selectedTags: [], + tagSearchQuery: '', + })) + setTagSearchQuery('') + }, [setGallerySetting]) + + const clearCameras = useCallback(() => { + setGallerySetting((prev) => ({ + ...prev, + selectedCameras: [], + cameraSearchQuery: '', + })) + setCameraSearchQuery('') + }, [setGallerySetting]) + + const clearLenses = useCallback(() => { + setGallerySetting((prev) => ({ + ...prev, + selectedLenses: [], + lensSearchQuery: '', + })) + setLensSearchQuery('') + }, [setGallerySetting]) + + // Search handlers with useCallback + const onTagSearchChange = useCallback( + (e: React.ChangeEvent) => { + const query = e.target.value + setTagSearchQuery(query) + setGallerySetting((prev) => ({ + ...prev, + tagSearchQuery: query, + })) + }, + [setGallerySetting], + ) + + const onCameraSearchChange = useCallback( + (e: React.ChangeEvent) => { + const query = e.target.value + setCameraSearchQuery(query) + setGallerySetting((prev) => ({ + ...prev, + cameraSearchQuery: query, + })) + }, + [setGallerySetting], + ) + + const onLensSearchChange = useCallback( + (e: React.ChangeEvent) => { + const query = e.target.value + setLensSearchQuery(query) + setGallerySetting((prev) => ({ + ...prev, + lensSearchQuery: query, + })) + }, + [setGallerySetting], + ) + + // Filter data based on regex search + const filterItems = (items: string[], searchQuery: string) => { + if (!searchQuery) return items + + try { + const regex = new RegExp(searchQuery, 'i') + return items.filter((item) => regex.test(item)) + } catch { + return items.filter((item) => + item.toLowerCase().includes(searchQuery.toLowerCase()), + ) + } + } + + const filteredTags = filterItems(allTags, tagSearchQuery) + const filteredCameras = filterItems( + allCameras.map((camera) => camera.displayName), + cameraSearchQuery, + ) + const filteredLenses = filterItems( + allLenses.map((lens) => lens.displayName), + lensSearchQuery, + ) + + // Tab data configuration with useMemo to prevent recreation + const tabs = useMemo( + () => [ + { + id: 'tags' as const, + label: t('action.tag.filter'), + icon: 'i-mingcute-tag-line', + count: gallerySetting.selectedTags.length, + data: allTags, + filteredData: filteredTags, + selectedItems: gallerySetting.selectedTags, + searchQuery: tagSearchQuery, + searchPlaceholder: t('action.tag.search'), + emptyMessage: t('action.tag.empty'), + notFoundMessage: t('action.tag.not-found'), + onToggle: toggleTag, + onClear: clearTags, + onSearchChange: onTagSearchChange, + }, + { + id: 'cameras' as const, + label: t('action.camera.filter'), + icon: 'i-mingcute-camera-line', + count: gallerySetting.selectedCameras.length, + data: allCameras.map((camera) => camera.displayName), + filteredData: filteredCameras, + selectedItems: gallerySetting.selectedCameras, + searchQuery: cameraSearchQuery, + searchPlaceholder: t('action.camera.search'), + emptyMessage: t('action.camera.empty'), + notFoundMessage: t('action.camera.not-found'), + onToggle: toggleCamera, + onClear: clearCameras, + onSearchChange: onCameraSearchChange, + }, + { + id: 'lenses' as const, + label: t('action.lens.filter'), + icon: 'i-mingcute-camera-lens-line', + count: gallerySetting.selectedLenses.length, + data: allLenses.map((lens) => lens.displayName), + filteredData: filteredLenses, + selectedItems: gallerySetting.selectedLenses, + searchQuery: lensSearchQuery, + searchPlaceholder: t('action.lens.search'), + emptyMessage: t('action.lens.empty'), + notFoundMessage: t('action.lens.not-found'), + onToggle: toggleLens, + onClear: clearLenses, + onSearchChange: onLensSearchChange, + }, + ], + [ + t, + gallerySetting.selectedTags, + gallerySetting.selectedCameras, + gallerySetting.selectedLenses, + filteredTags, + filteredCameras, + filteredLenses, + tagSearchQuery, + cameraSearchQuery, + lensSearchQuery, + toggleTag, + toggleCamera, + toggleLens, + clearTags, + clearCameras, + clearLenses, + onTagSearchChange, + onCameraSearchChange, + onLensSearchChange, + ], + ) + + const currentTab = useMemo( + () => tabs.find((tab) => tab.id === activeTab)!, + [tabs, activeTab], + ) + + return ( +
+ {/* Header with title */} +
+

+ {t('action.filter.title')} +

+ + {/* Reset all filters */} + +
+ + {/* Tab Navigation - Improved spacing and layout */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Tab Content */} +
+ {/* Search and Clear section - Aligned on same baseline */} +
+
+
+ + +
+ {currentTab.count > 0 && ( + + )} +
+
+ + {/* Content area - Clean list without background */} + {currentTab.data.length === 0 ? ( +
+ {currentTab.emptyMessage} +
+ ) : currentTab.filteredData.length === 0 ? ( +
+ {currentTab.notFoundMessage} +
+ ) : ( +
+ {currentTab.filteredData.map((item) => ( +
currentTab.onToggle(item)} + className={clsxm( + 'hover:bg-accent/50 flex cursor-pointer items-center rounded-md bg-transparent px-2 py-2.5 transition-colors lg:py-2', + currentTab.selectedItems.includes(item) && 'bg-accent/20', + )} + > + {item} + {currentTab.selectedItems.includes(item) && ( + + )} +
+ ))} +
+ )} +
+
+ ) +} diff --git a/apps/web/src/hooks/usePhotoViewer.ts b/apps/web/src/hooks/usePhotoViewer.ts index f25e86b1..163cdf92 100644 --- a/apps/web/src/hooks/usePhotoViewer.ts +++ b/apps/web/src/hooks/usePhotoViewer.ts @@ -1,10 +1,11 @@ import { photoLoader } from '@afilmory/data' import { atom, useAtom, useAtomValue } from 'jotai' -import { useCallback, useMemo } from 'react' +import { use, useCallback, useMemo } from 'react' import { gallerySettingAtom } from '~/atoms/app' import { jotaiStore } from '~/lib/jotai' import { trackView } from '~/lib/tracker' +import { PhotosContext } from '~/providers/photos-provider' const openAtom = atom(false) const currentIndexAtom = atom(0) @@ -14,16 +15,40 @@ const data = photoLoader.getPhotos() // 抽取照片筛选和排序逻辑为独立函数 const filterAndSortPhotos = ( selectedTags: string[], + selectedCameras: string[], + selectedLenses: string[], sortOrder: 'asc' | 'desc', ) => { - // 首先根据 tags 筛选 + // 根据 tags、cameras 和 lenses 筛选 let filteredPhotos = data + + // Tags 筛选:照片必须包含至少一个选中的标签 if (selectedTags.length > 0) { - filteredPhotos = data.filter((photo) => + filteredPhotos = filteredPhotos.filter((photo) => selectedTags.some((tag) => photo.tags.includes(tag)), ) } + // Cameras 筛选:照片的相机必须匹配选中的相机之一 + if (selectedCameras.length > 0) { + filteredPhotos = filteredPhotos.filter((photo) => { + if (!photo.exif?.Make || !photo.exif?.Model) return false + const cameraDisplayName = `${photo.exif.Make.trim()} ${photo.exif.Model.trim()}` + return selectedCameras.includes(cameraDisplayName) + }) + } + + // Lenses 筛选:照片的镜头必须匹配选中的镜头之一 + if (selectedLenses.length > 0) { + filteredPhotos = filteredPhotos.filter((photo) => { + if (!photo.exif?.LensModel) return false + const lensModel = photo.exif.LensModel.trim() + const lensMake = photo.exif.LensMake?.trim() + const lensDisplayName = lensMake ? `${lensMake} ${lensModel}` : lensModel + return selectedLenses.includes(lensDisplayName) + }) + } + // 然后排序 const sortedPhotos = filteredPhotos.toSorted((a, b) => { let aDateStr = '' @@ -55,20 +80,36 @@ export const getFilteredPhotos = () => { const currentGallerySetting = jotaiStore.get(gallerySettingAtom) return filterAndSortPhotos( currentGallerySetting.selectedTags, + currentGallerySetting.selectedCameras, + currentGallerySetting.selectedLenses, currentGallerySetting.sortOrder, ) } export const usePhotos = () => { - const { sortOrder, selectedTags } = useAtomValue(gallerySettingAtom) + const { sortOrder, selectedTags, selectedCameras, selectedLenses } = + useAtomValue(gallerySettingAtom) const masonryItems = useMemo(() => { - return filterAndSortPhotos(selectedTags, sortOrder) - }, [sortOrder, selectedTags]) + return filterAndSortPhotos( + selectedTags, + selectedCameras, + selectedLenses, + sortOrder, + ) + }, [sortOrder, selectedTags, selectedCameras, selectedLenses]) return masonryItems } +export const useContextPhotos = () => { + const photos = use(PhotosContext) + if (!photos) { + throw new Error('PhotosContext is not initialized') + } + return photos +} + export const usePhotoViewer = () => { const photos = usePhotos() const [isOpen, setIsOpen] = useAtom(openAtom) @@ -76,7 +117,7 @@ export const usePhotoViewer = () => { const [triggerElement, setTriggerElement] = useAtom(triggerElementAtom) const id = useMemo(() => { - return photos[currentIndex].id + return photos[currentIndex]?.id }, [photos, currentIndex]) const openViewer = useCallback( (index: number, element?: HTMLElement) => { diff --git a/apps/web/src/modules/gallery/ActionGroup.tsx b/apps/web/src/modules/gallery/ActionGroup.tsx index f597a344..b0f923d8 100644 --- a/apps/web/src/modules/gallery/ActionGroup.tsx +++ b/apps/web/src/modules/gallery/ActionGroup.tsx @@ -1,11 +1,11 @@ -import { photoLoader } from '@afilmory/data' import { useAtom, useSetAtom } from 'jotai' -import { useEffect, useRef, useState } from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router' import { Drawer } from 'vaul' import { gallerySettingAtom } from '~/atoms/app' +import { FilterPanel } from '~/components/gallery/FilterPanel' import { Button } from '~/components/ui/button' import { DropdownMenu, @@ -16,8 +16,6 @@ import { Slider } from '~/components/ui/slider' import { useMobile } from '~/hooks/useMobile' import { clsxm } from '~/lib/cn' -const allTags = photoLoader.getAllTags() - const SortPanel = () => { const { t } = useTranslation() const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom) @@ -62,131 +60,6 @@ const SortPanel = () => { ) } -const TagsPanel = () => { - const { t } = useTranslation() - const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom) - const [searchQuery, setSearchQuery] = useState(gallerySetting.tagSearchQuery) - const inputRef = useRef(null) - - // 当面板打开时自动聚焦输入框 - useEffect(() => { - if (gallerySetting.isTagsPanelOpen && inputRef.current) { - inputRef.current?.focus() - } - }, [gallerySetting.isTagsPanelOpen]) - - const toggleTag = (tag: string) => { - const newSelectedTags = gallerySetting.selectedTags.includes(tag) - ? gallerySetting.selectedTags.filter((t) => t !== tag) - : [...gallerySetting.selectedTags, tag] - - setGallerySetting({ - ...gallerySetting, - selectedTags: newSelectedTags, - }) - } - - const clearAllTags = () => { - setGallerySetting({ - ...gallerySetting, - selectedTags: [], - tagSearchQuery: '', // 清除搜索查询 - isTagsPanelOpen: false, // 关闭标签面板 - }) - } - - const onSearchChange = (e: React.ChangeEvent) => { - const query = e.target.value - setSearchQuery(query) - setGallerySetting((prev) => ({ - ...prev, - tagSearchQuery: query, // 同步搜索查询 - })) - } - - // 根据正则查询过滤标签 - const filteredTags = allTags.filter((tag) => { - if (!searchQuery) return true - - try { - const regex = new RegExp(searchQuery, 'i') - return regex.test(tag) - } catch { - // 如果正则表达式无效,回退到简单的包含查询 - return tag.toLowerCase().includes(searchQuery.toLowerCase()) - } - }) - - return ( -
-
-

- {t('action.tag.filter')} -

- {gallerySetting.selectedTags.length > 0 && ( - - )} -
- {/* 搜索栏 */} -
-
- - -
-
- - {allTags.length === 0 ? ( -
- {t('action.tag.empty')} -
- ) : filteredTags.length === 0 ? ( -
- {t('action.tag.not-found')} -
- ) : ( -
- {filteredTags.map((tag) => ( -
toggleTag(tag)} - className="hover:bg-accent/50 flex cursor-pointer items-center rounded-md bg-transparent px-2 py-3 lg:py-1" - > - {tag} - {gallerySetting.selectedTags.includes(tag) && ( - - )} -
- ))} -
- )} -
- ) -} - -const onTagsPanelOpenChange = ( - open: boolean, - setGallerySetting: (setting: any) => void, -) => { - setGallerySetting((prev) => ({ - ...prev, - isTagsPanelOpen: open, - })) -} - const ColumnsPanel = () => { const { t } = useTranslation() const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom) @@ -394,9 +267,16 @@ const ResponsiveActionButton = ({ export const ActionGroup = () => { const { t } = useTranslation() - const [gallerySetting] = useAtom(gallerySettingAtom) + const [gallerySetting, setGallerySetting] = useAtom(gallerySettingAtom) const navigate = useNavigate() + const onTagsPanelOpenChange = (open: boolean) => { + setGallerySetting((prev: any) => ({ + ...prev, + isTagsPanelOpen: open, + })) + } + return (
{/* 地图探索按钮 */} @@ -410,20 +290,25 @@ export const ActionGroup = () => { - {/* 标签筛选按钮 */} + {/* 过滤按钮 */} 0 - ? gallerySetting.selectedTags.length + gallerySetting.selectedTags.length + + gallerySetting.selectedCameras.length + + gallerySetting.selectedLenses.length > + 0 + ? gallerySetting.selectedTags.length + + gallerySetting.selectedCameras.length + + gallerySetting.selectedLenses.length : undefined } // 使用全局状态实现滚动时自动收起标签面板 globalOpen={gallerySetting.isTagsPanelOpen} onGlobalOpenChange={onTagsPanelOpenChange} > - + {/* 列数调整按钮 */} @@ -455,7 +340,7 @@ export const ActionGroup = () => { const panelMap = { sort: SortPanel, - tags: TagsPanel, + tags: FilterPanel, columns: ColumnsPanel, } diff --git a/apps/web/src/modules/gallery/FloatingActionButton.tsx b/apps/web/src/modules/gallery/FloatingActionButton.tsx index 0716cc74..9da50008 100644 --- a/apps/web/src/modules/gallery/FloatingActionButton.tsx +++ b/apps/web/src/modules/gallery/FloatingActionButton.tsx @@ -19,7 +19,7 @@ const actions: { icon: 'i-mingcute-sort-descending-line', title: 'action.sort.mode', }, - { id: 'tags', icon: 'i-mingcute-tag-line', title: 'action.tag.filter' }, + { id: 'tags', icon: 'i-mingcute-filter-line', title: 'action.filter.title' }, { id: 'columns', icon: 'i-mingcute-grid-line', diff --git a/apps/web/src/modules/gallery/MasonryPhotoItem.tsx b/apps/web/src/modules/gallery/MasonryPhotoItem.tsx index 7d7f871c..7f82c78c 100644 --- a/apps/web/src/modules/gallery/MasonryPhotoItem.tsx +++ b/apps/web/src/modules/gallery/MasonryPhotoItem.tsx @@ -4,6 +4,10 @@ import { Fragment, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Thumbhash } from '~/components/ui/thumbhash' +import { + useContextPhotos, + usePhotoViewer, +} from '~/hooks/usePhotoViewer' import { CarbonIsoOutline, MaterialSymbolsShutterSpeed, @@ -19,15 +23,13 @@ export const MasonryPhotoItem = ({ data, width, index: _, - onPhotoClick, - photos, }: { data: PhotoManifest width: number index: number - onPhotoClick: (index: number, element?: HTMLElement) => void - photos: PhotoManifest[] }) => { + const photos = useContextPhotos() + const photoViewer = usePhotoViewer() const { t } = useTranslation() const [imageLoaded, setImageLoaded] = useState(false) const [imageError, setImageError] = useState(false) @@ -55,7 +57,7 @@ export const MasonryPhotoItem = ({ const handleClick = () => { const photoIndex = photos.findIndex((photo) => photo.id === data.id) if (photoIndex !== -1 && imageRef.current) { - onPhotoClick(photoIndex, imageRef.current) + photoViewer.openViewer(photoIndex, imageRef.current) } } diff --git a/apps/web/src/modules/gallery/MasonryRoot.tsx b/apps/web/src/modules/gallery/MasonryRoot.tsx index bc7f76d2..04622f2f 100644 --- a/apps/web/src/modules/gallery/MasonryRoot.tsx +++ b/apps/web/src/modules/gallery/MasonryRoot.tsx @@ -6,7 +6,9 @@ import { gallerySettingAtom } from '~/atoms/app' import { DateRangeIndicator } from '~/components/ui/date-range-indicator' import { useScrollViewElement } from '~/components/ui/scroll-areas/hooks' import { useMobile } from '~/hooks/useMobile' -import { usePhotos, usePhotoViewer } from '~/hooks/usePhotoViewer' +import { + useContextPhotos, +} from '~/hooks/usePhotoViewer' import { useTypeScriptHappyCallback } from '~/hooks/useTypeScriptCallback' import { useVisiblePhotosDateRange } from '~/hooks/useVisiblePhotosDateRange' import { clsxm } from '~/lib/cn' @@ -51,15 +53,12 @@ export const MasonryRoot = () => { const [showFloatingActions, setShowFloatingActions] = useState(false) const [containerWidth, setContainerWidth] = useState(0) - const photos = usePhotos() + const photos = useContextPhotos() const masonryRef = useRef(null) - // useEffect(() => { - // nextFrame(() => masonryRef.current?.reposition()) - // }, [photos]) + const { dateRange, handleRender } = useVisiblePhotosDateRange(photos) const scrollElement = useScrollViewElement() - const photoViewer = usePhotoViewer() const handleAnimationComplete = useCallback(() => { hasAnimatedRef.current = true }, []) @@ -177,13 +176,11 @@ export const MasonryRoot = () => { (props) => ( ), - [handleAnimationComplete, photoViewer.openViewer, photos], + [handleAnimationComplete], )} onRender={handleRender} columnWidth={columnWidth} @@ -217,16 +214,13 @@ export const MasonryItem = memo( data, width, index, - onPhotoClick, - photos, + hasAnimated, onAnimationComplete, }: { data: MasonryItemType width: number index: number - onPhotoClick: (index: number, element?: HTMLElement) => void - photos: PhotoManifest[] hasAnimated: boolean onAnimationComplete: () => void }) => { @@ -269,7 +263,7 @@ export const MasonryItem = memo( } if (data instanceof MasonryHeaderItem) { - return + return } else { return ( ) diff --git a/apps/web/src/pages/(data)/manifest.tsx b/apps/web/src/pages/(data)/manifest.tsx index c9cc56b4..b83a222b 100644 --- a/apps/web/src/pages/(data)/manifest.tsx +++ b/apps/web/src/pages/(data)/manifest.tsx @@ -192,7 +192,7 @@ export const Component = () => { const photos = photoLoader.getPhotos() const manifestData = { - version: 'v5', + version: 'v6', data: photos, } @@ -355,7 +355,7 @@ export const Component = () => { diff --git a/apps/web/src/pages/(main)/[photoId]/index.tsx b/apps/web/src/pages/(main)/[photoId]/index.tsx index dde581dc..feb84d53 100644 --- a/apps/web/src/pages/(main)/[photoId]/index.tsx +++ b/apps/web/src/pages/(main)/[photoId]/index.tsx @@ -6,11 +6,14 @@ import { PhotoViewer } from '~/components/ui/photo-viewer' import { RootPortal } from '~/components/ui/portal' import { RootPortalProvider } from '~/components/ui/portal/provider' import { useTitle } from '~/hooks/common' -import { usePhotos, usePhotoViewer } from '~/hooks/usePhotoViewer' +import { + useContextPhotos, + usePhotoViewer, +} from '~/hooks/usePhotoViewer' export const Component = () => { const photoViewer = usePhotoViewer() - const photos = usePhotos() + const photos = useContextPhotos() const ref = useRef(null) const rootPortalValue = useMemo(() => { diff --git a/apps/web/src/pages/(main)/layout.tsx b/apps/web/src/pages/(main)/layout.tsx index 7ffd82a4..b76487d4 100644 --- a/apps/web/src/pages/(main)/layout.tsx +++ b/apps/web/src/pages/(main)/layout.tsx @@ -1,4 +1,5 @@ import { photoLoader } from '@afilmory/data' +import siteConfig from '@config' import { useAtomValue, useSetAtom } from 'jotai' // import { AnimatePresence } from 'motion/react' import { useEffect, useRef } from 'react' @@ -14,8 +15,13 @@ import { gallerySettingAtom } from '~/atoms/app' import { ScrollElementContext } from '~/components/ui/scroll-areas/ctx' import { ScrollArea } from '~/components/ui/scroll-areas/ScrollArea' import { useMobile } from '~/hooks/useMobile' -import { getFilteredPhotos, usePhotoViewer } from '~/hooks/usePhotoViewer' +import { + getFilteredPhotos, + usePhotos, + usePhotoViewer, +} from '~/hooks/usePhotoViewer' import { MasonryRoot } from '~/modules/gallery/MasonryRoot' +import { PhotosProvider } from '~/providers/photos-provider' export const Component = () => { useStateRestoreFromUrl() @@ -24,22 +30,38 @@ export const Component = () => { // const location = useLocation() const isMobile = useMobile() + const photos = usePhotos() return ( <> - {isMobile ? ( - - - - ) : ( - - - - )} + + {siteConfig.accentColor && ( +