@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.
- package/dist/components/index.d.mts +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +1 -1
- package/dist/index.d.mts +76 -4
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{upload-zone-DFStubbe.mjs → upload-zone-BQeUnuNK.mjs} +2 -2
- package/dist/{upload-zone-DFStubbe.mjs.map → upload-zone-BQeUnuNK.mjs.map} +1 -1
- package/dist/{uploadista-provider-CrS6TmpJ.d.mts → uploadista-provider-BIWt4Zfs.d.mts} +3 -3
- package/dist/{uploadista-provider-CrS6TmpJ.d.mts.map → uploadista-provider-BIWt4Zfs.d.mts.map} +1 -1
- package/dist/use-upload-BNYuUjSi.mjs +2 -0
- package/dist/use-upload-BNYuUjSi.mjs.map +1 -0
- package/dist/{use-upload-metrics-C1amBY1k.mjs → use-upload-metrics-9uwhKMWa.mjs} +2 -2
- package/dist/{use-upload-metrics-C1amBY1k.mjs.map → use-upload-metrics-9uwhKMWa.mjs.map} +1 -1
- package/dist/{use-upload-metrics-IXxUORce.d.mts → use-upload-metrics-Cd0ML_kN.d.mts} +2 -2
- package/dist/{use-upload-metrics-IXxUORce.d.mts.map → use-upload-metrics-Cd0ML_kN.d.mts.map} +1 -1
- package/dist/{use-uploadista-client--ivZPO88.d.mts → use-uploadista-client-B_VQ3UgO.d.mts} +4 -4
- package/dist/use-uploadista-client-B_VQ3UgO.d.mts.map +1 -0
- package/package.json +5 -5
- package/src/components/uploadista-provider.tsx +5 -1
- package/src/contexts/flow-manager-context.tsx +230 -0
- package/src/hooks/use-flow-upload.ts +47 -112
- package/src/hooks/use-uploadista-client.ts +1 -0
- package/src/index.ts +6 -0
- package/dist/use-upload-BNiPsNBv.mjs +0 -2
- package/dist/use-upload-BNiPsNBv.mjs.map +0 -1
- 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 {
|
|
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 {
|
|
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
|
|
117
|
-
* are automatically
|
|
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
|
-
|
|
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<
|
|
180
|
+
const managerRef = useRef<FlowManager<unknown, TOutput> | null>(null);
|
|
205
181
|
|
|
206
|
-
//
|
|
207
|
-
|
|
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
|
-
//
|
|
185
|
+
// Update refs on every render to capture latest callbacks
|
|
259
186
|
useEffect(() => {
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
283
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|