@marimo-team/frontend 0.25.3-dev2 → 0.25.3-dev21
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/assets/{CellStatus-BP2mavAq.js → CellStatus-O0rU3_5j.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-TprnxZeJ.js} +1 -1
- package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-BkSCfv3B.js} +2 -2
- package/dist/assets/JsonOutput-Dq6MGIMV.js +11 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-B4fCknq1.js} +2 -2
- package/dist/assets/MarimoErrorOutput-BLtCVffa.js +10 -0
- package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-PtQ99V0D.js} +1 -1
- package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-B8KRdPZ-.js} +1 -1
- package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-BOBZjUxI.js} +13 -13
- package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-DP62zXw2.js} +2 -2
- package/dist/assets/{agent-panel-D7kMZLG-.js → agent-panel-BFul7UiF.js} +5 -5
- package/dist/assets/ai-model-dropdown-sTKkdrbX.js +13 -0
- package/dist/assets/{any-language-editor-BLCAD3yx.js → any-language-editor-BdTWdMCI.js} +1 -1
- package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-Bp44UZdD.js} +1 -1
- package/dist/assets/{blob-D5nfNHOo.js → blob-ykpU69Lk.js} +1 -1
- package/dist/assets/{cell-editor-ADVfycpN.js → cell-editor-B7YlotyH.js} +9 -9
- package/dist/assets/{cell-link-LSth_8hT.js → cell-link-CmO9D2a2.js} +1 -1
- package/dist/assets/{cells-BRkCaSsh.js → cells-BMs-CYDt.js} +31 -31
- package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-Dw8HQAAY.js} +1 -1
- package/dist/assets/{chat-panel-DQ6HkWEF.js → chat-panel-DPDZrfoJ.js} +2 -2
- package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-BxnFw9Za.js} +1 -1
- package/dist/assets/code-visibility-CGVmpSdE.js +1 -0
- package/dist/assets/coerce-DWjFeXOY.js +1 -0
- package/dist/assets/{column-preview-Cm2D6vyX.js → column-preview-BQ4k86Lv.js} +1 -1
- package/dist/assets/{command-palette-DTSQ7gOC.js → command-palette-BfxYnBUD.js} +1 -1
- package/dist/assets/{common-Cu6CDbuw.js → common-CzWTWhcY.js} +1 -1
- package/dist/assets/{components-CGw16R_N.js → components-C-p2nJ78.js} +1 -1
- package/dist/assets/{components-CP_UoFtT.js → components-CskjK6xZ.js} +1 -1
- package/dist/assets/{components-CXfO-F47.js → components-YBmtV0p6.js} +1 -1
- package/dist/assets/config-BtRLgrlt.js +104 -0
- package/dist/assets/{config-VPSq73n8.js → config-CA1V-wjh.js} +1 -1
- package/dist/assets/config-CMUeR8Wr.js +1 -0
- package/dist/assets/{datasource-Cq6x_5ML.js → datasource-CEDUN47W.js} +1 -1
- package/dist/assets/{dates-BbFQ2oE4.js → dates-CfmdTL6B.js} +1 -1
- package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-KWuUrABU.js} +1 -1
- package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-T1wYs9YS.js} +1 -1
- package/dist/assets/{download-DCJXy66H.js → download-Bt4rM2gS.js} +1 -1
- package/dist/assets/edit-page-DIxL37jf.js +9 -0
- package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-COZ7SfYV.js} +1 -1
- package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-CQTemuon.js} +1 -1
- package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-BmlN965r.js} +1 -1
- package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
- package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-CO89l9HJ.js} +2 -2
- package/dist/assets/feedback-button-QWfd9e1X.js +1 -0
- package/dist/assets/{file-explorer-panel-BEEK5lcl.js → file-explorer-panel-Bs25Xwng.js} +1 -1
- package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-DgR3ynbz.js} +1 -1
- package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-kOJ_XOxZ.js} +1 -1
- package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page--AiS7R2S.js} +1 -1
- package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-FopeFe04.js} +1 -1
- package/dist/assets/{home-page-DMHnsRxm.js → home-page-DEUEDKZQ.js} +1 -1
- package/dist/assets/hooks-CsCHI0xl.js +1 -0
- package/dist/assets/index-6b4mf8Ai.css +2 -0
- package/dist/assets/index-BXheV3uz.js +30 -0
- package/dist/assets/{layout-DmcNEu3P.js → layout-CcQIAduN.js} +5 -5
- package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-Cj-mRfbP.js} +1 -1
- package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-Dg2Tj8xg.js} +1 -1
- package/dist/assets/{mermaid-a3xYymTy.js → mermaid-C4EiVqcd.js} +1 -1
- package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-DhWcLBL9.js} +1 -1
- package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-DCCN7CCz.js} +1 -1
- package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-cI3VRnI0.js} +1 -1
- package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-CrUAB8rV.js} +1 -1
- package/dist/assets/panels-DeK2CjCU.js +4 -0
- package/dist/assets/{process-output-D0KGq04R.js → process-output-C0AVlTcL.js} +1 -1
- package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-CEGKPKlf.js} +1 -1
- package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-kOLuuvuL.js} +1 -1
- package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-C1JwYcqW.js} +1 -1
- package/dist/assets/{reveal-component-ltaepAqw.js → reveal-component-m_Q_Dhsa.js} +3 -3
- package/dist/assets/{run-page-DF3c5cDk.js → run-page-ZPnT0tT8.js} +1 -1
- package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-DRpKx1d7.js} +1 -1
- package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
- package/dist/assets/{scratchpad-panel-Dwd6W5PE.js → scratchpad-panel-4r5I7_4T.js} +1 -1
- package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-D7KN4tVs.js} +1 -1
- package/dist/assets/session-panel-B-fUHivk.js +1 -0
- package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-QqliOy2l.js} +1 -1
- package/dist/assets/state-BAKG3nB6.js +3 -0
- package/dist/assets/{state-XsPO8hNi.js → state-BiuybHTd.js} +1 -1
- package/dist/assets/{terminal-Ck_vK-34.js → terminal-CP30at9D.js} +3 -3
- package/dist/assets/{tracing-Di6pUmX8.js → tracing-Cg6nJ9oa.js} +1 -1
- package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-_OkwL3ST.js} +2 -2
- package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-D5U7Yym8.js} +1 -1
- package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-C3jdhSc9.js} +1 -1
- package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-BHCcNH_F.js} +1 -1
- package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-Db-2kvJ0.js} +1 -1
- package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-D0dwhgwH.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-BjQVPzm0.js} +1 -1
- package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-B-krIeGp.js} +1 -1
- package/dist/assets/useNotebookActions-CY_gTNzg.js +1 -0
- package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-Dyf_vzJl.js} +1 -1
- package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-DA8ujlOv.js} +1 -1
- package/dist/assets/{useTheme-COhSHkCj.js → useTheme-n8Dj1zH7.js} +1 -1
- package/dist/assets/{vega-component-CnadTgoE.js → vega-component-CaZvZfgt.js} +1 -1
- package/dist/index.html +40 -41
- package/package.json +1 -1
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/mount.test.ts +28 -8
- package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
- package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
- package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
- package/src/components/data-table/context-menu.tsx +125 -26
- package/src/components/data-table/date-filter-inputs.tsx +15 -42
- package/src/components/data-table/export-actions.tsx +192 -116
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/data-table/utils.ts +18 -0
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
- package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
- package/src/components/editor/errors/send-error-report-button.tsx +69 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +35 -20
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +18 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/cells.test.ts +52 -0
- package/src/core/cells/__tests__/error-report.test.ts +139 -0
- package/src/core/cells/__tests__/session.test.ts +50 -1
- package/src/core/cells/error-report.ts +63 -0
- package/src/core/cells/session.ts +28 -1
- package/src/core/config/pair.ts +5 -3
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +5 -0
- package/src/core/host/__tests__/attachments.test.ts +57 -0
- package/src/core/host/__tests__/document.test.ts +124 -0
- package/src/core/host/__tests__/events.test.ts +46 -0
- package/src/core/host/__tests__/fixtures.ts +39 -0
- package/src/core/host/__tests__/notebook-client.test.ts +433 -0
- package/src/core/host/__tests__/stream.test.ts +211 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +61 -0
- package/src/core/host/notebook-client.ts +290 -0
- package/src/core/host/resources/attachments.ts +19 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/host/resources/resource.ts +59 -0
- package/src/core/host/stream.ts +213 -0
- package/src/core/network/connection.ts +4 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +55 -11
- package/src/core/runtime/runtime.ts +15 -1
- package/src/core/runtime/types.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +170 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +181 -65
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +88 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +59 -225
- package/src/mount.tsx +7 -3
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- package/src/utils/__tests__/dates.test.ts +116 -0
- package/src/utils/dates.ts +94 -0
- package/dist/assets/JsonOutput-D6HIIsCb.js +0 -11
- package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
- package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
- package/dist/assets/api-BeC_VSGv.js +0 -1
- package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
- package/dist/assets/coerce-CpE6kUdX.js +0 -1
- package/dist/assets/config-BOKzA3tF.js +0 -1
- package/dist/assets/config-DX8Y2WbJ.js +0 -1
- package/dist/assets/edit-page-ChcSgpke.js +0 -9
- package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
- package/dist/assets/hooks-93IwHd2u.js +0 -1
- package/dist/assets/index-Bcdzex-3.css +0 -2
- package/dist/assets/index-s-M1OXjs.js +0 -29
- package/dist/assets/pair-CaQyK6En.js +0 -1
- package/dist/assets/panels-CxVG8-v2.js +0 -4
- package/dist/assets/session-panel-VdDhEBa-.js +0 -1
- package/dist/assets/state-_Qai_QyX.js +0 -3
- package/dist/assets/useNotebookActions-NHN6A-cN.js +0 -1
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
TableIcon,
|
|
15
15
|
} from "lucide-react";
|
|
16
16
|
import React from "react";
|
|
17
|
+
import useEvent from "react-use-event-hook";
|
|
17
18
|
import { downloadSizeLimitAtom } from "./download-policy/atoms";
|
|
18
19
|
import type {
|
|
19
20
|
DownloadAsArgs,
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
ExportMetadata,
|
|
23
24
|
GetExportMetadata,
|
|
24
25
|
} from "./schemas";
|
|
26
|
+
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
25
27
|
import { logNever } from "@/utils/assertNever";
|
|
26
28
|
import { cn } from "@/utils/cn";
|
|
27
29
|
import { copyToClipboard } from "@/utils/copy";
|
|
@@ -274,19 +276,130 @@ const failureTitleForAction = (action: ExportAction): string =>
|
|
|
274
276
|
const failureDescription = (error: unknown): string =>
|
|
275
277
|
typeof error === "string" ? error : prettyError(error);
|
|
276
278
|
|
|
279
|
+
const OBSOLETE_GEOMETRY_MESSAGE =
|
|
280
|
+
"The selected geometry is no longer available. Choose another geometry to export.";
|
|
281
|
+
|
|
282
|
+
type GeographicBlocker =
|
|
283
|
+
| "format-unavailable"
|
|
284
|
+
| "geometry-required"
|
|
285
|
+
| "choose-geometry"
|
|
286
|
+
| "obsolete-geometry"
|
|
287
|
+
| "missing-crs";
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Find the first reason that a geographic export cannot start with the
|
|
291
|
+
* selected geometry. The dialog uses it to disable a format, and the retry
|
|
292
|
+
* after a package installation uses it to decide whether to export.
|
|
293
|
+
*/
|
|
294
|
+
function geographicBlocker(
|
|
295
|
+
metadata: ExportMetadata,
|
|
296
|
+
format: GeographicFormat,
|
|
297
|
+
geometryColumn: string | null,
|
|
298
|
+
): GeographicBlocker | null {
|
|
299
|
+
if (metadata.formats[format]?.available === false) {
|
|
300
|
+
return "format-unavailable";
|
|
301
|
+
}
|
|
302
|
+
const selected = metadata.geometry_columns.find(
|
|
303
|
+
(column) => column.name === geometryColumn,
|
|
304
|
+
);
|
|
305
|
+
if (geometryColumn !== null && !selected) {
|
|
306
|
+
return "obsolete-geometry";
|
|
307
|
+
}
|
|
308
|
+
if (metadata.geometry_columns.length === 0) {
|
|
309
|
+
return format === "geojson" ? "geometry-required" : null;
|
|
310
|
+
}
|
|
311
|
+
if (!selected) {
|
|
312
|
+
return "choose-geometry";
|
|
313
|
+
}
|
|
314
|
+
if (format === "geojson" && !selected.crs) {
|
|
315
|
+
return "missing-crs";
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const geometryDisplayName = (name: string | null): string | null =>
|
|
321
|
+
name === "" ? "(unnamed geometry)" : name;
|
|
322
|
+
|
|
323
|
+
function geographicBlockerReason(
|
|
324
|
+
blocker: GeographicBlocker | null,
|
|
325
|
+
formatLabel: string,
|
|
326
|
+
geometryColumn: string | null,
|
|
327
|
+
): string | null {
|
|
328
|
+
switch (blocker) {
|
|
329
|
+
case "choose-geometry":
|
|
330
|
+
return `Choose a primary geometry column to export ${formatLabel}.`;
|
|
331
|
+
case "obsolete-geometry":
|
|
332
|
+
return OBSOLETE_GEOMETRY_MESSAGE;
|
|
333
|
+
case "missing-crs":
|
|
334
|
+
return `Geometry column ${geometryDisplayName(geometryColumn)} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`;
|
|
335
|
+
case "format-unavailable":
|
|
336
|
+
case "geometry-required":
|
|
337
|
+
case null:
|
|
338
|
+
return null;
|
|
339
|
+
default:
|
|
340
|
+
logNever(blocker);
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* One open dialog for one table source. Closing the dialog or replacing the
|
|
347
|
+
* source creates a new context, and work started under an older context must
|
|
348
|
+
* not change the dialog or start an export.
|
|
349
|
+
*/
|
|
350
|
+
interface ExportContext {
|
|
351
|
+
source: unknown;
|
|
352
|
+
}
|
|
353
|
+
|
|
277
354
|
export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
278
355
|
const { getExportMetadata, metadataSource } = props;
|
|
279
356
|
const [exportDialogOpen, setExportDialogOpen] = React.useState(false);
|
|
280
357
|
const [failure, setFailure] = React.useState<ExportFailure | null>(null);
|
|
281
358
|
const [settings, setSettings] =
|
|
282
359
|
React.useState<ExportSettings>(DEFAULT_SETTINGS);
|
|
283
|
-
const [
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
360
|
+
const [exportContext, setExportContext] = React.useState<ExportContext>(
|
|
361
|
+
() => ({ source: metadataSource }),
|
|
362
|
+
);
|
|
363
|
+
if (exportContext.source !== metadataSource) {
|
|
364
|
+
setExportContext({ source: metadataSource });
|
|
365
|
+
}
|
|
366
|
+
const mounted = React.useRef(false);
|
|
367
|
+
const isCurrentContext = useEvent(
|
|
368
|
+
(context: ExportContext) => mounted.current && context === exportContext,
|
|
369
|
+
);
|
|
370
|
+
const metadataRequest = useAsyncData(async () => {
|
|
371
|
+
if (!exportDialogOpen || !getExportMetadata) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return { context: exportContext, metadata: await getExportMetadata({}) };
|
|
375
|
+
}, [exportDialogOpen, getExportMetadata, exportContext]);
|
|
376
|
+
// The hook keeps earlier data while it reloads and after a failure. Only a
|
|
377
|
+
// completed response for the current context can enable an export.
|
|
378
|
+
const metadata =
|
|
379
|
+
metadataRequest.status === "success" &&
|
|
380
|
+
metadataRequest.data?.context === exportContext
|
|
381
|
+
? metadataRequest.data.metadata
|
|
382
|
+
: null;
|
|
383
|
+
const metadataError =
|
|
384
|
+
metadataRequest.status === "error"
|
|
385
|
+
? failureDescription(metadataRequest.error)
|
|
386
|
+
: null;
|
|
287
387
|
const [geometryColumn, setGeometryColumn] = React.useState<string | null>(
|
|
288
388
|
null,
|
|
289
389
|
);
|
|
390
|
+
const [reconciledMetadata, setReconciledMetadata] =
|
|
391
|
+
React.useState<ExportMetadata | null>(null);
|
|
392
|
+
if (metadata !== reconciledMetadata) {
|
|
393
|
+
setReconciledMetadata(metadata);
|
|
394
|
+
if (metadata) {
|
|
395
|
+
setGeometryColumn((current) =>
|
|
396
|
+
current !== null &&
|
|
397
|
+
metadata.geometry_columns.some((column) => column.name === current)
|
|
398
|
+
? current
|
|
399
|
+
: metadata.default_geometry_column,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
290
403
|
const hasGeometry = !!metadata?.geometry_columns.length;
|
|
291
404
|
const [expandedFormat, setExpandedFormat] = React.useState<
|
|
292
405
|
ConfigurableFormat | GeographicFormat | null
|
|
@@ -294,43 +407,17 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
294
407
|
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
|
295
408
|
const dialogRef = React.useRef<HTMLDivElement>(null);
|
|
296
409
|
const latestActionId = React.useRef(0);
|
|
410
|
+
React.useLayoutEffect(() => {
|
|
411
|
+
mounted.current = true;
|
|
412
|
+
return () => {
|
|
413
|
+
mounted.current = false;
|
|
414
|
+
latestActionId.current += 1;
|
|
415
|
+
};
|
|
416
|
+
}, []);
|
|
297
417
|
React.useEffect(() => {
|
|
298
418
|
latestActionId.current += 1;
|
|
419
|
+
setFailure(null);
|
|
299
420
|
}, [metadataSource]);
|
|
300
|
-
React.useEffect(() => {
|
|
301
|
-
if (!exportDialogOpen || !getExportMetadata) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
let active = true;
|
|
305
|
-
setMetadata(null);
|
|
306
|
-
setMetadataLoading(true);
|
|
307
|
-
setMetadataError(null);
|
|
308
|
-
void getExportMetadata({}).then(
|
|
309
|
-
(result) => {
|
|
310
|
-
if (!active) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
setMetadata(result);
|
|
314
|
-
setGeometryColumn((current) =>
|
|
315
|
-
current !== null &&
|
|
316
|
-
result.geometry_columns.some((column) => column.name === current)
|
|
317
|
-
? current
|
|
318
|
-
: result.default_geometry_column,
|
|
319
|
-
);
|
|
320
|
-
setMetadataLoading(false);
|
|
321
|
-
},
|
|
322
|
-
(error: unknown) => {
|
|
323
|
-
if (!active) {
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
setMetadataError(failureDescription(error));
|
|
327
|
-
setMetadataLoading(false);
|
|
328
|
-
},
|
|
329
|
-
);
|
|
330
|
-
return () => {
|
|
331
|
-
active = false;
|
|
332
|
-
};
|
|
333
|
-
}, [exportDialogOpen, getExportMetadata, metadataSource, metadataRefresh]);
|
|
334
421
|
const policy = useAtomValue(downloadSizeLimitAtom);
|
|
335
422
|
const overLimit = !!(
|
|
336
423
|
policy &&
|
|
@@ -368,9 +455,8 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
368
455
|
setFailure(null);
|
|
369
456
|
setExpandedFormat(null);
|
|
370
457
|
setSettings(DEFAULT_SETTINGS);
|
|
371
|
-
setMetadata(null);
|
|
372
|
-
setMetadataError(null);
|
|
373
458
|
setGeometryColumn(null);
|
|
459
|
+
setExportContext({ source: metadataSource });
|
|
374
460
|
}
|
|
375
461
|
};
|
|
376
462
|
|
|
@@ -553,57 +639,67 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
553
639
|
}
|
|
554
640
|
};
|
|
555
641
|
|
|
556
|
-
|
|
557
|
-
|
|
642
|
+
// Package installation can finish after the dialog closes or the source
|
|
643
|
+
// changes, so the retry carries the context in which installation began.
|
|
644
|
+
const retryAfterInstall = useEvent(
|
|
645
|
+
async (action: ExportAction, context: ExportContext) => {
|
|
646
|
+
if (!isCurrentContext(context)) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (!isGeographic(action.format) || !getExportMetadata) {
|
|
650
|
+
retryAction(action);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
558
653
|
const actionId = latestActionId.current;
|
|
654
|
+
const isCurrent = () =>
|
|
655
|
+
isCurrentContext(context) && actionId === latestActionId.current;
|
|
559
656
|
let result: ExportMetadata;
|
|
560
657
|
try {
|
|
561
658
|
result = await getExportMetadata({});
|
|
562
|
-
} catch
|
|
563
|
-
if (
|
|
564
|
-
|
|
659
|
+
} catch {
|
|
660
|
+
if (isCurrent()) {
|
|
661
|
+
metadataRequest.refetch();
|
|
565
662
|
}
|
|
566
663
|
return;
|
|
567
664
|
}
|
|
568
|
-
if (
|
|
665
|
+
if (!isCurrent()) {
|
|
569
666
|
return;
|
|
570
667
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
action.format === "geojson" &&
|
|
598
|
-
!result.geometry_columns.find(
|
|
599
|
-
(column) => column.name === geometryColumn,
|
|
600
|
-
)?.crs
|
|
601
|
-
) {
|
|
602
|
-
setFailure(null);
|
|
603
|
-
return;
|
|
668
|
+
// Validation reads its own response, so a display refresh cannot
|
|
669
|
+
// start an export that this check rejected.
|
|
670
|
+
metadataRequest.refetch();
|
|
671
|
+
const blocker = geographicBlocker(result, action.format, geometryColumn);
|
|
672
|
+
switch (blocker) {
|
|
673
|
+
case null:
|
|
674
|
+
retryAction(action);
|
|
675
|
+
return;
|
|
676
|
+
case "obsolete-geometry":
|
|
677
|
+
setGeometryColumn(result.default_geometry_column);
|
|
678
|
+
setActionFailure(actionId, {
|
|
679
|
+
kind: "error",
|
|
680
|
+
title: failureTitleForAction(action),
|
|
681
|
+
description: OBSOLETE_GEOMETRY_MESSAGE,
|
|
682
|
+
action,
|
|
683
|
+
});
|
|
684
|
+
return;
|
|
685
|
+
case "choose-geometry":
|
|
686
|
+
return;
|
|
687
|
+
case "format-unavailable":
|
|
688
|
+
case "geometry-required":
|
|
689
|
+
case "missing-crs":
|
|
690
|
+
setFailure(null);
|
|
691
|
+
return;
|
|
692
|
+
default:
|
|
693
|
+
logNever(blocker);
|
|
604
694
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
695
|
+
},
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
const createInstallRetry = (action: ExportAction) => {
|
|
699
|
+
const context = exportContext;
|
|
700
|
+
return () => {
|
|
701
|
+
void retryAfterInstall(action, context);
|
|
702
|
+
};
|
|
607
703
|
};
|
|
608
704
|
|
|
609
705
|
const toggleExpanded = (format: ExportFormat) => {
|
|
@@ -670,9 +766,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
670
766
|
failure?.action.format === option.format ? failure : null;
|
|
671
767
|
const summary =
|
|
672
768
|
isGeographic(option.format) && hasGeometry
|
|
673
|
-
? geometryColumn
|
|
674
|
-
? "(unnamed geometry)"
|
|
675
|
-
: (geometryColumn ?? "Choose geometry")
|
|
769
|
+
? (geometryDisplayName(geometryColumn) ?? "Choose geometry")
|
|
676
770
|
: settingsSummary(settings, option.format);
|
|
677
771
|
const selectedGeometryIndex =
|
|
678
772
|
metadata?.geometry_columns.findIndex(
|
|
@@ -681,20 +775,14 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
681
775
|
const eligibility = isGeographic(option.format)
|
|
682
776
|
? metadata?.formats[option.format]
|
|
683
777
|
: undefined;
|
|
684
|
-
const
|
|
685
|
-
metadata
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
selectedGeometry !== undefined &&
|
|
689
|
-
!selectedGeometry.crs;
|
|
778
|
+
const blocker =
|
|
779
|
+
isGeographic(option.format) && metadata
|
|
780
|
+
? geographicBlocker(metadata, option.format, geometryColumn)
|
|
781
|
+
: null;
|
|
690
782
|
const geometryReason = metadataError
|
|
691
783
|
? `Could not load geometry options: ${metadataError}`
|
|
692
784
|
: (eligibility?.reason ??
|
|
693
|
-
(
|
|
694
|
-
? `Choose a primary geometry column to export ${optionLabel}.`
|
|
695
|
-
: missingCrs
|
|
696
|
-
? `Geometry column ${geometryColumn === "" ? "(unnamed geometry)" : geometryColumn} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`
|
|
697
|
-
: null));
|
|
785
|
+
geographicBlockerReason(blocker, optionLabel, geometryColumn));
|
|
698
786
|
const missingPackageFailure =
|
|
699
787
|
rowFailure?.kind === "missing-packages" ? rowFailure : null;
|
|
700
788
|
const missingPackages =
|
|
@@ -704,13 +792,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
704
792
|
: []);
|
|
705
793
|
const geographicUnavailable =
|
|
706
794
|
isGeographic(option.format) &&
|
|
707
|
-
((!!getExportMetadata && !metadata) ||
|
|
708
|
-
metadataLoading ||
|
|
709
|
-
!!metadataError ||
|
|
710
|
-
eligibility?.available === false ||
|
|
711
|
-
missingCrs ||
|
|
712
|
-
(!!metadata?.geometry_columns.length &&
|
|
713
|
-
geometryColumn === null));
|
|
795
|
+
((!!getExportMetadata && !metadata) || blocker !== null);
|
|
714
796
|
const copyUnavailable =
|
|
715
797
|
isGeographic(option.format) &&
|
|
716
798
|
(geographicUnavailable || missingPackages.length > 0);
|
|
@@ -888,9 +970,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
888
970
|
type="button"
|
|
889
971
|
size="xs"
|
|
890
972
|
variant="outline"
|
|
891
|
-
onClick={
|
|
892
|
-
setMetadataRefresh((current) => current + 1)
|
|
893
|
-
}
|
|
973
|
+
onClick={metadataRequest.refetch}
|
|
894
974
|
>
|
|
895
975
|
Retry
|
|
896
976
|
</Button>
|
|
@@ -906,17 +986,13 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
906
986
|
missingPackageFailure?.description ??
|
|
907
987
|
eligibility?.reason
|
|
908
988
|
}
|
|
909
|
-
onInstall={(
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
format:
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
: "parquet",
|
|
917
|
-
},
|
|
918
|
-
);
|
|
919
|
-
}}
|
|
989
|
+
onInstall={createInstallRetry(
|
|
990
|
+
missingPackageFailure?.action ?? {
|
|
991
|
+
destination: "download",
|
|
992
|
+
format:
|
|
993
|
+
option.format === "geojson" ? "geojson" : "parquet",
|
|
994
|
+
},
|
|
995
|
+
)}
|
|
920
996
|
className="items-start"
|
|
921
997
|
/>
|
|
922
998
|
</div>
|
|
@@ -3,42 +3,6 @@
|
|
|
3
3
|
import z from "zod";
|
|
4
4
|
import { rpc } from "@/plugins/core/rpc";
|
|
5
5
|
|
|
6
|
-
export type DownloadFormat = "csv" | "json" | "parquet" | "tsv" | "geojson";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Per-request export options. Each field is optional. A missing field keeps
|
|
10
|
-
* the widget's default for that setting.
|
|
11
|
-
*/
|
|
12
|
-
export interface DownloadAsOptions {
|
|
13
|
-
separator?: string;
|
|
14
|
-
encoding?: string;
|
|
15
|
-
ensure_ascii?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface DownloadAsRequest {
|
|
19
|
-
format: DownloadFormat;
|
|
20
|
-
options?: DownloadAsOptions;
|
|
21
|
-
geometry_column?: string | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type DownloadAsArgs = (req: DownloadAsRequest) => Promise<{
|
|
25
|
-
url: string;
|
|
26
|
-
filename: string;
|
|
27
|
-
error?: string | null;
|
|
28
|
-
missing_packages?: string[] | null;
|
|
29
|
-
code?:
|
|
30
|
-
| "geometry_required"
|
|
31
|
-
| "invalid_geometry"
|
|
32
|
-
| "invalid_metadata"
|
|
33
|
-
| "unsupported_representation"
|
|
34
|
-
| "unsupported_version"
|
|
35
|
-
| "missing_packages"
|
|
36
|
-
| "missing_crs"
|
|
37
|
-
| "conversion_failed"
|
|
38
|
-
| null;
|
|
39
|
-
column?: string | null;
|
|
40
|
-
}>;
|
|
41
|
-
|
|
42
6
|
const DownloadRequestSchema = z.object({
|
|
43
7
|
geometry_column: z.string().nullish(),
|
|
44
8
|
options: z
|
|
@@ -88,6 +52,20 @@ export const DownloadGeoJSONSchema = rpc
|
|
|
88
52
|
}),
|
|
89
53
|
);
|
|
90
54
|
|
|
55
|
+
export type DownloadAsRequest =
|
|
56
|
+
| z.infer<typeof DownloadAsSchema.input>
|
|
57
|
+
| z.infer<typeof DownloadGeoJSONSchema.input>;
|
|
58
|
+
|
|
59
|
+
type DownloadResponse =
|
|
60
|
+
| z.infer<typeof DownloadAsSchema.output>
|
|
61
|
+
| z.infer<typeof DownloadGeoJSONSchema.output>;
|
|
62
|
+
|
|
63
|
+
export type DownloadFormat = DownloadAsRequest["format"];
|
|
64
|
+
export type DownloadAsOptions = NonNullable<DownloadAsRequest["options"]>;
|
|
65
|
+
export type DownloadAsArgs = (
|
|
66
|
+
req: DownloadAsRequest,
|
|
67
|
+
) => Promise<DownloadResponse>;
|
|
68
|
+
|
|
91
69
|
export type DownloadAsFunction = (
|
|
92
70
|
req: z.infer<typeof DownloadAsSchema.input>,
|
|
93
71
|
) => Promise<z.infer<typeof DownloadAsSchema.output>>;
|
|
@@ -228,6 +228,24 @@ export function detectSentinel(
|
|
|
228
228
|
return null;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
type NullishSentinelType = Extract<CellValueSentinel["type"], "null" | "nat">;
|
|
232
|
+
|
|
233
|
+
const NULLISH_SENTINEL_TYPES: ReadonlySet<CellValueSentinel["type"]> =
|
|
234
|
+
new Set<NullishSentinelType>(["null", "nat"]);
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* True when the cell value represents a missing value (null, NaT)
|
|
238
|
+
* rather than a real value that happens to look special.
|
|
239
|
+
*/
|
|
240
|
+
export function isNullishSentinel(
|
|
241
|
+
value: unknown,
|
|
242
|
+
dataType: DataType | undefined,
|
|
243
|
+
dtype?: string,
|
|
244
|
+
): boolean {
|
|
245
|
+
const sentinel = detectSentinel(value, dataType, dtype);
|
|
246
|
+
return sentinel !== null && NULLISH_SENTINEL_TYPES.has(sentinel.type);
|
|
247
|
+
}
|
|
248
|
+
|
|
231
249
|
/**
|
|
232
250
|
* Stringify an unknown value. Converts objects to JSON strings.
|
|
233
251
|
* @param opts.value - The value to stringify.
|
|
@@ -66,7 +66,10 @@ export type OnRefactorWithAI = (opts: {
|
|
|
66
66
|
* Renders an output based on an OutputMessage.
|
|
67
67
|
*/
|
|
68
68
|
export const OutputRenderer: React.FC<{
|
|
69
|
-
message: Pick<
|
|
69
|
+
message: Pick<
|
|
70
|
+
OutputMessage,
|
|
71
|
+
"channel" | "data" | "mimetype" | "code" | "traceback_format"
|
|
72
|
+
>;
|
|
70
73
|
cellId?: CellId;
|
|
71
74
|
onRefactorWithAI?: OnRefactorWithAI;
|
|
72
75
|
wrapText?: boolean;
|
|
@@ -172,7 +175,9 @@ export const OutputRenderer: React.FC<{
|
|
|
172
175
|
|
|
173
176
|
case "application/vnd.marimo+error":
|
|
174
177
|
invariant(Array.isArray(data), "Expected array data");
|
|
175
|
-
return
|
|
178
|
+
return (
|
|
179
|
+
<MarimoErrorOutput cellId={cellId} errors={data} code={message.code} />
|
|
180
|
+
);
|
|
176
181
|
|
|
177
182
|
case "application/vnd.marimo+traceback":
|
|
178
183
|
invariant(
|
|
@@ -183,6 +188,8 @@ export const OutputRenderer: React.FC<{
|
|
|
183
188
|
<MarimoTracebackOutput
|
|
184
189
|
onRefactorWithAI={onRefactorWithAI}
|
|
185
190
|
traceback={data}
|
|
191
|
+
tracebackFormat={message.traceback_format}
|
|
192
|
+
code={message.code}
|
|
186
193
|
cellId={cellId}
|
|
187
194
|
/>
|
|
188
195
|
);
|