@marimo-team/frontend 0.24.1-dev99 → 0.24.2-dev129
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-wvvZkEPY.js → CellStatus-LoTwk7fR.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-Dkqk66Mv.js → ConnectedDataExplorerComponent-DW6cJ-Lr.js} +1 -1
- package/dist/assets/{ErrorBoundary-npvnLp9x.js → ErrorBoundary-CDVGFYfr.js} +1 -1
- package/dist/assets/JsonOutput-CzIGPEZQ.js +50 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-zSwE3dvU.js → LazyAnyLanguageCodeMirror-LboTfNDv.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-CeUo5oka.js → MarimoErrorOutput-DqpoYOvw.js} +1 -1
- package/dist/assets/RenderHTML-DwhMAd9l.js +1 -0
- package/dist/assets/{RunButton-BaV8A-z4.js → RunButton-BlpbweyH.js} +1 -1
- package/dist/assets/{VisuallyHidden-SChAyKIQ.js → VisuallyHidden-CBMDmozt.js} +1 -1
- package/dist/assets/{add-cell-with-ai-DoLkfzYn.js → add-cell-with-ai-R-3DS79K.js} +1 -1
- package/dist/assets/{add-connection-dialog-C1IF1nR0.js → add-connection-dialog-BGfpw5lC.js} +1 -1
- package/dist/assets/{agent-panel-CG154QrU.js → agent-panel-Cgt_K0tD.js} +1 -1
- package/dist/assets/ai-model-dropdown-DCmiuk7x.js +3 -0
- package/dist/assets/alert-dialog-CA-2mBs3.js +11 -0
- package/dist/assets/any-language-editor-DflLjPSQ.js +1 -0
- package/dist/assets/{app-config-button-ktBH1pEB.js → app-config-button-DUJC6b6x.js} +1 -1
- package/dist/assets/badge-BSgtkC2e.js +1 -0
- package/dist/assets/{blob-DVtvZmWH.js → blob-B2d_dHwI.js} +1 -1
- package/dist/assets/button-Dh8EwhsF.js +1 -0
- package/dist/assets/{cache-panel-C7jsiqCa.js → cache-panel-DqgtC98s.js} +1 -1
- package/dist/assets/{capabilities-LWzb3E3M.js → capabilities-D8kPgMKr.js} +1 -1
- package/dist/assets/{cell-editor-DoDqaCZp.js → cell-editor-Dw09J9yD.js} +6 -6
- package/dist/assets/{cell-link-Dqqt6Rgs.js → cell-link-6burT2XF.js} +1 -1
- package/dist/assets/{cells-D4RwofRx.js → cells-Cp-G34Rz.js} +56 -56
- package/dist/assets/{chat-display-COmzHgx5.js → chat-display-Bu405iyc.js} +1 -1
- package/dist/assets/{chat-panel-B2tyEfGW.js → chat-panel-BlUwNY5j.js} +1 -1
- package/dist/assets/{chat-ui-B5bsr-7V.js → chat-ui-DGiVDzNh.js} +1 -1
- package/dist/assets/{circle--ysFeW9z.js → circle-ByJdIf5p.js} +1 -1
- package/dist/assets/{column-preview-CW5DPFvy.js → column-preview-riSXbYe_.js} +1 -1
- package/dist/assets/{command-palette-B9dyvMvJ.js → command-palette-CCVzJcyf.js} +1 -1
- package/dist/assets/{common-CC3tuCpi.js → common-CHR7w7aQ.js} +1 -1
- package/dist/assets/{components-D02ZZZMP.js → components-C7mWrmXr.js} +1 -1
- package/dist/assets/{components-CBlGIIAX.js → components-DUYCXngU.js} +1 -1
- package/dist/assets/config-B-r81uZ6.js +1 -0
- package/dist/assets/{config-D4frVGQ4.js → config-BlNM0caL.js} +1 -1
- package/dist/assets/{copy-Cg05mM3C.js → copy-DlEomWzI.js} +1 -1
- package/dist/assets/{copy-icon-CRxVZTJ9.js → copy-icon-z8fuTE3w.js} +1 -1
- package/dist/assets/{createReducer-Bq-nQJiK.js → createReducer-B_XmHROl.js} +1 -1
- package/dist/assets/{datasource-Do8f2hFf.js → datasource-DrODSIs0.js} +1 -1
- package/dist/assets/{dates-DK5JikJo.js → dates-B58T85Jq.js} +1 -1
- package/dist/assets/dependency-graph-panel-AY5BEVH2.js +5 -0
- package/dist/assets/{dist-7qdHLg-L.js → dist-B0RXffUm.js} +1 -1
- package/dist/assets/dist-B0qYy1Ye.js +1 -0
- package/dist/assets/{dist-B97dKQGP.js → dist-B8pdsUrS.js} +1 -1
- package/dist/assets/{dist-BcfF-rkl.js → dist-BF7Lrekv.js} +1 -1
- package/dist/assets/dist-BL8RjfvI.js +1 -0
- package/dist/assets/dist-BVZxge7j.js +1 -0
- package/dist/assets/{dist-fxnYzq-R.js → dist-BaRNSaca.js} +1 -1
- package/dist/assets/dist-Bk2yQtw4.js +1 -0
- package/dist/assets/dist-BsfiiKoy.js +1 -0
- package/dist/assets/dist-BskmVd3B.js +1 -0
- package/dist/assets/{dist-DL1Thauf.js → dist-Bsu3h9O7.js} +1 -1
- package/dist/assets/{dist-PIe1xcZo.js → dist-BtC9ym6k.js} +7 -7
- package/dist/assets/{dist-DeuWxYVk.js → dist-Bv0S1RlZ.js} +1 -1
- package/dist/assets/{dist-CidBzUqR.js → dist-C--ZICxQ.js} +1 -1
- package/dist/assets/dist-C-2fD3XY.js +1 -0
- package/dist/assets/{dist-DzAWvPVK.js → dist-CKUpxYGQ.js} +1 -1
- package/dist/assets/dist-CMFMfBm1.js +1 -0
- package/dist/assets/dist-CZNULd6Z.js +2 -0
- package/dist/assets/dist-CjFduPiC.js +1 -0
- package/dist/assets/{dist-DoAjHgMj.js → dist-CmImLjus.js} +1 -1
- package/dist/assets/{dist-BKx0MLbb.js → dist-Cq8NOszX.js} +1 -1
- package/dist/assets/{dist-DCc0VfiO.js → dist-Cx4BE7jC.js} +3 -3
- package/dist/assets/dist-D45ScydS.js +1 -0
- package/dist/assets/dist-D6dg87r7.js +1 -0
- package/dist/assets/dist-DEjfQLz0.js +1 -0
- package/dist/assets/{dist-BcYW-EJk.js → dist-DJOWOALB.js} +1 -1
- package/dist/assets/{dist-CxxZaSme.js → dist-DPoYlxSC.js} +1 -1
- package/dist/assets/dist-DR-Y-nl3.js +1 -0
- package/dist/assets/dist-Dahlp9JQ.js +1 -0
- package/dist/assets/{dist-DIkuvChK.js → dist-Db-HC93w.js} +1 -1
- package/dist/assets/{dist-DTM7BAUu.js → dist-DdOlZ6mj.js} +1 -1
- package/dist/assets/{dist-Dz_04liz.js → dist-DiYjQAhI.js} +1 -1
- package/dist/assets/{dist-BO5c5n_b.js → dist-DkPPv0FB.js} +1 -1
- package/dist/assets/{dist-DkCO0b-0.js → dist-DmGtPaVM.js} +1 -1
- package/dist/assets/{dist-CXmhxecl.js → dist-DnxYYyU7.js} +1 -1
- package/dist/assets/{dist-CO1Hfv5H.js → dist-Dw9YRsCU.js} +1 -1
- package/dist/assets/dist-Dwx3KdUP.js +1 -0
- package/dist/assets/{dist-BgL43w5r.js → dist-DzCQtGkt.js} +1 -1
- package/dist/assets/dist-JweaDt6P.js +1 -0
- package/dist/assets/{dist-Dbu5sPgR.js → dist-gELFCfHH.js} +1 -1
- package/dist/assets/dist-kGDJ0AWF.js +1 -0
- package/dist/assets/{dist-Cqm1W-Zm.js → dist-kaLvV_d4.js} +1 -1
- package/dist/assets/{dist-CEeRVpMD.js → dist-qvk3g06H.js} +1 -1
- package/dist/assets/dist-wCw2twuB.js +1 -0
- package/dist/assets/dist-wF42tUcr.js +1 -0
- package/dist/assets/dist-wY2yia-1.js +3 -0
- package/dist/assets/{documentation-panel-DvHjxlli.js → documentation-panel-CaJF7e-j.js} +1 -1
- package/dist/assets/{download-BP8MrprC.js → download-DMxOzI7b.js} +1 -1
- package/dist/assets/dropdown-menu-C40jM9o4.js +1 -0
- package/dist/assets/{edit-page-71cK-Nhe.js → edit-page-Vqd9NedM.js} +6 -6
- package/dist/assets/{error-banner-BYN8YUMe.js → error-banner-Dw5YY9wq.js} +1 -1
- package/dist/assets/{error-entries-DOZ1u97X.js → error-entries-Cv2m1yhc.js} +1 -1
- package/dist/assets/{error-panel-CINcf76Z.js → error-panel-Bhlvt6_H.js} +1 -1
- package/dist/assets/{es-BuMFDpR4.js → es-CrFhA96G.js} +1 -1
- package/dist/assets/{esm-Dw0vvSg2.js → esm-BHMOvJzM.js} +1 -1
- package/dist/assets/{esm-BGpfQNhD.js → esm-DmqQGMel.js} +1 -1
- package/dist/assets/file-explorer-panel-6HvNfCrJ.js +1 -0
- package/dist/assets/{file-icons-DgKj9mrV.js → file-icons-DdGYuADJ.js} +1 -1
- package/dist/assets/file-name-input-K4MKESI_.js +3 -0
- package/dist/assets/{floating-outline-CRXIsrPm.js → floating-outline-DaUHnl_e.js} +1 -1
- package/dist/assets/{formats-DSzGCKeO.js → formats-C6s96nu2.js} +1 -1
- package/dist/assets/{formatting-BJlT1s_3.js → formatting-BWZH2nBg.js} +1 -1
- package/dist/assets/{fullscreen-0EEPR7uc.js → fullscreen-BH2jz-F4.js} +5 -5
- package/dist/assets/{gallery-page-TqnFYECu.js → gallery-page-C3pBpHlY.js} +1 -1
- package/dist/assets/{glide-data-editor-DcY3nXI8.js → glide-data-editor-sICUMEhk.js} +1 -1
- package/dist/assets/{glide-utils-CR1sWLZU.js → glide-utils-UGsrRHYj.js} +1 -1
- package/dist/assets/home-page-BLd6rnbk.js +4 -0
- package/dist/assets/{html-to-image-DMn_7CCA.js → html-to-image-D6GUC5q6.js} +1 -1
- package/dist/assets/index-BGT7have.css +2 -0
- package/dist/assets/index-ipFYAeCF.js +29 -0
- package/dist/assets/{input-Daz6sk8l.js → input-DEbY281O.js} +2 -2
- package/dist/assets/{kiosk-mode-CzGt3mc2.js → kiosk-mode-mxgb5lWf.js} +1 -1
- package/dist/assets/{layout-KJMQADEj.js → layout-DWnrxIo2.js} +5 -5
- package/dist/assets/links-DLvOXyO_.js +1 -0
- package/dist/assets/{logs-panel-DFibPeCT.js → logs-panel-BGS2v3ba.js} +1 -1
- package/dist/assets/lua-1tNpM92n.js +1 -0
- package/dist/assets/{lua-CRIS5afH.js → lua-DXBxhmso.js} +1 -1
- package/dist/assets/{markdown-renderer-CdZX2MvM.js → markdown-renderer-BZKlbm2Q.js} +1 -1
- package/dist/assets/menu-items-BSRA2YI9.js +1 -0
- package/dist/assets/{mermaid-CeseGGnS.js → mermaid-l6lQkCib.js} +1 -1
- package/dist/assets/{name-cell-input-B-d5Jwh4.js → name-cell-input-BMCdK-el.js} +1 -1
- package/dist/assets/{numbers-DKurV_vq.js → numbers-Bc-r3jsC.js} +1 -1
- package/dist/assets/{outline-panel-D2Wk10hY.js → outline-panel-Dr5u73V6.js} +1 -1
- package/dist/assets/{packages-panel-CQsbxIlG.js → packages-panel-BdpRg9tX.js} +1 -1
- package/dist/assets/{pair-with-agent-modal-CNxmIMFM.js → pair-with-agent-modal-CbUFKJHP.js} +1 -1
- package/dist/assets/{panel-accordion-state-D6-FMXE2.js → panel-accordion-state-DjUTN7i5.js} +1 -1
- package/dist/assets/{panels-BgcK_4M7.js → panels-CWn0Yh1D.js} +1 -1
- package/dist/assets/process-output-Ck7-P_xp.js +1 -0
- package/dist/assets/{readonly-code-display-BvqLeUsJ.js → readonly-code-display--S2daDa6.js} +1 -1
- package/dist/assets/{readonly-python-code-GSN4D4sb.js → readonly-python-code-DpIyjHqY.js} +1 -1
- package/dist/assets/{renderShortcut-DK_OucLT.js → renderShortcut-X-CJUSM8.js} +1 -1
- package/dist/assets/{reveal-component-u9ED2diI.js → reveal-component-nZIAapoD.js} +1 -1
- package/dist/assets/{run-page-U5iuhyz8.js → run-page-9pl8qhQR.js} +1 -1
- package/dist/assets/{runs-BQTlv6Ja.js → runs-euHSzlw_.js} +1 -1
- package/dist/assets/{scratchpad-panel--YOR0kUB.js → scratchpad-panel-BleGt2KM.js} +1 -1
- package/dist/assets/{scroll-to-bottom-button-xua0e9rA.js → scroll-to-bottom-button-DufiYUD7.js} +1 -1
- package/dist/assets/{secrets-panel-CZPbFAZH.js → secrets-panel-D3BpDIgg.js} +1 -1
- package/dist/assets/select-nLt2y0Sw.js +1 -0
- package/dist/assets/{session-CEdta5_Q.js → session-Sz5nSFku.js} +1 -1
- package/dist/assets/session-panel-DRGPlL5J.js +1 -0
- package/dist/assets/{share-C2MPy_VX.js → share-CPtBiNgr.js} +1 -1
- package/dist/assets/{skeleton-CsceCAjw.js → skeleton-BKeh8hf3.js} +1 -1
- package/dist/assets/{snippets-panel-BxwHsPmW.js → snippets-panel-D5yy7kj0.js} +1 -1
- package/dist/assets/{spec-CUFquO6R.js → spec-DKsAmLTi.js} +1 -1
- package/dist/assets/{state-BqrF--O0.js → state-B6AklCzD.js} +1 -1
- package/dist/assets/{state-C2c3_VuY.js → state-B8XRqyFg.js} +1 -1
- package/dist/assets/{state-CHplO0zn.js → state-Di9TbuV9.js} +1 -1
- package/dist/assets/{strings-CpYReX2Q.js → strings-CReV1AYG.js} +1 -1
- package/dist/assets/{swiper-component-Bot7ehYH.js → swiper-component-5Kdi1-Gn.js} +1 -1
- package/dist/assets/{switch-CtH5Ct94.js → switch-DjXr9Y9D.js} +1 -1
- package/dist/assets/{tabs-c6mpccpH.js → tabs-Cy1qXLzH.js} +1 -1
- package/dist/assets/{terminal-C73iz1fU.js → terminal-CQAFGBfN.js} +1 -1
- package/dist/assets/{tooltip-Cwltdez8.js → tooltip-BG35uS_N.js} +1 -1
- package/dist/assets/{tooltip-D7dq6bnc.js → tooltip-BUtPzKB4.js} +1 -1
- package/dist/assets/{tracing-Dhq121dR.js → tracing-DNk3gmWv.js} +1 -1
- package/dist/assets/{tracing-panel-IKt1933_.js → tracing-panel-Yqazr6w_.js} +2 -2
- package/dist/assets/{tree-actions-DIfWonCA.js → tree-actions-CG5g7aUQ.js} +1 -1
- package/dist/assets/{useAddCell-BYrVaYfZ.js → useAddCell-DdB_uBif.js} +1 -1
- package/dist/assets/{useBoolean-ZU5StAbr.js → useBoolean-BXC4CItZ.js} +1 -1
- package/dist/assets/{useCellActionButton-DbXDs0MA.js → useCellActionButton-B31neI3m.js} +1 -1
- package/dist/assets/{useDebounce-Dulm61-t.js → useDebounce-DgpSQhrP.js} +1 -1
- package/dist/assets/{useDeleteCell-Wgcl3CRG.js → useDeleteCell-Bz49_cFj.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-C5AU8GKL.js → useDependencyPanelTab-C_ps0r--.js} +1 -1
- package/dist/assets/{useEventListener-D27Dii_l.js → useEventListener-92uWqz-P.js} +1 -1
- package/dist/assets/{useIframeCapabilities-DEpTCh1Q.js → useIframeCapabilities-C7Di8f2y.js} +1 -1
- package/dist/assets/{useInstallPackage-u7d0wo79.js → useInstallPackage-DsD08SLK.js} +1 -1
- package/dist/assets/{useInterval-B1_J4wdT.js → useInterval-B4v7_zd0.js} +1 -1
- package/dist/assets/{useNotebookActions-FuMERtHq.js → useNotebookActions-CQtbk9CB.js} +2 -2
- package/dist/assets/{useRunCells-DTdMRbkE.js → useRunCells-CX7oW5zy.js} +1 -1
- package/dist/assets/{useSplitCell-2z0zEB8X.js → useSplitCell-Dy2_DmJR.js} +1 -1
- package/dist/assets/{useTheme-CF-zG9YH.js → useTheme-C-ri8BtY.js} +1 -1
- package/dist/assets/utils-D5Ywm7-q.js +1 -0
- package/dist/assets/{vega-component-BSXJcTJb.js → vega-component-C7TCzyaU.js} +1 -1
- package/dist/assets/write-secret-modal-D6zoZgLY.js +2 -0
- package/dist/index.html +73 -73
- package/package.json +26 -26
- package/src/components/ai/ai-provider-icon.tsx +2 -2
- package/src/components/ai/ai-utils.ts +1 -1
- package/src/components/app-config/ai-config.tsx +33 -37
- package/src/components/app-config/user-config-form.tsx +1 -1
- package/src/components/data-table/__tests__/column-explorer.test.tsx +64 -4
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +137 -4
- package/src/components/data-table/__tests__/url-detector.test.tsx +1 -1
- package/src/components/data-table/__tests__/useColumnVisibility.test.ts +153 -1
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +54 -25
- package/src/components/data-table/column-visibility-dropdown.tsx +62 -14
- package/src/components/data-table/date-popover.tsx +2 -1
- package/src/components/data-table/hooks/use-column-visibility.ts +76 -0
- package/src/components/data-table/renderers.tsx +4 -8
- package/src/components/data-table/show-only-column-button.tsx +58 -0
- package/src/components/dependency-graph/__tests__/elements.test.ts +151 -0
- package/src/components/dependency-graph/custom-edge.tsx +68 -0
- package/src/components/dependency-graph/custom-node.tsx +54 -26
- package/src/components/dependency-graph/dependency-graph-tree.tsx +110 -21
- package/src/components/dependency-graph/elements.ts +225 -31
- package/src/components/dependency-graph/panels.tsx +23 -2
- package/src/components/dependency-graph/utils/__tests__/layout.test.ts +78 -0
- package/src/components/dependency-graph/utils/layout.ts +108 -18
- package/src/components/editor/Output.tsx +40 -0
- package/src/components/editor/__tests__/Output.test.tsx +85 -2
- package/src/components/editor/connections/secret-combobox.tsx +3 -1
- package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +832 -0
- package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +27 -0
- package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +130 -0
- package/src/components/editor/file-tree/file-browser-row.tsx +82 -0
- package/src/components/editor/file-tree/file-explorer.tsx +377 -159
- package/src/components/editor/file-tree/file-operations.tsx +1 -1
- package/src/components/editor/file-tree/file-search-results.tsx +142 -0
- package/src/components/editor/file-tree/file-tree-row.tsx +40 -0
- package/src/components/editor/file-tree/file-viewer.tsx +2 -1
- package/src/components/editor/file-tree/renderers.tsx +4 -2
- package/src/components/editor/file-tree/requesting-tree.tsx +162 -32
- package/src/components/editor/file-tree/use-file-search.ts +142 -0
- package/src/components/editor/file-tree/use-hover-expand.ts +34 -0
- package/src/components/editor/navigation/__tests__/navigation.test.ts +30 -0
- package/src/components/editor/navigation/navigation.ts +20 -0
- package/src/components/forms/form.tsx +6 -4
- package/src/components/home/components.tsx +1 -0
- package/src/components/static-html/share-modal.tsx +3 -2
- package/src/components/ui/__tests__/badge.test.ts +63 -0
- package/src/components/ui/__tests__/button.test.ts +19 -0
- package/src/components/ui/__tests__/fullscreen.test.tsx +88 -0
- package/src/components/ui/__tests__/menu-items.test.ts +31 -0
- package/src/components/ui/badge.tsx +9 -7
- package/src/components/ui/button.tsx +1 -0
- package/src/components/ui/combobox.tsx +1 -1
- package/src/components/ui/date-input.tsx +1 -1
- package/src/components/ui/dialog.tsx +3 -1
- package/src/components/ui/fullscreen.tsx +43 -1
- package/src/components/ui/input.tsx +5 -1
- package/src/components/ui/links.tsx +2 -1
- package/src/components/ui/menu-items.tsx +9 -9
- package/src/components/ui/native-select.tsx +2 -2
- package/src/components/ui/navigation.tsx +1 -1
- package/src/components/ui/range-slider.tsx +5 -3
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/toast.tsx +8 -5
- package/src/components/ui/toaster.tsx +2 -2
- package/src/core/ai/ids/__tests__/ids.test.ts +0 -8
- package/src/core/ai/ids/ids.ts +1 -1
- package/src/core/cells/__tests__/add-missing-import.test.ts +20 -0
- package/src/core/codemirror/language/__tests__/utils.test.ts +1 -1
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/hotkeys/hotkeys.ts +5 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +14 -0
- package/src/core/network/requests-toasting.tsx +1 -1
- package/src/css/globals.css +28 -0
- package/src/hooks/useEventListener.ts +1 -1
- package/src/plugins/core/RenderHTML.tsx +6 -4
- package/src/plugins/impl/MatrixPlugin.tsx +1 -1
- package/src/plugins/impl/code/any-language-editor.tsx +7 -4
- package/src/plugins/impl/data-frames/__tests__/schema.test.ts +34 -1
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +25 -25
- package/src/plugins/impl/data-frames/schema.ts +5 -11
- package/src/plugins/impl/panel/__tests__/utils.test.ts +4 -4
- package/src/plugins/impl/vega/__tests__/vega.test.ts +1 -1
- package/src/stories/cell.stories.tsx +15 -11
- package/src/utils/__tests__/events.test.ts +1 -1
- package/dist/assets/JsonOutput-CRLQQVKf.js +0 -50
- package/dist/assets/RenderHTML-YkwVPe9V.js +0 -1
- package/dist/assets/ai-model-dropdown-CrSrb8Zz.js +0 -3
- package/dist/assets/alert-dialog-BVi_GyOB.js +0 -11
- package/dist/assets/any-language-editor-BL8H-DwB.js +0 -1
- package/dist/assets/badge-D_-uqy2N.js +0 -1
- package/dist/assets/button-Btj6GXNy.js +0 -1
- package/dist/assets/config-ycxJQP2b.js +0 -1
- package/dist/assets/dependency-graph-panel-Dg-tUB9T.js +0 -5
- package/dist/assets/dist-2oRmY-BC.js +0 -1
- package/dist/assets/dist-8lhGoJ54.js +0 -3
- package/dist/assets/dist-B4QaGCvF.js +0 -1
- package/dist/assets/dist-B_ABJVTW.js +0 -1
- package/dist/assets/dist-Bke0Ybxy.js +0 -1
- package/dist/assets/dist-Bw8CAY5i.js +0 -1
- package/dist/assets/dist-BwmO3SSk.js +0 -1
- package/dist/assets/dist-C3KxgR0m.js +0 -1
- package/dist/assets/dist-CLE562nZ.js +0 -1
- package/dist/assets/dist-CLi0j3bw.js +0 -1
- package/dist/assets/dist-CMIyXejt.js +0 -1
- package/dist/assets/dist-CbQrIaPV.js +0 -2
- package/dist/assets/dist-CpgfIIzU.js +0 -1
- package/dist/assets/dist-D7aYPqs9.js +0 -1
- package/dist/assets/dist-DKCF5R0T.js +0 -1
- package/dist/assets/dist-DbFa9cik.js +0 -1
- package/dist/assets/dist-DdPuhs_-.js +0 -1
- package/dist/assets/dist-DkfdZExz.js +0 -1
- package/dist/assets/dist-hOh6JBwi.js +0 -1
- package/dist/assets/dist-mY1j_5eA.js +0 -1
- package/dist/assets/dist-tg3aVnpD.js +0 -1
- package/dist/assets/dropdown-menu-CqNabM1f.js +0 -1
- package/dist/assets/file-explorer-panel-cflxW8a7.js +0 -1
- package/dist/assets/file-name-input-CQF4Ovwx.js +0 -3
- package/dist/assets/home-page-DPRHPAQM.js +0 -4
- package/dist/assets/index-BkIXRAbi.css +0 -2
- package/dist/assets/index-p49fbIlR.js +0 -29
- package/dist/assets/links-DPh7yoP-.js +0 -1
- package/dist/assets/lua-_4wuYo1j.js +0 -1
- package/dist/assets/menu-items-BSVrMleU.js +0 -1
- package/dist/assets/process-output-DvTtgoMs.js +0 -1
- package/dist/assets/select-czJLm6jf.js +0 -1
- package/dist/assets/session-panel-C_N3WGWQ.js +0 -1
- package/dist/assets/utils-CWSZQEjr.js +0 -1
- package/dist/assets/write-secret-modal-lNU_7c2x.js +0 -2
|
@@ -4,26 +4,177 @@ import type { Atom } from "jotai";
|
|
|
4
4
|
import { type Edge, MarkerType, type Node, type NodeProps } from "reactflow";
|
|
5
5
|
import { getNotebook } from "@/core/cells/cells";
|
|
6
6
|
import type { CellId } from "@/core/cells/ids";
|
|
7
|
-
import type { CellData } from "@/core/cells/types";
|
|
7
|
+
import type { CellData, CellRuntimeState } from "@/core/cells/types";
|
|
8
8
|
import { store } from "@/core/state/jotai";
|
|
9
|
-
import type { Variables } from "@/core/variables/types";
|
|
9
|
+
import type { Variable, VariableName, Variables } from "@/core/variables/types";
|
|
10
10
|
import { Arrays } from "@/utils/arrays";
|
|
11
|
+
import { extractCellPreview } from "./utils/cell-preview";
|
|
12
|
+
|
|
13
|
+
/** The edge type registered for dependency edges (see `custom-edge.tsx`). */
|
|
14
|
+
export const DEPENDENCY_EDGE_TYPE = "dependency";
|
|
15
|
+
|
|
16
|
+
// Stroke/marker colors shared by the dependency edge and its arrow marker.
|
|
17
|
+
export const STATE_FLOW_EDGE_COLOR = "var(--amber-10)";
|
|
18
|
+
export const DEFAULT_EDGE_COLOR = "var(--gray-8)";
|
|
19
|
+
|
|
20
|
+
export interface DependencyEdgeData {
|
|
21
|
+
/** Variables carried across this edge. */
|
|
22
|
+
variables: VariableName[];
|
|
23
|
+
/** True when any carried variable is a mutable `State`/`SetFunctor` flow. */
|
|
24
|
+
isStateFlow: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// A `State`/`SetFunctor` (from `mo.state`) is mutable mid-run, so it is drawn
|
|
28
|
+
// distinctly. `UIElement` is intentionally excluded: it is frontend-driven and
|
|
29
|
+
// immutable during a run.
|
|
30
|
+
const STATE_DATATYPES = new Set(["State", "SetFunctor"]);
|
|
31
|
+
|
|
32
|
+
export function isStateFlowVariable(variable: Variable): boolean {
|
|
33
|
+
return variable.dataType != null && STATE_DATATYPES.has(variable.dataType);
|
|
34
|
+
}
|
|
11
35
|
|
|
12
36
|
export interface NodeData {
|
|
13
37
|
atom: Atom<CellData>;
|
|
14
|
-
|
|
38
|
+
/** Variables this cell defines — the node's primary identity when collapsed. */
|
|
39
|
+
defs: VariableName[];
|
|
40
|
+
/** When true the node renders its full code instead of the compact ref list. */
|
|
41
|
+
expanded?: boolean;
|
|
15
42
|
}
|
|
16
43
|
export type CustomNodeProps = NodeProps<NodeData>;
|
|
17
44
|
|
|
45
|
+
const LINE_HEIGHT = 11; // matches TinyCode.css
|
|
46
|
+
|
|
18
47
|
export function getNodeHeight(linesOfCode: number) {
|
|
19
|
-
const LINE_HEIGHT = 11; // matches TinyCode.css
|
|
20
48
|
return Math.min(linesOfCode * LINE_HEIGHT + 35, 200);
|
|
21
49
|
}
|
|
22
50
|
|
|
51
|
+
// Compact-node sizing. A collapsed node wraps its defined variables into
|
|
52
|
+
// comma-separated lines at a character budget, so it grows taller rather than
|
|
53
|
+
// wider; the same budget drives the estimated width so dagre and the DOM agree.
|
|
54
|
+
export const NODE_HEADER_HEIGHT = 22;
|
|
55
|
+
const DEF_LINE_HEIGHT = 16;
|
|
56
|
+
const BODY_V_PADDING = 10;
|
|
57
|
+
const EXPANDED_NODE_WIDTH = 300;
|
|
58
|
+
const CHAR_WIDTH = 6.5; // text-xs monospace-ish
|
|
59
|
+
const NODE_H_PADDING = 22;
|
|
60
|
+
const MIN_NODE_WIDTH = 90;
|
|
61
|
+
const MAX_NODE_WIDTH = 240;
|
|
62
|
+
// Wrap defs to the longest single name, but never narrower than this — short
|
|
63
|
+
// names pack several per line, a long one gets a line to itself.
|
|
64
|
+
const WRAP_MIN_CHARS = 12;
|
|
65
|
+
// ... and never wider than fits MAX_NODE_WIDTH, or packed lines would be
|
|
66
|
+
// clipped by the width clamp. A single name over this budget still gets its
|
|
67
|
+
// own line; the renderer truncates it with an ellipsis and a title.
|
|
68
|
+
const WRAP_MAX_CHARS = Math.floor(
|
|
69
|
+
(MAX_NODE_WIDTH - NODE_H_PADDING) / CHAR_WIDTH,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export function collapsedNodeWidth(label: string): number {
|
|
73
|
+
const raw = label.length * CHAR_WIDTH + NODE_H_PADDING;
|
|
74
|
+
return Math.min(Math.max(raw, MIN_NODE_WIDTH), MAX_NODE_WIDTH);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The per-line character budget used to wrap a node's defs. */
|
|
78
|
+
function defsWrapBudget(defs: VariableName[]): number {
|
|
79
|
+
const longest = defs.reduce(
|
|
80
|
+
(max, def) => Math.max(max, def.length),
|
|
81
|
+
WRAP_MIN_CHARS,
|
|
82
|
+
);
|
|
83
|
+
return Math.min(longest, WRAP_MAX_CHARS);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Greedily pack `defs` into comma-separated lines, each no wider than the
|
|
88
|
+
* budget (the longest name, or `WRAP_MIN_CHARS`). No trailing commas.
|
|
89
|
+
*/
|
|
90
|
+
export function wrapDefs(defs: VariableName[]): string[] {
|
|
91
|
+
const budget = defsWrapBudget(defs);
|
|
92
|
+
const lines: string[] = [];
|
|
93
|
+
let current = "";
|
|
94
|
+
for (const def of defs) {
|
|
95
|
+
if (current === "") {
|
|
96
|
+
current = def;
|
|
97
|
+
} else if (current.length + 2 + def.length <= budget) {
|
|
98
|
+
current = `${current}, ${def}`;
|
|
99
|
+
} else {
|
|
100
|
+
lines.push(current);
|
|
101
|
+
current = def;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (current !== "") {
|
|
105
|
+
lines.push(current);
|
|
106
|
+
}
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The lines shown in a collapsed node's body: its defs wrapped to a budget, or
|
|
112
|
+
* a single code-preview line when the cell defines nothing.
|
|
113
|
+
*/
|
|
114
|
+
export function nodeBodyLines(data: NodeData): string[] {
|
|
115
|
+
if (data.defs.length > 0) {
|
|
116
|
+
return wrapDefs(data.defs);
|
|
117
|
+
}
|
|
118
|
+
const preview = extractCellPreview(store.get(data.atom).code).text;
|
|
119
|
+
return preview ? [preview] : [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function nodeDimensions(data: NodeData): {
|
|
123
|
+
width: number;
|
|
124
|
+
height: number;
|
|
125
|
+
} {
|
|
126
|
+
if (data.expanded) {
|
|
127
|
+
const lines = store.get(data.atom).code.trim().split("\n").length;
|
|
128
|
+
return { width: EXPANDED_NODE_WIDTH, height: getNodeHeight(lines) };
|
|
129
|
+
}
|
|
130
|
+
const lines = nodeBodyLines(data);
|
|
131
|
+
const widthChars =
|
|
132
|
+
data.defs.length > 0
|
|
133
|
+
? defsWrapBudget(data.defs)
|
|
134
|
+
: lines.reduce((max, line) => Math.max(max, line.length), 0);
|
|
135
|
+
const bodyLines = Math.max(lines.length, 1);
|
|
136
|
+
return {
|
|
137
|
+
width: collapsedNodeWidth("x".repeat(widthChars)),
|
|
138
|
+
height: NODE_HEADER_HEIGHT + bodyLines * DEF_LINE_HEIGHT + BODY_V_PADDING,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
23
142
|
// The nodes must have the same handle IDs to ensure edges connect correctly
|
|
24
143
|
export const OUTPUTS_HANDLE_ID = "outputs";
|
|
25
144
|
export const INPUTS_HANDLE_ID = "inputs";
|
|
26
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Cells hidden by the reusable-functions filter: those the kernel marked as
|
|
148
|
+
* valid top-level definitions (serialization hint `Valid`).
|
|
149
|
+
*/
|
|
150
|
+
export function reusableCellIds(
|
|
151
|
+
cellIds: CellId[],
|
|
152
|
+
cellRuntime: Record<CellId, CellRuntimeState>,
|
|
153
|
+
): Set<CellId> {
|
|
154
|
+
const reusable = new Set<CellId>();
|
|
155
|
+
for (const cellId of cellIds) {
|
|
156
|
+
if (cellRuntime[cellId]?.serialization?.toLowerCase() === "valid") {
|
|
157
|
+
reusable.add(cellId);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return reusable;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Map each cell to the names of the variables it declares. */
|
|
164
|
+
export function computeDefsByCell(
|
|
165
|
+
variables: Variables,
|
|
166
|
+
): Map<CellId, VariableName[]> {
|
|
167
|
+
const defsByCell = new Map<CellId, VariableName[]>();
|
|
168
|
+
for (const variable of Object.values(variables)) {
|
|
169
|
+
for (const cellId of variable.declaredBy) {
|
|
170
|
+
const defs = defsByCell.get(cellId) ?? [];
|
|
171
|
+
defs.push(variable.name);
|
|
172
|
+
defsByCell.set(cellId, defs);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return defsByCell;
|
|
176
|
+
}
|
|
177
|
+
|
|
27
178
|
interface ElementsBuilder {
|
|
28
179
|
createElements: (
|
|
29
180
|
cellIds: CellId[],
|
|
@@ -59,16 +210,17 @@ export class VerticalElementsBuilder implements ElementsBuilder {
|
|
|
59
210
|
private createNode(
|
|
60
211
|
id: string,
|
|
61
212
|
atom: Atom<CellData>,
|
|
213
|
+
defs: VariableName[],
|
|
62
214
|
prevY: number,
|
|
63
215
|
): Node<NodeData> {
|
|
64
|
-
const
|
|
65
|
-
const height =
|
|
216
|
+
const data: NodeData = { atom, defs };
|
|
217
|
+
const { width, height } = nodeDimensions(data);
|
|
66
218
|
return {
|
|
67
219
|
id: id,
|
|
68
|
-
data
|
|
69
|
-
width
|
|
220
|
+
data,
|
|
221
|
+
width,
|
|
70
222
|
type: "custom",
|
|
71
|
-
height
|
|
223
|
+
height,
|
|
72
224
|
position: { x: 0, y: prevY + 20 },
|
|
73
225
|
};
|
|
74
226
|
}
|
|
@@ -83,8 +235,14 @@ export class VerticalElementsBuilder implements ElementsBuilder {
|
|
|
83
235
|
let prevY = 0;
|
|
84
236
|
const nodes: Node<NodeData>[] = [];
|
|
85
237
|
const edges: Edge[] = [];
|
|
238
|
+
const defsByCell = computeDefsByCell(variables);
|
|
86
239
|
for (const [cellId, cellAtom] of Arrays.zip(cellIds, cellAtoms)) {
|
|
87
|
-
const node = this.createNode(
|
|
240
|
+
const node = this.createNode(
|
|
241
|
+
cellId,
|
|
242
|
+
cellAtom,
|
|
243
|
+
defsByCell.get(cellId) ?? [],
|
|
244
|
+
prevY,
|
|
245
|
+
);
|
|
88
246
|
nodes.push(node);
|
|
89
247
|
prevY = node.position.y + (node.height || 0);
|
|
90
248
|
}
|
|
@@ -110,16 +268,24 @@ export class VerticalElementsBuilder implements ElementsBuilder {
|
|
|
110
268
|
}
|
|
111
269
|
}
|
|
112
270
|
|
|
271
|
+
interface AggregatedEdge {
|
|
272
|
+
source: CellId;
|
|
273
|
+
target: CellId;
|
|
274
|
+
variables: VariableName[];
|
|
275
|
+
isStateFlow: boolean;
|
|
276
|
+
}
|
|
277
|
+
|
|
113
278
|
export class TreeElementsBuilder implements ElementsBuilder {
|
|
114
|
-
private createEdge(
|
|
279
|
+
private createEdge(edge: AggregatedEdge): Edge<DependencyEdgeData> {
|
|
280
|
+
const { source, target, variables, isStateFlow } = edge;
|
|
115
281
|
return {
|
|
116
|
-
|
|
282
|
+
type: DEPENDENCY_EDGE_TYPE,
|
|
117
283
|
markerEnd: {
|
|
118
284
|
type: MarkerType.ArrowClosed,
|
|
285
|
+
color: isStateFlow ? STATE_FLOW_EDGE_COLOR : DEFAULT_EDGE_COLOR,
|
|
119
286
|
},
|
|
120
287
|
id: `${source}-${target}`,
|
|
121
|
-
|
|
122
|
-
style: { strokeWidth: 2 },
|
|
288
|
+
data: { variables, isStateFlow },
|
|
123
289
|
source: source,
|
|
124
290
|
// Use the same handle ids as the custom node
|
|
125
291
|
sourceHandle: OUTPUTS_HANDLE_ID,
|
|
@@ -128,15 +294,19 @@ export class TreeElementsBuilder implements ElementsBuilder {
|
|
|
128
294
|
};
|
|
129
295
|
}
|
|
130
296
|
|
|
131
|
-
private createNode(
|
|
132
|
-
|
|
133
|
-
|
|
297
|
+
private createNode(
|
|
298
|
+
id: string,
|
|
299
|
+
atom: Atom<CellData>,
|
|
300
|
+
defs: VariableName[],
|
|
301
|
+
): Node<NodeData> {
|
|
302
|
+
const data: NodeData = { atom, defs };
|
|
303
|
+
const { width, height } = nodeDimensions(data);
|
|
134
304
|
return {
|
|
135
305
|
id: id,
|
|
136
|
-
data
|
|
137
|
-
width
|
|
306
|
+
data,
|
|
307
|
+
width,
|
|
138
308
|
type: "custom",
|
|
139
|
-
height
|
|
309
|
+
height,
|
|
140
310
|
position: { x: 0, y: 0 },
|
|
141
311
|
};
|
|
142
312
|
}
|
|
@@ -149,10 +319,19 @@ export class TreeElementsBuilder implements ElementsBuilder {
|
|
|
149
319
|
hideReusableFunctions: boolean,
|
|
150
320
|
) {
|
|
151
321
|
const nodes: Node<NodeData>[] = [];
|
|
152
|
-
|
|
322
|
+
|
|
323
|
+
const cellRuntime = getNotebook().cellRuntime;
|
|
324
|
+
// Hidden reusable cells are dropped along with their edges — a reusable
|
|
325
|
+
// function is nearly always referenced somewhere, so gating on "no edges"
|
|
326
|
+
// would make the filter a no-op.
|
|
327
|
+
const hiddenReusable = hideReusableFunctions
|
|
328
|
+
? reusableCellIds(cellIds, cellRuntime)
|
|
329
|
+
: new Set<CellId>();
|
|
153
330
|
|
|
154
331
|
const nodesWithEdges = new Set<CellId>();
|
|
155
|
-
|
|
332
|
+
// Dedupe cell→cell edges, aggregating every variable that crosses them so a
|
|
333
|
+
// single edge can carry multiple refs and flag whether any is a state flow.
|
|
334
|
+
const edgesByPair = new Map<string, AggregatedEdge>();
|
|
156
335
|
|
|
157
336
|
for (const variable of Object.values(variables)) {
|
|
158
337
|
// Skip marimo, since likely every cell uses it
|
|
@@ -160,40 +339,55 @@ export class TreeElementsBuilder implements ElementsBuilder {
|
|
|
160
339
|
continue;
|
|
161
340
|
}
|
|
162
341
|
|
|
342
|
+
const isState = isStateFlowVariable(variable);
|
|
163
343
|
const { declaredBy, usedBy } = variable;
|
|
164
344
|
for (const fromId of declaredBy) {
|
|
165
345
|
for (const toId of usedBy) {
|
|
166
|
-
|
|
167
|
-
if (visited.has(key)) {
|
|
346
|
+
if (hiddenReusable.has(fromId) || hiddenReusable.has(toId)) {
|
|
168
347
|
continue;
|
|
169
348
|
}
|
|
170
|
-
|
|
349
|
+
const key = `${fromId}-${toId}`;
|
|
350
|
+
const existing = edgesByPair.get(key);
|
|
351
|
+
if (existing) {
|
|
352
|
+
existing.variables.push(variable.name);
|
|
353
|
+
existing.isStateFlow ||= isState;
|
|
354
|
+
} else {
|
|
355
|
+
edgesByPair.set(key, {
|
|
356
|
+
source: fromId,
|
|
357
|
+
target: toId,
|
|
358
|
+
variables: [variable.name],
|
|
359
|
+
isStateFlow: isState,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
171
362
|
nodesWithEdges.add(fromId);
|
|
172
363
|
nodesWithEdges.add(toId);
|
|
173
|
-
edges.push(this.createEdge(fromId, toId));
|
|
174
364
|
}
|
|
175
365
|
}
|
|
176
366
|
}
|
|
177
367
|
|
|
178
|
-
const
|
|
368
|
+
const edges = [...edgesByPair.values()].map((edge) =>
|
|
369
|
+
this.createEdge(edge),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const defsByCell = computeDefsByCell(variables);
|
|
179
373
|
|
|
180
374
|
for (const [cellId, cellAtom] of Arrays.zip(cellIds, cellAtoms)) {
|
|
181
375
|
const code = store.get(cellAtom).code.trim();
|
|
182
376
|
const hasEdge = nodesWithEdges.has(cellId);
|
|
183
377
|
const isMarkdown = code.startsWith("mo.md");
|
|
184
|
-
const runtime = cellRuntime[cellId];
|
|
185
|
-
const isReusable = runtime?.serialization?.toLowerCase() === "valid";
|
|
186
378
|
|
|
187
379
|
// Apply filters
|
|
188
380
|
if (hidePureMarkdown && isMarkdown && !hasEdge) {
|
|
189
381
|
continue;
|
|
190
382
|
}
|
|
191
|
-
if (
|
|
383
|
+
if (hiddenReusable.has(cellId)) {
|
|
192
384
|
continue;
|
|
193
385
|
}
|
|
194
386
|
|
|
195
387
|
// Show every cell that wasn't filtered out
|
|
196
|
-
nodes.push(
|
|
388
|
+
nodes.push(
|
|
389
|
+
this.createNode(cellId, cellAtom, defsByCell.get(cellId) ?? []),
|
|
390
|
+
);
|
|
197
391
|
}
|
|
198
392
|
|
|
199
393
|
return { nodes, edges };
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ArrowRightFromLineIcon,
|
|
5
5
|
ArrowRightIcon,
|
|
6
6
|
ArrowRightToLineIcon,
|
|
7
|
+
ExternalLinkIcon,
|
|
7
8
|
MoreVerticalIcon,
|
|
8
9
|
NetworkIcon,
|
|
9
10
|
SettingsIcon,
|
|
@@ -13,17 +14,18 @@ import {
|
|
|
13
14
|
} from "lucide-react";
|
|
14
15
|
import React, { memo, useId } from "react";
|
|
15
16
|
import { type Edge, Panel } from "reactflow";
|
|
16
|
-
import { getCellEditorView } from "@/core/cells/cells";
|
|
17
|
+
import { getCellEditorView, useCellActions } from "@/core/cells/cells";
|
|
17
18
|
import type { CellId } from "@/core/cells/ids";
|
|
18
19
|
import { goToVariableDefinition } from "@/core/codemirror/go-to-definition/commands";
|
|
19
20
|
import type { Variable, Variables } from "@/core/variables/types";
|
|
20
21
|
import { ConnectionCellActionsDropdown } from "../editor/cell/cell-actions";
|
|
21
|
-
import { CellLink } from "../editor/links/cell-link";
|
|
22
|
+
import { CellLink, scrollAndHighlightCell } from "../editor/links/cell-link";
|
|
22
23
|
import { CellLinkList } from "../editor/links/cell-link-list";
|
|
23
24
|
import { Button } from "../ui/button";
|
|
24
25
|
import { Checkbox } from "../ui/checkbox";
|
|
25
26
|
import { Label } from "../ui/label";
|
|
26
27
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
28
|
+
import { Tooltip } from "../ui/tooltip";
|
|
27
29
|
import { VariableName } from "../variables/common";
|
|
28
30
|
import type { GraphSelection, GraphSettings, LayoutDirection } from "./types";
|
|
29
31
|
|
|
@@ -122,6 +124,8 @@ export const GraphSelectionPanel: React.FC<{
|
|
|
122
124
|
edges: Edge[];
|
|
123
125
|
variables: Variables;
|
|
124
126
|
}> = memo(({ selection, variables, onClearSelection }) => {
|
|
127
|
+
const { showCellIfHidden } = useCellActions();
|
|
128
|
+
|
|
125
129
|
if (!selection) {
|
|
126
130
|
return null;
|
|
127
131
|
}
|
|
@@ -194,6 +198,23 @@ export const GraphSelectionPanel: React.FC<{
|
|
|
194
198
|
<SquareFunction className="w-5 h-5" />
|
|
195
199
|
<CellLink cellId={selection.id} />
|
|
196
200
|
<div className="flex-1" />
|
|
201
|
+
<Tooltip content="Open cell in editor" delayDuration={200}>
|
|
202
|
+
<Button
|
|
203
|
+
aria-label="Open cell in editor"
|
|
204
|
+
variant="text"
|
|
205
|
+
size="icon"
|
|
206
|
+
onClick={() => {
|
|
207
|
+
// The cell may sit in a collapsed section; reveal it first
|
|
208
|
+
// or the scroll target won't be in the DOM.
|
|
209
|
+
showCellIfHidden({ cellId: selection.id });
|
|
210
|
+
requestAnimationFrame(() => {
|
|
211
|
+
scrollAndHighlightCell(selection.id, "focus");
|
|
212
|
+
});
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<ExternalLinkIcon className="w-4 h-4" />
|
|
216
|
+
</Button>
|
|
217
|
+
</Tooltip>
|
|
197
218
|
<ConnectionCellActionsDropdown cellId={selection.id}>
|
|
198
219
|
<Button variant="ghost" size="icon">
|
|
199
220
|
<MoreVerticalIcon className="w-4 h-4" />
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { Node } from "reactflow";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import type { NodeData } from "../../elements";
|
|
6
|
+
import { boundingBox, placeIslands } from "../layout";
|
|
7
|
+
|
|
8
|
+
function node(
|
|
9
|
+
id: string,
|
|
10
|
+
x: number,
|
|
11
|
+
y: number,
|
|
12
|
+
width: number,
|
|
13
|
+
height: number,
|
|
14
|
+
): Node<NodeData> {
|
|
15
|
+
return {
|
|
16
|
+
id,
|
|
17
|
+
position: { x, y },
|
|
18
|
+
width,
|
|
19
|
+
height,
|
|
20
|
+
data: { atom: {} as NodeData["atom"], defs: [] },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("boundingBox", () => {
|
|
25
|
+
it("is all-zero for an empty set", () => {
|
|
26
|
+
expect(boundingBox([])).toEqual({ minX: 0, minY: 0, maxX: 0, maxY: 0 });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("spans position + size across nodes", () => {
|
|
30
|
+
const box = boundingBox([
|
|
31
|
+
node("a", 0, 0, 50, 50),
|
|
32
|
+
node("b", 10, 20, 100, 40),
|
|
33
|
+
]);
|
|
34
|
+
expect(box).toEqual({ minX: 0, minY: 0, maxX: 110, maxY: 60 });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("reads size from style.width/height when width/height are absent", () => {
|
|
38
|
+
const groupNode: Node = {
|
|
39
|
+
id: "g",
|
|
40
|
+
position: { x: 5, y: 5 },
|
|
41
|
+
data: {},
|
|
42
|
+
style: { width: 200, height: 100 },
|
|
43
|
+
};
|
|
44
|
+
expect(boundingBox([groupNode])).toEqual({
|
|
45
|
+
minX: 5,
|
|
46
|
+
minY: 5,
|
|
47
|
+
maxX: 205,
|
|
48
|
+
maxY: 105,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("placeIslands", () => {
|
|
54
|
+
it("returns nothing when there are no islands", () => {
|
|
55
|
+
expect(placeIslands([], [node("a", 0, 0, 100, 100)])).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("stacks islands in a column to the right of the placed graph", () => {
|
|
59
|
+
const placed = [node("main", 0, 0, 200, 300)];
|
|
60
|
+
const islands = [node("i0", 0, 0, 90, 40), node("i1", 0, 0, 90, 40)];
|
|
61
|
+
const result = placeIslands(islands, placed);
|
|
62
|
+
// startX = maxX(200) + margin(40) = 240; startY = minY(0).
|
|
63
|
+
expect(result[0].position).toEqual({ x: 240, y: 0 });
|
|
64
|
+
// Second island stacked below the first (height 40 + gap 20).
|
|
65
|
+
expect(result[1].position).toEqual({ x: 240, y: 60 });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("wraps into a new column when the column gets too tall", () => {
|
|
69
|
+
const placed = [node("main", 0, 0, 200, 300)];
|
|
70
|
+
const islands = Array.from({ length: 10 }, (_, i) =>
|
|
71
|
+
node(`i${i}`, 0, 0, 90, 40),
|
|
72
|
+
);
|
|
73
|
+
const result = placeIslands(islands, placed);
|
|
74
|
+
const firstColumnX = result[0].position.x;
|
|
75
|
+
const wrapped = result.find((n) => n.position.x > firstColumnX);
|
|
76
|
+
expect(wrapped).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -1,45 +1,135 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { graphlib, layout } from "@dagrejs/dagre";
|
|
3
3
|
import type { Edge, Node } from "reactflow";
|
|
4
|
+
import type { NodeData } from "../elements";
|
|
4
5
|
import type { LayoutDirection } from "../types";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// Disconnected cells are stacked in a compact column to the side of the main
|
|
8
|
+
// graph, like TensorBoard's extracted/auxiliary nodes (extractXOffset 15,
|
|
9
|
+
// extractYOffset 20).
|
|
10
|
+
const ISLAND_GAP = 20;
|
|
11
|
+
const ISLAND_MARGIN = 40;
|
|
12
|
+
const ISLAND_MIN_COLUMN_HEIGHT = 320;
|
|
7
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Lay out cell nodes with dagre. Disconnected cells are pulled out of the
|
|
16
|
+
* dagre flow and stacked in a compact column to the side of the main graph.
|
|
17
|
+
*/
|
|
8
18
|
export const layoutElements = ({
|
|
9
19
|
nodes,
|
|
10
20
|
edges,
|
|
11
21
|
direction,
|
|
12
22
|
}: {
|
|
13
|
-
nodes: Node[];
|
|
23
|
+
nodes: Node<NodeData>[];
|
|
14
24
|
edges: Edge[];
|
|
15
25
|
direction: LayoutDirection;
|
|
16
|
-
}) => {
|
|
26
|
+
}): { nodes: Node<NodeData>[]; edges: Edge[] } => {
|
|
27
|
+
const g = new graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
|
17
28
|
g.setGraph({
|
|
18
29
|
rankdir: direction,
|
|
19
|
-
nodesep
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
30
|
+
// Tight spacing mirroring TensorBoard's graph (nodesep 5, ranksep 25,
|
|
31
|
+
// edgesep 5), nudged up a little since our nodes are wider ref boxes than
|
|
32
|
+
// TensorBoard's op ellipses. The dagre default ranker (network-simplex)
|
|
33
|
+
// gives cleaner layered routing with fewer crossings than longest-path.
|
|
34
|
+
nodesep: 20,
|
|
35
|
+
ranksep: 30,
|
|
36
|
+
edgesep: 10,
|
|
24
37
|
});
|
|
25
38
|
|
|
39
|
+
// Disconnected cells are pulled out of the dagre flow and stacked to the
|
|
40
|
+
// side, so they don't stretch the main graph's layout.
|
|
41
|
+
const connectedIds = new Set<string>();
|
|
42
|
+
for (const edge of edges) {
|
|
43
|
+
connectedIds.add(edge.source);
|
|
44
|
+
connectedIds.add(edge.target);
|
|
45
|
+
}
|
|
46
|
+
const isIsland = (node: Node<NodeData>) => !connectedIds.has(node.id);
|
|
47
|
+
const flowNodes = nodes.filter((node) => !isIsland(node));
|
|
48
|
+
const islands = nodes.filter(isIsland);
|
|
49
|
+
|
|
26
50
|
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
|
|
27
|
-
|
|
51
|
+
flowNodes.forEach((node) => {
|
|
28
52
|
g.setNode(node.id, {
|
|
29
53
|
...node,
|
|
30
54
|
width: node.width ?? 0,
|
|
31
55
|
height: node.height ?? 0,
|
|
32
|
-
})
|
|
33
|
-
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
34
58
|
|
|
35
59
|
layout(g);
|
|
36
60
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return { ...node, position: { x, y } };
|
|
42
|
-
}),
|
|
43
|
-
edges,
|
|
61
|
+
// dagre reports node coords as centers; ReactFlow wants top-left.
|
|
62
|
+
const topLeft = (id: string) => {
|
|
63
|
+
const { x, y, width, height } = g.node(id);
|
|
64
|
+
return { x: x - width / 2, y: y - height / 2 };
|
|
44
65
|
};
|
|
66
|
+
|
|
67
|
+
const cellNodes = flowNodes.map((node) => ({
|
|
68
|
+
...node,
|
|
69
|
+
position: topLeft(node.id),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
const islandNodes = placeIslands(islands, cellNodes);
|
|
73
|
+
return { nodes: [...cellNodes, ...islandNodes], edges };
|
|
45
74
|
};
|
|
75
|
+
|
|
76
|
+
/** Bounding box of already-positioned nodes (top-left position + size). */
|
|
77
|
+
export function boundingBox(nodes: Node[]): {
|
|
78
|
+
minX: number;
|
|
79
|
+
minY: number;
|
|
80
|
+
maxX: number;
|
|
81
|
+
maxY: number;
|
|
82
|
+
} {
|
|
83
|
+
if (nodes.length === 0) {
|
|
84
|
+
return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
85
|
+
}
|
|
86
|
+
let minX = Infinity;
|
|
87
|
+
let minY = Infinity;
|
|
88
|
+
let maxX = -Infinity;
|
|
89
|
+
let maxY = -Infinity;
|
|
90
|
+
for (const node of nodes) {
|
|
91
|
+
const styleW = node.style?.width;
|
|
92
|
+
const styleH = node.style?.height;
|
|
93
|
+
const w = node.width ?? (typeof styleW === "number" ? styleW : 0);
|
|
94
|
+
const h = node.height ?? (typeof styleH === "number" ? styleH : 0);
|
|
95
|
+
minX = Math.min(minX, node.position.x);
|
|
96
|
+
minY = Math.min(minY, node.position.y);
|
|
97
|
+
maxX = Math.max(maxX, node.position.x + w);
|
|
98
|
+
maxY = Math.max(maxY, node.position.y + h);
|
|
99
|
+
}
|
|
100
|
+
return { minX, minY, maxX, maxY };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Stack disconnected nodes in a compact column (wrapping into more columns when
|
|
105
|
+
* tall) just to the right of the main graph.
|
|
106
|
+
*/
|
|
107
|
+
export function placeIslands(
|
|
108
|
+
islands: Node<NodeData>[],
|
|
109
|
+
placed: Node[],
|
|
110
|
+
): Node<NodeData>[] {
|
|
111
|
+
if (islands.length === 0) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const box = boundingBox(placed);
|
|
115
|
+
const startX = (placed.length === 0 ? 0 : box.maxX) + ISLAND_MARGIN;
|
|
116
|
+
const startY = placed.length === 0 ? 0 : box.minY;
|
|
117
|
+
const columnHeight = Math.max(box.maxY - box.minY, ISLAND_MIN_COLUMN_HEIGHT);
|
|
118
|
+
|
|
119
|
+
let x = startX;
|
|
120
|
+
let y = startY;
|
|
121
|
+
let columnWidth = 0;
|
|
122
|
+
return islands.map((node) => {
|
|
123
|
+
const h = node.height ?? 0;
|
|
124
|
+
if (y > startY && y + h > startY + columnHeight) {
|
|
125
|
+
// Wrap to the next column.
|
|
126
|
+
x += columnWidth + ISLAND_MARGIN;
|
|
127
|
+
y = startY;
|
|
128
|
+
columnWidth = 0;
|
|
129
|
+
}
|
|
130
|
+
const position = { x, y };
|
|
131
|
+
y += h + ISLAND_GAP;
|
|
132
|
+
columnWidth = Math.max(columnWidth, node.width ?? 0);
|
|
133
|
+
return { ...node, position };
|
|
134
|
+
});
|
|
135
|
+
}
|