@muhgholy/next-drive 1.0.2 → 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
@@ -147,6 +147,49 @@ type MyFormData = z.infer<typeof myFormSchema>;
147
147
 
148
148
  ## Key Capabilities
149
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
+
150
193
  ### Server-Side File Access
151
194
 
152
195
  **Get File URL:**
@@ -1,6 +1,6 @@
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-CIbbTroj.js';
3
- export { o as driveFileSchemaZod } from '../index-CIbbTroj.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
4
  import 'zod';
5
5
 
6
6
  type TDriveContext = {
@@ -47,6 +47,14 @@ type TDriveContext = {
47
47
  };
48
48
  selectedFileIds: string[];
49
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
+ };
50
58
  navigateToFolder: (item: {
51
59
  id: string | null;
52
60
  name: string;
@@ -122,4 +130,4 @@ declare const DriveHeader: () => React.JSX.Element;
122
130
 
123
131
  declare const DriveSidebar: () => React.JSX.Element;
124
132
 
125
- 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";