opencami 1.0.0

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.
Files changed (337) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +111 -0
  3. package/bin/opencami.js +155 -0
  4. package/dist/client/apple-touch-icon-180x180.png +0 -0
  5. package/dist/client/assets/_sessionKey-B-MBYHgD.js +95 -0
  6. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  7. package/dist/client/assets/actionscript-3-CfeIJUat.js +1 -0
  8. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  9. package/dist/client/assets/andromeeda-C-Jbm3Hp.js +1 -0
  10. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  11. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  12. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  13. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  14. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  15. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  16. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  17. package/dist/client/assets/asciidoc-Dv7Oe6Be.js +1 -0
  18. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  19. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  20. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  21. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  22. package/dist/client/assets/ayu-dark-CmMr59Fi.js +1 -0
  23. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  24. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  25. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  26. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  27. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  28. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  29. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  30. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  31. package/dist/client/assets/button-D_9OBT_f.js +1 -0
  32. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  33. package/dist/client/assets/c3-VCDPK7BO.js +1 -0
  34. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  35. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  36. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  37. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  38. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  39. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  40. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  41. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  43. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  44. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  45. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  46. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  47. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  48. package/dist/client/assets/connect-BRasuRbB.js +2 -0
  49. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  50. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  51. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  52. package/dist/client/assets/csharp-K5feNrxe.js +1 -0
  53. package/dist/client/assets/css-DPfMkruS.js +1 -0
  54. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  55. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  56. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  57. package/dist/client/assets/d-85-TOEBH.js +1 -0
  58. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  59. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  60. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  61. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  62. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  63. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  64. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  65. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  66. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  67. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  68. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  69. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  70. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  71. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  72. package/dist/client/assets/erb-BOJIQeun.js +1 -0
  73. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  74. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  75. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  76. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  77. package/dist/client/assets/file-explorer-screen-Daf4rIq9.js +1 -0
  78. package/dist/client/assets/files-ByeIMLfw.js +2 -0
  79. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  80. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  81. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  82. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  83. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  84. package/dist/client/assets/gdresource-B7Tvp0Sc.js +1 -0
  85. package/dist/client/assets/gdscript-DTMYz4Jt.js +1 -0
  86. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  87. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  88. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  89. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  90. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  91. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  92. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  93. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  94. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  95. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  96. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  97. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  98. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  99. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  100. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  101. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  102. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  103. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  104. package/dist/client/assets/go-Dn2_MT6a.js +1 -0
  105. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  106. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  107. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  108. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  109. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  110. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  111. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  112. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  113. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  114. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  115. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  116. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  117. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  118. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  119. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  120. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  121. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  122. package/dist/client/assets/html-GMplVEZG.js +1 -0
  123. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  124. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  125. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  126. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  127. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  128. package/dist/client/assets/imba-DGztddWO.js +1 -0
  129. package/dist/client/assets/index-ByUDBI-n.js +14 -0
  130. package/dist/client/assets/index-DHGnKfAU.js +1 -0
  131. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  132. package/dist/client/assets/java-CylS5w8V.js +1 -0
  133. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  134. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  135. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  136. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  137. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  138. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  139. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  140. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  141. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  142. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  143. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  144. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  145. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  146. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  147. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  148. package/dist/client/assets/keyboard-shortcuts-dialog-agsWJ36q.js +1 -0
  149. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  150. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  151. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  152. package/dist/client/assets/latex-B4uzh10-.js +1 -0
  153. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  154. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  155. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  156. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  157. package/dist/client/assets/llvm-BtvRca6l.js +1 -0
  158. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  159. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  160. package/dist/client/assets/lua-BbnMAYS6.js +1 -0
  161. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  162. package/dist/client/assets/main-BAHT5yqU.js +81 -0
  163. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  164. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  165. package/dist/client/assets/marko-DZsq8hO1.js +1 -0
  166. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  167. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  168. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  169. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  170. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  171. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  172. package/dist/client/assets/mdc-DUICxH0z.js +1 -0
  173. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  174. package/dist/client/assets/menu-BhVaz8Ly.js +20 -0
  175. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  176. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  177. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  178. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  179. package/dist/client/assets/mojo-B93PlW-d.js +1 -0
  180. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  181. package/dist/client/assets/moonbit-Ba13S78F.js +1 -0
  182. package/dist/client/assets/move-Bu9oaDYs.js +1 -0
  183. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  184. package/dist/client/assets/new-DLlBm66g.js +1 -0
  185. package/dist/client/assets/nextflow-BrzmwbiE.js +1 -0
  186. package/dist/client/assets/nginx-DknmC5AR.js +1 -0
  187. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  188. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  189. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  190. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  191. package/dist/client/assets/nushell-C-sUppwS.js +1 -0
  192. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  193. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  194. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  195. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  196. package/dist/client/assets/one-light-PoHY5YXO.js +1 -0
  197. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  198. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  199. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  200. package/dist/client/assets/php-CDn_0X-4.js +1 -0
  201. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  202. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  203. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  204. package/dist/client/assets/po-BTJTHyun.js +1 -0
  205. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  206. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  207. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  208. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  209. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  210. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  211. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  212. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  213. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  214. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  215. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  216. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  217. package/dist/client/assets/qml-3beO22l8.js +1 -0
  218. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  219. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  220. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  221. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  222. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  223. package/dist/client/assets/razor-C1TweQQi.js +1 -0
  224. package/dist/client/assets/red-bN70gL4F.js +1 -0
  225. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  226. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  227. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  228. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  229. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  230. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  231. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  232. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  233. package/dist/client/assets/rst-B0xPkSld.js +1 -0
  234. package/dist/client/assets/ruby-BvKwtOVI.js +1 -0
  235. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  236. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  237. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  238. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  239. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  240. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  241. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  242. package/dist/client/assets/search-dialog-CHwnOyPS.js +1 -0
  243. package/dist/client/assets/session-export-dialog-uOlDUzgX.js +1 -0
  244. package/dist/client/assets/settings-dialog-D0aYDkAM.js +1 -0
  245. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  246. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  247. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  248. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  249. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  250. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  251. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  252. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  253. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  254. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  255. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  256. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  257. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  258. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  259. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  260. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  261. package/dist/client/assets/styles-7aVSlb6l.css +1 -0
  262. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  263. package/dist/client/assets/svelte-zxCyuUbr.js +1 -0
  264. package/dist/client/assets/swift-Dg5xB15N.js +1 -0
  265. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  266. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  267. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  268. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  269. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  270. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  271. package/dist/client/assets/templ-W15q3VgB.js +1 -0
  272. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  273. package/dist/client/assets/tex-CvyZ59Mk.js +1 -0
  274. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  275. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  276. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  277. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  278. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  279. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  280. package/dist/client/assets/twig-CO9l9SDP.js +1 -0
  281. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  282. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  283. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  284. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  285. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  286. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  287. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  288. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  289. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  290. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  291. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  292. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  293. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  294. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  295. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  296. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  297. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  298. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  299. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  300. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  301. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  302. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  303. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  304. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  305. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  306. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  307. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  308. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  309. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  310. package/dist/client/cover.jpg +0 -0
  311. package/dist/client/cover.webp +0 -0
  312. package/dist/client/favicon.svg +4 -0
  313. package/dist/client/manifest.json +40 -0
  314. package/dist/client/pwa-192x192.png +0 -0
  315. package/dist/client/pwa-512x512.png +0 -0
  316. package/dist/client/pwa-maskable-512x512.png +0 -0
  317. package/dist/client/robots.txt +3 -0
  318. package/dist/client/screenshot-file-explorer.png +0 -0
  319. package/dist/client/sw.js +165 -0
  320. package/dist/server/assets/_sessionKey-qlAPF_Ft.js +6130 -0
  321. package/dist/server/assets/_tanstack-start-manifest_v-El8F-kd-.js +4 -0
  322. package/dist/server/assets/button-DtQ3rV1m.js +53 -0
  323. package/dist/server/assets/connect-BWI_6rCm.js +80 -0
  324. package/dist/server/assets/file-explorer-screen-DJXPEG_J.js +2450 -0
  325. package/dist/server/assets/files-CONoVTGD.js +11 -0
  326. package/dist/server/assets/index-Bp8QskbI.js +28 -0
  327. package/dist/server/assets/index-COu2idHm.js +226 -0
  328. package/dist/server/assets/keyboard-shortcuts-dialog-D5hqVX2v.js +69 -0
  329. package/dist/server/assets/menu-D6n4DB0U.js +156 -0
  330. package/dist/server/assets/new-Dzk5YxE9.js +6 -0
  331. package/dist/server/assets/router-DbxyvprK.js +2749 -0
  332. package/dist/server/assets/search-dialog-JY8C3mqa.js +443 -0
  333. package/dist/server/assets/session-export-dialog-D87uafPD.js +80 -0
  334. package/dist/server/assets/settings-dialog-CBj7njLM.js +568 -0
  335. package/dist/server/assets/start-HYkvq4Ni.js +4 -0
  336. package/dist/server/server.js +1013 -0
  337. package/package.json +93 -0
@@ -0,0 +1,2450 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { memo, useMemo, useCallback, useEffect, useState, useRef } from "react";
3
+ import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
4
+ import { HugeiconsIcon } from "@hugeicons/react";
5
+ import { Home09Icon, SidebarLeft01Icon, ArrowLeft01Icon, Settings01Icon, ArrowRight01Icon, Home02Icon, Menu01Icon, GridViewIcon, ArrowUp01Icon, ArrowDown01Icon, Upload04Icon, FolderAddIcon, FolderOpenIcon, Download04Icon, FileEditIcon, Edit02Icon, Delete02Icon, Folder01Icon, Image01Icon, Video01Icon, MusicNote01Icon, Archive01Icon, Doc01Icon, CodeIcon, File01Icon, FloppyDiskIcon, Cancel01Icon } from "@hugeicons/core-free-icons";
6
+ import { motion, AnimatePresence } from "motion/react";
7
+ import { T as TooltipProvider, h as TooltipRoot, i as TooltipTrigger, j as TooltipContent, M as MenuRoot, e as MenuTrigger, f as MenuContent, g as MenuItem, D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose, k as chatUiQueryKey, l as getChatUiState, s as setChatUiState } from "./menu-D6n4DB0U.js";
8
+ import { c as cn, b as buttonVariants, B as Button } from "./button-DtQ3rV1m.js";
9
+ import { Link } from "@tanstack/react-router";
10
+ import { create } from "zustand";
11
+ import "@base-ui/react/tooltip";
12
+ import "@base-ui/react/dialog";
13
+ import "@base-ui/react/menu";
14
+ import "@base-ui/react/merge-props";
15
+ import "@base-ui/react/use-render";
16
+ import "class-variance-authority";
17
+ import "clsx";
18
+ import "tailwind-merge";
19
+ function WebClawIconBig({ className }) {
20
+ return /* @__PURE__ */ jsxs(
21
+ "svg",
22
+ {
23
+ xmlns: "http://www.w3.org/2000/svg",
24
+ width: "80",
25
+ height: "80",
26
+ viewBox: "0 0 80 80",
27
+ fill: "none",
28
+ className,
29
+ children: [
30
+ /* @__PURE__ */ jsx(
31
+ "rect",
32
+ {
33
+ width: "80",
34
+ height: "80",
35
+ fill: "currentColor",
36
+ className: "text-transparent"
37
+ }
38
+ ),
39
+ /* @__PURE__ */ jsx(
40
+ "path",
41
+ {
42
+ d: "M61.9624 45.5729C62.5862 44.3371 63.0907 43.3377 63.4782 41.9826C69.8221 28.8286 70.5847 14.0975 67.1723 4.32469C65.8317 0.249835 62.7688 0.254311 62.6374 3.58605C61.355 12.189 58.9061 17.6275 57.1756 21.0554C52.8087 29.7058 45.9949 37.7278 39.3706 42.7305C28.6217 50.8021 25.0716 44.614 24.3711 37.8823C23.8822 32.1203 24.7206 25.5721 26.24 18.2889C27.102 15.3261 24.3944 13.9592 22.5223 16.4122C20.4062 19.3487 18.5586 22.4209 17.1838 25.7318C12.9928 33.4462 10.3896 45.3332 11.4505 57.6273C12.2296 63.5357 13.1688 68.4057 14.1079 73.2757C15.2695 79.0941 19.1598 79.5073 24.3117 78.0088C39.7921 74.0789 54.143 58.872 59.7093 49.1011C60.7255 48.0229 61.3494 46.7872 61.9624 45.5729Z",
43
+ fill: "currentColor",
44
+ className: "text-primary-950"
45
+ }
46
+ )
47
+ ]
48
+ }
49
+ );
50
+ }
51
+ const useFileExplorerState = create((set, get) => ({
52
+ currentPath: "/",
53
+ viewMode: "list",
54
+ sortBy: "name",
55
+ sortAsc: true,
56
+ selectedFiles: /* @__PURE__ */ new Set(),
57
+ navigateTo: (path) => {
58
+ set({
59
+ currentPath: path,
60
+ selectedFiles: /* @__PURE__ */ new Set()
61
+ // Clear selection when navigating
62
+ });
63
+ },
64
+ setViewMode: (mode) => {
65
+ set({ viewMode: mode });
66
+ },
67
+ setSortBy: (sortBy) => {
68
+ const currentSortBy = get().sortBy;
69
+ set({
70
+ sortBy,
71
+ // If switching to the same sort, toggle direction
72
+ sortAsc: currentSortBy === sortBy ? !get().sortAsc : true
73
+ });
74
+ },
75
+ toggleSort: () => {
76
+ set((state) => ({ sortAsc: !state.sortAsc }));
77
+ },
78
+ selectFile: (path) => {
79
+ set((state) => {
80
+ const newSelection = new Set(state.selectedFiles);
81
+ newSelection.add(path);
82
+ return { selectedFiles: newSelection };
83
+ });
84
+ },
85
+ deselectFile: (path) => {
86
+ set((state) => {
87
+ const newSelection = new Set(state.selectedFiles);
88
+ newSelection.delete(path);
89
+ return { selectedFiles: newSelection };
90
+ });
91
+ },
92
+ clearSelection: () => {
93
+ set({ selectedFiles: /* @__PURE__ */ new Set() });
94
+ },
95
+ toggleFileSelection: (path) => {
96
+ const { selectedFiles, selectFile, deselectFile } = get();
97
+ if (selectedFiles.has(path)) {
98
+ deselectFile(path);
99
+ } else {
100
+ selectFile(path);
101
+ }
102
+ }
103
+ }));
104
+ function useReducedMotion$1() {
105
+ return useMemo(() => {
106
+ if (typeof window === "undefined") return false;
107
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
108
+ }, []);
109
+ }
110
+ function FilesSidebarComponent({
111
+ isCollapsed,
112
+ onToggleCollapse,
113
+ onSelectAction
114
+ }) {
115
+ const { navigateTo, currentPath } = useFileExplorerState();
116
+ const reduceMotion = useReducedMotion$1();
117
+ const transition = useMemo(() => ({
118
+ duration: reduceMotion ? 0 : 0.15,
119
+ ease: isCollapsed ? "easeIn" : "easeOut"
120
+ }), [isCollapsed, reduceMotion]);
121
+ const handleNavigate = useCallback((path) => {
122
+ navigateTo(path);
123
+ }, [navigateTo]);
124
+ const handleKeyDown = useCallback((e, action) => {
125
+ if (e.key === "Enter" || e.key === " ") {
126
+ e.preventDefault();
127
+ action();
128
+ }
129
+ }, []);
130
+ const quickAccessItems = useMemo(() => [
131
+ {
132
+ path: "/",
133
+ icon: Home09Icon,
134
+ label: "Home",
135
+ id: "home"
136
+ }
137
+ ], []);
138
+ const asideProps = useMemo(() => ({
139
+ className: "border-r border-primary-200 h-full overflow-hidden bg-primary-100 flex flex-col"
140
+ }), []);
141
+ const collapsedWidth = 48;
142
+ const expandedWidth = 300;
143
+ return /* @__PURE__ */ jsxs(
144
+ motion.aside,
145
+ {
146
+ initial: false,
147
+ animate: {
148
+ width: reduceMotion ? isCollapsed ? collapsedWidth : expandedWidth : void 0
149
+ },
150
+ style: {
151
+ width: reduceMotion ? void 0 : isCollapsed ? collapsedWidth : expandedWidth
152
+ },
153
+ transition,
154
+ className: asideProps.className,
155
+ role: "navigation",
156
+ "aria-label": "File explorer navigation",
157
+ children: [
158
+ /* @__PURE__ */ jsxs(
159
+ motion.div,
160
+ {
161
+ layout: true,
162
+ transition: { layout: transition },
163
+ className: cn("flex items-center h-12 px-2 justify-between"),
164
+ children: [
165
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: !isCollapsed ? /* @__PURE__ */ jsx(
166
+ motion.div,
167
+ {
168
+ initial: { opacity: 0 },
169
+ animate: { opacity: 1 },
170
+ exit: { opacity: 0 },
171
+ transition,
172
+ children: /* @__PURE__ */ jsxs(
173
+ Link,
174
+ {
175
+ to: "/new",
176
+ className: cn(
177
+ buttonVariants({ variant: "ghost", size: "sm" }),
178
+ "w-full pl-1.5 justify-start",
179
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
180
+ "touch-manipulation"
181
+ ),
182
+ children: [
183
+ /* @__PURE__ */ jsx(WebClawIconBig, { className: "size-5 rounded-sm", "aria-hidden": "true" }),
184
+ /* @__PURE__ */ jsx("span", { children: "WebClaw" })
185
+ ]
186
+ }
187
+ )
188
+ }
189
+ ) : null }),
190
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
191
+ /* @__PURE__ */ jsx(
192
+ TooltipTrigger,
193
+ {
194
+ onClick: onToggleCollapse,
195
+ onKeyDown: (e) => handleKeyDown(e, onToggleCollapse),
196
+ render: /* @__PURE__ */ jsx(
197
+ Button,
198
+ {
199
+ size: "icon-sm",
200
+ variant: "ghost",
201
+ "aria-label": isCollapsed ? "Expand sidebar" : "Collapse sidebar",
202
+ className: cn(
203
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
204
+ "touch-manipulation"
205
+ ),
206
+ children: /* @__PURE__ */ jsx(
207
+ HugeiconsIcon,
208
+ {
209
+ icon: SidebarLeft01Icon,
210
+ size: 20,
211
+ strokeWidth: 1.5,
212
+ "aria-hidden": "true"
213
+ }
214
+ )
215
+ }
216
+ )
217
+ }
218
+ ),
219
+ /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: isCollapsed ? "Expand sidebar" : "Collapse sidebar" })
220
+ ] }) })
221
+ ]
222
+ }
223
+ ),
224
+ /* @__PURE__ */ jsx("div", { className: "px-2 mb-4", children: /* @__PURE__ */ jsx(
225
+ motion.div,
226
+ {
227
+ layout: true,
228
+ transition: { layout: transition },
229
+ className: "w-full",
230
+ children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
231
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
232
+ Link,
233
+ {
234
+ to: "/chat/main",
235
+ className: cn(
236
+ buttonVariants({ variant: "ghost", size: "sm" }),
237
+ "w-full pl-1.5 justify-start",
238
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
239
+ "touch-manipulation"
240
+ ),
241
+ onClick: onSelectAction,
242
+ children: [
243
+ /* @__PURE__ */ jsx(
244
+ HugeiconsIcon,
245
+ {
246
+ icon: ArrowLeft01Icon,
247
+ size: 20,
248
+ strokeWidth: 1.5,
249
+ className: "min-w-5",
250
+ "aria-hidden": "true"
251
+ }
252
+ ),
253
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: !isCollapsed ? /* @__PURE__ */ jsx(
254
+ motion.span,
255
+ {
256
+ initial: { opacity: 0 },
257
+ animate: { opacity: 1 },
258
+ exit: { opacity: 0 },
259
+ transition,
260
+ className: "overflow-hidden whitespace-nowrap",
261
+ children: "Back to Chat"
262
+ }
263
+ ) : null })
264
+ ]
265
+ }
266
+ ) }),
267
+ isCollapsed ? /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Back to Chat" }) : null
268
+ ] }) })
269
+ }
270
+ ) }),
271
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 relative overflow-hidden", children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: !isCollapsed ? /* @__PURE__ */ jsx(
272
+ motion.div,
273
+ {
274
+ initial: { opacity: 0 },
275
+ animate: { opacity: 1 },
276
+ exit: { opacity: 0 },
277
+ transition,
278
+ className: "absolute inset-0 pt-0 flex flex-col w-[300px] min-h-0",
279
+ children: /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 px-2", children: [
280
+ /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium text-primary-700 mb-3 px-1.5", children: "Quick Access" }) }),
281
+ /* @__PURE__ */ jsx("nav", { className: "space-y-1", role: "list", "aria-label": "Quick access folders", children: quickAccessItems.map((item) => /* @__PURE__ */ jsx("div", { role: "listitem", children: /* @__PURE__ */ jsxs(
282
+ Button,
283
+ {
284
+ variant: "ghost",
285
+ size: "sm",
286
+ onClick: () => handleNavigate(item.path),
287
+ onKeyDown: (e) => handleKeyDown(e, () => handleNavigate(item.path)),
288
+ "aria-current": currentPath === item.path ? "page" : void 0,
289
+ className: cn(
290
+ "w-full pl-1.5 justify-start text-primary-600 hover:text-primary-900",
291
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
292
+ "touch-manipulation",
293
+ currentPath === item.path && "bg-primary-200 text-primary-900 font-medium"
294
+ ),
295
+ "aria-label": `Navigate to ${item.label}`,
296
+ children: [
297
+ /* @__PURE__ */ jsx(
298
+ HugeiconsIcon,
299
+ {
300
+ icon: item.icon,
301
+ size: 20,
302
+ strokeWidth: 1.5,
303
+ className: "min-w-5",
304
+ "aria-hidden": "true"
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsx("span", { className: "overflow-hidden whitespace-nowrap min-w-0 truncate", children: item.label })
308
+ ]
309
+ }
310
+ ) }, item.id)) })
311
+ ] })
312
+ },
313
+ "content"
314
+ ) : (
315
+ // Collapsed quick access
316
+ /* @__PURE__ */ jsx(
317
+ motion.div,
318
+ {
319
+ initial: { opacity: 0 },
320
+ animate: { opacity: 1 },
321
+ exit: { opacity: 0 },
322
+ transition,
323
+ className: "px-2 space-y-1",
324
+ children: quickAccessItems.map((item) => /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
325
+ /* @__PURE__ */ jsx(
326
+ TooltipTrigger,
327
+ {
328
+ onClick: () => handleNavigate(item.path),
329
+ onKeyDown: (e) => handleKeyDown(e, () => handleNavigate(item.path)),
330
+ render: /* @__PURE__ */ jsx(
331
+ Button,
332
+ {
333
+ size: "icon-sm",
334
+ variant: "ghost",
335
+ "aria-current": currentPath === item.path ? "page" : void 0,
336
+ "aria-label": `Navigate to ${item.label}`,
337
+ className: cn(
338
+ "w-full text-primary-600 hover:text-primary-900",
339
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
340
+ "touch-manipulation",
341
+ currentPath === item.path && "bg-primary-200 text-primary-900"
342
+ ),
343
+ children: /* @__PURE__ */ jsx(
344
+ HugeiconsIcon,
345
+ {
346
+ icon: item.icon,
347
+ size: 20,
348
+ strokeWidth: 1.5,
349
+ "aria-hidden": "true"
350
+ }
351
+ )
352
+ }
353
+ )
354
+ }
355
+ ),
356
+ /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: item.label })
357
+ ] }) }, item.id))
358
+ },
359
+ "collapsed-content"
360
+ )
361
+ ) }) }),
362
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-3 border-t border-primary-200 bg-primary-100", children: /* @__PURE__ */ jsx(
363
+ motion.div,
364
+ {
365
+ layout: true,
366
+ transition: { layout: transition },
367
+ className: "w-full",
368
+ children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
369
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
370
+ Button,
371
+ {
372
+ variant: "ghost",
373
+ size: isCollapsed ? "icon-sm" : "sm",
374
+ className: cn(
375
+ isCollapsed ? "w-full" : "w-full justify-start pl-1.5",
376
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
377
+ "touch-manipulation"
378
+ ),
379
+ "aria-label": "Settings",
380
+ children: [
381
+ /* @__PURE__ */ jsx(
382
+ HugeiconsIcon,
383
+ {
384
+ icon: Settings01Icon,
385
+ size: 20,
386
+ strokeWidth: 1.5,
387
+ className: "min-w-5",
388
+ "aria-hidden": "true"
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: !isCollapsed ? /* @__PURE__ */ jsx(
392
+ motion.span,
393
+ {
394
+ initial: { opacity: 0 },
395
+ animate: { opacity: 1 },
396
+ exit: { opacity: 0 },
397
+ transition,
398
+ className: "overflow-hidden whitespace-nowrap",
399
+ children: "Settings"
400
+ }
401
+ ) : null })
402
+ ]
403
+ }
404
+ ) }),
405
+ isCollapsed ? /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Settings" }) : null
406
+ ] }) })
407
+ }
408
+ ) })
409
+ ]
410
+ }
411
+ );
412
+ }
413
+ function areFilesSidebarPropsEqual(prevProps, nextProps) {
414
+ if (prevProps.isCollapsed !== nextProps.isCollapsed) return false;
415
+ return true;
416
+ }
417
+ const MemoizedFilesSidebar = memo(FilesSidebarComponent, areFilesSidebarPropsEqual);
418
+ function FileBreadcrumb({ path }) {
419
+ const { navigateTo } = useFileExplorerState();
420
+ const segments = path === "/" ? [] : path.split("/").filter(Boolean);
421
+ const breadcrumbs = [
422
+ { name: "Home", path: "/" },
423
+ ...segments.map((segment, index) => ({
424
+ name: segment,
425
+ path: "/" + segments.slice(0, index + 1).join("/")
426
+ }))
427
+ ];
428
+ const handleNavigate = useCallback((breadcrumbPath) => {
429
+ navigateTo(breadcrumbPath);
430
+ }, [navigateTo]);
431
+ const handleKeyDown = useCallback((e, breadcrumbPath) => {
432
+ if (e.key === "Enter" || e.key === " ") {
433
+ e.preventDefault();
434
+ navigateTo(breadcrumbPath);
435
+ }
436
+ }, [navigateTo]);
437
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "File navigation", className: "text-sm text-primary-600", children: /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1", children: [
438
+ index > 0 ? /* @__PURE__ */ jsx(
439
+ HugeiconsIcon,
440
+ {
441
+ icon: ArrowRight01Icon,
442
+ size: 16,
443
+ strokeWidth: 1.5,
444
+ className: "text-primary-400",
445
+ "aria-hidden": "true"
446
+ }
447
+ ) : null,
448
+ /* @__PURE__ */ jsxs(
449
+ "button",
450
+ {
451
+ type: "button",
452
+ onClick: () => handleNavigate(crumb.path),
453
+ onKeyDown: (e) => handleKeyDown(e, crumb.path),
454
+ "aria-current": index === breadcrumbs.length - 1 ? "page" : void 0,
455
+ className: cn(
456
+ "flex items-center gap-1.5 rounded-md px-2 py-1",
457
+ "transition-colors duration-150 ease-out",
458
+ "hover:bg-primary-100 hover:text-primary-900",
459
+ "focus-visible:bg-primary-100 focus-visible:text-primary-900",
460
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
461
+ "touch-manipulation",
462
+ index === breadcrumbs.length - 1 ? "text-primary-900 font-medium" : "text-primary-600"
463
+ ),
464
+ children: [
465
+ index === 0 ? /* @__PURE__ */ jsx(
466
+ HugeiconsIcon,
467
+ {
468
+ icon: Home02Icon,
469
+ size: 16,
470
+ strokeWidth: 1.5,
471
+ "aria-hidden": "true"
472
+ }
473
+ ) : null,
474
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: crumb.name })
475
+ ]
476
+ }
477
+ )
478
+ ] }, crumb.path)) }) });
479
+ }
480
+ const sortOptions = [
481
+ { label: "Name", value: "name" },
482
+ { label: "Size", value: "size" },
483
+ { label: "Modified", value: "modified" }
484
+ ];
485
+ function FileToolbar({ onUpload, onCreateFolder }) {
486
+ const {
487
+ viewMode,
488
+ sortBy,
489
+ sortAsc,
490
+ setViewMode,
491
+ setSortBy,
492
+ toggleSort
493
+ } = useFileExplorerState();
494
+ const handleListView = useCallback(() => {
495
+ setViewMode("list");
496
+ }, [setViewMode]);
497
+ const handleGridView = useCallback(() => {
498
+ setViewMode("grid");
499
+ }, [setViewMode]);
500
+ const handleSortChange = useCallback((newSortBy) => {
501
+ setSortBy(newSortBy);
502
+ }, [setSortBy]);
503
+ const handleKeyDown = useCallback((e, action) => {
504
+ if (e.key === "Enter" || e.key === " ") {
505
+ e.preventDefault();
506
+ action();
507
+ }
508
+ }, []);
509
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
510
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
511
+ /* @__PURE__ */ jsxs(
512
+ "div",
513
+ {
514
+ className: "flex items-center border border-primary-200 rounded-lg overflow-hidden",
515
+ role: "group",
516
+ "aria-label": "View mode",
517
+ children: [
518
+ /* @__PURE__ */ jsx(
519
+ Button,
520
+ {
521
+ size: "sm",
522
+ variant: viewMode === "list" ? "default" : "ghost",
523
+ onClick: handleListView,
524
+ onKeyDown: (e) => handleKeyDown(e, handleListView),
525
+ "aria-label": "Switch to list view",
526
+ "aria-pressed": viewMode === "list",
527
+ className: cn(
528
+ "rounded-none border-0",
529
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
530
+ "touch-manipulation"
531
+ ),
532
+ children: /* @__PURE__ */ jsx(
533
+ HugeiconsIcon,
534
+ {
535
+ icon: Menu01Icon,
536
+ size: 18,
537
+ strokeWidth: 1.5,
538
+ "aria-hidden": "true"
539
+ }
540
+ )
541
+ }
542
+ ),
543
+ /* @__PURE__ */ jsx(
544
+ Button,
545
+ {
546
+ size: "sm",
547
+ variant: viewMode === "grid" ? "default" : "ghost",
548
+ onClick: handleGridView,
549
+ onKeyDown: (e) => handleKeyDown(e, handleGridView),
550
+ "aria-label": "Switch to grid view",
551
+ "aria-pressed": viewMode === "grid",
552
+ className: cn(
553
+ "rounded-none border-0 border-l border-primary-200",
554
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
555
+ "touch-manipulation"
556
+ ),
557
+ children: /* @__PURE__ */ jsx(
558
+ HugeiconsIcon,
559
+ {
560
+ icon: GridViewIcon,
561
+ size: 18,
562
+ strokeWidth: 1.5,
563
+ "aria-hidden": "true"
564
+ }
565
+ )
566
+ }
567
+ )
568
+ ]
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsxs(MenuRoot, { children: [
572
+ /* @__PURE__ */ jsx(
573
+ MenuTrigger,
574
+ {
575
+ render: /* @__PURE__ */ jsxs(
576
+ Button,
577
+ {
578
+ variant: "ghost",
579
+ size: "sm",
580
+ className: cn(
581
+ "gap-1",
582
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
583
+ "touch-manipulation"
584
+ ),
585
+ "aria-label": `Sort by ${sortOptions.find((opt) => opt.value === sortBy)?.label}, ${sortAsc ? "ascending" : "descending"}`,
586
+ children: [
587
+ /* @__PURE__ */ jsxs("span", { children: [
588
+ "Sort: ",
589
+ sortOptions.find((opt) => opt.value === sortBy)?.label
590
+ ] }),
591
+ /* @__PURE__ */ jsx(
592
+ HugeiconsIcon,
593
+ {
594
+ icon: sortAsc ? ArrowUp01Icon : ArrowDown01Icon,
595
+ size: 16,
596
+ strokeWidth: 1.5,
597
+ "aria-hidden": "true"
598
+ }
599
+ )
600
+ ]
601
+ }
602
+ )
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsxs(MenuContent, { side: "bottom", align: "start", children: [
606
+ sortOptions.map((option) => /* @__PURE__ */ jsx(
607
+ MenuItem,
608
+ {
609
+ onClick: () => handleSortChange(option.value),
610
+ className: cn(
611
+ sortBy === option.value && "bg-primary-100 text-primary-900",
612
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none"
613
+ ),
614
+ "aria-current": sortBy === option.value ? "true" : "false",
615
+ children: option.label
616
+ },
617
+ option.value
618
+ )),
619
+ /* @__PURE__ */ jsxs(
620
+ MenuItem,
621
+ {
622
+ onClick: toggleSort,
623
+ className: "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
624
+ children: [
625
+ /* @__PURE__ */ jsx(
626
+ HugeiconsIcon,
627
+ {
628
+ icon: sortAsc ? ArrowDown01Icon : ArrowUp01Icon,
629
+ size: 16,
630
+ strokeWidth: 1.5,
631
+ "aria-hidden": "true"
632
+ }
633
+ ),
634
+ sortAsc ? "Sort Descending" : "Sort Ascending"
635
+ ]
636
+ }
637
+ )
638
+ ] })
639
+ ] })
640
+ ] }),
641
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
642
+ /* @__PURE__ */ jsxs(
643
+ Button,
644
+ {
645
+ variant: "ghost",
646
+ size: "sm",
647
+ onClick: onUpload,
648
+ onKeyDown: (e) => handleKeyDown(e, onUpload),
649
+ className: cn(
650
+ "gap-2",
651
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
652
+ "touch-manipulation"
653
+ ),
654
+ "aria-label": "Upload files",
655
+ children: [
656
+ /* @__PURE__ */ jsx(
657
+ HugeiconsIcon,
658
+ {
659
+ icon: Upload04Icon,
660
+ size: 18,
661
+ strokeWidth: 1.5,
662
+ "aria-hidden": "true"
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsx("span", { children: "Upload" })
666
+ ]
667
+ }
668
+ ),
669
+ /* @__PURE__ */ jsxs(
670
+ Button,
671
+ {
672
+ variant: "ghost",
673
+ size: "sm",
674
+ onClick: onCreateFolder,
675
+ onKeyDown: (e) => handleKeyDown(e, onCreateFolder),
676
+ className: cn(
677
+ "gap-2",
678
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
679
+ "touch-manipulation"
680
+ ),
681
+ "aria-label": "Create new folder",
682
+ children: [
683
+ /* @__PURE__ */ jsx(
684
+ HugeiconsIcon,
685
+ {
686
+ icon: FolderAddIcon,
687
+ size: 18,
688
+ strokeWidth: 1.5,
689
+ "aria-hidden": "true"
690
+ }
691
+ ),
692
+ /* @__PURE__ */ jsx("span", { children: "New Folder" })
693
+ ]
694
+ }
695
+ )
696
+ ] })
697
+ ] });
698
+ }
699
+ const fileQueryKeys = {
700
+ all: ["files"],
701
+ listing: (path) => ["files", "listing", path],
702
+ content: (path) => ["files", "content", path]
703
+ };
704
+ function useFileContent(path) {
705
+ return useQuery({
706
+ queryKey: fileQueryKeys.content(path || ""),
707
+ queryFn: async () => {
708
+ const response = await fetch(`/api/files/read?path=${encodeURIComponent(path)}`);
709
+ if (!response.ok) {
710
+ const error = await response.json().catch(() => ({ error: "Failed to read file" }));
711
+ throw new Error(error.error || "Failed to read file");
712
+ }
713
+ return response.json();
714
+ },
715
+ enabled: !!path,
716
+ staleTime: 30 * 1e3,
717
+ // 30 seconds
718
+ gcTime: 5 * 60 * 1e3
719
+ });
720
+ }
721
+ function useFileSave() {
722
+ const queryClient = useQueryClient();
723
+ return useMutation({
724
+ mutationFn: async ({ path, content }) => {
725
+ const response = await fetch("/api/files/save", {
726
+ method: "POST",
727
+ headers: { "Content-Type": "application/json" },
728
+ body: JSON.stringify({ path, content })
729
+ });
730
+ if (!response.ok) {
731
+ const error = await response.json().catch(() => ({ error: "Failed to save file" }));
732
+ throw new Error(error.error || "Failed to save file");
733
+ }
734
+ return response.json();
735
+ },
736
+ onSuccess: (_, variables) => {
737
+ void queryClient.invalidateQueries({
738
+ queryKey: fileQueryKeys.content(variables.path)
739
+ });
740
+ const parentPath = variables.path.split("/").slice(0, -1).join("/") || "/";
741
+ void queryClient.invalidateQueries({
742
+ queryKey: fileQueryKeys.listing(parentPath)
743
+ });
744
+ }
745
+ });
746
+ }
747
+ function useFileListing(path) {
748
+ return useQuery({
749
+ queryKey: fileQueryKeys.listing(path),
750
+ queryFn: async () => {
751
+ const response = await fetch(`/api/files/list?path=${encodeURIComponent(path)}`);
752
+ if (!response.ok) {
753
+ const error = await response.text();
754
+ throw new Error(`Failed to load files: ${error}`);
755
+ }
756
+ return response.json();
757
+ },
758
+ staleTime: 5 * 60 * 1e3,
759
+ // 5 minutes
760
+ gcTime: 10 * 60 * 1e3,
761
+ // 10 minutes
762
+ retry: 3,
763
+ onError: (error) => {
764
+ console.error("Failed to load file listing:", error);
765
+ }
766
+ });
767
+ }
768
+ function useFileDownload() {
769
+ const urls = /* @__PURE__ */ new Set();
770
+ useEffect(() => {
771
+ return () => {
772
+ urls.forEach((url) => URL.revokeObjectURL(url));
773
+ urls.clear();
774
+ };
775
+ }, []);
776
+ return useMutation({
777
+ mutationFn: async (filePath) => {
778
+ const response = await fetch(`/api/files/download?path=${encodeURIComponent(filePath)}`);
779
+ if (!response.ok) {
780
+ const error = await response.text();
781
+ throw new Error(`Download failed: ${error}`);
782
+ }
783
+ const blob = await response.blob();
784
+ const url = window.URL.createObjectURL(blob);
785
+ urls.add(url);
786
+ const filename = filePath.split("/").pop() || "download";
787
+ const a = document.createElement("a");
788
+ a.href = url;
789
+ a.download = filename;
790
+ document.body.appendChild(a);
791
+ a.click();
792
+ document.body.removeChild(a);
793
+ setTimeout(() => {
794
+ if (urls.has(url)) {
795
+ URL.revokeObjectURL(url);
796
+ urls.delete(url);
797
+ }
798
+ }, 100);
799
+ },
800
+ onError: (error) => {
801
+ console.error("Download failed:", error);
802
+ }
803
+ });
804
+ }
805
+ function useFileUpload() {
806
+ const queryClient = useQueryClient();
807
+ return useMutation({
808
+ mutationFn: async ({ files, path }) => {
809
+ const results = [];
810
+ for (let i = 0; i < files.length; i++) {
811
+ const formData = new FormData();
812
+ formData.append("path", path);
813
+ formData.append("file", files[i]);
814
+ const response = await fetch("/api/files/upload", {
815
+ method: "POST",
816
+ body: formData
817
+ });
818
+ if (!response.ok) {
819
+ const error = await response.text();
820
+ throw new Error(`Upload failed for ${files[i].name}: ${error}`);
821
+ }
822
+ results.push(await response.json());
823
+ }
824
+ return results;
825
+ },
826
+ onSuccess: (_, variables) => {
827
+ void queryClient.invalidateQueries({
828
+ queryKey: fileQueryKeys.listing(variables.path)
829
+ });
830
+ },
831
+ onError: (error) => {
832
+ console.error("Upload failed:", error);
833
+ }
834
+ });
835
+ }
836
+ function useFileDelete() {
837
+ const queryClient = useQueryClient();
838
+ return useMutation({
839
+ mutationFn: async (filePath) => {
840
+ const response = await fetch(`/api/files/delete?path=${encodeURIComponent(filePath)}`, {
841
+ method: "DELETE"
842
+ });
843
+ if (!response.ok) {
844
+ const error = await response.text();
845
+ throw new Error(`Delete failed: ${error}`);
846
+ }
847
+ return response.json();
848
+ },
849
+ onSuccess: (_, filePath) => {
850
+ const parentPath = filePath.split("/").slice(0, -1).join("/") || "/";
851
+ void queryClient.invalidateQueries({
852
+ queryKey: fileQueryKeys.listing(parentPath)
853
+ });
854
+ },
855
+ onError: (error) => {
856
+ console.error("Delete failed:", error);
857
+ }
858
+ });
859
+ }
860
+ function useCreateFolder() {
861
+ const queryClient = useQueryClient();
862
+ return useMutation({
863
+ mutationFn: async ({ path, name }) => {
864
+ const response = await fetch("/api/files/mkdir", {
865
+ method: "POST",
866
+ headers: { "Content-Type": "application/json" },
867
+ body: JSON.stringify({ path: `${path}/${name}` })
868
+ });
869
+ if (!response.ok) {
870
+ const error = await response.text();
871
+ throw new Error(`Create folder failed: ${error}`);
872
+ }
873
+ return response.json();
874
+ },
875
+ onSuccess: (_, variables) => {
876
+ void queryClient.invalidateQueries({
877
+ queryKey: fileQueryKeys.listing(variables.path)
878
+ });
879
+ },
880
+ onError: (error) => {
881
+ console.error("Create folder failed:", error);
882
+ }
883
+ });
884
+ }
885
+ function useFileRename() {
886
+ const queryClient = useQueryClient();
887
+ return useMutation({
888
+ mutationFn: async ({ oldPath, newName }) => {
889
+ const parentPath = oldPath.split("/").slice(0, -1).join("/");
890
+ const newPath = `${parentPath}/${newName}`;
891
+ const response = await fetch("/api/files/rename", {
892
+ method: "POST",
893
+ headers: { "Content-Type": "application/json" },
894
+ body: JSON.stringify({ src: oldPath, dst: newPath })
895
+ });
896
+ if (!response.ok) {
897
+ const error = await response.text();
898
+ throw new Error(`Rename failed: ${error}`);
899
+ }
900
+ return response.json();
901
+ },
902
+ onSuccess: (_, variables) => {
903
+ const parentPath = variables.oldPath.split("/").slice(0, -1).join("/") || "/";
904
+ void queryClient.invalidateQueries({
905
+ queryKey: fileQueryKeys.listing(parentPath)
906
+ });
907
+ },
908
+ onError: (error) => {
909
+ console.error("Rename failed:", error);
910
+ }
911
+ });
912
+ }
913
+ const EDITABLE_EXTENSIONS$1 = /* @__PURE__ */ new Set([
914
+ "txt",
915
+ "md",
916
+ "log",
917
+ "json",
918
+ "xml",
919
+ "yaml",
920
+ "yml",
921
+ "csv",
922
+ "ini",
923
+ "conf",
924
+ "cfg",
925
+ "js",
926
+ "ts",
927
+ "jsx",
928
+ "tsx",
929
+ "py",
930
+ "java",
931
+ "cpp",
932
+ "c",
933
+ "h",
934
+ "css",
935
+ "html",
936
+ "htm",
937
+ "php",
938
+ "rb",
939
+ "go",
940
+ "rs",
941
+ "sh",
942
+ "bash",
943
+ "zsh",
944
+ "toml",
945
+ "env",
946
+ "gitignore",
947
+ "dockerfile",
948
+ "makefile",
949
+ "sql",
950
+ "graphql",
951
+ "svelte",
952
+ "vue",
953
+ "scss",
954
+ "less"
955
+ ]);
956
+ function FileContextMenu({ item, children, onOpenFile }) {
957
+ const [menuOpen, setMenuOpen] = useState(false);
958
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
959
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
960
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
961
+ const [newName, setNewName] = useState(item.name);
962
+ const [renameError, setRenameError] = useState("");
963
+ const renameInputRef = useRef(null);
964
+ const { navigateTo } = useFileExplorerState();
965
+ const downloadMutation = useFileDownload();
966
+ const deleteMutation = useFileDelete();
967
+ const renameMutation = useFileRename();
968
+ const handleAction = useCallback((action) => {
969
+ switch (action) {
970
+ case "open":
971
+ if (item.isDir) {
972
+ navigateTo(item.path);
973
+ }
974
+ break;
975
+ case "edit":
976
+ if (onOpenFile) {
977
+ onOpenFile(item.path);
978
+ }
979
+ break;
980
+ case "download":
981
+ if (!item.isDir) {
982
+ downloadMutation.mutate(item.path);
983
+ }
984
+ break;
985
+ case "rename":
986
+ setNewName(item.name);
987
+ setRenameError("");
988
+ setRenameDialogOpen(true);
989
+ break;
990
+ case "delete":
991
+ setDeleteDialogOpen(true);
992
+ break;
993
+ }
994
+ }, [item, downloadMutation, navigateTo]);
995
+ const handleContextMenu = useCallback((e) => {
996
+ e.preventDefault();
997
+ e.stopPropagation();
998
+ setMenuPosition({ x: e.clientX, y: e.clientY });
999
+ setMenuOpen(true);
1000
+ }, []);
1001
+ const confirmRename = useCallback(async () => {
1002
+ if (!newName || newName === item.name) {
1003
+ setRenameDialogOpen(false);
1004
+ return;
1005
+ }
1006
+ setRenameError("");
1007
+ try {
1008
+ await renameMutation.mutateAsync({
1009
+ oldPath: item.path,
1010
+ newName: newName.trim()
1011
+ });
1012
+ setRenameDialogOpen(false);
1013
+ } catch (error) {
1014
+ const errorMessage = error instanceof Error ? error.message : "Failed to rename";
1015
+ setRenameError(errorMessage);
1016
+ }
1017
+ }, [newName, item, renameMutation]);
1018
+ const confirmDelete = useCallback(async () => {
1019
+ try {
1020
+ await deleteMutation.mutateAsync(item.path);
1021
+ setDeleteDialogOpen(false);
1022
+ } catch (error) {
1023
+ console.error("Delete failed:", error);
1024
+ }
1025
+ }, [item.path, deleteMutation]);
1026
+ const handleRenameKeyDown = useCallback((e) => {
1027
+ if (e.key === "Enter") {
1028
+ e.preventDefault();
1029
+ confirmRename();
1030
+ } else if (e.key === "Escape") {
1031
+ e.preventDefault();
1032
+ setRenameDialogOpen(false);
1033
+ }
1034
+ }, [confirmRename]);
1035
+ const handleRenameInputChange = useCallback((e) => {
1036
+ setNewName(e.target.value);
1037
+ if (renameError) {
1038
+ setRenameError("");
1039
+ }
1040
+ }, [renameError]);
1041
+ useEffect(() => {
1042
+ if (renameDialogOpen && renameInputRef.current) {
1043
+ const input = renameInputRef.current;
1044
+ input.focus();
1045
+ if (!item.isDir && item.extension) {
1046
+ const nameWithoutExt = item.name.replace(new RegExp(`\\.${item.extension}$`), "");
1047
+ input.setSelectionRange(0, nameWithoutExt.length);
1048
+ } else {
1049
+ input.select();
1050
+ }
1051
+ }
1052
+ }, [renameDialogOpen, item]);
1053
+ const isValidRename = newName.trim().length > 0 && !/[<>:"/\\|?*]/.test(newName) && !newName.includes("..") && newName !== item.name;
1054
+ const getDownloadText = () => {
1055
+ return downloadMutation.isPending ? "Downloading…" : "Download";
1056
+ };
1057
+ const getRenameText = () => {
1058
+ return renameMutation.isPending ? "Renaming…" : "Rename";
1059
+ };
1060
+ const getDeleteText = () => {
1061
+ return deleteMutation.isPending ? "Deleting…" : "Delete";
1062
+ };
1063
+ useEffect(() => {
1064
+ if (!menuOpen) return;
1065
+ const handleClose = () => setMenuOpen(false);
1066
+ const handleEsc = (e) => {
1067
+ if (e.key === "Escape") setMenuOpen(false);
1068
+ };
1069
+ const timer = setTimeout(() => {
1070
+ document.addEventListener("click", handleClose);
1071
+ document.addEventListener("contextmenu", handleClose);
1072
+ document.addEventListener("keydown", handleEsc);
1073
+ }, 0);
1074
+ return () => {
1075
+ clearTimeout(timer);
1076
+ document.removeEventListener("click", handleClose);
1077
+ document.removeEventListener("contextmenu", handleClose);
1078
+ document.removeEventListener("keydown", handleEsc);
1079
+ };
1080
+ }, [menuOpen]);
1081
+ const handleMenuItemClick = useCallback((action) => {
1082
+ setMenuOpen(false);
1083
+ handleAction(action);
1084
+ }, [handleAction]);
1085
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1086
+ /* @__PURE__ */ jsx("div", { onContextMenu: handleContextMenu, children }),
1087
+ menuOpen ? /* @__PURE__ */ jsx(
1088
+ "div",
1089
+ {
1090
+ className: "fixed inset-0 z-50",
1091
+ "aria-modal": "true",
1092
+ role: "dialog",
1093
+ children: /* @__PURE__ */ jsxs(
1094
+ "div",
1095
+ {
1096
+ className: "fixed min-w-[140px] rounded-lg bg-primary-50 p-1 text-sm text-primary-900 shadow-lg outline outline-primary-900/10 z-50",
1097
+ style: {
1098
+ left: menuPosition.x,
1099
+ top: menuPosition.y
1100
+ },
1101
+ role: "menu",
1102
+ onClick: (e) => e.stopPropagation(),
1103
+ children: [
1104
+ item.isDir ? /* @__PURE__ */ jsxs(
1105
+ "button",
1106
+ {
1107
+ type: "button",
1108
+ role: "menuitem",
1109
+ onClick: () => handleMenuItemClick("open"),
1110
+ className: cn(
1111
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-primary-900",
1112
+ "hover:bg-primary-100 select-none font-[450]",
1113
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none"
1114
+ ),
1115
+ children: [
1116
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: FolderOpenIcon, size: 16, strokeWidth: 1.5, "aria-hidden": "true" }),
1117
+ "Open"
1118
+ ]
1119
+ }
1120
+ ) : null,
1121
+ !item.isDir ? /* @__PURE__ */ jsxs(
1122
+ "button",
1123
+ {
1124
+ type: "button",
1125
+ role: "menuitem",
1126
+ onClick: () => handleMenuItemClick("download"),
1127
+ disabled: downloadMutation.isPending,
1128
+ className: cn(
1129
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-primary-900",
1130
+ "hover:bg-primary-100 select-none font-[450]",
1131
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none"
1132
+ ),
1133
+ children: [
1134
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Download04Icon, size: 16, strokeWidth: 1.5, "aria-hidden": "true" }),
1135
+ getDownloadText()
1136
+ ]
1137
+ }
1138
+ ) : null,
1139
+ !item.isDir && EDITABLE_EXTENSIONS$1.has(item.extension.toLowerCase()) ? /* @__PURE__ */ jsxs(
1140
+ "button",
1141
+ {
1142
+ type: "button",
1143
+ role: "menuitem",
1144
+ onClick: () => handleMenuItemClick("edit"),
1145
+ className: cn(
1146
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-primary-900",
1147
+ "hover:bg-primary-100 select-none font-[450]",
1148
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none"
1149
+ ),
1150
+ children: [
1151
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: FileEditIcon, size: 16, strokeWidth: 1.5, "aria-hidden": "true" }),
1152
+ "Edit"
1153
+ ]
1154
+ }
1155
+ ) : null,
1156
+ /* @__PURE__ */ jsxs(
1157
+ "button",
1158
+ {
1159
+ type: "button",
1160
+ role: "menuitem",
1161
+ onClick: () => handleMenuItemClick("rename"),
1162
+ className: cn(
1163
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-primary-900",
1164
+ "hover:bg-primary-100 select-none font-[450]",
1165
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none"
1166
+ ),
1167
+ children: [
1168
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Edit02Icon, size: 16, strokeWidth: 1.5, "aria-hidden": "true" }),
1169
+ "Rename"
1170
+ ]
1171
+ }
1172
+ ),
1173
+ /* @__PURE__ */ jsxs(
1174
+ "button",
1175
+ {
1176
+ type: "button",
1177
+ role: "menuitem",
1178
+ onClick: () => handleMenuItemClick("delete"),
1179
+ className: cn(
1180
+ "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm",
1181
+ "text-red-600 hover:bg-red-50 select-none font-[450]",
1182
+ "focus-visible:ring-2 focus-visible:ring-red-300 focus-visible:outline-none"
1183
+ ),
1184
+ children: [
1185
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Delete02Icon, size: 16, strokeWidth: 1.5, "aria-hidden": "true" }),
1186
+ "Delete"
1187
+ ]
1188
+ }
1189
+ )
1190
+ ]
1191
+ }
1192
+ )
1193
+ }
1194
+ ) : null,
1195
+ /* @__PURE__ */ jsx(DialogRoot, { open: renameDialogOpen, onOpenChange: setRenameDialogOpen, children: /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
1196
+ /* @__PURE__ */ jsxs(DialogTitle, { className: "mb-2", children: [
1197
+ "Rename ",
1198
+ item.isDir ? "Folder" : "File"
1199
+ ] }),
1200
+ /* @__PURE__ */ jsxs(DialogDescription, { className: "mb-4", children: [
1201
+ 'Enter a new name for "',
1202
+ item.name,
1203
+ '"'
1204
+ ] }),
1205
+ /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
1206
+ /* @__PURE__ */ jsxs(
1207
+ "label",
1208
+ {
1209
+ htmlFor: "rename-input",
1210
+ className: "block text-sm font-medium text-primary-900 mb-2",
1211
+ children: [
1212
+ item.isDir ? "Folder" : "File",
1213
+ " Name"
1214
+ ]
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ jsx(
1218
+ "input",
1219
+ {
1220
+ id: "rename-input",
1221
+ ref: renameInputRef,
1222
+ type: "text",
1223
+ value: newName,
1224
+ onChange: handleRenameInputChange,
1225
+ onKeyDown: handleRenameKeyDown,
1226
+ autoFocus: true,
1227
+ spellCheck: false,
1228
+ "aria-describedby": renameError ? "rename-error" : void 0,
1229
+ "aria-invalid": renameError ? "true" : "false",
1230
+ className: cn(
1231
+ "w-full px-3 py-2 border rounded-lg",
1232
+ "transition-colors duration-150 ease-out",
1233
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1234
+ "touch-manipulation",
1235
+ renameError ? "border-red-300 focus-visible:ring-red-300" : "border-primary-200 focus:border-primary-300"
1236
+ )
1237
+ }
1238
+ ),
1239
+ renameError ? /* @__PURE__ */ jsx(
1240
+ "p",
1241
+ {
1242
+ id: "rename-error",
1243
+ className: "text-sm text-red-600 mt-1",
1244
+ role: "alert",
1245
+ "aria-live": "polite",
1246
+ children: renameError
1247
+ }
1248
+ ) : null
1249
+ ] }),
1250
+ renameMutation.isPending ? /* @__PURE__ */ jsx("div", { className: "mb-4 p-3 bg-primary-50 rounded-lg", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600", children: "Renaming…" }) }) : null,
1251
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1252
+ /* @__PURE__ */ jsx(
1253
+ DialogClose,
1254
+ {
1255
+ disabled: renameMutation.isPending,
1256
+ className: cn(
1257
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1258
+ "touch-manipulation"
1259
+ ),
1260
+ children: "Cancel"
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ jsx(
1264
+ Button,
1265
+ {
1266
+ onClick: confirmRename,
1267
+ disabled: !isValidRename || renameMutation.isPending,
1268
+ className: cn(
1269
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1270
+ "touch-manipulation",
1271
+ (!isValidRename || renameMutation.isPending) && "opacity-50 cursor-not-allowed"
1272
+ ),
1273
+ children: getRenameText()
1274
+ }
1275
+ )
1276
+ ] })
1277
+ ] }) }) }),
1278
+ /* @__PURE__ */ jsx(DialogRoot, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
1279
+ /* @__PURE__ */ jsxs(DialogTitle, { className: "mb-2", children: [
1280
+ "Delete ",
1281
+ item.isDir ? "Folder" : "File"
1282
+ ] }),
1283
+ /* @__PURE__ */ jsxs(DialogDescription, { className: "mb-4", children: [
1284
+ 'Are you sure you want to delete "',
1285
+ item.name,
1286
+ '"?',
1287
+ item.isDir ? " This will delete the folder and all its contents." : "",
1288
+ " ",
1289
+ "This action cannot be undone."
1290
+ ] }),
1291
+ deleteMutation.isPending ? /* @__PURE__ */ jsx("div", { className: "mb-4 p-3 bg-red-50 rounded-lg", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-red-700", children: "Deleting…" }) }) : null,
1292
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1293
+ /* @__PURE__ */ jsx(
1294
+ DialogClose,
1295
+ {
1296
+ disabled: deleteMutation.isPending,
1297
+ className: cn(
1298
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1299
+ "touch-manipulation"
1300
+ ),
1301
+ children: "Cancel"
1302
+ }
1303
+ ),
1304
+ /* @__PURE__ */ jsx(
1305
+ Button,
1306
+ {
1307
+ variant: "destructive",
1308
+ onClick: confirmDelete,
1309
+ disabled: deleteMutation.isPending,
1310
+ className: cn(
1311
+ "focus-visible:ring-2 focus-visible:ring-red-300 focus-visible:outline-none",
1312
+ "touch-manipulation",
1313
+ deleteMutation.isPending && "opacity-50 cursor-not-allowed"
1314
+ ),
1315
+ children: getDeleteText()
1316
+ }
1317
+ )
1318
+ ] })
1319
+ ] }) }) })
1320
+ ] });
1321
+ }
1322
+ const EDITABLE_EXTENSIONS = /* @__PURE__ */ new Set([
1323
+ "txt",
1324
+ "md",
1325
+ "log",
1326
+ "json",
1327
+ "xml",
1328
+ "yaml",
1329
+ "yml",
1330
+ "csv",
1331
+ "ini",
1332
+ "conf",
1333
+ "cfg",
1334
+ "js",
1335
+ "ts",
1336
+ "jsx",
1337
+ "tsx",
1338
+ "py",
1339
+ "java",
1340
+ "cpp",
1341
+ "c",
1342
+ "h",
1343
+ "css",
1344
+ "html",
1345
+ "htm",
1346
+ "php",
1347
+ "rb",
1348
+ "go",
1349
+ "rs",
1350
+ "sh",
1351
+ "bash",
1352
+ "zsh",
1353
+ "toml",
1354
+ "env",
1355
+ "gitignore",
1356
+ "dockerfile",
1357
+ "makefile",
1358
+ "sql",
1359
+ "graphql",
1360
+ "svelte",
1361
+ "vue",
1362
+ "scss",
1363
+ "less"
1364
+ ]);
1365
+ function formatFileSize$1(bytes) {
1366
+ if (bytes === 0) return "0 B";
1367
+ const k = 1024;
1368
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
1369
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1370
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
1371
+ }
1372
+ function formatRelativeTime(dateString) {
1373
+ const date = new Date(dateString);
1374
+ const now = /* @__PURE__ */ new Date();
1375
+ const diffMs = now.getTime() - date.getTime();
1376
+ const diffSec = Math.floor(diffMs / 1e3);
1377
+ const diffMin = Math.floor(diffSec / 60);
1378
+ const diffHour = Math.floor(diffMin / 60);
1379
+ const diffDay = Math.floor(diffHour / 24);
1380
+ if (diffDay > 7) {
1381
+ return date.toLocaleDateString();
1382
+ } else if (diffDay > 0) {
1383
+ return diffDay === 1 ? "yesterday" : `${diffDay} days ago`;
1384
+ } else if (diffHour > 0) {
1385
+ return diffHour === 1 ? "1 hour ago" : `${diffHour} hours ago`;
1386
+ } else if (diffMin > 0) {
1387
+ return diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`;
1388
+ } else {
1389
+ return "just now";
1390
+ }
1391
+ }
1392
+ function getFileIcon(item) {
1393
+ if (item.isDir) return Folder01Icon;
1394
+ const ext = item.extension.toLowerCase();
1395
+ if (["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].includes(ext)) {
1396
+ return Image01Icon;
1397
+ }
1398
+ if (["mp4", "avi", "mkv", "mov", "wmv", "flv", "webm"].includes(ext)) {
1399
+ return Video01Icon;
1400
+ }
1401
+ if (["mp3", "wav", "flac", "aac", "ogg", "m4a"].includes(ext)) {
1402
+ return MusicNote01Icon;
1403
+ }
1404
+ if (["zip", "rar", "7z", "tar", "gz", "bz2"].includes(ext)) {
1405
+ return Archive01Icon;
1406
+ }
1407
+ if (["txt", "md", "log", "json", "xml", "yaml", "yml"].includes(ext)) {
1408
+ return Doc01Icon;
1409
+ }
1410
+ if (["js", "ts", "jsx", "tsx", "py", "java", "cpp", "c", "h", "css", "html", "php", "rb", "go", "rs"].includes(ext)) {
1411
+ return CodeIcon;
1412
+ }
1413
+ return File01Icon;
1414
+ }
1415
+ function useSortedItems(items, sortBy, sortAsc) {
1416
+ return useMemo(() => {
1417
+ const sorted = [...items].sort((a, b) => {
1418
+ if (a.isDir && !b.isDir) return -1;
1419
+ if (!a.isDir && b.isDir) return 1;
1420
+ let comparison = 0;
1421
+ switch (sortBy) {
1422
+ case "name":
1423
+ comparison = a.name.localeCompare(b.name);
1424
+ break;
1425
+ case "size":
1426
+ comparison = a.size - b.size;
1427
+ break;
1428
+ case "modified":
1429
+ comparison = new Date(a.modified).getTime() - new Date(b.modified).getTime();
1430
+ break;
1431
+ default:
1432
+ comparison = a.name.localeCompare(b.name);
1433
+ }
1434
+ return sortAsc ? comparison : -comparison;
1435
+ });
1436
+ return sorted;
1437
+ }, [items, sortBy, sortAsc]);
1438
+ }
1439
+ function useReducedMotion() {
1440
+ return useMemo(() => {
1441
+ if (typeof window === "undefined") return false;
1442
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1443
+ }, []);
1444
+ }
1445
+ function FileListSkeleton({ viewMode }) {
1446
+ const skeletonCount = viewMode === "grid" ? 12 : 8;
1447
+ if (viewMode === "grid") {
1448
+ return /* @__PURE__ */ jsx(
1449
+ "div",
1450
+ {
1451
+ className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3 p-4",
1452
+ "aria-busy": "true",
1453
+ "aria-label": "Loading files",
1454
+ children: Array.from({ length: skeletonCount }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center p-3 rounded-lg", children: [
1455
+ /* @__PURE__ */ jsx("div", { className: "w-12 h-12 bg-primary-200 rounded animate-pulse mb-2" }),
1456
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-4 bg-primary-200 rounded animate-pulse" })
1457
+ ] }, i))
1458
+ }
1459
+ );
1460
+ }
1461
+ return /* @__PURE__ */ jsx(
1462
+ "div",
1463
+ {
1464
+ className: "divide-y divide-primary-200",
1465
+ "aria-busy": "true",
1466
+ "aria-label": "Loading files",
1467
+ children: Array.from({ length: skeletonCount }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
1468
+ /* @__PURE__ */ jsx("div", { className: "w-6 h-6 bg-primary-200 rounded animate-pulse" }),
1469
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 h-4 bg-primary-200 rounded animate-pulse" }),
1470
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-4 bg-primary-200 rounded animate-pulse" }),
1471
+ /* @__PURE__ */ jsx("div", { className: "w-20 h-4 bg-primary-200 rounded animate-pulse" })
1472
+ ] }, i))
1473
+ }
1474
+ );
1475
+ }
1476
+ const GridItem = memo(function GridItem2({
1477
+ item,
1478
+ isSelected,
1479
+ onClick,
1480
+ onDoubleClick,
1481
+ onKeyDown,
1482
+ onOpenFile,
1483
+ reduceMotion
1484
+ }) {
1485
+ const handleClick = useCallback(() => {
1486
+ onClick(item);
1487
+ }, [onClick, item]);
1488
+ const handleDoubleClick = useCallback(() => {
1489
+ onDoubleClick(item);
1490
+ }, [onDoubleClick, item]);
1491
+ const handleKeyDown = useCallback((e) => {
1492
+ onKeyDown(e, item);
1493
+ }, [onKeyDown, item]);
1494
+ const getItemAriaLabel = () => {
1495
+ if (item.isDir) {
1496
+ return `${item.name}, folder`;
1497
+ }
1498
+ return `${item.name}, ${item.extension} file, ${formatFileSize$1(item.size)}, modified ${formatRelativeTime(item.modified)}`;
1499
+ };
1500
+ const itemComponent = /* @__PURE__ */ jsxs(
1501
+ motion.button,
1502
+ {
1503
+ type: "button",
1504
+ whileHover: reduceMotion ? {} : { scale: 1.02 },
1505
+ whileTap: reduceMotion ? {} : { scale: 0.98 },
1506
+ transition: { duration: reduceMotion ? 0 : 0.1 },
1507
+ onClick: handleClick,
1508
+ onDoubleClick: handleDoubleClick,
1509
+ onKeyDown: handleKeyDown,
1510
+ "aria-label": getItemAriaLabel(),
1511
+ className: cn(
1512
+ "flex flex-col items-center p-3 rounded-lg w-full",
1513
+ "transition-colors duration-150 ease-out",
1514
+ "hover:bg-primary-100 focus-visible:bg-primary-100",
1515
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1516
+ "touch-manipulation",
1517
+ isSelected && "bg-primary-200"
1518
+ ),
1519
+ children: [
1520
+ /* @__PURE__ */ jsx(
1521
+ HugeiconsIcon,
1522
+ {
1523
+ icon: getFileIcon(item),
1524
+ size: 48,
1525
+ strokeWidth: 1,
1526
+ className: cn(
1527
+ "mb-2",
1528
+ item.isDir ? "text-blue-500" : "text-primary-600"
1529
+ ),
1530
+ "aria-hidden": "true"
1531
+ }
1532
+ ),
1533
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-center text-primary-900 font-medium truncate w-full min-w-0", children: item.name }),
1534
+ !item.isDir ? /* @__PURE__ */ jsx("span", { className: "text-xs text-primary-500 mt-1 tabular-nums", children: formatFileSize$1(item.size) }) : null
1535
+ ]
1536
+ }
1537
+ );
1538
+ return /* @__PURE__ */ jsx(FileContextMenu, { item, onOpenFile, children: itemComponent });
1539
+ });
1540
+ const ListItem = memo(function ListItem2({
1541
+ item,
1542
+ isSelected,
1543
+ onClick,
1544
+ onDoubleClick,
1545
+ onKeyDown,
1546
+ onOpenFile,
1547
+ reduceMotion
1548
+ }) {
1549
+ const handleClick = useCallback(() => {
1550
+ onClick(item);
1551
+ }, [onClick, item]);
1552
+ const handleDoubleClick = useCallback(() => {
1553
+ onDoubleClick(item);
1554
+ }, [onDoubleClick, item]);
1555
+ const handleKeyDown = useCallback((e) => {
1556
+ onKeyDown(e, item);
1557
+ }, [onKeyDown, item]);
1558
+ const getItemAriaLabel = () => {
1559
+ if (item.isDir) {
1560
+ return `${item.name}, folder, modified ${formatRelativeTime(item.modified)}`;
1561
+ }
1562
+ return `${item.name}, ${item.extension} file, ${formatFileSize$1(item.size)}, modified ${formatRelativeTime(item.modified)}`;
1563
+ };
1564
+ const itemComponent = /* @__PURE__ */ jsxs(
1565
+ motion.button,
1566
+ {
1567
+ type: "button",
1568
+ whileHover: reduceMotion ? {} : { backgroundColor: "rgb(248 250 252)" },
1569
+ transition: { duration: reduceMotion ? 0 : 0.1 },
1570
+ onClick: handleClick,
1571
+ onDoubleClick: handleDoubleClick,
1572
+ onKeyDown: handleKeyDown,
1573
+ "aria-label": getItemAriaLabel(),
1574
+ className: cn(
1575
+ "w-full grid grid-cols-[auto_1fr_auto_auto] gap-4 px-4 py-3 text-left",
1576
+ "transition-colors duration-150 ease-out",
1577
+ "hover:bg-primary-100 focus-visible:bg-primary-100",
1578
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1579
+ "touch-manipulation",
1580
+ isSelected && "bg-primary-200"
1581
+ ),
1582
+ children: [
1583
+ /* @__PURE__ */ jsx(
1584
+ HugeiconsIcon,
1585
+ {
1586
+ icon: getFileIcon(item),
1587
+ size: 20,
1588
+ strokeWidth: 1.5,
1589
+ className: cn(
1590
+ "mt-0.5 shrink-0",
1591
+ item.isDir ? "text-blue-500" : "text-primary-600"
1592
+ ),
1593
+ "aria-hidden": "true"
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx("span", { className: "text-primary-900 font-medium truncate block", children: item.name }) }),
1597
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-600 text-right w-16 tabular-nums shrink-0", children: item.isDir ? "—" : formatFileSize$1(item.size) }),
1598
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-600 text-right w-20 tabular-nums shrink-0", children: formatRelativeTime(item.modified) })
1599
+ ]
1600
+ }
1601
+ );
1602
+ return /* @__PURE__ */ jsx(FileContextMenu, { item, onOpenFile, children: itemComponent });
1603
+ });
1604
+ const emptyState = /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1605
+ /* @__PURE__ */ jsx(
1606
+ HugeiconsIcon,
1607
+ {
1608
+ icon: Folder01Icon,
1609
+ size: 48,
1610
+ strokeWidth: 1,
1611
+ className: "mx-auto text-primary-400 mb-3",
1612
+ "aria-hidden": "true"
1613
+ }
1614
+ ),
1615
+ /* @__PURE__ */ jsx("p", { className: "text-primary-600 font-medium", children: "This folder is empty" }),
1616
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-500 mt-1", children: "Upload files or create a new folder to get started" })
1617
+ ] }) });
1618
+ function FileList({ listing, loading, onOpenFile }) {
1619
+ const {
1620
+ viewMode,
1621
+ sortBy,
1622
+ sortAsc,
1623
+ selectedFiles,
1624
+ navigateTo,
1625
+ toggleFileSelection
1626
+ } = useFileExplorerState();
1627
+ const [focusedIndex, setFocusedIndex] = useState(0);
1628
+ const containerRef = useRef(null);
1629
+ const reduceMotion = useReducedMotion();
1630
+ const sortedItems = useSortedItems(listing?.items || [], sortBy, sortAsc);
1631
+ const handleItemClick = useCallback((item) => {
1632
+ toggleFileSelection(item.path);
1633
+ }, [toggleFileSelection]);
1634
+ const handleItemDoubleClick = useCallback((item) => {
1635
+ if (item.isDir) {
1636
+ navigateTo(item.path);
1637
+ } else if (onOpenFile && EDITABLE_EXTENSIONS.has(item.extension.toLowerCase())) {
1638
+ onOpenFile(item.path);
1639
+ }
1640
+ }, [navigateTo, onOpenFile]);
1641
+ const handleKeyDown = useCallback((e, item) => {
1642
+ if (e.key === "Enter") {
1643
+ e.preventDefault();
1644
+ if (item.isDir) {
1645
+ navigateTo(item.path);
1646
+ } else {
1647
+ toggleFileSelection(item.path);
1648
+ }
1649
+ } else if (e.key === " ") {
1650
+ e.preventDefault();
1651
+ toggleFileSelection(item.path);
1652
+ }
1653
+ }, [navigateTo, toggleFileSelection]);
1654
+ useEffect(() => {
1655
+ const handleKeyDown2 = (e) => {
1656
+ if (!sortedItems.length) return;
1657
+ switch (e.key) {
1658
+ case "ArrowDown":
1659
+ e.preventDefault();
1660
+ setFocusedIndex((prev) => Math.min(prev + 1, sortedItems.length - 1));
1661
+ break;
1662
+ case "ArrowUp":
1663
+ e.preventDefault();
1664
+ setFocusedIndex((prev) => Math.max(prev - 1, 0));
1665
+ break;
1666
+ case "Home":
1667
+ e.preventDefault();
1668
+ setFocusedIndex(0);
1669
+ break;
1670
+ case "End":
1671
+ e.preventDefault();
1672
+ setFocusedIndex(sortedItems.length - 1);
1673
+ break;
1674
+ }
1675
+ };
1676
+ if (containerRef.current) {
1677
+ containerRef.current.addEventListener("keydown", handleKeyDown2);
1678
+ return () => {
1679
+ containerRef.current?.removeEventListener("keydown", handleKeyDown2);
1680
+ };
1681
+ }
1682
+ }, [sortedItems.length]);
1683
+ if (loading) {
1684
+ return /* @__PURE__ */ jsx(FileListSkeleton, { viewMode });
1685
+ }
1686
+ if (!listing) {
1687
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("p", { className: "text-primary-500", children: "No listing available" }) });
1688
+ }
1689
+ if (sortedItems.length === 0) {
1690
+ return emptyState;
1691
+ }
1692
+ if (viewMode === "grid") {
1693
+ return /* @__PURE__ */ jsx(
1694
+ "div",
1695
+ {
1696
+ ref: containerRef,
1697
+ className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3 p-4 overflow-y-auto",
1698
+ role: "grid",
1699
+ "aria-label": "Files grid",
1700
+ tabIndex: 0,
1701
+ children: sortedItems.map((item, index) => /* @__PURE__ */ jsx("div", { role: "gridcell", children: /* @__PURE__ */ jsx(
1702
+ GridItem,
1703
+ {
1704
+ item,
1705
+ isSelected: selectedFiles.has(item.path),
1706
+ onClick: handleItemClick,
1707
+ onDoubleClick: handleItemDoubleClick,
1708
+ onKeyDown: handleKeyDown,
1709
+ onOpenFile,
1710
+ reduceMotion
1711
+ }
1712
+ ) }, item.path))
1713
+ }
1714
+ );
1715
+ }
1716
+ return /* @__PURE__ */ jsx(
1717
+ "div",
1718
+ {
1719
+ ref: containerRef,
1720
+ className: "overflow-y-auto",
1721
+ role: "table",
1722
+ "aria-label": "Files list",
1723
+ tabIndex: 0,
1724
+ children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-primary-200", children: [
1725
+ /* @__PURE__ */ jsxs(
1726
+ "div",
1727
+ {
1728
+ className: "grid grid-cols-[auto_1fr_auto_auto] gap-4 px-4 py-2 text-sm font-medium text-primary-600 bg-primary-50",
1729
+ role: "row",
1730
+ children: [
1731
+ /* @__PURE__ */ jsx("div", { className: "w-6", role: "columnheader", "aria-label": "File type" }),
1732
+ /* @__PURE__ */ jsx("div", { role: "columnheader", children: "Name" }),
1733
+ /* @__PURE__ */ jsx("div", { className: "w-16 text-right tabular-nums", role: "columnheader", children: "Size" }),
1734
+ /* @__PURE__ */ jsx("div", { className: "w-20 text-right tabular-nums", role: "columnheader", children: "Modified" })
1735
+ ]
1736
+ }
1737
+ ),
1738
+ sortedItems.map((item, index) => /* @__PURE__ */ jsx("div", { role: "row", children: /* @__PURE__ */ jsx(
1739
+ ListItem,
1740
+ {
1741
+ item,
1742
+ isSelected: selectedFiles.has(item.path),
1743
+ onClick: handleItemClick,
1744
+ onDoubleClick: handleItemDoubleClick,
1745
+ onKeyDown: handleKeyDown,
1746
+ onOpenFile,
1747
+ reduceMotion
1748
+ }
1749
+ ) }, item.path))
1750
+ ] })
1751
+ }
1752
+ );
1753
+ }
1754
+ function formatFileSize(bytes) {
1755
+ if (bytes === 0) return "0 B";
1756
+ const k = 1024;
1757
+ const sizes = ["B", "KB", "MB", "GB"];
1758
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1759
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
1760
+ }
1761
+ function FileUploadDialog({ open, onOpenChange, currentPath }) {
1762
+ const [selectedFiles, setSelectedFiles] = useState([]);
1763
+ const [dragActive, setDragActive] = useState(false);
1764
+ const [uploadStatus, setUploadStatus] = useState("idle");
1765
+ const fileInputRef = useRef(null);
1766
+ const uploadMutation = useFileUpload();
1767
+ const handleReset = useCallback(() => {
1768
+ setSelectedFiles([]);
1769
+ setUploadStatus("idle");
1770
+ if (fileInputRef.current) {
1771
+ fileInputRef.current.value = "";
1772
+ }
1773
+ }, []);
1774
+ const handleFilesSelected = useCallback((files) => {
1775
+ if (!files) return;
1776
+ const newFiles = Array.from(files).map((file) => ({
1777
+ file,
1778
+ id: Math.random().toString(36).substr(2, 9)
1779
+ }));
1780
+ setSelectedFiles((prev) => [...prev, ...newFiles]);
1781
+ }, []);
1782
+ const handleFileInputChange = useCallback((e) => {
1783
+ handleFilesSelected(e.target.files);
1784
+ }, [handleFilesSelected]);
1785
+ const handleDrop = useCallback((e) => {
1786
+ e.preventDefault();
1787
+ setDragActive(false);
1788
+ handleFilesSelected(e.dataTransfer.files);
1789
+ }, [handleFilesSelected]);
1790
+ const handleDragOver = useCallback((e) => {
1791
+ e.preventDefault();
1792
+ setDragActive(true);
1793
+ }, []);
1794
+ const handleDragLeave = useCallback((e) => {
1795
+ e.preventDefault();
1796
+ setDragActive(false);
1797
+ }, []);
1798
+ const handleRemoveFile = useCallback((id) => {
1799
+ setSelectedFiles((prev) => prev.filter((f) => f.id !== id));
1800
+ }, []);
1801
+ const handleChooseFiles = useCallback(() => {
1802
+ fileInputRef.current?.click();
1803
+ }, []);
1804
+ const handleDropZoneKeyDown = useCallback((e) => {
1805
+ if (e.key === "Enter" || e.key === " ") {
1806
+ e.preventDefault();
1807
+ handleChooseFiles();
1808
+ }
1809
+ }, [handleChooseFiles]);
1810
+ const handleUpload = useCallback(async () => {
1811
+ if (selectedFiles.length === 0) return;
1812
+ const fileList = new DataTransfer();
1813
+ selectedFiles.forEach(({ file }) => {
1814
+ fileList.items.add(file);
1815
+ });
1816
+ setUploadStatus("uploading");
1817
+ try {
1818
+ await uploadMutation.mutateAsync({
1819
+ files: fileList.files,
1820
+ path: currentPath
1821
+ });
1822
+ setUploadStatus("success");
1823
+ handleReset();
1824
+ onOpenChange(false);
1825
+ } catch (error) {
1826
+ setUploadStatus("error");
1827
+ console.error("Upload failed:", error);
1828
+ }
1829
+ }, [selectedFiles, currentPath, uploadMutation, handleReset, onOpenChange]);
1830
+ const handleOpenChange = useCallback((newOpen) => {
1831
+ if (!newOpen) {
1832
+ handleReset();
1833
+ }
1834
+ onOpenChange(newOpen);
1835
+ }, [handleReset, onOpenChange]);
1836
+ const totalSize = selectedFiles.reduce((total, { file }) => total + file.size, 0);
1837
+ const fileCount = selectedFiles.length;
1838
+ const getUploadButtonText = () => {
1839
+ if (uploadMutation.isPending || uploadStatus === "uploading") {
1840
+ return "Uploading…";
1841
+ }
1842
+ if (fileCount === 0) {
1843
+ return "Upload";
1844
+ }
1845
+ return fileCount === 1 ? "Upload 1 File" : `Upload ${fileCount} Files`;
1846
+ };
1847
+ return /* @__PURE__ */ jsx(DialogRoot, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { className: "w-[min(500px,92vw)]", children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
1848
+ /* @__PURE__ */ jsx(DialogTitle, { className: "mb-2", children: "Upload Files" }),
1849
+ /* @__PURE__ */ jsxs(DialogDescription, { className: "mb-6", children: [
1850
+ "Upload files to ",
1851
+ currentPath === "/" ? "root directory" : currentPath
1852
+ ] }),
1853
+ /* @__PURE__ */ jsxs(
1854
+ "div",
1855
+ {
1856
+ onDrop: handleDrop,
1857
+ onDragOver: handleDragOver,
1858
+ onDragLeave: handleDragLeave,
1859
+ onClick: handleChooseFiles,
1860
+ onKeyDown: handleDropZoneKeyDown,
1861
+ tabIndex: 0,
1862
+ role: "button",
1863
+ "aria-label": "Drop files here or click to choose files",
1864
+ className: cn(
1865
+ "border-2 border-dashed rounded-lg p-8 text-center cursor-pointer",
1866
+ "transition-colors duration-150 ease-out",
1867
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1868
+ "touch-manipulation",
1869
+ dragActive ? "border-primary-400 bg-primary-50" : "border-primary-200 hover:border-primary-300 hover:bg-primary-50"
1870
+ ),
1871
+ children: [
1872
+ /* @__PURE__ */ jsx(
1873
+ HugeiconsIcon,
1874
+ {
1875
+ icon: Upload04Icon,
1876
+ size: 48,
1877
+ strokeWidth: 1,
1878
+ className: "mx-auto text-primary-400 mb-4",
1879
+ "aria-hidden": "true"
1880
+ }
1881
+ ),
1882
+ /* @__PURE__ */ jsx("p", { className: "text-primary-600 font-medium mb-2", children: "Drag and drop files here" }),
1883
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-500 mb-4", children: "or" }),
1884
+ /* @__PURE__ */ jsxs(
1885
+ Button,
1886
+ {
1887
+ variant: "outline",
1888
+ className: cn(
1889
+ "gap-2",
1890
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1891
+ "touch-manipulation"
1892
+ ),
1893
+ "aria-label": "Choose files from computer",
1894
+ children: [
1895
+ /* @__PURE__ */ jsx(
1896
+ HugeiconsIcon,
1897
+ {
1898
+ icon: File01Icon,
1899
+ size: 18,
1900
+ strokeWidth: 1.5,
1901
+ "aria-hidden": "true"
1902
+ }
1903
+ ),
1904
+ /* @__PURE__ */ jsx("span", { children: "Choose Files" })
1905
+ ]
1906
+ }
1907
+ ),
1908
+ /* @__PURE__ */ jsx(
1909
+ "input",
1910
+ {
1911
+ ref: fileInputRef,
1912
+ type: "file",
1913
+ multiple: true,
1914
+ onChange: handleFileInputChange,
1915
+ className: "hidden",
1916
+ "aria-hidden": "true"
1917
+ }
1918
+ )
1919
+ ]
1920
+ }
1921
+ ),
1922
+ /* @__PURE__ */ jsx(AnimatePresence, { children: selectedFiles.length > 0 ? /* @__PURE__ */ jsxs(
1923
+ motion.div,
1924
+ {
1925
+ initial: { opacity: 0, height: 0 },
1926
+ animate: { opacity: 1, height: "auto" },
1927
+ exit: { opacity: 0, height: 0 },
1928
+ className: "mt-6",
1929
+ children: [
1930
+ /* @__PURE__ */ jsxs("h4", { className: "text-sm font-medium text-primary-900 mb-3", children: [
1931
+ "Selected Files (",
1932
+ selectedFiles.length,
1933
+ ")"
1934
+ ] }),
1935
+ /* @__PURE__ */ jsx("div", { className: "max-h-32 overflow-y-auto border border-primary-200 rounded-lg", children: selectedFiles.map(({ file, id }) => /* @__PURE__ */ jsxs(
1936
+ motion.div,
1937
+ {
1938
+ initial: { opacity: 0, x: -10 },
1939
+ animate: { opacity: 1, x: 0 },
1940
+ exit: { opacity: 0, x: 10 },
1941
+ className: "flex items-center justify-between p-2 border-b border-primary-100 last:border-b-0",
1942
+ children: [
1943
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [
1944
+ /* @__PURE__ */ jsx(
1945
+ HugeiconsIcon,
1946
+ {
1947
+ icon: File01Icon,
1948
+ size: 16,
1949
+ strokeWidth: 1.5,
1950
+ className: "text-primary-600 shrink-0",
1951
+ "aria-hidden": "true"
1952
+ }
1953
+ ),
1954
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-primary-900 truncate min-w-0", children: file.name }),
1955
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-primary-500 shrink-0 tabular-nums", children: formatFileSize(file.size) })
1956
+ ] }),
1957
+ /* @__PURE__ */ jsx(
1958
+ Button,
1959
+ {
1960
+ size: "icon-sm",
1961
+ variant: "ghost",
1962
+ onClick: () => handleRemoveFile(id),
1963
+ className: cn(
1964
+ "text-primary-500 hover:text-red-600 shrink-0",
1965
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
1966
+ "touch-manipulation"
1967
+ ),
1968
+ "aria-label": `Remove ${file.name} from upload list`,
1969
+ children: /* @__PURE__ */ jsx(
1970
+ HugeiconsIcon,
1971
+ {
1972
+ icon: Delete02Icon,
1973
+ size: 14,
1974
+ strokeWidth: 1.5,
1975
+ "aria-hidden": "true"
1976
+ }
1977
+ )
1978
+ }
1979
+ )
1980
+ ]
1981
+ },
1982
+ id
1983
+ )) }),
1984
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-primary-600 mt-2 tabular-nums", children: [
1985
+ "Total size: ",
1986
+ formatFileSize(totalSize)
1987
+ ] })
1988
+ ]
1989
+ }
1990
+ ) : null }),
1991
+ uploadMutation.isPending || uploadStatus === "uploading" ? /* @__PURE__ */ jsxs(
1992
+ motion.div,
1993
+ {
1994
+ initial: { opacity: 0 },
1995
+ animate: { opacity: 1 },
1996
+ className: "mt-4 p-3 bg-primary-50 rounded-lg",
1997
+ "aria-live": "polite",
1998
+ children: [
1999
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600 mb-2", children: "Uploading files…" }),
2000
+ /* @__PURE__ */ jsx("div", { className: "bg-primary-200 rounded-full h-2", role: "progressbar", "aria-label": "Upload progress", children: /* @__PURE__ */ jsx("div", { className: "bg-primary-600 h-2 rounded-full w-1/2 animate-pulse" }) })
2001
+ ]
2002
+ }
2003
+ ) : null,
2004
+ uploadStatus === "success" ? /* @__PURE__ */ jsx("div", { className: "mt-4 p-3 bg-green-50 rounded-lg", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-green-700", children: "Files uploaded successfully!" }) }) : null,
2005
+ uploadStatus === "error" ? /* @__PURE__ */ jsx("div", { className: "mt-4 p-3 bg-red-50 rounded-lg", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-red-700", children: "Upload failed. Please try again." }) }) : null,
2006
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 mt-6", children: [
2007
+ /* @__PURE__ */ jsx(
2008
+ DialogClose,
2009
+ {
2010
+ disabled: uploadMutation.isPending || uploadStatus === "uploading",
2011
+ className: cn(
2012
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
2013
+ "touch-manipulation"
2014
+ ),
2015
+ children: "Cancel"
2016
+ }
2017
+ ),
2018
+ /* @__PURE__ */ jsx(
2019
+ Button,
2020
+ {
2021
+ onClick: handleUpload,
2022
+ disabled: selectedFiles.length === 0 || uploadMutation.isPending || uploadStatus === "uploading",
2023
+ className: cn(
2024
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
2025
+ "touch-manipulation"
2026
+ ),
2027
+ children: getUploadButtonText()
2028
+ }
2029
+ )
2030
+ ] })
2031
+ ] }) }) });
2032
+ }
2033
+ function CreateFolderDialog({ open, onOpenChange, currentPath }) {
2034
+ const [folderName, setFolderName] = useState("");
2035
+ const [error, setError] = useState("");
2036
+ const createFolderMutation = useCreateFolder();
2037
+ const handleCreate = useCallback(async () => {
2038
+ if (!folderName.trim()) return;
2039
+ setError("");
2040
+ try {
2041
+ await createFolderMutation.mutateAsync({
2042
+ path: currentPath,
2043
+ name: folderName.trim()
2044
+ });
2045
+ setFolderName("");
2046
+ onOpenChange(false);
2047
+ } catch (error2) {
2048
+ const errorMessage = error2 instanceof Error ? error2.message : "Failed to create folder";
2049
+ setError(errorMessage);
2050
+ console.error("Create folder failed:", error2);
2051
+ }
2052
+ }, [folderName, currentPath, createFolderMutation, onOpenChange]);
2053
+ const handleOpenChange = useCallback((newOpen) => {
2054
+ if (!newOpen) {
2055
+ setFolderName("");
2056
+ setError("");
2057
+ }
2058
+ onOpenChange(newOpen);
2059
+ }, [onOpenChange]);
2060
+ const handleKeyDown = useCallback((e) => {
2061
+ if (e.key === "Enter") {
2062
+ e.preventDefault();
2063
+ handleCreate();
2064
+ }
2065
+ }, [handleCreate]);
2066
+ const handleInputChange = useCallback((e) => {
2067
+ const value = e.target.value;
2068
+ setFolderName(value);
2069
+ if (error) {
2070
+ setError("");
2071
+ }
2072
+ }, [error]);
2073
+ const isValidName = folderName.trim().length > 0 && !/[<>:"/\\|?*]/.test(folderName) && !folderName.includes("..");
2074
+ const getButtonText = () => {
2075
+ if (createFolderMutation.isPending) {
2076
+ return "Creating…";
2077
+ }
2078
+ return "Create Folder";
2079
+ };
2080
+ const getValidationError = () => {
2081
+ if (!folderName.trim()) {
2082
+ return "";
2083
+ }
2084
+ if (folderName.includes("..")) {
2085
+ return 'Folder name cannot contain ".."';
2086
+ }
2087
+ if (/[<>:"/\\|?*]/.test(folderName)) {
2088
+ return 'Invalid characters: < > : " / \\ | ? *';
2089
+ }
2090
+ return "";
2091
+ };
2092
+ const validationError = getValidationError();
2093
+ const showError = error || validationError;
2094
+ return /* @__PURE__ */ jsx(DialogRoot, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
2095
+ /* @__PURE__ */ jsx(DialogTitle, { className: "mb-2", children: "Create New Folder" }),
2096
+ /* @__PURE__ */ jsxs(DialogDescription, { className: "mb-4", children: [
2097
+ "Create a new folder in ",
2098
+ currentPath === "/" ? "root directory" : currentPath
2099
+ ] }),
2100
+ /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
2101
+ /* @__PURE__ */ jsx(
2102
+ "label",
2103
+ {
2104
+ htmlFor: "folder-name",
2105
+ className: "block text-sm font-medium text-primary-900 mb-2",
2106
+ children: "Folder Name"
2107
+ }
2108
+ ),
2109
+ /* @__PURE__ */ jsx(
2110
+ "input",
2111
+ {
2112
+ id: "folder-name",
2113
+ type: "text",
2114
+ value: folderName,
2115
+ onChange: handleInputChange,
2116
+ onKeyDown: handleKeyDown,
2117
+ placeholder: "Enter folder name",
2118
+ autoComplete: "off",
2119
+ spellCheck: false,
2120
+ autoFocus: true,
2121
+ "aria-describedby": showError ? "folder-name-error" : void 0,
2122
+ "aria-invalid": showError ? "true" : "false",
2123
+ className: cn(
2124
+ "w-full px-3 py-2 border rounded-lg",
2125
+ "transition-colors duration-150 ease-out",
2126
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
2127
+ "touch-manipulation",
2128
+ showError ? "border-red-300 focus-visible:ring-red-300" : "border-primary-200 focus:border-primary-300"
2129
+ )
2130
+ }
2131
+ ),
2132
+ showError ? /* @__PURE__ */ jsx(
2133
+ "p",
2134
+ {
2135
+ id: "folder-name-error",
2136
+ className: "text-sm text-red-600 mt-1",
2137
+ role: "alert",
2138
+ "aria-live": "polite",
2139
+ children: error || validationError
2140
+ }
2141
+ ) : null
2142
+ ] }),
2143
+ createFolderMutation.isPending ? /* @__PURE__ */ jsx("div", { className: "mb-4 p-3 bg-primary-50 rounded-lg", "aria-live": "polite", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600", children: "Creating folder…" }) }) : null,
2144
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2145
+ /* @__PURE__ */ jsx(
2146
+ DialogClose,
2147
+ {
2148
+ disabled: createFolderMutation.isPending,
2149
+ className: cn(
2150
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
2151
+ "touch-manipulation"
2152
+ ),
2153
+ children: "Cancel"
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ jsx(
2157
+ Button,
2158
+ {
2159
+ onClick: handleCreate,
2160
+ disabled: !isValidName || createFolderMutation.isPending,
2161
+ className: cn(
2162
+ "focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none",
2163
+ "touch-manipulation",
2164
+ (!isValidName || createFolderMutation.isPending) && "opacity-50 cursor-not-allowed"
2165
+ ),
2166
+ "aria-describedby": !isValidName ? "create-button-help" : void 0,
2167
+ children: getButtonText()
2168
+ }
2169
+ )
2170
+ ] }),
2171
+ !isValidName && folderName.length > 0 ? /* @__PURE__ */ jsx(
2172
+ "p",
2173
+ {
2174
+ id: "create-button-help",
2175
+ className: "text-xs text-primary-500 mt-2",
2176
+ children: "Please enter a valid folder name"
2177
+ }
2178
+ ) : null
2179
+ ] }) }) });
2180
+ }
2181
+ const LANGUAGE_MAP = {
2182
+ js: "JavaScript",
2183
+ ts: "TypeScript",
2184
+ jsx: "JSX",
2185
+ tsx: "TSX",
2186
+ py: "Python",
2187
+ json: "JSON",
2188
+ yaml: "YAML",
2189
+ yml: "YAML",
2190
+ md: "Markdown",
2191
+ html: "HTML",
2192
+ css: "CSS",
2193
+ scss: "SCSS",
2194
+ sh: "Shell",
2195
+ bash: "Bash",
2196
+ sql: "SQL",
2197
+ xml: "XML",
2198
+ toml: "TOML",
2199
+ ini: "INI",
2200
+ conf: "Config",
2201
+ cfg: "Config",
2202
+ env: "Env",
2203
+ txt: "Text",
2204
+ log: "Log",
2205
+ csv: "CSV",
2206
+ go: "Go",
2207
+ rs: "Rust",
2208
+ rb: "Ruby",
2209
+ php: "PHP",
2210
+ java: "Java",
2211
+ c: "C",
2212
+ cpp: "C++",
2213
+ h: "Header",
2214
+ dockerfile: "Dockerfile",
2215
+ makefile: "Makefile",
2216
+ graphql: "GraphQL",
2217
+ svelte: "Svelte",
2218
+ vue: "Vue"
2219
+ };
2220
+ function FileEditor({ filePath, onClose }) {
2221
+ const contentQuery = useFileContent(filePath);
2222
+ const saveMutation = useFileSave();
2223
+ const textareaRef = useRef(null);
2224
+ const [editedContent, setEditedContent] = useState(null);
2225
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
2226
+ useEffect(() => {
2227
+ if (contentQuery.data && editedContent === null) {
2228
+ setEditedContent(contentQuery.data.content);
2229
+ }
2230
+ }, [contentQuery.data, editedContent]);
2231
+ const language = useMemo(() => {
2232
+ if (!contentQuery.data) return "";
2233
+ return LANGUAGE_MAP[contentQuery.data.extension.toLowerCase()] || contentQuery.data.extension.toUpperCase();
2234
+ }, [contentQuery.data]);
2235
+ const handleChange = useCallback((e) => {
2236
+ setEditedContent(e.target.value);
2237
+ setHasUnsavedChanges(e.target.value !== contentQuery.data?.content);
2238
+ }, [contentQuery.data]);
2239
+ const handleSave = useCallback(async () => {
2240
+ if (editedContent === null) return;
2241
+ try {
2242
+ await saveMutation.mutateAsync({ path: filePath, content: editedContent });
2243
+ setHasUnsavedChanges(false);
2244
+ } catch {
2245
+ }
2246
+ }, [filePath, editedContent, saveMutation]);
2247
+ const handleClose = useCallback(() => {
2248
+ if (hasUnsavedChanges) {
2249
+ if (!window.confirm("You have unsaved changes. Discard them?")) return;
2250
+ }
2251
+ onClose();
2252
+ }, [hasUnsavedChanges, onClose]);
2253
+ useEffect(() => {
2254
+ const handleKeyDown2 = (e) => {
2255
+ if ((e.ctrlKey || e.metaKey) && e.key === "s") {
2256
+ e.preventDefault();
2257
+ if (hasUnsavedChanges) handleSave();
2258
+ }
2259
+ if (e.key === "Escape") {
2260
+ handleClose();
2261
+ }
2262
+ };
2263
+ window.addEventListener("keydown", handleKeyDown2);
2264
+ return () => window.removeEventListener("keydown", handleKeyDown2);
2265
+ }, [handleSave, handleClose, hasUnsavedChanges]);
2266
+ const handleKeyDown = useCallback((e) => {
2267
+ if (e.key === "Tab") {
2268
+ e.preventDefault();
2269
+ const textarea = e.currentTarget;
2270
+ const start = textarea.selectionStart;
2271
+ const end = textarea.selectionEnd;
2272
+ const value = textarea.value;
2273
+ const newValue = value.substring(0, start) + " " + value.substring(end);
2274
+ setEditedContent(newValue);
2275
+ setHasUnsavedChanges(newValue !== contentQuery.data?.content);
2276
+ requestAnimationFrame(() => {
2277
+ textarea.selectionStart = textarea.selectionEnd = start + 2;
2278
+ });
2279
+ }
2280
+ }, [contentQuery.data]);
2281
+ const fileName = filePath.split("/").pop() || filePath;
2282
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 bg-surface flex flex-col", children: [
2283
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between px-4 py-2 border-b border-primary-200 bg-primary-50", children: [
2284
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
2285
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-primary-900 truncate", children: fileName }),
2286
+ language ? /* @__PURE__ */ jsx("span", { className: "text-xs text-primary-500 bg-primary-100 px-2 py-0.5 rounded-full shrink-0", children: language }) : null,
2287
+ hasUnsavedChanges ? /* @__PURE__ */ jsx("span", { className: "text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded-full shrink-0", children: "Unsaved" }) : null,
2288
+ saveMutation.isError ? /* @__PURE__ */ jsx("span", { className: "text-xs text-red-600", children: saveMutation.error.message }) : null
2289
+ ] }),
2290
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
2291
+ /* @__PURE__ */ jsxs(
2292
+ Button,
2293
+ {
2294
+ size: "sm",
2295
+ variant: "default",
2296
+ onClick: handleSave,
2297
+ disabled: !hasUnsavedChanges || saveMutation.isPending,
2298
+ className: cn(
2299
+ (!hasUnsavedChanges || saveMutation.isPending) && "opacity-50 cursor-not-allowed"
2300
+ ),
2301
+ children: [
2302
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: FloppyDiskIcon, size: 16, strokeWidth: 1.5, "aria-hidden": "true" }),
2303
+ saveMutation.isPending ? "Saving…" : "Save"
2304
+ ]
2305
+ }
2306
+ ),
2307
+ /* @__PURE__ */ jsx(Button, { size: "icon-sm", variant: "ghost", onClick: handleClose, "aria-label": "Close editor", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Cancel01Icon, size: 20, strokeWidth: 1.5, "aria-hidden": "true" }) })
2308
+ ] })
2309
+ ] }),
2310
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: contentQuery.isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("p", { className: "text-primary-500 text-sm", children: "Loading file…" }) }) : contentQuery.isError ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2311
+ /* @__PURE__ */ jsx("p", { className: "text-red-600 text-sm mb-2", children: contentQuery.error instanceof Error ? contentQuery.error.message : "Failed to load file" }),
2312
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: () => void contentQuery.refetch(), children: "Retry" })
2313
+ ] }) }) : /* @__PURE__ */ jsx(
2314
+ "textarea",
2315
+ {
2316
+ ref: textareaRef,
2317
+ value: editedContent ?? "",
2318
+ onChange: handleChange,
2319
+ onKeyDown: handleKeyDown,
2320
+ spellCheck: false,
2321
+ autoFocus: true,
2322
+ className: cn(
2323
+ "w-full h-full p-4 resize-none",
2324
+ "bg-primary-950 text-primary-100",
2325
+ "font-mono text-sm leading-relaxed",
2326
+ "focus:outline-none",
2327
+ "selection:bg-blue-800/50"
2328
+ )
2329
+ }
2330
+ ) }),
2331
+ contentQuery.data ? /* @__PURE__ */ jsxs("footer", { className: "flex items-center justify-between px-4 py-1 border-t border-primary-200 bg-primary-50 text-xs text-primary-500", children: [
2332
+ /* @__PURE__ */ jsx("span", { children: filePath }),
2333
+ /* @__PURE__ */ jsx("span", { children: formatSize(contentQuery.data.size) })
2334
+ ] }) : null
2335
+ ] });
2336
+ }
2337
+ function formatSize(bytes) {
2338
+ if (bytes < 1024) return `${bytes} B`;
2339
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2340
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2341
+ }
2342
+ function FileExplorerScreen() {
2343
+ const queryClient = useQueryClient();
2344
+ const { currentPath } = useFileExplorerState();
2345
+ const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
2346
+ const [createFolderDialogOpen, setCreateFolderDialogOpen] = useState(false);
2347
+ const [editingFile, setEditingFile] = useState(null);
2348
+ const listingQuery = useFileListing(currentPath);
2349
+ const uiQuery = useQuery({
2350
+ queryKey: chatUiQueryKey,
2351
+ queryFn: function readUiState() {
2352
+ return getChatUiState(queryClient);
2353
+ },
2354
+ initialData: function initialUiState() {
2355
+ return getChatUiState(queryClient);
2356
+ },
2357
+ staleTime: Infinity
2358
+ });
2359
+ const isSidebarCollapsed = uiQuery.data?.isSidebarCollapsed ?? false;
2360
+ const handleToggleSidebarCollapse = useCallback(() => {
2361
+ setChatUiState(queryClient, function toggle(state) {
2362
+ return { ...state, isSidebarCollapsed: !state.isSidebarCollapsed };
2363
+ });
2364
+ }, [queryClient]);
2365
+ const handleUploadDialogOpen = useCallback(() => {
2366
+ setUploadDialogOpen(true);
2367
+ }, []);
2368
+ const handleCreateFolderDialogOpen = useCallback(() => {
2369
+ setCreateFolderDialogOpen(true);
2370
+ }, []);
2371
+ const handleOpenFile = useCallback((filePath) => {
2372
+ setEditingFile(filePath);
2373
+ }, []);
2374
+ const handleCloseEditor = useCallback(() => {
2375
+ setEditingFile(null);
2376
+ }, []);
2377
+ const mainStyles = useMemo(() => ({
2378
+ display: "flex",
2379
+ flexDirection: "column",
2380
+ height: "100%",
2381
+ minHeight: 0
2382
+ }), []);
2383
+ const sidebar = /* @__PURE__ */ jsx(
2384
+ MemoizedFilesSidebar,
2385
+ {
2386
+ isCollapsed: isSidebarCollapsed,
2387
+ onToggleCollapse: handleToggleSidebarCollapse,
2388
+ onSelectAction: () => {
2389
+ }
2390
+ }
2391
+ );
2392
+ return /* @__PURE__ */ jsxs("div", { className: "h-screen bg-surface text-primary-900", children: [
2393
+ /* @__PURE__ */ jsxs("div", { className: cn("h-full overflow-hidden grid grid-cols-[auto_1fr]"), children: [
2394
+ sidebar,
2395
+ /* @__PURE__ */ jsxs("main", { "aria-label": "File explorer", style: mainStyles, children: [
2396
+ /* @__PURE__ */ jsxs("header", { className: "border-b border-primary-200 px-4 py-3", children: [
2397
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-2", children: /* @__PURE__ */ jsx(FileBreadcrumb, { path: currentPath }) }),
2398
+ /* @__PURE__ */ jsx(
2399
+ FileToolbar,
2400
+ {
2401
+ onUpload: handleUploadDialogOpen,
2402
+ onCreateFolder: handleCreateFolderDialogOpen
2403
+ }
2404
+ )
2405
+ ] }),
2406
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-auto", children: [
2407
+ /* @__PURE__ */ jsx(
2408
+ FileList,
2409
+ {
2410
+ listing: listingQuery.data,
2411
+ loading: listingQuery.isLoading,
2412
+ onOpenFile: handleOpenFile
2413
+ }
2414
+ ),
2415
+ listingQuery.isError ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center", "aria-live": "polite", children: [
2416
+ /* @__PURE__ */ jsx("p", { className: "text-red-600 text-sm", children: listingQuery.error instanceof Error ? listingQuery.error.message : "Failed to load files" }),
2417
+ /* @__PURE__ */ jsx(
2418
+ "button",
2419
+ {
2420
+ onClick: () => void listingQuery.refetch(),
2421
+ className: "mt-2 text-sm text-primary-600 hover:text-primary-900 underline focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:outline-none rounded-sm",
2422
+ children: "Retry"
2423
+ }
2424
+ )
2425
+ ] }) : null
2426
+ ] }),
2427
+ /* @__PURE__ */ jsx(
2428
+ FileUploadDialog,
2429
+ {
2430
+ open: uploadDialogOpen,
2431
+ onOpenChange: setUploadDialogOpen,
2432
+ currentPath
2433
+ }
2434
+ ),
2435
+ /* @__PURE__ */ jsx(
2436
+ CreateFolderDialog,
2437
+ {
2438
+ open: createFolderDialogOpen,
2439
+ onOpenChange: setCreateFolderDialogOpen,
2440
+ currentPath
2441
+ }
2442
+ )
2443
+ ] })
2444
+ ] }),
2445
+ editingFile ? /* @__PURE__ */ jsx(FileEditor, { filePath: editingFile, onClose: handleCloseEditor }) : null
2446
+ ] });
2447
+ }
2448
+ export {
2449
+ FileExplorerScreen
2450
+ };