@firecms/media_manager 3.1.0-canary.02232f4
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/LICENSE +6 -0
- package/dist/MediaManagerProvider.d.ts +14 -0
- package/dist/components/MediaAssetCard.d.ts +13 -0
- package/dist/components/MediaAssetDetails.d.ts +11 -0
- package/dist/components/MediaLibraryCard.d.ts +8 -0
- package/dist/components/MediaLibraryView.d.ts +9 -0
- package/dist/components/MediaUploadDialog.d.ts +13 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +1741 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +1737 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/locales/de.d.ts +45 -0
- package/dist/locales/en.d.ts +45 -0
- package/dist/locales/es.d.ts +45 -0
- package/dist/locales/fr.d.ts +45 -0
- package/dist/locales/hi.d.ts +45 -0
- package/dist/locales/it.d.ts +45 -0
- package/dist/locales/pt.d.ts +45 -0
- package/dist/types/config.d.ts +87 -0
- package/dist/types/controller.d.ts +66 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/media_asset.d.ts +70 -0
- package/dist/useMediaManagerController.d.ts +18 -0
- package/dist/useMediaManagerPlugin.d.ts +23 -0
- package/package.json +58 -0
- package/src/MediaManagerProvider.tsx +34 -0
- package/src/components/MediaAssetCard.tsx +134 -0
- package/src/components/MediaAssetDetails.tsx +353 -0
- package/src/components/MediaLibraryCard.tsx +58 -0
- package/src/components/MediaLibraryView.tsx +196 -0
- package/src/components/MediaUploadDialog.tsx +266 -0
- package/src/components/index.ts +5 -0
- package/src/index.ts +10 -0
- package/src/locales/de.ts +45 -0
- package/src/locales/en.ts +45 -0
- package/src/locales/es.ts +45 -0
- package/src/locales/fr.ts +45 -0
- package/src/locales/hi.ts +45 -0
- package/src/locales/it.ts +45 -0
- package/src/locales/pt.ts +45 -0
- package/src/types/config.ts +100 -0
- package/src/types/controller.ts +79 -0
- package/src/types/index.ts +3 -0
- package/src/types/media_asset.ts +84 -0
- package/src/useMediaManagerController.tsx +387 -0
- package/src/useMediaManagerPlugin.tsx +114 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const mediaManagerTranslationsPt: {
|
|
2
|
+
media_library: string;
|
|
3
|
+
media_library_description: string;
|
|
4
|
+
media_search_assets: string;
|
|
5
|
+
media_grid_view: string;
|
|
6
|
+
media_list_view: string;
|
|
7
|
+
media_refresh: string;
|
|
8
|
+
media_upload: string;
|
|
9
|
+
media_error_loading: string;
|
|
10
|
+
media_try_again: string;
|
|
11
|
+
media_no_assets: string;
|
|
12
|
+
media_upload_first_file: string;
|
|
13
|
+
media_asset_updated: string;
|
|
14
|
+
media_error_updating: string;
|
|
15
|
+
media_asset_deleted: string;
|
|
16
|
+
media_error_deleting: string;
|
|
17
|
+
media_error_getting_url: string;
|
|
18
|
+
media_preview_not_available: string;
|
|
19
|
+
media_dimensions: string;
|
|
20
|
+
media_size: string;
|
|
21
|
+
media_type: string;
|
|
22
|
+
media_created: string;
|
|
23
|
+
media_file_name: string;
|
|
24
|
+
media_title: string;
|
|
25
|
+
media_alt_text: string;
|
|
26
|
+
media_recommended_seo: string;
|
|
27
|
+
media_caption: string;
|
|
28
|
+
media_tags: string;
|
|
29
|
+
media_add_tag: string;
|
|
30
|
+
media_add: string;
|
|
31
|
+
media_save_changes: string;
|
|
32
|
+
media_delete_asset: string;
|
|
33
|
+
media_delete_confirmation: string;
|
|
34
|
+
media_file_too_large: string;
|
|
35
|
+
media_file_type_not_allowed: string;
|
|
36
|
+
media_upload_failed: string;
|
|
37
|
+
media_upload_files: string;
|
|
38
|
+
media_drop_files: string;
|
|
39
|
+
media_max_file_size: string;
|
|
40
|
+
media_selected_files_count: string;
|
|
41
|
+
media_remove: string;
|
|
42
|
+
media_uploading: string;
|
|
43
|
+
media_manage_description: string;
|
|
44
|
+
media_assets_count: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DataSourceDelegate, StorageSource } from "@firecms/core";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for a thumbnail size.
|
|
4
|
+
* Thumbnails are generated client-side before upload.
|
|
5
|
+
*/
|
|
6
|
+
export interface ThumbnailSize {
|
|
7
|
+
/**
|
|
8
|
+
* Unique name for this size (e.g., "small", "medium", "large").
|
|
9
|
+
* Used as a key in the thumbnails record.
|
|
10
|
+
*/
|
|
11
|
+
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* Maximum width in pixels.
|
|
14
|
+
* The image will be scaled to fit within this width while maintaining aspect ratio.
|
|
15
|
+
*/
|
|
16
|
+
width: number;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum height in pixels.
|
|
19
|
+
* The image will be scaled to fit within this height while maintaining aspect ratio.
|
|
20
|
+
*/
|
|
21
|
+
height: number;
|
|
22
|
+
/**
|
|
23
|
+
* JPEG quality for the thumbnail (0-1).
|
|
24
|
+
* @default 0.8
|
|
25
|
+
*/
|
|
26
|
+
quality?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration options for the Media Manager plugin.
|
|
30
|
+
*/
|
|
31
|
+
export interface MediaManagerConfig {
|
|
32
|
+
/**
|
|
33
|
+
* Storage source for file operations (upload, download, delete).
|
|
34
|
+
* Typically useFirebaseStorageSource() or similar.
|
|
35
|
+
*/
|
|
36
|
+
storageSource: StorageSource;
|
|
37
|
+
/**
|
|
38
|
+
* Data source delegate for metadata persistence.
|
|
39
|
+
* Typically useFirestoreDelegate() or similar.
|
|
40
|
+
*/
|
|
41
|
+
dataSourceDelegate: DataSourceDelegate;
|
|
42
|
+
/**
|
|
43
|
+
* Path in storage where media files will be uploaded.
|
|
44
|
+
* @default "media"
|
|
45
|
+
*/
|
|
46
|
+
storagePath?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Collection path in the database for storing asset metadata.
|
|
49
|
+
* @default "media_assets"
|
|
50
|
+
*/
|
|
51
|
+
collectionPath?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional bucket name for storage operations.
|
|
54
|
+
* If not specified, uses the default bucket.
|
|
55
|
+
*/
|
|
56
|
+
bucket?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Maximum file size allowed for uploads (in bytes).
|
|
59
|
+
* @default 52428800 (50MB)
|
|
60
|
+
*/
|
|
61
|
+
maxFileSize?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Allowed MIME types for uploads.
|
|
64
|
+
* If not specified, all file types are allowed.
|
|
65
|
+
*/
|
|
66
|
+
acceptedMimeTypes?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Thumbnail sizes to generate on upload.
|
|
69
|
+
* Thumbnails are generated client-side using canvas before upload.
|
|
70
|
+
* If not specified, no thumbnails are generated.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* thumbnailSizes: [
|
|
75
|
+
* { name: "small", width: 150, height: 150 },
|
|
76
|
+
* { name: "medium", width: 400, height: 400 }
|
|
77
|
+
* ]
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
thumbnailSizes?: ThumbnailSize[];
|
|
81
|
+
/**
|
|
82
|
+
* Subfolder path for storing thumbnails.
|
|
83
|
+
* Thumbnails are stored under `{storagePath}/{thumbnailPath}/{sizeName}/`.
|
|
84
|
+
* @default "thumbs"
|
|
85
|
+
*/
|
|
86
|
+
thumbnailPath?: string;
|
|
87
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MediaAsset } from "./media_asset";
|
|
2
|
+
/**
|
|
3
|
+
* Controller interface for managing media assets.
|
|
4
|
+
* Provides methods for CRUD operations and state management.
|
|
5
|
+
*/
|
|
6
|
+
export interface MediaManagerController {
|
|
7
|
+
/**
|
|
8
|
+
* Whether the controller is currently loading data
|
|
9
|
+
*/
|
|
10
|
+
loading: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Error if any operation failed
|
|
13
|
+
*/
|
|
14
|
+
error?: Error;
|
|
15
|
+
/**
|
|
16
|
+
* List of loaded media assets
|
|
17
|
+
*/
|
|
18
|
+
assets: MediaAsset[];
|
|
19
|
+
/**
|
|
20
|
+
* Total count of assets (may differ from assets.length during pagination)
|
|
21
|
+
*/
|
|
22
|
+
totalCount?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Currently selected asset (for details view)
|
|
25
|
+
*/
|
|
26
|
+
selectedAsset?: MediaAsset;
|
|
27
|
+
/**
|
|
28
|
+
* Select an asset for viewing/editing
|
|
29
|
+
*/
|
|
30
|
+
selectAsset: (asset: MediaAsset | undefined) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Upload a new file to the media library
|
|
33
|
+
* @param file The file to upload
|
|
34
|
+
* @param metadata Optional metadata to attach
|
|
35
|
+
* @returns The created MediaAsset
|
|
36
|
+
*/
|
|
37
|
+
uploadFile: (file: File, metadata?: Partial<Pick<MediaAsset, "title" | "altText" | "caption" | "tags">>) => Promise<MediaAsset>;
|
|
38
|
+
/**
|
|
39
|
+
* Delete an asset from storage and database
|
|
40
|
+
* @param assetId The ID of the asset to delete
|
|
41
|
+
*/
|
|
42
|
+
deleteAsset: (assetId: string) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Update asset metadata
|
|
45
|
+
* @param assetId The ID of the asset to update
|
|
46
|
+
* @param data The fields to update
|
|
47
|
+
*/
|
|
48
|
+
updateAsset: (assetId: string, data: Partial<MediaAsset>) => Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Refresh the assets list from the database
|
|
51
|
+
*/
|
|
52
|
+
refreshAssets: () => Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Search assets by text
|
|
55
|
+
* @param query Search query string
|
|
56
|
+
*/
|
|
57
|
+
searchAssets: (query: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Storage path configuration
|
|
60
|
+
*/
|
|
61
|
+
storagePath: string;
|
|
62
|
+
/**
|
|
63
|
+
* Collection path configuration
|
|
64
|
+
*/
|
|
65
|
+
collectionPath: string;
|
|
66
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a media asset stored in the media library.
|
|
3
|
+
* This interface defines the metadata stored in the database for each file.
|
|
4
|
+
*/
|
|
5
|
+
export interface MediaAsset {
|
|
6
|
+
/**
|
|
7
|
+
* Unique identifier for the asset (document ID in the database)
|
|
8
|
+
*/
|
|
9
|
+
id: string;
|
|
10
|
+
/**
|
|
11
|
+
* Original file name
|
|
12
|
+
*/
|
|
13
|
+
fileName: string;
|
|
14
|
+
/**
|
|
15
|
+
* Path in storage where the file is located
|
|
16
|
+
*/
|
|
17
|
+
storagePath: string;
|
|
18
|
+
/**
|
|
19
|
+
* Cached download URL for quick access
|
|
20
|
+
*/
|
|
21
|
+
downloadURL?: string;
|
|
22
|
+
/**
|
|
23
|
+
* MIME type of the file (e.g., "image/jpeg", "application/pdf")
|
|
24
|
+
*/
|
|
25
|
+
mimeType: string;
|
|
26
|
+
/**
|
|
27
|
+
* File size in bytes
|
|
28
|
+
*/
|
|
29
|
+
size: number;
|
|
30
|
+
/**
|
|
31
|
+
* Dimensions for image/video files
|
|
32
|
+
*/
|
|
33
|
+
dimensions?: {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* User-defined title for the asset
|
|
39
|
+
*/
|
|
40
|
+
title?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Alternative text for accessibility (SEO recommended)
|
|
43
|
+
*/
|
|
44
|
+
altText?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Optional caption/description
|
|
47
|
+
*/
|
|
48
|
+
caption?: string;
|
|
49
|
+
/**
|
|
50
|
+
* User-defined tags for categorization
|
|
51
|
+
*/
|
|
52
|
+
tags?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Timestamp when the asset was created
|
|
55
|
+
*/
|
|
56
|
+
createdAt: Date;
|
|
57
|
+
/**
|
|
58
|
+
* Timestamp when the asset was last updated
|
|
59
|
+
*/
|
|
60
|
+
updatedAt: Date;
|
|
61
|
+
/**
|
|
62
|
+
* Storage bucket name (for multi-bucket setups)
|
|
63
|
+
*/
|
|
64
|
+
bucket?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Thumbnail URLs keyed by size name (e.g., "small", "medium").
|
|
67
|
+
* These are generated during upload based on thumbnailSizes config.
|
|
68
|
+
*/
|
|
69
|
+
thumbnails?: Record<string, string>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DataSourceDelegate, StorageSource } from "@firecms/core";
|
|
2
|
+
import { MediaManagerController, ThumbnailSize } from "./types";
|
|
3
|
+
export interface UseMediaManagerControllerProps {
|
|
4
|
+
storageSource: StorageSource;
|
|
5
|
+
dataSourceDelegate: DataSourceDelegate;
|
|
6
|
+
storagePath: string;
|
|
7
|
+
collectionPath: string;
|
|
8
|
+
bucket?: string;
|
|
9
|
+
/** Thumbnail sizes to generate on upload */
|
|
10
|
+
thumbnailSizes?: ThumbnailSize[];
|
|
11
|
+
/** Path prefix for thumbnails. Default: "thumbs" */
|
|
12
|
+
thumbnailPath?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Hook that creates a MediaManagerController for managing media assets.
|
|
16
|
+
* Handles all CRUD operations for files and their metadata.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useMediaManagerController({ storageSource, dataSourceDelegate, storagePath, collectionPath, bucket, thumbnailSizes, thumbnailPath }: UseMediaManagerControllerProps): MediaManagerController;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FireCMSPlugin } from "@firecms/core";
|
|
2
|
+
import { MediaManagerConfig } from "./types";
|
|
3
|
+
export interface MediaManagerPluginProps extends MediaManagerConfig {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Hook to create the Media Manager plugin for FireCMS.
|
|
7
|
+
*
|
|
8
|
+
* The plugin automatically registers the Media Library view in the navigation.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const { plugin: mediaManagerPlugin } = useMediaManagerPlugin({
|
|
13
|
+
* storageSource,
|
|
14
|
+
* dataSourceDelegate: firestoreDelegate,
|
|
15
|
+
* storagePath: "media",
|
|
16
|
+
* collectionPath: "media_assets"
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Add plugin to your plugins array - view is auto-registered
|
|
20
|
+
* const plugins = [mediaManagerPlugin, ...otherPlugins];
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function useMediaManagerPlugin(props: MediaManagerPluginProps): FireCMSPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@firecms/media_manager",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "3.1.0-canary.02232f4",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"firebase",
|
|
10
|
+
"cms",
|
|
11
|
+
"media",
|
|
12
|
+
"media manager",
|
|
13
|
+
"asset manager",
|
|
14
|
+
"firecms",
|
|
15
|
+
"storage"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.es.js",
|
|
21
|
+
"require": "./dist/index.umd.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.umd.js",
|
|
26
|
+
"module": "./dist/index.es.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"source": "src/index.ts",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@firecms/core": "^3.1.0-canary.02232f4",
|
|
31
|
+
"@firecms/formex": "^3.1.0-canary.02232f4",
|
|
32
|
+
"@firecms/ui": "^3.1.0-canary.02232f4"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": ">=18.3.1 || >=19.0.0",
|
|
36
|
+
"react-dom": ">=18.3.1 || >=19.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.19.17",
|
|
40
|
+
"@types/react": "^19.2.3",
|
|
41
|
+
"@types/react-dom": "^19.2.3",
|
|
42
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
43
|
+
"babel-plugin-react-compiler": "^19.0.0-beta-af1b7da-20250417",
|
|
44
|
+
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vite": "^7.2.4"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "vite",
|
|
50
|
+
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
|
|
51
|
+
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"src"
|
|
56
|
+
],
|
|
57
|
+
"gitHead": "6281205d9f39f85991e1d8533474bfb9a542ed03"
|
|
58
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { createContext, PropsWithChildren, useContext } from "react";
|
|
2
|
+
import { MediaManagerController } from "./types";
|
|
3
|
+
|
|
4
|
+
const MediaManagerContext = createContext<MediaManagerController | undefined>(undefined);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook to access the MediaManagerController from context.
|
|
8
|
+
* Must be used within a MediaManagerProvider.
|
|
9
|
+
*/
|
|
10
|
+
export function useMediaManager(): MediaManagerController {
|
|
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
|
+
|
|
18
|
+
export interface MediaManagerProviderProps {
|
|
19
|
+
controller: MediaManagerController;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Provider component that makes the MediaManagerController available to all children.
|
|
24
|
+
*/
|
|
25
|
+
export function MediaManagerProvider({
|
|
26
|
+
controller,
|
|
27
|
+
children
|
|
28
|
+
}: PropsWithChildren<MediaManagerProviderProps>) {
|
|
29
|
+
return (
|
|
30
|
+
<MediaManagerContext.Provider value={controller}>
|
|
31
|
+
{children}
|
|
32
|
+
</MediaManagerContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
Typography,
|
|
5
|
+
cls,
|
|
6
|
+
ImageIcon,
|
|
7
|
+
DescriptionIcon,
|
|
8
|
+
VideoLibraryIcon,
|
|
9
|
+
AudiotrackIcon,
|
|
10
|
+
CheckIcon,
|
|
11
|
+
defaultBorderMixin
|
|
12
|
+
} from "@firecms/ui";
|
|
13
|
+
import { MediaAsset } from "../types";
|
|
14
|
+
|
|
15
|
+
export interface MediaAssetCardProps {
|
|
16
|
+
asset: MediaAsset;
|
|
17
|
+
viewMode: "grid" | "list";
|
|
18
|
+
onClick: () => void;
|
|
19
|
+
selected?: boolean;
|
|
20
|
+
/** Preferred thumbnail size to display (e.g., "small", "medium") */
|
|
21
|
+
thumbnailSize?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Card component for displaying a media asset in the grid or list.
|
|
26
|
+
*/
|
|
27
|
+
export function MediaAssetCard({
|
|
28
|
+
asset,
|
|
29
|
+
viewMode,
|
|
30
|
+
onClick,
|
|
31
|
+
selected,
|
|
32
|
+
thumbnailSize = "small"
|
|
33
|
+
}: MediaAssetCardProps) {
|
|
34
|
+
const isImage = asset.mimeType.startsWith("image/");
|
|
35
|
+
const isVideo = asset.mimeType.startsWith("video/");
|
|
36
|
+
const isAudio = asset.mimeType.startsWith("audio/");
|
|
37
|
+
|
|
38
|
+
const FileIcon = isImage ? ImageIcon
|
|
39
|
+
: isVideo ? VideoLibraryIcon
|
|
40
|
+
: isAudio ? AudiotrackIcon
|
|
41
|
+
: DescriptionIcon;
|
|
42
|
+
|
|
43
|
+
const formatSize = (bytes: number): string => {
|
|
44
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
45
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
46
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Use thumbnail if available, fallback to downloadURL
|
|
50
|
+
const imageUrl = asset.thumbnails?.[thumbnailSize] ?? asset.downloadURL;
|
|
51
|
+
|
|
52
|
+
const thumbnail = isImage && imageUrl ? (
|
|
53
|
+
<img
|
|
54
|
+
src={imageUrl}
|
|
55
|
+
alt={asset.altText || asset.fileName}
|
|
56
|
+
className="w-full h-full object-cover transition-transform duration-200 group-hover:scale-105"
|
|
57
|
+
loading="lazy"
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<div className="w-full h-full flex items-center justify-center bg-surface-100 dark:bg-surface-800">
|
|
61
|
+
<FileIcon size="large" className="text-surface-400 dark:text-surface-500" />
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (viewMode === "list") {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cls(
|
|
69
|
+
"p-3 cursor-pointer flex items-center gap-3 rounded-lg",
|
|
70
|
+
"hover:bg-surface-100 dark:hover:bg-surface-800",
|
|
71
|
+
"transition-colors duration-150",
|
|
72
|
+
`border ${defaultBorderMixin}`,
|
|
73
|
+
selected && "ring-2 ring-primary bg-primary/5"
|
|
74
|
+
)}
|
|
75
|
+
onClick={onClick}
|
|
76
|
+
>
|
|
77
|
+
<div className="w-12 h-12 rounded-md overflow-hidden flex-shrink-0 bg-surface-100 dark:bg-surface-800">
|
|
78
|
+
{thumbnail}
|
|
79
|
+
</div>
|
|
80
|
+
<div className="flex-1 min-w-0">
|
|
81
|
+
<Typography variant="body2" className="font-medium truncate text-surface-900 dark:text-white">
|
|
82
|
+
{asset.title || asset.fileName}
|
|
83
|
+
</Typography>
|
|
84
|
+
<Typography variant="caption" color="secondary">
|
|
85
|
+
{formatSize(asset.size)} • {asset.mimeType.split("/")[1]?.toUpperCase()}
|
|
86
|
+
</Typography>
|
|
87
|
+
</div>
|
|
88
|
+
{selected && (
|
|
89
|
+
<div className="w-6 h-6 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
90
|
+
<CheckIcon size="smallest" className="text-white" />
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Card
|
|
99
|
+
className={cls(
|
|
100
|
+
"cursor-pointer overflow-hidden group relative",
|
|
101
|
+
"transition-all duration-200",
|
|
102
|
+
"hover:shadow-lg hover:-translate-y-0.5",
|
|
103
|
+
selected && "ring-2 ring-primary"
|
|
104
|
+
)}
|
|
105
|
+
onClick={onClick}
|
|
106
|
+
>
|
|
107
|
+
<div className="aspect-square relative overflow-hidden bg-surface-100 dark:bg-surface-800">
|
|
108
|
+
{thumbnail}
|
|
109
|
+
|
|
110
|
+
{/* Hover overlay */}
|
|
111
|
+
<div className={cls(
|
|
112
|
+
"absolute inset-0 bg-black/0 group-hover:bg-black/20",
|
|
113
|
+
"transition-colors duration-200"
|
|
114
|
+
)} />
|
|
115
|
+
|
|
116
|
+
{/* Selection indicator */}
|
|
117
|
+
{selected && (
|
|
118
|
+
<div className="absolute top-2 right-2 w-6 h-6 rounded-full bg-primary flex items-center justify-center shadow-md">
|
|
119
|
+
<CheckIcon size="smallest" className="text-white" />
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className="p-3">
|
|
125
|
+
<Typography variant="body2" className="font-medium truncate text-surface-900 dark:text-white">
|
|
126
|
+
{asset.title || asset.fileName}
|
|
127
|
+
</Typography>
|
|
128
|
+
<Typography variant="caption" color="secondary" className="truncate block mt-0.5">
|
|
129
|
+
{formatSize(asset.size)} • {asset.mimeType.split("/")[1]?.toUpperCase()}
|
|
130
|
+
</Typography>
|
|
131
|
+
</div>
|
|
132
|
+
</Card>
|
|
133
|
+
);
|
|
134
|
+
}
|