virtualize remotes list
This commit is contained in:
Generated
+1
@@ -17,6 +17,7 @@
|
|||||||
"@tanstack/query-persist-client-core": "^5.101.4",
|
"@tanstack/query-persist-client-core": "^5.101.4",
|
||||||
"@tanstack/query-sync-storage-persister": "^5.101.4",
|
"@tanstack/query-sync-storage-persister": "^5.101.4",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
|
"@tanstack/react-virtual": "^3.11.3",
|
||||||
"@tauri-apps/api": "~2.11.0",
|
"@tauri-apps/api": "~2.11.0",
|
||||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"@tanstack/query-persist-client-core": "^5.101.4",
|
"@tanstack/query-persist-client-core": "^5.101.4",
|
||||||
"@tanstack/query-sync-storage-persister": "^5.101.4",
|
"@tanstack/query-sync-storage-persister": "^5.101.4",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
|
"@tanstack/react-virtual": "^3.11.3",
|
||||||
"@tauri-apps/api": "~2.11.0",
|
"@tauri-apps/api": "~2.11.0",
|
||||||
"@tauri-apps/plugin-autostart": "^2.5.1",
|
"@tauri-apps/plugin-autostart": "^2.5.1",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||||
import { ask, message } from '@tauri-apps/plugin-dialog'
|
import { ask, message } from '@tauri-apps/plugin-dialog'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +22,7 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { type ReactNode, startTransition, useEffect, useMemo, useState } from 'react'
|
import { type ReactNode, startTransition, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { onErrorDialog } from '../../../lib/errors'
|
import { onErrorDialog } from '../../../lib/errors'
|
||||||
import { formatBytes } from '../../../lib/format'
|
import { formatBytes } from '../../../lib/format'
|
||||||
@@ -64,6 +65,18 @@ export default function RemotesSection() {
|
|||||||
[sortedRemotes, searchQuery]
|
[sortedRemotes, searchQuery]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Virtualize the remotes list: each RemoteCard is a fixed-height (h-20 = 80px) card with a
|
||||||
|
// gap-2.5 (10px) between rows, so a row slot is 90px. Each card also fires its own queries,
|
||||||
|
// so windowing keeps a long list from mounting every card (and its request fan-out) at once.
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const rowVirtualizer = useVirtualizer({
|
||||||
|
count: filteredRemotes.length,
|
||||||
|
getScrollElement: () => scrollRef.current,
|
||||||
|
estimateSize: () => 90,
|
||||||
|
overscan: 6,
|
||||||
|
})
|
||||||
|
|
||||||
const [pickedRemote, setPickedRemote] = useState<string | null>(null)
|
const [pickedRemote, setPickedRemote] = useState<string | null>(null)
|
||||||
|
|
||||||
const [editingDrawerOpen, setEditingDrawerOpen] = useState(false)
|
const [editingDrawerOpen, setEditingDrawerOpen] = useState(false)
|
||||||
@@ -201,7 +214,7 @@ export default function RemotesSection() {
|
|||||||
{Placeholder}
|
{Placeholder}
|
||||||
|
|
||||||
{!Placeholder && (
|
{!Placeholder && (
|
||||||
<div className="flex flex-col gap-2.5 px-4 pb-10">
|
<div className="flex flex-col gap-2.5 px-4">
|
||||||
{sortedRemotes.length > 5 && (
|
{sortedRemotes.length > 5 && (
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search remotes..."
|
placeholder="Search remotes..."
|
||||||
@@ -216,36 +229,67 @@ export default function RemotesSection() {
|
|||||||
classNames={{ inputWrapper: 'bg-content2/60' }}
|
classNames={{ inputWrapper: 'bg-content2/60' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{filteredRemotes.map((remote) => (
|
<div
|
||||||
<RemoteCard
|
ref={scrollRef}
|
||||||
key={remote}
|
className="overflow-y-auto overscroll-none max-h-[calc(100dvh-14rem)] pb-10"
|
||||||
remote={remote}
|
>
|
||||||
onAutoMountPress={() => {
|
<div
|
||||||
startTransition(() => {
|
style={{
|
||||||
setPickedRemote(remote)
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||||
setAutoMountDrawerOpen(true)
|
position: 'relative',
|
||||||
})
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
onConfigPress={() => {
|
>
|
||||||
startTransition(() => {
|
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
setPickedRemote(remote)
|
const remote = filteredRemotes[virtualRow.index]
|
||||||
setEditingDrawerOpen(true)
|
return (
|
||||||
})
|
<div
|
||||||
}}
|
key={remote}
|
||||||
onDeletePress={async () => {
|
className="pb-2.5"
|
||||||
const confirmation = await ask(
|
style={{
|
||||||
`Are you sure you want to remove ${remote}? This action cannot be reverted.`,
|
position: 'absolute',
|
||||||
{ title: `Removing ${remote}`, kind: 'warning' }
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: `${virtualRow.size}px`,
|
||||||
|
transform: `translateY(${virtualRow.start}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RemoteCard
|
||||||
|
remote={remote}
|
||||||
|
onAutoMountPress={() => {
|
||||||
|
startTransition(() => {
|
||||||
|
setPickedRemote(remote)
|
||||||
|
setAutoMountDrawerOpen(true)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onConfigPress={() => {
|
||||||
|
startTransition(() => {
|
||||||
|
setPickedRemote(remote)
|
||||||
|
setEditingDrawerOpen(true)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onDeletePress={async () => {
|
||||||
|
const confirmation = await ask(
|
||||||
|
`Are you sure you want to remove ${remote}? This action cannot be reverted.`,
|
||||||
|
{
|
||||||
|
title: `Removing ${remote}`,
|
||||||
|
kind: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!confirmation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRemoteMutation.mutate(remote)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
})}
|
||||||
if (!confirmation) {
|
</div>
|
||||||
return
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
deleteRemoteMutation.mutate(remote)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -337,7 +381,7 @@ function RemoteCard({
|
|||||||
key={remote}
|
key={remote}
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
isBlurred={true}
|
isBlurred={true}
|
||||||
className="h-20 border-[0.5px] dark:border-none border-divider bg-content3/50 dark:bg-content2/90"
|
className="w-full h-20 border-[0.5px] dark:border-none border-divider bg-content3/50 dark:bg-content2/90"
|
||||||
isPressable={true}
|
isPressable={true}
|
||||||
onPress={onConfigPress}
|
onPress={onConfigPress}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user