Rich sort controls (#283)
* Generalize app switcher menus * Organize sort module * Build configuration for nav sort control * Refine sort menu styles * Upgrade next.js * Reset custom sort when clicking grid/full a second time * Light up sort button when overridden
This commit is contained in:
parent
65f7f539a7
commit
646f32e642
@ -153,7 +153,13 @@ Application behavior can be changed by configuring the following environment var
|
||||
- `uploaded-at`
|
||||
- `uploaded-at-oldest-first`
|
||||
- `NEXT_PUBLIC_PRIORITY_BASED_SORTING = 1` takes priority field into account when sorting photos (⚠️ enabling may have performance consequences)
|
||||
- `NEXT_PUBLIC_SHOW_SORT_CONTROL = 1` shows sort control in desktop nav on grid/full homepages
|
||||
- `NEXT_PUBLIC_NAV_SORT_CONTROL`
|
||||
- Controls sort UI on grid/full homepages
|
||||
- Accepted values:
|
||||
- `none`
|
||||
- `toggle` (default)
|
||||
- `menu`
|
||||
|
||||
|
||||
#### Display
|
||||
- `NEXT_PUBLIC_HIDE_KEYBOARD_SHORTCUT_TOOLTIPS = 1` hides keyboard shortcut hints in areas like the main nav, and previous/next photo links
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
isPathProtected,
|
||||
isPathTag,
|
||||
isPathTagPhoto,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { TAG_PRIVATE } from '@/tag';
|
||||
|
||||
const PHOTO_ID = 'UsKSGcbt';
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
getUniqueRecipesCached,
|
||||
getUniqueTagsCached,
|
||||
} from '@/photo/cache';
|
||||
import { PATH_ADMIN } from '@/app/paths';
|
||||
import { PATH_ADMIN } from '@/app/path';
|
||||
import PhotoEditPageClient from '@/photo/PhotoEditPageClient';
|
||||
import {
|
||||
AI_TEXT_GENERATION_ENABLED,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import AdminChildPage from '@/components/AdminChildPage';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getPhotosCached, getPhotosMetaCached } from '@/photo/cache';
|
||||
import { PATH_ADMIN, PATH_ADMIN_RECIPES, pathForRecipe } from '@/app/paths';
|
||||
import { PATH_ADMIN, PATH_ADMIN_RECIPES, pathForRecipe } from '@/app/path';
|
||||
import PhotoLightbox from '@/photo/PhotoLightbox';
|
||||
import AdminRecipeBadge from '@/admin/AdminRecipeBadge';
|
||||
import AdminRecipeForm from '@/admin/AdminRecipeForm';
|
||||
|
||||
@ -2,7 +2,7 @@ import AdminChildPage from '@/components/AdminChildPage';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getPhotosCached, getPhotosMetaCached } from '@/photo/cache';
|
||||
import AdminTagForm from '@/admin/AdminTagForm';
|
||||
import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/app/paths';
|
||||
import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/app/path';
|
||||
import PhotoLightbox from '@/photo/PhotoLightbox';
|
||||
import AdminTagBadge from '@/admin/AdminTagBadge';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PARAM_UPLOAD_TITLE, PATH_ADMIN } from '@/app/paths';
|
||||
import { PARAM_UPLOAD_TITLE, PATH_ADMIN } from '@/app/path';
|
||||
import { extractImageDataFromBlobPath } from '@/photo/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
import {
|
||||
|
||||
@ -3,7 +3,7 @@ import AppGrid from '@/components/AppGrid';
|
||||
import { getUniqueTagsCached } from '@/photo/cache';
|
||||
import AdminUploadsClient from '@/admin/AdminUploadsClient';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
|
||||
export const maxDuration = 60;
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosMetaCached,
|
||||
|
||||
@ -5,7 +5,7 @@ import FilmOverview from '@/film/FilmOverview';
|
||||
import { getPhotosFilmDataCached } from '@/film/data';
|
||||
import { Metadata } from 'next/types';
|
||||
import { cache } from 'react';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { staticallyGenerateCategoryIfConfigured } from '@/app/static';
|
||||
import { getAppText } from '@/i18n/state/server';
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotosNearIdCached, getPhotosMetaCached } from '@/photo/cache';
|
||||
import { cache } from 'react';
|
||||
|
||||
@ -3,7 +3,7 @@ import FocalLengthOverview from '@/focal/FocalLengthOverview';
|
||||
import { getPhotosFocalLengthDataCached } from '@/focal/data';
|
||||
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||
import { getUniqueFocalLengths } from '@/photo/db/query';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import type { Metadata } from 'next';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { cache } from 'react';
|
||||
|
||||
@ -5,8 +5,8 @@ import { cache } from 'react';
|
||||
import { getPhotos } from '@/photo/db/query';
|
||||
import PhotoFullPage from '@/photo/PhotoFullPage';
|
||||
import { getPhotosMetaCached } from '@/photo/cache';
|
||||
import { SortProps } from '@/photo/db/sort';
|
||||
import { getSortOptionsFromParams } from '@/photo/db/sort-path';
|
||||
import { SortProps } from '@/photo/sort';
|
||||
import { getSortOptionsFromParams } from '@/photo/sort/path';
|
||||
import { PhotoQueryOptions } from '@/photo/db';
|
||||
import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed';
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ import { cache } from 'react';
|
||||
import PhotoGridPage from '@/photo/PhotoGridPage';
|
||||
import { getDataForCategoriesCached } from '@/category/cache';
|
||||
import { getPhotosMetaCached } from '@/photo/cache';
|
||||
import { SortProps } from '@/photo/db/sort';
|
||||
import { getSortOptionsFromParams } from '@/photo/db/sort-path';
|
||||
import { SortProps } from '@/photo/sort';
|
||||
import { getSortOptionsFromParams } from '@/photo/sort/path';
|
||||
import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed';
|
||||
import { PhotoQueryOptions } from '@/photo/db';
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ import RecipeModal from '@/recipe/RecipeModal';
|
||||
import ThemeColors from '@/app/ThemeColors';
|
||||
import AppTextProvider from '@/i18n/state/AppTextProvider';
|
||||
import SharedHoverProvider from '@/components/shared-hover/SharedHoverProvider';
|
||||
import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/paths';
|
||||
import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/path';
|
||||
|
||||
import '../tailwind.css';
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosMetaCached,
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotoCached, getPhotosNearIdCached } from '@/photo/cache';
|
||||
import { cache } from 'react';
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosMetaCached,
|
||||
|
||||
@ -4,7 +4,7 @@ import RecentsOverview from '@/recents/RecentsOverview';
|
||||
import { getPhotosRecentsDataCached } from '@/recents/data';
|
||||
import { Metadata } from 'next/types';
|
||||
import { cache } from 'react';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getAppText } from '@/i18n/state/server';
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotosMetaCached, getPhotosNearIdCached } from '@/photo/cache';
|
||||
import { cache } from 'react';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||
import { getUniqueRecipes } from '@/photo/db/query';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import type { Metadata } from 'next';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { cache } from 'react';
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosMetaCached,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { auth } from '@/auth/server';
|
||||
import SignInForm from '@/auth/SignInForm';
|
||||
import { PATH_ADMIN, PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ADMIN, PATH_ROOT } from '@/app/path';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { redirect } from 'next/navigation';
|
||||
import LinkWithStatus from '@/components/LinkWithStatus';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
absolutePathForRecipe,
|
||||
absolutePathForTag,
|
||||
absolutePathForYear,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { isTagFavs } from '@/tag';
|
||||
import { BASE_URL, GRID_HOMEPAGE_ENABLED } from '@/app/config';
|
||||
import { getPhotoIdsAndUpdatedAt } from '@/photo/db/query';
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotosMetaCached, getPhotosNearIdCached } from '@/photo/cache';
|
||||
import { cache } from 'react';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo';
|
||||
import { getUniqueTags } from '@/photo/db/query';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import { generateMetaForTag } from '@/tag';
|
||||
import TagOverview from '@/tag/TagOverview';
|
||||
import { getPhotosTagDataCached } from '@/tag/data';
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
getPhotosMetaCached,
|
||||
getPhotosNearIdCached,
|
||||
} from '@/photo/cache';
|
||||
import { PATH_ROOT, absolutePathForPhoto } from '@/app/paths';
|
||||
import { PATH_ROOT, absolutePathForPhoto } from '@/app/path';
|
||||
import { TAG_PRIVATE } from '@/tag';
|
||||
import { Metadata } from 'next';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
@ -3,7 +3,7 @@ import Note from '@/components/Note';
|
||||
import AppGrid from '@/components/AppGrid';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import { getPhotosMetaCached, getPhotosNoStore } from '@/photo/cache';
|
||||
import { absolutePathForTag } from '@/app/paths';
|
||||
import { absolutePathForTag } from '@/app/path';
|
||||
import { TAG_PRIVATE, descriptionForTaggedPhotos, titleForTag } from '@/tag';
|
||||
import PrivateHeader from '@/tag/PrivateHeader';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
PATH_ROOT,
|
||||
absolutePathForPhoto,
|
||||
absolutePathForPhotoImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import {
|
||||
getPhotosMetaCached,
|
||||
|
||||
@ -5,7 +5,7 @@ import YearOverview from '@/years/YearOverview';
|
||||
import { getPhotosYearDataCached } from '@/years/data';
|
||||
import { Metadata } from 'next/types';
|
||||
import { cache } from 'react';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { staticallyGenerateCategoryIfConfigured } from '@/app/static';
|
||||
import { getAppText } from '@/i18n/state/server';
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
PATH_OG_SAMPLE,
|
||||
PREFIX_PHOTO,
|
||||
PREFIX_TAG,
|
||||
} from './src/app/paths';
|
||||
} from './src/app/path';
|
||||
|
||||
export default function middleware(req: NextRequest, res:NextResponse) {
|
||||
const pathname = req.nextUrl.pathname;
|
||||
|
||||
18
package.json
18
package.json
@ -10,8 +10,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.23",
|
||||
"@aws-sdk/client-s3": "3.844.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.844.0",
|
||||
"@aws-sdk/client-s3": "3.846.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.846.0",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
@ -21,16 +21,16 @@
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/blob": "^1.1.1",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"ai": "^4.3.17",
|
||||
"ai": "^4.3.19",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"framer-motion": "^12.23.3",
|
||||
"framer-motion": "^12.23.6",
|
||||
"nanoid": "^5.1.5",
|
||||
"next": "15.3.5",
|
||||
"next": "15.4.1",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"next-themes": "^0.4.6",
|
||||
"pg": "^8.16.3",
|
||||
@ -47,8 +47,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@next/bundle-analyzer": "15.3.5",
|
||||
"@next/eslint-plugin-next": "^15.3.5",
|
||||
"@next/bundle-analyzer": "15.4.1",
|
||||
"@next/eslint-plugin-next": "^15.4.1",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
@ -56,14 +56,14 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.0.13",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/pg": "^8.15.4",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"cross-fetch": "^4.1.0",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-config-next": "15.3.5",
|
||||
"eslint-config-next": "15.4.1",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"jest": "^30.0.4",
|
||||
"jest-environment-jsdom": "^30.0.4",
|
||||
|
||||
573
pnpm-lock.yaml
generated
573
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import {
|
||||
generateLocalNaivePostgresString,
|
||||
generateLocalPostgresString,
|
||||
} from '@/utility/date';
|
||||
import { pathForAdminUploadUrl } from '@/app/paths';
|
||||
import { pathForAdminUploadUrl } from '@/app/path';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ComponentProps, useState } from 'react';
|
||||
import IconAddUpload from '@/components/icons/IconAddUpload';
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import MoreMenu from '@/components/more/MoreMenu';
|
||||
import {
|
||||
PATH_ADMIN_CONFIGURATION,
|
||||
PATH_ADMIN_INSIGHTS,
|
||||
@ -10,7 +9,7 @@ import {
|
||||
PATH_ADMIN_TAGS,
|
||||
PATH_ADMIN_UPLOADS,
|
||||
PATH_GRID_INFERRED,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5';
|
||||
import { clsx } from 'clsx/lite';
|
||||
@ -30,19 +29,15 @@ import InsightsIndicatorDot from './insights/InsightsIndicatorDot';
|
||||
import MoreMenuItem from '@/components/more/MoreMenuItem';
|
||||
import Spinner from '@/components/Spinner';
|
||||
import { useAppText } from '@/i18n/state/client';
|
||||
import SwitcherItemMenu from '@/components/switcher/SwitcherItemMenu';
|
||||
import { MoreMenuSection } from '@/components/more/MoreMenu';
|
||||
|
||||
export default function AdminAppMenu({
|
||||
active,
|
||||
animateMenuClose,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
className,
|
||||
}: {
|
||||
active?: boolean
|
||||
animateMenuClose?: boolean
|
||||
isOpen?: boolean
|
||||
setIsOpen?: (isOpen: boolean) => void
|
||||
className?: string
|
||||
}) {
|
||||
const {
|
||||
photosCountTotal = 0,
|
||||
@ -66,8 +61,7 @@ export default function AdminAppMenu({
|
||||
|
||||
const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed;
|
||||
|
||||
const sectionUpload: ComponentProps<typeof MoreMenuItem>[] =
|
||||
useMemo(() => ([{
|
||||
const sectionUpload: MoreMenuSection = useMemo(() => ({ items: [{
|
||||
label: appText.admin.uploadPhotos,
|
||||
icon: <IconUpload
|
||||
size={15}
|
||||
@ -76,9 +70,9 @@ export default function AdminAppMenu({
|
||||
annotation: isLoadingAdminData &&
|
||||
<Spinner className="translate-y-[1.5px]" />,
|
||||
action: startUpload,
|
||||
}]), [appText, isLoadingAdminData, startUpload]);
|
||||
}]}), [appText, isLoadingAdminData, startUpload]);
|
||||
|
||||
const sectionMain: ComponentProps<typeof MoreMenuItem>[] = useMemo(() => {
|
||||
const sectionMain: MoreMenuSection = useMemo(() => {
|
||||
const items: ComponentProps<typeof MoreMenuItem>[] = [];
|
||||
|
||||
if (uploadsCount) {
|
||||
@ -188,7 +182,7 @@ export default function AdminAppMenu({
|
||||
: PATH_ADMIN_CONFIGURATION,
|
||||
});
|
||||
|
||||
return items;
|
||||
return { items };
|
||||
}, [
|
||||
appText,
|
||||
isSelecting,
|
||||
@ -201,28 +195,24 @@ export default function AdminAppMenu({
|
||||
uploadsCount,
|
||||
]);
|
||||
|
||||
const sectionSignOut: ComponentProps<typeof MoreMenuItem>[] =
|
||||
useMemo(() => ([{
|
||||
const sectionSignOut: MoreMenuSection = useMemo(() => ({
|
||||
items: [{
|
||||
label: appText.auth.signOut,
|
||||
icon: <IconSignOut size={15} />,
|
||||
action: () => signOutAction().then(clearAuthStateAndRedirectIfNecessary),
|
||||
}]), [appText.auth.signOut, clearAuthStateAndRedirectIfNecessary]);
|
||||
}],
|
||||
}), [appText.auth.signOut, clearAuthStateAndRedirectIfNecessary]);
|
||||
|
||||
const sections = useMemo(() =>
|
||||
[sectionUpload, sectionMain, sectionSignOut]
|
||||
, [sectionUpload, sectionMain, sectionSignOut]);
|
||||
|
||||
return (
|
||||
<MoreMenu
|
||||
<SwitcherItemMenu
|
||||
{...{ isOpen, setIsOpen }}
|
||||
icon={<div className={clsx(
|
||||
'w-[28px] h-[28px]',
|
||||
'overflow-hidden',
|
||||
)}>
|
||||
icon={<div className="w-[28px] h-[28px] overflow-hidden">
|
||||
<div className={clsx(
|
||||
'flex flex-col items-center justify-center gap-2',
|
||||
'relative transition-transform',
|
||||
animateMenuClose ? 'duration-300' : 'duration-0',
|
||||
'relative flex flex-col items-center justify-center gap-2',
|
||||
'translate-y-[-18px]',
|
||||
)}>
|
||||
<IoArrowDown size={16} className="shrink-0" />
|
||||
@ -233,28 +223,12 @@ export default function AdminAppMenu({
|
||||
sideOffset={12}
|
||||
alignOffset={-84}
|
||||
onOpen={refreshAdminData}
|
||||
className={clsx(
|
||||
'outline-medium',
|
||||
className,
|
||||
)}
|
||||
classNameButton={clsx(
|
||||
'p-0!',
|
||||
'w-full h-full',
|
||||
'flex items-center justify-center',
|
||||
'hover:bg-transparent dark:hover:bg-transparent',
|
||||
'active:bg-transparent dark:active:bg-transparent',
|
||||
'rounded-none focus:outline-none',
|
||||
active
|
||||
? 'text-black dark:text-white'
|
||||
: 'text-gray-400 dark:text-gray-600',
|
||||
)}
|
||||
classNameButtonOpen={clsx(
|
||||
'bg-dim text-main!',
|
||||
'[&>*>*]:translate-y-[6px]',
|
||||
!animateMenuClose && '[&>*>*]:duration-300',
|
||||
)}
|
||||
sections={sections}
|
||||
ariaLabel="Admin Menu"
|
||||
classNameButtonOpen={clsx(
|
||||
'[&>*>*]:translate-y-[6px]',
|
||||
'[&>*>*]:duration-300',
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import { IoCloseSharp } from 'react-icons/io5';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { TAG_FAVS, Tags } from '@/tag';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { PATH_GRID_INFERRED } from '@/app/paths';
|
||||
import { PATH_GRID_INFERRED } from '@/app/path';
|
||||
import PhotoTagFieldset from './PhotoTagFieldset';
|
||||
import { tagMultiplePhotosAction } from '@/photo/actions';
|
||||
import { toastSuccess } from '@/toast';
|
||||
|
||||
@ -4,7 +4,7 @@ import ErrorNote from '@/components/ErrorNote';
|
||||
import FieldsetWithStatus from '@/components/FieldsetWithStatus';
|
||||
import Container from '@/components/Container';
|
||||
import { addUploadsAction } from '@/photo/actions';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
import { Tags } from '@/tag';
|
||||
import {
|
||||
generateLocalNaivePostgresString,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS } from '@/app/paths';
|
||||
import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS } from '@/app/path';
|
||||
import ResponsiveText from '@/components/primitives/ResponsiveText';
|
||||
import clsx from 'clsx/lite';
|
||||
import ClearCacheButton from '@/admin/ClearCacheButton';
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
PATH_ADMIN_RECIPES,
|
||||
PATH_ADMIN_TAGS,
|
||||
PATH_ADMIN_UPLOADS,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import AdminNavClient from './AdminNavClient';
|
||||
import { getAppText } from '@/i18n/state/server';
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
checkPathPrefix,
|
||||
isPathAdminInfo,
|
||||
isPathTopLevelAdmin,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { differenceInMinutes } from 'date-fns';
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
PATH_ROOT,
|
||||
pathForAdminPhotoEdit,
|
||||
pathForTag,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import {
|
||||
deletePhotoAction,
|
||||
syncPhotoAction,
|
||||
@ -21,7 +21,7 @@ import {
|
||||
import { isPathFavs, isPhotoFav, TAG_PRIVATE } from '@/tag';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { BiTrash } from 'react-icons/bi';
|
||||
import MoreMenu from '@/components/more/MoreMenu';
|
||||
import MoreMenu, { MoreMenuSection } from '@/components/more/MoreMenu';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll';
|
||||
import { MdOutlineFileDownload } from 'react-icons/md';
|
||||
@ -139,7 +139,7 @@ export default function AdminPhotoMenu({
|
||||
...showKeyCommands && { keyCommand: KEY_COMMANDS.sync },
|
||||
});
|
||||
|
||||
return items;
|
||||
return { items };
|
||||
}, [
|
||||
appText,
|
||||
photo,
|
||||
@ -151,7 +151,8 @@ export default function AdminPhotoMenu({
|
||||
revalidatePhoto,
|
||||
]);
|
||||
|
||||
const sectionDelete: ComponentProps<typeof MoreMenuItem>[] = useMemo(() => [{
|
||||
const sectionDelete: MoreMenuSection = useMemo(() => ({
|
||||
items: [{
|
||||
label: appText.admin.delete,
|
||||
icon: <BiTrash
|
||||
size={15}
|
||||
@ -175,7 +176,8 @@ export default function AdminPhotoMenu({
|
||||
keyCommandModifier: KEY_COMMANDS.delete[0],
|
||||
keyCommand: KEY_COMMANDS.delete[1],
|
||||
},
|
||||
}], [
|
||||
}],
|
||||
}), [
|
||||
appText,
|
||||
photo,
|
||||
showKeyCommands,
|
||||
|
||||
@ -5,7 +5,7 @@ import AppGrid from '@/components/AppGrid';
|
||||
import AdminPhotosTable from '@/admin/AdminPhotosTable';
|
||||
import AdminPhotosTableInfinite from '@/admin/AdminPhotosTableInfinite';
|
||||
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
|
||||
import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/path';
|
||||
import { Photo } from '@/photo';
|
||||
import { StorageListResponse } from '@/platforms/storage';
|
||||
import AdminUploadsTable from './AdminUploadsTable';
|
||||
|
||||
@ -5,7 +5,7 @@ import AdminPhotosTable from '@/admin/AdminPhotosTable';
|
||||
import IconGrSync from '@/components/icons/IconGrSync';
|
||||
import Note from '@/components/Note';
|
||||
import AdminChildPage from '@/components/AdminChildPage';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { syncPhotosAction } from '@/photo/actions';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
@ -5,7 +5,7 @@ import AdminTable from './AdminTable';
|
||||
import { Fragment } from 'react';
|
||||
import PhotoSmall from '@/photo/PhotoSmall';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths';
|
||||
import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/path';
|
||||
import Link from 'next/link';
|
||||
import PhotoDate from '@/photo/PhotoDate';
|
||||
import EditButton from './EditButton';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
import InfinitePhotoScroll from '../photo/InfinitePhotoScroll';
|
||||
import AdminPhotosTable from './AdminPhotosTable';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||
import Link from 'next/link';
|
||||
import { PATH_ADMIN_RECIPES } from '@/app/paths';
|
||||
import { PATH_ADMIN_RECIPES } from '@/app/path';
|
||||
import FieldsetWithStatus from '@/components/FieldsetWithStatus';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { renamePhotoRecipeGloballyAction } from '@/photo/actions';
|
||||
|
||||
@ -5,7 +5,7 @@ import { Fragment } from 'react';
|
||||
import DeleteFormButton from '@/admin/DeleteFormButton';
|
||||
import { photoQuantityText } from '@/photo';
|
||||
import EditButton from '@/admin/EditButton';
|
||||
import { pathForAdminRecipeEdit } from '@/app/paths';
|
||||
import { pathForAdminRecipeEdit } from '@/app/path';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { formatRecipe, Recipes, sortRecipes } from '@/recipe';
|
||||
import AdminRecipeBadge from './AdminRecipeBadge';
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||
import Link from 'next/link';
|
||||
import { PATH_ADMIN_TAGS } from '@/app/paths';
|
||||
import { PATH_ADMIN_TAGS } from '@/app/path';
|
||||
import FieldsetWithStatus from '@/components/FieldsetWithStatus';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
import { renamePhotoTagGloballyAction } from '@/photo/actions';
|
||||
|
||||
@ -6,7 +6,7 @@ import DeleteFormButton from '@/admin/DeleteFormButton';
|
||||
import { photoQuantityText } from '@/photo';
|
||||
import { Tags, formatTag, sortTags } from '@/tag';
|
||||
import EditButton from '@/admin/EditButton';
|
||||
import { pathForAdminTagEdit } from '@/app/paths';
|
||||
import { pathForAdminTagEdit } from '@/app/path';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import AdminTagBadge from './AdminTagBadge';
|
||||
import { getAppText } from '@/i18n/state/server';
|
||||
|
||||
@ -8,7 +8,7 @@ import clsx from 'clsx/lite';
|
||||
import ResponsiveDate from '@/components/ResponsiveDate';
|
||||
import Spinner from '@/components/Spinner';
|
||||
import { FaRegCircleCheck } from 'react-icons/fa6';
|
||||
import { pathForAdminUploadUrl } from '@/app/paths';
|
||||
import { pathForAdminUploadUrl } from '@/app/path';
|
||||
import DeleteUploadButton from './DeleteUploadButton';
|
||||
import { Dispatch, SetStateAction, useEffect, useRef } from 'react';
|
||||
import { isElementEntirelyInViewport } from '@/utility/dom';
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { deleteUploadsAction } from '@/photo/actions';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
import { ComponentProps, useState } from 'react';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
|
||||
|
||||
@ -23,8 +23,8 @@ import { DEFAULT_CATEGORY_KEYS, getHiddenCategories } from '@/category';
|
||||
import { AI_AUTO_GENERATED_FIELDS_ALL } from '@/photo/ai';
|
||||
import clsx from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/paths';
|
||||
import { APP_DEFAULT_SORT_BY, SORT_BY_OPTIONS } from '@/photo/db/sort';
|
||||
import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/path';
|
||||
import { APP_DEFAULT_SORT_BY, SORT_BY_OPTIONS } from '@/photo/sort';
|
||||
import {
|
||||
AdminConfigSection,
|
||||
ConfigSectionKey,
|
||||
@ -86,7 +86,8 @@ export default function AdminAppConfigurationClient({
|
||||
hasDefaultSortBy,
|
||||
defaultSortBy,
|
||||
isSortWithPriority,
|
||||
showSortControl,
|
||||
hasNavSortControl,
|
||||
navSortControl,
|
||||
// Display
|
||||
showKeyboardShortcutTooltips,
|
||||
showExifInfo,
|
||||
@ -605,7 +606,7 @@ export default function AdminAppConfigurationClient({
|
||||
case 'Sorting':
|
||||
return <>
|
||||
<ChecklistRow
|
||||
title="Order"
|
||||
title="Default order"
|
||||
status={hasDefaultSortBy}
|
||||
optional
|
||||
>
|
||||
@ -634,13 +635,13 @@ export default function AdminAppConfigurationClient({
|
||||
{renderEnvVars(['NEXT_PUBLIC_PRIORITY_BASED_SORTING'])}
|
||||
</ChecklistRow>
|
||||
<ChecklistRow
|
||||
title="Show nav button"
|
||||
status={showSortControl}
|
||||
title={`Nav sort control: ${navSortControl}`}
|
||||
status={hasNavSortControl}
|
||||
optional
|
||||
>
|
||||
Set environment variable to {'"1"'} to
|
||||
show sort control in desktop nav on grid/full homepages:
|
||||
{renderEnvVars(['NEXT_PUBLIC_SHOW_SORT_CONTROL'])}
|
||||
Set environment variable to {'"none"'}, {'"toggle"'} (default),
|
||||
or {'"menu"'}, to control sort UI on grid/full homepages:
|
||||
{renderEnvVars(['NEXT_PUBLIC_NAV_SORT_CONTROL'])}
|
||||
</ChecklistRow>
|
||||
</>;
|
||||
case 'Display':
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
import EnvVar from '@/components/EnvVar';
|
||||
import { IoSyncCircle } from 'react-icons/io5';
|
||||
import clsx from 'clsx/lite';
|
||||
import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/path';
|
||||
import { LiaBroomSolid } from 'react-icons/lia';
|
||||
import { IoMdGrid } from 'react-icons/io';
|
||||
import { RiSpeedMiniLine } from 'react-icons/ri';
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
getAuthEmailCookie,
|
||||
} from '@/auth';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import { isPathProtected, PATH_ROOT } from '@/app/paths';
|
||||
import { isPathProtected, PATH_ROOT } from '@/app/path';
|
||||
import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload';
|
||||
import { RecipeProps } from '@/recipe';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
@ -1,34 +1,37 @@
|
||||
import Switcher from '@/components/Switcher';
|
||||
import SwitcherItem from '@/components/SwitcherItem';
|
||||
import Switcher from '@/components/switcher/Switcher';
|
||||
import SwitcherItem from '@/components/switcher/SwitcherItem';
|
||||
import IconFull from '@/components/icons/IconFull';
|
||||
import IconGrid from '@/components/icons/IconGrid';
|
||||
import {
|
||||
doesPathOfferSort,
|
||||
PATH_FULL_INFERRED,
|
||||
PATH_GRID_INFERRED,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import IconSearch from '../components/icons/IconSearch';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import {
|
||||
GRID_HOMEPAGE_ENABLED,
|
||||
SHOW_KEYBOARD_SHORTCUT_TOOLTIPS,
|
||||
SHOW_SORT_CONTROL,
|
||||
NAV_SORT_CONTROL,
|
||||
} from './config';
|
||||
import AdminAppMenu from '@/admin/AdminAppMenu';
|
||||
import Spinner from '@/components/Spinner';
|
||||
import clsx from 'clsx/lite';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import useKeydownHandler from '@/utility/useKeydownHandler';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { KEY_COMMANDS } from '@/photo/key-commands';
|
||||
import { useAppText } from '@/i18n/state/client';
|
||||
import IconSort from '@/components/icons/IconSort';
|
||||
import { getSortConfigFromPath } from '@/photo/db/sort-path';
|
||||
import { getSortConfigFromPath } from '@/photo/sort/path';
|
||||
import { motion } from 'framer-motion';
|
||||
import SortMenu from '@/photo/sort/SortMenu';
|
||||
import { SWR_KEYS } from '@/swr';
|
||||
|
||||
export type SwitcherSelection = 'full' | 'grid' | 'admin';
|
||||
|
||||
const GAP_CLASS = 'mr-1.5 sm:mr-2';
|
||||
const GAP_CLASS_RIGHT = 'mr-1.5 sm:mr-2';
|
||||
const GAP_CLASS_LEFT = 'ml-0.5 sm:ml-1';
|
||||
|
||||
export default function AppViewSwitcher({
|
||||
currentSelection,
|
||||
@ -50,20 +53,26 @@ export default function AppViewSwitcher({
|
||||
invalidateSwr,
|
||||
} = useAppState();
|
||||
|
||||
const showSortControl = SHOW_SORT_CONTROL && doesPathOfferSort(pathname);
|
||||
const sortConfig = useMemo(() => getSortConfigFromPath(pathname), [pathname]);
|
||||
|
||||
const {
|
||||
sortBy,
|
||||
isSortedByDefault,
|
||||
isAscending,
|
||||
pathGrid,
|
||||
pathFull,
|
||||
pathSort,
|
||||
} = getSortConfigFromPath(pathname);
|
||||
pathSortToggle,
|
||||
} = sortConfig;
|
||||
|
||||
const showSortControl =
|
||||
NAV_SORT_CONTROL !== 'none' &&
|
||||
doesPathOfferSort(pathname);
|
||||
|
||||
const hasLoadedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (hasLoadedRef.current) {
|
||||
// After initial load, invalidate cache every time sort changes
|
||||
invalidateSwr?.('INFINITE_PHOTO_SCROLL');
|
||||
invalidateSwr?.(SWR_KEYS.INFINITE_PHOTO_SCROLL);
|
||||
}
|
||||
hasLoadedRef.current = true;
|
||||
}, [invalidateSwr, sortBy]);
|
||||
@ -88,6 +97,7 @@ export default function AppViewSwitcher({
|
||||
}, [pathname, isUserSignedIn]);
|
||||
useKeydownHandler({ onKeyDown });
|
||||
|
||||
const [isSortMenuOpen, setIsSortMenuOpen] = useState(false);
|
||||
const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false);
|
||||
|
||||
const renderItemFull =
|
||||
@ -120,7 +130,7 @@ export default function AppViewSwitcher({
|
||||
<div className={clsx('flex', className)}>
|
||||
<Switcher
|
||||
className={clsx(
|
||||
GAP_CLASS,
|
||||
GAP_CLASS_RIGHT,
|
||||
// Apply offset due to outline strategy
|
||||
'translate-x-[1px]',
|
||||
)}
|
||||
@ -144,7 +154,10 @@ export default function AppViewSwitcher({
|
||||
<SwitcherItem
|
||||
icon={<AdminAppMenu
|
||||
isOpen={isAdminMenuOpen}
|
||||
setIsOpen={setIsAdminMenuOpen}
|
||||
setIsOpen={isOpen => {
|
||||
setIsAdminMenuOpen(isOpen);
|
||||
if (isOpen) { setIsSortMenuOpen(false); }
|
||||
}}
|
||||
/>}
|
||||
tooltip={{
|
||||
...!isAdminMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
|
||||
@ -162,9 +175,37 @@ export default function AppViewSwitcher({
|
||||
exit={{ opacity: 0, scale: 0.5 }}
|
||||
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
||||
>
|
||||
<Switcher className={clsx('max-sm:hidden', GAP_CLASS)}>
|
||||
<SwitcherItem
|
||||
href={pathSort}
|
||||
<Switcher
|
||||
className={clsx('max-sm:hidden', GAP_CLASS_LEFT)}
|
||||
type="borderless"
|
||||
>
|
||||
{NAV_SORT_CONTROL === 'menu'
|
||||
? <SwitcherItem
|
||||
className={clsx(
|
||||
!isSortedByDefault && '*:bg-medium *:text-main!',
|
||||
)}
|
||||
icon={<SortMenu
|
||||
{...sortConfig}
|
||||
isOpen={isSortMenuOpen}
|
||||
setIsOpen={isOpen => {
|
||||
setIsSortMenuOpen(isOpen);
|
||||
if (isOpen) { setIsAdminMenuOpen(false); }
|
||||
}}
|
||||
/>}
|
||||
tooltip={{
|
||||
...!isSortMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
|
||||
content: 'Sort',
|
||||
},
|
||||
}}
|
||||
width="narrow"
|
||||
noPadding
|
||||
/>
|
||||
: <SwitcherItem
|
||||
className={clsx(
|
||||
'*:w-full *:h-full *:flex *:items-center *:justify-center',
|
||||
!isSortedByDefault && '*:bg-medium *:text-main!',
|
||||
)}
|
||||
href={pathSortToggle}
|
||||
icon={<IconSort
|
||||
sort={isAscending ? 'asc' : 'desc'}
|
||||
className="translate-x-[0.5px] translate-y-[1px]"
|
||||
@ -174,7 +215,9 @@ export default function AppViewSwitcher({
|
||||
? appText.sort.newest
|
||||
: appText.sort.oldest,
|
||||
}}
|
||||
/>
|
||||
width="narrow"
|
||||
noPadding
|
||||
/>}
|
||||
</Switcher>
|
||||
</motion.div>}
|
||||
<motion.div
|
||||
@ -192,6 +235,7 @@ export default function AppViewSwitcher({
|
||||
keyCommandModifier: KEY_COMMANDS.search[0],
|
||||
keyCommand: KEY_COMMANDS.search[1],
|
||||
}}}
|
||||
width="narrow"
|
||||
/>
|
||||
</Switcher>
|
||||
</motion.div>
|
||||
|
||||
@ -7,7 +7,7 @@ import Link from 'next/link';
|
||||
import { SHOW_REPO_LINK } from '@/app/config';
|
||||
import RepoLink from '../components/RepoLink';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { PATH_ADMIN_PHOTOS, isPathAdmin, isPathSignIn } from './paths';
|
||||
import { PATH_ADMIN_PHOTOS, isPathAdmin, isPathSignIn } from './path';
|
||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||
import { signOutAction } from '@/auth/actions';
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
isPathGrid,
|
||||
isPathProtected,
|
||||
isPathSignIn,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import AnimateItems from '../components/AnimateItems';
|
||||
import {
|
||||
GRID_HOMEPAGE_ENABLED,
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import Switcher from '@/components/Switcher';
|
||||
import SwitcherItem from '@/components/SwitcherItem';
|
||||
import Switcher from '@/components/switcher/Switcher';
|
||||
import SwitcherItem from '@/components/switcher/SwitcherItem';
|
||||
import { BiDesktop, BiMoon, BiSun } from 'react-icons/bi';
|
||||
import { useAppText } from '@/i18n/state/client';
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
makeUrlAbsolute,
|
||||
shortenUrl,
|
||||
} from '@/utility/url';
|
||||
import { getSortByFromString } from '@/photo/db/sort';
|
||||
import { getNavSortControlFromString, getSortByFromString } from '@/photo/sort';
|
||||
|
||||
// HARD-CODED GLOBAL CONFIGURATION
|
||||
|
||||
@ -280,8 +280,8 @@ export const USER_DEFAULT_SORT_OPTIONS = {
|
||||
sortBy: USER_DEFAULT_SORT_BY,
|
||||
sortWithPriority: USER_DEFAULT_SORT_WITH_PRIORITY,
|
||||
};
|
||||
export const SHOW_SORT_CONTROL =
|
||||
process.env.NEXT_PUBLIC_SHOW_SORT_CONTROL === '1';
|
||||
export const NAV_SORT_CONTROL =
|
||||
getNavSortControlFromString(process.env.NEXT_PUBLIC_NAV_SORT_CONTROL);
|
||||
|
||||
// DISPLAY
|
||||
|
||||
@ -422,7 +422,8 @@ export const APP_CONFIGURATION = {
|
||||
hasDefaultSortBy: Boolean(process.env.NEXT_PUBLIC_DEFAULT_SORT),
|
||||
defaultSortBy: USER_DEFAULT_SORT_BY,
|
||||
isSortWithPriority: USER_DEFAULT_SORT_WITH_PRIORITY,
|
||||
showSortControl: SHOW_SORT_CONTROL,
|
||||
hasNavSortControl: Boolean(process.env.NEXT_PUBLIC_NAV_SORT_CONTROL),
|
||||
navSortControl: NAV_SORT_CONTROL,
|
||||
// Display
|
||||
showKeyboardShortcutTooltips: SHOW_KEYBOARD_SHORTCUT_TOOLTIPS,
|
||||
showExifInfo: SHOW_EXIF_DATA,
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
import IconLock from '@/components/icons/IconLock';
|
||||
import { useAppText } from '@/i18n/state/client';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { isPathProtected } from '@/app/paths';
|
||||
import { isPathProtected } from '@/app/path';
|
||||
import NextAuth, { User } from 'next-auth';
|
||||
import Credentials from 'next-auth/providers/credentials';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import { pathForCamera, pathForCameraImage } from '@/app/paths';
|
||||
import { pathForCamera, pathForCameraImage } from '@/app/path';
|
||||
import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
|
||||
import { Camera } from '.';
|
||||
import { descriptionForCameraPhotos, titleForCamera } from './meta';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { absolutePathForCamera } from '@/app/paths';
|
||||
import { absolutePathForCamera } from '@/app/path';
|
||||
import { PhotoSetAttributes } from '../category';
|
||||
import ShareModal from '@/share/ShareModal';
|
||||
import CameraOGTile from './CameraOGTile';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { AiFillApple } from 'react-icons/ai';
|
||||
import { pathForCamera } from '@/app/paths';
|
||||
import { pathForCamera } from '@/app/path';
|
||||
import { Camera, formatCameraText } from '.';
|
||||
import EntityLink, {
|
||||
EntityLinkExternalProps,
|
||||
|
||||
@ -8,7 +8,7 @@ import { Camera, cameraFromPhoto, formatCameraText } from '.';
|
||||
import {
|
||||
absolutePathForCamera,
|
||||
absolutePathForCameraImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { AppTextState } from '@/i18n/state';
|
||||
|
||||
// Meta functions moved to separate file to avoid
|
||||
|
||||
@ -32,7 +32,7 @@ import {
|
||||
pathForTag,
|
||||
pathForYear,
|
||||
PREFIX_RECENTS,
|
||||
} from '../app/paths';
|
||||
} from '../app/path';
|
||||
import Modal from '../components/Modal';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import AppGrid from './AppGrid';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function HttpStatusPage({
|
||||
|
||||
@ -6,7 +6,7 @@ import { clsx } from 'clsx/lite';
|
||||
import useClickInsideOutside from '@/utility/useClickInsideOutside';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import AnimateItems from './AnimateItems';
|
||||
import { PATH_ROOT } from '@/app/paths';
|
||||
import { PATH_ROOT } from '@/app/path';
|
||||
import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion';
|
||||
import useEscapeHandler from '@/utility/useEscapeHandler';
|
||||
import { useTheme } from 'next-themes';
|
||||
|
||||
@ -10,6 +10,11 @@ import { clsx } from 'clsx/lite';
|
||||
import { FiMoreHorizontal } from 'react-icons/fi';
|
||||
import MoreMenuItem from './MoreMenuItem';
|
||||
|
||||
export type MoreMenuSection = {
|
||||
label?: string
|
||||
items: ComponentProps<typeof MoreMenuItem>[]
|
||||
}
|
||||
|
||||
export default function MoreMenu({
|
||||
sections,
|
||||
icon,
|
||||
@ -26,7 +31,7 @@ export default function MoreMenu({
|
||||
onOpen,
|
||||
...props
|
||||
}: {
|
||||
sections: ComponentProps<typeof MoreMenuItem>[][]
|
||||
sections: MoreMenuSection[]
|
||||
icon?: ReactNode
|
||||
header?: ReactNode
|
||||
className?: string
|
||||
@ -84,7 +89,7 @@ export default function MoreMenu({
|
||||
'min-w-[8rem]',
|
||||
'component-surface',
|
||||
'py-1',
|
||||
'shadow-lg shadow-gray-900/10 dark:shadow-900',
|
||||
'not-dark:shadow-lg not-dark:shadow-gray-900/10',
|
||||
'data-[side=top]:dark:shadow-[0_0px_40px_rgba(0,0,0,0.6)]',
|
||||
'data-[side=bottom]:dark:shadow-[0_10px_40px_rgba(0,0,0,0.6)]',
|
||||
'data-[side=top]:animate-fade-in-from-bottom',
|
||||
@ -99,7 +104,7 @@ export default function MoreMenu({
|
||||
{header}
|
||||
</div>}
|
||||
<div className="divide-y divide-medium">
|
||||
{sections.map((section, index) =>
|
||||
{sections.map(({ label, items }, index) =>
|
||||
<div
|
||||
key={index}
|
||||
className={clsx(
|
||||
@ -107,11 +112,17 @@ export default function MoreMenu({
|
||||
'[&:not(:last-child)]:pb-1',
|
||||
)}
|
||||
>
|
||||
{section.map(props =>
|
||||
<div key={props.label} className="px-1">
|
||||
{label && <div className={clsx(
|
||||
'px-3.5 pt-1.5 pb-0.5 select-none',
|
||||
'text-extra-dim uppercase text-xs font-medium tracking-wide',
|
||||
)}>
|
||||
{label}
|
||||
</div>}
|
||||
{items.map(item =>
|
||||
<div key={item.label} className="px-1">
|
||||
<MoreMenuItem
|
||||
{...item}
|
||||
dismissMenu={dismissMenu}
|
||||
{...props}
|
||||
/>
|
||||
</div>,
|
||||
)}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config';
|
||||
import { ComponentProps, ReactNode, RefObject } from 'react';
|
||||
import Spinner from './Spinner';
|
||||
import LinkWithIconLoader from './LinkWithIconLoader';
|
||||
import Tooltip from './Tooltip';
|
||||
import Spinner from '../Spinner';
|
||||
import LinkWithIconLoader from '../LinkWithIconLoader';
|
||||
import Tooltip from '../Tooltip';
|
||||
|
||||
const WIDTH_CLASS = 'w-[42px]';
|
||||
const WIDTH_CLASS_NARROW = 'w-[36px]';
|
||||
|
||||
export default function SwitcherItem({
|
||||
icon,
|
||||
@ -19,6 +20,7 @@ export default function SwitcherItem({
|
||||
noPadding,
|
||||
prefetch = SHOULD_PREFETCH_ALL_LINKS,
|
||||
tooltip,
|
||||
width = 'normal',
|
||||
}: {
|
||||
icon: ReactNode
|
||||
title?: string
|
||||
@ -31,10 +33,12 @@ export default function SwitcherItem({
|
||||
noPadding?: boolean
|
||||
prefetch?: boolean
|
||||
tooltip?: ComponentProps<typeof Tooltip>
|
||||
width?: 'narrow' | 'normal'
|
||||
}) {
|
||||
const widthClass = width === 'narrow' ? WIDTH_CLASS_NARROW : WIDTH_CLASS;
|
||||
const className = clsx(
|
||||
'flex items-center justify-center',
|
||||
`${WIDTH_CLASS} h-[28px]`,
|
||||
`${widthClass} h-[28px]`,
|
||||
isInteractive && 'cursor-pointer',
|
||||
isInteractive && 'hover:bg-gray-100/60 active:bg-gray-100',
|
||||
isInteractive && 'dark:hover:bg-gray-900/75 dark:active:bg-gray-900',
|
||||
@ -75,7 +79,7 @@ export default function SwitcherItem({
|
||||
tooltip
|
||||
? <Tooltip
|
||||
{...tooltip}
|
||||
classNameTrigger={WIDTH_CLASS}
|
||||
classNameTrigger={widthClass}
|
||||
delayDuration={500}
|
||||
>
|
||||
{content}
|
||||
36
src/components/switcher/SwitcherItemMenu.tsx
Normal file
36
src/components/switcher/SwitcherItemMenu.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import MoreMenu from '@/components/more/MoreMenu';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
export default function SwitcherItemMenu({
|
||||
className,
|
||||
classNameButton,
|
||||
classNameButtonOpen,
|
||||
...props
|
||||
}: ComponentProps<typeof MoreMenu>) {
|
||||
return (
|
||||
<MoreMenu
|
||||
{...props}
|
||||
className={clsx(
|
||||
'outline-medium',
|
||||
className,
|
||||
)}
|
||||
classNameButton={clsx(
|
||||
'p-0!',
|
||||
'w-full h-full',
|
||||
'flex items-center justify-center',
|
||||
'hover:bg-transparent dark:hover:bg-transparent',
|
||||
'active:bg-transparent dark:active:bg-transparent',
|
||||
'rounded-none focus:outline-none',
|
||||
'text-gray-400 dark:text-gray-600 hover:text-main',
|
||||
classNameButton,
|
||||
)}
|
||||
classNameButtonOpen={clsx(
|
||||
'bg-dim text-main!',
|
||||
classNameButtonOpen,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -4,7 +4,7 @@ import {
|
||||
INFINITE_SCROLL_FULL_INITIAL,
|
||||
INFINITE_SCROLL_GRID_INITIAL,
|
||||
} from '../photo';
|
||||
import { SortBy } from '../photo/db/sort';
|
||||
import { SortBy } from '../photo/sort';
|
||||
import { FEED_PHOTO_REQUEST_LIMIT } from './programmatic';
|
||||
|
||||
const FEED_BASE_QUERY_OPTIONS: PhotoQueryOptions = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { absolutePathForPhoto } from '@/app/paths';
|
||||
import { absolutePathForPhoto } from '@/app/path';
|
||||
import {
|
||||
FEED_PHOTO_WIDTH_LARGE,
|
||||
FEED_PHOTO_WIDTH_MEDIUM,
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
generateFeedMedia,
|
||||
getCoreFeedFields,
|
||||
} from './programmatic';
|
||||
import { ABSOLUTE_PATH_RSS_XML, absolutePathForPhoto } from '@/app/paths';
|
||||
import { ABSOLUTE_PATH_RSS_XML, absolutePathForPhoto } from '@/app/path';
|
||||
import { formatDate } from '@/utility/date';
|
||||
import { formatStringForXml } from '@/utility/string';
|
||||
import { BASE_URL, META_DESCRIPTION, META_TITLE } from '@/app/config';
|
||||
|
||||
@ -4,7 +4,7 @@ import { Photo, PhotoDateRange } from '@/photo';
|
||||
import {
|
||||
pathForFilm,
|
||||
pathForFilmImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
|
||||
import { descriptionForFilmPhotos, titleForFilm } from '.';
|
||||
import { useAppText } from '@/i18n/state/client';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { absolutePathForFilm } from '@/app/paths';
|
||||
import { absolutePathForFilm } from '@/app/path';
|
||||
import { PhotoSetAttributes } from '../category';
|
||||
import ShareModal from '@/share/ShareModal';
|
||||
import FilmOGTile from './FilmOGTile';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import PhotoFilmIcon from './PhotoFilmIcon';
|
||||
import { pathForFilm } from '@/app/paths';
|
||||
import { pathForFilm } from '@/app/path';
|
||||
import EntityLink, {
|
||||
EntityLinkExternalProps,
|
||||
} from '@/components/entity/EntityLink';
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
import {
|
||||
absolutePathForFilm,
|
||||
absolutePathForFilmImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import {
|
||||
FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS,
|
||||
labelForFujifilmSimulation,
|
||||
|
||||
@ -4,7 +4,7 @@ import { Photo, PhotoDateRange } from '@/photo';
|
||||
import {
|
||||
pathForFocalLength,
|
||||
pathForFocalLengthImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
|
||||
import { descriptionForFocalLengthPhotos, titleForFocalLength } from '.';
|
||||
import { useAppText } from '@/i18n/state/client';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { absolutePathForFocalLength } from '@/app/paths';
|
||||
import { absolutePathForFocalLength } from '@/app/path';
|
||||
import { PhotoSetAttributes } from '../category';
|
||||
import ShareModal from '@/share/ShareModal';
|
||||
import FocalLengthOGTile from './FocalLengthOGTile';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { pathForFocalLength } from '@/app/paths';
|
||||
import { pathForFocalLength } from '@/app/path';
|
||||
import EntityLink, {
|
||||
EntityLinkExternalProps,
|
||||
} from '@/components/entity/EntityLink';
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
import {
|
||||
absolutePathForFocalLength,
|
||||
absolutePathForFocalLengthImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { AppTextState } from '@/i18n/state';
|
||||
import { CategoryQueryMeta } from '@/category';
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Photo, PhotoDateRange } from '@/photo';
|
||||
import { pathForLens, pathForLensImage } from '@/app/paths';
|
||||
import { pathForLens, pathForLensImage } from '@/app/path';
|
||||
import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
|
||||
import { Lens } from '.';
|
||||
import { titleForLens, descriptionForLensPhotos } from './meta';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { absolutePathForLens } from '@/app/paths';
|
||||
import { absolutePathForLens } from '@/app/path';
|
||||
import { PhotoSetAttributes } from '../category';
|
||||
import ShareModal from '@/share/ShareModal';
|
||||
import { formatLensText, Lens } from '.';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { pathForLens } from '@/app/paths';
|
||||
import { pathForLens } from '@/app/path';
|
||||
import { Lens, formatLensText } from '.';
|
||||
import EntityLink, {
|
||||
EntityLinkExternalProps,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Photo } from '@/photo';
|
||||
import { parameterize } from '@/utility/string';
|
||||
import { formatAppleLensText, isLensApple } from '../platforms/apple';
|
||||
import { MISSING_FIELD } from '@/app/paths';
|
||||
import { MISSING_FIELD } from '@/app/path';
|
||||
import { formatGoogleLensText, isLensGoogle } from '../platforms/google';
|
||||
import { CategoryQueryMeta } from '@/category';
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import { Lens, lensFromPhoto, formatLensText } from '.';
|
||||
import {
|
||||
absolutePathForLens,
|
||||
absolutePathForLensImage,
|
||||
} from '@/app/paths';
|
||||
} from '@/app/path';
|
||||
import { AppTextState } from '@/i18n/state';
|
||||
|
||||
// Meta functions moved to separate file to avoid
|
||||
|
||||
@ -17,7 +17,7 @@ import { clsx } from 'clsx/lite';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import useVisible from '@/utility/useVisible';
|
||||
import { ADMIN_DB_OPTIMIZE_ENABLED } from '@/app/config';
|
||||
import { SortBy } from './db/sort';
|
||||
import { SortBy } from './sort';
|
||||
import { SWR_KEYS } from '@/swr';
|
||||
|
||||
const SIZE_KEY_SEPARATOR = '__';
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import AdminChildPage from '@/components/AdminChildPage';
|
||||
import { Photo } from '.';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/path';
|
||||
import { PhotoFormData, convertPhotoToFormData } from './form';
|
||||
import PhotoForm from './form/PhotoForm';
|
||||
import { Tags } from '@/tag';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { getEscapePath } from '@/app/paths';
|
||||
import { getEscapePath } from '@/app/path';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import { useCallback } from 'react';
|
||||
import useEscapeHandler from '../utility/useEscapeHandler';
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
} from '.';
|
||||
import PhotosLarge from './PhotosLarge';
|
||||
import PhotosLargeInfinite from './PhotosLargeInfinite';
|
||||
import { SortBy } from './db/sort';
|
||||
import { SortBy } from './sort';
|
||||
|
||||
export default function PhotoFullPage({
|
||||
photos,
|
||||
|
||||
@ -7,7 +7,7 @@ import { clsx } from 'clsx/lite';
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
import { ComponentProps, useCallback, useState, ReactNode } from 'react';
|
||||
import { GRID_SPACE_CLASSNAME } from '@/components';
|
||||
import { SortBy } from './db/sort';
|
||||
import { SortBy } from './sort';
|
||||
|
||||
export default function PhotoGridContainer({
|
||||
cacheKey,
|
||||
|
||||
@ -4,7 +4,7 @@ import { INFINITE_SCROLL_GRID_MULTIPLE } from '.';
|
||||
import InfinitePhotoScroll from './InfinitePhotoScroll';
|
||||
import PhotoGrid from './PhotoGrid';
|
||||
import { ComponentProps } from 'react';
|
||||
import { SortBy } from './db/sort';
|
||||
import { SortBy } from './sort';
|
||||
|
||||
export default function PhotoGridInfinite({
|
||||
cacheKey,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Photo } from '.';
|
||||
import { PATH_GRID_INFERRED } from '@/app/paths';
|
||||
import { PATH_GRID_INFERRED } from '@/app/path';
|
||||
import PhotoGridSidebar from './PhotoGridSidebar';
|
||||
import PhotoGridContainer from './PhotoGridContainer';
|
||||
import { ComponentProps, useEffect, useRef } from 'react';
|
||||
@ -10,7 +10,7 @@ import clsx from 'clsx/lite';
|
||||
import useElementHeight from '@/utility/useElementHeight';
|
||||
import MaskedScroll from '@/components/MaskedScroll';
|
||||
import { IS_RECENTS_FIRST } from '@/app/config';
|
||||
import { SortBy } from './db/sort';
|
||||
import { SortBy } from './sort';
|
||||
|
||||
export default function PhotoGridPageClient({
|
||||
photos,
|
||||
|
||||
@ -15,7 +15,7 @@ import AppGrid from '@/components/AppGrid';
|
||||
import ImageLarge from '@/components/image/ImageLarge';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { pathForFocalLength, pathForPhoto } from '@/app/paths';
|
||||
import { pathForFocalLength, pathForPhoto } from '@/app/path';
|
||||
import PhotoTags from '@/tag/PhotoTags';
|
||||
import ShareButton from '@/share/ShareButton';
|
||||
import DownloadButton from '@/components/DownloadButton';
|
||||
|
||||
@ -5,7 +5,7 @@ import { Photo, titleForPhoto } from '@/photo';
|
||||
import { PhotoSetCategory } from '@/category';
|
||||
import { AnimationConfig } from '../components/AnimateItems';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { pathForPhoto } from '@/app/paths';
|
||||
import { pathForPhoto } from '@/app/path';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import LinkWithStatus from '@/components/LinkWithStatus';
|
||||
import Spinner from '@/components/Spinner';
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
import { PhotoSetCategory } from '../category';
|
||||
import ImageMedium from '@/components/image/ImageMedium';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { pathForPhoto } from '@/app/paths';
|
||||
import { pathForPhoto } from '@/app/path';
|
||||
import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config';
|
||||
import { useRef } from 'react';
|
||||
import useVisible from '@/utility/useVisible';
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
titleForPhoto,
|
||||
} from '@/photo';
|
||||
import { PhotoSetCategory } from '../category';
|
||||
import { pathForPhoto, pathForPhotoImage } from '@/app/paths';
|
||||
import { pathForPhoto, pathForPhotoImage } from '@/app/path';
|
||||
import OGTile, { OGTilePropsCore } from '@/components/og/OGTile';
|
||||
|
||||
export default function PhotoOGTile({
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '@/photo';
|
||||
import { PhotoSetCategory } from '../category';
|
||||
import PhotoLink from './PhotoLink';
|
||||
import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths';
|
||||
import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/path';
|
||||
import { useAppState } from '@/app/AppState';
|
||||
import { AnimationConfig } from '@/components/AnimateItems';
|
||||
import { clsx } from 'clsx/lite';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user