@devultur/convex 1.4.3 → 1.5.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 +100 -5
- package/dist/{chunk-JZYKPLI4.js → chunk-RDVBE3G3.js} +20 -1
- package/dist/index.d.ts +24 -7
- package/dist/index.js +22 -4
- package/dist/locales/es-formal.d.ts +5 -3
- package/dist/locales/es-formal.js +6 -3
- package/dist/locales/es.d.ts +5 -3
- package/dist/locales/es.js +5 -3
- package/dist/{preview-labels-B7HNa7Va.d.ts → preview-labels-C2nUUY_f.d.ts} +1 -1
- package/dist/react.d.ts +121 -85
- package/dist/react.js +303 -231
- package/dist/{types-DZxN6Eij.d.ts → types-C0ruHUcu.d.ts} +14 -8
- package/package.json +3 -3
package/dist/react.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./chunk-YYOZ3PDH.js";
|
|
27
27
|
|
|
28
28
|
// src/react.tsx
|
|
29
|
-
import
|
|
29
|
+
import React24 from "react";
|
|
30
30
|
import {
|
|
31
31
|
buildMediaUrl as buildMediaUrl2,
|
|
32
32
|
CaptionLocaleLabel,
|
|
@@ -36,12 +36,12 @@ import {
|
|
|
36
36
|
mediaUrl as mediaUrl4,
|
|
37
37
|
visibilityFromKey
|
|
38
38
|
} from "@devultur/core";
|
|
39
|
-
import {
|
|
40
|
-
UploadHttpError,
|
|
41
|
-
uploadFileWithProgress
|
|
42
|
-
} from "@devultur/react";
|
|
39
|
+
import { UploadHttpError, uploadFileWithProgress } from "@devultur/react";
|
|
43
40
|
import { useAction as useAction2, useConvex, useMutation, useQuery } from "convex/react";
|
|
44
41
|
|
|
42
|
+
// src/devultur-context.ts
|
|
43
|
+
import React from "react";
|
|
44
|
+
|
|
45
45
|
// src/capability-error.ts
|
|
46
46
|
var DevulturCapabilityError = class extends Error {
|
|
47
47
|
code = "capability_unavailable";
|
|
@@ -55,6 +55,24 @@ var DevulturCapabilityError = class extends Error {
|
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
// src/devultur-context.ts
|
|
59
|
+
var DevulturContext = /* @__PURE__ */ React.createContext(null);
|
|
60
|
+
var DevulturApiContext = /* @__PURE__ */ React.createContext(null);
|
|
61
|
+
function useDevultur() {
|
|
62
|
+
const context = React.use(DevulturContext);
|
|
63
|
+
if (!context) throw new Error("useDevultur must be used within <DevulturProvider> or <DevulturViewerProvider>");
|
|
64
|
+
return context;
|
|
65
|
+
}
|
|
66
|
+
function useDevulturOptional() {
|
|
67
|
+
return React.use(DevulturContext);
|
|
68
|
+
}
|
|
69
|
+
function useDevulturApi() {
|
|
70
|
+
const api = React.use(DevulturApiContext);
|
|
71
|
+
if (api) return api;
|
|
72
|
+
if (React.use(DevulturContext)) throw new DevulturCapabilityError("api");
|
|
73
|
+
throw new Error("useDevulturApi must be used within <DevulturProvider>");
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
// src/media-fetch.ts
|
|
59
77
|
import { SESSION_HEADER } from "@devultur/core";
|
|
60
78
|
|
|
@@ -157,17 +175,16 @@ async function readCapped(body, maxBytes) {
|
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
// src/media-session.ts
|
|
160
|
-
import
|
|
178
|
+
import React3 from "react";
|
|
161
179
|
import {
|
|
162
180
|
buildMediaUrl,
|
|
163
181
|
mediaCaptionPath,
|
|
164
182
|
mediaUrl,
|
|
165
183
|
SESSION_HEADER as SESSION_HEADER2
|
|
166
184
|
} from "@devultur/core";
|
|
167
|
-
import { useAction } from "convex/react";
|
|
168
185
|
|
|
169
186
|
// src/viewer-token.ts
|
|
170
|
-
import
|
|
187
|
+
import React2 from "react";
|
|
171
188
|
import { DEFAULT_TOKEN_EXPIRY_SECONDS } from "@devultur/core";
|
|
172
189
|
import { convexToJson } from "convex/values";
|
|
173
190
|
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
@@ -193,30 +210,34 @@ function sortKeys(json) {
|
|
|
193
210
|
function tokenArgsKey(tokenArgs) {
|
|
194
211
|
return JSON.stringify(sortKeys(convexToJson(tokenArgs ?? {})));
|
|
195
212
|
}
|
|
196
|
-
function useViewerToken({ issueViewerToken, sessionSecret, tokenArgs }) {
|
|
213
|
+
function useViewerToken({ issueViewerToken, sessionSecret, tokenArgs, isLazy = false }) {
|
|
197
214
|
const argsKey = tokenArgsKey(tokenArgs);
|
|
198
|
-
const stableArgs =
|
|
199
|
-
const [issued, setIssued] =
|
|
200
|
-
const [attempt, setAttempt] =
|
|
215
|
+
const stableArgs = React2.useMemo(() => tokenArgs ?? {}, [argsKey]);
|
|
216
|
+
const [issued, setIssued] = React2.useState(null);
|
|
217
|
+
const [attempt, setAttempt] = React2.useState(null);
|
|
218
|
+
const [isRequested, setIsRequested] = React2.useState(!isLazy);
|
|
201
219
|
const token = issued?.argsKey === argsKey ? issued.token : null;
|
|
202
220
|
const currentAttempt = attempt?.argsKey === argsKey ? attempt : null;
|
|
203
|
-
const currentArgsKeyRef =
|
|
204
|
-
const refreshTimerRef =
|
|
205
|
-
const inFlightRef =
|
|
206
|
-
|
|
221
|
+
const currentArgsKeyRef = React2.useRef(argsKey);
|
|
222
|
+
const refreshTimerRef = React2.useRef(null);
|
|
223
|
+
const inFlightRef = React2.useRef(null);
|
|
224
|
+
const isMountedRef = React2.useRef(true);
|
|
225
|
+
React2.useEffect(() => {
|
|
207
226
|
currentArgsKeyRef.current = argsKey;
|
|
208
227
|
}, [argsKey]);
|
|
209
|
-
|
|
228
|
+
React2.useEffect(() => {
|
|
229
|
+
isMountedRef.current = true;
|
|
210
230
|
return () => {
|
|
231
|
+
isMountedRef.current = false;
|
|
211
232
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
212
233
|
};
|
|
213
234
|
}, []);
|
|
214
|
-
const request =
|
|
235
|
+
const request = React2.useCallback(async () => {
|
|
215
236
|
const renew = () => {
|
|
216
237
|
request().catch((error) => console.warn("Devultur: could not renew the viewer token", error));
|
|
217
238
|
};
|
|
218
239
|
const result = await issueViewerToken({ ...stableArgs, sessionSecret });
|
|
219
|
-
if (currentArgsKeyRef.current !== argsKey) return null;
|
|
240
|
+
if (!isMountedRef.current || currentArgsKeyRef.current !== argsKey) return null;
|
|
220
241
|
const { tokenValue, ttlMs } = extractTokenResult(result);
|
|
221
242
|
setIssued({ token: tokenValue, argsKey });
|
|
222
243
|
setAttempt(null);
|
|
@@ -224,7 +245,7 @@ function useViewerToken({ issueViewerToken, sessionSecret, tokenArgs }) {
|
|
|
224
245
|
refreshTimerRef.current = setTimeout(renew, Math.max(ttlMs - REFRESH_BUFFER_MS, MIN_REFRESH_MS));
|
|
225
246
|
return tokenValue;
|
|
226
247
|
}, [issueViewerToken, stableArgs, argsKey, sessionSecret]);
|
|
227
|
-
const issueOnce =
|
|
248
|
+
const issueOnce = React2.useCallback(() => {
|
|
228
249
|
const inFlight = inFlightRef.current;
|
|
229
250
|
if (inFlight?.argsKey === argsKey) return inFlight.promise;
|
|
230
251
|
setAttempt({ argsKey, status: "loading" });
|
|
@@ -240,50 +261,62 @@ function useViewerToken({ issueViewerToken, sessionSecret, tokenArgs }) {
|
|
|
240
261
|
return promise;
|
|
241
262
|
}, [request, argsKey]);
|
|
242
263
|
const hasAttempt = currentAttempt !== null;
|
|
243
|
-
|
|
244
|
-
if (token || hasAttempt) return;
|
|
264
|
+
React2.useEffect(() => {
|
|
265
|
+
if (!isRequested || token || hasAttempt) return;
|
|
245
266
|
issueOnce().catch(() => {
|
|
246
267
|
});
|
|
247
|
-
}, [token, hasAttempt, issueOnce]);
|
|
248
|
-
const
|
|
268
|
+
}, [isRequested, token, hasAttempt, issueOnce]);
|
|
269
|
+
const requestToken = React2.useCallback(() => setIsRequested(true), []);
|
|
270
|
+
const reissueToken = React2.useCallback(
|
|
249
271
|
() => issueOnce().catch(() => {
|
|
250
272
|
setIssued((current) => current?.argsKey === argsKey ? null : current);
|
|
251
273
|
return null;
|
|
252
274
|
}),
|
|
253
275
|
[issueOnce, argsKey]
|
|
254
276
|
);
|
|
255
|
-
const retryToken =
|
|
277
|
+
const retryToken = React2.useCallback(() => {
|
|
278
|
+
setIsRequested(true);
|
|
279
|
+
return issueOnce().catch(() => null);
|
|
280
|
+
}, [issueOnce]);
|
|
256
281
|
const tokenStatus = token ? "ready" : currentAttempt?.status ?? "idle";
|
|
257
282
|
const tokenError = !token && currentAttempt?.status === "error" ? currentAttempt.error : null;
|
|
258
|
-
return { token, tokenStatus, tokenError, reissueToken, retryToken };
|
|
283
|
+
return { token, tokenStatus, tokenError, reissueToken, retryToken, requestToken };
|
|
259
284
|
}
|
|
260
285
|
|
|
261
286
|
// src/media-session.ts
|
|
262
|
-
function useMediaSession({ issueViewerToken, tokenArgs, baseUrl }) {
|
|
263
|
-
const [sessionSecret] =
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
issueViewerToken: issueViewerTokenAction,
|
|
287
|
+
function useMediaSession({ issueViewerToken, tokenArgs, baseUrl, isLazy }) {
|
|
288
|
+
const [sessionSecret] = React3.useState(() => typeof crypto !== "undefined" ? crypto.randomUUID() : "");
|
|
289
|
+
const { token, tokenStatus, tokenError, reissueToken, retryToken, requestToken } = useViewerToken({
|
|
290
|
+
issueViewerToken,
|
|
267
291
|
sessionSecret,
|
|
268
|
-
tokenArgs
|
|
292
|
+
tokenArgs,
|
|
293
|
+
isLazy
|
|
269
294
|
});
|
|
270
|
-
const
|
|
295
|
+
const hasRequestedRef = React3.useRef(!isLazy);
|
|
296
|
+
const requestTokenAfterRender = React3.useCallback(() => {
|
|
297
|
+
if (hasRequestedRef.current) return;
|
|
298
|
+
hasRequestedRef.current = true;
|
|
299
|
+
queueMicrotask(requestToken);
|
|
300
|
+
}, [requestToken]);
|
|
301
|
+
const url = React3.useCallback(
|
|
271
302
|
(media, options) => {
|
|
272
303
|
if (!baseUrl) throw new Error("Devultur `url()` requires `baseUrl` on <DevulturProvider>");
|
|
304
|
+
if (media.visibility === "private" && !token) requestTokenAfterRender();
|
|
273
305
|
return mediaUrl(baseUrl, media, { ...options, token: token ?? void 0 });
|
|
274
306
|
},
|
|
275
|
-
[baseUrl, token]
|
|
307
|
+
[baseUrl, token, requestTokenAfterRender]
|
|
276
308
|
);
|
|
277
|
-
const getCaptionsText =
|
|
309
|
+
const getCaptionsText = React3.useCallback(
|
|
278
310
|
async (media, locale) => {
|
|
279
311
|
if (!baseUrl) throw new Error("Devultur `getCaptionsText()` requires `baseUrl` on <DevulturProvider>");
|
|
280
312
|
const path = mediaCaptionPath(media, locale);
|
|
281
313
|
if (!path) throw new Error("getCaptionsText: the reference is not a video");
|
|
282
314
|
const isPrivate = media.visibility === "private";
|
|
283
|
-
const fetchWith = (
|
|
315
|
+
const fetchWith = (viewerToken2) => fetch(buildMediaUrl(baseUrl, path, { token: viewerToken2 }), {
|
|
284
316
|
headers: sessionSecret ? { [SESSION_HEADER2]: sessionSecret } : void 0
|
|
285
317
|
});
|
|
286
|
-
|
|
318
|
+
const viewerToken = isPrivate ? token ?? await retryToken() : null;
|
|
319
|
+
let response = await fetchWith(viewerToken ?? void 0);
|
|
287
320
|
if (isPrivate && response.status === 401 && await isSessionMismatch(response)) {
|
|
288
321
|
const fresh = await reissueToken();
|
|
289
322
|
if (fresh) response = await fetchWith(fresh);
|
|
@@ -291,23 +324,42 @@ function useMediaSession({ issueViewerToken, tokenArgs, baseUrl }) {
|
|
|
291
324
|
if (!response.ok) throw new Error(`Captions fetch failed: ${response.status}`);
|
|
292
325
|
return response.text();
|
|
293
326
|
},
|
|
294
|
-
[baseUrl, token, reissueToken, sessionSecret]
|
|
327
|
+
[baseUrl, token, reissueToken, retryToken, sessionSecret]
|
|
295
328
|
);
|
|
296
|
-
return
|
|
329
|
+
return React3.useMemo(
|
|
297
330
|
() => ({
|
|
298
331
|
token,
|
|
299
332
|
tokenStatus,
|
|
300
333
|
tokenError,
|
|
301
334
|
retryToken,
|
|
335
|
+
requestToken,
|
|
302
336
|
baseUrl: baseUrl ?? null,
|
|
303
337
|
sessionSecret,
|
|
304
338
|
url,
|
|
305
339
|
getCaptionsText,
|
|
306
340
|
reissueToken
|
|
307
341
|
}),
|
|
308
|
-
[
|
|
342
|
+
[
|
|
343
|
+
token,
|
|
344
|
+
tokenStatus,
|
|
345
|
+
tokenError,
|
|
346
|
+
retryToken,
|
|
347
|
+
requestToken,
|
|
348
|
+
baseUrl,
|
|
349
|
+
sessionSecret,
|
|
350
|
+
url,
|
|
351
|
+
getCaptionsText,
|
|
352
|
+
reissueToken
|
|
353
|
+
]
|
|
309
354
|
);
|
|
310
355
|
}
|
|
356
|
+
function useRequestTokenWhen(session, isNeeded) {
|
|
357
|
+
const { requestToken, token } = session;
|
|
358
|
+
const hasToken = token !== null;
|
|
359
|
+
React3.useEffect(() => {
|
|
360
|
+
if (isNeeded && !hasToken) requestToken();
|
|
361
|
+
}, [isNeeded, hasToken, requestToken]);
|
|
362
|
+
}
|
|
311
363
|
|
|
312
364
|
// src/media-upload-derive.ts
|
|
313
365
|
function deriveUploadState(local, videoState, retry) {
|
|
@@ -352,7 +404,7 @@ function deriveUploadState(local, videoState, retry) {
|
|
|
352
404
|
}
|
|
353
405
|
|
|
354
406
|
// src/crop-upload.tsx
|
|
355
|
-
import
|
|
407
|
+
import React4 from "react";
|
|
356
408
|
import { ImageCropper } from "@devultur/react";
|
|
357
409
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
358
410
|
var DEFAULT_LABELS = {
|
|
@@ -376,18 +428,18 @@ function CropUpload({
|
|
|
376
428
|
children
|
|
377
429
|
}) {
|
|
378
430
|
const text = { ...DEFAULT_LABELS, ...labels };
|
|
379
|
-
const [pendingFile, setPendingFile] =
|
|
380
|
-
const inputRef =
|
|
431
|
+
const [pendingFile, setPendingFile] = React4.useState(null);
|
|
432
|
+
const inputRef = React4.useRef(null);
|
|
381
433
|
const { upload, state, cancel, reset } = useMediaUpload({ visibility, replaces, onComplete });
|
|
382
|
-
const pickFile =
|
|
383
|
-
const handleCrop =
|
|
434
|
+
const pickFile = React4.useCallback(() => inputRef.current?.click(), []);
|
|
435
|
+
const handleCrop = React4.useCallback(
|
|
384
436
|
(baked) => {
|
|
385
437
|
setPendingFile(null);
|
|
386
438
|
upload(baked);
|
|
387
439
|
},
|
|
388
440
|
[upload]
|
|
389
441
|
);
|
|
390
|
-
const resetAll =
|
|
442
|
+
const resetAll = React4.useCallback(() => {
|
|
391
443
|
setPendingFile(null);
|
|
392
444
|
reset();
|
|
393
445
|
}, [reset]);
|
|
@@ -440,7 +492,7 @@ function CropUpload({
|
|
|
440
492
|
}
|
|
441
493
|
|
|
442
494
|
// src/document-renderers.tsx
|
|
443
|
-
import
|
|
495
|
+
import React5 from "react";
|
|
444
496
|
|
|
445
497
|
// src/attachment-icons.tsx
|
|
446
498
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -495,7 +547,7 @@ function AttachmentCard({
|
|
|
495
547
|
labels,
|
|
496
548
|
...articleProps
|
|
497
549
|
}) {
|
|
498
|
-
const headingId =
|
|
550
|
+
const headingId = React5.useId();
|
|
499
551
|
if (!media) return null;
|
|
500
552
|
const text = mergeAttachmentLabels(labels);
|
|
501
553
|
const kind = attachmentKindOf(media, contentType);
|
|
@@ -513,7 +565,7 @@ function AttachmentCard({
|
|
|
513
565
|
}
|
|
514
566
|
|
|
515
567
|
// src/image.tsx
|
|
516
|
-
import
|
|
568
|
+
import React6 from "react";
|
|
517
569
|
import { mediaUrl as mediaUrl2 } from "@devultur/core";
|
|
518
570
|
|
|
519
571
|
// src/responsive-image.ts
|
|
@@ -607,7 +659,7 @@ function Image({
|
|
|
607
659
|
}
|
|
608
660
|
if (!src) return /* @__PURE__ */ jsx4(Fragment, { children: fallback });
|
|
609
661
|
if (render) {
|
|
610
|
-
return
|
|
662
|
+
return React6.cloneElement(render, {
|
|
611
663
|
style: { ...render.props.style, backgroundImage: `url("${src}")` }
|
|
612
664
|
});
|
|
613
665
|
}
|
|
@@ -627,26 +679,28 @@ function Image({
|
|
|
627
679
|
);
|
|
628
680
|
}
|
|
629
681
|
function usePrivateImage(media, transforms, onLoadError) {
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
const [
|
|
633
|
-
const
|
|
634
|
-
|
|
682
|
+
const devultur = useDevultur();
|
|
683
|
+
const { token, tokenStatus, tokenError, baseUrl, reissueToken, sessionSecret } = devultur;
|
|
684
|
+
const [isFailed, setIsFailed] = React6.useState(false);
|
|
685
|
+
const [blobSrc, setBlobSrc] = React6.useState(null);
|
|
686
|
+
const sessionRef = React6.useRef({ token, reissueToken, onLoadError });
|
|
687
|
+
React6.useEffect(() => {
|
|
635
688
|
sessionRef.current = { token, reissueToken, onLoadError };
|
|
636
689
|
});
|
|
637
690
|
const isPrivate = media?.visibility === "private";
|
|
691
|
+
useRequestTokenWhen(devultur, isPrivate);
|
|
638
692
|
const hasToken = token !== null;
|
|
639
693
|
const isTokenUnavailable = isPrivate && tokenStatus === "error";
|
|
640
694
|
const { width, height, fit, format, quality } = transforms;
|
|
641
|
-
|
|
695
|
+
React6.useEffect(() => {
|
|
642
696
|
setIsFailed(false);
|
|
643
697
|
setBlobSrc(null);
|
|
644
698
|
}, [media]);
|
|
645
|
-
|
|
699
|
+
React6.useEffect(() => {
|
|
646
700
|
if (!isTokenUnavailable || !media) return;
|
|
647
701
|
sessionRef.current.onLoadError?.(tokenUnavailableError(tokenError));
|
|
648
702
|
}, [isTokenUnavailable, media, tokenError]);
|
|
649
|
-
|
|
703
|
+
React6.useEffect(() => {
|
|
650
704
|
if (!isPrivate || isFailed || !media || !hasToken) return;
|
|
651
705
|
let cancelled = false;
|
|
652
706
|
let objectUrl = null;
|
|
@@ -685,21 +739,21 @@ function usePrivateImage(media, transforms, onLoadError) {
|
|
|
685
739
|
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
686
740
|
};
|
|
687
741
|
}, [isPrivate, isFailed, media, hasToken, baseUrl, sessionSecret, width, height, fit, format, quality]);
|
|
688
|
-
const handleError =
|
|
742
|
+
const handleError = React6.useCallback(() => setIsFailed(true), []);
|
|
689
743
|
return { blobSrc, isFailed: isFailed || isTokenUnavailable, handleError };
|
|
690
744
|
}
|
|
691
745
|
|
|
692
746
|
// src/media-library.tsx
|
|
693
|
-
import
|
|
747
|
+
import React15 from "react";
|
|
694
748
|
|
|
695
749
|
// src/media-library-item.tsx
|
|
696
|
-
import
|
|
750
|
+
import React12 from "react";
|
|
697
751
|
|
|
698
752
|
// src/pdf-thumbnail.tsx
|
|
699
|
-
import
|
|
753
|
+
import React11 from "react";
|
|
700
754
|
|
|
701
755
|
// src/media-resource.ts
|
|
702
|
-
import
|
|
756
|
+
import React7 from "react";
|
|
703
757
|
import { mediaPath, mediaUrl as mediaUrl3 } from "@devultur/core";
|
|
704
758
|
var IDLE = { value: null, contentType: null, isLoading: false, error: null };
|
|
705
759
|
var LOADING = { value: null, contentType: null, isLoading: true, error: null };
|
|
@@ -707,19 +761,20 @@ function useMediaResource(media, options) {
|
|
|
707
761
|
const devultur = useDevultur();
|
|
708
762
|
const { isEnabled, maxBytes, contentType } = options;
|
|
709
763
|
const isPrivate = media?.visibility === "private";
|
|
764
|
+
useRequestTokenWhen(devultur, isPrivate && isEnabled);
|
|
710
765
|
const isReady = !isPrivate || devultur.token !== null;
|
|
711
766
|
const isTokenUnavailable = isPrivate && devultur.tokenStatus === "error";
|
|
712
|
-
const tokenFailure =
|
|
767
|
+
const tokenFailure = React7.useMemo(
|
|
713
768
|
() => isTokenUnavailable ? tokenUnavailableError(devultur.tokenError) : null,
|
|
714
769
|
[isTokenUnavailable, devultur.tokenError]
|
|
715
770
|
);
|
|
716
771
|
const requestKey = media && isEnabled ? [devultur.baseUrl, media.visibility, mediaPath(media), contentType ?? "", maxBytes ?? ""].join("|") : null;
|
|
717
|
-
const [settled, setSettled] =
|
|
718
|
-
const inputsRef =
|
|
719
|
-
|
|
772
|
+
const [settled, setSettled] = React7.useState(null);
|
|
773
|
+
const inputsRef = React7.useRef({ media, devultur, options });
|
|
774
|
+
React7.useEffect(() => {
|
|
720
775
|
inputsRef.current = { media, devultur, options };
|
|
721
776
|
});
|
|
722
|
-
|
|
777
|
+
React7.useEffect(() => {
|
|
723
778
|
if (!requestKey || !isReady) return;
|
|
724
779
|
const { media: target, devultur: context, options: current } = inputsRef.current;
|
|
725
780
|
if (!target || !context.baseUrl) return;
|
|
@@ -757,7 +812,7 @@ function useMediaResource(media, options) {
|
|
|
757
812
|
}
|
|
758
813
|
|
|
759
814
|
// src/pdf-context.tsx
|
|
760
|
-
import
|
|
815
|
+
import React8 from "react";
|
|
761
816
|
|
|
762
817
|
// src/preview-labels.ts
|
|
763
818
|
var ERROR_TEXT = {
|
|
@@ -793,14 +848,14 @@ function mergePreviewLabels(...sources) {
|
|
|
793
848
|
|
|
794
849
|
// src/pdf-context.tsx
|
|
795
850
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
796
|
-
var PdfPreviewContext = /* @__PURE__ */
|
|
851
|
+
var PdfPreviewContext = /* @__PURE__ */ React8.createContext({});
|
|
797
852
|
function PdfPreviewProvider({
|
|
798
853
|
pdfjs,
|
|
799
854
|
workerSrc,
|
|
800
855
|
labels,
|
|
801
856
|
children
|
|
802
857
|
}) {
|
|
803
|
-
const config =
|
|
858
|
+
const config = React8.useMemo(() => ({ pdfjs, workerSrc, labels }), [pdfjs, workerSrc, labels]);
|
|
804
859
|
return /* @__PURE__ */ jsx5(PdfPreviewContext.Provider, { value: config, children });
|
|
805
860
|
}
|
|
806
861
|
var loadedModules = /* @__PURE__ */ new WeakMap();
|
|
@@ -812,9 +867,9 @@ function loadOnce(loader) {
|
|
|
812
867
|
return loading;
|
|
813
868
|
}
|
|
814
869
|
function usePdfjs(workerSrcOverride) {
|
|
815
|
-
const { pdfjs: loader, workerSrc } =
|
|
816
|
-
const [loaded, setLoaded] =
|
|
817
|
-
|
|
870
|
+
const { pdfjs: loader, workerSrc } = React8.useContext(PdfPreviewContext);
|
|
871
|
+
const [loaded, setLoaded] = React8.useState(null);
|
|
872
|
+
React8.useEffect(() => {
|
|
818
873
|
if (!loader) return;
|
|
819
874
|
let isCurrent = true;
|
|
820
875
|
loadOnce(loader).then((pdfjs) => {
|
|
@@ -830,8 +885,8 @@ function usePdfjs(workerSrcOverride) {
|
|
|
830
885
|
return { status: "ready", pdfjs: loaded.pdfjs, workerSrc: workerSrcOverride ?? workerSrc };
|
|
831
886
|
}
|
|
832
887
|
function usePdfPageCounter() {
|
|
833
|
-
const { pdfjs: loader, workerSrc } =
|
|
834
|
-
return
|
|
888
|
+
const { pdfjs: loader, workerSrc } = React8.useContext(PdfPreviewContext);
|
|
889
|
+
return React8.useMemo(() => {
|
|
835
890
|
if (!loader) return null;
|
|
836
891
|
return async (file) => {
|
|
837
892
|
const pdfjs = await loadOnce(loader);
|
|
@@ -841,15 +896,15 @@ function usePdfPageCounter() {
|
|
|
841
896
|
}, [loader, workerSrc]);
|
|
842
897
|
}
|
|
843
898
|
function usePreviewLabels(labels) {
|
|
844
|
-
const providerLabels =
|
|
899
|
+
const providerLabels = React8.useContext(PdfPreviewContext).labels;
|
|
845
900
|
return mergePreviewLabels(providerLabels, labels);
|
|
846
901
|
}
|
|
847
902
|
|
|
848
903
|
// src/pdf-hooks.ts
|
|
849
|
-
import
|
|
904
|
+
import React9 from "react";
|
|
850
905
|
function useIsVisible(ref, rootMargin = "200px") {
|
|
851
|
-
const [isVisible, setIsVisible] =
|
|
852
|
-
|
|
906
|
+
const [isVisible, setIsVisible] = React9.useState(false);
|
|
907
|
+
React9.useEffect(() => {
|
|
853
908
|
const element = ref.current;
|
|
854
909
|
if (isVisible || !element) return;
|
|
855
910
|
if (typeof IntersectionObserver === "undefined") {
|
|
@@ -870,12 +925,12 @@ function useIsVisible(ref, rootMargin = "200px") {
|
|
|
870
925
|
return isVisible;
|
|
871
926
|
}
|
|
872
927
|
function usePdfDocument({ pdfjs, bytes, workerSrc }) {
|
|
873
|
-
const [state, setState] =
|
|
928
|
+
const [state, setState] = React9.useState({
|
|
874
929
|
bytes: null,
|
|
875
930
|
document: null,
|
|
876
931
|
error: null
|
|
877
932
|
});
|
|
878
|
-
|
|
933
|
+
React9.useEffect(() => {
|
|
879
934
|
let isCurrent = true;
|
|
880
935
|
const loadingTask = openPdf(pdfjs, bytes, workerSrc);
|
|
881
936
|
loadingTask.promise.then((document2) => {
|
|
@@ -894,8 +949,8 @@ function usePdfDocument({ pdfjs, bytes, workerSrc }) {
|
|
|
894
949
|
}
|
|
895
950
|
function usePageRender(options) {
|
|
896
951
|
const { document: document2, pageNumber, canvasRef, width, isEnabled, shouldExtractText = false } = options;
|
|
897
|
-
const [state, setState] =
|
|
898
|
-
|
|
952
|
+
const [state, setState] = React9.useState({ isRendered: false, text: "" });
|
|
953
|
+
React9.useEffect(() => {
|
|
899
954
|
const canvas = canvasRef.current;
|
|
900
955
|
if (!document2 || !isEnabled || !canvas) return;
|
|
901
956
|
let isCurrent = true;
|
|
@@ -925,7 +980,7 @@ function usePageRender(options) {
|
|
|
925
980
|
}
|
|
926
981
|
|
|
927
982
|
// src/pdf-preview.tsx
|
|
928
|
-
import
|
|
983
|
+
import React10 from "react";
|
|
929
984
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
930
985
|
var VISUALLY_HIDDEN = {
|
|
931
986
|
position: "absolute",
|
|
@@ -971,9 +1026,9 @@ function PdfPreview(props) {
|
|
|
971
1026
|
function PdfCanvasDocument(props) {
|
|
972
1027
|
const { pdfjs, workerSrc, bytes, label, className, renderFallback, loading, labels, maxPages } = props;
|
|
973
1028
|
const { document: document2, error } = usePdfDocument({ pdfjs, bytes, workerSrc });
|
|
974
|
-
const containerRef =
|
|
975
|
-
const [width, setWidth] =
|
|
976
|
-
|
|
1029
|
+
const containerRef = React10.useRef(null);
|
|
1030
|
+
const [width, setWidth] = React10.useState(null);
|
|
1031
|
+
React10.useLayoutEffect(() => {
|
|
977
1032
|
if (document2) setWidth(containerRef.current?.clientWidth ?? 0);
|
|
978
1033
|
}, [document2]);
|
|
979
1034
|
if (error) return /* @__PURE__ */ jsx6(Fragment2, { children: renderFallback(renderFailedError(error)) });
|
|
@@ -1009,8 +1064,8 @@ function PdfCanvasDocument(props) {
|
|
|
1009
1064
|
);
|
|
1010
1065
|
}
|
|
1011
1066
|
function PdfPageView({ document: document2, pageNumber, width, label }) {
|
|
1012
|
-
const pageRef =
|
|
1013
|
-
const canvasRef =
|
|
1067
|
+
const pageRef = React10.useRef(null);
|
|
1068
|
+
const canvasRef = React10.useRef(null);
|
|
1014
1069
|
const isVisible = useIsVisible(pageRef);
|
|
1015
1070
|
const { isRendered, text } = usePageRender({
|
|
1016
1071
|
document: document2,
|
|
@@ -1027,8 +1082,8 @@ function PdfPageView({ document: document2, pageNumber, width, label }) {
|
|
|
1027
1082
|
] });
|
|
1028
1083
|
}
|
|
1029
1084
|
function PdfFrame({ bytes, label, className }) {
|
|
1030
|
-
const [objectUrl, setObjectUrl] =
|
|
1031
|
-
|
|
1085
|
+
const [objectUrl, setObjectUrl] = React10.useState(null);
|
|
1086
|
+
React10.useEffect(() => {
|
|
1032
1087
|
const url = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));
|
|
1033
1088
|
setObjectUrl(url);
|
|
1034
1089
|
return () => URL.revokeObjectURL(url);
|
|
@@ -1043,7 +1098,7 @@ var DEFAULT_THUMBNAIL_WIDTH = 96;
|
|
|
1043
1098
|
var DEFAULT_THUMBNAIL_MAX_BYTES = 50 * 1024 * 1024;
|
|
1044
1099
|
function PdfThumbnail(props) {
|
|
1045
1100
|
const { media, contentType, width = DEFAULT_THUMBNAIL_WIDTH, fallback = null, loading = null } = props;
|
|
1046
|
-
const containerRef =
|
|
1101
|
+
const containerRef = React11.useRef(null);
|
|
1047
1102
|
const isVisible = useIsVisible(containerRef);
|
|
1048
1103
|
const pdfjsState = usePdfjs(props.pdfWorkerSrc);
|
|
1049
1104
|
const labels = usePreviewLabels(props.labels);
|
|
@@ -1076,7 +1131,7 @@ function PdfThumbnail(props) {
|
|
|
1076
1131
|
return /* @__PURE__ */ jsx7("div", { ref: containerRef, className: props.className, style: { width }, children: content() });
|
|
1077
1132
|
}
|
|
1078
1133
|
function ThumbnailCanvas({ pdfjs, workerSrc, bytes, width, label, fallback, loading }) {
|
|
1079
|
-
const canvasRef =
|
|
1134
|
+
const canvasRef = React11.useRef(null);
|
|
1080
1135
|
const { document: document2, error } = usePdfDocument({ pdfjs, bytes, workerSrc });
|
|
1081
1136
|
const { isRendered } = usePageRender({ document: document2, pageNumber: 1, canvasRef, width, isEnabled: true });
|
|
1082
1137
|
if (error) return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
|
|
@@ -1144,7 +1199,7 @@ var ITEM_STYLE = {
|
|
|
1144
1199
|
};
|
|
1145
1200
|
function MediaLibraryItem(props) {
|
|
1146
1201
|
const { ref, record, isSelected, isActive, tabIndex, classNames, renderItem, onChoose, onFocus } = props;
|
|
1147
|
-
const media =
|
|
1202
|
+
const media = React12.useMemo(() => mediaFromRecord(record), [record]);
|
|
1148
1203
|
const handleKeyDown = (event) => {
|
|
1149
1204
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1150
1205
|
event.preventDefault();
|
|
@@ -1207,13 +1262,13 @@ function mergeMediaLibraryLabels(labels) {
|
|
|
1207
1262
|
}
|
|
1208
1263
|
|
|
1209
1264
|
// src/use-media-library.ts
|
|
1210
|
-
import
|
|
1265
|
+
import React13 from "react";
|
|
1211
1266
|
import { usePaginatedQuery } from "convex/react";
|
|
1212
1267
|
var SEARCH_DEBOUNCE_MS = 250;
|
|
1213
1268
|
var DEFAULT_PAGE_SIZE = 24;
|
|
1214
1269
|
function useDebouncedSearch(value, delayMs) {
|
|
1215
|
-
const [debounced, setDebounced] =
|
|
1216
|
-
|
|
1270
|
+
const [debounced, setDebounced] = React13.useState(value);
|
|
1271
|
+
React13.useEffect(() => {
|
|
1217
1272
|
if (value === "") {
|
|
1218
1273
|
setDebounced("");
|
|
1219
1274
|
return;
|
|
@@ -1238,7 +1293,7 @@ function useMediaLibrary(options = {}) {
|
|
|
1238
1293
|
const args = isSearching ? { search, ...typeArg } : typeArg;
|
|
1239
1294
|
const result = usePaginatedQuery(reference, args, { initialNumItems: pageSize });
|
|
1240
1295
|
const resultLoadMore = result.loadMore;
|
|
1241
|
-
const loadMore =
|
|
1296
|
+
const loadMore = React13.useCallback(
|
|
1242
1297
|
(numItems) => resultLoadMore(numItems ?? pageSize),
|
|
1243
1298
|
[resultLoadMore, pageSize]
|
|
1244
1299
|
);
|
|
@@ -1253,7 +1308,7 @@ function useMediaLibrary(options = {}) {
|
|
|
1253
1308
|
}
|
|
1254
1309
|
|
|
1255
1310
|
// src/use-roving-focus.ts
|
|
1256
|
-
import
|
|
1311
|
+
import React14 from "react";
|
|
1257
1312
|
function nextIndex(key, current, count, columns) {
|
|
1258
1313
|
const last = count - 1;
|
|
1259
1314
|
const clamp = (index) => Math.min(Math.max(index, 0), last);
|
|
@@ -1281,16 +1336,16 @@ function columnCount(items) {
|
|
|
1281
1336
|
return Math.max(inFirstRow.length, 1);
|
|
1282
1337
|
}
|
|
1283
1338
|
function useRovingFocus(count) {
|
|
1284
|
-
const [activeIndex, setActiveIndex] =
|
|
1285
|
-
const itemsRef =
|
|
1339
|
+
const [activeIndex, setActiveIndex] = React14.useState(0);
|
|
1340
|
+
const itemsRef = React14.useRef([]);
|
|
1286
1341
|
const currentIndex = Math.min(activeIndex, Math.max(count - 1, 0));
|
|
1287
|
-
const itemRef =
|
|
1342
|
+
const itemRef = React14.useCallback(
|
|
1288
1343
|
(index) => (element) => {
|
|
1289
1344
|
itemsRef.current[index] = element;
|
|
1290
1345
|
},
|
|
1291
1346
|
[]
|
|
1292
1347
|
);
|
|
1293
|
-
const handleKeyDown =
|
|
1348
|
+
const handleKeyDown = React14.useCallback(
|
|
1294
1349
|
(event) => {
|
|
1295
1350
|
if (count === 0) return;
|
|
1296
1351
|
const items = itemsRef.current.slice(0, count);
|
|
@@ -1322,16 +1377,16 @@ var LIST_STYLE = {
|
|
|
1322
1377
|
padding: 0
|
|
1323
1378
|
};
|
|
1324
1379
|
function MediaLibrary(props) {
|
|
1325
|
-
const labels =
|
|
1380
|
+
const labels = React15.useMemo(() => mergeMediaLibraryLabels(props.labels), [props.labels]);
|
|
1326
1381
|
const classNames = { ...props.classNames, root: props.className ?? props.classNames?.root };
|
|
1327
|
-
const [search, setSearch] =
|
|
1328
|
-
const [chosenType, setChosenType] =
|
|
1329
|
-
const [selectedKey, setSelectedKey] =
|
|
1382
|
+
const [search, setSearch] = React15.useState("");
|
|
1383
|
+
const [chosenType, setChosenType] = React15.useState("");
|
|
1384
|
+
const [selectedKey, setSelectedKey] = React15.useState(null);
|
|
1330
1385
|
const type = props.type ?? (chosenType === "" ? void 0 : chosenType);
|
|
1331
1386
|
const { onSelect } = props;
|
|
1332
1387
|
const isSelectable = props.selectable ?? props.selectedKeys !== void 0;
|
|
1333
1388
|
const selectedKeys = props.selectedKeys ?? (selectedKey === null ? [] : [selectedKey]);
|
|
1334
|
-
const handleChoose =
|
|
1389
|
+
const handleChoose = React15.useCallback(
|
|
1335
1390
|
(record) => {
|
|
1336
1391
|
if (isSelectable) setSelectedKey(record.key);
|
|
1337
1392
|
onSelect(record, mediaFromRecord(record));
|
|
@@ -1443,7 +1498,7 @@ function LibraryResults(props) {
|
|
|
1443
1498
|
)
|
|
1444
1499
|
] });
|
|
1445
1500
|
}
|
|
1446
|
-
var LibraryErrorBoundary = class extends
|
|
1501
|
+
var LibraryErrorBoundary = class extends React15.Component {
|
|
1447
1502
|
state = { hasError: false, error: null };
|
|
1448
1503
|
static getDerivedStateFromError(error) {
|
|
1449
1504
|
return { hasError: true, error };
|
|
@@ -1485,7 +1540,7 @@ function useMediaObjectUrl(media, options) {
|
|
|
1485
1540
|
}
|
|
1486
1541
|
|
|
1487
1542
|
// src/media-picker.tsx
|
|
1488
|
-
import
|
|
1543
|
+
import React22 from "react";
|
|
1489
1544
|
|
|
1490
1545
|
// src/media-picker-accept.ts
|
|
1491
1546
|
function matchesAccept(candidate, accept) {
|
|
@@ -1525,10 +1580,10 @@ function recordMatchesAccept(record, accept) {
|
|
|
1525
1580
|
}
|
|
1526
1581
|
|
|
1527
1582
|
// src/media-picker-context.ts
|
|
1528
|
-
import
|
|
1529
|
-
var MediaPickerContext = /* @__PURE__ */
|
|
1583
|
+
import React16 from "react";
|
|
1584
|
+
var MediaPickerContext = /* @__PURE__ */ React16.createContext(null);
|
|
1530
1585
|
function useMediaPicker() {
|
|
1531
|
-
const picker =
|
|
1586
|
+
const picker = React16.use(MediaPickerContext);
|
|
1532
1587
|
if (!picker) throw new Error("useMediaPicker must be used within <MediaPicker>");
|
|
1533
1588
|
return picker;
|
|
1534
1589
|
}
|
|
@@ -1569,13 +1624,13 @@ function mergeMediaPickerLabels(labels) {
|
|
|
1569
1624
|
}
|
|
1570
1625
|
|
|
1571
1626
|
// src/media-picker-library.tsx
|
|
1572
|
-
import
|
|
1627
|
+
import React17 from "react";
|
|
1573
1628
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1574
1629
|
function MediaPickerLibrary(props) {
|
|
1575
1630
|
const picker = useMediaPicker();
|
|
1576
1631
|
const { accept, toggle } = picker;
|
|
1577
1632
|
const selectedKeys = picker.selected.map((media) => media.id);
|
|
1578
|
-
const filter =
|
|
1633
|
+
const filter = React17.useMemo(
|
|
1579
1634
|
() => accept ? (record) => recordMatchesAccept(record, accept) : void 0,
|
|
1580
1635
|
[accept]
|
|
1581
1636
|
);
|
|
@@ -1596,13 +1651,13 @@ function MediaPickerLibrary(props) {
|
|
|
1596
1651
|
}
|
|
1597
1652
|
|
|
1598
1653
|
// src/media-picker-selection.tsx
|
|
1599
|
-
import
|
|
1654
|
+
import React19 from "react";
|
|
1600
1655
|
|
|
1601
1656
|
// src/preview.tsx
|
|
1602
1657
|
import { mediaPath as mediaPath2 } from "@devultur/core";
|
|
1603
1658
|
|
|
1604
1659
|
// src/video-preview.tsx
|
|
1605
|
-
import
|
|
1660
|
+
import React18 from "react";
|
|
1606
1661
|
import { VideoPlayer } from "@devultur/react";
|
|
1607
1662
|
import { Fragment as Fragment6, jsx as jsx11 } from "react/jsx-runtime";
|
|
1608
1663
|
var UNAUTHORIZED = 401;
|
|
@@ -1616,13 +1671,13 @@ function fetchErrorOf(error) {
|
|
|
1616
1671
|
if (HLS_NETWORK_FAILURE.test(error.details)) return networkError(error);
|
|
1617
1672
|
return renderFailedError(error);
|
|
1618
1673
|
}
|
|
1619
|
-
function VideoPreview({ media, renderFallback, className }) {
|
|
1620
|
-
const { tokenStatus, tokenError, reissueToken } = useDevultur();
|
|
1674
|
+
function VideoPreview({ media, renderFallback, loading, metrics, className }) {
|
|
1675
|
+
const { token, tokenStatus, tokenError, reissueToken } = useDevultur();
|
|
1621
1676
|
const videoProps = useVideoProps(media);
|
|
1622
|
-
const [failure, setFailure] =
|
|
1623
|
-
const hasReissuedRef =
|
|
1677
|
+
const [failure, setFailure] = React18.useState(null);
|
|
1678
|
+
const hasReissuedRef = React18.useRef(false);
|
|
1624
1679
|
const playerToken = videoProps?.token;
|
|
1625
|
-
const handleError =
|
|
1680
|
+
const handleError = React18.useCallback(
|
|
1626
1681
|
(error) => {
|
|
1627
1682
|
const isFirstMismatch = error.code === "session_mismatch" && !hasReissuedRef.current;
|
|
1628
1683
|
if (isFirstMismatch) {
|
|
@@ -1635,18 +1690,18 @@ function VideoPreview({ media, renderFallback, className }) {
|
|
|
1635
1690
|
[reissueToken, playerToken]
|
|
1636
1691
|
);
|
|
1637
1692
|
if (!videoProps) return null;
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1693
|
+
const isPrivate = media.visibility === "private";
|
|
1694
|
+
if (isPrivate && tokenStatus === "error") return /* @__PURE__ */ jsx11(Fragment6, { children: renderFallback(tokenUnavailableError(tokenError)) });
|
|
1695
|
+
if (isPrivate && !token) return /* @__PURE__ */ jsx11(Fragment6, { children: loading });
|
|
1641
1696
|
if (failure && failure.token === playerToken) return /* @__PURE__ */ jsx11(Fragment6, { children: renderFallback(failure.error) });
|
|
1642
|
-
return /* @__PURE__ */ jsx11(VideoPlayer, { ...videoProps, className, onError: handleError });
|
|
1697
|
+
return /* @__PURE__ */ jsx11(VideoPlayer, { ...videoProps, className, metrics, onError: handleError });
|
|
1643
1698
|
}
|
|
1644
1699
|
|
|
1645
1700
|
// src/preview.tsx
|
|
1646
1701
|
import { Fragment as Fragment7, jsx as jsx12 } from "react/jsx-runtime";
|
|
1647
1702
|
var DEFAULT_MAX_TEXT_BYTES = 1024 * 1024;
|
|
1648
1703
|
var DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
|
|
1649
|
-
function Preview({ media, contentType, fallback, loading, labels, ...viewProps }) {
|
|
1704
|
+
function Preview({ media, contentType, fallback, loading, labels, metrics, ...viewProps }) {
|
|
1650
1705
|
const text = usePreviewLabels(labels);
|
|
1651
1706
|
if (!media) return null;
|
|
1652
1707
|
const renderFallback = (error) => {
|
|
@@ -1655,12 +1710,15 @@ function Preview({ media, contentType, fallback, loading, labels, ...viewProps }
|
|
|
1655
1710
|
if (fallback !== void 0) return fallback;
|
|
1656
1711
|
return /* @__PURE__ */ jsx12(DefaultFallback, { media, error: fetchError, labels: text });
|
|
1657
1712
|
};
|
|
1713
|
+
const loadingNode = loading === void 0 ? /* @__PURE__ */ jsx12(LoadingStatus, { text: text.loading }) : loading;
|
|
1658
1714
|
if (media.kind === "video") {
|
|
1659
1715
|
return /* @__PURE__ */ jsx12(
|
|
1660
1716
|
VideoPreview,
|
|
1661
1717
|
{
|
|
1662
1718
|
media,
|
|
1663
1719
|
renderFallback,
|
|
1720
|
+
loading: loadingNode,
|
|
1721
|
+
metrics,
|
|
1664
1722
|
className: viewProps.className
|
|
1665
1723
|
},
|
|
1666
1724
|
mediaPath2(media)
|
|
@@ -1669,7 +1727,6 @@ function Preview({ media, contentType, fallback, loading, labels, ...viewProps }
|
|
|
1669
1727
|
const expectedType = expectedContentType(media, contentType);
|
|
1670
1728
|
const kind = previewKindOf(expectedType);
|
|
1671
1729
|
if (!expectedType || !kind) return /* @__PURE__ */ jsx12(Fragment7, { children: renderFallback(unsupportedType(expectedType)) });
|
|
1672
|
-
const loadingNode = loading === void 0 ? /* @__PURE__ */ jsx12(LoadingStatus, { text: text.loading }) : loading;
|
|
1673
1730
|
const props = { ...viewProps, media, expectedType, renderFallback, loading: loadingNode, labels: text };
|
|
1674
1731
|
if (kind === "text") return /* @__PURE__ */ jsx12(TextPreview, { ...props });
|
|
1675
1732
|
if (kind === "pdf") return /* @__PURE__ */ jsx12(PdfPreview, { ...props, maxBytes: props.maxBytes ?? DEFAULT_MAX_BYTES });
|
|
@@ -1732,8 +1789,8 @@ var THUMBNAIL_WIDTH2 = 96;
|
|
|
1732
1789
|
function MediaPickerSelection({ renderItem, headingLevel = 3, className }) {
|
|
1733
1790
|
const picker = useMediaPicker();
|
|
1734
1791
|
const { labels, selected } = picker;
|
|
1735
|
-
const headingId =
|
|
1736
|
-
const [previewedId, setPreviewedId] =
|
|
1792
|
+
const headingId = React19.useId();
|
|
1793
|
+
const [previewedId, setPreviewedId] = React19.useState(null);
|
|
1737
1794
|
const Heading = `h${headingLevel}`;
|
|
1738
1795
|
const cardHeadingLevel = headingLevel + 1;
|
|
1739
1796
|
const regionClass = className ? `devultur-media-picker__selection ${className}` : "devultur-media-picker__selection";
|
|
@@ -1799,12 +1856,12 @@ function SelectionActions() {
|
|
|
1799
1856
|
}
|
|
1800
1857
|
|
|
1801
1858
|
// src/media-picker-upload.tsx
|
|
1802
|
-
import
|
|
1859
|
+
import React20 from "react";
|
|
1803
1860
|
import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1804
1861
|
function MediaPickerUpload({ className }) {
|
|
1805
1862
|
const picker = useMediaPicker();
|
|
1806
1863
|
const { labels, multiple } = picker;
|
|
1807
|
-
const [isDragging, setIsDragging] =
|
|
1864
|
+
const [isDragging, setIsDragging] = React20.useState(false);
|
|
1808
1865
|
const handleDragOver = (event) => {
|
|
1809
1866
|
event.preventDefault();
|
|
1810
1867
|
setIsDragging(true);
|
|
@@ -1861,7 +1918,7 @@ function UploadRow({ upload }) {
|
|
|
1861
1918
|
}
|
|
1862
1919
|
|
|
1863
1920
|
// src/use-media-picker-uploads.ts
|
|
1864
|
-
import
|
|
1921
|
+
import React21 from "react";
|
|
1865
1922
|
var isAbortError = (error) => typeof error === "object" && error !== null && error.name === "AbortError";
|
|
1866
1923
|
var isPdf = (file) => file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf");
|
|
1867
1924
|
var nextUploadId = 0;
|
|
@@ -1872,18 +1929,18 @@ async function pageCountOf(file, countPdfPages) {
|
|
|
1872
1929
|
}
|
|
1873
1930
|
function useMediaPickerUploads(options) {
|
|
1874
1931
|
const { upload } = useDevultur();
|
|
1875
|
-
const [uploads, setUploads] =
|
|
1876
|
-
const controllers =
|
|
1877
|
-
const optionsRef =
|
|
1932
|
+
const [uploads, setUploads] = React21.useState([]);
|
|
1933
|
+
const controllers = React21.useRef(/* @__PURE__ */ new Map());
|
|
1934
|
+
const optionsRef = React21.useRef(options);
|
|
1878
1935
|
optionsRef.current = options;
|
|
1879
|
-
const patch =
|
|
1936
|
+
const patch = React21.useCallback((id, changes) => {
|
|
1880
1937
|
setUploads((current) => current.map((entry) => entry.id === id ? { ...entry, ...changes } : entry));
|
|
1881
1938
|
}, []);
|
|
1882
|
-
const drop =
|
|
1939
|
+
const drop = React21.useCallback((id) => {
|
|
1883
1940
|
controllers.current.delete(id);
|
|
1884
1941
|
setUploads((current) => current.filter((entry) => entry.id !== id));
|
|
1885
1942
|
}, []);
|
|
1886
|
-
const run =
|
|
1943
|
+
const run = React21.useCallback(
|
|
1887
1944
|
async (id, file) => {
|
|
1888
1945
|
const { labels, announce } = optionsRef.current;
|
|
1889
1946
|
const controller = new AbortController();
|
|
@@ -1913,7 +1970,7 @@ function useMediaPickerUploads(options) {
|
|
|
1913
1970
|
},
|
|
1914
1971
|
[upload, patch, drop]
|
|
1915
1972
|
);
|
|
1916
|
-
const start =
|
|
1973
|
+
const start = React21.useCallback(
|
|
1917
1974
|
(files) => {
|
|
1918
1975
|
const { accept, multiple, labels } = optionsRef.current;
|
|
1919
1976
|
const chosen = multiple ? [...files] : [...files].slice(0, 1);
|
|
@@ -1935,8 +1992,8 @@ function useMediaPickerUploads(options) {
|
|
|
1935
1992
|
},
|
|
1936
1993
|
[run]
|
|
1937
1994
|
);
|
|
1938
|
-
const cancel =
|
|
1939
|
-
const retry =
|
|
1995
|
+
const cancel = React21.useCallback((id) => controllers.current.get(id)?.abort(), []);
|
|
1996
|
+
const retry = React21.useCallback(
|
|
1940
1997
|
(id) => {
|
|
1941
1998
|
const entry = uploads.find((candidate) => candidate.id === id);
|
|
1942
1999
|
if (!entry?.canRetry) return;
|
|
@@ -1944,7 +2001,7 @@ function useMediaPickerUploads(options) {
|
|
|
1944
2001
|
},
|
|
1945
2002
|
[uploads, run]
|
|
1946
2003
|
);
|
|
1947
|
-
|
|
2004
|
+
React21.useEffect(() => {
|
|
1948
2005
|
const active = controllers.current;
|
|
1949
2006
|
return () => {
|
|
1950
2007
|
for (const controller of active.values()) controller.abort();
|
|
@@ -1971,22 +2028,22 @@ function emit(props, next) {
|
|
|
1971
2028
|
var isSame = (a, b) => a.id === b.id;
|
|
1972
2029
|
function MediaPickerRoot(props) {
|
|
1973
2030
|
const isMultiple = props.multiple ?? false;
|
|
1974
|
-
const labels =
|
|
2031
|
+
const labels = React22.useMemo(() => mergeMediaPickerLabels(props.labels), [props.labels]);
|
|
1975
2032
|
const accept = uploadAcceptOf(props.accept, props.type);
|
|
1976
2033
|
const libraryType = props.type ?? mediaTypeOfAccept(props.accept);
|
|
1977
2034
|
const countPdfPages = usePdfPageCounter();
|
|
1978
|
-
const [announcement, setAnnouncement] =
|
|
1979
|
-
const inputRef =
|
|
1980
|
-
const selectionRef =
|
|
1981
|
-
const [ownSelection, setOwnSelection] =
|
|
2035
|
+
const [announcement, setAnnouncement] = React22.useState("");
|
|
2036
|
+
const inputRef = React22.useRef(null);
|
|
2037
|
+
const selectionRef = React22.useRef(null);
|
|
2038
|
+
const [ownSelection, setOwnSelection] = React22.useState(EMPTY);
|
|
1982
2039
|
const controlledSelection = selectionOf(props);
|
|
1983
2040
|
const selected = controlledSelection ?? ownSelection;
|
|
1984
|
-
const selectedRef =
|
|
2041
|
+
const selectedRef = React22.useRef(selected);
|
|
1985
2042
|
selectedRef.current = selected;
|
|
1986
|
-
const propsRef =
|
|
2043
|
+
const propsRef = React22.useRef(props);
|
|
1987
2044
|
propsRef.current = props;
|
|
1988
2045
|
const isControlled = controlledSelection !== void 0;
|
|
1989
|
-
const commit =
|
|
2046
|
+
const commit = React22.useCallback(
|
|
1990
2047
|
(next) => {
|
|
1991
2048
|
selectedRef.current = next;
|
|
1992
2049
|
if (!isControlled) setOwnSelection(next);
|
|
@@ -1994,15 +2051,15 @@ function MediaPickerRoot(props) {
|
|
|
1994
2051
|
},
|
|
1995
2052
|
[isControlled]
|
|
1996
2053
|
);
|
|
1997
|
-
const announce =
|
|
1998
|
-
const select =
|
|
2054
|
+
const announce = React22.useCallback((message) => setAnnouncement(message), []);
|
|
2055
|
+
const select = React22.useCallback(
|
|
1999
2056
|
(media) => {
|
|
2000
2057
|
const current = selectedRef.current.filter((item) => !isSame(item, media));
|
|
2001
2058
|
commit(isMultiple ? [...current, media] : [media]);
|
|
2002
2059
|
},
|
|
2003
2060
|
[commit, isMultiple]
|
|
2004
2061
|
);
|
|
2005
|
-
const remove =
|
|
2062
|
+
const remove = React22.useCallback(
|
|
2006
2063
|
(media) => {
|
|
2007
2064
|
commit(selectedRef.current.filter((item) => !isSame(item, media)));
|
|
2008
2065
|
announce(labels.removed(mediaName(media)));
|
|
@@ -2010,7 +2067,7 @@ function MediaPickerRoot(props) {
|
|
|
2010
2067
|
},
|
|
2011
2068
|
[commit, announce, labels]
|
|
2012
2069
|
);
|
|
2013
|
-
const toggle =
|
|
2070
|
+
const toggle = React22.useCallback(
|
|
2014
2071
|
(media) => {
|
|
2015
2072
|
const isSelected = selectedRef.current.some((item) => isSame(item, media));
|
|
2016
2073
|
if (isMultiple && isSelected) {
|
|
@@ -2022,12 +2079,12 @@ function MediaPickerRoot(props) {
|
|
|
2022
2079
|
},
|
|
2023
2080
|
[isMultiple, remove, select, announce, labels]
|
|
2024
2081
|
);
|
|
2025
|
-
const clear =
|
|
2082
|
+
const clear = React22.useCallback(() => {
|
|
2026
2083
|
commit([]);
|
|
2027
2084
|
announce(labels.cleared);
|
|
2028
2085
|
selectionRef.current?.focus();
|
|
2029
2086
|
}, [commit, announce, labels]);
|
|
2030
|
-
const handleUploaded =
|
|
2087
|
+
const handleUploaded = React22.useCallback(
|
|
2031
2088
|
(media) => {
|
|
2032
2089
|
select(media);
|
|
2033
2090
|
selectionRef.current?.focus();
|
|
@@ -2096,25 +2153,59 @@ var MediaPicker = /* @__PURE__ */ Object.assign(MediaPickerRoot, {
|
|
|
2096
2153
|
Selection: MediaPickerSelection
|
|
2097
2154
|
});
|
|
2098
2155
|
|
|
2099
|
-
// src/
|
|
2156
|
+
// src/viewer-provider.tsx
|
|
2157
|
+
import React23 from "react";
|
|
2158
|
+
import { useAction } from "convex/react";
|
|
2100
2159
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2101
|
-
var
|
|
2102
|
-
|
|
2103
|
-
|
|
2160
|
+
var VIEWER_ACTIONS = {
|
|
2161
|
+
uploadUrl: () => Promise.reject(new DevulturCapabilityError("uploadUrl")),
|
|
2162
|
+
upload: () => Promise.reject(new DevulturCapabilityError("upload")),
|
|
2163
|
+
deleteMedia: () => {
|
|
2164
|
+
throw new DevulturCapabilityError("deleteMedia");
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
var NO_TOKEN_ACTION = () => Promise.reject(new Error("<DevulturViewerProvider> has no `issueViewerToken`: private media can't be shown"));
|
|
2168
|
+
function DevulturViewerProvider({ issueViewerToken, ...props }) {
|
|
2169
|
+
if (!issueViewerToken) return /* @__PURE__ */ jsx16(ViewerSession, { ...props, issueViewerToken: NO_TOKEN_ACTION, lazyToken: true });
|
|
2170
|
+
return /* @__PURE__ */ jsx16(ConvexViewerSession, { ...props, issueViewerToken });
|
|
2171
|
+
}
|
|
2172
|
+
function ConvexViewerSession({
|
|
2173
|
+
issueViewerToken,
|
|
2174
|
+
...props
|
|
2175
|
+
}) {
|
|
2176
|
+
const issueViewerTokenAction = useAction(issueViewerToken);
|
|
2177
|
+
return /* @__PURE__ */ jsx16(ViewerSession, { issueViewerToken: issueViewerTokenAction, ...props });
|
|
2178
|
+
}
|
|
2179
|
+
function ViewerSession({
|
|
2180
|
+
baseUrl,
|
|
2181
|
+
issueViewerToken,
|
|
2182
|
+
tokenArgs,
|
|
2183
|
+
lazyToken = false,
|
|
2184
|
+
children
|
|
2185
|
+
}) {
|
|
2186
|
+
const session = useMediaSession({ issueViewerToken, tokenArgs, baseUrl, isLazy: lazyToken });
|
|
2187
|
+
const devulturValue = React23.useMemo(() => ({ ...session, ...VIEWER_ACTIONS }), [session]);
|
|
2188
|
+
return /* @__PURE__ */ jsx16(DevulturContext, { value: devulturValue, children });
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// src/react.tsx
|
|
2192
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2193
|
+
var ProgressContext = /* @__PURE__ */ React24.createContext(null);
|
|
2104
2194
|
function DevulturProvider({ api, baseUrl, tokenArgs, issueViewerToken, children }) {
|
|
2105
|
-
const
|
|
2195
|
+
const issueViewerTokenAction = useAction2(issueViewerToken ?? api.issueViewerToken);
|
|
2196
|
+
const session = useMediaSession({ issueViewerToken: issueViewerTokenAction, tokenArgs, baseUrl });
|
|
2106
2197
|
const createUploadUrlAction = useAction2(api.createUploadUrl);
|
|
2107
2198
|
const deleteMediaAction = useAction2(api.deleteMedia);
|
|
2108
2199
|
const confirmUploadAction = useAction2(api.confirmUpload ?? api.createUploadUrl);
|
|
2109
2200
|
const deleteVideoAction = useAction2(api.deleteVideo ?? api.deleteMedia);
|
|
2110
|
-
const uploadUrl =
|
|
2201
|
+
const uploadUrl = React24.useCallback(
|
|
2111
2202
|
async (file) => {
|
|
2112
2203
|
const result = await createUploadUrlAction({ filename: file.name, contentType: file.type });
|
|
2113
2204
|
return { url: result.url, key: result.key };
|
|
2114
2205
|
},
|
|
2115
2206
|
[createUploadUrlAction]
|
|
2116
2207
|
);
|
|
2117
|
-
const upload =
|
|
2208
|
+
const upload = React24.useCallback(
|
|
2118
2209
|
async (file, options) => {
|
|
2119
2210
|
const dimensions = await readImageDimensions(file);
|
|
2120
2211
|
options?.signal?.throwIfAborted();
|
|
@@ -2140,7 +2231,7 @@ function DevulturProvider({ api, baseUrl, tokenArgs, issueViewerToken, children
|
|
|
2140
2231
|
},
|
|
2141
2232
|
[createUploadUrlAction, confirmUploadAction, api.confirmUpload]
|
|
2142
2233
|
);
|
|
2143
|
-
const deleteMedia =
|
|
2234
|
+
const deleteMedia = React24.useCallback(
|
|
2144
2235
|
(media) => {
|
|
2145
2236
|
if (media.kind === "video" && api.deleteVideo) {
|
|
2146
2237
|
deleteVideoAction({ videoId: media.id, visibility: media.visibility }).catch(console.error);
|
|
@@ -2150,13 +2241,13 @@ function DevulturProvider({ api, baseUrl, tokenArgs, issueViewerToken, children
|
|
|
2150
2241
|
},
|
|
2151
2242
|
[deleteMediaAction, deleteVideoAction, api.deleteVideo]
|
|
2152
2243
|
);
|
|
2153
|
-
const devulturValue =
|
|
2244
|
+
const devulturValue = React24.useMemo(
|
|
2154
2245
|
() => ({ ...session, uploadUrl, upload, deleteMedia }),
|
|
2155
2246
|
[session, uploadUrl, upload, deleteMedia]
|
|
2156
2247
|
);
|
|
2157
|
-
let content = /* @__PURE__ */
|
|
2248
|
+
let content = /* @__PURE__ */ jsx17(DevulturApiContext, { value: api, children: /* @__PURE__ */ jsx17(DevulturContext, { value: devulturValue, children }) });
|
|
2158
2249
|
if (api.saveProgress && api.loadProgress && api.markComplete && api.resetProgress) {
|
|
2159
|
-
content = /* @__PURE__ */
|
|
2250
|
+
content = /* @__PURE__ */ jsx17(
|
|
2160
2251
|
ProgressBridge,
|
|
2161
2252
|
{
|
|
2162
2253
|
save: api.saveProgress,
|
|
@@ -2180,13 +2271,13 @@ function ProgressBridge({
|
|
|
2180
2271
|
const saveMutation = useMutation(save);
|
|
2181
2272
|
const markCompleteMutation = useMutation(markComplete);
|
|
2182
2273
|
const resetMutation = useMutation(reset);
|
|
2183
|
-
const onSave =
|
|
2274
|
+
const onSave = React24.useCallback(
|
|
2184
2275
|
async (videoId, position) => {
|
|
2185
2276
|
await saveMutation({ videoId, position });
|
|
2186
2277
|
},
|
|
2187
2278
|
[saveMutation]
|
|
2188
2279
|
);
|
|
2189
|
-
const onLoad =
|
|
2280
|
+
const onLoad = React24.useCallback(
|
|
2190
2281
|
async (videoId) => {
|
|
2191
2282
|
const result = await convex.query(load, { videoId });
|
|
2192
2283
|
if (!result) return null;
|
|
@@ -2197,60 +2288,34 @@ function ProgressBridge({
|
|
|
2197
2288
|
},
|
|
2198
2289
|
[convex, load]
|
|
2199
2290
|
);
|
|
2200
|
-
const onMarkComplete =
|
|
2291
|
+
const onMarkComplete = React24.useCallback(
|
|
2201
2292
|
async (videoId) => {
|
|
2202
2293
|
await markCompleteMutation({ videoId });
|
|
2203
2294
|
},
|
|
2204
2295
|
[markCompleteMutation]
|
|
2205
2296
|
);
|
|
2206
|
-
const onReset =
|
|
2297
|
+
const onReset = React24.useCallback(
|
|
2207
2298
|
async (videoId) => {
|
|
2208
2299
|
await resetMutation({ videoId });
|
|
2209
2300
|
},
|
|
2210
2301
|
[resetMutation]
|
|
2211
2302
|
);
|
|
2212
|
-
const value =
|
|
2303
|
+
const value = React24.useMemo(
|
|
2213
2304
|
() => ({ save: onSave, load: onLoad, markComplete: onMarkComplete, reset: onReset }),
|
|
2214
2305
|
[onSave, onLoad, onMarkComplete, onReset]
|
|
2215
2306
|
);
|
|
2216
|
-
return /* @__PURE__ */
|
|
2217
|
-
}
|
|
2218
|
-
function useDevultur() {
|
|
2219
|
-
const context = React22.use(DevulturContext);
|
|
2220
|
-
if (!context) throw new Error("useDevultur must be used within <DevulturProvider> or <DevulturViewerProvider>");
|
|
2221
|
-
return context;
|
|
2222
|
-
}
|
|
2223
|
-
function useDevulturApi() {
|
|
2224
|
-
const api = React22.use(DevulturApiContext);
|
|
2225
|
-
if (api) return api;
|
|
2226
|
-
if (React22.use(DevulturContext)) throw new DevulturCapabilityError("api");
|
|
2227
|
-
throw new Error("useDevulturApi must be used within <DevulturProvider>");
|
|
2228
|
-
}
|
|
2229
|
-
var VIEWER_ACTIONS = {
|
|
2230
|
-
uploadUrl: () => Promise.reject(new DevulturCapabilityError("uploadUrl")),
|
|
2231
|
-
upload: () => Promise.reject(new DevulturCapabilityError("upload")),
|
|
2232
|
-
deleteMedia: () => {
|
|
2233
|
-
throw new DevulturCapabilityError("deleteMedia");
|
|
2234
|
-
}
|
|
2235
|
-
};
|
|
2236
|
-
function DevulturViewerProvider({
|
|
2237
|
-
baseUrl,
|
|
2238
|
-
issueViewerToken,
|
|
2239
|
-
tokenArgs,
|
|
2240
|
-
children
|
|
2241
|
-
}) {
|
|
2242
|
-
const session = useMediaSession({ issueViewerToken, tokenArgs, baseUrl });
|
|
2243
|
-
const devulturValue = React22.useMemo(() => ({ ...session, ...VIEWER_ACTIONS }), [session]);
|
|
2244
|
-
return /* @__PURE__ */ jsx16(DevulturContext, { value: devulturValue, children });
|
|
2307
|
+
return /* @__PURE__ */ jsx17(ProgressContext, { value, children });
|
|
2245
2308
|
}
|
|
2246
2309
|
function useDevulturProgress() {
|
|
2247
|
-
const context =
|
|
2310
|
+
const context = React24.use(ProgressContext);
|
|
2248
2311
|
if (!context) throw new Error("useDevulturProgress requires progress API in DevulturProvider");
|
|
2249
2312
|
return context;
|
|
2250
2313
|
}
|
|
2251
2314
|
function useMediaUrl(key, options) {
|
|
2252
|
-
const
|
|
2253
|
-
|
|
2315
|
+
const devultur = useDevultur();
|
|
2316
|
+
const { token, baseUrl } = devultur;
|
|
2317
|
+
useRequestTokenWhen(devultur, Boolean(key) && visibilityFromKey(key ?? "") !== "public");
|
|
2318
|
+
return React24.useMemo(() => {
|
|
2254
2319
|
if (!key) return null;
|
|
2255
2320
|
if (!baseUrl) throw new Error("useMediaUrl requires `baseUrl` on <DevulturProvider>");
|
|
2256
2321
|
const isPublic = visibilityFromKey(key) === "public";
|
|
@@ -2276,9 +2341,11 @@ async function readImageDimensions(file) {
|
|
|
2276
2341
|
}
|
|
2277
2342
|
}
|
|
2278
2343
|
function useVideoProps(media, options) {
|
|
2279
|
-
const
|
|
2344
|
+
const devultur = useDevultur();
|
|
2345
|
+
const { token, baseUrl, sessionSecret } = devultur;
|
|
2346
|
+
useRequestTokenWhen(devultur, media?.kind === "video" && media.visibility === "private");
|
|
2280
2347
|
const locales = options?.locales;
|
|
2281
|
-
return
|
|
2348
|
+
return React24.useMemo(() => {
|
|
2282
2349
|
if (media?.kind !== "video") return null;
|
|
2283
2350
|
if (!baseUrl) throw new Error("useVideoProps requires `baseUrl` on <DevulturProvider>");
|
|
2284
2351
|
const viewerToken = media.visibility === "private" ? token ?? void 0 : void 0;
|
|
@@ -2297,8 +2364,10 @@ function useVideoProps(media, options) {
|
|
|
2297
2364
|
}, [media, baseUrl, token, locales, sessionSecret]);
|
|
2298
2365
|
}
|
|
2299
2366
|
function DownloadLink({ media, filename, onDownloadError, children, ...anchorProps }) {
|
|
2300
|
-
const
|
|
2301
|
-
const
|
|
2367
|
+
const devultur = useDevultur();
|
|
2368
|
+
const { url, token, tokenStatus, tokenError, baseUrl, sessionSecret, reissueToken } = devultur;
|
|
2369
|
+
useRequestTokenWhen(devultur, media?.visibility === "private");
|
|
2370
|
+
const handleClick = React24.useCallback(
|
|
2302
2371
|
async (event) => {
|
|
2303
2372
|
if (media?.visibility !== "private" || !baseUrl) return;
|
|
2304
2373
|
event.preventDefault();
|
|
@@ -2327,10 +2396,10 @@ function DownloadLink({ media, filename, onDownloadError, children, ...anchorPro
|
|
|
2327
2396
|
[media, baseUrl, token, tokenStatus, tokenError, filename, sessionSecret, reissueToken, onDownloadError]
|
|
2328
2397
|
);
|
|
2329
2398
|
if (!media) return null;
|
|
2330
|
-
return /* @__PURE__ */
|
|
2399
|
+
return /* @__PURE__ */ jsx17("a", { href: url(media, { download: filename ?? true }), download: true, ...anchorProps, onClick: handleClick, children });
|
|
2331
2400
|
}
|
|
2332
2401
|
function useDevulturMedia(key) {
|
|
2333
|
-
const apiCtx =
|
|
2402
|
+
const apiCtx = React24.use(DevulturApiContext);
|
|
2334
2403
|
if (!apiCtx?.getMedia) {
|
|
2335
2404
|
throw new Error("useDevulturMedia requires getMedia in DevulturProvider api");
|
|
2336
2405
|
}
|
|
@@ -2384,10 +2453,12 @@ function useDevulturMedia(key) {
|
|
|
2384
2453
|
};
|
|
2385
2454
|
}
|
|
2386
2455
|
function useVideoState(media, options) {
|
|
2387
|
-
const
|
|
2456
|
+
const devultur = useDevultur();
|
|
2457
|
+
const { token, baseUrl, sessionSecret } = devultur;
|
|
2458
|
+
useRequestTokenWhen(devultur, media?.kind === "video" && media.visibility === "private");
|
|
2388
2459
|
const status = useDevulturMedia(media?.kind === "video" ? media.id : null);
|
|
2389
2460
|
const locales = options?.locales;
|
|
2390
|
-
return
|
|
2461
|
+
return React24.useMemo(() => {
|
|
2391
2462
|
if (media?.kind !== "video") return { phase: "idle" };
|
|
2392
2463
|
if (status.isFailed) {
|
|
2393
2464
|
return {
|
|
@@ -2436,17 +2507,17 @@ function useMediaUpload(options) {
|
|
|
2436
2507
|
const deleteMediaAction = useAction2(api.deleteMedia);
|
|
2437
2508
|
const confirmUploadAction = useAction2(api.confirmUpload ?? api.createUploadUrl);
|
|
2438
2509
|
const processVideoAction = useAction2(api.processVideo ?? api.createUploadUrl);
|
|
2439
|
-
const [phase, setPhase] =
|
|
2440
|
-
const [percent, setPercent] =
|
|
2441
|
-
const [currentFile, setCurrentFile] =
|
|
2442
|
-
const [media, setMedia] =
|
|
2443
|
-
const [uploadFailure, setUploadFailure] =
|
|
2444
|
-
const [processRequested, setProcessRequested] =
|
|
2445
|
-
const abortRef =
|
|
2446
|
-
const lastFileRef =
|
|
2447
|
-
const lastKeyRef =
|
|
2510
|
+
const [phase, setPhase] = React24.useState("idle");
|
|
2511
|
+
const [percent, setPercent] = React24.useState(0);
|
|
2512
|
+
const [currentFile, setCurrentFile] = React24.useState(null);
|
|
2513
|
+
const [media, setMedia] = React24.useState(null);
|
|
2514
|
+
const [uploadFailure, setUploadFailure] = React24.useState(null);
|
|
2515
|
+
const [processRequested, setProcessRequested] = React24.useState(false);
|
|
2516
|
+
const abortRef = React24.useRef(null);
|
|
2517
|
+
const lastFileRef = React24.useRef(null);
|
|
2518
|
+
const lastKeyRef = React24.useRef(null);
|
|
2448
2519
|
const videoState = useVideoState(media, { locales });
|
|
2449
|
-
const triggerProcess =
|
|
2520
|
+
const triggerProcess = React24.useCallback(
|
|
2450
2521
|
(key) => {
|
|
2451
2522
|
if (!api.processVideo) {
|
|
2452
2523
|
throw new Error("useMediaUpload: processing a video requires `processVideo` in the <DevulturProvider> api");
|
|
@@ -2459,7 +2530,7 @@ function useMediaUpload(options) {
|
|
|
2459
2530
|
},
|
|
2460
2531
|
[api.processVideo, processVideoAction]
|
|
2461
2532
|
);
|
|
2462
|
-
const runUpload =
|
|
2533
|
+
const runUpload = React24.useCallback(
|
|
2463
2534
|
async (file) => {
|
|
2464
2535
|
const controller = new AbortController();
|
|
2465
2536
|
abortRef.current = controller;
|
|
@@ -2530,10 +2601,10 @@ function useMediaUpload(options) {
|
|
|
2530
2601
|
onError
|
|
2531
2602
|
]
|
|
2532
2603
|
);
|
|
2533
|
-
const cancel =
|
|
2604
|
+
const cancel = React24.useCallback(() => {
|
|
2534
2605
|
abortRef.current?.abort();
|
|
2535
2606
|
}, []);
|
|
2536
|
-
const reset =
|
|
2607
|
+
const reset = React24.useCallback(() => {
|
|
2537
2608
|
abortRef.current?.abort();
|
|
2538
2609
|
setPhase("idle");
|
|
2539
2610
|
setPercent(0);
|
|
@@ -2542,15 +2613,15 @@ function useMediaUpload(options) {
|
|
|
2542
2613
|
setUploadFailure(null);
|
|
2543
2614
|
setProcessRequested(false);
|
|
2544
2615
|
}, []);
|
|
2545
|
-
const retry =
|
|
2616
|
+
const retry = React24.useCallback(() => {
|
|
2546
2617
|
if (phase === "upload-failed" && lastFileRef.current) {
|
|
2547
2618
|
runUpload(lastFileRef.current);
|
|
2548
2619
|
return;
|
|
2549
2620
|
}
|
|
2550
2621
|
if (lastKeyRef.current) triggerProcess(lastKeyRef.current);
|
|
2551
2622
|
}, [phase, runUpload, triggerProcess]);
|
|
2552
|
-
|
|
2553
|
-
const state =
|
|
2623
|
+
React24.useEffect(() => () => abortRef.current?.abort(), []);
|
|
2624
|
+
const state = React24.useMemo(
|
|
2554
2625
|
() => deriveUploadState(
|
|
2555
2626
|
{ phase, percent, file: currentFile, failure: uploadFailure, media, processRequested },
|
|
2556
2627
|
videoState,
|
|
@@ -2558,7 +2629,7 @@ function useMediaUpload(options) {
|
|
|
2558
2629
|
),
|
|
2559
2630
|
[phase, percent, currentFile, uploadFailure, media, processRequested, videoState, retry]
|
|
2560
2631
|
);
|
|
2561
|
-
const upload =
|
|
2632
|
+
const upload = React24.useCallback((file) => runUpload(file), [runUpload]);
|
|
2562
2633
|
return { upload, state, cancel, reset };
|
|
2563
2634
|
}
|
|
2564
2635
|
export {
|
|
@@ -2586,6 +2657,7 @@ export {
|
|
|
2586
2657
|
useDevultur,
|
|
2587
2658
|
useDevulturApi,
|
|
2588
2659
|
useDevulturMedia,
|
|
2660
|
+
useDevulturOptional,
|
|
2589
2661
|
useDevulturProgress,
|
|
2590
2662
|
useMediaLibrary,
|
|
2591
2663
|
useMediaObjectUrl,
|