@marimo-team/islands 0.25.3-dev6 → 0.25.3-dev60
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-CNMliVzw.js → ConnectedDataExplorerComponent-Da5OoEUL.js} +14 -14
- package/dist/{ErrorBoundary-Ca-Sa492.js → ErrorBoundary-BmHVnL-A.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Bz3mrprX.js} +2 -2
- package/dist/any-language-editor-k2B8YI3Y.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-Cs5ujb9K.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
- package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
- package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-C0_lCzql.js} +1139 -1140
- package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-CcTSaxZt.js} +1 -1
- package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DcJcjOaO.js} +1 -1
- package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BVtDTjaF.js} +1 -1
- package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-BkDFkh8q.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-CfAt7HT5.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CGEjNYa2.js} +2 -2
- package/dist/{code-visibility-NGRjhbtA.js → code-visibility-BWERlt58.js} +6 -6
- package/dist/{common-B7_ZSg0P.js → common-E2zdIyjs.js} +4960 -4835
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-BW6NmfJi.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-CWg3CTuF.js} +1 -1
- package/dist/{data-grid-overlay-editor-B_cmrkBm.js → data-grid-overlay-editor-CHUwPg7P.js} +5 -5
- package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BPxtoS-z.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DA-vJdFL.js} +1 -1
- package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-D6bkR84S.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-m60g4cAT.js} +1 -1
- package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.js} +3 -3
- package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
- package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-B3eUiVJB.js} +177 -169
- package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-CK99sho2.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-Bryq0tdE.js} +1 -1
- package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
- package/dist/{error-entries-DSvFWT_M.js → error-entries-CMNtA8NK.js} +959 -959
- package/dist/{feedback-button-CTF2Nj9s.js → feedback-button-DwOUm3jt.js} +29 -29
- package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-D-3aRpfS.js} +2 -2
- package/dist/{formats-CeOmU1MU.js → formats-6pLWWrY2.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-BQHF9puC.js → glide-data-editor-C9WGlA66.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
- package/dist/{globals-Cj8iqMEp.js → globals-BwFL--QV.js} +3 -3
- package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
- package/dist/{html-to-image-B8s5SgGm.js → html-to-image-6xcoTKhC.js} +29 -29
- package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-s1C1kgfU.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-CCsRgolY.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-BrmxDlcc.js} +1 -1
- package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
- package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
- package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
- package/dist/main.js +3177 -2980
- package/dist/{mermaid-CHo49Yv-.js → mermaid-DKI0869I.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-641xJysF.js} +1 -1
- package/dist/{number-overlay-editor-DBcnm0SY.js → number-overlay-editor-Ci9h4-h1.js} +3 -3
- package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
- package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-keVUAUeS.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DHktXPO_.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-CKRXyszb.js → process-output-BJRJFobf.js} +4 -4
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BVEF4OV_.js} +2 -2
- package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
- package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CWo-UAKV.js} +1 -1
- package/dist/{reveal-component-DTBSuGkC.js → reveal-component-Ay-npdvI.js} +19 -19
- package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CHQvLnq4.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-C-LRKGtT.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-BOJZgbUJ.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-C53XXdYH.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-D2o1gOtw.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BHFzN1Wk.js} +2 -2
- package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
- package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
- package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-rv7QUa46.js} +1 -1
- package/dist/{toDate-vIXljnsr.js → toDate-CjPuEPou.js} +21 -20
- package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
- package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
- package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
- package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-ChgNtdmE.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Bet7kSsI.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-VVmz_24o.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-DympCsQg.js} +5 -5
- package/dist/{vega-component-DTG6zxaY.js → vega-component-DnfQeoyt.js} +147 -147
- package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
- package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
- package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-D36Zk7t-.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-BLBolvyQ.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CfH57l0v.js} +2 -2
- package/package.json +4 -4
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/components/app-config/ai-config.tsx +6 -2
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/chat-panel.tsx +3 -7
- package/src/components/chat/chat-utils.ts +4 -8
- package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
- package/src/components/data-table/charts/charts.tsx +14 -19
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +2 -1
- package/src/components/data-table/context-menu.tsx +36 -20
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +267 -163
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
- package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
- package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
- package/src/components/editor/cell/code/cell-editor.tsx +1 -1
- package/src/components/editor/cell/code/language-toggle.tsx +8 -7
- package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
- package/src/components/editor/connections/components.tsx +1 -1
- package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/dynamic-favicon.tsx +21 -40
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
- package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
- package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
- package/src/components/editor/file-tree/file-explorer.tsx +10 -5
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/file-tree/use-file-search.ts +35 -21
- package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
- package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
- package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
- package/src/components/icons/large-spinner.tsx +3 -14
- package/src/components/pages/home-page.tsx +4 -4
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/core/attachments/state.ts +5 -8
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/cells.ts +2 -1
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
- package/src/core/datasets/data-source-discovery.ts +4 -6
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +11 -2
- package/src/core/host/__tests__/attachments.test.ts +56 -0
- package/src/core/host/__tests__/document.test.ts +126 -0
- package/src/core/host/__tests__/events.test.ts +49 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +496 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +68 -0
- package/src/core/host/notebook-client.ts +316 -0
- package/src/core/host/resources/attachments.ts +19 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/network/connection.ts +4 -2
- package/src/core/network/useConnectionNotice.ts +8 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +5 -12
- package/src/core/runtime/runtime.ts +5 -5
- package/src/core/static/files.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 +149 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
- 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 +18 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/sse.ts +14 -2
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +78 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
- package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
- package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
- package/src/hooks/__tests__/useScript.test.tsx +77 -0
- package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useDebounce.ts +5 -8
- package/src/hooks/useDeepCompareMemoize.ts +1 -1
- package/src/hooks/useDelayElapsed.ts +14 -10
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useInternalStateWithSync.ts +6 -6
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/hooks/useScript.ts +67 -70
- package/src/hooks/useStateForKey.ts +93 -0
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/DataTablePlugin.tsx +14 -15
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/SliderPlugin.tsx +6 -8
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
- package/src/plugins/impl/anywidget/runtime.ts +21 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
- package/src/plugins/impl/chat/chat-ui.tsx +10 -9
- package/src/plugins/impl/code/any-language-editor.tsx +2 -2
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
- package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
- package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
- package/src/plugins/impl/panel/utils.ts +17 -0
- package/src/plugins/impl/vega/make-selectable.ts +3 -6
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/numbers.test.ts +48 -1
- package/src/utils/__tests__/objects.test.ts +21 -0
- package/src/utils/arrays.ts +10 -0
- package/src/utils/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/dist/any-language-editor-DZ_YTVus.js +0 -76
- package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
- package/dist/precisionRound-DMIVQrt2.js +0 -97
- package/src/core/host/__tests__/protocol.test.ts +0 -214
- package/src/core/host/protocol.ts +0 -1943
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -12,7 +12,7 @@ import { Logger } from "@/utils/Logger";
|
|
|
12
12
|
import type { Cell } from "@tanstack/react-table";
|
|
13
13
|
import { useAtomValue } from "jotai";
|
|
14
14
|
import { CopyIcon, FilterIcon, SquareStack } from "lucide-react";
|
|
15
|
-
import type
|
|
15
|
+
import { type RefObject, useRef, useState } from "react";
|
|
16
16
|
import useEvent from "react-use-event-hook";
|
|
17
17
|
import {
|
|
18
18
|
ContextMenu,
|
|
@@ -123,8 +123,21 @@ export const DataTableContextMenu = <TData,>({
|
|
|
123
123
|
tableRef: RefObject<HTMLTableSectionElement | null>;
|
|
124
124
|
copyAllCells: () => void;
|
|
125
125
|
}) => {
|
|
126
|
+
const [activeCell, setActiveCell] = useState<Cell<TData, unknown> | null>(
|
|
127
|
+
null,
|
|
128
|
+
);
|
|
129
|
+
const highlightedCell = useRef<Element | null>(null);
|
|
126
130
|
const handleContextMenuChange = useEvent((open: boolean) => {
|
|
131
|
+
highlightedCell.current?.classList.remove("bg-(--green-4)");
|
|
132
|
+
highlightedCell.current = null;
|
|
133
|
+
if (!open) {
|
|
134
|
+
// Forget the cell so a later right-click outside any cell can't reuse it
|
|
135
|
+
contextMenuRef.current = null;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
const cell = contextMenuRef.current;
|
|
140
|
+
setActiveCell(cell);
|
|
128
141
|
if (!cell) {
|
|
129
142
|
return;
|
|
130
143
|
}
|
|
@@ -138,42 +151,45 @@ export const DataTableContextMenu = <TData,>({
|
|
|
138
151
|
return;
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} else {
|
|
144
|
-
cellElement.classList.remove("bg-(--green-4)");
|
|
145
|
-
}
|
|
154
|
+
cellElement.classList.add("bg-(--green-4)");
|
|
155
|
+
highlightedCell.current = cellElement;
|
|
146
156
|
});
|
|
147
157
|
|
|
148
158
|
return (
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
// Open via the trigger so a right-click while already open retargets the
|
|
160
|
+
// menu; Radix only reports open changes, so use it for closing.
|
|
161
|
+
<ContextMenu
|
|
162
|
+
onOpenChange={(open) => {
|
|
163
|
+
if (!open) {
|
|
164
|
+
handleContextMenuChange(false);
|
|
165
|
+
}
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<ContextMenuTrigger
|
|
169
|
+
asChild={true}
|
|
170
|
+
onContextMenu={() => handleContextMenuChange(true)}
|
|
171
|
+
>
|
|
172
|
+
{tableBody}
|
|
173
|
+
</ContextMenuTrigger>
|
|
151
174
|
<ContextMenuPortal>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
/>
|
|
175
|
+
{activeCell && (
|
|
176
|
+
<CellContextMenu cell={activeCell} copySelectedCells={copyAllCells} />
|
|
177
|
+
)}
|
|
156
178
|
</ContextMenuPortal>
|
|
157
179
|
</ContextMenu>
|
|
158
180
|
);
|
|
159
181
|
};
|
|
160
182
|
|
|
161
183
|
export const CellContextMenu = <TData,>({
|
|
162
|
-
|
|
184
|
+
cell,
|
|
163
185
|
copySelectedCells,
|
|
164
186
|
}: {
|
|
165
|
-
|
|
187
|
+
cell: Cell<TData, unknown>;
|
|
166
188
|
copySelectedCells: () => void;
|
|
167
189
|
}) => {
|
|
168
190
|
const selectedCells = useAtomValue(selectedCellsAtom);
|
|
169
191
|
const multipleSelectedCells = selectedCells.size > 1;
|
|
170
192
|
|
|
171
|
-
const cell = cellRef.current;
|
|
172
|
-
if (!cell) {
|
|
173
|
-
Logger.error("No cell found in context menu");
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
193
|
const table = cell.getContext().table;
|
|
178
194
|
const displayedValue = cell.getValue();
|
|
179
195
|
const rawValue =
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
3
|
import { useDateFormatter, useLocale } from "react-aria";
|
|
4
4
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
5
5
|
|
|
@@ -45,6 +45,7 @@ export const DatePopover: React.FC<DatePopoverProps> = ({
|
|
|
45
45
|
|
|
46
46
|
const TimezoneDisplay = ({ date }: { date: Date }) => {
|
|
47
47
|
const { locale } = useLocale();
|
|
48
|
+
// The system timezone can change without the locale changing.
|
|
48
49
|
const localTimezone = new Intl.DateTimeFormat(locale).resolvedOptions()
|
|
49
50
|
.timeZone;
|
|
50
51
|
const hasSubSeconds = date.getUTCMilliseconds() !== 0;
|
|
@@ -116,9 +117,10 @@ const RelativeTime = ({ date }: { date: Date }) => {
|
|
|
116
117
|
const { locale } = useLocale();
|
|
117
118
|
|
|
118
119
|
// Initialize relative time formatter with current locale and "auto" numeric style
|
|
119
|
-
const relativeTimeFormatter =
|
|
120
|
-
numeric: "auto",
|
|
121
|
-
|
|
120
|
+
const relativeTimeFormatter = useMemo(
|
|
121
|
+
() => new Intl.RelativeTimeFormat(locale, { numeric: "auto" }),
|
|
122
|
+
[locale],
|
|
123
|
+
);
|
|
122
124
|
|
|
123
125
|
const currentTime = new Date();
|
|
124
126
|
// Get difference in seconds between now and the input date
|
|
@@ -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,8 @@ import type {
|
|
|
22
23
|
ExportMetadata,
|
|
23
24
|
GetExportMetadata,
|
|
24
25
|
} from "./schemas";
|
|
26
|
+
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
27
|
+
import { useStateForKey } from "@/hooks/useStateForKey";
|
|
25
28
|
import { logNever } from "@/utils/assertNever";
|
|
26
29
|
import { cn } from "@/utils/cn";
|
|
27
30
|
import { copyToClipboard } from "@/utils/copy";
|
|
@@ -274,19 +277,133 @@ const failureTitleForAction = (action: ExportAction): string =>
|
|
|
274
277
|
const failureDescription = (error: unknown): string =>
|
|
275
278
|
typeof error === "string" ? error : prettyError(error);
|
|
276
279
|
|
|
280
|
+
const OBSOLETE_GEOMETRY_MESSAGE =
|
|
281
|
+
"The selected geometry is no longer available. Choose another geometry to export.";
|
|
282
|
+
|
|
283
|
+
type GeographicBlocker =
|
|
284
|
+
| "format-unavailable"
|
|
285
|
+
| "geometry-required"
|
|
286
|
+
| "choose-geometry"
|
|
287
|
+
| "obsolete-geometry"
|
|
288
|
+
| "missing-crs";
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Find the first reason that a geographic export cannot start with the
|
|
292
|
+
* selected geometry. The dialog uses it to disable a format, and the retry
|
|
293
|
+
* after a package installation uses it to decide whether to export.
|
|
294
|
+
*/
|
|
295
|
+
function geographicBlocker(
|
|
296
|
+
metadata: ExportMetadata,
|
|
297
|
+
format: GeographicFormat,
|
|
298
|
+
geometryColumn: string | null,
|
|
299
|
+
): GeographicBlocker | null {
|
|
300
|
+
if (metadata.formats[format]?.available === false) {
|
|
301
|
+
return "format-unavailable";
|
|
302
|
+
}
|
|
303
|
+
const selected = metadata.geometry_columns.find(
|
|
304
|
+
(column) => column.name === geometryColumn,
|
|
305
|
+
);
|
|
306
|
+
if (geometryColumn !== null && !selected) {
|
|
307
|
+
return "obsolete-geometry";
|
|
308
|
+
}
|
|
309
|
+
if (metadata.geometry_columns.length === 0) {
|
|
310
|
+
return format === "geojson" ? "geometry-required" : null;
|
|
311
|
+
}
|
|
312
|
+
if (!selected) {
|
|
313
|
+
return "choose-geometry";
|
|
314
|
+
}
|
|
315
|
+
if (format === "geojson" && !selected.crs) {
|
|
316
|
+
return "missing-crs";
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const geometryDisplayName = (name: string | null): string | null =>
|
|
322
|
+
name === "" ? "(unnamed geometry)" : name;
|
|
323
|
+
|
|
324
|
+
function geographicBlockerReason(
|
|
325
|
+
blocker: GeographicBlocker | null,
|
|
326
|
+
formatLabel: string,
|
|
327
|
+
geometryColumn: string | null,
|
|
328
|
+
): string | null {
|
|
329
|
+
switch (blocker) {
|
|
330
|
+
case "choose-geometry":
|
|
331
|
+
return `Choose a primary geometry column to export ${formatLabel}.`;
|
|
332
|
+
case "obsolete-geometry":
|
|
333
|
+
return OBSOLETE_GEOMETRY_MESSAGE;
|
|
334
|
+
case "missing-crs":
|
|
335
|
+
return `Geometry column ${geometryDisplayName(geometryColumn)} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`;
|
|
336
|
+
case "format-unavailable":
|
|
337
|
+
case "geometry-required":
|
|
338
|
+
case null:
|
|
339
|
+
return null;
|
|
340
|
+
default:
|
|
341
|
+
logNever(blocker);
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* One open dialog for one table source. Closing the dialog or replacing the
|
|
348
|
+
* source creates a new context, and work started under an older context must
|
|
349
|
+
* not change the dialog or start an export.
|
|
350
|
+
*/
|
|
351
|
+
interface ExportContext {
|
|
352
|
+
source: unknown;
|
|
353
|
+
}
|
|
354
|
+
|
|
277
355
|
export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
278
356
|
const { getExportMetadata, metadataSource } = props;
|
|
279
357
|
const [exportDialogOpen, setExportDialogOpen] = React.useState(false);
|
|
280
|
-
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 [failure, setFailure] = useStateForKey<ExportFailure | null>(
|
|
367
|
+
exportContext,
|
|
368
|
+
() => null,
|
|
369
|
+
);
|
|
370
|
+
const mounted = React.useRef(false);
|
|
371
|
+
const isCurrentContext = useEvent(
|
|
372
|
+
(context: ExportContext) => mounted.current && context === exportContext,
|
|
373
|
+
);
|
|
374
|
+
const metadataRequest = useAsyncData(async () => {
|
|
375
|
+
if (!exportDialogOpen || !getExportMetadata) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return { context: exportContext, metadata: await getExportMetadata({}) };
|
|
379
|
+
}, [exportDialogOpen, getExportMetadata, exportContext]);
|
|
380
|
+
// The hook keeps earlier data while it reloads and after a failure. Only a
|
|
381
|
+
// completed response for the current context can enable an export.
|
|
382
|
+
const metadata =
|
|
383
|
+
metadataRequest.status === "success" &&
|
|
384
|
+
metadataRequest.data?.context === exportContext
|
|
385
|
+
? metadataRequest.data.metadata
|
|
386
|
+
: null;
|
|
387
|
+
const metadataError =
|
|
388
|
+
metadataRequest.status === "error"
|
|
389
|
+
? failureDescription(metadataRequest.error)
|
|
390
|
+
: null;
|
|
287
391
|
const [geometryColumn, setGeometryColumn] = React.useState<string | null>(
|
|
288
392
|
null,
|
|
289
393
|
);
|
|
394
|
+
const [reconciledMetadata, setReconciledMetadata] =
|
|
395
|
+
React.useState<ExportMetadata | null>(null);
|
|
396
|
+
if (metadata !== reconciledMetadata) {
|
|
397
|
+
setReconciledMetadata(metadata);
|
|
398
|
+
if (metadata) {
|
|
399
|
+
setGeometryColumn((current) =>
|
|
400
|
+
current !== null &&
|
|
401
|
+
metadata.geometry_columns.some((column) => column.name === current)
|
|
402
|
+
? current
|
|
403
|
+
: metadata.default_geometry_column,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
290
407
|
const hasGeometry = !!metadata?.geometry_columns.length;
|
|
291
408
|
const [expandedFormat, setExpandedFormat] = React.useState<
|
|
292
409
|
ConfigurableFormat | GeographicFormat | null
|
|
@@ -294,43 +411,16 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
294
411
|
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
|
295
412
|
const dialogRef = React.useRef<HTMLDivElement>(null);
|
|
296
413
|
const latestActionId = React.useRef(0);
|
|
414
|
+
React.useLayoutEffect(() => {
|
|
415
|
+
mounted.current = true;
|
|
416
|
+
return () => {
|
|
417
|
+
mounted.current = false;
|
|
418
|
+
latestActionId.current += 1;
|
|
419
|
+
};
|
|
420
|
+
}, []);
|
|
297
421
|
React.useEffect(() => {
|
|
298
422
|
latestActionId.current += 1;
|
|
299
423
|
}, [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
424
|
const policy = useAtomValue(downloadSizeLimitAtom);
|
|
335
425
|
const overLimit = !!(
|
|
336
426
|
policy &&
|
|
@@ -365,12 +455,10 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
365
455
|
setExportDialogOpen(open);
|
|
366
456
|
if (!open) {
|
|
367
457
|
latestActionId.current += 1;
|
|
368
|
-
setFailure(null);
|
|
369
458
|
setExpandedFormat(null);
|
|
370
459
|
setSettings(DEFAULT_SETTINGS);
|
|
371
|
-
setMetadata(null);
|
|
372
|
-
setMetadataError(null);
|
|
373
460
|
setGeometryColumn(null);
|
|
461
|
+
setExportContext({ source: metadataSource });
|
|
374
462
|
}
|
|
375
463
|
};
|
|
376
464
|
|
|
@@ -553,57 +641,67 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
553
641
|
}
|
|
554
642
|
};
|
|
555
643
|
|
|
556
|
-
|
|
557
|
-
|
|
644
|
+
// Package installation can finish after the dialog closes or the source
|
|
645
|
+
// changes, so the retry carries the context in which installation began.
|
|
646
|
+
const retryAfterInstall = useEvent(
|
|
647
|
+
async (action: ExportAction, context: ExportContext) => {
|
|
648
|
+
if (!isCurrentContext(context)) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (!isGeographic(action.format) || !getExportMetadata) {
|
|
652
|
+
retryAction(action);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
558
655
|
const actionId = latestActionId.current;
|
|
656
|
+
const isCurrent = () =>
|
|
657
|
+
isCurrentContext(context) && actionId === latestActionId.current;
|
|
559
658
|
let result: ExportMetadata;
|
|
560
659
|
try {
|
|
561
660
|
result = await getExportMetadata({});
|
|
562
|
-
} catch
|
|
563
|
-
if (
|
|
564
|
-
|
|
661
|
+
} catch {
|
|
662
|
+
if (isCurrent()) {
|
|
663
|
+
metadataRequest.refetch();
|
|
565
664
|
}
|
|
566
665
|
return;
|
|
567
666
|
}
|
|
568
|
-
if (
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
setMetadata(result);
|
|
572
|
-
setMetadataError(null);
|
|
573
|
-
if (result.formats[action.format]?.available === false) {
|
|
574
|
-
setFailure(null);
|
|
667
|
+
if (!isCurrent()) {
|
|
575
668
|
return;
|
|
576
669
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return;
|
|
670
|
+
// Validation reads its own response, so a display refresh cannot
|
|
671
|
+
// start an export that this check rejected.
|
|
672
|
+
metadataRequest.refetch();
|
|
673
|
+
const blocker = geographicBlocker(result, action.format, geometryColumn);
|
|
674
|
+
switch (blocker) {
|
|
675
|
+
case null:
|
|
676
|
+
retryAction(action);
|
|
677
|
+
return;
|
|
678
|
+
case "obsolete-geometry":
|
|
679
|
+
setGeometryColumn(result.default_geometry_column);
|
|
680
|
+
setActionFailure(actionId, {
|
|
681
|
+
kind: "error",
|
|
682
|
+
title: failureTitleForAction(action),
|
|
683
|
+
description: OBSOLETE_GEOMETRY_MESSAGE,
|
|
684
|
+
action,
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
case "choose-geometry":
|
|
688
|
+
return;
|
|
689
|
+
case "format-unavailable":
|
|
690
|
+
case "geometry-required":
|
|
691
|
+
case "missing-crs":
|
|
692
|
+
setFailure(null);
|
|
693
|
+
return;
|
|
694
|
+
default:
|
|
695
|
+
logNever(blocker);
|
|
604
696
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
697
|
+
},
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
const createInstallRetry = (action: ExportAction) => {
|
|
701
|
+
const context = exportContext;
|
|
702
|
+
return () => {
|
|
703
|
+
void retryAfterInstall(action, context);
|
|
704
|
+
};
|
|
607
705
|
};
|
|
608
706
|
|
|
609
707
|
const toggleExpanded = (format: ExportFormat) => {
|
|
@@ -665,14 +763,15 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
665
763
|
const expanded =
|
|
666
764
|
configurableFormat !== null &&
|
|
667
765
|
expandedFormat === configurableFormat;
|
|
766
|
+
const FormatHeader = configurableFormat ? "button" : "div";
|
|
668
767
|
const panelId = `export-options-${option.format}`;
|
|
768
|
+
const descriptionId = `${panelId}-description`;
|
|
769
|
+
const summaryId = `${panelId}-summary`;
|
|
669
770
|
const rowFailure =
|
|
670
771
|
failure?.action.format === option.format ? failure : null;
|
|
671
772
|
const summary =
|
|
672
773
|
isGeographic(option.format) && hasGeometry
|
|
673
|
-
? geometryColumn
|
|
674
|
-
? "(unnamed geometry)"
|
|
675
|
-
: (geometryColumn ?? "Choose geometry")
|
|
774
|
+
? (geometryDisplayName(geometryColumn) ?? "Choose geometry")
|
|
676
775
|
: settingsSummary(settings, option.format);
|
|
677
776
|
const selectedGeometryIndex =
|
|
678
777
|
metadata?.geometry_columns.findIndex(
|
|
@@ -681,20 +780,14 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
681
780
|
const eligibility = isGeographic(option.format)
|
|
682
781
|
? metadata?.formats[option.format]
|
|
683
782
|
: undefined;
|
|
684
|
-
const
|
|
685
|
-
metadata
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
selectedGeometry !== undefined &&
|
|
689
|
-
!selectedGeometry.crs;
|
|
783
|
+
const blocker =
|
|
784
|
+
isGeographic(option.format) && metadata
|
|
785
|
+
? geographicBlocker(metadata, option.format, geometryColumn)
|
|
786
|
+
: null;
|
|
690
787
|
const geometryReason = metadataError
|
|
691
788
|
? `Could not load geometry options: ${metadataError}`
|
|
692
789
|
: (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));
|
|
790
|
+
geographicBlockerReason(blocker, optionLabel, geometryColumn));
|
|
698
791
|
const missingPackageFailure =
|
|
699
792
|
rowFailure?.kind === "missing-packages" ? rowFailure : null;
|
|
700
793
|
const missingPackages =
|
|
@@ -704,13 +797,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
704
797
|
: []);
|
|
705
798
|
const geographicUnavailable =
|
|
706
799
|
isGeographic(option.format) &&
|
|
707
|
-
((!!getExportMetadata && !metadata) ||
|
|
708
|
-
metadataLoading ||
|
|
709
|
-
!!metadataError ||
|
|
710
|
-
eligibility?.available === false ||
|
|
711
|
-
missingCrs ||
|
|
712
|
-
(!!metadata?.geometry_columns.length &&
|
|
713
|
-
geometryColumn === null));
|
|
800
|
+
((!!getExportMetadata && !metadata) || blocker !== null);
|
|
714
801
|
const copyUnavailable =
|
|
715
802
|
isGeographic(option.format) &&
|
|
716
803
|
(geographicUnavailable || missingPackages.length > 0);
|
|
@@ -720,55 +807,78 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
720
807
|
data-testid={`export-row-${option.format}`}
|
|
721
808
|
className={cn(expanded && "bg-muted/40")}
|
|
722
809
|
>
|
|
723
|
-
<div className="grid
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
810
|
+
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center hover:bg-accent/50">
|
|
811
|
+
<FormatHeader
|
|
812
|
+
type={configurableFormat ? "button" : undefined}
|
|
813
|
+
className={cn(
|
|
814
|
+
"grid min-h-[60px] min-w-0 grid-cols-[28px_minmax(0,1fr)] items-center gap-x-1 py-2 pl-2.5 pr-1 text-left",
|
|
815
|
+
configurableFormat &&
|
|
816
|
+
"cursor-pointer focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
|
|
817
|
+
)}
|
|
818
|
+
aria-label={
|
|
819
|
+
configurableFormat ? `${optionLabel} options` : undefined
|
|
820
|
+
}
|
|
821
|
+
aria-describedby={
|
|
822
|
+
configurableFormat
|
|
823
|
+
? [descriptionId, summary && summaryId]
|
|
824
|
+
.filter(Boolean)
|
|
825
|
+
.join(" ")
|
|
826
|
+
: undefined
|
|
827
|
+
}
|
|
828
|
+
aria-expanded={configurableFormat ? expanded : undefined}
|
|
829
|
+
aria-controls={configurableFormat ? panelId : undefined}
|
|
830
|
+
onClick={
|
|
831
|
+
configurableFormat
|
|
832
|
+
? () => toggleExpanded(option.format)
|
|
833
|
+
: undefined
|
|
834
|
+
}
|
|
835
|
+
>
|
|
836
|
+
<span
|
|
837
|
+
aria-hidden={true}
|
|
838
|
+
className="inline-flex h-6 w-6 items-center justify-center"
|
|
733
839
|
>
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
840
|
+
{configurableFormat && (
|
|
841
|
+
<ChevronRightIcon
|
|
842
|
+
className={cn(
|
|
843
|
+
"h-4 w-4 text-muted-foreground transition-transform",
|
|
844
|
+
expanded && "rotate-90",
|
|
845
|
+
)}
|
|
846
|
+
/>
|
|
847
|
+
)}
|
|
848
|
+
</span>
|
|
849
|
+
<span className="flex min-w-0 items-center gap-3 pl-1">
|
|
850
|
+
<option.icon
|
|
851
|
+
aria-hidden={true}
|
|
852
|
+
data-testid={`export-format-icon-${option.format}`}
|
|
853
|
+
className="h-4 w-4 shrink-0 text-muted-foreground"
|
|
739
854
|
/>
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
<div className="flex min-w-0 items-center gap-3 pl-1">
|
|
745
|
-
<option.icon
|
|
746
|
-
aria-hidden={true}
|
|
747
|
-
data-testid={`export-format-icon-${option.format}`}
|
|
748
|
-
className="h-4 w-4 shrink-0 text-muted-foreground"
|
|
749
|
-
/>
|
|
750
|
-
<div className="grid min-w-0 gap-1">
|
|
751
|
-
<span className="flex min-w-0 items-baseline gap-x-2">
|
|
752
|
-
<span className="text-sm font-medium leading-tight">
|
|
753
|
-
{optionLabel}
|
|
754
|
-
</span>
|
|
755
|
-
{summary && (
|
|
756
|
-
<span
|
|
757
|
-
data-testid={`export-summary-${option.format}`}
|
|
758
|
-
className="min-w-0 truncate text-xs text-muted-foreground"
|
|
759
|
-
>
|
|
760
|
-
{summary}
|
|
855
|
+
<span className="grid min-w-0 gap-1">
|
|
856
|
+
<span className="flex min-w-0 items-baseline gap-x-2">
|
|
857
|
+
<span className="text-sm font-medium leading-tight">
|
|
858
|
+
{optionLabel}
|
|
761
859
|
</span>
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
860
|
+
{summary && (
|
|
861
|
+
<span
|
|
862
|
+
id={summaryId}
|
|
863
|
+
data-testid={`export-summary-${option.format}`}
|
|
864
|
+
className="min-w-0 truncate text-xs text-muted-foreground"
|
|
865
|
+
>
|
|
866
|
+
{summary}
|
|
867
|
+
</span>
|
|
868
|
+
)}
|
|
869
|
+
</span>
|
|
870
|
+
<span
|
|
871
|
+
id={descriptionId}
|
|
872
|
+
className="text-xs text-muted-foreground leading-tight"
|
|
873
|
+
>
|
|
874
|
+
{option.format === "parquet" && hasGeometry
|
|
875
|
+
? "Parquet with geometry and CRS metadata"
|
|
876
|
+
: option.description}
|
|
877
|
+
</span>
|
|
768
878
|
</span>
|
|
769
|
-
</
|
|
770
|
-
</
|
|
771
|
-
<div className="flex items-center gap-0.5">
|
|
879
|
+
</span>
|
|
880
|
+
</FormatHeader>
|
|
881
|
+
<div className="flex items-center gap-0.5 pr-2.5">
|
|
772
882
|
<Tooltip
|
|
773
883
|
content={
|
|
774
884
|
option.canDownload
|
|
@@ -888,9 +998,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
888
998
|
type="button"
|
|
889
999
|
size="xs"
|
|
890
1000
|
variant="outline"
|
|
891
|
-
onClick={
|
|
892
|
-
setMetadataRefresh((current) => current + 1)
|
|
893
|
-
}
|
|
1001
|
+
onClick={metadataRequest.refetch}
|
|
894
1002
|
>
|
|
895
1003
|
Retry
|
|
896
1004
|
</Button>
|
|
@@ -906,17 +1014,13 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
|
|
|
906
1014
|
missingPackageFailure?.description ??
|
|
907
1015
|
eligibility?.reason
|
|
908
1016
|
}
|
|
909
|
-
onInstall={(
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
format:
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
: "parquet",
|
|
917
|
-
},
|
|
918
|
-
);
|
|
919
|
-
}}
|
|
1017
|
+
onInstall={createInstallRetry(
|
|
1018
|
+
missingPackageFailure?.action ?? {
|
|
1019
|
+
destination: "download",
|
|
1020
|
+
format:
|
|
1021
|
+
option.format === "geojson" ? "geojson" : "parquet",
|
|
1022
|
+
},
|
|
1023
|
+
)}
|
|
920
1024
|
className="items-start"
|
|
921
1025
|
/>
|
|
922
1026
|
</div>
|