grist-widget-sdk 0.3.25 → 0.3.26
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/CHANGELOG.md +12 -0
- package/dist/advanced.d.cts +3 -2
- package/dist/advanced.d.ts +3 -2
- package/dist/app.cjs +5176 -0
- package/dist/app.cjs.map +1 -0
- package/dist/app.d.cts +147 -0
- package/dist/app.d.ts +147 -0
- package/dist/app.js +527 -0
- package/dist/app.js.map +1 -0
- package/dist/chunk-PDGEZ24B.js +2366 -0
- package/dist/chunk-PDGEZ24B.js.map +1 -0
- package/dist/{create-grist-emulator-_3_JK7Rr.d.ts → create-grist-emulator-BCqp1BF9.d.ts} +2 -2
- package/dist/{create-grist-emulator-K-iUKUCZ.d.cts → create-grist-emulator-DBM8OSIR.d.cts} +2 -2
- package/dist/emulator/testing.d.cts +2 -2
- package/dist/emulator/testing.d.ts +2 -2
- package/dist/emulator/transports.d.cts +3 -3
- package/dist/emulator/transports.d.ts +3 -3
- package/dist/emulator.d.cts +5 -5
- package/dist/emulator.d.ts +5 -5
- package/dist/index.d.cts +9 -500
- package/dist/index.d.ts +9 -500
- package/dist/index.js +57 -2291
- package/dist/index.js.map +1 -1
- package/dist/{inline-A8yaddmp.d.cts → inline-C7DkvuET.d.cts} +1 -1
- package/dist/{inline-B7QQeWul.d.ts → inline-DpcT8tV9.d.ts} +1 -1
- package/dist/{templates-CB_VwjEU.d.cts → templates-8k2LKoO-.d.cts} +1 -1
- package/dist/{templates-pRIXc2RR.d.ts → templates-DIwi_mvI.d.ts} +1 -1
- package/dist/{types-YcRfo7oy.d.cts → types-D3JxUCfL.d.cts} +1 -1
- package/dist/{types-YcRfo7oy.d.ts → types-D3JxUCfL.d.ts} +1 -1
- package/dist/{use-grist-widget-options-from-context-hl-NwOwP.d.ts → use-grist-reads-DUMvJgV7.d.ts} +3 -44
- package/dist/{use-grist-widget-options-from-context-Bbd5-f39.d.cts → use-grist-reads-DpjVuI0H.d.cts} +3 -44
- package/dist/use-grist-types-DdOLEJXN.d.cts +499 -0
- package/dist/use-grist-types-kwxoFeub.d.ts +499 -0
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.cts +43 -0
- package/dist/use-grist-widget-options-from-context-CGOlQ5Vc.d.ts +43 -0
- package/package.json +11 -1
package/dist/index.js
CHANGED
|
@@ -1,45 +1,63 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
GRIST_BOUNDARY_PREPARING_COPY,
|
|
3
|
+
GristBoundary,
|
|
4
|
+
GristBoundaryScreen,
|
|
5
|
+
GristWidgetProvider,
|
|
6
|
+
buildReplicaColumnsFromColumnar,
|
|
7
|
+
buildReplicaDocumentFromDocApi,
|
|
8
|
+
deriveBoundaryBootLabel,
|
|
9
|
+
deriveBoundaryBootPhase,
|
|
10
|
+
deriveBoundaryView,
|
|
11
|
+
extractAttachmentIdsFromUploadPayload,
|
|
12
|
+
extractFirstAttachmentIdFromUploadPayload,
|
|
13
|
+
extractGristAttachmentId,
|
|
14
|
+
extractGristAttachmentIdsFromCell,
|
|
15
|
+
fetchAttachmentBase64,
|
|
16
|
+
fetchAttachmentBase64ForCell,
|
|
17
|
+
fetchAttachmentBlob,
|
|
18
|
+
formatAccessInsufficientMessage,
|
|
19
|
+
formatBoundaryUserMessage,
|
|
20
|
+
formatColumnMappingAlertMessage,
|
|
21
|
+
formatLinkStaleAlertMessage,
|
|
22
|
+
formatMappingPendingAlertMessage,
|
|
23
|
+
formatMappingUnreportedAlertMessage,
|
|
24
|
+
formatSectionNotLinkedAlertMessage,
|
|
25
|
+
formatSourceNotWiredAlertMessage,
|
|
26
|
+
getAttachmentDownloadUrl,
|
|
27
|
+
getAttachmentDownloadUrlForCell,
|
|
28
|
+
getGristSdkAlertDescriptors,
|
|
29
|
+
getGristSdkAlertSeverity,
|
|
30
|
+
getGristSdkAlertTitle,
|
|
31
|
+
gristAttachmentCellValue,
|
|
32
|
+
gristAttachmentCellValues,
|
|
33
|
+
inferGristFormulaKind,
|
|
34
|
+
isAccessInsufficientError,
|
|
35
|
+
isWidgetSectionNotLinked,
|
|
36
|
+
loadColumnMetadataByTable,
|
|
37
|
+
mergeGristAttachmentCellValue,
|
|
38
|
+
orderedColumnIds,
|
|
39
|
+
parseGristAttachmentUploadResponse,
|
|
40
|
+
parseGristAttachmentUploadResponseIds,
|
|
41
|
+
resolveGristFormulaKind,
|
|
42
|
+
serializeJsonFriendly,
|
|
43
|
+
uploadGristAttachment,
|
|
44
|
+
useGrist,
|
|
45
|
+
useGristContext,
|
|
46
|
+
useGristOptional,
|
|
47
|
+
useGristSchema,
|
|
48
|
+
useGristSdkAlertDescriptors,
|
|
49
|
+
useGristSelection,
|
|
50
|
+
useGristStatus,
|
|
51
|
+
useGristTheme,
|
|
52
|
+
useGristWrites,
|
|
53
|
+
useWidgetMetadata
|
|
54
|
+
} from "./chunk-PDGEZ24B.js";
|
|
55
|
+
import {
|
|
14
56
|
fetchWithGristAuth,
|
|
15
57
|
getGristAccessToken,
|
|
16
|
-
|
|
17
|
-
useGristCore,
|
|
18
|
-
useGristCoreContext,
|
|
19
|
-
useGristCurrentTableContext,
|
|
20
|
-
useGristFromProvider,
|
|
21
|
-
useGristManager,
|
|
22
|
-
useGristManagerOptional,
|
|
23
|
-
useGristReady,
|
|
24
|
-
useGristSelectionContext,
|
|
25
|
-
useGristThemeContext,
|
|
26
|
-
useGristWidgetOptionsFromContext,
|
|
27
|
-
useGristWritesContext,
|
|
28
|
-
useRpcHeartbeatCoalesce
|
|
58
|
+
useGristWidgetOptionsFromContext
|
|
29
59
|
} from "./chunk-EPEBKXVN.js";
|
|
30
60
|
import {
|
|
31
|
-
createGristHandshakeManager,
|
|
32
|
-
deriveCapabilities,
|
|
33
|
-
deriveColumnMappingStatus,
|
|
34
|
-
deriveErrorMessage,
|
|
35
|
-
deriveStatus,
|
|
36
|
-
extractResolvedMappings,
|
|
37
|
-
initialGristSnapshot,
|
|
38
|
-
newRecordEvent,
|
|
39
|
-
optionsEvent,
|
|
40
|
-
parseGristWidgetLinkingInfo,
|
|
41
|
-
recordEvent,
|
|
42
|
-
recordsEvent,
|
|
43
61
|
validateColumnMappings
|
|
44
62
|
} from "./chunk-TZB3OZA6.js";
|
|
45
63
|
import {
|
|
@@ -62,9 +80,7 @@ import {
|
|
|
62
80
|
sortRowsByGristOrder,
|
|
63
81
|
tableDataToRows
|
|
64
82
|
} from "./chunk-JASMM2YC.js";
|
|
65
|
-
import
|
|
66
|
-
gristAccessLevelLabel
|
|
67
|
-
} from "./chunk-N3X46JW2.js";
|
|
83
|
+
import "./chunk-N3X46JW2.js";
|
|
68
84
|
import {
|
|
69
85
|
coerceGristDocApiRefRowId,
|
|
70
86
|
decodeGristValue,
|
|
@@ -96,2256 +112,6 @@ import {
|
|
|
96
112
|
safeParseGristTableData,
|
|
97
113
|
safeParseGristValues
|
|
98
114
|
} from "./chunk-64SVRWW7.js";
|
|
99
|
-
|
|
100
|
-
// src/sdk/helpers/grist-attachment-download.ts
|
|
101
|
-
function attachmentIdFromObjectRecord(record) {
|
|
102
|
-
for (const candidate of [record.id, record.attachmentId, record.fileId]) {
|
|
103
|
-
if (candidate === void 0 || candidate === null) continue;
|
|
104
|
-
return String(candidate);
|
|
105
|
-
}
|
|
106
|
-
return void 0;
|
|
107
|
-
}
|
|
108
|
-
function extractGristAttachmentIdsFromCell(value) {
|
|
109
|
-
const out = [];
|
|
110
|
-
const seen = /* @__PURE__ */ new Set();
|
|
111
|
-
const push = (id) => {
|
|
112
|
-
if (seen.has(id)) return;
|
|
113
|
-
seen.add(id);
|
|
114
|
-
out.push(id);
|
|
115
|
-
};
|
|
116
|
-
const walk = (v) => {
|
|
117
|
-
if (v === void 0 || v === null) return;
|
|
118
|
-
if (typeof v === "number") {
|
|
119
|
-
push(String(v));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (typeof v === "string") {
|
|
123
|
-
const trimmed = v.trim();
|
|
124
|
-
if (!trimmed) return;
|
|
125
|
-
if (/^\d+$/.test(trimmed)) {
|
|
126
|
-
push(trimmed);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
walk(JSON.parse(trimmed));
|
|
131
|
-
} catch {
|
|
132
|
-
}
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (Array.isArray(v)) {
|
|
136
|
-
for (const item of v) walk(item);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (typeof v === "object") {
|
|
140
|
-
const record = v;
|
|
141
|
-
const direct = attachmentIdFromObjectRecord(record);
|
|
142
|
-
if (direct !== void 0) {
|
|
143
|
-
push(direct);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
for (const nested of Object.values(record)) walk(nested);
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
walk(value);
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
function extractGristAttachmentId(value) {
|
|
153
|
-
return extractGristAttachmentIdsFromCell(value)[0];
|
|
154
|
-
}
|
|
155
|
-
async function getAttachmentDownloadUrl(docApi, attachmentId, options) {
|
|
156
|
-
const getAccessToken = docApi.getAccessToken;
|
|
157
|
-
if (!getAccessToken) {
|
|
158
|
-
throw new Error("getAccessToken is not available on docApi");
|
|
159
|
-
}
|
|
160
|
-
const { token, baseUrl } = await getAccessToken({ readOnly: options?.readOnly ?? false });
|
|
161
|
-
return `${baseUrl}/attachments/${attachmentId}/download?auth=${token}`;
|
|
162
|
-
}
|
|
163
|
-
async function getAttachmentDownloadUrlForCell(docApi, cellValue, index, options) {
|
|
164
|
-
const ids = extractGristAttachmentIdsFromCell(cellValue);
|
|
165
|
-
const id = ids[index];
|
|
166
|
-
if (!id) return null;
|
|
167
|
-
return getAttachmentDownloadUrl(docApi, id, options);
|
|
168
|
-
}
|
|
169
|
-
async function fetchBytesFromSignedAttachmentUrl(url) {
|
|
170
|
-
const response = await fetch(url);
|
|
171
|
-
const contentType = response.headers.get("content-type")?.trim() || "application/octet-stream";
|
|
172
|
-
if (!response.ok) {
|
|
173
|
-
throw new Error(`Attachment download failed (${response.status})`);
|
|
174
|
-
}
|
|
175
|
-
const buffer = await response.arrayBuffer();
|
|
176
|
-
return { buffer, contentType };
|
|
177
|
-
}
|
|
178
|
-
function bufferToBase64(buffer) {
|
|
179
|
-
const bytes = new Uint8Array(buffer);
|
|
180
|
-
let binary = "";
|
|
181
|
-
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
182
|
-
return btoa(binary);
|
|
183
|
-
}
|
|
184
|
-
async function fetchAttachmentBase64(docApi, attachmentId, options) {
|
|
185
|
-
const url = await getAttachmentDownloadUrl(docApi, attachmentId, options);
|
|
186
|
-
const { buffer, contentType } = await fetchBytesFromSignedAttachmentUrl(url);
|
|
187
|
-
return { base64: bufferToBase64(buffer), contentType };
|
|
188
|
-
}
|
|
189
|
-
async function fetchAttachmentBlob(docApi, attachmentId, options) {
|
|
190
|
-
const url = await getAttachmentDownloadUrl(docApi, attachmentId, options);
|
|
191
|
-
const { buffer, contentType } = await fetchBytesFromSignedAttachmentUrl(url);
|
|
192
|
-
return { blob: new Blob([buffer], { type: contentType }), contentType };
|
|
193
|
-
}
|
|
194
|
-
async function fetchAttachmentBase64ForCell(docApi, cellValue, index, options) {
|
|
195
|
-
const url = await getAttachmentDownloadUrlForCell(docApi, cellValue, index, options);
|
|
196
|
-
if (!url) return null;
|
|
197
|
-
const { buffer, contentType } = await fetchBytesFromSignedAttachmentUrl(url);
|
|
198
|
-
return { base64: bufferToBase64(buffer), contentType };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/sdk/helpers/grist-attachment-upload.ts
|
|
202
|
-
var ATTACHMENTS_COLUMN = { type: "Attachments" };
|
|
203
|
-
function parseGristAttachmentUploadResponseIds(raw, options) {
|
|
204
|
-
const trimmed = raw.trim();
|
|
205
|
-
if (!trimmed) {
|
|
206
|
-
throw new Error("Attachment upload returned an empty response.");
|
|
207
|
-
}
|
|
208
|
-
let parsed;
|
|
209
|
-
try {
|
|
210
|
-
parsed = JSON.parse(trimmed);
|
|
211
|
-
} catch {
|
|
212
|
-
if (options?.strict) {
|
|
213
|
-
throw new Error("Attachment upload returned non-JSON response.");
|
|
214
|
-
}
|
|
215
|
-
parsed = trimmed;
|
|
216
|
-
}
|
|
217
|
-
return extractAttachmentIdsFromUploadPayload(parsed, options);
|
|
218
|
-
}
|
|
219
|
-
function parseGristAttachmentUploadResponse(raw, options) {
|
|
220
|
-
const ids = parseGristAttachmentUploadResponseIds(raw, options);
|
|
221
|
-
return ids[0];
|
|
222
|
-
}
|
|
223
|
-
function extractAttachmentIdsFromUploadPayload(parsed, options) {
|
|
224
|
-
if (options?.strict) {
|
|
225
|
-
return extractAttachmentIdsFromUploadPayloadStrict(parsed);
|
|
226
|
-
}
|
|
227
|
-
const first = extractFirstAttachmentIdFromUploadPayload(parsed);
|
|
228
|
-
if (first !== void 0) {
|
|
229
|
-
if (Array.isArray(parsed)) {
|
|
230
|
-
const ids = [];
|
|
231
|
-
const seen = /* @__PURE__ */ new Set();
|
|
232
|
-
for (const item of parsed) {
|
|
233
|
-
const id = coerceAttachmentId(item);
|
|
234
|
-
if (id && !seen.has(id)) {
|
|
235
|
-
seen.add(id);
|
|
236
|
-
ids.push(id);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return ids.length > 0 ? ids : [first];
|
|
240
|
-
}
|
|
241
|
-
return [first];
|
|
242
|
-
}
|
|
243
|
-
throw new Error("Attachment upload returned an unexpected response shape.");
|
|
244
|
-
}
|
|
245
|
-
function extractAttachmentIdsFromUploadPayloadStrict(parsed) {
|
|
246
|
-
if (typeof parsed === "number" && Number.isFinite(parsed)) {
|
|
247
|
-
return [String(parsed)];
|
|
248
|
-
}
|
|
249
|
-
if (Array.isArray(parsed)) {
|
|
250
|
-
const ids = [];
|
|
251
|
-
const seen = /* @__PURE__ */ new Set();
|
|
252
|
-
for (const item of parsed) {
|
|
253
|
-
if (typeof item !== "number" || !Number.isFinite(item)) {
|
|
254
|
-
throw new Error("Attachment upload returned an unexpected response shape.");
|
|
255
|
-
}
|
|
256
|
-
const id = String(item);
|
|
257
|
-
if (!seen.has(id)) {
|
|
258
|
-
seen.add(id);
|
|
259
|
-
ids.push(id);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (ids.length === 0) {
|
|
263
|
-
throw new Error("Attachment upload returned an empty id list.");
|
|
264
|
-
}
|
|
265
|
-
return ids;
|
|
266
|
-
}
|
|
267
|
-
throw new Error("Attachment upload returned an unexpected response shape.");
|
|
268
|
-
}
|
|
269
|
-
function extractFirstAttachmentIdFromUploadPayload(parsed) {
|
|
270
|
-
if (typeof parsed === "number" && Number.isFinite(parsed)) {
|
|
271
|
-
return String(parsed);
|
|
272
|
-
}
|
|
273
|
-
if (typeof parsed === "string" && parsed.trim()) {
|
|
274
|
-
return parsed.trim();
|
|
275
|
-
}
|
|
276
|
-
if (Array.isArray(parsed)) {
|
|
277
|
-
for (const item of parsed) {
|
|
278
|
-
const id = coerceAttachmentId(item);
|
|
279
|
-
if (id) return id;
|
|
280
|
-
}
|
|
281
|
-
return void 0;
|
|
282
|
-
}
|
|
283
|
-
if (parsed && typeof parsed === "object") {
|
|
284
|
-
const record = parsed;
|
|
285
|
-
for (const key of ["id", "attachmentId", "fileId"]) {
|
|
286
|
-
const id = coerceAttachmentId(record[key]);
|
|
287
|
-
if (id) return id;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return void 0;
|
|
291
|
-
}
|
|
292
|
-
function coerceAttachmentId(value) {
|
|
293
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
294
|
-
return String(value);
|
|
295
|
-
}
|
|
296
|
-
if (typeof value === "string" && value.trim()) {
|
|
297
|
-
return value.trim();
|
|
298
|
-
}
|
|
299
|
-
return void 0;
|
|
300
|
-
}
|
|
301
|
-
function normalizeAttachmentIdForWire(id) {
|
|
302
|
-
const numeric = typeof id === "number" ? id : Number(id);
|
|
303
|
-
if (Number.isFinite(numeric) && String(numeric) === String(id).trim()) {
|
|
304
|
-
return numeric;
|
|
305
|
-
}
|
|
306
|
-
return typeof id === "number" ? String(id) : id.trim();
|
|
307
|
-
}
|
|
308
|
-
function gristAttachmentCellValues(attachmentIds) {
|
|
309
|
-
const items = attachmentIds.map(normalizeAttachmentIdForWire);
|
|
310
|
-
const encoded = encodeGristValue(items, ATTACHMENTS_COLUMN);
|
|
311
|
-
if (!Array.isArray(encoded) || encoded[0] !== "L") {
|
|
312
|
-
return ["L", ...items];
|
|
313
|
-
}
|
|
314
|
-
return encoded;
|
|
315
|
-
}
|
|
316
|
-
function gristAttachmentCellValue(attachmentId) {
|
|
317
|
-
return gristAttachmentCellValues([attachmentId]);
|
|
318
|
-
}
|
|
319
|
-
function mergeGristAttachmentCellValue(existingCell, newIds) {
|
|
320
|
-
const existing = extractGristAttachmentIdsFromCell(existingCell);
|
|
321
|
-
const merged = [];
|
|
322
|
-
const seen = /* @__PURE__ */ new Set();
|
|
323
|
-
const push = (raw) => {
|
|
324
|
-
const id = String(raw).trim();
|
|
325
|
-
if (!id || seen.has(id)) return;
|
|
326
|
-
seen.add(id);
|
|
327
|
-
merged.push(id);
|
|
328
|
-
};
|
|
329
|
-
for (const id of existing) push(id);
|
|
330
|
-
for (const id of newIds) push(id);
|
|
331
|
-
return gristAttachmentCellValues(merged);
|
|
332
|
-
}
|
|
333
|
-
var UPLOAD_HEADERS = {
|
|
334
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
335
|
-
Accept: "application/json"
|
|
336
|
-
};
|
|
337
|
-
async function uploadGristAttachment(docApi, file, options) {
|
|
338
|
-
const form = new FormData();
|
|
339
|
-
const fileName = options?.fileName ?? (file instanceof File && file.name ? file.name : "upload");
|
|
340
|
-
form.append("upload", file, fileName);
|
|
341
|
-
let response;
|
|
342
|
-
try {
|
|
343
|
-
response = await fetchWithGristAuth(docApi, "/attachments", {
|
|
344
|
-
method: "POST",
|
|
345
|
-
body: form,
|
|
346
|
-
readOnly: false,
|
|
347
|
-
headers: UPLOAD_HEADERS
|
|
348
|
-
});
|
|
349
|
-
} catch (err) {
|
|
350
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
351
|
-
if (/failed to fetch/i.test(msg)) {
|
|
352
|
-
const widgetOrigin = typeof globalThis.location !== "undefined" ? globalThis.location.origin : "this widget origin";
|
|
353
|
-
throw new Error(
|
|
354
|
-
`Attachment upload was blocked by the Grist host (CORS). The browser could not POST from ${widgetOrigin} to the document API. If upload worked from a dev URL (e.g. ngrok) but fails after deploy, add the new widget URL to your Grist server CORS allowlist (see grist-core#2040).`,
|
|
355
|
-
{ cause: err }
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
throw err;
|
|
359
|
-
}
|
|
360
|
-
const raw = await response.text();
|
|
361
|
-
if (!response.ok) {
|
|
362
|
-
throw new Error(
|
|
363
|
-
raw.trim() ? `Attachment upload failed (${response.status}): ${raw.slice(0, 200)}` : `Attachment upload failed (${response.status})`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
const ids = parseGristAttachmentUploadResponseIds(raw);
|
|
367
|
-
return { ids, firstId: ids[0] };
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// src/sdk/helpers/grist-schema-formula.ts
|
|
371
|
-
function inferGristFormulaKind(isFormula, formulaTrimmed) {
|
|
372
|
-
if (formulaTrimmed.length > 0) return isFormula ? "computed" : "trigger";
|
|
373
|
-
return isFormula ? "empty_slot" : "none";
|
|
374
|
-
}
|
|
375
|
-
function resolveGristFormulaKind(col) {
|
|
376
|
-
if (col.formulaKind) return col.formulaKind;
|
|
377
|
-
const f = col.formula?.trim() ?? "";
|
|
378
|
-
if (f.length > 0) return col.isFormula === true ? "computed" : "trigger";
|
|
379
|
-
return col.isFormula === true ? "empty_slot" : "none";
|
|
380
|
-
}
|
|
381
|
-
function serializeJsonFriendly(value) {
|
|
382
|
-
if (value === null || value === void 0) return null;
|
|
383
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
384
|
-
try {
|
|
385
|
-
return JSON.parse(JSON.stringify(value));
|
|
386
|
-
} catch {
|
|
387
|
-
return String(value);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// src/sdk/helpers/build-replica-document.ts
|
|
392
|
-
var FORMULA_PREVIEW_MAX = 500;
|
|
393
|
-
function readCellStr(table, key, rowIndex) {
|
|
394
|
-
const col = table[key];
|
|
395
|
-
if (!Array.isArray(col)) return void 0;
|
|
396
|
-
const v = col[rowIndex];
|
|
397
|
-
if (v === null || v === void 0) return void 0;
|
|
398
|
-
if (typeof v === "string") return v;
|
|
399
|
-
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
400
|
-
return void 0;
|
|
401
|
-
}
|
|
402
|
-
function readCellNum(table, key, rowIndex) {
|
|
403
|
-
const col = table[key];
|
|
404
|
-
if (!Array.isArray(col)) return void 0;
|
|
405
|
-
const v = col[rowIndex];
|
|
406
|
-
if (typeof v === "number" && !Number.isNaN(v)) return v;
|
|
407
|
-
if (typeof v === "string" && v.trim() !== "") {
|
|
408
|
-
const n = Number(v);
|
|
409
|
-
if (!Number.isNaN(n)) return n;
|
|
410
|
-
}
|
|
411
|
-
return void 0;
|
|
412
|
-
}
|
|
413
|
-
function pickColumnMeta(meta) {
|
|
414
|
-
if (!meta) return {};
|
|
415
|
-
const out = {};
|
|
416
|
-
if (meta.colRef !== void 0) out.colRef = meta.colRef;
|
|
417
|
-
if (meta.type) out.type = meta.type;
|
|
418
|
-
if (meta.label) out.label = meta.label;
|
|
419
|
-
if (meta.parentPos !== void 0) out.parentPos = meta.parentPos;
|
|
420
|
-
if (meta.isFormula) out.isFormula = true;
|
|
421
|
-
const formulaRaw = meta.formula?.trim() ?? "";
|
|
422
|
-
out.formulaKind = inferGristFormulaKind(Boolean(meta.isFormula), formulaRaw);
|
|
423
|
-
if (formulaRaw) {
|
|
424
|
-
out.formula = formulaRaw.length > FORMULA_PREVIEW_MAX ? `${formulaRaw.slice(0, FORMULA_PREVIEW_MAX)}...` : formulaRaw;
|
|
425
|
-
}
|
|
426
|
-
if (meta.description) out.description = meta.description;
|
|
427
|
-
if (meta.widgetOptions && Object.keys(meta.widgetOptions).length > 0) {
|
|
428
|
-
out.widgetOptions = meta.widgetOptions;
|
|
429
|
-
}
|
|
430
|
-
if (meta.displayColRef !== void 0) out.displayColRef = meta.displayColRef;
|
|
431
|
-
if (meta.visibleColRef !== void 0) out.visibleColRef = meta.visibleColRef;
|
|
432
|
-
return out;
|
|
433
|
-
}
|
|
434
|
-
async function loadColumnMetadataByTable(docApi) {
|
|
435
|
-
const byTable = /* @__PURE__ */ new Map();
|
|
436
|
-
try {
|
|
437
|
-
const tables = await docApi.fetchTable("_grist_Tables");
|
|
438
|
-
const columns = await docApi.fetchTable("_grist_Tables_column");
|
|
439
|
-
const tableIds = tables.tableId;
|
|
440
|
-
const tableInternalIds = tables.id;
|
|
441
|
-
if (!Array.isArray(tableIds) || !Array.isArray(tableInternalIds)) return byTable;
|
|
442
|
-
const internalIdToTableId = /* @__PURE__ */ new Map();
|
|
443
|
-
for (let i = 0; i < tableInternalIds.length; i++) {
|
|
444
|
-
const tid = tableIds[i];
|
|
445
|
-
const iid = coerceGristDocApiRefRowId(tableInternalIds[i]);
|
|
446
|
-
if (typeof tid === "string" && iid !== void 0) internalIdToTableId.set(iid, tid);
|
|
447
|
-
}
|
|
448
|
-
const parentIds = columns.parentId;
|
|
449
|
-
const colIds = columns.colId;
|
|
450
|
-
const columnIds = columns.id;
|
|
451
|
-
if (!Array.isArray(parentIds) || !Array.isArray(colIds) || !Array.isArray(columnIds) || parentIds.length !== colIds.length || colIds.length !== columnIds.length) {
|
|
452
|
-
return byTable;
|
|
453
|
-
}
|
|
454
|
-
const n = parentIds.length;
|
|
455
|
-
for (let i = 0; i < n; i++) {
|
|
456
|
-
const parentRowId = coerceGristDocApiRefRowId(parentIds[i]);
|
|
457
|
-
const colRef = coerceGristDocApiRefRowId(columnIds[i]);
|
|
458
|
-
const colId = colIds[i];
|
|
459
|
-
if (parentRowId === void 0 || colRef === void 0 || typeof colId !== "string") continue;
|
|
460
|
-
const tableId = internalIdToTableId.get(parentRowId);
|
|
461
|
-
if (!tableId) continue;
|
|
462
|
-
let colMap = byTable.get(tableId);
|
|
463
|
-
if (!colMap) {
|
|
464
|
-
colMap = /* @__PURE__ */ new Map();
|
|
465
|
-
byTable.set(tableId, colMap);
|
|
466
|
-
}
|
|
467
|
-
const parentPos = readCellNum(columns, "parentPos", i);
|
|
468
|
-
const isFormulaCol = columns.isFormula;
|
|
469
|
-
const isFormula = Array.isArray(isFormulaCol) ? Boolean(isFormulaCol[i]) : false;
|
|
470
|
-
const woCol = columns.widgetOptions;
|
|
471
|
-
const widgetOptions = Array.isArray(woCol) && i < woCol.length ? parseGristWidgetOptionsCell(woCol[i]) : void 0;
|
|
472
|
-
const displayColRef = coerceGristDocApiRefRowId(
|
|
473
|
-
Array.isArray(columns.displayCol) ? columns.displayCol[i] : void 0
|
|
474
|
-
);
|
|
475
|
-
const visibleColRef = coerceGristDocApiRefRowId(
|
|
476
|
-
Array.isArray(columns.visibleCol) ? columns.visibleCol[i] : void 0
|
|
477
|
-
);
|
|
478
|
-
colMap.set(colId, {
|
|
479
|
-
colRef,
|
|
480
|
-
type: readCellStr(columns, "type", i),
|
|
481
|
-
label: readCellStr(columns, "label", i),
|
|
482
|
-
parentPos: parentPos !== void 0 ? parentPos : void 0,
|
|
483
|
-
isFormula,
|
|
484
|
-
formula: readCellStr(columns, "formula", i),
|
|
485
|
-
description: readCellStr(columns, "description", i) ?? readCellStr(columns, "descr", i),
|
|
486
|
-
widgetOptions,
|
|
487
|
-
displayColRef,
|
|
488
|
-
visibleColRef
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
} catch {
|
|
492
|
-
}
|
|
493
|
-
return byTable;
|
|
494
|
-
}
|
|
495
|
-
function orderedColumnIds(data, metaByCol) {
|
|
496
|
-
const keys = Object.keys(data).filter((k) => Array.isArray(data[k]));
|
|
497
|
-
keys.sort((a, b) => {
|
|
498
|
-
const pa = metaByCol.get(a)?.parentPos;
|
|
499
|
-
const pb = metaByCol.get(b)?.parentPos;
|
|
500
|
-
if (pa !== void 0 && pb !== void 0 && pa !== pb) return pa - pb;
|
|
501
|
-
if (pa !== void 0 && pb === void 0) return -1;
|
|
502
|
-
if (pa === void 0 && pb !== void 0) return 1;
|
|
503
|
-
return a.localeCompare(b);
|
|
504
|
-
});
|
|
505
|
-
return keys;
|
|
506
|
-
}
|
|
507
|
-
function buildReplicaColumnsFromColumnar(data, colMeta) {
|
|
508
|
-
const columnIds = orderedColumnIds(data, colMeta);
|
|
509
|
-
const columns = {};
|
|
510
|
-
for (const colId of columnIds) {
|
|
511
|
-
const arr = data[colId];
|
|
512
|
-
if (!Array.isArray(arr)) continue;
|
|
513
|
-
const meta = colMeta.get(colId);
|
|
514
|
-
columns[colId] = pickColumnMeta(meta);
|
|
515
|
-
}
|
|
516
|
-
return columns;
|
|
517
|
-
}
|
|
518
|
-
function rowModeFromIncludeRows(includeRows) {
|
|
519
|
-
if (includeRows === false) return "schema-only";
|
|
520
|
-
if (includeRows === "samples") return "schema+samples";
|
|
521
|
-
return "schema+data";
|
|
522
|
-
}
|
|
523
|
-
async function buildReplicaDocumentFromDocApi(docApi, options) {
|
|
524
|
-
const includeSystemTableData = options?.includeSystemTableData ?? false;
|
|
525
|
-
const includeRows = options?.includeRows ?? "samples";
|
|
526
|
-
const sampleRowLimit = options?.sampleRowLimit ?? 10;
|
|
527
|
-
const rowMode = rowModeFromIncludeRows(includeRows);
|
|
528
|
-
const metaByTable = await loadColumnMetadataByTable(docApi);
|
|
529
|
-
let docName;
|
|
530
|
-
try {
|
|
531
|
-
docName = String(await Promise.resolve(docApi.getDocName()));
|
|
532
|
-
} catch {
|
|
533
|
-
docName = void 0;
|
|
534
|
-
}
|
|
535
|
-
const tableIdsRaw = await Promise.resolve(docApi.listTables());
|
|
536
|
-
const tableIds = Array.isArray(tableIdsRaw) ? [...tableIdsRaw] : [];
|
|
537
|
-
const tables = {};
|
|
538
|
-
for (const tableId of tableIds) {
|
|
539
|
-
const omitData = !includeSystemTableData && tableId.startsWith("_grist");
|
|
540
|
-
if (omitData) {
|
|
541
|
-
tables[tableId] = {
|
|
542
|
-
rowCount: 0,
|
|
543
|
-
dataOmitted: true,
|
|
544
|
-
dataOmittedReason: "Row scan skipped for tables whose id starts with `_grist` unless `includeSystemTableData` is true.",
|
|
545
|
-
columns: {}
|
|
546
|
-
};
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
try {
|
|
550
|
-
const data = await docApi.fetchTable(tableId);
|
|
551
|
-
const rowCount = computeRowCount(data);
|
|
552
|
-
const colMeta = metaByTable.get(tableId) ?? /* @__PURE__ */ new Map();
|
|
553
|
-
const columnIds = orderedColumnIds(data, colMeta);
|
|
554
|
-
const columns = buildReplicaColumnsFromColumnar(data, colMeta);
|
|
555
|
-
const table = { columns, rowCount };
|
|
556
|
-
if (includeRows === "full") {
|
|
557
|
-
table.rows = decodeColumnarToReplicaRows(data, columnIds, columns);
|
|
558
|
-
} else if (includeRows === "samples") {
|
|
559
|
-
table.rows = decodeColumnarToReplicaRows(data, columnIds, columns, sampleRowLimit);
|
|
560
|
-
}
|
|
561
|
-
tables[tableId] = table;
|
|
562
|
-
} catch (err) {
|
|
563
|
-
tables[tableId] = {
|
|
564
|
-
rowCount: 0,
|
|
565
|
-
error: err instanceof Error ? err.message : String(err),
|
|
566
|
-
columns: {}
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
const out = {
|
|
571
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
572
|
-
docName,
|
|
573
|
-
mode: rowMode,
|
|
574
|
-
tables
|
|
575
|
-
};
|
|
576
|
-
if (options?.source !== void 0 && options.source !== "") {
|
|
577
|
-
out.source = options.source;
|
|
578
|
-
}
|
|
579
|
-
return out;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// src/sdk/helpers/grist-boundary-view.ts
|
|
583
|
-
function deriveBoundaryBootLabel(snapshot) {
|
|
584
|
-
const { lifecycle } = snapshot;
|
|
585
|
-
switch (lifecycle.phase) {
|
|
586
|
-
case "idle":
|
|
587
|
-
return "Starting\u2026";
|
|
588
|
-
case "detecting":
|
|
589
|
-
return lifecycle.attempts > 0 ? `Looking for Grist\u2026 (attempt ${lifecycle.attempts})` : "Looking for Grist\u2026";
|
|
590
|
-
case "negotiating":
|
|
591
|
-
return "Asking Grist for the access this widget needs\u2026";
|
|
592
|
-
case "online":
|
|
593
|
-
return "Connected";
|
|
594
|
-
case "terminated":
|
|
595
|
-
return "Connection ended";
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
function deriveBoundaryBootPhase(snapshot) {
|
|
599
|
-
switch (snapshot.lifecycle.phase) {
|
|
600
|
-
case "idle":
|
|
601
|
-
return "idle";
|
|
602
|
-
case "detecting":
|
|
603
|
-
return "detecting";
|
|
604
|
-
case "negotiating":
|
|
605
|
-
return "negotiating";
|
|
606
|
-
default:
|
|
607
|
-
return "generic";
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function formatBoundaryUserMessage(raw, snapshot) {
|
|
611
|
-
if (snapshot.authz.state === "denied") {
|
|
612
|
-
return snapshot.authz.message ?? `Set Access level to at least "${gristAccessLevelLabel(snapshot.authz.declared)}" in the Custom widget settings.`;
|
|
613
|
-
}
|
|
614
|
-
if (!raw?.trim()) {
|
|
615
|
-
return "Something went wrong connecting to Grist. Try reconnecting below, or reload the page.";
|
|
616
|
-
}
|
|
617
|
-
const text = raw.trim();
|
|
618
|
-
if (/timed out/i.test(text)) {
|
|
619
|
-
return "Grist took too long to respond. Check your network, then try reconnecting.";
|
|
620
|
-
}
|
|
621
|
-
if (/link lost/i.test(text)) {
|
|
622
|
-
return "The link to Grist was interrupted. Try reconnecting to restore live data.";
|
|
623
|
-
}
|
|
624
|
-
if (/not embedded|custom widget inside/i.test(text)) {
|
|
625
|
-
return text;
|
|
626
|
-
}
|
|
627
|
-
if (/plugin API did not load/i.test(text)) {
|
|
628
|
-
return "The Grist plugin did not load in time. Reload the page, or open this URL as a custom widget inside a Grist document.";
|
|
629
|
-
}
|
|
630
|
-
if (/denied requested access/i.test(text)) {
|
|
631
|
-
const declared = snapshot.authz.state === "pending" ? snapshot.authz.declared : snapshot.authz.state === "granted" ? snapshot.authz.declared : null;
|
|
632
|
-
if (declared) {
|
|
633
|
-
return `Set Access level to at least "${gristAccessLevelLabel(declared)}" in the Custom widget settings.`;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return text;
|
|
637
|
-
}
|
|
638
|
-
function blockingTitle(kind, bootPhase, snapshot) {
|
|
639
|
-
if (kind === "error" && snapshot.authz.state === "denied") {
|
|
640
|
-
return "More access needed";
|
|
641
|
-
}
|
|
642
|
-
if (kind === "unavailable") return "Open inside Grist";
|
|
643
|
-
if (kind === "error") return "Something went wrong";
|
|
644
|
-
switch (bootPhase) {
|
|
645
|
-
case "detecting":
|
|
646
|
-
return "Connecting to Grist";
|
|
647
|
-
case "negotiating":
|
|
648
|
-
return "Setting up access";
|
|
649
|
-
case "idle":
|
|
650
|
-
return "Starting";
|
|
651
|
-
default:
|
|
652
|
-
return "Connecting to Grist";
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
function blockingMessage(snapshot, status, error) {
|
|
656
|
-
if (status === "unavailable") {
|
|
657
|
-
if (snapshot.lifecycle.phase === "terminated") {
|
|
658
|
-
if (snapshot.lifecycle.reason.kind === "not_embedded") {
|
|
659
|
-
return "Open this page as a custom widget inside a Grist document (it runs in an embedded panel, not on its own).";
|
|
660
|
-
}
|
|
661
|
-
if (snapshot.lifecycle.reason.kind === "script_timeout") {
|
|
662
|
-
return formatBoundaryUserMessage(
|
|
663
|
-
error ?? "The Grist plugin API did not load in time.",
|
|
664
|
-
snapshot
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
return "This page must be opened from a Grist document as a custom widget.";
|
|
669
|
-
}
|
|
670
|
-
if (status === "error") {
|
|
671
|
-
return formatBoundaryUserMessage(error, snapshot);
|
|
672
|
-
}
|
|
673
|
-
return deriveBoundaryBootLabel(snapshot);
|
|
674
|
-
}
|
|
675
|
-
function deriveBoundaryView(snapshot) {
|
|
676
|
-
const status = deriveStatus(snapshot);
|
|
677
|
-
if (status === "ready") {
|
|
678
|
-
return { mode: "ready" };
|
|
679
|
-
}
|
|
680
|
-
const kind = status === "unavailable" ? "unavailable" : status === "error" ? "error" : "booting";
|
|
681
|
-
const bootPhase = kind === "booting" ? deriveBoundaryBootPhase(snapshot) : void 0;
|
|
682
|
-
const error = deriveErrorMessage(snapshot);
|
|
683
|
-
return {
|
|
684
|
-
mode: "blocking",
|
|
685
|
-
kind,
|
|
686
|
-
bootPhase,
|
|
687
|
-
title: blockingTitle(kind, bootPhase ?? "generic", snapshot),
|
|
688
|
-
message: blockingMessage(snapshot, status, error),
|
|
689
|
-
showRetry: kind === "unavailable" || kind === "error"
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
var GRIST_BOUNDARY_PREPARING_COPY = {
|
|
693
|
-
title: "Map your columns",
|
|
694
|
-
message: "Open the widget settings in Grist (Creator Panel \u2192 Custom widget) and connect each required column to your table."
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
// src/sdk/helpers/grist-sdk-alerts.ts
|
|
698
|
-
function getGristSdkAlertTitle(kind) {
|
|
699
|
-
switch (kind) {
|
|
700
|
-
case "column-mapping":
|
|
701
|
-
return "Column mapping";
|
|
702
|
-
case "mapping-pending":
|
|
703
|
-
return "Column mapping";
|
|
704
|
-
case "mapping-unreported":
|
|
705
|
-
return "Column mapping";
|
|
706
|
-
case "link-stale":
|
|
707
|
-
return "Grist connection";
|
|
708
|
-
case "section-not-linked":
|
|
709
|
-
return "Widget linking";
|
|
710
|
-
case "source-not-wired":
|
|
711
|
-
return "Widget linking";
|
|
712
|
-
case "current-table-error":
|
|
713
|
-
return "Current table";
|
|
714
|
-
case "action-error":
|
|
715
|
-
return "Action error";
|
|
716
|
-
case "access-insufficient":
|
|
717
|
-
return "Access level";
|
|
718
|
-
case "map-back-skip":
|
|
719
|
-
return "Write skipped";
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
function getGristSdkAlertSeverity(kind) {
|
|
723
|
-
switch (kind) {
|
|
724
|
-
case "action-error":
|
|
725
|
-
case "current-table-error":
|
|
726
|
-
case "access-insufficient":
|
|
727
|
-
return "error";
|
|
728
|
-
case "column-mapping":
|
|
729
|
-
case "mapping-unreported":
|
|
730
|
-
case "link-stale":
|
|
731
|
-
case "section-not-linked":
|
|
732
|
-
case "source-not-wired":
|
|
733
|
-
case "map-back-skip":
|
|
734
|
-
return "warning";
|
|
735
|
-
case "mapping-pending":
|
|
736
|
-
return "info";
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
function formatColumnMappingAlertMessage(status, hint) {
|
|
740
|
-
const parts = ["Column mapping is incomplete."];
|
|
741
|
-
if (status.missing.length > 0) {
|
|
742
|
-
parts.push(`Required columns: ${status.missing.join(", ")}.`);
|
|
743
|
-
}
|
|
744
|
-
if (status.emptyMultiples.length > 0) {
|
|
745
|
-
parts.push(
|
|
746
|
-
`Assign at least one sheet column for: ${status.emptyMultiples.join(", ")}.`
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
if (hint?.trim()) {
|
|
750
|
-
parts.push(hint.trim());
|
|
751
|
-
}
|
|
752
|
-
return parts.join(" ");
|
|
753
|
-
}
|
|
754
|
-
function formatMappingPendingAlertMessage(hint) {
|
|
755
|
-
const base = "Waiting for Grist to report column mappings.";
|
|
756
|
-
return hint?.trim() ? `${base} ${hint.trim()}` : base;
|
|
757
|
-
}
|
|
758
|
-
function formatMappingUnreportedAlertMessage(hint) {
|
|
759
|
-
const base = "Grist has not reported column mappings yet. Open the widget panel to map columns.";
|
|
760
|
-
return hint?.trim() ? `${base} ${hint.trim()}` : base;
|
|
761
|
-
}
|
|
762
|
-
function formatLinkStaleAlertMessage(snapshot) {
|
|
763
|
-
const missed = snapshot.link.missedPings;
|
|
764
|
-
const suffix = missed > 0 ? ` (${missed} missed heartbeat${missed === 1 ? "" : "s"})` : "";
|
|
765
|
-
return `Connection to Grist is stale. Edits may fail until the link recovers.${suffix}`;
|
|
766
|
-
}
|
|
767
|
-
function isWidgetSectionNotLinked(interaction) {
|
|
768
|
-
if (!interaction || typeof interaction !== "object") return false;
|
|
769
|
-
const linking = parseGristWidgetLinkingInfo(
|
|
770
|
-
interaction.linking ?? interaction.linking
|
|
771
|
-
);
|
|
772
|
-
if (!linking) return false;
|
|
773
|
-
return linking.asTarget == null;
|
|
774
|
-
}
|
|
775
|
-
function formatSectionNotLinkedAlertMessage(hint) {
|
|
776
|
-
const base = "This widget section is not linked to a table or selector. Link it from the page settings so the selected row is sent to the widget.";
|
|
777
|
-
return hint?.trim() ? `${base} ${hint.trim()}` : base;
|
|
778
|
-
}
|
|
779
|
-
function formatSourceNotWiredAlertMessage(hint) {
|
|
780
|
-
const base = "This widget is configured as a selector (allowSelectBy) but no other section is using it as a link source yet. Link another section to read from this widget.";
|
|
781
|
-
return hint?.trim() ? `${base} ${hint.trim()}` : base;
|
|
782
|
-
}
|
|
783
|
-
function pushDescriptor(out, partial) {
|
|
784
|
-
out.push({
|
|
785
|
-
title: partial.title ?? getGristSdkAlertTitle(partial.kind),
|
|
786
|
-
severity: partial.severity ?? getGristSdkAlertSeverity(partial.kind),
|
|
787
|
-
id: partial.id,
|
|
788
|
-
kind: partial.kind,
|
|
789
|
-
ariaRole: partial.ariaRole,
|
|
790
|
-
message: partial.message
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
function appendSnapshotAlerts(snapshot, options, out) {
|
|
794
|
-
const { config, link, sync } = snapshot;
|
|
795
|
-
if (config.mappings.state === "unreported") {
|
|
796
|
-
pushDescriptor(out, {
|
|
797
|
-
id: "mapping-unreported",
|
|
798
|
-
kind: "mapping-unreported",
|
|
799
|
-
ariaRole: "status",
|
|
800
|
-
message: formatMappingUnreportedAlertMessage(options?.columnMappingHint)
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
if (link.state === "stale") {
|
|
804
|
-
pushDescriptor(out, {
|
|
805
|
-
id: "link-stale",
|
|
806
|
-
kind: "link-stale",
|
|
807
|
-
ariaRole: "status",
|
|
808
|
-
message: formatLinkStaleAlertMessage(snapshot)
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
if (sync.currentTable.state === "errored") {
|
|
812
|
-
pushDescriptor(out, {
|
|
813
|
-
id: "current-table-error",
|
|
814
|
-
kind: "current-table-error",
|
|
815
|
-
ariaRole: "alert",
|
|
816
|
-
message: sync.currentTable.message
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
function getGristSdkAlertDescriptors(widget, options) {
|
|
821
|
-
const out = [];
|
|
822
|
-
if (widget.status !== "ready") {
|
|
823
|
-
return out;
|
|
824
|
-
}
|
|
825
|
-
if (widget.columnMappingStatus.pending) {
|
|
826
|
-
pushDescriptor(out, {
|
|
827
|
-
id: "mapping-pending",
|
|
828
|
-
kind: "mapping-pending",
|
|
829
|
-
ariaRole: "status",
|
|
830
|
-
message: formatMappingPendingAlertMessage(options?.mappingPendingHint)
|
|
831
|
-
});
|
|
832
|
-
} else if (!widget.columnMappingStatus.ok) {
|
|
833
|
-
pushDescriptor(out, {
|
|
834
|
-
id: "column-mapping",
|
|
835
|
-
kind: "column-mapping",
|
|
836
|
-
ariaRole: "status",
|
|
837
|
-
message: formatColumnMappingAlertMessage(
|
|
838
|
-
widget.columnMappingStatus,
|
|
839
|
-
options?.columnMappingHint
|
|
840
|
-
)
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
const accessErrorSource = widget.actionError ?? widget.readError;
|
|
844
|
-
if (accessErrorSource && isAccessInsufficientError(accessErrorSource)) {
|
|
845
|
-
pushDescriptor(out, {
|
|
846
|
-
id: "access-insufficient",
|
|
847
|
-
kind: "access-insufficient",
|
|
848
|
-
ariaRole: "alert",
|
|
849
|
-
message: formatAccessInsufficientMessage(options?.accessInsufficientHint)
|
|
850
|
-
});
|
|
851
|
-
} else if (widget.actionError) {
|
|
852
|
-
pushDescriptor(out, {
|
|
853
|
-
id: "action-error",
|
|
854
|
-
kind: "action-error",
|
|
855
|
-
ariaRole: "alert",
|
|
856
|
-
message: widget.actionError
|
|
857
|
-
});
|
|
858
|
-
} else if (widget.readError) {
|
|
859
|
-
pushDescriptor(out, {
|
|
860
|
-
id: "action-error",
|
|
861
|
-
kind: "action-error",
|
|
862
|
-
ariaRole: "alert",
|
|
863
|
-
message: widget.readError
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
const skipped = widget.mapBackSkipped ?? [];
|
|
867
|
-
if (skipped.length > 0) {
|
|
868
|
-
pushDescriptor(out, {
|
|
869
|
-
id: "map-back-skip",
|
|
870
|
-
kind: "map-back-skip",
|
|
871
|
-
ariaRole: "status",
|
|
872
|
-
message: formatMapBackSkipMessage(skipped, options?.mapBackHint)
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
if (widget.allowSelectBy) {
|
|
876
|
-
const linking = widget.widgetInteraction ? parseGristWidgetLinkingInfo(
|
|
877
|
-
widget.widgetInteraction.linking ?? widget.widgetInteraction.linking
|
|
878
|
-
) : void 0;
|
|
879
|
-
if (linking && !linking.asSource) {
|
|
880
|
-
pushDescriptor(out, {
|
|
881
|
-
id: "source-not-wired",
|
|
882
|
-
kind: "source-not-wired",
|
|
883
|
-
ariaRole: "status",
|
|
884
|
-
message: formatSourceNotWiredAlertMessage(options?.sourceNotWiredHint)
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
} else if (isWidgetSectionNotLinked(widget.widgetInteraction)) {
|
|
888
|
-
pushDescriptor(out, {
|
|
889
|
-
id: "section-not-linked",
|
|
890
|
-
kind: "section-not-linked",
|
|
891
|
-
ariaRole: "status",
|
|
892
|
-
message: formatSectionNotLinkedAlertMessage(options?.sectionNotLinkedHint)
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
if (options?.snapshot) {
|
|
896
|
-
appendSnapshotAlerts(options.snapshot, options, out);
|
|
897
|
-
}
|
|
898
|
-
if (options?.suppressKinds?.length) {
|
|
899
|
-
return out.filter((a) => !options.suppressKinds.includes(a.kind));
|
|
900
|
-
}
|
|
901
|
-
return out;
|
|
902
|
-
}
|
|
903
|
-
function formatMapBackSkipMessage(skipped, hint) {
|
|
904
|
-
const list = skipped.join(", ");
|
|
905
|
-
const base = `mapBack skipped multi-mapped column${skipped.length === 1 ? "" : "s"}: ${list}.`;
|
|
906
|
-
return hint?.trim() ? `${base} ${hint.trim()}` : base;
|
|
907
|
-
}
|
|
908
|
-
var ACCESS_ERROR_PATTERNS = [
|
|
909
|
-
/access not granted/i,
|
|
910
|
-
/need higher access level/i,
|
|
911
|
-
/requires.*full.*access/i,
|
|
912
|
-
/RPC_ONCALL_ERROR Access/i
|
|
913
|
-
];
|
|
914
|
-
function isAccessInsufficientError(message) {
|
|
915
|
-
return ACCESS_ERROR_PATTERNS.some((re) => re.test(message));
|
|
916
|
-
}
|
|
917
|
-
function formatAccessInsufficientMessage(hint) {
|
|
918
|
-
const base = 'This operation requires "Full document access". Set the Access level to "Full document access" in the Custom widget configuration panel.';
|
|
919
|
-
return hint?.trim() ? `${base} ${hint.trim()}` : base;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// src/sdk/hooks/use-grist-sdk-alert-descriptors.ts
|
|
923
|
-
import { useMemo as useMemo12, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
924
|
-
|
|
925
|
-
// src/sdk/components/grist-widget-provider.tsx
|
|
926
|
-
import {
|
|
927
|
-
useContext,
|
|
928
|
-
useEffect as useEffect4,
|
|
929
|
-
useRef as useRef5
|
|
930
|
-
} from "react";
|
|
931
|
-
|
|
932
|
-
// src/sdk/internal/grist-widget-context-tree.tsx
|
|
933
|
-
import { createContext, useMemo as useMemo11 } from "react";
|
|
934
|
-
|
|
935
|
-
// src/sdk/internal/compose-grist-widget.ts
|
|
936
|
-
import { useMemo as useMemo10 } from "react";
|
|
937
|
-
|
|
938
|
-
// src/sdk/internal/hooks/use-grist-current-table.ts
|
|
939
|
-
import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
940
|
-
function useGristCurrentTable(docApi) {
|
|
941
|
-
const manager = useGristManager();
|
|
942
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
943
|
-
const snapshot = useSyncExternalStore(
|
|
944
|
-
manager.subscribe,
|
|
945
|
-
manager.getSnapshot,
|
|
946
|
-
initialGristSnapshot
|
|
947
|
-
);
|
|
948
|
-
const currentTable = snapshot.sync.currentTable;
|
|
949
|
-
const currentTableId = currentTable.state === "known" ? currentTable.tableId : null;
|
|
950
|
-
const currentTableLoading = currentTable.state === "known" ? currentTable.refreshing : false;
|
|
951
|
-
const tableError = currentTable.state === "errored" ? currentTable.message : null;
|
|
952
|
-
const refreshCurrentTable = useCallback(async () => {
|
|
953
|
-
if (!docApi) return;
|
|
954
|
-
if (typeof grist === "undefined") return;
|
|
955
|
-
const atMs = Date.now();
|
|
956
|
-
manager.dispatch({ type: "CURRENT_TABLE_REFRESH_START", atMs });
|
|
957
|
-
try {
|
|
958
|
-
const next = await coalesceRpc(
|
|
959
|
-
() => Promise.resolve(grist.selectedTable?.getTableId())
|
|
960
|
-
);
|
|
961
|
-
if (next) {
|
|
962
|
-
manager.dispatch({ type: "CURRENT_TABLE_KNOWN", tableId: next, atMs: Date.now() });
|
|
963
|
-
}
|
|
964
|
-
} catch (err) {
|
|
965
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
966
|
-
manager.dispatch({ type: "CURRENT_TABLE_ERROR", message, atMs: Date.now() });
|
|
967
|
-
}
|
|
968
|
-
}, [docApi, coalesceRpc, manager]);
|
|
969
|
-
useEffect(() => {
|
|
970
|
-
if (docApi) void refreshCurrentTable();
|
|
971
|
-
}, [docApi, refreshCurrentTable]);
|
|
972
|
-
useEffect(() => {
|
|
973
|
-
return recordEvent.subscribe(() => {
|
|
974
|
-
if (!docApi) return;
|
|
975
|
-
void refreshCurrentTable();
|
|
976
|
-
});
|
|
977
|
-
}, [docApi, refreshCurrentTable]);
|
|
978
|
-
return useMemo(
|
|
979
|
-
() => ({
|
|
980
|
-
currentTableId,
|
|
981
|
-
currentTableLoading,
|
|
982
|
-
refreshCurrentTable,
|
|
983
|
-
tableError
|
|
984
|
-
}),
|
|
985
|
-
[currentTableId, currentTableLoading, refreshCurrentTable, tableError]
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// src/sdk/internal/hooks/use-grist-selection.ts
|
|
990
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef, useState } from "react";
|
|
991
|
-
var EMPTY_MAPPINGS = Object.freeze({});
|
|
992
|
-
function stableColumnMappingStatus(prev, mappingState, columns) {
|
|
993
|
-
const next = deriveColumnMappingStatus(mappingState, columns);
|
|
994
|
-
if (prev && prev.ok === next.ok && prev.pending === next.pending && prev.missing.length === next.missing.length && prev.emptyMultiples.length === next.emptyMultiples.length && prev.missing.every((m, i) => m === next.missing[i]) && prev.emptyMultiples.every((m, i) => m === next.emptyMultiples[i])) {
|
|
995
|
-
return prev;
|
|
996
|
-
}
|
|
997
|
-
return next;
|
|
998
|
-
}
|
|
999
|
-
function copyRowRecord(record) {
|
|
1000
|
-
if (!record) return null;
|
|
1001
|
-
return { ...record };
|
|
1002
|
-
}
|
|
1003
|
-
function copyRowRecords(records) {
|
|
1004
|
-
if (!records) return null;
|
|
1005
|
-
return records.map((row) => ({ ...row }));
|
|
1006
|
-
}
|
|
1007
|
-
function copyMappedRecord(mapped) {
|
|
1008
|
-
if (mapped === null || mapped === void 0) return null;
|
|
1009
|
-
if (typeof mapped !== "object") return mapped;
|
|
1010
|
-
return { ...mapped };
|
|
1011
|
-
}
|
|
1012
|
-
function mappingStateKey(state) {
|
|
1013
|
-
return JSON.stringify(state);
|
|
1014
|
-
}
|
|
1015
|
-
function useMappingProjection() {
|
|
1016
|
-
const manager = useGristManager();
|
|
1017
|
-
const [mappingState, setMappingState] = useState(
|
|
1018
|
-
() => manager.getSnapshot().config.mappings
|
|
1019
|
-
);
|
|
1020
|
-
useEffect2(() => {
|
|
1021
|
-
let lastKey = mappingStateKey(manager.getSnapshot().config.mappings);
|
|
1022
|
-
return manager.subscribe(() => {
|
|
1023
|
-
const next = manager.getSnapshot().config.mappings;
|
|
1024
|
-
const nextKey = mappingStateKey(next);
|
|
1025
|
-
if (nextKey !== lastKey) {
|
|
1026
|
-
lastKey = nextKey;
|
|
1027
|
-
setMappingState(next);
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
}, [manager]);
|
|
1031
|
-
return mappingState;
|
|
1032
|
-
}
|
|
1033
|
-
function useGristSelectionSubscription(docApi, options) {
|
|
1034
|
-
const subscribeToWidgetOptions = options?.subscribeToWidgetOptions ?? true;
|
|
1035
|
-
const subscribeToNewRecord = options?.subscribeToNewRecord ?? true;
|
|
1036
|
-
const mappingState = useMappingProjection();
|
|
1037
|
-
const [record, setRecord] = useState(null);
|
|
1038
|
-
const [records, setRecords] = useState(null);
|
|
1039
|
-
const [mappedRecord, setMappedRecord] = useState(null);
|
|
1040
|
-
const [streamMappings, setStreamMappings] = useState(null);
|
|
1041
|
-
const [recordsMappings, setRecordsMappings] = useState(null);
|
|
1042
|
-
const [widgetOptions, setWidgetOptionsMirror] = useState(null);
|
|
1043
|
-
const [widgetInteraction, setWidgetInteraction] = useState(null);
|
|
1044
|
-
const [isNewRecord, setIsNewRecord] = useState(false);
|
|
1045
|
-
const [newRecordMappings, setNewRecordMappings] = useState(null);
|
|
1046
|
-
const [mapBackSkipped, setMapBackSkipped] = useState([]);
|
|
1047
|
-
const mapBackSkippedRef = useRef([]);
|
|
1048
|
-
const fsmMappings = extractResolvedMappings(mappingState);
|
|
1049
|
-
const mappings = useMemo2(
|
|
1050
|
-
() => fsmMappings ?? streamMappings ?? EMPTY_MAPPINGS,
|
|
1051
|
-
[fsmMappings, streamMappings]
|
|
1052
|
-
);
|
|
1053
|
-
const columnMappingStatusRef = useRef(void 0);
|
|
1054
|
-
const columnMappingStatus = useMemo2(() => {
|
|
1055
|
-
const next = stableColumnMappingStatus(
|
|
1056
|
-
columnMappingStatusRef.current,
|
|
1057
|
-
mappingState,
|
|
1058
|
-
options?.columns
|
|
1059
|
-
);
|
|
1060
|
-
columnMappingStatusRef.current = next;
|
|
1061
|
-
return next;
|
|
1062
|
-
}, [mappingState, options?.columns]);
|
|
1063
|
-
const applyRecordPayload = useCallback2(
|
|
1064
|
-
({ record: next, mappings: nextMappings }) => {
|
|
1065
|
-
setStreamMappings(nextMappings);
|
|
1066
|
-
setRecord(copyRowRecord(next));
|
|
1067
|
-
if (!next) {
|
|
1068
|
-
setMappedRecord(null);
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
setIsNewRecord(false);
|
|
1072
|
-
if (typeof grist !== "undefined" && typeof grist.mapColumnNames === "function") {
|
|
1073
|
-
setMappedRecord(copyMappedRecord(grist.mapColumnNames(next)));
|
|
1074
|
-
} else {
|
|
1075
|
-
setMappedRecord(copyMappedRecord(next));
|
|
1076
|
-
}
|
|
1077
|
-
},
|
|
1078
|
-
[]
|
|
1079
|
-
);
|
|
1080
|
-
useEffect2(() => {
|
|
1081
|
-
if (typeof grist === "undefined") return;
|
|
1082
|
-
return recordEvent.subscribe(applyRecordPayload);
|
|
1083
|
-
}, [applyRecordPayload]);
|
|
1084
|
-
useEffect2(() => {
|
|
1085
|
-
if (typeof grist === "undefined") return;
|
|
1086
|
-
return recordsEvent.subscribe(({ records: next, mappings: nextMappings }) => {
|
|
1087
|
-
setRecords(copyRowRecords(next));
|
|
1088
|
-
setRecordsMappings(nextMappings);
|
|
1089
|
-
});
|
|
1090
|
-
}, []);
|
|
1091
|
-
useEffect2(() => {
|
|
1092
|
-
if (!subscribeToWidgetOptions || typeof grist === "undefined") return;
|
|
1093
|
-
return optionsEvent.subscribe(({ options: next, interaction }) => {
|
|
1094
|
-
setWidgetOptionsMirror(next);
|
|
1095
|
-
setWidgetInteraction(interaction);
|
|
1096
|
-
});
|
|
1097
|
-
}, [subscribeToWidgetOptions]);
|
|
1098
|
-
useEffect2(() => {
|
|
1099
|
-
if (!subscribeToNewRecord || typeof grist === "undefined") return;
|
|
1100
|
-
if (typeof grist.onNewRecord !== "function") return;
|
|
1101
|
-
return newRecordEvent.subscribe(({ mappings: nextMappings }) => {
|
|
1102
|
-
setIsNewRecord(true);
|
|
1103
|
-
setNewRecordMappings(nextMappings);
|
|
1104
|
-
});
|
|
1105
|
-
}, [subscribeToNewRecord]);
|
|
1106
|
-
const prevDocApiRef = useRef(null);
|
|
1107
|
-
useEffect2(() => {
|
|
1108
|
-
const hadDocApi = prevDocApiRef.current !== null;
|
|
1109
|
-
prevDocApiRef.current = docApi;
|
|
1110
|
-
if (docApi) {
|
|
1111
|
-
if (!hadDocApi && recordEvent.ready) {
|
|
1112
|
-
const cached = recordEvent.peek();
|
|
1113
|
-
if (cached !== void 0) applyRecordPayload(cached);
|
|
1114
|
-
}
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
if (!hadDocApi) return;
|
|
1118
|
-
setRecord(null);
|
|
1119
|
-
setRecords(null);
|
|
1120
|
-
setMappedRecord(null);
|
|
1121
|
-
setStreamMappings(null);
|
|
1122
|
-
setRecordsMappings(null);
|
|
1123
|
-
setIsNewRecord(false);
|
|
1124
|
-
setNewRecordMappings(null);
|
|
1125
|
-
setWidgetOptionsMirror(null);
|
|
1126
|
-
setWidgetInteraction(null);
|
|
1127
|
-
}, [docApi, applyRecordPayload]);
|
|
1128
|
-
const resolveMappedColumnId = useCallback2(
|
|
1129
|
-
(logicalFieldName) => {
|
|
1130
|
-
const value = mappings[logicalFieldName];
|
|
1131
|
-
if (typeof value === "string") return value;
|
|
1132
|
-
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
1133
|
-
return logicalFieldName;
|
|
1134
|
-
},
|
|
1135
|
-
[mappings]
|
|
1136
|
-
);
|
|
1137
|
-
const mapBack = useCallback2(
|
|
1138
|
-
(patch) => {
|
|
1139
|
-
const skipped = [];
|
|
1140
|
-
for (const name of Object.keys(patch)) {
|
|
1141
|
-
if (Array.isArray(mappings[name])) skipped.push(name);
|
|
1142
|
-
}
|
|
1143
|
-
const computeResult = () => {
|
|
1144
|
-
if (typeof grist.mapColumnNamesBack === "function") {
|
|
1145
|
-
const out2 = grist.mapColumnNamesBack(patch);
|
|
1146
|
-
for (const key of Object.keys(out2)) {
|
|
1147
|
-
if (out2[key] === void 0) {
|
|
1148
|
-
delete out2[key];
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
return out2;
|
|
1152
|
-
}
|
|
1153
|
-
const out = {};
|
|
1154
|
-
for (const [name, value] of Object.entries(patch)) {
|
|
1155
|
-
const target = mappings[name];
|
|
1156
|
-
if (Array.isArray(target)) continue;
|
|
1157
|
-
out[target ?? name] = value;
|
|
1158
|
-
}
|
|
1159
|
-
return out;
|
|
1160
|
-
};
|
|
1161
|
-
const result = computeResult();
|
|
1162
|
-
const previous = mapBackSkippedRef.current;
|
|
1163
|
-
const same = previous.length === skipped.length && previous.every((name, i) => name === skipped[i]);
|
|
1164
|
-
if (!same) {
|
|
1165
|
-
mapBackSkippedRef.current = skipped;
|
|
1166
|
-
setMapBackSkipped(skipped);
|
|
1167
|
-
}
|
|
1168
|
-
return result;
|
|
1169
|
-
},
|
|
1170
|
-
[mappings]
|
|
1171
|
-
);
|
|
1172
|
-
const mode = isNewRecord ? "new-row" : record ? "row" : "empty";
|
|
1173
|
-
return useMemo2(
|
|
1174
|
-
() => ({
|
|
1175
|
-
record,
|
|
1176
|
-
records,
|
|
1177
|
-
mappedRecord,
|
|
1178
|
-
mappings,
|
|
1179
|
-
recordsMappings,
|
|
1180
|
-
columnMappingStatus,
|
|
1181
|
-
mode,
|
|
1182
|
-
resolveMappedColumnId,
|
|
1183
|
-
mapBack,
|
|
1184
|
-
mapBackSkipped,
|
|
1185
|
-
isNewRecord,
|
|
1186
|
-
newRecordMappings,
|
|
1187
|
-
widgetOptions,
|
|
1188
|
-
widgetInteraction
|
|
1189
|
-
}),
|
|
1190
|
-
[
|
|
1191
|
-
record,
|
|
1192
|
-
records,
|
|
1193
|
-
mappedRecord,
|
|
1194
|
-
mappings,
|
|
1195
|
-
recordsMappings,
|
|
1196
|
-
columnMappingStatus,
|
|
1197
|
-
mode,
|
|
1198
|
-
resolveMappedColumnId,
|
|
1199
|
-
mapBack,
|
|
1200
|
-
mapBackSkipped,
|
|
1201
|
-
isNewRecord,
|
|
1202
|
-
newRecordMappings,
|
|
1203
|
-
widgetOptions,
|
|
1204
|
-
widgetInteraction
|
|
1205
|
-
]
|
|
1206
|
-
);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
// src/sdk/internal/hooks/use-grist-table-ops-bound.ts
|
|
1210
|
-
import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
1211
|
-
function resolveSelectedTableOps() {
|
|
1212
|
-
if (grist.selectedTable) return grist.selectedTable;
|
|
1213
|
-
throw new Error("grist.selectedTable is not available in this Grist version");
|
|
1214
|
-
}
|
|
1215
|
-
function resolveTableOps(tableId) {
|
|
1216
|
-
if (typeof grist.getTable !== "function") {
|
|
1217
|
-
throw new Error("grist.getTable is not available in this Grist version");
|
|
1218
|
-
}
|
|
1219
|
-
return grist.getTable(tableId);
|
|
1220
|
-
}
|
|
1221
|
-
function useGristTableOpsBound(docApi) {
|
|
1222
|
-
const [actionStatus, setActionStatus] = useState2("idle");
|
|
1223
|
-
const [actionError, setActionError] = useState2(null);
|
|
1224
|
-
const tableCacheRef = useRef2(/* @__PURE__ */ new Map());
|
|
1225
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
1226
|
-
const trackOp = useCallback3(
|
|
1227
|
-
async (fn) => {
|
|
1228
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1229
|
-
setActionStatus("running");
|
|
1230
|
-
setActionError(null);
|
|
1231
|
-
try {
|
|
1232
|
-
const out = await coalesceRpc(fn);
|
|
1233
|
-
setActionStatus("idle");
|
|
1234
|
-
return out;
|
|
1235
|
-
} catch (err) {
|
|
1236
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1237
|
-
setActionError(message);
|
|
1238
|
-
setActionStatus("error");
|
|
1239
|
-
throw err;
|
|
1240
|
-
}
|
|
1241
|
-
},
|
|
1242
|
-
[docApi, coalesceRpc]
|
|
1243
|
-
);
|
|
1244
|
-
const wrapTableOps = useCallback3(
|
|
1245
|
-
(resolve) => ({
|
|
1246
|
-
getTableId: () => trackOp(() => resolve().getTableId()),
|
|
1247
|
-
create: (records, opOptions) => trackOp(() => resolve().create(records, opOptions)),
|
|
1248
|
-
update: (records, opOptions) => trackOp(() => resolve().update(records, opOptions)),
|
|
1249
|
-
upsert: (records, opOptions) => trackOp(() => resolve().upsert(records, opOptions)),
|
|
1250
|
-
destroy: (recordIds) => trackOp(() => resolve().destroy(recordIds))
|
|
1251
|
-
}),
|
|
1252
|
-
[trackOp]
|
|
1253
|
-
);
|
|
1254
|
-
const table = useMemo3(() => wrapTableOps(() => resolveSelectedTableOps()), [wrapTableOps]);
|
|
1255
|
-
const getTable = useCallback3(
|
|
1256
|
-
(tableId) => {
|
|
1257
|
-
const cached = tableCacheRef.current.get(tableId);
|
|
1258
|
-
if (cached) return cached;
|
|
1259
|
-
const wrapped = wrapTableOps(() => resolveTableOps(tableId));
|
|
1260
|
-
tableCacheRef.current.set(tableId, wrapped);
|
|
1261
|
-
return wrapped;
|
|
1262
|
-
},
|
|
1263
|
-
[wrapTableOps]
|
|
1264
|
-
);
|
|
1265
|
-
const applyActions = useCallback3(
|
|
1266
|
-
async (actions, applyOptions) => {
|
|
1267
|
-
if (!docApi) throw new Error("Grist docApi is not ready");
|
|
1268
|
-
setActionStatus("running");
|
|
1269
|
-
setActionError(null);
|
|
1270
|
-
try {
|
|
1271
|
-
const out = await coalesceRpc(() => docApi.applyUserActions(actions, applyOptions));
|
|
1272
|
-
setActionStatus("idle");
|
|
1273
|
-
return out;
|
|
1274
|
-
} catch (err) {
|
|
1275
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1276
|
-
setActionError(message);
|
|
1277
|
-
setActionStatus("error");
|
|
1278
|
-
throw err;
|
|
1279
|
-
}
|
|
1280
|
-
},
|
|
1281
|
-
[docApi, coalesceRpc]
|
|
1282
|
-
);
|
|
1283
|
-
return useMemo3(
|
|
1284
|
-
() => ({ table, getTable, actionStatus, actionError, applyActions }),
|
|
1285
|
-
[table, getTable, actionStatus, actionError, applyActions]
|
|
1286
|
-
);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
// src/sdk/internal/hooks/use-grist-reads.ts
|
|
1290
|
-
import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
|
|
1291
|
-
var SELECTED_FETCH_DEFAULTS = {
|
|
1292
|
-
keepEncoded: false,
|
|
1293
|
-
format: "rows"
|
|
1294
|
-
};
|
|
1295
|
-
function useGristReads(docApi) {
|
|
1296
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
1297
|
-
const manager = useGristManagerOptional();
|
|
1298
|
-
const [readError, setReadError] = useState3(null);
|
|
1299
|
-
const readErrorRef = useRef3(null);
|
|
1300
|
-
const requireFullAccess = useCallback4(
|
|
1301
|
-
(label) => {
|
|
1302
|
-
const snap = manager?.getSnapshot();
|
|
1303
|
-
if (!snap) return;
|
|
1304
|
-
const { authz, sync } = snap;
|
|
1305
|
-
if (sync.options.state !== "hot") return;
|
|
1306
|
-
if (authz.state === "granted" && authz.level !== "full") {
|
|
1307
|
-
const msg = `${label} requires "Full document access". Current access level is "${authz.level}". Set Access level to "Full document access" in the Custom widget configuration panel.`;
|
|
1308
|
-
if (readErrorRef.current !== msg) {
|
|
1309
|
-
readErrorRef.current = msg;
|
|
1310
|
-
setReadError(msg);
|
|
1311
|
-
}
|
|
1312
|
-
throw new Error(msg);
|
|
1313
|
-
}
|
|
1314
|
-
},
|
|
1315
|
-
[manager]
|
|
1316
|
-
);
|
|
1317
|
-
const guardedRpc = useCallback4(
|
|
1318
|
-
async (fn) => {
|
|
1319
|
-
try {
|
|
1320
|
-
const result = await coalesceRpc(fn);
|
|
1321
|
-
if (readErrorRef.current !== null) {
|
|
1322
|
-
readErrorRef.current = null;
|
|
1323
|
-
setReadError(null);
|
|
1324
|
-
}
|
|
1325
|
-
return result;
|
|
1326
|
-
} catch (err) {
|
|
1327
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1328
|
-
if (isAccessInsufficientError(msg)) {
|
|
1329
|
-
if (readErrorRef.current !== msg) {
|
|
1330
|
-
readErrorRef.current = msg;
|
|
1331
|
-
setReadError(msg);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
throw err;
|
|
1335
|
-
}
|
|
1336
|
-
},
|
|
1337
|
-
[coalesceRpc]
|
|
1338
|
-
);
|
|
1339
|
-
const fetchTable = useCallback4(
|
|
1340
|
-
async (tableId) => {
|
|
1341
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1342
|
-
requireFullAccess("fetchTable");
|
|
1343
|
-
return guardedRpc(() => docApi.fetchTable(tableId));
|
|
1344
|
-
},
|
|
1345
|
-
[docApi, requireFullAccess, guardedRpc]
|
|
1346
|
-
);
|
|
1347
|
-
const fetchRow = useCallback4(
|
|
1348
|
-
async (tableId, rowId) => {
|
|
1349
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1350
|
-
requireFullAccess("fetchRow");
|
|
1351
|
-
return guardedRpc(() => fetchGristRow(docApi, tableId, rowId));
|
|
1352
|
-
},
|
|
1353
|
-
[docApi, requireFullAccess, guardedRpc]
|
|
1354
|
-
);
|
|
1355
|
-
const fetchTableRows = useCallback4(
|
|
1356
|
-
async (tableId, options) => {
|
|
1357
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1358
|
-
requireFullAccess("fetchTableRows");
|
|
1359
|
-
if (options?.safeParse) {
|
|
1360
|
-
return guardedRpc(
|
|
1361
|
-
() => fetchGristTableRows(
|
|
1362
|
-
docApi,
|
|
1363
|
-
tableId,
|
|
1364
|
-
options
|
|
1365
|
-
)
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
if (options) {
|
|
1369
|
-
return guardedRpc(() => fetchGristTableRows(docApi, tableId, options));
|
|
1370
|
-
}
|
|
1371
|
-
return guardedRpc(() => fetchGristTableRows(docApi, tableId));
|
|
1372
|
-
},
|
|
1373
|
-
[docApi, requireFullAccess, guardedRpc]
|
|
1374
|
-
);
|
|
1375
|
-
const listTables = useCallback4(
|
|
1376
|
-
async (options) => {
|
|
1377
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1378
|
-
return guardedRpc(async () => {
|
|
1379
|
-
const out = await Promise.resolve(docApi.listTables());
|
|
1380
|
-
const tables = [...out];
|
|
1381
|
-
if (options?.includeSystem) return tables;
|
|
1382
|
-
return tables.filter(
|
|
1383
|
-
(t) => !t.startsWith("_grist") && !t.startsWith("GristHidden_")
|
|
1384
|
-
);
|
|
1385
|
-
});
|
|
1386
|
-
},
|
|
1387
|
-
[docApi, guardedRpc]
|
|
1388
|
-
);
|
|
1389
|
-
const listColumns = useCallback4(
|
|
1390
|
-
async (tableId, options) => {
|
|
1391
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1392
|
-
requireFullAccess("listColumns");
|
|
1393
|
-
return guardedRpc(async () => {
|
|
1394
|
-
const metaByTable = await loadColumnMetadataByTable(docApi);
|
|
1395
|
-
const colMeta = metaByTable.get(tableId);
|
|
1396
|
-
if (!colMeta) return [];
|
|
1397
|
-
const noiseOpts = {
|
|
1398
|
-
excludeId: options?.excludeId ?? true,
|
|
1399
|
-
excludeGristHelpers: options?.excludeGristHelpers ?? true,
|
|
1400
|
-
excludeLoggingColumns: options?.excludeLoggingColumns ?? true,
|
|
1401
|
-
excludeManualSort: options?.excludeManualSort ?? true
|
|
1402
|
-
};
|
|
1403
|
-
const result = [];
|
|
1404
|
-
for (const [colId, meta] of colMeta) {
|
|
1405
|
-
if (!options?.includeSystem && isGristNoiseColumn(colId, meta, noiseOpts)) {
|
|
1406
|
-
continue;
|
|
1407
|
-
}
|
|
1408
|
-
result.push({
|
|
1409
|
-
colId,
|
|
1410
|
-
type: meta.type,
|
|
1411
|
-
label: meta.label,
|
|
1412
|
-
isFormula: meta.isFormula,
|
|
1413
|
-
formula: meta.formula,
|
|
1414
|
-
description: meta.description
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
return result;
|
|
1418
|
-
});
|
|
1419
|
-
},
|
|
1420
|
-
[docApi, requireFullAccess, guardedRpc]
|
|
1421
|
-
);
|
|
1422
|
-
const getDocName = useCallback4(async () => {
|
|
1423
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1424
|
-
return guardedRpc(async () => String(await Promise.resolve(docApi.getDocName())));
|
|
1425
|
-
}, [docApi, guardedRpc]);
|
|
1426
|
-
const buildReplicaDocumentFromDocApi2 = useCallback4(
|
|
1427
|
-
async (opts) => {
|
|
1428
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1429
|
-
requireFullAccess("buildReplicaDocumentFromDocApi");
|
|
1430
|
-
return guardedRpc(() => buildReplicaDocumentFromDocApi(docApi, opts));
|
|
1431
|
-
},
|
|
1432
|
-
[docApi, requireFullAccess, guardedRpc]
|
|
1433
|
-
);
|
|
1434
|
-
const fetchSelectedTable = useCallback4(
|
|
1435
|
-
async (fetchOptions) => {
|
|
1436
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1437
|
-
const impl = docApi.fetchSelectedTable ?? grist.fetchSelectedTable;
|
|
1438
|
-
if (!impl) throw new Error("fetchSelectedTable is not available in this Grist version");
|
|
1439
|
-
const merged = { ...SELECTED_FETCH_DEFAULTS, ...fetchOptions };
|
|
1440
|
-
return guardedRpc(async () => await impl(merged));
|
|
1441
|
-
},
|
|
1442
|
-
[docApi, guardedRpc]
|
|
1443
|
-
);
|
|
1444
|
-
const fetchSelectedRecord = useCallback4(
|
|
1445
|
-
async (rowId, fetchOptions) => {
|
|
1446
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1447
|
-
const impl = docApi.fetchSelectedRecord ?? grist.fetchSelectedRecord;
|
|
1448
|
-
if (!impl) throw new Error("fetchSelectedRecord is not available in this Grist version");
|
|
1449
|
-
const merged = { ...SELECTED_FETCH_DEFAULTS, ...fetchOptions };
|
|
1450
|
-
return guardedRpc(async () => await impl(rowId, merged));
|
|
1451
|
-
},
|
|
1452
|
-
[docApi, guardedRpc]
|
|
1453
|
-
);
|
|
1454
|
-
return useMemo4(
|
|
1455
|
-
() => ({
|
|
1456
|
-
fetchTable,
|
|
1457
|
-
fetchTableRows,
|
|
1458
|
-
fetchRow,
|
|
1459
|
-
listTables,
|
|
1460
|
-
listColumns,
|
|
1461
|
-
getDocName,
|
|
1462
|
-
buildReplicaDocumentFromDocApi: buildReplicaDocumentFromDocApi2,
|
|
1463
|
-
fetchSelectedTable,
|
|
1464
|
-
fetchSelectedRecord,
|
|
1465
|
-
readError
|
|
1466
|
-
}),
|
|
1467
|
-
[
|
|
1468
|
-
fetchTable,
|
|
1469
|
-
fetchTableRows,
|
|
1470
|
-
fetchRow,
|
|
1471
|
-
listTables,
|
|
1472
|
-
listColumns,
|
|
1473
|
-
getDocName,
|
|
1474
|
-
buildReplicaDocumentFromDocApi2,
|
|
1475
|
-
fetchSelectedTable,
|
|
1476
|
-
fetchSelectedRecord,
|
|
1477
|
-
readError
|
|
1478
|
-
]
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
// src/sdk/internal/hooks/use-grist-widget-options-api.ts
|
|
1483
|
-
import { useCallback as useCallback5, useMemo as useMemo5 } from "react";
|
|
1484
|
-
function useGristWidgetOptionsApi() {
|
|
1485
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
1486
|
-
const getWidgetOptions = useCallback5(
|
|
1487
|
-
async () => coalesceRpc(async () => {
|
|
1488
|
-
const next = await grist.getOptions();
|
|
1489
|
-
return next;
|
|
1490
|
-
}),
|
|
1491
|
-
[coalesceRpc]
|
|
1492
|
-
);
|
|
1493
|
-
const getWidgetOption = useCallback5(
|
|
1494
|
-
async (key) => coalesceRpc(() => Promise.resolve(grist.getOption(key))),
|
|
1495
|
-
[coalesceRpc]
|
|
1496
|
-
);
|
|
1497
|
-
const setWidgetOption = useCallback5(
|
|
1498
|
-
async (key, value) => {
|
|
1499
|
-
await coalesceRpc(() => Promise.resolve(grist.setOption(key, value)));
|
|
1500
|
-
},
|
|
1501
|
-
[coalesceRpc]
|
|
1502
|
-
);
|
|
1503
|
-
const setWidgetOptions = useCallback5(
|
|
1504
|
-
async (next) => {
|
|
1505
|
-
await coalesceRpc(() => Promise.resolve(grist.setOptions(next)));
|
|
1506
|
-
},
|
|
1507
|
-
[coalesceRpc]
|
|
1508
|
-
);
|
|
1509
|
-
const patchWidgetOptions = useCallback5(
|
|
1510
|
-
async (patch) => {
|
|
1511
|
-
await coalesceRpc(async () => {
|
|
1512
|
-
const current = await grist.getOptions();
|
|
1513
|
-
await grist.setOptions({ ...current ?? {}, ...patch });
|
|
1514
|
-
});
|
|
1515
|
-
},
|
|
1516
|
-
[coalesceRpc]
|
|
1517
|
-
);
|
|
1518
|
-
const clearWidgetOptions = useCallback5(
|
|
1519
|
-
async () => {
|
|
1520
|
-
await coalesceRpc(() => Promise.resolve(grist.clearOptions()));
|
|
1521
|
-
},
|
|
1522
|
-
[coalesceRpc]
|
|
1523
|
-
);
|
|
1524
|
-
return useMemo5(
|
|
1525
|
-
() => ({
|
|
1526
|
-
getWidgetOptions,
|
|
1527
|
-
getWidgetOption,
|
|
1528
|
-
setWidgetOption,
|
|
1529
|
-
setWidgetOptions,
|
|
1530
|
-
patchWidgetOptions,
|
|
1531
|
-
clearWidgetOptions
|
|
1532
|
-
}),
|
|
1533
|
-
[
|
|
1534
|
-
getWidgetOptions,
|
|
1535
|
-
getWidgetOption,
|
|
1536
|
-
setWidgetOption,
|
|
1537
|
-
setWidgetOptions,
|
|
1538
|
-
patchWidgetOptions,
|
|
1539
|
-
clearWidgetOptions
|
|
1540
|
-
]
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
// src/sdk/internal/hooks/use-grist-linking.ts
|
|
1545
|
-
import { useCallback as useCallback6, useMemo as useMemo6 } from "react";
|
|
1546
|
-
function useGristLinking() {
|
|
1547
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
1548
|
-
const setCursorPosition = useCallback6(
|
|
1549
|
-
async (pos) => {
|
|
1550
|
-
if (!grist.setCursorPos) throw new Error("setCursorPos is not available in this Grist version");
|
|
1551
|
-
await coalesceRpc(() => Promise.resolve(grist.setCursorPos(pos)));
|
|
1552
|
-
},
|
|
1553
|
-
[coalesceRpc]
|
|
1554
|
-
);
|
|
1555
|
-
const setLinkedRowSelection = useCallback6(
|
|
1556
|
-
async (rowIds) => {
|
|
1557
|
-
if (!grist.setSelectedRows) throw new Error("setSelectedRows is not available in this Grist version");
|
|
1558
|
-
await coalesceRpc(() => Promise.resolve(grist.setSelectedRows(rowIds)));
|
|
1559
|
-
},
|
|
1560
|
-
[coalesceRpc]
|
|
1561
|
-
);
|
|
1562
|
-
return useMemo6(
|
|
1563
|
-
() => ({ setCursorPosition, setLinkedRowSelection }),
|
|
1564
|
-
[setCursorPosition, setLinkedRowSelection]
|
|
1565
|
-
);
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
// src/sdk/internal/hooks/use-grist-attachments-rest.ts
|
|
1569
|
-
import { useCallback as useCallback7, useMemo as useMemo7, useRef as useRef4, useState as useState4 } from "react";
|
|
1570
|
-
function useGristAttachmentsRest(docApi) {
|
|
1571
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
1572
|
-
const [readError, setReadError] = useState4(null);
|
|
1573
|
-
const readErrorRef = useRef4(null);
|
|
1574
|
-
const guardedRpc = useCallback7(
|
|
1575
|
-
async (fn) => {
|
|
1576
|
-
try {
|
|
1577
|
-
const result = await coalesceRpc(fn);
|
|
1578
|
-
if (readErrorRef.current !== null) {
|
|
1579
|
-
readErrorRef.current = null;
|
|
1580
|
-
setReadError(null);
|
|
1581
|
-
}
|
|
1582
|
-
return result;
|
|
1583
|
-
} catch (err) {
|
|
1584
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1585
|
-
if (isAccessInsufficientError(msg)) {
|
|
1586
|
-
if (readErrorRef.current !== msg) {
|
|
1587
|
-
readErrorRef.current = msg;
|
|
1588
|
-
setReadError(msg);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
throw err;
|
|
1592
|
-
}
|
|
1593
|
-
},
|
|
1594
|
-
[coalesceRpc]
|
|
1595
|
-
);
|
|
1596
|
-
const getAttachmentUrl = useCallback7(
|
|
1597
|
-
async (attachmentId, opts) => {
|
|
1598
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1599
|
-
return guardedRpc(() => getAttachmentDownloadUrl(docApi, attachmentId, opts));
|
|
1600
|
-
},
|
|
1601
|
-
[docApi, guardedRpc]
|
|
1602
|
-
);
|
|
1603
|
-
const fetchAttachmentBase642 = useCallback7(
|
|
1604
|
-
async (attachmentId, opts) => {
|
|
1605
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1606
|
-
return guardedRpc(() => fetchAttachmentBase64(docApi, attachmentId, opts));
|
|
1607
|
-
},
|
|
1608
|
-
[docApi, guardedRpc]
|
|
1609
|
-
);
|
|
1610
|
-
const fetchAttachmentBlob2 = useCallback7(
|
|
1611
|
-
async (attachmentId, opts) => {
|
|
1612
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1613
|
-
return guardedRpc(() => fetchAttachmentBlob(docApi, attachmentId, opts));
|
|
1614
|
-
},
|
|
1615
|
-
[docApi, guardedRpc]
|
|
1616
|
-
);
|
|
1617
|
-
const getAccessToken = useCallback7(
|
|
1618
|
-
async (opts) => {
|
|
1619
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1620
|
-
return guardedRpc(() => getGristAccessToken(docApi, opts));
|
|
1621
|
-
},
|
|
1622
|
-
[docApi, guardedRpc]
|
|
1623
|
-
);
|
|
1624
|
-
const fetchWithAuth = useCallback7(
|
|
1625
|
-
(path, init) => {
|
|
1626
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1627
|
-
return guardedRpc(() => fetchWithGristAuth(docApi, path, init));
|
|
1628
|
-
},
|
|
1629
|
-
[docApi, guardedRpc]
|
|
1630
|
-
);
|
|
1631
|
-
const uploadAttachment = useCallback7(
|
|
1632
|
-
(file, opts) => {
|
|
1633
|
-
if (!docApi) throw new Error("Grist is not ready");
|
|
1634
|
-
return guardedRpc(() => uploadGristAttachment(docApi, file, opts));
|
|
1635
|
-
},
|
|
1636
|
-
[docApi, guardedRpc]
|
|
1637
|
-
);
|
|
1638
|
-
return useMemo7(
|
|
1639
|
-
() => ({
|
|
1640
|
-
getAttachmentUrl,
|
|
1641
|
-
fetchAttachmentBase64: fetchAttachmentBase642,
|
|
1642
|
-
fetchAttachmentBlob: fetchAttachmentBlob2,
|
|
1643
|
-
getAccessToken,
|
|
1644
|
-
fetchWithAuth,
|
|
1645
|
-
uploadAttachment,
|
|
1646
|
-
readError
|
|
1647
|
-
}),
|
|
1648
|
-
[
|
|
1649
|
-
getAttachmentUrl,
|
|
1650
|
-
fetchAttachmentBase642,
|
|
1651
|
-
fetchAttachmentBlob2,
|
|
1652
|
-
getAccessToken,
|
|
1653
|
-
fetchWithAuth,
|
|
1654
|
-
uploadAttachment,
|
|
1655
|
-
readError
|
|
1656
|
-
]
|
|
1657
|
-
);
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// src/sdk/internal/hooks/use-grist-theme.ts
|
|
1661
|
-
import { useEffect as useEffect3, useMemo as useMemo8, useState as useState5 } from "react";
|
|
1662
|
-
|
|
1663
|
-
// src/sdk/helpers/grist-theme-payload.ts
|
|
1664
|
-
function parseGristThemePayload(payload) {
|
|
1665
|
-
if (!payload || typeof payload !== "object") return null;
|
|
1666
|
-
const raw = payload;
|
|
1667
|
-
const top = raw.theme;
|
|
1668
|
-
if (top === "light" || top === "dark") return top;
|
|
1669
|
-
if (top && typeof top === "object") {
|
|
1670
|
-
const appearance = top.appearance;
|
|
1671
|
-
if (appearance === "light" || appearance === "dark") return appearance;
|
|
1672
|
-
}
|
|
1673
|
-
return null;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// src/sdk/internal/hooks/use-grist-theme.ts
|
|
1677
|
-
function useGristTheme(_docApi) {
|
|
1678
|
-
const [theme, setTheme] = useState5(null);
|
|
1679
|
-
useEffect3(() => {
|
|
1680
|
-
if (typeof grist === "undefined" || typeof grist.on !== "function") return;
|
|
1681
|
-
const apply = (payload) => {
|
|
1682
|
-
const next = parseGristThemePayload(payload);
|
|
1683
|
-
if (next) setTheme(next);
|
|
1684
|
-
};
|
|
1685
|
-
grist.on("message", (msg) => {
|
|
1686
|
-
if (!msg || typeof msg !== "object") return;
|
|
1687
|
-
if (!("theme" in msg)) return;
|
|
1688
|
-
apply(msg);
|
|
1689
|
-
});
|
|
1690
|
-
}, []);
|
|
1691
|
-
return useMemo8(() => ({ theme }), [theme]);
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
// src/sdk/internal/hooks/use-grist-section-api.ts
|
|
1695
|
-
import { useCallback as useCallback8, useMemo as useMemo9 } from "react";
|
|
1696
|
-
function useGristSectionApi() {
|
|
1697
|
-
const coalesceRpc = useRpcHeartbeatCoalesce();
|
|
1698
|
-
const configure = useCallback8(
|
|
1699
|
-
async (next) => {
|
|
1700
|
-
if (!grist.sectionApi?.configure) {
|
|
1701
|
-
throw new Error("grist.sectionApi.configure is not available in this Grist version");
|
|
1702
|
-
}
|
|
1703
|
-
await coalesceRpc(() => Promise.resolve(grist.sectionApi.configure(next)));
|
|
1704
|
-
},
|
|
1705
|
-
[coalesceRpc]
|
|
1706
|
-
);
|
|
1707
|
-
const refreshMappings = useCallback8(
|
|
1708
|
-
async () => {
|
|
1709
|
-
if (!grist.sectionApi?.mappings) {
|
|
1710
|
-
throw new Error("grist.sectionApi.mappings is not available in this Grist version");
|
|
1711
|
-
}
|
|
1712
|
-
return coalesceRpc(() => grist.sectionApi.mappings());
|
|
1713
|
-
},
|
|
1714
|
-
[coalesceRpc]
|
|
1715
|
-
);
|
|
1716
|
-
return useMemo9(() => ({ configure, refreshMappings }), [configure, refreshMappings]);
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
// src/sdk/internal/compose-grist-widget.ts
|
|
1720
|
-
function useComposeGristWidgetSlices(options) {
|
|
1721
|
-
const core = useGristCore(options);
|
|
1722
|
-
const currentTable = useGristCurrentTable(core.docApi);
|
|
1723
|
-
const selection = useGristSelectionSubscription(core.docApi, options);
|
|
1724
|
-
const writes = useGristTableOpsBound(core.docApi);
|
|
1725
|
-
const reads = useGristReads(core.docApi);
|
|
1726
|
-
const widgetOptionsApi = useGristWidgetOptionsApi();
|
|
1727
|
-
const linking = useGristLinking();
|
|
1728
|
-
const attachmentsRest = useGristAttachmentsRest(core.docApi);
|
|
1729
|
-
const theme = useGristTheme(core.docApi);
|
|
1730
|
-
const sectionApi = useGristSectionApi();
|
|
1731
|
-
const coreSlice = useMemo10(
|
|
1732
|
-
() => ({
|
|
1733
|
-
...core,
|
|
1734
|
-
tableError: currentTable.tableError
|
|
1735
|
-
}),
|
|
1736
|
-
[core, currentTable.tableError]
|
|
1737
|
-
);
|
|
1738
|
-
const suppressAlerts = options?.suppressAlerts;
|
|
1739
|
-
const allowSelectBy = options?.allowSelectBy;
|
|
1740
|
-
return useMemo10(
|
|
1741
|
-
() => ({
|
|
1742
|
-
core: coreSlice,
|
|
1743
|
-
selection,
|
|
1744
|
-
currentTable,
|
|
1745
|
-
writes,
|
|
1746
|
-
reads,
|
|
1747
|
-
widgetOptionsApi,
|
|
1748
|
-
linking,
|
|
1749
|
-
attachmentsRest,
|
|
1750
|
-
theme,
|
|
1751
|
-
sectionApi,
|
|
1752
|
-
suppressAlerts,
|
|
1753
|
-
allowSelectBy
|
|
1754
|
-
}),
|
|
1755
|
-
[
|
|
1756
|
-
coreSlice,
|
|
1757
|
-
selection,
|
|
1758
|
-
currentTable,
|
|
1759
|
-
writes,
|
|
1760
|
-
reads,
|
|
1761
|
-
widgetOptionsApi,
|
|
1762
|
-
linking,
|
|
1763
|
-
attachmentsRest,
|
|
1764
|
-
theme,
|
|
1765
|
-
sectionApi,
|
|
1766
|
-
suppressAlerts,
|
|
1767
|
-
allowSelectBy
|
|
1768
|
-
]
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
// src/sdk/internal/grist-widget-context-tree.tsx
|
|
1773
|
-
import { jsx } from "react/jsx-runtime";
|
|
1774
|
-
var GristContext = createContext(null);
|
|
1775
|
-
function GristWidgetContextTree({ options, children }) {
|
|
1776
|
-
const slices = useComposeGristWidgetSlices(options);
|
|
1777
|
-
const fullResult = useMemo11(
|
|
1778
|
-
() => mergeGristResultFromSlices(slices),
|
|
1779
|
-
[
|
|
1780
|
-
slices.core,
|
|
1781
|
-
slices.selection,
|
|
1782
|
-
slices.currentTable,
|
|
1783
|
-
slices.writes,
|
|
1784
|
-
slices.reads,
|
|
1785
|
-
slices.widgetOptionsApi,
|
|
1786
|
-
slices.linking,
|
|
1787
|
-
slices.attachmentsRest,
|
|
1788
|
-
slices.theme,
|
|
1789
|
-
slices.sectionApi
|
|
1790
|
-
]
|
|
1791
|
-
);
|
|
1792
|
-
return /* @__PURE__ */ jsx(GristCoreContext.Provider, { value: slices.core, children: /* @__PURE__ */ jsx(GristSelectionContext.Provider, { value: slices.selection, children: /* @__PURE__ */ jsx(GristCurrentTableContext.Provider, { value: slices.currentTable, children: /* @__PURE__ */ jsx(GristWritesContext.Provider, { value: slices.writes, children: /* @__PURE__ */ jsx(GristReadsContext.Provider, { value: slices.reads, children: /* @__PURE__ */ jsx(GristWidgetOptionsApiContext.Provider, { value: slices.widgetOptionsApi, children: /* @__PURE__ */ jsx(GristLinkingContext.Provider, { value: slices.linking, children: /* @__PURE__ */ jsx(GristAttachmentsRestContext.Provider, { value: slices.attachmentsRest, children: /* @__PURE__ */ jsx(GristThemeContext.Provider, { value: slices.theme, children: /* @__PURE__ */ jsx(GristSectionApiContext.Provider, { value: slices.sectionApi, children: /* @__PURE__ */ jsx(GristContext.Provider, { value: fullResult, children }) }) }) }) }) }) }) }) }) }) });
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
// src/sdk/components/grist-widget-provider.tsx
|
|
1796
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1797
|
-
function GristWidgetProvider({ options, children }) {
|
|
1798
|
-
const managerRef = useRef5(null);
|
|
1799
|
-
if (managerRef.current === null) {
|
|
1800
|
-
managerRef.current = createGristHandshakeManager(buildManagerOptionsFromGristOptions(options));
|
|
1801
|
-
}
|
|
1802
|
-
const manager = managerRef.current;
|
|
1803
|
-
useEffect4(() => {
|
|
1804
|
-
manager.start();
|
|
1805
|
-
return () => {
|
|
1806
|
-
manager.stop();
|
|
1807
|
-
};
|
|
1808
|
-
}, [manager]);
|
|
1809
|
-
return /* @__PURE__ */ jsx2(GristHandshakeManagerContext.Provider, { value: manager, children: /* @__PURE__ */ jsx2(GristWidgetContextTree, { options, children }) });
|
|
1810
|
-
}
|
|
1811
|
-
function useGristContext() {
|
|
1812
|
-
const ctx = useContext(GristContext);
|
|
1813
|
-
if (!ctx) {
|
|
1814
|
-
throw new Error("useGristContext() must be used inside <GristWidgetProvider>");
|
|
1815
|
-
}
|
|
1816
|
-
return useGristFromProvider();
|
|
1817
|
-
}
|
|
1818
|
-
function useGristOptional() {
|
|
1819
|
-
return useContext(GristContext);
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
// src/sdk/hooks/use-grist-sdk-alert-descriptors.ts
|
|
1823
|
-
function noopSubscribe() {
|
|
1824
|
-
return () => {
|
|
1825
|
-
};
|
|
1826
|
-
}
|
|
1827
|
-
function useGristSdkAlertDescriptors(widget, options) {
|
|
1828
|
-
const fromContext = useGristOptional();
|
|
1829
|
-
const w = widget ?? fromContext;
|
|
1830
|
-
const manager = useGristManagerOptional();
|
|
1831
|
-
const snapshot = useSyncExternalStore2(
|
|
1832
|
-
manager?.subscribe ?? noopSubscribe,
|
|
1833
|
-
manager?.getSnapshot ?? initialGristSnapshot,
|
|
1834
|
-
initialGristSnapshot
|
|
1835
|
-
);
|
|
1836
|
-
const resolvedSnapshot = options?.snapshot ?? (manager ? snapshot : void 0);
|
|
1837
|
-
const widgetInteraction = w?.widgetInteraction;
|
|
1838
|
-
const readError = w && "readError" in w ? w.readError : null;
|
|
1839
|
-
const alertInputs = w ? [
|
|
1840
|
-
w.status,
|
|
1841
|
-
w.mode,
|
|
1842
|
-
widgetInteraction,
|
|
1843
|
-
w.columnMappingStatus,
|
|
1844
|
-
w.actionError,
|
|
1845
|
-
readError,
|
|
1846
|
-
w.mapBackSkipped
|
|
1847
|
-
] : null;
|
|
1848
|
-
const suppressFromSlice = w?.suppressAlerts;
|
|
1849
|
-
return useMemo12(
|
|
1850
|
-
() => w ? getGristSdkAlertDescriptors(w, {
|
|
1851
|
-
...options,
|
|
1852
|
-
suppressKinds: options?.suppressKinds ?? suppressFromSlice,
|
|
1853
|
-
snapshot: resolvedSnapshot
|
|
1854
|
-
}) : [],
|
|
1855
|
-
[alertInputs, options, resolvedSnapshot, w, suppressFromSlice]
|
|
1856
|
-
);
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
// src/sdk/components/grist-boundary.tsx
|
|
1860
|
-
import {
|
|
1861
|
-
useCallback as useCallback9,
|
|
1862
|
-
useEffect as useEffect5,
|
|
1863
|
-
useMemo as useMemo13,
|
|
1864
|
-
useState as useState6,
|
|
1865
|
-
useSyncExternalStore as useSyncExternalStore3
|
|
1866
|
-
} from "react";
|
|
1867
|
-
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1868
|
-
var shellStyle = {
|
|
1869
|
-
alignItems: "center",
|
|
1870
|
-
background: "var(--background)",
|
|
1871
|
-
boxSizing: "border-box",
|
|
1872
|
-
color: "var(--foreground)",
|
|
1873
|
-
display: "flex",
|
|
1874
|
-
flexDirection: "column",
|
|
1875
|
-
justifyContent: "center",
|
|
1876
|
-
minHeight: "100%",
|
|
1877
|
-
padding: "clamp(16px, 5vh, 32px)",
|
|
1878
|
-
width: "100%"
|
|
1879
|
-
};
|
|
1880
|
-
var cardStyle = {
|
|
1881
|
-
background: "var(--card)",
|
|
1882
|
-
border: "1px solid var(--border)",
|
|
1883
|
-
borderRadius: 12,
|
|
1884
|
-
boxShadow: "0 1px 3px rgb(0 0 0 / 0.12)",
|
|
1885
|
-
maxWidth: 420,
|
|
1886
|
-
padding: "20px 22px",
|
|
1887
|
-
textAlign: "center",
|
|
1888
|
-
width: "100%"
|
|
1889
|
-
};
|
|
1890
|
-
var titleStyle = {
|
|
1891
|
-
color: "var(--foreground)",
|
|
1892
|
-
fontSize: 15,
|
|
1893
|
-
fontWeight: 600,
|
|
1894
|
-
lineHeight: 1.35,
|
|
1895
|
-
margin: 0
|
|
1896
|
-
};
|
|
1897
|
-
var messageStyle = {
|
|
1898
|
-
color: "var(--muted-foreground)",
|
|
1899
|
-
fontSize: 14,
|
|
1900
|
-
lineHeight: 1.5,
|
|
1901
|
-
margin: "10px 0 0"
|
|
1902
|
-
};
|
|
1903
|
-
var buttonStyle = {
|
|
1904
|
-
background: "var(--background)",
|
|
1905
|
-
border: "1px solid var(--border)",
|
|
1906
|
-
borderRadius: 8,
|
|
1907
|
-
color: "var(--foreground)",
|
|
1908
|
-
cursor: "pointer",
|
|
1909
|
-
fontSize: 13,
|
|
1910
|
-
fontWeight: 500,
|
|
1911
|
-
marginTop: 16,
|
|
1912
|
-
padding: "8px 14px"
|
|
1913
|
-
};
|
|
1914
|
-
function noopSubscribe2() {
|
|
1915
|
-
return () => {
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
function GristBoundaryScreen({
|
|
1919
|
-
title,
|
|
1920
|
-
message,
|
|
1921
|
-
tone = "default",
|
|
1922
|
-
showRetry = false,
|
|
1923
|
-
actions,
|
|
1924
|
-
retryLabel = "Try reconnecting",
|
|
1925
|
-
retryBusyLabel = "Reconnecting\u2026"
|
|
1926
|
-
}) {
|
|
1927
|
-
return /* @__PURE__ */ jsx3("div", { style: shellStyle, role: tone === "error" ? "alert" : "status", children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
|
|
1928
|
-
/* @__PURE__ */ jsx3("h2", { style: titleStyle, children: title }),
|
|
1929
|
-
/* @__PURE__ */ jsx3("p", { style: messageStyle, children: message }),
|
|
1930
|
-
showRetry && actions ? /* @__PURE__ */ jsx3(
|
|
1931
|
-
"button",
|
|
1932
|
-
{
|
|
1933
|
-
disabled: actions.isReloading,
|
|
1934
|
-
onClick: () => void actions.reload(),
|
|
1935
|
-
style: {
|
|
1936
|
-
...buttonStyle,
|
|
1937
|
-
cursor: actions.isReloading ? "not-allowed" : "pointer",
|
|
1938
|
-
opacity: actions.isReloading ? 0.7 : 1
|
|
1939
|
-
},
|
|
1940
|
-
type: "button",
|
|
1941
|
-
children: actions.isReloading ? retryBusyLabel : retryLabel
|
|
1942
|
-
}
|
|
1943
|
-
) : null
|
|
1944
|
-
] }) });
|
|
1945
|
-
}
|
|
1946
|
-
function BootingScreen({ message }) {
|
|
1947
|
-
return /* @__PURE__ */ jsx3(
|
|
1948
|
-
GristBoundaryScreen,
|
|
1949
|
-
{
|
|
1950
|
-
title: "Connecting to Grist",
|
|
1951
|
-
message,
|
|
1952
|
-
showRetry: false
|
|
1953
|
-
}
|
|
1954
|
-
);
|
|
1955
|
-
}
|
|
1956
|
-
function blockingScreenFromView(view, actions) {
|
|
1957
|
-
return /* @__PURE__ */ jsx3(
|
|
1958
|
-
GristBoundaryScreen,
|
|
1959
|
-
{
|
|
1960
|
-
title: view.title,
|
|
1961
|
-
message: view.message,
|
|
1962
|
-
tone: view.kind === "error" ? "error" : "default",
|
|
1963
|
-
showRetry: view.showRetry,
|
|
1964
|
-
actions,
|
|
1965
|
-
retryLabel: view.kind === "error" ? "Try again" : "Try reconnecting",
|
|
1966
|
-
retryBusyLabel: view.kind === "error" ? "Retrying\u2026" : "Reconnecting\u2026"
|
|
1967
|
-
}
|
|
1968
|
-
);
|
|
1969
|
-
}
|
|
1970
|
-
function legacyBlockingFromWidget(w, actions) {
|
|
1971
|
-
if (w.status === "unavailable") {
|
|
1972
|
-
return /* @__PURE__ */ jsx3(
|
|
1973
|
-
GristBoundaryScreen,
|
|
1974
|
-
{
|
|
1975
|
-
title: "Open inside Grist",
|
|
1976
|
-
message: "This page must be opened from a Grist document as a custom widget.",
|
|
1977
|
-
showRetry: true,
|
|
1978
|
-
actions
|
|
1979
|
-
}
|
|
1980
|
-
);
|
|
1981
|
-
}
|
|
1982
|
-
if (w.status === "error") {
|
|
1983
|
-
return /* @__PURE__ */ jsx3(
|
|
1984
|
-
GristBoundaryScreen,
|
|
1985
|
-
{
|
|
1986
|
-
title: "Something went wrong",
|
|
1987
|
-
message: w.error ?? "Something went wrong connecting to Grist. Try reconnecting below, or reload the page.",
|
|
1988
|
-
tone: "error",
|
|
1989
|
-
showRetry: true,
|
|
1990
|
-
actions,
|
|
1991
|
-
retryLabel: "Try again",
|
|
1992
|
-
retryBusyLabel: "Retrying\u2026"
|
|
1993
|
-
}
|
|
1994
|
-
);
|
|
1995
|
-
}
|
|
1996
|
-
if (w.status !== "ready") {
|
|
1997
|
-
return /* @__PURE__ */ jsx3(BootingScreen, { message: "Connecting to Grist\u2026" });
|
|
1998
|
-
}
|
|
1999
|
-
return null;
|
|
2000
|
-
}
|
|
2001
|
-
function GristBoundary({
|
|
2002
|
-
widget,
|
|
2003
|
-
gate = "ready",
|
|
2004
|
-
bootingFallback,
|
|
2005
|
-
preparingFallback,
|
|
2006
|
-
unavailableFallback,
|
|
2007
|
-
errorFallback,
|
|
2008
|
-
unavailableGraceMs = 5e3,
|
|
2009
|
-
children
|
|
2010
|
-
}) {
|
|
2011
|
-
const fromContext = useGristOptional();
|
|
2012
|
-
const w = widget ?? fromContext;
|
|
2013
|
-
const manager = useGristManagerOptional();
|
|
2014
|
-
const snapshot = useSyncExternalStore3(
|
|
2015
|
-
manager?.subscribe ?? noopSubscribe2,
|
|
2016
|
-
manager?.getSnapshot ?? initialGristSnapshot,
|
|
2017
|
-
initialGristSnapshot
|
|
2018
|
-
);
|
|
2019
|
-
if (!w) {
|
|
2020
|
-
throw new Error(
|
|
2021
|
-
"<GristBoundary> requires either a `widget` prop or a parent <GristWidgetProvider>"
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
|
-
const boundaryView = useMemo13(
|
|
2025
|
-
() => manager ? deriveBoundaryView(snapshot) : null,
|
|
2026
|
-
[manager, snapshot]
|
|
2027
|
-
);
|
|
2028
|
-
const [showUnavailable, setShowUnavailable] = useState6(false);
|
|
2029
|
-
const [isReloading, setIsReloading] = useState6(false);
|
|
2030
|
-
useEffect5(() => {
|
|
2031
|
-
if (w.status !== "unavailable") {
|
|
2032
|
-
setShowUnavailable(false);
|
|
2033
|
-
return;
|
|
2034
|
-
}
|
|
2035
|
-
const timer = window.setTimeout(() => setShowUnavailable(true), unavailableGraceMs);
|
|
2036
|
-
return () => window.clearTimeout(timer);
|
|
2037
|
-
}, [unavailableGraceMs, w.status]);
|
|
2038
|
-
const reload = useCallback9(async () => {
|
|
2039
|
-
const MIN_RELOAD_VISIBILITY_MS = 700;
|
|
2040
|
-
const startedAt = Date.now();
|
|
2041
|
-
setIsReloading(true);
|
|
2042
|
-
try {
|
|
2043
|
-
await w.reload();
|
|
2044
|
-
} catch (error) {
|
|
2045
|
-
console.error("[grist-sdk] reload failed:", error);
|
|
2046
|
-
} finally {
|
|
2047
|
-
const elapsed = Date.now() - startedAt;
|
|
2048
|
-
const remaining = Math.max(0, MIN_RELOAD_VISIBILITY_MS - elapsed);
|
|
2049
|
-
if (remaining > 0) {
|
|
2050
|
-
await new Promise((resolve) => window.setTimeout(resolve, remaining));
|
|
2051
|
-
}
|
|
2052
|
-
setIsReloading(false);
|
|
2053
|
-
}
|
|
2054
|
-
}, [w]);
|
|
2055
|
-
const actions = useMemo13(
|
|
2056
|
-
() => ({ reload, isReloading }),
|
|
2057
|
-
[isReloading, reload]
|
|
2058
|
-
);
|
|
2059
|
-
const canRender = gate !== "canRender" || !manager || deriveCapabilities(snapshot).canRender;
|
|
2060
|
-
const defaultPreparing = /* @__PURE__ */ jsx3(
|
|
2061
|
-
GristBoundaryScreen,
|
|
2062
|
-
{
|
|
2063
|
-
title: GRIST_BOUNDARY_PREPARING_COPY.title,
|
|
2064
|
-
message: GRIST_BOUNDARY_PREPARING_COPY.message,
|
|
2065
|
-
showRetry: false
|
|
2066
|
-
}
|
|
2067
|
-
);
|
|
2068
|
-
if (!manager) {
|
|
2069
|
-
const legacy = legacyBlockingFromWidget(w, actions);
|
|
2070
|
-
if (legacy) return /* @__PURE__ */ jsx3(Fragment, { children: legacy });
|
|
2071
|
-
if (!canRender) return /* @__PURE__ */ jsx3(Fragment, { children: preparingFallback ?? defaultPreparing });
|
|
2072
|
-
return /* @__PURE__ */ jsx3(Fragment, { children });
|
|
2073
|
-
}
|
|
2074
|
-
if (w.status === "unavailable") {
|
|
2075
|
-
if (!showUnavailable) {
|
|
2076
|
-
const boot = boundaryView?.mode === "blocking" && boundaryView.kind === "booting" ? boundaryView.message : "Connecting to Grist\u2026";
|
|
2077
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: bootingFallback ?? /* @__PURE__ */ jsx3(BootingScreen, { message: boot }) });
|
|
2078
|
-
}
|
|
2079
|
-
if (typeof unavailableFallback === "function") {
|
|
2080
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: unavailableFallback(actions) });
|
|
2081
|
-
}
|
|
2082
|
-
if (unavailableFallback) return /* @__PURE__ */ jsx3(Fragment, { children: unavailableFallback });
|
|
2083
|
-
if (boundaryView?.mode === "blocking") {
|
|
2084
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: blockingScreenFromView(boundaryView, actions) });
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
if (w.status === "error") {
|
|
2088
|
-
if (errorFallback) {
|
|
2089
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: errorFallback(w.error ?? "Grist connection error", actions) });
|
|
2090
|
-
}
|
|
2091
|
-
if (boundaryView?.mode === "blocking") {
|
|
2092
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: blockingScreenFromView(boundaryView, actions) });
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
if (w.status !== "ready") {
|
|
2096
|
-
if (boundaryView?.mode === "blocking" && boundaryView.kind === "booting") {
|
|
2097
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: bootingFallback ?? /* @__PURE__ */ jsx3(BootingScreen, { message: boundaryView.message }) });
|
|
2098
|
-
}
|
|
2099
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: bootingFallback ?? /* @__PURE__ */ jsx3(BootingScreen, { message: "Connecting to Grist\u2026" }) });
|
|
2100
|
-
}
|
|
2101
|
-
if (!canRender) {
|
|
2102
|
-
return /* @__PURE__ */ jsx3(Fragment, { children: preparingFallback ?? defaultPreparing });
|
|
2103
|
-
}
|
|
2104
|
-
return /* @__PURE__ */ jsx3(Fragment, { children });
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
// src/sdk/use-grist.ts
|
|
2108
|
-
import { useMemo as useMemo14 } from "react";
|
|
2109
|
-
import { useContext as useContext2 } from "react";
|
|
2110
|
-
function useGristStandalone(options) {
|
|
2111
|
-
const slices = useComposeGristWidgetSlices(options);
|
|
2112
|
-
return useMemo14(() => mergeGristResultFromSlices(slices), [slices]);
|
|
2113
|
-
}
|
|
2114
|
-
function useGrist(options) {
|
|
2115
|
-
if (useContext2(GristCoreContext)) {
|
|
2116
|
-
return useGristFromProvider();
|
|
2117
|
-
}
|
|
2118
|
-
return useGristStandalone(options);
|
|
2119
|
-
}
|
|
2120
|
-
function useGristStatus() {
|
|
2121
|
-
const core = useGristCoreContext();
|
|
2122
|
-
const currentTable = useGristCurrentTableContext();
|
|
2123
|
-
return useMemo14(() => ({ ...core, ...currentTable }), [core, currentTable]);
|
|
2124
|
-
}
|
|
2125
|
-
function useGristSelection() {
|
|
2126
|
-
return useGristSelectionContext();
|
|
2127
|
-
}
|
|
2128
|
-
function useGristWrites() {
|
|
2129
|
-
return useGristWritesContext();
|
|
2130
|
-
}
|
|
2131
|
-
function useGristTheme2() {
|
|
2132
|
-
return useGristThemeContext();
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
// src/sdk/use-grist-schema.ts
|
|
2136
|
-
import { useCallback as useCallback10, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo15, useState as useState7 } from "react";
|
|
2137
|
-
function filterReplicaDocumentColumns(document2, options, extraExcludeColumn) {
|
|
2138
|
-
return {
|
|
2139
|
-
...document2,
|
|
2140
|
-
tables: Object.fromEntries(
|
|
2141
|
-
Object.entries(document2.tables).map(([tableId, table]) => [
|
|
2142
|
-
tableId,
|
|
2143
|
-
{
|
|
2144
|
-
...table,
|
|
2145
|
-
columns: Object.fromEntries(
|
|
2146
|
-
Object.entries(table.columns ?? {}).filter(([colId, col]) => {
|
|
2147
|
-
if (isGristNoiseColumn(colId, col, options)) return false;
|
|
2148
|
-
if (extraExcludeColumn?.({ tableId, colId, col })) return false;
|
|
2149
|
-
return true;
|
|
2150
|
-
})
|
|
2151
|
-
)
|
|
2152
|
-
}
|
|
2153
|
-
])
|
|
2154
|
-
)
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
function useGristSchema(options) {
|
|
2158
|
-
const requiredAccess = options?.requiredAccess ?? "read table";
|
|
2159
|
-
const requestedTableId = options?.tableId;
|
|
2160
|
-
const excludeSystemTableData = options?.excludeSystemTableData ?? true;
|
|
2161
|
-
const replicaRowMode = options?.replicaRowMode ?? "schema+samples";
|
|
2162
|
-
const sampleRowLimit = options?.sampleRowLimit ?? 10;
|
|
2163
|
-
const replicaSource = options?.source;
|
|
2164
|
-
const excludeId = options?.excludeId ?? true;
|
|
2165
|
-
const excludeGristHelpers = options?.excludeGristHelpers ?? true;
|
|
2166
|
-
const excludeLoggingColumns = options?.excludeLoggingColumns ?? true;
|
|
2167
|
-
const excludeManualSort = options?.excludeManualSort ?? false;
|
|
2168
|
-
const extraExcludeColumn = options?.extraExcludeColumn;
|
|
2169
|
-
const { isAvailable, docApi, error: readyError } = useGristReady({
|
|
2170
|
-
requiredAccess,
|
|
2171
|
-
columns: options?.columns,
|
|
2172
|
-
allowSelectBy: options?.allowSelectBy,
|
|
2173
|
-
hasCustomOptions: options?.hasCustomOptions,
|
|
2174
|
-
onEditOptions: options?.onEditOptions
|
|
2175
|
-
});
|
|
2176
|
-
const providerBuildReplica = useContext3(GristReadsContext)?.buildReplicaDocumentFromDocApi;
|
|
2177
|
-
const [document2, setDocument] = useState7(null);
|
|
2178
|
-
const [loading, setLoading] = useState7(false);
|
|
2179
|
-
const [error, setError] = useState7(null);
|
|
2180
|
-
const includeRowsForBuild = replicaRowMode === "schema-only" ? false : replicaRowMode === "schema+data" ? "full" : "samples";
|
|
2181
|
-
const load = useCallback10(async () => {
|
|
2182
|
-
if (!docApi) return;
|
|
2183
|
-
setLoading(true);
|
|
2184
|
-
setError(null);
|
|
2185
|
-
try {
|
|
2186
|
-
const buildOpts = {
|
|
2187
|
-
includeSystemTableData: !excludeSystemTableData,
|
|
2188
|
-
includeRows: includeRowsForBuild,
|
|
2189
|
-
sampleRowLimit,
|
|
2190
|
-
source: replicaSource
|
|
2191
|
-
};
|
|
2192
|
-
const raw = providerBuildReplica ? await providerBuildReplica(buildOpts) : await buildReplicaDocumentFromDocApi(docApi, buildOpts);
|
|
2193
|
-
const filtered = filterReplicaDocumentColumns(
|
|
2194
|
-
raw,
|
|
2195
|
-
{
|
|
2196
|
-
excludeId,
|
|
2197
|
-
excludeGristHelpers,
|
|
2198
|
-
excludeLoggingColumns,
|
|
2199
|
-
excludeManualSort
|
|
2200
|
-
},
|
|
2201
|
-
extraExcludeColumn
|
|
2202
|
-
);
|
|
2203
|
-
setDocument(filtered);
|
|
2204
|
-
} catch (err) {
|
|
2205
|
-
setDocument(null);
|
|
2206
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
2207
|
-
} finally {
|
|
2208
|
-
setLoading(false);
|
|
2209
|
-
}
|
|
2210
|
-
}, [
|
|
2211
|
-
docApi,
|
|
2212
|
-
providerBuildReplica,
|
|
2213
|
-
excludeSystemTableData,
|
|
2214
|
-
replicaRowMode,
|
|
2215
|
-
sampleRowLimit,
|
|
2216
|
-
excludeId,
|
|
2217
|
-
excludeGristHelpers,
|
|
2218
|
-
excludeLoggingColumns,
|
|
2219
|
-
excludeManualSort,
|
|
2220
|
-
extraExcludeColumn,
|
|
2221
|
-
replicaSource
|
|
2222
|
-
]);
|
|
2223
|
-
useEffect6(() => {
|
|
2224
|
-
if (docApi) void load();
|
|
2225
|
-
}, [docApi, load]);
|
|
2226
|
-
const buildDocumentJson = useCallback10((target, options2) => {
|
|
2227
|
-
const includeRows = options2?.includeRows ?? "full";
|
|
2228
|
-
const space = options2?.space ?? 2;
|
|
2229
|
-
const payload = buildReplicaJsonPayload(target, {
|
|
2230
|
-
includeRows,
|
|
2231
|
-
sampleRowLimit: options2?.sampleRowLimit ?? sampleRowLimit
|
|
2232
|
-
});
|
|
2233
|
-
return JSON.stringify(payload, null, space);
|
|
2234
|
-
}, [sampleRowLimit]);
|
|
2235
|
-
const getDocumentJson = useCallback10(
|
|
2236
|
-
(options2) => {
|
|
2237
|
-
if (!document2) return null;
|
|
2238
|
-
return buildDocumentJson(document2, options2);
|
|
2239
|
-
},
|
|
2240
|
-
[document2, buildDocumentJson]
|
|
2241
|
-
);
|
|
2242
|
-
const table = useMemo15(() => {
|
|
2243
|
-
if (!document2 || !requestedTableId) return null;
|
|
2244
|
-
const slice = document2.tables[requestedTableId];
|
|
2245
|
-
if (!slice) return null;
|
|
2246
|
-
return { tableId: requestedTableId, ...slice };
|
|
2247
|
-
}, [document2, requestedTableId]);
|
|
2248
|
-
const buildTableJson = useCallback10(
|
|
2249
|
-
(target, options2) => {
|
|
2250
|
-
const includeRows = options2?.includeRows ?? "full";
|
|
2251
|
-
const limit = options2?.sampleRowLimit ?? sampleRowLimit;
|
|
2252
|
-
const space = options2?.space ?? 2;
|
|
2253
|
-
const { tableId, ...rest } = target;
|
|
2254
|
-
let body = { ...rest };
|
|
2255
|
-
if (includeRows === "none") {
|
|
2256
|
-
const { rows: _r, ...r } = body;
|
|
2257
|
-
body = r;
|
|
2258
|
-
} else if (includeRows === "samples") {
|
|
2259
|
-
const r = body.rows ?? [];
|
|
2260
|
-
body = { ...body, rows: r.slice(0, limit) };
|
|
2261
|
-
}
|
|
2262
|
-
return JSON.stringify({ tableId, ...body }, null, space);
|
|
2263
|
-
},
|
|
2264
|
-
[sampleRowLimit]
|
|
2265
|
-
);
|
|
2266
|
-
const getTableJson = useCallback10(
|
|
2267
|
-
(options2) => {
|
|
2268
|
-
if (!table) return null;
|
|
2269
|
-
return buildTableJson(table, options2);
|
|
2270
|
-
},
|
|
2271
|
-
[table, buildTableJson]
|
|
2272
|
-
);
|
|
2273
|
-
const noiseColumnMatcher = useCallback10(
|
|
2274
|
-
(colId, col) => isGristNoiseColumn(colId, col, {
|
|
2275
|
-
excludeId,
|
|
2276
|
-
excludeGristHelpers,
|
|
2277
|
-
excludeLoggingColumns,
|
|
2278
|
-
excludeManualSort
|
|
2279
|
-
}),
|
|
2280
|
-
[excludeId, excludeGristHelpers, excludeLoggingColumns, excludeManualSort]
|
|
2281
|
-
);
|
|
2282
|
-
const sortColumns = useCallback10((colIds, columns) => {
|
|
2283
|
-
return [...colIds].sort((a, b) => {
|
|
2284
|
-
const la = (columns[a]?.label || a).toLowerCase();
|
|
2285
|
-
const lb = (columns[b]?.label || b).toLowerCase();
|
|
2286
|
-
if (la !== lb) return la.localeCompare(lb);
|
|
2287
|
-
return a.localeCompare(b);
|
|
2288
|
-
});
|
|
2289
|
-
}, []);
|
|
2290
|
-
return {
|
|
2291
|
-
isAvailable,
|
|
2292
|
-
document: document2,
|
|
2293
|
-
replicaDocument: document2,
|
|
2294
|
-
table,
|
|
2295
|
-
documentJson: document2 ? buildDocumentJson(document2) : null,
|
|
2296
|
-
getDocumentJson,
|
|
2297
|
-
buildDocumentJson,
|
|
2298
|
-
tableJson: table ? buildTableJson(table) : null,
|
|
2299
|
-
getTableJson,
|
|
2300
|
-
buildTableJson,
|
|
2301
|
-
isNoiseColumnId: noiseColumnMatcher,
|
|
2302
|
-
sortColumnIds: sortColumns,
|
|
2303
|
-
loading,
|
|
2304
|
-
error: readyError ?? error,
|
|
2305
|
-
reload: load
|
|
2306
|
-
};
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
// src/sdk/use-widget-metadata.ts
|
|
2310
|
-
import { useEffect as useEffect7 } from "react";
|
|
2311
|
-
function useWidgetMetadata(input) {
|
|
2312
|
-
useEffect7(() => {
|
|
2313
|
-
if (!input) return;
|
|
2314
|
-
const { title, description, syncToParent = true } = input;
|
|
2315
|
-
const canonicalUrl = window.location.href;
|
|
2316
|
-
document.title = title;
|
|
2317
|
-
setMetaTag("name", "title", title);
|
|
2318
|
-
setMetaTag("name", "description", description);
|
|
2319
|
-
setMetaTag("property", "og:title", title);
|
|
2320
|
-
setMetaTag("property", "og:description", description);
|
|
2321
|
-
setMetaTag("property", "og:type", "website");
|
|
2322
|
-
setMetaTag("property", "og:url", canonicalUrl);
|
|
2323
|
-
setMetaTag("name", "twitter:card", "summary");
|
|
2324
|
-
setMetaTag("name", "twitter:title", title);
|
|
2325
|
-
setMetaTag("name", "twitter:description", description);
|
|
2326
|
-
if (!syncToParent) return;
|
|
2327
|
-
window.parent?.postMessage(
|
|
2328
|
-
{
|
|
2329
|
-
source: "grist-playground-widget",
|
|
2330
|
-
type: "metadata",
|
|
2331
|
-
title,
|
|
2332
|
-
description
|
|
2333
|
-
},
|
|
2334
|
-
"*"
|
|
2335
|
-
);
|
|
2336
|
-
}, [input]);
|
|
2337
|
-
}
|
|
2338
|
-
function setMetaTag(key, value, content) {
|
|
2339
|
-
let meta = document.head.querySelector(
|
|
2340
|
-
`meta[${key}="${value}"]`
|
|
2341
|
-
);
|
|
2342
|
-
if (!meta) {
|
|
2343
|
-
meta = document.createElement("meta");
|
|
2344
|
-
meta.setAttribute(key, value);
|
|
2345
|
-
document.head.appendChild(meta);
|
|
2346
|
-
}
|
|
2347
|
-
meta.setAttribute("content", content);
|
|
2348
|
-
}
|
|
2349
115
|
export {
|
|
2350
116
|
GRIST_BOUNDARY_PREPARING_COPY,
|
|
2351
117
|
GristBoundary,
|
|
@@ -2441,7 +207,7 @@ export {
|
|
|
2441
207
|
useGristSdkAlertDescriptors,
|
|
2442
208
|
useGristSelection,
|
|
2443
209
|
useGristStatus,
|
|
2444
|
-
|
|
210
|
+
useGristTheme,
|
|
2445
211
|
useGristWidgetOptionsFromContext,
|
|
2446
212
|
useGristWrites,
|
|
2447
213
|
useWidgetMetadata,
|