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,149 @@
1
+ import QtQuick
2
+ import Quickshell
3
+ import Quickshell.Io
4
+ import "js/FleaBin.js" as FleaBin
5
+
6
+ // Listings can block inside FUSE. Only this read-only worker is replaceable; selection identities
7
+ // belong to the picker's separate backend, and the file manager's write backend is never signalled.
8
+ Item {
9
+ id: root
10
+ signal message(var value)
11
+ signal pathsResolved(var paths, int request)
12
+ signal failed(string reason)
13
+ signal quitReady()
14
+ property var current: null
15
+ property var pending: null
16
+ property bool quitting: false
17
+
18
+ function clear() {
19
+ pending = null
20
+ if (current) {
21
+ current.obsolete = true
22
+ if (current.running) current.signal(9)
23
+ }
24
+ }
25
+ function request(value) {
26
+ if (quitting) return
27
+ // Only list and listpaths have a worker to serve them, so any other request names itself here.
28
+ if (value.c !== "list" && value.c !== "listpaths") { console.warn("PickerListing refused request " + value.c); return }
29
+ pending = value
30
+ if (current) {
31
+ current.obsolete = true
32
+ if (current.running) current.signal(9)
33
+ } else launch()
34
+ }
35
+ function launch() {
36
+ if (quitting || !pending || current) return
37
+ var value = pending
38
+ pending = null
39
+ current = worker.createObject(root, {request: value})
40
+ current.running = true
41
+ }
42
+ function window(start, count) {
43
+ if (current && current.running && !current.obsolete && !quitting)
44
+ current.write(JSON.stringify({c: "window", start: start, count: count}) + "\n")
45
+ }
46
+ // Resolve names without asking metadata for rows outside the held window.
47
+ function paths(rows, request) {
48
+ if (!current || !current.running || current.obsolete || quitting) return false
49
+ current.pathRequests = current.pathRequests.concat([request])
50
+ current.write(JSON.stringify({c: "paths", rows: rows}) + "\n")
51
+ return true
52
+ }
53
+ // Storage class for the grid planner, asked after the listing lands.
54
+ function fsinfo() {
55
+ if (current && current.running && !current.obsolete && !quitting)
56
+ current.write(JSON.stringify({c: "fsinfo"}) + "\n")
57
+ }
58
+ // Sort reorders the worker's own listing without replacing it; the caller re-asks the window after.
59
+ function sort(by, desc, foldersFirst, groupByKind, hiddenLast) {
60
+ if (current && current.running && !current.obsolete && !quitting)
61
+ current.write(JSON.stringify({c: "sort", by: by, desc: desc, foldersFirst: foldersFirst, groupByKind: groupByKind, hiddenLast: hiddenLast}) + "\n")
62
+ }
63
+ // Thumbnails are planned against the rows this worker holds, so the ask goes to it and not to
64
+ // the picker's selection backend, which holds no listing at all. An empty ask names nothing.
65
+ function thumb(rows, cacheOnly) {
66
+ if (!rows || rows.length === 0) return
67
+ if (current && current.running && !current.obsolete && !quitting)
68
+ current.write(JSON.stringify({c: "thumb", rows: rows, cacheOnly: cacheOnly === true}) + "\n")
69
+ }
70
+ // An empty rows cancels everything queued, so an empty list is never sent; see docs/protocol.md.
71
+ function thumbcancel(rows) {
72
+ if (!rows || rows.length === 0) return
73
+ if (current && current.running && !current.obsolete && !quitting)
74
+ current.write(JSON.stringify({c: "thumbcancel", rows: rows}) + "\n")
75
+ }
76
+ // The path bar's folder jump, asked once per open of the field; the worker is the full backend, so it answers the main window's jumped line.
77
+ function jump(id, ranking, favourites, recent) {
78
+ if (current && current.running && !current.obsolete && !quitting) {
79
+ var message = {c: "jump", id: id, favourites: favourites, recent: recent}
80
+ if (ranking !== 0) message.ranking = ranking
81
+ current.write(JSON.stringify(message) + "\n")
82
+ }
83
+ }
84
+ // Tab completion behind the picker's field: the directory the head names.
85
+ function peek(dir, first, hidden) {
86
+ if (current && current.running && !current.obsolete && !quitting)
87
+ current.write(JSON.stringify({c: "peek", path: dir, first: first, hidden: hidden}) + "\n")
88
+ }
89
+ // A typed file's row, named from the listing's own names: nothing is statted and no window is walked.
90
+ function locate(path) {
91
+ if (current && current.running && !current.obsolete && !quitting)
92
+ current.write(JSON.stringify({c: "locate", path: path}) + "\n")
93
+ }
94
+ function quit() {
95
+ quitting = true
96
+ pending = null
97
+ if (!current) { quitReady(); return }
98
+ current.obsolete = true
99
+ if (current.running) current.signal(9)
100
+ }
101
+ function ended(process, reason) {
102
+ if (current !== process) return
103
+ current = null
104
+ process.destroy()
105
+ if (quitting) quitReady()
106
+ else if (pending) Qt.callLater(launch)
107
+ else if (!process.obsolete) failed(reason)
108
+ }
109
+ Component.onDestruction: if (current && current.running) current.signal(9)
110
+
111
+ Component {
112
+ id: worker
113
+ Process {
114
+ id: process
115
+ property var request
116
+ property bool obsolete: false
117
+ property bool started: false
118
+ property var pathRequests: []
119
+ command: FleaBin.command(Quickshell.env("FLEA_BIN"), Quickshell.env("BUNINU_BUN"), Quickshell.shellDir, ["--backend"])
120
+ stdinEnabled: true
121
+ onStarted: {
122
+ started = true
123
+ if (obsolete || root.quitting) { signal(9); return }
124
+ write(JSON.stringify(request) + "\n")
125
+ }
126
+ stdout: SplitParser {
127
+ onRead: function(line) {
128
+ if (process.obsolete || root.quitting || root.current !== process || !line) return
129
+ // Sample reply: {"t":"paths","paths":["/folder/file.txt"]}; replies follow request order on this worker.
130
+ try {
131
+ var message = JSON.parse(line)
132
+ if (message.t === "paths") {
133
+ var request = process.pathRequests.shift()
134
+ if (request !== undefined) root.pathsResolved(message.paths, request)
135
+ } else root.message(message)
136
+ }
137
+ catch (error) { console.warn("PickerListing could not handle a worker reply: " + error); root.failed("This folder could not be listed.") }
138
+ }
139
+ }
140
+ onExited: function(code, status) {
141
+ // The signal(9) above ends an obsolete or quitting worker on purpose, so only an unasked exit is worth a warning.
142
+ if (!process.obsolete && !root.quitting) console.warn("PickerListing worker exited with code " + code)
143
+ root.ended(process, "Flea stopped responding, so this folder could not be listed.")
144
+ }
145
+ // A failed QProcess spawn emits runningChanged, but never exited.
146
+ onRunningChanged: if (!running && !started) root.ended(process, "This folder could not be listed. Restart Flea.")
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,127 @@
1
+ import QtQuick
2
+ import "js/Format.js" as Format
3
+ import "js/PathBar.js" as PathBar
4
+ import "js/Picker.js" as Picker
5
+
6
+ // A committed picker line, named by the backend's peek first: a folder opens, a file answers through its row's accept, nothing else.
7
+ Item {
8
+ id: root
9
+
10
+ required property var picker
11
+ required property var listing
12
+ required property var backend
13
+ required property var entry
14
+
15
+ // Recent is a location and not a path, so a relative line resolves against home there, never onto the location token.
16
+ readonly property string basePath: root.picker.recent ? root.picker.home : root.picker.path
17
+
18
+ // The path peeked and waiting for its answer.
19
+ property string asked: ""
20
+ // A typed file's path, waiting for its parent's listing to name its row, and that row once named.
21
+ property string leaf: ""
22
+ property string leafParent: ""
23
+ property int leafIndex: -1
24
+ // A listing that fails answers a waiting typed file with the footer's own error, so the leaf waits on nothing.
25
+ readonly property bool listingFailed: root.picker.listingFailed
26
+ onListingFailedChanged: if (root.listingFailed) root.leaf = ""
27
+
28
+ function commitTyped(text) {
29
+ var target = PathBar.resolve(text, root.basePath, root.picker.home)
30
+ if (target === "") {
31
+ // A URI on another host says its sentence; an empty line only closes, as the chrome's commit does.
32
+ if (PathBar.refused(text)) {
33
+ root.picker.say(PathBar.REFUSED_SENTENCE, false)
34
+ return
35
+ }
36
+ root.entry.closeEdit()
37
+ return
38
+ }
39
+ root.ask(target)
40
+ }
41
+
42
+ // A peek of count zero is the stat and nothing else; Tab's own peek asks for rows, so the two replies never mix.
43
+ function ask(target) {
44
+ root.asked = target
45
+ root.leaf = ""
46
+ root.listing.peek(target, 0, root.picker.showHidden)
47
+ }
48
+
49
+ function answered(path, readFailed, mode) {
50
+ if (path !== root.asked) return
51
+ root.asked = ""
52
+ var kind = PathBar.kindOfPeek(readFailed, mode)
53
+ if (kind === "missing") {
54
+ root.picker.say(Format.fill(PathBar.MISSING_SENTENCE, path), true)
55
+ } else if (kind === "folder") {
56
+ root.picker.open(path)
57
+ root.entry.closeEdit()
58
+ } else {
59
+ root.openFile(path)
60
+ }
61
+ }
62
+
63
+ function openFile(path) {
64
+ // A file where a folder answers is the backend's own refusal: the mark names the folder type and fails it, and a mark in flight keeps its accept.
65
+ if (root.picker.saving || root.picker.folderMode) {
66
+ root.picker.pathProbe = root.picker.check({op: "mark", path: path, directory: true, multiple: false})
67
+ return
68
+ }
69
+ root.leaf = path
70
+ root.leafParent = Picker.parentOf(path)
71
+ root.leafIndex = -1
72
+ if (root.leafParent !== root.picker.path) root.picker.open(root.leafParent)
73
+ else if (root.picker.listingFailed) root.leaf = ""
74
+ else if (root.picker.pendingListings === 0) root.listing.locate(path)
75
+ }
76
+
77
+ // Sample input: {"t":"located","directory":"/d","path":"/d/a.txt","index":42}; -1 is a name the filter or the hidden files setting left out.
78
+ function located(message) {
79
+ // A reply against another listing is stale; the leaf's own listing locates it again when it lands.
80
+ if (root.leaf === "" || message.path !== root.leaf || message.directory !== root.leafParent) return
81
+ if (message.index < 0) {
82
+ root.picker.say(Format.fill(PathBar.FILTERED_SENTENCE, root.leaf.substring(root.leaf.lastIndexOf("/") + 1)), true)
83
+ root.leaf = ""
84
+ return
85
+ }
86
+ root.leafIndex = message.index
87
+ root.picker.cursorIndex = message.index
88
+ root.picker.viewItem().reshow(message.index)
89
+ root.take()
90
+ }
91
+
92
+ // The row's own accept runs once its window is held; the reshow above asks for that window when it is not.
93
+ function take() {
94
+ if (root.leaf === "" || root.leafIndex < 0 || !root.picker.rowFor(root.leafIndex)) return
95
+ var index = root.leafIndex
96
+ root.leaf = ""
97
+ root.leafIndex = -1
98
+ root.picker.activate(index)
99
+ }
100
+
101
+ // The field closing, or the picker moving on, drops whatever was still being looked for.
102
+ function editClosed() {
103
+ root.asked = ""
104
+ root.leaf = ""
105
+ root.picker.focusDefault()
106
+ }
107
+
108
+ // Every listing of the leaf's folder, its first or a re-read, renumbers the rows, so the leaf is located afresh against it.
109
+ Connections {
110
+ target: root.backend
111
+ function onListed() {
112
+ if (root.leaf === "") return
113
+ if (root.picker.path !== root.leafParent) { root.leaf = ""; return }
114
+ root.leafIndex = -1
115
+ root.listing.locate(root.leaf)
116
+ }
117
+ function onLocated(message) { root.located(message) }
118
+ function onRows() { root.take() }
119
+ function onJumped(id, favourites, zoxide, recent, frecency) {
120
+ if (root.entry.jump !== null) root.entry.jump.take(id, favourites, zoxide, recent, frecency)
121
+ }
122
+ function onPeeked(path, hidden, total, rows, readFailed, mode, hiddenLast, first) {
123
+ if (first === 0) root.answered(path, readFailed, mode)
124
+ else root.entry.completeWith(path, hidden, rows)
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,189 @@
1
+ import QtQuick
2
+ import "." as Flea
3
+ import "js/Icons.js" as Icons
4
+ import "js/Picker.js" as Picker
5
+ import "js/Places.js" as Places
6
+ import "js/Keymap.js" as Keymap
7
+
8
+ // The chooser shares Flea's favourites and home locations without importing legacy GTK bookmarks.
9
+ Item {
10
+ id: root
11
+
12
+ property string home: ""
13
+ required property var picker
14
+ property string current: ""
15
+ // The ink the picker draws every rule in, handed down by ui/picker.qml, which says why; the
16
+ // window's own rule colour stands in so an unwired rail still draws a seam and never a black one.
17
+ property color edge: Theme.color.surface
18
+
19
+ signal chosen(string path)
20
+ signal networkCompleted(string requestId, string uri, bool success, string reason, bool connectFailed)
21
+
22
+ // SendPicker.html draws Recent above Home, and a save has no history to write into, so the one
23
+ // mode that cannot use the location does not offer it.
24
+ property bool offerRecent: true
25
+
26
+ // Recent is a location and not a path, so its row carries the token ui/js/Picker.js names; the
27
+ // rail's own cursor and activation then work on it exactly as they do on a favourite.
28
+ readonly property var recentRow: [{
29
+ path: Picker.RECENT, label: Picker.RECENT_LABEL, group: "favorite", kind: "favorite", glyph: "history"
30
+ }]
31
+ readonly property var placeEntries: (root.offerRecent ? root.recentRow : [])
32
+ .concat(Places.storedEntries(Flea.Favourites.records, root.home), Places.homeEntries(root.home, Flea.RailPlaces.probed, Icons.sidebarGlyphFor))
33
+ // Directive 55, from a user's report: a dialog that cannot reach a disk or a share is a
34
+ // dialog that makes you type the path, so the chooser draws the window's own NETWORK and DEVICES
35
+ // groups from the same listings. Rows only: nothing here trashes, ejects or renames.
36
+ // flea-qml: the drives found by looking close the list as plain folders, as they close the window's DEVICES group.
37
+ readonly property var entries: root.placeEntries.concat(network.entries, devices.entries, phones.entries, Places.volumeEntries(Flea.RailPlaces.probedVolumes, devices.entries))
38
+
39
+ implicitWidth: Math.min(parent.width / 3, Theme.space(172))
40
+ readonly property alias focusItem: rail
41
+ property bool awaitingNetwork: false
42
+ property string awaitingDevice: ""
43
+
44
+ Rectangle {
45
+ anchors.fill: parent
46
+ color: Theme.color.background
47
+ }
48
+
49
+ Rectangle {
50
+ anchors.right: parent.right
51
+ width: Theme.spacing.hairline
52
+ height: parent.height
53
+ color: root.edge
54
+ }
55
+
56
+ ListView {
57
+ id: rail
58
+ anchors.fill: parent
59
+ anchors.topMargin: Theme.spacing.rowPaddingY
60
+ clip: true
61
+ boundsBehavior: Flickable.StopAtBounds
62
+ model: root.entries
63
+ activeFocusOnTab: true
64
+ currentIndex: 0
65
+ onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain)
66
+ Flea.FastScrollHandler { flickable: rail }
67
+ // No bar and no lane: rows fill the rail and still scroll by wheel, touchpad and keys.
68
+ Keys.onTabPressed: function(event) { root.picker.stepFocus(rail, (event.modifiers & Qt.ShiftModifier) !== 0) }
69
+ Keys.onBacktabPressed: root.picker.stepFocus(rail, true)
70
+ Keys.onPressed: function(event) {
71
+ var action = Keymap.lookup(event.key, event.text, event.modifiers, "rail")
72
+ event.accepted = true
73
+ if (action === "cursorUp") rail.currentIndex = Math.max(0, rail.currentIndex - 1)
74
+ else if (action === "cursorDown") rail.currentIndex = Math.min(root.entries.length - 1, rail.currentIndex + 1)
75
+ else if (action === "open" || action === "pageForward" || event.key === Qt.Key_Space) root.choose(rail.currentIndex)
76
+ else event.accepted = false
77
+ }
78
+
79
+ // index and modelData are required on ui/SidebarRow.qml itself, so the view fills them;
80
+ // redeclaring them here left the delegate uninitialised and the rail drew nothing.
81
+ delegate: Flea.SidebarRow {
82
+ lit: rail.activeFocus ? index === rail.currentIndex : modelData.path === root.current
83
+ focused: rail.activeFocus
84
+ onActivated: function (at) { root.choose(at) }
85
+ }
86
+ }
87
+
88
+ function choose(index) {
89
+ if (root.picker.submitting || root.picker.backendUnavailable) return
90
+ var entry = root.entries[index]
91
+ if (!entry) return
92
+ if (entry.error) { root.picker.say(entry.error, true); return }
93
+ root.awaitingNetwork = false
94
+ // The three groups below the places, routed the way ui/Sidebar.qml routes them.
95
+ if (entry.group === "device" && entry.kind === "phone") {
96
+ root.awaitingNetwork = true
97
+ network.openShare(entry.uri, entry.mounted, entry.label)
98
+ return
99
+ }
100
+ if (entry.group === "device") {
101
+ // The row this dialog is waiting on, by device: any other mount's open is not its answer.
102
+ root.awaitingDevice = entry.device
103
+ devices.activate(index - root.placeEntries.length - network.entries.length)
104
+ return
105
+ }
106
+ if (entry.group === "network") {
107
+ root.awaitingNetwork = true
108
+ network.activate(index - root.placeEntries.length)
109
+ return
110
+ }
111
+ if (entry.path.indexOf("file://") === 0) {
112
+ try {
113
+ var path = decodeURIComponent(entry.path.substring(7))
114
+ if (path.charAt(0) !== "/" || path.indexOf("\0") >= 0) throw new Error("not a local file URI")
115
+ root.chosen(path)
116
+ } catch (error) { root.picker.say("This favorite points to a location Flea cannot open.", true) }
117
+ } else if (entry.path.indexOf("://") >= 0) {
118
+ root.awaitingNetwork = true
119
+ network.openShare(entry.path, false, entry.label, false)
120
+ } else root.chosen(entry.path)
121
+ }
122
+ // Only the row the dialog asked for answers it, which the device says and a path alone cannot.
123
+ function deviceOpened(path) {
124
+ var awaited = root.awaitingDevice
125
+ if (awaited.length === 0) return
126
+ for (var i = 0; i < devices.entries.length; i++) {
127
+ if (devices.entries[i].device === awaited && devices.entries[i].path === path) {
128
+ root.awaitingDevice = ""
129
+ root.chosen(path)
130
+ return
131
+ }
132
+ }
133
+ }
134
+
135
+ function openChild(uri, label) { network.openChildShare(uri, label) }
136
+ function retry(requestId, uri, label, password) { root.awaitingNetwork = true; network.saveLocation(uri, label, password, requestId) }
137
+ function cancelNetwork(requestId) {
138
+ root.awaitingNetwork = false
139
+ network.cancelLocation(requestId)
140
+ }
141
+ Connections {
142
+ target: root.picker
143
+ function onPathChanged() { root.awaitingNetwork = false; root.awaitingDevice = "" }
144
+ function onBackendUnavailableChanged() {
145
+ if (root.picker.backendUnavailable) { root.awaitingNetwork = false; root.awaitingDevice = "" }
146
+ }
147
+ }
148
+ function controls() {
149
+ var out = []
150
+ for (var i = 0; i < rail.count; i++) {
151
+ var item = rail.itemAtIndex(i)
152
+ if (item) out.push(root.picker.control(root.entries[i].label, item, !root.entries[i].error && !root.picker.submitting && !root.picker.backendUnavailable))
153
+ }
154
+ return out
155
+ }
156
+ // Standing, not loaded on demand: its own listing is what the NETWORK rows are, and the window
157
+ // pays the same five-second rhythm for them.
158
+ Flea.NetworkMounts {
159
+ id: network
160
+ savedFavourites: Flea.Favourites.records
161
+ onCompleted: function(requestId, uri, success, reason, connectFailed) { root.networkCompleted(requestId, uri, success, reason, connectFailed) }
162
+ onOpened: function(path) { if (root.awaitingNetwork) root.chosen(path) }
163
+ // A FUSE path that is a file: the chooser selects it, the way a favourite file resolves.
164
+ onOpenFileRequested: function(path) { if (root.awaitingNetwork) root.chosen(path) }
165
+ onMessage: function(text, error) { root.picker.say(text, error) }
166
+ onRetryRequested: function(uri, label, password, reason, failed) {
167
+ if (root.awaitingNetwork) root.picker.retryNetwork(uri, label, password, reason, failed)
168
+ }
169
+ onSharesListed: function(uri, label, names) {
170
+ if (root.awaitingNetwork) root.picker.showShares(uri, label, names)
171
+ }
172
+ }
173
+
174
+ Flea.DeviceMounts {
175
+ id: devices
176
+ onOpened: function (path) { root.deviceOpened(path) }
177
+ // A message carries no device, so it cannot end a wait: only the awaited device's own open does.
178
+ onMessage: function (text, error) { root.picker.say(text, error) }
179
+ // The picker's one-line message has no detail view, so the headline stands alone.
180
+ onOperationResult: function (headline, detail, error) { root.picker.say(headline, error) }
181
+ }
182
+
183
+ // The phone rows the window draws, read off the same gio listing; their mount is the share leg.
184
+ Flea.PhoneMounts {
185
+ id: phones
186
+ listingText: network.mountListing
187
+ onMessage: function (text, error) { root.picker.say(text, error) }
188
+ }
189
+ }
@@ -0,0 +1,92 @@
1
+ import Quickshell
2
+ import Quickshell.Io
3
+ import QtQuick
4
+ import QtQml.XmlListModel
5
+ import "js/Format.js" as Format
6
+ import "js/Recent.js" as Recent
7
+
8
+ // The desktop's own recent history, read and never written. Qt's XML reader does the parsing, so
9
+ // this file has no parser of its own: XmlListModel is QXmlStreamReader behind a query, and a
10
+ // history Flea hand-parsed would be a second XBEL implementation on a file it does not own.
11
+ // The read is lazy, so a picker whose user never asks for Recent never opens the file at all.
12
+ QtObject {
13
+ id: root
14
+
15
+ // The validated absolute paths, newest first; empty until refresh() has been asked for.
16
+ property var paths: []
17
+ property var visits: ({})
18
+ signal refreshed()
19
+
20
+ readonly property string file: Recent.historyPath(Quickshell.env("XDG_DATA_HOME"), Quickshell.env("HOME"))
21
+ function joinRequesters(current, requester) { return Recent.joinRequesters(current, requester) }
22
+
23
+ // Asked for when the Recent location is opened, so the file is re-read rather than remembered:
24
+ // every other application on the box appends to it while this window is up.
25
+ function refresh() {
26
+ if (historyFile.path === root.file) historyFile.reload()
27
+ else historyFile.path = root.file
28
+ }
29
+
30
+ // Read first so only an absent file is quiet: XmlListModel warns on one, and a fresh account has none.
31
+ property FileView historyFile: FileView {
32
+ printErrors: false
33
+ onLoaded: root.parse()
34
+ onLoadFailed: function (error) {
35
+ if (error !== FileViewError.FileNotFound)
36
+ console.warn("PickerRecent: could not read " + root.file + ": " + error)
37
+ root.paths = []
38
+ root.visits = ({})
39
+ root.refreshed()
40
+ }
41
+ }
42
+
43
+ // The model re-reads what the check just read, so a present history is a second read from the page cache.
44
+ function parse() {
45
+ var url = Format.fileUri(root.file)
46
+ if (history.source.toString() === url) history.reload()
47
+ else history.source = url
48
+ }
49
+
50
+ property XmlListModel historyModel: XmlListModel {
51
+ id: history
52
+ query: "/xbel/bookmark"
53
+ // Never on the construction-time Null: that answered refreshed with no rows, and the path jump takes the first answer.
54
+ onStatusChanged: if (status === XmlListModel.Ready || status === XmlListModel.Error) root.rebuild()
55
+ XmlListModelRole { name: "href"; attributeName: "href" }
56
+ XmlListModelRole { name: "visited"; attributeName: "visited" }
57
+ XmlListModelRole { name: "modified"; attributeName: "modified" }
58
+ XmlListModelRole { name: "added"; attributeName: "added" }
59
+ }
60
+
61
+ // Qt 6 gives XmlListModel no get(), so the rows are read off the model itself: data() is
62
+ // invokable and the roles are numbered from Qt.UserRole in the order declared above.
63
+ readonly property int hrefRole: Qt.UserRole
64
+ readonly property int visitedRole: Qt.UserRole + 1
65
+ readonly property int modifiedRole: Qt.UserRole + 2
66
+ readonly property int addedRole: Qt.UserRole + 3
67
+
68
+ // Every bookmark is read, because ui/js/Recent.js LIMIT bounds the rail and not the read: XBEL
69
+ // promises no order, so a bound in file order hands the sort an arbitrary slice and lists the
70
+ // newest of that. Measured on oldest-first fixtures, six batches whole against two capped: a
71
+ // rebuild costs about 3 ms at 500 bookmarks whichever way, tens of ms against 3 at 5,000, and
72
+ // hundreds of ms against 4 at 50,000, over an XmlListModel parse both pay that is itself a
73
+ // sixth of a second there. The Instantiator this replaced cost seconds at 50,000.
74
+ function rebuild() {
75
+ var found = []
76
+ var wanted = history.count
77
+ for (var i = 0; i < wanted; i++) {
78
+ var at = history.index(i, 0)
79
+ // visited is when the file itself was last opened, which is what Recent means; the
80
+ // other two stamp the bookmark and stand in for a writer that left visited out.
81
+ var visited = String(history.data(at, root.visitedRole) || "")
82
+ var modified = String(history.data(at, root.modifiedRole) || "")
83
+ var added = String(history.data(at, root.addedRole) || "")
84
+ found.push({ href: String(history.data(at, root.hrefRole) || ""),
85
+ stamp: visited.length > 0 ? visited : (modified.length > 0 ? modified : added) })
86
+ }
87
+ var entries = Recent.entries(found)
88
+ root.visits = Recent.visits(entries)
89
+ root.paths = entries.map(function (entry) { return entry.path })
90
+ root.refreshed()
91
+ }
92
+ }
@@ -0,0 +1,101 @@
1
+ import QtQuick
2
+ import "." as Flea
3
+ import "js/Format.js" as Format
4
+ import "js/Picker.js" as Picker
5
+
6
+ // The save area: Filename and its field in one column, and under it the collision review's error line.
7
+ Item {
8
+ id: root
9
+ required property var picker
10
+ property Item keyTarget: null
11
+ // Only keys left unhandled by Filename reach the listing, after editing and the field's Tab walk.
12
+ Keys.forwardTo: root.keyTarget ? [root.keyTarget] : []
13
+ signal nameEdited(string text)
14
+ signal accepted()
15
+ readonly property alias fieldItem: field
16
+ readonly property alias scrollItem: body
17
+ readonly property alias statusItem: statusLine
18
+ readonly property string askedName: root.picker.saveName || root.picker.req.name
19
+ readonly property bool refused: root.askedName.length > 0 && !Picker.validName(root.askedName)
20
+ visible: root.picker.saving
21
+ property real maximumHeight: parent.height
22
+ // The answer row below is the column's last row and brings the gap under the field; the column keeps only its top padding.
23
+ implicitHeight: visible ? Math.min(maximumHeight, body.wanted + Theme.spacing.rowPaddingX) : 0
24
+
25
+ Rectangle { anchors.fill: parent; color: Theme.color.surface }
26
+ Rectangle {
27
+ width: parent.width
28
+ height: Theme.spacing.hairline
29
+ color: root.picker.edge
30
+ }
31
+ Flea.CardScroll {
32
+ id: body
33
+ bleed: Theme.focusClearance
34
+ anchors.fill: parent
35
+ anchors.margins: Theme.spacing.rowPaddingX - Theme.focusClearance
36
+ anchors.bottomMargin: -Theme.focusClearance
37
+ Column {
38
+ id: column
39
+ width: parent.width
40
+ spacing: Theme.spacing.gap
41
+
42
+ Text {
43
+ text: "Filename"
44
+ textFormat: Text.PlainText
45
+ color: Theme.color.foreground
46
+ font { family: Theme.font.family; pixelSize: Theme.font.caption }
47
+ }
48
+ Rectangle {
49
+ width: parent.width
50
+ height: Math.max(Theme.hitMin, Theme.rowHeight - Theme.spacing.rowPaddingY)
51
+ color: Theme.color.background
52
+ border.width: Theme.spacing.hairline
53
+ // Muted at rest, accent on focus: the frame DialogField, MenuActionDialog, OpenWithDialog and PermissionsDialog draw.
54
+ border.color: field.activeFocus ? Theme.color.accent : Theme.color.mutedFrame
55
+ TextInput {
56
+ id: field
57
+ anchors.fill: parent
58
+ anchors.leftMargin: Theme.spacing.gap
59
+ anchors.rightMargin: Theme.spacing.gap
60
+ verticalAlignment: TextInput.AlignVCenter
61
+ text: root.picker.saveName
62
+ color: Theme.color.foreground
63
+ selectionColor: Theme.color.accent
64
+ selectedTextColor: Theme.color.background
65
+ font { family: Theme.font.family; pixelSize: Theme.font.body }
66
+ clip: true
67
+ activeFocusOnTab: true
68
+ enabled: !root.picker.submitting
69
+ Accessible.name: "Filename"
70
+ onTextEdited: root.nameEdited(text)
71
+ onAccepted: root.accepted()
72
+ // Enter remains the field's save action when it opens a collision review.
73
+ Keys.onReturnPressed: root.accepted()
74
+ Keys.onEnterPressed: root.accepted()
75
+ Keys.onTabPressed: function(event) { root.picker.stepFocus(field, (event.modifiers & Qt.ShiftModifier) !== 0) }
76
+ Keys.onBacktabPressed: root.picker.stepFocus(field, true)
77
+ }
78
+ }
79
+ Text {
80
+ id: statusLine
81
+ width: parent.width
82
+ visible: text.length > 0
83
+ text: root.refused ? Format.fill(Picker.SAVE_REFUSED, root.askedName, Picker.NAME_REFUSED)
84
+ : root.picker.saveError || (root.picker.collisionReview ? Picker.collisionLine(Picker.jointName(root.picker.saveName)) : "")
85
+ textFormat: Text.PlainText
86
+ wrapMode: Text.Wrap
87
+ color: Theme.color.errorOnSurface
88
+ font { family: Theme.font.family; pixelSize: Theme.font.caption }
89
+ }
90
+ }
91
+ }
92
+ // The review returns the keyboard to the field with the stem selected and the caret at its end.
93
+ function selectStem() {
94
+ field.forceActiveFocus(Qt.TabFocusReason)
95
+ field.select(0, Picker.stemEnd(field.text))
96
+ }
97
+ function focusItems() { return root.visible ? [field] : [] }
98
+ function controls() {
99
+ return root.focusItems().map(function(item) { return root.picker.control("Filename", item, item.enabled) })
100
+ }
101
+ }