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,298 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// Which columns a list row of a given width draws. The name is the one column a file manager
|
|
4
|
+
// cannot do without, so it is the one that never loses: each metadata column drops instead, at
|
|
5
|
+
// the width where keeping it would push the name under its floor. ui/Header.qml and ui/Row.qml
|
|
6
|
+
// both resolve their set here, from the same width, so the header can never head a column the
|
|
7
|
+
// rows below it are not drawing.
|
|
8
|
+
|
|
9
|
+
// The optional columns, widest first, which is the order they drop in. Kind goes first: the row
|
|
10
|
+
// already marks its kind with a glyph and most names carry the extension, so it is the most
|
|
11
|
+
// redundant column as well as the widest. Mode goes last, because it is the permanent column.
|
|
12
|
+
var DROP_ORDER = ["kind", "date", "size", "mode"]
|
|
13
|
+
|
|
14
|
+
// The row width each optional column needs before it is drawn, keyed by column. t carries the
|
|
15
|
+
// tokens ui/Theme.qml resolved: rowPaddingX, gap, iconSize, nameMin, and one width per column.
|
|
16
|
+
// A floor includes every column that outlives it, because they drop in order and a wider column
|
|
17
|
+
// never survives a narrower one, which is what makes the four floors nest.
|
|
18
|
+
function floors(t) {
|
|
19
|
+
// The name's own slot at its floor: the row padding either side, the mark and the gap after it.
|
|
20
|
+
var running = t.rowPaddingX + t.iconSize + t.gap + t.nameMin + t.rowPaddingX
|
|
21
|
+
var out = {}
|
|
22
|
+
for (var i = DROP_ORDER.length - 1; i >= 0; i--) {
|
|
23
|
+
var key = DROP_ORDER[i]
|
|
24
|
+
running += t[key] + t.gap
|
|
25
|
+
out[key] = running
|
|
26
|
+
}
|
|
27
|
+
return out
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// One boolean per optional column, for a row of this width. hidden is the user's own set (keys
|
|
31
|
+
// "mode"/"size"/"date"/"kind", from qs module ViewState), subtracted from what the width
|
|
32
|
+
// affords: a hidden column never draws, and width still wins over a column the user wants back,
|
|
33
|
+
// so the name cannot be crowded out by a column the pane is too narrow to carry.
|
|
34
|
+
function set(width, t, hidden) {
|
|
35
|
+
var f = floors(t)
|
|
36
|
+
var h = {}
|
|
37
|
+
var list = hidden || []
|
|
38
|
+
for (var i = 0; i < list.length; i++) h[list[i]] = true
|
|
39
|
+
return {
|
|
40
|
+
location: false,
|
|
41
|
+
mode: width >= f.mode && !h["mode"],
|
|
42
|
+
size: width >= f.size && !h["size"],
|
|
43
|
+
date: width >= f.date && !h["date"],
|
|
44
|
+
kind: width >= f.kind && !h["kind"]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// DualPane protects its name floor without reserving absent Mode and Kind columns.
|
|
49
|
+
function dualSet(width, t, hidden) {
|
|
50
|
+
var base = 2 * t.rowPaddingX + t.iconSize + t.gap + t.nameMin
|
|
51
|
+
var showSize = (hidden || []).indexOf("size") < 0 && width >= base + t.size
|
|
52
|
+
var showDate = (hidden || []).indexOf("date") < 0
|
|
53
|
+
&& width >= base + (showSize ? t.size : 0) + t.date
|
|
54
|
+
return {mode: false, kind: false, location: false, size: showSize, date: showDate}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Recent protects its pane's name floor and reserves every non-hidden metadata width for Location.
|
|
58
|
+
function recentSet(width, t, hidden, dual) {
|
|
59
|
+
var base = 2 * t.rowPaddingX + t.iconSize + t.gap + t.nameMin
|
|
60
|
+
var metadataGap = dual ? 0 : t.gap
|
|
61
|
+
var h = hidden || []
|
|
62
|
+
var showSize = h.indexOf("size") < 0 && width >= base + t.size + metadataGap
|
|
63
|
+
var sizeSlot = showSize ? t.size + metadataGap : 0
|
|
64
|
+
var showDate = h.indexOf("date") < 0 && width >= base + sizeSlot + t.date + metadataGap
|
|
65
|
+
var locationFloor = base + (h.indexOf("size") < 0 ? t.size + metadataGap : 0)
|
|
66
|
+
+ (h.indexOf("date") < 0 ? t.date + metadataGap : 0) + t.location + t.gap
|
|
67
|
+
return {mode: false, kind: false,
|
|
68
|
+
size: showSize,
|
|
69
|
+
date: showDate,
|
|
70
|
+
location: width >= locationFloor}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// A peeked reply is keyed by what ordered it, so a stale ancestor column never survives the hidden-last toggle: path plus hidden plus hiddenLast.
|
|
74
|
+
// Sample input: peekKey("/a", true, true) is "/a\n11".
|
|
75
|
+
function peekKey(path, hidden, hiddenLast) {
|
|
76
|
+
return String(path) + "\n" + (hidden === true ? "1" : "0") + (hiddenLast === true ? "1" : "0")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The folder's own remembered order a column asks under and its reply echoes: "" for name order, else the key and 1 when descending.
|
|
80
|
+
// Sample input: orderTag("size", true) is "size1", orderTag("", false) is "".
|
|
81
|
+
function orderTag(by, desc) {
|
|
82
|
+
return by ? String(by) + (desc === true ? "1" : "0") : ""
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The row count and the folder order join the key only for an outstanding ask, so a resize or a re-sort never orphans a stored column.
|
|
86
|
+
// Sample input: sentKey("/a\n00", 35) is "/a\n00\n35", and sentKey("/a\n00", 35, "size1") is "/a\n00\n35\nsize1".
|
|
87
|
+
function sentKey(key, first, order) {
|
|
88
|
+
return String(key) + "\n" + Math.floor(Number(first)) + (order ? "\n" + String(order) : "")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// An ask this view made and still awaits, counted per sentKey (path, flags, row count, order), so another client's reply never lands in its column and a column re-asked in flight lands both replies.
|
|
92
|
+
// Sample input: trackAsk(trackAsk({}, "/a\n10\n35"), "/a\n10\n35") holds that key twice.
|
|
93
|
+
function trackAsk(pending, key) {
|
|
94
|
+
var next = {}
|
|
95
|
+
var src = pending || {}
|
|
96
|
+
for (var k in src) next[k] = src[k]
|
|
97
|
+
next[String(key)] = (next[String(key)] || 0) + 1
|
|
98
|
+
return next
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// True only for a reply this view asked for; anything else is another client's.
|
|
102
|
+
// Sample input: hasAsk(trackAsk({}, "/a\n10"), "/a\n10") is true.
|
|
103
|
+
function hasAsk(pending, key) {
|
|
104
|
+
return ((pending || {})[String(key)] || 0) > 0
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// One ask answered or superseded; a new listing drops them all.
|
|
108
|
+
// Sample input: dropAsk(trackAsk({}, "/a\n10"), "/a\n10") holds nothing, and dropAsk of a key asked twice holds it once.
|
|
109
|
+
function dropAsk(pending, key) {
|
|
110
|
+
var next = {}
|
|
111
|
+
var want = String(key)
|
|
112
|
+
var src = pending || {}
|
|
113
|
+
for (var k in src) {
|
|
114
|
+
var left = k === want ? src[k] - 1 : src[k]
|
|
115
|
+
if (left > 0) next[k] = left
|
|
116
|
+
}
|
|
117
|
+
return next
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The columns view's count follows the window width, capped by Settings View's limit.
|
|
121
|
+
// Sample input: (899, 5) answers 2.
|
|
122
|
+
var COUNT_4_AT = 1700
|
|
123
|
+
var COUNT_5_AT = 2300
|
|
124
|
+
var COUNT_NARROW_AT = 900
|
|
125
|
+
var COUNT_MIN = 2
|
|
126
|
+
var COUNT_MAX = 5
|
|
127
|
+
// The shipped View limit: a default install holds 3 columns from 900 px up.
|
|
128
|
+
var COUNT_DEFAULT = 3
|
|
129
|
+
|
|
130
|
+
// A stored number clamps to 2 to 5, anything else reads as the shipped default.
|
|
131
|
+
function cappedLimit(limit) {
|
|
132
|
+
if (typeof limit !== "number")
|
|
133
|
+
return COUNT_DEFAULT
|
|
134
|
+
var n = Math.floor(limit)
|
|
135
|
+
if (!(n >= COUNT_MIN) && !(n <= COUNT_MAX))
|
|
136
|
+
return COUNT_DEFAULT
|
|
137
|
+
if (n < COUNT_MIN)
|
|
138
|
+
return COUNT_MIN
|
|
139
|
+
if (n > COUNT_MAX)
|
|
140
|
+
return COUNT_MAX
|
|
141
|
+
return n
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// One integer for a window of this width; resizing re-lays columns without re-reading.
|
|
145
|
+
function columnCountForWidth(width, limit) {
|
|
146
|
+
var count = width < COUNT_NARROW_AT ? 2 : width < COUNT_4_AT ? 3
|
|
147
|
+
: width < COUNT_5_AT ? 4 : 5
|
|
148
|
+
var cap = cappedLimit(limit)
|
|
149
|
+
if (count > cap)
|
|
150
|
+
count = cap
|
|
151
|
+
if (count < COUNT_MIN)
|
|
152
|
+
count = COUNT_MIN
|
|
153
|
+
return count
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Extra columns are ancestors, read with the same viewport-scoped peek as the parent today.
|
|
157
|
+
function ancestorsForCount(count) {
|
|
158
|
+
var n = Math.floor(Number(count))
|
|
159
|
+
if (!(n >= COUNT_MIN))
|
|
160
|
+
return 0
|
|
161
|
+
return Math.min(n, COUNT_MAX) - 2
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The parent one step up, with a trailing slash trimmed except at the root itself.
|
|
165
|
+
// Sample input: ancestorParent("/home/") is "/".
|
|
166
|
+
function ancestorParent(path) {
|
|
167
|
+
var text = String(path)
|
|
168
|
+
if (text.length > 1 && text.charAt(text.length - 1) === "/")
|
|
169
|
+
text = text.substring(0, text.length - 1)
|
|
170
|
+
var cut = text.lastIndexOf("/")
|
|
171
|
+
return cut <= 0 ? "/" : text.substring(0, cut)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// The nearest distinct ancestors of a path, oldest first, stopping where the climb stops.
|
|
175
|
+
// Sample input: ancestors("/home/gm", 3) is ["/", "/home"].
|
|
176
|
+
function ancestors(path, count) {
|
|
177
|
+
var n = Math.floor(Number(count))
|
|
178
|
+
if (!(n >= 1))
|
|
179
|
+
return []
|
|
180
|
+
var text = String(path)
|
|
181
|
+
if (text.length === 0)
|
|
182
|
+
return []
|
|
183
|
+
if (text.length > 1 && text.charAt(text.length - 1) === "/")
|
|
184
|
+
text = text.substring(0, text.length - 1)
|
|
185
|
+
var near = []
|
|
186
|
+
var cur = text
|
|
187
|
+
for (var i = 0; i < n; i++) {
|
|
188
|
+
var p = ancestorParent(cur)
|
|
189
|
+
if (p === cur || near.indexOf(p) >= 0)
|
|
190
|
+
break
|
|
191
|
+
near.push(p)
|
|
192
|
+
cur = p
|
|
193
|
+
}
|
|
194
|
+
return near.reverse()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// True while an ancestor column of this depth names a directory of its own.
|
|
198
|
+
// Sample input: ancestorShown("/home", 2) is false.
|
|
199
|
+
function ancestorShown(path, depth) {
|
|
200
|
+
var d = Math.floor(Number(depth))
|
|
201
|
+
if (!(d >= 1))
|
|
202
|
+
return false
|
|
203
|
+
return ancestors(path, d).length >= d
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Sample input: neighbourAsks("/home/gm", 1200, 5) is ["/home"], the shown ancestors oldest first.
|
|
207
|
+
function neighbourAsks(path, width, limit) {
|
|
208
|
+
var want = ancestorsForCount(columnCountForWidth(Number(width), limit))
|
|
209
|
+
if (!(want >= 1))
|
|
210
|
+
return []
|
|
211
|
+
return ancestors(path, want)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Sample input: keepAsks({ drawn: ["/a"] }, ["/a"], "/a/b") answers [{path "/a", again false}, {path "/a/b", again true}] and leaves drawn ["/a", "/a/b"]; again names a column not drawn last refresh.
|
|
215
|
+
function keepAsks(keep, asks, child) {
|
|
216
|
+
var shown = child.length > 0 && asks.indexOf(child) < 0 ? asks.concat([child]) : asks.slice()
|
|
217
|
+
var plan = shown.map(function (path) { return { path: path, again: keep.drawn.indexOf(path) < 0 } })
|
|
218
|
+
keep.drawn = shown
|
|
219
|
+
return plan
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ui.json carries whatever a hand edit wrote, so only a finite number is a stored width; anything else keeps the measured one.
|
|
223
|
+
// Sample input: storedNumber("120") is NaN, storedNumber(120.6) is 121.
|
|
224
|
+
function storedNumber(raw) {
|
|
225
|
+
if (typeof raw !== "number" || !isFinite(raw) || raw < 0)
|
|
226
|
+
return NaN
|
|
227
|
+
return Math.round(raw)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ListColumns040 board: a dragged edge clamps to the rails, and a double click fits the
|
|
231
|
+
// widest value the window holds, never a directory-wide scan. Sample input: 9999 clamps.
|
|
232
|
+
var MIN_LIST_WIDTH = 48
|
|
233
|
+
var MAX_LIST_WIDTH = 480
|
|
234
|
+
|
|
235
|
+
// One whole pixel inside the rails, whatever the drag handed in.
|
|
236
|
+
function clampListWidth(px) {
|
|
237
|
+
var n = Math.round(Number(px))
|
|
238
|
+
if (!(n >= 0))
|
|
239
|
+
return MIN_LIST_WIDTH
|
|
240
|
+
if (n < MIN_LIST_WIDTH)
|
|
241
|
+
return MIN_LIST_WIDTH
|
|
242
|
+
if (n > MAX_LIST_WIDTH)
|
|
243
|
+
return MAX_LIST_WIDTH
|
|
244
|
+
return n
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// The widest held cell, clamped; an empty window keeps the column it has, so a fit can narrow as well as widen.
|
|
248
|
+
function autofitWidth(widths, fallback) {
|
|
249
|
+
var cells = widths || []
|
|
250
|
+
if (cells.length === 0)
|
|
251
|
+
return clampListWidth(fallback)
|
|
252
|
+
var best = MIN_LIST_WIDTH
|
|
253
|
+
for (var i = 0; i < cells.length; i++) {
|
|
254
|
+
var w = Math.round(Number(cells[i]))
|
|
255
|
+
if (w >= 0 && w > best)
|
|
256
|
+
best = w
|
|
257
|
+
}
|
|
258
|
+
return clampListWidth(best)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function names(s) {
|
|
262
|
+
var out = ["name"]
|
|
263
|
+
if (s.location) out.push("location")
|
|
264
|
+
for (var i = DROP_ORDER.length - 1; i >= 0; i--) {
|
|
265
|
+
var key = DROP_ORDER[i]
|
|
266
|
+
if (s[key])
|
|
267
|
+
out.push(key)
|
|
268
|
+
}
|
|
269
|
+
return out.join(",")
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// An unanswered folder keeps the old column by data, never by picture.
|
|
273
|
+
// Sample input: folderDataHold(true, false) is true.
|
|
274
|
+
function folderDataHold(cursorIsDir, answered) {
|
|
275
|
+
return cursorIsDir === true && answered !== true
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A file load needs a real file row: null never loads and a folder waits by data.
|
|
279
|
+
// Sample input: isFileRow({d: false}) is true.
|
|
280
|
+
function isFileRow(row) {
|
|
281
|
+
return row !== null && row !== undefined && row.d !== true
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// A landed peek shows the cursor folder while it is still the one waiting.
|
|
285
|
+
// Sample input: showFolderOnPeek("/a", "/b", true) is true.
|
|
286
|
+
function showFolderOnPeek(childPath, shownChildPath, answered) {
|
|
287
|
+
return answered === true && childPath.length > 0 && childPath !== shownChildPath
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// A data-held empty folder lands settled, the whole frame the picture hold revealed.
|
|
291
|
+
// Sample input: shouldSettleHero(true, "/a", "/a", false, 0) is true.
|
|
292
|
+
function shouldSettleHero(cursorIsDir, path, childPath, readFailed, rowCount) {
|
|
293
|
+
if (cursorIsDir !== true || readFailed === true)
|
|
294
|
+
return false
|
|
295
|
+
if (String(path) !== String(childPath))
|
|
296
|
+
return false
|
|
297
|
+
return Math.floor(Number(rowCount)) === 0
|
|
298
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// WCAG 2.1 relative luminance, so a test can assert a ratio rather than an eye judging a screenshot.
|
|
4
|
+
function channel(c) {
|
|
5
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function luminance(r, g, b) {
|
|
9
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Sample input: "#c8ccd0"
|
|
13
|
+
function parse(hex) {
|
|
14
|
+
var s = String(hex).replace("#", "")
|
|
15
|
+
if (s.length === 8)
|
|
16
|
+
s = s.substring(2)
|
|
17
|
+
return [parseInt(s.substr(0, 2), 16) / 255,
|
|
18
|
+
parseInt(s.substr(2, 2), 16) / 255,
|
|
19
|
+
parseInt(s.substr(4, 2), 16) / 255]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function over(fg, alpha, bg) {
|
|
23
|
+
var f = parse(fg)
|
|
24
|
+
var b = parse(bg)
|
|
25
|
+
return [f[0] * alpha + b[0] * (1 - alpha),
|
|
26
|
+
f[1] * alpha + b[1] * (1 - alpha),
|
|
27
|
+
f[2] * alpha + b[2] * (1 - alpha)]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ratioOf(a, b) {
|
|
31
|
+
var la = luminance(a[0], a[1], a[2])
|
|
32
|
+
var lb = luminance(b[0], b[1], b[2])
|
|
33
|
+
var hi = Math.max(la, lb)
|
|
34
|
+
var lo = Math.min(la, lb)
|
|
35
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ratio(fgHex, bgHex) {
|
|
39
|
+
return ratioOf(parse(fgHex), parse(bgHex))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pad2(n) {
|
|
43
|
+
return n < 16 ? "0" + n.toString(16) : n.toString(16)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hexOf(rgb) {
|
|
47
|
+
return "#" + pad2(Math.round(Math.max(0, Math.min(1, rgb[0])) * 255))
|
|
48
|
+
+ pad2(Math.round(Math.max(0, Math.min(1, rgb[1])) * 255))
|
|
49
|
+
+ pad2(Math.round(Math.max(0, Math.min(1, rgb[2])) * 255))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mix(a, b, t) {
|
|
53
|
+
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Keep hue; walk lightness toward black or white until WCAG AA (or minRatio) holds.
|
|
57
|
+
function ensureRatio(fgHex, bgHex, minRatio) {
|
|
58
|
+
var need = minRatio > 0 ? minRatio : 4.5
|
|
59
|
+
if (ratio(fgHex, bgHex) >= need)
|
|
60
|
+
return String(fgHex)
|
|
61
|
+
var fg = parse(fgHex)
|
|
62
|
+
var bg = parse(bgHex)
|
|
63
|
+
var toward = luminance(bg[0], bg[1], bg[2]) > 0.179 ? [0, 0, 0] : [1, 1, 1]
|
|
64
|
+
var lo = 0
|
|
65
|
+
var hi = 1
|
|
66
|
+
var best = toward
|
|
67
|
+
for (var i = 0; i < 18; i++) {
|
|
68
|
+
var mid = (lo + hi) / 2
|
|
69
|
+
var cand = mix(fg, toward, mid)
|
|
70
|
+
if (ratioOf(cand, bg) >= need) {
|
|
71
|
+
best = cand
|
|
72
|
+
hi = mid
|
|
73
|
+
} else {
|
|
74
|
+
lo = mid
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (ratioOf(best, bg) < need)
|
|
78
|
+
best = toward
|
|
79
|
+
return stepped(hexOf(best), bgHex, need, toward)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Rounding to 8 bits can land just under the ratio asked for, 2.99 for 3 on 20 of the 22 stock palettes.
|
|
83
|
+
function stepped(hex, bgHex, need, toward) {
|
|
84
|
+
var c = parse(hex)
|
|
85
|
+
var step = toward[0] > 0.5 ? 1 / 255 : -1 / 255
|
|
86
|
+
var bg = parse(bgHex)
|
|
87
|
+
for (var i = 0; i < 255; i++) {
|
|
88
|
+
if (ratioOf(c, bg) >= need)
|
|
89
|
+
break
|
|
90
|
+
// Each channel clamps on its own, so one already at the extreme cannot carry the others out.
|
|
91
|
+
var next = [clamp(c[0] + step), clamp(c[1] + step), clamp(c[2] + step)]
|
|
92
|
+
// A step that moves no channel at all is the extreme itself, and there is nowhere further to go.
|
|
93
|
+
if (hexOf(next) === hexOf(c))
|
|
94
|
+
break
|
|
95
|
+
c = next
|
|
96
|
+
}
|
|
97
|
+
return hexOf(c)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function clamp(v) {
|
|
101
|
+
return v < 0 ? 0 : (v > 1 ? 1 : v)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The seven tag names, member for member with src/backend/tags.rs NAMES and ui/js/Tags.js NAMES.
|
|
105
|
+
var TAG_NAMES = ["Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Gray"];
|
|
106
|
+
var TAG_ROLES = { Red: "red", Orange: "orange", Yellow: "yellow", Green: "green",
|
|
107
|
+
Blue: "blue", Purple: "magenta", Gray: "dark_foreground" };
|
|
108
|
+
var TAG_HUES = { Red: [335, 12], Orange: [8, 45], Yellow: [28, 70],
|
|
109
|
+
Green: [80, 170], Blue: [195, 255], Purple: [250, 335] };
|
|
110
|
+
|
|
111
|
+
// Each role lifted to 3:1 on the listing ground and the menu ground, plus the digit fallback, decided once per theme load.
|
|
112
|
+
function tagTheme(found, bg, surface) {
|
|
113
|
+
var colors = {};
|
|
114
|
+
var menuColors = {};
|
|
115
|
+
var seen = {};
|
|
116
|
+
var fallback = false;
|
|
117
|
+
for (var i = 0; i < TAG_NAMES.length; i++) {
|
|
118
|
+
var name = TAG_NAMES[i];
|
|
119
|
+
var hex = (found || {})[TAG_ROLES[name]] || "";
|
|
120
|
+
if (!hex) {
|
|
121
|
+
fallback = true;
|
|
122
|
+
} else {
|
|
123
|
+
var key = String(hex).toLowerCase();
|
|
124
|
+
if (seen[key]) fallback = true;
|
|
125
|
+
seen[key] = true;
|
|
126
|
+
}
|
|
127
|
+
colors[name] = hex ? ensureRatio(hex, bg, 3) : "";
|
|
128
|
+
menuColors[name] = hex ? ensureRatio(hex, surface, 3) : "";
|
|
129
|
+
if (name !== "Gray" && hex) {
|
|
130
|
+
var rgb = parse(hex);
|
|
131
|
+
var hi = Math.max(rgb[0], rgb[1], rgb[2]);
|
|
132
|
+
var lo = Math.min(rgb[0], rgb[1], rgb[2]);
|
|
133
|
+
// Almost no chroma is a grey whatever its name claims.
|
|
134
|
+
if (hi === 0 || (hi - lo) / hi < 0.12) fallback = true;
|
|
135
|
+
else if (!tagHueHolds(rgb, TAG_HUES[name])) fallback = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { colors: colors, menuColors: menuColors, fallback: fallback };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// A hue inside a window that wraps past red, so one comparison covers both ends.
|
|
142
|
+
function tagHueHolds(rgb, window) {
|
|
143
|
+
var hi = Math.max(rgb[0], rgb[1], rgb[2]);
|
|
144
|
+
var lo = Math.min(rgb[0], rgb[1], rgb[2]);
|
|
145
|
+
var delta = hi - lo;
|
|
146
|
+
var hue = 0;
|
|
147
|
+
if (delta > 0) {
|
|
148
|
+
if (hi === rgb[0]) {
|
|
149
|
+
hue = 60 * (((rgb[1] - rgb[2]) / delta + 6) % 6);
|
|
150
|
+
} else if (hi === rgb[1]) {
|
|
151
|
+
hue = 60 * ((rgb[2] - rgb[0]) / delta + 2);
|
|
152
|
+
} else {
|
|
153
|
+
hue = 60 * ((rgb[0] - rgb[1]) / delta + 4);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return window[0] <= window[1] ? hue >= window[0] && hue <= window[1] : hue >= window[0] || hue <= window[1];
|
|
157
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// One job: the numbers every surface draws from, so tests/js/density.js pins them.
|
|
4
|
+
|
|
5
|
+
// The four stops in the board's own order, Tight first as the opt-in below Compact.
|
|
6
|
+
var STOPS = ["tight", "compact", "normal", "comfortable"]
|
|
7
|
+
var LABELS = ["Tight", "Compact", "Normal", "Comfortable"]
|
|
8
|
+
|
|
9
|
+
// Tight drops the padding, Compact keeps half, Normal keeps all, Comfortable one half more.
|
|
10
|
+
var RATIOS = ({ tight: 0, compact: 0.5, normal: 1, comfortable: 1.5 })
|
|
11
|
+
|
|
12
|
+
// Sample input: ratioFor("tight") is 0, ratioFor("nope") is 0.5.
|
|
13
|
+
function ratioFor(density) {
|
|
14
|
+
return RATIOS[density] !== undefined ? RATIOS[density] : 0.5
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Sample input: rowHeight(23, 7, "tight") is 23, the line box with no padding.
|
|
18
|
+
function rowHeight(lineBox, rowPaddingY, density) {
|
|
19
|
+
return lineBox + 2 * Math.round(rowPaddingY * ratioFor(density))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The six thumbnail stops in the board's walk order, Small 48 to Largest 256.
|
|
23
|
+
var THUMB_SIZES = ({ small: 48, medium: 64, large: 96, xlarge: 128, huge: 192, largest: 256 })
|
|
24
|
+
|
|
25
|
+
// Sample input: thumbPixelsFor("huge") is 192, thumbPixelsFor("nope") is 64.
|
|
26
|
+
function thumbPixelsFor(size) {
|
|
27
|
+
return THUMB_SIZES[size] !== undefined ? THUMB_SIZES[size] : 64
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A folder mark never grows past its 128 px size, centred in the larger slots.
|
|
31
|
+
var GLYPH_CAP = 128
|
|
32
|
+
|
|
33
|
+
// Sample input: glyphCap(192) is 128, glyphCap(48) is 48.
|
|
34
|
+
function glyphCap(pixels) {
|
|
35
|
+
return Math.min(pixels, GLYPH_CAP)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The grid's vertical pad tracks density with Compact anchored at today's pad.
|
|
39
|
+
var GRID_BASE = 0.5
|
|
40
|
+
|
|
41
|
+
// Sample input: gridPadY(14, "compact") is 14, gridPadY(14, "tight") is 7.
|
|
42
|
+
function gridPadY(padX, density) {
|
|
43
|
+
return Math.round(padX * (GRID_BASE + ratioFor(density)))
|
|
44
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// A row this listing has asked about: null means asked and waiting, an object is the answer.
|
|
4
|
+
var ASKED = null
|
|
5
|
+
|
|
6
|
+
function empty() {
|
|
7
|
+
return { file: {}, order: [] }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Only visible rows, only rows the backend called directories, only rows never asked.
|
|
11
|
+
function plan(state, rows, held, first, last) {
|
|
12
|
+
var ask = []
|
|
13
|
+
for (var i = first; i <= last; i++) {
|
|
14
|
+
if (state.file[i] !== undefined) {
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
var row = rows[i - held]
|
|
18
|
+
if (row && row.d) {
|
|
19
|
+
ask.push(i)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return ask
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The wrapper is new so a QML binding on it re-evaluates; the map inside is mutated in place.
|
|
26
|
+
function applied(state, ask) {
|
|
27
|
+
for (var i = 0; i < ask.length; i++) {
|
|
28
|
+
state.file[ask[i]] = ASKED
|
|
29
|
+
state.order.push(ask[i])
|
|
30
|
+
}
|
|
31
|
+
return { file: state.file, order: state.order }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Gates the cancel wire message, since a fling fires onContentYChanged on every pixel and a fresh listing has nothing queued to cancel yet.
|
|
35
|
+
function hasPending(state) {
|
|
36
|
+
for (var i = 0; i < state.order.length; i++) {
|
|
37
|
+
if (state.file[state.order[i]] === ASKED) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A scroll cancels every row still waiting, not only the ones off-viewport, matching what the backend's own dirsizecancel means.
|
|
45
|
+
function cancelled(state) {
|
|
46
|
+
var file = {}
|
|
47
|
+
var order = []
|
|
48
|
+
for (var i = 0; i < state.order.length; i++) {
|
|
49
|
+
var row = state.order[i]
|
|
50
|
+
if (state.file[row] === ASKED) {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
file[row] = state.file[row]
|
|
54
|
+
order.push(row)
|
|
55
|
+
}
|
|
56
|
+
return { file: file, order: order }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A live answer re-lists some folders: each answered size follows its row, by name, to that row's new index. Pending asks do not follow.
|
|
60
|
+
function carried(state, oldRows, oldHeld, newRows, newHeld) {
|
|
61
|
+
var at = Object.create(null)
|
|
62
|
+
for (var j = 0; j < newRows.length; j++) {
|
|
63
|
+
if (newRows[j].d) {
|
|
64
|
+
at[newRows[j].n] = newHeld + j
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
var next = empty()
|
|
68
|
+
for (var k = 0; k < state.order.length; k++) {
|
|
69
|
+
var answer = state.file[state.order[k]]
|
|
70
|
+
var row = oldRows[state.order[k] - oldHeld]
|
|
71
|
+
if (answer && row && at[row.n] !== undefined) {
|
|
72
|
+
next.file[at[row.n]] = answer
|
|
73
|
+
next.order.push(at[row.n])
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return next
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Sizes a live answer carried on its rows land with those rows; with none the state comes back as it was.
|
|
80
|
+
// Sample input: (null, [{n: "x", d: true}, {n: "c", d: true, z: 96}], 20) answers file {21: {bytes: 96, partial: false}}, order [21].
|
|
81
|
+
function inlined(state, rows, held) {
|
|
82
|
+
var next = null
|
|
83
|
+
for (var i = 0; i < rows.length; i++) {
|
|
84
|
+
if (rows[i].z === undefined)
|
|
85
|
+
continue
|
|
86
|
+
next = next || state || empty()
|
|
87
|
+
if (next.file[held + i] === undefined)
|
|
88
|
+
next.order.push(held + i)
|
|
89
|
+
next.file[held + i] = { bytes: rows[i].z, partial: false }
|
|
90
|
+
}
|
|
91
|
+
return next || state
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The cap bounds a policy bug, not normal use, the same reason ui/js/Thumbs.js has one; callers pass Pane's own thumbCap.
|
|
95
|
+
function remember(state, row, bytes, partial, cap) {
|
|
96
|
+
if (state.file[row] === undefined) {
|
|
97
|
+
state.order.push(row)
|
|
98
|
+
}
|
|
99
|
+
state.file[row] = { bytes: bytes, partial: partial }
|
|
100
|
+
while (state.order.length > cap) {
|
|
101
|
+
delete state.file[state.order.shift()]
|
|
102
|
+
}
|
|
103
|
+
return { file: state.file, order: state.order }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Sample input: ("network") trues, ("usb") falses.
|
|
107
|
+
function isRemoteClass(storageClass) {
|
|
108
|
+
return storageClass === "network" || storageClass === "phone"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Sample input: (["size"]) trues, ([]) falses.
|
|
112
|
+
function sizeHidden(hiddenCols) {
|
|
113
|
+
return hiddenCols.indexOf("size") >= 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Sample input: ("list", [], "", true) trues, ("grid", [], "", true) falses.
|
|
117
|
+
function wantsSizes(viewMode, hiddenCols, storageClass, storageKnown) {
|
|
118
|
+
if (!storageKnown) return false
|
|
119
|
+
if (isRemoteClass(storageClass)) return false
|
|
120
|
+
if (viewMode === "grid") return false
|
|
121
|
+
if (viewMode === "list" && sizeHidden(hiddenCols)) return false
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// null covers both "never asked" and "asked and still waiting": the Size cell renders both as the middle dot.
|
|
126
|
+
function sizeFor(state, row) {
|
|
127
|
+
var value = state.file[row]
|
|
128
|
+
return value && typeof value === "object" ? value : null
|
|
129
|
+
}
|