@marimo-team/islands 0.23.12-dev2 → 0.23.12-dev20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-WqG-xX4l.js → ConnectedDataExplorerComponent-Du3_nUzI.js} +13 -13
- package/dist/{ErrorBoundary-BNx_OSVo.js → ErrorBoundary-DE6tzZf-.js} +2 -2
- package/dist/{any-language-editor-rPSlOll9.js → any-language-editor-DN1R-1KZ.js} +5 -5
- package/dist/{button-vQhauTmO.js → button-BacYv-bE.js} +7 -1
- package/dist/{capabilities-BEHzIS99.js → capabilities-D_4LYhSU.js} +1 -1
- package/dist/{chat-ui-k2kqhCv5.js → chat-ui-CsPewo4h.js} +16 -16
- package/dist/{check-nrzHDi45.js → check-C9OoNtR4.js} +1 -1
- package/dist/{code-visibility-DZ_6U5hT.js → code-visibility-02AuLxDs.js} +664 -663
- package/dist/{copy-UhDed7D4.js → copy-COam1EG7.js} +2 -2
- package/dist/{dist-DYGLrbYQ.js → dist--2Bqjvs0.js} +2 -2
- package/dist/{error-banner-BHAkVFc2.js → error-banner-DFPfz_Qf.js} +2 -2
- package/dist/{esm-Bqu9AE2K.js → esm-M837UxV5.js} +1 -1
- package/dist/{extends-9Yl5BEcg.js → extends-9MVIxxRo.js} +4 -4
- package/dist/{formats-BV4bOfMI.js → formats-d6MhLuQ9.js} +4 -4
- package/dist/{glide-data-editor-BDTq6YUb.js → glide-data-editor-DkzAInWG.js} +9 -9
- package/dist/{html-to-image-C86pQALH.js → html-to-image-DXwLcQ6l.js} +95 -88
- package/dist/{input-AKkGXdyV.js → input-CbEz_aj_.js} +6 -6
- package/dist/{label-E3ZJXHu8.js → label-WfTSU8L4.js} +2 -2
- package/dist/{loader-YPuQvn1Y.js → loader-Boph2xIS.js} +1 -1
- package/dist/main.js +1753 -1626
- package/dist/{mermaid-QFAR9YgY.js → mermaid-CJW9vIyO.js} +5 -5
- package/dist/{process-output-nNw4OpSj.js → process-output-C6_e1pT_.js} +3 -3
- package/dist/{reveal-component-BxDb5eK0.js → reveal-component-CX0nM3qj.js} +11 -11
- package/dist/{spec-B45_YCNI.js → spec-Bv-XlYiv.js} +4 -4
- package/dist/{strings-Cq2s9_EQ.js → strings-Dq_j3Rxw.js} +4 -4
- package/dist/style.css +2 -2
- package/dist/{swiper-component-BNa_4kh2.js → swiper-component-5HoSsPi1.js} +2 -2
- package/dist/{toDate-Do1xRzAo.js → toDate-D-l5s8nn.js} +3 -3
- package/dist/{tooltip-Bz3OAwrU.js → tooltip-Czds6Qr8.js} +3 -3
- package/dist/{types-D8gEGs4R.js → types-C2Ir191_.js} +1 -1
- package/dist/{useAsyncData-CL3o2p4i.js → useAsyncData-1Dhzjfwf.js} +1 -1
- package/dist/{useDateFormatter-BC6iSz9g.js → useDateFormatter-CMnRuVmN.js} +2 -2
- package/dist/{useDeepCompareMemoize-BPx2MuOK.js → useDeepCompareMemoize-CDWT3BDz.js} +1 -1
- package/dist/{useIframeCapabilities-C6Ta3EyP.js → useIframeCapabilities-DWIYvDh7.js} +1 -1
- package/dist/{useLifecycle-C3Ec71q0.js → useLifecycle-AHlswLw-.js} +3 -3
- package/dist/{useTheme-ZhT6uIu3.js → useTheme-BrYvK-_A.js} +2 -2
- package/dist/{vega-component-C3AWYGAL.js → vega-component-Pk6lyc_a.js} +10 -10
- package/dist/{zod-DXqkaI_w.js → zod-CijjQh4u.js} +1 -1
- package/package.json +3 -3
- package/src/components/ai/display-helpers.tsx +5 -5
- package/src/components/app-config/ai-config.tsx +5 -5
- package/src/components/app-config/mcp-config.tsx +3 -3
- package/src/components/chat/acp/agent-panel.tsx +3 -3
- package/src/components/chat/acp/blocks.tsx +36 -38
- package/src/components/chat/acp/common.tsx +12 -16
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +1 -1
- package/src/components/chat/acp/session-tabs.tsx +2 -2
- package/src/components/chat/chat-history-popover.tsx +1 -1
- package/src/components/chat/chat-panel.tsx +47 -23
- package/src/components/data-table/TableBottomBar.tsx +4 -1
- package/src/components/data-table/columns.tsx +2 -2
- package/src/components/data-table/data-table.tsx +26 -17
- package/src/components/data-table/filter-pill-editor.tsx +1 -1
- package/src/components/dependency-graph/minimap-content.tsx +1 -1
- package/src/components/editor/RecoveryButton.tsx +1 -1
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/useNotebookActions.tsx +4 -4
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +91 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/completion-utils.ts +86 -1
- package/src/components/editor/cell/CreateCellButton.tsx +1 -1
- package/src/components/editor/chrome/panels/empty-state.tsx +1 -1
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +1 -1
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +1 -1
- package/src/components/editor/columns/cell-column.tsx +1 -1
- package/src/components/editor/columns/sortable-column.tsx +2 -2
- package/src/components/editor/output/MarimoErrorOutput.tsx +1 -1
- package/src/components/editor/output/TextOutput.tsx +2 -2
- package/src/components/home/components.tsx +4 -4
- package/src/components/icons/github.tsx +21 -0
- package/src/components/icons/youtube.tsx +21 -0
- package/src/components/slides/minimap.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +1 -1
- package/src/components/storage/components.tsx +3 -7
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/command.tsx +2 -2
- package/src/components/ui/reorderable-list.tsx +1 -1
- package/src/components/ui/table.tsx +2 -5
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +67 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +47 -0
- package/src/core/codemirror/go-to-definition/commands.ts +47 -30
- package/src/core/codemirror/go-to-definition/utils.ts +0 -1
- package/src/core/codemirror/language/languages/sql/renderers.tsx +60 -68
- package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +54 -0
- package/src/core/codemirror/reactive-references/analyzer.ts +44 -35
- package/src/core/hotkeys/hotkeys.ts +1 -0
- package/src/core/islands/__tests__/bridge.test.ts +25 -0
- package/src/core/islands/__tests__/parse.test.ts +585 -1
- package/src/core/islands/__tests__/test-utils.tsx +10 -1
- package/src/core/islands/bridge.ts +6 -1
- package/src/core/islands/constants.ts +2 -0
- package/src/core/islands/parse.ts +293 -13
- package/src/plugins/impl/DataTablePlugin.tsx +20 -1
- package/src/plugins/impl/FileBrowserPlugin.tsx +165 -74
- package/src/plugins/impl/MatrixPlugin.tsx +2 -2
- package/src/plugins/impl/TabsPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +141 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +314 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +4 -1
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +34 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +19 -0
- package/src/plugins/impl/anywidget/model.ts +15 -0
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +1 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +155 -98
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +154 -1
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
ISLAND_DATA_ATTRIBUTES,
|
|
5
5
|
ISLAND_TAG_NAMES,
|
|
6
|
+
ISLANDS_JSON_SCRIPT_TYPE,
|
|
6
7
|
} from "@/core/islands/constants";
|
|
7
8
|
import { Logger } from "@/utils/Logger";
|
|
8
9
|
|
|
@@ -23,6 +24,10 @@ export interface MarimoIslandApp {
|
|
|
23
24
|
* ID since we allow multiple apps on the same page.
|
|
24
25
|
*/
|
|
25
26
|
id: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether cells came from a supported JSON payload instead of DOM parsing.
|
|
29
|
+
*/
|
|
30
|
+
payloadBacked?: boolean;
|
|
26
31
|
/**
|
|
27
32
|
* Cells in the app.
|
|
28
33
|
*/
|
|
@@ -42,6 +47,30 @@ interface MarimoIslandCell {
|
|
|
42
47
|
* Index of the cell.
|
|
43
48
|
*/
|
|
44
49
|
idx: number;
|
|
50
|
+
/**
|
|
51
|
+
* Stable cell identifier, when provided by the island payload.
|
|
52
|
+
*/
|
|
53
|
+
cellId?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the generated marimo cell should be present but not executed.
|
|
56
|
+
*/
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface MarimoIslandPayload {
|
|
61
|
+
schemaVersion: 1;
|
|
62
|
+
appId: string;
|
|
63
|
+
cells: MarimoIslandPayloadCell[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface MarimoIslandPayloadCell {
|
|
67
|
+
cellId: string;
|
|
68
|
+
code: string;
|
|
69
|
+
outputHtml: string;
|
|
70
|
+
outputMimetype: string;
|
|
71
|
+
reactive: boolean;
|
|
72
|
+
displayCode: boolean;
|
|
73
|
+
displayOutput: boolean;
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
/**
|
|
@@ -51,14 +80,20 @@ interface MarimoIslandCell {
|
|
|
51
80
|
export function parseMarimoIslandApps(
|
|
52
81
|
root: Document | Element = document,
|
|
53
82
|
): MarimoIslandApp[] {
|
|
54
|
-
const embeds =
|
|
83
|
+
const embeds = [
|
|
84
|
+
...root.querySelectorAll<HTMLElement>(ISLAND_TAG_NAMES.ISLAND),
|
|
85
|
+
];
|
|
86
|
+
const payloads = parseMarimoIslandPayloads(root);
|
|
55
87
|
if (embeds.length === 0) {
|
|
56
88
|
Logger.warn("No embedded marimo apps found.");
|
|
57
89
|
return [];
|
|
58
90
|
}
|
|
59
91
|
|
|
60
|
-
|
|
61
|
-
|
|
92
|
+
if (payloads.length > 0) {
|
|
93
|
+
return parsePayloadBackedApps(embeds, payloads);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parseIslandElementsIntoApps(embeds);
|
|
62
97
|
}
|
|
63
98
|
|
|
64
99
|
/**
|
|
@@ -90,11 +125,12 @@ export function parseIslandElementsIntoApps(
|
|
|
90
125
|
continue;
|
|
91
126
|
}
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
let app = apps.get(appId);
|
|
129
|
+
if (!app) {
|
|
130
|
+
app = { id: appId, cells: [] };
|
|
131
|
+
apps.set(appId, app);
|
|
95
132
|
}
|
|
96
133
|
|
|
97
|
-
const app = apps.get(appId)!;
|
|
98
134
|
const idx = app.cells.length;
|
|
99
135
|
app.cells.push({
|
|
100
136
|
output: cellData.output,
|
|
@@ -109,6 +145,159 @@ export function parseIslandElementsIntoApps(
|
|
|
109
145
|
return [...apps.values()];
|
|
110
146
|
}
|
|
111
147
|
|
|
148
|
+
function parsePayloadBackedApps(
|
|
149
|
+
embeds: HTMLElement[],
|
|
150
|
+
payloads: MarimoIslandPayload[],
|
|
151
|
+
): MarimoIslandApp[] {
|
|
152
|
+
const apps = new Map<string, MarimoIslandApp>();
|
|
153
|
+
const matchedPayloadCells = new Map<MarimoIslandPayloadCell, HTMLElement>();
|
|
154
|
+
const consumedEmbeds = new Set<HTMLElement>();
|
|
155
|
+
const acceptedPayloads: MarimoIslandPayload[] = [];
|
|
156
|
+
|
|
157
|
+
for (const payload of payloads) {
|
|
158
|
+
let hasMatchedIsland = false;
|
|
159
|
+
for (const cell of payload.cells) {
|
|
160
|
+
const embed = findMatchingIsland({
|
|
161
|
+
embeds,
|
|
162
|
+
appId: payload.appId,
|
|
163
|
+
cell,
|
|
164
|
+
consumedEmbeds,
|
|
165
|
+
});
|
|
166
|
+
if (!embed) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
consumedEmbeds.add(embed);
|
|
170
|
+
matchedPayloadCells.set(cell, embed);
|
|
171
|
+
materializeIslandPayload(embed, cell);
|
|
172
|
+
hasMatchedIsland = true;
|
|
173
|
+
}
|
|
174
|
+
// Only payloads matched to island anchors can start runtime apps.
|
|
175
|
+
if (hasMatchedIsland) {
|
|
176
|
+
acceptedPayloads.push(payload);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const payloadAppIds = new Set(
|
|
181
|
+
acceptedPayloads.map((payload) => payload.appId),
|
|
182
|
+
);
|
|
183
|
+
const reactivePayloadAppIds = new Set(
|
|
184
|
+
acceptedPayloads
|
|
185
|
+
.filter((payload) => payload.cells.some((cell) => cell.reactive))
|
|
186
|
+
.map((payload) => payload.appId),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
for (const payload of acceptedPayloads) {
|
|
190
|
+
for (const cell of payload.cells) {
|
|
191
|
+
const embed = matchedPayloadCells.get(cell);
|
|
192
|
+
// Static-only payload apps render from HTML and do not need a Pyodide
|
|
193
|
+
// session.
|
|
194
|
+
if (!reactivePayloadAppIds.has(payload.appId)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let app = apps.get(payload.appId);
|
|
199
|
+
if (!app) {
|
|
200
|
+
app = { id: payload.appId, payloadBacked: true, cells: [] };
|
|
201
|
+
apps.set(payload.appId, app);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const idx = app.cells.length;
|
|
205
|
+
const appCell: MarimoIslandCell = {
|
|
206
|
+
cellId: cell.cellId,
|
|
207
|
+
output: cell.outputHtml,
|
|
208
|
+
code: cell.reactive ? cell.code : "",
|
|
209
|
+
idx: idx,
|
|
210
|
+
};
|
|
211
|
+
// Keep static cells in the generated file so later reactive cells keep
|
|
212
|
+
// stable runtime indices without executing static code.
|
|
213
|
+
if (!cell.reactive) {
|
|
214
|
+
appCell.disabled = true;
|
|
215
|
+
}
|
|
216
|
+
app.cells.push(appCell);
|
|
217
|
+
if (cell.reactive) {
|
|
218
|
+
embed?.setAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX, idx.toString());
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// A supported payload is the runtime source for its app. Extra same-app DOM
|
|
224
|
+
// islands are disconnected from runtime binding.
|
|
225
|
+
for (const embed of embeds) {
|
|
226
|
+
const appId = embed.getAttribute(ISLAND_DATA_ATTRIBUTES.APP_ID);
|
|
227
|
+
if (appId && payloadAppIds.has(appId) && !consumedEmbeds.has(embed)) {
|
|
228
|
+
embed.removeAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID);
|
|
229
|
+
embed.removeAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX);
|
|
230
|
+
embed.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "false");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const domOnlyEmbeds = embeds.filter((embed) => {
|
|
235
|
+
const appId = embed.getAttribute(ISLAND_DATA_ATTRIBUTES.APP_ID);
|
|
236
|
+
return !appId || !payloadAppIds.has(appId);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return [...apps.values(), ...parseIslandElementsIntoApps(domOnlyEmbeds)];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function findMatchingIsland({
|
|
243
|
+
embeds,
|
|
244
|
+
appId,
|
|
245
|
+
cell,
|
|
246
|
+
consumedEmbeds,
|
|
247
|
+
}: {
|
|
248
|
+
embeds: HTMLElement[];
|
|
249
|
+
appId: string;
|
|
250
|
+
cell: MarimoIslandPayloadCell;
|
|
251
|
+
consumedEmbeds: Set<HTMLElement>;
|
|
252
|
+
}): HTMLElement | undefined {
|
|
253
|
+
return embeds.find((embed) => {
|
|
254
|
+
if (consumedEmbeds.has(embed)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
return (
|
|
258
|
+
embed.getAttribute(ISLAND_DATA_ATTRIBUTES.APP_ID) === appId &&
|
|
259
|
+
embed.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID) === cell.cellId
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function materializeIslandPayload(
|
|
265
|
+
embed: HTMLElement,
|
|
266
|
+
cell: MarimoIslandPayloadCell,
|
|
267
|
+
): void {
|
|
268
|
+
embed.setAttribute(
|
|
269
|
+
ISLAND_DATA_ATTRIBUTES.REACTIVE,
|
|
270
|
+
JSON.stringify(cell.reactive),
|
|
271
|
+
);
|
|
272
|
+
// The runtime file is synthesized from payload order, so DOM anchors bind
|
|
273
|
+
// by index.
|
|
274
|
+
embed.removeAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID);
|
|
275
|
+
if (!cell.reactive) {
|
|
276
|
+
embed.removeAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const output = ensureIslandChild(embed, ISLAND_TAG_NAMES.CELL_OUTPUT);
|
|
280
|
+
output.innerHTML = cell.displayOutput ? cell.outputHtml : "";
|
|
281
|
+
|
|
282
|
+
const code = ensureIslandChild(embed, ISLAND_TAG_NAMES.CELL_CODE);
|
|
283
|
+
code.hidden = true;
|
|
284
|
+
code.textContent = encodeURIComponent(cell.code);
|
|
285
|
+
|
|
286
|
+
const editor = embed.querySelector<HTMLElement>(ISLAND_TAG_NAMES.CODE_EDITOR);
|
|
287
|
+
if (editor) {
|
|
288
|
+
editor.setAttribute("data-initial-value", JSON.stringify(cell.code));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ensureIslandChild(embed: HTMLElement, tagName: string): HTMLElement {
|
|
293
|
+
let child = embed.querySelector<HTMLElement>(tagName);
|
|
294
|
+
if (!child) {
|
|
295
|
+
child = embed.ownerDocument.createElement(tagName);
|
|
296
|
+
embed.appendChild(child);
|
|
297
|
+
}
|
|
298
|
+
return child;
|
|
299
|
+
}
|
|
300
|
+
|
|
112
301
|
/**
|
|
113
302
|
* Parses a single island element into cell data
|
|
114
303
|
* @param embed - The island HTML element
|
|
@@ -132,26 +321,35 @@ export function parseIslandElement(
|
|
|
132
321
|
};
|
|
133
322
|
}
|
|
134
323
|
|
|
135
|
-
export function createMarimoFile(app: {
|
|
324
|
+
export function createMarimoFile(app: {
|
|
325
|
+
cells: { code: string; disabled?: boolean }[];
|
|
326
|
+
}): string {
|
|
136
327
|
const lines = [
|
|
137
328
|
"import marimo",
|
|
138
329
|
"app = marimo.App()",
|
|
139
330
|
app.cells
|
|
140
331
|
.map((cell) => {
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
332
|
+
// Disabled payload cells are placeholders. Emit pass so static code
|
|
333
|
+
// does not define names in the runtime graph.
|
|
334
|
+
const sourceCode = cell.disabled ? "" : cell.code;
|
|
335
|
+
const code = sourceCode
|
|
336
|
+
? sourceCode
|
|
337
|
+
.split("\n")
|
|
338
|
+
.map((line) => ` ${line}`)
|
|
339
|
+
.join("\n")
|
|
340
|
+
: " pass";
|
|
146
341
|
|
|
147
342
|
// TODO: Handle async cells better
|
|
148
343
|
// This is probably not the best way to check if the code is async
|
|
149
344
|
// Ideally this is pushed into the Python code
|
|
150
345
|
const isAsync = code.includes("await ");
|
|
151
346
|
const prefix = isAsync ? "async def" : "def";
|
|
347
|
+
const decorator = cell.disabled
|
|
348
|
+
? "@app.cell(disabled=True)"
|
|
349
|
+
: "@app.cell";
|
|
152
350
|
|
|
153
351
|
// Wrap in a function
|
|
154
|
-
return
|
|
352
|
+
return `${decorator}\n${prefix} __():\n${code}\n return`;
|
|
155
353
|
})
|
|
156
354
|
.join("\n"),
|
|
157
355
|
];
|
|
@@ -204,3 +402,85 @@ export function extractIslandCodeFromEmbed(embed: HTMLElement): string {
|
|
|
204
402
|
|
|
205
403
|
return "";
|
|
206
404
|
}
|
|
405
|
+
|
|
406
|
+
function parseMarimoIslandPayloads(
|
|
407
|
+
root: Document | Element,
|
|
408
|
+
): MarimoIslandPayload[] {
|
|
409
|
+
const scripts = root.querySelectorAll<HTMLScriptElement>(
|
|
410
|
+
`script[type="${ISLANDS_JSON_SCRIPT_TYPE}"]`,
|
|
411
|
+
);
|
|
412
|
+
const payloads: MarimoIslandPayload[] = [];
|
|
413
|
+
|
|
414
|
+
for (const script of scripts) {
|
|
415
|
+
if (isNestedIslandPayloadScript(script)) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const payload = parseMarimoIslandPayload(script.textContent);
|
|
419
|
+
if (payload) {
|
|
420
|
+
payloads.push(payload);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return payloads;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function isNestedIslandPayloadScript(script: HTMLScriptElement): boolean {
|
|
428
|
+
return Boolean(
|
|
429
|
+
script.closest(ISLAND_TAG_NAMES.ISLAND) ||
|
|
430
|
+
script.closest(ISLAND_TAG_NAMES.CELL_OUTPUT),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function parseMarimoIslandPayload(
|
|
435
|
+
text: string | undefined | null,
|
|
436
|
+
): MarimoIslandPayload | null {
|
|
437
|
+
if (!text) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
const payload = JSON.parse(text);
|
|
443
|
+
if (isMarimoIslandPayload(payload)) {
|
|
444
|
+
return payload;
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function isMarimoIslandPayload(
|
|
454
|
+
payload: unknown,
|
|
455
|
+
): payload is MarimoIslandPayload {
|
|
456
|
+
if (!isRecord(payload)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
return (
|
|
460
|
+
payload.schemaVersion === 1 &&
|
|
461
|
+
typeof payload.appId === "string" &&
|
|
462
|
+
Array.isArray(payload.cells) &&
|
|
463
|
+
payload.cells.every(isMarimoIslandPayloadCell)
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function isMarimoIslandPayloadCell(
|
|
468
|
+
cell: unknown,
|
|
469
|
+
): cell is MarimoIslandPayloadCell {
|
|
470
|
+
if (!isRecord(cell)) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
return (
|
|
474
|
+
typeof cell.cellId === "string" &&
|
|
475
|
+
typeof cell.code === "string" &&
|
|
476
|
+
typeof cell.outputHtml === "string" &&
|
|
477
|
+
typeof cell.outputMimetype === "string" &&
|
|
478
|
+
typeof cell.reactive === "boolean" &&
|
|
479
|
+
typeof cell.displayCode === "boolean" &&
|
|
480
|
+
typeof cell.displayOutput === "boolean"
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
485
|
+
return typeof value === "object" && value !== null;
|
|
486
|
+
}
|
|
@@ -21,6 +21,7 @@ import React, {
|
|
|
21
21
|
useMemo,
|
|
22
22
|
useState,
|
|
23
23
|
} from "react";
|
|
24
|
+
import { useLocale } from "react-aria";
|
|
24
25
|
import useEvent from "react-use-event-hook";
|
|
25
26
|
import { z } from "zod";
|
|
26
27
|
import type { CellSelectionState } from "@/components/data-table/cell-selection/types";
|
|
@@ -75,6 +76,7 @@ import { useEffectSkipFirstRender } from "@/hooks/useEffectSkipFirstRender";
|
|
|
75
76
|
import { Arrays } from "@/utils/arrays";
|
|
76
77
|
import { Functions } from "@/utils/functions";
|
|
77
78
|
import { Logger } from "@/utils/Logger";
|
|
79
|
+
import { prettyNumber } from "@/utils/numbers";
|
|
78
80
|
import {
|
|
79
81
|
generateColumns,
|
|
80
82
|
inferFieldTypes,
|
|
@@ -699,7 +701,9 @@ export const LoadingDataTableComponent = memo(
|
|
|
699
701
|
>(async () => {
|
|
700
702
|
// TODO: props.get_column_summaries is always true,
|
|
701
703
|
// so we are unable to detect if the function is registered
|
|
702
|
-
|
|
704
|
+
// Column summaries come from a kernel RPC, absent in static exports.
|
|
705
|
+
const isStatic = isStaticNotebook();
|
|
706
|
+
if (props.totalRows === 0 || !props.showColumnSummaries || isStatic) {
|
|
703
707
|
return {
|
|
704
708
|
data: null,
|
|
705
709
|
stats: {},
|
|
@@ -875,10 +879,13 @@ const DataTableComponent = ({
|
|
|
875
879
|
sizeBytesIsLoading?: boolean;
|
|
876
880
|
}): JSX.Element => {
|
|
877
881
|
const id = useId();
|
|
882
|
+
const { locale } = useLocale();
|
|
878
883
|
const [viewedRowIdx, setViewedRowIdx] = useState(0);
|
|
879
884
|
const { isPanelOpen, isAnyPanelOpen, togglePanel, panelType, setPanelType } =
|
|
880
885
|
usePanelOwnership(id, cellId);
|
|
881
886
|
|
|
887
|
+
const isStatic = isStaticNotebook();
|
|
888
|
+
|
|
882
889
|
const chartSpecModel = useMemo(() => {
|
|
883
890
|
if (!columnSummaries) {
|
|
884
891
|
return ColumnChartSpecModel.EMPTY;
|
|
@@ -949,6 +956,11 @@ const DataTableComponent = ({
|
|
|
949
956
|
showDataTypes = false;
|
|
950
957
|
}
|
|
951
958
|
|
|
959
|
+
// Row/cell selection writes back to the kernel, absent in static exports.
|
|
960
|
+
if (isStatic) {
|
|
961
|
+
selection = null;
|
|
962
|
+
}
|
|
963
|
+
|
|
952
964
|
const columns = useMemo(
|
|
953
965
|
() =>
|
|
954
966
|
generateColumns({
|
|
@@ -1109,6 +1121,13 @@ const DataTableComponent = ({
|
|
|
1109
1121
|
Result clipped. Showing {shownColumns} of {totalColumns} columns.
|
|
1110
1122
|
</Banner>
|
|
1111
1123
|
)}
|
|
1124
|
+
{isStatic && typeof totalRows === "number" && data.length < totalRows && (
|
|
1125
|
+
<Banner className="mb-1 rounded">
|
|
1126
|
+
Showing the first <strong>{prettyNumber(data.length, locale)}</strong>{" "}
|
|
1127
|
+
of <strong>{prettyNumber(totalRows, locale)}</strong> rows. Increase
|
|
1128
|
+
the table's <code>page_size</code> to embed more in the static export.
|
|
1129
|
+
</Banner>
|
|
1130
|
+
)}
|
|
1112
1131
|
{columnSummaries?.is_disabled && (
|
|
1113
1132
|
// Note: Keep the text in sync with the constant defined in table_manager.py
|
|
1114
1133
|
// This hard-code can be removed when Functions can pass structural
|