@ttt-productions/upload-ui 0.7.16 → 0.7.18
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/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/react/guard/guarded-link.d.ts +11 -0
- package/dist/react/guard/guarded-link.d.ts.map +1 -0
- package/dist/react/guard/guarded-link.js.map +1 -0
- package/dist/react/guard/index.d.ts +3 -3
- package/dist/react/guard/index.d.ts.map +1 -1
- package/dist/react/guard/index.js +3 -3
- package/dist/react/guard/index.js.map +1 -1
- package/dist/react/guard/local-upload-guard-provider.d.ts +31 -0
- package/dist/react/guard/local-upload-guard-provider.d.ts.map +1 -0
- package/dist/react/{local-upload-guard-provider.js → guard/local-upload-guard-provider.js} +3 -60
- package/dist/react/guard/local-upload-guard-provider.js.map +1 -0
- package/dist/react/guard/use-guarded-navigation.d.ts +3 -0
- package/dist/react/guard/use-guarded-navigation.d.ts.map +1 -0
- package/dist/react/guard/use-guarded-navigation.js +12 -0
- package/dist/react/guard/use-guarded-navigation.js.map +1 -0
- package/dist/react/tray/in-flight-uploads-provider.d.ts +141 -0
- package/dist/react/tray/in-flight-uploads-provider.d.ts.map +1 -0
- package/dist/react/{in-flight-uploads-provider.js → tray/in-flight-uploads-provider.js} +16 -136
- package/dist/react/tray/in-flight-uploads-provider.js.map +1 -0
- package/dist/react/tray/index.d.ts +4 -4
- package/dist/react/tray/index.d.ts.map +1 -1
- package/dist/react/tray/index.js +4 -4
- package/dist/react/tray/index.js.map +1 -1
- package/dist/react/tray/use-clear-upload-activity.d.ts +8 -0
- package/dist/react/tray/use-clear-upload-activity.d.ts.map +1 -0
- package/dist/react/tray/use-clear-upload-activity.js +13 -0
- package/dist/react/tray/use-clear-upload-activity.js.map +1 -0
- package/dist/react/{use-mark-upload-activity-seen.d.ts → tray/use-mark-upload-activity-seen.d.ts} +0 -4
- package/dist/react/tray/use-mark-upload-activity-seen.d.ts.map +1 -0
- package/dist/react/{use-mark-upload-activity-seen.js → tray/use-mark-upload-activity-seen.js} +0 -21
- package/dist/react/tray/use-mark-upload-activity-seen.js.map +1 -0
- package/dist/react/{use-upload-processing.d.ts → tray/use-upload-processing.d.ts} +0 -10
- package/dist/react/tray/use-upload-processing.d.ts.map +1 -0
- package/dist/react/{use-upload-processing.js → tray/use-upload-processing.js} +10 -20
- package/dist/react/tray/use-upload-processing.js.map +1 -0
- package/dist/react/upload/deferred-upload-form-shell.d.ts +28 -0
- package/dist/react/upload/deferred-upload-form-shell.d.ts.map +1 -0
- package/dist/react/{deferred-upload-form-shell.js → upload/deferred-upload-form-shell.js} +5 -2
- package/dist/react/upload/deferred-upload-form-shell.js.map +1 -0
- package/dist/react/upload/index.d.ts +2 -2
- package/dist/react/upload/index.d.ts.map +1 -1
- package/dist/react/upload/index.js +2 -2
- package/dist/react/upload/index.js.map +1 -1
- package/dist/react/upload/use-guarded-upload.d.ts +16 -0
- package/dist/react/upload/use-guarded-upload.d.ts.map +1 -0
- package/dist/react/{use-guarded-upload.js → upload/use-guarded-upload.js} +2 -15
- package/dist/react/upload/use-guarded-upload.js.map +1 -0
- package/dist/upload-display.d.ts +0 -14
- package/dist/upload-display.d.ts.map +1 -1
- package/dist/upload-display.js +11 -12
- package/dist/upload-display.js.map +1 -1
- package/package.json +5 -5
- package/dist/react/deferred-upload-form-shell.d.ts +0 -46
- package/dist/react/deferred-upload-form-shell.d.ts.map +0 -1
- package/dist/react/deferred-upload-form-shell.js.map +0 -1
- package/dist/react/guarded-link.d.ts +0 -38
- package/dist/react/guarded-link.d.ts.map +0 -1
- package/dist/react/guarded-link.js.map +0 -1
- package/dist/react/in-flight-uploads-provider.d.ts +0 -266
- package/dist/react/in-flight-uploads-provider.d.ts.map +0 -1
- package/dist/react/in-flight-uploads-provider.js.map +0 -1
- package/dist/react/local-upload-guard-provider.d.ts +0 -112
- package/dist/react/local-upload-guard-provider.d.ts.map +0 -1
- package/dist/react/local-upload-guard-provider.js.map +0 -1
- package/dist/react/use-clear-upload-activity.d.ts +0 -33
- package/dist/react/use-clear-upload-activity.d.ts.map +0 -1
- package/dist/react/use-clear-upload-activity.js +0 -31
- package/dist/react/use-clear-upload-activity.js.map +0 -1
- package/dist/react/use-guarded-navigation.d.ts +0 -10
- package/dist/react/use-guarded-navigation.d.ts.map +0 -1
- package/dist/react/use-guarded-navigation.js +0 -40
- package/dist/react/use-guarded-navigation.js.map +0 -1
- package/dist/react/use-guarded-upload.d.ts +0 -45
- package/dist/react/use-guarded-upload.d.ts.map +0 -1
- package/dist/react/use-guarded-upload.js.map +0 -1
- package/dist/react/use-mark-upload-activity-seen.d.ts.map +0 -1
- package/dist/react/use-mark-upload-activity-seen.js.map +0 -1
- package/dist/react/use-upload-processing.d.ts.map +0 -1
- package/dist/react/use-upload-processing.js.map +0 -1
- /package/dist/react/{guarded-link.js → guard/guarded-link.js} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { formatFileSize, createUploadDisplayFormatters } from
|
|
2
|
-
export type { UploadDisplayCopy, UploadDisplayConfig, UploadDisplayFormatters } from
|
|
1
|
+
export { formatFileSize, createUploadDisplayFormatters } from './upload-display.js';
|
|
2
|
+
export type { UploadDisplayCopy, UploadDisplayConfig, UploadDisplayFormatters, } from './upload-display.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
// `/react/upload`, and `/react/tray`.
|
|
3
|
-
export { formatFileSize, createUploadDisplayFormatters } from "./upload-display.js";
|
|
1
|
+
export { formatFileSize, createUploadDisplayFormatters } from './upload-display.js';
|
|
4
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { formatFileSize, createUploadDisplayFormatters } from './upload-display.js';\nexport type {\n UploadDisplayCopy,\n UploadDisplayConfig,\n UploadDisplayFormatters,\n} from './upload-display.js';\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MouseEvent, ReactNode } from 'react';
|
|
2
|
+
export interface GuardedLinkRenderArgs {
|
|
3
|
+
onClick: (event: MouseEvent<HTMLElement>) => void;
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export interface GuardedLinkProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
renderLink: (args: GuardedLinkRenderArgs) => ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function GuardedLink(props: GuardedLinkProps): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=guarded-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guarded-link.d.ts","sourceRoot":"","sources":["../../../src/react/guard/guarded-link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGnD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAClD,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,SAAS,CAAC;CACxD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAWlD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guarded-link.js","sourceRoot":"","sources":["../../../src/react/guard/guarded-link.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAYvE,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IACvC,MAAM,EAAE,iBAAiB,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAEpD,MAAM,OAAO,GAAG,CAAC,KAA8B,EAAE,EAAE;QACjD,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;YACzB,KAAK,CAAC,cAAc,EAAE,CAAC;QACzB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,4BAAG,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAI,CAAC;AAClD,CAAC","sourcesContent":["'use client';\n\nimport type { MouseEvent, ReactNode } from 'react';\nimport { useLocalUploadGuard } from './local-upload-guard-provider.js';\n\nexport interface GuardedLinkRenderArgs {\n onClick: (event: MouseEvent<HTMLElement>) => void;\n children: ReactNode;\n}\n\nexport interface GuardedLinkProps {\n children: ReactNode;\n renderLink: (args: GuardedLinkRenderArgs) => ReactNode;\n}\n\nexport function GuardedLink(props: GuardedLinkProps) {\n const { children, renderLink } = props;\n const { confirmNavigation } = useLocalUploadGuard();\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n if (!confirmNavigation()) {\n event.preventDefault();\n }\n };\n\n return <>{renderLink({ onClick, children })}</>;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { LocalUploadGuardProvider, useLocalUploadGuard, useLocalUploadGuardContext, useOptionalLocalUploadGuard, useUnsavedWorkGuard, type ConfirmNavigationOptions, type LocalUploadGuardProviderProps, } from '
|
|
2
|
-
export { useGuardedNavigation } from '
|
|
3
|
-
export { GuardedLink, type GuardedLinkProps, type GuardedLinkRenderArgs
|
|
1
|
+
export { LocalUploadGuardProvider, useLocalUploadGuard, useLocalUploadGuardContext, useOptionalLocalUploadGuard, useUnsavedWorkGuard, type ConfirmNavigationOptions, type LocalUploadGuardProviderProps, } from './local-upload-guard-provider.js';
|
|
2
|
+
export { useGuardedNavigation } from './use-guarded-navigation.js';
|
|
3
|
+
export { GuardedLink, type GuardedLinkProps, type GuardedLinkRenderArgs } from './guarded-link.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/guard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,GACnC,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/guard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,GACnC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { LocalUploadGuardProvider, useLocalUploadGuard, useLocalUploadGuardContext, useOptionalLocalUploadGuard, useUnsavedWorkGuard, } from '
|
|
2
|
-
export { useGuardedNavigation } from '
|
|
3
|
-
export { GuardedLink
|
|
1
|
+
export { LocalUploadGuardProvider, useLocalUploadGuard, useLocalUploadGuardContext, useOptionalLocalUploadGuard, useUnsavedWorkGuard, } from './local-upload-guard-provider.js';
|
|
2
|
+
export { useGuardedNavigation } from './use-guarded-navigation.js';
|
|
3
|
+
export { GuardedLink } from './guarded-link.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/guard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,GAGpB,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/guard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,GAGpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,WAAW,EAAqD,MAAM,mBAAmB,CAAC","sourcesContent":["export {\n LocalUploadGuardProvider,\n useLocalUploadGuard,\n useLocalUploadGuardContext,\n useOptionalLocalUploadGuard,\n useUnsavedWorkGuard,\n type ConfirmNavigationOptions,\n type LocalUploadGuardProviderProps,\n} from './local-upload-guard-provider.js';\nexport { useGuardedNavigation } from './use-guarded-navigation.js';\nexport { GuardedLink, type GuardedLinkProps, type GuardedLinkRenderArgs } from './guarded-link.js';\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface ConfirmNavigationOptions {
|
|
3
|
+
fullPageNavigation?: boolean;
|
|
4
|
+
}
|
|
5
|
+
interface LocalUploadGuardContextValue {
|
|
6
|
+
activeUploadCount: number;
|
|
7
|
+
unsavedWorkCount: number;
|
|
8
|
+
guardedWorkCount: number;
|
|
9
|
+
registerUpload: (id: string) => void;
|
|
10
|
+
unregisterUpload: (id: string) => void;
|
|
11
|
+
registerUnsavedWork: (id: string) => void;
|
|
12
|
+
unregisterUnsavedWork: (id: string) => void;
|
|
13
|
+
shouldConfirmNavigation: () => boolean;
|
|
14
|
+
confirmNavigation: (options?: ConfirmNavigationOptions) => boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface LocalUploadGuardProviderProps {
|
|
17
|
+
beforeUnloadMessage?: string;
|
|
18
|
+
navigationConfirmMessage?: string;
|
|
19
|
+
unsavedWorkConfirmMessage?: string;
|
|
20
|
+
uploadAndUnsavedWorkConfirmMessage?: string;
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
export declare function LocalUploadGuardProvider(props: LocalUploadGuardProviderProps): import("react").JSX.Element;
|
|
24
|
+
export declare function useLocalUploadGuardContext(): LocalUploadGuardContextValue;
|
|
25
|
+
export declare function useOptionalLocalUploadGuard(): LocalUploadGuardContextValue | null;
|
|
26
|
+
export declare function useLocalUploadGuard(): LocalUploadGuardContextValue;
|
|
27
|
+
export declare function useUnsavedWorkGuard(hasUnsavedWork: boolean): {
|
|
28
|
+
release: () => void;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=local-upload-guard-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-upload-guard-provider.d.ts","sourceRoot":"","sources":["../../../src/react/guard/local-upload-guard-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EASL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,UAAU,4BAA4B;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mBAAmB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,qBAAqB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,uBAAuB,EAAE,MAAM,OAAO,CAAC;IACvC,iBAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,wBAAwB,KAAK,OAAO,CAAC;CACpE;AAID,MAAM,WAAW,6BAA6B;IAC5C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,QAAQ,EAAE,SAAS,CAAC;CACrB;AAoCD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,+BA6G5E;AAED,wBAAgB,0BAA0B,IAAI,4BAA4B,CAMzE;AAED,wBAAgB,2BAA2B,IAAI,4BAA4B,GAAG,IAAI,CAEjF;AAED,wBAAgB,mBAAmB,IAAI,4BAA4B,CAElE;AAED,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAUpF"}
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* The navigation and leave guard: warns before the user leaves while work that leaving would lose
|
|
5
|
-
* is registered — a LOCAL byte upload in flight, or an editor's unsaved input.
|
|
6
|
-
*
|
|
7
|
-
* Uploads: phases `preparing` and `uploading` only. `finalizing` and backend `pendingMedia`
|
|
8
|
-
* processing are safe to leave (the bytes are already in Storage), so they are not registered.
|
|
9
|
-
* Unsaved work: an editor registers while it holds input its user has not saved, and unregisters
|
|
10
|
-
* when the input is saved, discarded, or the editor unmounts.
|
|
11
|
-
*
|
|
12
|
-
* While anything is registered the provider installs ONE `beforeunload` listener (tab close,
|
|
13
|
-
* reload, full-page navigation), and `confirmNavigation()` asks before an in-app navigation. It is
|
|
14
|
-
* the one leave guard: an editor never installs its own `beforeunload` handler (FRONTEND-207).
|
|
15
|
-
*
|
|
16
|
-
* Consumers register with a stable id per piece of work; registering a registered id, or
|
|
17
|
-
* unregistering an absent one, is a no-op. Uploads and unsaved work are counted separately, so the
|
|
18
|
-
* same id may be both.
|
|
19
|
-
*/
|
|
20
3
|
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, } from 'react';
|
|
21
4
|
const LocalUploadGuardContext = createContext(undefined);
|
|
22
5
|
const DEFAULT_NAVIGATION_CONFIRM_MESSAGE = 'An upload is currently in progress. If you leave this page, the upload will be canceled. Continue?';
|
|
@@ -44,9 +27,7 @@ function useRegistry(onRegister) {
|
|
|
44
27
|
}
|
|
45
28
|
export function LocalUploadGuardProvider(props) {
|
|
46
29
|
const { children, beforeUnloadMessage, navigationConfirmMessage = DEFAULT_NAVIGATION_CONFIRM_MESSAGE, unsavedWorkConfirmMessage = DEFAULT_UNSAVED_WORK_CONFIRM_MESSAGE, uploadAndUnsavedWorkConfirmMessage = DEFAULT_UPLOAD_AND_UNSAVED_WORK_CONFIRM_MESSAGE, } = props;
|
|
47
|
-
// One-shot: the user already confirmed the full-page navigation now unloading.
|
|
48
30
|
const bypassNextUnloadRef = useRef(false);
|
|
49
|
-
// Also run when work registers: work that starts after a confirmation was not covered by it.
|
|
50
31
|
const clearBypass = useCallback(() => {
|
|
51
32
|
bypassNextUnloadRef.current = false;
|
|
52
33
|
}, []);
|
|
@@ -56,7 +37,6 @@ export function LocalUploadGuardProvider(props) {
|
|
|
56
37
|
const unsavedWorkIdsRef = unsavedWork.idsRef;
|
|
57
38
|
const guardedWorkCount = uploads.count + unsavedWork.count;
|
|
58
39
|
const hasGuardedWork = guardedWorkCount > 0;
|
|
59
|
-
// Read at the moment of asking, so a message always matches what is registered right then.
|
|
60
40
|
const confirmMessage = useCallback(() => {
|
|
61
41
|
const uploading = uploadIdsRef.current.size > 0;
|
|
62
42
|
const unsaved = unsavedWorkIdsRef.current.size > 0;
|
|
@@ -79,8 +59,7 @@ export function LocalUploadGuardProvider(props) {
|
|
|
79
59
|
return;
|
|
80
60
|
}
|
|
81
61
|
event.preventDefault();
|
|
82
|
-
// Modern browsers may ignore the custom string and show their own generic confirmation
|
|
83
|
-
// what matters is that leaving is warned, not the exact text.
|
|
62
|
+
// Modern browsers may ignore the custom string and show their own generic confirmation.
|
|
84
63
|
event.returnValue =
|
|
85
64
|
beforeUnloadMessage ??
|
|
86
65
|
(uploadIdsRef.current.size > 0
|
|
@@ -93,8 +72,8 @@ export function LocalUploadGuardProvider(props) {
|
|
|
93
72
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
94
73
|
};
|
|
95
74
|
}, [hasGuardedWork, beforeUnloadMessage, uploadIdsRef]);
|
|
96
|
-
// A back/forward-cache return re-shows this page without reloading it
|
|
97
|
-
//
|
|
75
|
+
// A back/forward-cache return re-shows this page without reloading it, so an armed bypass must not
|
|
76
|
+
// outlive the navigation that left.
|
|
98
77
|
useEffect(() => {
|
|
99
78
|
window.addEventListener('pageshow', clearBypass);
|
|
100
79
|
return () => {
|
|
@@ -103,7 +82,6 @@ export function LocalUploadGuardProvider(props) {
|
|
|
103
82
|
}, [clearBypass]);
|
|
104
83
|
const shouldConfirmNavigation = useCallback(() => uploadIdsRef.current.size > 0 || unsavedWorkIdsRef.current.size > 0, [uploadIdsRef, unsavedWorkIdsRef]);
|
|
105
84
|
const confirmNavigation = useCallback((options) => {
|
|
106
|
-
// SSR / non-browser safety: if `window` isn't available, default to allow.
|
|
107
85
|
if (typeof window === 'undefined')
|
|
108
86
|
return true;
|
|
109
87
|
const proceed = !shouldConfirmNavigation() || window.confirm(confirmMessage());
|
|
@@ -134,11 +112,6 @@ export function LocalUploadGuardProvider(props) {
|
|
|
134
112
|
]);
|
|
135
113
|
return (_jsx(LocalUploadGuardContext.Provider, { value: value, children: children }));
|
|
136
114
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Low-level accessor for the guard context. Throws if used outside a
|
|
139
|
-
* `LocalUploadGuardProvider`. Most consumers should use `useLocalUploadGuard`
|
|
140
|
-
* (the public re-export below) instead.
|
|
141
|
-
*/
|
|
142
115
|
export function useLocalUploadGuardContext() {
|
|
143
116
|
const ctx = useContext(LocalUploadGuardContext);
|
|
144
117
|
if (ctx === undefined) {
|
|
@@ -146,42 +119,12 @@ export function useLocalUploadGuardContext() {
|
|
|
146
119
|
}
|
|
147
120
|
return ctx;
|
|
148
121
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Optional accessor: returns the guard context, or `null` when rendered
|
|
151
|
-
* outside a `LocalUploadGuardProvider`. For package components (e.g. the
|
|
152
|
-
* chat composer registering an in-flight send) that must degrade gracefully
|
|
153
|
-
* in consumers that don't mount the provider, instead of throwing.
|
|
154
|
-
*/
|
|
155
122
|
export function useOptionalLocalUploadGuard() {
|
|
156
123
|
return useContext(LocalUploadGuardContext) ?? null;
|
|
157
124
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Public hook for the navigation and leave guard.
|
|
160
|
-
*
|
|
161
|
-
* Typical usage in an upload flow:
|
|
162
|
-
*
|
|
163
|
-
* const { registerUpload, unregisterUpload } = useLocalUploadGuard();
|
|
164
|
-
* useEffect(() => {
|
|
165
|
-
* if (phase === 'preparing' || phase === 'uploading') {
|
|
166
|
-
* registerUpload(uploadId);
|
|
167
|
-
* return () => unregisterUpload(uploadId);
|
|
168
|
-
* }
|
|
169
|
-
* }, [phase, uploadId, registerUpload, unregisterUpload]);
|
|
170
|
-
*
|
|
171
|
-
* An editor holding input uses `useUnsavedWorkGuard(hasUnsavedWork)` instead.
|
|
172
|
-
*/
|
|
173
125
|
export function useLocalUploadGuard() {
|
|
174
126
|
return useLocalUploadGuardContext();
|
|
175
127
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Registers the calling editor's unsaved input with the leave guard while `hasUnsavedWork` is
|
|
178
|
-
* true, and unregisters it when that turns false or the editor unmounts. While registered, closing
|
|
179
|
-
* the tab, reloading, a full-page navigation, and every guarded in-app navigation ask first.
|
|
180
|
-
* Throws outside a `LocalUploadGuardProvider`.
|
|
181
|
-
*
|
|
182
|
-
* Returns `release`, which unregisters at once: an editor that saves and then navigates in the
|
|
183
|
-
* same handler calls it after the save, because its dirty flag clears only on the next render.
|
|
184
|
-
*/
|
|
185
128
|
export function useUnsavedWorkGuard(hasUnsavedWork) {
|
|
186
129
|
const { registerUnsavedWork, unregisterUnsavedWork } = useLocalUploadGuardContext();
|
|
187
130
|
const id = useId();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-upload-guard-provider.js","sourceRoot":"","sources":["../../../src/react/guard/local-upload-guard-provider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nexport interface ConfirmNavigationOptions {\n fullPageNavigation?: boolean;\n}\n\ninterface LocalUploadGuardContextValue {\n activeUploadCount: number;\n unsavedWorkCount: number;\n guardedWorkCount: number;\n registerUpload: (id: string) => void;\n unregisterUpload: (id: string) => void;\n registerUnsavedWork: (id: string) => void;\n unregisterUnsavedWork: (id: string) => void;\n shouldConfirmNavigation: () => boolean;\n confirmNavigation: (options?: ConfirmNavigationOptions) => boolean;\n}\n\nconst LocalUploadGuardContext = createContext<LocalUploadGuardContextValue | undefined>(undefined);\n\nexport interface LocalUploadGuardProviderProps {\n beforeUnloadMessage?: string;\n navigationConfirmMessage?: string;\n unsavedWorkConfirmMessage?: string;\n uploadAndUnsavedWorkConfirmMessage?: string;\n children: ReactNode;\n}\n\nconst DEFAULT_NAVIGATION_CONFIRM_MESSAGE =\n 'An upload is currently in progress. If you leave this page, the upload will be canceled. Continue?';\nconst DEFAULT_UNSAVED_WORK_CONFIRM_MESSAGE =\n 'You have unsaved changes. If you leave this page, they will be lost. Continue?';\nconst DEFAULT_UPLOAD_AND_UNSAVED_WORK_CONFIRM_MESSAGE =\n 'An upload is in progress and you have unsaved changes. If you leave this page, the upload will be canceled and your changes will be lost. Continue?';\nconst DEFAULT_UPLOAD_BEFORE_UNLOAD_MESSAGE =\n 'File upload in progress. Are you sure you want to leave and end the file upload?';\nconst DEFAULT_UNSAVED_WORK_BEFORE_UNLOAD_MESSAGE =\n 'You have unsaved changes. Are you sure you want to leave and lose them?';\n\nfunction useRegistry(onRegister: () => void) {\n const idsRef = useRef<Set<string>>(new Set());\n const [count, setCount] = useState(0);\n\n const register = useCallback(\n (id: string) => {\n if (idsRef.current.has(id)) return;\n onRegister();\n idsRef.current.add(id);\n setCount(idsRef.current.size);\n },\n [onRegister],\n );\n\n const unregister = useCallback((id: string) => {\n if (!idsRef.current.has(id)) return;\n idsRef.current.delete(id);\n setCount(idsRef.current.size);\n }, []);\n\n return { idsRef, count, register, unregister };\n}\n\nexport function LocalUploadGuardProvider(props: LocalUploadGuardProviderProps) {\n const {\n children,\n beforeUnloadMessage,\n navigationConfirmMessage = DEFAULT_NAVIGATION_CONFIRM_MESSAGE,\n unsavedWorkConfirmMessage = DEFAULT_UNSAVED_WORK_CONFIRM_MESSAGE,\n uploadAndUnsavedWorkConfirmMessage = DEFAULT_UPLOAD_AND_UNSAVED_WORK_CONFIRM_MESSAGE,\n } = props;\n const bypassNextUnloadRef = useRef(false);\n const clearBypass = useCallback(() => {\n bypassNextUnloadRef.current = false;\n }, []);\n\n const uploads = useRegistry(clearBypass);\n const unsavedWork = useRegistry(clearBypass);\n const uploadIdsRef = uploads.idsRef;\n const unsavedWorkIdsRef = unsavedWork.idsRef;\n const guardedWorkCount = uploads.count + unsavedWork.count;\n const hasGuardedWork = guardedWorkCount > 0;\n\n const confirmMessage = useCallback((): string => {\n const uploading = uploadIdsRef.current.size > 0;\n const unsaved = unsavedWorkIdsRef.current.size > 0;\n if (uploading && unsaved) return uploadAndUnsavedWorkConfirmMessage;\n return uploading ? navigationConfirmMessage : unsavedWorkConfirmMessage;\n }, [\n uploadIdsRef,\n unsavedWorkIdsRef,\n navigationConfirmMessage,\n unsavedWorkConfirmMessage,\n uploadAndUnsavedWorkConfirmMessage,\n ]);\n\n useEffect(() => {\n if (!hasGuardedWork) return;\n\n const handleBeforeUnload = (event: BeforeUnloadEvent) => {\n if (bypassNextUnloadRef.current) {\n bypassNextUnloadRef.current = false;\n return;\n }\n event.preventDefault();\n // Modern browsers may ignore the custom string and show their own generic confirmation.\n event.returnValue =\n beforeUnloadMessage ??\n (uploadIdsRef.current.size > 0\n ? DEFAULT_UPLOAD_BEFORE_UNLOAD_MESSAGE\n : DEFAULT_UNSAVED_WORK_BEFORE_UNLOAD_MESSAGE);\n return event.returnValue;\n };\n\n window.addEventListener('beforeunload', handleBeforeUnload);\n return () => {\n window.removeEventListener('beforeunload', handleBeforeUnload);\n };\n }, [hasGuardedWork, beforeUnloadMessage, uploadIdsRef]);\n\n // A back/forward-cache return re-shows this page without reloading it, so an armed bypass must not\n // outlive the navigation that left.\n useEffect(() => {\n window.addEventListener('pageshow', clearBypass);\n return () => {\n window.removeEventListener('pageshow', clearBypass);\n };\n }, [clearBypass]);\n\n const shouldConfirmNavigation = useCallback(\n () => uploadIdsRef.current.size > 0 || unsavedWorkIdsRef.current.size > 0,\n [uploadIdsRef, unsavedWorkIdsRef],\n );\n\n const confirmNavigation = useCallback(\n (options?: ConfirmNavigationOptions): boolean => {\n if (typeof window === 'undefined') return true;\n const proceed = !shouldConfirmNavigation() || window.confirm(confirmMessage());\n if (proceed && options?.fullPageNavigation) bypassNextUnloadRef.current = true;\n return proceed;\n },\n [shouldConfirmNavigation, confirmMessage],\n );\n\n const value = useMemo<LocalUploadGuardContextValue>(\n () => ({\n activeUploadCount: uploads.count,\n unsavedWorkCount: unsavedWork.count,\n guardedWorkCount,\n registerUpload: uploads.register,\n unregisterUpload: uploads.unregister,\n registerUnsavedWork: unsavedWork.register,\n unregisterUnsavedWork: unsavedWork.unregister,\n shouldConfirmNavigation,\n confirmNavigation,\n }),\n [\n uploads.count,\n uploads.register,\n uploads.unregister,\n unsavedWork.count,\n unsavedWork.register,\n unsavedWork.unregister,\n guardedWorkCount,\n shouldConfirmNavigation,\n confirmNavigation,\n ],\n );\n\n return (\n <LocalUploadGuardContext.Provider value={value}>{children}</LocalUploadGuardContext.Provider>\n );\n}\n\nexport function useLocalUploadGuardContext(): LocalUploadGuardContextValue {\n const ctx = useContext(LocalUploadGuardContext);\n if (ctx === undefined) {\n throw new Error('useLocalUploadGuard must be used within LocalUploadGuardProvider');\n }\n return ctx;\n}\n\nexport function useOptionalLocalUploadGuard(): LocalUploadGuardContextValue | null {\n return useContext(LocalUploadGuardContext) ?? null;\n}\n\nexport function useLocalUploadGuard(): LocalUploadGuardContextValue {\n return useLocalUploadGuardContext();\n}\n\nexport function useUnsavedWorkGuard(hasUnsavedWork: boolean): { release: () => void } {\n const { registerUnsavedWork, unregisterUnsavedWork } = useLocalUploadGuardContext();\n const id = useId();\n useEffect(() => {\n if (!hasUnsavedWork) return;\n registerUnsavedWork(id);\n return () => unregisterUnsavedWork(id);\n }, [hasUnsavedWork, id, registerUnsavedWork, unregisterUnsavedWork]);\n const release = useCallback(() => unregisterUnsavedWork(id), [id, unregisterUnsavedWork]);\n return useMemo(() => ({ release }), [release]);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-guarded-navigation.d.ts","sourceRoot":"","sources":["../../../src/react/guard/use-guarded-navigation.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,kCAAkC,CAAC;AAE1C,wBAAgB,oBAAoB,IAAI,CACtC,iBAAiB,EAAE,MAAM,IAAI,EAC7B,OAAO,CAAC,EAAE,wBAAwB,KAC/B,IAAI,CAUR"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { useLocalUploadGuard, } from './local-upload-guard-provider.js';
|
|
4
|
+
export function useGuardedNavigation() {
|
|
5
|
+
const { confirmNavigation } = useLocalUploadGuard();
|
|
6
|
+
return useCallback((performNavigation, options) => {
|
|
7
|
+
if (confirmNavigation(options)) {
|
|
8
|
+
performNavigation();
|
|
9
|
+
}
|
|
10
|
+
}, [confirmNavigation]);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=use-guarded-navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-guarded-navigation.js","sourceRoot":"","sources":["../../../src/react/guard/use-guarded-navigation.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EACL,mBAAmB,GAEpB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,UAAU,oBAAoB;IAIlC,MAAM,EAAE,iBAAiB,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACpD,OAAO,WAAW,CAChB,CAAC,iBAA6B,EAAE,OAAkC,EAAE,EAAE;QACpE,IAAI,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,iBAAiB,EAAE,CAAC;QACtB,CAAC;IACH,CAAC,EACD,CAAC,iBAAiB,CAAC,CACpB,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { useCallback } from 'react';\nimport {\n useLocalUploadGuard,\n type ConfirmNavigationOptions,\n} from './local-upload-guard-provider.js';\n\nexport function useGuardedNavigation(): (\n performNavigation: () => void,\n options?: ConfirmNavigationOptions,\n) => void {\n const { confirmNavigation } = useLocalUploadGuard();\n return useCallback(\n (performNavigation: () => void, options?: ConfirmNavigationOptions) => {\n if (confirmNavigation(options)) {\n performNavigation();\n }\n },\n [confirmNavigation],\n );\n}\n"]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { PendingMediaStatusSchema, type PendingMediaNonTerminalStatus, type PendingMediaStatus } from '@ttt-productions/media-schemas';
|
|
3
|
+
export type InFlightUploadStatus = PendingMediaStatus;
|
|
4
|
+
export type InFlightUploadBase<TFileOrigin extends string = string> = {
|
|
5
|
+
id: string;
|
|
6
|
+
fileOrigin: TFileOrigin;
|
|
7
|
+
surface: string;
|
|
8
|
+
createdAt: number;
|
|
9
|
+
targetIds?: string[];
|
|
10
|
+
originalContentType?: string;
|
|
11
|
+
originalSize?: number;
|
|
12
|
+
};
|
|
13
|
+
export type InFlightUploadActive<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
14
|
+
status: PendingMediaNonTerminalStatus;
|
|
15
|
+
};
|
|
16
|
+
export type InFlightUploadCompleted<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
17
|
+
status: typeof PendingMediaStatusSchema.enum.completed;
|
|
18
|
+
completedAt: number;
|
|
19
|
+
uploadTrayClearedAt?: number;
|
|
20
|
+
uploadTrayClearedBy?: string;
|
|
21
|
+
uploadTraySeenAt?: number;
|
|
22
|
+
uploadTraySeenBy?: string;
|
|
23
|
+
};
|
|
24
|
+
export type InFlightUploadFailed<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
25
|
+
status: typeof PendingMediaStatusSchema.enum.failed;
|
|
26
|
+
failedAt: number;
|
|
27
|
+
errorCategory: string;
|
|
28
|
+
errorMessage: string;
|
|
29
|
+
uploadTrayClearedAt?: number;
|
|
30
|
+
uploadTrayClearedBy?: string;
|
|
31
|
+
uploadTraySeenAt?: number;
|
|
32
|
+
uploadTraySeenBy?: string;
|
|
33
|
+
};
|
|
34
|
+
export type InFlightUploadRejected<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
35
|
+
status: typeof PendingMediaStatusSchema.enum.rejected;
|
|
36
|
+
rejectedAt: number;
|
|
37
|
+
rejectionType: 'text' | 'media';
|
|
38
|
+
errorMessage: string;
|
|
39
|
+
violationId?: string;
|
|
40
|
+
uploadTrayClearedAt?: number;
|
|
41
|
+
uploadTrayClearedBy?: string;
|
|
42
|
+
uploadTraySeenAt?: number;
|
|
43
|
+
uploadTraySeenBy?: string;
|
|
44
|
+
};
|
|
45
|
+
export type InFlightUpload<TFileOrigin extends string = string> = InFlightUploadActive<TFileOrigin> | InFlightUploadCompleted<TFileOrigin> | InFlightUploadFailed<TFileOrigin> | InFlightUploadRejected<TFileOrigin>;
|
|
46
|
+
export type ParsedPendingMedia<TFileOrigin extends string = string> = {
|
|
47
|
+
id: string;
|
|
48
|
+
userId: string;
|
|
49
|
+
fileOrigin: TFileOrigin;
|
|
50
|
+
status: InFlightUploadStatus;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
surface: string;
|
|
53
|
+
targetIds?: string[];
|
|
54
|
+
originalContentType?: string;
|
|
55
|
+
originalSize?: number;
|
|
56
|
+
completedAt?: number;
|
|
57
|
+
failedAt?: number;
|
|
58
|
+
rejectedAt?: number;
|
|
59
|
+
errorCategory?: string;
|
|
60
|
+
errorMessage?: string;
|
|
61
|
+
rejectionType?: 'text' | 'media';
|
|
62
|
+
violationId?: string;
|
|
63
|
+
uploadTrayClearedAt?: number;
|
|
64
|
+
uploadTrayClearedBy?: string;
|
|
65
|
+
uploadTraySeenAt?: number;
|
|
66
|
+
uploadTraySeenBy?: string;
|
|
67
|
+
domainEvents?: unknown;
|
|
68
|
+
};
|
|
69
|
+
export type InFlightUploadsAdapter<TFileOrigin extends string = string> = {
|
|
70
|
+
getCurrentUserId: () => string | null;
|
|
71
|
+
db: unknown;
|
|
72
|
+
collectionPath: string;
|
|
73
|
+
listenerWindowMs: number;
|
|
74
|
+
parsePendingMedia: (raw: unknown) => ParsedPendingMedia<TFileOrigin>;
|
|
75
|
+
onUploadCompleted: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
76
|
+
onUploadFailed: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
77
|
+
onUploadRejected: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
78
|
+
onMonitoringEvent: (event: InFlightUploadsMonitoringEvent<TFileOrigin>) => void;
|
|
79
|
+
};
|
|
80
|
+
export type InFlightUploadsMonitoringEvent<TFileOrigin extends string = string> = {
|
|
81
|
+
type: 'breadcrumb';
|
|
82
|
+
category: 'upload.terminal';
|
|
83
|
+
level: 'info';
|
|
84
|
+
message: string;
|
|
85
|
+
data: {
|
|
86
|
+
id: string;
|
|
87
|
+
fileOrigin: TFileOrigin;
|
|
88
|
+
status: InFlightUploadStatus;
|
|
89
|
+
surface: string;
|
|
90
|
+
};
|
|
91
|
+
} | {
|
|
92
|
+
type: 'parse-error';
|
|
93
|
+
docId: string;
|
|
94
|
+
error: unknown;
|
|
95
|
+
data: unknown;
|
|
96
|
+
} | {
|
|
97
|
+
type: 'listener-error';
|
|
98
|
+
error: unknown;
|
|
99
|
+
};
|
|
100
|
+
export type FirestoreSubscribeFn = (args: {
|
|
101
|
+
db: unknown;
|
|
102
|
+
collectionPath: string;
|
|
103
|
+
userId: string;
|
|
104
|
+
windowStartMs: number;
|
|
105
|
+
onSnapshot: (snapshot: FirestoreLikeSnapshot) => void;
|
|
106
|
+
onError: (error: unknown) => void;
|
|
107
|
+
}) => () => void;
|
|
108
|
+
export type FirestoreLikeSnapshot = {
|
|
109
|
+
docChanges: () => FirestoreLikeDocChange[];
|
|
110
|
+
metadata?: {
|
|
111
|
+
fromCache: boolean;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
/** Firestore serves cached snapshots offline without firing the error callback, so liveness comes from metadata. */
|
|
115
|
+
export type UploadsSourceState = 'connecting' | 'live' | 'offline' | 'error';
|
|
116
|
+
export type FirestoreLikeDocChange = {
|
|
117
|
+
type: 'added' | 'modified' | 'removed';
|
|
118
|
+
doc: {
|
|
119
|
+
id: string;
|
|
120
|
+
data: () => unknown;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
export interface UploadsSourceRetry {
|
|
124
|
+
retry: () => void;
|
|
125
|
+
retrying: boolean;
|
|
126
|
+
}
|
|
127
|
+
export interface InFlightUploadsProviderProps<TFileOrigin extends string = string> {
|
|
128
|
+
adapter: InFlightUploadsAdapter<TFileOrigin>;
|
|
129
|
+
subscribe?: FirestoreSubscribeFn;
|
|
130
|
+
children: ReactNode;
|
|
131
|
+
}
|
|
132
|
+
export declare function InFlightUploadsProvider<TFileOrigin extends string = string>(props: InFlightUploadsProviderProps<TFileOrigin>): import("react").JSX.Element;
|
|
133
|
+
export declare function useInFlightUploadsState<TFileOrigin extends string = string>(): Map<string, InFlightUpload<TFileOrigin>>;
|
|
134
|
+
export declare function useInFlightUpload<TFileOrigin extends string = string>(id: string | null | undefined): InFlightUpload<TFileOrigin> | undefined;
|
|
135
|
+
export declare function useUploadsSourceState(): UploadsSourceState;
|
|
136
|
+
export declare function useUploadsSourceRetry(): UploadsSourceRetry;
|
|
137
|
+
export declare function isTerminalUpload<TFileOrigin extends string = string>(item: InFlightUpload<TFileOrigin>): item is Exclude<InFlightUpload<TFileOrigin>, {
|
|
138
|
+
status: PendingMediaNonTerminalStatus;
|
|
139
|
+
}>;
|
|
140
|
+
export declare function useUploadActivityState<TFileOrigin extends string = string>(): InFlightUpload<TFileOrigin>[];
|
|
141
|
+
//# sourceMappingURL=in-flight-uploads-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-flight-uploads-provider.d.ts","sourceRoot":"","sources":["../../../src/react/tray/in-flight-uploads-provider.tsx"],"names":[],"mappings":"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"}
|