@firecms/media_manager 3.1.0-canary.1df3b2c

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.
@@ -0,0 +1,1280 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { c } from "react-compiler-runtime";
3
+ import { createContext, useContext, useState, useCallback, useEffect, useRef, useMemo } from "react";
4
+ import Compressor from "compressorjs";
5
+ import { ImageIcon, VideoLibraryIcon, AudiotrackIcon, DescriptionIcon, cls, defaultBorderMixin, Typography, CheckIcon, Card, IconButton, DownloadIcon, DeleteIcon, CloseIcon, TextField, Chip, Button, CircularProgress, Dialog, DialogContent, DialogActions, CloudUploadIcon, Tooltip, Container, AddIcon, SearchBar, AppsIcon, Icon, RefreshIcon } from "@firecms/ui";
6
+ import { useCreateFormex } from "@firecms/formex";
7
+ import { useSnackbarController, useStorageSource } from "@firecms/core";
8
+ import { Link } from "react-router-dom";
9
+ const MediaManagerContext = createContext(void 0);
10
+ function useMediaManager() {
11
+ const context = useContext(MediaManagerContext);
12
+ if (!context) {
13
+ throw new Error("useMediaManager must be used within a MediaManagerProvider");
14
+ }
15
+ return context;
16
+ }
17
+ function MediaManagerProvider(t0) {
18
+ const $ = c(3);
19
+ const {
20
+ controller,
21
+ children
22
+ } = t0;
23
+ let t1;
24
+ if ($[0] !== children || $[1] !== controller) {
25
+ t1 = /* @__PURE__ */ jsx(MediaManagerContext.Provider, { value: controller, children });
26
+ $[0] = children;
27
+ $[1] = controller;
28
+ $[2] = t1;
29
+ } else {
30
+ t1 = $[2];
31
+ }
32
+ return t1;
33
+ }
34
+ const DEFAULT_THUMBNAIL_PATH = "thumbs";
35
+ function useMediaManagerController({
36
+ storageSource,
37
+ dataSourceDelegate,
38
+ storagePath,
39
+ collectionPath,
40
+ bucket,
41
+ thumbnailSizes,
42
+ thumbnailPath = DEFAULT_THUMBNAIL_PATH
43
+ }) {
44
+ const [loading, setLoading] = useState(true);
45
+ const [error, setError] = useState();
46
+ const [assets, setAssets] = useState([]);
47
+ const [selectedAsset, setSelectedAsset] = useState();
48
+ const [searchQuery, setSearchQuery] = useState("");
49
+ const fetchDownloadURL = useCallback(async (asset) => {
50
+ try {
51
+ const downloadConfig = await storageSource.getDownloadURL(asset.storagePath, asset.bucket);
52
+ return {
53
+ ...asset,
54
+ downloadURL: downloadConfig.url ?? void 0
55
+ };
56
+ } catch (err) {
57
+ console.warn(`Failed to get download URL for ${asset.storagePath}:`, err);
58
+ return {
59
+ ...asset,
60
+ downloadURL: void 0
61
+ };
62
+ }
63
+ }, [storageSource]);
64
+ const refreshAssets = useCallback(async () => {
65
+ setLoading(true);
66
+ setError(void 0);
67
+ try {
68
+ console.log("Fetching media assets from:", collectionPath);
69
+ const entities = await dataSourceDelegate.fetchCollection({
70
+ path: collectionPath,
71
+ orderBy: "createdAt",
72
+ order: "desc"
73
+ });
74
+ console.log("Fetched entities:", entities.length);
75
+ if (entities.length === 0) {
76
+ setAssets([]);
77
+ return;
78
+ }
79
+ const loadedAssets = await Promise.all(entities.map(async (entity) => {
80
+ const values = entity.values;
81
+ const baseAsset = {
82
+ id: entity.id,
83
+ fileName: values.fileName,
84
+ storagePath: values.storagePath,
85
+ mimeType: values.mimeType,
86
+ size: values.size,
87
+ dimensions: values.dimensions,
88
+ title: values.title,
89
+ altText: values.altText,
90
+ caption: values.caption,
91
+ tags: values.tags,
92
+ bucket: values.bucket,
93
+ createdAt: values.createdAt instanceof Date ? values.createdAt : values.createdAt?.toDate ? values.createdAt.toDate() : new Date(values.createdAt),
94
+ updatedAt: values.updatedAt instanceof Date ? values.updatedAt : values.updatedAt?.toDate ? values.updatedAt.toDate() : new Date(values.updatedAt)
95
+ };
96
+ if (baseAsset.mimeType?.startsWith("image/") || baseAsset.mimeType?.startsWith("video/")) {
97
+ return await fetchDownloadURL(baseAsset);
98
+ }
99
+ return {
100
+ ...baseAsset,
101
+ downloadURL: void 0
102
+ };
103
+ }));
104
+ console.log("Loaded assets with URLs:", loadedAssets.length);
105
+ setAssets(loadedAssets);
106
+ } catch (err_0) {
107
+ console.error("Error fetching media assets:", err_0);
108
+ setError(err_0 instanceof Error ? err_0 : new Error(String(err_0)));
109
+ } finally {
110
+ setLoading(false);
111
+ }
112
+ }, [dataSourceDelegate, collectionPath, fetchDownloadURL]);
113
+ useEffect(() => {
114
+ refreshAssets();
115
+ }, [refreshAssets]);
116
+ const uploadFile = useCallback(async (file, metadata) => {
117
+ console.log("Uploading file:", file.name, "to path:", storagePath);
118
+ const uploadResult = await storageSource.uploadFile({
119
+ file,
120
+ fileName: file.name,
121
+ path: storagePath,
122
+ bucket
123
+ });
124
+ console.log("Upload result:", uploadResult);
125
+ let downloadURL;
126
+ try {
127
+ const downloadConfig_0 = await storageSource.getDownloadURL(uploadResult.path, uploadResult.bucket);
128
+ downloadURL = downloadConfig_0.url ?? void 0;
129
+ console.log("Got download URL:", downloadURL);
130
+ } catch (err_1) {
131
+ console.warn("Failed to get download URL after upload:", err_1);
132
+ }
133
+ let dimensions;
134
+ if (file.type.startsWith("image/")) {
135
+ try {
136
+ dimensions = await getImageDimensions(file);
137
+ } catch (err_2) {
138
+ console.warn("Failed to get image dimensions:", err_2);
139
+ }
140
+ }
141
+ const thumbnails = {};
142
+ const isRasterImage = file.type.startsWith("image/") && file.type !== "image/svg+xml";
143
+ if (thumbnailSizes && thumbnailSizes.length > 0 && isRasterImage) {
144
+ console.log("Generating thumbnails for sizes:", thumbnailSizes.map((s) => s.name));
145
+ for (const size of thumbnailSizes) {
146
+ try {
147
+ const thumbnailBlob = await generateThumbnail(file, size.width, size.height, size.quality ?? 0.8);
148
+ const thumbFileName = `${Date.now()}_${size.name}_${file.name}`;
149
+ const thumbPath = `${storagePath}/${thumbnailPath}/${size.name}`;
150
+ const thumbUploadResult = await storageSource.uploadFile({
151
+ file: new File([thumbnailBlob], thumbFileName, {
152
+ type: "image/jpeg"
153
+ }),
154
+ fileName: thumbFileName,
155
+ path: thumbPath,
156
+ bucket
157
+ });
158
+ const thumbDownloadConfig = await storageSource.getDownloadURL(thumbUploadResult.path, thumbUploadResult.bucket);
159
+ if (thumbDownloadConfig.url) {
160
+ thumbnails[size.name] = thumbDownloadConfig.url;
161
+ console.log(`Generated ${size.name} thumbnail:`, thumbDownloadConfig.url);
162
+ }
163
+ } catch (err_3) {
164
+ console.warn(`Failed to generate ${size.name} thumbnail:`, err_3);
165
+ }
166
+ }
167
+ }
168
+ const now = /* @__PURE__ */ new Date();
169
+ const assetData = {
170
+ fileName: file.name,
171
+ storagePath: uploadResult.path,
172
+ mimeType: file.type,
173
+ size: file.size,
174
+ createdAt: now,
175
+ updatedAt: now
176
+ };
177
+ if (dimensions) assetData.dimensions = dimensions;
178
+ if (metadata?.title) assetData.title = metadata.title;
179
+ if (metadata?.altText) assetData.altText = metadata.altText;
180
+ if (metadata?.caption) assetData.caption = metadata.caption;
181
+ if (metadata?.tags && metadata.tags.length > 0) assetData.tags = metadata.tags;
182
+ if (uploadResult.bucket) assetData.bucket = uploadResult.bucket;
183
+ if (downloadURL) assetData.downloadURL = downloadURL;
184
+ if (Object.keys(thumbnails).length > 0) assetData.thumbnails = thumbnails;
185
+ console.log("Saving asset data to database:", assetData);
186
+ const entity_0 = await dataSourceDelegate.saveEntity({
187
+ path: collectionPath,
188
+ values: assetData,
189
+ status: "new"
190
+ });
191
+ console.log("Saved entity:", entity_0.id);
192
+ const newAsset = {
193
+ id: entity_0.id,
194
+ fileName: file.name,
195
+ storagePath: uploadResult.path,
196
+ downloadURL,
197
+ mimeType: file.type,
198
+ size: file.size,
199
+ createdAt: now,
200
+ updatedAt: now,
201
+ dimensions,
202
+ title: metadata?.title,
203
+ altText: metadata?.altText,
204
+ caption: metadata?.caption,
205
+ tags: metadata?.tags,
206
+ bucket: uploadResult.bucket,
207
+ thumbnails: Object.keys(thumbnails).length > 0 ? thumbnails : void 0
208
+ };
209
+ setAssets((prev) => [newAsset, ...prev]);
210
+ return newAsset;
211
+ }, [storageSource, dataSourceDelegate, storagePath, collectionPath, bucket, thumbnailSizes, thumbnailPath]);
212
+ const deleteAsset = useCallback(async (assetId) => {
213
+ const asset_0 = assets.find((a) => a.id === assetId);
214
+ if (!asset_0) {
215
+ throw new Error(`Asset with id ${assetId} not found`);
216
+ }
217
+ console.log("Deleting asset:", assetId, asset_0.storagePath);
218
+ try {
219
+ await storageSource.deleteFile(asset_0.storagePath, asset_0.bucket);
220
+ } catch (err_4) {
221
+ console.warn("Failed to delete from storage (may not exist):", err_4);
222
+ }
223
+ await dataSourceDelegate.deleteEntity({
224
+ entity: {
225
+ id: assetId,
226
+ path: collectionPath,
227
+ values: asset_0
228
+ }
229
+ });
230
+ setAssets((prev_0) => prev_0.filter((a_0) => a_0.id !== assetId));
231
+ if (selectedAsset?.id === assetId) {
232
+ setSelectedAsset(void 0);
233
+ }
234
+ }, [assets, storageSource, dataSourceDelegate, collectionPath, selectedAsset]);
235
+ const updateAsset = useCallback(async (assetId_0, data) => {
236
+ const asset_1 = assets.find((a_1) => a_1.id === assetId_0);
237
+ if (!asset_1) {
238
+ throw new Error(`Asset with id ${assetId_0} not found`);
239
+ }
240
+ const cleanData = {};
241
+ Object.entries(data).forEach(([key, value]) => {
242
+ if (value !== void 0) {
243
+ cleanData[key] = value;
244
+ }
245
+ });
246
+ cleanData.updatedAt = /* @__PURE__ */ new Date();
247
+ console.log("Updating asset:", assetId_0, cleanData);
248
+ await dataSourceDelegate.saveEntity({
249
+ path: collectionPath,
250
+ entityId: assetId_0,
251
+ values: cleanData,
252
+ previousValues: asset_1,
253
+ status: "existing"
254
+ });
255
+ setAssets((prev_1) => prev_1.map((a_2) => a_2.id === assetId_0 ? {
256
+ ...a_2,
257
+ ...cleanData
258
+ } : a_2));
259
+ if (selectedAsset?.id === assetId_0) {
260
+ setSelectedAsset((prev_2) => prev_2 ? {
261
+ ...prev_2,
262
+ ...cleanData
263
+ } : prev_2);
264
+ }
265
+ }, [assets, dataSourceDelegate, collectionPath, selectedAsset]);
266
+ const searchAssets = useCallback((query) => {
267
+ setSearchQuery(query);
268
+ }, []);
269
+ const selectAsset = useCallback((asset_2) => {
270
+ setSelectedAsset(asset_2);
271
+ }, []);
272
+ const filteredAssets = searchQuery ? assets.filter((asset_3) => asset_3.fileName.toLowerCase().includes(searchQuery.toLowerCase()) || asset_3.title?.toLowerCase().includes(searchQuery.toLowerCase()) || asset_3.tags?.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))) : assets;
273
+ return {
274
+ loading,
275
+ error,
276
+ assets: filteredAssets,
277
+ totalCount: assets.length,
278
+ selectedAsset,
279
+ selectAsset,
280
+ uploadFile,
281
+ deleteAsset,
282
+ updateAsset,
283
+ refreshAssets,
284
+ searchAssets,
285
+ storagePath,
286
+ collectionPath
287
+ };
288
+ }
289
+ function getImageDimensions(file) {
290
+ return new Promise((resolve, reject) => {
291
+ const img = new Image();
292
+ img.onload = () => {
293
+ resolve({
294
+ width: img.width,
295
+ height: img.height
296
+ });
297
+ URL.revokeObjectURL(img.src);
298
+ };
299
+ img.onerror = reject;
300
+ img.src = URL.createObjectURL(file);
301
+ });
302
+ }
303
+ async function generateThumbnail(file, maxWidth, maxHeight, quality) {
304
+ return new Promise((resolve, reject) => {
305
+ new Compressor(file, {
306
+ quality,
307
+ maxWidth,
308
+ maxHeight,
309
+ mimeType: "image/jpeg",
310
+ success: (result) => {
311
+ resolve(result);
312
+ },
313
+ error: reject
314
+ });
315
+ });
316
+ }
317
+ function MediaAssetCard(t0) {
318
+ const $ = c(53);
319
+ const {
320
+ asset,
321
+ viewMode,
322
+ onClick,
323
+ selected,
324
+ thumbnailSize: t1
325
+ } = t0;
326
+ const thumbnailSize = t1 === void 0 ? "small" : t1;
327
+ let t2;
328
+ if ($[0] !== asset.mimeType) {
329
+ t2 = asset.mimeType.startsWith("image/");
330
+ $[0] = asset.mimeType;
331
+ $[1] = t2;
332
+ } else {
333
+ t2 = $[1];
334
+ }
335
+ const isImage = t2;
336
+ const isVideo = asset.mimeType.startsWith("video/");
337
+ const isAudio = asset.mimeType.startsWith("audio/");
338
+ const FileIcon = isImage ? ImageIcon : isVideo ? VideoLibraryIcon : isAudio ? AudiotrackIcon : DescriptionIcon;
339
+ const formatSize = _temp;
340
+ const imageUrl = asset.thumbnails?.[thumbnailSize] ?? asset.downloadURL;
341
+ let t3;
342
+ if ($[2] !== FileIcon || $[3] !== asset.altText || $[4] !== asset.fileName || $[5] !== imageUrl || $[6] !== isImage) {
343
+ t3 = isImage && imageUrl ? /* @__PURE__ */ jsx("img", { src: imageUrl, alt: asset.altText || asset.fileName, className: "w-full h-full object-cover transition-transform duration-200 group-hover:scale-105", loading: "lazy" }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center bg-surface-100 dark:bg-surface-800", children: /* @__PURE__ */ jsx(FileIcon, { size: "large", className: "text-surface-400 dark:text-surface-500" }) });
344
+ $[2] = FileIcon;
345
+ $[3] = asset.altText;
346
+ $[4] = asset.fileName;
347
+ $[5] = imageUrl;
348
+ $[6] = isImage;
349
+ $[7] = t3;
350
+ } else {
351
+ t3 = $[7];
352
+ }
353
+ const thumbnail = t3;
354
+ if (viewMode === "list") {
355
+ const t42 = selected && "ring-2 ring-primary bg-primary/5";
356
+ let t52;
357
+ if ($[8] !== t42) {
358
+ t52 = cls("p-3 cursor-pointer flex items-center gap-3 rounded-lg", "hover:bg-surface-100 dark:hover:bg-surface-800", "transition-colors duration-150", `border ${defaultBorderMixin}`, t42);
359
+ $[8] = t42;
360
+ $[9] = t52;
361
+ } else {
362
+ t52 = $[9];
363
+ }
364
+ let t62;
365
+ if ($[10] !== thumbnail) {
366
+ t62 = /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-md overflow-hidden flex-shrink-0 bg-surface-100 dark:bg-surface-800", children: thumbnail });
367
+ $[10] = thumbnail;
368
+ $[11] = t62;
369
+ } else {
370
+ t62 = $[11];
371
+ }
372
+ const t72 = asset.title || asset.fileName;
373
+ let t82;
374
+ if ($[12] !== t72) {
375
+ t82 = /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "font-medium truncate text-surface-900 dark:text-white", children: t72 });
376
+ $[12] = t72;
377
+ $[13] = t82;
378
+ } else {
379
+ t82 = $[13];
380
+ }
381
+ const t92 = formatSize(asset.size);
382
+ let t102;
383
+ if ($[14] !== asset.mimeType) {
384
+ t102 = asset.mimeType.split("/")[1]?.toUpperCase();
385
+ $[14] = asset.mimeType;
386
+ $[15] = t102;
387
+ } else {
388
+ t102 = $[15];
389
+ }
390
+ let t112;
391
+ if ($[16] !== t102 || $[17] !== t92) {
392
+ t112 = /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
393
+ t92,
394
+ " • ",
395
+ t102
396
+ ] });
397
+ $[16] = t102;
398
+ $[17] = t92;
399
+ $[18] = t112;
400
+ } else {
401
+ t112 = $[18];
402
+ }
403
+ let t122;
404
+ if ($[19] !== t112 || $[20] !== t82) {
405
+ t122 = /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
406
+ t82,
407
+ t112
408
+ ] });
409
+ $[19] = t112;
410
+ $[20] = t82;
411
+ $[21] = t122;
412
+ } else {
413
+ t122 = $[21];
414
+ }
415
+ let t132;
416
+ if ($[22] !== selected) {
417
+ t132 = selected && /* @__PURE__ */ jsx("div", { className: "w-6 h-6 rounded-full bg-primary flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest", className: "text-white" }) });
418
+ $[22] = selected;
419
+ $[23] = t132;
420
+ } else {
421
+ t132 = $[23];
422
+ }
423
+ let t142;
424
+ if ($[24] !== onClick || $[25] !== t122 || $[26] !== t132 || $[27] !== t52 || $[28] !== t62) {
425
+ t142 = /* @__PURE__ */ jsxs("div", { className: t52, onClick, children: [
426
+ t62,
427
+ t122,
428
+ t132
429
+ ] });
430
+ $[24] = onClick;
431
+ $[25] = t122;
432
+ $[26] = t132;
433
+ $[27] = t52;
434
+ $[28] = t62;
435
+ $[29] = t142;
436
+ } else {
437
+ t142 = $[29];
438
+ }
439
+ return t142;
440
+ }
441
+ const t4 = selected && "ring-2 ring-primary";
442
+ let t5;
443
+ if ($[30] !== t4) {
444
+ t5 = cls("cursor-pointer overflow-hidden group relative", "transition-all duration-200", "hover:shadow-lg hover:-translate-y-0.5", t4);
445
+ $[30] = t4;
446
+ $[31] = t5;
447
+ } else {
448
+ t5 = $[31];
449
+ }
450
+ let t6;
451
+ if ($[32] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
452
+ t6 = /* @__PURE__ */ jsx("div", { className: cls("absolute inset-0 bg-black/0 group-hover:bg-black/20", "transition-colors duration-200") });
453
+ $[32] = t6;
454
+ } else {
455
+ t6 = $[32];
456
+ }
457
+ let t7;
458
+ if ($[33] !== selected) {
459
+ t7 = selected && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 w-6 h-6 rounded-full bg-primary flex items-center justify-center shadow-md", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest", className: "text-white" }) });
460
+ $[33] = selected;
461
+ $[34] = t7;
462
+ } else {
463
+ t7 = $[34];
464
+ }
465
+ let t8;
466
+ if ($[35] !== t7 || $[36] !== thumbnail) {
467
+ t8 = /* @__PURE__ */ jsxs("div", { className: "aspect-square relative overflow-hidden bg-surface-100 dark:bg-surface-800", children: [
468
+ thumbnail,
469
+ t6,
470
+ t7
471
+ ] });
472
+ $[35] = t7;
473
+ $[36] = thumbnail;
474
+ $[37] = t8;
475
+ } else {
476
+ t8 = $[37];
477
+ }
478
+ const t9 = asset.title || asset.fileName;
479
+ let t10;
480
+ if ($[38] !== t9) {
481
+ t10 = /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "font-medium truncate text-surface-900 dark:text-white", children: t9 });
482
+ $[38] = t9;
483
+ $[39] = t10;
484
+ } else {
485
+ t10 = $[39];
486
+ }
487
+ const t11 = formatSize(asset.size);
488
+ let t12;
489
+ if ($[40] !== asset.mimeType) {
490
+ t12 = asset.mimeType.split("/")[1]?.toUpperCase();
491
+ $[40] = asset.mimeType;
492
+ $[41] = t12;
493
+ } else {
494
+ t12 = $[41];
495
+ }
496
+ let t13;
497
+ if ($[42] !== t11 || $[43] !== t12) {
498
+ t13 = /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", className: "truncate block mt-0.5", children: [
499
+ t11,
500
+ " • ",
501
+ t12
502
+ ] });
503
+ $[42] = t11;
504
+ $[43] = t12;
505
+ $[44] = t13;
506
+ } else {
507
+ t13 = $[44];
508
+ }
509
+ let t14;
510
+ if ($[45] !== t10 || $[46] !== t13) {
511
+ t14 = /* @__PURE__ */ jsxs("div", { className: "p-3", children: [
512
+ t10,
513
+ t13
514
+ ] });
515
+ $[45] = t10;
516
+ $[46] = t13;
517
+ $[47] = t14;
518
+ } else {
519
+ t14 = $[47];
520
+ }
521
+ let t15;
522
+ if ($[48] !== onClick || $[49] !== t14 || $[50] !== t5 || $[51] !== t8) {
523
+ t15 = /* @__PURE__ */ jsxs(Card, { className: t5, onClick, children: [
524
+ t8,
525
+ t14
526
+ ] });
527
+ $[48] = onClick;
528
+ $[49] = t14;
529
+ $[50] = t5;
530
+ $[51] = t8;
531
+ $[52] = t15;
532
+ } else {
533
+ t15 = $[52];
534
+ }
535
+ return t15;
536
+ }
537
+ function _temp(bytes) {
538
+ if (bytes < 1024) {
539
+ return `${bytes} B`;
540
+ }
541
+ if (bytes < 1048576) {
542
+ return `${(bytes / 1024).toFixed(1)} KB`;
543
+ }
544
+ return `${(bytes / 1048576).toFixed(1)} MB`;
545
+ }
546
+ function MediaAssetDetails({
547
+ asset,
548
+ onClose,
549
+ onUpdate,
550
+ onDelete
551
+ }) {
552
+ const snackbarController = useSnackbarController();
553
+ const storageSource = useStorageSource();
554
+ const [saving, setSaving] = useState(false);
555
+ const [deleting, setDeleting] = useState(false);
556
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
557
+ const [tagInput, setTagInput] = useState("");
558
+ const {
559
+ values,
560
+ setFieldValue,
561
+ dirty
562
+ } = useCreateFormex({
563
+ initialValues: {
564
+ title: asset.title ?? "",
565
+ altText: asset.altText ?? "",
566
+ caption: asset.caption ?? "",
567
+ tags: asset.tags ?? []
568
+ }
569
+ });
570
+ const handleSave = useCallback(async () => {
571
+ setSaving(true);
572
+ try {
573
+ await onUpdate(asset.id, values);
574
+ snackbarController.open({
575
+ type: "success",
576
+ message: "Asset updated successfully"
577
+ });
578
+ } catch (error) {
579
+ snackbarController.open({
580
+ type: "error",
581
+ message: `Error updating asset: ${error instanceof Error ? error.message : String(error)}`
582
+ });
583
+ } finally {
584
+ setSaving(false);
585
+ }
586
+ }, [asset.id, values, onUpdate, snackbarController]);
587
+ const handleDelete = useCallback(async () => {
588
+ setDeleting(true);
589
+ try {
590
+ await onDelete(asset.id);
591
+ snackbarController.open({
592
+ type: "success",
593
+ message: "Asset deleted successfully"
594
+ });
595
+ onClose();
596
+ } catch (error_0) {
597
+ snackbarController.open({
598
+ type: "error",
599
+ message: `Error deleting asset: ${error_0 instanceof Error ? error_0.message : String(error_0)}`
600
+ });
601
+ } finally {
602
+ setDeleting(false);
603
+ setDeleteDialogOpen(false);
604
+ }
605
+ }, [asset.id, onDelete, snackbarController, onClose]);
606
+ const handleDownload = useCallback(async () => {
607
+ try {
608
+ const downloadConfig = await storageSource.getDownloadURL(asset.storagePath, asset.bucket);
609
+ if (downloadConfig.url) {
610
+ window.open(downloadConfig.url, "_blank");
611
+ }
612
+ } catch (error_1) {
613
+ snackbarController.open({
614
+ type: "error",
615
+ message: "Error getting download URL"
616
+ });
617
+ }
618
+ }, [asset, storageSource, snackbarController]);
619
+ const handleAddTag = useCallback(() => {
620
+ const tag = tagInput.trim();
621
+ if (tag && !values.tags?.includes(tag)) {
622
+ setFieldValue("tags", [...values.tags ?? [], tag]);
623
+ setTagInput("");
624
+ }
625
+ }, [tagInput, values.tags, setFieldValue]);
626
+ const handleRemoveTag = useCallback((tagToRemove) => {
627
+ setFieldValue("tags", values.tags?.filter((t) => t !== tagToRemove) ?? []);
628
+ }, [values.tags, setFieldValue]);
629
+ const formatSize = (bytes) => {
630
+ if (bytes < 1024) return `${bytes} B`;
631
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
632
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
633
+ };
634
+ const formatDate = (date) => {
635
+ return new Intl.DateTimeFormat(void 0, {
636
+ year: "numeric",
637
+ month: "short",
638
+ day: "numeric"
639
+ }).format(date);
640
+ };
641
+ const isImage = asset.mimeType.startsWith("image/");
642
+ const isVideo = asset.mimeType.startsWith("video/");
643
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
644
+ /* @__PURE__ */ jsxs("div", { className: cls("fixed inset-y-0 right-0 w-full sm:w-96 lg:w-[480px]", "bg-surface-50 dark:bg-surface-900", "border-l border-surface-accent-200 dark:border-surface-accent-700", "shadow-xl z-50", "flex flex-col", "animate-slide-in-right"), children: [
645
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4 border-b border-surface-accent-200 dark:border-surface-accent-700", children: [
646
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", className: "font-medium truncate flex-1 mr-2", children: asset.title || asset.fileName }),
647
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
648
+ /* @__PURE__ */ jsx(IconButton, { onClick: handleDownload, children: /* @__PURE__ */ jsx(DownloadIcon, { size: "small" }) }),
649
+ /* @__PURE__ */ jsx(IconButton, { onClick: () => setDeleteDialogOpen(true), className: "text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20", children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" }) }),
650
+ /* @__PURE__ */ jsx(IconButton, { onClick: onClose, children: /* @__PURE__ */ jsx(CloseIcon, { size: "small" }) })
651
+ ] })
652
+ ] }),
653
+ /* @__PURE__ */ jsx("div", { className: "p-4 bg-surface-accent-100 dark:bg-surface-accent-800 flex items-center justify-center min-h-48 max-h-64", children: isImage && asset.downloadURL ? /* @__PURE__ */ jsx("img", { src: asset.downloadURL, alt: asset.altText || asset.fileName, className: "max-w-full max-h-full object-contain" }) : isVideo && asset.downloadURL ? /* @__PURE__ */ jsx("video", { src: asset.downloadURL, className: "max-w-full max-h-full", controls: true }) : /* @__PURE__ */ jsx("div", { className: "text-surface-accent-400", children: "Preview not available" }) }),
654
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 space-y-4", children: [
655
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
656
+ asset.dimensions && /* @__PURE__ */ jsxs("div", { children: [
657
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500", children: "Dimensions" }),
658
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", children: [
659
+ asset.dimensions.width,
660
+ " × ",
661
+ asset.dimensions.height,
662
+ " px"
663
+ ] })
664
+ ] }),
665
+ /* @__PURE__ */ jsxs("div", { children: [
666
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500", children: "Size" }),
667
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: formatSize(asset.size) })
668
+ ] }),
669
+ /* @__PURE__ */ jsxs("div", { children: [
670
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500", children: "Type" }),
671
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: asset.mimeType })
672
+ ] }),
673
+ /* @__PURE__ */ jsxs("div", { children: [
674
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500", children: "Created" }),
675
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: formatDate(asset.createdAt) })
676
+ ] })
677
+ ] }),
678
+ /* @__PURE__ */ jsx("hr", { className: "border-surface-accent-200 dark:border-surface-accent-700" }),
679
+ /* @__PURE__ */ jsx(TextField, { label: "File Name", value: asset.fileName, disabled: true, size: "small" }),
680
+ /* @__PURE__ */ jsx(TextField, { label: "Title", value: values.title ?? "", onChange: (e) => setFieldValue("title", e.target.value), size: "small" }),
681
+ /* @__PURE__ */ jsxs("div", { children: [
682
+ /* @__PURE__ */ jsx(TextField, { label: "Alt Text", value: values.altText ?? "", onChange: (e_0) => setFieldValue("altText", e_0.target.value), size: "small" }),
683
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500 mt-1", children: "Recommended for SEO" })
684
+ ] }),
685
+ /* @__PURE__ */ jsx(TextField, { label: "Caption", value: values.caption ?? "", onChange: (e_1) => setFieldValue("caption", e_1.target.value), size: "small", multiline: true }),
686
+ /* @__PURE__ */ jsxs("div", { children: [
687
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500 mb-1 block", children: "Tags" }),
688
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 mb-2", children: values.tags?.map((tag_0) => /* @__PURE__ */ jsxs(Chip, { size: "small", colorScheme: "blueLighter", onClick: () => handleRemoveTag(tag_0), children: [
689
+ tag_0,
690
+ " ×"
691
+ ] }, tag_0)) }),
692
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
693
+ /* @__PURE__ */ jsx(TextField, { placeholder: "Add a tag...", value: tagInput, onChange: (e_2) => setTagInput(e_2.target.value), size: "small", className: "flex-1", onKeyDown: (e_3) => {
694
+ if (e_3.key === "Enter") {
695
+ e_3.preventDefault();
696
+ handleAddTag();
697
+ }
698
+ } }),
699
+ /* @__PURE__ */ jsx(Button, { variant: "text", size: "small", onClick: handleAddTag, disabled: !tagInput.trim(), children: "Add" })
700
+ ] })
701
+ ] })
702
+ ] }),
703
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-t border-surface-accent-200 dark:border-surface-accent-700", children: /* @__PURE__ */ jsx(Button, { variant: "filled", onClick: handleSave, disabled: !dirty || saving, className: "w-full", children: saving ? /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) : "Save Changes" }) })
704
+ ] }),
705
+ /* @__PURE__ */ jsxs(Dialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: [
706
+ /* @__PURE__ */ jsxs(DialogContent, { children: [
707
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", className: "font-medium mb-2", children: "Delete Asset?" }),
708
+ /* @__PURE__ */ jsxs(Typography, { className: "text-surface-accent-600 dark:text-surface-accent-400", children: [
709
+ 'Are you sure you want to delete "',
710
+ asset.title || asset.fileName,
711
+ '"? This action cannot be undone.'
712
+ ] })
713
+ ] }),
714
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
715
+ /* @__PURE__ */ jsx(Button, { variant: "text", onClick: () => setDeleteDialogOpen(false), disabled: deleting, children: "Cancel" }),
716
+ /* @__PURE__ */ jsx(Button, { variant: "filled", color: "error", onClick: handleDelete, disabled: deleting, children: deleting ? /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) : "Delete" })
717
+ ] })
718
+ ] })
719
+ ] });
720
+ }
721
+ function MediaUploadDialog({
722
+ open,
723
+ onClose,
724
+ onUpload,
725
+ maxFileSize = 52428800,
726
+ // 50MB default
727
+ acceptedMimeTypes
728
+ }) {
729
+ const [isDragging, setIsDragging] = useState(false);
730
+ const [uploading, setUploading] = useState(false);
731
+ const [error, setError] = useState(null);
732
+ const [selectedFiles, setSelectedFiles] = useState([]);
733
+ const validateFiles = useCallback((files) => {
734
+ const valid = [];
735
+ const errors = [];
736
+ for (const file of files) {
737
+ if (maxFileSize && file.size > maxFileSize) {
738
+ errors.push(`${file.name}: File too large (max ${formatSize(maxFileSize)})`);
739
+ continue;
740
+ }
741
+ if (acceptedMimeTypes && !acceptedMimeTypes.some((type) => {
742
+ if (type.endsWith("/*")) {
743
+ return file.type.startsWith(type.slice(0, -1));
744
+ }
745
+ return file.type === type;
746
+ })) {
747
+ errors.push(`${file.name}: File type not allowed`);
748
+ continue;
749
+ }
750
+ valid.push(file);
751
+ }
752
+ return {
753
+ valid,
754
+ errors
755
+ };
756
+ }, [maxFileSize, acceptedMimeTypes]);
757
+ const handleDragOver = useCallback((e) => {
758
+ e.preventDefault();
759
+ setIsDragging(true);
760
+ }, []);
761
+ const handleDragLeave = useCallback((e_0) => {
762
+ e_0.preventDefault();
763
+ setIsDragging(false);
764
+ }, []);
765
+ const handleDrop = useCallback((e_1) => {
766
+ e_1.preventDefault();
767
+ setIsDragging(false);
768
+ const files_0 = Array.from(e_1.dataTransfer.files);
769
+ const {
770
+ valid: valid_0,
771
+ errors: errors_0
772
+ } = validateFiles(files_0);
773
+ if (errors_0.length > 0) {
774
+ setError(errors_0.join("\n"));
775
+ } else {
776
+ setError(null);
777
+ }
778
+ setSelectedFiles((prev) => [...prev, ...valid_0]);
779
+ }, [validateFiles]);
780
+ const handleFileSelect = useCallback((e_2) => {
781
+ const files_1 = Array.from(e_2.target.files ?? []);
782
+ const {
783
+ valid: valid_1,
784
+ errors: errors_1
785
+ } = validateFiles(files_1);
786
+ if (errors_1.length > 0) {
787
+ setError(errors_1.join("\n"));
788
+ } else {
789
+ setError(null);
790
+ }
791
+ setSelectedFiles((prev_0) => [...prev_0, ...valid_1]);
792
+ }, [validateFiles]);
793
+ const handleRemoveFile = useCallback((index) => {
794
+ setSelectedFiles((prev_1) => prev_1.filter((_, i) => i !== index));
795
+ }, []);
796
+ const handleUpload = useCallback(async () => {
797
+ if (selectedFiles.length === 0) return;
798
+ setUploading(true);
799
+ setError(null);
800
+ try {
801
+ await onUpload(selectedFiles);
802
+ setSelectedFiles([]);
803
+ onClose();
804
+ } catch (err) {
805
+ setError(err instanceof Error ? err.message : "Upload failed");
806
+ } finally {
807
+ setUploading(false);
808
+ }
809
+ }, [selectedFiles, onUpload, onClose]);
810
+ const handleClose = useCallback(() => {
811
+ if (!uploading) {
812
+ setSelectedFiles([]);
813
+ setError(null);
814
+ onClose();
815
+ }
816
+ }, [uploading, onClose]);
817
+ const formatSize = (bytes) => {
818
+ if (bytes < 1024) return `${bytes} B`;
819
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
820
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
821
+ };
822
+ return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange: (open_0) => !open_0 && handleClose(), maxWidth: "md", children: [
823
+ /* @__PURE__ */ jsxs(DialogContent, { className: "p-0", children: [
824
+ /* @__PURE__ */ jsx("div", { className: "p-4 border-b border-surface-accent-200 dark:border-surface-accent-700", children: /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Upload Files" }) }),
825
+ /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
826
+ /* @__PURE__ */ jsxs("div", { className: cls("border-2 border-dashed rounded-lg p-8", "flex flex-col items-center justify-center gap-4", "transition-colors duration-150", isDragging ? "border-primary bg-primary/5" : "border-surface-accent-300 dark:border-surface-accent-600", "hover:border-primary hover:bg-primary/5", "cursor-pointer"), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: () => document.getElementById("file-upload-input")?.click(), children: [
827
+ /* @__PURE__ */ jsx(CloudUploadIcon, { size: "large", className: cls(isDragging ? "text-primary" : "text-surface-accent-400") }),
828
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
829
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-medium", children: "Drop files here or click to browse" }),
830
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-surface-accent-500", children: [
831
+ "Maximum file size: ",
832
+ formatSize(maxFileSize)
833
+ ] })
834
+ ] }),
835
+ /* @__PURE__ */ jsx("input", { id: "file-upload-input", type: "file", multiple: true, accept: acceptedMimeTypes?.join(","), onChange: handleFileSelect, className: "hidden" })
836
+ ] }),
837
+ error && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-red-500 mt-2 block whitespace-pre-line", children: error }),
838
+ selectedFiles.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2", children: [
839
+ /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-surface-accent-500", children: [
840
+ "Selected files (",
841
+ selectedFiles.length,
842
+ ")"
843
+ ] }),
844
+ /* @__PURE__ */ jsx("div", { className: "max-h-40 overflow-auto space-y-1", children: selectedFiles.map((file_0, index_0) => /* @__PURE__ */ jsxs("div", { className: cls("flex items-center justify-between p-2 rounded", "bg-surface-accent-50 dark:bg-surface-accent-800"), children: [
845
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 mr-2", children: [
846
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "truncate", children: file_0.name }),
847
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-500", children: formatSize(file_0.size) })
848
+ ] }),
849
+ /* @__PURE__ */ jsx(Button, { variant: "text", size: "small", onClick: (e_3) => {
850
+ e_3.stopPropagation();
851
+ handleRemoveFile(index_0);
852
+ }, disabled: uploading, children: "Remove" })
853
+ ] }, `${file_0.name}-${index_0}`)) })
854
+ ] })
855
+ ] })
856
+ ] }),
857
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
858
+ /* @__PURE__ */ jsx(Button, { variant: "text", onClick: handleClose, disabled: uploading, children: "Cancel" }),
859
+ /* @__PURE__ */ jsx(Button, { variant: "filled", onClick: handleUpload, disabled: selectedFiles.length === 0 || uploading, children: uploading ? /* @__PURE__ */ jsxs(Fragment, { children: [
860
+ /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }),
861
+ "Uploading..."
862
+ ] }) : `Upload ${selectedFiles.length > 0 ? `(${selectedFiles.length})` : ""}` })
863
+ ] })
864
+ ] });
865
+ }
866
+ function MediaLibraryView(t0) {
867
+ const $ = c(58);
868
+ const {
869
+ maxFileSize,
870
+ acceptedMimeTypes
871
+ } = t0;
872
+ const controller = useMediaManager();
873
+ const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
874
+ const [viewMode, setViewMode] = useState("grid");
875
+ useRef(null);
876
+ let t1;
877
+ if ($[0] !== controller) {
878
+ t1 = (query) => {
879
+ controller.searchAssets(query ?? "");
880
+ };
881
+ $[0] = controller;
882
+ $[1] = t1;
883
+ } else {
884
+ t1 = $[1];
885
+ }
886
+ const handleSearch = t1;
887
+ let t2;
888
+ if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
889
+ t2 = () => {
890
+ setUploadDialogOpen(true);
891
+ };
892
+ $[2] = t2;
893
+ } else {
894
+ t2 = $[2];
895
+ }
896
+ const handleUploadClick = t2;
897
+ let t3;
898
+ if ($[3] !== controller) {
899
+ t3 = async (files) => {
900
+ for (const file of files) {
901
+ await controller.uploadFile(file);
902
+ }
903
+ setUploadDialogOpen(false);
904
+ };
905
+ $[3] = controller;
906
+ $[4] = t3;
907
+ } else {
908
+ t3 = $[4];
909
+ }
910
+ const handleFileSelect = t3;
911
+ let t4;
912
+ if ($[5] !== controller) {
913
+ t4 = () => {
914
+ controller.refreshAssets();
915
+ };
916
+ $[5] = controller;
917
+ $[6] = t4;
918
+ } else {
919
+ t4 = $[6];
920
+ }
921
+ const handleRefresh = t4;
922
+ let t5;
923
+ if ($[7] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
924
+ t5 = /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "font-semibold", children: "Media Library" });
925
+ $[7] = t5;
926
+ } else {
927
+ t5 = $[7];
928
+ }
929
+ let t6;
930
+ if ($[8] !== controller.totalCount) {
931
+ t6 = controller.totalCount !== void 0 && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "bg-surface-accent-100 dark:bg-surface-accent-800 px-2 py-0.5 rounded-full", children: [
932
+ controller.totalCount,
933
+ " assets"
934
+ ] });
935
+ $[8] = controller.totalCount;
936
+ $[9] = t6;
937
+ } else {
938
+ t6 = $[9];
939
+ }
940
+ let t7;
941
+ if ($[10] !== t6) {
942
+ t7 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
943
+ t5,
944
+ t6
945
+ ] });
946
+ $[10] = t6;
947
+ $[11] = t7;
948
+ } else {
949
+ t7 = $[11];
950
+ }
951
+ let t8;
952
+ if ($[12] !== handleSearch) {
953
+ t8 = /* @__PURE__ */ jsx(SearchBar, { onTextSearch: handleSearch, placeholder: "Search assets...", className: "flex-1 sm:w-64" });
954
+ $[12] = handleSearch;
955
+ $[13] = t8;
956
+ } else {
957
+ t8 = $[13];
958
+ }
959
+ let t9;
960
+ if ($[14] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
961
+ t9 = () => setViewMode("grid");
962
+ $[14] = t9;
963
+ } else {
964
+ t9 = $[14];
965
+ }
966
+ const t10 = viewMode === "grid" && "bg-surface-accent-100 dark:bg-surface-accent-800";
967
+ let t11;
968
+ if ($[15] !== t10) {
969
+ t11 = cls(t10);
970
+ $[15] = t10;
971
+ $[16] = t11;
972
+ } else {
973
+ t11 = $[16];
974
+ }
975
+ let t12;
976
+ if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
977
+ t12 = /* @__PURE__ */ jsx(AppsIcon, { size: "small" });
978
+ $[17] = t12;
979
+ } else {
980
+ t12 = $[17];
981
+ }
982
+ let t13;
983
+ if ($[18] !== t11) {
984
+ t13 = /* @__PURE__ */ jsx(Tooltip, { title: "Grid view", children: /* @__PURE__ */ jsx(IconButton, { onClick: t9, className: t11, children: t12 }) });
985
+ $[18] = t11;
986
+ $[19] = t13;
987
+ } else {
988
+ t13 = $[19];
989
+ }
990
+ let t14;
991
+ if ($[20] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
992
+ t14 = () => setViewMode("list");
993
+ $[20] = t14;
994
+ } else {
995
+ t14 = $[20];
996
+ }
997
+ const t15 = viewMode === "list" && "bg-surface-accent-100 dark:bg-surface-accent-800";
998
+ let t16;
999
+ if ($[21] !== t15) {
1000
+ t16 = cls(t15);
1001
+ $[21] = t15;
1002
+ $[22] = t16;
1003
+ } else {
1004
+ t16 = $[22];
1005
+ }
1006
+ let t17;
1007
+ if ($[23] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1008
+ t17 = /* @__PURE__ */ jsx(Icon, { iconKey: "list", size: "small" });
1009
+ $[23] = t17;
1010
+ } else {
1011
+ t17 = $[23];
1012
+ }
1013
+ let t18;
1014
+ if ($[24] !== t16) {
1015
+ t18 = /* @__PURE__ */ jsx(Tooltip, { title: "List view", children: /* @__PURE__ */ jsx(IconButton, { onClick: t14, className: t16, children: t17 }) });
1016
+ $[24] = t16;
1017
+ $[25] = t18;
1018
+ } else {
1019
+ t18 = $[25];
1020
+ }
1021
+ let t19;
1022
+ if ($[26] !== t13 || $[27] !== t18) {
1023
+ t19 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 border-l border-surface-accent-200 dark:border-surface-accent-700 pl-2 ml-2", children: [
1024
+ t13,
1025
+ t18
1026
+ ] });
1027
+ $[26] = t13;
1028
+ $[27] = t18;
1029
+ $[28] = t19;
1030
+ } else {
1031
+ t19 = $[28];
1032
+ }
1033
+ let t20;
1034
+ if ($[29] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1035
+ t20 = /* @__PURE__ */ jsx(RefreshIcon, { size: "small" });
1036
+ $[29] = t20;
1037
+ } else {
1038
+ t20 = $[29];
1039
+ }
1040
+ let t21;
1041
+ if ($[30] !== controller.loading || $[31] !== handleRefresh) {
1042
+ t21 = /* @__PURE__ */ jsx(Tooltip, { title: "Refresh", children: /* @__PURE__ */ jsx(IconButton, { onClick: handleRefresh, disabled: controller.loading, children: t20 }) });
1043
+ $[30] = controller.loading;
1044
+ $[31] = handleRefresh;
1045
+ $[32] = t21;
1046
+ } else {
1047
+ t21 = $[32];
1048
+ }
1049
+ let t22;
1050
+ if ($[33] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1051
+ t22 = /* @__PURE__ */ jsxs(Button, { variant: "filled", onClick: handleUploadClick, children: [
1052
+ /* @__PURE__ */ jsx(AddIcon, { size: "small" }),
1053
+ "Upload"
1054
+ ] });
1055
+ $[33] = t22;
1056
+ } else {
1057
+ t22 = $[33];
1058
+ }
1059
+ let t23;
1060
+ if ($[34] !== t19 || $[35] !== t21 || $[36] !== t8) {
1061
+ t23 = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full sm:w-auto", children: [
1062
+ t8,
1063
+ t19,
1064
+ t21,
1065
+ t22
1066
+ ] });
1067
+ $[34] = t19;
1068
+ $[35] = t21;
1069
+ $[36] = t8;
1070
+ $[37] = t23;
1071
+ } else {
1072
+ t23 = $[37];
1073
+ }
1074
+ let t24;
1075
+ if ($[38] !== t23 || $[39] !== t7) {
1076
+ t24 = /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 border-b border-surface-accent-200 dark:border-surface-accent-700 bg-surface-50 dark:bg-surface-900", children: /* @__PURE__ */ jsx(Container, { maxWidth: "6xl", className: "py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between", children: [
1077
+ t7,
1078
+ t23
1079
+ ] }) }) });
1080
+ $[38] = t23;
1081
+ $[39] = t7;
1082
+ $[40] = t24;
1083
+ } else {
1084
+ t24 = $[40];
1085
+ }
1086
+ let t25;
1087
+ if ($[41] !== controller || $[42] !== handleRefresh || $[43] !== viewMode) {
1088
+ t25 = /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsx(Container, { maxWidth: "6xl", className: "py-6", children: controller.loading && controller.assets.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-64", children: /* @__PURE__ */ jsx(CircularProgress, {}) }) : controller.error ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-64 gap-4", children: [
1089
+ /* @__PURE__ */ jsxs(Typography, { className: "text-red-500", children: [
1090
+ "Error loading assets: ",
1091
+ controller.error.message
1092
+ ] }),
1093
+ /* @__PURE__ */ jsx(Button, { onClick: handleRefresh, children: "Try Again" })
1094
+ ] }) : controller.assets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-64 gap-4", children: [
1095
+ /* @__PURE__ */ jsx(Typography, { className: "text-surface-accent-500", children: "No media assets yet" }),
1096
+ /* @__PURE__ */ jsxs(Button, { onClick: handleUploadClick, children: [
1097
+ /* @__PURE__ */ jsx(AddIcon, { size: "small" }),
1098
+ "Upload your first file"
1099
+ ] })
1100
+ ] }) : /* @__PURE__ */ jsx("div", { className: cls(viewMode === "grid" ? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4" : "flex flex-col gap-2"), children: controller.assets.map((asset) => /* @__PURE__ */ jsx(MediaAssetCard, { asset, viewMode, onClick: () => controller.selectAsset(asset), selected: controller.selectedAsset?.id === asset.id }, asset.id)) }) }) });
1101
+ $[41] = controller;
1102
+ $[42] = handleRefresh;
1103
+ $[43] = viewMode;
1104
+ $[44] = t25;
1105
+ } else {
1106
+ t25 = $[44];
1107
+ }
1108
+ let t26;
1109
+ if ($[45] !== controller) {
1110
+ t26 = controller.selectedAsset && /* @__PURE__ */ jsx(MediaAssetDetails, { asset: controller.selectedAsset, onClose: () => controller.selectAsset(void 0), onUpdate: controller.updateAsset, onDelete: controller.deleteAsset });
1111
+ $[45] = controller;
1112
+ $[46] = t26;
1113
+ } else {
1114
+ t26 = $[46];
1115
+ }
1116
+ let t27;
1117
+ if ($[47] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1118
+ t27 = () => setUploadDialogOpen(false);
1119
+ $[47] = t27;
1120
+ } else {
1121
+ t27 = $[47];
1122
+ }
1123
+ let t28;
1124
+ if ($[48] !== acceptedMimeTypes || $[49] !== handleFileSelect || $[50] !== maxFileSize || $[51] !== uploadDialogOpen) {
1125
+ t28 = /* @__PURE__ */ jsx(MediaUploadDialog, { open: uploadDialogOpen, onClose: t27, onUpload: handleFileSelect, maxFileSize, acceptedMimeTypes });
1126
+ $[48] = acceptedMimeTypes;
1127
+ $[49] = handleFileSelect;
1128
+ $[50] = maxFileSize;
1129
+ $[51] = uploadDialogOpen;
1130
+ $[52] = t28;
1131
+ } else {
1132
+ t28 = $[52];
1133
+ }
1134
+ let t29;
1135
+ if ($[53] !== t24 || $[54] !== t25 || $[55] !== t26 || $[56] !== t28) {
1136
+ t29 = /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col overflow-hidden", children: [
1137
+ t24,
1138
+ t25,
1139
+ t26,
1140
+ t28
1141
+ ] });
1142
+ $[53] = t24;
1143
+ $[54] = t25;
1144
+ $[55] = t26;
1145
+ $[56] = t28;
1146
+ $[57] = t29;
1147
+ } else {
1148
+ t29 = $[57];
1149
+ }
1150
+ return t29;
1151
+ }
1152
+ const DEFAULT_STORAGE_PATH = "media";
1153
+ const DEFAULT_COLLECTION_PATH = "media_assets";
1154
+ const MediaManagerConfigContext = createContext(null);
1155
+ function useMediaManagerConfig() {
1156
+ const config = useContext(MediaManagerConfigContext);
1157
+ if (!config) {
1158
+ throw new Error("useMediaManagerConfig must be used within MediaManagerConfigProvider");
1159
+ }
1160
+ return config;
1161
+ }
1162
+ function MediaLibraryViewInternal() {
1163
+ const $ = c(14);
1164
+ const config = useMediaManagerConfig();
1165
+ const t0 = config.storagePath ?? DEFAULT_STORAGE_PATH;
1166
+ const t1 = config.collectionPath ?? DEFAULT_COLLECTION_PATH;
1167
+ let t2;
1168
+ if ($[0] !== config.bucket || $[1] !== config.dataSourceDelegate || $[2] !== config.storageSource || $[3] !== config.thumbnailPath || $[4] !== config.thumbnailSizes || $[5] !== t0 || $[6] !== t1) {
1169
+ t2 = {
1170
+ storageSource: config.storageSource,
1171
+ dataSourceDelegate: config.dataSourceDelegate,
1172
+ storagePath: t0,
1173
+ collectionPath: t1,
1174
+ bucket: config.bucket,
1175
+ thumbnailSizes: config.thumbnailSizes,
1176
+ thumbnailPath: config.thumbnailPath
1177
+ };
1178
+ $[0] = config.bucket;
1179
+ $[1] = config.dataSourceDelegate;
1180
+ $[2] = config.storageSource;
1181
+ $[3] = config.thumbnailPath;
1182
+ $[4] = config.thumbnailSizes;
1183
+ $[5] = t0;
1184
+ $[6] = t1;
1185
+ $[7] = t2;
1186
+ } else {
1187
+ t2 = $[7];
1188
+ }
1189
+ const controller = useMediaManagerController(t2);
1190
+ let t3;
1191
+ if ($[8] !== config.acceptedMimeTypes || $[9] !== config.maxFileSize) {
1192
+ t3 = /* @__PURE__ */ jsx(MediaLibraryView, { maxFileSize: config.maxFileSize, acceptedMimeTypes: config.acceptedMimeTypes });
1193
+ $[8] = config.acceptedMimeTypes;
1194
+ $[9] = config.maxFileSize;
1195
+ $[10] = t3;
1196
+ } else {
1197
+ t3 = $[10];
1198
+ }
1199
+ let t4;
1200
+ if ($[11] !== controller || $[12] !== t3) {
1201
+ t4 = /* @__PURE__ */ jsx(MediaManagerProvider, { controller, children: t3 });
1202
+ $[11] = controller;
1203
+ $[12] = t3;
1204
+ $[13] = t4;
1205
+ } else {
1206
+ t4 = $[13];
1207
+ }
1208
+ return t4;
1209
+ }
1210
+ function buildMediaView() {
1211
+ return {
1212
+ path: "media",
1213
+ name: "Media Library",
1214
+ description: "Manage your media files and assets",
1215
+ group: "Media",
1216
+ icon: "perm_media",
1217
+ view: /* @__PURE__ */ jsx(MediaLibraryViewInternal, {})
1218
+ };
1219
+ }
1220
+ const MEDIA_VIEW = buildMediaView();
1221
+ function useMediaManagerPlugin(props) {
1222
+ return useMemo(() => ({
1223
+ key: "media_manager",
1224
+ views: [MEDIA_VIEW],
1225
+ provider: {
1226
+ Component: ({
1227
+ children
1228
+ }) => /* @__PURE__ */ jsx(MediaManagerConfigContext.Provider, { value: props, children })
1229
+ }
1230
+ }), []);
1231
+ }
1232
+ function MediaLibraryCard(t0) {
1233
+ const $ = c(3);
1234
+ const {
1235
+ group
1236
+ } = t0;
1237
+ if (group !== "Media") {
1238
+ return null;
1239
+ }
1240
+ let t1;
1241
+ if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1242
+ t1 = cls("p-4 cursor-pointer", "hover:bg-surface-accent-100 dark:hover:bg-surface-accent-800", "transition-colors duration-200", "flex flex-col gap-2");
1243
+ $[0] = t1;
1244
+ } else {
1245
+ t1 = $[0];
1246
+ }
1247
+ let t2;
1248
+ if ($[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1249
+ t2 = /* @__PURE__ */ jsx("div", { className: cls("w-10 h-10 rounded-lg", "bg-primary/10 dark:bg-primary/20", "flex items-center justify-center"), children: /* @__PURE__ */ jsx(ImageIcon, { className: "text-primary", size: "medium" }) });
1250
+ $[1] = t2;
1251
+ } else {
1252
+ t2 = $[1];
1253
+ }
1254
+ let t3;
1255
+ if ($[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
1256
+ t3 = /* @__PURE__ */ jsx(Link, { to: "/media", className: "no-underline", children: /* @__PURE__ */ jsx(Card, { className: t1, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1257
+ t2,
1258
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1259
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", className: "font-medium", children: "Media Library" }),
1260
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-surface-accent-600 dark:text-surface-accent-400", children: "Manage images and files" })
1261
+ ] })
1262
+ ] }) }) });
1263
+ $[2] = t3;
1264
+ } else {
1265
+ t3 = $[2];
1266
+ }
1267
+ return t3;
1268
+ }
1269
+ export {
1270
+ MediaAssetCard,
1271
+ MediaAssetDetails,
1272
+ MediaLibraryCard,
1273
+ MediaLibraryView,
1274
+ MediaManagerProvider,
1275
+ MediaUploadDialog,
1276
+ useMediaManager,
1277
+ useMediaManagerController,
1278
+ useMediaManagerPlugin
1279
+ };
1280
+ //# sourceMappingURL=index.es.js.map