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