@justai/cuts 0.32.0 → 0.33.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/package.json +1 -1
- package/src/FilePicker.astro +39 -3
package/package.json
CHANGED
package/src/FilePicker.astro
CHANGED
|
@@ -94,7 +94,16 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
94
94
|
// would silently take the first one's place.
|
|
95
95
|
import atrium from "@justai/atrium";
|
|
96
96
|
|
|
97
|
-
interface Picked {
|
|
97
|
+
interface Picked {
|
|
98
|
+
path: string;
|
|
99
|
+
name: string;
|
|
100
|
+
mime: string;
|
|
101
|
+
bytes: ArrayBuffer;
|
|
102
|
+
/** The same file as a Blob — for URL.createObjectURL, an upload body, an <img> src. */
|
|
103
|
+
blob(): Blob;
|
|
104
|
+
/** The same file as a data: URL — for anything that must EMBED it (an SVG, a saved document). */
|
|
105
|
+
dataUrl(): Promise<string>;
|
|
106
|
+
}
|
|
98
107
|
interface PickRequest { accept?: string; title?: string; timeout?: number }
|
|
99
108
|
|
|
100
109
|
for (const root of document.querySelectorAll<HTMLElement>("[data-picker]")) {
|
|
@@ -139,6 +148,11 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
139
148
|
async function pick(req: PickRequest = {}): Promise<Picked | null> {
|
|
140
149
|
const bus = await router();
|
|
141
150
|
if (!frame.getAttribute("src")) frame.setAttribute("src", root.dataset.src!);
|
|
151
|
+
// ONE HEADING, and it is the sheet's. A per-call title retitles the sheet rather than being
|
|
152
|
+
// forwarded into the frame: the kernel renders a heading of its own for callers that have no
|
|
153
|
+
// sheet around them, and sending it one here printed "Upload logo" twice, one under the other.
|
|
154
|
+
const heading = sheet.querySelector<HTMLElement>(".sheet-title");
|
|
155
|
+
if (heading && req.title) heading.textContent = req.title;
|
|
142
156
|
sheet.showModal();
|
|
143
157
|
|
|
144
158
|
// TWO WAYS THIS ENDS, and only one of them is the kernel's.
|
|
@@ -155,14 +169,36 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
155
169
|
.proxy({
|
|
156
170
|
service: "files",
|
|
157
171
|
method: "pick",
|
|
158
|
-
args: { accept: req.accept
|
|
172
|
+
args: { accept: req.accept },
|
|
159
173
|
options: { timeout: req.timeout ?? 300000 },
|
|
160
174
|
}))
|
|
161
175
|
.catch(() => null);
|
|
162
176
|
|
|
163
177
|
const result = await Promise.race([answered, dismissed]);
|
|
164
178
|
if (sheet.open) sheet.close();
|
|
165
|
-
|
|
179
|
+
if (!result) return null;
|
|
180
|
+
|
|
181
|
+
// THE CUT BRINGS ITS OWN GROUND, and bytes are not ground. Every consumer of this needs the
|
|
182
|
+
// file in some other shape, and the obvious hand-written conversion is a trap:
|
|
183
|
+
//
|
|
184
|
+
// btoa(String.fromCharCode(...new Uint8Array(bytes)))
|
|
185
|
+
//
|
|
186
|
+
// makes one argument per BYTE, so a 7.5 MB photo is a 7.5-million-argument call and the stack
|
|
187
|
+
// gives out — "Maximum call stack size exceeded", thrown far from anything that mentions size,
|
|
188
|
+
// and invisible to any test whose fixture image is small. qr wrote exactly that line and a
|
|
189
|
+
// person found it with a real photograph. It belongs here once, not in every persona.
|
|
190
|
+
const r = result as any;
|
|
191
|
+
const blob = () => new Blob([r.bytes], { type: r.mime });
|
|
192
|
+
return {
|
|
193
|
+
...r,
|
|
194
|
+
blob,
|
|
195
|
+
dataUrl: () => new Promise<string>((resolve, reject) => {
|
|
196
|
+
const rd = new FileReader();
|
|
197
|
+
rd.onload = () => resolve(rd.result as string);
|
|
198
|
+
rd.onerror = () => reject(rd.error);
|
|
199
|
+
rd.readAsDataURL(blob()); // no ceiling, unlike the spread above
|
|
200
|
+
}),
|
|
201
|
+
} as Picked;
|
|
166
202
|
}
|
|
167
203
|
|
|
168
204
|
(host as any).pick = pick;
|