wip pt3
This commit is contained in:
parent
e789118264
commit
243987aa40
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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 = ``
|
||||||
|
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
|
Loading…
x
Reference in New Issue
Block a user