This commit is contained in:
highperfocused 2025-04-13 13:07:52 +02:00
parent e789118264
commit 243987aa40
2 changed files with 153 additions and 145 deletions

View File

@ -4,10 +4,8 @@ export default function UploadPage() {
return ( return (
<div className="container mx-auto py-8"> <div className="container mx-auto py-8">
<div className="max-w-3xl mx-auto"> <div className="max-w-3xl mx-auto">
<h1 className="text-3xl font-bold mb-6">Upload Image</h1> <h1 className="text-3xl font-bold mb-6">Uploader</h1>
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md"> <UploadComponent />
<UploadComponent />
</div>
</div> </div>
</div> </div>
) )

View File

@ -7,20 +7,13 @@ import { Input } from "@/components/ui/input"
import type { NostrEvent } from "nostr-tools" import type { NostrEvent } from "nostr-tools"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import {
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerDescription,
DrawerFooter,
} from "@/components/ui/drawer"
import { signEvent } from "@/lib/utils" import { signEvent } from "@/lib/utils"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Copy, Check, ExternalLink, FileImage, Clock, Database } from "lucide-react" import { Copy, Check, ExternalLink, FileImage, Clock, Database, ArrowLeft } from "lucide-react"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { Spinner } from "./spinner"
interface UploadResponse { interface UploadResponse {
url: string url: string
@ -75,149 +68,166 @@ const CopyButton = ({ text }: { text: string }) => {
) )
} }
const UploadResponseView = ({ data }: { data: UploadResponse }) => { const UploadResponseView = ({ data, onReset }: { data: UploadResponse; onReset: () => void }) => {
const dimensions = data.nip94.tags.find((tag) => tag[0] === "dim")?.[1] || "" const dimensions = data.nip94.tags.find((tag) => tag[0] === "dim")?.[1] || ""
const blurhash = data.nip94.tags.find((tag) => tag[0] === "blurhash")?.[1] || "" const blurhash = data.nip94.tags.find((tag) => tag[0] === "blurhash")?.[1] || ""
const thumbUrl = data.nip94.tags.find((tag) => tag[0] === "thumb")?.[1] || "" const thumbUrl = data.nip94.tags.find((tag) => tag[0] === "thumb")?.[1] || ""
return ( return (
<Card className="w-full"> <div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-300">
<CardHeader> <div className="flex items-center">
<div className="flex items-center justify-between"> <Button variant="ghost" size="sm" onClick={onReset} className="gap-1">
<CardTitle>Upload Successful</CardTitle> <ArrowLeft className="h-4 w-4" />
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200"> Upload Another
{data.type} </Button>
</Badge> </div>
</div>
<CardDescription>File uploaded on {formatDate(data.uploaded)}</CardDescription> <Card className="w-full">
</CardHeader> <CardHeader>
<CardContent className="space-y-6"> <div className="flex items-center justify-between">
<div className="flex flex-col md:flex-row gap-6"> <CardTitle>Upload Successful</CardTitle>
<div className="flex-1"> <Badge variant="outline" className="bg-green-50 text-green-700 border-green-200">
<img {data.type}
src={data.url || "/placeholder.svg"} </Badge>
alt="Uploaded image"
className="rounded-md w-full h-auto object-cover max-h-64"
/>
</div> </div>
<div className="flex-1 space-y-4"> <CardDescription>File uploaded on {formatDate(data.uploaded)}</CardDescription>
<div className="space-y-2"> </CardHeader>
<div className="flex items-center text-sm text-muted-foreground"> <CardContent className="space-y-6">
<FileImage className="mr-2 h-4 w-4" /> <div className="flex flex-col md:flex-row gap-6">
File Size <div className="flex-1">
</div> <img
<div className="font-medium">{formatBytes(data.size)}</div> src={data.url || "/placeholder.svg"}
alt="Uploaded image"
className="rounded-md w-full h-auto object-cover max-h-64"
/>
</div> </div>
<div className="flex-1 space-y-4">
<div className="space-y-2">
<div className="flex items-center text-sm text-muted-foreground">
<Clock className="mr-2 h-4 w-4" />
Upload Time
</div>
<div className="font-medium">{formatDate(data.uploaded)}</div>
</div>
{dimensions && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center text-sm text-muted-foreground"> <div className="flex items-center text-sm text-muted-foreground">
<Database className="mr-2 h-4 w-4" /> <FileImage className="mr-2 h-4 w-4" />
Dimensions File Size
</div> </div>
<div className="font-medium">{dimensions}</div> <div className="font-medium">{formatBytes(data.size)}</div>
</div> </div>
)}
</div>
</div>
<Tabs defaultValue="url" className="w-full"> <div className="space-y-2">
<TabsList className="grid grid-cols-3"> <div className="flex items-center text-sm text-muted-foreground">
<TabsTrigger value="url">URL</TabsTrigger> <Clock className="mr-2 h-4 w-4" />
<TabsTrigger value="hash">Hash</TabsTrigger> Upload Time
<TabsTrigger value="metadata">Metadata</TabsTrigger>
</TabsList>
<TabsContent value="url" className="space-y-4">
<div className="mt-2 space-y-2">
<Label>Image URL</Label>
<div className="flex items-center">
<code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{data.url}</code>
<div className="flex ml-2">
<CopyButton text={data.url} />
<Button variant="ghost" size="icon" className="h-6 w-6" asChild>
<a href={data.url} target="_blank" rel="noopener noreferrer">
<ExternalLink className="h-3 w-3" />
</a>
</Button>
</div> </div>
<div className="font-medium">{formatDate(data.uploaded)}</div>
</div> </div>
{dimensions && (
<div className="space-y-2">
<div className="flex items-center text-sm text-muted-foreground">
<Database className="mr-2 h-4 w-4" />
Dimensions
</div>
<div className="font-medium">{dimensions}</div>
</div>
)}
</div> </div>
</div>
{thumbUrl && ( <Tabs defaultValue="url" className="w-full">
<TabsList className="grid grid-cols-3">
<TabsTrigger value="url">URL</TabsTrigger>
<TabsTrigger value="hash">Hash</TabsTrigger>
<TabsTrigger value="metadata">Metadata</TabsTrigger>
</TabsList>
<TabsContent value="url" className="space-y-4">
<div className="mt-2 space-y-2"> <div className="mt-2 space-y-2">
<Label>Thumbnail URL</Label> <Label>Image URL</Label>
<div className="flex items-center"> <div className="flex items-center">
<code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{thumbUrl}</code> <code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{data.url}</code>
<div className="flex ml-2"> <div className="flex ml-2">
<CopyButton text={thumbUrl} /> <CopyButton text={data.url} />
<Button variant="ghost" size="icon" className="h-6 w-6" asChild> <Button variant="ghost" size="icon" className="h-6 w-6" asChild>
<a href={thumbUrl} target="_blank" rel="noopener noreferrer"> <a href={data.url} target="_blank" rel="noopener noreferrer">
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</a> </a>
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>
)}
</TabsContent>
<TabsContent value="hash" className="space-y-4"> {thumbUrl && (
<div className="mt-2 space-y-2"> <div className="mt-2 space-y-2">
<Label>SHA256</Label> <Label>Thumbnail URL</Label>
<div className="flex items-center"> <div className="flex items-center">
<code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{data.sha256}</code> <code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{thumbUrl}</code>
<CopyButton text={data.sha256} /> <div className="flex ml-2">
</div> <CopyButton text={thumbUrl} />
</div> <Button variant="ghost" size="icon" className="h-6 w-6" asChild>
<a href={thumbUrl} target="_blank" rel="noopener noreferrer">
<ExternalLink className="h-3 w-3" />
</a>
</Button>
</div>
</div>
</div>
)}
</TabsContent>
{blurhash && ( <TabsContent value="hash" className="space-y-4">
<div className="mt-2 space-y-2"> <div className="mt-2 space-y-2">
<Label>Blurhash</Label> <Label>SHA256</Label>
<div className="flex items-center"> <div className="flex items-center">
<code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{blurhash}</code> <code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{data.sha256}</code>
<CopyButton text={blurhash} /> <CopyButton text={data.sha256} />
</div> </div>
</div> </div>
)}
</TabsContent>
<TabsContent value="metadata" className="space-y-4"> {blurhash && (
<div className="mt-2"> <div className="mt-2 space-y-2">
<Label className="mb-2 block">NIP94 Tags</Label> <Label>Blurhash</Label>
<div className="grid gap-2"> <div className="flex items-center">
{data.nip94.tags.map((tag, index) => ( <code className="bg-muted p-2 rounded text-xs flex-1 overflow-x-auto">{blurhash}</code>
<div key={index} className="flex items-center bg-muted/50 p-2 rounded"> <CopyButton text={blurhash} />
<Badge variant="outline" className="mr-2">
{tag[0]}
</Badge>
<code className="text-xs flex-1 overflow-x-auto">{tag[1]}</code>
<CopyButton text={tag[1]} />
</div> </div>
))} </div>
</div> )}
</div> </TabsContent>
</TabsContent>
</Tabs>
<div className="flex justify-end"> <TabsContent value="metadata" className="space-y-4">
<Button variant="outline" className="mr-2" asChild> <div className="mt-2">
<a href={data.url} target="_blank" rel="noopener noreferrer"> <Label className="mb-2 block">NIP94 Tags</Label>
Open Image <div className="grid gap-2">
</a> {data.nip94.tags.map((tag, index) => (
</Button> <div key={index} className="flex items-center bg-muted/50 p-2 rounded">
<Button>Copy Markdown</Button> <Badge variant="outline" className="mr-2">
</div> {tag[0]}
</CardContent> </Badge>
</Card> <code className="text-xs flex-1 overflow-x-auto">{tag[1]}</code>
<CopyButton text={tag[1]} />
</div>
))}
</div>
</div>
</TabsContent>
</Tabs>
<div className="flex justify-end">
<Button variant="outline" className="mr-2" asChild>
<a href={data.url} target="_blank" rel="noopener noreferrer">
Open Image
</a>
</Button>
<Button
onClick={() => {
const markdown = `![Image](${data.url})`
navigator.clipboard.writeText(markdown)
alert("Markdown copied to clipboard!")
}}
>
Copy Markdown
</Button>
</div>
</CardContent>
</Card>
</div>
) )
} }
@ -262,7 +272,6 @@ const UploadComponent = () => {
const loginType = typeof window !== "undefined" ? window.localStorage.getItem("loginType") : null const loginType = typeof window !== "undefined" ? window.localStorage.getItem("loginType") : null
const [previewUrl, setPreviewUrl] = useState("") const [previewUrl, setPreviewUrl] = useState("")
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const [uploadedNoteId, setUploadedNoteId] = useState("") const [uploadedNoteId, setUploadedNoteId] = useState("")
const [retryCount, setRetryCount] = useState(0) const [retryCount, setRetryCount] = useState(0)
const [shouldFetch, setShouldFetch] = useState(false) const [shouldFetch, setShouldFetch] = useState(false)
@ -270,6 +279,7 @@ const UploadComponent = () => {
const [events, setEvents] = useState<any[]>([]) const [events, setEvents] = useState<any[]>([])
const [isNoteLoading, setIsNoteLoading] = useState(false) const [isNoteLoading, setIsNoteLoading] = useState(false)
const [uploadResponse, setUploadResponse] = useState<UploadResponse | null>(null) const [uploadResponse, setUploadResponse] = useState<UploadResponse | null>(null)
const [showUploadForm, setShowUploadForm] = useState(true)
useEffect(() => { useEffect(() => {
if (uploadedNoteId) { if (uploadedNoteId) {
@ -322,6 +332,16 @@ const UploadComponent = () => {
setServerChoice(value) setServerChoice(value)
} }
const resetUpload = () => {
setUploadResponse(null)
setShowUploadForm(true)
setPreviewUrl("")
setUploadedNoteId("")
setEvents([])
setRetryCount(0)
setShouldFetch(false)
}
async function onSubmit(event: FormEvent<HTMLFormElement>) { async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault() event.preventDefault()
setIsLoading(true) setIsLoading(true)
@ -421,7 +441,7 @@ const UploadComponent = () => {
setIsLoading(false) setIsLoading(false)
if (finalFileUrl != null) { if (finalFileUrl != null) {
setIsDrawerOpen(true) setShowUploadForm(false)
setShouldFetch(true) setShouldFetch(true)
setRetryCount(0) setRetryCount(0)
} }
@ -438,8 +458,8 @@ const UploadComponent = () => {
} }
return ( return (
<> <div className="w-full max-w-2xl mx-auto">
<div className="w-full max-w-2xl mx-auto"> {showUploadForm ? (
<form className="space-y-4" onSubmit={onSubmit}> <form className="space-y-4" onSubmit={onSubmit}>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="file">Upload Image</Label> <Label htmlFor="file">Upload Image</Label>
@ -474,26 +494,16 @@ const UploadComponent = () => {
</Button> </Button>
)} )}
</form> </form>
</div> ) : uploadResponse ? (
<UploadResponseView data={uploadResponse} onReset={resetUpload} />
<Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}> ) : (
<DrawerContent className="max-h-[90vh] overflow-y-auto"> <div className="flex flex-col items-center justify-center p-8">
<DrawerHeader> <Spinner />
<DrawerTitle>Upload Result</DrawerTitle> <p className="mt-4">Processing upload...</p>
<DrawerDescription>Your file has been successfully uploaded</DrawerDescription> </div>
</DrawerHeader> )}
</div>
<div className="px-4 py-2">{uploadResponse && <UploadResponseView data={uploadResponse} />}</div>
<DrawerFooter className="flex flex-col space-y-2">
<Button variant="outline" onClick={() => setIsDrawerOpen(false)} className="w-full">
Close
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
) )
} }
export default UploadComponent export default UploadComponent