@colixsystems/widget-sdk 0.220.0 → 0.221.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/README.md +19 -8
- package/dist/contract.cjs +24 -12
- package/dist/contract.js +24 -12
- package/dist/hooks.js +21 -11
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/contract.js +24 -12
package/README.md
CHANGED
|
@@ -84,7 +84,22 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
84
84
|
|
|
85
85
|
## Status
|
|
86
86
|
|
|
87
|
-
`v0.
|
|
87
|
+
`v0.221.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
88
|
+
|
|
89
|
+
### What's new in 0.221.0 (contract 1.177.0)
|
|
90
|
+
|
|
91
|
+
**A row's file goes in the row's record space, and `upload()` names the record per call.** A file a widget keeps in a `FILE` column belongs in that row's `record` space: exactly the people who may read the row may read the file, so a private table's attachments stay private, and a table that lets everyone read still serves them to logged-out visitors. A `public` file, by contrast, is readable by anyone holding its link whatever the table's permissions say. A row must exist before a file can be attached to it, and a create form only learns the new row's id inside its submit handler, after the hook rendered. So `upload(file, { recordId })` now takes the record per call, the way it already took `folderId`:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
const { create, update } = useDatastoreMutation(tableId);
|
|
95
|
+
const { upload } = useFilestoreUpload({ spaceType: "record" });
|
|
96
|
+
|
|
97
|
+
const row = await create({ Title: title }); // without the file
|
|
98
|
+
const stored = await upload(file, { recordId: row.id }); // into that row's space
|
|
99
|
+
await update(row.id, { Receipt: stored.id });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A per-call `recordId` wins over the hook's; outside a `record` space it is ignored. A record upload that names no record now rejects with `useFilestoreUpload: a record space needs a recordId` instead of the misleading `Sign in to upload`. A logged-out visitor cannot upload into a record space, so ask them to sign in to attach a file. `useCamera()` and `useImageEditor()` now point at `useFilestoreUpload` for the upload: their descriptions named `ctx.assets.upload`, which widget code cannot reach. Additive.
|
|
88
103
|
|
|
89
104
|
### What's new in 0.220.0 (contract 1.176.0)
|
|
90
105
|
|
|
@@ -1496,9 +1511,7 @@ const { edit } = useImageEditor();
|
|
|
1496
1511
|
const shot = await capture();
|
|
1497
1512
|
const small = await edit(shot.uri, [{ resize: { width: 800 } }], { format: "jpeg", compress: 0.8 });
|
|
1498
1513
|
|
|
1499
|
-
|
|
1500
|
-
fd.append("file", small.file);
|
|
1501
|
-
await ctx.assets.upload(fd);
|
|
1514
|
+
await upload(small.file); // upload from useFilestoreUpload()
|
|
1502
1515
|
```
|
|
1503
1516
|
|
|
1504
1517
|
`edit(uri, actions, options?)` applies `actions` in order and resolves the SAME normalised asset shape `useCamera()` yields, so capture → edit → upload is one code path on both hosts. Actions are `{ resize: { width?, height? } }`, `{ crop: { originX, originY, width, height } }`, `{ rotate: degrees }` and `{ flip: "horizontal" | "vertical" }`; output is `{ format: "jpeg" | "png" | "webp", compress, base64? }`.
|
|
@@ -1712,12 +1725,10 @@ When you take the `useCanWrite`-only route, omit the USER column for a guest (`i
|
|
|
1712
1725
|
|
|
1713
1726
|
**Dismissing the picker resolves `null`, not an error.** Backing out is the most common outcome, so it is deliberately not a rejection — your happy path needs no `try/catch`. A genuine failure (permission refused, no host broker) rejects with a structured `CameraError` (new named export) carrying a stable `.code` (`PERMISSION_DENIED` / `UNSUPPORTED` / `INTERNAL`).
|
|
1714
1727
|
|
|
1715
|
-
**One upload path on both hosts.** The resolved asset is normalised to `{ uri, name, mimeType, width, height, size, file }`, where `uri` is directly displayable (`<Image source={{ uri }} />`) and `file` is already the right upload part for the platform — a browser `File` on web, expo-file-system's `File` on native. (Never React Native's legacy `{ uri, name, type }` part: the native host streams the file off disk, and Expo's fetch rejects that part outright anyway. Do not branch on the part's type — an expo-file-system `File` is NOT `instanceof Blob` at runtime.) So the same
|
|
1728
|
+
**One upload path on both hosts.** The resolved asset is normalised to `{ uri, name, mimeType, width, height, size, file }`, where `uri` is directly displayable (`<Image source={{ uri }} />`) and `file` is already the right upload part for the platform — a browser `File` on web, expo-file-system's `File` on native. (Never React Native's legacy `{ uri, name, type }` part: the native host streams the file off disk, and Expo's fetch rejects that part outright anyway. Do not branch on the part's type — an expo-file-system `File` is NOT `instanceof Blob` at runtime.) So the same line works everywhere:
|
|
1716
1729
|
|
|
1717
1730
|
```js
|
|
1718
|
-
|
|
1719
|
-
fd.append("file", asset.file);
|
|
1720
|
-
await ctx.assets.upload(fd);
|
|
1731
|
+
await upload(asset.file); // upload from useFilestoreUpload()
|
|
1721
1732
|
```
|
|
1722
1733
|
|
|
1723
1734
|
`options` (`{ allowsEditing, quality }`) are **hints**: the Expo export applies both, the web camera applies `quality` only — so never depend on a cropped result. `reset()` clears the asset and releases it (on web that revokes the blob URL, which otherwise leaks for the life of the document). **Gate your camera button on `supported`** — a host that brokers no camera reports `false` rather than throwing. The web Player brokers it via `getUserMedia` — a live preview with a shutter — on every web host: it is the only camera a DESKTOP browser will open, and a phone reaches the same preview rather than its own camera app. It falls back to a file input (`capture="environment"`) only where getUserMedia is absent up front: an insecure origin or an older browser. Once the permission prompt has been shown the user gesture is spent, so a camera that then fails to open rejects (`PERMISSION_DENIED` if refused, `UNSUPPORTED` if there is no usable device) rather than falling back to a file dialog the browser would refuse to open. The Expo export uses `expo-image-picker`, whose config plugin declares the camera and photo-library permissions the runtime needs.
|
package/dist/contract.cjs
CHANGED
|
@@ -1369,7 +1369,7 @@ const HOOKS = [
|
|
|
1369
1369
|
"from the host context, builds a multipart FormData with the " +
|
|
1370
1370
|
"snake_case fields the backend reads verbatim (`space_type`, " +
|
|
1371
1371
|
"`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
|
|
1372
|
-
"ctx.filestore.files.upload. `upload(file, { folderId?, compress?, fileName? })` " +
|
|
1372
|
+
"ctx.filestore.files.upload. `upload(file, { folderId?, recordId?, compress?, fileName? })` " +
|
|
1373
1373
|
"resolves to the created file row or throws the wire error. `file` must be a BLOB — a browser File on web, " +
|
|
1374
1374
|
"expo-file-system's File on native (what FilePicker / useCamera / useImageEditor hand back); Expo's fetch rejects " +
|
|
1375
1375
|
"React Native's legacy { uri, name, type } part with \"Unsupported FormDataPart implementation\". The stored name " +
|
|
@@ -1387,13 +1387,20 @@ const HOOKS = [
|
|
|
1387
1387
|
"ORIGINAL matters (a document archive, a photo the user re-downloads, " +
|
|
1388
1388
|
"anything with an exact-bytes requirement). " +
|
|
1389
1389
|
"ALWAYS pass spaceType explicitly — omitting it falls back to " +
|
|
1390
|
-
"'project', which is unreadable by a logged-out visitor.
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
"
|
|
1394
|
-
"
|
|
1390
|
+
"'project', which is unreadable by a logged-out visitor. A file that " +
|
|
1391
|
+
"belongs to a datastore row — anything stored in a FILE column — goes " +
|
|
1392
|
+
"in that row's 'record' space, so exactly the people who may read the " +
|
|
1393
|
+
"row may read the file (logged-out visitors too, when the table lets " +
|
|
1394
|
+
"everyone read). A file can only be attached to a row that exists: " +
|
|
1395
|
+
"create the row, call upload(file, { recordId: row.id }) — the per-call " +
|
|
1396
|
+
"recordId names the row the hook could not know at render — then write " +
|
|
1397
|
+
"the returned id into the FILE column with update(). A logged-out " +
|
|
1398
|
+
"visitor cannot upload into a record space. For a file that belongs to " +
|
|
1399
|
+
"no row: 'public' for content the app shows to anyone browsing, " +
|
|
1400
|
+
"'project' for signed-in workspace users only, 'personal' for a file " +
|
|
1401
|
+
"private to the uploading app user.",
|
|
1395
1402
|
returnShape: {
|
|
1396
|
-
upload: "(file, { folderId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1403
|
+
upload: "(file, { folderId?, recordId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1397
1404
|
uploading: "boolean",
|
|
1398
1405
|
error: "Error | null",
|
|
1399
1406
|
lastUploaded: "FilestoreFile | null",
|
|
@@ -2818,8 +2825,8 @@ const HOOKS = [
|
|
|
2818
2825
|
"common case and is deliberately NOT an error, so no try/catch is needed on the happy path. They reject with a " +
|
|
2819
2826
|
"CameraError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. `asset.file` is already the right " +
|
|
2820
2827
|
"upload part for the host — a BLOB on both (a File on web, expo-file-system's File on native), because Expo's fetch " +
|
|
2821
|
-
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright:
|
|
2822
|
-
"
|
|
2828
|
+
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright: pass it " +
|
|
2829
|
+
"straight to upload(asset.file) from useFilestoreUpload — one code path on both platforms. reset() clears the asset and releases it. Check " +
|
|
2823
2830
|
"`supported` before rendering a camera button. options: { allowsEditing, quality } are HINTS the host honours where it " +
|
|
2824
2831
|
"can — the Expo export applies both, the web camera applies quality only, so never depend on a cropped result. " +
|
|
2825
2832
|
"Behaviour is otherwise identical on web (a getUserMedia preview on EVERY host, phones included; a file input " +
|
|
@@ -2885,7 +2892,7 @@ const HOOKS = [
|
|
|
2885
2892
|
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
2886
2893
|
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
2887
2894
|
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
2888
|
-
"
|
|
2895
|
+
"pass result.file to upload() from useFilestoreUpload. " +
|
|
2889
2896
|
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
2890
2897
|
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
2891
2898
|
"{ flip: \"horizontal\" | \"vertical\" }. A crop rect must lie INSIDE the surface the earlier actions produced, and " +
|
|
@@ -3022,7 +3029,7 @@ const PRIMITIVES = [
|
|
|
3022
3029
|
},
|
|
3023
3030
|
// sc-1378 — `<FilePicker accept onPick>` lets a widget surface a native
|
|
3024
3031
|
// file-picker on the web Player so end users can upload files to the
|
|
3025
|
-
// backend (paired with useFilestoreUpload
|
|
3032
|
+
// backend (paired with useFilestoreUpload). Web wraps
|
|
3026
3033
|
// a hidden DOM `<input type="file">`; native renders a disabled trigger
|
|
3027
3034
|
// and exposes `FilePicker.isSupported = false` until a vetted Expo
|
|
3028
3035
|
// picker pin lands.
|
|
@@ -5856,7 +5863,12 @@ const CONTRACT = deepFreeze({
|
|
|
5856
5863
|
// a widget never receives it. `account`'s photo actions are now
|
|
5857
5864
|
// optional members, and `user.email` is documented as nullable: an
|
|
5858
5865
|
// account without an email has none.
|
|
5859
|
-
|
|
5866
|
+
// 1.177.0: additive (sc-10937) — `useFilestoreUpload().upload(file,
|
|
5867
|
+
// { recordId })` names the record per call, so a widget can create a row
|
|
5868
|
+
// and attach to it in one handler. A FILE column's file goes in its
|
|
5869
|
+
// row's record space; useCamera / useImageEditor point at
|
|
5870
|
+
// useFilestoreUpload, since widget code cannot reach ctx.assets.
|
|
5871
|
+
version: "1.177.0",
|
|
5860
5872
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5861
5873
|
hooks: HOOKS,
|
|
5862
5874
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1369,7 +1369,7 @@ const HOOKS = [
|
|
|
1369
1369
|
"from the host context, builds a multipart FormData with the " +
|
|
1370
1370
|
"snake_case fields the backend reads verbatim (`space_type`, " +
|
|
1371
1371
|
"`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
|
|
1372
|
-
"ctx.filestore.files.upload. `upload(file, { folderId?, compress?, fileName? })` " +
|
|
1372
|
+
"ctx.filestore.files.upload. `upload(file, { folderId?, recordId?, compress?, fileName? })` " +
|
|
1373
1373
|
"resolves to the created file row or throws the wire error. `file` must be a BLOB — a browser File on web, " +
|
|
1374
1374
|
"expo-file-system's File on native (what FilePicker / useCamera / useImageEditor hand back); Expo's fetch rejects " +
|
|
1375
1375
|
"React Native's legacy { uri, name, type } part with \"Unsupported FormDataPart implementation\". The stored name " +
|
|
@@ -1387,13 +1387,20 @@ const HOOKS = [
|
|
|
1387
1387
|
"ORIGINAL matters (a document archive, a photo the user re-downloads, " +
|
|
1388
1388
|
"anything with an exact-bytes requirement). " +
|
|
1389
1389
|
"ALWAYS pass spaceType explicitly — omitting it falls back to " +
|
|
1390
|
-
"'project', which is unreadable by a logged-out visitor.
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
"
|
|
1394
|
-
"
|
|
1390
|
+
"'project', which is unreadable by a logged-out visitor. A file that " +
|
|
1391
|
+
"belongs to a datastore row — anything stored in a FILE column — goes " +
|
|
1392
|
+
"in that row's 'record' space, so exactly the people who may read the " +
|
|
1393
|
+
"row may read the file (logged-out visitors too, when the table lets " +
|
|
1394
|
+
"everyone read). A file can only be attached to a row that exists: " +
|
|
1395
|
+
"create the row, call upload(file, { recordId: row.id }) — the per-call " +
|
|
1396
|
+
"recordId names the row the hook could not know at render — then write " +
|
|
1397
|
+
"the returned id into the FILE column with update(). A logged-out " +
|
|
1398
|
+
"visitor cannot upload into a record space. For a file that belongs to " +
|
|
1399
|
+
"no row: 'public' for content the app shows to anyone browsing, " +
|
|
1400
|
+
"'project' for signed-in workspace users only, 'personal' for a file " +
|
|
1401
|
+
"private to the uploading app user.",
|
|
1395
1402
|
returnShape: {
|
|
1396
|
-
upload: "(file, { folderId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1403
|
+
upload: "(file, { folderId?, recordId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1397
1404
|
uploading: "boolean",
|
|
1398
1405
|
error: "Error | null",
|
|
1399
1406
|
lastUploaded: "FilestoreFile | null",
|
|
@@ -2818,8 +2825,8 @@ const HOOKS = [
|
|
|
2818
2825
|
"common case and is deliberately NOT an error, so no try/catch is needed on the happy path. They reject with a " +
|
|
2819
2826
|
"CameraError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. `asset.file` is already the right " +
|
|
2820
2827
|
"upload part for the host — a BLOB on both (a File on web, expo-file-system's File on native), because Expo's fetch " +
|
|
2821
|
-
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright:
|
|
2822
|
-
"
|
|
2828
|
+
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright: pass it " +
|
|
2829
|
+
"straight to upload(asset.file) from useFilestoreUpload — one code path on both platforms. reset() clears the asset and releases it. Check " +
|
|
2823
2830
|
"`supported` before rendering a camera button. options: { allowsEditing, quality } are HINTS the host honours where it " +
|
|
2824
2831
|
"can — the Expo export applies both, the web camera applies quality only, so never depend on a cropped result. " +
|
|
2825
2832
|
"Behaviour is otherwise identical on web (a getUserMedia preview on EVERY host, phones included; a file input " +
|
|
@@ -2885,7 +2892,7 @@ const HOOKS = [
|
|
|
2885
2892
|
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
2886
2893
|
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
2887
2894
|
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
2888
|
-
"
|
|
2895
|
+
"pass result.file to upload() from useFilestoreUpload. " +
|
|
2889
2896
|
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
2890
2897
|
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
2891
2898
|
"{ flip: \"horizontal\" | \"vertical\" }. A crop rect must lie INSIDE the surface the earlier actions produced, and " +
|
|
@@ -3022,7 +3029,7 @@ const PRIMITIVES = [
|
|
|
3022
3029
|
},
|
|
3023
3030
|
// sc-1378 — `<FilePicker accept onPick>` lets a widget surface a native
|
|
3024
3031
|
// file-picker on the web Player so end users can upload files to the
|
|
3025
|
-
// backend (paired with useFilestoreUpload
|
|
3032
|
+
// backend (paired with useFilestoreUpload). Web wraps
|
|
3026
3033
|
// a hidden DOM `<input type="file">`; native renders a disabled trigger
|
|
3027
3034
|
// and exposes `FilePicker.isSupported = false` until a vetted Expo
|
|
3028
3035
|
// picker pin lands.
|
|
@@ -5852,7 +5859,12 @@ const CONTRACT = deepFreeze({
|
|
|
5852
5859
|
// a widget never receives it. `account`'s photo actions are now
|
|
5853
5860
|
// optional members, and `user.email` is documented as nullable: an
|
|
5854
5861
|
// account without an email has none.
|
|
5855
|
-
|
|
5862
|
+
// 1.177.0: additive (sc-10937) — `useFilestoreUpload().upload(file,
|
|
5863
|
+
// { recordId })` names the record per call, so a widget can create a row
|
|
5864
|
+
// and attach to it in one handler. A FILE column's file goes in its
|
|
5865
|
+
// row's record space; useCamera / useImageEditor point at
|
|
5866
|
+
// useFilestoreUpload, since widget code cannot reach ctx.assets.
|
|
5867
|
+
version: "1.177.0",
|
|
5856
5868
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5857
5869
|
hooks: HOOKS,
|
|
5858
5870
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -4700,7 +4700,10 @@ export function useFilestoreFile(fileId) {
|
|
|
4700
4700
|
* same way the read hooks do (tenant for PROJECT, app user for PERSONAL, the
|
|
4701
4701
|
* record for RECORD) and posts a multipart FormData to `ctx.filestore.files.upload`.
|
|
4702
4702
|
*
|
|
4703
|
-
* `upload(file, {
|
|
4703
|
+
* sc-10937: `upload(file, { recordId })` names the record per call, so a
|
|
4704
|
+
* widget can create a row and attach to it in the same handler.
|
|
4705
|
+
*
|
|
4706
|
+
* `upload(file, { folderId?, recordId?, fileName? })` takes a browser `File` on the web
|
|
4704
4707
|
* Player and expo-file-system's `File` on the Expo export, as `<FilePicker>`,
|
|
4705
4708
|
* `useCamera()` and `useImageEditor()` all hand back. Never React Native's
|
|
4706
4709
|
* legacy `{ uri, name, type }` part: the native host streams the file off
|
|
@@ -4734,7 +4737,8 @@ export function useFilestoreUpload(options) {
|
|
|
4734
4737
|
folderId: defaultFolderId = null,
|
|
4735
4738
|
compress: defaultCompress = true,
|
|
4736
4739
|
} = options || {};
|
|
4737
|
-
const
|
|
4740
|
+
const isRecordSpace = String(spaceType || "project").toUpperCase() === "RECORD";
|
|
4741
|
+
const defaultOwnerId = _filestoreOwnerId(ctx, spaceType, recordId);
|
|
4738
4742
|
|
|
4739
4743
|
const [uploading, setUploading] = useState(false);
|
|
4740
4744
|
const [error, setError] = useState(null);
|
|
@@ -4746,19 +4750,25 @@ export function useFilestoreUpload(options) {
|
|
|
4746
4750
|
const upload = useCallback(
|
|
4747
4751
|
async (file, overrides) => {
|
|
4748
4752
|
if (!file) throw new Error("useFilestoreUpload: file is required");
|
|
4753
|
+
const hasOverride = (key) =>
|
|
4754
|
+
Boolean(overrides) && Object.prototype.hasOwnProperty.call(overrides, key);
|
|
4755
|
+
const ownerId =
|
|
4756
|
+
isRecordSpace && hasOverride("recordId")
|
|
4757
|
+
? overrides.recordId || null
|
|
4758
|
+
: defaultOwnerId;
|
|
4759
|
+
if (isRecordSpace && !ownerId) {
|
|
4760
|
+
// Not "Sign in": the widget forgot which row to attach to.
|
|
4761
|
+
const err = new Error("useFilestoreUpload: a record space needs a recordId");
|
|
4762
|
+
setError(err);
|
|
4763
|
+
throw err;
|
|
4764
|
+
}
|
|
4749
4765
|
if (!ownerId) {
|
|
4750
4766
|
const err = new Error("Sign in to upload");
|
|
4751
4767
|
setError(err);
|
|
4752
4768
|
throw err;
|
|
4753
4769
|
}
|
|
4754
|
-
const folderId =
|
|
4755
|
-
|
|
4756
|
-
? overrides.folderId
|
|
4757
|
-
: defaultFolderId;
|
|
4758
|
-
const compress =
|
|
4759
|
-
overrides && Object.prototype.hasOwnProperty.call(overrides, "compress")
|
|
4760
|
-
? overrides.compress
|
|
4761
|
-
: defaultCompress;
|
|
4770
|
+
const folderId = hasOverride("folderId") ? overrides.folderId : defaultFolderId;
|
|
4771
|
+
const compress = hasOverride("compress") ? overrides.compress : defaultCompress;
|
|
4762
4772
|
const form = new FormData();
|
|
4763
4773
|
form.append("space_type", String(spaceType || "project").toUpperCase());
|
|
4764
4774
|
form.append("owner_id", ownerId);
|
|
@@ -4800,7 +4810,7 @@ export function useFilestoreUpload(options) {
|
|
|
4800
4810
|
throw err;
|
|
4801
4811
|
}
|
|
4802
4812
|
},
|
|
4803
|
-
[
|
|
4813
|
+
[defaultOwnerId, isRecordSpace, defaultFolderId, defaultCompress, spaceType],
|
|
4804
4814
|
);
|
|
4805
4815
|
|
|
4806
4816
|
return { upload, uploading, error, lastUploaded };
|
package/dist/index.d.ts
CHANGED
|
@@ -2452,7 +2452,7 @@ export interface CameraAsset {
|
|
|
2452
2452
|
/**
|
|
2453
2453
|
* Ready-to-upload part — a Blob on both hosts: a `File` on web,
|
|
2454
2454
|
* expo-file-system's `File` on native. Expo's fetch rejects any other shape.
|
|
2455
|
-
*
|
|
2455
|
+
* Pass it to `upload(file)` from `useFilestoreUpload`.
|
|
2456
2456
|
*/
|
|
2457
2457
|
file: unknown;
|
|
2458
2458
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.221.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -1369,7 +1369,7 @@ const HOOKS = [
|
|
|
1369
1369
|
"from the host context, builds a multipart FormData with the " +
|
|
1370
1370
|
"snake_case fields the backend reads verbatim (`space_type`, " +
|
|
1371
1371
|
"`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
|
|
1372
|
-
"ctx.filestore.files.upload. `upload(file, { folderId?, compress?, fileName? })` " +
|
|
1372
|
+
"ctx.filestore.files.upload. `upload(file, { folderId?, recordId?, compress?, fileName? })` " +
|
|
1373
1373
|
"resolves to the created file row or throws the wire error. `file` must be a BLOB — a browser File on web, " +
|
|
1374
1374
|
"expo-file-system's File on native (what FilePicker / useCamera / useImageEditor hand back); Expo's fetch rejects " +
|
|
1375
1375
|
"React Native's legacy { uri, name, type } part with \"Unsupported FormDataPart implementation\". The stored name " +
|
|
@@ -1387,13 +1387,20 @@ const HOOKS = [
|
|
|
1387
1387
|
"ORIGINAL matters (a document archive, a photo the user re-downloads, " +
|
|
1388
1388
|
"anything with an exact-bytes requirement). " +
|
|
1389
1389
|
"ALWAYS pass spaceType explicitly — omitting it falls back to " +
|
|
1390
|
-
"'project', which is unreadable by a logged-out visitor.
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
"
|
|
1394
|
-
"
|
|
1390
|
+
"'project', which is unreadable by a logged-out visitor. A file that " +
|
|
1391
|
+
"belongs to a datastore row — anything stored in a FILE column — goes " +
|
|
1392
|
+
"in that row's 'record' space, so exactly the people who may read the " +
|
|
1393
|
+
"row may read the file (logged-out visitors too, when the table lets " +
|
|
1394
|
+
"everyone read). A file can only be attached to a row that exists: " +
|
|
1395
|
+
"create the row, call upload(file, { recordId: row.id }) — the per-call " +
|
|
1396
|
+
"recordId names the row the hook could not know at render — then write " +
|
|
1397
|
+
"the returned id into the FILE column with update(). A logged-out " +
|
|
1398
|
+
"visitor cannot upload into a record space. For a file that belongs to " +
|
|
1399
|
+
"no row: 'public' for content the app shows to anyone browsing, " +
|
|
1400
|
+
"'project' for signed-in workspace users only, 'personal' for a file " +
|
|
1401
|
+
"private to the uploading app user.",
|
|
1395
1402
|
returnShape: {
|
|
1396
|
-
upload: "(file, { folderId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1403
|
+
upload: "(file, { folderId?, recordId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1397
1404
|
uploading: "boolean",
|
|
1398
1405
|
error: "Error | null",
|
|
1399
1406
|
lastUploaded: "FilestoreFile | null",
|
|
@@ -2818,8 +2825,8 @@ const HOOKS = [
|
|
|
2818
2825
|
"common case and is deliberately NOT an error, so no try/catch is needed on the happy path. They reject with a " +
|
|
2819
2826
|
"CameraError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. `asset.file` is already the right " +
|
|
2820
2827
|
"upload part for the host — a BLOB on both (a File on web, expo-file-system's File on native), because Expo's fetch " +
|
|
2821
|
-
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright:
|
|
2822
|
-
"
|
|
2828
|
+
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright: pass it " +
|
|
2829
|
+
"straight to upload(asset.file) from useFilestoreUpload — one code path on both platforms. reset() clears the asset and releases it. Check " +
|
|
2823
2830
|
"`supported` before rendering a camera button. options: { allowsEditing, quality } are HINTS the host honours where it " +
|
|
2824
2831
|
"can — the Expo export applies both, the web camera applies quality only, so never depend on a cropped result. " +
|
|
2825
2832
|
"Behaviour is otherwise identical on web (a getUserMedia preview on EVERY host, phones included; a file input " +
|
|
@@ -2885,7 +2892,7 @@ const HOOKS = [
|
|
|
2885
2892
|
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
2886
2893
|
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
2887
2894
|
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
2888
|
-
"
|
|
2895
|
+
"pass result.file to upload() from useFilestoreUpload. " +
|
|
2889
2896
|
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
2890
2897
|
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
2891
2898
|
"{ flip: \"horizontal\" | \"vertical\" }. A crop rect must lie INSIDE the surface the earlier actions produced, and " +
|
|
@@ -3022,7 +3029,7 @@ const PRIMITIVES = [
|
|
|
3022
3029
|
},
|
|
3023
3030
|
// sc-1378 — `<FilePicker accept onPick>` lets a widget surface a native
|
|
3024
3031
|
// file-picker on the web Player so end users can upload files to the
|
|
3025
|
-
// backend (paired with useFilestoreUpload
|
|
3032
|
+
// backend (paired with useFilestoreUpload). Web wraps
|
|
3026
3033
|
// a hidden DOM `<input type="file">`; native renders a disabled trigger
|
|
3027
3034
|
// and exposes `FilePicker.isSupported = false` until a vetted Expo
|
|
3028
3035
|
// picker pin lands.
|
|
@@ -5852,7 +5859,12 @@ const CONTRACT = deepFreeze({
|
|
|
5852
5859
|
// a widget never receives it. `account`'s photo actions are now
|
|
5853
5860
|
// optional members, and `user.email` is documented as nullable: an
|
|
5854
5861
|
// account without an email has none.
|
|
5855
|
-
|
|
5862
|
+
// 1.177.0: additive (sc-10937) — `useFilestoreUpload().upload(file,
|
|
5863
|
+
// { recordId })` names the record per call, so a widget can create a row
|
|
5864
|
+
// and attach to it in one handler. A FILE column's file goes in its
|
|
5865
|
+
// row's record space; useCamera / useImageEditor point at
|
|
5866
|
+
// useFilestoreUpload, since widget code cannot reach ctx.assets.
|
|
5867
|
+
version: "1.177.0",
|
|
5856
5868
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5857
5869
|
hooks: HOOKS,
|
|
5858
5870
|
primitives: PRIMITIVES,
|