@lotics/app-sdk 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/download.d.ts +1 -0
- package/dist/src/download.js +54 -0
- package/dist/src/hooks.d.ts +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +3 -2
- package/dist/src/row.d.ts +20 -0
- package/dist/src/row.js +29 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function downloadFile(filename: string, data: Uint8Array | Blob | string, mimeType?: string): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save bytes the app generated in the browser to the visitor's device.
|
|
3
|
+
*
|
|
4
|
+
* This is the client-side counterpart to `openExternal`. The platform's other
|
|
5
|
+
* download path is server-side: a workflow generates a file, it comes back in
|
|
6
|
+
* `WorkflowResult.files[]` with a `.url`, and the app opens that URL with
|
|
7
|
+
* `openExternal`. But data an app builds *in the browser* (an .xlsx exported
|
|
8
|
+
* from a query result, a CSV, a generated PDF) never gets a server URL — there
|
|
9
|
+
* was no primitive to save it. This is that primitive.
|
|
10
|
+
*
|
|
11
|
+
* It is pure DOM, not an RPC: the embed iframe is sandboxed with
|
|
12
|
+
* `allow-downloads` (see `app_iframe_host`), so a same-origin blob download
|
|
13
|
+
* triggered from a user gesture is permitted without host mediation. In
|
|
14
|
+
* standalone (public) mode the app is a normal top-level page, where downloads
|
|
15
|
+
* always work. Call it synchronously from the click handler that produced the
|
|
16
|
+
* bytes so the browser attributes the download to the user gesture.
|
|
17
|
+
*
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { downloadFile } from "@lotics/app-sdk";
|
|
20
|
+
* import { buildDataWorkbook, exportWorkbook } from "@lotics/xlsx";
|
|
21
|
+
*
|
|
22
|
+
* const wb = buildDataWorkbook({ columns, rows });
|
|
23
|
+
* downloadFile("report.xlsx", exportWorkbook(wb), XLSX_MIME);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_MIME = "application/octet-stream";
|
|
27
|
+
function toBlob(data, mimeType) {
|
|
28
|
+
if (data instanceof Blob)
|
|
29
|
+
return data;
|
|
30
|
+
if (typeof data === "string")
|
|
31
|
+
return new Blob([data], { type: mimeType });
|
|
32
|
+
// Re-wrap the bytes: the DOM lib types a generic `Uint8Array<ArrayBufferLike>`
|
|
33
|
+
// as incompatible with `BlobPart`, and `new Uint8Array(data)` narrows it to a
|
|
34
|
+
// fresh `ArrayBuffer`-backed view (the pattern `save_workbook_version` uses).
|
|
35
|
+
return new Blob([new Uint8Array(data)], { type: mimeType });
|
|
36
|
+
}
|
|
37
|
+
export function downloadFile(filename, data, mimeType) {
|
|
38
|
+
const url = URL.createObjectURL(toBlob(data, mimeType ?? DEFAULT_MIME));
|
|
39
|
+
try {
|
|
40
|
+
const a = document.createElement("a");
|
|
41
|
+
a.href = url;
|
|
42
|
+
a.download = filename;
|
|
43
|
+
a.rel = "noopener";
|
|
44
|
+
a.style.display = "none";
|
|
45
|
+
document.body.appendChild(a);
|
|
46
|
+
a.click();
|
|
47
|
+
a.remove();
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
// Revoke after the current task so the navigation to the blob URL has
|
|
51
|
+
// started — revoking synchronously can cancel the download in some browsers.
|
|
52
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/src/hooks.d.ts
CHANGED
|
@@ -202,7 +202,7 @@ export declare function useInfiniteQuery<K extends keyof AppQueries & string>(al
|
|
|
202
202
|
export declare function useInfiniteQuery(alias: string, params?: Record<string, unknown>, opts?: InfiniteQueryOptions): InfiniteQueryState<Record<string, unknown>>;
|
|
203
203
|
/**
|
|
204
204
|
* Page-model query with a total — the data hook behind a numbered, jumpable
|
|
205
|
-
* table (
|
|
205
|
+
* table (pairs with `@lotics/ui/pagination`). It owns the page
|
|
206
206
|
* cursor and fetches two things: the current page of rows, and a `count` over
|
|
207
207
|
* the filtered set (keyed independently of page + sort, so paging and
|
|
208
208
|
* re-sorting never recount). The `(params, filter)` tuple is the result-set
|
package/dist/src/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* full setup — `@lotics/ui` + react-native + react-native-web, the
|
|
12
12
|
* react-native→react-native-web Vite alias, `@lotics/ui/index.css` +
|
|
13
13
|
* `fonts.css`, and a `PortalHost` for overlays. Build dashboards by
|
|
14
|
-
* composing `@lotics/ui` components (Card, KpiCard, charts,
|
|
14
|
+
* composing `@lotics/ui` components (Card, KpiCard, charts, Table, …),
|
|
15
15
|
* not raw HTML/CSS. See `docs/apps.md` → "Styling & components".
|
|
16
16
|
*/
|
|
17
17
|
export { mount } from "./mount.js";
|
|
@@ -26,13 +26,14 @@ export type { GeofenceZone, GeoCoords, GeofenceOutcome, GeofenceOptions } from "
|
|
|
26
26
|
export { rpc } from "./rpc.js";
|
|
27
27
|
export type { RpcOp } from "./rpc.js";
|
|
28
28
|
export { openExternal } from "./open_external.js";
|
|
29
|
+
export { downloadFile } from "./download.js";
|
|
29
30
|
export { readMembers } from "./members.js";
|
|
30
31
|
export type { ResolvedMember } from "./members.js";
|
|
31
32
|
export { readSelect } from "./select.js";
|
|
32
33
|
export type { ResolvedOption } from "./select.js";
|
|
33
34
|
export type { AppFixture } from "./mock.js";
|
|
34
35
|
export type { AppWorkflows, AppQueries } from "./types.js";
|
|
35
|
-
export { row, readLinks, readFiles } from "./row.js";
|
|
36
|
+
export { row, readLinks, readFiles, readLocked } from "./row.js";
|
|
36
37
|
export type { ResolvedLink, AppFile } from "./row.js";
|
|
37
38
|
export { useOptimistic } from "./use_optimistic.js";
|
|
38
39
|
export type { OptimisticApi } from "./use_optimistic.js";
|
package/dist/src/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* full setup — `@lotics/ui` + react-native + react-native-web, the
|
|
12
12
|
* react-native→react-native-web Vite alias, `@lotics/ui/index.css` +
|
|
13
13
|
* `fonts.css`, and a `PortalHost` for overlays. Build dashboards by
|
|
14
|
-
* composing `@lotics/ui` components (Card, KpiCard, charts,
|
|
14
|
+
* composing `@lotics/ui` components (Card, KpiCard, charts, Table, …),
|
|
15
15
|
* not raw HTML/CSS. See `docs/apps.md` → "Styling & components".
|
|
16
16
|
*/
|
|
17
17
|
export { mount } from "./mount.js";
|
|
@@ -21,8 +21,9 @@ export { useViewer } from "./viewer.js";
|
|
|
21
21
|
export { requestGeofencedLocation, isWithinZone } from "./geolocation.js";
|
|
22
22
|
export { rpc } from "./rpc.js";
|
|
23
23
|
export { openExternal } from "./open_external.js";
|
|
24
|
+
export { downloadFile } from "./download.js";
|
|
24
25
|
export { readMembers } from "./members.js";
|
|
25
26
|
export { readSelect } from "./select.js";
|
|
26
|
-
export { row, readLinks, readFiles } from "./row.js";
|
|
27
|
+
export { row, readLinks, readFiles, readLocked } from "./row.js";
|
|
27
28
|
export { useOptimistic } from "./use_optimistic.js";
|
|
28
29
|
export { useRecents } from "./use_recents.js";
|
package/dist/src/row.d.ts
CHANGED
|
@@ -28,6 +28,16 @@ declare function bool(v: unknown): boolean;
|
|
|
28
28
|
* fields are not handled here — they have no consumer yet.)
|
|
29
29
|
*/
|
|
30
30
|
declare function date(v: unknown): Date | null;
|
|
31
|
+
/**
|
|
32
|
+
* Date/datetime field → a LOCAL Date that KEEPS the stored wall-clock time, so
|
|
33
|
+
* `getHours()` / `toLocaleTimeString()` render the time as written. The stored
|
|
34
|
+
* value is a timezone-less workspace wall-clock (see the serialization note
|
|
35
|
+
* above), so it is read verbatim — no UTC conversion. Use this when the time
|
|
36
|
+
* matters (a check-in time, an appointment); `date` keeps only the calendar day.
|
|
37
|
+
* Parses `YYYY-MM-DD` with an optional `T`-or-space `HH:mm[:ss]`; a missing time
|
|
38
|
+
* is midnight. Null if absent or unparseable.
|
|
39
|
+
*/
|
|
40
|
+
declare function datetime(v: unknown): Date | null;
|
|
31
41
|
/** A linked record cell entry — the target record's id + its display text. */
|
|
32
42
|
export interface ResolvedLink {
|
|
33
43
|
/** The linked record's id (e.g. "rec_…"). Use to correlate/filter. */
|
|
@@ -65,12 +75,22 @@ export interface AppFile {
|
|
|
65
75
|
* an unservable file. Map to `@lotics/ui` `DisplayFile` for FileThumbnail/Gallery.
|
|
66
76
|
*/
|
|
67
77
|
export declare function readFiles(v: unknown): AppFile[];
|
|
78
|
+
/**
|
|
79
|
+
* Record lock state for a `useQuery` row. The query layer emits a row-level
|
|
80
|
+
* `__source_locked` addressing column (alongside `__source_record_id`). A locked
|
|
81
|
+
* record rejects direct writes, so an app reads this to show a locked state and
|
|
82
|
+
* route edits through a `request_locked_field_change` workflow instead of an
|
|
83
|
+
* `update_records` save. Pass the whole row (not a cell). False for any
|
|
84
|
+
* non-object / absent flag.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readLocked(rowValue: unknown): boolean;
|
|
68
87
|
export declare const row: {
|
|
69
88
|
opt: typeof opt;
|
|
70
89
|
text: typeof text;
|
|
71
90
|
num: typeof num;
|
|
72
91
|
bool: typeof bool;
|
|
73
92
|
date: typeof date;
|
|
93
|
+
datetime: typeof datetime;
|
|
74
94
|
link: typeof link;
|
|
75
95
|
};
|
|
76
96
|
export {};
|
package/dist/src/row.js
CHANGED
|
@@ -60,6 +60,21 @@ function date(v) {
|
|
|
60
60
|
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(text(v));
|
|
61
61
|
return m ? new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : null;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Date/datetime field → a LOCAL Date that KEEPS the stored wall-clock time, so
|
|
65
|
+
* `getHours()` / `toLocaleTimeString()` render the time as written. The stored
|
|
66
|
+
* value is a timezone-less workspace wall-clock (see the serialization note
|
|
67
|
+
* above), so it is read verbatim — no UTC conversion. Use this when the time
|
|
68
|
+
* matters (a check-in time, an appointment); `date` keeps only the calendar day.
|
|
69
|
+
* Parses `YYYY-MM-DD` with an optional `T`-or-space `HH:mm[:ss]`; a missing time
|
|
70
|
+
* is midnight. Null if absent or unparseable.
|
|
71
|
+
*/
|
|
72
|
+
function datetime(v) {
|
|
73
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}))?/.exec(text(v));
|
|
74
|
+
if (!m)
|
|
75
|
+
return null;
|
|
76
|
+
return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), m[4] ? Number(m[4]) : 0, m[5] ? Number(m[5]) : 0);
|
|
77
|
+
}
|
|
63
78
|
/** One `{ id, display }` object → ResolvedLink, or null if absent/malformed. */
|
|
64
79
|
function asLink(v) {
|
|
65
80
|
if (!v || typeof v !== "object")
|
|
@@ -118,4 +133,17 @@ export function readFiles(v) {
|
|
|
118
133
|
}
|
|
119
134
|
return out;
|
|
120
135
|
}
|
|
121
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Record lock state for a `useQuery` row. The query layer emits a row-level
|
|
138
|
+
* `__source_locked` addressing column (alongside `__source_record_id`). A locked
|
|
139
|
+
* record rejects direct writes, so an app reads this to show a locked state and
|
|
140
|
+
* route edits through a `request_locked_field_change` workflow instead of an
|
|
141
|
+
* `update_records` save. Pass the whole row (not a cell). False for any
|
|
142
|
+
* non-object / absent flag.
|
|
143
|
+
*/
|
|
144
|
+
export function readLocked(rowValue) {
|
|
145
|
+
if (!rowValue || typeof rowValue !== "object")
|
|
146
|
+
return false;
|
|
147
|
+
return bool(rowValue["__source_locked"]);
|
|
148
|
+
}
|
|
149
|
+
export const row = { opt, text, num, bool, date, datetime, link };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/app-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Runtime SDK for Lotics custom-code apps — typed hooks, postMessage bridge, mount entry point",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -46,4 +46,4 @@
|
|
|
46
46
|
"url": "https://github.com/lotics/lotics.git",
|
|
47
47
|
"directory": "packages/app-sdk"
|
|
48
48
|
}
|
|
49
|
-
}
|
|
49
|
+
}
|