flea-qml 0.0.0-stage → 0.1.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 (625) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +478 -2
  3. package/THIRD-PARTY-NOTICES.md +29 -0
  4. package/demos/App.java +5 -0
  5. package/demos/Dockerfile +4 -0
  6. package/demos/Main.kt +3 -0
  7. package/demos/Program.cs +1 -0
  8. package/demos/README.md +12 -0
  9. package/demos/app.dart +3 -0
  10. package/demos/app.deb +0 -0
  11. package/demos/app.js +1 -0
  12. package/demos/backup.tar +0 -0
  13. package/demos/budget.xlsx +0 -0
  14. package/demos/build.sh +2 -0
  15. package/demos/bundle.zip +0 -0
  16. package/demos/clip.mp4 +0 -0
  17. package/demos/data.sqlite +0 -0
  18. package/demos/disk.iso +0 -0
  19. package/demos/engine.cpp +5 -0
  20. package/demos/firmware.bin +0 -0
  21. package/demos/flake.nix +4 -0
  22. package/demos/folder/inside.txt +1 -0
  23. package/demos/font.ttf +0 -0
  24. package/demos/index.html +5 -0
  25. package/demos/index.php +2 -0
  26. package/demos/init.lua +1 -0
  27. package/demos/letter.docx +0 -0
  28. package/demos/lib.rs +3 -0
  29. package/demos/main.c +6 -0
  30. package/demos/main.h +3 -0
  31. package/demos/math-and-diagrams.md +200 -0
  32. package/demos/notes.tar.gz +0 -0
  33. package/demos/notes.txt +3 -0
  34. package/demos/photo.png +0 -0
  35. package/demos/program +2 -0
  36. package/demos/report.pdf +32 -0
  37. package/demos/script.py +1 -0
  38. package/demos/server.go +7 -0
  39. package/demos/settings.json +4 -0
  40. package/demos/slides.pptx +0 -0
  41. package/demos/song.mp3 +0 -0
  42. package/demos/style.css +4 -0
  43. package/demos/tool.rb +1 -0
  44. package/demos/types.ts +2 -0
  45. package/docs/pele.md +23 -0
  46. package/docs/protocol.md +3420 -0
  47. package/docs/temml.md +63 -0
  48. package/index.js +107 -0
  49. package/package.json +34 -4
  50. package/src/archive.js +402 -0
  51. package/src/archiveops.js +697 -0
  52. package/src/backend.js +369 -0
  53. package/src/bulkrename.js +95 -0
  54. package/src/clip-host.js +76 -0
  55. package/src/clip.js +462 -0
  56. package/src/code.js +168 -0
  57. package/src/compare.js +74 -0
  58. package/src/create.js +128 -0
  59. package/src/dirsize.js +147 -0
  60. package/src/figure-svg.js +530 -0
  61. package/src/figure-thread.js +23 -0
  62. package/src/figure.js +132 -0
  63. package/src/find.js +55 -0
  64. package/src/flea.js +39 -0
  65. package/src/fsinfo.js +39 -0
  66. package/src/highlight.js +141 -0
  67. package/src/html.js +52 -0
  68. package/src/kinds.js +89 -0
  69. package/src/listing.js +130 -0
  70. package/src/markdown-html.js +317 -0
  71. package/src/mathml.js +186 -0
  72. package/src/menu.js +180 -0
  73. package/src/meta.js +129 -0
  74. package/src/office.js +810 -0
  75. package/src/order.js +100 -0
  76. package/src/peek.js +49 -0
  77. package/src/pele-wrapper.js +695 -0
  78. package/src/permissions.js +180 -0
  79. package/src/places.js +313 -0
  80. package/src/preview-language.js +10 -0
  81. package/src/transfer.js +669 -0
  82. package/src/trash-record.js +41 -0
  83. package/src/trash.js +210 -0
  84. package/src/trashbrowse.js +326 -0
  85. package/src/uistate.js +393 -0
  86. package/src/uistore.js +168 -0
  87. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  88. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  89. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  90. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  91. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  92. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  93. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  94. package/src/vendor/pele.js +145 -0
  95. package/src/vendor/temml.mjs +14708 -0
  96. package/src/watch.js +98 -0
  97. package/src/wayland/client.js +109 -0
  98. package/src/wayland/protocol.js +55 -0
  99. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  100. package/src/wayland/protocols/wayland.xml +3248 -0
  101. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  102. package/src/wayland/resource-io.js +6 -0
  103. package/src/wayland/socket-discovery.js +26 -0
  104. package/src/wayland/transport-posix.js +120 -0
  105. package/src/wayland/transport-win32.js +11 -0
  106. package/src/wayland/transport.js +3 -0
  107. package/src/wayland/win32-resources.js +80 -0
  108. package/src/wayland/win32-wire.js +62 -0
  109. package/test/archive.test.js +80 -0
  110. package/test/bench-markdown-scroll.mjs +190 -0
  111. package/test/clip-host.test.js +113 -0
  112. package/test/code-preview.test.js +35 -0
  113. package/test/compare-ui-paths.test.js +24 -0
  114. package/test/compare.test.js +54 -0
  115. package/test/delete-trash.test.js +67 -0
  116. package/test/dirsize.test.js +138 -0
  117. package/test/file-ops.test.js +241 -0
  118. package/test/find-contents.test.js +161 -0
  119. package/test/find-input.test.js +66 -0
  120. package/test/fixtures/pele-parity.json +1006 -0
  121. package/test/highlight-launcher.test.js +35 -0
  122. package/test/html-preview.test.js +22 -0
  123. package/test/input.test.js +15 -0
  124. package/test/keymap.test.js +26 -0
  125. package/test/language-icons.test.js +36 -0
  126. package/test/launcher.test.js +51 -0
  127. package/test/manual-code-scroll.mjs +28 -0
  128. package/test/manual-column-scroll.mjs +24 -0
  129. package/test/manual-compare-ui.mjs +20 -0
  130. package/test/manual-find-status.mjs +37 -0
  131. package/test/manual-html-security.mjs +23 -0
  132. package/test/manual-html-ui.mjs +30 -0
  133. package/test/manual-syntax-preview.mjs +26 -0
  134. package/test/manual-trash-ui.mjs +86 -0
  135. package/test/markdown-diagrams.test.js +93 -0
  136. package/test/markdown-html.test.js +105 -0
  137. package/test/mathml.test.js +92 -0
  138. package/test/menu-transfer.test.js +57 -0
  139. package/test/office-preview.test.js +154 -0
  140. package/test/pele-parity.test.js +28 -0
  141. package/test/places.test.js +134 -0
  142. package/test/preview-keys.test.js +60 -0
  143. package/test/preview-language.test.js +22 -0
  144. package/test/status-filename.test.js +20 -0
  145. package/test/transfer-replace-merge.test.js +187 -0
  146. package/test/trash-address.test.js +23 -0
  147. package/test/trash-view.test.js +37 -0
  148. package/test/trashbrowse.test.js +135 -0
  149. package/ui/AboutFacts.qml +78 -0
  150. package/ui/ArchiveCards.qml +68 -0
  151. package/ui/ArchiveCompressDialog.qml +378 -0
  152. package/ui/ArchivePasswordDialog.qml +257 -0
  153. package/ui/Backend.qml +771 -0
  154. package/ui/BulkOldNames.qml +47 -0
  155. package/ui/BulkRenameDialog.qml +380 -0
  156. package/ui/CardFrame.qml +67 -0
  157. package/ui/CardScroll.qml +79 -0
  158. package/ui/CheckBox.qml +73 -0
  159. package/ui/ChromeBar.qml +360 -0
  160. package/ui/ChromeButton.qml +131 -0
  161. package/ui/ChromeModes.qml +43 -0
  162. package/ui/CodeLines.qml +256 -0
  163. package/ui/CodeRefusal.qml +52 -0
  164. package/ui/CollideConfirm.qml +147 -0
  165. package/ui/CollideHost.qml +167 -0
  166. package/ui/ColumnPane.qml +523 -0
  167. package/ui/ColumnRow.qml +380 -0
  168. package/ui/ColumnsArea.qml +594 -0
  169. package/ui/ColumnsLanding.qml +147 -0
  170. package/ui/CompareView.qml +321 -0
  171. package/ui/ContextMenu.qml +743 -0
  172. package/ui/ConvertDialog.qml +456 -0
  173. package/ui/CountedWorker.qml +7 -0
  174. package/ui/Crumb.qml +64 -0
  175. package/ui/CrumbDrop.qml +25 -0
  176. package/ui/CsvTable.qml +394 -0
  177. package/ui/CursorMark.qml +41 -0
  178. package/ui/DefaultClaim.qml +130 -0
  179. package/ui/DeviceLock.qml +47 -0
  180. package/ui/DeviceMounts.qml +540 -0
  181. package/ui/DialogButton.qml +150 -0
  182. package/ui/DialogField.qml +147 -0
  183. package/ui/DialogHeader.qml +31 -0
  184. package/ui/DialogScrim.qml +20 -0
  185. package/ui/DialogTitle.qml +37 -0
  186. package/ui/Divider.qml +9 -0
  187. package/ui/DropInto.qml +126 -0
  188. package/ui/DropTarget.qml +35 -0
  189. package/ui/DropboxMark.qml +60 -0
  190. package/ui/EmptyState.qml +150 -0
  191. package/ui/EscapeHint.qml +11 -0
  192. package/ui/FactsTable.qml +54 -0
  193. package/ui/FastScrollHandler.qml +476 -0
  194. package/ui/FatNamesConfirm.qml +157 -0
  195. package/ui/Favourites.qml +143 -0
  196. package/ui/FigureService.qml +120 -0
  197. package/ui/FileDrag.qml +267 -0
  198. package/ui/FilterStrip.qml +133 -0
  199. package/ui/FitMetrics.qml +22 -0
  200. package/ui/FlashDriveRows.qml +137 -0
  201. package/ui/FlashHost.qml +72 -0
  202. package/ui/FlashImageRow.qml +104 -0
  203. package/ui/FlashRunner.qml +119 -0
  204. package/ui/FlashStepRow.qml +172 -0
  205. package/ui/FlashStrip.qml +38 -0
  206. package/ui/FlashView.qml +397 -0
  207. package/ui/FleaMark.qml +88 -0
  208. package/ui/FlyoutFade.qml +23 -0
  209. package/ui/FontPane.qml +170 -0
  210. package/ui/Glass.qml +114 -0
  211. package/ui/GlassTagger.qml +59 -0
  212. package/ui/Glyph.qml +64 -0
  213. package/ui/GlyphSolid.qml +22 -0
  214. package/ui/GridArea.qml +664 -0
  215. package/ui/GridTile.qml +325 -0
  216. package/ui/GvfsBridge.qml +164 -0
  217. package/ui/Header.qml +433 -0
  218. package/ui/HoverTimers.qml +39 -0
  219. package/ui/HtmlClip.qml +30 -0
  220. package/ui/HtmlPane.qml +63 -0
  221. package/ui/HyprlandMark.qml +45 -0
  222. package/ui/Ipc.qml +1005 -0
  223. package/ui/IpcControls.qml +7 -0
  224. package/ui/IpcMenuState.qml +48 -0
  225. package/ui/IpcPreviewState.qml +185 -0
  226. package/ui/JumpPath.qml +51 -0
  227. package/ui/KeymapSheet.qml +561 -0
  228. package/ui/KeymapSheetResult.qml +89 -0
  229. package/ui/KindStack.qml +40 -0
  230. package/ui/LangMark.qml +51 -0
  231. package/ui/LangMarkLoader.qml +26 -0
  232. package/ui/List.qml +480 -0
  233. package/ui/LoadingState.qml +54 -0
  234. package/ui/LocalSend.qml +56 -0
  235. package/ui/LocalSendMark.qml +73 -0
  236. package/ui/MarkdownBlockView.qml +311 -0
  237. package/ui/MarkdownBlocks.qml +35 -0
  238. package/ui/MarkdownFigure.qml +254 -0
  239. package/ui/MarkdownImages.qml +72 -0
  240. package/ui/MarkdownList.qml +81 -0
  241. package/ui/MarkdownMathsText.qml +97 -0
  242. package/ui/MarkdownPane.qml +83 -0
  243. package/ui/MarkdownPictures.qml +41 -0
  244. package/ui/MarkdownQuote.qml +52 -0
  245. package/ui/MarkdownRemote.qml +82 -0
  246. package/ui/MarkdownSource.qml +155 -0
  247. package/ui/MarkdownTable.qml +222 -0
  248. package/ui/MarkdownTableScroll.qml +64 -0
  249. package/ui/MarkdownTableWheel.qml +126 -0
  250. package/ui/MarkdownText.qml +87 -0
  251. package/ui/MarkdownWorker.js +132 -0
  252. package/ui/MatchText.qml +99 -0
  253. package/ui/MediaSession.qml +38 -0
  254. package/ui/MediaSound.qml +26 -0
  255. package/ui/MediaStrip.qml +191 -0
  256. package/ui/MediaTrack.qml +36 -0
  257. package/ui/MediaVolume.qml +54 -0
  258. package/ui/MenuActionDialog.qml +396 -0
  259. package/ui/MenuEdgeFade.qml +24 -0
  260. package/ui/MenuHost.qml +227 -0
  261. package/ui/MenuRow.qml +351 -0
  262. package/ui/MountListing.qml +100 -0
  263. package/ui/NetworkDialog.qml +435 -0
  264. package/ui/NetworkForm.qml +426 -0
  265. package/ui/NetworkMounts.qml +913 -0
  266. package/ui/NetworkPlaces.qml +127 -0
  267. package/ui/OpenWithDialog.qml +613 -0
  268. package/ui/Opener.qml +115 -0
  269. package/ui/Pane.qml +1296 -0
  270. package/ui/PaneMenuActions.qml +527 -0
  271. package/ui/PanePath.qml +90 -0
  272. package/ui/PaneRail.qml +239 -0
  273. package/ui/PaneStates.qml +88 -0
  274. package/ui/PaneSwap.qml +339 -0
  275. package/ui/PaneWire.qml +605 -0
  276. package/ui/ParkedWait.qml +9 -0
  277. package/ui/PathEntry.qml +186 -0
  278. package/ui/PathJump.qml +400 -0
  279. package/ui/PdfViewer.qml +297 -0
  280. package/ui/PermissionsDialog.qml +658 -0
  281. package/ui/PermissionsHeadings.qml +31 -0
  282. package/ui/PhoneMounts.qml +67 -0
  283. package/ui/PickerAnswers.qml +75 -0
  284. package/ui/PickerBox.qml +26 -0
  285. package/ui/PickerChrome.qml +295 -0
  286. package/ui/PickerFilter.qml +97 -0
  287. package/ui/PickerGrid.qml +308 -0
  288. package/ui/PickerHeader.qml +28 -0
  289. package/ui/PickerLifecycle.qml +27 -0
  290. package/ui/PickerList.qml +218 -0
  291. package/ui/PickerListing.qml +149 -0
  292. package/ui/PickerPathBar.qml +127 -0
  293. package/ui/PickerPlaces.qml +189 -0
  294. package/ui/PickerRecent.qml +92 -0
  295. package/ui/PickerSave.qml +101 -0
  296. package/ui/PickerSelection.qml +155 -0
  297. package/ui/PickerWindow.qml +846 -0
  298. package/ui/PowerSectorsReader.qml +10 -0
  299. package/ui/PrebuildDriver.qml +146 -0
  300. package/ui/Preview.qml +1004 -0
  301. package/ui/PreviewAnimation.qml +24 -0
  302. package/ui/PreviewArchive.qml +66 -0
  303. package/ui/PreviewColumn.qml +551 -0
  304. package/ui/PreviewEdge.qml +63 -0
  305. package/ui/PreviewFacts.qml +30 -0
  306. package/ui/PreviewFactsLine.qml +34 -0
  307. package/ui/PreviewHtml.qml +233 -0
  308. package/ui/PreviewImage.qml +253 -0
  309. package/ui/PreviewImagePan.qml +13 -0
  310. package/ui/PreviewImageSharp.qml +32 -0
  311. package/ui/PreviewLines.qml +106 -0
  312. package/ui/PreviewMarkdown.qml +706 -0
  313. package/ui/PreviewMedia.qml +250 -0
  314. package/ui/PreviewPdf.qml +482 -0
  315. package/ui/PreviewScroll.qml +201 -0
  316. package/ui/PreviewSwap.qml +335 -0
  317. package/ui/PreviewText.qml +149 -0
  318. package/ui/ProtocolChip.qml +18 -0
  319. package/ui/QuickLookArchive.qml +38 -0
  320. package/ui/QuickLookBar.qml +103 -0
  321. package/ui/QuickLookPrepare.qml +355 -0
  322. package/ui/QuickLookSwap.qml +10 -0
  323. package/ui/QuickLookWarm.qml +65 -0
  324. package/ui/RailDrop.qml +155 -0
  325. package/ui/RailHeading.qml +19 -0
  326. package/ui/RailPlaces.qml +38 -0
  327. package/ui/RailTrashRow.qml +67 -0
  328. package/ui/RenameField.qml +353 -0
  329. package/ui/RendererRetry.qml +11 -0
  330. package/ui/ResizeHandle.qml +52 -0
  331. package/ui/Reveal.qml +54 -0
  332. package/ui/Row.qml +476 -0
  333. package/ui/RowDrag.qml +213 -0
  334. package/ui/RowDropTarget.qml +27 -0
  335. package/ui/RunCard.qml +286 -0
  336. package/ui/Scripts.qml +65 -0
  337. package/ui/SearchStrip.qml +232 -0
  338. package/ui/SelectionBand.qml +140 -0
  339. package/ui/SelectionPreview.qml +370 -0
  340. package/ui/SettingsFavourite.qml +122 -0
  341. package/ui/SettingsGroup.qml +96 -0
  342. package/ui/SettingsHint.qml +12 -0
  343. package/ui/SettingsMetrics.qml +29 -0
  344. package/ui/SettingsPane.qml +178 -0
  345. package/ui/SettingsPanel.qml +547 -0
  346. package/ui/SettingsRail.qml +128 -0
  347. package/ui/SettingsRow.qml +409 -0
  348. package/ui/SettingsRuler.qml +85 -0
  349. package/ui/SettingsSegment.qml +85 -0
  350. package/ui/ShareBrowser.qml +113 -0
  351. package/ui/ShareLink.qml +67 -0
  352. package/ui/ShelfPins.qml +101 -0
  353. package/ui/Sidebar.qml +732 -0
  354. package/ui/SidebarRow.qml +358 -0
  355. package/ui/Spinner.qml +60 -0
  356. package/ui/StableModel.qml +89 -0
  357. package/ui/StateMessage.qml +84 -0
  358. package/ui/StatusBar.qml +512 -0
  359. package/ui/StatusFileName.qml +27 -0
  360. package/ui/TabBar.qml +673 -0
  361. package/ui/TabDragGeometry.qml +60 -0
  362. package/ui/TagMenuMark.qml +52 -0
  363. package/ui/TagRailSection.qml +53 -0
  364. package/ui/TagSlot.qml +40 -0
  365. package/ui/TagState.qml +150 -0
  366. package/ui/TagSwatch.qml +28 -0
  367. package/ui/Taildrop.qml +86 -0
  368. package/ui/TailscaleMark.qml +50 -0
  369. package/ui/Theme.qml +460 -0
  370. package/ui/ThemeColors.qml +43 -0
  371. package/ui/TransferCard.qml +239 -0
  372. package/ui/TrashConfirm.qml +129 -0
  373. package/ui/TrashDropHandler.qml +11 -0
  374. package/ui/TrashHost.qml +202 -0
  375. package/ui/TrashMonitor.qml +30 -0
  376. package/ui/TrashToolbar.qml +19 -0
  377. package/ui/TrashView.qml +492 -0
  378. package/ui/UnlockDialog.qml +450 -0
  379. package/ui/UnlockHost.qml +206 -0
  380. package/ui/UpdateCheck.qml +97 -0
  381. package/ui/UserDirs.qml +25 -0
  382. package/ui/ViewState.qml +565 -0
  383. package/ui/ViewportScrollBar.qml +195 -0
  384. package/ui/ViewportScrollBars.qml +25 -0
  385. package/ui/WebPreview.qml +33 -0
  386. package/ui/WebPreviewView.qml +26 -0
  387. package/ui/WindowBody.qml +827 -0
  388. package/ui/WindowDrag.qml +35 -0
  389. package/ui/boot/Commons/AudioNodes.qml +50 -0
  390. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  391. package/ui/boot/Commons/Border.qml +243 -0
  392. package/ui/boot/Commons/BorderGeometry.js +373 -0
  393. package/ui/boot/Commons/Color.qml +258 -0
  394. package/ui/boot/Commons/I18n.qml +118 -0
  395. package/ui/boot/Commons/InputDevices.qml +112 -0
  396. package/ui/boot/Commons/InputMethodState.qml +38 -0
  397. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  398. package/ui/boot/Commons/Numbers.js +19 -0
  399. package/ui/boot/Commons/Plural.js +91 -0
  400. package/ui/boot/Commons/ShellIpc.qml +12 -0
  401. package/ui/boot/Commons/Style.qml +561 -0
  402. package/ui/boot/Commons/Translations.qml +100 -0
  403. package/ui/boot/Commons/UntypedInput.qml +152 -0
  404. package/ui/boot/Commons/Util.qml +188 -0
  405. package/ui/boot/Commons/qmldir +13 -0
  406. package/ui/boot/LICENSE-omarchy +20 -0
  407. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  408. package/ui/boot/Ui/BarIconButton.qml +106 -0
  409. package/ui/boot/Ui/BarIconContent.qml +38 -0
  410. package/ui/boot/Ui/BarIndicator.qml +53 -0
  411. package/ui/boot/Ui/BarWidget.qml +50 -0
  412. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  413. package/ui/boot/Ui/BorderSurface.qml +40 -0
  414. package/ui/boot/Ui/Button.qml +224 -0
  415. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  416. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  417. package/ui/boot/Ui/CursorSurface.qml +42 -0
  418. package/ui/boot/Ui/Dropdown.qml +245 -0
  419. package/ui/boot/Ui/FilterInput.qml +26 -0
  420. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  421. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  422. package/ui/boot/Ui/MultiSelect.qml +624 -0
  423. package/ui/boot/Ui/NumberField.qml +86 -0
  424. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  425. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  426. package/ui/boot/Ui/Panel.qml +60 -0
  427. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  428. package/ui/boot/Ui/PanelController.qml +16 -0
  429. package/ui/boot/Ui/PanelHero.qml +112 -0
  430. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  431. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  432. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  433. package/ui/boot/Ui/PanelSlider.qml +156 -0
  434. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  435. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  436. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  437. package/ui/boot/Ui/PopupCard.qml +181 -0
  438. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  439. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  440. package/ui/boot/Ui/SizedImage.qml +125 -0
  441. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  442. package/ui/boot/Ui/TextField.qml +58 -0
  443. package/ui/boot/Ui/Toggle.qml +118 -0
  444. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  445. package/ui/boot/Ui/WidgetButton.qml +172 -0
  446. package/ui/boot/Ui/qmldir +41 -0
  447. package/ui/boot/fleatab.qml +50 -0
  448. package/ui/boot/picker.qml +39 -0
  449. package/ui/boot/shell.qml +122 -0
  450. package/ui/boot/tabtearoff.qml +51 -0
  451. package/ui/js/Anchor.js +379 -0
  452. package/ui/js/AnchorHold.js +162 -0
  453. package/ui/js/Archive.js +157 -0
  454. package/ui/js/ArchiveCard.js +44 -0
  455. package/ui/js/ArchiveOps.js +44 -0
  456. package/ui/js/BackReturn.js +118 -0
  457. package/ui/js/BulkRename.js +100 -0
  458. package/ui/js/Buttons.js +54 -0
  459. package/ui/js/ChromeKeys.js +116 -0
  460. package/ui/js/ClipMarks.js +73 -0
  461. package/ui/js/ClipSelection.js +67 -0
  462. package/ui/js/Clipboard.js +179 -0
  463. package/ui/js/Cloud.js +97 -0
  464. package/ui/js/CodeRuns.js +72 -0
  465. package/ui/js/Collide.js +212 -0
  466. package/ui/js/ColumnFit.js +28 -0
  467. package/ui/js/ColumnMenu.js +111 -0
  468. package/ui/js/Columns.js +298 -0
  469. package/ui/js/ColumnsCarry.js +103 -0
  470. package/ui/js/Compare.js +74 -0
  471. package/ui/js/Contrast.js +157 -0
  472. package/ui/js/ControlReader.js +42 -0
  473. package/ui/js/Convert.js +52 -0
  474. package/ui/js/CopyAs.js +52 -0
  475. package/ui/js/Crumbs.js +178 -0
  476. package/ui/js/Csv.js +231 -0
  477. package/ui/js/Density.js +44 -0
  478. package/ui/js/Devices.js +440 -0
  479. package/ui/js/DirSizes.js +180 -0
  480. package/ui/js/Drag.js +364 -0
  481. package/ui/js/DragOut.js +166 -0
  482. package/ui/js/DropDev.js +52 -0
  483. package/ui/js/Dropbox.js +41 -0
  484. package/ui/js/Eject.js +217 -0
  485. package/ui/js/Errors.js +188 -0
  486. package/ui/js/ExtThumbs.js +115 -0
  487. package/ui/js/Facts.js +402 -0
  488. package/ui/js/FigureDate.js +14 -0
  489. package/ui/js/Filter.js +285 -0
  490. package/ui/js/Find.js +46 -0
  491. package/ui/js/Flash.js +184 -0
  492. package/ui/js/FlashCard.js +79 -0
  493. package/ui/js/FlashEndings.js +125 -0
  494. package/ui/js/FlashPane.js +116 -0
  495. package/ui/js/FleaBin.js +12 -0
  496. package/ui/js/Focus.js +502 -0
  497. package/ui/js/FolderSorts.js +108 -0
  498. package/ui/js/FolderViews.js +44 -0
  499. package/ui/js/Format.js +305 -0
  500. package/ui/js/Fuzzy.js +141 -0
  501. package/ui/js/GlassTag.js +72 -0
  502. package/ui/js/Grid.js +33 -0
  503. package/ui/js/GridGeometry.js +90 -0
  504. package/ui/js/GridNames.js +153 -0
  505. package/ui/js/GvfsBridge.js +200 -0
  506. package/ui/js/HostClipboard.js +29 -0
  507. package/ui/js/Icons.js +218 -0
  508. package/ui/js/Input.js +50 -0
  509. package/ui/js/Jump.js +200 -0
  510. package/ui/js/KeyName.js +64 -0
  511. package/ui/js/Keymap.js +401 -0
  512. package/ui/js/Kinds.js +164 -0
  513. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  514. package/ui/js/LangMarkPaths.js +47 -0
  515. package/ui/js/LangMarks.js +120 -0
  516. package/ui/js/Lists.js +11 -0
  517. package/ui/js/LocalSend.js +39 -0
  518. package/ui/js/LockedMenu.js +63 -0
  519. package/ui/js/MakeDefault.js +186 -0
  520. package/ui/js/MarkBox.js +7 -0
  521. package/ui/js/Markdown.js +119 -0
  522. package/ui/js/MarkdownLists.js +86 -0
  523. package/ui/js/MarkdownMaths.js +58 -0
  524. package/ui/js/MarkdownPictures.js +32 -0
  525. package/ui/js/MarkdownPrepared.js +139 -0
  526. package/ui/js/MarkdownTableFit.js +34 -0
  527. package/ui/js/Marks.js +148 -0
  528. package/ui/js/Marquee.js +96 -0
  529. package/ui/js/Match.js +93 -0
  530. package/ui/js/Material.js +109 -0
  531. package/ui/js/MdBlocks.js +437 -0
  532. package/ui/js/MdBreak.js +71 -0
  533. package/ui/js/MdChunks.js +70 -0
  534. package/ui/js/MdContainer.js +137 -0
  535. package/ui/js/MdDocument.js +337 -0
  536. package/ui/js/MdEmph.js +97 -0
  537. package/ui/js/MdEntity.js +108 -0
  538. package/ui/js/MdEntityTable.js +223 -0
  539. package/ui/js/MdEscape.js +59 -0
  540. package/ui/js/MdFront.js +37 -0
  541. package/ui/js/MdHold.js +32 -0
  542. package/ui/js/MdHtml.js +307 -0
  543. package/ui/js/MdHtmlBlock.js +290 -0
  544. package/ui/js/MdHtmlHeading.js +53 -0
  545. package/ui/js/MdHtmlImage.js +179 -0
  546. package/ui/js/MdInline.js +154 -0
  547. package/ui/js/MdItems.js +212 -0
  548. package/ui/js/MdLeaf.js +309 -0
  549. package/ui/js/MdLink.js +196 -0
  550. package/ui/js/MdMath.js +67 -0
  551. package/ui/js/MdParagraphs.js +138 -0
  552. package/ui/js/MdRefs.js +232 -0
  553. package/ui/js/MdResolve.js +226 -0
  554. package/ui/js/MdRun.js +300 -0
  555. package/ui/js/MdUrl.js +226 -0
  556. package/ui/js/Menu.js +514 -0
  557. package/ui/js/MenuContext.js +85 -0
  558. package/ui/js/MenuFit.js +13 -0
  559. package/ui/js/MenuRefresh.js +74 -0
  560. package/ui/js/MenuWheel.js +35 -0
  561. package/ui/js/Messages.js +186 -0
  562. package/ui/js/Motion.js +16 -0
  563. package/ui/js/Mounts.js +320 -0
  564. package/ui/js/MouseNav.js +13 -0
  565. package/ui/js/Nav.js +329 -0
  566. package/ui/js/NetFs.js +26 -0
  567. package/ui/js/OpenWith.js +83 -0
  568. package/ui/js/Ops.js +585 -0
  569. package/ui/js/Palette.js +33 -0
  570. package/ui/js/PathBar.js +260 -0
  571. package/ui/js/Permissions.js +179 -0
  572. package/ui/js/Phones.js +112 -0
  573. package/ui/js/Picker.js +353 -0
  574. package/ui/js/PlaceMenu.js +96 -0
  575. package/ui/js/Places.js +265 -0
  576. package/ui/js/PreviewAsk.js +21 -0
  577. package/ui/js/PreviewKeys.js +155 -0
  578. package/ui/js/PreviewSettle.js +29 -0
  579. package/ui/js/PreviewSwap.js +101 -0
  580. package/ui/js/PropertyFacts.js +144 -0
  581. package/ui/js/Protocols.js +296 -0
  582. package/ui/js/QuickLookKinds.js +27 -0
  583. package/ui/js/QuickLookRect.js +24 -0
  584. package/ui/js/RailDrop.js +51 -0
  585. package/ui/js/RailKeys.js +87 -0
  586. package/ui/js/RailMenu.js +133 -0
  587. package/ui/js/Recent.js +135 -0
  588. package/ui/js/RecentMode.js +156 -0
  589. package/ui/js/Reload.js +60 -0
  590. package/ui/js/Renderer.js +9 -0
  591. package/ui/js/RunFile.js +143 -0
  592. package/ui/js/Scripts.js +28 -0
  593. package/ui/js/Scroll.js +304 -0
  594. package/ui/js/ScrollOff.js +69 -0
  595. package/ui/js/Search.js +478 -0
  596. package/ui/js/Selection.js +97 -0
  597. package/ui/js/Settings.js +476 -0
  598. package/ui/js/SettingsAbout.js +97 -0
  599. package/ui/js/SettingsShelf.js +52 -0
  600. package/ui/js/SheetKeys.js +42 -0
  601. package/ui/js/SheetQuery.js +281 -0
  602. package/ui/js/ShelfPile.js +27 -0
  603. package/ui/js/SlowClick.js +53 -0
  604. package/ui/js/SlowOp.js +6 -0
  605. package/ui/js/Sort.js +134 -0
  606. package/ui/js/Startup.js +63 -0
  607. package/ui/js/StateWords.js +17 -0
  608. package/ui/js/Status.js +238 -0
  609. package/ui/js/Swap.js +166 -0
  610. package/ui/js/TabMove.js +39 -0
  611. package/ui/js/Tabs.js +736 -0
  612. package/ui/js/Tags.js +134 -0
  613. package/ui/js/Taildrop.js +128 -0
  614. package/ui/js/Tap.js +174 -0
  615. package/ui/js/TextSize.js +107 -0
  616. package/ui/js/Thumbs.js +158 -0
  617. package/ui/js/Transfer.js +172 -0
  618. package/ui/js/Trash.js +81 -0
  619. package/ui/js/TrashDates.js +47 -0
  620. package/ui/js/TrashDrop.js +64 -0
  621. package/ui/js/UiState.js +438 -0
  622. package/ui/js/Unlock.js +152 -0
  623. package/ui/js/Update.js +130 -0
  624. package/ui/js/UserDirs.js +56 -0
  625. package/ui/qmldir +228 -0
@@ -0,0 +1,827 @@
1
+ import Quickshell
2
+ import QtQuick
3
+ import qs.Commons
4
+ import "."
5
+ import "." as Flea
6
+ import "js/Focus.js" as Focus
7
+ import "js/MarkdownPrepared.js" as Prepared
8
+ import "js/TextSize.js" as TextSize
9
+ import "js/MouseNav.js" as MouseNav
10
+ import "js/Nav.js" as Nav
11
+ import "js/Ops.js" as Ops
12
+ import "js/RailKeys.js" as RailKeys
13
+ import "js/RailMenu.js" as RailMenu
14
+ import "js/Search.js" as Search
15
+ import "js/Startup.js" as Startup
16
+ import "js/Tabs.js" as Tabs
17
+
18
+ // Everything inside the window ui/boot/shell.qml maps, arriving by file: URL on the first frame
19
+ // because that is the only way Qt caches it; see AGENTS.md "The first window".
20
+ Rectangle {
21
+ id: view
22
+ anchors.fill: parent
23
+ // On glass each plane draws its own ground, so this one would put a second plane under every pixel.
24
+ color: Glass.on ? "transparent" : Theme.color.background
25
+ // The window this body was loaded into. Named host, not fleaWindow: a root property of
26
+ // that name would bind to itself through Ipc and read null.
27
+ property var host
28
+ readonly property bool dualMode: ViewState.state.view === "dual"
29
+ property int focusSide: (ViewState.state.dual || {}).focus === 1 ? 1 : 0
30
+ // dual.split: the left listing's share of the room right of the rail in ten-thousandths, 5000 the equal split.
31
+ readonly property int dualSplitScale: 10000
32
+ readonly property int dualEqualSplit: 5000
33
+ readonly property real dualRoom: view.width - primaryPane.railInset - Theme.spacing.hairline
34
+ readonly property real dualSplit: typeof (ViewState.state.dual || {}).split === "number" ? ViewState.state.dual.split : view.dualEqualSplit
35
+ readonly property real dualShare: view.dualShareOf(view.dualSplit)
36
+ // False when the room fits no two narrowest listings: nothing moves there, so a drag writes nothing.
37
+ readonly property bool dualFits: Theme.dualMinWidth / view.dualRoom <= 0.5
38
+ // Held between the narrowest listing and its mirror, equal when the room fits no two; the stored split is never rewritten by it.
39
+ function dualShareOf(split) {
40
+ var low = Theme.dualMinWidth / view.dualRoom
41
+ return low > 0.5 ? 0.5 : Math.min(1 - low, Math.max(low, split / view.dualSplitScale))
42
+ }
43
+ // The run card, null until the first ask builds it for the window and keeps it.
44
+ property var runCard: null
45
+ function runLane() {
46
+ if (!view.runCard) view.runCard = Qt.createComponent("RunCard.qml").createObject(view, { z: 2 })
47
+ return view.runCard
48
+ }
49
+ function askRunCard(pane, info) { view.runLane().open(info, pane) }
50
+ // Null until the first Quick Look opens, see previewLoader below.
51
+ readonly property var quickLook: previewLoader.item
52
+ readonly property bool quickLookActive: view.quickLook !== null && view.quickLook.active
53
+ // QuickLookWarm, built by the cursor's first landing on a file and kept: a move off it must not drop the compiled units. A Markdown file builds it in that turn, any other file off the key once a key has moved the cursor, so a launch builds none.
54
+ readonly property var cursorFile: view.currentPane.cursorRow
55
+ onCursorFileChanged: {
56
+ var markdown = Prepared.isMarkdownRow(view.cursorFile)
57
+ if (warmLoader.active || !view.cursorFile || view.cursorFile.d || !(markdown || view.currentPane.inputAt > 0)) return
58
+ warmLoader.asynchronous = !markdown; warmLoader.active = true
59
+ }
60
+ Loader { id: warmLoader; active: false; source: "QuickLookWarm.qml"; onLoaded: item.pane = Qt.binding(function () { return view.currentPane }) }
61
+ readonly property var currentPane: dualMode && focusSide === 1 && secondPane.item ? secondPane.item.pane : primaryPane
62
+ property bool initialized: false
63
+ property bool closing: false
64
+ // Built once per window on first use, so the flasher stays out of the first frame.
65
+ property var flashHost: null
66
+ function openFlash(pane, image, device) {
67
+ if (!view.flashHost)
68
+ view.flashHost = Qt.createComponent("FlashHost.qml").createObject(view, { z: 2 })
69
+ view.flashHost.openFor(pane, image, device)
70
+ }
71
+ function closeFlash() {
72
+ if (view.flashHost)
73
+ view.flashHost.close()
74
+ }
75
+ property var drained: [false, false]
76
+
77
+ function focusPane(side) {
78
+ if (!dualMode) return
79
+ focusSide = side
80
+ // The pane left behind gets its list back, so the ring resumes from the list and never from a rail, bar or strip stop the user left (Shift+Tab from the right lands on the left list, not the rail).
81
+ if (side === 1) primaryPane.focusView = Focus.LIST
82
+ else if (secondPane.item) secondPane.item.pane.focusView = Focus.LIST
83
+ currentPane.forceActiveFocus()
84
+ Qt.callLater(view.rememberDual)
85
+ }
86
+ function switchToLeftRail() { focusPane(0); primaryPane.focusView = Focus.RAIL }
87
+ function rememberPaths() {
88
+ // Path and tab changes in one turn share the deferred navigation patch.
89
+ if (initialized && !dualMode && primaryPane.path)
90
+ view.queueTabStrip()
91
+ if (!initialized || !dualMode || !secondPane.item || !primaryPane.path || !secondPane.item.pane.path) return
92
+ Qt.callLater(view.rememberDual)
93
+ }
94
+ // Strip changes coalesce; an in-flight navigation waits for its path and rows to land before saving.
95
+ property bool tabStripQueued: false
96
+ // Stage trace, on only with FLEA_TRACE_TABDRAG=1; read once, silent otherwise.
97
+ readonly property bool tabTrace: Quickshell.env("FLEA_TRACE_TABDRAG") === "1"
98
+ function traceTab(stage, detail) { if (view.tabTrace) console.log("TABDRAG " + stage + " pid=" + Quickshell.processId + " " + detail) }
99
+ function queueTabStrip() {
100
+ if (view.tabStripQueued)
101
+ return
102
+ view.tabStripQueued = true
103
+ Qt.callLater(function() { view.tabStripQueued = false; view.rememberTabStrip() })
104
+ }
105
+ function rememberTabStrip() {
106
+ if (initialized && !dualMode && primaryPane.path && !primaryPane.listInFlight)
107
+ ViewState.rememberNavigation(primaryPane.path, Tabs.remembered(primaryPane))
108
+ }
109
+ // The mode binding reads ViewState.state; its handlers must finish before persistence replaces it.
110
+ function rememberDual() {
111
+ if (!initialized || !dualMode) return
112
+ var saved = {focus: focusSide}
113
+ if (secondPane.item && primaryPane.path && secondPane.item.pane.path)
114
+ saved.paths = [primaryPane.path, secondPane.item.pane.path]
115
+ ViewState.changeLeaf("dual", saved)
116
+ }
117
+ function quitBackends() {
118
+ if (closing) return
119
+ closing = true
120
+ drained = [false, secondPane.item === null]
121
+ backend.quit()
122
+ if (secondPane.item) secondPane.item.backend.quit()
123
+ }
124
+ function backendDrained(side) {
125
+ var next = drained.slice()
126
+ next[side] = true
127
+ drained = next
128
+ // Finish quitReady delivery before SIGTERM can interrupt another observer.
129
+ if (closing && drained[0] && drained[1]) Qt.callLater(view.finishQuit)
130
+ }
131
+ function finishQuit() {
132
+ Quickshell.execDetached(["kill", String(Quickshell.processId)])
133
+ }
134
+ onDualModeChanged: {
135
+ if (!initialized) return
136
+ if (!dualMode) { focusSide = 0; primaryPane.forceActiveFocus() }
137
+ else if (initialized) { focusPane(focusSide); rememberPaths() }
138
+ }
139
+
140
+
141
+ // Issue 9's chords, aliased by keys.toml onto the Display section's own text size. The
142
+ // panel writes ui/ViewState.qml directly and shows the value in the row; a chord has no
143
+ // readout of its own with the panel shut, so this one adds the status line.
144
+ function applyTextSize(direction) {
145
+ if (direction === 0)
146
+ ViewState.followTextSize()
147
+ else
148
+ ViewState.stepTextSize(direction)
149
+ view.currentPane.message(TextSize.announce(ViewState.textSize, ViewState.omarchyBase), false)
150
+ }
151
+
152
+ // The backend is told first and answers when it has drained, so closing never leaves a half
153
+ // file; the last-window handler itself is in ui/boot/shell.qml, which always exists.
154
+ Connections { target: backend; function onQuitReady() { view.backendDrained(0) } }
155
+
156
+ Backend {
157
+ id: backend
158
+ preserveSort: view.dualMode
159
+ }
160
+
161
+ // Built with the window and outliving the rail, so hiding the rail mid-mount kills no wait.
162
+ Flea.NetworkMounts {
163
+ id: networkHost
164
+ backend: primaryPane.backend
165
+ origin: primaryPane
166
+ savedFavourites: Favourites.records
167
+ }
168
+ readonly property var networkService: networkHost
169
+ function ensureNetworkService() {
170
+ return networkHost
171
+ }
172
+
173
+ // The canvas's own top chrome: where you are on the left, how you are looking at it on
174
+ // the right. The path lives here, which is why the status bar below carries counts instead.
175
+ Flea.ChromeBar {
176
+ id: chrome
177
+ inputLive: !view.quickLookActive
178
+ visible: view.dualMode || (!view.currentPane.trash.opened && !view.currentPane.flashActive)
179
+ height: visible ? Theme.chromeHeight : 0
180
+ anchors.left: parent.left
181
+ anchors.right: parent.right
182
+ anchors.top: parent.top
183
+ // A history is a location and not a directory, so the chrome says its own name.
184
+ path: view.currentPane.flashActive ? "Flash image"
185
+ : view.currentPane.trash.opened ? "trash:///"
186
+ : view.currentPane.recentMode === "trash" ? "trash:///" : view.currentPane.recentMode.length > 0 ? "Recent"
187
+ : view.currentPane.path
188
+ // True only when the drawn path's listing failed, so the bar retries it and nothing else.
189
+ pathFailed: Nav.pathFailed(view.currentPane)
190
+ home: view.currentPane.home
191
+ pathAlias: view.currentPane.pathAlias
192
+ dropPane: view.currentPane
193
+ stopFocused: view.currentPane.focusView === Focus.CHROME
194
+ canGoBack: view.currentPane.canGoBack
195
+ canGoUp: view.currentPane.canGoUp
196
+ viewMode: view.dualMode ? "dual" : view.currentPane.viewMode
197
+ showPath: !view.dualMode
198
+ showHidden: view.currentPane.showHidden
199
+ // flea-qml: Filter stands over Find's results as over a folder's, so it is always available.
200
+ canFilter: true
201
+ canSort: view.currentPane.searchMode.length === 0
202
+ onBackRequested: view.currentPane.goBack()
203
+ onUpRequested: view.currentPane.openParent()
204
+ // flea-qml: each press flips its own toggle, leaving the other as it stands.
205
+ filterOn: view.currentPane.filterTyping || view.currentPane.filterQuery.length > 0
206
+ searchOn: view.currentPane.searchMode.length > 0
207
+ onSearchRequested: view.currentPane.toggleFind()
208
+ onFilterRequested: view.currentPane.toggleFilter()
209
+ onSortRequested: view.currentPane.act("sortNext")
210
+ // The primary pane is the one the window keeps after Dual, so every view choice, the secondary pane's keys included, lands there.
211
+ onViewChosen: function (mode) { primaryPane.chooseView(mode) }
212
+ // The path bar's four. The primaryPane navigates and answers for the keyboard exactly as it
213
+ // does for every other route in, so a path typed and a row opened end the same way.
214
+ // Issue 194: a typed smb:// (or other network) address mounts through the window-long
215
+ // host's own open-a-share path, which lists a server's shares, rather than listing a
216
+ // local path. A network address is never listed as a folder: with no host it is refused
217
+ // with a sentence, the way 0.3.5 refused the line.
218
+ onPathEntered: function (path) {
219
+ if (path === "trash:///") { view.currentPane.trash.open(); return }
220
+ if (/^(smb|sftp|ftp|ftps|dav|davs|nfs|afp):\/\//i.test(path)) {
221
+ var target = view.currentPane
222
+ var service = target ? target.ensureNetworkService() : null
223
+ if (service) service.openShare(path, false, "", false, { origin: target })
224
+ else target.message("That network address cannot be opened here.", true)
225
+ return
226
+ }
227
+ view.currentPane.open(path)
228
+ }
229
+ onEditClosed: view.currentPane.forceActiveFocus()
230
+ // Tab peeks in name order with dotfiles first; the columns view asked nothing, so it drops the reply.
231
+ onCompleteRequested: function (dir, hidden) { view.currentPane.backend.peek(dir, view.currentPane.windowSize, hidden, false) }
232
+ onJumpRequested: function (id, ranking, favourites, recent) { view.currentPane.backend.jump(id, ranking, favourites, recent) }
233
+ onSaid: function (text) { bar.say(text, false) }
234
+ onSettingsRequested: settingsPanel.open(view.currentPane)
235
+ }
236
+
237
+ // The peek behind Tab. Every peeked line carries the directory and the hidden flag it
238
+ // answers for, so the bar takes the reply to its own request and the columns view, which
239
+ // peeks the same wire for the primaryPane's ancestors, goes on taking its own.
240
+ Connections {
241
+ target: view.currentPane.backend
242
+ function onPeeked(path, hidden, total, rows, readFailed, mode) { chrome.completeWith(path, hidden, rows) }
243
+ // A bar never opened sent no ask, so an answer with no jump is dropped like a stale one.
244
+ function onJumped(id, favourites, zoxide, recent, frecency) { if (chrome.jump !== null) chrome.jump.take(id, favourites, zoxide, recent, frecency) }
245
+ }
246
+
247
+ Flea.TabBar {
248
+ id: tabBar
249
+ anchors.left: parent.left
250
+ anchors.right: parent.right
251
+ anchors.top: chrome.bottom
252
+ pane: view.currentPane
253
+ stopFocused: view.currentPane.focusView === Focus.STRIP
254
+ }
255
+
256
+ // Load the acknowledgment handler from the boot URL to preserve startup without ui/qmldir.
257
+ Loader {
258
+ id: tabAck
259
+ active: true
260
+ source: "file://" + Quickshell.shellDir + "/fleatab.qml"
261
+ onLoaded: {
262
+ item.tabBar = tabBar
263
+ item.view = view
264
+ item.tabs = Tabs
265
+ item.panes = Qt.binding(function () { return [primaryPane, secondPane.item ? secondPane.item.pane : null] })
266
+ }
267
+ }
268
+
269
+ // A hidden lone-tab strip still needs a pointer-transparent receiver with the strip's validation.
270
+ DropArea {
271
+ anchors.fill: parent
272
+ keys: [Tabs.TAB_MIME]
273
+ enabled: !view.dualMode && !(view.currentPane.tabs && view.currentPane.tabs.items
274
+ && view.currentPane.tabs.items.length > 1)
275
+ onEntered: function (drag) {
276
+ var ok = Tabs.enterAccepts(drag.formats, drag.getDataAsString(Tabs.TAB_MIME), undefined, Tabs.canReceive(view.currentPane), false)
277
+ view.traceTab("enter-window", "formats=" + String(drag.formats) + " ok=" + ok)
278
+ if (!ok)
279
+ drag.accepted = false
280
+ }
281
+ onExited: view.traceTab("leave-window", "")
282
+ onDropped: function (drop) {
283
+ var payload = drop.getDataAsString(Tabs.TAB_MIME)
284
+ view.traceTab("drop-window", "empty=" + (payload.length === 0) + " len=" + payload.length)
285
+ var info = Tabs.parseTabMime(payload)
286
+ if (!info) {
287
+ view.traceTab("drop-skip", "reason=window-bad-payload")
288
+ return
289
+ }
290
+ if (Tabs.isOwnTab(info)) {
291
+ view.traceTab("drop-skip", "reason=window-own-tab")
292
+ return
293
+ }
294
+ // The take decision answers Move at once; the peek behind it may still refuse, and then no ack goes out.
295
+ if (Tabs.dropDecision(info, undefined, false, Tabs.canReceive(view.currentPane)) !== Tabs.DROP_TAKE) {
296
+ view.traceTab("drop-skip", "reason=window-decision-ignore canReceive=" + Tabs.canReceive(view.currentPane))
297
+ return
298
+ }
299
+ drop.accept(Qt.MoveAction)
300
+ tabBar.acceptTabDrop(payload, info, -1)
301
+ }
302
+ }
303
+
304
+ Flea.Pane {
305
+ id: primaryPane
306
+ anchors.left: parent.left
307
+ width: view.dualMode ? railInset + (view.width - railInset - Theme.spacing.hairline) * view.dualShare : view.width
308
+ anchors.top: tabBar.bottom
309
+ anchors.bottom: bar.top
310
+ backend: backend
311
+ networkService: view.networkService
312
+ dualMode: view.dualMode
313
+ paneFocused: view.currentPane === primaryPane
314
+ railPane: view.currentPane
315
+ chromeBar: chrome
316
+ tabStrip: tabBar
317
+ onFocusRequested: view.focusPane(0)
318
+ onSwitchPane: view.focusPane(1)
319
+ onSwitchToRail: view.switchToLeftRail()
320
+ onPathChanged: view.rememberPaths()
321
+ onListingStateChanged: if (primaryPane.listingState !== "loading") ipcLoader.active = true
322
+ onListInFlightChanged: { if (!primaryPane.listInFlight) view.queueTabStrip() }
323
+ onTabsChanged: view.queueTabStrip()
324
+ onClipboardChanged: if (secondPane.item && secondPane.item.pane.clipboard !== clipboard) secondPane.item.pane.clipboard = clipboard
325
+ overlayParent: view
326
+ preview: view.quickLook
327
+ previewLoader: previewLoader
328
+ shareBrowser: shareBrowser
329
+ keymapSheet: keymapSheet
330
+ settingsPanel: settingsPanel
331
+ statusBar: bar
332
+ onMessage: function (text, isError) { bar.say(text, isError) }
333
+ onArmPrompted: function (text) { bar.sayArm(text) }
334
+ onForgetMessage: function (text) { bar.forget(text) }
335
+ onOperationResult: function (headline, detail, isError) { bar.say(headline, isError, detail) }
336
+ // A running operation's line, which stands until the operation replaces it; see ui/StatusBar.qml.
337
+ onSticky: function (text) { bar.setActivity(primaryPane, text, primaryPane.transfer) }
338
+ onStickyStarting: function (text) { bar.setActivity(primaryPane, text, primaryPane.transfer, true) }
339
+ onConvertRequested: function (name) { convertDialog.open(name, primaryPane) }
340
+ // Archives040: compress opens over resolved paths, extract over the named archive.
341
+ onCompressPasswordRequested: function (paths, menuId) {
342
+ view.openArchiveCards().askCompressPassword(primaryPane, paths, menuId)
343
+ }
344
+ onExtractPasswordRequested: function (path, dest, err) { view.openArchiveCards().openExtractPassword({ path: path, dest: dest }, primaryPane, err) }
345
+ onPermissionsRequested: function (path) { permissionsDialog.open(path, primaryPane) }
346
+ // Permissions040: the whole selection's paths for the multi-row card.
347
+ onPermissionsBatchRequested: function (paths) { permissionsDialog.openMany(paths, primaryPane) }
348
+ // Bulk rename takes the whole selection's paths for its card.
349
+ onBulkRenameRequested: function (paths) { view.openBulkRename(paths, primaryPane) }
350
+ onPathBarRequested: chrome.startEdit()
351
+ // Issue 9. ViewState persists the stop and Theme derives its own tokens from it, so
352
+ // the whole window follows without any surface reading the chord itself.
353
+ onTextSizeRequested: function (direction) { view.applyTextSize(direction) }
354
+ onOpened: function (path) { if (shareBrowser.owner === primaryPane) shareBrowser.close() }
355
+ }
356
+
357
+ Loader {
358
+ id: secondPane
359
+ anchors { left: primaryPane.right; right: parent.right; top: tabBar.bottom; bottom: bar.top }
360
+ anchors.leftMargin: Theme.spacing.hairline
361
+ property bool built: false
362
+ active: view.dualMode || built
363
+ visible: view.dualMode
364
+ sourceComponent: Item {
365
+ readonly property alias pane: otherPane
366
+ readonly property alias backend: otherBackend
367
+ Backend {
368
+ id: otherBackend
369
+ preserveSort: true
370
+ onQuitReady: view.backendDrained(1)
371
+ }
372
+ Flea.Pane {
373
+ id: otherPane
374
+ anchors.fill: parent
375
+ backend: otherBackend
376
+ clipboardState: primaryPane.clipboardState
377
+ sharedSidebar: primaryPane.sidebar
378
+ chromeBar: chrome
379
+ tabStrip: tabBar
380
+ networkService: view.networkService
381
+ sharedNetworkService: primaryPane.networkService
382
+ dualMode: true
383
+ listOnly: true
384
+ paneFocused: view.currentPane === otherPane
385
+ overlayParent: view
386
+ preview: primaryPane.preview
387
+ previewLoader: primaryPane.previewLoader
388
+ shareBrowser: primaryPane.shareBrowser
389
+ keymapSheet: primaryPane.keymapSheet
390
+ settingsPanel: primaryPane.settingsPanel
391
+ statusBar: bar
392
+ onFocusRequested: view.focusPane(1)
393
+ onViewRequested: function (mode) { chrome.viewChosen(mode) }
394
+ onSwitchPane: view.focusPane(0)
395
+ onSwitchToRail: view.switchToLeftRail()
396
+ onPathChanged: view.rememberPaths()
397
+ onClipboardChanged: if (primaryPane.clipboard !== clipboard) primaryPane.clipboard = clipboard
398
+ onMessage: function(text, error) { bar.say(text, error) }
399
+ onArmPrompted: function(text) { bar.sayArm(text) }
400
+ onOperationResult: function(headline, detail, error) { bar.say(headline, error, detail) }
401
+ onSticky: function(text) { bar.setActivity(otherPane, text, otherPane.transfer) }
402
+ onStickyStarting: function(text) { bar.setActivity(otherPane, text, otherPane.transfer, true) }
403
+ onConvertRequested: function(name) { convertDialog.open(name, otherPane) }
404
+ onCompressPasswordRequested: function (paths, menuId) {
405
+ view.openArchiveCards().askCompressPassword(otherPane, paths, menuId)
406
+ }
407
+ onExtractPasswordRequested: function (path, dest, err) { view.openArchiveCards().openExtractPassword({ path: path, dest: dest }, otherPane, err) }
408
+ onPermissionsRequested: function(path) { permissionsDialog.open(path, otherPane) }
409
+ onPermissionsBatchRequested: function (paths) { permissionsDialog.openMany(paths, otherPane) }
410
+ onBulkRenameRequested: function (paths) { view.openBulkRename(paths, otherPane) }
411
+ onPathBarRequested: chrome.startEdit()
412
+ onTextSizeRequested: function(direction) { view.applyTextSize(direction) }
413
+ onOpened: if (otherPane.shareBrowser.owner === otherPane) otherPane.shareBrowser.close()
414
+ }
415
+ }
416
+ onLoaded: {
417
+ built = true
418
+ // Before initialized this is the launch, the only time a command-line folder applies.
419
+ var named = view.initialized ? "" : (Quickshell.env("FLEA_PATH") || "")
420
+ var pair = Startup.dualPaths(ViewState.state.dual, primaryPane.path || primaryPane.home, named)
421
+ item.pane.clipboard = primaryPane.clipboard
422
+ if (pair.launchSide === 1) {
423
+ item.pane.pendingSelect = Quickshell.env("FLEA_SELECT") || ""
424
+ Tabs.prepareCursor(item.pane, named ? Quickshell.env("FLEA_TAB_CURSOR") : "")
425
+ }
426
+ item.pane.open(pair.paths[1])
427
+ if (view.initialized && view.dualMode) view.focusPane(view.focusSide)
428
+ }
429
+ }
430
+
431
+ // The zone starts on the divider pixel and runs right over the second listing's row padding, so the first listing keeps its scroll lane whole.
432
+ Loader {
433
+ x: primaryPane.width
434
+ y: primaryPane.y
435
+ active: view.dualMode
436
+ sourceComponent: Item {
437
+ id: divider
438
+ property bool dragging: false
439
+ property bool moved: false
440
+ property real startX: 0
441
+ property real dx: 0
442
+ readonly property real leftWidth: primaryPane.width - primaryPane.railInset
443
+ width: handle.width
444
+ height: primaryPane.height
445
+ function shareAt(travel) { return view.dualShareOf(view.dualSplitScale * (divider.leftWidth + travel) / view.dualRoom) }
446
+ function begin(mouse) {
447
+ divider.startX = handle.mapToItem(view, mouse.x, mouse.y).x
448
+ divider.dx = 0
449
+ divider.moved = false
450
+ divider.dragging = true
451
+ }
452
+ // A press that never travels is a click and writes nothing; the accent line follows the pointer, held to the clamp.
453
+ function move(mouse) {
454
+ if (!divider.dragging) return
455
+ var travel = handle.mapToItem(view, mouse.x, mouse.y).x - divider.startX
456
+ if (!divider.moved && Math.abs(travel) < 1) return
457
+ divider.moved = true
458
+ divider.dx = view.dualRoom * divider.shareAt(travel) - divider.leftWidth
459
+ }
460
+ // One leaf on release, so both listings re-lay from it in the same frame.
461
+ function end() {
462
+ if (!divider.dragging) return
463
+ if (divider.moved && view.dualFits) ViewState.changeLeaf("dual", {split: Math.round(view.dualSplitScale * divider.shareAt(divider.dx))})
464
+ divider.reset()
465
+ }
466
+ function reset() { divider.dragging = false; divider.moved = false; divider.dx = 0 }
467
+ Rectangle {
468
+ width: Theme.spacing.hairline
469
+ height: parent.height
470
+ color: Theme.color.mutedFrame
471
+ }
472
+ Flea.ResizeHandle {
473
+ id: handle
474
+ height: parent.height
475
+ lineX: divider.dx
476
+ hot: divider.dragging
477
+ onPressed: function (mouse) { divider.begin(mouse) }
478
+ onMoved: function (mouse) { divider.move(mouse) }
479
+ onCanceled: divider.reset()
480
+ onReleased: divider.end()
481
+ onDoubleClicked: ViewState.changeLeaf("dual", {split: view.dualEqualSplit})
482
+ }
483
+ }
484
+ }
485
+
486
+ Flea.StatusBar {
487
+ id: bar
488
+ anchors.left: parent.left
489
+ anchors.right: parent.right
490
+ anchors.bottom: parent.bottom
491
+ path: view.currentPane.trash.opened ? "trash:///"
492
+ : view.currentPane.path
493
+ total: view.currentPane.flashActive && view.flashHost ? view.flashHost.count
494
+ : view.currentPane.trash.opened ? view.currentPane.trash.total : view.currentPane.total
495
+ // The counts zone names the drives while the flasher owns the pane; disk facts stay the covered folder's.
496
+ flashCount: view.currentPane.flashActive && view.flashHost ? view.flashHost.countText : ""
497
+ listingState: view.currentPane.listingState
498
+ pane: (view.currentPane.trash.opened || view.currentPane.flashActive) ? null : view.currentPane
499
+ filePane: view.currentPane.flashActive ? null : view.currentPane
500
+ armOwner: view.currentPane.trash.opened ? view.currentPane.trash.item : view.currentPane
501
+ selectionCount: view.currentPane.flashActive ? 0
502
+ : view.currentPane.trash.opened ? view.currentPane.trash.selectedCount : view.currentPane.selectionCount()
503
+ fsName: view.currentPane.fsName
504
+ fsFree: view.currentPane.fsFree
505
+ fsPending: !view.currentPane.fsAnswered
506
+ searchRunning: view.currentPane.searchRunning
507
+ searchLine: Search.live(view.currentPane)
508
+ ? Search.statusLine(view.currentPane.searchRunning, view.currentPane.total, view.currentPane.searchScanned, view.currentPane.searchMs)
509
+ : ""
510
+ retryLine: view.currentPane.trash.opened || view.currentPane.flashActive ? "" : view.currentPane.retrySelectionText
511
+ onTransferCancelRequested: function (id) {
512
+ // A flash cancels through its own request: only Cancel stops one, wherever the card sits.
513
+ if (bar.transfer.flash === true) bar.transferOwner.backend.flashCancel(id)
514
+ else bar.transferOwner.backend.transfercancel(id)
515
+ }
516
+ }
517
+
518
+ // Quick Look is built by the first Space, so a window that never previews pays nothing for it; Pane.quickLook() turns it on.
519
+ Loader {
520
+ id: previewLoader
521
+ anchors.fill: parent
522
+ z: 1
523
+ active: false
524
+ source: "Preview.qml"
525
+ onLoaded: item.pane = Qt.binding(function () { return view.currentPane })
526
+ }
527
+
528
+ MouseArea {
529
+ anchors.fill: parent
530
+ z: 4
531
+ visible: primaryPane.trash.confirming || (secondPane.item && secondPane.item.pane.trash.confirming)
532
+ hoverEnabled: true
533
+ acceptedButtons: Qt.AllButtons
534
+ onWheel: function(wheel) { wheel.accepted = true }
535
+ }
536
+
537
+ // Every overlay below is built by its first open and kept, see AGENTS.md rule 6: a launch
538
+ // that never opens one pays neither its compile nor its objects. Each Loader carries the
539
+ // one or two members its callers read, and ui/Ipc.qml reads the built item or null.
540
+ // Each Loader carries its item's z, because a z set inside the item orders it only within
541
+ // the Loader, and the share browser below would otherwise paint over an open card.
542
+ Loader {
543
+ id: convertDialog
544
+ z: 2
545
+ anchors.fill: parent
546
+ active: false
547
+ source: "ConvertDialog.qml"
548
+ readonly property bool opened: item !== null && item.opened
549
+ property var owner: null
550
+ function open(name, holder) { owner = holder; active = true; item.open(name, holder) }
551
+ }
552
+ Connections {
553
+ target: convertDialog.item
554
+ function onAccepted(source, format, strip, requestId) { Ops.convert(convertDialog.owner, source, format, strip, requestId) }
555
+ }
556
+
557
+ // Archives040: both password cards build on first open, never at startup.
558
+ property var archiveCards: null
559
+ function openArchiveCards() {
560
+ if (!view.archiveCards) {
561
+ var component = Qt.createComponent("ArchiveCards.qml")
562
+ view.archiveCards = component.status === Component.Ready ? component.createObject(view, { z: 2 }) : null
563
+ if (!view.archiveCards) throw new Error("ArchiveCards.qml did not build: " + component.errorString())
564
+ }
565
+ return view.archiveCards
566
+ }
567
+
568
+ Loader {
569
+ id: permissionsDialog
570
+ z: 2
571
+ anchors.fill: parent
572
+ active: false
573
+ source: "PermissionsDialog.qml"
574
+ readonly property bool opened: item !== null && item.opened
575
+ property var owner: null
576
+ function open(path, holder) { owner = holder; active = true; item.open(path, holder.listArea) }
577
+ function openMany(paths, holder) { owner = holder; active = true; item.openMany(paths, holder.listArea) }
578
+ }
579
+ Connections {
580
+ target: permissionsDialog.item
581
+ function onRequested(message) { permissionsDialog.owner.backend.send(message) }
582
+ function onChanged(note) { permissionsDialog.owner.refresh("", true); bar.say(note && note.length > 0 ? note : "Permissions changed.", false) }
583
+ function onRefreshNeeded() { permissionsDialog.owner.refresh("", true) }
584
+ }
585
+ // The bulk card is built once per window on its first multi-row Rename, with no placeholder before that.
586
+ property var bulkRenameDialog: null
587
+ function openBulkRename(paths, holder) {
588
+ if (!view.bulkRenameDialog) view.bulkRenameDialog = Qt.createComponent("BulkRenameDialog.qml").createObject(view, { z: 2 })
589
+ view.bulkRenameDialog.open(paths, holder)
590
+ }
591
+ Connections {
592
+ target: permissionsDialog.owner ? permissionsDialog.owner.backend : null
593
+ function onPermissionsResult(message) {
594
+ if (permissionsDialog.item) permissionsDialog.item.receive(message)
595
+ }
596
+ function onFailed(where, input, message, mode) {
597
+ if (where === "backend" && permissionsDialog.item) permissionsDialog.item.backendFailed()
598
+ }
599
+ }
600
+
601
+ // The keymap sheet ? opens, over the whole window as the convert popup is.
602
+ Loader {
603
+ id: keymapSheet
604
+ z: 2
605
+ anchors.fill: parent
606
+ active: false
607
+ source: "KeymapSheet.qml"
608
+ readonly property bool opened: item !== null && item.opened
609
+ function open(holder) { active = true; item.open(holder) }
610
+ }
611
+
612
+ // The settings panel, reached by the comma key from either view, by the toolbar's sliders
613
+ // button, and by the third door the Settings board draws: the background menu's own
614
+ // Settings row, which ui/js/Menu.js backgroundEntries builds and ui/Pane.qml act routes.
615
+ Loader {
616
+ id: settingsPanel
617
+ z: 3
618
+ anchors.fill: parent
619
+ active: false
620
+ source: "SettingsPanel.qml"
621
+ readonly property bool opened: item !== null && item.opened
622
+ function open(holder) { active = true; item.open(holder) }
623
+ }
624
+
625
+ Loader {
626
+ id: networkDialog
627
+ z: 2
628
+ anchors.fill: parent
629
+ active: false
630
+ source: "NetworkDialog.qml"
631
+ property var owner: null
632
+ property Item origin: null
633
+ readonly property bool opened: item !== null && item.opened
634
+ function open() {
635
+ if (opened) return
636
+ origin = view.currentPane
637
+ active = true
638
+ item.open()
639
+ }
640
+ function openLocation(uri, label, password, reason, failedConnect, fromPane) {
641
+ if (opened || !fromPane) return
642
+ origin = fromPane
643
+ active = true
644
+ item.openLocation(uri, label, password, reason, failedConnect)
645
+ }
646
+ }
647
+ Connections {
648
+ target: networkDialog.item
649
+ // FocusScope remembers its own last-focused child, list or rail, and restores it.
650
+ function onClosed() { if (view.currentPane.sidebar) view.currentPane.sidebar.editingPlace = ""; view.currentPane.forceActiveFocus() }
651
+ function onMountRequested(requestId, uri, label, password) {
652
+ if (!networkDialog.origin) {
653
+ networkDialog.item.mountFinished(requestId, uri, false, "The requesting pane is no longer available.", false)
654
+ return
655
+ }
656
+ // The host outlives the rail, so hiding the rail mid-dialog loses no answer: the
657
+ // completed below still lands and the dialog still finishes.
658
+ var sidebar = networkDialog.origin ? networkDialog.origin.sidebar : null
659
+ if (sidebar) RailMenu.placeSubmitted(sidebar, requestId)
660
+ networkDialog.owner = view.ensureNetworkService()
661
+ networkDialog.owner.saveLocation(uri, label, password, requestId, networkDialog.origin)
662
+ }
663
+ function onCancelRequested(requestId) { if (networkDialog.owner) networkDialog.owner.cancelLocation(requestId) }
664
+ }
665
+
666
+ // The dialog's answer lands here rather than on the rail, so it lands whether the rail is
667
+ // shown or has been unloaded mid-mount. Every host answer routes once by origin here;
668
+ // ui/PaneRail.qml carries no duplicate handling, so a dual-view open cannot fire twice.
669
+ Connections {
670
+ target: view.networkService
671
+ function onCompleted(requestId, uri, success, reason, connectFailed) {
672
+ var sidebar = view.currentPane.sidebar
673
+ if (sidebar && view.networkService) RailMenu.placeSaved(sidebar, view.networkService, requestId, uri, success)
674
+ if (networkDialog.item) networkDialog.item.mountFinished(requestId, uri, success, reason, connectFailed)
675
+ }
676
+ function onSharesListed(baseUri, baseLabel, names, origin) { if (origin) shareBrowser.open(baseUri, baseLabel, names, origin) }
677
+ function onRetryRequested(uri, label, password, reason, failedConnect, origin) {
678
+ networkDialog.openLocation(uri, label, password, reason, failedConnect, origin)
679
+ }
680
+ function onOpened(path, origin) { if (origin) RailKeys.openFrom(origin, path, origin.sidebar) }
681
+ function onOpenFileRequested(path, origin) { if (origin) origin.openFile(path) }
682
+ function onMessage(text, isError) {
683
+ var pane = view.currentPane
684
+ if (pane.sidebar) RailKeys.messaged(pane.sidebar, isError)
685
+ pane.message(text, isError)
686
+ }
687
+ function onSticky(text, origin) { (origin || view.currentPane).sticky(text) }
688
+ function onStartingBridge(text, origin) { (origin || view.currentPane).stickyStarting(text) }
689
+ function onRenamed() { if (view.currentPane.sidebar) view.currentPane.sidebar.reloadBookmarks() }
690
+ }
691
+
692
+ Connections {
693
+ target: view.currentPane.sidebar
694
+ function onAddRequested() { networkDialog.open() }
695
+ function onEditRequested(uri, label, password, reason, failedConnect, origin) {
696
+ networkDialog.openLocation(uri, label, password, reason, failedConnect, origin)
697
+ }
698
+ function onUnlockRequested(device, label, size, fstype) { view.ensureUnlockHost().openFor(device, label, size, fstype, view.currentPane.sidebar) }
699
+ }
700
+
701
+ // Unlock (U039): created once on first use and reused after, so no placeholder object stands before it.
702
+ property var unlockHost: null
703
+ function ensureUnlockHost() {
704
+ if (view.unlockHost === null)
705
+ view.unlockHost = Qt.createComponent("UnlockHost.qml").createObject(view, { z: 2, window: view, footer: bar })
706
+ return view.unlockHost
707
+ }
708
+
709
+ // A bare Network entry's own shares, same listArea placement as EmptyState above.
710
+ // An Item fronts this Loader because its callers read active, which is a Loader's own load switch.
711
+ Item {
712
+ id: shareBrowser
713
+ property Item owner: primaryPane
714
+ onOwnerChanged: if (!owner) close()
715
+ visible: owner !== null && (owner === primaryPane || view.dualMode)
716
+ x: owner ? (owner === primaryPane ? primaryPane.x : secondPane.x) + owner.listSlot.x : 0
717
+ y: owner ? tabBar.y + tabBar.height + owner.listSlot.y : 0
718
+ width: owner ? owner.listSlot.width : 0
719
+ height: owner ? owner.listSlot.height : 0
720
+ readonly property bool active: shareLoader.item !== null && shareLoader.item.active
721
+ function open(uri, label, names, origin) { owner = origin; shareLoader.active = true; shareLoader.item.open(uri, label, names) }
722
+ function close() { if (shareLoader.item) shareLoader.item.close() }
723
+ // ui/js/Focus.js shareBrowserAct's two other verbs, reached only while the overlay is up.
724
+ function moveCursor(delta) { if (shareLoader.item) shareLoader.item.moveCursor(delta) }
725
+ function activateCursor() { if (shareLoader.item) shareLoader.item.activateCursor() }
726
+ Loader { id: shareLoader; anchors.fill: parent; active: false; source: "ShareBrowser.qml" }
727
+ }
728
+ Connections {
729
+ target: shareLoader.item
730
+ function onClosed() { view.currentPane.forceActiveFocus() }
731
+ function onActivated(uri, label) {
732
+ view.focusPane(shareBrowser.owner === primaryPane ? 0 : 1)
733
+ // The host outlives the rail, so a hidden rail never blocks a share the browser lists.
734
+ var service = shareBrowser.owner.ensureNetworkService()
735
+ if (!service) {
736
+ shareBrowser.owner.message("That network share cannot be opened here.", true)
737
+ return
738
+ }
739
+ service.openChildShare(uri, label, shareBrowser.owner)
740
+ }
741
+ }
742
+
743
+ // Issue 20: the mouse's side buttons belong to the window, not a row; Nav.js mouseBack and MouseNav.js forward decide, and the overlays below refuse both.
744
+ TapHandler {
745
+ acceptedButtons: Qt.BackButton | Qt.ForwardButton
746
+ onTapped: function (eventPoint, button) {
747
+ if (view.currentPane.menuActions.opened || settingsPanel.opened || view.currentPane.trash.confirming || chrome.editing || convertDialog.opened || permissionsDialog.opened || (view.bulkRenameDialog !== null && view.bulkRenameDialog.opened) || keymapSheet.opened
748
+ || networkDialog.opened || (view.unlockHost !== null && view.unlockHost.opened) || (shareBrowser.active && shareBrowser.owner === view.currentPane) || view.quickLookActive
749
+ || view.currentPane.renameEditor() !== null || (view.currentPane.sidebar && view.currentPane.sidebar.renameEditor() !== null))
750
+ return
751
+ if (button === Qt.ForwardButton) MouseNav.forward(view.currentPane)
752
+ else if (view.currentPane.flashActive) view.closeFlash()
753
+ else if (view.currentPane.trash.opened) view.currentPane.trash.close()
754
+ else Nav.mouseBack(view.currentPane)
755
+ }
756
+ }
757
+
758
+ // The block view's diagrams are drawn by a window's backend (ui/FigureService.qml).
759
+ Component.onDestruction: FigureService.release(backend)
760
+
761
+ Component.onCompleted: {
762
+ FigureService.adopt(backend)
763
+ var home = Quickshell.env("HOME")
764
+ var named = Quickshell.env("FLEA_PATH") || ""
765
+ var start = Startup.startPath(ViewState.state, home, named)
766
+ var pair = Startup.dualPaths(ViewState.state.dual, start, named)
767
+ // Read once, and only on the side that took the named folder; Pane.applyPendingSelect() forgets it after the first rows.
768
+ if (!view.dualMode || pair.launchSide !== 1) {
769
+ primaryPane.pendingSelect = Quickshell.env("FLEA_SELECT") || ""
770
+ Tabs.prepareCursor(primaryPane, named ? Quickshell.env("FLEA_TAB_CURSOR") : "")
771
+ }
772
+ // Tabs040 callout 2: Last folder reopens every remembered tab in order at its folder.
773
+ // Dual startup keeps its own pair, and a named path outranks the strip either way.
774
+ var plan = view.dualMode ? null : Tabs.restorePlan(ViewState.state, named)
775
+ if (plan)
776
+ primaryPane.tabs = Tabs.pack(Tabs.restoreItems(primaryPane, plan.paths), plan.index)
777
+ primaryPane.open(plan ? plan.paths[plan.index] : (view.dualMode ? pair.paths[0] : start))
778
+ view.initialized = true
779
+ // flea --unlock opens the same dialog a rail row opens, for scripts and a udev rule.
780
+ var unlockDevice = Quickshell.env("FLEA_UNLOCK") || ""
781
+ if (unlockDevice.length > 0)
782
+ view.ensureUnlockHost().openForDevice(unlockDevice, view.currentPane.sidebar)
783
+ if (view.dualMode) view.focusPane(view.focusSide)
784
+ trashSweep.start()
785
+ UpdateCheck.startPolling()
786
+ // Last, behind the listing request, and still before ui/boot/shell.qml creates the window and reads this body's colour.
787
+ Glass.launch()
788
+ }
789
+
790
+ // The 30 day sweep runs off the startup path, not on it: a Trash listing costs one gio call per
791
+ // item and first paint is measured. Late enough that the window is up and the backend is
792
+ // answering, long before anyone reaches the Trash rail row. ui/TrashHost.qml refuses it when the
793
+ // setting is off, when it has already run today, and when the Trash window exists at all.
794
+ Timer {
795
+ id: trashSweep
796
+ interval: 2000
797
+ repeat: false
798
+ onTriggered: primaryPane.trash.sweep()
799
+ }
800
+
801
+ // The test seam (AGENTS.md "Testing"), built once the first listing answers or waits, never for the first frame.
802
+ Loader {
803
+ id: ipcLoader
804
+ active: false
805
+ asynchronous: true
806
+ Component.onCompleted: setSource("Ipc.qml", {
807
+ fleaWindow: Qt.binding(function () { return view.host }),
808
+ pane: Qt.binding(function () { return view.currentPane }),
809
+ panes: Qt.binding(function () { return [primaryPane, secondPane.item ? secondPane.item.pane : null] }),
810
+ bar: Qt.binding(function () { return bar }),
811
+ backend: Qt.binding(function () { return view.currentPane.backend }),
812
+ chrome: Qt.binding(function () { return chrome }),
813
+ tabBar: Qt.binding(function () { return tabBar }),
814
+ convertDialog: Qt.binding(function () { return convertDialog.item }),
815
+ permissionsDialog: Qt.binding(function () { return permissionsDialog.item }),
816
+ bulkRenameDialog: Qt.binding(function () { return view.bulkRenameDialog }),
817
+ keymapSheet: Qt.binding(function () { return keymapSheet.item }),
818
+ settingsPanel: Qt.binding(function () { return settingsPanel.item }),
819
+ networkDialog: Qt.binding(function () { return networkDialog.item }),
820
+ shareBrowser: Qt.binding(function () { return shareLoader.item }),
821
+ emptyState: Qt.binding(function () { return view.currentPane.emptyState }),
822
+ archiveCards: Qt.binding(function () { return view.archiveCards }),
823
+ unlockHost: Qt.binding(function () { return view.unlockHost })
824
+ })
825
+ }
826
+ readonly property bool clearSurface: Glass.clear
827
+ }