@burdenoff/fe-libs 2026.521.4 → 2026.521.5
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/storage/SaveToFilesButton.d.ts +55 -0
- package/dist/storage/SaveToFilesButton.d.ts.map +1 -0
- package/dist/storage/SaveToFilesButton.js +56 -0
- package/dist/storage/SaveToFilesDialog.d.ts +13 -0
- package/dist/storage/SaveToFilesDialog.d.ts.map +1 -0
- package/dist/storage/SaveToFilesDialog.js +88 -0
- package/dist/storage/StorageFolderPicker.js +134 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +6 -0
- package/dist/storage/uploadBlob.d.ts +21 -0
- package/dist/storage/uploadBlob.d.ts.map +1 -0
- package/dist/storage/uploadBlob.js +65 -0
- package/dist/storage/useSaveToFiles.d.ts +17 -0
- package/dist/storage/useSaveToFiles.d.ts.map +1 -0
- package/dist/storage/useSaveToFiles.js +73 -0
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SavedFile } from './useSaveToFiles';
|
|
2
|
+
/** Two shapes — pick one based on where the bytes come from. */
|
|
3
|
+
export type SaveToFilesSource = {
|
|
4
|
+
kind: 'blob';
|
|
5
|
+
/**
|
|
6
|
+
* Producer thunk. Returning `null` cleanly aborts the save (e.g. user
|
|
7
|
+
* cancelled a downstream confirm). Producers MAY override the filename
|
|
8
|
+
* and mime type at runtime (useful when the bytes carry metadata only
|
|
9
|
+
* known after a slow GraphQL call resolves).
|
|
10
|
+
*/
|
|
11
|
+
getBlob: () => Promise<{
|
|
12
|
+
blob: Blob;
|
|
13
|
+
filename?: string;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
} | null>;
|
|
16
|
+
/** Default filename — used by the browser-download path. */
|
|
17
|
+
filename: string;
|
|
18
|
+
/** Default MIME type — used by the browser-download path. */
|
|
19
|
+
mimeType: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'sandbox-artifact';
|
|
22
|
+
sandboxId: string;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
/** Artifact path inside the sandbox session. */
|
|
25
|
+
path: string;
|
|
26
|
+
/** Optional override; otherwise the sandbox's reported filename wins. */
|
|
27
|
+
filename?: string;
|
|
28
|
+
};
|
|
29
|
+
interface Props {
|
|
30
|
+
source: SaveToFilesSource;
|
|
31
|
+
/**
|
|
32
|
+
* Pre-selected destination folder path. The dialog will lazily create
|
|
33
|
+
* missing ancestors via `ensureFolderByPath` on submit. The user can edit
|
|
34
|
+
* the path in the dialog.
|
|
35
|
+
*/
|
|
36
|
+
defaultFolderPath?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Browser-download action. Called when the user clicks the primary button.
|
|
39
|
+
* SaveToFilesButton does not implement the download itself — callers may
|
|
40
|
+
* have specialised tracking, anti-flood, or transformation logic.
|
|
41
|
+
*/
|
|
42
|
+
onDownload: () => void | Promise<void>;
|
|
43
|
+
/** Called after a successful Save to Files. */
|
|
44
|
+
onSaved?: (file: SavedFile) => void;
|
|
45
|
+
/** Primary button label. Default "Download". */
|
|
46
|
+
label?: string;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
/** ARIA-style test ID prefix; defaults to `save-to-files`. */
|
|
49
|
+
testId?: string;
|
|
50
|
+
/** Pass-through className for the outer wrapper. */
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function SaveToFilesButton({ source, defaultFolderPath, onDownload, onSaved, label, disabled, testId, className, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=SaveToFilesButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaveToFilesButton.d.ts","sourceRoot":"","sources":["../../src/storage/SaveToFilesButton.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACpF,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,UAAU,KAAK;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,+CAA+C;IAC/C,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAOD,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,iBAAiB,EACjB,UAAU,EACV,OAAO,EACP,KAAkB,EAClB,QAAQ,EACR,MAAwB,EACxB,SAAS,GACV,EAAE,KAAK,2CAsDP"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Button as e } from "../ui/button.js";
|
|
2
|
+
import { DropdownMenu as t, DropdownMenuContent as n, DropdownMenuItem as r, DropdownMenuTrigger as i } from "../ui/dropdown-menu.js";
|
|
3
|
+
import { SaveToFilesDialog as a } from "./SaveToFilesDialog.js";
|
|
4
|
+
import { useState as o } from "react";
|
|
5
|
+
import { Download as s, FolderInput as c } from "lucide-react";
|
|
6
|
+
import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
|
+
//#region src/storage/SaveToFilesButton.tsx
|
|
8
|
+
function f(e) {
|
|
9
|
+
return e.kind === "blob" ? e.filename : e.filename ?? e.path.split("/").filter(Boolean).pop() ?? "artifact";
|
|
10
|
+
}
|
|
11
|
+
function p({ source: p, defaultFolderPath: m, onDownload: h, onSaved: g, label: _ = "Download", disabled: v, testId: y = "save-to-files", className: b }) {
|
|
12
|
+
let [x, S] = o(!1), C = f(p);
|
|
13
|
+
return /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ d("div", {
|
|
14
|
+
className: `inline-flex items-center ${b ?? ""}`,
|
|
15
|
+
children: [/* @__PURE__ */ d(e, {
|
|
16
|
+
variant: "outline",
|
|
17
|
+
size: "sm",
|
|
18
|
+
disabled: v,
|
|
19
|
+
onClick: () => void h(),
|
|
20
|
+
"data-testid": `${y}-download`,
|
|
21
|
+
className: "rounded-r-none",
|
|
22
|
+
children: [/* @__PURE__ */ u(s, { className: "mr-1.5 h-3.5 w-3.5" }), _]
|
|
23
|
+
}), /* @__PURE__ */ d(t, { children: [/* @__PURE__ */ u(i, {
|
|
24
|
+
asChild: !0,
|
|
25
|
+
children: /* @__PURE__ */ u(e, {
|
|
26
|
+
variant: "outline",
|
|
27
|
+
size: "sm",
|
|
28
|
+
disabled: v,
|
|
29
|
+
"aria-label": "More save actions",
|
|
30
|
+
"data-testid": `${y}-menu`,
|
|
31
|
+
className: "rounded-l-none border-l-0 px-2",
|
|
32
|
+
children: "▾"
|
|
33
|
+
})
|
|
34
|
+
}), /* @__PURE__ */ d(n, {
|
|
35
|
+
align: "end",
|
|
36
|
+
children: [/* @__PURE__ */ d(r, {
|
|
37
|
+
onSelect: () => void h(),
|
|
38
|
+
"data-testid": `${y}-menu-download`,
|
|
39
|
+
children: [/* @__PURE__ */ u(s, { className: "mr-2 h-3.5 w-3.5" }), "Download"]
|
|
40
|
+
}), /* @__PURE__ */ d(r, {
|
|
41
|
+
onSelect: () => S(!0),
|
|
42
|
+
"data-testid": `${y}-menu-save`,
|
|
43
|
+
children: [/* @__PURE__ */ u(c, { className: "mr-2 h-3.5 w-3.5" }), "Save to Files…"]
|
|
44
|
+
})]
|
|
45
|
+
})] })]
|
|
46
|
+
}), /* @__PURE__ */ u(a, {
|
|
47
|
+
open: x,
|
|
48
|
+
onOpenChange: S,
|
|
49
|
+
source: p,
|
|
50
|
+
defaultFolderPath: m,
|
|
51
|
+
filename: C,
|
|
52
|
+
onSaved: g
|
|
53
|
+
})] });
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { p as SaveToFilesButton };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SaveToFilesSource } from './SaveToFilesButton';
|
|
2
|
+
import { SavedFile } from './useSaveToFiles';
|
|
3
|
+
interface Props {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
source: SaveToFilesSource;
|
|
7
|
+
defaultFolderPath?: string;
|
|
8
|
+
filename: string;
|
|
9
|
+
onSaved?: (file: SavedFile) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function SaveToFilesDialog({ open, onOpenChange, source, defaultFolderPath, filename, onSaved }: Props): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=SaveToFilesDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaveToFilesDialog.d.ts","sourceRoot":"","sources":["../../src/storage/SaveToFilesDialog.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,2CAqF5G"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Button as e } from "../ui/button.js";
|
|
2
|
+
import { Dialog as t, DialogContent as n, DialogDescription as r, DialogFooter as i, DialogHeader as a, DialogTitle as o } from "../ui/dialog.js";
|
|
3
|
+
import { Input as s } from "../ui/input.js";
|
|
4
|
+
import { Label as c } from "../ui/label.js";
|
|
5
|
+
import { Spinner as l } from "../ui/spinner.js";
|
|
6
|
+
import { useSaveToFiles as u } from "./useSaveToFiles.js";
|
|
7
|
+
import { useEffect as d, useState as f } from "react";
|
|
8
|
+
import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
9
|
+
//#region src/storage/SaveToFilesDialog.tsx
|
|
10
|
+
function g({ open: g, onOpenChange: _, source: v, defaultFolderPath: y, filename: b, onSaved: x }) {
|
|
11
|
+
let [S, C] = f(y ?? "/Saved"), { save: w, isPending: T, error: E, progress: D } = u();
|
|
12
|
+
return d(() => {
|
|
13
|
+
g && C(y ?? "/Saved");
|
|
14
|
+
}, [g, y]), /* @__PURE__ */ m(t, {
|
|
15
|
+
open: g,
|
|
16
|
+
onOpenChange: _,
|
|
17
|
+
children: /* @__PURE__ */ h(n, { children: [
|
|
18
|
+
/* @__PURE__ */ h(a, { children: [/* @__PURE__ */ m(o, { children: "Save to Files" }), /* @__PURE__ */ h(r, { children: [
|
|
19
|
+
"Save ",
|
|
20
|
+
/* @__PURE__ */ m("span", {
|
|
21
|
+
className: "font-medium",
|
|
22
|
+
children: b
|
|
23
|
+
}),
|
|
24
|
+
" into a folder in this workspace."
|
|
25
|
+
] })] }),
|
|
26
|
+
/* @__PURE__ */ h("div", {
|
|
27
|
+
className: "space-y-3 py-2",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ h("div", { children: [
|
|
30
|
+
/* @__PURE__ */ m(c, {
|
|
31
|
+
htmlFor: "save-to-files-folder",
|
|
32
|
+
children: "Folder"
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ m(s, {
|
|
35
|
+
id: "save-to-files-folder",
|
|
36
|
+
value: S,
|
|
37
|
+
onChange: (e) => C(e.target.value),
|
|
38
|
+
placeholder: "/Vibecontrols/AI Assistant/Artifacts",
|
|
39
|
+
disabled: T,
|
|
40
|
+
"data-testid": "save-to-files-folder-input"
|
|
41
|
+
}),
|
|
42
|
+
/* @__PURE__ */ m("p", {
|
|
43
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
44
|
+
children: "Missing parent folders are created automatically."
|
|
45
|
+
})
|
|
46
|
+
] }),
|
|
47
|
+
v.kind === "blob" && T && /* @__PURE__ */ m("div", {
|
|
48
|
+
className: "h-1 w-full overflow-hidden rounded bg-muted",
|
|
49
|
+
role: "progressbar",
|
|
50
|
+
"aria-valuenow": D,
|
|
51
|
+
"aria-valuemin": 0,
|
|
52
|
+
"aria-valuemax": 100,
|
|
53
|
+
children: /* @__PURE__ */ m("div", {
|
|
54
|
+
className: "h-full bg-primary transition-[width]",
|
|
55
|
+
style: { width: `${D}%` }
|
|
56
|
+
})
|
|
57
|
+
}),
|
|
58
|
+
E && /* @__PURE__ */ m("p", {
|
|
59
|
+
className: "text-xs text-destructive",
|
|
60
|
+
"data-testid": "save-to-files-error",
|
|
61
|
+
children: E.message
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ h(i, { children: [/* @__PURE__ */ m(e, {
|
|
66
|
+
variant: "ghost",
|
|
67
|
+
disabled: T,
|
|
68
|
+
onClick: () => _(!1),
|
|
69
|
+
children: "Cancel"
|
|
70
|
+
}), /* @__PURE__ */ m(e, {
|
|
71
|
+
disabled: T || S.trim().length === 0,
|
|
72
|
+
onClick: async () => {
|
|
73
|
+
try {
|
|
74
|
+
let e = await w({
|
|
75
|
+
source: v,
|
|
76
|
+
folderPath: S
|
|
77
|
+
});
|
|
78
|
+
x?.(e), _(!1);
|
|
79
|
+
} catch {}
|
|
80
|
+
},
|
|
81
|
+
"data-testid": "save-to-files-save",
|
|
82
|
+
children: T ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(l, { className: "mr-2 h-3 w-3" }), "Saving…"] }) : "Save"
|
|
83
|
+
})] })
|
|
84
|
+
] })
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { g as SaveToFilesDialog };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useEffect as e, useState as t } from "react";
|
|
2
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
3
|
+
import { gql as i } from "@apollo/client";
|
|
4
|
+
import { useApolloClient as a } from "@apollo/client/react";
|
|
5
|
+
//#region src/storage/StorageFolderPicker.tsx
|
|
6
|
+
var o = i`
|
|
7
|
+
query StorageFolderPicker_ListFolders($context: ContextInput!, $parentId: ID) {
|
|
8
|
+
listFiles(context: $context, parentId: $parentId, kind: FOLDER) {
|
|
9
|
+
items {
|
|
10
|
+
id
|
|
11
|
+
name
|
|
12
|
+
path
|
|
13
|
+
parentId
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
`, s = i`
|
|
18
|
+
mutation StorageFolderPicker_CreateFolder($input: CreateFolderInput!) {
|
|
19
|
+
createFolder(input: $input) {
|
|
20
|
+
id
|
|
21
|
+
name
|
|
22
|
+
path
|
|
23
|
+
parentId
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
function c({ workspaceId: i, value: c, onChange: l, operations: u, initialParentId: d, disabled: f }) {
|
|
28
|
+
let p = a(), [m, h] = t(null), [g, _] = t(!1), [v, y] = t(""), [b, x] = t(null), S = u?.list ?? o, C = u?.create ?? s;
|
|
29
|
+
return e(() => {
|
|
30
|
+
let e = !1;
|
|
31
|
+
return h(null), x(null), p.query({
|
|
32
|
+
query: S,
|
|
33
|
+
variables: {
|
|
34
|
+
context: { workspaceId: i },
|
|
35
|
+
parentId: d ?? null
|
|
36
|
+
},
|
|
37
|
+
fetchPolicy: "network-only"
|
|
38
|
+
}).then((t) => {
|
|
39
|
+
e || h((t.data?.listFiles?.items ?? []).map(({ __typename: e, ...t }) => t));
|
|
40
|
+
}).catch((t) => {
|
|
41
|
+
e || x(t instanceof Error ? t.message : String(t));
|
|
42
|
+
}), () => {
|
|
43
|
+
e = !0;
|
|
44
|
+
};
|
|
45
|
+
}, [
|
|
46
|
+
p,
|
|
47
|
+
S,
|
|
48
|
+
i,
|
|
49
|
+
d
|
|
50
|
+
]), /* @__PURE__ */ r("div", {
|
|
51
|
+
className: "space-y-2",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ n("label", {
|
|
54
|
+
className: "block text-xs font-medium text-text-secondary",
|
|
55
|
+
children: "Save to folder"
|
|
56
|
+
}),
|
|
57
|
+
b && /* @__PURE__ */ n("p", {
|
|
58
|
+
className: "text-xs text-status-error-text",
|
|
59
|
+
children: b
|
|
60
|
+
}),
|
|
61
|
+
m == null ? /* @__PURE__ */ n("p", {
|
|
62
|
+
className: "text-xs text-text-tertiary",
|
|
63
|
+
children: "Loading folders…"
|
|
64
|
+
}) : m.length === 0 ? /* @__PURE__ */ n("p", {
|
|
65
|
+
className: "text-xs text-text-tertiary",
|
|
66
|
+
children: "No folders yet — create one."
|
|
67
|
+
}) : /* @__PURE__ */ r("select", {
|
|
68
|
+
disabled: f,
|
|
69
|
+
value: c?.id ?? "",
|
|
70
|
+
onChange: (e) => {
|
|
71
|
+
l(m.find((t) => t.id === e.target.value) ?? null);
|
|
72
|
+
},
|
|
73
|
+
className: "w-full rounded-md border border-border-subtle bg-bg-surface px-2 py-1.5 text-sm",
|
|
74
|
+
"aria-label": "Folder",
|
|
75
|
+
children: [/* @__PURE__ */ n("option", {
|
|
76
|
+
value: "",
|
|
77
|
+
children: "— Pick a folder —"
|
|
78
|
+
}), m.map((e) => /* @__PURE__ */ n("option", {
|
|
79
|
+
value: e.id,
|
|
80
|
+
children: e.path
|
|
81
|
+
}, e.id))]
|
|
82
|
+
}),
|
|
83
|
+
g ? /* @__PURE__ */ r("div", {
|
|
84
|
+
className: "flex items-center gap-2",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ n("input", {
|
|
87
|
+
type: "text",
|
|
88
|
+
value: v,
|
|
89
|
+
onChange: (e) => y(e.target.value),
|
|
90
|
+
placeholder: "New folder name",
|
|
91
|
+
className: "flex-1 rounded-md border border-border-subtle bg-bg-surface px-2 py-1.5 text-sm"
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ n("button", {
|
|
94
|
+
type: "button",
|
|
95
|
+
onClick: async () => {
|
|
96
|
+
let e = v.trim();
|
|
97
|
+
if (e) try {
|
|
98
|
+
let t = (await p.mutate({
|
|
99
|
+
mutation: C,
|
|
100
|
+
variables: { input: {
|
|
101
|
+
name: e,
|
|
102
|
+
parentId: d ?? null,
|
|
103
|
+
context: { workspaceId: i }
|
|
104
|
+
} }
|
|
105
|
+
})).data?.createFolder;
|
|
106
|
+
t && (h((e) => e ? [...e, t] : [t]), y(""), _(!1), l(t));
|
|
107
|
+
} catch (e) {
|
|
108
|
+
x(e instanceof Error ? e.message : String(e));
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
className: "rounded-md bg-status-info-bg-default px-2.5 py-1.5 text-xs text-status-info-text-inverse",
|
|
112
|
+
children: "Create"
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ n("button", {
|
|
115
|
+
type: "button",
|
|
116
|
+
onClick: () => {
|
|
117
|
+
_(!1), y("");
|
|
118
|
+
},
|
|
119
|
+
className: "rounded-md border border-border-subtle px-2.5 py-1.5 text-xs text-text-secondary",
|
|
120
|
+
children: "Cancel"
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
}) : /* @__PURE__ */ n("button", {
|
|
124
|
+
type: "button",
|
|
125
|
+
onClick: () => _(!0),
|
|
126
|
+
disabled: f,
|
|
127
|
+
className: "text-xs text-status-info-text hover:underline disabled:opacity-50",
|
|
128
|
+
children: "+ New folder"
|
|
129
|
+
})
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { c as StorageFolderPicker };
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { StorageFolderPicker } from './StorageFolderPicker';
|
|
2
2
|
export type { StorageFolder } from './StorageFolderPicker';
|
|
3
|
+
export { SaveToFilesButton } from './SaveToFilesButton';
|
|
4
|
+
export type { SaveToFilesSource } from './SaveToFilesButton';
|
|
5
|
+
export { SaveToFilesDialog } from './SaveToFilesDialog';
|
|
6
|
+
export { useSaveToFiles } from './useSaveToFiles';
|
|
7
|
+
export type { SavedFile, UseSaveToFilesResult } from './useSaveToFiles';
|
|
8
|
+
export { uploadBlobToFiles, FILES_AFTER_SAVE_REFETCH } from './uploadBlob';
|
|
9
|
+
export type { UploadBlobParams, UploadedFile } from './uploadBlob';
|
|
3
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StorageFolderPicker as e } from "./StorageFolderPicker.js";
|
|
2
|
+
import { FILES_AFTER_SAVE_REFETCH as t, uploadBlobToFiles as n } from "./uploadBlob.js";
|
|
3
|
+
import { useSaveToFiles as r } from "./useSaveToFiles.js";
|
|
4
|
+
import { SaveToFilesDialog as i } from "./SaveToFilesDialog.js";
|
|
5
|
+
import { SaveToFilesButton as a } from "./SaveToFilesButton.js";
|
|
6
|
+
export { t as FILES_AFTER_SAVE_REFETCH, a as SaveToFilesButton, i as SaveToFilesDialog, e as StorageFolderPicker, n as uploadBlobToFiles, r as useSaveToFiles };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApolloClient } from '@apollo/client';
|
|
2
|
+
/**
|
|
3
|
+
* Apollo invalidations every "Save to Files" caller benefits from — pulled
|
|
4
|
+
* from microfe-files' useUploadFile to keep the browse views in sync.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FILES_AFTER_SAVE_REFETCH: string[];
|
|
7
|
+
export interface UploadBlobParams {
|
|
8
|
+
client: ApolloClient;
|
|
9
|
+
blob: Blob;
|
|
10
|
+
filename: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
folderId: string;
|
|
13
|
+
onProgress?: (pct: number) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface UploadedFile {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
folderId: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function uploadBlobToFiles(params: UploadBlobParams): Promise<UploadedFile>;
|
|
21
|
+
//# sourceMappingURL=uploadBlob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadBlob.d.ts","sourceRoot":"","sources":["../../src/storage/uploadBlob.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAyBxD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,UAKpC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoEvF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { gql as e } from "@apollo/client";
|
|
2
|
+
//#region src/storage/uploadBlob.ts
|
|
3
|
+
var t = e`
|
|
4
|
+
mutation SaveToFiles_InitiateUpload($input: InitiateUploadInput!) {
|
|
5
|
+
initiateUpload(input: $input) {
|
|
6
|
+
session {
|
|
7
|
+
id
|
|
8
|
+
}
|
|
9
|
+
uploadUrl
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`, n = e`
|
|
13
|
+
mutation SaveToFiles_CompleteUpload($input: CompleteUploadInput!) {
|
|
14
|
+
completeUpload(input: $input) {
|
|
15
|
+
file {
|
|
16
|
+
id
|
|
17
|
+
name
|
|
18
|
+
folderId
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`, r = [
|
|
23
|
+
"GetFilesRootBrowsePage",
|
|
24
|
+
"GetFilesFolderBrowsePage",
|
|
25
|
+
"GetFilesRecentPage",
|
|
26
|
+
"GetFiles"
|
|
27
|
+
];
|
|
28
|
+
async function i(e) {
|
|
29
|
+
let { client: i, blob: a, filename: o, mimeType: s, folderId: c, onProgress: l } = e, u = o.includes(".") ? o.substring(o.lastIndexOf(".") + 1) : "";
|
|
30
|
+
l?.(0);
|
|
31
|
+
let d = await i.mutate({
|
|
32
|
+
mutation: t,
|
|
33
|
+
variables: { input: {
|
|
34
|
+
fileName: o,
|
|
35
|
+
fileSize: String(a.size),
|
|
36
|
+
mimeType: s,
|
|
37
|
+
extension: u,
|
|
38
|
+
folderId: c
|
|
39
|
+
} }
|
|
40
|
+
}), f = d.data?.initiateUpload?.session, p = d.data?.initiateUpload?.uploadUrl;
|
|
41
|
+
if (!f || !p) throw Error("initiateUpload did not return a presigned URL");
|
|
42
|
+
l?.(10), await new Promise((e, t) => {
|
|
43
|
+
let n = new XMLHttpRequest();
|
|
44
|
+
n.upload.addEventListener("progress", (e) => {
|
|
45
|
+
if (e.lengthComputable) {
|
|
46
|
+
let t = 10 + Math.round(e.loaded / e.total * 80);
|
|
47
|
+
l?.(t);
|
|
48
|
+
}
|
|
49
|
+
}), n.addEventListener("load", () => {
|
|
50
|
+
n.status >= 200 && n.status < 300 ? (l?.(90), e()) : t(/* @__PURE__ */ Error(`Upload PUT failed with status ${n.status}`));
|
|
51
|
+
}), n.addEventListener("error", () => t(/* @__PURE__ */ Error("Upload PUT network error"))), n.addEventListener("abort", () => t(/* @__PURE__ */ Error("Upload PUT aborted"))), n.open("PUT", p), n.setRequestHeader("Content-Type", s), n.send(a);
|
|
52
|
+
});
|
|
53
|
+
let m = (await i.mutate({
|
|
54
|
+
mutation: n,
|
|
55
|
+
variables: { input: {
|
|
56
|
+
sessionId: f.id,
|
|
57
|
+
folderId: c
|
|
58
|
+
} },
|
|
59
|
+
refetchQueries: r
|
|
60
|
+
})).data?.completeUpload?.file;
|
|
61
|
+
if (!m) throw Error("completeUpload did not return a file");
|
|
62
|
+
return l?.(100), m;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { r as FILES_AFTER_SAVE_REFETCH, i as uploadBlobToFiles };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SaveToFilesSource } from './SaveToFilesButton';
|
|
2
|
+
export interface SavedFile {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
folderId: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface UseSaveToFilesResult {
|
|
8
|
+
save: (params: {
|
|
9
|
+
source: SaveToFilesSource;
|
|
10
|
+
folderPath: string;
|
|
11
|
+
}) => Promise<SavedFile>;
|
|
12
|
+
isPending: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
progress: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function useSaveToFiles(): UseSaveToFilesResult;
|
|
17
|
+
//# sourceMappingURL=useSaveToFiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSaveToFiles.d.ts","sourceRoot":"","sources":["../../src/storage/useSaveToFiles.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAuB7D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxF,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,cAAc,IAAI,oBAAoB,CAgErD"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { FILES_AFTER_SAVE_REFETCH as e, uploadBlobToFiles as t } from "./uploadBlob.js";
|
|
2
|
+
import { useState as n } from "react";
|
|
3
|
+
import { gql as r } from "@apollo/client";
|
|
4
|
+
import { useApolloClient as i } from "@apollo/client/react";
|
|
5
|
+
//#region src/storage/useSaveToFiles.ts
|
|
6
|
+
var a = r`
|
|
7
|
+
mutation SaveToFiles_EnsureFolderByPath($path: String!) {
|
|
8
|
+
ensureFolderByPath(path: $path) {
|
|
9
|
+
id
|
|
10
|
+
name
|
|
11
|
+
path
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`, o = r`
|
|
15
|
+
mutation SaveToFiles_SaveSandboxArtifact($input: SaveSandboxArtifactInput!) {
|
|
16
|
+
saveSandboxArtifactToFiles(input: $input) {
|
|
17
|
+
id
|
|
18
|
+
name
|
|
19
|
+
folderId
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
function s() {
|
|
24
|
+
let r = i(), [s, c] = n(!1), [l, u] = n(null), [d, f] = n(0);
|
|
25
|
+
return {
|
|
26
|
+
save: async (n) => {
|
|
27
|
+
c(!0), u(null), f(0);
|
|
28
|
+
try {
|
|
29
|
+
let i = (await r.mutate({
|
|
30
|
+
mutation: a,
|
|
31
|
+
variables: { path: n.folderPath }
|
|
32
|
+
})).data?.ensureFolderByPath?.id;
|
|
33
|
+
if (!i) throw Error(`Could not resolve folder for path: ${n.folderPath}`);
|
|
34
|
+
if (n.source.kind === "sandbox-artifact") {
|
|
35
|
+
let t = (await r.mutate({
|
|
36
|
+
mutation: o,
|
|
37
|
+
variables: { input: {
|
|
38
|
+
sandboxId: n.source.sandboxId,
|
|
39
|
+
sessionId: n.source.sessionId,
|
|
40
|
+
path: n.source.path,
|
|
41
|
+
folderId: i,
|
|
42
|
+
filename: n.source.filename ?? null
|
|
43
|
+
} },
|
|
44
|
+
refetchQueries: e
|
|
45
|
+
})).data?.saveSandboxArtifactToFiles;
|
|
46
|
+
if (!t) throw Error("saveSandboxArtifactToFiles returned no file");
|
|
47
|
+
return f(100), t;
|
|
48
|
+
}
|
|
49
|
+
let s = await n.source.getBlob();
|
|
50
|
+
if (!s) throw Error("Cancelled: no bytes to save");
|
|
51
|
+
let c = s.blob;
|
|
52
|
+
return await t({
|
|
53
|
+
client: r,
|
|
54
|
+
blob: c,
|
|
55
|
+
filename: s.filename ?? n.source.filename,
|
|
56
|
+
mimeType: s.mimeType ?? n.source.mimeType,
|
|
57
|
+
folderId: i,
|
|
58
|
+
onProgress: f
|
|
59
|
+
});
|
|
60
|
+
} catch (e) {
|
|
61
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
62
|
+
throw u(t), t;
|
|
63
|
+
} finally {
|
|
64
|
+
c(!1);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
isPending: s,
|
|
68
|
+
error: l,
|
|
69
|
+
progress: d
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { s as useSaveToFiles };
|