Show category counts on hover
This commit is contained in:
parent
35b61a79af
commit
69ec607e37
@ -241,8 +241,12 @@ export const MATTE_COLOR_DARK =
|
|||||||
|
|
||||||
export const CATEGORY_VISIBILITY = getOrderedCategoriesFromString(
|
export const CATEGORY_VISIBILITY = getOrderedCategoriesFromString(
|
||||||
process.env.NEXT_PUBLIC_CATEGORY_VISIBILITY);
|
process.env.NEXT_PUBLIC_CATEGORY_VISIBILITY);
|
||||||
|
export const SHOW_CAMERAS =
|
||||||
|
CATEGORY_VISIBILITY.includes('cameras');
|
||||||
export const SHOW_LENSES =
|
export const SHOW_LENSES =
|
||||||
CATEGORY_VISIBILITY.includes('lenses');
|
CATEGORY_VISIBILITY.includes('lenses');
|
||||||
|
export const SHOW_TAGS =
|
||||||
|
CATEGORY_VISIBILITY.includes('tags');
|
||||||
export const SHOW_RECIPES =
|
export const SHOW_RECIPES =
|
||||||
CATEGORY_VISIBILITY.includes('recipes');
|
CATEGORY_VISIBILITY.includes('recipes');
|
||||||
export const SHOW_FILM_SIMULATIONS =
|
export const SHOW_FILM_SIMULATIONS =
|
||||||
|
|||||||
@ -10,12 +10,8 @@ import { isCameraApple } from '@/platforms/apple';
|
|||||||
export default function PhotoCamera({
|
export default function PhotoCamera({
|
||||||
camera,
|
camera,
|
||||||
hideAppleIcon,
|
hideAppleIcon,
|
||||||
type,
|
|
||||||
badged,
|
|
||||||
contrast,
|
|
||||||
prefetch,
|
|
||||||
countOnHover,
|
countOnHover,
|
||||||
className,
|
...props
|
||||||
}: {
|
}: {
|
||||||
camera: Camera
|
camera: Camera
|
||||||
hideAppleIcon?: boolean
|
hideAppleIcon?: boolean
|
||||||
@ -26,6 +22,7 @@ export default function PhotoCamera({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityLink
|
<EntityLink
|
||||||
|
{...props}
|
||||||
label={formatCameraText(camera)}
|
label={formatCameraText(camera)}
|
||||||
href={pathForCamera(camera)}
|
href={pathForCamera(camera)}
|
||||||
icon={showAppleIcon
|
icon={showAppleIcon
|
||||||
@ -38,11 +35,6 @@ export default function PhotoCamera({
|
|||||||
size={15}
|
size={15}
|
||||||
className="translate-x-[-0.5px] translate-y-[-0.5px]"
|
className="translate-x-[-0.5px] translate-y-[-0.5px]"
|
||||||
/>}
|
/>}
|
||||||
type={type}
|
|
||||||
className={className}
|
|
||||||
badged={badged}
|
|
||||||
contrast={contrast}
|
|
||||||
prefetch={prefetch}
|
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
42
src/category/actions.ts
Normal file
42
src/category/actions.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { createLensKey } from '@/lens';
|
||||||
|
import { getDataForCategories } from './data';
|
||||||
|
|
||||||
|
export const getCountsForCategoriesAction = async () => {
|
||||||
|
const [
|
||||||
|
cameras,
|
||||||
|
lenses,
|
||||||
|
tags,
|
||||||
|
recipes,
|
||||||
|
filmSimulations,
|
||||||
|
focalLengths,
|
||||||
|
] = await Promise.all(getDataForCategories());
|
||||||
|
|
||||||
|
return {
|
||||||
|
cameras: cameras.reduce((acc, camera) => {
|
||||||
|
acc[camera.cameraKey] = camera.count;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
lenses: lenses.reduce((acc, lens) => {
|
||||||
|
acc[createLensKey(lens.lens)] = lens.count;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
tags: tags.reduce((acc, tag) => {
|
||||||
|
acc[tag.tag] = tag.count;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
recipes: recipes.reduce((acc, recipe) => {
|
||||||
|
acc[recipe.recipe] = recipe.count;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
filmSimulations: filmSimulations.reduce((acc, filmSimulation) => {
|
||||||
|
acc[filmSimulation.simulation] = filmSimulation.count;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
focalLengths: focalLengths.reduce((acc, focalLength) => {
|
||||||
|
acc[focalLength.focal] = focalLength.count;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -11,23 +11,29 @@ import {
|
|||||||
SHOW_FOCAL_LENGTHS,
|
SHOW_FOCAL_LENGTHS,
|
||||||
SHOW_LENSES,
|
SHOW_LENSES,
|
||||||
SHOW_RECIPES,
|
SHOW_RECIPES,
|
||||||
|
SHOW_CAMERAS,
|
||||||
|
SHOW_TAGS,
|
||||||
} from '@/app/config';
|
} from '@/app/config';
|
||||||
import { sortTagsByCount } from '@/tag';
|
import { sortTagsByCount } from '@/tag';
|
||||||
import { sortCategoriesByCount } from '@/category';
|
import { sortCategoriesByCount } from '@/category';
|
||||||
import { sortFocalLengths } from '@/focal';
|
import { sortFocalLengths } from '@/focal';
|
||||||
|
|
||||||
export const getDataForCategories = () => [
|
export const getDataForCategories = () => [
|
||||||
getUniqueCameras()
|
SHOW_CAMERAS
|
||||||
|
? getUniqueCameras()
|
||||||
.then(sortCategoriesByCount)
|
.then(sortCategoriesByCount)
|
||||||
.catch(() => []),
|
.catch(() => [])
|
||||||
|
: [],
|
||||||
SHOW_LENSES
|
SHOW_LENSES
|
||||||
? getUniqueLenses()
|
? getUniqueLenses()
|
||||||
.then(sortCategoriesByCount)
|
.then(sortCategoriesByCount)
|
||||||
.catch(() => [])
|
.catch(() => [])
|
||||||
: [],
|
: [],
|
||||||
getUniqueTags()
|
SHOW_TAGS
|
||||||
|
? getUniqueTags()
|
||||||
.then(sortTagsByCount)
|
.then(sortTagsByCount)
|
||||||
.catch(() => []),
|
.catch(() => [])
|
||||||
|
: [],
|
||||||
SHOW_RECIPES
|
SHOW_RECIPES
|
||||||
? getUniqueRecipes()
|
? getUniqueRecipes()
|
||||||
.then(sortCategoriesByCount)
|
.then(sortCategoriesByCount)
|
||||||
|
|||||||
49
src/category/useCategoryCounts.ts
Normal file
49
src/category/useCategoryCounts.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { createCameraKey } from '@/camera';
|
||||||
|
import { createLensKey } from '@/lens';
|
||||||
|
import { Camera } from '@/camera';
|
||||||
|
import { Lens } from '@/lens';
|
||||||
|
import { useAppState } from '@/state/AppState';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export default function useCategoryCounts() {
|
||||||
|
const { categoriesWithCounts } = useAppState();
|
||||||
|
|
||||||
|
const getCameraCount = useCallback((camera: Camera) => {
|
||||||
|
const cameraCounts = categoriesWithCounts?.cameras ?? {};
|
||||||
|
return cameraCounts[createCameraKey(camera)];
|
||||||
|
}, [categoriesWithCounts]);
|
||||||
|
|
||||||
|
const getLensCount = useCallback((lens: Lens) => {
|
||||||
|
const lensCounts = categoriesWithCounts?.lenses ?? {};
|
||||||
|
return lensCounts[createLensKey(lens)];
|
||||||
|
}, [categoriesWithCounts]);
|
||||||
|
|
||||||
|
const getTagCount = useCallback((tag: string) => {
|
||||||
|
const tagCounts = categoriesWithCounts?.tags ?? {};
|
||||||
|
return tagCounts[tag];
|
||||||
|
}, [categoriesWithCounts]);
|
||||||
|
|
||||||
|
const getRecipeCount = useCallback((recipe: string) => {
|
||||||
|
const recipeCounts = categoriesWithCounts?.recipes ?? {};
|
||||||
|
return recipeCounts[recipe];
|
||||||
|
}, [categoriesWithCounts]);
|
||||||
|
|
||||||
|
const getFilmSimulationCount = useCallback((simulation: string) => {
|
||||||
|
const filmSimulationCounts = categoriesWithCounts?.filmSimulations ?? {};
|
||||||
|
return filmSimulationCounts[simulation];
|
||||||
|
}, [categoriesWithCounts]);
|
||||||
|
|
||||||
|
const getFocalLengthCount = useCallback((focalLength: number) => {
|
||||||
|
const focalLengthCounts = categoriesWithCounts?.focalLengths ?? {};
|
||||||
|
return focalLengthCounts[focalLength];
|
||||||
|
}, [categoriesWithCounts]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getCameraCount,
|
||||||
|
getLensCount,
|
||||||
|
getTagCount,
|
||||||
|
getRecipeCount,
|
||||||
|
getFilmSimulationCount,
|
||||||
|
getFocalLengthCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
47
src/category/useCategoryCountsForPhoto.ts
Normal file
47
src/category/useCategoryCountsForPhoto.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Photo } from '@/photo';
|
||||||
|
import useCategoryCounts from './useCategoryCounts';
|
||||||
|
import { cameraFromPhoto } from '@/camera';
|
||||||
|
import { lensFromPhoto } from '@/lens';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export default function useCategoryCountsForPhoto(photo: Photo) {
|
||||||
|
const {
|
||||||
|
getCameraCount,
|
||||||
|
getLensCount,
|
||||||
|
getTagCount,
|
||||||
|
getRecipeCount,
|
||||||
|
getFilmSimulationCount,
|
||||||
|
getFocalLengthCount,
|
||||||
|
} = useCategoryCounts();
|
||||||
|
|
||||||
|
const camera = cameraFromPhoto(photo);
|
||||||
|
const lens = lensFromPhoto(photo);
|
||||||
|
|
||||||
|
const categoryCounts = useMemo(() => ({
|
||||||
|
cameraCount: getCameraCount(camera),
|
||||||
|
lensCount: getLensCount(lens),
|
||||||
|
tagCounts: photo.tags.reduce((acc, tag) => {
|
||||||
|
acc[tag] = getTagCount(tag);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>),
|
||||||
|
recipeCount: photo.recipeTitle ? getRecipeCount(photo.recipeTitle) : 0,
|
||||||
|
simulationCount:
|
||||||
|
photo.filmSimulation ? getFilmSimulationCount(photo.filmSimulation) : 0,
|
||||||
|
focalCount: photo.focalLength ? getFocalLengthCount(photo.focalLength) : 0,
|
||||||
|
}), [
|
||||||
|
getCameraCount,
|
||||||
|
getLensCount,
|
||||||
|
getRecipeCount,
|
||||||
|
getFilmSimulationCount,
|
||||||
|
getFocalLengthCount,
|
||||||
|
getTagCount,
|
||||||
|
camera,
|
||||||
|
lens,
|
||||||
|
photo.tags,
|
||||||
|
photo.recipeTitle,
|
||||||
|
photo.filmSimulation,
|
||||||
|
photo.focalLength,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return categoryCounts;
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ export interface EntityLinkExternalProps {
|
|||||||
type?: LabeledIconType
|
type?: LabeledIconType
|
||||||
badged?: boolean
|
badged?: boolean
|
||||||
contrast?: ComponentProps<typeof Badge>['contrast']
|
contrast?: ComponentProps<typeof Badge>['contrast']
|
||||||
|
uppercase?: boolean
|
||||||
prefetch?: boolean
|
prefetch?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,26 +7,18 @@ import IconFocalLength from '@/components/icons/IconFocalLength';
|
|||||||
|
|
||||||
export default function PhotoFocalLength({
|
export default function PhotoFocalLength({
|
||||||
focal,
|
focal,
|
||||||
type,
|
|
||||||
badged,
|
|
||||||
contrast,
|
|
||||||
prefetch,
|
|
||||||
countOnHover,
|
countOnHover,
|
||||||
className,
|
...props
|
||||||
}: {
|
}: {
|
||||||
focal: number
|
focal: number
|
||||||
countOnHover?: number
|
countOnHover?: number
|
||||||
} & EntityLinkExternalProps) {
|
} & EntityLinkExternalProps) {
|
||||||
return (
|
return (
|
||||||
<EntityLink
|
<EntityLink
|
||||||
|
{...props}
|
||||||
label={formatFocalLength(focal)}
|
label={formatFocalLength(focal)}
|
||||||
href={pathForFocalLength(focal)}
|
href={pathForFocalLength(focal)}
|
||||||
icon={<IconFocalLength />}
|
icon={<IconFocalLength />}
|
||||||
type={type}
|
|
||||||
className={className}
|
|
||||||
badged={badged}
|
|
||||||
contrast={contrast}
|
|
||||||
prefetch={prefetch}
|
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,13 +7,9 @@ import IconLens from '@/components/icons/IconLens';
|
|||||||
|
|
||||||
export default function PhotoLens({
|
export default function PhotoLens({
|
||||||
lens,
|
lens,
|
||||||
type,
|
|
||||||
badged,
|
|
||||||
contrast,
|
|
||||||
prefetch,
|
|
||||||
countOnHover,
|
countOnHover,
|
||||||
className,
|
|
||||||
shortText,
|
shortText,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
lens: Lens
|
lens: Lens
|
||||||
countOnHover?: number
|
countOnHover?: number
|
||||||
@ -21,17 +17,13 @@ export default function PhotoLens({
|
|||||||
} & EntityLinkExternalProps) {
|
} & EntityLinkExternalProps) {
|
||||||
return (
|
return (
|
||||||
<EntityLink
|
<EntityLink
|
||||||
|
{...props}
|
||||||
label={formatLensText(lens, shortText ? 'short' : 'medium')}
|
label={formatLensText(lens, shortText ? 'short' : 'medium')}
|
||||||
href={pathForLens(lens)}
|
href={pathForLens(lens)}
|
||||||
icon={<IconLens
|
icon={<IconLens
|
||||||
size={14}
|
size={14}
|
||||||
className="translate-x-[-0.5px]"
|
className="translate-x-[-0.5px]"
|
||||||
/>}
|
/>}
|
||||||
type={type}
|
|
||||||
className={className}
|
|
||||||
badged={badged}
|
|
||||||
contrast={contrast}
|
|
||||||
prefetch={prefetch}
|
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -51,6 +51,7 @@ import PhotoRecipe from '@/recipe/PhotoRecipe';
|
|||||||
import PhotoLens from '@/lens/PhotoLens';
|
import PhotoLens from '@/lens/PhotoLens';
|
||||||
import { lensFromPhoto } from '@/lens';
|
import { lensFromPhoto } from '@/lens';
|
||||||
import MaskedScroll from '@/components/MaskedScroll';
|
import MaskedScroll from '@/components/MaskedScroll';
|
||||||
|
import useCategoryCountsForPhoto from '@/category/useCategoryCountsForPhoto';
|
||||||
|
|
||||||
export default function PhotoLarge({
|
export default function PhotoLarge({
|
||||||
photo,
|
photo,
|
||||||
@ -114,6 +115,14 @@ export default function PhotoLarge({
|
|||||||
isUserSignedIn,
|
isUserSignedIn,
|
||||||
} = useAppState();
|
} = useAppState();
|
||||||
|
|
||||||
|
const {
|
||||||
|
cameraCount,
|
||||||
|
lensCount,
|
||||||
|
tagCounts,
|
||||||
|
recipeCount,
|
||||||
|
simulationCount,
|
||||||
|
} = useCategoryCountsForPhoto(photo);
|
||||||
|
|
||||||
const showZoomControls = showZoomControlsProp && areZoomControlsShown;
|
const showZoomControls = showZoomControlsProp && areZoomControlsShown;
|
||||||
|
|
||||||
const refRecipe = useRef<HTMLDivElement>(null);
|
const refRecipe = useRef<HTMLDivElement>(null);
|
||||||
@ -306,6 +315,7 @@ export default function PhotoLarge({
|
|||||||
camera={camera}
|
camera={camera}
|
||||||
contrast="medium"
|
contrast="medium"
|
||||||
prefetch={prefetchRelatedLinks}
|
prefetch={prefetchRelatedLinks}
|
||||||
|
countOnHover={cameraCount}
|
||||||
/>}
|
/>}
|
||||||
{showLensContent &&
|
{showLensContent &&
|
||||||
<PhotoLens
|
<PhotoLens
|
||||||
@ -313,6 +323,7 @@ export default function PhotoLarge({
|
|||||||
contrast="medium"
|
contrast="medium"
|
||||||
prefetch={prefetchRelatedLinks}
|
prefetch={prefetchRelatedLinks}
|
||||||
shortText
|
shortText
|
||||||
|
countOnHover={lensCount}
|
||||||
/>}
|
/>}
|
||||||
</div>}
|
</div>}
|
||||||
{showRecipeContent && recipeTitle &&
|
{showRecipeContent && recipeTitle &&
|
||||||
@ -320,10 +331,12 @@ export default function PhotoLarge({
|
|||||||
recipe={recipeTitle}
|
recipe={recipeTitle}
|
||||||
contrast="medium"
|
contrast="medium"
|
||||||
prefetch={prefetchRelatedLinks}
|
prefetch={prefetchRelatedLinks}
|
||||||
|
countOnHover={recipeCount}
|
||||||
/>}
|
/>}
|
||||||
{showTagsContent &&
|
{showTagsContent &&
|
||||||
<PhotoTags
|
<PhotoTags
|
||||||
tags={tags}
|
tags={tags}
|
||||||
|
tagCounts={tagCounts}
|
||||||
contrast="medium"
|
contrast="medium"
|
||||||
prefetch={prefetchRelatedLinks}
|
prefetch={prefetchRelatedLinks}
|
||||||
/>}
|
/>}
|
||||||
@ -384,6 +397,7 @@ export default function PhotoLarge({
|
|||||||
<PhotoFilmSimulation
|
<PhotoFilmSimulation
|
||||||
simulation={photo.filmSimulation}
|
simulation={photo.filmSimulation}
|
||||||
prefetch={prefetchRelatedLinks}
|
prefetch={prefetchRelatedLinks}
|
||||||
|
countOnHover={simulationCount}
|
||||||
/>}
|
/>}
|
||||||
{showRecipeButton &&
|
{showRecipeButton &&
|
||||||
<Tooltip content="Fujifilm Recipe">
|
<Tooltip content="Fujifilm Recipe">
|
||||||
|
|||||||
@ -9,15 +9,11 @@ import IconRecipe from '@/components/icons/IconRecipe';
|
|||||||
|
|
||||||
export default function PhotoRecipe({
|
export default function PhotoRecipe({
|
||||||
recipe,
|
recipe,
|
||||||
type,
|
|
||||||
badged,
|
|
||||||
contrast,
|
|
||||||
prefetch,
|
|
||||||
countOnHover,
|
countOnHover,
|
||||||
className,
|
|
||||||
refButton,
|
refButton,
|
||||||
isOpen,
|
isOpen,
|
||||||
recipeOnClick,
|
recipeOnClick,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
recipe: string
|
recipe: string
|
||||||
refButton?: RefObject<HTMLButtonElement | null>
|
refButton?: RefObject<HTMLButtonElement | null>
|
||||||
@ -28,22 +24,18 @@ export default function PhotoRecipe({
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full gap-2">
|
<div className="flex w-full gap-2">
|
||||||
<EntityLink
|
<EntityLink
|
||||||
|
{...props}
|
||||||
title="Recipe"
|
title="Recipe"
|
||||||
label={formatRecipe(recipe)}
|
label={formatRecipe(recipe)}
|
||||||
href={pathForRecipe(recipe)}
|
href={pathForRecipe(recipe)}
|
||||||
icon={<IconRecipe
|
icon={<IconRecipe
|
||||||
size={16}
|
size={16}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
badged
|
props.badged
|
||||||
? 'translate-x-[-1px] translate-y-[0.5px]'
|
? 'translate-x-[-1px] translate-y-[0.5px]'
|
||||||
: 'translate-y-[-0.5px]',
|
: 'translate-y-[-0.5px]',
|
||||||
)}
|
)}
|
||||||
/>}
|
/>}
|
||||||
className={className}
|
|
||||||
type={type}
|
|
||||||
badged={badged}
|
|
||||||
contrast={contrast}
|
|
||||||
prefetch={prefetch}
|
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
{recipeOnClick &&
|
{recipeOnClick &&
|
||||||
|
|||||||
@ -13,9 +13,8 @@ export default function PhotoFilmSimulation({
|
|||||||
type = 'icon-last',
|
type = 'icon-last',
|
||||||
badged = true,
|
badged = true,
|
||||||
contrast = 'low',
|
contrast = 'low',
|
||||||
prefetch,
|
|
||||||
countOnHover,
|
countOnHover,
|
||||||
className,
|
...props
|
||||||
}: {
|
}: {
|
||||||
simulation: FilmSimulation
|
simulation: FilmSimulation
|
||||||
countOnHover?: number
|
countOnHover?: number
|
||||||
@ -25,6 +24,7 @@ export default function PhotoFilmSimulation({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityLink
|
<EntityLink
|
||||||
|
{...props}
|
||||||
label={medium}
|
label={medium}
|
||||||
labelSmall={small}
|
labelSmall={small}
|
||||||
href={pathForFilmSimulation(simulation)}
|
href={pathForFilmSimulation(simulation)}
|
||||||
@ -39,10 +39,8 @@ export default function PhotoFilmSimulation({
|
|||||||
/>}
|
/>}
|
||||||
title={`Film Simulation: ${large}`}
|
title={`Film Simulation: ${large}`}
|
||||||
type={type}
|
type={type}
|
||||||
className={className}
|
|
||||||
badged={badged}
|
badged={badged}
|
||||||
contrast={contrast}
|
contrast={contrast}
|
||||||
prefetch={prefetch}
|
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
iconWide
|
iconWide
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { InsightsIndicatorStatus } from '@/admin/insights';
|
|||||||
import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload';
|
import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload';
|
||||||
import { AdminData } from '@/admin/actions';
|
import { AdminData } from '@/admin/actions';
|
||||||
import { RecipeProps } from '@/recipe';
|
import { RecipeProps } from '@/recipe';
|
||||||
|
import { getCountsForCategoriesAction } from '@/category/actions';
|
||||||
export type AppStateContext = {
|
export type AppStateContext = {
|
||||||
// CORE
|
// CORE
|
||||||
previousPathname?: string
|
previousPathname?: string
|
||||||
@ -24,6 +24,8 @@ export type AppStateContext = {
|
|||||||
clearNextPhotoAnimation?: () => void
|
clearNextPhotoAnimation?: () => void
|
||||||
shouldRespondToKeyboardCommands?: boolean
|
shouldRespondToKeyboardCommands?: boolean
|
||||||
setShouldRespondToKeyboardCommands?: Dispatch<SetStateAction<boolean>>
|
setShouldRespondToKeyboardCommands?: Dispatch<SetStateAction<boolean>>
|
||||||
|
categoriesWithCounts?:
|
||||||
|
Awaited<ReturnType<typeof getCountsForCategoriesAction>>
|
||||||
// MODAL
|
// MODAL
|
||||||
isCommandKOpen?: boolean
|
isCommandKOpen?: boolean
|
||||||
setIsCommandKOpen?: Dispatch<SetStateAction<boolean>>
|
setIsCommandKOpen?: Dispatch<SetStateAction<boolean>>
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { useRouter, usePathname } from 'next/navigation';
|
|||||||
import { isPathAdmin, PATH_SIGN_IN } from '@/app/paths';
|
import { isPathAdmin, PATH_SIGN_IN } from '@/app/paths';
|
||||||
import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload';
|
import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload';
|
||||||
import { RecipeProps } from '@/recipe';
|
import { RecipeProps } from '@/recipe';
|
||||||
|
import { getCountsForCategoriesAction } from '@/category/actions';
|
||||||
|
|
||||||
export default function AppStateProvider({
|
export default function AppStateProvider({
|
||||||
children,
|
children,
|
||||||
@ -90,6 +91,11 @@ export default function AppStateProvider({
|
|||||||
|
|
||||||
const invalidateSwr = useCallback(() => setSwrTimestamp(Date.now()), []);
|
const invalidateSwr = useCallback(() => setSwrTimestamp(Date.now()), []);
|
||||||
|
|
||||||
|
const { data: categoriesWithCounts } = useSWR(
|
||||||
|
'getDataForCategories',
|
||||||
|
getCountsForCategoriesAction,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: auth,
|
data: auth,
|
||||||
error: authError,
|
error: authError,
|
||||||
@ -167,6 +173,7 @@ export default function AppStateProvider({
|
|||||||
clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined),
|
clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined),
|
||||||
shouldRespondToKeyboardCommands,
|
shouldRespondToKeyboardCommands,
|
||||||
setShouldRespondToKeyboardCommands,
|
setShouldRespondToKeyboardCommands,
|
||||||
|
categoriesWithCounts,
|
||||||
// MODAL
|
// MODAL
|
||||||
isCommandKOpen,
|
isCommandKOpen,
|
||||||
setIsCommandKOpen,
|
setIsCommandKOpen,
|
||||||
|
|||||||
@ -7,26 +7,18 @@ import IconTag from '@/components/icons/IconTag';
|
|||||||
|
|
||||||
export default function PhotoTag({
|
export default function PhotoTag({
|
||||||
tag,
|
tag,
|
||||||
type,
|
|
||||||
badged,
|
|
||||||
contrast,
|
|
||||||
prefetch,
|
|
||||||
countOnHover,
|
countOnHover,
|
||||||
className,
|
...props
|
||||||
}: {
|
}: {
|
||||||
tag: string
|
tag: string
|
||||||
countOnHover?: number
|
countOnHover?: number
|
||||||
} & EntityLinkExternalProps) {
|
} & EntityLinkExternalProps) {
|
||||||
return (
|
return (
|
||||||
<EntityLink
|
<EntityLink
|
||||||
|
{...props}
|
||||||
label={formatTag(tag)}
|
label={formatTag(tag)}
|
||||||
href={pathForTag(tag)}
|
href={pathForTag(tag)}
|
||||||
icon={<IconTag size={14} className="translate-x-[0.5px]" />}
|
icon={<IconTag size={14} className="translate-x-[0.5px]" />}
|
||||||
type={type}
|
|
||||||
className={className}
|
|
||||||
badged={badged}
|
|
||||||
contrast={contrast}
|
|
||||||
prefetch={prefetch}
|
|
||||||
hoverEntity={countOnHover}
|
hoverEntity={countOnHover}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,18 +6,27 @@ import { Fragment } from 'react';
|
|||||||
|
|
||||||
export default function PhotoTags({
|
export default function PhotoTags({
|
||||||
tags,
|
tags,
|
||||||
|
tagCounts = {},
|
||||||
contrast,
|
contrast,
|
||||||
prefetch,
|
prefetch,
|
||||||
}: {
|
}: {
|
||||||
tags: string[]
|
tags: string[]
|
||||||
|
tagCounts?: Record<string, number>
|
||||||
} & EntityLinkExternalProps) {
|
} & EntityLinkExternalProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{tags.map(tag =>
|
{tags.map(tag =>
|
||||||
<Fragment key={tag}>
|
<Fragment key={tag}>
|
||||||
{isTagFavs(tag)
|
{isTagFavs(tag)
|
||||||
? <FavsTag {...{ contrast, prefetch }} />
|
? <FavsTag {...{
|
||||||
: <PhotoTag {...{ tag, contrast, prefetch }} />}
|
contrast,
|
||||||
|
prefetch,
|
||||||
|
countOnHover: tagCounts[tag],
|
||||||
|
}} />
|
||||||
|
: <PhotoTag {...{
|
||||||
|
tag,
|
||||||
|
contrast,
|
||||||
|
prefetch, countOnHover: tagCounts[tag] }} />}
|
||||||
</Fragment>)}
|
</Fragment>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@custom-variant hover {
|
||||||
|
@media (pointer: fine) {
|
||||||
|
&:hover {
|
||||||
|
@slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user