@justai/cuts 0.40.0 → 0.42.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/ActionRow.astro +9 -2
- package/src/FilePicker.astro +28 -5
package/package.json
CHANGED
package/src/ActionRow.astro
CHANGED
|
@@ -51,11 +51,18 @@ interface Props {
|
|
|
51
51
|
// without knowing this component's internals.
|
|
52
52
|
/** Key for a runtime translator — lands on the LABEL, never on the row. */
|
|
53
53
|
i18n?: string;
|
|
54
|
+
/**
|
|
55
|
+
* ANYTHING ELSE LANDS ON THE BUTTON. A consumer's `data-*` is how strings reach a bundled script
|
|
56
|
+
* in this ecosystem — a module script cannot read the frontmatter, so the DOM carries them — and
|
|
57
|
+
* a component that silently swallowed them would send the consumer looking for a bug in its own
|
|
58
|
+
* code. Astro drops unknown props unless they are spread, so they are spread.
|
|
59
|
+
*/
|
|
60
|
+
[key: string]: unknown;
|
|
54
61
|
}
|
|
55
|
-
const { id, label, tone = "plain", detail, hidden, i18n } = Astro.props;
|
|
62
|
+
const { id, label, tone = "plain", detail, hidden, i18n, ...rest } = Astro.props;
|
|
56
63
|
---
|
|
57
64
|
|
|
58
|
-
<button class:list={["jal-row", tone !== "plain" && `is-${tone}`]} type="button" id={id} hidden={hidden}>
|
|
65
|
+
<button class:list={["jal-row", tone !== "plain" && `is-${tone}`]} type="button" id={id} hidden={hidden} {...rest}>
|
|
59
66
|
<span class="jal-ico" aria-hidden="true"><slot name="icon" /></span>
|
|
60
67
|
<span class="jal-label" id={id ? `${id}-label` : undefined} data-i={i18n}>{label}</span>
|
|
61
68
|
{detail && <span class="jal-detail">{detail}</span>}
|
package/src/FilePicker.astro
CHANGED
|
@@ -43,8 +43,9 @@ import Sheet from "./Sheet.astro";
|
|
|
43
43
|
*
|
|
44
44
|
* const saved = await document.getElementById("logoPicker").save({
|
|
45
45
|
* name: "café wifi.qr", bytes, mime: "application/vnd.justai.qr+json",
|
|
46
|
+
* thumb: await canvas.convertToBlob(), // optional: give the file a face
|
|
46
47
|
* });
|
|
47
|
-
* // → { path, name } | null
|
|
48
|
+
* // → { path, id, name } | null
|
|
48
49
|
*
|
|
49
50
|
* One element rather than two cuts, because it is one room: two frames would be two documents,
|
|
50
51
|
* two atrium routers and two copies of the same kernel open at once, to ask the same file system
|
|
@@ -152,6 +153,19 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
152
153
|
/** Save BACK to a file already saved once. NO QUESTION IS ASKED: it was answered the first time. */
|
|
153
154
|
id?: string;
|
|
154
155
|
bytes: ArrayBuffer | Uint8Array;
|
|
156
|
+
/**
|
|
157
|
+
* THE FILE'S FACE — the ecosystem's optional standard, and worth taking.
|
|
158
|
+
*
|
|
159
|
+
* Most files can show themselves; a persona's OWN format cannot, and the persona is the only
|
|
160
|
+
* thing in the system that knows what it looks like. Hand over a small render (a 256px PNG is
|
|
161
|
+
* plenty) and the file has a face in the list and in its detail sheet instead of a large empty
|
|
162
|
+
* box saying there is no preview for this kind. Say nothing and nothing is lost.
|
|
163
|
+
*
|
|
164
|
+
* Kept small on purpose: the kernel drops a face over 64 KB rather than let a thumbnail become
|
|
165
|
+
* a second copy of the work in an evictable quota. Over the ceiling the FILE is still written —
|
|
166
|
+
* a cosmetic loss must never become a refused save.
|
|
167
|
+
*/
|
|
168
|
+
thumb?: ArrayBuffer | Uint8Array | Blob;
|
|
155
169
|
mime?: string;
|
|
156
170
|
title?: string;
|
|
157
171
|
timeout?: number;
|
|
@@ -302,10 +316,19 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
302
316
|
// text does) would otherwise have to know that the kernel wants the buffer, and that a typed
|
|
303
317
|
// array's `.buffer` may be a larger pool it is only a window onto — a subtle way to save a
|
|
304
318
|
// file with somebody else's bytes stuck to the end of it.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
319
|
+
// THE CUT BRINGS ITS OWN GROUND for both payloads. A caller holds whatever its work produced
|
|
320
|
+
// — a typed array from an encoder, a Blob from a canvas — and must not have to know that a
|
|
321
|
+
// typed array's `.buffer` may be a larger pool it is only a window onto, which is a subtle
|
|
322
|
+
// way to save a file with somebody else's bytes stuck to the end of it.
|
|
323
|
+
const buffer = async (v: any): Promise<ArrayBuffer | undefined> => {
|
|
324
|
+
if (!v) return undefined;
|
|
325
|
+
if (v instanceof ArrayBuffer) return v;
|
|
326
|
+
if (typeof Blob !== "undefined" && v instanceof Blob) return v.arrayBuffer();
|
|
327
|
+
return v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength);
|
|
328
|
+
};
|
|
329
|
+
const bytes = (await buffer(req.bytes))!;
|
|
330
|
+
const thumb = await buffer(req.thumb);
|
|
331
|
+
const args = { name: req.name, ext: req.ext, id: req.id, bytes, thumb, mime: req.mime };
|
|
309
332
|
// SAVING BACK OPENS NO SHEET, and that is the point rather than an optimisation. The person
|
|
310
333
|
// named this file the first time; asking again on every save is not care, it is a persona
|
|
311
334
|
// that cannot remember what it is working on. So this path never touches `ask`.
|