feat: support dnd to reorder favorite relays
This commit is contained in:
@@ -1,17 +1,63 @@
|
|||||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
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 { useTranslation } from 'react-i18next'
|
||||||
import RelayItem from './RelayItem'
|
import RelayItem from './RelayItem'
|
||||||
|
|
||||||
export default function FavoriteRelayList() {
|
export default function FavoriteRelayList() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { favoriteRelays } = useFavoriteRelays()
|
const { favoriteRelays, reorderFavoriteRelays } = 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 = favoriteRelays.findIndex((relay) => relay === active.id)
|
||||||
|
const newIndex = favoriteRelays.findIndex((relay) => relay === over.id)
|
||||||
|
|
||||||
|
const reorderedRelays = arrayMove(favoriteRelays, oldIndex, newIndex)
|
||||||
|
reorderFavoriteRelays(reorderedRelays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-muted-foreground font-semibold select-none">{t('Relays')}</div>
|
<div className="text-muted-foreground font-semibold select-none">{t('Relays')}</div>
|
||||||
{favoriteRelays.map((relay) => (
|
<DndContext
|
||||||
<RelayItem key={relay} relay={relay} />
|
sensors={sensors}
|
||||||
))}
|
collisionDetection={closestCenter}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
|
||||||
|
>
|
||||||
|
<SortableContext items={favoriteRelays} strategy={verticalListSortingStrategy}>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
{favoriteRelays.map((relay) => (
|
||||||
|
<RelayItem key={relay} relay={relay} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,43 @@
|
|||||||
import { toRelay } from '@/lib/link'
|
import { toRelay } from '@/lib/link'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
|
import { GripVertical } from 'lucide-react'
|
||||||
import RelayIcon from '../RelayIcon'
|
import RelayIcon from '../RelayIcon'
|
||||||
import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu'
|
import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu'
|
||||||
|
|
||||||
export default function RelayItem({ relay }: { relay: string }) {
|
export default function RelayItem({ relay }: { relay: string }) {
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
|
id: relay
|
||||||
|
})
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
opacity: isDragging ? 0.5 : 1
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex gap-2 border rounded-lg p-4 items-center clickable select-none"
|
className="relative group clickable flex gap-2 border rounded-lg p-2 pr-2.5 items-center justify-between select-none"
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
onClick={() => push(toRelay(relay))}
|
onClick={() => push(toRelay(relay))}
|
||||||
>
|
>
|
||||||
<RelayIcon url={relay} />
|
<div className="flex items-center gap-1 flex-1">
|
||||||
<div className="flex-1 w-0 truncate font-semibold">{relay}</div>
|
<div
|
||||||
|
className="cursor-grab active:cursor-grabbing p-2 hover:bg-muted rounded touch-none shrink-0"
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
>
|
||||||
|
<GripVertical className="size-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 items-center flex-1">
|
||||||
|
<RelayIcon url={relay} />
|
||||||
|
<div className="flex-1 w-0 truncate font-semibold">{relay}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<SaveRelayDropdownMenu urls={[relay]} />
|
<SaveRelayDropdownMenu urls={[relay]} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ export default function SaveRelayDropdownMenu({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild className="px-2">
|
||||||
|
{trigger}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent onClick={(e) => e.stopPropagation()}>
|
<DropdownMenuContent onClick={(e) => e.stopPropagation()}>
|
||||||
<DropdownMenuLabel>{t('Save to')} ...</DropdownMenuLabel>
|
<DropdownMenuLabel>{t('Save to')} ...</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type TFavoriteRelaysContext = {
|
|||||||
favoriteRelays: string[]
|
favoriteRelays: string[]
|
||||||
addFavoriteRelays: (relayUrls: string[]) => Promise<void>
|
addFavoriteRelays: (relayUrls: string[]) => Promise<void>
|
||||||
deleteFavoriteRelays: (relayUrls: string[]) => Promise<void>
|
deleteFavoriteRelays: (relayUrls: string[]) => Promise<void>
|
||||||
|
reorderFavoriteRelays: (reorderedRelays: string[]) => Promise<void>
|
||||||
relaySets: TRelaySet[]
|
relaySets: TRelaySet[]
|
||||||
createRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise<void>
|
createRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise<void>
|
||||||
addRelaySets: (newRelaySetEvents: Event[]) => Promise<void>
|
addRelaySets: (newRelaySetEvents: Event[]) => Promise<void>
|
||||||
@@ -219,6 +220,13 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reorderFavoriteRelays = async (reorderedRelays: string[]) => {
|
||||||
|
setFavoriteRelays(reorderedRelays)
|
||||||
|
const draftEvent = createFavoriteRelaysDraftEvent(reorderedRelays, relaySetEvents)
|
||||||
|
const newFavoriteRelaysEvent = await publish(draftEvent)
|
||||||
|
updateFavoriteRelaysEvent(newFavoriteRelaysEvent)
|
||||||
|
}
|
||||||
|
|
||||||
const reorderRelaySets = async (reorderedSets: TRelaySet[]) => {
|
const reorderRelaySets = async (reorderedSets: TRelaySet[]) => {
|
||||||
setRelaySets(reorderedSets)
|
setRelaySets(reorderedSets)
|
||||||
const draftEvent = createFavoriteRelaysDraftEvent(
|
const draftEvent = createFavoriteRelaysDraftEvent(
|
||||||
@@ -235,6 +243,7 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode
|
|||||||
favoriteRelays,
|
favoriteRelays,
|
||||||
addFavoriteRelays,
|
addFavoriteRelays,
|
||||||
deleteFavoriteRelays,
|
deleteFavoriteRelays,
|
||||||
|
reorderFavoriteRelays,
|
||||||
relaySets,
|
relaySets,
|
||||||
createRelaySet,
|
createRelaySet,
|
||||||
addRelaySets,
|
addRelaySets,
|
||||||
|
|||||||
Reference in New Issue
Block a user