@colixsystems/widget-sdk 0.174.0 → 0.176.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 +12 -2
- package/dist/contract.cjs +42 -10
- package/dist/contract.js +42 -10
- package/dist/hooks.js +81 -4
- package/dist/manifest.cjs +6 -1
- package/dist/manifest.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
60
60
|
| **DIRECTORY** | `useGroups(query)` (optional: query) | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
|
|
61
61
|
| **DIRECTORY** | `useInvites(query)` (optional: query) | `{ invites, loading, error, refetch, resend, revoke }` | `directory.invites.*` — `users.write:*` + the SystemAcl `users.write` capability (the whole invite surface, list included). `query` is `{ status?, limit?, offset? }` with `status` ∈ `pending \| accepted \| revoked \| expired \| all` (endpoint default `all`). |
|
|
62
62
|
| **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
|
|
63
|
-
| **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId })` (optional: folderId) | `{ exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.exportPdf` — `files.write:*`. `
|
|
63
|
+
| **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId })` (optional: spaceType, folderId) | `{ openPdf, opening, canOpenPdf, exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.renderPdf` + `ctx.device.pdfViewer`, `ctx.filestore.files.exportPdf` — `files.write:*`. `openPdf(html, { fileName? })` renders the HTML to a PDF server-side and OPENS it (web: PDF tab; native: print / share sheet) without saving. `exportToPdf(html, { fileName?, folderId? })` renders it and saves it as a file (`application/pdf`). Same server-side renderer on web + native. |
|
|
64
64
|
| **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser`. Rejects with `PaymentError { code, message, retryable }`; when `retryable` is `false` show `message` and drop the retry. Charges are accepted ONLY in the currency the workspace sells in — omit `currency` and the platform applies it (a disagreeing literal is a publish-blocking `payment-currency` finding). |
|
|
65
65
|
| **NOTIFICATIONS** (`ctx.notifications`) | `useSendNotification()` | `{ send, sending, error }` | `ctx.notifications.send` — `notifications.send:appUser`. `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace; call from an event handler (never render); rejects with `NotificationError`. |
|
|
66
66
|
| **IDENTIFICATION** (`ctx.identification`) | `useIdentification({ provider, purpose, pollIntervalMs })` (optional: every key, and the whole argument) | `{ available, status, qr, autoStartToken, message, identity, identificationId, start, refresh, cancel, reset, … }` | `ctx.identification.*` — no scope (the visitor is deliberately NOT signed in). Gate the UI on `available`; `start()` opens the order and the hook polls to completion. `identity` carries `personal_number_masked` + a stable `subject_hash` — never a raw personal number. |
|
|
@@ -73,7 +73,17 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
73
73
|
|
|
74
74
|
## Status
|
|
75
75
|
|
|
76
|
-
`v0.
|
|
76
|
+
`v0.176.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.176.0 (contract 1.137.0)
|
|
79
|
+
|
|
80
|
+
**`manifest.description` is optional.** A widget publishes with a name and no description; when present, the description must still be a string. The storefront shows no blurb for a widget without one.
|
|
81
|
+
|
|
82
|
+
### What's new in 0.175.0 (contract 1.136.0)
|
|
83
|
+
|
|
84
|
+
**Open a PDF for the user to view, print or share, without saving it (sc-9135).** `usePdfExport()` gains `openPdf(html, { fileName? })`, `opening` and `canOpenPdf`. The HTML is rendered server-side by the same headless-Chromium pipeline as `exportToPdf`, and the host opens the result: a new browser tab with the PDF viewer on the web Player, and the OS print and share sheet in the native export (`expo-print`). Nothing is stored, so a widget that only opens PDFs calls `usePdfExport()` with no arguments. Call `openPdf` directly from the press handler, because the web host opens its tab inside that press and a tab opened after an `await` is refused by the popup blocker. `canOpenPdf` is false on a host with no PDF viewer, where `openPdf` rejects with `err.code` `UNSUPPORTED`. The wire errors arrive on `err.status`: 401 not signed in, 413 over the size cap, 429 too many renders, 503 renderer busy. The HTML runs no scripts and loads only `https:` / `data:` images and `https:` fonts. On native, closing the print sheet resolves `openPdf` like closing the web tab does. Needs `@colixsystems/filestore-client@0.11.0` (`files.renderPdf`). The host slice is the new `device.pdfViewer`.
|
|
85
|
+
|
|
86
|
+
**Breaking for authors: `createObjectURL` is now a banned API** (lint rule `no-createobjecturl`). Widgets used it to open a hand-built plain-text blob as a stand-in "print version": it only works in a browser, and on the web it opens widget-built bytes on the host's origin. A widget version that calls it fails lint at submission; versions already published keep running. Open a generated document with `openPdf`, upload a file with `useFilestoreUpload`, and preview a picked photo from the `uri` `useCamera` returns.
|
|
77
87
|
|
|
78
88
|
### What's new in 0.174.0 (contract 1.135.0)
|
|
79
89
|
|
package/dist/contract.cjs
CHANGED
|
@@ -1179,9 +1179,22 @@ const HOOKS = [
|
|
|
1179
1179
|
{
|
|
1180
1180
|
name: "usePdfExport",
|
|
1181
1181
|
signature: "usePdfExport({ spaceType, folderId })",
|
|
1182
|
-
optionalArgs: ["folderId"],
|
|
1183
|
-
description:
|
|
1184
|
-
"Render an HTML string to a PDF server-side
|
|
1182
|
+
optionalArgs: ["spaceType", "folderId"],
|
|
1183
|
+
description:
|
|
1184
|
+
"Render an HTML string to a PDF server-side, then either OPEN it or SAVE " +
|
|
1185
|
+
"it. `openPdf(html, { fileName? })` is THE way to give the user a PDF " +
|
|
1186
|
+
"to view, print, download or share: it renders the HTML and opens the " +
|
|
1187
|
+
"result — the browser's PDF viewer in a new tab on the web Player, the " +
|
|
1188
|
+
"OS print / share sheet in the native export — and SAVES NOTHING, so " +
|
|
1189
|
+
"a widget that only opens PDFs calls `usePdfExport()` with no " +
|
|
1190
|
+
"arguments (`spaceType` names where exportToPdf saves). Call " +
|
|
1191
|
+
"it straight from the press handler (the web tab opens before the " +
|
|
1192
|
+
"render so popup blockers allow it). It resolves once the document is " +
|
|
1193
|
+
"handed to the viewer and rejects with `err.code` UNSUPPORTED when the " +
|
|
1194
|
+
"host has no PDF viewer (check `canOpenPdf`), or the wire error " +
|
|
1195
|
+
"(`err.status` 401 not signed in, 413 over the size cap, 429 too many " +
|
|
1196
|
+
"renders, 503 renderer busy — try again). The rest of this entry is " +
|
|
1197
|
+
"the SAVE path. `exportToPdf` renders and SAVES it as a file in " +
|
|
1185
1198
|
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
1186
1199
|
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
1187
1200
|
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
@@ -1194,6 +1207,9 @@ const HOOKS = [
|
|
|
1194
1207
|
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
1195
1208
|
"Player and the native export, so no browser-only PDF library is used.",
|
|
1196
1209
|
returnShape: {
|
|
1210
|
+
openPdf: "(html, { fileName? }) => Promise<void>",
|
|
1211
|
+
opening: "boolean",
|
|
1212
|
+
canOpenPdf: "boolean",
|
|
1197
1213
|
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
1198
1214
|
exporting: "boolean",
|
|
1199
1215
|
error: "Error | null",
|
|
@@ -2582,8 +2598,8 @@ const MANIFEST_SCHEMA = {
|
|
|
2582
2598
|
},
|
|
2583
2599
|
description: {
|
|
2584
2600
|
type: "string",
|
|
2585
|
-
required:
|
|
2586
|
-
description: "1-2 sentence storefront description.",
|
|
2601
|
+
required: false,
|
|
2602
|
+
description: "Optional. 1-2 sentence storefront description.",
|
|
2587
2603
|
},
|
|
2588
2604
|
author: {
|
|
2589
2605
|
type: "object",
|
|
@@ -2958,10 +2974,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2958
2974
|
"speech: { isSupported() -> boolean, start(options, { onResult, onError, onEnd }) -> Promise<{ stop(), abort() }> }, " +
|
|
2959
2975
|
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }, " +
|
|
2960
2976
|
"imageEditor: { isSupported() -> boolean, edit(uri, actions, options?) -> Promise<asset> }, " +
|
|
2961
|
-
"barcode: { isSupported() -> boolean, scan(options?) -> Promise<{ value, format } | null> }
|
|
2962
|
-
"
|
|
2963
|
-
"
|
|
2964
|
-
"
|
|
2977
|
+
"barcode: { isSupported() -> boolean, scan(options?) -> Promise<{ value, format } | null> }, " +
|
|
2978
|
+
"pdfViewer: { isSupported() -> boolean, open({ fileName, load: () => Promise<ArrayBuffer> }) -> Promise<void> } }. " +
|
|
2979
|
+
"Backs useGeolocation(), useSpeechToText(), useCamera(), useImageEditor(), useBarcodeScanner() and usePdfExport().openPdf. The web Player brokers them via " +
|
|
2980
|
+
"navigator.geolocation, window.SpeechRecognition, a getUserMedia camera preview, a host-side canvas, BarcodeDetector and a new browser tab; the Expo export via " +
|
|
2981
|
+
"expo-location, expo-speech-recognition, expo-image-picker, expo-image-manipulator, expo-camera and expo-print. " +
|
|
2982
|
+
"pdfViewer.open must be CALLED synchronously from the user's press: the web broker opens its tab before awaiting `load` " +
|
|
2983
|
+
"(the server render), which is what keeps a popup blocker from refusing it; the Expo export writes the bytes to the " +
|
|
2984
|
+
"cache and hands them to the OS print sheet. " +
|
|
2965
2985
|
"imageEditor.edit applies resize / crop / rotate / flip in order and rejects with an ImageEditorError " +
|
|
2966
2986
|
"(.code UNSUPPORTED | INVALID_ACTION | DECODE_FAILED | ENCODE_FAILED | INTERNAL). " +
|
|
2967
2987
|
"getCurrentPosition rejects with a GeolocationError (.code PERMISSION_DENIED | UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL). " +
|
|
@@ -2989,6 +3009,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2989
3009
|
camera: "object",
|
|
2990
3010
|
imageEditor: "object",
|
|
2991
3011
|
barcode: "object",
|
|
3012
|
+
pdfViewer: "object",
|
|
2992
3013
|
},
|
|
2993
3014
|
},
|
|
2994
3015
|
};
|
|
@@ -3053,6 +3074,11 @@ const BANNED_APIS = [
|
|
|
3053
3074
|
reason: "Dynamic import bypasses the loader's allowlist.",
|
|
3054
3075
|
},
|
|
3055
3076
|
{ identifier: "globalThis", reason: "Host environment escape." },
|
|
3077
|
+
{
|
|
3078
|
+
identifier: "createObjectURL",
|
|
3079
|
+
reason:
|
|
3080
|
+
"Web-only and opens widget-built bytes on the host origin; open a PDF with usePdfExport().openPdf.",
|
|
3081
|
+
},
|
|
3056
3082
|
];
|
|
3057
3083
|
|
|
3058
3084
|
// REQ-WSDK-PLATFORM §3.4, §5: vetted package allowlist. Each entry is a
|
|
@@ -4684,7 +4710,13 @@ const CONTRACT = deepFreeze({
|
|
|
4684
4710
|
// options and a new `options` arg on useDatastoreRecord stamp each row
|
|
4685
4711
|
// with `_capabilities`, so a widget shows Edit/Delete only on rows the
|
|
4686
4712
|
// user may change. New linter warning `row-action-not-gated`.
|
|
4687
|
-
|
|
4713
|
+
// 1.136.0: additive (sc-9135) — `usePdfExport()` gains `openPdf(html,
|
|
4714
|
+
// { fileName? })`, `opening` and `canOpenPdf`: render server-side and
|
|
4715
|
+
// OPEN the PDF (web: browser viewer tab; native: expo-print sheet)
|
|
4716
|
+
// without saving it, through the new `device.pdfViewer` host slice. And
|
|
4717
|
+
// `createObjectURL` joins `bannedApis`: widgets reached for it to fake a
|
|
4718
|
+
// "print version" as a plain-text blob that never worked on native.
|
|
4719
|
+
version: "1.137.0",
|
|
4688
4720
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4689
4721
|
hooks: HOOKS,
|
|
4690
4722
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1179,9 +1179,22 @@ const HOOKS = [
|
|
|
1179
1179
|
{
|
|
1180
1180
|
name: "usePdfExport",
|
|
1181
1181
|
signature: "usePdfExport({ spaceType, folderId })",
|
|
1182
|
-
optionalArgs: ["folderId"],
|
|
1183
|
-
description:
|
|
1184
|
-
"Render an HTML string to a PDF server-side
|
|
1182
|
+
optionalArgs: ["spaceType", "folderId"],
|
|
1183
|
+
description:
|
|
1184
|
+
"Render an HTML string to a PDF server-side, then either OPEN it or SAVE " +
|
|
1185
|
+
"it. `openPdf(html, { fileName? })` is THE way to give the user a PDF " +
|
|
1186
|
+
"to view, print, download or share: it renders the HTML and opens the " +
|
|
1187
|
+
"result — the browser's PDF viewer in a new tab on the web Player, the " +
|
|
1188
|
+
"OS print / share sheet in the native export — and SAVES NOTHING, so " +
|
|
1189
|
+
"a widget that only opens PDFs calls `usePdfExport()` with no " +
|
|
1190
|
+
"arguments (`spaceType` names where exportToPdf saves). Call " +
|
|
1191
|
+
"it straight from the press handler (the web tab opens before the " +
|
|
1192
|
+
"render so popup blockers allow it). It resolves once the document is " +
|
|
1193
|
+
"handed to the viewer and rejects with `err.code` UNSUPPORTED when the " +
|
|
1194
|
+
"host has no PDF viewer (check `canOpenPdf`), or the wire error " +
|
|
1195
|
+
"(`err.status` 401 not signed in, 413 over the size cap, 429 too many " +
|
|
1196
|
+
"renders, 503 renderer busy — try again). The rest of this entry is " +
|
|
1197
|
+
"the SAVE path. `exportToPdf` renders and SAVES it as a file in " +
|
|
1185
1198
|
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
1186
1199
|
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
1187
1200
|
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
@@ -1194,6 +1207,9 @@ const HOOKS = [
|
|
|
1194
1207
|
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
1195
1208
|
"Player and the native export, so no browser-only PDF library is used.",
|
|
1196
1209
|
returnShape: {
|
|
1210
|
+
openPdf: "(html, { fileName? }) => Promise<void>",
|
|
1211
|
+
opening: "boolean",
|
|
1212
|
+
canOpenPdf: "boolean",
|
|
1197
1213
|
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
1198
1214
|
exporting: "boolean",
|
|
1199
1215
|
error: "Error | null",
|
|
@@ -2578,8 +2594,8 @@ const MANIFEST_SCHEMA = {
|
|
|
2578
2594
|
},
|
|
2579
2595
|
description: {
|
|
2580
2596
|
type: "string",
|
|
2581
|
-
required:
|
|
2582
|
-
description: "1-2 sentence storefront description.",
|
|
2597
|
+
required: false,
|
|
2598
|
+
description: "Optional. 1-2 sentence storefront description.",
|
|
2583
2599
|
},
|
|
2584
2600
|
author: {
|
|
2585
2601
|
type: "object",
|
|
@@ -2954,10 +2970,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2954
2970
|
"speech: { isSupported() -> boolean, start(options, { onResult, onError, onEnd }) -> Promise<{ stop(), abort() }> }, " +
|
|
2955
2971
|
"camera: { isSupported() -> boolean, capture(options?) -> Promise<asset | null>, pick(options?) -> Promise<asset | null> }, " +
|
|
2956
2972
|
"imageEditor: { isSupported() -> boolean, edit(uri, actions, options?) -> Promise<asset> }, " +
|
|
2957
|
-
"barcode: { isSupported() -> boolean, scan(options?) -> Promise<{ value, format } | null> }
|
|
2958
|
-
"
|
|
2959
|
-
"
|
|
2960
|
-
"
|
|
2973
|
+
"barcode: { isSupported() -> boolean, scan(options?) -> Promise<{ value, format } | null> }, " +
|
|
2974
|
+
"pdfViewer: { isSupported() -> boolean, open({ fileName, load: () => Promise<ArrayBuffer> }) -> Promise<void> } }. " +
|
|
2975
|
+
"Backs useGeolocation(), useSpeechToText(), useCamera(), useImageEditor(), useBarcodeScanner() and usePdfExport().openPdf. The web Player brokers them via " +
|
|
2976
|
+
"navigator.geolocation, window.SpeechRecognition, a getUserMedia camera preview, a host-side canvas, BarcodeDetector and a new browser tab; the Expo export via " +
|
|
2977
|
+
"expo-location, expo-speech-recognition, expo-image-picker, expo-image-manipulator, expo-camera and expo-print. " +
|
|
2978
|
+
"pdfViewer.open must be CALLED synchronously from the user's press: the web broker opens its tab before awaiting `load` " +
|
|
2979
|
+
"(the server render), which is what keeps a popup blocker from refusing it; the Expo export writes the bytes to the " +
|
|
2980
|
+
"cache and hands them to the OS print sheet. " +
|
|
2961
2981
|
"imageEditor.edit applies resize / crop / rotate / flip in order and rejects with an ImageEditorError " +
|
|
2962
2982
|
"(.code UNSUPPORTED | INVALID_ACTION | DECODE_FAILED | ENCODE_FAILED | INTERNAL). " +
|
|
2963
2983
|
"getCurrentPosition rejects with a GeolocationError (.code PERMISSION_DENIED | UNAVAILABLE | TIMEOUT | UNSUPPORTED | INTERNAL). " +
|
|
@@ -2985,6 +3005,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
2985
3005
|
camera: "object",
|
|
2986
3006
|
imageEditor: "object",
|
|
2987
3007
|
barcode: "object",
|
|
3008
|
+
pdfViewer: "object",
|
|
2988
3009
|
},
|
|
2989
3010
|
},
|
|
2990
3011
|
};
|
|
@@ -3049,6 +3070,11 @@ const BANNED_APIS = [
|
|
|
3049
3070
|
reason: "Dynamic import bypasses the loader's allowlist.",
|
|
3050
3071
|
},
|
|
3051
3072
|
{ identifier: "globalThis", reason: "Host environment escape." },
|
|
3073
|
+
{
|
|
3074
|
+
identifier: "createObjectURL",
|
|
3075
|
+
reason:
|
|
3076
|
+
"Web-only and opens widget-built bytes on the host origin; open a PDF with usePdfExport().openPdf.",
|
|
3077
|
+
},
|
|
3052
3078
|
];
|
|
3053
3079
|
|
|
3054
3080
|
// REQ-WSDK-PLATFORM §3.4, §5: vetted package allowlist. Each entry is a
|
|
@@ -4680,7 +4706,13 @@ const CONTRACT = deepFreeze({
|
|
|
4680
4706
|
// options and a new `options` arg on useDatastoreRecord stamp each row
|
|
4681
4707
|
// with `_capabilities`, so a widget shows Edit/Delete only on rows the
|
|
4682
4708
|
// user may change. New linter warning `row-action-not-gated`.
|
|
4683
|
-
|
|
4709
|
+
// 1.136.0: additive (sc-9135) — `usePdfExport()` gains `openPdf(html,
|
|
4710
|
+
// { fileName? })`, `opening` and `canOpenPdf`: render server-side and
|
|
4711
|
+
// OPEN the PDF (web: browser viewer tab; native: expo-print sheet)
|
|
4712
|
+
// without saving it, through the new `device.pdfViewer` host slice. And
|
|
4713
|
+
// `createObjectURL` joins `bannedApis`: widgets reached for it to fake a
|
|
4714
|
+
// "print version" as a plain-text blob that never worked on native.
|
|
4715
|
+
version: "1.137.0",
|
|
4684
4716
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4685
4717
|
hooks: HOOKS,
|
|
4686
4718
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -4149,9 +4149,18 @@ export function useFilestoreUpload(options) {
|
|
|
4149
4149
|
}
|
|
4150
4150
|
|
|
4151
4151
|
/**
|
|
4152
|
-
* sc-2314 — render an HTML string to a PDF server-side
|
|
4153
|
-
*
|
|
4154
|
-
* lastExported }`.
|
|
4152
|
+
* sc-2314 / sc-9135 — render an HTML string to a PDF server-side, then OPEN it
|
|
4153
|
+
* or SAVE it. Returns `{ openPdf, opening, canOpenPdf, exportToPdf, exporting,
|
|
4154
|
+
* error, lastExported }`.
|
|
4155
|
+
*
|
|
4156
|
+
* `openPdf(html, { fileName? })` renders without saving and hands the bytes to
|
|
4157
|
+
* the host's `device.pdfViewer`: a browser PDF tab on web, the OS print sheet
|
|
4158
|
+
* on native. It is how a widget offers "print / view / download as PDF".
|
|
4159
|
+
* `canOpenPdf` is false on a host with no viewer, where openPdf rejects with
|
|
4160
|
+
* `err.code` UNSUPPORTED.
|
|
4161
|
+
*
|
|
4162
|
+
* `exportToPdf` SAVES into the end-user's Filestore space. The widget passes
|
|
4163
|
+
* the SPACE (`{ spaceType, folderId? }`);
|
|
4155
4164
|
* the hook resolves `owner_id` the same way the upload hook does (tenant for
|
|
4156
4165
|
* PROJECT, app user for PERSONAL) and posts JSON to
|
|
4157
4166
|
* `ctx.filestore.files.exportPdf`.
|
|
@@ -4189,6 +4198,45 @@ export function usePdfExport(options) {
|
|
|
4189
4198
|
|
|
4190
4199
|
const filesRef = useRef(ctx.filestore.files);
|
|
4191
4200
|
filesRef.current = ctx.filestore.files;
|
|
4201
|
+
const viewerRef = useRef(ctx.device && ctx.device.pdfViewer);
|
|
4202
|
+
viewerRef.current = ctx.device && ctx.device.pdfViewer;
|
|
4203
|
+
const [opening, setOpening] = useState(false);
|
|
4204
|
+
const openingCountRef = useRef(0);
|
|
4205
|
+
|
|
4206
|
+
const canOpenPdf =
|
|
4207
|
+
_canOpenPdf(viewerRef.current) &&
|
|
4208
|
+
typeof ctx.filestore.files.renderPdf === "function";
|
|
4209
|
+
|
|
4210
|
+
// Deliberately no `await` before viewer.open: the web broker must open its
|
|
4211
|
+
// tab inside the press that called us, or a popup blocker refuses it.
|
|
4212
|
+
const openPdf = useCallback(async (html, overrides) => {
|
|
4213
|
+
if (typeof html !== "string" || html.trim().length === 0) {
|
|
4214
|
+
throw new Error("usePdfExport: html is required");
|
|
4215
|
+
}
|
|
4216
|
+
const viewer = viewerRef.current;
|
|
4217
|
+
const files = filesRef.current;
|
|
4218
|
+
if (!_canOpenPdf(viewer) || typeof files.renderPdf !== "function") {
|
|
4219
|
+
const err = new Error("This app cannot open PDFs here.");
|
|
4220
|
+
err.code = "UNSUPPORTED";
|
|
4221
|
+
setError(err);
|
|
4222
|
+
throw err;
|
|
4223
|
+
}
|
|
4224
|
+
setError(null);
|
|
4225
|
+
openingCountRef.current += 1;
|
|
4226
|
+
setOpening(true);
|
|
4227
|
+
try {
|
|
4228
|
+
await viewer.open({
|
|
4229
|
+
fileName: _pdfFileName(overrides && overrides.fileName),
|
|
4230
|
+
load: () => files.renderPdf({ html }),
|
|
4231
|
+
});
|
|
4232
|
+
} catch (err) {
|
|
4233
|
+
setError(err);
|
|
4234
|
+
throw err;
|
|
4235
|
+
} finally {
|
|
4236
|
+
openingCountRef.current -= 1;
|
|
4237
|
+
if (openingCountRef.current === 0) setOpening(false);
|
|
4238
|
+
}
|
|
4239
|
+
}, []);
|
|
4192
4240
|
|
|
4193
4241
|
const exportToPdf = useCallback(
|
|
4194
4242
|
async (html, overrides) => {
|
|
@@ -4228,7 +4276,36 @@ export function usePdfExport(options) {
|
|
|
4228
4276
|
[ownerId, defaultFolderId, spaceType],
|
|
4229
4277
|
);
|
|
4230
4278
|
|
|
4231
|
-
return {
|
|
4279
|
+
return {
|
|
4280
|
+
openPdf,
|
|
4281
|
+
opening,
|
|
4282
|
+
canOpenPdf,
|
|
4283
|
+
exportToPdf,
|
|
4284
|
+
exporting,
|
|
4285
|
+
error,
|
|
4286
|
+
lastExported,
|
|
4287
|
+
};
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
function _canOpenPdf(viewer) {
|
|
4291
|
+
return Boolean(
|
|
4292
|
+
viewer &&
|
|
4293
|
+
typeof viewer.open === "function" &&
|
|
4294
|
+
(typeof viewer.isSupported !== "function" || viewer.isSupported()),
|
|
4295
|
+
);
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
// The name the viewer offers when the user saves or shares the document.
|
|
4299
|
+
function _pdfFileName(fileName) {
|
|
4300
|
+
const base =
|
|
4301
|
+
typeof fileName === "string" && fileName.trim()
|
|
4302
|
+
? Array.from(fileName.trim(), (ch) =>
|
|
4303
|
+
ch < " " || ch === "/" || ch === "\\" ? "_" : ch,
|
|
4304
|
+
)
|
|
4305
|
+
.join("")
|
|
4306
|
+
.slice(0, 120)
|
|
4307
|
+
: "document";
|
|
4308
|
+
return /\.pdf$/i.test(base) ? base : `${base}.pdf`;
|
|
4232
4309
|
}
|
|
4233
4310
|
|
|
4234
4311
|
/**
|
package/dist/manifest.cjs
CHANGED
|
@@ -196,7 +196,12 @@ function validateManifest(m) {
|
|
|
196
196
|
);
|
|
197
197
|
|
|
198
198
|
pushIf(errors, isNonEmptyString(manifest.icon), "manifest.icon must be a non-empty string");
|
|
199
|
-
|
|
199
|
+
// sc-9142: optional — a listing needs a name, not a blurb.
|
|
200
|
+
pushIf(
|
|
201
|
+
errors,
|
|
202
|
+
manifest.description === undefined || typeof manifest.description === "string",
|
|
203
|
+
"manifest.description must be a string when present",
|
|
204
|
+
);
|
|
200
205
|
|
|
201
206
|
if (manifest.author === null || typeof manifest.author !== "object") {
|
|
202
207
|
errors.push("manifest.author must be an object with a name");
|
package/dist/manifest.js
CHANGED
|
@@ -242,7 +242,12 @@ function validateManifest(m) {
|
|
|
242
242
|
);
|
|
243
243
|
|
|
244
244
|
pushIf(errors, isNonEmptyString(manifest.icon), "manifest.icon must be a non-empty string");
|
|
245
|
-
|
|
245
|
+
// sc-9142: optional — a listing needs a name, not a blurb.
|
|
246
|
+
pushIf(
|
|
247
|
+
errors,
|
|
248
|
+
manifest.description === undefined || typeof manifest.description === "string",
|
|
249
|
+
"manifest.description must be a string when present",
|
|
250
|
+
);
|
|
246
251
|
|
|
247
252
|
if (manifest.author === null || typeof manifest.author !== "object") {
|
|
248
253
|
errors.push("manifest.author must be an object with a name");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.176.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",
|