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