@muhgholy/next-drive 1.0.1 → 1.1.0
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/README.md
CHANGED
|
@@ -61,8 +61,9 @@ export const drive = driveConfiguration({
|
|
|
61
61
|
information: async (req): Promise<TDriveConfigInformation> => {
|
|
62
62
|
// Implement your auth verification here
|
|
63
63
|
const auth = await verifyAuth(req);
|
|
64
|
+
if (!auth) throw new Error("Unauthenticated");
|
|
64
65
|
return {
|
|
65
|
-
key:
|
|
66
|
+
key: { userId: auth.userId },
|
|
66
67
|
storage: { quotaInBytes: 1024 * 1024 * 1024 }, // 1GB limit
|
|
67
68
|
};
|
|
68
69
|
},
|
|
@@ -132,7 +133,7 @@ You can use the exported `driveFileSchemaZod` to validate file data in your form
|
|
|
132
133
|
|
|
133
134
|
```typescript
|
|
134
135
|
import { z } from "zod";
|
|
135
|
-
import { driveFileSchemaZod } from "@muhgholy/next-drive/
|
|
136
|
+
import { driveFileSchemaZod } from "@muhgholy/next-drive/client";
|
|
136
137
|
|
|
137
138
|
// Use in your form schema
|
|
138
139
|
const myFormSchema = z.object({
|
|
@@ -146,6 +147,49 @@ type MyFormData = z.infer<typeof myFormSchema>;
|
|
|
146
147
|
|
|
147
148
|
## Key Capabilities
|
|
148
149
|
|
|
150
|
+
### Client-Side File URLs
|
|
151
|
+
|
|
152
|
+
**Generate File URL:**
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { useDrive } from "@muhgholy/next-drive/client";
|
|
156
|
+
import type { TDriveFile } from "@muhgholy/next-drive/client";
|
|
157
|
+
|
|
158
|
+
function MyComponent() {
|
|
159
|
+
const { createUrl } = useDrive();
|
|
160
|
+
|
|
161
|
+
// Basic URL generation
|
|
162
|
+
const url = createUrl(driveFile);
|
|
163
|
+
// Returns: /api/drive?action=serve&id={fileId}
|
|
164
|
+
|
|
165
|
+
// With image quality and format
|
|
166
|
+
const url = createUrl(driveFile, {
|
|
167
|
+
quality: "medium",
|
|
168
|
+
format: "webp",
|
|
169
|
+
});
|
|
170
|
+
// Returns: /api/drive?action=serve&id={fileId}&q=medium&format=webp
|
|
171
|
+
|
|
172
|
+
// Use in Next.js Image component
|
|
173
|
+
return <Image src={createUrl(driveFile)} alt={driveFile.file.name} />;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Responsive Image SrcSet:**
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { useDrive } from "@muhgholy/next-drive/client";
|
|
181
|
+
|
|
182
|
+
function ResponsiveImage({ driveFile }: { driveFile: TDriveFile }) {
|
|
183
|
+
const { createUrl, createSrcSet } = useDrive();
|
|
184
|
+
|
|
185
|
+
// Generate responsive srcSet for optimal image loading
|
|
186
|
+
const { srcSet, sizes } = createSrcSet(driveFile, "webp");
|
|
187
|
+
|
|
188
|
+
// Use in img tag
|
|
189
|
+
return <img src={createUrl(driveFile, { quality: "medium" })} srcSet={srcSet} sizes={sizes} alt={driveFile.file.name} />;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
149
193
|
### Server-Side File Access
|
|
150
194
|
|
|
151
195
|
**Get File URL:**
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { l as TDrivePathItem, d as TDatabaseDrive, m as TDriveQuota,
|
|
2
|
+
import { l as TDrivePathItem, d as TDatabaseDrive, m as TDriveQuota, e as TDriveFile, n as TImageQuality, o as TImageFormat, i as TDriveAPIResponse, p as TDriveUploadState } from '../index-CuwenLsj.js';
|
|
3
|
+
export { q as driveFileSchemaZod } from '../index-CuwenLsj.js';
|
|
4
|
+
import 'zod';
|
|
3
5
|
|
|
4
6
|
type TDriveContext = {
|
|
5
7
|
apiEndpoint: string;
|
|
@@ -45,6 +47,14 @@ type TDriveContext = {
|
|
|
45
47
|
};
|
|
46
48
|
selectedFileIds: string[];
|
|
47
49
|
setSelectedFileIds: React.Dispatch<React.SetStateAction<string[]>>;
|
|
50
|
+
createUrl: (driveFile: TDriveFile, options?: {
|
|
51
|
+
quality?: TImageQuality;
|
|
52
|
+
format?: TImageFormat;
|
|
53
|
+
}) => string;
|
|
54
|
+
createSrcSet: (driveFile: TDriveFile, format?: TImageFormat) => {
|
|
55
|
+
srcSet: string;
|
|
56
|
+
sizes: string;
|
|
57
|
+
};
|
|
48
58
|
navigateToFolder: (item: {
|
|
49
59
|
id: string | null;
|
|
50
60
|
name: string;
|
|
@@ -120,4 +130,4 @@ declare const DriveHeader: () => React.JSX.Element;
|
|
|
120
130
|
|
|
121
131
|
declare const DriveSidebar: () => React.JSX.Element;
|
|
122
132
|
|
|
123
|
-
export { DriveExplorer, DriveFileChooser, DriveHeader, DrivePathBar, DriveProvider, DriveSidebar, DriveStorageIndicator, DriveUpload, type TDriveContext, TDriveFile, useDrive, useUpload };
|
|
133
|
+
export { DriveExplorer, DriveFileChooser, DriveHeader, DrivePathBar, DriveProvider, DriveSidebar, DriveStorageIndicator, DriveUpload, type TDriveContext, TDriveFile, TImageFormat, TImageQuality, useDrive, useUpload };
|
package/dist/client/index.js
CHANGED
|
@@ -1,6 +1,75 @@
|
|
|
1
1
|
// src/client/context.tsx
|
|
2
2
|
import React, { createContext, useContext, useState, useCallback, useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
// src/client/utils.tsx
|
|
5
|
+
import { clsx } from "clsx";
|
|
6
|
+
import { twMerge } from "tailwind-merge";
|
|
7
|
+
import { File, Folder, Image, Video, Music, FileText, FileCode, FileArchive } from "lucide-react";
|
|
3
8
|
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
function cn(...inputs) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
12
|
+
var formatBytes = (bytes, decimals = 2) => {
|
|
13
|
+
if (bytes === 0) return "0 Bytes";
|
|
14
|
+
const k = 1024;
|
|
15
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
16
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
17
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
18
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
19
|
+
};
|
|
20
|
+
var getFileIcon = (mime, isFolder, className = "w-6 h-6") => {
|
|
21
|
+
if (isFolder) return /* @__PURE__ */ jsx(Folder, { className: cn("text-blue-500 fill-blue-500/20", className) });
|
|
22
|
+
if (mime.startsWith("image/")) return /* @__PURE__ */ jsx(Image, { className: cn("text-purple-500", className) });
|
|
23
|
+
if (mime.startsWith("video/")) return /* @__PURE__ */ jsx(Video, { className: cn("text-red-500", className) });
|
|
24
|
+
if (mime.startsWith("audio/")) return /* @__PURE__ */ jsx(Music, { className: cn("text-yellow-500", className) });
|
|
25
|
+
if (mime === "application/pdf") return /* @__PURE__ */ jsx(FileText, { className: cn("text-orange-500", className) });
|
|
26
|
+
if (mime.includes("text") || mime.includes("document")) return /* @__PURE__ */ jsx(FileText, { className: cn("text-slate-500", className) });
|
|
27
|
+
if (mime.includes("zip") || mime.includes("compressed")) return /* @__PURE__ */ jsx(FileArchive, { className: cn("text-amber-500", className) });
|
|
28
|
+
if (mime.includes("javascript") || mime.includes("typescript") || mime.includes("json") || mime.includes("html") || mime.includes("css")) return /* @__PURE__ */ jsx(FileCode, { className: cn("text-green-500", className) });
|
|
29
|
+
return /* @__PURE__ */ jsx(File, { className: cn("text-gray-400", className) });
|
|
30
|
+
};
|
|
31
|
+
var matchesMimeFilter = (mime, isFolder, filter) => {
|
|
32
|
+
if (!filter) return true;
|
|
33
|
+
if (isFolder) return true;
|
|
34
|
+
if (filter === "*/*") return true;
|
|
35
|
+
const types = filter.split(",").map((t) => t.trim());
|
|
36
|
+
return types.some((type) => {
|
|
37
|
+
if (type === mime) return true;
|
|
38
|
+
if (type.endsWith("/*")) {
|
|
39
|
+
const prefix = type.slice(0, -2);
|
|
40
|
+
return mime.startsWith(`${prefix}/`);
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
var driveCreateUrl = (driveFile, apiEndpoint, options) => {
|
|
46
|
+
const params = new URLSearchParams({
|
|
47
|
+
action: "serve",
|
|
48
|
+
id: driveFile.id
|
|
49
|
+
});
|
|
50
|
+
if (options?.quality) params.set("q", options.quality);
|
|
51
|
+
if (options?.format) params.set("format", options.format);
|
|
52
|
+
return `${apiEndpoint}?${params.toString()}`;
|
|
53
|
+
};
|
|
54
|
+
var driveCreateSrcSet = (driveFile, apiEndpoint, format = "webp") => {
|
|
55
|
+
const qualities = ["ultralow", "low", "medium", "high"];
|
|
56
|
+
const qualityWidthMap = {
|
|
57
|
+
ultralow: 200,
|
|
58
|
+
low: 400,
|
|
59
|
+
medium: 800,
|
|
60
|
+
high: 1200,
|
|
61
|
+
normal: 1600
|
|
62
|
+
};
|
|
63
|
+
const srcSet = qualities.map((quality) => {
|
|
64
|
+
const url = driveCreateUrl(driveFile, apiEndpoint, { quality, format });
|
|
65
|
+
return `${url} ${qualityWidthMap[quality]}w`;
|
|
66
|
+
}).join(", ");
|
|
67
|
+
const sizes = "(max-width: 320px) 200px, (max-width: 480px) 400px, (max-width: 768px) 800px, 1200px";
|
|
68
|
+
return { srcSet, sizes };
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/client/context.tsx
|
|
72
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
4
73
|
var DriveContext = createContext(null);
|
|
5
74
|
var DriveProvider = (props) => {
|
|
6
75
|
const { children, apiEndpoint, initialActiveAccountId = null, initialSelectionMode = { type: "SINGLE" }, defaultSelectedFileIds = [] } = props;
|
|
@@ -216,7 +285,13 @@ var DriveProvider = (props) => {
|
|
|
216
285
|
React.useEffect(() => {
|
|
217
286
|
refreshAccounts();
|
|
218
287
|
}, [refreshAccounts]);
|
|
219
|
-
|
|
288
|
+
const createUrl = useCallback((driveFile, options) => {
|
|
289
|
+
return driveCreateUrl(driveFile, apiEndpoint, options);
|
|
290
|
+
}, [apiEndpoint]);
|
|
291
|
+
const createSrcSet = useCallback((driveFile, format) => {
|
|
292
|
+
return driveCreateSrcSet(driveFile, apiEndpoint, format);
|
|
293
|
+
}, [apiEndpoint]);
|
|
294
|
+
return /* @__PURE__ */ jsx2(DriveContext.Provider, { value: {
|
|
220
295
|
apiEndpoint,
|
|
221
296
|
currentFolderId,
|
|
222
297
|
path,
|
|
@@ -247,6 +322,8 @@ var DriveProvider = (props) => {
|
|
|
247
322
|
selectionMode,
|
|
248
323
|
selectedFileIds,
|
|
249
324
|
setSelectedFileIds,
|
|
325
|
+
createUrl,
|
|
326
|
+
createSrcSet,
|
|
250
327
|
navigateToFolder,
|
|
251
328
|
navigateUp,
|
|
252
329
|
refreshItems,
|
|
@@ -415,48 +492,6 @@ var useUpload = (apiEndpoint, activeAccountId, onUploadComplete) => {
|
|
|
415
492
|
// src/client/file-chooser.tsx
|
|
416
493
|
import { useState as useState6, useCallback as useCallback4, useMemo as useMemo3, useEffect as useEffect5 } from "react";
|
|
417
494
|
|
|
418
|
-
// src/client/utils.tsx
|
|
419
|
-
import { clsx } from "clsx";
|
|
420
|
-
import { twMerge } from "tailwind-merge";
|
|
421
|
-
import { File, Folder, Image, Video, Music, FileText, FileCode, FileArchive } from "lucide-react";
|
|
422
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
423
|
-
function cn(...inputs) {
|
|
424
|
-
return twMerge(clsx(inputs));
|
|
425
|
-
}
|
|
426
|
-
var formatBytes = (bytes, decimals = 2) => {
|
|
427
|
-
if (bytes === 0) return "0 Bytes";
|
|
428
|
-
const k = 1024;
|
|
429
|
-
const dm = decimals < 0 ? 0 : decimals;
|
|
430
|
-
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
431
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
432
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
433
|
-
};
|
|
434
|
-
var getFileIcon = (mime, isFolder, className = "w-6 h-6") => {
|
|
435
|
-
if (isFolder) return /* @__PURE__ */ jsx2(Folder, { className: cn("text-blue-500 fill-blue-500/20", className) });
|
|
436
|
-
if (mime.startsWith("image/")) return /* @__PURE__ */ jsx2(Image, { className: cn("text-purple-500", className) });
|
|
437
|
-
if (mime.startsWith("video/")) return /* @__PURE__ */ jsx2(Video, { className: cn("text-red-500", className) });
|
|
438
|
-
if (mime.startsWith("audio/")) return /* @__PURE__ */ jsx2(Music, { className: cn("text-yellow-500", className) });
|
|
439
|
-
if (mime === "application/pdf") return /* @__PURE__ */ jsx2(FileText, { className: cn("text-orange-500", className) });
|
|
440
|
-
if (mime.includes("text") || mime.includes("document")) return /* @__PURE__ */ jsx2(FileText, { className: cn("text-slate-500", className) });
|
|
441
|
-
if (mime.includes("zip") || mime.includes("compressed")) return /* @__PURE__ */ jsx2(FileArchive, { className: cn("text-amber-500", className) });
|
|
442
|
-
if (mime.includes("javascript") || mime.includes("typescript") || mime.includes("json") || mime.includes("html") || mime.includes("css")) return /* @__PURE__ */ jsx2(FileCode, { className: cn("text-green-500", className) });
|
|
443
|
-
return /* @__PURE__ */ jsx2(File, { className: cn("text-gray-400", className) });
|
|
444
|
-
};
|
|
445
|
-
var matchesMimeFilter = (mime, isFolder, filter) => {
|
|
446
|
-
if (!filter) return true;
|
|
447
|
-
if (isFolder) return true;
|
|
448
|
-
if (filter === "*/*") return true;
|
|
449
|
-
const types = filter.split(",").map((t) => t.trim());
|
|
450
|
-
return types.some((type) => {
|
|
451
|
-
if (type === mime) return true;
|
|
452
|
-
if (type.endsWith("/*")) {
|
|
453
|
-
const prefix = type.slice(0, -2);
|
|
454
|
-
return mime.startsWith(`${prefix}/`);
|
|
455
|
-
}
|
|
456
|
-
return false;
|
|
457
|
-
});
|
|
458
|
-
};
|
|
459
|
-
|
|
460
495
|
// src/client/components/drive/explorer.tsx
|
|
461
496
|
import React7, { useMemo as useMemo2, useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
462
497
|
import { Folder as Folder2, Loader2 as Loader22, RotateCcw, ChevronRight as ChevronRight3 } from "lucide-react";
|
|
@@ -2682,6 +2717,17 @@ var DriveFileChooser = (props) => {
|
|
|
2682
2717
|
] }) })
|
|
2683
2718
|
] });
|
|
2684
2719
|
};
|
|
2720
|
+
|
|
2721
|
+
// src/types/client/index.ts
|
|
2722
|
+
import { z } from "zod";
|
|
2723
|
+
var driveFileSchemaZod = z.object({
|
|
2724
|
+
id: z.string(),
|
|
2725
|
+
file: z.object({
|
|
2726
|
+
name: z.string(),
|
|
2727
|
+
mime: z.string(),
|
|
2728
|
+
size: z.number()
|
|
2729
|
+
})
|
|
2730
|
+
});
|
|
2685
2731
|
export {
|
|
2686
2732
|
DriveExplorer,
|
|
2687
2733
|
DriveFileChooser,
|
|
@@ -2691,6 +2737,7 @@ export {
|
|
|
2691
2737
|
DriveSidebar,
|
|
2692
2738
|
DriveStorageIndicator,
|
|
2693
2739
|
DriveUpload,
|
|
2740
|
+
driveFileSchemaZod,
|
|
2694
2741
|
useDrive,
|
|
2695
2742
|
useUpload
|
|
2696
2743
|
};
|