commander sorting
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Button, Divider, Tooltip } from '@heroui/react'
|
import { Button, Divider, Tooltip } from '@heroui/react'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { FolderPlusIcon } from 'lucide-react'
|
import { ChevronDownIcon, ChevronUpIcon, FolderPlusIcon } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
startTransition,
|
startTransition,
|
||||||
@@ -352,12 +352,54 @@ const FilePanel = forwardRef<
|
|||||||
|
|
||||||
<div className="relative flex flex-col w-full h-full overflow-hidden">
|
<div className="relative flex flex-col w-full h-full overflow-hidden">
|
||||||
<div
|
<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 />
|
||||||
<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 />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ import {
|
|||||||
serializeRemotePath,
|
serializeRemotePath,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
|
const nameCollator = new Intl.Collator(undefined, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'base',
|
||||||
|
})
|
||||||
|
|
||||||
export default function useFileNavigation({
|
export default function useFileNavigation({
|
||||||
initialRemote,
|
initialRemote,
|
||||||
initialPath,
|
initialPath,
|
||||||
@@ -56,6 +61,10 @@ export default function useFileNavigation({
|
|||||||
const [cwd, setCwd] = useState<string>(initialPath ?? '')
|
const [cwd, setCwd] = useState<string>(initialPath ?? '')
|
||||||
const [pathInput, setPathInput] = useState<string>('')
|
const [pathInput, setPathInput] = useState<string>('')
|
||||||
const [searchTerm, setSearchTerm] = 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 [items, setItems] = useState<Entry[]>([])
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@@ -88,10 +97,65 @@ export default function useFileNavigation({
|
|||||||
|
|
||||||
const visibleItems = useMemo(() => {
|
const visibleItems = useMemo(() => {
|
||||||
const base = allowFiles ? items : items.filter((it) => it.isDir)
|
const base = allowFiles ? items : items.filter((it) => it.isDir)
|
||||||
if (!searchTerm) return base
|
|
||||||
const lower = searchTerm.toLowerCase()
|
const lower = searchTerm.toLowerCase()
|
||||||
return base.filter((item) => item.name.toLowerCase().includes(lower))
|
const filtered = searchTerm
|
||||||
}, [allowFiles, items, 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 virtualizedItems: (VirtualizedEntry | PaddingItem)[] = useMemo(() => {
|
||||||
const base: (VirtualizedEntry | PaddingItem)[] = visibleItems.map((item) => ({
|
const base: (VirtualizedEntry | PaddingItem)[] = visibleItems.map((item) => ({
|
||||||
@@ -783,6 +847,7 @@ export default function useFileNavigation({
|
|||||||
error,
|
error,
|
||||||
isUpDisabled,
|
isUpDisabled,
|
||||||
searchTerm,
|
searchTerm,
|
||||||
|
sortDescriptor,
|
||||||
selectedPaths,
|
selectedPaths,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
isRemote,
|
isRemote,
|
||||||
@@ -795,6 +860,7 @@ export default function useFileNavigation({
|
|||||||
// Actions
|
// Actions
|
||||||
setPathInput,
|
setPathInput,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
|
handleSort,
|
||||||
handleNavigate,
|
handleNavigate,
|
||||||
navigateUp,
|
navigateUp,
|
||||||
navigateTo,
|
navigateTo,
|
||||||
|
|||||||
Reference in New Issue
Block a user