buninu-browser 0.2.4 → 0.4.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.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +102 -2
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +57 -0
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +78 -13
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/paint/frame.js +35 -3
- package/lib/paint/raw-frames.js +47 -0
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/compositor-channel.js +103 -0
- package/lib/renderer/compositor.js +25 -33
- package/lib/renderer/flags.js +1 -1
- package/lib/renderer/host.js +128 -92
- package/lib/renderer/main.js +38 -4
- package/lib/renderer/page-realm.js +32 -1
- package/lib/renderer/page-renderer.js +250 -21
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +54 -0
- package/test/fixtures/browser-raw-demo.html +36 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/raw-capture-benchmark.json +76 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/raw-capture.test.js +152 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
- package/test/window-resize.test.js +36 -0
- package/tools/bench-raw-capture.js +32 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "DirSizes.js" as DirSizes
|
|
4
|
+
.import "FolderSorts.js" as FolderSorts
|
|
5
|
+
.import "RecentMode.js" as RecentMode
|
|
6
|
+
.import "Thumbs.js" as Thumbs
|
|
7
|
+
|
|
8
|
+
// What the header's click and the s and S keys do, taking ui/Pane.qml's root the way Nav.js and
|
|
9
|
+
// Ops.js do: the pane holds the state, this holds what the state does. ui/Backend.qml records the
|
|
10
|
+
// order the listing is actually in, because list re-sorts by name ascending and only this file
|
|
11
|
+
// changes it after that.
|
|
12
|
+
|
|
13
|
+
// The orders the backend can actually produce, in the order s steps through them, and the only keys
|
|
14
|
+
// that may move the recorded order. It is not the list of what gets refused: docs/protocol.md "sort"
|
|
15
|
+
// refuses every other key by name, and the backend is the one that says so, see ui/js/Errors.js, aliased from FolderSorts.ORDERS, the one key table.
|
|
16
|
+
var ORDERS = FolderSorts.ORDERS
|
|
17
|
+
|
|
18
|
+
// The three sort decisions, kept apart from what a pane does with them: the chooser makes the same ones over its narrower list.
|
|
19
|
+
|
|
20
|
+
// The sorted column reverses; any other starts ascending, the order the canvas header draws beside "Name".
|
|
21
|
+
function columnOrder(orders, by, desc, key) {
|
|
22
|
+
if (orders.indexOf(key) < 0)
|
|
23
|
+
return null
|
|
24
|
+
return { key: key, desc: by === key ? !desc : false }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Always ascending, column and direction separate; an outside order, a saved kind in the chooser, starts over at the first.
|
|
28
|
+
function nextOrder(orders, by) {
|
|
29
|
+
return { key: orders[(orders.indexOf(by) + 1) % orders.length], desc: false }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Whichever order the listing is in, offered or inherited: the user asked for the reverse of what they have.
|
|
33
|
+
function reverseOrder(by, desc) {
|
|
34
|
+
return { key: by, desc: !desc }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// An edit open or a rename pending holds the sort and commits an open edit; it applies once the rename settles.
|
|
38
|
+
function holdForRename(pane, request) {
|
|
39
|
+
if (!(pane.renamingIndex >= 0 || pane.renamePending)) return false
|
|
40
|
+
pane.pendingSort = request
|
|
41
|
+
if (pane.renamingIndex >= 0 && !pane.renamePending) pane.commitOpenRename()
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ui/Header.qml's click: only ORDERS leave this file except the forget row.
|
|
46
|
+
function column(pane, key) {
|
|
47
|
+
// The flyout's last row (issue 179) forgets the folder and lists it on the default.
|
|
48
|
+
// Recent stands on the root, so forgetting re-asks its history instead of listing the root over it.
|
|
49
|
+
if (key === "__default__") {
|
|
50
|
+
// Forgetting re-lists, so it holds like any other sort request.
|
|
51
|
+
if (holdForRename(pane, { forget: true })) return
|
|
52
|
+
if ((pane.recentMode || "").length > 0) {
|
|
53
|
+
RecentMode.run(pane, pane.recentPaths || [])
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
if (pane.backend && pane.backend.forgetFolderSort)
|
|
57
|
+
pane.backend.forgetFolderSort(pane.path)
|
|
58
|
+
// A preserved pane skips the reset a list would do, so refresh here or it keeps its old order.
|
|
59
|
+
if (pane.backend && pane.backend.resetSort)
|
|
60
|
+
pane.backend.resetSort(pane.path)
|
|
61
|
+
pane.openWithoutHistory(pane.path)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
// Unsupported columns remain labels rather than sending a sort the backend must refuse.
|
|
65
|
+
var order = columnOrder(ORDERS, pane.backend.sortBy, pane.backend.sortDesc, key)
|
|
66
|
+
if (order)
|
|
67
|
+
resort(pane, order.key, order.desc)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// s walks ORDERS so it never lands on a refusal-only column; an aimed click earns the reason, a walking key only noise.
|
|
71
|
+
function next(pane) {
|
|
72
|
+
var order = nextOrder(ORDERS, pane.backend.sortBy)
|
|
73
|
+
resort(pane, order.key, order.desc)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// S: the capital-is-the-variant pair g/G and j/J use.
|
|
77
|
+
function reverse(pane) {
|
|
78
|
+
var order = reverseOrder(pane.backend.sortBy, pane.backend.sortDesc)
|
|
79
|
+
resort(pane, order.key, order.desc)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The request goes out for every key, so the refusal is the backend's alone. Only an order it will
|
|
83
|
+
// really produce moves the recorded one, or the mark would describe a listing that never changed.
|
|
84
|
+
function resort(pane, key, desc) {
|
|
85
|
+
if (holdForRename(pane, { key: key, desc: desc })) return
|
|
86
|
+
// Asking for the order the listing is already in would drop every row-indexed cache and put the
|
|
87
|
+
// cursor back to redraw the rows already on screen, so it is not asked for at all.
|
|
88
|
+
if (pane.backend.sortBy === key && pane.backend.sortDesc === desc) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
pane.backend.sort(key, desc)
|
|
92
|
+
if (ORDERS.indexOf(key) < 0) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
pane.backend.sortBy = key
|
|
96
|
+
pane.backend.sortDesc = desc
|
|
97
|
+
// A walk's rows are matches, not the folder's, so sorting them writes nothing. Recent stands
|
|
98
|
+
// on the root, so its order is never remembered as the root's own.
|
|
99
|
+
if (pane.backend && pane.backend.rememberFolderSort
|
|
100
|
+
&& (pane.searchMode || "") === "" && (pane.recentMode || "") === "")
|
|
101
|
+
pane.backend.rememberFolderSort(pane.path, key, desc)
|
|
102
|
+
// A reorder moves every row, so the caches keyed by a row index are as stale as a new listing's,
|
|
103
|
+
// and a selection of row indices would silently come to name different files.
|
|
104
|
+
pane.thumbState = Thumbs.empty()
|
|
105
|
+
pane.dirSizeState = DirSizes.empty()
|
|
106
|
+
pane.clearSelection()
|
|
107
|
+
pane.setCursor(0)
|
|
108
|
+
// sort emits no rows of its own, so the reordered window is asked for here; see docs/protocol.md.
|
|
109
|
+
pane.backend.window(0, pane.windowSize)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The held sort a resort deferred; runs when the edit ends or the listing lands.
|
|
113
|
+
function applyPending(pane) {
|
|
114
|
+
var held = pane.pendingSort
|
|
115
|
+
if (!held) return
|
|
116
|
+
// Not settled yet, or nowhere to send it: the hold stands.
|
|
117
|
+
if (pane.listInFlight || pane.renamingIndex >= 0 || pane.renamePending) return
|
|
118
|
+
if (!pane.backend || pane.backend.quitting || !pane.backend.running) { pane.pendingSort = null; return }
|
|
119
|
+
pane.pendingSort = null
|
|
120
|
+
if (held.forget) { column(pane, "__default__"); return }
|
|
121
|
+
resort(pane, held.key, held.desc)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// A sorted column's title: the label and the geometric mark of the direction, characters the stock monospace has, so the mark scales with the font like the label.
|
|
125
|
+
function title(label, descending) {
|
|
126
|
+
return label + " " + (descending ? "\u25be" : "\u25b4")
|
|
127
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Format.js" as Format
|
|
4
|
+
|
|
5
|
+
// The state word an errored menu row says in its hint slot: a fixed value here, never a provider's reason.
|
|
6
|
+
var WORDS = {
|
|
7
|
+
taildropFolder: "files only",
|
|
8
|
+
taildropSignedOut: "signed out",
|
|
9
|
+
taildropNoPeers: "no devices",
|
|
10
|
+
taildropDown: "offline",
|
|
11
|
+
localsendFolder: "files only",
|
|
12
|
+
localsendNoPeers: "no devices",
|
|
13
|
+
dropboxSignedOut: "signed out",
|
|
14
|
+
dropboxDown: "not running",
|
|
15
|
+
permissionsLink: "symlink",
|
|
16
|
+
permissionsSpecial: "special file",
|
|
17
|
+
permissionsNotYours: "not yours"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Sample input: { rowIsFile: false, taildropCode: "" }, or { rowIsFile: true, taildropCode: "signedOut" }; codes are ui/js/Taildrop.js status().
|
|
21
|
+
function taildrop(p) {
|
|
22
|
+
if (p.rowIsFile === false) return WORDS.taildropFolder
|
|
23
|
+
if (p.taildropCode === "signedOut") return WORDS.taildropSignedOut
|
|
24
|
+
return p.taildropCode === "noPeers" ? WORDS.taildropNoPeers : WORDS.taildropDown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Sample input: { rowIsFile: false }. The send finds nobody on the network for any other cause.
|
|
28
|
+
function localsend(p) {
|
|
29
|
+
return p.rowIsFile === false ? WORDS.localsendFolder : WORDS.localsendNoPeers
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Sample input: { dropboxCode: "signedOut" }; codes are ui/js/Dropbox.js account() and status().
|
|
33
|
+
function dropbox(p) {
|
|
34
|
+
return p.dropboxCode === "signedOut" ? WORDS.dropboxSignedOut : WORDS.dropboxDown
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A link names itself; any other kind that is neither a file nor a folder is special, and a mode never read names nothing.
|
|
38
|
+
function permissions(mode) {
|
|
39
|
+
var kind = (Number(mode) || 0) & Format.S_IFMT
|
|
40
|
+
if (kind === 0) return ""
|
|
41
|
+
return kind === Format.S_IFLNK ? WORDS.permissionsLink : WORDS.permissionsSpecial
|
|
42
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// The listing swap's decisions, see AGENTS.md "The listing swap"; ui/PaneSwap.qml holds the state and the timer.
|
|
4
|
+
|
|
5
|
+
// Both swaps' cap, the pane mark's hold-off and a PDF page render's cap: with four 120 Hz frames for input, the Timer and the draw, the mark shows by 100 ms.
|
|
6
|
+
var CAP_MS = 66
|
|
7
|
+
|
|
8
|
+
// The hold-off of a loading state nothing keeps MIN_SHOW_MS: LoadingState outside the pane, the folder fallback, a search walk's mark.
|
|
9
|
+
var HOLD_MS = 150
|
|
10
|
+
|
|
11
|
+
// A drawn loading state stands this long before content replaces it; under 100 ms the two changes read as one blink.
|
|
12
|
+
var MIN_SHOW_MS = 100
|
|
13
|
+
|
|
14
|
+
// Answered while a listing is out: the navigations take its place, escape touches no row, the rest change the window.
|
|
15
|
+
var ANSWERED_WHILE_LISTING = ["open", "parent", "historyBack", "historyForward", "escape",
|
|
16
|
+
"viewList", "viewColumns", "viewGrid", "viewDual", "sidebar", "windowNew", "togglePreview",
|
|
17
|
+
"reload"]
|
|
18
|
+
|
|
19
|
+
// What a swallowed key says, the sentence every refused navigation already gives.
|
|
20
|
+
var LOADING = "A directory is already loading."
|
|
21
|
+
|
|
22
|
+
// What a listed or rows line does: dropped, kept for the rows behind it, applied alone, or landed with the kept line.
|
|
23
|
+
var DROP = "drop"
|
|
24
|
+
var KEEP = "keep"
|
|
25
|
+
var APPLY = "apply"
|
|
26
|
+
var LAND = "land"
|
|
27
|
+
var WAIT = "wait"
|
|
28
|
+
|
|
29
|
+
function idle() {
|
|
30
|
+
return { holding: false, listed: null, fellBack: false, waits: false, walk: false, query: "", since: 0, relist: false }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Only a settled listing is held, plus a walk's drawn state for a listing after it; see ui/PaneSwap.qml hold.
|
|
34
|
+
function begin(state, listingState, searchMode, ask, now) {
|
|
35
|
+
// The exit holds the whole search state, strip and rows, until the folder lands in one turn.
|
|
36
|
+
var exit = ask.holdWalk === true
|
|
37
|
+
var afterWalk = state.walk === true && !exit
|
|
38
|
+
var holding = ask.clearAtOnce !== true && listingState !== "loading" && (searchMode === "" || exit) && !afterWalk
|
|
39
|
+
// An unheld Back or Forward still keeps its listed line, so the count, the rows and the cursor land in one turn.
|
|
40
|
+
var waits = !holding && !!ask.restore && String(ask.restore.name || "").length > 0
|
|
41
|
+
return { holding: holding, listed: null, fellBack: false, waits: waits, walk: state.walk, query: ask.keptQuery || "", since: now,
|
|
42
|
+
relist: ask.relist === true }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// A session line lands only on its open session's newest sent gen; a closed exit's lines never land either.
|
|
46
|
+
function dropSessionLine(searchMode, gen, searchGen, leaving, openedGen) {
|
|
47
|
+
if (gen > 0 && leaving === true) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
return gen > 0 && ((searchMode || "").length === 0 || gen !== searchGen || gen <= (openedGen || 0))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// A live answer holds old rows until its own rows land, no listing out.
|
|
54
|
+
function keepLive(searchMode, inFlight) {
|
|
55
|
+
return (searchMode || "").length > 0 && inFlight !== true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// While a list is out, only a listed line naming the directory asked for answers it; an earlier sort's or search's is dropped.
|
|
59
|
+
// Held rows, a fallback or a loading mark already drawn keep it for the rows, so the mark never gives way to a count over none.
|
|
60
|
+
function onListed(state, inFlight, path, asked, marked) {
|
|
61
|
+
if (inFlight === true && path.length > 0 && path !== asked)
|
|
62
|
+
return DROP
|
|
63
|
+
return state.holding || state.fellBack || state.waits || marked === true ? KEEP : APPLY
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A rows line ahead of its listed line answers a listing already replaced; one behind a kept listed line lands with it,
|
|
67
|
+
// once the loading mark it replaces has stood waitMs more.
|
|
68
|
+
function onRows(state, inFlight, listedSeen, waitMs) {
|
|
69
|
+
if (inFlight === true && listedSeen !== true)
|
|
70
|
+
return DROP
|
|
71
|
+
if (state.listed === null)
|
|
72
|
+
return APPLY
|
|
73
|
+
return waitMs > 0 ? WAIT : LAND
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// How much longer a loading state drawn at since stands before it may go; 0 when none is drawn.
|
|
77
|
+
function showLeft(since, now) {
|
|
78
|
+
return since > 0 ? Math.max(0, since + MIN_SHOW_MS - now) : 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// How long a held listing begun at state.since still holds before it falls back; -1 when nothing is held.
|
|
82
|
+
function capLeft(state, now) {
|
|
83
|
+
return state.holding === true ? Math.max(0, state.since + CAP_MS - now) : -1
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The pane mark's hold-off: a directory listing keeps its mark MIN_SHOW_MS, so it draws at the cap; a walk's does not.
|
|
87
|
+
function markHoldOff(searchMode) {
|
|
88
|
+
return (searchMode || "") === "" ? CAP_MS : HOLD_MS
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A directory listing out under a drawn mark; a walk's or a search's matches stream into a loading pane, never waiting on it.
|
|
92
|
+
function marked(since, inFlight, searchMode, walk) {
|
|
93
|
+
return since > 0 && inFlight === true && (searchMode || "") === "" && walk !== true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function kept(state, listed) {
|
|
97
|
+
return Object.assign({}, state, { listed: listed })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Every listed line that is applied rather than kept says whether the rows it brings are a walk's.
|
|
101
|
+
function heard(state, walk) {
|
|
102
|
+
return Object.assign({}, state, { walk: walk === true })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A held listing is only ever a directory's, which is what begin() asked for.
|
|
106
|
+
function landed(state) {
|
|
107
|
+
return Object.assign({}, state, { holding: false, listed: null, fellBack: false, waits: false, walk: false, relist: false })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The cap ran out: the pane shows the loading state until the rows land, and a listed line already kept waits for them.
|
|
111
|
+
function expired(state) {
|
|
112
|
+
return Object.assign({}, state, { holding: false, fellBack: true })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// A failed or refused listing ends the hold without a swap; the failure draws what it always did.
|
|
116
|
+
function dropped(state) {
|
|
117
|
+
return Object.assign({}, state, { holding: false, listed: null, fellBack: false, waits: false, relist: false })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The listing is no longer out, however it ended.
|
|
121
|
+
function ended(state) {
|
|
122
|
+
return Object.assign({}, state, { holding: false, listed: null, fellBack: false, waits: false, relist: false })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A stale or window refusal ended only its own request, and so does a scan failure naming a folder no longer asked for; any other failure ends the listing, letting held rows go while it is still out.
|
|
126
|
+
function failListing(pane, where, path) {
|
|
127
|
+
if (where === "stale" || where === "window")
|
|
128
|
+
return false
|
|
129
|
+
if (where === "scan" && !!path && path !== pane.listingPath)
|
|
130
|
+
return false
|
|
131
|
+
// An Esc relist that died leaves no cursor file armed for whatever listing comes next.
|
|
132
|
+
if (pane.swap.phase.relist === true)
|
|
133
|
+
pane.pendingSelect = ""
|
|
134
|
+
pane.swap.drop()
|
|
135
|
+
pane.listInFlight = false
|
|
136
|
+
pane.listedSeen = false
|
|
137
|
+
return true
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Sample input: {"c":"transfer","op":"move","rows":[3],"dest":"/home/gm/omarchy"} with held 7 gains "listing":7, unless it names one.
|
|
141
|
+
function named(request, held) {
|
|
142
|
+
if (request.rows === undefined || request.listing !== undefined || !(held > 0))
|
|
143
|
+
return request
|
|
144
|
+
return Object.assign({}, request, { listing: held })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A key swallowed while a listing is out, held or loading: the backend already numbers rows for the directory asked for.
|
|
148
|
+
function swallows(inFlight, action) {
|
|
149
|
+
return inFlight === true && action.length > 0 && ANSWERED_WHILE_LISTING.indexOf(action) < 0
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// A re-sort renumbers every row without a new list, so a selection made while it was in flight names other files once the new numbering lands.
|
|
153
|
+
function clearsSelection(oldHeld, listing) {
|
|
154
|
+
return oldHeld > 0 && listing > 0 && listing !== oldHeld
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// A blank frame: a listing out and loading before the cap, or no row drawn while a live count still names rows.
|
|
158
|
+
function frameKind(inFlight, listingState, fellBack, drawn, named, live) {
|
|
159
|
+
if (inFlight === true && listingState === "loading") {
|
|
160
|
+
return fellBack === true ? "loading" : "blank"
|
|
161
|
+
}
|
|
162
|
+
if (live === true && drawn === 0 && named > 0) {
|
|
163
|
+
return "blank"
|
|
164
|
+
}
|
|
165
|
+
return ""
|
|
166
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Format.js" as Format
|
|
4
|
+
.import "KeyName.js" as KeyName
|
|
5
|
+
|
|
6
|
+
// Flea's text size, which is the Display section's one control. The SettingsScale board rules the
|
|
7
|
+
// shape: Omarchy owns the size by default, and an override pins one stop from Omarchy's own range
|
|
8
|
+
// rather than a free number or a multiplier of its own. The monitor scale beside it belongs to the
|
|
9
|
+
// compositor, which is why nothing here steps or cycles one.
|
|
10
|
+
|
|
11
|
+
// Whole sentences with "%1" for a value, filled by fill so each translates as one.
|
|
12
|
+
var WORDS = {
|
|
13
|
+
followsOmarchy: "Text size follows Omarchy, %1",
|
|
14
|
+
override: "Text size %1. %2 follows Omarchy again",
|
|
15
|
+
overrideAlone: "Text size %1"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The board's seven documented stops, in px, read off its own layout table. They are not
|
|
19
|
+
// contiguous: 13, 15, 17, 18 and 19 are not stops. The range is the OEM one, 9 to 20, so Flea
|
|
20
|
+
// offers no size Omarchy does not.
|
|
21
|
+
var STOPS = [9, 10, 11, 12, 14, 16, 20]
|
|
22
|
+
|
|
23
|
+
// Omarchy's own type ladder, from Commons/Style.qml, whose bodySmall is fontPx(0.917) and caption
|
|
24
|
+
// fontPx(0.833) over the base size. Running the same two ratios at a stop is what makes an override
|
|
25
|
+
// the size Omarchy itself would have drawn, rather than a second ladder that can disagree with it.
|
|
26
|
+
var BODY_RATIO = 1.0
|
|
27
|
+
var BODY_SMALL_RATIO = 0.917
|
|
28
|
+
var CAPTION_RATIO = 0.833
|
|
29
|
+
|
|
30
|
+
var FOLLOW = "system"
|
|
31
|
+
|
|
32
|
+
function follow() {
|
|
33
|
+
return { mode: FOLLOW }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Follow Omarchy is the default, and the board names its stored shape: {"mode":"system"}. An
|
|
37
|
+
// override is the same key holding a number instead, which is src/uischema.rs's own display.textSize
|
|
38
|
+
// rule: "system", or one stop the OEM panel could have produced. There is no second stored shape.
|
|
39
|
+
function following(stored) {
|
|
40
|
+
return !stored || !(Number(stored.mode) > 0)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The nearest stop, so a base Omarchy invented that is not on the list still enters the override on
|
|
44
|
+
// a real stop, and a hand-edited file cannot pin a free number. A tie takes the smaller stop, which
|
|
45
|
+
// is what the strict comparison below does: 13 enters at 12 and 15 at 14, so nothing grows unasked.
|
|
46
|
+
function nearest(px) {
|
|
47
|
+
var best = STOPS[0]
|
|
48
|
+
for (var i = 1; i < STOPS.length; i++) {
|
|
49
|
+
if (Math.abs(STOPS[i] - px) < Math.abs(best - px))
|
|
50
|
+
best = STOPS[i]
|
|
51
|
+
}
|
|
52
|
+
return best
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The size Flea actually draws at: Omarchy's own while following, the stored stop while overriding.
|
|
56
|
+
function effective(stored, omarchyBase) {
|
|
57
|
+
return following(stored) ? omarchyBase : nearest(Number(stored.mode))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Switching to Override starts on the stop beside the size already on screen, so the switch itself
|
|
61
|
+
// changes nothing and only a step does.
|
|
62
|
+
function pin(stored, omarchyBase) {
|
|
63
|
+
return { mode: nearest(effective(stored, omarchyBase)) }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// One stop along the list, clamped at both ends. Stepping while following becomes an override,
|
|
67
|
+
// which is what the first press of Ctrl+Shift+Plus should do.
|
|
68
|
+
function stepped(stored, omarchyBase, direction) {
|
|
69
|
+
var at = STOPS.indexOf(pin(stored, omarchyBase).mode)
|
|
70
|
+
var to = Math.max(0, Math.min(STOPS.length - 1, at + direction))
|
|
71
|
+
return { mode: STOPS[to] }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// A stored value another hand wrote, and 0.1.3's own {"mode":"override","px":N}: anything this
|
|
75
|
+
// cannot read as a size follows Omarchy, which is both the default and the state deleting ui.json
|
|
76
|
+
// restores, and a number that is not a stop lands on the nearest one rather than pinning a size the
|
|
77
|
+
// OEM panel could not produce. src/uistate.rs refuses the same shapes on the way in.
|
|
78
|
+
function parse(stored) {
|
|
79
|
+
var px = stored ? Number(stored.mode) : NaN
|
|
80
|
+
return px > 0 ? { mode: nearest(px) } : follow()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// One action-to-delta mapping for every caller, so Focus and the Trash host cannot drift apart.
|
|
84
|
+
function direction(action) {
|
|
85
|
+
return action === "textSizeReset" ? 0 : (action === "textSizeUp" ? 1 : -1)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function body(base) {
|
|
89
|
+
return Math.max(1, Math.round(base * BODY_RATIO))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function bodySmall(base) {
|
|
93
|
+
return Math.max(1, Math.round(base * BODY_SMALL_RATIO))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function caption(base) {
|
|
97
|
+
return Math.max(1, Math.round(base * CAPTION_RATIO))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The sentence the status bar shows, because a size changed by a chord with the panel shut is
|
|
101
|
+
// otherwise a state with no readout and no named way back.
|
|
102
|
+
function announce(stored, omarchyBase) {
|
|
103
|
+
var px = effective(stored, omarchyBase) + "px"
|
|
104
|
+
if (following(stored)) return Format.fill(WORDS.followsOmarchy, px)
|
|
105
|
+
var reset = KeyName.keyFor("textSizeReset")
|
|
106
|
+
return reset.length > 0 ? Format.fill(WORDS.override, px, reset) : Format.fill(WORDS.overrideAlone, px)
|
|
107
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// A row this listing has dealt with: null means asked and waiting, a string is the answer.
|
|
4
|
+
// A cache-only ask carries a mark, so its empty answer means "not tried" and a class switch forgets it.
|
|
5
|
+
var ASKED = null
|
|
6
|
+
var CACHE_ASKED = "cache-asked"
|
|
7
|
+
var CACHE_MISS = "cache-missed"
|
|
8
|
+
|
|
9
|
+
// Asked and not answered yet, a plain ask or a cache-only one; only these have a job to cancel.
|
|
10
|
+
function pending(value) {
|
|
11
|
+
return value === ASKED || value === CACHE_ASKED
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function empty() {
|
|
15
|
+
return { file: {}, order: [] }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The rows a request may name: the viewport in row indices, clamped to the last row of the listing.
|
|
19
|
+
function viewport(contentY, rowHeight, visibleRows, total) {
|
|
20
|
+
var first = Math.floor(contentY / rowHeight)
|
|
21
|
+
// A window that is not row aligned straddles one more row than it holds, so the bottom comes off its pixel extent.
|
|
22
|
+
var bottom = contentY + visibleRows * rowHeight
|
|
23
|
+
return { first: first, last: Math.min(total - 1, Math.ceil(bottom / rowHeight) - 1) }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Only visible rows, only rows the backend called thumbnailable, only rows never asked.
|
|
27
|
+
function plan(state, rows, held, first, last, mode) {
|
|
28
|
+
var ask = []
|
|
29
|
+
for (var i = first; i <= last; i++) {
|
|
30
|
+
if (state.file[i] !== undefined) {
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
var row = rows[i - held]
|
|
34
|
+
if (allowed(row, mode)) {
|
|
35
|
+
ask.push(i)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
var drop = []
|
|
39
|
+
for (var key in state.file) {
|
|
40
|
+
if (!pending(state.file[key])) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
var at = Number(key)
|
|
44
|
+
if (at < first || at > last || !allowed(rows[at - held], mode)) {
|
|
45
|
+
drop.push(at)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { ask: ask, drop: drop }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A cancelled row is forgotten here too, because the backend forgets it when it drops the job.
|
|
52
|
+
function forget(state, row) {
|
|
53
|
+
delete state.file[row]
|
|
54
|
+
var at = state.order.indexOf(row)
|
|
55
|
+
if (at >= 0) {
|
|
56
|
+
state.order.splice(at, 1)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// A miss on a generating class returns to unasked; on a cache-only class it stays a miss.
|
|
61
|
+
function miss(state, row, generating, cap) {
|
|
62
|
+
if (generating === true) {
|
|
63
|
+
forget(state, row)
|
|
64
|
+
return { file: state.file, order: state.order }
|
|
65
|
+
}
|
|
66
|
+
return remember(state, row, "", cap)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The wrapper is new so a QML binding on it re-evaluates; the map inside is mutated in place.
|
|
70
|
+
function applied(state, work) {
|
|
71
|
+
for (var i = 0; i < work.drop.length; i++) {
|
|
72
|
+
forget(state, work.drop[i])
|
|
73
|
+
}
|
|
74
|
+
var mark = work.cacheOnly === true ? CACHE_ASKED : ASKED
|
|
75
|
+
for (var j = 0; j < work.ask.length; j++) {
|
|
76
|
+
state.file[work.ask[j]] = mark
|
|
77
|
+
state.order.push(work.ask[j])
|
|
78
|
+
}
|
|
79
|
+
return { file: state.file, order: state.order }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A live answer re-lists some rows: each finished thumbnail follows its row, by name, to that row's new index. Asked and missed rows do not follow.
|
|
83
|
+
function carried(state, oldRows, oldHeld, newRows, newHeld) {
|
|
84
|
+
var at = Object.create(null)
|
|
85
|
+
for (var j = 0; j < newRows.length; j++) {
|
|
86
|
+
if (newRows[j].t) {
|
|
87
|
+
at[newRows[j].n] = newHeld + j
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
var next = empty()
|
|
91
|
+
for (var k = 0; k < state.order.length; k++) {
|
|
92
|
+
var file = state.file[state.order[k]]
|
|
93
|
+
var row = oldRows[state.order[k] - oldHeld]
|
|
94
|
+
if (file && file !== CACHE_ASKED && file !== CACHE_MISS && row && at[row.n] !== undefined) {
|
|
95
|
+
next.file[at[row.n]] = file
|
|
96
|
+
next.order.push(at[row.n])
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return next
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The cap bounds a policy bug, not normal use: only the rows a viewport held are ever recorded.
|
|
103
|
+
function remember(state, row, file, cap) {
|
|
104
|
+
if (state.file[row] === undefined) {
|
|
105
|
+
state.order.push(row)
|
|
106
|
+
}
|
|
107
|
+
// An empty answer to a cache-only ask was never a decode, so it keeps its mark.
|
|
108
|
+
state.file[row] = (file === "" && state.file[row] === CACHE_ASKED) ? CACHE_MISS : file
|
|
109
|
+
while (state.order.length > cap) {
|
|
110
|
+
delete state.file[state.order.shift()]
|
|
111
|
+
}
|
|
112
|
+
return { file: state.file, order: state.order }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function fileFor(state, row) {
|
|
116
|
+
var value = state.file[row]
|
|
117
|
+
if (value === CACHE_ASKED || value === CACHE_MISS) return ""
|
|
118
|
+
return typeof value === "string" ? value : ""
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Answered with nothing, which is not the same as not answered yet. A thumbnailer that could not
|
|
122
|
+
// read the file reports an empty name, and only once that has arrived is it honest to say so.
|
|
123
|
+
function refused(state, row) {
|
|
124
|
+
return state.file[row] === ""
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/backend/thumbs.rs THUMB_SIZE: the longest side of a cache file made from a larger original.
|
|
128
|
+
var CACHE_SIZE = 256
|
|
129
|
+
|
|
130
|
+
// How much a picture may be enlarged to fit its box, and never past limit: 1 for an original, which
|
|
131
|
+
// draws at its own size when it is smaller than the box (decided 2026-09-24). Sample input: (754, 471, 120, 68, 1) is 1.
|
|
132
|
+
function fitScale(boxWidth, boxHeight, width, height, limit) {
|
|
133
|
+
return Math.min(limit, boxWidth / Math.max(1, width), boxHeight / Math.max(1, height))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A cache file stands in for its original, so it is enlarged at most to the original's own size. The
|
|
137
|
+
// longest side is compared because EXIF can turn either one. Unknown dimensions leave a full-size cache
|
|
138
|
+
// file free to fill the box, while a smaller one already is the original. Sample input: (256, 171, 300, 200) is 300/256.
|
|
139
|
+
function thumbLimit(thumbWidth, thumbHeight, originalWidth, originalHeight) {
|
|
140
|
+
var thumbLongest = Math.max(1, thumbWidth, thumbHeight)
|
|
141
|
+
var originalLongest = Math.max(Number(originalWidth) || 0, Number(originalHeight) || 0)
|
|
142
|
+
if (originalLongest > 0)
|
|
143
|
+
return originalLongest / thumbLongest
|
|
144
|
+
return thumbLongest < CACHE_SIZE ? 1 : Infinity
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A video poster fills the frame; an image keeps the never-enlarge cap.
|
|
148
|
+
function posterLimit(isVideo, thumbWidth, thumbHeight, originalWidth, originalHeight) {
|
|
149
|
+
if (isVideo)
|
|
150
|
+
return Infinity
|
|
151
|
+
return thumbLimit(thumbWidth, thumbHeight, originalWidth, originalHeight)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Backend icon identity distinguishes image thumbnails from video without opening any extra file; an AppImage's own icon is its identity, so Images draws a row the backend flagged a.
|
|
155
|
+
function allowed(row, mode) {
|
|
156
|
+
if (!row || !row.t || mode === "off") return false
|
|
157
|
+
return mode !== "images" || row.i === "image-x-generic" || row.a === true
|
|
158
|
+
}
|