2026-01-14 11:05:27 +01:00

188 lines
6.3 KiB
TypeScript

import type { Nip95Config } from '@/lib/configStorageTypes'
import { Button, Card, Input } from '../ui'
import { t } from '@/lib/i18n'
import { DragHandle } from '../DragHandle'
import { getApiCardClassName } from './getApiCardClassName'
function Nip95ApiCardContent(params: {
api: Nip95Config
priority: number
isEditing: boolean
onStartEditing: (id: string) => void
onStopEditing: () => void
onUpdateUrl: (id: string, url: string) => void
onToggleEnabled: (id: string, enabled: boolean) => void
onRemoveApi: (id: string) => void
onDragStart: (e: React.DragEvent<HTMLDivElement>, id: string) => void
}): React.ReactElement {
return (
<>
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-3 flex-1">
<DragGrip apiId={params.api.id} onDragStart={params.onDragStart} />
<UrlCell
api={params.api}
isEditing={params.isEditing}
onStartEditing={params.onStartEditing}
onStopEditing={params.onStopEditing}
onUpdateUrl={params.onUpdateUrl}
/>
</div>
<ActionsCell api={params.api} onToggleEnabled={params.onToggleEnabled} onRemoveApi={params.onRemoveApi} />
</div>
<PriorityRow priority={params.priority} apiId={params.api.id} />
</>
)
}
export function Nip95ApiCard(params: {
api: Nip95Config
priority: number
isEditing: boolean
draggedId: string | null
dragOverId: string | null
onStartEditing: (id: string) => void
onStopEditing: () => void
onUpdateUrl: (id: string, url: string) => void
onToggleEnabled: (id: string, enabled: boolean) => void
onRemoveApi: (id: string) => void
onDragStart: (e: React.DragEvent<HTMLDivElement>, id: string) => void
onDragOver: (e: React.DragEvent<HTMLDivElement>, id: string) => void
onDragLeave: () => void
onDrop: (e: React.DragEvent<HTMLDivElement>, targetId: string) => void
}): React.ReactElement {
return (
<div
onDragOver={(e: React.DragEvent<HTMLDivElement>) => params.onDragOver(e, params.api.id)}
onDragLeave={params.onDragLeave}
onDrop={(e: React.DragEvent<HTMLDivElement>) => params.onDrop(e, params.api.id)}
className={getApiCardClassName(params.api.id, params.draggedId, params.dragOverId)}
>
<Card variant="default" className="bg-cyber-dark space-y-3 transition-all">
<Nip95ApiCardContent
api={params.api}
priority={params.priority}
isEditing={params.isEditing}
onStartEditing={params.onStartEditing}
onStopEditing={params.onStopEditing}
onUpdateUrl={params.onUpdateUrl}
onToggleEnabled={params.onToggleEnabled}
onRemoveApi={params.onRemoveApi}
onDragStart={params.onDragStart}
/>
</Card>
</div>
)
}
function DragGrip(params: { apiId: string; onDragStart: (e: React.DragEvent<HTMLDivElement>, id: string) => void }): React.ReactElement {
return (
<div
className="drag-handle cursor-grab active:cursor-grabbing"
draggable
onDragStart={(e) => {
params.onDragStart(e, params.apiId)
e.stopPropagation()
}}
onMouseDown={(e) => e.stopPropagation()}
>
<DragHandle />
</div>
)
}
function UrlCell(params: {
api: Nip95Config
isEditing: boolean
onStartEditing: (id: string) => void
onStopEditing: () => void
onUpdateUrl: (id: string, url: string) => void
}): React.ReactElement {
return <div className="flex-1">{params.isEditing ? <UrlEditor api={params.api} onStop={params.onStopEditing} onUpdate={params.onUpdateUrl} /> : <UrlText api={params.api} onStartEditing={params.onStartEditing} />}</div>
}
function UrlText(params: { api: Nip95Config; onStartEditing: (id: string) => void }): React.ReactElement {
return (
<div
className="text-neon-cyan cursor-pointer hover:text-neon-green transition-colors"
onClick={() => params.onStartEditing(params.api.id)}
title={t('settings.nip95.list.editUrl')}
>
{params.api.url}
</div>
)
}
function UrlEditor(params: { api: Nip95Config; onStop: () => void; onUpdate: (id: string, url: string) => void }): React.ReactElement {
return (
<Input
type="url"
defaultValue={params.api.url}
onBlur={(e) => {
const next = e.target.value
if (next !== params.api.url) {
params.onUpdate(params.api.id, next)
} else {
params.onStop()
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur()
} else if (e.key === 'Escape') {
params.onStop()
}
}}
className="w-full bg-cyber-darker border-neon-cyan/50 text-cyber-light"
autoFocus
/>
)
}
function ActionsCell(params: { api: Nip95Config; onToggleEnabled: (id: string, enabled: boolean) => void; onRemoveApi: (id: string) => void }): React.ReactElement {
return (
<div className="flex items-center gap-2">
<EnabledToggle api={params.api} onToggleEnabled={params.onToggleEnabled} />
<RemoveButton apiId={params.api.id} onRemove={params.onRemoveApi} />
</div>
)
}
function EnabledToggle(params: { api: Nip95Config; onToggleEnabled: (id: string, enabled: boolean) => void }): React.ReactElement {
return (
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={params.api.enabled}
onChange={(e) => params.onToggleEnabled(params.api.id, e.target.checked)}
className="w-4 h-4 text-neon-cyan bg-cyber-darker border-cyber-accent rounded focus:ring-neon-cyan"
/>
<span className="text-sm text-cyber-accent">
{params.api.enabled ? t('settings.nip95.list.enabled') : t('settings.nip95.list.disabled')}
</span>
</label>
)
}
function RemoveButton(params: { apiId: string; onRemove: (id: string) => void }): React.ReactElement {
return (
<Button
type="button"
variant="danger"
size="small"
onClick={() => params.onRemove(params.apiId)}
aria-label={t('settings.nip95.list.remove')}
>
{t('settings.nip95.list.remove')}
</Button>
)
}
function PriorityRow(params: { priority: number; apiId: string }): React.ReactElement {
return (
<div className="flex items-center gap-2 text-xs text-cyber-accent/70">
<span>{t('settings.nip95.list.priorityLabel', { priority: params.priority, id: params.apiId })}</span>
</div>
)
}