mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-06 18:33:38 +02:00
Fix sketchy pagination (#31)
* Fix test watcher and notifications on linux * Improve pagination; no items are repeated, items loaded ahead are kept and many calculations have been moved to subscriptions Closes #28
This commit is contained in:
parent
ea68c92c73
commit
4b9f99ecc2
9 changed files with 3187 additions and 69 deletions
|
|
@ -9,7 +9,7 @@ module.exports = function (config) {
|
|||
plugins: [
|
||||
'karma-cljs-test',
|
||||
'karma-chrome-launcher',
|
||||
'karma-growl-reporter'
|
||||
'karma-notify-reporter' // reporters are set in package.json
|
||||
],
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
|
|
|
|||
269
package-lock.json
generated
269
package-lock.json
generated
|
|
@ -123,6 +123,12 @@
|
|||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
|
||||
"dev": true
|
||||
},
|
||||
"ansicolors": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
|
||||
"integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
|
||||
"dev": true
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||
|
|
@ -843,6 +849,16 @@
|
|||
"map-obj": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"cardinal": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz",
|
||||
"integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansicolors": "~0.3.2",
|
||||
"redeyed": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
|
||||
|
|
@ -923,6 +939,33 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"cli-table": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz",
|
||||
"integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colors": "1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli-usage": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.8.tgz",
|
||||
"integrity": "sha512-EZJ+ty1TsqdnhZNt2QbI+ed3IUNHTH31blSOJLVph3oL4IExskPRyCDGJH7RuCBPy3QBmWgpbeUxXPhK0isXIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"marked": "^0.5.0",
|
||||
"marked-terminal": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
|
|
@ -2841,12 +2884,6 @@
|
|||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true
|
||||
},
|
||||
"growly": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/growly/-/growly-1.1.1.tgz",
|
||||
"integrity": "sha1-60NKDlbwJB2Chky/1BEscJESQvo=",
|
||||
"dev": true
|
||||
},
|
||||
"har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
|
|
@ -3645,13 +3682,13 @@
|
|||
"integrity": "sha1-y4YF7w4R+ab20o9Wul298m84mSM=",
|
||||
"dev": true
|
||||
},
|
||||
"karma-growl-reporter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/karma-growl-reporter/-/karma-growl-reporter-1.0.0.tgz",
|
||||
"integrity": "sha1-w4fel2epG8ScSpYwmg4dXXO03/I=",
|
||||
"karma-notify-reporter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/karma-notify-reporter/-/karma-notify-reporter-1.0.1.tgz",
|
||||
"integrity": "sha1-2b+0UrxTU2cUO25gMl3UAPSfIMg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"growly": "~1.1.0"
|
||||
"node-notifier": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
|
|
@ -3730,6 +3767,66 @@
|
|||
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._arraycopy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
|
||||
"integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._arrayeach": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz",
|
||||
"integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._baseassign": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
|
||||
"integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._basecopy": "^3.0.0",
|
||||
"lodash.keys": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash._baseclone": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz",
|
||||
"integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._arraycopy": "^3.0.0",
|
||||
"lodash._arrayeach": "^3.0.0",
|
||||
"lodash._baseassign": "^3.0.0",
|
||||
"lodash._basefor": "^3.0.0",
|
||||
"lodash.isarray": "^3.0.0",
|
||||
"lodash.keys": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash._basecopy": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
|
||||
"integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._basefor": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz",
|
||||
"integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._bindcallback": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
|
||||
"integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._getnative": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
|
||||
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.assign": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||
|
|
@ -3748,12 +3845,41 @@
|
|||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isarray": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
|
||||
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.keys": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
|
||||
"integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._getnative": "^3.0.0",
|
||||
"lodash.isarguments": "^3.0.0",
|
||||
"lodash.isarray": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
|
||||
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.toarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
|
||||
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
|
||||
"dev": true
|
||||
},
|
||||
"log4js": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-2.11.0.tgz",
|
||||
|
|
@ -3952,6 +4078,56 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"marked": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.5.1.tgz",
|
||||
"integrity": "sha512-iUkBZegCZou4AdwbKTwSW/lNDcz5OuRSl3qdcl31Ia0B2QPG0Jn+tKblh/9/eP9/6+4h27vpoh8wel/vQOV0vw==",
|
||||
"dev": true
|
||||
},
|
||||
"marked-terminal": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.1.1.tgz",
|
||||
"integrity": "sha512-7UBFww1rdx0w9HehLMCVYa8/AxXaiDigDfMsJcj82/wgLQG9cj+oiMAVlJpeWD57VFJY2OYY+bKeEVIjIlxi+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cardinal": "^2.1.1",
|
||||
"chalk": "^2.4.1",
|
||||
"cli-table": "^0.3.1",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"node-emoji": "^1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
||||
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
|
||||
|
|
@ -4153,6 +4329,15 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node-emoji": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz",
|
||||
"integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.toarray": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
|
|
@ -4221,6 +4406,45 @@
|
|||
"vm-browserify": "0.0.4"
|
||||
}
|
||||
},
|
||||
"node-notifier": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "http://registry.npmjs.org/node-notifier/-/node-notifier-4.6.1.tgz",
|
||||
"integrity": "sha1-BW0UJE89zBzq3+aK+c/wxUc6M/M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cli-usage": "^0.1.1",
|
||||
"growly": "^1.2.0",
|
||||
"lodash.clonedeep": "^3.0.0",
|
||||
"minimist": "^1.1.1",
|
||||
"semver": "^5.1.0",
|
||||
"shellwords": "^0.1.0",
|
||||
"which": "^1.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"growly": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
|
||||
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz",
|
||||
"integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._baseclone": "^3.0.0",
|
||||
"lodash._bindcallback": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-sass": {
|
||||
"version": "4.9.2",
|
||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz",
|
||||
|
|
@ -5215,6 +5439,23 @@
|
|||
"strip-indent": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"redeyed": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz",
|
||||
"integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esprima": "~4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
|
||||
|
|
@ -5587,6 +5828,12 @@
|
|||
"jsonify": "~0.0.0"
|
||||
}
|
||||
},
|
||||
"shellwords": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
|
||||
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
|
||||
"dev": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
"deploy": "npm run build && gh-pages -d public -m \"Deploying $(git rev-parse --short HEAD)\"",
|
||||
"dev:cljs": "shadow-cljs watch app test",
|
||||
"dev:sass": "npm run build:sass; node-sass -w src/sass/app.sass public/app/style.css",
|
||||
"dev:test": "karma start --reporters growl,progress --auto-watch",
|
||||
"dev": "rm -r public/*; npm-run-all copy:* -p dev:*",
|
||||
"dev:test": "karma start --reporters notify,progress --auto-watch",
|
||||
"dev": "rm -r public/*; npm-run-all copy:* test:compile -p dev:*",
|
||||
"test": "run-s test:compile test:run",
|
||||
"test:compile": "shadow-cljs compile test",
|
||||
"test:run": "karma start --single-run"
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
"karma": "^2.0.5",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-cljs-test": "^0.1.0",
|
||||
"karma-growl-reporter": "^1.0.0",
|
||||
"karma-notify-reporter": "^1.0.1",
|
||||
"node-sass": "^4.9.2",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"react-flip-move": "^3.0.1",
|
||||
|
|
|
|||
|
|
@ -2,21 +2,40 @@
|
|||
(:require [re-frame.core :as re-frame]
|
||||
[airsonic-ui.config :as conf]))
|
||||
|
||||
(defn complete-library
|
||||
"Concatenates all responses of one type of library to make paging through
|
||||
it a bit easier."
|
||||
[responses [_ kind]]
|
||||
(let [sorted-albums (->> (filter (fn [[[_ params] _]]
|
||||
(= kind (:type params))) responses)
|
||||
;; first some helper functions to make the structure a bit clearer
|
||||
|
||||
(defn filter-response-kind
|
||||
"Takes all library responses and returns only the ones matching a specific kind"
|
||||
[kind responses]
|
||||
(filter (fn [[[_ params] _]]
|
||||
(= kind (:type params))) responses))
|
||||
|
||||
(defn partition-responses
|
||||
"Returns a map of responses, where each response is neatly mapped to the page
|
||||
it show on."
|
||||
[kind responses]
|
||||
(->> (filter-response-kind kind responses)
|
||||
(sort-by (fn [[[_ params] _]] (:offset params)))
|
||||
(map (comp :album val)))]
|
||||
;; NOTE: we concatenate this manually to avoid duplication; we have to do
|
||||
;; this because fetch more than conf/albums-per-page per page, otherwise we
|
||||
;; can't know whether to show a link to the next page
|
||||
(concat (mapcat (partial take conf/albums-per-page) (butlast sorted-albums))
|
||||
(last sorted-albums))))
|
||||
(mapcat (fn [[[_ params] {albums :album}]]
|
||||
(let [start-page (/ (:offset params) conf/albums-per-page)]
|
||||
(zipmap (drop start-page (range))
|
||||
(partition-all conf/albums-per-page albums)))))
|
||||
(into (sorted-map))))
|
||||
|
||||
;; `complete-library` is the subscription that is actually exported
|
||||
|
||||
(defn paginated-library
|
||||
"Returns a sorted map that can be used to access the library content loaded
|
||||
from the server. Each key represents a page and the associated value
|
||||
represents the page's content."
|
||||
[responses [_ kind]]
|
||||
;; note that we "humanize" the keys, meaning page 1 is the page with offset 0
|
||||
(->> (partition-responses kind responses)
|
||||
(map (fn [[k v]] [(inc k) v]))
|
||||
(into (sorted-map))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:library/complete
|
||||
:library/paginated
|
||||
:<- [:api/responses-for-endpoint "getAlbumList2"]
|
||||
complete-library)
|
||||
paginated-library)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,45 +12,43 @@
|
|||
^{:key idx} [:li (when (= params active-item)
|
||||
{:class-name "is-active"})
|
||||
[:a {:href (apply url-for route)} label]]))]])
|
||||
|
||||
;; the pagination should be used like this
|
||||
;; [pagination {:per-page 12
|
||||
;; :max-pages nil
|
||||
;; :url-fn generate-url
|
||||
;; :current-page 0
|
||||
;; :items [,,,]}]
|
||||
|
||||
(defn num-pages [items per-page max-pages]
|
||||
(min (Math/ceil (/ (count items) per-page)) max-pages))
|
||||
;; this variable determines how many pages before the first known page we should list
|
||||
(def page-padding 2)
|
||||
|
||||
(defn pagination
|
||||
"Builds a pagination, calling `url-fn` for every rendered page link with the
|
||||
page as its argument. When `max-pages` is `nil` an infinite pagination
|
||||
will be rendered."
|
||||
[{:keys [items per-page max-pages current-page url-fn]
|
||||
:or {max-pages (.-MAX_VALUE js/Number)}}]
|
||||
(let [num-pages (num-pages items per-page max-pages)
|
||||
[{:keys [items current-page url-fn]}]
|
||||
;; NOTE: This is currently slightly flawed. We don't have any good way to
|
||||
;; know whether we're on the last possible page so we take the last loaded
|
||||
;; page instead
|
||||
(let [num-pages (last (keys items))
|
||||
first-page? (= current-page 1)
|
||||
last-page? (= current-page num-pages)]
|
||||
[:nav.pagination {:role "pagination", :aria-label "pagination"}
|
||||
pages (range (max 1 (- current-page page-padding))
|
||||
(min (inc (+ current-page page-padding)) (inc num-pages))) ]
|
||||
[:nav.pagination.is-centered {:role "pagination", :aria-label "pagination"}
|
||||
;; now we add buttons to progress one page in each direction
|
||||
[:a.pagination-previous (if first-page?
|
||||
{:disabled true}
|
||||
{:href (url-fn (dec current-page))}) "Previous page"]
|
||||
[:a.pagination-next (if last-page?
|
||||
{:disabled true}
|
||||
{:href (url-fn (inc current-page))}) "Next page"]
|
||||
[:a.pagination-next {:href (url-fn (inc current-page))} "Next page"]
|
||||
;; and here we modify the links around our current page
|
||||
[:ul.pagination-list
|
||||
(when (> current-page 3)
|
||||
^{:key "ellipsis-before"} [:li>span.pagination-ellipsis "…"])
|
||||
(for [page (range (max 1 (- current-page 2))
|
||||
(min (+ current-page 3) (inc num-pages)))]
|
||||
;; some indication that there are previous pages
|
||||
(when (> current-page (inc page-padding))
|
||||
[:li>span.pagination-ellipsis "…"])
|
||||
;; all pagination links around our current page
|
||||
(for [page pages]
|
||||
(let [current-page? (= page current-page)]
|
||||
^{:key page} [(cond-> :li>a.pagination-link
|
||||
current-page? (add-classes :is-current))
|
||||
(cond-> {:href (url-fn page), :aria-label (str "Page " page)}
|
||||
current-page? (assoc :aria-current "page")) page]))
|
||||
(when (< current-page (- num-pages 2))
|
||||
^{:key "ellipsis-after"} [:li>span.pagination-ellipsis "…"])]]))
|
||||
;; some indication that there are more pages after
|
||||
(when (< current-page (- num-pages page-padding))
|
||||
[:li>span.pagination-ellipsis "…"])]]))
|
||||
|
||||
|
||||
(def tab-items [[[::routes/library {:kind "recent"} nil] "Recently played"]
|
||||
[[::routes/library {:kind "newest"} nil] "Newest additions"]
|
||||
|
|
@ -63,13 +61,11 @@
|
|||
[[_ {:keys [kind]} {:keys [page]
|
||||
:or {page 1}}]
|
||||
{:keys [scan-status]}]
|
||||
(let [library @(subscribe [:library/complete kind])
|
||||
;; FIXME: vv Views shouldn't do calculations vv
|
||||
visible (->> (drop (* (dec (int page)) conf/albums-per-page) library)
|
||||
(take conf/albums-per-page))
|
||||
(let [page (int page)
|
||||
library @(subscribe [:library/paginated kind])
|
||||
current-items (get library page)
|
||||
url-fn #(url-for ::routes/library {:kind kind} {:page %})
|
||||
pagination [pagination {:current-page (int page)
|
||||
:per-page conf/albums-per-page
|
||||
pagination [pagination {:current-page page
|
||||
:items library
|
||||
:url-fn url-fn}]]
|
||||
[:div
|
||||
|
|
@ -82,5 +78,5 @@
|
|||
[:section.section>div.container
|
||||
[tabs {:items tab-items :active-item {:kind kind}}]
|
||||
pagination
|
||||
[:section.section [collection/listing visible]]
|
||||
pagination]]))
|
||||
[:section.section [collection/listing current-items]
|
||||
pagination]]]))
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
|
||||
;; how many covers are shown per page when browsing the library
|
||||
(def albums-per-page 20)
|
||||
(def albums-prefetch-factor 5)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
;; we fetch more than just the albums needed for the current page so we can
|
||||
;; page through it faster
|
||||
[:api/request "getAlbumList2" {:type kind
|
||||
:size (* 3 conf/albums-per-page)
|
||||
:size (* conf/albums-prefetch-factor conf/albums-per-page)
|
||||
:offset (* (dec (int page)) conf/albums-per-page)}]]
|
||||
[:routes/do-navigation [route-id {:kind "recent"} {:page 1}]]))
|
||||
|
||||
|
|
|
|||
2834
test/cljs/airsonic_ui/components/library/fixtures.cljs
Normal file
2834
test/cljs/airsonic_ui/components/library/fixtures.cljs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,33 @@
|
|||
(ns airsonic-ui.components.library.subs-test
|
||||
(:require [cljs.test :refer-macros [deftest testing is]]
|
||||
[airsonic-ui.config :as conf]
|
||||
[airsonic-ui.components.library.fixtures :as fixtures]
|
||||
[airsonic-ui.components.library.subs :as sub]))
|
||||
|
||||
(def responses {["getAlbumList2" {:type "recent" :offset 1}] {:album '(5 6 7 8)}
|
||||
["getAlbumList2" {:type "recent" :offset 0}] {:album '(1 2 3 4)}
|
||||
["getAlbumList2" {:type "newest" :offset 1}] {:album '(9 8 7 6)}})
|
||||
(deftest partition-library
|
||||
(testing "Should give us a map of page -> content"
|
||||
(let [pages (sub/partition-responses "recent" fixtures/responses)]
|
||||
(is (map? pages))
|
||||
(is (every? int? (keys pages)))
|
||||
(is (every? seq? (vals pages)))))
|
||||
(testing "Should map each response correctly to a page"
|
||||
(let [first-response (select-keys fixtures/responses [["getAlbumList2" {:type "recent", :size 100, :offset 0}]])]
|
||||
(is (= (range 5) (keys (sub/partition-responses "recent" first-response)))))
|
||||
(let [first-and-third (select-keys fixtures/responses [["getAlbumList2" {:type "recent", :size 100, :offset 0}]
|
||||
["getAlbumList2" {:type "recent", :size 100, :offset 40}]])]
|
||||
;; there will be overlapping content for pages 2, 3 and 4 (with a zero-based index)
|
||||
(is (= (range 7) (keys (sub/partition-responses "recent" first-and-third)))))))
|
||||
|
||||
(deftest complete-library
|
||||
(testing "Should concatenate all album list responses for a given type of list"
|
||||
(is (= '(1 2 3 4 5 6 7 8)
|
||||
(sub/complete-library responses [:library/complete "recent"])))))
|
||||
(deftest paginated-library
|
||||
(testing "Should humanize page offsets"
|
||||
(let [responses (select-keys fixtures/responses [["getAlbumList2" {:type "recent", :size 100, :offset 0}]])
|
||||
paginated (sub/paginated-library responses [:sub/paginated-library "recent"])]
|
||||
(is (= [1 2 3 4 5] (keys paginated)))))
|
||||
(testing "Should concatenate and deduplicate all album list responses"
|
||||
(let [responses (select-keys fixtures/responses [["getAlbumList2" {:type "recent", :size 100, :offset 0}]
|
||||
["getAlbumList2" {:type "recent", :size 100, :offset 20}]
|
||||
["getAlbumList2" {:type "recent", :size 100, :offset 40}]])
|
||||
paginated (sub/paginated-library responses [:sub/paginated-library "recent"])]
|
||||
(is (= [1 2 3 4 5 6 7] (keys paginated)))
|
||||
(is (= 140 (count (mapcat val paginated))))
|
||||
(is (= 140 (count (set (mapcat val paginated))))))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue