commander sorting
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Button, Divider, Tooltip } from '@heroui/react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { FolderPlusIcon } from 'lucide-react'
|
||||
import { ChevronDownIcon, ChevronUpIcon, FolderPlusIcon } from 'lucide-react'
|
||||
import {
|
||||
forwardRef,
|
||||
startTransition,
|
||||
@@ -352,12 +352,54 @@ const FilePanel = forwardRef<
|
||||
|
||||
<div className="relative flex flex-col w-full h-full overflow-hidden">
|
||||
<div
|
||||
className={`sticky top-0 z-10 grid ${showPreviewColumn ? 'grid-cols-[2.5rem_1fr_6rem_9rem_11rem]' : 'grid-cols-[2.5rem_1fr_6rem_9rem_2.5rem]'} items-center py-2 bg-default-100`}
|
||||
className={`sticky top-0 z-10 grid ${showPreviewColumn ? 'grid-cols-[2.5rem_1fr_6rem_9rem_11rem]' : 'grid-cols-[2.5rem_1fr_6rem_9rem_2.5rem]'} items-stretch bg-default-100`}
|
||||
>
|
||||
<div />
|
||||
<div className="pl-2 font-semibold text-small">Name</div>
|
||||
<div className="font-semibold text-small">Size</div>
|
||||
<div className="font-semibold text-small">Last Modified</div>
|
||||
{(
|
||||
[
|
||||
{ column: 'name', label: 'Name', className: 'pl-2' },
|
||||
{ column: 'size', label: 'Size', className: '' },
|
||||
{
|
||||
column: 'modTime',
|
||||
label: 'Last Modified',
|
||||
className: '',
|
||||
},
|
||||
] as const
|
||||
).map(({ column, label, className }) => {
|
||||
const isSorted = nav.sortDescriptor.column === column
|
||||
const nextDirection =
|
||||
isSorted && nav.sortDescriptor.direction === 'ascending'
|
||||
? 'descending'
|
||||
: 'ascending'
|
||||
const sortStatus = isSorted
|
||||
? `, sorted ${nav.sortDescriptor.direction}`
|
||||
: ''
|
||||
return (
|
||||
<div key={column}>
|
||||
<button
|
||||
type="button"
|
||||
className={`flex items-center w-full h-full gap-1 py-2 font-semibold text-left rounded-small text-small hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary ${className}`}
|
||||
aria-pressed={isSorted}
|
||||
aria-label={`${label}${sortStatus}. Activate to sort ${nextDirection}.`}
|
||||
onClick={() => nav.handleSort(column)}
|
||||
>
|
||||
<span>{label}</span>
|
||||
{isSorted &&
|
||||
(nav.sortDescriptor.direction === 'ascending' ? (
|
||||
<ChevronUpIcon
|
||||
className="size-3.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<ChevronDownIcon
|
||||
className="size-3.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
))}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ import {
|
||||
serializeRemotePath,
|
||||
} from './utils'
|
||||
|
||||
const nameCollator = new Intl.Collator(undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'base',
|
||||
})
|
||||
|
||||
export default function useFileNavigation({
|
||||
initialRemote,
|
||||
initialPath,
|
||||
@@ -56,6 +61,10 @@ export default function useFileNavigation({
|
||||
const [cwd, setCwd] = useState<string>(initialPath ?? '')
|
||||
const [pathInput, setPathInput] = useState<string>('')
|
||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||
const [sortDescriptor, setSortDescriptor] = useState<{
|
||||
column: 'name' | 'size' | 'modTime'
|
||||
direction: 'ascending' | 'descending'
|
||||
}>({ column: 'name', direction: 'ascending' })
|
||||
const [items, setItems] = useState<Entry[]>([])
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -88,10 +97,65 @@ export default function useFileNavigation({
|
||||
|
||||
const visibleItems = useMemo(() => {
|
||||
const base = allowFiles ? items : items.filter((it) => it.isDir)
|
||||
if (!searchTerm) return base
|
||||
const lower = searchTerm.toLowerCase()
|
||||
return base.filter((item) => item.name.toLowerCase().includes(lower))
|
||||
}, [allowFiles, items, searchTerm])
|
||||
const filtered = searchTerm
|
||||
? base.filter((item) => item.name.toLowerCase().includes(lower))
|
||||
: base
|
||||
const direction = sortDescriptor.direction === 'ascending' ? 1 : -1
|
||||
|
||||
return [...filtered].sort((a, b) => {
|
||||
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1
|
||||
|
||||
const nameComparison =
|
||||
nameCollator.compare(a.name, b.name) ||
|
||||
a.name.localeCompare(b.name) ||
|
||||
a.key.localeCompare(b.key)
|
||||
|
||||
if (sortDescriptor.column === 'name') return nameComparison * direction
|
||||
|
||||
const aValue =
|
||||
sortDescriptor.column === 'size'
|
||||
? typeof a.size === 'number' && a.size >= 0
|
||||
? a.size
|
||||
: undefined
|
||||
: a.modTime
|
||||
? Date.parse(a.modTime)
|
||||
: undefined
|
||||
const bValue =
|
||||
sortDescriptor.column === 'size'
|
||||
? typeof b.size === 'number' && b.size >= 0
|
||||
? b.size
|
||||
: undefined
|
||||
: b.modTime
|
||||
? Date.parse(b.modTime)
|
||||
: undefined
|
||||
const normalizedA =
|
||||
aValue !== undefined && Number.isFinite(aValue) ? aValue : undefined
|
||||
const normalizedB =
|
||||
bValue !== undefined && Number.isFinite(bValue) ? bValue : undefined
|
||||
|
||||
if (normalizedA === undefined && normalizedB !== undefined) return 1
|
||||
if (normalizedA !== undefined && normalizedB === undefined) return -1
|
||||
if (
|
||||
normalizedA !== undefined &&
|
||||
normalizedB !== undefined &&
|
||||
normalizedA !== normalizedB
|
||||
) {
|
||||
return (normalizedA - normalizedB) * direction
|
||||
}
|
||||
return nameComparison
|
||||
})
|
||||
}, [allowFiles, items, searchTerm, sortDescriptor])
|
||||
|
||||
const handleSort = useCallback((column: 'name' | 'size' | 'modTime') => {
|
||||
setSortDescriptor((current) => ({
|
||||
column,
|
||||
direction:
|
||||
current.column === column && current.direction === 'ascending'
|
||||
? 'descending'
|
||||
: 'ascending',
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const virtualizedItems: (VirtualizedEntry | PaddingItem)[] = useMemo(() => {
|
||||
const base: (VirtualizedEntry | PaddingItem)[] = visibleItems.map((item) => ({
|
||||
@@ -783,6 +847,7 @@ export default function useFileNavigation({
|
||||
error,
|
||||
isUpDisabled,
|
||||
searchTerm,
|
||||
sortDescriptor,
|
||||
selectedPaths,
|
||||
selectedCount,
|
||||
isRemote,
|
||||
@@ -795,6 +860,7 @@ export default function useFileNavigation({
|
||||
// Actions
|
||||
setPathInput,
|
||||
setSearchTerm,
|
||||
handleSort,
|
||||
handleNavigate,
|
||||
navigateUp,
|
||||
navigateTo,
|
||||
|
||||
Reference in New Issue
Block a user