@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: auth ? { userId: auth.userId } : null,
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/server";
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:**
@@ -1,5 +1,7 @@
1
1
  import React, { ReactNode } from 'react';
2
- import { l as TDrivePathItem, d as TDatabaseDrive, m as TDriveQuota, i as TDriveAPIResponse, n as TDriveUploadState, e as TDriveFile } from '../index-DE7-rwJm.js';
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 };
@@ -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
- return /* @__PURE__ */ jsx(DriveContext.Provider, { value: {
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
  };