@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.
@@ -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 };
@@ -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/fe-libs",
3
- "version": "2026.521.4",
3
+ "version": "2026.521.5",
4
4
  "description": "Burdenoff frontend primitives and domain libraries",
5
5
  "type": "module",
6
6
  "imports": {