@dyrected/admin 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +1 -0
- package/dist/admin.css +2 -0
- package/dist/components/auth/auth-gate.d.ts +13 -0
- package/dist/components/error-boundary.d.ts +16 -0
- package/dist/components/forms/field-renderer.d.ts +22 -0
- package/dist/components/forms/fields/block-builder.d.ts +9 -0
- package/dist/components/forms/fields/date-picker.d.ts +8 -0
- package/dist/components/forms/fields/json-editor.d.ts +8 -0
- package/dist/components/forms/fields/media-picker.d.ts +12 -0
- package/dist/components/forms/fields/multi-select.d.ts +19 -0
- package/dist/components/forms/fields/radio-field.d.ts +8 -0
- package/dist/components/forms/fields/relationship-picker.d.ts +10 -0
- package/dist/components/forms/fields/rich-text-editor.d.ts +9 -0
- package/dist/components/forms/fields/select-field.d.ts +8 -0
- package/dist/components/forms/fields/switch-field.d.ts +6 -0
- package/dist/components/forms/fields/text-area-field.d.ts +8 -0
- package/dist/components/forms/fields/text-field.d.ts +8 -0
- package/dist/components/forms/form-engine.d.ts +14 -0
- package/dist/components/forms/form-field-renderer.d.ts +20 -0
- package/dist/components/forms/utils.d.ts +11 -0
- package/dist/components/layout/admin-shell.d.ts +5 -0
- package/dist/components/layout/branding-provider.d.ts +4 -0
- package/dist/components/live-preview/LivePreviewPane.d.ts +7 -0
- package/dist/components/media/focal-point-picker.d.ts +12 -0
- package/dist/components/media/media-card.d.ts +8 -0
- package/dist/components/media/media-grid.d.ts +8 -0
- package/dist/components/media/media-library-dialog.d.ts +11 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/calendar.d.ts +8 -0
- package/dist/components/ui/card.d.ts +8 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/command.d.ts +80 -0
- package/dist/components/ui/data-table.d.ts +14 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/form.d.ts +23 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/page-header.d.ts +10 -0
- package/dist/components/ui/pagination.d.ts +11 -0
- package/dist/components/ui/popover.d.ts +6 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/render-cell.d.ts +8 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/select.d.ts +13 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/sheet.d.ts +25 -0
- package/dist/components/ui/sidebar.d.ts +65 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-preferences.d.ts +6 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.mjs +69091 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/main.d.ts +0 -0
- package/dist/pages/auth/first-user-page.d.ts +4 -0
- package/dist/pages/auth/login-page.d.ts +4 -0
- package/dist/pages/collections/edit-page.d.ts +1 -0
- package/dist/pages/collections/list-page.d.ts +5 -0
- package/dist/pages/dashboard/dashboard.d.ts +1 -0
- package/dist/pages/globals/editor-page.d.ts +1 -0
- package/dist/pages/media/media-page.d.ts +4 -0
- package/dist/pages/setup/setup-prompt.d.ts +6 -0
- package/dist/providers/dyrected-provider.d.ts +29 -0
- package/dist/providers/query-provider.d.ts +3 -0
- package/package.json +6 -3
- package/CHANGELOG.md +0 -153
- package/components.json +0 -17
- package/eslint.config.js +0 -22
- package/index.html +0 -13
- package/postcss.config.js +0 -6
- package/scripts/prefix-tailwind-precision.py +0 -98
- package/scripts/prefix-tailwind.py +0 -67
- package/src/App.css +0 -184
- package/src/App.tsx +0 -25
- package/src/assets/dyrected.svg +0 -155
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +0 -1
- package/src/assets/vite.svg +0 -1
- package/src/components/auth/auth-gate.tsx +0 -64
- package/src/components/error-boundary.tsx +0 -45
- package/src/components/forms/field-renderer.tsx +0 -111
- package/src/components/forms/fields/block-builder.tsx +0 -213
- package/src/components/forms/fields/date-picker.tsx +0 -60
- package/src/components/forms/fields/json-editor.tsx +0 -62
- package/src/components/forms/fields/media-picker.tsx +0 -286
- package/src/components/forms/fields/multi-select.tsx +0 -145
- package/src/components/forms/fields/radio-field.tsx +0 -51
- package/src/components/forms/fields/relationship-picker.tsx +0 -143
- package/src/components/forms/fields/rich-text-editor.tsx +0 -224
- package/src/components/forms/fields/select-field.tsx +0 -35
- package/src/components/forms/fields/switch-field.tsx +0 -16
- package/src/components/forms/fields/text-area-field.tsx +0 -15
- package/src/components/forms/fields/text-field.tsx +0 -24
- package/src/components/forms/form-engine.tsx +0 -87
- package/src/components/forms/form-field-renderer.tsx +0 -269
- package/src/components/forms/utils.ts +0 -97
- package/src/components/layout/admin-shell.tsx +0 -479
- package/src/components/layout/branding-provider.tsx +0 -112
- package/src/components/live-preview/LivePreviewPane.tsx +0 -128
- package/src/components/media/focal-point-picker.tsx +0 -66
- package/src/components/media/media-card.tsx +0 -44
- package/src/components/media/media-grid.tsx +0 -32
- package/src/components/media/media-library-dialog.tsx +0 -465
- package/src/components/ui/aspect-ratio.tsx +0 -7
- package/src/components/ui/badge.tsx +0 -36
- package/src/components/ui/button.tsx +0 -56
- package/src/components/ui/calendar.tsx +0 -214
- package/src/components/ui/card.tsx +0 -79
- package/src/components/ui/checkbox.tsx +0 -28
- package/src/components/ui/command.tsx +0 -151
- package/src/components/ui/data-table.tsx +0 -219
- package/src/components/ui/dialog.tsx +0 -122
- package/src/components/ui/dropdown-menu.tsx +0 -200
- package/src/components/ui/form.tsx +0 -178
- package/src/components/ui/input.tsx +0 -24
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/page-header.tsx +0 -30
- package/src/components/ui/pagination.tsx +0 -57
- package/src/components/ui/popover.tsx +0 -29
- package/src/components/ui/progress.tsx +0 -26
- package/src/components/ui/radio-group.tsx +0 -42
- package/src/components/ui/render-cell.tsx +0 -110
- package/src/components/ui/scroll-area.tsx +0 -46
- package/src/components/ui/select.tsx +0 -160
- package/src/components/ui/separator.tsx +0 -29
- package/src/components/ui/sheet.tsx +0 -140
- package/src/components/ui/sidebar.tsx +0 -771
- package/src/components/ui/skeleton.tsx +0 -15
- package/src/components/ui/sonner.tsx +0 -27
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/table.tsx +0 -117
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/textarea.tsx +0 -22
- package/src/components/ui/toggle.tsx +0 -43
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/hooks/use-preferences.ts +0 -56
- package/src/index.css +0 -111
- package/src/index.tsx +0 -198
- package/src/lib/utils.ts +0 -36
- package/src/main.tsx +0 -10
- package/src/pages/auth/first-user-page.tsx +0 -115
- package/src/pages/auth/login-page.tsx +0 -91
- package/src/pages/collections/edit-page.tsx +0 -280
- package/src/pages/collections/list-page.tsx +0 -343
- package/src/pages/dashboard/dashboard.tsx +0 -150
- package/src/pages/globals/editor-page.tsx +0 -122
- package/src/pages/media/media-page.tsx +0 -564
- package/src/pages/setup/setup-prompt.tsx +0 -181
- package/src/providers/dyrected-provider.tsx +0 -122
- package/src/providers/query-provider.tsx +0 -19
- package/src/types/jexl.d.ts +0 -11
- package/tailwind.config.ts +0 -103
- package/tsconfig.app.json +0 -28
- package/tsconfig.json +0 -12
- package/tsconfig.node.json +0 -25
- package/vite.config.ts +0 -39
- /package/{public → dist}/favicon.svg +0 -0
- /package/{public → dist}/icons.svg +0 -0
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
|
3
|
-
import { toast } from "sonner"
|
|
4
|
-
import { useDyrected } from "../../providers/dyrected-provider"
|
|
5
|
-
import { Button } from "../../components/ui/button"
|
|
6
|
-
import { Input } from "../../components/ui/input"
|
|
7
|
-
import { cn, getMediaUrl } from "../../lib/utils"
|
|
8
|
-
import {
|
|
9
|
-
Card,
|
|
10
|
-
CardContent,
|
|
11
|
-
CardHeader,
|
|
12
|
-
} from "../../components/ui/card"
|
|
13
|
-
import {
|
|
14
|
-
Dialog,
|
|
15
|
-
DialogContent,
|
|
16
|
-
DialogHeader,
|
|
17
|
-
DialogTitle,
|
|
18
|
-
DialogTrigger,
|
|
19
|
-
} from "../../components/ui/dialog"
|
|
20
|
-
import { ScrollArea } from "../../components/ui/scroll-area"
|
|
21
|
-
import { AspectRatio } from "../../components/ui/aspect-ratio"
|
|
22
|
-
import {
|
|
23
|
-
Upload,
|
|
24
|
-
Search,
|
|
25
|
-
FileIcon,
|
|
26
|
-
Trash2,
|
|
27
|
-
ExternalLink,
|
|
28
|
-
Image as ImageIcon,
|
|
29
|
-
Copy,
|
|
30
|
-
Info
|
|
31
|
-
} from "lucide-react"
|
|
32
|
-
import { useDropzone } from "react-dropzone"
|
|
33
|
-
import { Progress } from "../../components/ui/progress"
|
|
34
|
-
import {
|
|
35
|
-
Sheet,
|
|
36
|
-
SheetContent,
|
|
37
|
-
SheetHeader,
|
|
38
|
-
SheetTitle,
|
|
39
|
-
} from "../../components/ui/sheet"
|
|
40
|
-
import { Separator } from "../../components/ui/separator"
|
|
41
|
-
// import { FocalPointPicker } from "../../components/media/focal-point-picker"
|
|
42
|
-
import { Blurhash } from "react-blurhash"
|
|
43
|
-
|
|
44
|
-
export function MediaPage({ collectionSlug, schema }: { collectionSlug?: string, schema?: any }) {
|
|
45
|
-
const { client } = useDyrected()
|
|
46
|
-
const queryClient = useQueryClient()
|
|
47
|
-
const [search, setSearch] = React.useState("")
|
|
48
|
-
const [isUploadOpen, setIsUploadOpen] = React.useState(false)
|
|
49
|
-
const [selectedItem, setSelectedItem] = React.useState<any>(null)
|
|
50
|
-
|
|
51
|
-
const { data: mediaResponse, isLoading } = useQuery({
|
|
52
|
-
queryKey: ["media", collectionSlug, search],
|
|
53
|
-
queryFn: () => client!.listMedia({ where: search ? { filename: { contains: search } } : undefined }, collectionSlug).then(r => r.docs),
|
|
54
|
-
enabled: !!client,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const deleteMutation = useMutation({
|
|
58
|
-
mutationFn: (id: string) => client!.deleteMedia(id, collectionSlug),
|
|
59
|
-
onSuccess: () => {
|
|
60
|
-
queryClient.invalidateQueries({ queryKey: ["media"] })
|
|
61
|
-
toast.success("Asset deleted successfully")
|
|
62
|
-
},
|
|
63
|
-
onError: (error: any) => {
|
|
64
|
-
toast.error("Failed to delete asset", {
|
|
65
|
-
description: error.message
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const updateMutation = useMutation({
|
|
71
|
-
mutationFn: (args: { id: string, data: any }) => client!.update(collectionSlug || "media", args.id, args.data),
|
|
72
|
-
onSuccess: (data) => {
|
|
73
|
-
queryClient.invalidateQueries({ queryKey: ["media"] })
|
|
74
|
-
setSelectedItem(data)
|
|
75
|
-
toast.success("Asset details updated")
|
|
76
|
-
},
|
|
77
|
-
onError: (error: any) => {
|
|
78
|
-
toast.error("Failed to update asset", {
|
|
79
|
-
description: error.message
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const onDrop = React.useCallback((acceptedFiles: File[]) => {
|
|
85
|
-
if (acceptedFiles.length > 0) {
|
|
86
|
-
setIsUploadOpen(true)
|
|
87
|
-
}
|
|
88
|
-
}, [])
|
|
89
|
-
|
|
90
|
-
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
91
|
-
onDrop,
|
|
92
|
-
noClick: true, // Only trigger on drop, not on background click
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<div {...getRootProps()} className="dy-min-h-full dy-space-y-8 dy-animate-in dy-relative">
|
|
97
|
-
<input {...getInputProps()} />
|
|
98
|
-
|
|
99
|
-
{isDragActive && (
|
|
100
|
-
<div className="dy-absolute dy-inset-0 dy-z-50 dy-bg-primary/10 dy-backdrop-blur-[2px] dy-border-4 dy-border-dashed dy-border-primary dy-rounded-2xl dy-flex dy-items-center dy-justify-center dy-pointer-events-none">
|
|
101
|
-
<div className="dy-bg-white dy-p-8 dy-rounded-2xl dy-shadow-2xl dy-flex dy-flex-col dy-items-center dy-gap-4">
|
|
102
|
-
<div className="dy-h-16 dy-w-16 dy-rounded-full dy-bg-primary/10 dy-flex dy-items-center dy-justify-center">
|
|
103
|
-
<Upload className="dy-h-8 dy-w-8 dy-text-primary dy-animate-bounce" />
|
|
104
|
-
</div>
|
|
105
|
-
<p className="dy-text-xl dy-font-bold">Drop to upload assets</p>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
109
|
-
<div className="dy-flex dy-items-end dy-justify-between dy-border-b dy-border-border/50 dy-pb-6">
|
|
110
|
-
<div>
|
|
111
|
-
<div className="dy-flex dy-items-center dy-gap-2 dy-mb-1">
|
|
112
|
-
<ImageIcon className="dy-h-5 dy-w-5 dy-text-primary" />
|
|
113
|
-
<h1 className="dy-text-3xl dy-font-bold dy-tracking-tight dy-text-foreground">
|
|
114
|
-
{schema?.labels?.plural ?? schema?.label ?? "Media Library"}
|
|
115
|
-
</h1>
|
|
116
|
-
</div>
|
|
117
|
-
<p className="dy-text-sm dy-text-muted-foreground">
|
|
118
|
-
Manage your images, documents, and other assets for this site.
|
|
119
|
-
</p>
|
|
120
|
-
</div>
|
|
121
|
-
<Dialog open={isUploadOpen} onOpenChange={setIsUploadOpen}>
|
|
122
|
-
<DialogTrigger asChild>
|
|
123
|
-
<Button className="dy-h-10 dy-px-4 dy-rounded-lg dy-bg-primary hover:dy-bg-primary/90 dy-shadow-md dy-transition-all active:dy-scale-95">
|
|
124
|
-
<Upload className="dy-mr-2 dy-h-4 dy-w-4" />
|
|
125
|
-
Upload Assets
|
|
126
|
-
</Button>
|
|
127
|
-
</DialogTrigger>
|
|
128
|
-
<DialogContent className="sm:dy-max-w-[600px] dy-rounded-2xl dy-overflow-hidden dy-border-none dy-shadow-2xl">
|
|
129
|
-
<DialogHeader className="dy-pb-4 dy-border-b dy-border-border/40">
|
|
130
|
-
<DialogTitle className="dy-text-xl dy-font-bold">Upload Media Assets</DialogTitle>
|
|
131
|
-
</DialogHeader>
|
|
132
|
-
<FileUploader
|
|
133
|
-
collectionSlug={collectionSlug}
|
|
134
|
-
onComplete={() => {
|
|
135
|
-
setIsUploadOpen(false)
|
|
136
|
-
queryClient.invalidateQueries({ queryKey: ["media", collectionSlug] })
|
|
137
|
-
}}
|
|
138
|
-
/>
|
|
139
|
-
</DialogContent>
|
|
140
|
-
</Dialog>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<div className="dy-flex dy-items-center dy-gap-4">
|
|
144
|
-
<div className="dy-relative dy-flex-1 dy-max-w-sm">
|
|
145
|
-
<Search className="dy-absolute dy-left-3 dy-top-1/2 dy--translate-y-1/2 dy-h-4 dy-w-4 dy-text-muted-foreground/60" />
|
|
146
|
-
<Input
|
|
147
|
-
placeholder="Search assets by filename..."
|
|
148
|
-
className="dy-pl-10 dy-h-11 dy-bg-white dy-border-border/60 dy-rounded-xl dy-shadow-sm focus-visible:dy-ring-primary/20"
|
|
149
|
-
value={search}
|
|
150
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
151
|
-
/>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<ScrollArea className="dy-h-[calc(100vh-320px)] dy-pr-4">
|
|
156
|
-
{isLoading ? (
|
|
157
|
-
<div className="dy-flex dy-h-60 dy-items-center dy-justify-center">
|
|
158
|
-
<div className="dy-animate-spin dy-rounded-full dy-border-4 dy-border-primary/20 dy-border-t-primary dy-h-10 dy-w-10"></div>
|
|
159
|
-
</div>
|
|
160
|
-
) : mediaResponse?.length === 0 ? (
|
|
161
|
-
<div className="dy-flex dy-h-80 dy-flex-col dy-items-center dy-justify-center dy-rounded-2xl dy-border-2 dy-border-dashed dy-border-border/60 dy-bg-muted/5 dy-text-center dy-animate-in">
|
|
162
|
-
<div className="dy-h-16 dy-w-16 dy-rounded-2xl dy-bg-muted/40 dy-flex dy-items-center dy-justify-center dy-mb-4">
|
|
163
|
-
<FileIcon className="dy-h-8 dy-w-8 dy-text-muted-foreground/50" />
|
|
164
|
-
</div>
|
|
165
|
-
<h3 className="dy-text-lg dy-font-bold dy-text-foreground">No assets found</h3>
|
|
166
|
-
<p className="dy-text-sm dy-text-muted-foreground dy-max-w-xs dy-mx-auto">
|
|
167
|
-
Your media library is empty. Upload some files to start building your content.
|
|
168
|
-
</p>
|
|
169
|
-
</div>
|
|
170
|
-
) : (
|
|
171
|
-
<div className="dy-grid dy-grid-cols-2 md:dy-grid-cols-3 lg:dy-grid-cols-4 xl:dy-grid-cols-5 2xl:dy-grid-cols-6 dy-gap-6 dy-pb-8">
|
|
172
|
-
{mediaResponse?.map((item) => (
|
|
173
|
-
<MediaCard
|
|
174
|
-
key={item.id}
|
|
175
|
-
item={item}
|
|
176
|
-
baseUrl={client!.getBaseUrl()}
|
|
177
|
-
onDelete={() => deleteMutation.mutate(item.id)}
|
|
178
|
-
onClick={() => setSelectedItem(item)}
|
|
179
|
-
isSelected={selectedItem?.id === item.id}
|
|
180
|
-
/>
|
|
181
|
-
))}
|
|
182
|
-
</div>
|
|
183
|
-
)}
|
|
184
|
-
</ScrollArea>
|
|
185
|
-
|
|
186
|
-
<MediaSidebar
|
|
187
|
-
item={selectedItem}
|
|
188
|
-
onClose={() => setSelectedItem(null)}
|
|
189
|
-
baseUrl={client!.getBaseUrl()}
|
|
190
|
-
onUpdate={(data) => updateMutation.mutate({ id: selectedItem.id, data })}
|
|
191
|
-
/>
|
|
192
|
-
</div>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function MediaCard({ item, baseUrl, onDelete, onClick, isSelected }: {
|
|
197
|
-
item: any,
|
|
198
|
-
baseUrl: string,
|
|
199
|
-
onDelete: () => void,
|
|
200
|
-
onClick: () => void,
|
|
201
|
-
isSelected: boolean
|
|
202
|
-
}) {
|
|
203
|
-
const isImage = item.mimeType?.startsWith("image/")
|
|
204
|
-
const url = getMediaUrl(item, baseUrl)
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<Card
|
|
208
|
-
className={cn(
|
|
209
|
-
"dy-overflow-hidden dy-group dy-relative dy-border-border/40 dy-bg-white dy-shadow-sm hover:dy-shadow-xl dy-transition-all dy-duration-300 dy-rounded-xl dy-cursor-pointer",
|
|
210
|
-
isSelected && "dy-ring-2 dy-ring-primary dy-ring-offset-2 dy-shadow-lg dy-scale-[0.98]"
|
|
211
|
-
)}
|
|
212
|
-
onClick={onClick}
|
|
213
|
-
>
|
|
214
|
-
<CardHeader className="dy-p-0 dy-border-b dy-border-border/10">
|
|
215
|
-
<AspectRatio ratio={1 / 1} className="dy-bg-muted/30 dy-overflow-hidden dy-relative">
|
|
216
|
-
{isImage ? (
|
|
217
|
-
<>
|
|
218
|
-
{item.blurhash && (
|
|
219
|
-
<div className="dy-absolute dy-inset-0 dy-z-0">
|
|
220
|
-
<Blurhash
|
|
221
|
-
hash={item.blurhash}
|
|
222
|
-
width="100%"
|
|
223
|
-
height="100%"
|
|
224
|
-
resolutionX={32}
|
|
225
|
-
resolutionY={32}
|
|
226
|
-
punch={1}
|
|
227
|
-
/>
|
|
228
|
-
</div>
|
|
229
|
-
)}
|
|
230
|
-
<img
|
|
231
|
-
src={url}
|
|
232
|
-
alt={item.filename}
|
|
233
|
-
className="dy-object-cover dy-w-full dy-h-full dy-transition-transform dy-duration-500 dy-group-hover:dy-scale-110 dy-relative dy-z-10"
|
|
234
|
-
loading="lazy"
|
|
235
|
-
/>
|
|
236
|
-
</>
|
|
237
|
-
) : (
|
|
238
|
-
<div className="dy-flex dy-items-center dy-justify-center dy-h-full dy-bg-primary/5">
|
|
239
|
-
<FileIcon className="dy-h-10 dy-w-10 dy-text-primary/40" />
|
|
240
|
-
</div>
|
|
241
|
-
)}
|
|
242
|
-
</AspectRatio>
|
|
243
|
-
</CardHeader>
|
|
244
|
-
<CardContent className="dy-p-3 dy-bg-white">
|
|
245
|
-
<p className="dy-text-[11px] dy-font-bold dy-truncate dy-text-foreground/90 dy-mb-0.5" title={item.filename}>
|
|
246
|
-
{item.filename}
|
|
247
|
-
</p>
|
|
248
|
-
<div className="dy-flex dy-items-center dy-justify-between">
|
|
249
|
-
<p className="dy-text-[9px] dy-text-muted-foreground dy-font-bold dy-uppercase dy-tracking-wider">
|
|
250
|
-
{item.mimeType?.split("/")[1] || "file"}
|
|
251
|
-
</p>
|
|
252
|
-
<p className="dy-text-[9px] dy-text-muted-foreground dy-font-medium">
|
|
253
|
-
{((item.filesize || item.size || 0) / 1024).toFixed(1)} KB
|
|
254
|
-
</p>
|
|
255
|
-
</div>
|
|
256
|
-
</CardContent>
|
|
257
|
-
<div className="dy-absolute dy-top-2 dy-right-2 dy-flex dy-gap-2 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
|
|
258
|
-
<Button
|
|
259
|
-
size="icon"
|
|
260
|
-
variant="destructive"
|
|
261
|
-
className="dy-h-7 dy-w-7 dy-rounded-lg dy-shadow-lg"
|
|
262
|
-
onClick={(e) => {
|
|
263
|
-
e.stopPropagation()
|
|
264
|
-
if (confirm("Are you sure you want to delete this file?")) {
|
|
265
|
-
onDelete()
|
|
266
|
-
}
|
|
267
|
-
}}
|
|
268
|
-
>
|
|
269
|
-
<Trash2 className="dy-h-3.5 dy-w-3.5" />
|
|
270
|
-
</Button>
|
|
271
|
-
</div>
|
|
272
|
-
</Card>
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function MediaSidebar({ item, onClose, baseUrl, onUpdate }: {
|
|
277
|
-
item: any,
|
|
278
|
-
onClose: () => void,
|
|
279
|
-
baseUrl: string,
|
|
280
|
-
onUpdate: (data: any) => void
|
|
281
|
-
}) {
|
|
282
|
-
const [formData, setFormData] = React.useState<any>({})
|
|
283
|
-
const [isSaving, setIsSaving] = React.useState(false)
|
|
284
|
-
|
|
285
|
-
React.useEffect(() => {
|
|
286
|
-
if (item) {
|
|
287
|
-
setFormData({
|
|
288
|
-
alt: item.alt || "",
|
|
289
|
-
caption: item.caption || "",
|
|
290
|
-
filename: item.filename || "",
|
|
291
|
-
})
|
|
292
|
-
}
|
|
293
|
-
}, [item])
|
|
294
|
-
|
|
295
|
-
if (!item) return null
|
|
296
|
-
|
|
297
|
-
const isImage = item.mimeType?.startsWith("image/")
|
|
298
|
-
const url = getMediaUrl(item, baseUrl)
|
|
299
|
-
|
|
300
|
-
const handleSave = async () => {
|
|
301
|
-
setIsSaving(true)
|
|
302
|
-
try {
|
|
303
|
-
await onUpdate(formData)
|
|
304
|
-
} finally {
|
|
305
|
-
setIsSaving(false)
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const hasChanges =
|
|
310
|
-
formData.alt !== (item.alt || "") ||
|
|
311
|
-
formData.caption !== (item.caption || "") ||
|
|
312
|
-
formData.filename !== (item.filename || "")
|
|
313
|
-
|
|
314
|
-
return (
|
|
315
|
-
<Sheet open={!!item} onOpenChange={onClose}>
|
|
316
|
-
<SheetContent className="sm:dy-max-w-md dy-p-0 dy-flex dy-flex-col dy-h-full dy-border-l dy-border-border/40 dy-bg-white dy-shadow-2xl">
|
|
317
|
-
<SheetHeader className="dy-p-6 dy-border-b dy-border-border/40 dy-bg-white">
|
|
318
|
-
<SheetTitle className="dy-flex dy-items-center dy-gap-2">
|
|
319
|
-
<Info className="dy-h-5 dy-w-5 dy-text-primary" />
|
|
320
|
-
File Details
|
|
321
|
-
</SheetTitle>
|
|
322
|
-
</SheetHeader>
|
|
323
|
-
|
|
324
|
-
<ScrollArea className="dy-flex-1 dy-bg-white">
|
|
325
|
-
<div className="dy-p-6 dy-space-y-8">
|
|
326
|
-
<div className="dy-rounded-xl dy-overflow-hidden dy-border dy-border-border/40 dy-bg-muted/10 dy-relative dy-shadow-inner">
|
|
327
|
-
<AspectRatio ratio={16 / 9}>
|
|
328
|
-
{isImage ? (
|
|
329
|
-
<>
|
|
330
|
-
{item.blurhash && (
|
|
331
|
-
<div className="dy-absolute dy-inset-0 dy-z-0">
|
|
332
|
-
<Blurhash
|
|
333
|
-
hash={item.blurhash}
|
|
334
|
-
width="100%"
|
|
335
|
-
height="100%"
|
|
336
|
-
resolutionX={32}
|
|
337
|
-
resolutionY={32}
|
|
338
|
-
punch={1}
|
|
339
|
-
/>
|
|
340
|
-
</div>
|
|
341
|
-
)}
|
|
342
|
-
<img src={url} alt={item.filename} className="dy-object-contain dy-w-full dy-h-full dy-bg-checkered dy-relative dy-z-10" />
|
|
343
|
-
</>
|
|
344
|
-
) : (
|
|
345
|
-
<div className="dy-flex dy-items-center dy-justify-center dy-h-full">
|
|
346
|
-
<FileIcon className="dy-h-16 dy-w-16 dy-text-muted-foreground/30" />
|
|
347
|
-
</div>
|
|
348
|
-
)}
|
|
349
|
-
</AspectRatio>
|
|
350
|
-
</div>
|
|
351
|
-
|
|
352
|
-
<div className="dy-space-y-6">
|
|
353
|
-
<div className="dy-space-y-4">
|
|
354
|
-
<div className="dy-space-y-2">
|
|
355
|
-
<label className="dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-widest dy-text-muted-foreground/80">Filename</label>
|
|
356
|
-
<Input
|
|
357
|
-
value={formData.filename}
|
|
358
|
-
onChange={(e) => setFormData({ ...formData, filename: e.target.value })}
|
|
359
|
-
className="dy-h-10 dy-rounded-lg dy-bg-white dy-border-border/60 focus:dy-ring-1 focus:dy-ring-primary/20"
|
|
360
|
-
/>
|
|
361
|
-
</div>
|
|
362
|
-
<div className="dy-space-y-2">
|
|
363
|
-
<label className="dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-widest dy-text-muted-foreground/80">Alt Text</label>
|
|
364
|
-
<Input
|
|
365
|
-
value={formData.alt}
|
|
366
|
-
onChange={(e) => setFormData({ ...formData, alt: e.target.value })}
|
|
367
|
-
placeholder="Describe the image for accessibility..."
|
|
368
|
-
className="dy-h-10 dy-rounded-lg dy-bg-white dy-border-border/60 focus:dy-ring-1 focus:dy-ring-primary/20"
|
|
369
|
-
/>
|
|
370
|
-
</div>
|
|
371
|
-
<div className="dy-space-y-2">
|
|
372
|
-
<label className="dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-widest dy-text-muted-foreground/80">Caption</label>
|
|
373
|
-
<textarea
|
|
374
|
-
value={formData.caption}
|
|
375
|
-
onChange={(e) => setFormData({ ...formData, caption: e.target.value })}
|
|
376
|
-
placeholder="Add a caption..."
|
|
377
|
-
className="dy-flex dy-min-h-[80px] dy-w-full dy-rounded-lg dy-border dy-border-border/60 dy-bg-white dy-px-3 dy-py-2 dy-text-sm dy-ring-offset-background placeholder:dy-text-muted-foreground focus-visible:dy-outline-none focus-visible:dy-ring-1 focus-visible:dy-ring-primary/20 disabled:dy-cursor-not-allowed disabled:dy-opacity-50"
|
|
378
|
-
/>
|
|
379
|
-
</div>
|
|
380
|
-
</div>
|
|
381
|
-
|
|
382
|
-
<Separator className="dy-bg-border/40" />
|
|
383
|
-
|
|
384
|
-
<div className="dy-grid dy-grid-cols-2 dy-gap-6">
|
|
385
|
-
<DetailItem label="File ID" value={item.id} copyable />
|
|
386
|
-
<DetailItem label="Size" value={`${((item.filesize || item.size || 0) / 1024).toFixed(1)} KB`} />
|
|
387
|
-
<DetailItem label="Type" value={item.mimeType || "Unknown"} />
|
|
388
|
-
<DetailItem label="Dimensions" value={item.width ? `${item.width}x${item.height}` : "N/A"} />
|
|
389
|
-
</div>
|
|
390
|
-
|
|
391
|
-
<DetailItem label="URL" value={url} copyable />
|
|
392
|
-
<DetailItem label="Created At" value={item?.createdAt ? new Date(item?.createdAt).toLocaleString() : "N/A"} />
|
|
393
|
-
</div>
|
|
394
|
-
|
|
395
|
-
{/* {isImage && (
|
|
396
|
-
<div className="dy-space-y-4">
|
|
397
|
-
<Separator className="dy-bg-border/40" />
|
|
398
|
-
<div className="dy-space-y-4">
|
|
399
|
-
<label className="dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-widest dy-text-muted-foreground/80">Focal Point</label>
|
|
400
|
-
<FocalPointPicker
|
|
401
|
-
url={url}
|
|
402
|
-
value={item.focalPoint}
|
|
403
|
-
onChange={(fp) => {
|
|
404
|
-
onUpdate({ focalPoint: fp })
|
|
405
|
-
}}
|
|
406
|
-
/>
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
)} */}
|
|
410
|
-
</div>
|
|
411
|
-
</ScrollArea>
|
|
412
|
-
|
|
413
|
-
<div className="dy-p-6 dy-border-t dy-border-border/40 dy-bg-muted/5 dy-space-y-3">
|
|
414
|
-
{hasChanges && (
|
|
415
|
-
<Button
|
|
416
|
-
className="dy-w-full dy-h-12 dy-rounded-xl dy-font-bold dy-bg-primary dy-text-white dy-shadow-lg dy-shadow-primary/20 dy-animate-in dy-fade-in dy-slide-in-from-bottom-2"
|
|
417
|
-
onClick={handleSave}
|
|
418
|
-
disabled={isSaving}
|
|
419
|
-
>
|
|
420
|
-
{isSaving ? "Saving..." : "Save Changes"}
|
|
421
|
-
</Button>
|
|
422
|
-
)}
|
|
423
|
-
<Button className="dy-w-full dy-h-11 dy-rounded-xl dy-font-bold dy-gap-2 dy-bg-white" variant="outline" asChild>
|
|
424
|
-
<a href={url} target="_blank" rel="noreferrer">
|
|
425
|
-
<ExternalLink className="dy-h-4 dy-w-4" />
|
|
426
|
-
Open Original
|
|
427
|
-
</a>
|
|
428
|
-
</Button>
|
|
429
|
-
</div>
|
|
430
|
-
</SheetContent>
|
|
431
|
-
</Sheet>
|
|
432
|
-
)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function DetailItem({ label, value, copyable }: {
|
|
436
|
-
label: string,
|
|
437
|
-
value: string,
|
|
438
|
-
copyable?: boolean
|
|
439
|
-
}) {
|
|
440
|
-
return (
|
|
441
|
-
<div className="dy-space-y-1.5">
|
|
442
|
-
<label className="dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-widest dy-text-muted-foreground/80">{label}</label>
|
|
443
|
-
<div className="dy-flex dy-items-center dy-gap-2 dy-group">
|
|
444
|
-
<p className="dy-text-sm dy-font-medium dy-text-foreground dy-truncate dy-flex-1">{value}</p>
|
|
445
|
-
{copyable && (
|
|
446
|
-
<Button
|
|
447
|
-
size="icon"
|
|
448
|
-
variant="ghost"
|
|
449
|
-
className="dy-h-7 dy-w-7 dy-text-muted-foreground hover:dy-text-primary dy-transition-colors"
|
|
450
|
-
onClick={() => navigator.clipboard.writeText(value)}
|
|
451
|
-
>
|
|
452
|
-
<Copy className="dy-h-3.5 dy-w-3.5" />
|
|
453
|
-
</Button>
|
|
454
|
-
)}
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function FileUploader({ collectionSlug, onComplete }: { collectionSlug?: string, onComplete: () => void }) {
|
|
461
|
-
const { client } = useDyrected()
|
|
462
|
-
const [files, setFiles] = React.useState<File[]>([])
|
|
463
|
-
const [uploading, setUploading] = React.useState(false)
|
|
464
|
-
const [progress, setProgress] = React.useState(0)
|
|
465
|
-
|
|
466
|
-
const onDrop = React.useCallback((acceptedFiles: File[]) => {
|
|
467
|
-
setFiles(prev => [...prev, ...acceptedFiles])
|
|
468
|
-
}, [])
|
|
469
|
-
|
|
470
|
-
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
|
|
471
|
-
|
|
472
|
-
const handleUpload = async () => {
|
|
473
|
-
if (files.length === 0) return
|
|
474
|
-
setUploading(true)
|
|
475
|
-
setProgress(0)
|
|
476
|
-
|
|
477
|
-
try {
|
|
478
|
-
for (let i = 0; i < files.length; i++) {
|
|
479
|
-
await client!.uploadMedia(files[i], collectionSlug)
|
|
480
|
-
setProgress(((i + 1) / files.length) * 100)
|
|
481
|
-
}
|
|
482
|
-
onComplete()
|
|
483
|
-
toast.success(`${files.length} assets uploaded successfully`)
|
|
484
|
-
} catch (error: any) {
|
|
485
|
-
console.error("Upload failed", error)
|
|
486
|
-
toast.error("Failed to upload assets", {
|
|
487
|
-
description: error.message
|
|
488
|
-
})
|
|
489
|
-
} finally {
|
|
490
|
-
setUploading(false)
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return (
|
|
495
|
-
<div className="dy-space-y-6 dy-py-6 dy-px-4">
|
|
496
|
-
<div
|
|
497
|
-
{...getRootProps()}
|
|
498
|
-
className={`border-2 border-dashed rounded-2xl p-12 text-center cursor-pointer transition-all duration-300 ${isDragActive
|
|
499
|
-
? "border-primary bg-primary/5 scale-[0.98]"
|
|
500
|
-
: "border-muted-foreground/20 hover:border-primary/40 hover:bg-muted/5"
|
|
501
|
-
}`}
|
|
502
|
-
>
|
|
503
|
-
<input {...getInputProps()} />
|
|
504
|
-
<div className="dy-h-16 dy-w-16 dy-rounded-2xl dy-bg-primary/10 dy-flex dy-items-center dy-justify-center dy-mx-auto dy-mb-4">
|
|
505
|
-
<Upload className="dy-h-8 dy-w-8 dy-text-primary" />
|
|
506
|
-
</div>
|
|
507
|
-
<p className="dy-text-xl dy-font-bold dy-text-foreground">Drag & drop assets</p>
|
|
508
|
-
<p className="dy-text-sm dy-text-muted-foreground dy-mt-1">or click to browse your files</p>
|
|
509
|
-
</div>
|
|
510
|
-
|
|
511
|
-
{files.length > 0 && (
|
|
512
|
-
<div className="dy-space-y-4 dy-animate-in dy-fade-in dy-slide-in-from-bottom-4">
|
|
513
|
-
<div className="dy-flex dy-items-center dy-justify-between">
|
|
514
|
-
<p className="dy-text-sm dy-font-bold dy-text-foreground">{files.length} assets selected</p>
|
|
515
|
-
<Button variant="ghost" size="sm" onClick={() => setFiles([])} disabled={uploading} className="dy-text-xs dy-h-8">
|
|
516
|
-
Clear All
|
|
517
|
-
</Button>
|
|
518
|
-
</div>
|
|
519
|
-
|
|
520
|
-
<div className="dy-max-h-[240px] dy-overflow-auto dy-space-y-2 dy-pr-2 dy-custom-scrollbar">
|
|
521
|
-
{files.map((file, idx) => (
|
|
522
|
-
<div key={idx} className="dy-flex dy-items-center dy-justify-between dy-p-3 dy-bg-muted/30 dy-border dy-border-border/40 dy-rounded-xl dy-text-sm dy-group dy-transition-colors hover:dy-bg-muted/50">
|
|
523
|
-
<div className="dy-flex dy-items-center dy-gap-3 dy-truncate">
|
|
524
|
-
<div className="dy-h-8 dy-w-8 dy-rounded-lg dy-bg-white dy-border dy-border-border/60 dy-flex dy-items-center dy-justify-center dy-flex-shrink-0">
|
|
525
|
-
<FileIcon className="dy-h-4 dy-w-4 dy-text-muted-foreground" />
|
|
526
|
-
</div>
|
|
527
|
-
<span className="dy-truncate dy-font-medium dy-text-foreground/80">{file.name}</span>
|
|
528
|
-
</div>
|
|
529
|
-
<span className="dy-text-muted-foreground dy-text-[10px] dy-font-bold dy-bg-white dy-px-2 dy-py-1 dy-rounded dy-border dy-border-border/40 dy-ml-4">
|
|
530
|
-
{(file.size / 1024).toFixed(1)} KB
|
|
531
|
-
</span>
|
|
532
|
-
</div>
|
|
533
|
-
))}
|
|
534
|
-
</div>
|
|
535
|
-
|
|
536
|
-
{uploading && (
|
|
537
|
-
<div className="dy-space-y-2 dy-pt-2">
|
|
538
|
-
<div className="dy-flex dy-justify-between dy-text-[11px] dy-font-bold dy-uppercase dy-tracking-wider dy-text-muted-foreground">
|
|
539
|
-
<span>Uploading...</span>
|
|
540
|
-
<span>{Math.round(progress)}%</span>
|
|
541
|
-
</div>
|
|
542
|
-
<Progress value={progress} className="dy-h-2 dy-rounded-full" />
|
|
543
|
-
</div>
|
|
544
|
-
)}
|
|
545
|
-
|
|
546
|
-
<div className="dy-flex dy-justify-end dy-pt-4 dy-border-t dy-border-border/40">
|
|
547
|
-
<Button
|
|
548
|
-
onClick={handleUpload}
|
|
549
|
-
disabled={uploading || files.length === 0}
|
|
550
|
-
className="dy-w-full dy-h-12 dy-rounded-xl dy-bg-primary hover:dy-bg-primary/90 dy-text-white dy-font-bold dy-shadow-lg dy-shadow-primary/20 dy-transition-all active:dy-scale-[0.98]"
|
|
551
|
-
>
|
|
552
|
-
{uploading ? (
|
|
553
|
-
<span className="dy-flex dy-items-center dy-gap-2">
|
|
554
|
-
<div className="dy-h-4 dy-w-4 dy-border-2 dy-border-white/30 dy-border-t-white dy-rounded-full dy-animate-spin" />
|
|
555
|
-
Uploading Assets...
|
|
556
|
-
</span>
|
|
557
|
-
) : `Upload ${files.length} Assets`}
|
|
558
|
-
</Button>
|
|
559
|
-
</div>
|
|
560
|
-
</div>
|
|
561
|
-
)}
|
|
562
|
-
</div>
|
|
563
|
-
)
|
|
564
|
-
}
|