@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
|
@@ -206,7 +206,7 @@ export const FileActionsDropdown = ({
|
|
|
206
206
|
</DropdownMenuTrigger>
|
|
207
207
|
<DropdownMenuContent
|
|
208
208
|
align="end"
|
|
209
|
-
className={contentClassName ?? "print:hidden w-[
|
|
209
|
+
className={contentClassName ?? "print:hidden w-[240px]"}
|
|
210
210
|
onClick={(e) => e.stopPropagation()}
|
|
211
211
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
212
212
|
>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { FileIcon, FolderIcon } from "lucide-react";
|
|
4
|
+
import type { RefObject } from "react";
|
|
5
|
+
import {
|
|
6
|
+
Tree,
|
|
7
|
+
type NodeRendererProps,
|
|
8
|
+
type RowRendererProps,
|
|
9
|
+
type TreeApi,
|
|
10
|
+
} from "react-arborist";
|
|
11
|
+
import { Spinner } from "@/components/icons/spinner";
|
|
12
|
+
import { cn } from "@/utils/cn";
|
|
13
|
+
import { useTreeDndManager } from "./dnd-wrapper";
|
|
14
|
+
import { FileTreeRow } from "./file-tree-row";
|
|
15
|
+
import type { FileTreeNode } from "./requesting-tree";
|
|
16
|
+
import type { useFileSearch } from "./use-file-search";
|
|
17
|
+
|
|
18
|
+
export function FileSearchResults({
|
|
19
|
+
treeRef,
|
|
20
|
+
search,
|
|
21
|
+
height,
|
|
22
|
+
onReveal,
|
|
23
|
+
}: {
|
|
24
|
+
treeRef: RefObject<TreeApi<FileTreeNode> | null>;
|
|
25
|
+
search: ReturnType<typeof useFileSearch>;
|
|
26
|
+
height: number;
|
|
27
|
+
onReveal: (node: FileTreeNode) => Promise<void>;
|
|
28
|
+
}) {
|
|
29
|
+
const dndManager = useTreeDndManager();
|
|
30
|
+
const { state, refetch } = search;
|
|
31
|
+
if (state.status === "idle") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (state.status === "error") {
|
|
36
|
+
return (
|
|
37
|
+
<div role="alert" className="px-3 py-2 text-sm">
|
|
38
|
+
Could not search files.{" "}
|
|
39
|
+
<button type="button" className="underline" onClick={refetch}>
|
|
40
|
+
Retry
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (state.status === "loading") {
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
role="status"
|
|
49
|
+
className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground"
|
|
50
|
+
>
|
|
51
|
+
<Spinner size="small" />
|
|
52
|
+
Searching files…
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const { files, hasMore, isPartial } = state;
|
|
57
|
+
const count = files.length;
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
onKeyDownCapture={(event) => {
|
|
61
|
+
if (
|
|
62
|
+
event.key === "Enter" &&
|
|
63
|
+
event.target instanceof HTMLElement &&
|
|
64
|
+
event.target.closest('[role="tree"]')
|
|
65
|
+
) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
treeRef.current?.focusedNode?.activate();
|
|
69
|
+
}
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{isPartial && (
|
|
73
|
+
<div role="alert" className="px-3 py-2 text-xs text-muted-foreground">
|
|
74
|
+
Some locations could not be searched.{" "}
|
|
75
|
+
<button type="button" className="underline" onClick={refetch}>
|
|
76
|
+
Retry
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
<p role="status" className="px-3 py-2 text-xs text-muted-foreground">
|
|
81
|
+
{files.length
|
|
82
|
+
? `${count} ${count === 1 ? "match" : "matches"}`
|
|
83
|
+
: "No matching files or folders."}
|
|
84
|
+
{hasMore && " · Refine your search for more results."}
|
|
85
|
+
</p>
|
|
86
|
+
<Tree<FileTreeNode>
|
|
87
|
+
ref={treeRef}
|
|
88
|
+
data={files}
|
|
89
|
+
childrenAccessor={() => null}
|
|
90
|
+
width="100%"
|
|
91
|
+
height={Math.max(0, height - 48 - (isPartial ? 48 : 0))}
|
|
92
|
+
rowHeight={48}
|
|
93
|
+
overscanCount={8}
|
|
94
|
+
dndManager={dndManager}
|
|
95
|
+
disableDrag={true}
|
|
96
|
+
disableDrop={true}
|
|
97
|
+
disableEdit={true}
|
|
98
|
+
disableMultiSelection={true}
|
|
99
|
+
selectionFollowsFocus={true}
|
|
100
|
+
rowClassName="outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
|
|
101
|
+
renderRow={SearchResultRow}
|
|
102
|
+
onActivate={(node) => {
|
|
103
|
+
void onReveal(node.data);
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
{SearchResult}
|
|
107
|
+
</Tree>
|
|
108
|
+
<p className="px-3 text-xs text-muted-foreground">
|
|
109
|
+
Enter or double-click to open · Esc to clear
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function SearchResultRow(props: RowRendererProps<FileTreeNode>) {
|
|
116
|
+
return <FileTreeRow {...props} ariaLabel={props.node.data.path} />;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function SearchResult({ node }: NodeRendererProps<FileTreeNode>) {
|
|
120
|
+
const Icon = node.data.isDirectory ? FolderIcon : FileIcon;
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
className={cn(
|
|
124
|
+
"flex h-full items-start gap-2.5 px-3 py-1 cursor-pointer hover:bg-accent/50",
|
|
125
|
+
node.isSelected && "bg-accent/60",
|
|
126
|
+
)}
|
|
127
|
+
onDoubleClick={(event) => {
|
|
128
|
+
event.stopPropagation();
|
|
129
|
+
node.activate();
|
|
130
|
+
}}
|
|
131
|
+
title={node.data.path}
|
|
132
|
+
>
|
|
133
|
+
<Icon className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
134
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
135
|
+
<div className="truncate text-sm leading-5">{node.data.name}</div>
|
|
136
|
+
<div className="truncate text-xs leading-4 text-muted-foreground">
|
|
137
|
+
{node.data.path}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { RowRendererProps } from "react-arborist";
|
|
4
|
+
import type { FileTreeNode } from "./requesting-tree";
|
|
5
|
+
|
|
6
|
+
export function FileTreeRow({
|
|
7
|
+
node,
|
|
8
|
+
attrs,
|
|
9
|
+
innerRef,
|
|
10
|
+
children,
|
|
11
|
+
ariaLabel = node.data.name,
|
|
12
|
+
}: RowRendererProps<FileTreeNode> & { ariaLabel?: string }) {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
{...attrs}
|
|
16
|
+
ref={innerRef}
|
|
17
|
+
aria-label={ariaLabel}
|
|
18
|
+
aria-expanded={node.isInternal ? node.isOpen : undefined}
|
|
19
|
+
onFocus={(event) => {
|
|
20
|
+
event.stopPropagation();
|
|
21
|
+
attrs.onFocus?.(event);
|
|
22
|
+
if (event.target === event.currentTarget && !node.isFocused) {
|
|
23
|
+
node.focus();
|
|
24
|
+
}
|
|
25
|
+
}}
|
|
26
|
+
onClick={(event) => {
|
|
27
|
+
if (attrs.onClick) {
|
|
28
|
+
attrs.onClick(event);
|
|
29
|
+
} else {
|
|
30
|
+
node.select();
|
|
31
|
+
}
|
|
32
|
+
if (!event.defaultPrevented) {
|
|
33
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
34
|
+
}
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -156,11 +156,12 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
156
156
|
onDownload={disableFileDownloads ? undefined : handleDownload}
|
|
157
157
|
actions={
|
|
158
158
|
<>
|
|
159
|
-
{file.isMarimoFile && onOpenNotebook && !isWasm() && (
|
|
159
|
+
{data.file.isMarimoFile && onOpenNotebook && !isWasm() && (
|
|
160
160
|
<Tooltip content="Open notebook">
|
|
161
161
|
<Button
|
|
162
162
|
variant="text"
|
|
163
163
|
size="xs"
|
|
164
|
+
aria-label="Open notebook"
|
|
164
165
|
onClick={(evt) => onOpenNotebook(evt)}
|
|
165
166
|
>
|
|
166
167
|
<ExternalLinkIcon className="h-3.5 w-3.5" />
|
|
@@ -189,6 +189,8 @@ export const MediaRenderer: React.FC<MediaSource & { mimeType: string }> = ({
|
|
|
189
189
|
* based on MIME type. Used by both the local file viewer and the
|
|
190
190
|
* storage file viewer.
|
|
191
191
|
*/
|
|
192
|
+
const EMPTY_EXTENSIONS: readonly Extension[] = [];
|
|
193
|
+
|
|
192
194
|
export const FileContentRenderer: React.FC<{
|
|
193
195
|
mimeType: string;
|
|
194
196
|
filename?: string;
|
|
@@ -199,7 +201,7 @@ export const FileContentRenderer: React.FC<{
|
|
|
199
201
|
readOnly?: boolean;
|
|
200
202
|
onChange?: (value: string) => void;
|
|
201
203
|
/** Additional CodeMirror extensions (e.g. save hotkey). */
|
|
202
|
-
extensions?: Extension[];
|
|
204
|
+
extensions?: readonly Extension[];
|
|
203
205
|
}> = ({
|
|
204
206
|
mimeType,
|
|
205
207
|
filename,
|
|
@@ -207,7 +209,7 @@ export const FileContentRenderer: React.FC<{
|
|
|
207
209
|
mediaSource,
|
|
208
210
|
readOnly = true,
|
|
209
211
|
onChange,
|
|
210
|
-
extensions =
|
|
212
|
+
extensions = EMPTY_EXTENSIONS,
|
|
211
213
|
}) => {
|
|
212
214
|
const { theme } = useTheme();
|
|
213
215
|
|
|
@@ -22,6 +22,7 @@ export type FileTreeNode = Omit<FileInfo, "children"> & {
|
|
|
22
22
|
isRoot: boolean;
|
|
23
23
|
rootPath: string;
|
|
24
24
|
isPrimaryRoot: boolean;
|
|
25
|
+
loadState?: "unloaded" | "loading" | "loaded" | "error";
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -66,6 +67,7 @@ export class RequestingTree {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
private roots: FileRoot[] = [];
|
|
70
|
+
private pendingExpansions = new Map<string, Promise<boolean>>();
|
|
69
71
|
private onChange: (data: FileTreeNode[]) => void = Functions.NOOP;
|
|
70
72
|
private path = new PathBuilder("/");
|
|
71
73
|
|
|
@@ -87,12 +89,7 @@ export class RequestingTree {
|
|
|
87
89
|
const data = await this.callbacks.listFiles({
|
|
88
90
|
path: primaryRoot.path,
|
|
89
91
|
});
|
|
90
|
-
this.
|
|
91
|
-
id: this.getPrimaryRootId(),
|
|
92
|
-
changes: {
|
|
93
|
-
children: annotateFiles(data.files, primaryRoot),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
92
|
+
this.updateDirectory(this.getPrimaryRootId(), data.files);
|
|
96
93
|
}
|
|
97
94
|
} catch (error) {
|
|
98
95
|
toast({
|
|
@@ -105,24 +102,91 @@ export class RequestingTree {
|
|
|
105
102
|
this.emitChange();
|
|
106
103
|
};
|
|
107
104
|
|
|
108
|
-
|
|
105
|
+
expand(id: string): Promise<boolean> {
|
|
106
|
+
return this.requestDirectory(id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private requestDirectory(id: string, force = false): Promise<boolean> {
|
|
110
|
+
const pending = this.pendingExpansions.get(id);
|
|
111
|
+
if (pending) {
|
|
112
|
+
return pending;
|
|
113
|
+
}
|
|
109
114
|
const node = this.delegate.find(id);
|
|
110
115
|
if (!node?.data.isDirectory) {
|
|
111
|
-
return false;
|
|
116
|
+
return Promise.resolve(false);
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
if (
|
|
115
|
-
return true;
|
|
119
|
+
if (!force && node.data.loadState === "loaded") {
|
|
120
|
+
return Promise.resolve(true);
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
id,
|
|
121
|
-
changes: {
|
|
122
|
-
children: annotateFiles(data.files, this.getRootForNode(node.data)),
|
|
123
|
-
},
|
|
123
|
+
const request = this.loadDirectory(id, node.data).finally(() => {
|
|
124
|
+
this.pendingExpansions.delete(id);
|
|
124
125
|
});
|
|
126
|
+
this.pendingExpansions.set(id, request);
|
|
127
|
+
return request;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async loadDirectory(
|
|
131
|
+
id: string,
|
|
132
|
+
node: FileTreeNode,
|
|
133
|
+
): Promise<boolean> {
|
|
134
|
+
this.delegate.update({ id, changes: { loadState: "loading" } });
|
|
125
135
|
this.emitChange();
|
|
136
|
+
try {
|
|
137
|
+
const data = await this.callbacks.listFiles({ path: node.path });
|
|
138
|
+
this.updateDirectory(id, data.files);
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.delegate.update({ id, changes: { loadState: "error" } });
|
|
142
|
+
toast({
|
|
143
|
+
title: `Could not load ${node.name}`,
|
|
144
|
+
description: prettyError(error),
|
|
145
|
+
});
|
|
146
|
+
return false;
|
|
147
|
+
} finally {
|
|
148
|
+
this.emitChange();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getRoots(): readonly FileRoot[] {
|
|
153
|
+
return this.roots;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async reveal(node: FileTreeNode): Promise<boolean> {
|
|
157
|
+
const relative = relativePath(node.path as FilePath, node.rootPath);
|
|
158
|
+
if (relative === null) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
let path = node.rootPath as FilePath;
|
|
162
|
+
const parts = relative.split(pathDelimiter(node.rootPath)).filter(Boolean);
|
|
163
|
+
if (!node.isDirectory) {
|
|
164
|
+
parts.pop();
|
|
165
|
+
}
|
|
166
|
+
for (const part of [null, ...parts]) {
|
|
167
|
+
if (part !== null) {
|
|
168
|
+
const parentId = fileTreeNodeId(node.rootPath, path);
|
|
169
|
+
path = joinPath(path, part);
|
|
170
|
+
// Search sees the live filesystem; a cached parent may not contain
|
|
171
|
+
// a directory that was created since its last listing.
|
|
172
|
+
if (!this.delegate.find(fileTreeNodeId(node.rootPath, path))) {
|
|
173
|
+
if (!(await this.requestDirectory(parentId, true))) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const id = fileTreeNodeId(node.rootPath, path);
|
|
179
|
+
if (!this.delegate.find(id)) {
|
|
180
|
+
toast({
|
|
181
|
+
title: "Could not reveal folder",
|
|
182
|
+
description: `The folder may have moved or been deleted: ${path}`,
|
|
183
|
+
});
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (!(await this.expand(id))) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
126
190
|
return true;
|
|
127
191
|
}
|
|
128
192
|
|
|
@@ -252,7 +316,13 @@ export class RequestingTree {
|
|
|
252
316
|
.map((id) => this.delegate.find(id)?.data.path)
|
|
253
317
|
.filter((path): path is string => Boolean(path)),
|
|
254
318
|
];
|
|
255
|
-
await this.refreshPaths(
|
|
319
|
+
await this.refreshPaths(
|
|
320
|
+
paths,
|
|
321
|
+
new Set([
|
|
322
|
+
...this.roots.map((root) => fileTreeNodeId(root.path, root.path)),
|
|
323
|
+
...ids,
|
|
324
|
+
]),
|
|
325
|
+
);
|
|
256
326
|
};
|
|
257
327
|
|
|
258
328
|
refreshPath = async (path: FilePath): Promise<void> => {
|
|
@@ -344,8 +414,13 @@ export class RequestingTree {
|
|
|
344
414
|
return node;
|
|
345
415
|
}
|
|
346
416
|
|
|
347
|
-
private refreshPaths = async (
|
|
348
|
-
|
|
417
|
+
private refreshPaths = async (
|
|
418
|
+
paths: string[],
|
|
419
|
+
retainedIds?: ReadonlySet<string>,
|
|
420
|
+
): Promise<void> => {
|
|
421
|
+
const uniquePaths = [...new Set(paths)].toSorted(
|
|
422
|
+
(left, right) => left.length - right.length,
|
|
423
|
+
);
|
|
349
424
|
if (uniquePaths.length === 0) {
|
|
350
425
|
return;
|
|
351
426
|
}
|
|
@@ -360,31 +435,84 @@ export class RequestingTree {
|
|
|
360
435
|
if (!result) {
|
|
361
436
|
continue;
|
|
362
437
|
}
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
// namespace and preserving its root metadata.
|
|
438
|
+
// Fetch overlapping paths once, but retain loaded folders independently
|
|
439
|
+
// for each root-qualified occurrence.
|
|
366
440
|
for (const root of this.roots) {
|
|
367
|
-
const
|
|
368
|
-
if (!
|
|
369
|
-
|
|
441
|
+
const id = fileTreeNodeId(root.path, path);
|
|
442
|
+
if (!retainedIds || retainedIds.has(id)) {
|
|
443
|
+
this.updateDirectory(id, result.files, retainedIds);
|
|
370
444
|
}
|
|
371
|
-
this.delegate.update({
|
|
372
|
-
id: node.id,
|
|
373
|
-
changes: {
|
|
374
|
-
children: annotateFiles(result.files, root),
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
445
|
}
|
|
378
446
|
}
|
|
379
447
|
this.emitChange();
|
|
380
448
|
};
|
|
381
449
|
|
|
450
|
+
private updateDirectory(
|
|
451
|
+
id: string,
|
|
452
|
+
files: FileInfo[],
|
|
453
|
+
retainedIds?: ReadonlySet<string>,
|
|
454
|
+
): void {
|
|
455
|
+
const node = this.delegate.find(id)?.data;
|
|
456
|
+
if (!node?.isDirectory) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const children = mergeDirectoryChildren(
|
|
460
|
+
annotateFiles(files, this.getRootForNode(node)),
|
|
461
|
+
node.children,
|
|
462
|
+
);
|
|
463
|
+
this.delegate.update({
|
|
464
|
+
id,
|
|
465
|
+
changes: {
|
|
466
|
+
children: retainedIds
|
|
467
|
+
? invalidateClosedDirectories(children, retainedIds)
|
|
468
|
+
: children,
|
|
469
|
+
loadState: "loaded",
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
382
474
|
private emitChange(): void {
|
|
383
475
|
const data = this.delegate.data;
|
|
384
|
-
|
|
476
|
+
// SimpleTree mutates child arrays in place. Publish a new array so React
|
|
477
|
+
// observes completed directory loads even when the root is flattened.
|
|
478
|
+
this.onChange(
|
|
479
|
+
this.roots.length === 1 ? [...(data[0]?.children ?? [])] : data,
|
|
480
|
+
);
|
|
385
481
|
}
|
|
386
482
|
}
|
|
387
483
|
|
|
484
|
+
function mergeDirectoryChildren(
|
|
485
|
+
files: FileTreeNode[],
|
|
486
|
+
previous: FileTreeNode[],
|
|
487
|
+
): FileTreeNode[] {
|
|
488
|
+
const byId = new Map(previous.map((node) => [node.id, node]));
|
|
489
|
+
return files.map((file) => {
|
|
490
|
+
const old = byId.get(file.id);
|
|
491
|
+
if (!file.isDirectory || !old?.isDirectory) {
|
|
492
|
+
return file;
|
|
493
|
+
}
|
|
494
|
+
return { ...file, children: old.children, loadState: old.loadState };
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function invalidateClosedDirectories(
|
|
499
|
+
files: FileTreeNode[],
|
|
500
|
+
openIds: ReadonlySet<string>,
|
|
501
|
+
): FileTreeNode[] {
|
|
502
|
+
return files.map((file) => {
|
|
503
|
+
if (!file.isDirectory) {
|
|
504
|
+
return file;
|
|
505
|
+
}
|
|
506
|
+
if (!openIds.has(file.id)) {
|
|
507
|
+
return { ...file, children: [], loadState: "unloaded" };
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
...file,
|
|
511
|
+
children: invalidateClosedDirectories(file.children, openIds),
|
|
512
|
+
};
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
388
516
|
function toRootNode(root: FileRoot): FileTreeNode {
|
|
389
517
|
return {
|
|
390
518
|
id: fileTreeNodeId(root.path, root.path),
|
|
@@ -396,6 +524,7 @@ function toRootNode(root: FileRoot): FileTreeNode {
|
|
|
396
524
|
isRoot: true,
|
|
397
525
|
rootPath: root.path,
|
|
398
526
|
isPrimaryRoot: root.isPrimary,
|
|
527
|
+
loadState: "unloaded",
|
|
399
528
|
};
|
|
400
529
|
}
|
|
401
530
|
|
|
@@ -404,6 +533,7 @@ function annotateFiles(files: FileInfo[], root: FileRoot): FileTreeNode[] {
|
|
|
404
533
|
...file,
|
|
405
534
|
id: fileTreeNodeId(root.path, file.path),
|
|
406
535
|
children: annotateFiles(file.children ?? [], root),
|
|
536
|
+
loadState: file.children?.length ? "loaded" : "unloaded",
|
|
407
537
|
isRoot: false,
|
|
408
538
|
rootPath: root.path,
|
|
409
539
|
isPrimaryRoot: root.isPrimary,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useRequestClient } from "@/core/network/requests";
|
|
5
|
+
import { useDebounce } from "@/hooks/useDebounce";
|
|
6
|
+
import {
|
|
7
|
+
fileTreeNodeId,
|
|
8
|
+
type FileTreeNode,
|
|
9
|
+
type RequestingTree,
|
|
10
|
+
} from "./requesting-tree";
|
|
11
|
+
|
|
12
|
+
const RESULT_LIMIT = 200;
|
|
13
|
+
|
|
14
|
+
export type FileSearchState =
|
|
15
|
+
| { status: "idle" }
|
|
16
|
+
| { status: "loading"; query: string }
|
|
17
|
+
| { status: "error"; query: string; error: unknown }
|
|
18
|
+
| {
|
|
19
|
+
status: "success";
|
|
20
|
+
query: string;
|
|
21
|
+
files: FileTreeNode[];
|
|
22
|
+
hasMore: boolean;
|
|
23
|
+
isPartial: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function useFileSearch({
|
|
27
|
+
query,
|
|
28
|
+
tree,
|
|
29
|
+
showHiddenFiles,
|
|
30
|
+
}: {
|
|
31
|
+
query: string;
|
|
32
|
+
tree: RequestingTree;
|
|
33
|
+
showHiddenFiles: boolean;
|
|
34
|
+
}): { state: FileSearchState; refetch: () => void } {
|
|
35
|
+
const { sendSearchFiles } = useRequestClient();
|
|
36
|
+
const debouncedQuery = useDebounce(query, 300);
|
|
37
|
+
const pending = useRef<Promise<void>>(Promise.resolve());
|
|
38
|
+
const [revision, setRevision] = useState(0);
|
|
39
|
+
const refetch = useCallback(() => setRevision((value) => value + 1), []);
|
|
40
|
+
const [result, setResult] = useState<FileSearchState>({ status: "idle" });
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
if (!query || query !== debouncedQuery) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
setResult({ status: "loading", query });
|
|
48
|
+
// Let the in-flight server scan finish before starting another. Superseded
|
|
49
|
+
// queued queries and remaining roots are skipped, keeping scan work bounded.
|
|
50
|
+
const request = pending.current.then(async () => {
|
|
51
|
+
const filesByPath = new Map<string, FileTreeNode>();
|
|
52
|
+
let rootLimitReached = false;
|
|
53
|
+
let failedRootCount = 0;
|
|
54
|
+
const roots = tree.getRoots();
|
|
55
|
+
for (const root of roots) {
|
|
56
|
+
if (cancelled) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const response = await sendSearchFiles({
|
|
61
|
+
query,
|
|
62
|
+
path: root.path,
|
|
63
|
+
includeFiles: true,
|
|
64
|
+
includeDirectories: true,
|
|
65
|
+
includeHidden: showHiddenFiles,
|
|
66
|
+
depth: 20,
|
|
67
|
+
limit: RESULT_LIMIT,
|
|
68
|
+
});
|
|
69
|
+
rootLimitReached ||= response.files.length >= RESULT_LIMIT;
|
|
70
|
+
for (const file of response.files) {
|
|
71
|
+
const existing = filesByPath.get(file.path);
|
|
72
|
+
if (existing && (!root.isPrimary || existing.isPrimaryRoot)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
filesByPath.set(file.path, {
|
|
76
|
+
...file,
|
|
77
|
+
id: fileTreeNodeId(root.path, file.path),
|
|
78
|
+
children: [],
|
|
79
|
+
isRoot: false,
|
|
80
|
+
rootPath: root.path,
|
|
81
|
+
isPrimaryRoot: root.isPrimary,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
failedRootCount += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!cancelled) {
|
|
89
|
+
if (roots.length > 0 && failedRootCount === roots.length) {
|
|
90
|
+
setResult({
|
|
91
|
+
status: "error",
|
|
92
|
+
query,
|
|
93
|
+
error: new Error("Could not search any root"),
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const rank = (file: FileTreeNode) => {
|
|
98
|
+
const name = file.name.toLowerCase();
|
|
99
|
+
const needle = query.toLowerCase();
|
|
100
|
+
if (name === needle) {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
if (name.startsWith(needle)) {
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
return 2;
|
|
107
|
+
};
|
|
108
|
+
const sortedFiles = [...filesByPath.values()].toSorted(
|
|
109
|
+
(left, right) =>
|
|
110
|
+
rank(left) - rank(right) || left.name.localeCompare(right.name),
|
|
111
|
+
);
|
|
112
|
+
setResult({
|
|
113
|
+
status: "success",
|
|
114
|
+
isPartial: failedRootCount > 0,
|
|
115
|
+
query,
|
|
116
|
+
files: sortedFiles.slice(0, RESULT_LIMIT),
|
|
117
|
+
hasMore: rootLimitReached || sortedFiles.length > RESULT_LIMIT,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
pending.current = request.catch((error: unknown) => {
|
|
122
|
+
if (!cancelled) {
|
|
123
|
+
setResult({ status: "error", query, error });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return () => {
|
|
127
|
+
cancelled = true;
|
|
128
|
+
};
|
|
129
|
+
}, [query, debouncedQuery, tree, showHiddenFiles, revision, sendSearchFiles]);
|
|
130
|
+
|
|
131
|
+
if (!query) {
|
|
132
|
+
return { state: { status: "idle" }, refetch };
|
|
133
|
+
}
|
|
134
|
+
if (
|
|
135
|
+
query !== debouncedQuery ||
|
|
136
|
+
result.status === "idle" ||
|
|
137
|
+
result.query !== query
|
|
138
|
+
) {
|
|
139
|
+
return { state: { status: "loading", query }, refetch };
|
|
140
|
+
}
|
|
141
|
+
return { state: result, refetch };
|
|
142
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import type { NodeApi } from "react-arborist";
|
|
5
|
+
import type { FileTreeNode } from "./requesting-tree";
|
|
6
|
+
|
|
7
|
+
export const HOVER_EXPAND_DELAY = 600;
|
|
8
|
+
|
|
9
|
+
export function useHoverExpand(
|
|
10
|
+
node: NodeApi<FileTreeNode>,
|
|
11
|
+
isHovering: boolean,
|
|
12
|
+
) {
|
|
13
|
+
const tree = node.tree;
|
|
14
|
+
const attemptedNode = useRef<string | null>(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!isHovering) {
|
|
17
|
+
attemptedNode.current = null;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (
|
|
21
|
+
!node.data.isDirectory ||
|
|
22
|
+
node.isOpen ||
|
|
23
|
+
attemptedNode.current === node.id
|
|
24
|
+
) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const timeout = window.setTimeout(() => {
|
|
28
|
+
// A failed load closes the folder. Retry only after leaving and reentering.
|
|
29
|
+
attemptedNode.current = node.id;
|
|
30
|
+
tree.open(node.id);
|
|
31
|
+
}, HOVER_EXPAND_DELAY);
|
|
32
|
+
return () => window.clearTimeout(timeout);
|
|
33
|
+
}, [isHovering, node.data.isDirectory, node.isOpen, node.id, tree]);
|
|
34
|
+
}
|