Move all share buttons to internal app state

This commit is contained in:
Sam Becker 2025-01-11 15:20:26 -06:00
parent 76a6f40e77
commit 375dd9e034
13 changed files with 116 additions and 70 deletions

View File

@ -1,6 +1,6 @@
import { absolutePathForCamera, pathForCamera } from '@/site/paths'; import { absolutePathForCamera } from '@/site/paths';
import { PhotoSetAttributes } from '../photo'; import { PhotoSetAttributes } from '../photo';
import ShareModal from '@/components/ShareModal'; import ShareModal from '@/share/ShareModal';
import CameraOGTile from './CameraOGTile'; import CameraOGTile from './CameraOGTile';
import { Camera } from '.'; import { Camera } from '.';
import { shareTextForCamera } from './meta'; import { shareTextForCamera } from './meta';
@ -16,7 +16,6 @@ export default function CameraShareModal({
return ( return (
<ShareModal <ShareModal
pathShare={absolutePathForCamera(camera)} pathShare={absolutePathForCamera(camera)}
pathClose={pathForCamera(camera)}
socialText={shareTextForCamera(camera, photos)} socialText={shareTextForCamera(camera, photos)}
> >
<CameraOGTile {...{ camera, photos, count, dateRange }} /> <CameraOGTile {...{ camera, photos, count, dateRange }} />

View File

@ -1,33 +0,0 @@
import { TbPhotoShare } from 'react-icons/tb';
import PathLoaderButton from './primitives/PathLoaderButton';
import { clsx } from 'clsx/lite';
export default function ShareButton({
path,
prefetch,
shouldScroll,
dim,
className,
}: {
path: string
prefetch?: boolean
shouldScroll?: boolean
dim?: boolean
className?: string
}) {
return (
<PathLoaderButton
path={path}
className={clsx(
className,
dim ? 'text-dim' : 'text-medium',
)}
icon={<TbPhotoShare size={16} />}
spinnerColor="dim"
prefetch={prefetch}
shouldScroll={shouldScroll}
styleAs="link"
shouldReplace
/>
);
}

View File

@ -1,6 +1,6 @@
import { absolutePathForFocalLength, pathForFocalLength } from '@/site/paths'; import { absolutePathForFocalLength } from '@/site/paths';
import { PhotoSetAttributes } from '../photo'; import { PhotoSetAttributes } from '../photo';
import ShareModal from '@/components/ShareModal'; import ShareModal from '@/share/ShareModal';
import FocalLengthOGTile from './FocalLengthOGTile'; import FocalLengthOGTile from './FocalLengthOGTile';
import { shareTextFocalLength } from '.'; import { shareTextFocalLength } from '.';
@ -15,7 +15,6 @@ export default function FocalLengthShareModal({
return ( return (
<ShareModal <ShareModal
pathShare={absolutePathForFocalLength(focal)} pathShare={absolutePathForFocalLength(focal)}
pathClose={pathForFocalLength(focal)}
socialText={shareTextFocalLength(focal)} socialText={shareTextFocalLength(focal)}
> >
<FocalLengthOGTile {...{ focal, photos, count, dateRange }} /> <FocalLengthOGTile {...{ focal, photos, count, dateRange }} />

View File

@ -8,7 +8,7 @@ import {
dateRangeForPhotos, dateRangeForPhotos,
titleForPhoto, titleForPhoto,
} from '.'; } from '.';
import ShareButton from '@/components/ShareButton'; import ShareButton from '@/share/ShareButton';
import AnimateItems from '@/components/AnimateItems'; import AnimateItems from '@/components/AnimateItems';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
@ -140,8 +140,15 @@ export default function PhotoHeader({
{entityDescription} {entityDescription}
{sharePath && {sharePath &&
<ShareButton <ShareButton
photos={photos}
tag={tag}
camera={camera}
simulation={simulation}
focal={focal}
count={count}
dateRange={dateRange}
className="translate-y-[1.5px]" className="translate-y-[1.5px]"
path={sharePath} prefetch
dim dim
/>} />}
</> </>

View File

@ -15,10 +15,9 @@ import Link from 'next/link';
import { import {
pathForFocalLength, pathForFocalLength,
pathForPhoto, pathForPhoto,
pathForPhotoShare,
} from '@/site/paths'; } from '@/site/paths';
import PhotoTags from '@/tag/PhotoTags'; import PhotoTags from '@/tag/PhotoTags';
import ShareButton from '@/components/ShareButton'; import ShareButton from '@/share/ShareButton';
import DownloadButton from '@/components/DownloadButton'; import DownloadButton from '@/components/DownloadButton';
import PhotoCamera from '../camera/PhotoCamera'; import PhotoCamera from '../camera/PhotoCamera';
import { cameraFromPhoto } from '@/camera'; import { cameraFromPhoto } from '@/camera';
@ -54,7 +53,6 @@ export default function PhotoLarge({
shouldShareCamera, shouldShareCamera,
shouldShareSimulation, shouldShareSimulation,
shouldShareFocalLength, shouldShareFocalLength,
shouldScrollOnShare,
includeFavoriteInAdminMenu, includeFavoriteInAdminMenu,
onVisible, onVisible,
}: { }: {
@ -258,17 +256,14 @@ export default function PhotoLarge({
)}> )}>
{shouldShare && {shouldShare &&
<ShareButton <ShareButton
path={pathForPhotoShare({ photo={photo}
photo, tag={shouldShareTag ? primaryTag : undefined}
tag: shouldShareTag ? primaryTag : undefined, camera={shouldShareCamera ? camera : undefined}
camera: shouldShareCamera ? camera : undefined, // eslint-disable-next-line max-len
// eslint-disable-next-line max-len simulation={shouldShareSimulation? photo.filmSimulation : undefined}
simulation: shouldShareSimulation ? photo.filmSimulation : undefined, // eslint-disable-next-line max-len
// eslint-disable-next-line max-len focal={shouldShareFocalLength ? photo.focalLength : undefined}
focal: shouldShareFocalLength ? photo.focalLength : undefined,
})}
prefetch={prefetchRelatedLinks} prefetch={prefetchRelatedLinks}
shouldScroll={shouldScrollOnShare}
/>} />}
{ALLOW_PUBLIC_DOWNLOADS && {ALLOW_PUBLIC_DOWNLOADS &&
<DownloadButton <DownloadButton

View File

@ -1,7 +1,7 @@
import PhotoOGTile from '@/photo/PhotoOGTile'; import PhotoOGTile from '@/photo/PhotoOGTile';
import { absolutePathForPhoto, pathForPhoto } from '@/site/paths'; import { absolutePathForPhoto } from '@/site/paths';
import { Photo, PhotoSetCategory } from '.'; import { Photo, PhotoSetCategory } from '.';
import ShareModal from '@/components/ShareModal'; import ShareModal from '@/share/ShareModal';
export default function PhotoShareModal(props: { export default function PhotoShareModal(props: {
photo: Photo photo: Photo
@ -9,7 +9,6 @@ export default function PhotoShareModal(props: {
return ( return (
<ShareModal <ShareModal
pathShare={absolutePathForPhoto(props)} pathShare={absolutePathForPhoto(props)}
pathClose={pathForPhoto(props)}
socialText="Check out this photo" socialText="Check out this photo"
> >
<PhotoOGTile photo={props.photo} /> <PhotoOGTile photo={props.photo} />

46
src/share/ShareButton.tsx Normal file
View File

@ -0,0 +1,46 @@
'use client';
import { TbPhotoShare } from 'react-icons/tb';
import { clsx } from 'clsx/lite';
import LoaderButton from '@/components/primitives/LoaderButton';
import { useAppState } from '@/state/AppState';
import { getSharePathFromShareModalProps, ShareModalProps } from '.';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function ShareButton({
dim,
prefetch,
className,
...rest
}: {
dim?: boolean
prefetch?: boolean
className?: string
} & ShareModalProps) {
const { setShareModalProps } = useAppState();
const router = useRouter();
const absoluteImagePath = getSharePathFromShareModalProps({ ...rest });
useEffect(() => {
if (prefetch && absoluteImagePath) {
console.log('prefetching', absoluteImagePath);
router.prefetch(absoluteImagePath);
}
}, [prefetch, absoluteImagePath, router]);
return (
<LoaderButton
onClick={() => setShareModalProps?.({ ...rest })}
className={clsx(
className,
dim ? 'text-dim' : 'text-medium',
)}
icon={<TbPhotoShare size={16} />}
spinnerColor="dim"
styleAs="link"
/>
);
}

View File

@ -10,20 +10,21 @@ import { toastSuccess } from '@/toast';
import { PiXLogo } from 'react-icons/pi'; import { PiXLogo } from 'react-icons/pi';
import { SHOW_SOCIAL } from '@/site/config'; import { SHOW_SOCIAL } from '@/site/config';
import { generateXPostText } from '@/utility/social'; import { generateXPostText } from '@/utility/social';
import { useAppState } from '@/state/AppState';
export default function ShareModal({ export default function ShareModal({
title, title,
pathShare, pathShare,
pathClose,
socialText, socialText,
children, children,
}: { }: {
title?: string title?: string
pathShare: string pathShare: string
pathClose: string
socialText: string socialText: string
children: ReactNode children: ReactNode
}) { }) {
const { setShareModalProps } = useAppState();
const renderIcon = ( const renderIcon = (
icon: JSX.Element, icon: JSX.Element,
action: () => void, action: () => void,
@ -44,7 +45,7 @@ export default function ShareModal({
</div>; </div>;
return ( return (
<Modal onClosePath={pathClose}> <Modal onClose={() => setShareModalProps?.(undefined)}>
<div className="space-y-3 md:space-y-4 w-full"> <div className="space-y-3 md:space-y-4 w-full">
{title && {title &&
<div className={clsx( <div className={clsx(

View File

@ -1,6 +1,37 @@
import { Photo, PhotoSetAttributes, PhotoSetCategory } from '@/photo'; import { Photo, PhotoSetAttributes, PhotoSetCategory } from '@/photo';
import { absolutePathForPhotoImage } from '@/site/paths';
import { absolutePathForTagImage } from '@/site/paths';
import {
absolutePathForCamera,
absolutePathForFilmSimulation,
} from '@/site/paths';
import { absolutePathForFocalLength } from '@/site/paths';
export type ShareModalProps = Omit<PhotoSetAttributes, 'photos'> & { export type ShareModalProps = Omit<PhotoSetAttributes, 'photos'> & {
photo?: Photo photo?: Photo
photos?: Photo[] photos?: Photo[]
} & PhotoSetCategory; } & PhotoSetCategory;
export const getSharePathFromShareModalProps = ({
photo,
tag,
camera,
simulation,
focal,
}: ShareModalProps) => {
if (photo) {
return absolutePathForPhotoImage(photo);
}
if (tag) {
return absolutePathForTagImage(tag);
}
if (camera) {
return absolutePathForCamera(camera);
}
if (simulation) {
return absolutePathForFilmSimulation(simulation);
}
if (focal) {
return absolutePathForFocalLength(focal);
}
};

View File

@ -1,9 +1,6 @@
import { import { absolutePathForFilmSimulation } from '@/site/paths';
absolutePathForFilmSimulation,
pathForFilmSimulation,
} from '@/site/paths';
import { PhotoSetAttributes } from '../photo'; import { PhotoSetAttributes } from '../photo';
import ShareModal from '@/components/ShareModal'; import ShareModal from '@/share/ShareModal';
import FilmSimulationOGTile from './FilmSimulationOGTile'; import FilmSimulationOGTile from './FilmSimulationOGTile';
import { FilmSimulation, shareTextForFilmSimulation } from '.'; import { FilmSimulation, shareTextForFilmSimulation } from '.';
@ -18,7 +15,6 @@ export default function FilmSimulationShareModal({
return ( return (
<ShareModal <ShareModal
pathShare={absolutePathForFilmSimulation(simulation)} pathShare={absolutePathForFilmSimulation(simulation)}
pathClose={pathForFilmSimulation(simulation)}
socialText={shareTextForFilmSimulation(simulation)} socialText={shareTextForFilmSimulation(simulation)}
> >
<FilmSimulationOGTile {...{ simulation, photos, count, dateRange }} /> <FilmSimulationOGTile {...{ simulation, photos, count, dateRange }} />

View File

@ -3,7 +3,7 @@ import { AnimationConfig } from '@/components/AnimateItems';
import { ShareModalProps } from '@/share'; import { ShareModalProps } from '@/share';
export interface AppStateContext { export interface AppStateContext {
// GLOBAL // CORE
previousPathname?: string previousPathname?: string
hasLoaded?: boolean hasLoaded?: boolean
setHasLoaded?: Dispatch<SetStateAction<boolean>> setHasLoaded?: Dispatch<SetStateAction<boolean>>

View File

@ -8,6 +8,7 @@ import { getAuthAction } from '@/auth/actions';
import useSWR from 'swr'; import useSWR from 'swr';
import { HIGH_DENSITY_GRID, MATTE_PHOTOS } from '@/site/config'; import { HIGH_DENSITY_GRID, MATTE_PHOTOS } from '@/site/config';
import { getPhotosHiddenMetaCachedAction } from '@/photo/actions'; import { getPhotosHiddenMetaCachedAction } from '@/photo/actions';
import { ShareModalProps } from '@/share';
export default function AppStateProvider({ export default function AppStateProvider({
children, children,
@ -25,8 +26,11 @@ export default function AppStateProvider({
useState<AnimationConfig>(); useState<AnimationConfig>();
const [shouldRespondToKeyboardCommands, setShouldRespondToKeyboardCommands] = const [shouldRespondToKeyboardCommands, setShouldRespondToKeyboardCommands] =
useState(true); useState(true);
// MODAL
const [isCommandKOpen, setIsCommandKOpen] = const [isCommandKOpen, setIsCommandKOpen] =
useState(false); useState(false);
const [shareModalProps, setShareModalProps] =
useState<ShareModalProps>();
// ADMIN // ADMIN
const [userEmail, setUserEmail] = const [userEmail, setUserEmail] =
useState<string>(); useState<string>();
@ -89,8 +93,11 @@ export default function AppStateProvider({
clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined), clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined),
shouldRespondToKeyboardCommands, shouldRespondToKeyboardCommands,
setShouldRespondToKeyboardCommands, setShouldRespondToKeyboardCommands,
// MODAL
isCommandKOpen, isCommandKOpen,
setIsCommandKOpen, setIsCommandKOpen,
shareModalProps,
setShareModalProps,
// ADMIN // ADMIN
userEmail, userEmail,
setUserEmail, setUserEmail,

View File

@ -1,6 +1,6 @@
import { absolutePathForTag, pathForTag } from '@/site/paths'; import { absolutePathForTag } from '@/site/paths';
import { PhotoSetAttributes } from '../photo'; import { PhotoSetAttributes } from '../photo';
import ShareModal from '@/components/ShareModal'; import ShareModal from '@/share/ShareModal';
import TagOGTile from './TagOGTile'; import TagOGTile from './TagOGTile';
import { shareTextForTag } from '.'; import { shareTextForTag } from '.';
@ -15,7 +15,6 @@ export default function TagShareModal({
return ( return (
<ShareModal <ShareModal
pathShare={absolutePathForTag(tag)} pathShare={absolutePathForTag(tag)}
pathClose={pathForTag(tag)}
socialText={shareTextForTag(tag)} socialText={shareTextForTag(tag)}
> >
<TagOGTile {...{ tag, photos, count, dateRange }} /> <TagOGTile {...{ tag, photos, count, dateRange }} />