Add special "favs" tag
This commit is contained in:
parent
91e1fb2166
commit
0f632fe236
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -11,6 +11,7 @@
|
|||||||
"exif",
|
"exif",
|
||||||
"exifr",
|
"exifr",
|
||||||
"exiftool",
|
"exiftool",
|
||||||
|
"favs",
|
||||||
"ghijklmnopqrstuv",
|
"ghijklmnopqrstuv",
|
||||||
"hgetall",
|
"hgetall",
|
||||||
"hset",
|
"hset",
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { getPhotosCached } from '@/cache';
|
||||||
getPhotosCached,
|
|
||||||
getPhotosCountCached,
|
|
||||||
getUniqueCamerasCached,
|
|
||||||
getUniqueFilmSimulationsCached,
|
|
||||||
getUniqueTagsCached,
|
|
||||||
} from '@/cache';
|
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import { generateOgImageMetaForPhotos } from '@/photo';
|
import { generateOgImageMetaForPhotos } from '@/photo';
|
||||||
import PhotoGrid from '@/photo/PhotoGrid';
|
import PhotoGrid from '@/photo/PhotoGrid';
|
||||||
@ -17,7 +11,7 @@ import {
|
|||||||
getPaginationForSearchParams,
|
getPaginationForSearchParams,
|
||||||
} from '@/site/pagination';
|
} from '@/site/pagination';
|
||||||
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
|
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
|
||||||
import { SHOW_FILM_SIMULATIONS } from '@/site/config';
|
import { getPhotoSidebarDataCached } from '@/photo/data';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
@ -37,10 +31,7 @@ export default async function GridPage({ searchParams }: PaginationParams) {
|
|||||||
simulations,
|
simulations,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getPhotosCached({ limit }),
|
getPhotosCached({ limit }),
|
||||||
getPhotosCountCached(),
|
...getPhotoSidebarDataCached(),
|
||||||
getUniqueTagsCached(),
|
|
||||||
getUniqueCamerasCached(),
|
|
||||||
SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const showMorePath = photosCount > photos.length
|
const showMorePath = photosCount > photos.length
|
||||||
|
|||||||
@ -1,17 +1,11 @@
|
|||||||
import {
|
import { getPhotosCached } from '@/cache';
|
||||||
getPhotosCached,
|
|
||||||
getPhotosCountCached,
|
|
||||||
getUniqueCamerasCached,
|
|
||||||
getUniqueFilmSimulationsCached,
|
|
||||||
getUniqueTagsCached,
|
|
||||||
} from '@/cache';
|
|
||||||
import InfoBlock from '@/components/InfoBlock';
|
import InfoBlock from '@/components/InfoBlock';
|
||||||
import RedirectOnDesktop from '@/components/RedirectOnDesktop';
|
import RedirectOnDesktop from '@/components/RedirectOnDesktop';
|
||||||
import SiteGrid from '@/components/SiteGrid';
|
import SiteGrid from '@/components/SiteGrid';
|
||||||
import { generateOgImageMetaForPhotos } from '@/photo';
|
import { generateOgImageMetaForPhotos } from '@/photo';
|
||||||
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
|
import PhotoGridSidebar from '@/photo/PhotoGridSidebar';
|
||||||
|
import { getPhotoSidebarDataCached } from '@/photo/data';
|
||||||
import { MAX_PHOTOS_TO_SHOW_OG } from '@/photo/image-response';
|
import { MAX_PHOTOS_TO_SHOW_OG } from '@/photo/image-response';
|
||||||
import { SHOW_FILM_SIMULATIONS } from '@/site/config';
|
|
||||||
import { PATH_GRID } from '@/site/paths';
|
import { PATH_GRID } from '@/site/paths';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
@ -26,12 +20,7 @@ export default async function SetsPage() {
|
|||||||
tags,
|
tags,
|
||||||
cameras,
|
cameras,
|
||||||
simulations,
|
simulations,
|
||||||
] = await Promise.all([
|
] = await Promise.all(getPhotoSidebarDataCached());
|
||||||
getPhotosCountCached(),
|
|
||||||
getUniqueTagsCached(),
|
|
||||||
getUniqueCamerasCached(),
|
|
||||||
SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SiteGrid
|
<SiteGrid
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export default function PhotoCamera({
|
|||||||
hideAppleIcon,
|
hideAppleIcon,
|
||||||
type = 'icon-first',
|
type = 'icon-first',
|
||||||
badged,
|
badged,
|
||||||
dim,
|
contrast,
|
||||||
countOnHover,
|
countOnHover,
|
||||||
}: {
|
}: {
|
||||||
camera: Camera
|
camera: Camera
|
||||||
@ -45,7 +45,7 @@ export default function PhotoCamera({
|
|||||||
/>}
|
/>}
|
||||||
type={showAppleIcon && isCameraApple ? 'icon-first' : type}
|
type={showAppleIcon && isCameraApple ? 'icon-first' : type}
|
||||||
badged={badged}
|
badged={badged}
|
||||||
dim={dim}
|
contrast={contrast}
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,30 +2,39 @@ import { clsx } from 'clsx';
|
|||||||
|
|
||||||
export default function Badge({
|
export default function Badge({
|
||||||
children,
|
children,
|
||||||
type = 'primary',
|
type = 'large',
|
||||||
|
highContrast,
|
||||||
uppercase,
|
uppercase,
|
||||||
interactive,
|
interactive,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
type?: 'primary' | 'secondary' | 'text-only'
|
type?: 'large' | 'small' | 'text-only'
|
||||||
|
highContrast?: boolean
|
||||||
uppercase?: boolean
|
uppercase?: boolean
|
||||||
interactive?: boolean
|
interactive?: boolean
|
||||||
}) {
|
}) {
|
||||||
const stylesForType = () => {
|
const stylesForType = () => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'primary': return clsx(
|
case 'large':
|
||||||
'px-1.5 py-[0.3rem] rounded-md',
|
return clsx(
|
||||||
'bg-gray-100/80 dark:bg-gray-900/80',
|
'px-1.5 py-[0.3rem] rounded-md',
|
||||||
'border border-gray-200/60 dark:border-gray-800/75'
|
'bg-gray-100/80 dark:bg-gray-900/80',
|
||||||
);
|
'border border-gray-200/60 dark:border-gray-800/75'
|
||||||
case 'secondary': return clsx(
|
);
|
||||||
'px-[0.3rem] py-1 rounded-[0.25rem]',
|
case 'small':
|
||||||
'bg-gray-300/30 dark:bg-gray-700/50',
|
return clsx(
|
||||||
'text-medium',
|
'px-[0.3rem] py-1 rounded-[0.25rem]',
|
||||||
'font-medium text-[0.7rem]',
|
'text-[0.7rem] font-medium',
|
||||||
interactive && 'hover:text-gray-900 dark:hover:text-gray-100',
|
highContrast
|
||||||
interactive && 'active:bg-gray-200 dark:active:bg-gray-700/60',
|
? 'text-invert bg-main'
|
||||||
);
|
: 'text-medium bg-gray-300/30 dark:bg-gray-700/50',
|
||||||
|
interactive && highContrast
|
||||||
|
? 'hover:opacity-70'
|
||||||
|
: 'hover:text-gray-900 dark:hover:text-gray-100',
|
||||||
|
interactive && highContrast
|
||||||
|
? 'active:opacity-90'
|
||||||
|
: 'active:bg-gray-200 dark:active:bg-gray-700/60',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { clsx } from 'clsx';
|
|||||||
export interface EntityLinkExternalProps {
|
export interface EntityLinkExternalProps {
|
||||||
type?: 'icon-last' | 'icon-first' | 'icon-only' | 'text-only'
|
type?: 'icon-last' | 'icon-first' | 'icon-only' | 'text-only'
|
||||||
badged?: boolean
|
badged?: boolean
|
||||||
dim?: boolean
|
contrast?: 'low' | 'medium' | 'high'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EntityLink({
|
export default function EntityLink({
|
||||||
@ -17,8 +17,8 @@ export default function EntityLink({
|
|||||||
title,
|
title,
|
||||||
type = 'icon-first',
|
type = 'icon-first',
|
||||||
badged,
|
badged,
|
||||||
|
contrast,
|
||||||
hoverEntity,
|
hoverEntity,
|
||||||
dim,
|
|
||||||
}: {
|
}: {
|
||||||
label: ReactNode
|
label: ReactNode
|
||||||
labelSmall?: ReactNode
|
labelSmall?: ReactNode
|
||||||
@ -44,13 +44,18 @@ export default function EntityLink({
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'inline-flex gap-[0.23rem]',
|
'inline-flex gap-[0.23rem]',
|
||||||
!badged && 'text-main hover:text-gray-900 dark:hover:text-gray-100',
|
!badged && 'text-main hover:text-gray-900 dark:hover:text-gray-100',
|
||||||
dim && 'text-dim',
|
contrast === 'low' && 'text-dim',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{type !== 'icon-only' && <>
|
{type !== 'icon-only' && <>
|
||||||
{badged
|
{badged
|
||||||
? <span className="h-6 inline-flex items-center">
|
? <span className="h-6 inline-flex items-center">
|
||||||
<Badge type="secondary" uppercase interactive>
|
<Badge
|
||||||
|
type="small"
|
||||||
|
highContrast={contrast === 'high'}
|
||||||
|
uppercase
|
||||||
|
interactive
|
||||||
|
>
|
||||||
{renderLabel()}
|
{renderLabel()}
|
||||||
</Badge>
|
</Badge>
|
||||||
</span>
|
</span>
|
||||||
@ -61,9 +66,11 @@ export default function EntityLink({
|
|||||||
{icon && type !== 'text-only' &&
|
{icon && type !== 'text-only' &&
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
'flex-shrink-0',
|
'flex-shrink-0',
|
||||||
'text-dim inline-flex min-w-[0.9rem]',
|
'inline-flex min-w-[0.9rem]',
|
||||||
|
contrast === 'low' ? 'text-dim' : 'text-main',
|
||||||
type === 'icon-first' && 'order-first',
|
type === 'icon-first' && 'order-first',
|
||||||
badged && 'translate-y-[4px]',
|
badged && 'translate-y-[4px]',
|
||||||
|
hoverEntity !== undefined && 'group-hover:hidden',
|
||||||
)}>
|
)}>
|
||||||
{icon}
|
{icon}
|
||||||
</span>}
|
</span>}
|
||||||
|
|||||||
@ -5,12 +5,11 @@ 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 { Tags } from '@/tag';
|
import { TAG_FAVS, Tags } from '@/tag';
|
||||||
import PhotoFilmSimulation from
|
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
|
||||||
'@/simulation/PhotoFilmSimulation';
|
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
|
||||||
import PhotoFilmSimulationIcon from
|
|
||||||
'@/simulation/PhotoFilmSimulationIcon';
|
|
||||||
import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation';
|
import { FilmSimulations, sortFilmSimulationsWithCount } from '@/simulation';
|
||||||
|
import FavsTag from '../tag/FavsTag';
|
||||||
|
|
||||||
export default function PhotoGridSidebar({
|
export default function PhotoGridSidebar({
|
||||||
tags,
|
tags,
|
||||||
@ -32,8 +31,14 @@ export default function PhotoGridSidebar({
|
|||||||
{tags.length > 0 && <HeaderList
|
{tags.length > 0 && <HeaderList
|
||||||
title='Tags'
|
title='Tags'
|
||||||
icon={<FaTag size={12} className="text-icon" />}
|
icon={<FaTag size={12} className="text-icon" />}
|
||||||
items={tags.map(({ tag, count }) =>
|
items={tags.map(({ tag, count }) => tag === TAG_FAVS
|
||||||
<PhotoTag
|
? <FavsTag
|
||||||
|
key={TAG_FAVS}
|
||||||
|
countOnHover={count}
|
||||||
|
type="icon-last"
|
||||||
|
badged
|
||||||
|
/>
|
||||||
|
: <PhotoTag
|
||||||
key={tag}
|
key={tag}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
type="text-only"
|
type="text-only"
|
||||||
|
|||||||
17
src/photo/data.ts
Normal file
17
src/photo/data.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
getPhotosCountCached,
|
||||||
|
getUniqueCamerasCached,
|
||||||
|
getUniqueFilmSimulationsCached,
|
||||||
|
getUniqueTagsCached,
|
||||||
|
} from '@/cache';
|
||||||
|
import { SHOW_FILM_SIMULATIONS } from '@/site/config';
|
||||||
|
import { TAG_FAVS, Tags } from '@/tag';
|
||||||
|
|
||||||
|
export const getPhotoSidebarDataCached = () => [
|
||||||
|
getPhotosCountCached(),
|
||||||
|
getUniqueTagsCached().then(tags =>
|
||||||
|
([tags.find(({ tag }) => tag === TAG_FAVS) ?? []] as Tags)
|
||||||
|
.concat(tags.filter(({ tag }) => tag !== TAG_FAVS))),
|
||||||
|
getUniqueCamerasCached(),
|
||||||
|
SHOW_FILM_SIMULATIONS ? getUniqueFilmSimulationsCached() : [],
|
||||||
|
] as const;
|
||||||
@ -8,7 +8,7 @@ export default function PhotoFilmSimulation({
|
|||||||
simulation,
|
simulation,
|
||||||
type = 'icon-last',
|
type = 'icon-last',
|
||||||
badged = true,
|
badged = true,
|
||||||
dim,
|
contrast,
|
||||||
countOnHover,
|
countOnHover,
|
||||||
}: {
|
}: {
|
||||||
simulation: FilmSimulation
|
simulation: FilmSimulation
|
||||||
@ -28,7 +28,7 @@ export default function PhotoFilmSimulation({
|
|||||||
title={`Film Simulation: ${large}`}
|
title={`Film Simulation: ${large}`}
|
||||||
type={type}
|
type={type}
|
||||||
badged={badged}
|
badged={badged}
|
||||||
dim={dim}
|
contrast={contrast}
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -132,4 +132,8 @@
|
|||||||
@apply
|
@apply
|
||||||
text-red-500 dark:text-red-400
|
text-red-500 dark:text-red-400
|
||||||
}
|
}
|
||||||
|
.bg-main {
|
||||||
|
@apply
|
||||||
|
bg-gray-900 dark:bg-gray-100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
src/tag/FavsTag.tsx
Normal file
38
src/tag/FavsTag.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { FaStar } from 'react-icons/fa';
|
||||||
|
import EntityLink, { EntityLinkExternalProps } from '@/components/EntityLink';
|
||||||
|
import { TAG_FAVS } from '.';
|
||||||
|
import { pathForTag } from '@/site/paths';
|
||||||
|
|
||||||
|
export default function FavsTag({
|
||||||
|
type,
|
||||||
|
badged,
|
||||||
|
contrast,
|
||||||
|
countOnHover,
|
||||||
|
}: {
|
||||||
|
countOnHover?: number
|
||||||
|
} & EntityLinkExternalProps) {
|
||||||
|
return (
|
||||||
|
<EntityLink
|
||||||
|
label={
|
||||||
|
badged
|
||||||
|
? <span className="inline-flex gap-1">
|
||||||
|
{TAG_FAVS}
|
||||||
|
<FaStar
|
||||||
|
size={10}
|
||||||
|
className="text-amber-500"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
: TAG_FAVS}
|
||||||
|
href={pathForTag(TAG_FAVS)}
|
||||||
|
icon={!badged &&
|
||||||
|
<FaStar
|
||||||
|
size={12}
|
||||||
|
className="text-amber-500 translate-y-[4px]"
|
||||||
|
/>}
|
||||||
|
type={type}
|
||||||
|
hoverEntity={countOnHover}
|
||||||
|
badged={badged}
|
||||||
|
contrast={contrast}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ export default function PhotoTag({
|
|||||||
tag,
|
tag,
|
||||||
type,
|
type,
|
||||||
badged,
|
badged,
|
||||||
dim,
|
contrast,
|
||||||
countOnHover,
|
countOnHover,
|
||||||
}: {
|
}: {
|
||||||
tag: string
|
tag: string
|
||||||
@ -23,7 +23,7 @@ export default function PhotoTag({
|
|||||||
/>}
|
/>}
|
||||||
type={type}
|
type={type}
|
||||||
badged={badged}
|
badged={badged}
|
||||||
dim={dim}
|
contrast={contrast}
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { Photo, PhotoDateRange } from '@/photo';
|
import { Photo, PhotoDateRange } from '@/photo';
|
||||||
import PhotoTag from './PhotoTag';
|
import PhotoTag from './PhotoTag';
|
||||||
import { descriptionForTaggedPhotos } from '.';
|
import { descriptionForTaggedPhotos, isTagFavs } from '.';
|
||||||
import { pathForTagShare } from '@/site/paths';
|
import { pathForTagShare } from '@/site/paths';
|
||||||
import PhotoSetHeader from '@/photo/PhotoSetHeader';
|
import PhotoSetHeader from '@/photo/PhotoSetHeader';
|
||||||
|
import FavsTag from './FavsTag';
|
||||||
|
|
||||||
export default function TagHeader({
|
export default function TagHeader({
|
||||||
tag,
|
tag,
|
||||||
@ -19,7 +20,9 @@ export default function TagHeader({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<PhotoSetHeader
|
<PhotoSetHeader
|
||||||
entity={<PhotoTag tag={tag} />}
|
entity={isTagFavs(tag)
|
||||||
|
? <FavsTag />
|
||||||
|
: <PhotoTag tag={tag} />}
|
||||||
entityVerb="Tagged"
|
entityVerb="Tagged"
|
||||||
entityDescription={descriptionForTaggedPhotos(photos, undefined, count)}
|
entityDescription={descriptionForTaggedPhotos(photos, undefined, count)}
|
||||||
photos={photos}
|
photos={photos}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import {
|
|||||||
import { absolutePathForTag, absolutePathForTagImage } from '@/site/paths';
|
import { absolutePathForTag, absolutePathForTagImage } from '@/site/paths';
|
||||||
import { capitalizeWords } from '@/utility/string';
|
import { capitalizeWords } from '@/utility/string';
|
||||||
|
|
||||||
|
export const TAG_FAVS = 'favs';
|
||||||
|
|
||||||
export type Tags = {
|
export type Tags = {
|
||||||
tag: string
|
tag: string
|
||||||
count: number
|
count: number
|
||||||
@ -50,3 +52,5 @@ export const generateMetaForTag = (
|
|||||||
descriptionForTaggedPhotos(photos, true, explicitCount, explicitDateRange),
|
descriptionForTaggedPhotos(photos, true, explicitCount, explicitDateRange),
|
||||||
images: absolutePathForTagImage(tag),
|
images: absolutePathForTagImage(tag),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const isTagFavs = (tag: string) => tag === TAG_FAVS;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user