@ai-matrx/media 0.2.2 → 0.3.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/CHANGELOG.md +67 -0
- package/README.md +26 -3
- package/dist/core.cjs +177 -39
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +84 -4
- package/dist/core.d.ts +84 -4
- package/dist/core.js +164 -26
- package/dist/core.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +230 -79
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +208 -56
- package/dist/react.js.map +1 -1
- package/package.json +3 -7
package/dist/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { MediaClient, MediaHostPorts, MediaRefLike, MediaResolution, MediaUnavailableReason, DurableSrc, MediaActionContext, MediaActionKind, MediaSlotComponent, MediaSharePopoverProps } from './index.js';
|
|
3
|
+
import { MediaClient, MediaHostPorts, MediaRefLike, MediaFailureInfo, MediaResolution, MediaUnavailableReason, DurableSrc, MediaActionContext, MediaActionKind, MediaSlotComponent, MediaSharePopoverProps } from './index.js';
|
|
4
4
|
export { M as MediaUploadEntry, a as MediaUploadEntryStatus, b as MediaUploadProgress, U as UseMediaUploadResult, u as useMediaUpload } from './use-media-upload-Dg2HSvTq.js';
|
|
5
5
|
|
|
6
6
|
interface MediaProviderProps {
|
|
@@ -29,6 +29,58 @@ declare function useMediaHostPorts(): MediaHostPorts;
|
|
|
29
29
|
|
|
30
30
|
declare function stableRefKey(ref: MediaRefLike | null | undefined): string;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* src/core/diagnostics.ts — the terminal-failure capture machinery.
|
|
34
|
+
*
|
|
35
|
+
* Born from the 2026-08-30 prod outage: server-side cookie-secret drift
|
|
36
|
+
* killed every private image, the package rendered its honest "unavailable"
|
|
37
|
+
* panel — and nothing reached the host's Error Inspector or error DB,
|
|
38
|
+
* because the package had no diagnostics port and the host cannot capture
|
|
39
|
+
* what it never learns about. Per ruling C22 the capture logic lives HERE,
|
|
40
|
+
* so every consuming repo inherits it.
|
|
41
|
+
*
|
|
42
|
+
* The contract:
|
|
43
|
+
*
|
|
44
|
+
* - every terminal failure path emits exactly ONE {@link MediaFailureInfo}
|
|
45
|
+
* per media identity (latched — re-renders, remounts and the unavailable
|
|
46
|
+
* panel backstop never spam);
|
|
47
|
+
* - a bound `MediaHostPorts.diagnostics.capture` receives the payload;
|
|
48
|
+
* WITHOUT one (or if it throws) the package `console.error`s the same
|
|
49
|
+
* structured payload — the loud default. A media failure is NEVER silent;
|
|
50
|
+
* - the payload never carries a signed URL: identity is the file id or a
|
|
51
|
+
* query-stripped URL key ({@link failureMediaIdentity}).
|
|
52
|
+
*
|
|
53
|
+
* The latch lives on `globalThis` under a `Symbol.for` slot (same reason as
|
|
54
|
+
* context.ts: with `splitting: false` + dual formats, module-level state
|
|
55
|
+
* silently duplicates across bundle/loader graphs).
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Clear the once-per-failure latch. For tests, and for hosts at auth
|
|
60
|
+
* boundaries (a sign-in can genuinely fix a previously-terminal failure, and
|
|
61
|
+
* a recurrence after it deserves a fresh capture).
|
|
62
|
+
*/
|
|
63
|
+
declare function resetMediaFailureLatch(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Durable diagnostics identity for a media reference. NEVER a signed URL:
|
|
66
|
+
* file ids win, and any URL is stripped to origin + path (signatures ride
|
|
67
|
+
* query strings).
|
|
68
|
+
*/
|
|
69
|
+
declare function failureMediaIdentity(ref: MediaRefLike | null | undefined, src?: string | null): string;
|
|
70
|
+
/**
|
|
71
|
+
* Emit one media-failure capture through the host sink, or the loud
|
|
72
|
+
* console.error default when no sink is bound (or the sink throws).
|
|
73
|
+
* Latched per (terminal|warning, mediaRef) — one capture per failure,
|
|
74
|
+
* however many renders, hooks, or backstops observe it.
|
|
75
|
+
*/
|
|
76
|
+
declare function emitMediaFailure(capture: ((info: MediaFailureInfo) => void) | undefined, info: MediaFailureInfo): void;
|
|
77
|
+
/**
|
|
78
|
+
* Hook form: a stable capture function wired to the host ports. Every core
|
|
79
|
+
* hook and the unavailable-panel backstop route through this — the phase
|
|
80
|
+
* vocabulary and the latch stay in ONE place.
|
|
81
|
+
*/
|
|
82
|
+
declare function useMediaFailureCapture(): (info: Omit<MediaFailureInfo, "source">) => void;
|
|
83
|
+
|
|
32
84
|
/**
|
|
33
85
|
* src/core/use-media-resolution.ts — ref → durable resolution, as state.
|
|
34
86
|
*
|
|
@@ -36,8 +88,10 @@ declare function stableRefKey(ref: MediaRefLike | null | undefined): string;
|
|
|
36
88
|
* even a caller-supplied URL string is classified, never trusted. Law 15 of
|
|
37
89
|
* the handler (unavailable is a rendered state): a throwing `resolve()` is
|
|
38
90
|
* classified via `classifyError` and returned as typed state — never
|
|
39
|
-
* re-thrown
|
|
40
|
-
*
|
|
91
|
+
* re-thrown. It IS however a terminal failure, so it also emits exactly one
|
|
92
|
+
* `MediaFailureInfo` (phase "resolve") through the diagnostics machinery —
|
|
93
|
+
* the 2026-08-30 outage rule: an unavailable render is honest to the USER but
|
|
94
|
+
* must never be silent to the SYSTEM.
|
|
41
95
|
*/
|
|
42
96
|
|
|
43
97
|
interface MediaResolutionState {
|
|
@@ -65,6 +119,26 @@ declare function useMediaResolution(ref: MediaRefLike | null | undefined): Media
|
|
|
65
119
|
* `recoverable: false` resolutions (foreign URLs, blob: object URLs) skip the
|
|
66
120
|
* client and fail straight through — a session refresh can't resurrect
|
|
67
121
|
* someone else's link rot.
|
|
122
|
+
*
|
|
123
|
+
* Every terminal outcome emits exactly one `MediaFailureInfo` through the
|
|
124
|
+
* diagnostics machinery (the 2026-08-30 outage rule):
|
|
125
|
+
*
|
|
126
|
+
* - element error, recovery impossible → phase "element-load",
|
|
127
|
+
* retryOutcome "not-attempted"
|
|
128
|
+
* - terminal verdict after a granted retry → phase "element-load",
|
|
129
|
+
* retryOutcome "retried"
|
|
130
|
+
* - terminal verdict on the FIRST attempt → phase "recovery",
|
|
131
|
+
* retryOutcome "not-attempted"
|
|
132
|
+
* (the recovery machinery itself declined — usually the session mint
|
|
133
|
+
* came back unusable)
|
|
134
|
+
* - `recoverLoadError` rejected → phase "session",
|
|
135
|
+
* retryOutcome "recovery-failed"
|
|
136
|
+
*
|
|
137
|
+
* A structured `MediaRecoveryReport` verdict may additionally carry the
|
|
138
|
+
* `/files/session` mint's `secret_source`; `"ephemeral"` emits a NON-terminal
|
|
139
|
+
* warning ("private renders will break across restarts/instances") the moment
|
|
140
|
+
* the client sees it — before the outage, not after. The bare string verdict
|
|
141
|
+
* stays fully supported; the field is tolerated absent.
|
|
68
142
|
*/
|
|
69
143
|
|
|
70
144
|
interface MediaLoadRecovery {
|
|
@@ -82,6 +156,12 @@ declare function useMediaLoadRecovery(src: DurableSrc | string | null, options?:
|
|
|
82
156
|
recoverable?: boolean | undefined;
|
|
83
157
|
/** Fires once recovery is exhausted, with the original element event. */
|
|
84
158
|
onTerminal?: ((event?: unknown) => void) | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Durable identity of the media being recovered, for diagnostics —
|
|
161
|
+
* failures are reported against the file id / ref key, never the URL.
|
|
162
|
+
* Falls back to the query-stripped src when absent.
|
|
163
|
+
*/
|
|
164
|
+
failureRef?: MediaRefLike | null | undefined;
|
|
85
165
|
}): MediaLoadRecovery;
|
|
86
166
|
|
|
87
167
|
/**
|
|
@@ -211,4 +291,4 @@ declare function getFileKindDetails(fileName: string, mimeType?: string | null):
|
|
|
211
291
|
/** Folder icon details (open picks the open-folder glyph). */
|
|
212
292
|
declare function getFolderKindDetails(open?: boolean): FileKindDetails;
|
|
213
293
|
|
|
214
|
-
export { type FileCategory, type FileIconName, type FileKindDetails, type MediaActionsState, type MediaBlobState, type MediaLoadRecovery, MediaProvider, type MediaProviderProps, type MediaResolutionState, type ThumbnailMode, type ThumbnailSourceState, type UseMediaActionsOptions, type UseThumbnailSourceOptions, getFileKindDetails, getFolderKindDetails, stableRefKey, useMediaActions, useMediaBlob, useMediaClient, useMediaHostPorts, useMediaLoadRecovery, useMediaResolution, useThumbnailSource };
|
|
294
|
+
export { type FileCategory, type FileIconName, type FileKindDetails, type MediaActionsState, type MediaBlobState, type MediaLoadRecovery, MediaProvider, type MediaProviderProps, type MediaResolutionState, type ThumbnailMode, type ThumbnailSourceState, type UseMediaActionsOptions, type UseThumbnailSourceOptions, emitMediaFailure, failureMediaIdentity, getFileKindDetails, getFolderKindDetails, resetMediaFailureLatch, stableRefKey, useMediaActions, useMediaBlob, useMediaClient, useMediaFailureCapture, useMediaHostPorts, useMediaLoadRecovery, useMediaResolution, useThumbnailSource };
|
package/dist/core.js
CHANGED
|
@@ -50,29 +50,107 @@ function stableRefKey(ref) {
|
|
|
50
50
|
return "<empty-ref>";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// src/core/diagnostics.ts
|
|
54
|
+
import { useCallback, useRef } from "react";
|
|
55
|
+
var LATCH_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.failure-latch");
|
|
56
|
+
function latch() {
|
|
57
|
+
const host = globalThis;
|
|
58
|
+
host[LATCH_SLOT] ??= /* @__PURE__ */ new Set();
|
|
59
|
+
return host[LATCH_SLOT];
|
|
60
|
+
}
|
|
61
|
+
function resetMediaFailureLatch() {
|
|
62
|
+
latch().clear();
|
|
63
|
+
}
|
|
64
|
+
function failureMediaIdentity(ref, src) {
|
|
65
|
+
if (ref && typeof ref === "object" && ref.file_id) return `file:${ref.file_id}`;
|
|
66
|
+
const raw = typeof ref === "string" ? ref : (ref && typeof ref === "object" ? ref.url ?? null : null) ?? src ?? null;
|
|
67
|
+
if (!raw) return "<unknown-media>";
|
|
68
|
+
if (raw.startsWith("blob:")) return "blob:object-url";
|
|
69
|
+
try {
|
|
70
|
+
const u = new URL(raw);
|
|
71
|
+
return `url:${u.origin}${u.pathname}`;
|
|
72
|
+
} catch {
|
|
73
|
+
const clean = raw.split(/[?#]/, 1)[0] ?? raw;
|
|
74
|
+
return `ref:${clean}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function emitMediaFailure(capture, info) {
|
|
78
|
+
const key = `${info.terminal ? "terminal" : "warning"}|${info.mediaRef}`;
|
|
79
|
+
const seen = latch();
|
|
80
|
+
if (seen.has(key)) return;
|
|
81
|
+
seen.add(key);
|
|
82
|
+
if (capture) {
|
|
83
|
+
try {
|
|
84
|
+
capture(info);
|
|
85
|
+
return;
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.error(
|
|
90
|
+
"[@ai-matrx/media] media failure (no diagnostics sink captured it \u2014 bind MediaHostPorts.diagnostics.capture to your error system)",
|
|
91
|
+
info
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function useMediaFailureCapture() {
|
|
95
|
+
const ports = useMediaHostPorts();
|
|
96
|
+
const capture = ports.diagnostics?.capture;
|
|
97
|
+
const captureRef = useRef(capture);
|
|
98
|
+
captureRef.current = capture;
|
|
99
|
+
return useCallback((info) => {
|
|
100
|
+
emitMediaFailure(captureRef.current, { source: "media", ...info });
|
|
101
|
+
}, []);
|
|
102
|
+
}
|
|
103
|
+
|
|
53
104
|
// src/core/use-media-resolution.ts
|
|
54
|
-
import { useMemo } from "react";
|
|
105
|
+
import { useEffect, useMemo } from "react";
|
|
55
106
|
function useMediaResolution(ref) {
|
|
56
107
|
const client = useMediaClient();
|
|
108
|
+
const capture = useMediaFailureCapture();
|
|
57
109
|
const refKey = stableRefKey(ref);
|
|
58
|
-
|
|
59
|
-
if (!ref)
|
|
110
|
+
const { state, failureMessage } = useMemo(() => {
|
|
111
|
+
if (!ref) {
|
|
112
|
+
return {
|
|
113
|
+
state: { resolution: null, status: "empty", reason: null },
|
|
114
|
+
failureMessage: null
|
|
115
|
+
};
|
|
116
|
+
}
|
|
60
117
|
try {
|
|
61
118
|
const resolution = client.resolve(ref);
|
|
62
|
-
if (!resolution)
|
|
63
|
-
|
|
119
|
+
if (!resolution) {
|
|
120
|
+
return {
|
|
121
|
+
state: { resolution: null, status: "empty", reason: null },
|
|
122
|
+
failureMessage: null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
state: { resolution, status: "ready", reason: null },
|
|
127
|
+
failureMessage: null
|
|
128
|
+
};
|
|
64
129
|
} catch (err) {
|
|
65
130
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
131
|
+
state: {
|
|
132
|
+
resolution: null,
|
|
133
|
+
status: "unavailable",
|
|
134
|
+
reason: client.classifyError(err)
|
|
135
|
+
},
|
|
136
|
+
failureMessage: err instanceof Error ? err.message : String(err ?? "resolve() failed")
|
|
69
137
|
};
|
|
70
138
|
}
|
|
71
139
|
}, [client, refKey]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (state.status !== "unavailable") return;
|
|
142
|
+
capture({
|
|
143
|
+
phase: "resolve",
|
|
144
|
+
mediaRef: failureMediaIdentity(ref ?? null),
|
|
145
|
+
terminal: true,
|
|
146
|
+
message: `resolve() refused (${state.reason ?? "unknown"}): ${failureMessage ?? "no error message"}`
|
|
147
|
+
});
|
|
148
|
+
}, [refKey, state.status]);
|
|
149
|
+
return state;
|
|
72
150
|
}
|
|
73
151
|
|
|
74
152
|
// src/core/use-media-load-recovery.ts
|
|
75
|
-
import { useEffect, useRef, useState } from "react";
|
|
153
|
+
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
76
154
|
|
|
77
155
|
// src/types.ts
|
|
78
156
|
function mintDurableSrc(url) {
|
|
@@ -80,26 +158,51 @@ function mintDurableSrc(url) {
|
|
|
80
158
|
}
|
|
81
159
|
|
|
82
160
|
// src/core/use-media-load-recovery.ts
|
|
161
|
+
function normalizeOutcome(outcome) {
|
|
162
|
+
return typeof outcome === "string" ? { verdict: outcome } : outcome;
|
|
163
|
+
}
|
|
164
|
+
var EPHEMERAL_SECRET_IDENTITY = "server:files-session-secret";
|
|
83
165
|
function useMediaLoadRecovery(src, options) {
|
|
84
166
|
const client = useMediaClient();
|
|
167
|
+
const capture = useMediaFailureCapture();
|
|
85
168
|
const recoverable = options?.recoverable !== false;
|
|
86
169
|
const onTerminal = options?.onTerminal;
|
|
170
|
+
const failureRef = options?.failureRef;
|
|
87
171
|
const [result, setResult] = useState({ source: src, retryKey: 0, failed: false });
|
|
88
|
-
const attemptRef =
|
|
89
|
-
|
|
172
|
+
const attemptRef = useRef2(0);
|
|
173
|
+
useEffect2(() => {
|
|
90
174
|
attemptRef.current = 0;
|
|
91
175
|
}, [src]);
|
|
176
|
+
const identity = () => failureMediaIdentity(failureRef ?? null, src);
|
|
92
177
|
const onLoadError = (event) => {
|
|
93
178
|
if (!src || !recoverable) {
|
|
94
179
|
setResult({ source: src, retryKey: 0, failed: true });
|
|
180
|
+
if (src) {
|
|
181
|
+
capture({
|
|
182
|
+
phase: "element-load",
|
|
183
|
+
mediaRef: identity(),
|
|
184
|
+
terminal: true,
|
|
185
|
+
retryOutcome: "not-attempted",
|
|
186
|
+
message: "media element failed to load and the source is unrecoverable (foreign/object URL \u2014 a session refresh cannot fix it)"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
95
189
|
onTerminal?.(event);
|
|
96
190
|
return;
|
|
97
191
|
}
|
|
98
192
|
attemptRef.current += 1;
|
|
99
193
|
const attempt = attemptRef.current;
|
|
100
194
|
client.recoverLoadError(mintDurableSrc(src), attempt).then(
|
|
101
|
-
(
|
|
102
|
-
|
|
195
|
+
(outcome) => {
|
|
196
|
+
const report = normalizeOutcome(outcome);
|
|
197
|
+
if (report.secretSource === "ephemeral") {
|
|
198
|
+
capture({
|
|
199
|
+
phase: "session",
|
|
200
|
+
mediaRef: EPHEMERAL_SECRET_IDENTITY,
|
|
201
|
+
terminal: false,
|
|
202
|
+
message: "server cookie secret is ephemeral \u2014 private renders will break across restarts/instances (set a persistent files-session secret on the server)"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (report.verdict === "retry") {
|
|
103
206
|
setResult((current) => ({
|
|
104
207
|
source: src,
|
|
105
208
|
retryKey: current.source === src ? current.retryKey + 1 : 1,
|
|
@@ -107,11 +210,26 @@ function useMediaLoadRecovery(src, options) {
|
|
|
107
210
|
}));
|
|
108
211
|
} else {
|
|
109
212
|
setResult({ source: src, retryKey: 0, failed: true });
|
|
213
|
+
capture({
|
|
214
|
+
phase: attempt > 1 ? "element-load" : "recovery",
|
|
215
|
+
mediaRef: identity(),
|
|
216
|
+
terminal: true,
|
|
217
|
+
retryOutcome: attempt > 1 ? "retried" : "not-attempted",
|
|
218
|
+
message: attempt > 1 ? "media element failed to load after the session-refresh retry" : "media load recovery declined on the first attempt (session refresh did not produce a usable session)",
|
|
219
|
+
...report.status !== void 0 ? { status: report.status } : {}
|
|
220
|
+
});
|
|
110
221
|
onTerminal?.(event);
|
|
111
222
|
}
|
|
112
223
|
},
|
|
113
|
-
() => {
|
|
224
|
+
(err) => {
|
|
114
225
|
setResult({ source: src, retryKey: 0, failed: true });
|
|
226
|
+
capture({
|
|
227
|
+
phase: "session",
|
|
228
|
+
mediaRef: identity(),
|
|
229
|
+
terminal: true,
|
|
230
|
+
retryOutcome: "recovery-failed",
|
|
231
|
+
message: `session recovery failed: ${err instanceof Error ? err.message : String(err ?? "unknown error")}`
|
|
232
|
+
});
|
|
115
233
|
onTerminal?.(event);
|
|
116
234
|
}
|
|
117
235
|
);
|
|
@@ -124,13 +242,21 @@ function useMediaLoadRecovery(src, options) {
|
|
|
124
242
|
}
|
|
125
243
|
|
|
126
244
|
// src/core/use-media-blob.ts
|
|
127
|
-
import { useEffect as
|
|
245
|
+
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
246
|
+
function statusOf(err) {
|
|
247
|
+
if (err && typeof err === "object") {
|
|
248
|
+
const candidate = err.status ?? err.statusCode;
|
|
249
|
+
if (typeof candidate === "number") return candidate;
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
128
253
|
function useMediaBlob(ref) {
|
|
129
254
|
const client = useMediaClient();
|
|
255
|
+
const capture = useMediaFailureCapture();
|
|
130
256
|
const refKey = stableRefKey(ref);
|
|
131
257
|
const [state, setState] = useState2({ url: null, blob: null, loading: !!ref, error: null });
|
|
132
258
|
const [retryToken, setRetryToken] = useState2(0);
|
|
133
|
-
|
|
259
|
+
useEffect3(() => {
|
|
134
260
|
if (!ref) {
|
|
135
261
|
setState({ url: null, blob: null, loading: false, error: null });
|
|
136
262
|
return void 0;
|
|
@@ -151,6 +277,14 @@ function useMediaBlob(ref) {
|
|
|
151
277
|
if (cancelled) return;
|
|
152
278
|
const message = err instanceof Error ? err.message : String(err ?? "Failed to load file");
|
|
153
279
|
setState({ url: null, blob: null, loading: false, error: message });
|
|
280
|
+
const status = statusOf(err);
|
|
281
|
+
capture({
|
|
282
|
+
phase: "blob",
|
|
283
|
+
mediaRef: failureMediaIdentity(ref),
|
|
284
|
+
terminal: true,
|
|
285
|
+
message: `getBlob() failed: ${message}`,
|
|
286
|
+
...status !== void 0 ? { status } : {}
|
|
287
|
+
});
|
|
154
288
|
}
|
|
155
289
|
);
|
|
156
290
|
return () => {
|
|
@@ -165,7 +299,7 @@ function useMediaBlob(ref) {
|
|
|
165
299
|
}
|
|
166
300
|
|
|
167
301
|
// src/core/use-media-upload.ts
|
|
168
|
-
import { useCallback, useRef as
|
|
302
|
+
import { useCallback as useCallback2, useRef as useRef3, useState as useState3 } from "react";
|
|
169
303
|
var entrySeq = 0;
|
|
170
304
|
function useMediaUpload() {
|
|
171
305
|
const client = useMediaClient();
|
|
@@ -174,8 +308,8 @@ function useMediaUpload() {
|
|
|
174
308
|
const [result, setResult] = useState3(null);
|
|
175
309
|
const [error, setError] = useState3(null);
|
|
176
310
|
const [entries, setEntries] = useState3([]);
|
|
177
|
-
const activeCount =
|
|
178
|
-
const patchEntry =
|
|
311
|
+
const activeCount = useRef3(0);
|
|
312
|
+
const patchEntry = useCallback2(
|
|
179
313
|
(id, patch) => {
|
|
180
314
|
setEntries(
|
|
181
315
|
(current) => current.map((e) => e.id === id ? { ...e, ...patch } : e)
|
|
@@ -183,7 +317,7 @@ function useMediaUpload() {
|
|
|
183
317
|
},
|
|
184
318
|
[]
|
|
185
319
|
);
|
|
186
|
-
const upload =
|
|
320
|
+
const upload = useCallback2(
|
|
187
321
|
async (file, opts = {}) => {
|
|
188
322
|
const id = `up-${++entrySeq}`;
|
|
189
323
|
const fileName = opts.fileName ?? (file instanceof File ? file.name : "file");
|
|
@@ -237,7 +371,7 @@ function useMediaUpload() {
|
|
|
237
371
|
},
|
|
238
372
|
[client, patchEntry]
|
|
239
373
|
);
|
|
240
|
-
const uploadMany =
|
|
374
|
+
const uploadMany = useCallback2(
|
|
241
375
|
async (files, opts = {}) => {
|
|
242
376
|
if (client.uploadMany) {
|
|
243
377
|
activeCount.current += 1;
|
|
@@ -266,10 +400,10 @@ function useMediaUpload() {
|
|
|
266
400
|
},
|
|
267
401
|
[client, upload]
|
|
268
402
|
);
|
|
269
|
-
const clearEntry =
|
|
403
|
+
const clearEntry = useCallback2((id) => {
|
|
270
404
|
setEntries((current) => current.filter((e) => e.id !== id));
|
|
271
405
|
}, []);
|
|
272
|
-
const reset =
|
|
406
|
+
const reset = useCallback2(() => {
|
|
273
407
|
setResult(null);
|
|
274
408
|
setError(null);
|
|
275
409
|
setProgress(null);
|
|
@@ -361,7 +495,7 @@ function useThumbnailSource(ref, options = {}) {
|
|
|
361
495
|
}
|
|
362
496
|
|
|
363
497
|
// src/core/use-media-actions.ts
|
|
364
|
-
import { useCallback as
|
|
498
|
+
import { useCallback as useCallback3, useMemo as useMemo3, useState as useState4 } from "react";
|
|
365
499
|
function useMediaActions(ref, options = {}) {
|
|
366
500
|
const ports = useMediaHostPorts();
|
|
367
501
|
const { resolution } = useMediaResolution(ref);
|
|
@@ -373,7 +507,7 @@ function useMediaActions(ref, options = {}) {
|
|
|
373
507
|
if (!ref) return null;
|
|
374
508
|
return { ref, resolution, fileName, alt };
|
|
375
509
|
}, [ref, resolution, fileName, alt]);
|
|
376
|
-
const isAvailable =
|
|
510
|
+
const isAvailable = useCallback3(
|
|
377
511
|
(kind) => {
|
|
378
512
|
if (!context || !actions) return false;
|
|
379
513
|
if (kind === "share") return Boolean(actions.share || actions.SharePopover);
|
|
@@ -381,7 +515,7 @@ function useMediaActions(ref, options = {}) {
|
|
|
381
515
|
},
|
|
382
516
|
[context, actions]
|
|
383
517
|
);
|
|
384
|
-
const run =
|
|
518
|
+
const run = useCallback3(
|
|
385
519
|
async (kind) => {
|
|
386
520
|
if (!context || !actions) return;
|
|
387
521
|
const handler = actions[kind];
|
|
@@ -544,12 +678,16 @@ function getFolderKindDetails(open = false) {
|
|
|
544
678
|
}
|
|
545
679
|
export {
|
|
546
680
|
MediaProvider,
|
|
681
|
+
emitMediaFailure,
|
|
682
|
+
failureMediaIdentity,
|
|
547
683
|
getFileKindDetails,
|
|
548
684
|
getFolderKindDetails,
|
|
685
|
+
resetMediaFailureLatch,
|
|
549
686
|
stableRefKey,
|
|
550
687
|
useMediaActions,
|
|
551
688
|
useMediaBlob,
|
|
552
689
|
useMediaClient,
|
|
690
|
+
useMediaFailureCapture,
|
|
553
691
|
useMediaHostPorts,
|
|
554
692
|
useMediaLoadRecovery,
|
|
555
693
|
useMediaResolution,
|