@marimo-team/islands 0.24.1-dev99 → 0.24.2-dev1
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-D9EG584s.js → ConnectedDataExplorerComponent-qIKbwYin.js} +12 -12
- package/dist/{ErrorBoundary-D-EKz8oE.js → ErrorBoundary-CR-ZQN_c.js} +3 -3
- package/dist/any-language-editor-CpTnK43-.js +76 -0
- package/dist/{apl-DbeWBitN.js → apl-BYaaig9_.js} +1 -1
- package/dist/{asciiarmor-DVGcwGvr.js → asciiarmor-GKQFJXQG.js} +1 -1
- package/dist/{asn1-C7ZkyMi3.js → asn1-BsSb7HP5.js} +1 -1
- package/dist/assets/__vite-browser-external-BdKGjRZ4.js +1 -0
- package/dist/assets/{worker-C3TCEtlU.js → worker-DlIBfp9S.js} +2 -2
- package/dist/{asterisk-4f1CDJJt.js → asterisk-B3I8SNb5.js} +1 -1
- package/dist/{brainfuck-Cr9wPNgk.js → brainfuck-DXjk3bQe.js} +1 -1
- package/dist/{button-DlqUC8W-.js → button-CKBkqR9J.js} +6 -1
- package/dist/{capabilities-CGBDXz_E.js → capabilities-CMBBJTcc.js} +1 -1
- package/dist/{chat-ui-CHoI65Rg.js → chat-ui-DttQ-5Ut.js} +27 -27
- package/dist/{check-BcJDmQdF.js → check-CHig47QV.js} +1 -1
- package/dist/{clike-BqAajeY2.js → clike-E2lFbGVj.js} +1 -1
- package/dist/{clojure-Bug5Gc5H.js → clojure-Dn_K4RlK.js} +1 -1
- package/dist/{cmake-R8uEfQdt.js → cmake-DpN2Top5.js} +1 -1
- package/dist/{cobol-DCX3p7Cj.js → cobol-Chf22-GM.js} +1 -1
- package/dist/{coffeescript-CA08GUrM.js → coffeescript-BVe1OiD9.js} +1 -1
- package/dist/{common-BTbFryuV.js → common-DjEjXIEn.js} +6846 -6695
- package/dist/{commonlisp-CUlYkvkK.js → commonlisp-DxoBG7fU.js} +1 -1
- package/dist/{copy-D9h0DuU7.js → copy-CD7S58CY.js} +2 -2
- package/dist/{crystal-CHw9Y8bp.js → crystal-DCM3yc4r.js} +1 -1
- package/dist/{css-BR752P21.js → css-DkZ9mMyI.js} +1 -1
- package/dist/{cypher-CGXYVdtR.js → cypher-Drw_8U5I.js} +1 -1
- package/dist/{d-B3Jeg95D.js → d-CsVGcKMF.js} +1 -1
- package/dist/{diff-BskCEFEO.js → diff-CBikYCUF.js} +1 -1
- package/dist/{dist-wdOJ5T7-.js → dist-3_o3T78I.js} +15 -15
- package/dist/{dist-C2unEUWZ.js → dist-BEmkrGoQ.js} +1492 -1484
- package/dist/{dist-3YfamrHv.js → dist-BVRM2y1P.js} +2 -2
- package/dist/{dist-cnCt-QhO.js → dist-BbSMhhD2.js} +2 -2
- package/dist/{dist-C9fJPKPl.js → dist-BrQGcfQ9.js} +4 -4
- package/dist/{dist-Bsa-FD4p.js → dist-Byj2bV8o.js} +1 -1
- package/dist/{dist-t3GZQZfR.js → dist-BzN60Z2s.js} +1 -1
- package/dist/{dist-CDiyTpo-.js → dist-C5Bn52W4.js} +3 -3
- package/dist/{dist-C1_Y0fKn.js → dist-C7v-9nUK.js} +3 -3
- package/dist/{dist-B59W3SQC.js → dist-CGAxEiaM.js} +2 -2
- package/dist/{dist-wGdFe2i2.js → dist-CO5R6VMi.js} +2 -2
- package/dist/{dist-CkF6zgs_.js → dist-CRjAdnqF.js} +14 -12
- package/dist/{dist-BDP-ZzBX.js → dist-D4Z_qdP4.js} +1 -1
- package/dist/{dist-CdoyP1Fz.js → dist-DEeLqRlU.js} +1 -1
- package/dist/{dist-TouP85Vh.js → dist-DJFiGP2k.js} +2 -2
- package/dist/{dist-Ceegh7bg.js → dist-DVFbIwc5.js} +1 -1
- package/dist/{dist-xIlsWj6K.js → dist-DcgzXCfy.js} +2 -2
- package/dist/{dist-BywyHJ0k.js → dist-Re9sOPOx.js} +2 -2
- package/dist/{dist-CN2pQuxD.js → dist-VdvbxG72.js} +1 -1
- package/dist/{dist-Bwzl7sp6.js → dist-_XvgytiS.js} +1 -1
- package/dist/{dist-BdxSRiPP.js → dist-dAqZAPcF.js} +3 -3
- package/dist/{dist-Cri9-7UA.js → dist-hXd65f0_.js} +1 -1
- package/dist/{dist-BmR-cjzI.js → dist-jWUab2d1.js} +1 -1
- package/dist/{dist-CeTE0w_x.js → dist-qhLI-SHd.js} +2 -2
- package/dist/{dockerfile-HPpWCSsC.js → dockerfile-DJnSntVB.js} +2 -2
- package/dist/{dropdown-menu-Baf29jmS.js → dropdown-menu-DhCfcscX.js} +726 -716
- package/dist/{dtd-9Bm4j20r.js → dtd-BHa4v0Bn.js} +1 -1
- package/dist/{dylan-BOpu3dto.js → dylan-BypL2Zz8.js} +1 -1
- package/dist/{ebnf-BogGWV2V.js → ebnf-5guncNu-.js} +1 -1
- package/dist/{ecl-DAcPDOW1.js → ecl-iRcVIKYZ.js} +1 -1
- package/dist/{eiffel-C4NgXX-Y.js → eiffel-BEqF9aU4.js} +1 -1
- package/dist/{elm-BiY4zCKH.js → elm-DYcfNE9c.js} +1 -1
- package/dist/{erlang-Dg5zi-Qk.js → erlang-BtAuIoF0.js} +1 -1
- package/dist/{error-banner-xVkWHg51.js → error-banner-WDhUzJyg.js} +203 -203
- package/dist/{esm-ElfO9dV2.js → esm-CR1nhb1c.js} +108 -107
- package/dist/{esm-DkklGms7.js → esm-CesmlDrs.js} +5 -5
- package/dist/{extends-KH0CV__g.js → extends-BPB6_lTK.js} +8 -7
- package/dist/{factor-B0-OaK4k.js → factor-3nzOwLuN.js} +2 -2
- package/dist/{fcl-DwSn1rXM.js → fcl-C90TGnJJ.js} +1 -1
- package/dist/{formats-nPPeLOk2.js → formats-CJMVUfb3.js} +3 -3
- package/dist/{forth-BGTlwczu.js → forth-DEaRrLUi.js} +1 -1
- package/dist/{fortran-UUzzVROb.js → fortran-Bv6JRUjY.js} +1 -1
- package/dist/{gas-CjkVDeev.js → gas-JNerWj3-.js} +1 -1
- package/dist/{gherkin-CF2G_sqt.js → gherkin-CSrl-H70.js} +1 -1
- package/dist/{glide-data-editor-n5E9BAD8.js → glide-data-editor-sPZ8fUFn.js} +8 -8
- package/dist/{glide-utils-C0dRuWI_.js → glide-utils-NShAPPtn.js} +3 -3
- package/dist/{groovy-YcvlOKLA.js → groovy-X7euO_vE.js} +1 -1
- package/dist/{haskell-BdvVmmhX.js → haskell-C48Z70Az.js} +1 -1
- package/dist/{haxe-Dkk4Avys.js → haxe-CAUWMmwR.js} +1 -1
- package/dist/{html-to-image-B09MiZT1.js → html-to-image-BTYnN4ev.js} +3782 -3784
- package/dist/{http-Cncc-AcB.js → http-BrSBnhDH.js} +1 -1
- package/dist/{idl-D5eSUN-2.js → idl-BXuPleZv.js} +1 -1
- package/dist/{javascript-DV7jbCN7.js → javascript-B4EPWJf_.js} +1 -1
- package/dist/{julia-DRMVPyrg.js → julia-ke9Kh8b7.js} +1 -1
- package/dist/{label-CEgg1Ymb.js → label-lwzzBHUW.js} +2 -2
- package/dist/{livescript-3tVlEl0n.js → livescript-Ce4uqGAC.js} +1 -1
- package/dist/{loader-D8jS1unC.js → loader-VZ__bv4g.js} +1 -1
- package/dist/{lua-DvuDcwhK.js → lua-D6fti_Jk.js} +2 -2
- package/dist/main.js +6189 -6189
- package/dist/{mathematica-DeUYrmDQ.js → mathematica-DcEpZ3Vj.js} +1 -1
- package/dist/{mbox-CP8ncY6Y.js → mbox-DFBbOu0X.js} +1 -1
- package/dist/{mermaid-CKjZUiG7.js → mermaid-BsHdQeQf.js} +4 -4
- package/dist/{mirc-D0BFuM5A.js → mirc-CqeibO3N.js} +1 -1
- package/dist/{mllike-EW0YU24D.js → mllike-D54QZYTP.js} +1 -1
- package/dist/{modelica-0c53v3vH.js → modelica-DuML2-8V.js} +1 -1
- package/dist/{mscgen-DXtnmkk0.js → mscgen-CJmKwp3b.js} +1 -1
- package/dist/{mumps-DuZvAoq5.js → mumps-DaVgFtkd.js} +1 -1
- package/dist/{nginx-BgxmKoMB.js → nginx-BXN1rqI0.js} +1 -1
- package/dist/{nsis-CXKR4weY.js → nsis-CMfRqf0V.js} +2 -2
- package/dist/{ntriples-BvwIFlGB.js → ntriples-BiOGlMDR.js} +1 -1
- package/dist/{octave-Ddh8-k4Y.js → octave-CgVnzxKk.js} +1 -1
- package/dist/{oz-DASBLPdZ.js → oz-B2mky97X.js} +1 -1
- package/dist/{pascal-DKEHBGVg.js → pascal-BoZhBf6s.js} +1 -1
- package/dist/{perl-CG83_G1y.js → perl-DjzMqXtt.js} +1 -1
- package/dist/{pig-C1IvQ7dn.js → pig-B2mB-01d.js} +1 -1
- package/dist/{powershell-Mh2j_Y9A.js → powershell-D0zT88Aj.js} +1 -1
- package/dist/{process-output-kUlWbyzk.js → process-output-CsALk6tp.js} +3 -3
- package/dist/{properties-Cr3dDyK_.js → properties-3_5qCgOj.js} +1 -1
- package/dist/{protobuf-5RkBxiY2.js → protobuf-Dgoxfi1H.js} +1 -1
- package/dist/{pug-CZBnH0wJ.js → pug-CNRdd8oG.js} +2 -2
- package/dist/{puppet-CjiD4zLe.js → puppet-DzILxZfz.js} +1 -1
- package/dist/{python-n5psJoQh.js → python-AfVdMn_l.js} +1 -1
- package/dist/{q-tF-lfOc1.js → q-C6p-CGWh.js} +1 -1
- package/dist/{r-E-Ox73Cl.js → r-CzKF1D2X.js} +1 -1
- package/dist/{reveal-component-AkbqvWB3.js → reveal-component-g415wUxR.js} +446 -446
- package/dist/{rpm-6OeX6y2d.js → rpm-Bx7FkjTO.js} +1 -1
- package/dist/{ruby-BfKhtJ9W.js → ruby-BMzgq2bf.js} +1 -1
- package/dist/{sas-BXINOUe8.js → sas-ClGVnK3F.js} +1 -1
- package/dist/{schemas-JTyiKlcH.js → schemas-DzK6U94N.js} +1380 -1364
- package/dist/{scheme-Bl28UhhC.js → scheme-C0MXqXep.js} +1 -1
- package/dist/{select-DQ9jU_B_.js → select-3qHpFKF9.js} +295 -295
- package/dist/{shell-C6dbyRNm.js → shell-CiQZv6oG.js} +1 -1
- package/dist/{sieve-CtzpqaVa.js → sieve-BiIMePvV.js} +1 -1
- package/dist/{simple-mode-Fa0cHplh.js → simple-mode-8ETW2RS6.js} +1 -1
- package/dist/{smalltalk-SWUbrj2a.js → smalltalk-9-llftyG.js} +1 -1
- package/dist/{solr-DrqND0L5.js → solr-CxCKEuwq.js} +1 -1
- package/dist/{sparql-DNeyanaw.js → sparql-BRmy7pzx.js} +1 -1
- package/dist/{spec-DNZWpXiu.js → spec-CutWdqo3.js} +4 -4
- package/dist/{spreadsheet-DUXiDdBd.js → spreadsheet-UCNUIGsn.js} +1 -1
- package/dist/{sql-BKgWZE5S.js → sql-D0hSGZ-H.js} +1 -1
- package/dist/{stex-DylRJo9v.js → stex-UeT_Aofp.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{stylus-dWe8kzGp.js → stylus-Fi4waA2H.js} +1 -1
- package/dist/{swift-BmWs7LJc.js → swift-NqPyaBye.js} +1 -1
- package/dist/{swiper-component-deYugN58.js → swiper-component-C90YjhaV.js} +2 -2
- package/dist/{tcl-DxdPnAuD.js → tcl-B4f0xfmM.js} +1 -1
- package/dist/{textile-CJnP1piA.js → textile-CnNbnPTe.js} +1 -1
- package/dist/{tiddlywiki-DwhP379Y.js → tiddlywiki-CSGSOg38.js} +1 -1
- package/dist/{tiki-1YDPYy3B.js → tiki-9F02WBA6.js} +1 -1
- package/dist/{toDate-DfFzRDkN.js → toDate-D-Rx3ux5.js} +3 -3
- package/dist/{toml-Cm-dCBP-.js → toml-AesZ6RBt.js} +1 -1
- package/dist/{tooltip-7p_32zdw.js → tooltip-Dt77sBeT.js} +88 -88
- package/dist/{troff-DNWcQdLA.js → troff-DlfvveK1.js} +1 -1
- package/dist/{ttcn-CaUsDYVT.js → ttcn--YVSkGs_.js} +1 -1
- package/dist/{ttcn-cfg-BqNhpRrY.js → ttcn-cfg-Bb1Knl_J.js} +1 -1
- package/dist/{turtle-CK42qVcQ.js → turtle-DC24v1bz.js} +1 -1
- package/dist/{useAsyncData-Cyh2jLTR.js → useAsyncData-CH6-CgJF.js} +1 -1
- package/dist/{useDateFormatter-CcY1uXEk.js → useDateFormatter-Cp9Jdxls.js} +2 -2
- package/dist/{useDeepCompareMemoize-BqcHK9ZZ.js → useDeepCompareMemoize-JC87C_jO.js} +1 -1
- package/dist/{useIframeCapabilities-BmYPLMe1.js → useIframeCapabilities-De3C-9b4.js} +1 -1
- package/dist/{useLifecycle-foJH5Q8T.js → useLifecycle-B4RGlyf0.js} +10 -10
- package/dist/{useTheme-C2ZreN00.js → useTheme-CVJ0UOf2.js} +3 -2
- package/dist/{vb-DUEXFGmn.js → vb-Dpc8vhvF.js} +1 -1
- package/dist/{vbscript-O6Mq-1Bt.js → vbscript-B-Y1XnZe.js} +1 -1
- package/dist/{vega-component-LgU-A6O9.js → vega-component-D-IXRnlx.js} +9 -9
- package/dist/{velocity-DTlmtsPi.js → velocity-DHMu9kc-.js} +1 -1
- package/dist/{verilog-prDoxOJS.js → verilog-DhiEaoUl.js} +1 -1
- package/dist/{vhdl-BAJ9qF1C.js → vhdl-D4X23YKO.js} +1 -1
- package/dist/{webidl-ybLbdZxR.js → webidl-SyzNxWpJ.js} +1 -1
- package/dist/{xquery-81N7rqzd.js → xquery-BC2R4Hk6.js} +1 -1
- package/dist/{yacas-BwVftjEP.js → yacas-DLNXPMmH.js} +1 -1
- package/dist/{z80-CmT0MNUO.js → z80-CDXWHOO6.js} +1 -1
- 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/any-language-editor-D751ztAP.js +0 -76
- package/dist/assets/__vite-browser-external-Cf4O9gnt.js +0 -1
|
@@ -16,14 +16,17 @@ import {
|
|
|
16
16
|
PlaySquareIcon,
|
|
17
17
|
UploadIcon,
|
|
18
18
|
ViewIcon,
|
|
19
|
+
XIcon,
|
|
19
20
|
} from "lucide-react";
|
|
20
21
|
import React, { Suspense, use, useRef, useState } from "react";
|
|
21
22
|
import {
|
|
23
|
+
type DragPreviewProps,
|
|
22
24
|
type NodeApi,
|
|
23
25
|
type NodeRendererProps,
|
|
24
26
|
Tree,
|
|
25
27
|
type TreeApi,
|
|
26
28
|
} from "react-arborist";
|
|
29
|
+
import { createPortal } from "react-dom";
|
|
27
30
|
import useEvent from "react-use-event-hook";
|
|
28
31
|
import {
|
|
29
32
|
FILE_ICON,
|
|
@@ -54,6 +57,7 @@ import {
|
|
|
54
57
|
DropdownMenuSeparator,
|
|
55
58
|
} from "@/components/ui/dropdown-menu";
|
|
56
59
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
60
|
+
import { SearchInput } from "@/components/ui/input";
|
|
57
61
|
import { toast } from "@/components/ui/use-toast";
|
|
58
62
|
import { useCellActions } from "@/core/cells/cells";
|
|
59
63
|
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
@@ -61,6 +65,8 @@ import { disableFileDownloadsAtom } from "@/core/config/config";
|
|
|
61
65
|
import { useRequestClient } from "@/core/network/requests";
|
|
62
66
|
import { isWasm } from "@/core/wasm/utils";
|
|
63
67
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
68
|
+
import { useFileSearch } from "./use-file-search";
|
|
69
|
+
import { useHoverExpand } from "./use-hover-expand";
|
|
64
70
|
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
|
|
65
71
|
import { cn } from "@/utils/cn";
|
|
66
72
|
import { copyToClipboard } from "@/utils/copy";
|
|
@@ -71,6 +77,8 @@ import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
|
71
77
|
import { useTreeDndManager } from "./dnd-wrapper";
|
|
72
78
|
import { downloadFile } from "./download";
|
|
73
79
|
import { FileViewer } from "./file-viewer";
|
|
80
|
+
import { FileSearchResults } from "./file-search-results";
|
|
81
|
+
import { FileBrowserRow } from "./file-browser-row";
|
|
74
82
|
import type { FileTreeNode, RequestingTree } from "./requesting-tree";
|
|
75
83
|
import { openStateAtom, treeAtom } from "./state";
|
|
76
84
|
import { PYTHON_CODE_FOR_FILE_TYPE } from "./types";
|
|
@@ -102,6 +110,10 @@ export const FileExplorer: React.FC<{
|
|
|
102
110
|
externalDropDestinationPath?: FilePath | null;
|
|
103
111
|
}> = ({ height, externalDropDestinationPath = null }) => {
|
|
104
112
|
const treeRef = useRef<TreeApi<FileTreeNode>>(null);
|
|
113
|
+
const searchTreeRef = useRef<TreeApi<FileTreeNode>>(null);
|
|
114
|
+
const searchRef = useRef<HTMLInputElement>(null);
|
|
115
|
+
const [query, setQuery] = useState("");
|
|
116
|
+
const [revealId, setRevealId] = useState<string | null>(null);
|
|
105
117
|
const dndManager = useTreeDndManager();
|
|
106
118
|
const [tree] = useAtom(treeAtom);
|
|
107
119
|
const [data, setData] = useState<FileTreeNode[]>([]);
|
|
@@ -112,6 +124,7 @@ export const FileExplorer: React.FC<{
|
|
|
112
124
|
} | null>(null);
|
|
113
125
|
const [showHiddenFiles, setShowHiddenFiles] =
|
|
114
126
|
useAtom<boolean>(hiddenFilesState);
|
|
127
|
+
const search = useFileSearch({ query: query.trim(), tree, showHiddenFiles });
|
|
115
128
|
// Keep external state to remember which folders are open when this
|
|
116
129
|
// component is unmounted.
|
|
117
130
|
const [openState, setOpenState] = useAtom(openStateAtom);
|
|
@@ -135,14 +148,12 @@ export const FileExplorer: React.FC<{
|
|
|
135
148
|
openUploadPicker();
|
|
136
149
|
});
|
|
137
150
|
|
|
138
|
-
const { openPrompt } = useImperativeModal();
|
|
151
|
+
const { openPrompt, openConfirm } = useImperativeModal();
|
|
139
152
|
const { isPending, error } = useAsyncData(() => tree.initialize(setData), []);
|
|
140
153
|
|
|
141
|
-
const handleRefresh = useEvent(() => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Object.keys(openState).filter((id) => openState[id]),
|
|
145
|
-
);
|
|
154
|
+
const handleRefresh = useEvent(async () => {
|
|
155
|
+
await tree.refreshAll(Object.keys(openState).filter((id) => openState[id]));
|
|
156
|
+
search.refetch();
|
|
146
157
|
});
|
|
147
158
|
|
|
148
159
|
const handleHiddenFilesToggle = useEvent(() => {
|
|
@@ -204,6 +215,77 @@ export const FileExplorer: React.FC<{
|
|
|
204
215
|
[tree, handleUploadFiles, externalDropDestinationPath],
|
|
205
216
|
);
|
|
206
217
|
|
|
218
|
+
React.useEffect(() => {
|
|
219
|
+
if (revealId && !openFile) {
|
|
220
|
+
const activeTree = query.trim() ? searchTreeRef.current : treeRef.current;
|
|
221
|
+
activeTree?.openParents(revealId);
|
|
222
|
+
activeTree?.select(revealId);
|
|
223
|
+
const node = activeTree?.get(revealId);
|
|
224
|
+
if (node?.data.isDirectory) {
|
|
225
|
+
node.open();
|
|
226
|
+
}
|
|
227
|
+
setRevealId(null);
|
|
228
|
+
}
|
|
229
|
+
}, [revealId, query, openFile, data]);
|
|
230
|
+
|
|
231
|
+
const handleReveal = async (node: FileTreeNode) => {
|
|
232
|
+
if (!node.isDirectory) {
|
|
233
|
+
setOpenFile(node);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (await tree.reveal(node)) {
|
|
237
|
+
setQuery("");
|
|
238
|
+
setRevealId(node.id);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const handleTreeKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
243
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "f") {
|
|
244
|
+
event.preventDefault();
|
|
245
|
+
event.stopPropagation();
|
|
246
|
+
searchRef.current?.focus();
|
|
247
|
+
searchRef.current?.select();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (event.key === "Escape" && query) {
|
|
251
|
+
event.preventDefault();
|
|
252
|
+
event.stopPropagation();
|
|
253
|
+
setQuery("");
|
|
254
|
+
searchRef.current?.focus();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const role =
|
|
258
|
+
event.target instanceof Element
|
|
259
|
+
? event.target.getAttribute("role")
|
|
260
|
+
: null;
|
|
261
|
+
if (query.trim() || (role !== "treeitem" && role !== "tree")) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const node = treeRef.current?.focusedNode;
|
|
265
|
+
if (!node || treeRef.current?.isEditing) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (event.key === "Enter") {
|
|
269
|
+
event.preventDefault();
|
|
270
|
+
event.stopPropagation();
|
|
271
|
+
if (node.data.isDirectory) {
|
|
272
|
+
node.toggle();
|
|
273
|
+
} else {
|
|
274
|
+
node.activate();
|
|
275
|
+
}
|
|
276
|
+
} else if (event.key === "F2" && !node.data.isRoot) {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
event.stopPropagation();
|
|
279
|
+
node.edit();
|
|
280
|
+
} else if (event.key === "Delete" || event.key === "Backspace") {
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
event.stopPropagation();
|
|
283
|
+
if (!node.data.isRoot) {
|
|
284
|
+
void node.tree.delete(node.id);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
207
289
|
if (isPending) {
|
|
208
290
|
return <Spinner size="medium" centered={true} />;
|
|
209
291
|
}
|
|
@@ -212,128 +294,230 @@ export const FileExplorer: React.FC<{
|
|
|
212
294
|
return <ErrorBanner error={error} />;
|
|
213
295
|
}
|
|
214
296
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
297
|
+
const preview = openFile ? (
|
|
298
|
+
<>
|
|
299
|
+
<div className="flex items-center pl-1 pr-3 shrink-0 border-b justify-between">
|
|
300
|
+
<Button
|
|
301
|
+
autoFocus={true}
|
|
302
|
+
aria-label="Back to files"
|
|
303
|
+
onClick={() => {
|
|
304
|
+
setRevealId(openFile.id);
|
|
305
|
+
setOpenFile(null);
|
|
306
|
+
}}
|
|
307
|
+
data-testid="file-explorer-back-button"
|
|
308
|
+
variant="text"
|
|
309
|
+
size="xs"
|
|
310
|
+
className="mb-0"
|
|
311
|
+
>
|
|
312
|
+
<ArrowLeftIcon size={16} />
|
|
313
|
+
</Button>
|
|
314
|
+
<span className="font-bold">{openFile.name}</span>
|
|
315
|
+
</div>
|
|
316
|
+
<Suspense>
|
|
317
|
+
<FileViewer
|
|
318
|
+
onOpenNotebook={
|
|
319
|
+
tree.isPrimaryNode(openFile)
|
|
320
|
+
? (evt) => {
|
|
321
|
+
const path = tree.getPrimaryRelativePath(
|
|
322
|
+
openFile.path as FilePath,
|
|
323
|
+
);
|
|
324
|
+
if (path !== null) {
|
|
325
|
+
openMarimoNotebook(evt, path);
|
|
241
326
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
327
|
+
}
|
|
328
|
+
: undefined
|
|
329
|
+
}
|
|
330
|
+
file={openFile}
|
|
331
|
+
/>
|
|
332
|
+
</Suspense>
|
|
333
|
+
</>
|
|
334
|
+
) : null;
|
|
250
335
|
|
|
251
336
|
return (
|
|
252
337
|
<>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
selectedFolder?.path ?? tree.getPrimaryRootPath(),
|
|
271
|
-
)}
|
|
272
|
-
onUpload={handleUploadFiles}
|
|
273
|
-
/>
|
|
274
|
-
<RequestingTreeContext value={contextValue}>
|
|
275
|
-
<Tree<FileTreeNode>
|
|
276
|
-
width="100%"
|
|
277
|
-
ref={treeRef}
|
|
278
|
-
height={height - 33}
|
|
279
|
-
className="h-full"
|
|
280
|
-
data={visibleData}
|
|
281
|
-
initialOpenState={openState}
|
|
282
|
-
openByDefault={false}
|
|
283
|
-
// Use shared DnD manager to prevent "Cannot have two HTML5 backends" error
|
|
284
|
-
dndManager={dndManager}
|
|
285
|
-
// Hide the drop cursor
|
|
286
|
-
renderCursor={() => null}
|
|
287
|
-
// Disable dropping files into files
|
|
288
|
-
disableDrag={(node) => node.isRoot}
|
|
289
|
-
disableDrop={({ parentNode, dragNodes }) =>
|
|
290
|
-
dragNodes.some((node) => node.data.isRoot) ||
|
|
291
|
-
(parentNode ? !parentNode.data.isDirectory : false)
|
|
292
|
-
}
|
|
293
|
-
onDelete={async ({ ids }) => {
|
|
294
|
-
for (const id of ids) {
|
|
295
|
-
await tree.delete(id);
|
|
338
|
+
{preview}
|
|
339
|
+
<div hidden={!!openFile} onKeyDownCapture={handleTreeKeyDown}>
|
|
340
|
+
<input
|
|
341
|
+
data-testid="file-explorer-upload-input"
|
|
342
|
+
{...getUploadInputProps()}
|
|
343
|
+
/>
|
|
344
|
+
<div>
|
|
345
|
+
<Toolbar
|
|
346
|
+
onRefresh={handleRefresh}
|
|
347
|
+
onHidden={handleHiddenFilesToggle}
|
|
348
|
+
showHiddenFiles={showHiddenFiles}
|
|
349
|
+
onCreateFile={handleCreateFile}
|
|
350
|
+
onCreateNotebook={handleCreateNotebook}
|
|
351
|
+
onCreateFolder={handleCreateFolder}
|
|
352
|
+
onCollapseAll={handleCollapseAll}
|
|
353
|
+
uploadDestinationPath={
|
|
354
|
+
selectedFolder?.path ?? tree.getPrimaryRootPath()
|
|
296
355
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
356
|
+
uploadDestinationLabel={getUploadDestinationLabel(
|
|
357
|
+
tree,
|
|
358
|
+
selectedFolder?.path ?? tree.getPrimaryRootPath(),
|
|
359
|
+
)}
|
|
360
|
+
onUpload={handleUploadFiles}
|
|
361
|
+
/>
|
|
362
|
+
<div className="relative -mt-1">
|
|
363
|
+
<SearchInput
|
|
364
|
+
ref={searchRef}
|
|
365
|
+
className="rounded-none pr-6 focus-visible:outline-1 focus-visible:outline-ring/50"
|
|
366
|
+
clearable={false}
|
|
367
|
+
aria-label="Search files and folders"
|
|
368
|
+
placeholder="Search"
|
|
369
|
+
title="Search names in all configured roots, up to 20 folders deep. Generated directories such as node_modules and .git are excluded."
|
|
370
|
+
value={query}
|
|
371
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
372
|
+
onKeyDown={(event) => {
|
|
373
|
+
const activeTree = query.trim()
|
|
374
|
+
? searchTreeRef.current
|
|
375
|
+
: treeRef.current;
|
|
376
|
+
if (event.key === "ArrowDown") {
|
|
377
|
+
event.preventDefault();
|
|
378
|
+
activeTree?.focus(activeTree.firstNode);
|
|
379
|
+
} else if (event.key === "Enter" && query.trim()) {
|
|
380
|
+
event.preventDefault();
|
|
381
|
+
activeTree?.firstNode?.activate();
|
|
382
|
+
}
|
|
383
|
+
}}
|
|
384
|
+
/>
|
|
385
|
+
{query && (
|
|
386
|
+
<Button
|
|
387
|
+
variant="text"
|
|
388
|
+
size="icon"
|
|
389
|
+
className="absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2"
|
|
390
|
+
aria-label="Clear search"
|
|
391
|
+
onClick={() => {
|
|
392
|
+
setQuery("");
|
|
393
|
+
searchRef.current?.focus();
|
|
394
|
+
}}
|
|
395
|
+
>
|
|
396
|
+
<XIcon className="h-3 w-3" />
|
|
397
|
+
</Button>
|
|
398
|
+
)}
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
<div hidden={!query.trim()}>
|
|
402
|
+
<FileSearchResults
|
|
403
|
+
treeRef={searchTreeRef}
|
|
404
|
+
search={search}
|
|
405
|
+
height={Math.max(0, height - 63)}
|
|
406
|
+
onReveal={handleReveal}
|
|
407
|
+
/>
|
|
408
|
+
</div>
|
|
409
|
+
<div hidden={!!query.trim()} className="relative">
|
|
410
|
+
<RequestingTreeContext value={contextValue}>
|
|
411
|
+
<Tree<FileTreeNode>
|
|
412
|
+
width="100%"
|
|
413
|
+
ref={treeRef}
|
|
414
|
+
height={Math.max(0, height - 63)}
|
|
415
|
+
className="h-full"
|
|
416
|
+
data={visibleData}
|
|
417
|
+
childrenAccessor={(node) =>
|
|
418
|
+
node.isDirectory ? node.children : null
|
|
419
|
+
}
|
|
420
|
+
selectionFollowsFocus={true}
|
|
421
|
+
rowClassName="outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring"
|
|
422
|
+
renderRow={FileBrowserRow}
|
|
423
|
+
renderDragPreview={(props) => (
|
|
424
|
+
<FileDragPreview {...props} tree={treeRef.current} />
|
|
425
|
+
)}
|
|
426
|
+
disableEdit={(node) => node.isRoot}
|
|
427
|
+
initialOpenState={openState}
|
|
428
|
+
openByDefault={false}
|
|
429
|
+
// Use shared DnD manager to prevent "Cannot have two HTML5 backends" error
|
|
430
|
+
dndManager={dndManager}
|
|
431
|
+
// Hide the drop cursor
|
|
432
|
+
renderCursor={() => null}
|
|
433
|
+
// Disable dropping files into files
|
|
434
|
+
disableDrag={(node) => node.isRoot}
|
|
435
|
+
disableDrop={({ parentNode, dragNodes }) =>
|
|
436
|
+
dragNodes.some((node) => node.data.isRoot) ||
|
|
437
|
+
(parentNode ? !parentNode.data.isDirectory : false)
|
|
438
|
+
}
|
|
439
|
+
onDelete={async ({ ids }) => {
|
|
440
|
+
openConfirm({
|
|
441
|
+
title: "Delete file",
|
|
442
|
+
description:
|
|
443
|
+
"Are you sure you want to delete the selected file or folder?",
|
|
444
|
+
confirmAction: (
|
|
445
|
+
<AlertDialogDestructiveAction
|
|
446
|
+
onClick={async () => {
|
|
447
|
+
for (const id of ids) {
|
|
448
|
+
await tree.delete(id);
|
|
449
|
+
}
|
|
450
|
+
}}
|
|
451
|
+
>
|
|
452
|
+
Delete
|
|
453
|
+
</AlertDialogDestructiveAction>
|
|
454
|
+
),
|
|
455
|
+
});
|
|
456
|
+
}}
|
|
457
|
+
onRename={async ({ id, name }) => {
|
|
458
|
+
await tree.rename(id, name);
|
|
459
|
+
}}
|
|
460
|
+
onMove={async ({ dragIds, parentId }) => {
|
|
461
|
+
await tree.move(dragIds, parentId);
|
|
462
|
+
}}
|
|
463
|
+
onSelect={(nodes) => {
|
|
464
|
+
const first = nodes[0];
|
|
465
|
+
if (!first) {
|
|
466
|
+
setSelectedFolder(null);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (first.data.isDirectory) {
|
|
470
|
+
setSelectedFolder({
|
|
471
|
+
id: first.id,
|
|
472
|
+
path: first.data.path as FilePath,
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
setSelectedFolder(null);
|
|
477
|
+
}}
|
|
478
|
+
onActivate={(node) => {
|
|
479
|
+
if (!node.data.isDirectory) {
|
|
480
|
+
setOpenFile(node.data);
|
|
481
|
+
}
|
|
482
|
+
}}
|
|
483
|
+
onToggle={async (id) => {
|
|
484
|
+
const isOpen =
|
|
485
|
+
treeRef.current?.isOpen(id) ?? !(openState[id] ?? false);
|
|
486
|
+
if (!isOpen) {
|
|
487
|
+
setOpenState((previous) => ({ ...previous, [id]: false }));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const loaded = await tree.expand(id);
|
|
491
|
+
if (!loaded) {
|
|
492
|
+
treeRef.current?.close(id);
|
|
493
|
+
}
|
|
494
|
+
const remainsOpen =
|
|
495
|
+
loaded && (treeRef.current?.isOpen(id) ?? false);
|
|
496
|
+
setOpenState((previous) => ({
|
|
497
|
+
...previous,
|
|
498
|
+
[id]: remainsOpen,
|
|
499
|
+
}));
|
|
500
|
+
}}
|
|
501
|
+
padding={15}
|
|
502
|
+
rowHeight={30}
|
|
503
|
+
indent={INDENT_STEP}
|
|
504
|
+
overscanCount={8}
|
|
505
|
+
// Disable multi-selection
|
|
506
|
+
disableMultiSelection={true}
|
|
507
|
+
>
|
|
508
|
+
{Node}
|
|
509
|
+
</Tree>
|
|
510
|
+
</RequestingTreeContext>
|
|
511
|
+
{!visibleData.length && (
|
|
512
|
+
<p
|
|
513
|
+
role="status"
|
|
514
|
+
className="absolute top-2 left-3 text-sm text-muted-foreground"
|
|
515
|
+
>
|
|
516
|
+
No visible files.
|
|
517
|
+
</p>
|
|
518
|
+
)}
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
337
521
|
</>
|
|
338
522
|
);
|
|
339
523
|
};
|
|
@@ -368,7 +552,7 @@ const Toolbar = ({
|
|
|
368
552
|
const uploadLabel = `Upload files to ${uploadDestinationLabel}`;
|
|
369
553
|
|
|
370
554
|
return (
|
|
371
|
-
<div className="flex items-center justify-end px-2 shrink-0
|
|
555
|
+
<div className="flex items-center justify-end px-2 shrink-0">
|
|
372
556
|
<Tooltip content="Add notebook">
|
|
373
557
|
<Button
|
|
374
558
|
data-testid="file-explorer-add-notebook-button"
|
|
@@ -445,16 +629,7 @@ const Show = ({
|
|
|
445
629
|
) => void;
|
|
446
630
|
}) => {
|
|
447
631
|
const label = (
|
|
448
|
-
<span
|
|
449
|
-
className="flex-1 overflow-hidden text-ellipsis"
|
|
450
|
-
onClick={(e) => {
|
|
451
|
-
if (node.data.isDirectory) {
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
e.stopPropagation();
|
|
455
|
-
node.select();
|
|
456
|
-
}}
|
|
457
|
-
>
|
|
632
|
+
<span className="flex-1 overflow-hidden text-ellipsis">
|
|
458
633
|
{node.data.name}
|
|
459
634
|
{node.data.isMarimoFile && node.data.isPrimaryRoot && !isWasm() && (
|
|
460
635
|
<span
|
|
@@ -484,7 +659,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
|
|
|
484
659
|
: guessFileIconType(node.data.name);
|
|
485
660
|
|
|
486
661
|
const Icon = FILE_ICON[fileType];
|
|
487
|
-
const {
|
|
662
|
+
const { openPrompt } = useImperativeModal();
|
|
488
663
|
const { createNewCell } = useCellActions();
|
|
489
664
|
const lastFocusedCellId = useLastFocusedCellId();
|
|
490
665
|
|
|
@@ -501,6 +676,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
|
|
|
501
676
|
const isExternalDropTarget =
|
|
502
677
|
node.data.isDirectory &&
|
|
503
678
|
fileExplorer?.externalDropDestinationPath === node.data.path;
|
|
679
|
+
useHoverExpand(node, isExternalDropTarget);
|
|
504
680
|
|
|
505
681
|
const handleOpenMarimoFile = async (
|
|
506
682
|
evt: Pick<Event, "stopPropagation" | "preventDefault">,
|
|
@@ -514,20 +690,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
|
|
|
514
690
|
const handleDeleteFile = async (evt: Event) => {
|
|
515
691
|
evt.stopPropagation();
|
|
516
692
|
evt.preventDefault();
|
|
517
|
-
|
|
518
|
-
title: "Delete file",
|
|
519
|
-
description: `Are you sure you want to delete ${node.data.name}?`,
|
|
520
|
-
confirmAction: (
|
|
521
|
-
<AlertDialogDestructiveAction
|
|
522
|
-
onClick={async () => {
|
|
523
|
-
await node.tree.delete(node.id);
|
|
524
|
-
}}
|
|
525
|
-
aria-label="Confirm"
|
|
526
|
-
>
|
|
527
|
-
Delete
|
|
528
|
-
</AlertDialogDestructiveAction>
|
|
529
|
-
),
|
|
530
|
-
});
|
|
693
|
+
await node.tree.delete(node.id);
|
|
531
694
|
};
|
|
532
695
|
|
|
533
696
|
const handleCreateFolder = useEvent(async () => {
|
|
@@ -576,40 +739,53 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
|
|
|
576
739
|
? { [FILE_EXPLORER_DIRECTORY_PATH_ATTRIBUTE]: node.data.path }
|
|
577
740
|
: {})}
|
|
578
741
|
className={cn(
|
|
579
|
-
"flex items-center cursor-pointer ml-1 text-muted-foreground whitespace-nowrap group",
|
|
742
|
+
"relative flex h-full items-center cursor-pointer ml-1 text-muted-foreground whitespace-nowrap group",
|
|
580
743
|
)}
|
|
581
744
|
draggable={!node.data.isRoot}
|
|
582
|
-
onClick={(
|
|
583
|
-
evt.stopPropagation();
|
|
745
|
+
onClick={() => {
|
|
584
746
|
if (node.data.isDirectory) {
|
|
585
|
-
node.select();
|
|
586
747
|
node.toggle();
|
|
587
748
|
}
|
|
588
749
|
}}
|
|
750
|
+
onDoubleClick={(event) => {
|
|
751
|
+
event.stopPropagation();
|
|
752
|
+
if (!node.data.isDirectory) {
|
|
753
|
+
node.activate();
|
|
754
|
+
}
|
|
755
|
+
}}
|
|
589
756
|
>
|
|
757
|
+
{Array.from({ length: node.level }, (_, level) => (
|
|
758
|
+
<span
|
|
759
|
+
key={level}
|
|
760
|
+
aria-hidden="true"
|
|
761
|
+
className="pointer-events-none absolute inset-y-0 border-l border-border"
|
|
762
|
+
style={{ left: level * INDENT_STEP + 8 }}
|
|
763
|
+
/>
|
|
764
|
+
))}
|
|
590
765
|
<FolderArrow node={node} />
|
|
591
766
|
<span
|
|
592
767
|
className={cn(
|
|
593
768
|
"flex items-center pl-1 py-1 cursor-pointer hover:bg-accent/50 hover:text-accent-foreground rounded-l flex-1 overflow-hidden group",
|
|
594
769
|
node.willReceiveDrop &&
|
|
770
|
+
node.tree.canDrop() &&
|
|
595
771
|
node.data.isDirectory &&
|
|
596
|
-
"bg-
|
|
772
|
+
"bg-primary/15 hover:bg-primary/15 text-accent-foreground",
|
|
597
773
|
node.isSelected &&
|
|
598
774
|
"bg-accent/60 hover:bg-accent/60 text-accent-foreground",
|
|
599
775
|
isExternalDropTarget &&
|
|
600
|
-
"bg-primary/15 hover:bg-primary/15 text-accent-foreground
|
|
776
|
+
"bg-primary/15 hover:bg-primary/15 text-accent-foreground",
|
|
601
777
|
)}
|
|
602
778
|
>
|
|
603
779
|
{node.data.isRoot ? (
|
|
604
780
|
<FolderRootIcon
|
|
605
|
-
className="w-
|
|
781
|
+
className="w-4 h-4 shrink-0 mr-2 text-primary"
|
|
606
782
|
strokeWidth={1.5}
|
|
607
783
|
/>
|
|
608
784
|
) : node.data.isMarimoFile ? (
|
|
609
|
-
<MarimoIcon className="w-
|
|
785
|
+
<MarimoIcon className="w-4 h-4 shrink-0 mr-2" strokeWidth={1.5} />
|
|
610
786
|
) : (
|
|
611
787
|
<Icon
|
|
612
|
-
className={cn("w-
|
|
788
|
+
className={cn("w-4 h-4 shrink-0 mr-2", FILE_ICON_COLOR[fileType])}
|
|
613
789
|
strokeWidth={1.5}
|
|
614
790
|
/>
|
|
615
791
|
)}
|
|
@@ -624,7 +800,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
|
|
|
624
800
|
>
|
|
625
801
|
{!node.data.isDirectory && (
|
|
626
802
|
<DropdownMenuItem
|
|
627
|
-
onSelect={() => node.
|
|
803
|
+
onSelect={() => node.activate()}
|
|
628
804
|
data-testid="file-explorer-open-file-menu-item"
|
|
629
805
|
>
|
|
630
806
|
<ViewIcon className={MENU_ITEM_ICON_CLASS} />
|
|
@@ -794,6 +970,18 @@ const FolderArrow = ({ node }: { node: NodeApi<FileTreeNode> }) => {
|
|
|
794
970
|
return <span className="w-4 h-4 shrink-0" />;
|
|
795
971
|
}
|
|
796
972
|
|
|
973
|
+
if (node.data.loadState === "loading") {
|
|
974
|
+
return (
|
|
975
|
+
<span
|
|
976
|
+
role="status"
|
|
977
|
+
aria-label={`Loading ${node.data.name}`}
|
|
978
|
+
className="w-4 h-4 shrink-0"
|
|
979
|
+
>
|
|
980
|
+
<Spinner size="small" />
|
|
981
|
+
</span>
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
797
985
|
return <TreeChevron isExpanded={node.isOpen} className="w-4 h-4" />;
|
|
798
986
|
};
|
|
799
987
|
|
|
@@ -852,3 +1040,33 @@ function treeContainsId(list: FileTreeNode[], id: string): boolean {
|
|
|
852
1040
|
(item.children ? treeContainsId(item.children, id) : false),
|
|
853
1041
|
);
|
|
854
1042
|
}
|
|
1043
|
+
|
|
1044
|
+
function FileDragPreview({
|
|
1045
|
+
mouse,
|
|
1046
|
+
id,
|
|
1047
|
+
isDragging,
|
|
1048
|
+
tree,
|
|
1049
|
+
}: DragPreviewProps & { tree: TreeApi<FileTreeNode> | null }) {
|
|
1050
|
+
const node = tree?.get(id);
|
|
1051
|
+
if (!isDragging || !mouse || !node) {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
const fileType = node.data.isDirectory
|
|
1055
|
+
? "directory"
|
|
1056
|
+
: guessFileIconType(node.data.name);
|
|
1057
|
+
const Icon = FILE_ICON[fileType];
|
|
1058
|
+
return createPortal(
|
|
1059
|
+
<div
|
|
1060
|
+
className="pointer-events-none fixed left-0 top-0 z-[1000] opacity-[0.85]"
|
|
1061
|
+
style={{
|
|
1062
|
+
transform: `translate3d(${mouse.x + 12}px, ${mouse.y + 12}px, 0)`,
|
|
1063
|
+
}}
|
|
1064
|
+
>
|
|
1065
|
+
<div className="flex max-w-72 items-center gap-2 rounded-md border border-border bg-popover px-3 py-2 text-sm text-popover-foreground shadow-md">
|
|
1066
|
+
<Icon className={cn("h-4 w-4 shrink-0", FILE_ICON_COLOR[fileType])} />
|
|
1067
|
+
<span className="truncate">{node.data.name}</span>
|
|
1068
|
+
</div>
|
|
1069
|
+
</div>,
|
|
1070
|
+
document.body,
|
|
1071
|
+
);
|
|
1072
|
+
}
|