@uploadista/react 0.0.15-beta.2 → 0.0.15-beta.3

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.
Files changed (28) hide show
  1. package/dist/components/index.d.mts +2 -2
  2. package/dist/components/index.mjs +1 -1
  3. package/dist/hooks/index.d.mts +2 -2
  4. package/dist/hooks/index.mjs +1 -1
  5. package/dist/index.d.mts +76 -4
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +1 -1
  8. package/dist/{upload-zone-DFStubbe.mjs → upload-zone-BQeUnuNK.mjs} +2 -2
  9. package/dist/{upload-zone-DFStubbe.mjs.map → upload-zone-BQeUnuNK.mjs.map} +1 -1
  10. package/dist/{uploadista-provider-CrS6TmpJ.d.mts → uploadista-provider-BIWt4Zfs.d.mts} +3 -3
  11. package/dist/{uploadista-provider-CrS6TmpJ.d.mts.map → uploadista-provider-BIWt4Zfs.d.mts.map} +1 -1
  12. package/dist/use-upload-BNYuUjSi.mjs +2 -0
  13. package/dist/use-upload-BNYuUjSi.mjs.map +1 -0
  14. package/dist/{use-upload-metrics-C1amBY1k.mjs → use-upload-metrics-9uwhKMWa.mjs} +2 -2
  15. package/dist/{use-upload-metrics-C1amBY1k.mjs.map → use-upload-metrics-9uwhKMWa.mjs.map} +1 -1
  16. package/dist/{use-upload-metrics-IXxUORce.d.mts → use-upload-metrics-Cd0ML_kN.d.mts} +2 -2
  17. package/dist/{use-upload-metrics-IXxUORce.d.mts.map → use-upload-metrics-Cd0ML_kN.d.mts.map} +1 -1
  18. package/dist/{use-uploadista-client--ivZPO88.d.mts → use-uploadista-client-B_VQ3UgO.d.mts} +4 -4
  19. package/dist/use-uploadista-client-B_VQ3UgO.d.mts.map +1 -0
  20. package/package.json +5 -5
  21. package/src/components/uploadista-provider.tsx +5 -1
  22. package/src/contexts/flow-manager-context.tsx +230 -0
  23. package/src/hooks/use-flow-upload.ts +47 -112
  24. package/src/hooks/use-uploadista-client.ts +1 -0
  25. package/src/index.ts +6 -0
  26. package/dist/use-upload-BNiPsNBv.mjs +0 -2
  27. package/dist/use-upload-BNiPsNBv.mjs.map +0 -1
  28. package/dist/use-uploadista-client--ivZPO88.d.mts.map +0 -1
@@ -0,0 +1,230 @@
1
+ import type { UploadistaEvent } from "@uploadista/client-browser";
2
+ import {
3
+ FlowManager,
4
+ type FlowManagerCallbacks,
5
+ type FlowUploadState,
6
+ } from "@uploadista/client-core";
7
+ import { EventType, type FlowEvent } from "@uploadista/core/flow";
8
+ import { UploadEventType } from "@uploadista/core/types";
9
+ import type { ReactNode } from "react";
10
+ import {
11
+ createContext,
12
+ useCallback,
13
+ useContext,
14
+ useEffect,
15
+ useRef,
16
+ } from "react";
17
+ import { useUploadistaContext } from "../components/uploadista-provider";
18
+ import type { FlowUploadOptions } from "@uploadista/client-browser";
19
+
20
+ /**
21
+ * Type guard to check if an event is a flow event
22
+ */
23
+ function isFlowEvent(event: UploadistaEvent): event is FlowEvent {
24
+ const flowEvent = event as FlowEvent;
25
+ return (
26
+ flowEvent.eventType === EventType.FlowStart ||
27
+ flowEvent.eventType === EventType.FlowEnd ||
28
+ flowEvent.eventType === EventType.FlowError ||
29
+ flowEvent.eventType === EventType.NodeStart ||
30
+ flowEvent.eventType === EventType.NodeEnd ||
31
+ flowEvent.eventType === EventType.NodePause ||
32
+ flowEvent.eventType === EventType.NodeResume ||
33
+ flowEvent.eventType === EventType.NodeError
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Internal manager registry entry with ref counting
39
+ */
40
+ interface ManagerEntry<TOutput> {
41
+ manager: FlowManager<unknown, TOutput>;
42
+ refCount: number;
43
+ flowId: string;
44
+ }
45
+
46
+ /**
47
+ * Context value providing access to flow managers
48
+ */
49
+ interface FlowManagerContextValue {
50
+ /**
51
+ * Get or create a flow manager for the given flow ID.
52
+ * Increments ref count - must call releaseManager when done.
53
+ *
54
+ * @param flowId - Unique identifier for the flow
55
+ * @param callbacks - Callbacks for state changes and lifecycle events
56
+ * @param options - Flow configuration options
57
+ * @returns FlowManager instance
58
+ */
59
+ getManager: <TOutput = unknown>(
60
+ flowId: string,
61
+ callbacks: FlowManagerCallbacks<TOutput>,
62
+ options: FlowUploadOptions<TOutput>,
63
+ ) => FlowManager<unknown, TOutput>;
64
+
65
+ /**
66
+ * Release a flow manager reference.
67
+ * Decrements ref count and cleans up when reaching zero.
68
+ *
69
+ * @param flowId - Unique identifier for the flow to release
70
+ */
71
+ releaseManager: (flowId: string) => void;
72
+ }
73
+
74
+ const FlowManagerContext = createContext<FlowManagerContextValue | undefined>(
75
+ undefined,
76
+ );
77
+
78
+ /**
79
+ * Props for FlowManagerProvider
80
+ */
81
+ interface FlowManagerProviderProps {
82
+ children: ReactNode;
83
+ }
84
+
85
+ /**
86
+ * Provider that manages FlowManager instances with ref counting and event routing.
87
+ * Ensures managers persist across component re-renders and are only cleaned up
88
+ * when all consuming components unmount.
89
+ *
90
+ * This provider should be nested inside UploadistaProvider to access the upload client
91
+ * and event subscription system.
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * <UploadistaProvider baseUrl="https://api.example.com" storageId="default">
96
+ * <FlowManagerProvider>
97
+ * <App />
98
+ * </FlowManagerProvider>
99
+ * </UploadistaProvider>
100
+ * ```
101
+ */
102
+ export function FlowManagerProvider({ children }: FlowManagerProviderProps) {
103
+ const { client, subscribeToEvents } = useUploadistaContext();
104
+ const managersRef = useRef(
105
+ new Map<string, ManagerEntry<unknown>>(),
106
+ );
107
+
108
+ // Subscribe to all events and route to appropriate managers
109
+ useEffect(() => {
110
+ const unsubscribe = subscribeToEvents((event: UploadistaEvent) => {
111
+ // Route flow events to all managers (they filter by jobId internally)
112
+ if (isFlowEvent(event)) {
113
+ for (const entry of managersRef.current.values()) {
114
+ entry.manager.handleFlowEvent(event);
115
+ }
116
+ return;
117
+ }
118
+
119
+ // Route upload progress events to all managers
120
+ if (
121
+ "type" in event &&
122
+ event.type === UploadEventType.UPLOAD_PROGRESS &&
123
+ "data" in event
124
+ ) {
125
+ const uploadEvent = event as {
126
+ type: UploadEventType;
127
+ uploadId: string;
128
+ data: { progress: number; total: number | null };
129
+ };
130
+
131
+ for (const entry of managersRef.current.values()) {
132
+ entry.manager.handleUploadProgress(
133
+ uploadEvent.uploadId,
134
+ uploadEvent.data.progress,
135
+ uploadEvent.data.total,
136
+ );
137
+ }
138
+ }
139
+ });
140
+
141
+ return unsubscribe;
142
+ }, [subscribeToEvents]);
143
+
144
+ const getManager = useCallback(
145
+ <TOutput,>(
146
+ flowId: string,
147
+ callbacks: FlowManagerCallbacks<TOutput>,
148
+ options: FlowUploadOptions<TOutput>,
149
+ ): FlowManager<unknown, TOutput> => {
150
+ const existing = managersRef.current.get(flowId);
151
+
152
+ if (existing) {
153
+ // Increment ref count for existing manager
154
+ existing.refCount++;
155
+ return existing.manager as FlowManager<unknown, TOutput>;
156
+ }
157
+
158
+ // Create new manager using client from hook scope
159
+ const flowUploadFn = (
160
+ input: unknown,
161
+ flowConfig: FlowUploadOptions<TOutput>["flowConfig"],
162
+ internalOptions: unknown,
163
+ ) => {
164
+ return client.uploadWithFlow(input, flowConfig, internalOptions);
165
+ };
166
+
167
+ const manager = new FlowManager<unknown, TOutput>(
168
+ flowUploadFn,
169
+ callbacks,
170
+ options,
171
+ );
172
+
173
+ managersRef.current.set(flowId, {
174
+ manager: manager as FlowManager<unknown, unknown>,
175
+ refCount: 1,
176
+ flowId,
177
+ });
178
+
179
+ return manager;
180
+ },
181
+ [client],
182
+ );
183
+
184
+ const releaseManager = useCallback((flowId: string) => {
185
+ const existing = managersRef.current.get(flowId);
186
+ if (!existing) return;
187
+
188
+ existing.refCount--;
189
+
190
+ // Clean up when no more refs
191
+ if (existing.refCount <= 0) {
192
+ existing.manager.cleanup();
193
+ managersRef.current.delete(flowId);
194
+ }
195
+ }, []);
196
+
197
+ return (
198
+ <FlowManagerContext.Provider value={{ getManager, releaseManager }}>
199
+ {children}
200
+ </FlowManagerContext.Provider>
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Hook to access the FlowManager context.
206
+ * Must be used within a FlowManagerProvider.
207
+ *
208
+ * @returns FlowManager context value with getManager and releaseManager functions
209
+ * @throws Error if used outside of FlowManagerProvider
210
+ *
211
+ * @example
212
+ * ```tsx
213
+ * function MyComponent() {
214
+ * const { getManager, releaseManager } = useFlowManagerContext();
215
+ * // Use to create managers...
216
+ * }
217
+ * ```
218
+ */
219
+ export function useFlowManagerContext(): FlowManagerContextValue {
220
+ const context = useContext(FlowManagerContext);
221
+
222
+ if (context === undefined) {
223
+ throw new Error(
224
+ "useFlowManagerContext must be used within a FlowManagerProvider. " +
225
+ "Make sure to wrap your component tree with <FlowManagerProvider>.",
226
+ );
227
+ }
228
+
229
+ return context;
230
+ }
@@ -1,36 +1,13 @@
1
- import type {
2
- FlowUploadOptions,
3
- UploadistaEvent,
4
- } from "@uploadista/client-browser";
1
+ import type { FlowUploadOptions } from "@uploadista/client-browser";
5
2
  import {
6
- FlowManager,
3
+ type FlowManager,
7
4
  type FlowUploadState,
8
5
  type FlowUploadStatus,
9
- type InternalFlowUploadOptions,
10
6
  } from "@uploadista/client-core";
11
- import { EventType, type FlowEvent } from "@uploadista/core/flow";
7
+ import type { TypedOutput } from "@uploadista/core/flow";
12
8
  import type { UploadFile } from "@uploadista/core/types";
13
- import { UploadEventType } from "@uploadista/core/types";
14
9
  import { useCallback, useEffect, useRef, useState } from "react";
15
- import { useUploadistaContext } from "../components/uploadista-provider";
16
-
17
- /**
18
- * Type guard to check if an event is a flow event
19
- */
20
- function isFlowEvent(event: UploadistaEvent): event is FlowEvent {
21
- // FlowEvent has eventType, not type
22
- const flowEvent = event as FlowEvent;
23
- return (
24
- flowEvent.eventType === EventType.FlowStart ||
25
- flowEvent.eventType === EventType.FlowEnd ||
26
- flowEvent.eventType === EventType.FlowError ||
27
- flowEvent.eventType === EventType.NodeStart ||
28
- flowEvent.eventType === EventType.NodeEnd ||
29
- flowEvent.eventType === EventType.NodePause ||
30
- flowEvent.eventType === EventType.NodeResume ||
31
- flowEvent.eventType === EventType.NodeError
32
- );
33
- }
10
+ import { useFlowManagerContext } from "../contexts/flow-manager-context";
34
11
 
35
12
  // Re-export types from core for convenience
36
13
  export type { FlowUploadState, FlowUploadStatus };
@@ -113,8 +90,8 @@ const initialState: FlowUploadState = {
113
90
  * The flow engine processes the uploaded file through a DAG of nodes, which can
114
91
  * perform operations like image optimization, storage saving, webhooks, etc.
115
92
  *
116
- * Must be used within an UploadistaProvider. Flow events (node start/end, flow complete)
117
- * are automatically subscribed through the provider context.
93
+ * Must be used within FlowManagerProvider (which must be within UploadistaProvider).
94
+ * Flow events are automatically routed by the provider to the appropriate manager.
118
95
  *
119
96
  * @template TOutput - Type of the final result from the flow (defaults to UploadFile)
120
97
  * @param options - Flow upload configuration including flow ID and event handlers
@@ -196,99 +173,57 @@ const initialState: FlowUploadState = {
196
173
  export function useFlowUpload<TOutput = UploadFile>(
197
174
  options: FlowUploadOptions<TOutput>,
198
175
  ): UseFlowUploadReturn<TOutput> {
199
- // Get client from context
200
- const client = useUploadistaContext();
176
+ const { getManager, releaseManager } = useFlowManagerContext();
201
177
  const [state, setState] = useState<FlowUploadState<TOutput>>(
202
178
  initialState as FlowUploadState<TOutput>,
203
179
  );
204
- const managerRef = useRef<FlowManager<File | Blob, TOutput> | null>(null);
180
+ const managerRef = useRef<FlowManager<unknown, TOutput> | null>(null);
205
181
 
206
- // Create FlowManager instance once (only recreate if client changes)
207
- // Note: We don't include options in deps to avoid recreating the manager on every render
208
- // The manager will use the latest options values through closures
209
- useEffect(() => {
210
- managerRef.current = new FlowManager(
211
- async (
212
- file: File | Blob,
213
- flowConfig: {
214
- flowId: string;
215
- storageId: string;
216
- outputNodeId?: string;
217
- metadata?: Record<string, string>;
218
- },
219
- internalOptions: InternalFlowUploadOptions,
220
- ) => {
221
- const result = await client.client.uploadWithFlow(file, flowConfig, {
222
- onJobStart: internalOptions.onJobStart,
223
- onProgress: internalOptions.onProgress,
224
- onChunkComplete: internalOptions.onChunkComplete,
225
- onSuccess: internalOptions.onSuccess,
226
- onError: internalOptions.onError,
227
- onShouldRetry: internalOptions.onShouldRetry,
228
- });
229
- // Return only abort and pause (ignore jobId and return value)
230
- return {
231
- abort: async () => {
232
- await result.abort();
233
- },
234
- pause: async () => {
235
- await result.pause();
236
- // Ignore the FlowJob return value
237
- },
238
- };
239
- },
240
- {
241
- onStateChange: setState,
242
- onProgress: options.onProgress,
243
- onChunkComplete: options.onChunkComplete,
244
- onFlowComplete: options.onFlowComplete,
245
- onSuccess: options.onSuccess,
246
- onError: options.onError,
247
- onAbort: options.onAbort,
248
- },
249
- options,
250
- );
251
-
252
- return () => {
253
- managerRef.current?.cleanup();
254
- };
255
- // eslint-disable-next-line react-hooks/exhaustive-deps
256
- }, [client]);
182
+ // Store callbacks in refs so they can be updated without recreating the manager
183
+ const callbacksRef = useRef(options);
257
184
 
258
- // Subscribe to events and forward them to the manager
185
+ // Update refs on every render to capture latest callbacks
259
186
  useEffect(() => {
260
- const unsubscribe = client.subscribeToEvents((event: UploadistaEvent) => {
261
- // Handle flow events
262
- if (isFlowEvent(event)) {
263
- console.log("[useFlowUpload] Flow event received:", event.eventType, "jobId:", event.jobId, "current:", managerRef.current?.getJobId());
264
- managerRef.current?.handleFlowEvent(event);
265
- return;
266
- }
187
+ callbacksRef.current = options;
188
+ });
267
189
 
268
- // Handle upload progress events for this job's upload
269
- const uploadEvent = event as {
270
- type: string;
271
- data?: { id: string; progress: number; total: number };
272
- flow?: { jobId: string };
273
- };
190
+ // Get or create manager from context when component mounts
191
+ // Manager lifecycle is now handled by FlowManagerProvider
192
+ useEffect(() => {
193
+ const flowId = options.flowConfig.flowId;
274
194
 
275
- if (
276
- uploadEvent.type === UploadEventType.UPLOAD_PROGRESS &&
277
- uploadEvent.flow?.jobId === managerRef.current?.getJobId() &&
278
- uploadEvent.data
279
- ) {
280
- const { progress: bytesUploaded, total: totalBytes } = uploadEvent.data;
195
+ // Create stable callback wrappers that call the latest callbacks via refs
196
+ const stableCallbacks = {
197
+ onStateChange: setState,
198
+ onProgress: (uploadId: string, bytesUploaded: number, totalBytes: number | null) => {
199
+ callbacksRef.current.onProgress?.(uploadId, bytesUploaded, totalBytes);
200
+ },
201
+ onChunkComplete: (chunkSize: number, bytesAccepted: number, bytesTotal: number | null) => {
202
+ callbacksRef.current.onChunkComplete?.(chunkSize, bytesAccepted, bytesTotal);
203
+ },
204
+ onFlowComplete: (outputs: TypedOutput[]) => {
205
+ callbacksRef.current.onFlowComplete?.(outputs);
206
+ },
207
+ onSuccess: (result: TOutput) => {
208
+ callbacksRef.current.onSuccess?.(result);
209
+ },
210
+ onError: (error: Error) => {
211
+ callbacksRef.current.onError?.(error);
212
+ },
213
+ onAbort: () => {
214
+ callbacksRef.current.onAbort?.();
215
+ },
216
+ };
281
217
 
282
- managerRef.current?.handleUploadProgress(
283
- uploadEvent.data.id,
284
- bytesUploaded,
285
- totalBytes,
286
- );
287
- }
288
- });
218
+ // Get manager from context (creates if doesn't exist, increments ref count)
219
+ managerRef.current = getManager(flowId, stableCallbacks, options);
289
220
 
290
- return unsubscribe;
291
- }, [client]);
221
+ // Release manager when component unmounts or flowId changes
222
+ return () => {
223
+ releaseManager(flowId);
224
+ managerRef.current = null;
225
+ };
226
+ }, [options.flowConfig.flowId, options.flowConfig.storageId, options.flowConfig.outputNodeId, getManager, releaseManager]);
292
227
 
293
228
  // Wrap manager methods with useCallback
294
229
  const upload = useCallback(async (file: File | Blob) => {
@@ -120,6 +120,7 @@ export function useUploadistaClient(
120
120
  // IMPORTANT: We depend on individual config values, not the entire options object,
121
121
  // to prevent unnecessary client recreation when the options object reference changes
122
122
  const client = useMemo(() => {
123
+ console.log("[useUploadistaClient] Creating NEW client instance with onEvent:", options.onEvent);
123
124
  return createUploadistaClient({
124
125
  baseUrl: options.baseUrl,
125
126
  storageId: options.storageId,
package/src/index.ts CHANGED
@@ -51,6 +51,12 @@ export {
51
51
  UploadistaProvider,
52
52
  useUploadistaContext,
53
53
  } from "./components/uploadista-provider";
54
+
55
+ // Contexts
56
+ export {
57
+ FlowManagerProvider,
58
+ useFlowManagerContext,
59
+ } from "./contexts/flow-manager-context";
54
60
  export type {
55
61
  DragDropOptions,
56
62
  DragDropState,
@@ -1,2 +0,0 @@
1
- import{createContext as e,useCallback as t,useContext as n,useEffect as r,useMemo as i,useRef as a,useState as o}from"react";import{createUploadistaClient as s}from"@uploadista/client-browser";import{jsx as c}from"react/jsx-runtime";import{FlowManager as l,UploadManager as u}from"@uploadista/client-core";import{EventType as d}from"@uploadista/core/flow";import{UploadEventType as f}from"@uploadista/core/types";function p(e){let t=a(e);return t.current=e,{client:i(()=>s({baseUrl:e.baseUrl,storageId:e.storageId,uploadistaBasePath:e.uploadistaBasePath,chunkSize:e.chunkSize,storeFingerprintForResuming:e.storeFingerprintForResuming,retryDelays:e.retryDelays,parallelUploads:e.parallelUploads,parallelChunkSize:e.parallelChunkSize,uploadStrategy:e.uploadStrategy,smartChunking:e.smartChunking,networkMonitoring:e.networkMonitoring,uploadMetrics:e.uploadMetrics,connectionPooling:e.connectionPooling,auth:e.auth,onEvent:e.onEvent}),[e.baseUrl,e.storageId,e.uploadistaBasePath,e.chunkSize,e.storeFingerprintForResuming,e.retryDelays,e.parallelUploads,e.parallelChunkSize,e.uploadStrategy,e.smartChunking,e.networkMonitoring,e.uploadMetrics,e.connectionPooling,e.auth,e.onEvent]),config:e}}const m=e(null);function h({children:e,...n}){let r=a(new Set),o=t(e=>{console.log(`[UploadistaProvider] Received event:`,e),n.onEvent?.(e),console.log(`[UploadistaProvider] Broadcasting to`,r.current.size,`subscribers`),r.current.forEach(t=>{try{t(e)}catch(e){console.error(`Error in event subscriber:`,e)}})},[n.onEvent]),s=p({...n,onEvent:o}),l=t(e=>(r.current.add(e),()=>{r.current.delete(e)}),[]),u=i(()=>({...s,subscribeToEvents:l}),[s,l]);return c(m.Provider,{value:u,children:e})}function g(){let e=n(m);if(e===null)throw Error(`useUploadistaContext must be used within an UploadistaProvider. Make sure to wrap your component tree with <UploadistaProvider>.`);return e}function _(e){let n=g(),[r,i]=o([]),s=a(new Map),c=a([]),l=a(0),u=e.maxConcurrent??3,d=t(e=>{if(e.length===0)return 0;let t=e.reduce((e,t)=>e+t.progress,0);return Math.round(t/e.length)},[]),f=t(async()=>{if(l.current>=u||c.current.length===0)return;let t=c.current.shift();if(!t)return;let a=r.find(e=>e.id===t);if(!a||a.status!==`pending`){f();return}l.current++,i(e=>e.map(e=>e.id===t?{...e,status:`uploading`}:e));try{let{abort:r,jobId:o}=await n.client.uploadWithFlow(a.file,e.flowConfig,{onJobStart:e=>{i(n=>n.map(n=>n.id===t?{...n,jobId:e}:n))},onProgress:(n,r,a)=>{let o=a?Math.round(r/a*100):0;i(n=>{let i=n.map(e=>e.id===t?{...e,progress:o,bytesUploaded:r,totalBytes:a||0}:e),s=i.find(e=>e.id===t);return s&&e.onItemProgress?.(s),i})},onSuccess:n=>{i(r=>{let i=r.map(e=>e.id===t?{...e,status:`success`,result:n,progress:100}:e),a=i.find(e=>e.id===t);return a&&e.onItemSuccess?.(a),i.every(e=>e.status===`success`||e.status===`error`||e.status===`aborted`)&&e.onComplete?.(i),i}),s.current.delete(t),l.current--,f()},onError:n=>{i(r=>{let i=r.map(e=>e.id===t?{...e,status:`error`,error:n}:e),a=i.find(e=>e.id===t);return a&&e.onItemError?.(a,n),i.every(e=>e.status===`success`||e.status===`error`||e.status===`aborted`)&&e.onComplete?.(i),i}),s.current.delete(t),l.current--,f()},onShouldRetry:e.onShouldRetry});s.current.set(t,r),i(e=>e.map(e=>e.id===t?{...e,jobId:o}:e))}catch(e){i(n=>n.map(n=>n.id===t?{...n,status:`error`,error:e}:n)),l.current--,f()}},[n,r,u,e]),p=t(e=>{let t=Array.from(e).map(e=>({id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:e,status:`pending`,progress:0,bytesUploaded:0,totalBytes:e.size,error:null,result:null,jobId:null}));i(e=>[...e,...t])},[]),m=t(e=>{let t=s.current.get(e);t&&(t(),s.current.delete(e)),i(t=>t.filter(t=>t.id!==e)),c.current=c.current.filter(t=>t!==e)},[]),h=t(()=>{let e=r.filter(e=>e.status===`pending`);c.current.push(...e.map(e=>e.id));for(let e=0;e<u;e++)f()},[r,u,f]),_=t(e=>{let t=s.current.get(e);t&&(t(),s.current.delete(e),i(t=>t.map(t=>t.id===e?{...t,status:`aborted`}:t)),l.current--,f())},[f]),v=t(()=>{for(let e of s.current.values())e();s.current.clear(),c.current=[],l.current=0,i(e=>e.map(e=>e.status===`uploading`?{...e,status:`aborted`}:e))},[]),y=t(()=>{v(),i([])},[v]),b=t(e=>{i(t=>t.map(t=>t.id===e?{...t,status:`pending`,progress:0,bytesUploaded:0,error:null}:t)),c.current.push(e),f()},[f]),x={items:r,totalProgress:d(r),activeUploads:r.filter(e=>e.status===`uploading`).length,completedUploads:r.filter(e=>e.status===`success`).length,failedUploads:r.filter(e=>e.status===`error`).length};return{state:x,addFiles:p,removeFile:m,startUpload:h,abortUpload:_,abortAll:v,clear:y,retryUpload:b,isUploading:x.activeUploads>0}}const v={isDragging:!1,isOver:!1,isValid:!0,errors:[]};function y(e={}){let{accept:n,maxFiles:r,maxFileSize:i,multiple:s=!0,validator:c,onFilesReceived:l,onValidationError:u,onDragStateChange:d}=e,[f,p]=o(v),m=a(null),h=a(0),g=t(e=>{p(t=>({...t,...e}))},[]),_=t(e=>{let t=[];r&&e.length>r&&t.push(`Maximum ${r} files allowed. You selected ${e.length} files.`);for(let r of e){if(i&&r.size>i){let e=(i/(1024*1024)).toFixed(1),n=(r.size/(1024*1024)).toFixed(1);t.push(`File "${r.name}" (${n}MB) exceeds maximum size of ${e}MB.`)}n&&n.length>0&&(n.some(e=>{if(e.startsWith(`.`))return r.name.toLowerCase().endsWith(e.toLowerCase());if(e.endsWith(`/*`)){let t=e.slice(0,-2);return r.type.startsWith(t)}else return r.type===e})||t.push(`File "${r.name}" type "${r.type}" is not accepted. Accepted types: ${n.join(`, `)}.`))}if(c){let n=c(e);n&&t.push(...n)}return t},[n,r,i,c]),y=t(e=>{let t=Array.from(e),n=_(t);n.length>0?(g({errors:n,isValid:!1}),u?.(n)):(g({errors:[],isValid:!0}),l?.(t))},[_,g,l,u]),b=t(e=>{let t=[];if(e.items)for(let n=0;n<e.items.length;n++){let r=e.items[n];if(r&&r.kind===`file`){let e=r.getAsFile();e&&t.push(e)}}else for(let n=0;n<e.files.length;n++){let r=e.files[n];r&&t.push(r)}return t},[]),x=t(e=>{e.preventDefault(),e.stopPropagation(),h.current++,h.current===1&&(g({isDragging:!0,isOver:!0}),d?.(!0))},[g,d]),S=t(e=>{e.preventDefault(),e.stopPropagation(),e.dataTransfer&&(e.dataTransfer.dropEffect=`copy`)},[]),C=t(e=>{e.preventDefault(),e.stopPropagation(),h.current--,h.current===0&&(g({isDragging:!1,isOver:!1,errors:[]}),d?.(!1))},[g,d]),w=t(e=>{if(e.preventDefault(),e.stopPropagation(),h.current=0,g({isDragging:!1,isOver:!1}),d?.(!1),e.dataTransfer){let t=b(e.dataTransfer);t.length>0&&y(t)}},[g,d,b,y]),T=t(()=>{m.current?.click()},[]),E=t(e=>{e.target.files&&e.target.files.length>0&&y(Array.from(e.target.files)),e.target.value=``},[y]),D=t(()=>{p(v),h.current=0},[]);return{state:f,dragHandlers:{onDragEnter:x,onDragOver:S,onDragLeave:C,onDrop:w},inputProps:{type:`file`,multiple:s,accept:n?.join(`, `),onChange:E,style:{display:`none`},ref:m},openFilePicker:T,processFiles:y,reset:D}}function b(e){let t=e;return t.eventType===d.FlowStart||t.eventType===d.FlowEnd||t.eventType===d.FlowError||t.eventType===d.NodeStart||t.eventType===d.NodeEnd||t.eventType===d.NodePause||t.eventType===d.NodeResume||t.eventType===d.NodeError}const x={status:`idle`,progress:0,bytesUploaded:0,totalBytes:null,error:null,result:null,jobId:null,flowStarted:!1,currentNodeName:null,currentNodeType:null,flowOutputs:null};function S(e){let n=g(),[i,s]=o(x),c=a(null);return r(()=>(c.current=new l(async(e,t,r)=>{let i=await n.client.uploadWithFlow(e,t,{onJobStart:r.onJobStart,onProgress:r.onProgress,onChunkComplete:r.onChunkComplete,onSuccess:r.onSuccess,onError:r.onError,onShouldRetry:r.onShouldRetry});return{abort:async()=>{await i.abort()},pause:async()=>{await i.pause()}}},{onStateChange:s,onProgress:e.onProgress,onChunkComplete:e.onChunkComplete,onFlowComplete:e.onFlowComplete,onSuccess:e.onSuccess,onError:e.onError,onAbort:e.onAbort},e),()=>{c.current?.cleanup()}),[n]),r(()=>n.subscribeToEvents(e=>{if(b(e)){console.log(`[useFlowUpload] Flow event received:`,e.eventType,`jobId:`,e.jobId,`current:`,c.current?.getJobId()),c.current?.handleFlowEvent(e);return}let t=e;if(t.type===f.UPLOAD_PROGRESS&&t.flow?.jobId===c.current?.getJobId()&&t.data){let{progress:e,total:n}=t.data;c.current?.handleUploadProgress(t.data.id,e,n)}}),[n]),{state:i,upload:t(async e=>{await c.current?.upload(e)},[]),abort:t(()=>{c.current?.abort()},[]),pause:t(()=>{c.current?.pause()},[]),reset:t(()=>{c.current?.reset()},[]),isUploading:i.status===`uploading`||i.status===`processing`,isUploadingFile:i.status===`uploading`,isProcessing:i.status===`processing`}}function C(e={}){let n=g(),{maxConcurrent:r=3}=e,[i,s]=o([]),c=a([]),l=a(0),u=a(new Set),d=a(new Map);c.current=i;let f=t(()=>`upload-${Date.now()}-${l.current++}`,[]),p=t((e,t)=>{s(n=>{let r=n.map(n=>n.id===e?{...n,state:{...n.state,...t}}:n);return c.current=r,r})},[]),m=t(()=>{let t=c.current;if(t.every(e=>[`success`,`error`,`aborted`].includes(e.state.status))&&t.length>0){let n=t.filter(e=>e.state.status===`success`),r=t.filter(e=>[`error`,`aborted`].includes(e.state.status));e.onComplete?.({successful:n,failed:r,total:t.length})}},[e]),h=t(()=>{if(u.current.size>=r)return;let t=c.current.find(e=>e.state.status===`idle`&&!u.current.has(e.id));t&&(async()=>{u.current.add(t.id),e.onUploadStart?.(t),p(t.id,{status:`uploading`});try{let r=await n.client.upload(t.file,{metadata:e.metadata,uploadLengthDeferred:e.uploadLengthDeferred,uploadSize:e.uploadSize,onProgress:(n,r,i)=>{let a=i?Math.round(r/i*100):0;p(t.id,{progress:a,bytesUploaded:r,totalBytes:i}),e.onUploadProgress?.(t,a,r,i)},onChunkComplete:()=>{},onSuccess:n=>{p(t.id,{status:`success`,result:n,progress:100});let r={...t,state:{...t.state,status:`success`,result:n}};e.onUploadSuccess?.(r,n),u.current.delete(t.id),d.current.delete(t.id),h(),m()},onError:n=>{p(t.id,{status:`error`,error:n});let r={...t,state:{...t.state,status:`error`,error:n}};e.onUploadError?.(r,n),u.current.delete(t.id),d.current.delete(t.id),h(),m()},onShouldRetry:e.onShouldRetry});d.current.set(t.id,r)}catch(n){p(t.id,{status:`error`,error:n});let r={...t,state:{...t.state,status:`error`,error:n}};e.onUploadError?.(r,n),u.current.delete(t.id),d.current.delete(t.id),h(),m()}})()},[r,n,e,p,m]),_={total:i.length,completed:i.filter(e=>[`success`,`error`,`aborted`].includes(e.state.status)).length,successful:i.filter(e=>e.state.status===`success`).length,failed:i.filter(e=>[`error`,`aborted`].includes(e.state.status)).length,uploading:i.filter(e=>e.state.status===`uploading`).length,progress:i.length>0?Math.round(i.reduce((e,t)=>e+t.state.progress,0)/i.length):0,totalBytesUploaded:i.reduce((e,t)=>e+t.state.bytesUploaded,0),totalBytes:i.reduce((e,t)=>e+(t.state.totalBytes||0),0),isUploading:i.some(e=>e.state.status===`uploading`),isComplete:i.length>0&&i.every(e=>[`success`,`error`,`aborted`].includes(e.state.status))},v=t(e=>{let t=e.map(e=>({id:f(),file:e,state:{status:`idle`,progress:0,bytesUploaded:0,totalBytes:e instanceof File?e.size:null,error:null,result:null}}));console.log(`addFiles: Adding`,t.length,`files`);let n=[...c.current,...t];c.current=n,console.log(`addFiles: Updated itemsRef.current to`,n.length,`items`),s(n)},[f]),y=t(e=>{let t=c.current.find(t=>t.id===e);if(t&&t.state.status===`uploading`){let t=d.current.get(e);t&&(t.abort(),d.current.delete(e))}s(t=>{let n=t.filter(t=>t.id!==e);return c.current=n,n}),u.current.delete(e)},[]),b=t(e=>{let t=c.current.find(t=>t.id===e);if(t&&t.state.status===`uploading`){let t=d.current.get(e);t&&(t.abort(),d.current.delete(e)),u.current.delete(e),s(t=>{let n=t.map(t=>t.id===e?{...t,state:{...t.state,status:`aborted`}}:t);return c.current=n,n}),h()}},[h]),x=t(e=>{let t=c.current.find(t=>t.id===e);t&&[`error`,`aborted`].includes(t.state.status)&&(s(t=>{let n=t.map(t=>t.id===e?{...t,state:{...t.state,status:`idle`,error:null}}:t);return c.current=n,n}),setTimeout(()=>h(),0))},[h]),S=t(()=>{let e=c.current;console.log(`Starting all uploads`,e);let t=e.filter(e=>e.state.status===`idle`),n=r-u.current.size,i=t.slice(0,n);for(let e of i)console.log(`Starting next upload`,e),h()},[r,h]),C=t(()=>{c.current.filter(e=>e.state.status===`uploading`).forEach(e=>{let t=d.current.get(e.id);t&&(t.abort(),d.current.delete(e.id))}),u.current.clear(),s(e=>{let t=e.map(e=>e.state.status===`uploading`?{...e,state:{...e.state,status:`aborted`}}:e);return c.current=t,t})},[]);return{state:_,items:i,addFiles:v,removeItem:y,removeFile:y,startAll:S,abortUpload:b,abortAll:C,retryUpload:x,retryFailed:t(()=>{let e=c.current.filter(e=>[`error`,`aborted`].includes(e.state.status));e.length>0&&(s(t=>{let n=t.map(t=>e.some(e=>e.id===t.id)?{...t,state:{...t.state,status:`idle`,error:null}}:t);return c.current=n,n}),setTimeout(S,0))},[S]),clearCompleted:t(()=>{s(e=>{let t=e.filter(e=>![`success`,`error`,`aborted`].includes(e.state.status));return c.current=t,t})},[]),clearAll:t(()=>{C(),s([]),c.current=[],u.current.clear()},[C]),getItemsByStatus:t(e=>c.current.filter(t=>t.state.status===e),[]),metrics:{getInsights:()=>n.client.getChunkingInsights(),exportMetrics:()=>n.client.exportMetrics(),getNetworkMetrics:()=>n.client.getNetworkMetrics(),getNetworkCondition:()=>n.client.getNetworkCondition(),resetMetrics:()=>n.client.resetMetrics()}}}const w={status:`idle`,progress:0,bytesUploaded:0,totalBytes:null,error:null,result:null};function T(e={}){let n=g(),[i,s]=o(w),c=a(null);return r(()=>(c.current=new u((e,t)=>n.client.upload(e,t),{onStateChange:s,onProgress:e.onProgress,onChunkComplete:e.onChunkComplete,onSuccess:e.onSuccess,onError:e.onError,onAbort:e.onAbort},{metadata:e.metadata,uploadLengthDeferred:e.uploadLengthDeferred,uploadSize:e.uploadSize,onShouldRetry:e.onShouldRetry}),()=>{c.current?.cleanup()}),[n,e]),{state:i,upload:t(e=>{c.current?.upload(e)},[]),abort:t(()=>{c.current?.abort()},[]),reset:t(()=>{c.current?.reset()},[]),retry:t(()=>{c.current?.retry()},[]),isUploading:i.status===`uploading`,canRetry:c.current?.canRetry()??!1,metrics:{getInsights:()=>n.client.getChunkingInsights(),exportMetrics:()=>n.client.exportMetrics(),getNetworkMetrics:()=>n.client.getNetworkMetrics(),getNetworkCondition:()=>n.client.getNetworkCondition(),resetMetrics:()=>n.client.resetMetrics()}}}export{_ as a,p as c,y as i,C as n,h as o,S as r,g as s,T as t};
2
- //# sourceMappingURL=use-upload-BNiPsNBv.mjs.map