Bpistle/src/components/RelaySettings/RelayGroup.tsx
2024-12-21 23:20:30 +08:00

195 lines
6 KiB
TypeScript

import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { Check, ChevronDown, Circle, CircleCheck, EllipsisVertical } from 'lucide-react'
import { useState } from 'react'
import RelayUrls from './RelayUrl'
import { useRelaySettingsComponent } from './provider'
import { TRelayGroup } from './types'
import { useTranslation } from 'react-i18next'
export default function RelayGroup({ group }: { group: TRelayGroup }) {
const { t } = useTranslation()
const { expandedRelayGroup } = useRelaySettingsComponent()
const { temporaryRelayUrls } = useRelaySettings()
const { groupName, relayUrls } = group
const isActive = temporaryRelayUrls.length === 0 && group.isActive
return (
<div
className={`w-full border rounded-lg p-4 ${isActive ? 'border-highlight bg-highlight/5' : ''}`}
>
<div className="flex justify-between items-center">
<div className="flex space-x-2 items-center">
<RelayGroupActiveToggle
groupName={groupName}
isActive={isActive}
canActive={relayUrls.length > 0}
/>
<RelayGroupName groupName={groupName} />
</div>
<div className="flex gap-1">
<RelayUrlsExpandToggle groupName={groupName}>
{t('n relays', { n: relayUrls.length })}
</RelayUrlsExpandToggle>
<RelayGroupOptions group={group} />
</div>
</div>
{expandedRelayGroup === groupName && <RelayUrls groupName={groupName} />}
</div>
)
}
function RelayGroupActiveToggle({
groupName,
isActive,
canActive
}: {
groupName: string
isActive: boolean
canActive: boolean
}) {
const { switchRelayGroup } = useRelaySettings()
return isActive ? (
<CircleCheck size={18} className="text-highlight shrink-0" />
) : (
<Circle
size={18}
className={`text-muted-foreground shrink-0 ${canActive ? 'cursor-pointer hover:text-foreground ' : ''}`}
onClick={() => {
if (canActive) {
switchRelayGroup(groupName)
}
}}
/>
)
}
function RelayGroupName({ groupName }: { groupName: string }) {
const { t } = useTranslation()
const [newGroupName, setNewGroupName] = useState(groupName)
const [newNameError, setNewNameError] = useState<string | null>(null)
const { relayGroups, switchRelayGroup, renameRelayGroup } = useRelaySettings()
const { renamingGroup, setRenamingGroup } = useRelaySettingsComponent()
const hasRelayUrls = relayGroups.find((group) => group.groupName === groupName)?.relayUrls.length
const saveNewGroupName = () => {
if (groupName === newGroupName) {
return setRenamingGroup(null)
}
if (relayGroups.find((group) => group.groupName === newGroupName)) {
return setNewNameError(t('relay collection name already exists'))
}
const errMsg = renameRelayGroup(groupName, newGroupName)
if (errMsg) {
setNewNameError(errMsg)
return
}
setRenamingGroup(null)
}
const handleRenameInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewGroupName(e.target.value)
setNewNameError(null)
}
const handleRenameInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault()
saveNewGroupName()
}
}
return renamingGroup === groupName ? (
<div className="flex gap-1 items-center">
<Input
value={newGroupName}
onChange={handleRenameInputChange}
onBlur={saveNewGroupName}
onKeyDown={handleRenameInputKeyDown}
className={`font-semibold w-28 ${newNameError ? 'border-destructive' : ''}`}
/>
<Button variant="ghost" size="icon" onClick={saveNewGroupName}>
<Check size={18} className="text-green-500" />
</Button>
{newNameError && <div className="text-xs text-destructive">{newNameError}</div>}
</div>
) : (
<div
className={`h-8 font-semibold flex items-center ${hasRelayUrls ? 'cursor-pointer' : 'text-muted-foreground'}`}
onClick={() => {
if (hasRelayUrls) {
switchRelayGroup(groupName)
}
}}
>
{groupName}
</div>
)
}
function RelayUrlsExpandToggle({
groupName,
children
}: {
groupName: string
children: React.ReactNode
}) {
const { expandedRelayGroup, setExpandedRelayGroup } = useRelaySettingsComponent()
return (
<div
className="text-sm text-muted-foreground flex items-center gap-1 cursor-pointer hover:text-foreground"
onClick={() => setExpandedRelayGroup((pre) => (pre === groupName ? null : groupName))}
>
<div className="select-none">{children}</div>
<ChevronDown
size={16}
className={`transition-transform duration-200 ${expandedRelayGroup === groupName ? 'rotate-180' : ''}`}
/>
</div>
)
}
function RelayGroupOptions({ group }: { group: TRelayGroup }) {
const { t } = useTranslation()
const { deleteRelayGroup } = useRelaySettings()
const { setRenamingGroup } = useRelaySettingsComponent()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => setRenamingGroup(group.groupName)}>
{t('Rename')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(
`https://jumble.social/?${group.relayUrls.map((url) => 'r=' + url).join('&')}`
)
}}
>
{t('Copy share link')}
</DropdownMenuItem>
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={() => deleteRelayGroup(group.groupName)}
>
{t('Delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}