188 lines
6.3 KiB
TypeScript
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>
|
|
)
|
|
}
|