@colixsystems/widget-sdk 0.167.0 → 0.169.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 +14 -3
- package/dist/contract.cjs +60 -6
- package/dist/contract.js +60 -6
- package/dist/hooks.js +149 -1
- package/dist/host.js +5 -0
- package/dist/index.js +2 -0
- package/dist/index.native.js +2 -0
- package/dist/navigation.cjs +59 -1
- package/dist/navigation.js +58 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,18 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
73
73
|
|
|
74
74
|
## Status
|
|
75
75
|
|
|
76
|
-
`v0.
|
|
76
|
+
`v0.169.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**.
|
|
77
|
+
|
|
78
|
+
### What's new in 0.169.0 (contract 1.130.0)
|
|
79
|
+
|
|
80
|
+
**Widgets can create folders and copy files, including between spaces.** Two new hooks over `ctx.filestore`:
|
|
81
|
+
|
|
82
|
+
- `useFilestoreCreateFolder({ spaceType, parentFolderId? })` → `{ createFolder, creating, error, lastCreated }`. `createFolder(name, { parentFolderId? })` resolves to the new folder row; `parentFolderId` omitted or `null` places it at the space root. Refetch your `useFilestoreFolders` afterwards to show it.
|
|
83
|
+
- `useFilestoreCopy()` → `{ copy, copying, error, lastCopied }`. `copy(fileId, { spaceType, folderId?, name? })` duplicates a file into any space the user may add files to — project, public, or their own personal space — and resolves to the **new** file row with its own `id` and `url`. `spaceType` names the destination and is required. The bytes are duplicated server-side, so the copy and its source are independent afterwards. A name already taken at the destination lands as `name (2).ext` and never replaces the existing file.
|
|
84
|
+
|
|
85
|
+
The public space keeps its usual gate: copying into it needs the same upload access the workspace operator grants for uploads, and creating a folder there needs the right to manage that space, so an app user's public `createFolder()` is refused unless they were given it. Copying into the user's own personal space works whenever they may download the source.
|
|
86
|
+
|
|
87
|
+
Refusals keep the usual shape: branch on `err.status`, with the server's code at `err.details.code`. The copy-specific codes are `FILE_COPY_NOT_PERMITTED` (the user may only view the source), `UPLOAD_NOT_PERMITTED` (the destination refuses the add), `STORAGE_QUOTA_EXCEEDED` (413) and `FILE_PROCESSING` (409, a video still being processed). Both hooks need the `files.write:*` scope and behave identically on the web Player and the native export. Needs `@colixsystems/filestore-client` 0.10.0, which adds `files.copy(id, body)`.
|
|
77
88
|
|
|
78
89
|
### What's new in 0.167.0 (contract 1.129.0)
|
|
79
90
|
|
|
@@ -93,7 +104,7 @@ Render an **assistant** message with `<RichText value={m.content} stripLegacyHtm
|
|
|
93
104
|
A binding may now carry an opacity: `"theme:colors.primary/20"` is the primary brand colour at 20%.
|
|
94
105
|
|
|
95
106
|
- **`parseThemeColorTokenRef(value)` / `formatThemeColorTokenRef(path, percent)`** publish the grammar beside the existing `isThemeColorTokenRef` predicate. The BARE form inherits whatever opacity the token itself carries — a workspace may author a translucent brand colour — so `/100` is a real value, not a redundant spelling: it is how "opaque, whatever the brand does" is said. One opacity has one spelling otherwise (no leading zeros, nothing above 100).
|
|
96
|
-
- **`withHexAlpha(hex, percent)` / `hexAlphaPercent(hex)`** are the percent ↔ alpha-byte conversion the Studio's
|
|
107
|
+
- **`withHexAlpha(hex, percent)` / `hexAlphaPercent(hex)`** are the percent ↔ alpha-byte conversion, so the 20% a panel reads back is the 20% a render paints. The Studio's `frontend/src/utils/color.js` keeps its own copy of this maths rather than importing it — that module is reached by a `node --test` suite, and the SDK is a Vite alias with no node resolution — and the two are pinned equal by the SDK's suite.
|
|
97
108
|
- **An authored opacity replaces the token's.** With none authored the token is handed back verbatim, including a token that is itself translucent — reading the bare form as 100 would quietly paint a see-through brand colour solid.
|
|
98
109
|
- **A token resolving to a non-hex colour stays opaque** — alpha cannot be applied without rewriting the author's notation — and the Studio disables the opacity control for that case rather than lying about it.
|
|
99
110
|
- **Nothing changes for a widget author.** The host still hands your code an ordinary colour string; it is simply `#RRGGBBAA` when an opacity was authored.
|
|
@@ -1593,7 +1604,7 @@ Also: `useFileSignatures(fileIds)` is now **self-scoped** (the caller's own sign
|
|
|
1593
1604
|
**Filestore browsing + BankID file signing for widgets (REQ-FS / REQ-SIGN).** Three new hooks read a newly-injected `ctx.filestore` (the `@colixsystems/filestore-client`, now constructed by both the web and native hosts):
|
|
1594
1605
|
- `useFilestoreFiles({ spaceType, folderId?, q?, type? })` → `{ files, loading, error, refetch }` — browses the end-user's Filestore space. The hook resolves `owner_id` from the host context (tenant for a project space, the app user for a personal space), so the widget only picks the space. Every row carries a ready-to-render `url` (its absolutized `presigned_url`), so a gallery renders `files.map(f => f.url)` directly — no per-row `useFilestoreFile` call. Every row ALSO carries `urls` — the size ladder `{ thumbnail, card, large, hero }` at 128 / 512 / 1024 / 2048px longest edge — so a thumbnail grid should render `f.urls.thumbnail` rather than pulling the full-size original for every tile.
|
|
1595
1606
|
- `useFilestoreFile(fileId)` → `{ file, url, urls, loading, error, refetch }` — resolves ONE file id to a displayable `url` (its `presigned_url`, absolutized for web + native) via `ctx.filestore.files.get(id)`. **Read the top-level `url`** — the returned `file` is `null` until the fetch resolves (and for an empty id), so `file.url` throws on the first render; it is correct only after a null check. **Never compose a file URL yourself** — the bytes are served only from a server-signed token URL, so a hand-built path like `/api/files/<id>` can never resolve (the linter's `no-host-api-url` rule flags it, including the `${location.origin}/api/files/…` form). A datastore `FILE` column holds — and reads back as — that **bare file-id string**; it is NOT hydrated into an object the way a `RELATION` (`{ id, label }`) or `USER` (`{ id, name }`) column is, so pass the value straight to the hook (no `{ id }` / `{ url }` unwrapping guard). Empty / deleted / not-found ids resolve to `url: null` so a display widget shows a fallback. When you render `url` in an `<Image>` that fills its container, size it with `aspectRatio` (e.g. `{ width: "100%", aspectRatio: 1 }`) or pixels — never a percentage `height`, which React Native collapses to 0 against a content-sized parent, so the image loads but is invisible. Requires `files.read:*`.
|
|
1596
|
-
- `useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })` → `{ folders, loading, error, refetch }` — the folder-navigation companion to `useFilestoreFiles`; pass `enabled:false` to suspend fetching.
|
|
1607
|
+
- `useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })` → `{ folders, loading, error, refetch }` — the folder-navigation companion to `useFilestoreFiles`; pass `enabled:false` to suspend fetching. (Folder creation and file copy arrived later, in 0.169.0 — see above.)
|
|
1597
1608
|
- `useFilestoreUpload({ spaceType, folderId?, compress? })` → `{ upload, uploading, error, lastUploaded }` — POSTs a multipart upload to `ctx.filestore.files.upload`. Resolves `owner_id` from the host context (like the read hooks) so the widget only picks the space + destination folder. Uploaded images are compressed to WebP by the backend; pass `compress: false` (on the hook, or per `upload(file, { compress })`) to store the file byte-for-byte — use it whenever the original matters. Pair with the `<FilePicker>` primitive for the visible trigger. Requires the `files.write:*` scope.
|
|
1598
1609
|
- `useFileSignature(fileId)` → `{ status, qr, signerName, verdict, initiate, refresh, cancel, verify, … }` — drives a BankID signing flow for a file (the backend hashes the bytes server-side, binds the digest into the signature, and verifies the proof offline).
|
|
1599
1610
|
|
package/dist/contract.cjs
CHANGED
|
@@ -1217,6 +1217,56 @@ const HOOKS = [
|
|
|
1217
1217
|
requiredContextSlice: ["filestore.folders"],
|
|
1218
1218
|
scopes: ["files.read:*"],
|
|
1219
1219
|
},
|
|
1220
|
+
{
|
|
1221
|
+
name: "useFilestoreCreateFolder",
|
|
1222
|
+
signature: "useFilestoreCreateFolder({ spaceType, parentFolderId })",
|
|
1223
|
+
optionalArgs: ["parentFolderId"],
|
|
1224
|
+
description:
|
|
1225
|
+
"Create a folder in the end-user's Filestore space. The widget passes the " +
|
|
1226
|
+
"SPACE (`{ spaceType, parentFolderId? }`); the hook resolves owner_id from " +
|
|
1227
|
+
"the host context and posts `{ name, space_type, owner_id, parent_folder_id }` " +
|
|
1228
|
+
"through ctx.filestore.folders.create. `createFolder(name, { parentFolderId? })` " +
|
|
1229
|
+
"resolves to the created folder row or throws the wire error: 422 for an " +
|
|
1230
|
+
"invalid name, 404 for a parent that is not in this space, 403/404 when the " +
|
|
1231
|
+
"caller may not add a folder there (the server's FOLDER_CREATE_NOT_PERMITTED " +
|
|
1232
|
+
"is at `err.details.code`). In the public space only managers of that space " +
|
|
1233
|
+
"may create folders. Refetch useFilestoreFolders afterwards to show it.",
|
|
1234
|
+
returnShape: {
|
|
1235
|
+
createFolder: "(name, { parentFolderId? }) => Promise<FilestoreFolder>",
|
|
1236
|
+
creating: "boolean",
|
|
1237
|
+
error: "Error | null",
|
|
1238
|
+
lastCreated: "FilestoreFolder | null",
|
|
1239
|
+
},
|
|
1240
|
+
requiredContextSlice: ["filestore.folders"],
|
|
1241
|
+
scopes: ["files.write:*"],
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
name: "useFilestoreCopy",
|
|
1245
|
+
signature: "useFilestoreCopy()",
|
|
1246
|
+
description:
|
|
1247
|
+
"Copy a Filestore file into any space and folder the caller may add files " +
|
|
1248
|
+
"to, including ACROSS spaces (project, public, and the signed-in user's " +
|
|
1249
|
+
"personal space). `copy(fileId, { spaceType, folderId?, name? })` names the " +
|
|
1250
|
+
"DESTINATION (spaceType is required); the hook resolves its owner_id from " +
|
|
1251
|
+
"the host context and posts through ctx.filestore.files.copy. Resolves to " +
|
|
1252
|
+
"the NEW file row (its own id and url) — the source is untouched and the " +
|
|
1253
|
+
"bytes are duplicated, so later changes to one never affect the other. A " +
|
|
1254
|
+
"name already taken at the destination lands as \"name (2).ext\" instead of " +
|
|
1255
|
+
"replacing it. Copying INTO public needs the same operator upload grant as " +
|
|
1256
|
+
"uploading there. Errors (code at `err.details.code`): 404 source not visible; " +
|
|
1257
|
+
"403 FILE_COPY_NOT_PERMITTED when the caller may only view the source; " +
|
|
1258
|
+
"403/404 UPLOAD_NOT_PERMITTED when the destination refuses the add; 413 " +
|
|
1259
|
+
"STORAGE_QUOTA_EXCEEDED when the destination space is full; 409 " +
|
|
1260
|
+
"FILE_PROCESSING while a video is still being processed.",
|
|
1261
|
+
returnShape: {
|
|
1262
|
+
copy: "(fileId, { spaceType, folderId?, name? }) => Promise<FilestoreFile>",
|
|
1263
|
+
copying: "boolean",
|
|
1264
|
+
error: "Error | null",
|
|
1265
|
+
lastCopied: "FilestoreFile | null",
|
|
1266
|
+
},
|
|
1267
|
+
requiredContextSlice: ["filestore.files"],
|
|
1268
|
+
scopes: ["files.write:*"],
|
|
1269
|
+
},
|
|
1220
1270
|
{
|
|
1221
1271
|
name: "useFileSignature",
|
|
1222
1272
|
signature: "useFileSignature(fileId, existingSignatureId)",
|
|
@@ -2692,10 +2742,10 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2692
2742
|
filestore: {
|
|
2693
2743
|
description:
|
|
2694
2744
|
"Injected @colixsystems/filestore-client instance — the end-user file archive (project / personal / public spaces) + BankID file signing. " +
|
|
2695
|
-
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), remove(id), preview(id) }, " +
|
|
2745
|
+
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), copy(id, body), remove(id), preview(id) }, " +
|
|
2696
2746
|
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
2697
2747
|
"signatures: { initiate(fileId), status(id), cancel(id), verify(id) }, objectUrl(token), fetchObject(token) }. " +
|
|
2698
|
-
"Backs useFilestoreFiles() + useFilestoreFolders() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
2748
|
+
"Backs useFilestoreFiles() + useFilestoreFolders() + useFilestoreCreateFolder() + useFilestoreCopy() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
2699
2749
|
required: false,
|
|
2700
2750
|
fields: { files: "object", folders: "object", shares: "object", signatures: "object" },
|
|
2701
2751
|
},
|
|
@@ -4539,10 +4589,10 @@ const CONTRACT = deepFreeze({
|
|
|
4539
4589
|
// field bound, so "our brand colour at 20%" forced the author back to a
|
|
4540
4590
|
// frozen hex, which then stopped following a rebrand — the exact thing
|
|
4541
4591
|
// binding exists to prevent. `parseThemeColorTokenRef` /
|
|
4542
|
-
// `formatThemeColorTokenRef` publish the grammar and
|
|
4592
|
+
// `formatThemeColorTokenRef` publish the grammar, and
|
|
4543
4593
|
// `withHexAlpha` / `hexAlphaPercent` the percent-to-byte conversion the
|
|
4544
|
-
// Studio's control and both hosts' resolver
|
|
4545
|
-
// painted. The BARE form inherits the token's own opacity (a brand colour
|
|
4594
|
+
// Studio's control and both hosts' resolver answer alike, so 20% authored
|
|
4595
|
+
// is 20% painted. The BARE form inherits the token's own opacity (a brand colour
|
|
4546
4596
|
// may be authored translucent), which makes `/100` a real value rather
|
|
4547
4597
|
// than a redundant spelling: it is how "opaque, whatever the brand does"
|
|
4548
4598
|
// is said. Additive: an unsuffixed binding parses and resolves exactly as
|
|
@@ -4553,7 +4603,11 @@ const CONTRACT = deepFreeze({
|
|
|
4553
4603
|
// it removed every `<...>`, so an AI reply lost `Promise<void>`. The
|
|
4554
4604
|
// emphasis grammar now follows CommonMark flanking rules, so `created_at`
|
|
4555
4605
|
// keeps its underscore and `2 * 4` keeps its asterisk.
|
|
4556
|
-
|
|
4606
|
+
// 1.130.0: additive (sc-9057) — `useFilestoreCreateFolder` and
|
|
4607
|
+
// `useFilestoreCopy`. Widgets can now create Filestore folders and copy a
|
|
4608
|
+
// file into another folder or space (project / public / personal); the
|
|
4609
|
+
// filestore client gains `files.copy`.
|
|
4610
|
+
version: "1.130.0",
|
|
4557
4611
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4558
4612
|
hooks: HOOKS,
|
|
4559
4613
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1217,6 +1217,56 @@ const HOOKS = [
|
|
|
1217
1217
|
requiredContextSlice: ["filestore.folders"],
|
|
1218
1218
|
scopes: ["files.read:*"],
|
|
1219
1219
|
},
|
|
1220
|
+
{
|
|
1221
|
+
name: "useFilestoreCreateFolder",
|
|
1222
|
+
signature: "useFilestoreCreateFolder({ spaceType, parentFolderId })",
|
|
1223
|
+
optionalArgs: ["parentFolderId"],
|
|
1224
|
+
description:
|
|
1225
|
+
"Create a folder in the end-user's Filestore space. The widget passes the " +
|
|
1226
|
+
"SPACE (`{ spaceType, parentFolderId? }`); the hook resolves owner_id from " +
|
|
1227
|
+
"the host context and posts `{ name, space_type, owner_id, parent_folder_id }` " +
|
|
1228
|
+
"through ctx.filestore.folders.create. `createFolder(name, { parentFolderId? })` " +
|
|
1229
|
+
"resolves to the created folder row or throws the wire error: 422 for an " +
|
|
1230
|
+
"invalid name, 404 for a parent that is not in this space, 403/404 when the " +
|
|
1231
|
+
"caller may not add a folder there (the server's FOLDER_CREATE_NOT_PERMITTED " +
|
|
1232
|
+
"is at `err.details.code`). In the public space only managers of that space " +
|
|
1233
|
+
"may create folders. Refetch useFilestoreFolders afterwards to show it.",
|
|
1234
|
+
returnShape: {
|
|
1235
|
+
createFolder: "(name, { parentFolderId? }) => Promise<FilestoreFolder>",
|
|
1236
|
+
creating: "boolean",
|
|
1237
|
+
error: "Error | null",
|
|
1238
|
+
lastCreated: "FilestoreFolder | null",
|
|
1239
|
+
},
|
|
1240
|
+
requiredContextSlice: ["filestore.folders"],
|
|
1241
|
+
scopes: ["files.write:*"],
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
name: "useFilestoreCopy",
|
|
1245
|
+
signature: "useFilestoreCopy()",
|
|
1246
|
+
description:
|
|
1247
|
+
"Copy a Filestore file into any space and folder the caller may add files " +
|
|
1248
|
+
"to, including ACROSS spaces (project, public, and the signed-in user's " +
|
|
1249
|
+
"personal space). `copy(fileId, { spaceType, folderId?, name? })` names the " +
|
|
1250
|
+
"DESTINATION (spaceType is required); the hook resolves its owner_id from " +
|
|
1251
|
+
"the host context and posts through ctx.filestore.files.copy. Resolves to " +
|
|
1252
|
+
"the NEW file row (its own id and url) — the source is untouched and the " +
|
|
1253
|
+
"bytes are duplicated, so later changes to one never affect the other. A " +
|
|
1254
|
+
"name already taken at the destination lands as \"name (2).ext\" instead of " +
|
|
1255
|
+
"replacing it. Copying INTO public needs the same operator upload grant as " +
|
|
1256
|
+
"uploading there. Errors (code at `err.details.code`): 404 source not visible; " +
|
|
1257
|
+
"403 FILE_COPY_NOT_PERMITTED when the caller may only view the source; " +
|
|
1258
|
+
"403/404 UPLOAD_NOT_PERMITTED when the destination refuses the add; 413 " +
|
|
1259
|
+
"STORAGE_QUOTA_EXCEEDED when the destination space is full; 409 " +
|
|
1260
|
+
"FILE_PROCESSING while a video is still being processed.",
|
|
1261
|
+
returnShape: {
|
|
1262
|
+
copy: "(fileId, { spaceType, folderId?, name? }) => Promise<FilestoreFile>",
|
|
1263
|
+
copying: "boolean",
|
|
1264
|
+
error: "Error | null",
|
|
1265
|
+
lastCopied: "FilestoreFile | null",
|
|
1266
|
+
},
|
|
1267
|
+
requiredContextSlice: ["filestore.files"],
|
|
1268
|
+
scopes: ["files.write:*"],
|
|
1269
|
+
},
|
|
1220
1270
|
{
|
|
1221
1271
|
name: "useFileSignature",
|
|
1222
1272
|
signature: "useFileSignature(fileId, existingSignatureId)",
|
|
@@ -2688,10 +2738,10 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2688
2738
|
filestore: {
|
|
2689
2739
|
description:
|
|
2690
2740
|
"Injected @colixsystems/filestore-client instance — the end-user file archive (project / personal / public spaces) + BankID file signing. " +
|
|
2691
|
-
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), remove(id), preview(id) }, " +
|
|
2741
|
+
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), copy(id, body), remove(id), preview(id) }, " +
|
|
2692
2742
|
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
2693
2743
|
"signatures: { initiate(fileId), status(id), cancel(id), verify(id) }, objectUrl(token), fetchObject(token) }. " +
|
|
2694
|
-
"Backs useFilestoreFiles() + useFilestoreFolders() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
2744
|
+
"Backs useFilestoreFiles() + useFilestoreFolders() + useFilestoreCreateFolder() + useFilestoreCopy() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
2695
2745
|
required: false,
|
|
2696
2746
|
fields: { files: "object", folders: "object", shares: "object", signatures: "object" },
|
|
2697
2747
|
},
|
|
@@ -4535,10 +4585,10 @@ const CONTRACT = deepFreeze({
|
|
|
4535
4585
|
// field bound, so "our brand colour at 20%" forced the author back to a
|
|
4536
4586
|
// frozen hex, which then stopped following a rebrand — the exact thing
|
|
4537
4587
|
// binding exists to prevent. `parseThemeColorTokenRef` /
|
|
4538
|
-
// `formatThemeColorTokenRef` publish the grammar and
|
|
4588
|
+
// `formatThemeColorTokenRef` publish the grammar, and
|
|
4539
4589
|
// `withHexAlpha` / `hexAlphaPercent` the percent-to-byte conversion the
|
|
4540
|
-
// Studio's control and both hosts' resolver
|
|
4541
|
-
// painted. The BARE form inherits the token's own opacity (a brand colour
|
|
4590
|
+
// Studio's control and both hosts' resolver answer alike, so 20% authored
|
|
4591
|
+
// is 20% painted. The BARE form inherits the token's own opacity (a brand colour
|
|
4542
4592
|
// may be authored translucent), which makes `/100` a real value rather
|
|
4543
4593
|
// than a redundant spelling: it is how "opaque, whatever the brand does"
|
|
4544
4594
|
// is said. Additive: an unsuffixed binding parses and resolves exactly as
|
|
@@ -4549,7 +4599,11 @@ const CONTRACT = deepFreeze({
|
|
|
4549
4599
|
// it removed every `<...>`, so an AI reply lost `Promise<void>`. The
|
|
4550
4600
|
// emphasis grammar now follows CommonMark flanking rules, so `created_at`
|
|
4551
4601
|
// keeps its underscore and `2 * 4` keeps its asterisk.
|
|
4552
|
-
|
|
4602
|
+
// 1.130.0: additive (sc-9057) — `useFilestoreCreateFolder` and
|
|
4603
|
+
// `useFilestoreCopy`. Widgets can now create Filestore folders and copy a
|
|
4604
|
+
// file into another folder or space (project / public / personal); the
|
|
4605
|
+
// filestore client gains `files.copy`.
|
|
4606
|
+
version: "1.130.0",
|
|
4553
4607
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4554
4608
|
hooks: HOOKS,
|
|
4555
4609
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -3850,7 +3850,8 @@ export function useAssetsByTag(tag, options) {
|
|
|
3850
3850
|
* FILESTORE CLIENT — ctx.filestore (@colixsystems/filestore-client)
|
|
3851
3851
|
*
|
|
3852
3852
|
* The end-user file archive (project / personal spaces) + BankID file signing.
|
|
3853
|
-
* Covers: useFilestoreFiles,
|
|
3853
|
+
* Covers: useFilestoreFiles, useFilestoreFolders, useFilestoreCreateFolder,
|
|
3854
|
+
* useFilestoreCopy, useFilestoreUpload, useFileSignature.
|
|
3854
3855
|
* ==========================================================================*/
|
|
3855
3856
|
|
|
3856
3857
|
// Resolve the owner_id a filestore query needs from the host context: a PROJECT
|
|
@@ -4302,6 +4303,153 @@ export function useFilestoreFolders(options) {
|
|
|
4302
4303
|
return { folders, loading, error, refetch };
|
|
4303
4304
|
}
|
|
4304
4305
|
|
|
4306
|
+
/**
|
|
4307
|
+
* Create a folder in the end-user's Filestore space. Returns `{ createFolder,
|
|
4308
|
+
* creating, error, lastCreated }`. The widget passes the SPACE (`{ spaceType,
|
|
4309
|
+
* parentFolderId? }`); the hook resolves owner_id from the host context and
|
|
4310
|
+
* posts `{ name, space_type, owner_id, parent_folder_id }` through
|
|
4311
|
+
* `ctx.filestore.folders.create`.
|
|
4312
|
+
*
|
|
4313
|
+
* `createFolder(name, { parentFolderId? })` resolves to the created folder row
|
|
4314
|
+
* or throws the wire error: 422 for an invalid name, 404 for a parent that is
|
|
4315
|
+
* not in this space, and 403/404 when the caller may not add a folder there
|
|
4316
|
+
* (403 when they can see the position). The server's
|
|
4317
|
+
* `FOLDER_CREATE_NOT_PERMITTED` is at `err.details.code`.
|
|
4318
|
+
*/
|
|
4319
|
+
export function useFilestoreCreateFolder(options) {
|
|
4320
|
+
const ctx = useWidgetContextOrThrow("useFilestoreCreateFolder");
|
|
4321
|
+
if (
|
|
4322
|
+
!ctx.filestore ||
|
|
4323
|
+
!ctx.filestore.folders ||
|
|
4324
|
+
typeof ctx.filestore.folders.create !== "function"
|
|
4325
|
+
) {
|
|
4326
|
+
throw new Error(
|
|
4327
|
+
"useFilestoreCreateFolder: host did not inject a filestore client (ctx.filestore.folders.create)",
|
|
4328
|
+
);
|
|
4329
|
+
}
|
|
4330
|
+
const { spaceType = "project", parentFolderId: defaultParentId = null } =
|
|
4331
|
+
options || {};
|
|
4332
|
+
const ownerId = _filestoreOwnerId(ctx, spaceType);
|
|
4333
|
+
|
|
4334
|
+
const [creating, setCreating] = useState(false);
|
|
4335
|
+
const [error, setError] = useState(null);
|
|
4336
|
+
const [lastCreated, setLastCreated] = useState(null);
|
|
4337
|
+
|
|
4338
|
+
const foldersRef = useRef(ctx.filestore.folders);
|
|
4339
|
+
foldersRef.current = ctx.filestore.folders;
|
|
4340
|
+
|
|
4341
|
+
const createFolder = useCallback(
|
|
4342
|
+
async (name, overrides) => {
|
|
4343
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
4344
|
+
throw new Error("useFilestoreCreateFolder: name is required");
|
|
4345
|
+
}
|
|
4346
|
+
if (!ownerId) {
|
|
4347
|
+
const err = new Error("Sign in to create a folder");
|
|
4348
|
+
setError(err);
|
|
4349
|
+
throw err;
|
|
4350
|
+
}
|
|
4351
|
+
const parentFolderId =
|
|
4352
|
+
overrides &&
|
|
4353
|
+
Object.prototype.hasOwnProperty.call(overrides, "parentFolderId")
|
|
4354
|
+
? overrides.parentFolderId
|
|
4355
|
+
: defaultParentId;
|
|
4356
|
+
const body = {
|
|
4357
|
+
name: name.trim(),
|
|
4358
|
+
space_type: String(spaceType || "project").toUpperCase(),
|
|
4359
|
+
owner_id: ownerId,
|
|
4360
|
+
};
|
|
4361
|
+
if (parentFolderId) body.parent_folder_id = parentFolderId;
|
|
4362
|
+
setCreating(true);
|
|
4363
|
+
setError(null);
|
|
4364
|
+
try {
|
|
4365
|
+
const created = await foldersRef.current.create(body);
|
|
4366
|
+
setLastCreated(created || null);
|
|
4367
|
+
setCreating(false);
|
|
4368
|
+
return created;
|
|
4369
|
+
} catch (err) {
|
|
4370
|
+
setError(err);
|
|
4371
|
+
setCreating(false);
|
|
4372
|
+
throw err;
|
|
4373
|
+
}
|
|
4374
|
+
},
|
|
4375
|
+
[ownerId, defaultParentId, spaceType],
|
|
4376
|
+
);
|
|
4377
|
+
|
|
4378
|
+
return { createFolder, creating, error, lastCreated };
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
/**
|
|
4382
|
+
* Copy a Filestore file into any space and folder the caller may add files
|
|
4383
|
+
* to — including ACROSS spaces (project, public, the user's personal space).
|
|
4384
|
+
* Returns `{ copy, copying, error, lastCopied }`.
|
|
4385
|
+
*
|
|
4386
|
+
* `copy(fileId, { spaceType, folderId?, name? })` names the DESTINATION;
|
|
4387
|
+
* `spaceType` is required. The hook resolves the destination owner_id the same
|
|
4388
|
+
* way the upload hook does and posts through `ctx.filestore.files.copy`. It
|
|
4389
|
+
* resolves to the NEW file row (its own id and url); the source is untouched.
|
|
4390
|
+
* A name already taken at the destination lands as "name (2).ext" rather than
|
|
4391
|
+
* replacing that file.
|
|
4392
|
+
*
|
|
4393
|
+
* Errors: 404 when the source is not visible, 403 `FILE_COPY_NOT_PERMITTED`
|
|
4394
|
+
* when it is view-only, 403/404 `UPLOAD_NOT_PERMITTED` when the destination
|
|
4395
|
+
* refuses the add, 413 `STORAGE_QUOTA_EXCEEDED` when the destination space is
|
|
4396
|
+
* full, 409 `FILE_PROCESSING` while a video is still being processed. The code
|
|
4397
|
+
* is at `err.details.code`.
|
|
4398
|
+
*/
|
|
4399
|
+
export function useFilestoreCopy() {
|
|
4400
|
+
const ctx = useWidgetContextOrThrow("useFilestoreCopy");
|
|
4401
|
+
if (
|
|
4402
|
+
!ctx.filestore ||
|
|
4403
|
+
!ctx.filestore.files ||
|
|
4404
|
+
typeof ctx.filestore.files.copy !== "function"
|
|
4405
|
+
) {
|
|
4406
|
+
throw new Error(
|
|
4407
|
+
"useFilestoreCopy: host did not inject a filestore client (ctx.filestore.files.copy)",
|
|
4408
|
+
);
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
const [copying, setCopying] = useState(false);
|
|
4412
|
+
const [error, setError] = useState(null);
|
|
4413
|
+
const [lastCopied, setLastCopied] = useState(null);
|
|
4414
|
+
|
|
4415
|
+
const ctxRef = useRef(ctx);
|
|
4416
|
+
ctxRef.current = ctx;
|
|
4417
|
+
|
|
4418
|
+
const copy = useCallback(async (fileId, destination) => {
|
|
4419
|
+
if (!fileId) throw new Error("useFilestoreCopy: fileId is required");
|
|
4420
|
+
const { spaceType, folderId, name } = destination || {};
|
|
4421
|
+
if (!spaceType) {
|
|
4422
|
+
throw new Error("useFilestoreCopy: destination spaceType is required");
|
|
4423
|
+
}
|
|
4424
|
+
const ownerId = _filestoreOwnerId(ctxRef.current, spaceType);
|
|
4425
|
+
if (!ownerId) {
|
|
4426
|
+
const err = new Error("Sign in to copy files");
|
|
4427
|
+
setError(err);
|
|
4428
|
+
throw err;
|
|
4429
|
+
}
|
|
4430
|
+
const body = {
|
|
4431
|
+
space_type: String(spaceType).toUpperCase(),
|
|
4432
|
+
owner_id: ownerId,
|
|
4433
|
+
};
|
|
4434
|
+
if (folderId) body.folder_id = folderId;
|
|
4435
|
+
if (name) body.name = name;
|
|
4436
|
+
setCopying(true);
|
|
4437
|
+
setError(null);
|
|
4438
|
+
try {
|
|
4439
|
+
const created = await ctxRef.current.filestore.files.copy(fileId, body);
|
|
4440
|
+
setLastCopied(created || null);
|
|
4441
|
+
setCopying(false);
|
|
4442
|
+
return created;
|
|
4443
|
+
} catch (err) {
|
|
4444
|
+
setError(err);
|
|
4445
|
+
setCopying(false);
|
|
4446
|
+
throw err;
|
|
4447
|
+
}
|
|
4448
|
+
}, []);
|
|
4449
|
+
|
|
4450
|
+
return { copy, copying, error, lastCopied };
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4305
4453
|
/**
|
|
4306
4454
|
* Drive a BankID signing flow for one Filestore file. Returns
|
|
4307
4455
|
* `{ status, qr, autoStartToken, message, signerName, signedAt, verdict,
|
package/dist/host.js
CHANGED
|
@@ -74,6 +74,9 @@ export {
|
|
|
74
74
|
// SIDEBAR local menu hangs in the rail and the drawer. The compiler asks it
|
|
75
75
|
// once per screen at build time and the Player once per render, so the two
|
|
76
76
|
// chromes derive the same tree from the same code.
|
|
77
|
+
// REQ-NAV-LOCAL (sc-8901): `resolveLocalNavModel` answers WHICH model a page
|
|
78
|
+
// wears in one device view, so the same menu can be a bottom bar on a phone and
|
|
79
|
+
// unfold in the rail on a desktop.
|
|
77
80
|
export {
|
|
78
81
|
normaliseNavigation,
|
|
79
82
|
menuItemCap,
|
|
@@ -94,6 +97,8 @@ export {
|
|
|
94
97
|
DEFAULT_TOP_STRIPE_HEIGHT_PX,
|
|
95
98
|
DEFAULT_TOP_STRIPE_PADDING_X_PX,
|
|
96
99
|
resolveSidebarUnfold,
|
|
100
|
+
resolveLocalNavModel,
|
|
101
|
+
drawsLocalNavAnywhere,
|
|
97
102
|
} from "./navigation.js";
|
|
98
103
|
|
|
99
104
|
// sc-5717: the codec behind `useWidgetRoute()` — how a widget's own internal
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/dist/navigation.cjs
CHANGED
|
@@ -675,6 +675,64 @@ function resolveSidebarUnfold(input) {
|
|
|
675
675
|
return { under: section.key, items: marked, standalone: true };
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
+
/**
|
|
679
|
+
* REQ-NAV-LOCAL (sc-8901) — which local-navigation model a page wears in ONE
|
|
680
|
+
* device view, so the same saved menu can be a bottom bar on a phone and unfold
|
|
681
|
+
* in the rail on a desktop.
|
|
682
|
+
*
|
|
683
|
+
* `local_nav` is the MOBILE value and the base of the cascade: a page authored
|
|
684
|
+
* before the wider views existed leaves both of them unset and therefore
|
|
685
|
+
* renders identically at every width. The cascade is REQ-LAY-07's — the one the
|
|
686
|
+
* layout grid and every responsive widget prop already use — because a page's
|
|
687
|
+
* chrome and its content must not disagree about what a tablet is.
|
|
688
|
+
*
|
|
689
|
+
* The vocabulary stays the API's: any non-empty string is a model and the
|
|
690
|
+
* host's own switch decides what it can draw, so a kind a newer backend allows
|
|
691
|
+
* cascades correctly here instead of being flattened to `NONE` on the way.
|
|
692
|
+
*
|
|
693
|
+
* @param {object|null|undefined} page — a page row carrying `local_nav` and,
|
|
694
|
+
* optionally, `local_nav_tablet` / `local_nav_desktop`.
|
|
695
|
+
* @param {"mobile"|"tablet"|"desktop"} view — the device view being drawn.
|
|
696
|
+
* @returns {string} the model for that view; `NONE` when the page sets none.
|
|
697
|
+
*/
|
|
698
|
+
function resolveLocalNavModel(page, view) {
|
|
699
|
+
const at = (key) => {
|
|
700
|
+
const value = isPlainObject(page) ? page[key] : null;
|
|
701
|
+
return typeof value === "string" && value ? value : null;
|
|
702
|
+
};
|
|
703
|
+
const mobile = at("local_nav") || "NONE";
|
|
704
|
+
if (view === "desktop") {
|
|
705
|
+
return at("local_nav_desktop") || at("local_nav_tablet") || mobile;
|
|
706
|
+
}
|
|
707
|
+
if (view === "tablet") return at("local_nav_tablet") || mobile;
|
|
708
|
+
return mobile;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// sc-8901: the three device views a local bar can differ across — REQ-LAY-07's
|
|
712
|
+
// own list, so a page's chrome and its content classify a width alike. Ordered
|
|
713
|
+
// narrowest first, which is also the cascade's direction.
|
|
714
|
+
const LOCAL_NAV_VIEWS = ["mobile", "tablet", "desktop"];
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* REQ-NAV-LOCAL (sc-8901) — does this page draw a local bar in ANY device view?
|
|
718
|
+
*
|
|
719
|
+
* The saved-menu reference is shared by all three views, so it is required — and
|
|
720
|
+
* resolved onto the wire — as soon as one of them draws. Asking
|
|
721
|
+
* `local_nav !== "NONE"` alone would miss a page that carries a bar only on the
|
|
722
|
+
* wider views.
|
|
723
|
+
*
|
|
724
|
+
* Beside the cascade it is derived from, so the API rule, the export's screen
|
|
725
|
+
* bake and Copy Workspace cannot disagree about which pages have a bar.
|
|
726
|
+
*
|
|
727
|
+
* @param {object|null|undefined} page — a page row.
|
|
728
|
+
* @returns {boolean}
|
|
729
|
+
*/
|
|
730
|
+
function drawsLocalNavAnywhere(page) {
|
|
731
|
+
return LOCAL_NAV_VIEWS.some(
|
|
732
|
+
(view) => resolveLocalNavModel(page, view) !== "NONE",
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
|
|
678
736
|
// REQ-THEME-LOOK: the divider's width in px, bounded HERE so both hosts inherit
|
|
679
737
|
// one range. The export clamped it on its way into the StyleSheet
|
|
680
738
|
// (`coerceBorderWidth(...) ?? 1`) where the Player passed any finite number
|
|
@@ -711,4 +769,4 @@ function borderWidthOr(...values) {
|
|
|
711
769
|
return BORDER_WIDTH_MIN;
|
|
712
770
|
}
|
|
713
771
|
|
|
714
|
-
module.exports = { normaliseNavigation, menuItemCap, resolveFooterTokens, resolveSidebarTokens, resolveTopStripeLayout, resolveTopBarTokens, topStripeShowKey, TOP_STRIPE_ALWAYS_SHOWN_SLOTS, TOP_STRIPE_SLOTS, MIN_TOP_STRIPE_HEIGHT_PX, MAX_TOP_STRIPE_HEIGHT_PX, MIN_TOP_STRIPE_PADDING_X_PX, MAX_TOP_STRIPE_PADDING_X_PX, MAX_TOP_STRIPE_OFFSET_Y_PX, DEFAULT_TOP_STRIPE_HEIGHT_PX, DEFAULT_TOP_STRIPE_PADDING_X_PX, resolveSidebarUnfold };
|
|
772
|
+
module.exports = { normaliseNavigation, menuItemCap, resolveFooterTokens, resolveSidebarTokens, resolveTopStripeLayout, resolveTopBarTokens, topStripeShowKey, TOP_STRIPE_ALWAYS_SHOWN_SLOTS, TOP_STRIPE_SLOTS, MIN_TOP_STRIPE_HEIGHT_PX, MAX_TOP_STRIPE_HEIGHT_PX, MIN_TOP_STRIPE_PADDING_X_PX, MAX_TOP_STRIPE_PADDING_X_PX, MAX_TOP_STRIPE_OFFSET_Y_PX, DEFAULT_TOP_STRIPE_HEIGHT_PX, DEFAULT_TOP_STRIPE_PADDING_X_PX, resolveSidebarUnfold, resolveLocalNavModel, drawsLocalNavAnywhere };
|
package/dist/navigation.js
CHANGED
|
@@ -666,6 +666,64 @@ export function resolveSidebarUnfold(input) {
|
|
|
666
666
|
return { under: section.key, items: marked, standalone: true };
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
+
/**
|
|
670
|
+
* REQ-NAV-LOCAL (sc-8901) — which local-navigation model a page wears in ONE
|
|
671
|
+
* device view, so the same saved menu can be a bottom bar on a phone and unfold
|
|
672
|
+
* in the rail on a desktop.
|
|
673
|
+
*
|
|
674
|
+
* `local_nav` is the MOBILE value and the base of the cascade: a page authored
|
|
675
|
+
* before the wider views existed leaves both of them unset and therefore
|
|
676
|
+
* renders identically at every width. The cascade is REQ-LAY-07's — the one the
|
|
677
|
+
* layout grid and every responsive widget prop already use — because a page's
|
|
678
|
+
* chrome and its content must not disagree about what a tablet is.
|
|
679
|
+
*
|
|
680
|
+
* The vocabulary stays the API's: any non-empty string is a model and the
|
|
681
|
+
* host's own switch decides what it can draw, so a kind a newer backend allows
|
|
682
|
+
* cascades correctly here instead of being flattened to `NONE` on the way.
|
|
683
|
+
*
|
|
684
|
+
* @param {object|null|undefined} page — a page row carrying `local_nav` and,
|
|
685
|
+
* optionally, `local_nav_tablet` / `local_nav_desktop`.
|
|
686
|
+
* @param {"mobile"|"tablet"|"desktop"} view — the device view being drawn.
|
|
687
|
+
* @returns {string} the model for that view; `NONE` when the page sets none.
|
|
688
|
+
*/
|
|
689
|
+
export function resolveLocalNavModel(page, view) {
|
|
690
|
+
const at = (key) => {
|
|
691
|
+
const value = isPlainObject(page) ? page[key] : null;
|
|
692
|
+
return typeof value === "string" && value ? value : null;
|
|
693
|
+
};
|
|
694
|
+
const mobile = at("local_nav") || "NONE";
|
|
695
|
+
if (view === "desktop") {
|
|
696
|
+
return at("local_nav_desktop") || at("local_nav_tablet") || mobile;
|
|
697
|
+
}
|
|
698
|
+
if (view === "tablet") return at("local_nav_tablet") || mobile;
|
|
699
|
+
return mobile;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// sc-8901: the three device views a local bar can differ across — REQ-LAY-07's
|
|
703
|
+
// own list, so a page's chrome and its content classify a width alike. Ordered
|
|
704
|
+
// narrowest first, which is also the cascade's direction.
|
|
705
|
+
const LOCAL_NAV_VIEWS = ["mobile", "tablet", "desktop"];
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* REQ-NAV-LOCAL (sc-8901) — does this page draw a local bar in ANY device view?
|
|
709
|
+
*
|
|
710
|
+
* The saved-menu reference is shared by all three views, so it is required — and
|
|
711
|
+
* resolved onto the wire — as soon as one of them draws. Asking
|
|
712
|
+
* `local_nav !== "NONE"` alone would miss a page that carries a bar only on the
|
|
713
|
+
* wider views.
|
|
714
|
+
*
|
|
715
|
+
* Beside the cascade it is derived from, so the API rule, the export's screen
|
|
716
|
+
* bake and Copy Workspace cannot disagree about which pages have a bar.
|
|
717
|
+
*
|
|
718
|
+
* @param {object|null|undefined} page — a page row.
|
|
719
|
+
* @returns {boolean}
|
|
720
|
+
*/
|
|
721
|
+
export function drawsLocalNavAnywhere(page) {
|
|
722
|
+
return LOCAL_NAV_VIEWS.some(
|
|
723
|
+
(view) => resolveLocalNavModel(page, view) !== "NONE",
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
669
727
|
// REQ-THEME-LOOK: the divider's width in px, bounded HERE so both hosts inherit
|
|
670
728
|
// one range. The export clamped it on its way into the StyleSheet
|
|
671
729
|
// (`coerceBorderWidth(...) ?? 1`) where the Player passed any finite number
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.169.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",
|