@dyrected/admin 2.4.0 → 2.4.1
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,465 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { useQuery } from "@tanstack/react-query"
|
|
3
|
-
import { useDyrected } from "../../providers/dyrected-provider"
|
|
4
|
-
import { Button } from "../ui/button"
|
|
5
|
-
import {
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogContent,
|
|
8
|
-
DialogTitle,
|
|
9
|
-
} from "../ui/dialog"
|
|
10
|
-
import {
|
|
11
|
-
Tabs,
|
|
12
|
-
TabsContent,
|
|
13
|
-
TabsList,
|
|
14
|
-
TabsTrigger
|
|
15
|
-
} from "../ui/tabs"
|
|
16
|
-
import {
|
|
17
|
-
Image as ImageIcon,
|
|
18
|
-
Video,
|
|
19
|
-
Search,
|
|
20
|
-
Upload,
|
|
21
|
-
Library,
|
|
22
|
-
Check,
|
|
23
|
-
Link as LinkIcon,
|
|
24
|
-
Globe,
|
|
25
|
-
Info,
|
|
26
|
-
Sparkles
|
|
27
|
-
} from "lucide-react"
|
|
28
|
-
import { ScrollArea } from "../ui/scroll-area"
|
|
29
|
-
import { Input } from "../ui/input"
|
|
30
|
-
import { getMediaUrl, cn } from "../../lib/utils"
|
|
31
|
-
|
|
32
|
-
interface MediaLibraryDialogProps {
|
|
33
|
-
collection: string
|
|
34
|
-
isOpen: boolean
|
|
35
|
-
onOpenChange: (open: boolean) => void
|
|
36
|
-
selectedValues: string[]
|
|
37
|
-
onSelect: (id: string) => void
|
|
38
|
-
multiple?: boolean
|
|
39
|
-
onConfirm?: (selectedIds: string[]) => void
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function MediaLibraryDialog({
|
|
43
|
-
collection,
|
|
44
|
-
isOpen,
|
|
45
|
-
onOpenChange,
|
|
46
|
-
selectedValues,
|
|
47
|
-
onSelect,
|
|
48
|
-
multiple,
|
|
49
|
-
onConfirm
|
|
50
|
-
}: MediaLibraryDialogProps) {
|
|
51
|
-
const { client } = useDyrected()
|
|
52
|
-
const [searchQuery, setSearchQuery] = React.useState("")
|
|
53
|
-
const [externalUrl, setExternalUrl] = React.useState("")
|
|
54
|
-
const [activeTab, setActiveTab] = React.useState("library")
|
|
55
|
-
const [selectedItem, setSelectedItem] = React.useState<any>(null)
|
|
56
|
-
const [isUploading, setIsUploading] = React.useState(false)
|
|
57
|
-
|
|
58
|
-
const { data: media, refetch } = useQuery({
|
|
59
|
-
queryKey: [collection, searchQuery],
|
|
60
|
-
queryFn: () => client!.listMedia({
|
|
61
|
-
where: searchQuery ? { filename: { contains: searchQuery } } : undefined
|
|
62
|
-
}, collection).then((r: any) => r.docs),
|
|
63
|
-
enabled: isOpen && !!client,
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
67
|
-
const file = e.target.files?.[0]
|
|
68
|
-
if (!file || !client) return
|
|
69
|
-
|
|
70
|
-
setIsUploading(true)
|
|
71
|
-
try {
|
|
72
|
-
const result = await client.collection(collection).upload(file, {})
|
|
73
|
-
await refetch()
|
|
74
|
-
onSelect(result.id)
|
|
75
|
-
if (!multiple) onOpenChange(false)
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error("Upload failed:", error)
|
|
78
|
-
alert("Upload failed. Please try again.")
|
|
79
|
-
} finally {
|
|
80
|
-
setIsUploading(false)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const handleExternalUrlSubmit = async () => {
|
|
85
|
-
if (!externalUrl || !client) return
|
|
86
|
-
|
|
87
|
-
setIsUploading(true)
|
|
88
|
-
try {
|
|
89
|
-
let mimeType = 'application/octet-stream'
|
|
90
|
-
let filename = 'External Asset'
|
|
91
|
-
let idPrefix = 'ext'
|
|
92
|
-
|
|
93
|
-
// YouTube Detection
|
|
94
|
-
const ytMatch = externalUrl.match(/(?:youtu\.be\/|youtube\.com\/(?:v\/|u\/\w\/|embed\/|watch\?v=))([^#\&\?]*)/)
|
|
95
|
-
if (ytMatch && ytMatch[1]) {
|
|
96
|
-
mimeType = 'video/youtube'
|
|
97
|
-
filename = `YouTube: ${ytMatch[1]}`
|
|
98
|
-
idPrefix = `yt_${ytMatch[1]}`
|
|
99
|
-
}
|
|
100
|
-
// Vimeo Detection
|
|
101
|
-
else if (externalUrl.match(/vimeo\.com\/(?:video\/)?([0-9]+)/)) {
|
|
102
|
-
const vimeoId = externalUrl.match(/vimeo\.com\/(?:video\/)?([0-9]+)/)![1]
|
|
103
|
-
mimeType = 'video/vimeo'
|
|
104
|
-
filename = `Vimeo: ${vimeoId}`
|
|
105
|
-
idPrefix = `vm_${vimeoId}`
|
|
106
|
-
}
|
|
107
|
-
// Image Detection
|
|
108
|
-
else if (externalUrl.match(/\.(jpeg|jpg|gif|png|webp|svg|avif)(?:\?.*)?$/i)) {
|
|
109
|
-
mimeType = 'image/external'
|
|
110
|
-
filename = externalUrl.split('/').pop()?.split('?')[0] || 'External Image'
|
|
111
|
-
idPrefix = `img_${Math.random().toString(36).substring(7)}`
|
|
112
|
-
}
|
|
113
|
-
// Default / Generic
|
|
114
|
-
else {
|
|
115
|
-
filename = externalUrl.split('/').pop()?.split('?')[0] || 'External File'
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const result = await client.collection(collection).create({
|
|
119
|
-
filename,
|
|
120
|
-
url: externalUrl,
|
|
121
|
-
mimeType,
|
|
122
|
-
filesize: 0,
|
|
123
|
-
id: idPrefix
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
await refetch()
|
|
127
|
-
onSelect(result.id)
|
|
128
|
-
if (!multiple) onOpenChange(false)
|
|
129
|
-
setExternalUrl("")
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error("Failed to add external URL:", error)
|
|
132
|
-
alert("Failed to add URL. Please make sure it is valid.")
|
|
133
|
-
} finally {
|
|
134
|
-
setIsUploading(false)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const getPreviewUrl = (item: any) => {
|
|
139
|
-
if (!item) return ""
|
|
140
|
-
if (item.mimeType === 'video/youtube') {
|
|
141
|
-
const match = item.url?.match(/(?:youtu\.be\/|youtube\.com\/(?:v\/|u\/\w\/|embed\/|watch\?v=))([^#\&\?]*)/)
|
|
142
|
-
const videoId = match && match[1]
|
|
143
|
-
return `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`
|
|
144
|
-
}
|
|
145
|
-
if (item.mimeType === 'video/vimeo') {
|
|
146
|
-
// Vimeo thumbnails are harder to get purely client side without API,
|
|
147
|
-
// but we can use a placeholder or better, try to fetch if we had a proper utility.
|
|
148
|
-
// For now, let's use a generic vimeo-style placeholder or icon
|
|
149
|
-
return "https://vimeo.com/assets/images/logo_vimeo_blue.png"
|
|
150
|
-
}
|
|
151
|
-
if (item.mimeType === 'image/external') {
|
|
152
|
-
return item.url
|
|
153
|
-
}
|
|
154
|
-
return getMediaUrl(item, client?.getBaseUrl() || "");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const handleConfirm = () => {
|
|
158
|
-
if (onConfirm) {
|
|
159
|
-
onConfirm(selectedValues)
|
|
160
|
-
}
|
|
161
|
-
onOpenChange(false)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
|
166
|
-
<DialogContent className="sm:dy-max-w-[900px] dy-p-0 dy-overflow-hidden dy-gap-0 dy-bg-background dy-border-none dy-shadow-2xl">
|
|
167
|
-
<Tabs value={activeTab} onValueChange={setActiveTab} className="dy-flex dy-flex-col dy-h-[650px]">
|
|
168
|
-
<div className="dy-px-6 dy-py-4 dy-border-b dy-flex dy-items-center dy-justify-between dy-bg-muted/20">
|
|
169
|
-
<div className="dy-flex dy-items-center dy-gap-4">
|
|
170
|
-
<DialogTitle className="dy-text-xl dy-font-serif dy-font-bold dy-tracking-tight">Media Library</DialogTitle>
|
|
171
|
-
{multiple && selectedValues.length > 0 && (
|
|
172
|
-
<div className="dy-flex dy-items-center dy-gap-2 dy-px-3 dy-py-1 dy-bg-primary/10 dy-rounded-full dy-border dy-border-primary/20 dy-animate-in dy-fade-in dy-slide-in-from-left-2">
|
|
173
|
-
<span className="dy-text-xs dy-font-bold dy-text-primary">{selectedValues.length} Selected</span>
|
|
174
|
-
<Button variant="ghost" size="icon" className="dy-h-4 dy-w-4 dy-text-primary hover:dy-bg-transparent" onClick={handleConfirm}>
|
|
175
|
-
<Check className="dy-h-3 dy-w-3" />
|
|
176
|
-
</Button>
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
</div>
|
|
180
|
-
<TabsList className="dy-bg-muted/50 dy-p-1 dy-rounded-xl">
|
|
181
|
-
<TabsTrigger value="library" className="dy-gap-2 dy-rounded-lg dy-px-4 dy-font-bold dy-text-xs dy-uppercase dy-tracking-wider dy-transition-all data-[state=active]:dy-bg-background data-[state=active]:dy-shadow-sm">
|
|
182
|
-
<Library className="dy-h-3.5 dy-w-3.5" /> Library
|
|
183
|
-
</TabsTrigger>
|
|
184
|
-
<TabsTrigger value="upload" className="dy-gap-2 dy-rounded-lg dy-px-4 dy-font-bold dy-text-xs dy-uppercase dy-tracking-wider dy-transition-all data-[state=active]:dy-bg-background data-[state=active]:dy-shadow-sm">
|
|
185
|
-
<Upload className="dy-h-3.5 dy-w-3.5" /> Upload
|
|
186
|
-
</TabsTrigger>
|
|
187
|
-
<TabsTrigger value="external" className="dy-gap-2 dy-rounded-lg dy-px-4 dy-font-bold dy-text-xs dy-uppercase dy-tracking-wider dy-transition-all data-[state=active]:dy-bg-background data-[state=active]:dy-shadow-sm">
|
|
188
|
-
<Globe className="dy-h-3.5 dy-w-3.5" /> External URL
|
|
189
|
-
</TabsTrigger>
|
|
190
|
-
</TabsList>
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
<div className="dy-flex-1 dy-overflow-hidden">
|
|
194
|
-
<TabsContent value="library" className="dy-h-full dy-m-0 dy-p-0 focus-visible:dy-ring-0">
|
|
195
|
-
<div className="dy-flex dy-h-full">
|
|
196
|
-
<div className="dy-flex-1 dy-flex dy-flex-col dy-p-6 dy-space-y-4 dy-border-r">
|
|
197
|
-
<div className="dy-flex dy-items-center dy-gap-4">
|
|
198
|
-
<div className="dy-relative dy-flex-1 dy-group">
|
|
199
|
-
<Search className="dy-absolute dy-left-3.5 dy-top-1/2 dy--translate-y-1/2 dy-h-4 dy-w-4 dy-text-muted-foreground dy-group-focus-within:dy-text-primary dy-transition-colors" />
|
|
200
|
-
<Input
|
|
201
|
-
placeholder="Search your media library..."
|
|
202
|
-
className="dy-pl-11 dy-h-11 dy-rounded-xl dy-border-muted dy-bg-muted/10 focus:dy-bg-background dy-transition-all"
|
|
203
|
-
value={searchQuery}
|
|
204
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
205
|
-
/>
|
|
206
|
-
</div>
|
|
207
|
-
{multiple && (
|
|
208
|
-
<div className="dy-flex dy-items-center dy-gap-1 dy-bg-muted/30 dy-p-1 dy-rounded-lg">
|
|
209
|
-
<Button
|
|
210
|
-
variant="ghost"
|
|
211
|
-
size="sm"
|
|
212
|
-
className="dy-h-8 dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-wider dy-px-3 hover:dy-bg-background dy-rounded-md"
|
|
213
|
-
onClick={() => {
|
|
214
|
-
media?.forEach((item: any) => {
|
|
215
|
-
if (!selectedValues.includes(item.id)) onSelect(item.id)
|
|
216
|
-
})
|
|
217
|
-
}}
|
|
218
|
-
>
|
|
219
|
-
Select All
|
|
220
|
-
</Button>
|
|
221
|
-
<div className="dy-w-px dy-h-4 dy-bg-border/50 dy-mx-1" />
|
|
222
|
-
<Button
|
|
223
|
-
variant="ghost"
|
|
224
|
-
size="sm"
|
|
225
|
-
className="dy-h-8 dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-wider dy-px-3 dy-text-destructive hover:dy-text-destructive hover:dy-bg-destructive/10 dy-rounded-md"
|
|
226
|
-
onClick={() => {
|
|
227
|
-
selectedValues.forEach(id => onSelect(id))
|
|
228
|
-
}}
|
|
229
|
-
>
|
|
230
|
-
Clear
|
|
231
|
-
</Button>
|
|
232
|
-
</div>
|
|
233
|
-
)}
|
|
234
|
-
</div>
|
|
235
|
-
<ScrollArea className="dy-flex-1 dy--mx-2 dy-px-2">
|
|
236
|
-
<div className="dy-grid dy-grid-cols-3 sm:dy-grid-cols-4 md:dy-grid-cols-5 dy-gap-4 dy-pb-4">
|
|
237
|
-
{media?.map((item: any) => (
|
|
238
|
-
<button
|
|
239
|
-
key={item.id}
|
|
240
|
-
type="button"
|
|
241
|
-
onClick={() => {
|
|
242
|
-
if (multiple) {
|
|
243
|
-
onSelect(item.id)
|
|
244
|
-
setSelectedItem(item)
|
|
245
|
-
} else {
|
|
246
|
-
if (selectedItem?.id === item.id) {
|
|
247
|
-
onSelect(item.id)
|
|
248
|
-
onOpenChange(false)
|
|
249
|
-
} else {
|
|
250
|
-
setSelectedItem(item)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}}
|
|
254
|
-
className={cn(
|
|
255
|
-
"dy-relative dy-group dy-rounded-2xl dy-overflow-hidden dy-border-2 dy-aspect-square dy-transition-all hover:dy-scale-[1.02] active:dy-scale-95 dy-shadow-sm dy-bg-muted/5",
|
|
256
|
-
selectedItem?.id === item.id
|
|
257
|
-
? "dy-border-primary dy-ring-4 dy-ring-primary/10 dy-shadow-lg dy-shadow-primary/5"
|
|
258
|
-
: "dy-border-border/40 hover:dy-border-border"
|
|
259
|
-
)}
|
|
260
|
-
>
|
|
261
|
-
<img
|
|
262
|
-
src={getPreviewUrl(item)}
|
|
263
|
-
alt={item.filename}
|
|
264
|
-
className="dy-object-cover dy-w-full dy-h-full"
|
|
265
|
-
/>
|
|
266
|
-
{selectedValues.includes(item.id) && (
|
|
267
|
-
<div className="dy-absolute dy-top-2.5 dy-right-2.5 dy-h-7 dy-w-7 dy-bg-primary dy-rounded-full dy-flex dy-items-center dy-justify-center dy-text-white dy-shadow-xl dy-animate-in dy-zoom-in dy-border-2 dy-border-white">
|
|
268
|
-
<Check className="dy-h-4 dy-w-4" />
|
|
269
|
-
</div>
|
|
270
|
-
)}
|
|
271
|
-
{(item.mimeType?.startsWith('video/') || item.mimeType === 'video/youtube' || item.mimeType === 'video/vimeo') && (
|
|
272
|
-
<div className="dy-absolute dy-inset-0 dy-flex dy-items-center dy-justify-center dy-bg-black/20 dy-group-hover:dy-bg-black/40 dy-transition-colors">
|
|
273
|
-
<div className="dy-h-10 dy-w-10 dy-bg-white/20 dy-backdrop-blur-md dy-rounded-full dy-flex dy-items-center dy-justify-center dy-border dy-border-white/30 dy-shadow-2xl">
|
|
274
|
-
<Video className="dy-h-5 dy-w-5 dy-text-white" />
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
)}
|
|
278
|
-
<div className="dy-absolute dy-inset-x-0 dy-bottom-0 dy-p-2.5 dy-bg-gradient-to-t dy-from-black/80 dy-via-black/40 dy-to-transparent dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity">
|
|
279
|
-
<p className="dy-text-[10px] dy-text-white dy-truncate dy-font-bold dy-uppercase dy-tracking-wider">{item.filename}</p>
|
|
280
|
-
</div>
|
|
281
|
-
</button>
|
|
282
|
-
))}
|
|
283
|
-
</div>
|
|
284
|
-
</ScrollArea>
|
|
285
|
-
</div>
|
|
286
|
-
|
|
287
|
-
<div className="dy-w-80 dy-bg-muted/5 dy-p-6 dy-flex dy-flex-col dy-gap-6 dy-overflow-y-auto dy-border-l dy-border-muted/20">
|
|
288
|
-
{selectedItem ? (
|
|
289
|
-
<>
|
|
290
|
-
<div className="dy-space-y-5">
|
|
291
|
-
<div className="dy-aspect-square dy-rounded-3xl dy-overflow-hidden dy-border dy-bg-background dy-shadow-2xl dy-group dy-relative dy-ring-1 dy-ring-border/50">
|
|
292
|
-
<img
|
|
293
|
-
src={getPreviewUrl(selectedItem)}
|
|
294
|
-
className="dy-w-full dy-h-full dy-object-contain dy-p-2"
|
|
295
|
-
alt=""
|
|
296
|
-
/>
|
|
297
|
-
<div className="dy-absolute dy-inset-0 dy-bg-black/40 dy-opacity-0 dy-group-hover:dy-opacity-100 dy-transition-opacity dy-flex dy-items-center dy-justify-center dy-backdrop-blur-sm">
|
|
298
|
-
<Button variant="secondary" size="sm" className="dy-rounded-full dy-shadow-lg dy-font-bold" onClick={() => window.open(getPreviewUrl(selectedItem), '_blank')}>
|
|
299
|
-
View Full
|
|
300
|
-
</Button>
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
<div className="dy-space-y-2">
|
|
304
|
-
<h4 className="dy-font-bold dy-text-sm dy-truncate dy-leading-tight" title={selectedItem.filename}>
|
|
305
|
-
{selectedItem.filename}
|
|
306
|
-
</h4>
|
|
307
|
-
<div className="dy-flex dy-flex-wrap dy-items-center dy-gap-2">
|
|
308
|
-
<span className="dy-text-[9px] dy-font-black dy-uppercase dy-tracking-widest dy-bg-primary/10 dy-text-primary dy-px-2 dy-py-1 dy-rounded-md dy-border dy-border-primary/10">
|
|
309
|
-
{selectedItem.mimeType?.split('/')[1] || selectedItem.mimeType}
|
|
310
|
-
</span>
|
|
311
|
-
<span className="dy-text-[10px] dy-font-bold dy-text-muted-foreground/60">
|
|
312
|
-
{selectedItem.filesize ? `${(selectedItem.filesize / 1024).toFixed(1)} KB` : 'External Asset'}
|
|
313
|
-
</span>
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
|
-
|
|
318
|
-
<div className="dy-space-y-3 dy-pt-6 dy-border-t dy-border-muted/20">
|
|
319
|
-
{multiple ? (
|
|
320
|
-
<>
|
|
321
|
-
<Button
|
|
322
|
-
className="dy-w-full dy-h-11 dy-rounded-xl dy-shadow-sm dy-font-bold dy-tracking-tight dy-transition-all"
|
|
323
|
-
variant={selectedValues.includes(selectedItem.id) ? "outline" : "default"}
|
|
324
|
-
onClick={() => onSelect(selectedItem.id)}
|
|
325
|
-
>
|
|
326
|
-
{selectedValues.includes(selectedItem.id) ? "Deselect Item" : "Add to Selection"}
|
|
327
|
-
</Button>
|
|
328
|
-
{selectedValues.length > 0 && (
|
|
329
|
-
<Button
|
|
330
|
-
className="dy-w-full dy-h-11 dy-rounded-xl dy-shadow-xl dy-bg-primary hover:dy-bg-primary/90 dy-font-bold dy-tracking-tight dy-transition-all dy-group"
|
|
331
|
-
onClick={handleConfirm}
|
|
332
|
-
>
|
|
333
|
-
<span>Confirm {selectedValues.length} {selectedValues.length === 1 ? 'Asset' : 'Assets'}</span>
|
|
334
|
-
<Sparkles className="dy-ml-2 dy-h-4 dy-w-4 dy-opacity-50 dy-group-hover:dy-opacity-100 dy-group-hover:dy-scale-110 dy-transition-all" />
|
|
335
|
-
</Button>
|
|
336
|
-
)}
|
|
337
|
-
</>
|
|
338
|
-
) : (
|
|
339
|
-
<Button
|
|
340
|
-
className="dy-w-full dy-h-11 dy-rounded-xl dy-shadow-lg dy-font-bold dy-tracking-tight dy-bg-primary hover:dy-bg-primary/90 dy-transition-all"
|
|
341
|
-
onClick={() => {
|
|
342
|
-
onSelect(selectedItem.id)
|
|
343
|
-
onOpenChange(false)
|
|
344
|
-
}}
|
|
345
|
-
>
|
|
346
|
-
Select Media
|
|
347
|
-
</Button>
|
|
348
|
-
)}
|
|
349
|
-
</div>
|
|
350
|
-
</>
|
|
351
|
-
) : (
|
|
352
|
-
<div className="dy-flex-1 dy-flex dy-flex-col dy-items-center dy-justify-center dy-text-center dy-space-y-5 dy-text-muted-foreground/30">
|
|
353
|
-
<div className="dy-p-6 dy-bg-muted/10 dy-rounded-full dy-border dy-border-muted/20 dy-shadow-inner">
|
|
354
|
-
<ImageIcon className="dy-h-10 dy-w-10" />
|
|
355
|
-
</div>
|
|
356
|
-
<div className="dy-space-y-1">
|
|
357
|
-
<p className="dy-text-xs dy-font-bold dy-uppercase dy-tracking-widest">No Selection</p>
|
|
358
|
-
<p className="dy-text-[10px] dy-font-medium dy-max-w-[150px] dy-leading-relaxed">Select an item from the library to view details and metadata</p>
|
|
359
|
-
</div>
|
|
360
|
-
</div>
|
|
361
|
-
)}
|
|
362
|
-
</div>
|
|
363
|
-
</div>
|
|
364
|
-
</TabsContent>
|
|
365
|
-
|
|
366
|
-
<TabsContent value="upload" className="dy-h-full dy-m-0 dy-p-8 focus-visible:dy-ring-0">
|
|
367
|
-
<div className="dy-h-full dy-flex dy-flex-col dy-items-center dy-justify-center dy-border-2 dy-border-dashed dy-border-primary/20 dy-rounded-[2.5rem] dy-bg-primary/5 hover:dy-bg-primary/10 dy-transition-all dy-group dy-relative dy-overflow-hidden">
|
|
368
|
-
<div className="dy-absolute dy-inset-0 dy-bg-[radial-gradient(circle_at_center,var(--primary)_0%,transparent_100%)] dy-opacity-[0.03]" />
|
|
369
|
-
<input
|
|
370
|
-
type="file"
|
|
371
|
-
id="media-upload-dialog"
|
|
372
|
-
className="dy-hidden"
|
|
373
|
-
onChange={handleUpload}
|
|
374
|
-
disabled={isUploading}
|
|
375
|
-
/>
|
|
376
|
-
<label
|
|
377
|
-
htmlFor="media-upload-dialog"
|
|
378
|
-
className="dy-flex dy-flex-col dy-items-center dy-gap-8 dy-cursor-pointer dy-p-12 dy-text-center dy-relative dy-z-10"
|
|
379
|
-
>
|
|
380
|
-
<div className="dy-h-24 dy-w-24 dy-bg-background dy-rounded-full dy-flex dy-items-center dy-justify-center dy-text-primary dy-group-hover:dy-scale-110 dy-transition-all dy-shadow-2xl dy-shadow-primary/20 dy-border dy-border-primary/10">
|
|
381
|
-
<Upload className="dy-h-10 dy-w-10" />
|
|
382
|
-
</div>
|
|
383
|
-
<div className="dy-space-y-2">
|
|
384
|
-
<p className="dy-font-serif dy-font-bold dy-text-3xl dy-tracking-tight">Upload new assets</p>
|
|
385
|
-
<p className="dy-text-muted-foreground/60 dy-font-medium">Drag and drop files here or click to browse your computer</p>
|
|
386
|
-
</div>
|
|
387
|
-
<Button variant="secondary" className="dy-rounded-full dy-px-8 dy-h-12 dy-font-bold dy-shadow-sm dy-pointer-events-none dy-group-hover:dy-bg-primary dy-group-hover:dy-text-white dy-transition-all">
|
|
388
|
-
Choose Files
|
|
389
|
-
</Button>
|
|
390
|
-
</label>
|
|
391
|
-
</div>
|
|
392
|
-
</TabsContent>
|
|
393
|
-
|
|
394
|
-
<TabsContent value="external" className="dy-h-full dy-m-0 dy-p-12 focus-visible:dy-ring-0">
|
|
395
|
-
<div className="dy-max-w-2xl dy-mx-auto dy-space-y-10 dy-pt-4">
|
|
396
|
-
<div className="dy-text-center dy-space-y-4">
|
|
397
|
-
<div className="dy-h-20 dy-w-20 dy-bg-primary/10 dy-text-primary dy-rounded-3xl dy-flex dy-items-center dy-justify-center dy-mx-auto dy-mb-6 dy-shadow-sm dy-rotate-3 dy-group-hover:dy-rotate-0 dy-transition-transform">
|
|
398
|
-
<Globe className="dy-h-10 dy-w-10" />
|
|
399
|
-
</div>
|
|
400
|
-
<h3 className="dy-text-3xl dy-font-serif dy-font-bold dy-tracking-tight">Add External Resource</h3>
|
|
401
|
-
<p className="dy-text-sm dy-text-muted-foreground/70 dy-leading-relaxed dy-max-w-md dy-mx-auto">
|
|
402
|
-
Paste a link to any image, YouTube video, Vimeo link, or file to add it to your library without uploading.
|
|
403
|
-
</p>
|
|
404
|
-
</div>
|
|
405
|
-
|
|
406
|
-
<div className="dy-space-y-6">
|
|
407
|
-
<div className="dy-flex dy-gap-3">
|
|
408
|
-
<div className="dy-relative dy-flex-1">
|
|
409
|
-
<LinkIcon className="dy-absolute dy-left-4 dy-top-1/2 dy--translate-y-1/2 dy-h-4 dy-w-4 dy-text-muted-foreground" />
|
|
410
|
-
<Input
|
|
411
|
-
placeholder="https://example.com/image.jpg or video link..."
|
|
412
|
-
className="dy-h-14 dy-rounded-2xl dy-shadow-xl dy-border-muted dy-bg-muted/5 dy-pl-12 dy-text-base dy-font-medium focus:dy-bg-background dy-transition-all"
|
|
413
|
-
value={externalUrl}
|
|
414
|
-
onChange={(e) => setExternalUrl(e.target.value)}
|
|
415
|
-
/>
|
|
416
|
-
</div>
|
|
417
|
-
<Button
|
|
418
|
-
onClick={handleExternalUrlSubmit}
|
|
419
|
-
disabled={isUploading || !externalUrl}
|
|
420
|
-
className="dy-h-14 dy-rounded-2xl dy-px-10 dy-font-bold dy-shadow-xl dy-shadow-primary/20 dy-bg-primary hover:dy-bg-primary/90 dy-transition-all active:dy-scale-95"
|
|
421
|
-
>
|
|
422
|
-
{isUploading ? "Adding..." : "Add URL"}
|
|
423
|
-
</Button>
|
|
424
|
-
</div>
|
|
425
|
-
|
|
426
|
-
<div className="dy-grid dy-grid-cols-2 dy-gap-4">
|
|
427
|
-
<div className="dy-p-4 dy-rounded-2xl dy-bg-red-50/50 dy-border dy-border-red-100 dy-flex dy-items-start dy-gap-3">
|
|
428
|
-
<div className="dy-mt-0.5 dy-p-1.5 dy-bg-red-100 dy-rounded-lg dy-text-red-600">
|
|
429
|
-
<Video className="dy-h-4 dy-w-4" />
|
|
430
|
-
</div>
|
|
431
|
-
<div>
|
|
432
|
-
<p className="dy-text-xs dy-font-bold dy-text-red-900">Video Streaming</p>
|
|
433
|
-
<p className="dy-text-[10px] dy-text-red-700/70 dy-leading-relaxed dy-font-medium dy-mt-0.5">Supports YouTube & Vimeo. We recommend these for the best performance and compatibility.</p>
|
|
434
|
-
</div>
|
|
435
|
-
</div>
|
|
436
|
-
<div className="dy-p-4 dy-rounded-2xl dy-bg-blue-50/50 dy-border dy-border-blue-100 dy-flex dy-items-start dy-gap-3">
|
|
437
|
-
<div className="dy-mt-0.5 dy-p-1.5 dy-bg-blue-100 dy-rounded-lg dy-text-blue-600">
|
|
438
|
-
<ImageIcon className="dy-h-4 dy-w-4" />
|
|
439
|
-
</div>
|
|
440
|
-
<div>
|
|
441
|
-
<p className="dy-text-xs dy-font-bold dy-text-blue-900">External Assets</p>
|
|
442
|
-
<p className="dy-text-[10px] dy-text-blue-700/70 dy-leading-relaxed dy-font-medium dy-mt-0.5">Add direct links to images or files from other CDNs. We'll automatically detect the type.</p>
|
|
443
|
-
</div>
|
|
444
|
-
</div>
|
|
445
|
-
</div>
|
|
446
|
-
|
|
447
|
-
<div className="dy-flex dy-items-center dy-gap-3 dy-p-4 dy-rounded-2xl dy-bg-muted/20 dy-border dy-border-muted/30">
|
|
448
|
-
<div className="dy-p-2 dy-bg-background dy-rounded-xl dy-shadow-sm dy-text-muted-foreground">
|
|
449
|
-
<Info className="dy-h-4 dy-w-4" />
|
|
450
|
-
</div>
|
|
451
|
-
<p className="dy-text-[11px] dy-text-muted-foreground/80 dy-font-medium dy-leading-relaxed">
|
|
452
|
-
<span className="dy-font-bold dy-text-foreground">Pro Tip:</span> External videos are better streamed from
|
|
453
|
-
<span className="dy-text-red-600 dy-font-bold dy-ml-1">YouTube</span> or
|
|
454
|
-
<span className="dy-text-blue-500 dy-font-bold dy-ml-1">Vimeo</span> to ensure smooth playback on all devices.
|
|
455
|
-
</p>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
</div>
|
|
459
|
-
</TabsContent>
|
|
460
|
-
</div>
|
|
461
|
-
</Tabs>
|
|
462
|
-
</DialogContent>
|
|
463
|
-
</Dialog>
|
|
464
|
-
)
|
|
465
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
|
|
4
|
-
import { cn } from "../../lib/utils"
|
|
5
|
-
|
|
6
|
-
const badgeVariants = cva(
|
|
7
|
-
"dy-inline-flex dy-items-center dy-rounded-full dy-border dy-px-2.5 dy-py-0.5 dy-text-xs dy-font-semibold dy-transition-colors focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-ring focus:dy-ring-offset-2",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default:
|
|
12
|
-
"dy-border-transparent dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/80",
|
|
13
|
-
secondary:
|
|
14
|
-
"dy-border-transparent dy-bg-secondary dy-text-secondary-foreground hover:dy-bg-secondary/80",
|
|
15
|
-
destructive:
|
|
16
|
-
"dy-border-transparent dy-bg-destructive dy-text-destructive-foreground hover:dy-bg-destructive/80",
|
|
17
|
-
outline: "dy-text-foreground",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
defaultVariants: {
|
|
21
|
-
variant: "default",
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export interface BadgeProps
|
|
27
|
-
extends React.HTMLAttributes<HTMLDivElement>,
|
|
28
|
-
VariantProps<typeof badgeVariants> {}
|
|
29
|
-
|
|
30
|
-
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
31
|
-
return (
|
|
32
|
-
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export { Badge, badgeVariants }
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils"
|
|
6
|
-
|
|
7
|
-
const buttonVariants = cva(
|
|
8
|
-
"dy-inline-flex dy-items-center dy-justify-center dy-gap-2 dy-whitespace-nowrap dy-rounded-md dy-text-sm dy-font-medium dy-ring-offset-background dy-transition-colors focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring focus-visible:dy-ring-offset-2 disabled:dy-pointer-events-none disabled:dy-opacity-50 [&_svg]:dy-pointer-events-none [&_svg]:dy-size-4 [&_svg]:dy-shrink-0",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default: "dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/90",
|
|
13
|
-
destructive:
|
|
14
|
-
"dy-bg-destructive dy-text-destructive-foreground hover:dy-bg-destructive/90",
|
|
15
|
-
outline:
|
|
16
|
-
"dy-border dy-border-input dy-bg-background hover:dy-bg-accent hover:dy-text-accent-foreground",
|
|
17
|
-
secondary:
|
|
18
|
-
"dy-bg-secondary dy-text-secondary-foreground hover:dy-bg-secondary/80",
|
|
19
|
-
ghost: "hover:dy-bg-accent hover:dy-text-accent-foreground",
|
|
20
|
-
link: "dy-text-primary dy-underline-offset-4 hover:dy-underline",
|
|
21
|
-
},
|
|
22
|
-
size: {
|
|
23
|
-
default: "dy-h-10 dy-px-4 dy-py-2",
|
|
24
|
-
sm: "dy-h-9 dy-rounded-md dy-px-3",
|
|
25
|
-
lg: "dy-h-11 dy-rounded-md dy-px-8",
|
|
26
|
-
icon: "dy-h-10 dy-w-10",
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
defaultVariants: {
|
|
30
|
-
variant: "default",
|
|
31
|
-
size: "default",
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
export interface ButtonProps
|
|
37
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
38
|
-
VariantProps<typeof buttonVariants> {
|
|
39
|
-
asChild?: boolean
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
43
|
-
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
44
|
-
const Comp = asChild ? Slot : "button"
|
|
45
|
-
return (
|
|
46
|
-
<Comp
|
|
47
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
48
|
-
ref={ref}
|
|
49
|
-
{...props}
|
|
50
|
-
/>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
)
|
|
54
|
-
Button.displayName = "Button"
|
|
55
|
-
|
|
56
|
-
export { Button, buttonVariants }
|