Refactor core navigation to support grid-first root
This commit is contained in:
parent
6ff4a72c20
commit
2ed96eb2f4
@ -36,6 +36,7 @@ const SHARE = 'share';
|
|||||||
|
|
||||||
const PATH_ROOT = '/';
|
const PATH_ROOT = '/';
|
||||||
const PATH_GRID = '/grid';
|
const PATH_GRID = '/grid';
|
||||||
|
const PATH_FEED = '/feed';
|
||||||
const PATH_ADMIN = '/admin/photos';
|
const PATH_ADMIN = '/admin/photos';
|
||||||
const PATH_OG = '/og';
|
const PATH_OG = '/og';
|
||||||
const PATH_OG_ALL = `${PATH_OG}/all`;
|
const PATH_OG_ALL = `${PATH_OG}/all`;
|
||||||
@ -202,27 +203,28 @@ describe('Paths', () => {
|
|||||||
// Root
|
// Root
|
||||||
expect(getEscapePath(PATH_ROOT)).toEqual(undefined);
|
expect(getEscapePath(PATH_ROOT)).toEqual(undefined);
|
||||||
expect(getEscapePath(PATH_GRID)).toEqual(undefined);
|
expect(getEscapePath(PATH_GRID)).toEqual(undefined);
|
||||||
|
expect(getEscapePath(PATH_FEED)).toEqual(undefined);
|
||||||
expect(getEscapePath(PATH_ADMIN)).toEqual(undefined);
|
expect(getEscapePath(PATH_ADMIN)).toEqual(undefined);
|
||||||
// Photo
|
// Photo
|
||||||
expect(getEscapePath(PATH_PHOTO)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_PHOTO)).toEqual(PATH_ROOT);
|
||||||
expect(getEscapePath(PATH_PHOTO_SHARE)).toEqual(PATH_PHOTO);
|
expect(getEscapePath(PATH_PHOTO_SHARE)).toEqual(PATH_PHOTO);
|
||||||
// Tag
|
// Tag
|
||||||
expect(getEscapePath(PATH_TAG)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_TAG)).toEqual(PATH_ROOT);
|
||||||
expect(getEscapePath(PATH_TAG_SHARE)).toEqual(PATH_TAG);
|
expect(getEscapePath(PATH_TAG_SHARE)).toEqual(PATH_TAG);
|
||||||
expect(getEscapePath(PATH_TAG_PHOTO)).toEqual(PATH_TAG);
|
expect(getEscapePath(PATH_TAG_PHOTO)).toEqual(PATH_TAG);
|
||||||
expect(getEscapePath(PATH_TAG_PHOTO_SHARE)).toEqual(PATH_TAG_PHOTO);
|
expect(getEscapePath(PATH_TAG_PHOTO_SHARE)).toEqual(PATH_TAG_PHOTO);
|
||||||
// Camera
|
// Camera
|
||||||
expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_CAMERA)).toEqual(PATH_ROOT);
|
||||||
expect(getEscapePath(PATH_CAMERA_SHARE)).toEqual(PATH_CAMERA);
|
expect(getEscapePath(PATH_CAMERA_SHARE)).toEqual(PATH_CAMERA);
|
||||||
expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA);
|
expect(getEscapePath(PATH_CAMERA_PHOTO)).toEqual(PATH_CAMERA);
|
||||||
expect(getEscapePath(PATH_CAMERA_PHOTO_SHARE)).toEqual(PATH_CAMERA_PHOTO);
|
expect(getEscapePath(PATH_CAMERA_PHOTO_SHARE)).toEqual(PATH_CAMERA_PHOTO);
|
||||||
// Film Simulation
|
// Film Simulation
|
||||||
expect(getEscapePath(PATH_FILM_SIMULATION)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_FILM_SIMULATION)).toEqual(PATH_ROOT);
|
||||||
expect(getEscapePath(PATH_FILM_SIMULATION_SHARE)).toEqual(PATH_FILM_SIMULATION);
|
expect(getEscapePath(PATH_FILM_SIMULATION_SHARE)).toEqual(PATH_FILM_SIMULATION);
|
||||||
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO)).toEqual(PATH_FILM_SIMULATION);
|
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO)).toEqual(PATH_FILM_SIMULATION);
|
||||||
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO_SHARE)).toEqual(PATH_FILM_SIMULATION_PHOTO);
|
expect(getEscapePath(PATH_FILM_SIMULATION_PHOTO_SHARE)).toEqual(PATH_FILM_SIMULATION_PHOTO);
|
||||||
// Focal Length
|
// Focal Length
|
||||||
expect(getEscapePath(PATH_FOCAL_LENGTH)).toEqual(PATH_GRID);
|
expect(getEscapePath(PATH_FOCAL_LENGTH)).toEqual(PATH_ROOT);
|
||||||
expect(getEscapePath(PATH_FOCAL_LENGTH_SHARE)).toEqual(PATH_FOCAL_LENGTH);
|
expect(getEscapePath(PATH_FOCAL_LENGTH_SHARE)).toEqual(PATH_FOCAL_LENGTH);
|
||||||
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO)).toEqual(PATH_FOCAL_LENGTH);
|
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO)).toEqual(PATH_FOCAL_LENGTH);
|
||||||
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO_SHARE)).toEqual(PATH_FOCAL_LENGTH_PHOTO);
|
expect(getEscapePath(PATH_FOCAL_LENGTH_PHOTO_SHARE)).toEqual(PATH_FOCAL_LENGTH_PHOTO);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import LoaderButton from '@/components/primitives/LoaderButton';
|
|||||||
import { addAllUploadsAction } from '@/photo/actions';
|
import { addAllUploadsAction } from '@/photo/actions';
|
||||||
import { PATH_ADMIN_PHOTOS } from '@/site/paths';
|
import { PATH_ADMIN_PHOTOS } from '@/site/paths';
|
||||||
import {
|
import {
|
||||||
TagsWithMeta,
|
Tags,
|
||||||
convertTagsForForm,
|
convertTagsForForm,
|
||||||
getValidationMessageForTags,
|
getValidationMessageForTags,
|
||||||
} from '@/tag';
|
} from '@/tag';
|
||||||
@ -32,7 +32,7 @@ export default function AdminAddAllUploads({
|
|||||||
setAddedUploadUrls,
|
setAddedUploadUrls,
|
||||||
}: {
|
}: {
|
||||||
storageUrls: string[]
|
storageUrls: string[]
|
||||||
uniqueTags?: TagsWithMeta
|
uniqueTags?: Tags
|
||||||
isAdding: boolean
|
isAdding: boolean
|
||||||
setIsAdding: (isAdding: boolean) => void
|
setIsAdding: (isAdding: boolean) => void
|
||||||
setAddedUploadUrls?: Dispatch<SetStateAction<string[]>>
|
setAddedUploadUrls?: Dispatch<SetStateAction<string[]>>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import AdminTable from '@/admin/AdminTable';
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import DeleteButton from '@/admin/DeleteButton';
|
import DeleteButton from '@/admin/DeleteButton';
|
||||||
import { photoQuantityText } from '@/photo';
|
import { photoQuantityText } from '@/photo';
|
||||||
import { TagsWithMeta, formatTag, sortTagsObject } from '@/tag';
|
import { Tags, formatTag, sortTagsObject } from '@/tag';
|
||||||
import EditButton from '@/admin/EditButton';
|
import EditButton from '@/admin/EditButton';
|
||||||
import { pathForAdminTagEdit } from '@/site/paths';
|
import { pathForAdminTagEdit } from '@/site/paths';
|
||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
@ -13,7 +13,7 @@ import AdminTagBadge from './AdminTagBadge';
|
|||||||
export default function AdminTagTable({
|
export default function AdminTagTable({
|
||||||
tags,
|
tags,
|
||||||
}: {
|
}: {
|
||||||
tags: TagsWithMeta
|
tags: Tags
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<AdminTable>
|
<AdminTable>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { StorageListResponse } from '@/services/storage';
|
|||||||
import AdminAddAllUploads from './AdminAddAllUploads';
|
import AdminAddAllUploads from './AdminAddAllUploads';
|
||||||
import AdminUploadsTable from './AdminUploadsTable';
|
import AdminUploadsTable from './AdminUploadsTable';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { TagsWithMeta } from '@/tag';
|
import { Tags } from '@/tag';
|
||||||
|
|
||||||
export default function AdminUploadsClient({
|
export default function AdminUploadsClient({
|
||||||
title,
|
title,
|
||||||
@ -13,7 +13,7 @@ export default function AdminUploadsClient({
|
|||||||
}: {
|
}: {
|
||||||
title?: string
|
title?: string
|
||||||
urls: StorageListResponse
|
urls: StorageListResponse
|
||||||
uniqueTags?: TagsWithMeta
|
uniqueTags?: Tags
|
||||||
}) {
|
}) {
|
||||||
const [isAdding, setIsAdding] = useState(false);
|
const [isAdding, setIsAdding] = useState(false);
|
||||||
const [addedUploadUrls, setAddedUploadUrls] = useState<string[]>([]);
|
const [addedUploadUrls, setAddedUploadUrls] = useState<string[]>([]);
|
||||||
|
|||||||
41
src/app/feed/page.tsx
Normal file
41
src/app/feed/page.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
INFINITE_SCROLL_FEED_INITIAL,
|
||||||
|
generateOgImageMetaForPhotos,
|
||||||
|
} from '@/photo';
|
||||||
|
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||||
|
import { Metadata } from 'next/types';
|
||||||
|
import { cache } from 'react';
|
||||||
|
import { getPhotos, getPhotosMeta } from '@/photo/db/query';
|
||||||
|
import PhotoFeedPage from '@/photo/PhotoFeedPage';
|
||||||
|
|
||||||
|
export const dynamic = 'force-static';
|
||||||
|
export const maxDuration = 60;
|
||||||
|
|
||||||
|
const getPhotosCached = cache(() => getPhotos({
|
||||||
|
limit: INFINITE_SCROLL_FEED_INITIAL,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
|
const photos = await getPhotosCached()
|
||||||
|
.catch(() => []);
|
||||||
|
return generateOgImageMetaForPhotos(photos);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function FeedPage() {
|
||||||
|
const [
|
||||||
|
photos,
|
||||||
|
photosCount,
|
||||||
|
] = await Promise.all([
|
||||||
|
getPhotosCached()
|
||||||
|
.catch(() => []),
|
||||||
|
getPhotosMeta()
|
||||||
|
.then(({ count }) => count)
|
||||||
|
.catch(() => 0),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
photos.length > 0
|
||||||
|
? <PhotoFeedPage {...{ photos, photosCount }} />
|
||||||
|
: <PhotosEmptyState />
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { FilmSimulation, generateMetaForFilmSimulation } from '@/simulation';
|
import { FilmSimulation, generateMetaForFilmSimulation } from '@/simulation';
|
||||||
import FilmSimulationOverview from '@/simulation/FilmSimulationOverview';
|
import FilmSimulationOverview from '@/simulation/FilmSimulationOverview';
|
||||||
import { getPhotosFilmSimulationDataCached } from '@/simulation/data';
|
import { getPhotosFilmSimulationDataCached } from '@/simulation/data';
|
||||||
@ -20,7 +20,7 @@ export async function generateMetadata({
|
|||||||
{ count, dateRange },
|
{ count, dateRange },
|
||||||
] = await getPhotosFilmSimulationDataCachedCached({
|
] = await getPhotosFilmSimulationDataCachedCached({
|
||||||
simulation,
|
simulation,
|
||||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
limit: INFINITE_SCROLL_GRID_INITIAL,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -55,7 +55,7 @@ export default async function FilmSimulationPage({
|
|||||||
{ count, dateRange },
|
{ count, dateRange },
|
||||||
] = await getPhotosFilmSimulationDataCachedCached({
|
] = await getPhotosFilmSimulationDataCachedCached({
|
||||||
simulation,
|
simulation,
|
||||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
limit: INFINITE_SCROLL_GRID_INITIAL,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { FilmSimulation, generateMetaForFilmSimulation } from '@/simulation';
|
import { FilmSimulation, generateMetaForFilmSimulation } from '@/simulation';
|
||||||
import FilmSimulationOverview from '@/simulation/FilmSimulationOverview';
|
import FilmSimulationOverview from '@/simulation/FilmSimulationOverview';
|
||||||
import FilmSimulationShareModal from '@/simulation/FilmSimulationShareModal';
|
import FilmSimulationShareModal from '@/simulation/FilmSimulationShareModal';
|
||||||
@ -9,7 +9,7 @@ import { cache } from 'react';
|
|||||||
const getPhotosFilmSimulationDataCachedCached =
|
const getPhotosFilmSimulationDataCachedCached =
|
||||||
cache((simulation: FilmSimulation) => getPhotosFilmSimulationDataCached({
|
cache((simulation: FilmSimulation) => getPhotosFilmSimulationDataCached({
|
||||||
simulation,
|
simulation,
|
||||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
limit: INFINITE_SCROLL_GRID_INITIAL,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface FilmSimulationProps {
|
interface FilmSimulationProps {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
|
import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
|
||||||
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
||||||
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { PATH_ROOT } from '@/site/paths';
|
import { PATH_ROOT } from '@/site/paths';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
@ -10,7 +10,7 @@ import { cache } from 'react';
|
|||||||
const getPhotosFocalDataCachedCached = cache((focal: number) =>
|
const getPhotosFocalDataCachedCached = cache((focal: number) =>
|
||||||
getPhotosFocalLengthDataCached({
|
getPhotosFocalLengthDataCached({
|
||||||
focal,
|
focal,
|
||||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
limit: INFINITE_SCROLL_GRID_INITIAL,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface FocalLengthProps {
|
interface FocalLengthProps {
|
||||||
|
|||||||
@ -2,14 +2,14 @@ import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal';
|
|||||||
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
||||||
import FocalLengthShareModal from '@/focal/FocalLengthShareModal';
|
import FocalLengthShareModal from '@/focal/FocalLengthShareModal';
|
||||||
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
const getPhotosFocalLengthDataCachedCached = cache((focal: number) =>
|
const getPhotosFocalLengthDataCachedCached = cache((focal: number) =>
|
||||||
getPhotosFocalLengthDataCached({
|
getPhotosFocalLengthDataCached({
|
||||||
focal,
|
focal,
|
||||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
limit: INFINITE_SCROLL_GRID_INITIAL,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface FocalLengthProps {
|
interface FocalLengthProps {
|
||||||
|
|||||||
@ -1,20 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
INFINITE_SCROLL_GRID_INITIAL,
|
||||||
generateOgImageMetaForPhotos,
|
generateOgImageMetaForPhotos,
|
||||||
} from '@/photo';
|
} from '@/photo';
|
||||||
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||||
import { Metadata } from 'next/types';
|
import { Metadata } from 'next/types';
|
||||||
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
|
|
||||||
import { getPhotoSidebarData } from '@/photo/data';
|
import { getPhotoSidebarData } from '@/photo/data';
|
||||||
import { getPhotos } from '@/photo/db/query';
|
import { getPhotos, getPhotosMeta } from '@/photo/db/query';
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
import PhotoGridPage from '@/photo/PhotoGridPage';
|
||||||
import { PATH_GRID } from '@/site/paths';
|
|
||||||
|
|
||||||
export const dynamic = 'force-static';
|
export const dynamic = 'force-static';
|
||||||
|
|
||||||
const getPhotosCached = cache(() => getPhotos({
|
const getPhotosCached = cache(() => getPhotos({
|
||||||
limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
limit: INFINITE_SCROLL_GRID_INITIAL,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
@ -33,23 +31,16 @@ export default async function GridPage() {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getPhotosCached()
|
getPhotosCached()
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
|
getPhotosMeta()
|
||||||
|
.then(({ count }) => count)
|
||||||
|
.catch(() => 0),
|
||||||
...getPhotoSidebarData(),
|
...getPhotoSidebarData(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
photos.length > 0
|
photos.length > 0
|
||||||
? <PhotoGridPage
|
? <PhotoGridPage
|
||||||
cacheKey={`page-${PATH_GRID}`}
|
{...{ photos, photosCount, tags, cameras, simulations }}
|
||||||
photos={photos}
|
|
||||||
count={photosCount}
|
|
||||||
sidebar={<div className="sticky top-4 space-y-4 mt-[-4px]">
|
|
||||||
<PhotoGridSidebar {...{
|
|
||||||
tags,
|
|
||||||
cameras,
|
|
||||||
simulations,
|
|
||||||
photosCount,
|
|
||||||
}} />
|
|
||||||
</div>}
|
|
||||||
/>
|
/>
|
||||||
: <PhotosEmptyState />
|
: <PhotosEmptyState />
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
INFINITE_SCROLL_GRID_INITIAL,
|
||||||
INFINITE_SCROLL_GRID_PHOTO_MULTIPLE,
|
INFINITE_SCROLL_GRID_MULTIPLE,
|
||||||
} from '@/photo';
|
} from '@/photo';
|
||||||
import { getPhotosCached } from '@/photo/cache';
|
import { getPhotosCached } from '@/photo/cache';
|
||||||
import { getPhotosMeta } from '@/photo/db/query';
|
import { getPhotosMeta } from '@/photo/db/query';
|
||||||
@ -12,7 +12,7 @@ export default async function OGPage() {
|
|||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getPhotosCached({ limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL })
|
getPhotosCached({ limit: INFINITE_SCROLL_GRID_INITIAL })
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
getPhotosMeta()
|
getPhotosMeta()
|
||||||
.then(({ count }) => count)
|
.then(({ count }) => count)
|
||||||
@ -26,7 +26,7 @@ export default async function OGPage() {
|
|||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<StaggeredOgPhotosInfinite
|
<StaggeredOgPhotosInfinite
|
||||||
initialOffset={photos.length}
|
initialOffset={photos.length}
|
||||||
itemsPerPage={INFINITE_SCROLL_GRID_PHOTO_MULTIPLE}
|
itemsPerPage={INFINITE_SCROLL_GRID_MULTIPLE}
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,20 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
INFINITE_SCROLL_LARGE_PHOTO_INITIAL,
|
INFINITE_SCROLL_FEED_INITIAL,
|
||||||
INFINITE_SCROLL_LARGE_PHOTO_MULTIPLE,
|
INFINITE_SCROLL_GRID_INITIAL,
|
||||||
generateOgImageMetaForPhotos,
|
generateOgImageMetaForPhotos,
|
||||||
} from '@/photo';
|
} from '@/photo';
|
||||||
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||||
import { Metadata } from 'next/types';
|
import { Metadata } from 'next/types';
|
||||||
import PhotosLarge from '@/photo/PhotosLarge';
|
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
import { getPhotos, getPhotosMeta } from '@/photo/db/query';
|
import { getPhotos, getPhotosMeta } from '@/photo/db/query';
|
||||||
import PhotosLargeInfinite from '@/photo/PhotosLargeInfinite';
|
import { SHOW_GRID_FIRST } from '@/site/config';
|
||||||
|
import { getPhotoSidebarData } from '@/photo/data';
|
||||||
|
import PhotoGridPage from '@/photo/PhotoGridPage';
|
||||||
|
import PhotoFeedPage from '@/photo/PhotoFeedPage';
|
||||||
|
|
||||||
export const dynamic = 'force-static';
|
export const dynamic = 'force-static';
|
||||||
export const maxDuration = 60;
|
export const maxDuration = 60;
|
||||||
|
|
||||||
const getPhotosCached = cache(() => getPhotos({
|
const getPhotosCached = cache(() => getPhotos({
|
||||||
limit: INFINITE_SCROLL_LARGE_PHOTO_INITIAL,
|
limit: SHOW_GRID_FIRST
|
||||||
|
? INFINITE_SCROLL_GRID_INITIAL
|
||||||
|
: INFINITE_SCROLL_FEED_INITIAL,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
@ -27,24 +31,29 @@ export default async function HomePage() {
|
|||||||
const [
|
const [
|
||||||
photos,
|
photos,
|
||||||
photosCount,
|
photosCount,
|
||||||
|
tags,
|
||||||
|
cameras,
|
||||||
|
simulations,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getPhotosCached()
|
getPhotosCached()
|
||||||
.catch(() => []),
|
.catch(() => []),
|
||||||
getPhotosMeta()
|
getPhotosMeta()
|
||||||
.then(({ count }) => count)
|
.then(({ count }) => count)
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
|
...(SHOW_GRID_FIRST
|
||||||
|
? getPhotoSidebarData()
|
||||||
|
: [[], [], []]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
photos.length > 0
|
photos.length > 0
|
||||||
? <div className="space-y-1">
|
? SHOW_GRID_FIRST
|
||||||
<PhotosLarge {...{ photos }} />
|
? <PhotoGridPage
|
||||||
{photosCount > photos.length &&
|
{...{ photos, photosCount, tags, cameras, simulations }}
|
||||||
<PhotosLargeInfinite
|
/>
|
||||||
initialOffset={INFINITE_SCROLL_LARGE_PHOTO_INITIAL}
|
: <PhotoFeedPage
|
||||||
itemsPerPage={INFINITE_SCROLL_LARGE_PHOTO_MULTIPLE}
|
{...{ photos, photosCount }}
|
||||||
/>}
|
/>
|
||||||
</div>
|
|
||||||
: <PhotosEmptyState />
|
: <PhotosEmptyState />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Metadata } from 'next/types';
|
import { Metadata } from 'next/types';
|
||||||
import { CameraProps } from '@/camera';
|
import { CameraProps } from '@/camera';
|
||||||
import { generateMetaForCamera } from '@/camera/meta';
|
import { generateMetaForCamera } from '@/camera/meta';
|
||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { getPhotosCameraDataCached } from '@/camera/data';
|
import { getPhotosCameraDataCached } from '@/camera/data';
|
||||||
import CameraOverview from '@/camera/CameraOverview';
|
import CameraOverview from '@/camera/CameraOverview';
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
@ -12,7 +12,7 @@ const getPhotosCameraDataCachedCached = cache((
|
|||||||
) => getPhotosCameraDataCached(
|
) => getPhotosCameraDataCached(
|
||||||
make,
|
make,
|
||||||
model,
|
model,
|
||||||
INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
INFINITE_SCROLL_GRID_INITIAL,
|
||||||
));
|
));
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { CameraProps } from '@/camera';
|
|||||||
import CameraShareModal from '@/camera/CameraShareModal';
|
import CameraShareModal from '@/camera/CameraShareModal';
|
||||||
import { generateMetaForCamera } from '@/camera/meta';
|
import { generateMetaForCamera } from '@/camera/meta';
|
||||||
import { Metadata } from 'next/types';
|
import { Metadata } from 'next/types';
|
||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { getPhotosCameraDataCached } from '@/camera/data';
|
import { getPhotosCameraDataCached } from '@/camera/data';
|
||||||
import CameraOverview from '@/camera/CameraOverview';
|
import CameraOverview from '@/camera/CameraOverview';
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
@ -13,7 +13,7 @@ const getPhotosCameraDataCachedCached = cache((
|
|||||||
) => getPhotosCameraDataCached(
|
) => getPhotosCameraDataCached(
|
||||||
make,
|
make,
|
||||||
model,
|
model,
|
||||||
INFINITE_SCROLL_GRID_PHOTO_INITIAL,
|
INFINITE_SCROLL_GRID_INITIAL,
|
||||||
));
|
));
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { PATH_ROOT } from '@/site/paths';
|
import { PATH_ROOT } from '@/site/paths';
|
||||||
import { generateMetaForTag } from '@/tag';
|
import { generateMetaForTag } from '@/tag';
|
||||||
import TagOverview from '@/tag/TagOverview';
|
import TagOverview from '@/tag/TagOverview';
|
||||||
@ -8,7 +8,7 @@ import { redirect } from 'next/navigation';
|
|||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
const getPhotosTagDataCachedCached = cache((tag: string) =>
|
const getPhotosTagDataCachedCached = cache((tag: string) =>
|
||||||
getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL}));
|
getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_INITIAL}));
|
||||||
|
|
||||||
interface TagProps {
|
interface TagProps {
|
||||||
params: { tag: string }
|
params: { tag: string }
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { INFINITE_SCROLL_GRID_PHOTO_INITIAL } from '@/photo';
|
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||||
import { generateMetaForTag } from '@/tag';
|
import { generateMetaForTag } from '@/tag';
|
||||||
import TagOverview from '@/tag/TagOverview';
|
import TagOverview from '@/tag/TagOverview';
|
||||||
import TagShareModal from '@/tag/TagShareModal';
|
import TagShareModal from '@/tag/TagShareModal';
|
||||||
@ -7,7 +7,7 @@ import type { Metadata } from 'next';
|
|||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
const getPhotosTagDataCachedCached = cache((tag: string) =>
|
const getPhotosTagDataCachedCached = cache((tag: string) =>
|
||||||
getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_PHOTO_INITIAL }));
|
getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_INITIAL }));
|
||||||
|
|
||||||
interface TagProps {
|
interface TagProps {
|
||||||
params: { tag: string }
|
params: { tag: string }
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Photo, PhotoDateRange } from '@/photo';
|
import { Photo, PhotoDateRange } from '@/photo';
|
||||||
import { Camera, createCameraKey } from '.';
|
import { Camera, createCameraKey } from '.';
|
||||||
import CameraHeader from './CameraHeader';
|
import CameraHeader from './CameraHeader';
|
||||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
import PhotoGridContainer from '@/photo/PhotoGridContainer';
|
||||||
|
|
||||||
export default function CameraOverview({
|
export default function CameraOverview({
|
||||||
camera,
|
camera,
|
||||||
@ -17,7 +17,7 @@ export default function CameraOverview({
|
|||||||
animateOnFirstLoadOnly?: boolean,
|
animateOnFirstLoadOnly?: boolean,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<PhotoGridPage {...{
|
<PhotoGridContainer {...{
|
||||||
cacheKey: `camera-${createCameraKey(camera)}`,
|
cacheKey: `camera-${createCameraKey(camera)}`,
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import { getKeywordsForPhoto, titleForPhoto } from '@/photo';
|
|||||||
import PhotoDate from '@/photo/PhotoDate';
|
import PhotoDate from '@/photo/PhotoDate';
|
||||||
import PhotoSmall from '@/photo/PhotoSmall';
|
import PhotoSmall from '@/photo/PhotoSmall';
|
||||||
import { FaCheck } from 'react-icons/fa6';
|
import { FaCheck } from 'react-icons/fa6';
|
||||||
import { TagsWithMeta, addHiddenToTags, formatTag } from '@/tag';
|
import { Tags, addHiddenToTags, formatTag } from '@/tag';
|
||||||
import { FaTag } from 'react-icons/fa';
|
import { FaTag } from 'react-icons/fa';
|
||||||
import { formatCount, formatCountDescriptive } from '@/utility/string';
|
import { formatCount, formatCountDescriptive } from '@/utility/string';
|
||||||
import CommandKItem from './CommandKItem';
|
import CommandKItem from './CommandKItem';
|
||||||
@ -68,7 +68,7 @@ export default function CommandKClient({
|
|||||||
showDebugTools,
|
showDebugTools,
|
||||||
footer,
|
footer,
|
||||||
}: {
|
}: {
|
||||||
tags: TagsWithMeta
|
tags: Tags
|
||||||
serverSections?: CommandKSection[]
|
serverSections?: CommandKSection[]
|
||||||
showDebugTools?: boolean
|
showDebugTools?: boolean
|
||||||
footer?: string
|
footer?: string
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Photo, PhotoDateRange } from '@/photo';
|
import { Photo, PhotoDateRange } from '@/photo';
|
||||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
import PhotoGridContainer from '@/photo/PhotoGridContainer';
|
||||||
import FocalLengthHeader from './FocalLengthHeader';
|
import FocalLengthHeader from './FocalLengthHeader';
|
||||||
|
|
||||||
export default function FocalLengthOverview({
|
export default function FocalLengthOverview({
|
||||||
@ -16,7 +16,7 @@ export default function FocalLengthOverview({
|
|||||||
animateOnFirstLoadOnly?: boolean,
|
animateOnFirstLoadOnly?: boolean,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<PhotoGridPage {...{
|
<PhotoGridContainer {...{
|
||||||
cacheKey: `focal-${focal}`,
|
cacheKey: `focal-${focal}`,
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Photo } from '../photo';
|
import { Photo } from '../photo';
|
||||||
import IconFullFrame from '@/site/IconFullFrame';
|
import IconFeed from '@/site/IconFeed';
|
||||||
import IconGrid from '@/site/IconGrid';
|
import IconGrid from '@/site/IconGrid';
|
||||||
import ImagePhotoGrid from './components/ImagePhotoGrid';
|
import ImagePhotoGrid from './components/ImagePhotoGrid';
|
||||||
import { NextImageSize } from '@/services/next-image';
|
import { NextImageSize } from '@/services/next-image';
|
||||||
@ -66,7 +66,7 @@ export default function TemplateImageResponse({
|
|||||||
color: '#333',
|
color: '#333',
|
||||||
borderRight: '2px solid #333',
|
borderRight: '2px solid #333',
|
||||||
}}>
|
}}>
|
||||||
<IconFullFrame includeTitle={false} width={80} />
|
<IconFeed includeTitle={false} width={80} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import PhotoForm from './form/PhotoForm';
|
|||||||
import { useFormState } from 'react-dom';
|
import { useFormState } from 'react-dom';
|
||||||
import { areSimpleObjectsEqual } from '@/utility/object';
|
import { areSimpleObjectsEqual } from '@/utility/object';
|
||||||
import { getExifDataAction } from './actions';
|
import { getExifDataAction } from './actions';
|
||||||
import { TagsWithMeta } from '@/tag';
|
import { Tags } from '@/tag';
|
||||||
import AiButton from './ai/AiButton';
|
import AiButton from './ai/AiButton';
|
||||||
import usePhotoFormParent from './form/usePhotoFormParent';
|
import usePhotoFormParent from './form/usePhotoFormParent';
|
||||||
import ExifSyncButton from '@/admin/ExifSyncButton';
|
import ExifSyncButton from '@/admin/ExifSyncButton';
|
||||||
@ -24,7 +24,7 @@ export default function PhotoEditPageClient({
|
|||||||
blurData,
|
blurData,
|
||||||
}: {
|
}: {
|
||||||
photo: Photo
|
photo: Photo
|
||||||
uniqueTags: TagsWithMeta
|
uniqueTags: Tags
|
||||||
hasAiTextGeneration: boolean
|
hasAiTextGeneration: boolean
|
||||||
imageThumbnailBase64: string
|
imageThumbnailBase64: string
|
||||||
blurData: string
|
blurData: string
|
||||||
|
|||||||
26
src/photo/PhotoFeedPage.tsx
Normal file
26
src/photo/PhotoFeedPage.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
INFINITE_SCROLL_FEED_INITIAL,
|
||||||
|
INFINITE_SCROLL_FEED_MULTIPLE,
|
||||||
|
Photo,
|
||||||
|
} from '.';
|
||||||
|
import PhotosLarge from './PhotosLarge';
|
||||||
|
import PhotosLargeInfinite from './PhotosLargeInfinite';
|
||||||
|
|
||||||
|
export default function PhotoFeedPage({
|
||||||
|
photos,
|
||||||
|
photosCount,
|
||||||
|
}:{
|
||||||
|
photos: Photo[]
|
||||||
|
photosCount: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<PhotosLarge {...{ photos }} />
|
||||||
|
{photosCount > photos.length &&
|
||||||
|
<PhotosLargeInfinite
|
||||||
|
initialOffset={INFINITE_SCROLL_FEED_INITIAL}
|
||||||
|
itemsPerPage={INFINITE_SCROLL_FEED_MULTIPLE}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
84
src/photo/PhotoGridContainer.tsx
Normal file
84
src/photo/PhotoGridContainer.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
|
import { Photo } from '.';
|
||||||
|
import PhotoGrid from './PhotoGrid';
|
||||||
|
import PhotoGridInfinite from './PhotoGridInfinite';
|
||||||
|
import { Camera } from '@/camera';
|
||||||
|
import { clsx } from 'clsx/lite';
|
||||||
|
import AnimateItems from '@/components/AnimateItems';
|
||||||
|
import { FilmSimulation } from '@/simulation';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
export default function PhotoGridContainer({
|
||||||
|
cacheKey,
|
||||||
|
photos,
|
||||||
|
count,
|
||||||
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
|
animateOnFirstLoadOnly,
|
||||||
|
header,
|
||||||
|
sidebar,
|
||||||
|
}: {
|
||||||
|
cacheKey: string
|
||||||
|
photos: Photo[]
|
||||||
|
count: number
|
||||||
|
tag?: string
|
||||||
|
camera?: Camera
|
||||||
|
simulation?: FilmSimulation
|
||||||
|
focal?: number
|
||||||
|
animateOnFirstLoadOnly?: boolean
|
||||||
|
header?: JSX.Element
|
||||||
|
sidebar?: JSX.Element
|
||||||
|
}) {
|
||||||
|
const [
|
||||||
|
shouldAnimateDynamicItems,
|
||||||
|
setShouldAnimateDynamicItems,
|
||||||
|
] = useState(false);
|
||||||
|
|
||||||
|
const onAnimationComplete = useCallback(() =>
|
||||||
|
setShouldAnimateDynamicItems(true), []);
|
||||||
|
|
||||||
|
const initialOffset = photos.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SiteGrid
|
||||||
|
contentMain={<div className={clsx(
|
||||||
|
header && 'space-y-8 mt-4',
|
||||||
|
)}>
|
||||||
|
{header &&
|
||||||
|
<AnimateItems
|
||||||
|
type="bottom"
|
||||||
|
items={[header]}
|
||||||
|
animateOnFirstLoadOnly
|
||||||
|
/>}
|
||||||
|
<div className="space-y-0.5 sm:space-y-1">
|
||||||
|
<PhotoGrid {...{
|
||||||
|
photos,
|
||||||
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
|
animateOnFirstLoadOnly,
|
||||||
|
onAnimationComplete,
|
||||||
|
}} />
|
||||||
|
{count > initialOffset &&
|
||||||
|
<PhotoGridInfinite {...{
|
||||||
|
cacheKey,
|
||||||
|
initialOffset,
|
||||||
|
canStart: shouldAnimateDynamicItems,
|
||||||
|
tag,
|
||||||
|
camera,
|
||||||
|
simulation,
|
||||||
|
focal,
|
||||||
|
animateOnFirstLoadOnly,
|
||||||
|
}} />}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
contentSide={sidebar}
|
||||||
|
sideHiddenOnMobile
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Camera } from '@/camera';
|
import { Camera } from '@/camera';
|
||||||
import { INFINITE_SCROLL_GRID_PHOTO_MULTIPLE } from '.';
|
import { INFINITE_SCROLL_GRID_MULTIPLE } from '.';
|
||||||
import InfinitePhotoScroll from './InfinitePhotoScroll';
|
import InfinitePhotoScroll from './InfinitePhotoScroll';
|
||||||
import PhotoGrid from './PhotoGrid';
|
import PhotoGrid from './PhotoGrid';
|
||||||
import { FilmSimulation } from '@/simulation';
|
import { FilmSimulation } from '@/simulation';
|
||||||
@ -29,7 +29,7 @@ export default function PhotoGridInfinite({
|
|||||||
<InfinitePhotoScroll
|
<InfinitePhotoScroll
|
||||||
cacheKey={cacheKey}
|
cacheKey={cacheKey}
|
||||||
initialOffset={initialOffset}
|
initialOffset={initialOffset}
|
||||||
itemsPerPage={INFINITE_SCROLL_GRID_PHOTO_MULTIPLE}
|
itemsPerPage={INFINITE_SCROLL_GRID_MULTIPLE}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
simulation={simulation}
|
simulation={simulation}
|
||||||
|
|||||||
@ -1,84 +1,37 @@
|
|||||||
'use client';
|
import { Tags } from '@/tag';
|
||||||
|
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
|
||||||
import { Photo } from '.';
|
import { Photo } from '.';
|
||||||
import PhotoGrid from './PhotoGrid';
|
import { Cameras } from '@/camera';
|
||||||
import PhotoGridInfinite from './PhotoGridInfinite';
|
import { FilmSimulations } from '@/simulation';
|
||||||
import { Camera } from '@/camera';
|
import { PATH_GRID } from '@/site/paths';
|
||||||
import { clsx } from 'clsx/lite';
|
import PhotoGridSidebar from './PhotoGridSidebar';
|
||||||
import AnimateItems from '@/components/AnimateItems';
|
import PhotoGridContainer from './PhotoGridContainer';
|
||||||
import { FilmSimulation } from '@/simulation';
|
|
||||||
import { useCallback, useState } from 'react';
|
|
||||||
|
|
||||||
export default function PhotoGridPage({
|
export default function PhotoGridPage({
|
||||||
cacheKey,
|
|
||||||
photos,
|
photos,
|
||||||
count,
|
photosCount,
|
||||||
tag,
|
tags,
|
||||||
camera,
|
cameras,
|
||||||
simulation,
|
simulations,
|
||||||
focal,
|
}:{
|
||||||
animateOnFirstLoadOnly,
|
|
||||||
header,
|
|
||||||
sidebar,
|
|
||||||
}: {
|
|
||||||
cacheKey: string
|
|
||||||
photos: Photo[]
|
photos: Photo[]
|
||||||
count: number
|
photosCount: number
|
||||||
tag?: string
|
tags: Tags
|
||||||
camera?: Camera
|
cameras: Cameras
|
||||||
simulation?: FilmSimulation
|
simulations: FilmSimulations
|
||||||
focal?: number
|
|
||||||
animateOnFirstLoadOnly?: boolean
|
|
||||||
header?: JSX.Element
|
|
||||||
sidebar?: JSX.Element
|
|
||||||
}) {
|
}) {
|
||||||
const [
|
|
||||||
shouldAnimateDynamicItems,
|
|
||||||
setShouldAnimateDynamicItems,
|
|
||||||
] = useState(false);
|
|
||||||
|
|
||||||
const onAnimationComplete = useCallback(() =>
|
|
||||||
setShouldAnimateDynamicItems(true), []);
|
|
||||||
|
|
||||||
const initialOffset = photos.length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SiteGrid
|
<PhotoGridContainer
|
||||||
contentMain={<div className={clsx(
|
cacheKey={`page-${PATH_GRID}`}
|
||||||
header && 'space-y-8 mt-4',
|
photos={photos}
|
||||||
)}>
|
count={photosCount}
|
||||||
{header &&
|
sidebar={<div className="sticky top-4 space-y-4 mt-[-4px]">
|
||||||
<AnimateItems
|
<PhotoGridSidebar {...{
|
||||||
type="bottom"
|
tags,
|
||||||
items={[header]}
|
cameras,
|
||||||
animateOnFirstLoadOnly
|
simulations,
|
||||||
/>}
|
photosCount,
|
||||||
<div className="space-y-0.5 sm:space-y-1">
|
}} />
|
||||||
<PhotoGrid {...{
|
|
||||||
photos,
|
|
||||||
tag,
|
|
||||||
camera,
|
|
||||||
simulation,
|
|
||||||
focal,
|
|
||||||
animateOnFirstLoadOnly,
|
|
||||||
onAnimationComplete,
|
|
||||||
}} />
|
|
||||||
{count > initialOffset &&
|
|
||||||
<PhotoGridInfinite {...{
|
|
||||||
cacheKey,
|
|
||||||
initialOffset,
|
|
||||||
canStart: shouldAnimateDynamicItems,
|
|
||||||
tag,
|
|
||||||
camera,
|
|
||||||
simulation,
|
|
||||||
focal,
|
|
||||||
animateOnFirstLoadOnly,
|
|
||||||
}} />}
|
|
||||||
</div>
|
|
||||||
</div>}
|
</div>}
|
||||||
contentSide={sidebar}
|
|
||||||
sideHiddenOnMobile
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import PhotoTag from '@/tag/PhotoTag';
|
|||||||
import { FaTag } from 'react-icons/fa';
|
import { FaTag } from 'react-icons/fa';
|
||||||
import { IoMdCamera } from 'react-icons/io';
|
import { IoMdCamera } from 'react-icons/io';
|
||||||
import { PhotoDateRange, dateRangeForPhotos, photoQuantityText } from '.';
|
import { PhotoDateRange, dateRangeForPhotos, photoQuantityText } from '.';
|
||||||
import { TAG_FAVS, TAG_HIDDEN, TagsWithMeta, addHiddenToTags } from '@/tag';
|
import { TAG_FAVS, TAG_HIDDEN, Tags, addHiddenToTags } from '@/tag';
|
||||||
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
|
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
|
||||||
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
|
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
|
||||||
import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation';
|
import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation';
|
||||||
@ -23,7 +23,7 @@ export default function PhotoGridSidebar({
|
|||||||
photosCount,
|
photosCount,
|
||||||
photosDateRange,
|
photosDateRange,
|
||||||
}: {
|
}: {
|
||||||
tags: TagsWithMeta
|
tags: Tags
|
||||||
cameras: Cameras
|
cameras: Cameras
|
||||||
simulations: FilmSimulations
|
simulations: FilmSimulations
|
||||||
photosCount: number
|
photosCount: number
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import AdminChildPage from '@/components/AdminChildPage';
|
|||||||
import { PATH_ADMIN_UPLOADS } from '@/site/paths';
|
import { PATH_ADMIN_UPLOADS } from '@/site/paths';
|
||||||
import { PhotoFormData, generateTakenAtFields } from './form';
|
import { PhotoFormData, generateTakenAtFields } from './form';
|
||||||
import PhotoForm from './form/PhotoForm';
|
import PhotoForm from './form/PhotoForm';
|
||||||
import { TagsWithMeta } from '@/tag';
|
import { Tags } from '@/tag';
|
||||||
import usePhotoFormParent from './form/usePhotoFormParent';
|
import usePhotoFormParent from './form/usePhotoFormParent';
|
||||||
import AiButton from './ai/AiButton';
|
import AiButton from './ai/AiButton';
|
||||||
import { AiAutoGeneratedField } from './ai';
|
import { AiAutoGeneratedField } from './ai';
|
||||||
@ -21,7 +21,7 @@ export default function UploadPageClient({
|
|||||||
}: {
|
}: {
|
||||||
blobId?: string
|
blobId?: string
|
||||||
photoFormExif: Partial<PhotoFormData>
|
photoFormExif: Partial<PhotoFormData>
|
||||||
uniqueTags: TagsWithMeta
|
uniqueTags: Tags
|
||||||
hasAiTextGeneration?: boolean
|
hasAiTextGeneration?: boolean
|
||||||
textFieldsToAutoGenerate?: AiAutoGeneratedField[],
|
textFieldsToAutoGenerate?: AiAutoGeneratedField[],
|
||||||
imageThumbnailBase64?: string
|
imageThumbnailBase64?: string
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import {
|
|||||||
PATHS_ADMIN,
|
PATHS_ADMIN,
|
||||||
PATHS_TO_CACHE,
|
PATHS_TO_CACHE,
|
||||||
PATH_ADMIN,
|
PATH_ADMIN,
|
||||||
|
PATH_FEED,
|
||||||
PATH_GRID,
|
PATH_GRID,
|
||||||
PATH_ROOT,
|
PATH_ROOT,
|
||||||
PREFIX_CAMERA,
|
PREFIX_CAMERA,
|
||||||
@ -126,6 +127,7 @@ export const revalidatePhoto = (photoId: string) => {
|
|||||||
revalidatePath(pathForPhoto({ photo: photoId }), 'layout');
|
revalidatePath(pathForPhoto({ photo: photoId }), 'layout');
|
||||||
revalidatePath(PATH_ROOT, 'layout');
|
revalidatePath(PATH_ROOT, 'layout');
|
||||||
revalidatePath(PATH_GRID, 'layout');
|
revalidatePath(PATH_GRID, 'layout');
|
||||||
|
revalidatePath(PATH_FEED, 'layout');
|
||||||
revalidatePath(PREFIX_TAG, 'layout');
|
revalidatePath(PREFIX_TAG, 'layout');
|
||||||
revalidatePath(PREFIX_CAMERA, 'layout');
|
revalidatePath(PREFIX_CAMERA, 'layout');
|
||||||
revalidatePath(PREFIX_FILM_SIMULATION, 'layout');
|
revalidatePath(PREFIX_FILM_SIMULATION, 'layout');
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
getPhotosMetaCached,
|
|
||||||
getUniqueCamerasCached,
|
getUniqueCamerasCached,
|
||||||
getUniqueFilmSimulationsCached,
|
getUniqueFilmSimulationsCached,
|
||||||
getUniqueTagsCached,
|
getUniqueTagsCached,
|
||||||
} from '@/photo/cache';
|
} from '@/photo/cache';
|
||||||
import {
|
import {
|
||||||
getPhotosMeta,
|
|
||||||
getUniqueCameras,
|
getUniqueCameras,
|
||||||
getUniqueFilmSimulations,
|
getUniqueFilmSimulations,
|
||||||
getUniqueTags,
|
getUniqueTags,
|
||||||
@ -14,9 +12,6 @@ import { SHOW_FILM_SIMULATIONS } from '@/site/config';
|
|||||||
import { sortTagsObject } from '@/tag';
|
import { sortTagsObject } from '@/tag';
|
||||||
|
|
||||||
export const getPhotoSidebarData = () => [
|
export const getPhotoSidebarData = () => [
|
||||||
getPhotosMeta()
|
|
||||||
.then(({ count }) => count)
|
|
||||||
.catch(() => 0),
|
|
||||||
getUniqueTags().then(sortTagsObject).catch(() => []),
|
getUniqueTags().then(sortTagsObject).catch(() => []),
|
||||||
getUniqueCameras().catch(() => []),
|
getUniqueCameras().catch(() => []),
|
||||||
SHOW_FILM_SIMULATIONS
|
SHOW_FILM_SIMULATIONS
|
||||||
@ -25,9 +20,6 @@ export const getPhotoSidebarData = () => [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const getPhotoSidebarDataCached = () => [
|
export const getPhotoSidebarDataCached = () => [
|
||||||
getPhotosMetaCached()
|
|
||||||
.then(({ count }) => count)
|
|
||||||
.catch(() => 0),
|
|
||||||
getUniqueTagsCached().then(sortTagsObject),
|
getUniqueTagsCached().then(sortTagsObject),
|
||||||
getUniqueCamerasCached(),
|
getUniqueCamerasCached(),
|
||||||
SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [],
|
SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [],
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
PhotoDateRange,
|
PhotoDateRange,
|
||||||
} from '@/photo';
|
} from '@/photo';
|
||||||
import { Cameras, createCameraKey } from '@/camera';
|
import { Cameras, createCameraKey } from '@/camera';
|
||||||
import { TagsWithMeta } from '@/tag';
|
import { Tags } from '@/tag';
|
||||||
import { FilmSimulation, FilmSimulations } from '@/simulation';
|
import { FilmSimulation, FilmSimulations } from '@/simulation';
|
||||||
import { SHOULD_DEBUG_SQL } from '@/site/config';
|
import { SHOULD_DEBUG_SQL } from '@/site/config';
|
||||||
import {
|
import {
|
||||||
@ -261,7 +261,7 @@ export const getUniqueTags = async () =>
|
|||||||
WHERE hidden IS NOT TRUE
|
WHERE hidden IS NOT TRUE
|
||||||
GROUP BY tag
|
GROUP BY tag
|
||||||
ORDER BY tag ASC
|
ORDER BY tag ASC
|
||||||
`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({
|
`.then(({ rows }): Tags => rows.map(({ tag, count }) => ({
|
||||||
tag: tag as string,
|
tag: tag as string,
|
||||||
count: parseInt(count, 10),
|
count: parseInt(count, 10),
|
||||||
})))
|
})))
|
||||||
@ -273,7 +273,7 @@ export const getUniqueTagsHidden = async () =>
|
|||||||
FROM photos
|
FROM photos
|
||||||
GROUP BY tag
|
GROUP BY tag
|
||||||
ORDER BY tag ASC
|
ORDER BY tag ASC
|
||||||
`.then(({ rows }): TagsWithMeta => rows.map(({ tag, count }) => ({
|
`.then(({ rows }): Tags => rows.map(({ tag, count }) => ({
|
||||||
tag: tag as string,
|
tag: tag as string,
|
||||||
count: parseInt(count, 10),
|
count: parseInt(count, 10),
|
||||||
})))
|
})))
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/site/paths';
|
|||||||
import { toastSuccess, toastWarning } from '@/toast';
|
import { toastSuccess, toastWarning } from '@/toast';
|
||||||
import { getDimensionsFromSize } from '@/utility/size';
|
import { getDimensionsFromSize } from '@/utility/size';
|
||||||
import ImageWithFallback from '@/components/image/ImageWithFallback';
|
import ImageWithFallback from '@/components/image/ImageWithFallback';
|
||||||
import { TagsWithMeta, convertTagsForForm } from '@/tag';
|
import { Tags, convertTagsForForm } from '@/tag';
|
||||||
import { AiContent } from '../ai/useAiImageQueries';
|
import { AiContent } from '../ai/useAiImageQueries';
|
||||||
import AiButton from '../ai/AiButton';
|
import AiButton from '../ai/AiButton';
|
||||||
import Spinner from '@/components/Spinner';
|
import Spinner from '@/components/Spinner';
|
||||||
@ -49,7 +49,7 @@ export default function PhotoForm({
|
|||||||
initialPhotoForm: Partial<PhotoFormData>
|
initialPhotoForm: Partial<PhotoFormData>
|
||||||
updatedExifData?: Partial<PhotoFormData>
|
updatedExifData?: Partial<PhotoFormData>
|
||||||
updatedBlurData?: string
|
updatedBlurData?: string
|
||||||
uniqueTags?: TagsWithMeta
|
uniqueTags?: Tags
|
||||||
aiContent?: AiContent
|
aiContent?: AiContent
|
||||||
shouldStripGpsData?: boolean
|
shouldStripGpsData?: boolean
|
||||||
onTitleChange?: (updatedTitle: string) => void
|
onTitleChange?: (updatedTitle: string) => void
|
||||||
|
|||||||
@ -16,17 +16,17 @@ import type { Metadata } from 'next';
|
|||||||
|
|
||||||
export const OUTDATED_THRESHOLD = new Date('2024-06-16');
|
export const OUTDATED_THRESHOLD = new Date('2024-06-16');
|
||||||
|
|
||||||
// INFINITE SCROLL: LARGE PHOTOS
|
// INFINITE SCROLL: FEED
|
||||||
export const INFINITE_SCROLL_LARGE_PHOTO_INITIAL =
|
export const INFINITE_SCROLL_FEED_INITIAL =
|
||||||
process.env.NODE_ENV === 'development' ? 2 : 12;
|
process.env.NODE_ENV === 'development' ? 2 : 12;
|
||||||
export const INFINITE_SCROLL_LARGE_PHOTO_MULTIPLE =
|
export const INFINITE_SCROLL_FEED_MULTIPLE =
|
||||||
process.env.NODE_ENV === 'development' ? 2 : 24;
|
process.env.NODE_ENV === 'development' ? 2 : 24;
|
||||||
|
|
||||||
// INFINITE SCROLL: GRID PHOTOS
|
// INFINITE SCROLL: GRID
|
||||||
export const INFINITE_SCROLL_GRID_PHOTO_INITIAL = HIGH_DENSITY_GRID
|
export const INFINITE_SCROLL_GRID_INITIAL = HIGH_DENSITY_GRID
|
||||||
? process.env.NODE_ENV === 'development' ? 12 : 24
|
? process.env.NODE_ENV === 'development' ? 12 : 24
|
||||||
: process.env.NODE_ENV === 'development' ? 12 : 24;
|
: process.env.NODE_ENV === 'development' ? 12 : 24;
|
||||||
export const INFINITE_SCROLL_GRID_PHOTO_MULTIPLE = HIGH_DENSITY_GRID
|
export const INFINITE_SCROLL_GRID_MULTIPLE = HIGH_DENSITY_GRID
|
||||||
? process.env.NODE_ENV === 'development' ? 12 : 48
|
? process.env.NODE_ENV === 'development' ? 12 : 48
|
||||||
: process.env.NODE_ENV === 'development' ? 12 : 48;
|
: process.env.NODE_ENV === 'development' ? 12 : 48;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Photo, PhotoDateRange } from '@/photo';
|
import { Photo, PhotoDateRange } from '@/photo';
|
||||||
import FilmSimulationHeader from './FilmSimulationHeader';
|
import FilmSimulationHeader from './FilmSimulationHeader';
|
||||||
import { FilmSimulation } from '.';
|
import { FilmSimulation } from '.';
|
||||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
import PhotoGridContainer from '@/photo/PhotoGridContainer';
|
||||||
|
|
||||||
export default function FilmSimulationOverview({
|
export default function FilmSimulationOverview({
|
||||||
simulation,
|
simulation,
|
||||||
@ -17,7 +17,7 @@ export default function FilmSimulationOverview({
|
|||||||
animateOnFirstLoadOnly?: boolean,
|
animateOnFirstLoadOnly?: boolean,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<PhotoGridPage {...{
|
<PhotoGridContainer {...{
|
||||||
cacheKey: `simulation-${simulation}`,
|
cacheKey: `simulation-${simulation}`,
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
const INTRINSIC_WIDTH = 28;
|
const INTRINSIC_WIDTH = 28;
|
||||||
const INTRINSIC_HEIGHT = 24;
|
const INTRINSIC_HEIGHT = 24;
|
||||||
|
|
||||||
export default function IconFullFrame({
|
export default function IconFeed({
|
||||||
width = INTRINSIC_WIDTH,
|
width = INTRINSIC_WIDTH,
|
||||||
includeTitle = true,
|
includeTitle = true,
|
||||||
}: {
|
}: {
|
||||||
@ -8,12 +8,14 @@ import ViewSwitcher, { SwitcherSelection } from '@/site/ViewSwitcher';
|
|||||||
import {
|
import {
|
||||||
PATH_ROOT,
|
PATH_ROOT,
|
||||||
isPathAdmin,
|
isPathAdmin,
|
||||||
|
isPathFeed,
|
||||||
isPathGrid,
|
isPathGrid,
|
||||||
isPathProtected,
|
isPathProtected,
|
||||||
isPathSignIn,
|
isPathSignIn,
|
||||||
} from '@/site/paths';
|
} from '@/site/paths';
|
||||||
import AnimateItems from '../components/AnimateItems';
|
import AnimateItems from '../components/AnimateItems';
|
||||||
import { useAppState } from '@/state/AppState';
|
import { useAppState } from '@/state/AppState';
|
||||||
|
import { SHOW_GRID_FIRST } from './config';
|
||||||
|
|
||||||
export default function Nav({
|
export default function Nav({
|
||||||
siteDomainOrTitle,
|
siteDomainOrTitle,
|
||||||
@ -36,9 +38,11 @@ export default function Nav({
|
|||||||
|
|
||||||
const switcherSelectionForPath = (): SwitcherSelection | undefined => {
|
const switcherSelectionForPath = (): SwitcherSelection | undefined => {
|
||||||
if (pathname === PATH_ROOT) {
|
if (pathname === PATH_ROOT) {
|
||||||
return 'full-frame';
|
return SHOW_GRID_FIRST ? 'grid' : 'feed';
|
||||||
} else if (isPathGrid(pathname)) {
|
} else if (isPathGrid(pathname)) {
|
||||||
return 'grid';
|
return 'grid';
|
||||||
|
} else if (isPathFeed(pathname)) {
|
||||||
|
return 'feed';
|
||||||
} else if (isPathProtected(pathname)) {
|
} else if (isPathProtected(pathname)) {
|
||||||
return 'admin';
|
return 'admin';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
import Switcher from '@/components/Switcher';
|
import Switcher from '@/components/Switcher';
|
||||||
import SwitcherItem from '@/components/SwitcherItem';
|
import SwitcherItem from '@/components/SwitcherItem';
|
||||||
import IconFullFrame from '@/site/IconFullFrame';
|
import IconFeed from '@/site/IconFeed';
|
||||||
import IconGrid from '@/site/IconGrid';
|
import IconGrid from '@/site/IconGrid';
|
||||||
import { PATH_ADMIN_PHOTOS, PATH_GRID } from '@/site/paths';
|
import {
|
||||||
|
PATH_ADMIN_PHOTOS,
|
||||||
|
PATH_FEED,
|
||||||
|
PATH_GRID,
|
||||||
|
PATH_ROOT,
|
||||||
|
} from '@/site/paths';
|
||||||
import { BiLockAlt } from 'react-icons/bi';
|
import { BiLockAlt } from 'react-icons/bi';
|
||||||
import IconSearch from './IconSearch';
|
import IconSearch from './IconSearch';
|
||||||
import { useAppState } from '@/state/AppState';
|
import { useAppState } from '@/state/AppState';
|
||||||
|
import { SHOW_GRID_FIRST } from './config';
|
||||||
|
|
||||||
export type SwitcherSelection = 'full-frame' | 'grid' | 'sets' | 'admin';
|
export type SwitcherSelection = 'feed' | 'grid' | 'admin';
|
||||||
|
|
||||||
export default function ViewSwitcher({
|
export default function ViewSwitcher({
|
||||||
currentSelection,
|
currentSelection,
|
||||||
@ -18,21 +24,27 @@ export default function ViewSwitcher({
|
|||||||
}) {
|
}) {
|
||||||
const { setIsCommandKOpen } = useAppState();
|
const { setIsCommandKOpen } = useAppState();
|
||||||
|
|
||||||
|
const renderItemFeed = () =>
|
||||||
|
<SwitcherItem
|
||||||
|
icon={<IconFeed />}
|
||||||
|
href={SHOW_GRID_FIRST ? PATH_FEED : PATH_ROOT}
|
||||||
|
active={currentSelection === 'feed'}
|
||||||
|
noPadding
|
||||||
|
/>;
|
||||||
|
|
||||||
|
const renderItemGrid = () =>
|
||||||
|
<SwitcherItem
|
||||||
|
icon={<IconGrid />}
|
||||||
|
href={SHOW_GRID_FIRST ? PATH_ROOT : PATH_GRID}
|
||||||
|
active={currentSelection === 'grid'}
|
||||||
|
noPadding
|
||||||
|
/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-1 sm:gap-2">
|
<div className="flex gap-1 sm:gap-2">
|
||||||
<Switcher>
|
<Switcher>
|
||||||
<SwitcherItem
|
{SHOW_GRID_FIRST ? renderItemGrid() : renderItemFeed()}
|
||||||
icon={<IconFullFrame />}
|
{SHOW_GRID_FIRST ? renderItemFeed() : renderItemGrid()}
|
||||||
href="/"
|
|
||||||
active={currentSelection === 'full-frame'}
|
|
||||||
noPadding
|
|
||||||
/>
|
|
||||||
<SwitcherItem
|
|
||||||
icon={<IconGrid />}
|
|
||||||
href={PATH_GRID}
|
|
||||||
active={currentSelection === 'grid'}
|
|
||||||
noPadding
|
|
||||||
/>
|
|
||||||
{showAdmin &&
|
{showAdmin &&
|
||||||
<SwitcherItem
|
<SwitcherItem
|
||||||
icon={<BiLockAlt size={16} className="translate-y-[-0.5px]" />}
|
icon={<BiLockAlt size={16} className="translate-y-[-0.5px]" />}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { TAG_HIDDEN } from '@/tag';
|
|||||||
// Core paths
|
// Core paths
|
||||||
export const PATH_ROOT = '/';
|
export const PATH_ROOT = '/';
|
||||||
export const PATH_GRID = '/grid';
|
export const PATH_GRID = '/grid';
|
||||||
|
export const PATH_FEED = '/feed';
|
||||||
export const PATH_ADMIN = '/admin';
|
export const PATH_ADMIN = '/admin';
|
||||||
export const PATH_API = '/api';
|
export const PATH_API = '/api';
|
||||||
export const PATH_SIGN_IN = '/sign-in';
|
export const PATH_SIGN_IN = '/sign-in';
|
||||||
@ -37,8 +38,8 @@ export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
|
|||||||
export const PATH_ADMIN_BASELINE = `${PATH_ADMIN}/baseline`;
|
export const PATH_ADMIN_BASELINE = `${PATH_ADMIN}/baseline`;
|
||||||
|
|
||||||
// Debug paths
|
// Debug paths
|
||||||
export const PATH_OG_ALL = `${PATH_OG}/all`;
|
export const PATH_OG_ALL = `${PATH_OG}/all`;
|
||||||
export const PATH_OG_SAMPLE = `${PATH_OG}/sample`;
|
export const PATH_OG_SAMPLE = `${PATH_OG}/sample`;
|
||||||
|
|
||||||
// API paths
|
// API paths
|
||||||
export const PATH_API_STORAGE = `${PATH_API}/storage`;
|
export const PATH_API_STORAGE = `${PATH_API}/storage`;
|
||||||
@ -60,6 +61,7 @@ export const PATHS_ADMIN = [
|
|||||||
export const PATHS_TO_CACHE = [
|
export const PATHS_TO_CACHE = [
|
||||||
PATH_ROOT,
|
PATH_ROOT,
|
||||||
PATH_GRID,
|
PATH_GRID,
|
||||||
|
PATH_FEED,
|
||||||
PATH_OG,
|
PATH_OG,
|
||||||
PATH_PHOTO_DYNAMIC,
|
PATH_PHOTO_DYNAMIC,
|
||||||
PATH_TAG_DYNAMIC,
|
PATH_TAG_DYNAMIC,
|
||||||
@ -252,6 +254,9 @@ export const checkPathPrefix = (pathname = '', prefix: string) =>
|
|||||||
export const isPathGrid = (pathname?: string) =>
|
export const isPathGrid = (pathname?: string) =>
|
||||||
checkPathPrefix(pathname, PATH_GRID);
|
checkPathPrefix(pathname, PATH_GRID);
|
||||||
|
|
||||||
|
export const isPathFeed = (pathname?: string) =>
|
||||||
|
checkPathPrefix(pathname, PATH_FEED);
|
||||||
|
|
||||||
export const isPathSignIn = (pathname?: string) =>
|
export const isPathSignIn = (pathname?: string) =>
|
||||||
checkPathPrefix(pathname, PATH_SIGN_IN);
|
checkPathPrefix(pathname, PATH_SIGN_IN);
|
||||||
|
|
||||||
@ -334,7 +339,7 @@ export const getEscapePath = (pathname?: string) => {
|
|||||||
(simulation && isPathFilmSimulation(pathname)) ||
|
(simulation && isPathFilmSimulation(pathname)) ||
|
||||||
(focal && isPathFocalLength(pathname))
|
(focal && isPathFocalLength(pathname))
|
||||||
) {
|
) {
|
||||||
return PATH_GRID;
|
return PATH_ROOT;
|
||||||
} else if (photoId && isPathTagPhotoShare(pathname)) {
|
} else if (photoId && isPathTagPhotoShare(pathname)) {
|
||||||
return pathForPhoto({ photo: photoId, tag });
|
return pathForPhoto({ photo: photoId, tag });
|
||||||
} else if (photoId && isPathCameraPhotoShare(pathname)) {
|
} else if (photoId && isPathCameraPhotoShare(pathname)) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Photo, PhotoDateRange } from '@/photo';
|
import { Photo, PhotoDateRange } from '@/photo';
|
||||||
import TagHeader from './TagHeader';
|
import TagHeader from './TagHeader';
|
||||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
import PhotoGridContainer from '@/photo/PhotoGridContainer';
|
||||||
|
|
||||||
export default function TagOverview({
|
export default function TagOverview({
|
||||||
tag,
|
tag,
|
||||||
@ -16,7 +16,7 @@ export default function TagOverview({
|
|||||||
animateOnFirstLoadOnly?: boolean,
|
animateOnFirstLoadOnly?: boolean,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<PhotoGridPage {...{
|
<PhotoGridContainer {...{
|
||||||
cacheKey: `tag-${tag}`,
|
cacheKey: `tag-${tag}`,
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
export const TAG_FAVS = 'favs';
|
export const TAG_FAVS = 'favs';
|
||||||
export const TAG_HIDDEN = 'hidden';
|
export const TAG_HIDDEN = 'hidden';
|
||||||
|
|
||||||
export type TagsWithMeta = {
|
export type Tags = {
|
||||||
tag: string
|
tag: string
|
||||||
count: number
|
count: number
|
||||||
}[]
|
}[]
|
||||||
@ -57,7 +57,7 @@ export const sortTags = (
|
|||||||
.sort((a, b) => isTagFavs(a) ? -1 : a.localeCompare(b));
|
.sort((a, b) => isTagFavs(a) ? -1 : a.localeCompare(b));
|
||||||
|
|
||||||
export const sortTagsObject = (
|
export const sortTagsObject = (
|
||||||
tags: TagsWithMeta,
|
tags: Tags,
|
||||||
tagToHide?: string,
|
tagToHide?: string,
|
||||||
) => tags
|
) => tags
|
||||||
.filter(({ tag }) => tag!== tagToHide)
|
.filter(({ tag }) => tag!== tagToHide)
|
||||||
@ -66,7 +66,7 @@ export const sortTagsObject = (
|
|||||||
export const sortTagsWithoutFavs = (tags: string[]) =>
|
export const sortTagsWithoutFavs = (tags: string[]) =>
|
||||||
sortTags(tags, TAG_FAVS);
|
sortTags(tags, TAG_FAVS);
|
||||||
|
|
||||||
export const sortTagsObjectWithoutFavs = (tags: TagsWithMeta) =>
|
export const sortTagsObjectWithoutFavs = (tags: Tags) =>
|
||||||
sortTagsObject(tags, TAG_FAVS);
|
sortTagsObject(tags, TAG_FAVS);
|
||||||
|
|
||||||
export const descriptionForTaggedPhotos = (
|
export const descriptionForTaggedPhotos = (
|
||||||
@ -105,7 +105,7 @@ export const isPathFavs = (pathname?: string) =>
|
|||||||
|
|
||||||
export const isTagHidden = (tag: string) => tag.toLowerCase() === TAG_HIDDEN;
|
export const isTagHidden = (tag: string) => tag.toLowerCase() === TAG_HIDDEN;
|
||||||
|
|
||||||
export const addHiddenToTags = (tags: TagsWithMeta, hiddenPhotosCount = 0) => {
|
export const addHiddenToTags = (tags: Tags, hiddenPhotosCount = 0) => {
|
||||||
if (hiddenPhotosCount > 0) {
|
if (hiddenPhotosCount > 0) {
|
||||||
return tags
|
return tags
|
||||||
.filter(({ tag }) => tag === TAG_FAVS)
|
.filter(({ tag }) => tag === TAG_FAVS)
|
||||||
@ -116,7 +116,7 @@ export const addHiddenToTags = (tags: TagsWithMeta, hiddenPhotosCount = 0) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const convertTagsForForm = (tags: TagsWithMeta = []) =>
|
export const convertTagsForForm = (tags: Tags = []) =>
|
||||||
sortTagsObjectWithoutFavs(tags)
|
sortTagsObjectWithoutFavs(tags)
|
||||||
.map(({ tag, count }) => ({
|
.map(({ tag, count }) => ({
|
||||||
value: tag,
|
value: tag,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user