diff --git a/src/app/AppViewSwitcher.tsx b/src/app/AppViewSwitcher.tsx
index 1efc52c8..a919e3b7 100644
--- a/src/app/AppViewSwitcher.tsx
+++ b/src/app/AppViewSwitcher.tsx
@@ -3,7 +3,6 @@ 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/path';
@@ -23,7 +22,7 @@ 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/sort/path';
+import { getSortStateFromPath } from '@/photo/sort/path';
import { motion } from 'framer-motion';
import SortMenu from '@/photo/sort/SortMenu';
import { SWR_KEYS } from '@/swr';
@@ -53,10 +52,11 @@ export default function AppViewSwitcher({
invalidateSwr,
} = useAppState();
- const sortConfig = useMemo(() => getSortConfigFromPath(pathname), [pathname]);
+ const sortConfig = useMemo(() => getSortStateFromPath(pathname), [pathname]);
const {
sortBy,
+ doesPathOfferSort,
isSortedByDefault,
isAscending,
pathGrid,
@@ -66,7 +66,7 @@ export default function AppViewSwitcher({
const showSortControl =
NAV_SORT_CONTROL !== 'none' &&
- doesPathOfferSort(pathname);
+ doesPathOfferSort;
const hasLoadedRef = useRef(false);
useEffect(() => {
@@ -194,7 +194,7 @@ export default function AppViewSwitcher({
/>}
tooltip={{
...!isSortMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
- content: 'Sort',
+ content: appText.sort.sort,
},
}}
width="narrow"
@@ -210,11 +210,11 @@ export default function AppViewSwitcher({
sort={isAscending ? 'asc' : 'desc'}
className="translate-x-[0.5px] translate-y-[1px]"
/>}
- tooltip={{
+ tooltip={{...SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && {
content: isAscending
- ? appText.sort.newest
- : appText.sort.oldest,
- }}
+ ? appText.sort.viewNewest
+ : appText.sort.viewOldest,
+ }}}
width="narrow"
noPadding
/>}
diff --git a/src/cmdk/CommandKClient.tsx b/src/cmdk/CommandKClient.tsx
index 4a3a6ea7..7d39f86b 100644
--- a/src/cmdk/CommandKClient.tsx
+++ b/src/cmdk/CommandKClient.tsx
@@ -40,7 +40,7 @@ import Spinner from '../components/Spinner';
import { usePathname, useRouter } from 'next/navigation';
import { useTheme } from 'next-themes';
import { BiDesktop, BiLockAlt, BiMoon, BiSun } from 'react-icons/bi';
-import { IoInvertModeSharp } from 'react-icons/io5';
+import { IoClose, IoInvertModeSharp } from 'react-icons/io5';
import { useAppState } from '@/app/AppState';
import { searchPhotosAction } from '@/photo/actions';
import { RiToolsFill } from 'react-icons/ri';
@@ -86,10 +86,12 @@ import IconFavs from '@/components/icons/IconFavs';
import { useAppText } from '@/i18n/state/client';
import LoaderButton from '@/components/primitives/LoaderButton';
import IconRecents from '@/components/icons/IconRecents';
-import { CgFileDocument } from 'react-icons/cg';
+import { CgClose, CgFileDocument } from 'react-icons/cg';
import { FaRegUserCircle } from 'react-icons/fa';
import { formatDistanceToNow } from 'date-fns';
import IconCheck from '@/components/icons/IconCheck';
+import { getSortStateFromPath } from '@/photo/sort/path';
+import IconSort from '@/components/icons/IconSort';
const DIALOG_TITLE = 'Global Command-K Menu';
const DIALOG_DESCRIPTION = 'For searching photos, views, and settings';
@@ -116,6 +118,11 @@ type CommandKSection = {
items: CommandKItem[]
}
+const renderCheck = (isChecked?: boolean) =>
+ isChecked
+ ?
+ : undefined;
+
const renderToggle = (
label: string,
onToggle?: Dispatch>,
@@ -123,7 +130,7 @@ const renderToggle = (
): CommandKItem => ({
label: `Toggle ${label}`,
action: () => onToggle?.(prev => !prev),
- annotation: isEnabled ? : undefined,
+ annotation: renderCheck(isEnabled),
});
export default function CommandKClient({
@@ -172,6 +179,19 @@ export default function CommandKClient({
setShouldDebugRecipeOverlays,
} = useAppState();
+ const {
+ doesPathOfferSort,
+ isSortedByDefault,
+ pathNewest,
+ pathOldest,
+ pathTakenAt,
+ pathUploadedAt,
+ pathClearSort,
+ isAscending,
+ isTakenAt,
+ isUploadedAt,
+ } = useMemo(() => getSortStateFromPath(pathname), [pathname]);
+
const appText = useAppText();
const isOpenRef = useRef(isOpen);
@@ -209,7 +229,7 @@ export default function CommandKClient({
}, [isWaiting, setIsOpen]);
// Raw query values
- const [queryLiveRaw, setQueryLive] = useState('');
+ const [queryLiveRaw, setQueryLiveRaw] = useState('');
const [queryDebouncedRaw] =
useDebounce(queryLiveRaw, 500, { trailing: true });
@@ -289,7 +309,7 @@ export default function CommandKClient({
useEffect(() => {
if (!isOpen) {
- setQueryLive('');
+ setQueryLiveRaw('');
setQueriedSections([]);
setIsLoading(false);
}
@@ -305,6 +325,7 @@ export default function CommandKClient({
return count ? { count, subhead } : undefined;
}, [recent, appText]);
+ // Years only accessible by search
const years = useMemo(() =>
_years.filter(({ year }) => queryLive && year.includes(queryLive))
, [_years, queryLive]);
@@ -503,6 +524,40 @@ export default function CommandKClient({
});
}
+ const sortItems = [{
+ label: appText.sort.newestFirst,
+ path: pathNewest,
+ annotation: renderCheck(!isAscending),
+ }, {
+ label: appText.sort.oldestFirst,
+ path: pathOldest,
+ annotation: renderCheck(isAscending),
+ }, {
+ label: appText.sort.byTakenAt,
+ path: pathTakenAt,
+ annotation: renderCheck(isTakenAt),
+ }, {
+ label: appText.sort.byUploadedAt,
+ path: pathUploadedAt,
+ annotation: renderCheck(isUploadedAt),
+ }];
+
+ if (!isSortedByDefault) {
+ sortItems.push({
+ label: appText.sort.clearSort,
+ path: pathClearSort,
+ annotation: ,
+ });
+ }
+
+ const sortSection: CommandKSection = {
+ heading: appText.sort.sort,
+ accessory: ,
+ items: doesPathOfferSort
+ ? sortItems
+ : [],
+ };
+
const pageFull: CommandKItem = {
label: GRID_HOMEPAGE_ENABLED
? appText.nav.full
@@ -522,13 +577,13 @@ export default function CommandKClient({
: [pageFull, pageGrid];
const sectionPages: CommandKSection = {
- heading: 'Pages',
+ heading: appText.cmdk.pages,
accessory: ,
items: pageItems,
};
const adminSection: CommandKSection = {
- heading: 'Admin',
+ heading: appText.nav.admin,
accessory:
{
- setQueryLive(e.currentTarget.value);
+ value={queryLiveRaw}
+ onValueChange={value => {
+ setQueryLiveRaw(value);
updateMask();
}}
className={clsx(
@@ -667,20 +723,30 @@ export default function CommandKClient({
disabled={isPending}
/>
{isLoading && !isPending
- ?
-
+ ?
+
:
setIsOpen?.(false)}
+ onClick={() => {
+ if (queryLiveRaw) {
+ setQueryLiveRaw('');
+ updateMask();
+ } else {
+ setIsOpen?.(false);
+ }
+ }}
>
- ESC
+ {queryLiveRaw
+ ?
+ : 'ESC'}
}
@@ -698,6 +764,7 @@ export default function CommandKClient({
{queriedSections
.concat(categorySections)
+ .concat(sortSection)
.concat(sectionPages)
.concat(adminSection)
.concat(clientSections)
diff --git a/src/i18n/locales/bd-bn.ts b/src/i18n/locales/bd-bn.ts
index 985336b0..ee609356 100644
--- a/src/i18n/locales/bd-bn.ts
+++ b/src/i18n/locales/bd-bn.ts
@@ -55,13 +55,25 @@ export const TEXT: I18N = {
nextShort: 'পরবর্তী',
},
sort: {
- newest: 'নতুনতম দেখুন',
- oldest: 'পুরাতনতম দেখুন',
+ sort: 'সাজান',
+ newest: 'নতুনতম',
+ oldest: 'পুরাতনতম',
+ newestFirst: 'নতুনতম প্রথমে',
+ oldestFirst: 'পুরাতনতম প্রথমে',
+ viewNewest: 'নতুনতম দেখুন',
+ viewOldest: 'পুরাতনতম দেখুন',
+ takenAt: 'তোলা হয়েছে',
+ byTakenAt: 'তোলার সময় অনুযায়ী',
+ uploadedAt: 'আপলোড হয়েছে',
+ byUploadedAt: 'আপলোডের সময় অনুযায়ী',
+ uploadedAtShort: 'আপলোড',
+ clearSort: 'সাজানো মুছুন',
},
cmdk: {
placeholder: 'ছবি, ভিউ, সেটিংস অনুসন্ধান করুন ...',
searching: 'অনুসন্ধান হচ্ছে ...',
noResults: 'কোনো ফলাফল পাওয়া যায়নি',
+ pages: 'পৃষ্ঠাসমূহ',
},
tooltip: {
'35mm': '৩৫মিমি সমতুল্য',
diff --git a/src/i18n/locales/en-us.ts b/src/i18n/locales/en-us.ts
index e65a26f0..406a7519 100644
--- a/src/i18n/locales/en-us.ts
+++ b/src/i18n/locales/en-us.ts
@@ -53,13 +53,25 @@ export const TEXT = {
nextShort: 'Next',
},
sort: {
- newest: 'View newest',
- oldest: 'View oldest',
+ sort: 'Sort',
+ newest: 'Newest',
+ oldest: 'Oldest',
+ newestFirst: 'Newest first',
+ oldestFirst: 'Oldest first',
+ viewNewest: 'View newest',
+ viewOldest: 'View oldest',
+ takenAt: 'Taken at',
+ byTakenAt: 'By taken at',
+ uploadedAt: 'Uploaded at',
+ byUploadedAt: 'By uploaded at',
+ uploadedAtShort: 'Uploaded',
+ clearSort: 'Clear sort',
},
cmdk: {
placeholder: 'Search photos, views, settings ...',
searching: 'Searching ...',
noResults: 'No results found',
+ pages: 'Pages',
},
tooltip: {
'35mm': '35mm Equivalent',
diff --git a/src/i18n/locales/id-id.ts b/src/i18n/locales/id-id.ts
index 9413ba71..59cc150c 100644
--- a/src/i18n/locales/id-id.ts
+++ b/src/i18n/locales/id-id.ts
@@ -54,13 +54,25 @@ export const TEXT: I18N = {
nextShort: 'Brkt',
},
sort: {
- newest: 'Lihat terbaru',
- oldest: 'Lihat terlama',
+ sort: 'Urutkan',
+ newest: 'Terbaru',
+ oldest: 'Terlama',
+ newestFirst: 'Terbaru dulu',
+ oldestFirst: 'Terlama dulu',
+ viewNewest: 'Lihat terbaru',
+ viewOldest: 'Lihat terlama',
+ takenAt: 'Diambil pada',
+ byTakenAt: 'Berdasarkan waktu pengambilan',
+ uploadedAt: 'Diunggah pada',
+ byUploadedAt: 'Berdasarkan waktu unggahan',
+ uploadedAtShort: 'Diunggah',
+ clearSort: 'Hapus pengurutan',
},
cmdk: {
placeholder: 'Cari foto, tampilan, pengaturan ...',
searching: 'Mencari ...',
noResults: 'Tidak ada hasil ditemukan',
+ pages: 'Halaman',
},
tooltip: {
'35mm': 'Setara 35mm',
diff --git a/src/i18n/locales/pt-br.ts b/src/i18n/locales/pt-br.ts
index 6864a370..9b376984 100644
--- a/src/i18n/locales/pt-br.ts
+++ b/src/i18n/locales/pt-br.ts
@@ -54,13 +54,25 @@ export const TEXT: I18N = {
nextShort: 'Próx',
},
sort: {
- newest: 'Ver mais recentes',
- oldest: 'Ver mais antigas',
+ sort: 'Ordenar',
+ newest: 'Mais recentes',
+ oldest: 'Mais antigas',
+ newestFirst: 'Mais recentes primeiro',
+ oldestFirst: 'Mais antigas primeiro',
+ viewNewest: 'Ver mais recentes',
+ viewOldest: 'Ver mais antigas',
+ takenAt: 'Tirado em',
+ byTakenAt: 'Por data de captura',
+ uploadedAt: 'Enviado em',
+ byUploadedAt: 'Por data de envio',
+ uploadedAtShort: 'Enviado',
+ clearSort: 'Limpar ordenação',
},
cmdk: {
placeholder: 'Pesquisar fotos, visualizações, configurações ...',
searching: 'Pesquisando ...',
noResults: 'Nenhum resultado encontrado',
+ pages: 'Páginas',
},
tooltip: {
'35mm': 'Equivalente em 35mm',
diff --git a/src/i18n/locales/pt-pt.ts b/src/i18n/locales/pt-pt.ts
index e45cc899..11f41421 100644
--- a/src/i18n/locales/pt-pt.ts
+++ b/src/i18n/locales/pt-pt.ts
@@ -54,13 +54,25 @@ export const TEXT: I18N = {
nextShort: 'Próx',
},
sort: {
- newest: 'Ver mais recentes',
- oldest: 'Ver mais antigas',
+ sort: 'Ordenar',
+ newest: 'Mais recentes',
+ oldest: 'Mais antigas',
+ newestFirst: 'Mais recentes primeiro',
+ oldestFirst: 'Mais antigas primeiro',
+ viewNewest: 'Ver mais recentes',
+ viewOldest: 'Ver mais antigas',
+ takenAt: 'Tirado em',
+ byTakenAt: 'Por data de captura',
+ uploadedAt: 'Enviado em',
+ byUploadedAt: 'Por data de envio',
+ uploadedAtShort: 'Enviado',
+ clearSort: 'Limpar ordenação',
},
cmdk: {
placeholder: 'Pesquisar fotografias, visualizações, configurações ...',
searching: 'A pesquisar ...',
noResults: 'Nenhum resultado encontrado',
+ pages: 'Páginas',
},
tooltip: {
'35mm': 'Equivalente em 35mm',
diff --git a/src/i18n/locales/zh-cn.ts b/src/i18n/locales/zh-cn.ts
index 48939a89..ba988f9d 100644
--- a/src/i18n/locales/zh-cn.ts
+++ b/src/i18n/locales/zh-cn.ts
@@ -54,13 +54,25 @@ export const TEXT: I18N = {
nextShort: '下一页',
},
sort: {
- newest: '查看最新',
- oldest: '查看最旧',
+ sort: '排序',
+ newest: '最新',
+ oldest: '最旧',
+ newestFirst: '最新优先',
+ oldestFirst: '最旧优先',
+ viewNewest: '查看最新',
+ viewOldest: '查看最旧',
+ takenAt: '拍摄时间',
+ byTakenAt: '按拍摄时间',
+ uploadedAt: '上传时间',
+ byUploadedAt: '按上传时间',
+ uploadedAtShort: '上传',
+ clearSort: '清除排序',
},
cmdk: {
placeholder: '搜索照片、视图、设置...',
searching: '搜索中...',
noResults: '未找到结果',
+ pages: '页面',
},
tooltip: {
'35mm': '35mm 等效焦距',
diff --git a/src/photo/sort/SortMenu.tsx b/src/photo/sort/SortMenu.tsx
index ecb64853..a9706b95 100644
--- a/src/photo/sort/SortMenu.tsx
+++ b/src/photo/sort/SortMenu.tsx
@@ -1,8 +1,9 @@
import IconSort from '@/components/icons/IconSort';
import SwitcherItemMenu from '@/components/switcher/SwitcherItemMenu';
-import { getSortConfigFromPath } from './path';
+import { getSortStateFromPath } from './path';
import IconCheck from '@/components/icons/IconCheck';
import { clsx } from 'clsx/lite';
+import { useAppText } from '@/i18n/state/client';
export default function SortMenu({
isOpen,
@@ -17,7 +18,9 @@ export default function SortMenu({
}: {
isOpen?: boolean
setIsOpen?: (isOpen: boolean) => void
-} & ReturnType) {
+} & ReturnType) {
+ const appText = useAppText();
+
const renderIcon = (isChecked: boolean) => isChecked
?
: ;
@@ -38,21 +41,21 @@ export default function SortMenu({
/>}
sections={[{
items: [{
- ...renderLabel('Newest', !isAscending),
+ ...renderLabel(appText.sort.newest, !isAscending),
icon: renderIcon(!isAscending),
href: pathNewest,
}, {
- ...renderLabel('Oldest', isAscending),
+ ...renderLabel(appText.sort.oldest, isAscending),
icon: renderIcon(isAscending),
href: pathOldest,
}],
}, {
items: [{
- ...renderLabel('Taken at', isTakenAt),
+ ...renderLabel(appText.sort.takenAt, isTakenAt),
icon: renderIcon(isTakenAt),
href: pathTakenAt,
}, {
- ...renderLabel('Uploaded', isUploadedAt),
+ ...renderLabel(appText.sort.uploadedAtShort, isUploadedAt),
icon: renderIcon(isUploadedAt),
href: pathUploadedAt,
}],
diff --git a/src/photo/sort/path.ts b/src/photo/sort/path.ts
index e1e8eefb..e286ffd4 100644
--- a/src/photo/sort/path.ts
+++ b/src/photo/sort/path.ts
@@ -2,6 +2,7 @@
// to avoid circular dependencies
import {
+ doesPathOfferSort as _doesPathOfferSort,
PARAM_SORT_ORDER_NEWEST,
PARAM_SORT_ORDER_OLDEST,
PARAM_SORT_TYPE_TAKEN_AT,
@@ -16,7 +17,7 @@ import {
USER_DEFAULT_SORT_WITH_PRIORITY,
} from '@/app/config';
-export const getSortByComponents = (sortBy: SortBy): {
+const getSortByComponents = (sortBy: SortBy): {
sortType: string
sortOrder: string
} => {
@@ -40,7 +41,7 @@ export const getSortByComponents = (sortBy: SortBy): {
}
};
-const {
+export const {
sortType: DEFAULT_SORT_TYPE,
sortOrder: DEFAULT_SORT_ORDER,
} = getSortByComponents(USER_DEFAULT_SORT_BY);
@@ -95,7 +96,9 @@ const getPathSortComponents = (pathname: string) => {
};
};
-export const getSortConfigFromPath = (pathname: string) => {
+export const getSortStateFromPath = (pathname: string) => {
+ const doesPathOfferSort = _doesPathOfferSort(pathname);
+
const {
gridOrFull: _gridOrFull,
sortType,
@@ -153,8 +156,14 @@ export const getSortConfigFromPath = (pathname: string) => {
const pathUploadedAt =
getPath({ sortType: PARAM_SORT_TYPE_UPLOADED_AT, sortOrder });
+ // Sort clear
+ const pathClearSort = _gridOrFull === 'grid'
+ ? PATH_GRID_INFERRED
+ : PATH_FULL_INFERRED;
+
return {
sortBy,
+ doesPathOfferSort,
isSortedByDefault,
isAscending,
isTakenAt,
@@ -165,6 +174,7 @@ export const getSortConfigFromPath = (pathname: string) => {
pathOldest,
pathTakenAt,
pathUploadedAt,
+ pathClearSort,
pathSortToggle,
};
};