feat: support dnd to reorder relay sets

This commit is contained in:
codytseng
2025-08-17 18:04:08 +08:00
parent a7c4d1e450
commit 9bdee807ee
8 changed files with 163 additions and 46 deletions

View File

@@ -0,0 +1,17 @@
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useTranslation } from 'react-i18next'
import RelayItem from './RelayItem'
export default function FavoriteRelayList() {
const { t } = useTranslation()
const { favoriteRelays } = useFavoriteRelays()
return (
<div className="space-y-2">
<div className="text-muted-foreground font-semibold select-none">{t('Relays')}</div>
{favoriteRelays.map((relay) => (
<RelayItem key={relay} relay={relay} />
))}
</div>
)
}

View File

@@ -10,12 +10,15 @@ import { Input } from '@/components/ui/input'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { TRelaySet } from '@/types'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import {
Check,
ChevronDown,
Edit,
EllipsisVertical,
FolderClosed,
GripVertical,
Link,
Trash2
} from 'lucide-react'
@@ -28,24 +31,44 @@ import { useRelaySetsSettingComponent } from './provider'
export default function RelaySet({ relaySet }: { relaySet: TRelaySet }) {
const { t } = useTranslation()
const { expandedRelaySetId } = useRelaySetsSettingComponent()
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: relaySet.id
})
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1
}
return (
<div className="w-full border rounded-lg pl-4 pr-2 py-2.5">
<div className="flex justify-between items-center">
<div className="flex gap-2 items-center">
<div className="flex justify-center items-center w-6 h-6 shrink-0">
<FolderClosed className="size-4" />
<div ref={setNodeRef} style={style} className="relative group">
<div className="w-full border rounded-lg px-2 py-2.5">
<div className="flex justify-between items-center">
<div className="flex items-center">
<div
className="cursor-grab active:cursor-grabbing p-2 hover:bg-muted rounded touch-none"
{...attributes}
{...listeners}
>
<GripVertical className="size-4 text-muted-foreground" />
</div>
<div className="flex gap-2 items-center">
<div className="flex justify-center items-center w-6 h-6 shrink-0">
<FolderClosed className="size-4" />
</div>
<RelaySetName relaySet={relaySet} />
</div>
</div>
<div className="flex gap-1">
<RelayUrlsExpandToggle relaySetId={relaySet.id}>
{t('n relays', { n: relaySet.relayUrls.length })}
</RelayUrlsExpandToggle>
<RelaySetOptions relaySet={relaySet} />
</div>
<RelaySetName relaySet={relaySet} />
</div>
<div className="flex gap-1">
<RelayUrlsExpandToggle relaySetId={relaySet.id}>
{t('n relays', { n: relaySet.relayUrls.length })}
</RelayUrlsExpandToggle>
<RelaySetOptions relaySet={relaySet} />
</div>
{expandedRelaySetId === relaySet.id && <RelayUrls relaySetId={relaySet.id} />}
</div>
{expandedRelaySetId === relaySet.id && <RelayUrls relaySetId={relaySet.id} />}
</div>
)
}

View File

@@ -0,0 +1,72 @@
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import {
closestCenter,
DndContext,
DragEndEvent,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from '@dnd-kit/core'
import { restrictToParentElement, restrictToVerticalAxis } from '@dnd-kit/modifiers'
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy
} from '@dnd-kit/sortable'
import { useTranslation } from 'react-i18next'
import PullRelaySetsButton from './PullRelaySetsButton'
import RelaySet from './RelaySet'
export default function RelaySetList() {
const { t } = useTranslation()
const { relaySets, reorderRelaySets } = useFavoriteRelays()
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
)
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
if (over && active.id !== over.id) {
const oldIndex = relaySets.findIndex((item) => item.id === active.id)
const newIndex = relaySets.findIndex((item) => item.id === over.id)
const reorderedSets = arrayMove(relaySets, oldIndex, newIndex)
reorderRelaySets(reorderedSets)
}
}
return (
<div className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="text-muted-foreground font-semibold select-none shrink-0">
{t('Relay sets')}
</div>
<PullRelaySetsButton />
</div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
>
<SortableContext
items={relaySets.map((set) => set.id)}
strategy={verticalListSortingStrategy}
>
<div className="grid gap-2">
{relaySets.map((relaySet) => (
<RelaySet key={relaySet.id} relaySet={relaySet} />
))}
</div>
</SortableContext>
</DndContext>
</div>
)
}

View File

@@ -1,39 +1,18 @@
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useTranslation } from 'react-i18next'
import AddNewRelay from './AddNewRelay'
import AddNewRelaySet from './AddNewRelaySet'
import FavoriteRelayList from './FavoriteRelayList'
import { RelaySetsSettingComponentProvider } from './provider'
import RelayItem from './RelayItem'
import RelaySet from './RelaySet'
import RelaySetList from './RelaySetList'
import TemporaryRelaySet from './TemporaryRelaySet'
import PullRelaySetsButton from './PullRelaySetsButton'
export default function FavoriteRelaysSetting() {
const { t } = useTranslation()
const { relaySets, favoriteRelays } = useFavoriteRelays()
return (
<RelaySetsSettingComponentProvider>
<div className="space-y-4">
<TemporaryRelaySet />
<div className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="text-muted-foreground font-semibold select-none shrink-0">
{t('Relay sets')}
</div>
<PullRelaySetsButton />
</div>
{relaySets.map((relaySet) => (
<RelaySet key={relaySet.id} relaySet={relaySet} />
))}
</div>
<RelaySetList />
<AddNewRelaySet />
<div className="space-y-2">
<div className="text-muted-foreground font-semibold select-none">{t('Relays')}</div>
{favoriteRelays.map((relay) => (
<RelayItem key={relay} relay={relay} />
))}
</div>
<FavoriteRelayList />
<AddNewRelay />
</div>
</RelaySetsSettingComponentProvider>