Compare commits

...

186 Commits

Author SHA1 Message Date
advplyr 52a3bc224a Version bump v2.12.3 2024-08-09 16:59:19 -05:00
advplyr 54d67e5216 Merge pull request #3245 from ic1415/LibraryItemController
Update LibraryItemController.js
2024-08-09 16:48:30 -05:00
advplyr b55d8250cc Download log update 2024-08-09 16:48:21 -05:00
advplyr 3a1e9abd68 Revert unicode sqlite extension to fix db corruption #3241 2024-08-09 16:41:52 -05:00
advplyr c5ba40a178 Merge pull request #3262 from Vito0912/lang/add-year-in-review
lang/localization of "year in review"
2024-08-09 16:26:12 -05:00
Vito0912 f0c6dccadb Added max width 2024-08-09 19:24:43 +02:00
Vito0912 e701d1ab6a now? please 2024-08-09 18:59:20 +02:00
Vito0912 e10c8093c9 localization of year in review 2024-08-09 18:48:29 +02:00
advplyr e81b3461b2 Version bump v2.12.2 2024-08-08 17:17:22 -05:00
advplyr 9345cb3934 Update version check get changelogs 2024-08-08 17:04:50 -05:00
advplyr eb36a0b3dd Merge pull request #3247 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-08 16:58:06 -05:00
advplyr 7e442ecb3d Revert MemoryStore used in expressSession 2024-08-08 16:54:48 -05:00
tonttula f07c5eb725 Translated using Weblate (Finnish)
Currently translated at 32.2% (275 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-08-08 23:54:39 +02:00
SunSpring a486be92cb Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-08-08 23:54:39 +02:00
tonttula 4d84060036 Translated using Weblate (Finnish)
Currently translated at 26.2% (224 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-08-08 23:54:39 +02:00
tonttula fc503691fe Translated using Weblate (Finnish)
Currently translated at 25.9% (221 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-08-08 23:54:39 +02:00
Charlie c80dd43a3e Translated using Weblate (French)
Currently translated at 99.7% (851 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-08 23:54:39 +02:00
Charlie a4a62e0c18 Translated using Weblate (French)
Currently translated at 99.7% (851 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-08 23:54:39 +02:00
advplyr 2f98cb9b6d Update:Changelog shows all releases matching minor version and lower than current #3250 2024-08-07 17:56:55 -05:00
advplyr 91dc6eebb0 Merge pull request #3254 from mikiher/unc-path-support
Fix path normalization to support UNC paths
2024-08-07 15:27:53 -05:00
mikiher d72e0a4418 Fix path normalization to support UNC paths 2024-08-07 21:18:53 +03:00
advplyr 2c8ebd43cc Update ApiCacheManager unit test 2024-08-06 17:26:36 -05:00
advplyr 9f561aa296 Update:Skip library api cache for random sort #3249 2024-08-06 17:20:53 -05:00
advplyr 930bacd45d Fix:Podcast episode download request failing due to user-agent string #3246 2024-08-05 16:59:37 -05:00
ic1415 ef2d736b20 Update LibraryItemController.js
standardizing log messages for all download cases
2024-08-05 16:33:56 -04:00
ic1415 f3a453be20 Update LibraryItemController.js
Additional logging for single file downloads coming from download function
2024-08-05 16:19:28 -04:00
advplyr 45c97a778d Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-08-05 09:01:53 -05:00
advplyr 6ebc64f73b Version bump v2.12.1 2024-08-05 09:01:45 -05:00
advplyr 52807d0d49 Merge pull request #3233 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-05 08:58:13 -05:00
SunSpring a5e18e99bc Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2024-08-05 15:55:41 +02:00
Charlie f545b3e745 Translated using Weblate (French)
Currently translated at 99.7% (851 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-08-05 15:55:41 +02:00
Vito0912 e0877803e3 Translated using Weblate (German)
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-05 15:55:41 +02:00
advplyr 4916887c8d Merge pull request #3236 from devnoname120/arm64/fix-unicode-so-load
Fix unicode.so loading
2024-08-05 08:55:37 -05:00
Paul 20eb573897 Fix unicode.so loading
`unicode.so` is dynamically linked against glibc but Alpine only comes with musl. Installing the compatibility layer fixes it.

Fix https://github.com/advplyr/audiobookshelf/issues/3231
Fix https://github.com/advplyr/audiobookshelf/issues/3234
Close https://github.com/advplyr/audiobookshelf/pull/3235
2024-08-05 13:34:56 +02:00
advplyr 8ff7b6b6e6 Add server log for process.platform and process.arch #3231 2024-08-04 17:08:55 -05:00
advplyr 06eaee8909 Fix:Binary manager dylib file ext check #3231 2024-08-04 16:51:07 -05:00
advplyr 8f9487ba70 Version bump v2.12.0 2024-08-04 16:36:38 -05:00
advplyr eca51457b7 Update jsdocs and auto-formatting 2024-08-04 16:13:40 -05:00
advplyr 15c6fce648 Remove duplicate dependency ms 2024-08-04 12:52:52 -05:00
advplyr 6c872263c6 Fix:Show changelog when clicking version on config side rail #3232 2024-08-04 12:30:18 -05:00
advplyr 4d3b3d1740 Update:Replace default express-session MemoryStore with stable MemoryStore #2538 2024-08-04 12:00:10 -05:00
advplyr bba8920855 Merge pull request #3228 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-08-04 10:10:02 -05:00
gallegonovato f56b9487ff Translated using Weblate (Spanish)
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:30 +02:00
Mario 1946d8296b Translated using Weblate (German)
Currently translated at 100.0% (853 of 853 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:29 +02:00
burghy86 41e5d7f820 Translated using Weblate (Italian)
Currently translated at 100.0% (852 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2024-08-04 00:09:28 +02:00
weblate.user.1274 2507568103 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.4% (711 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2024-08-04 00:09:27 +02:00
gallegonovato 19733798fa Translated using Weblate (Spanish)
Currently translated at 100.0% (852 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:26 +02:00
Mario 427d6da360 Translated using Weblate (German)
Currently translated at 100.0% (852 of 852 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:25 +02:00
Mario 2b67d3d1c5 Translated using Weblate (German)
Currently translated at 100.0% (850 of 850 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:24 +02:00
gallegonovato 6926a40ad6 Translated using Weblate (Spanish)
Currently translated at 100.0% (850 of 850 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:24 +02:00
Illia Pyshniak 7a8da5bf3a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2024-08-04 00:09:23 +02:00
gallegonovato fc8fa17c6f Translated using Weblate (Spanish)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:22 +02:00
Mario 0a88659a9f Translated using Weblate (German)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:21 +02:00
Illia Pyshniak 9967858c44 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2024-08-04 00:09:21 +02:00
Mario e2ce388f90 Translated using Weblate (German)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-08-04 00:09:20 +02:00
gallegonovato f31649f1d2 Translated using Weblate (Spanish)
Currently translated at 100.0% (849 of 849 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-08-04 00:09:20 +02:00
advplyr a55c167dde Fix:Cleanup media progress when deleting podcasts, remove usage of old user model 2024-08-03 17:09:17 -05:00
advplyr 642cf232ba Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-08-03 15:08:45 -05:00
advplyr 164b4525c4 Replace function for cleaning up user seriesHideFromContinueListening to not use old model 2024-08-03 15:08:03 -05:00
advplyr 39c26d2bee Merge pull request #3217 from ic1415/LibraryItemController
Update LibraryItemController.js
2024-08-02 16:33:23 -05:00
advplyr 2a69955cc1 Update server/controllers/LibraryItemController.js 2024-08-02 16:30:21 -05:00
advplyr 4a5345dd5d Update:devcontainer dev.js default to not skip binaries check, fail gracefully if required binary env variables are not set when skipping 2024-08-01 14:25:57 -05:00
advplyr 1e6dd0e3e0 Add jsdocs for Ffmpeg and tools controller 2024-07-31 17:32:51 -05:00
advplyr 91cca2e358 Merge pull request #3214 from faush01/feature/persist_encoding_options
persist and show the advanced encoding options
2024-07-31 16:52:36 -05:00
advplyr 816a9be618 Merge pull request #3208 from nichwall/release_issue_workflow
Add: workflow to close issues on release
2024-07-31 16:07:24 -05:00
ic1415 9eb0ec76fe Update LibraryItemController.js
update library item controller to log downloaded ebooks to fix #3215
2024-07-31 10:48:41 -04:00
Shaun 49054d5239 persist the advanced encoding options, show the encoding options used with in progress encodes 2024-07-31 16:44:24 +10:00
advplyr 787c4e45a8 Merge pull request #3212 from mikiher/library-fetch
On item pages, fetch the item's library data to the store if it's not available
2024-07-30 16:52:51 -05:00
advplyr 34cb7a4d02 Remove unused loadLibraryFilterData func 2024-07-30 16:49:02 -05:00
advplyr 006241163b Replace setCurrentLibrary calls with fetch to ensure filterData matches library 2024-07-30 16:35:26 -05:00
advplyr 03818fadee Remove unnecessary setCurrentLibrary on mounted item page 2024-07-30 16:20:36 -05:00
mikiher 897c3ea625 on item pages, fetch item's library data if unavailable 2024-07-30 20:02:03 +03:00
Nicholas Wallace 73e4293f04 Fix: label has space in name 2024-07-29 18:08:40 -07:00
Nicholas Wallace 6f5ffcb1f8 Add: workflow to close issues on release 2024-07-29 18:06:05 -07:00
advplyr ed70f3af83 Fix:Material symbols icon to use check instead of checkmark 2024-07-29 17:34:05 -05:00
advplyr 73196f9be8 Update:Match tab support clicking current value to set it #3200 2024-07-29 17:31:52 -05:00
advplyr a77f4e9d77 Merge pull request #3204 from mikiher/embed-permissions
Fix permission issues in embed/merge
2024-07-29 16:49:12 -05:00
mikiher 294490f814 Fix permission issues in embed/merge 2024-07-29 20:19:58 +03:00
advplyr 6183001fca Merge pull request #3199 from mikiher/unaccent
Support accent-insensitive search using SQLean unicode sqlite3 extension
2024-07-28 17:21:32 -05:00
advplyr 3ac604c665 Remove ffmpeg binaries install step from debian preinst script 2024-07-28 16:55:45 -05:00
advplyr e342b07cd0 Update:OPF metadata parser supports namespaces on creator and identifier tags #3201 2024-07-28 14:54:17 -05:00
advplyr b524cbd1b3 Update:Parse epub cover image uses cover specified in opf meta #3201 2024-07-28 14:34:31 -05:00
advplyr 88693d73bd Fix:Shares not working with timeouts longer than 23 days #3164 2024-07-27 17:40:51 -05:00
mikiher 2c453a34ee Remove redundant console.log() message 2024-07-27 23:09:46 +03:00
mikiher 3d2b2e43b1 Set execution permission for downloaded binaries 2024-07-27 23:03:40 +03:00
mikiher c3f3fca896 Remove dependency on libs/ffbinaries from BinaryManager test 2024-07-27 22:44:01 +03:00
mikiher dedf6e5d4b Support accent-insensitive matching using the sqlean sqlite3 unicode extension 2024-07-27 21:56:07 +03:00
mikiher 6c379fc3a7 Add /unicode* to .gitignore 2024-07-27 21:52:33 +03:00
mikiher 329e9c9eb2 BinaryManager support for libraries and downloading from github release assets 2024-07-27 21:51:31 +03:00
advplyr ee53086444 Update chapter modal colors for consistency 2024-07-26 17:36:39 -05:00
advplyr 43d6c6678f Add:Random library sorting option for libraries and series #3166
- Fixed author sort and match button not showing
2024-07-25 16:10:42 -05:00
advplyr 82f136ba79 Merge pull request #3195 from mikiher/remove-match-logic
Simplify ItemSearchCard component by removing matching logic
2024-07-25 15:46:12 -05:00
mikiher e40d3dd64d Simplify ItemSearchCard component 2024-07-25 09:40:18 +03:00
advplyr a5897fd64b Fix:Set series and collection RSS feed cover image using first item with cover #3193 2024-07-24 16:40:45 -05:00
advplyr e786e3c057 Remove references to matrix server 2024-07-23 16:08:47 -05:00
advplyr d347645475 Update:Format numbers on user stats page #3187 2024-07-22 17:43:42 -05:00
advplyr 215b78c162 Merge pull request #3186 from nichwall/author_image_restore
Ensure author and items folder is created before restoring backup
2024-07-22 16:41:55 -05:00
Nicholas Wallace ee271519f9 Ensure author folder is created before extracting files 2024-07-21 18:04:46 +00:00
advplyr b350277bbc Remove unused files 2024-07-21 11:12:17 -05:00
advplyr 604ae080ac Merge pull request #3185 from mikiher/genre-search
Adds genres to gloabl search
2024-07-21 11:07:43 -05:00
advplyr a191dab359 Show numItems and numBooks on search cards 2024-07-21 11:07:54 -05:00
advplyr 3223011b13 Fix:Switching libraries on search page losing q query string 2024-07-21 10:52:23 -05:00
advplyr f746e246e4 Merge pull request #3184 from brendans-bits/optionalDependencies
Moved cypress to `optionalDependencies`
2024-07-21 09:54:05 -05:00
mikiher 0476b68585 fix: Encode search query parameter in search.vue 2024-07-21 14:00:55 +03:00
mikiher ec395bed72 fix: Encode search query parameter in GlobalSearch.vue 2024-07-21 13:53:10 +03:00
mikiher bff56220c2 Adds genres to gloabl search 2024-07-21 11:10:05 +03:00
Brendan 3006405a52 Moved cypress to optionalDependencies 2024-07-21 11:30:50 +10:00
advplyr 9cd0ac80b1 Merge pull request #3182 from mikiher/ffmpeg-progress
Add progress and fixes to m4b and embed tools
2024-07-20 15:31:03 -05:00
mikiher da51d38ba2 Improve documentation and arg names in TrackProgressMonitor.js 2024-07-20 21:42:58 +03:00
advplyr 5ba6459069 Merge pull request #3179 from nichwall/api_spec_opml
Update: podcast opml endpoints
2024-07-20 11:02:15 -05:00
mikiher 75899242fd put MessageEmbedFailed in akphabetic order. 2024-07-20 13:05:03 +03:00
mikiher 7faf42d892 Merge branch 'advplyr:master' into ffmpeg-progress 2024-07-20 12:28:47 +03:00
mikiher 10f5f331d7 Fixes + add progress to m4b and embed tools 2024-07-20 12:28:06 +03:00
Nicholas Wallace b1414388e1 Add: podcast tags to opml endpoints 2024-07-20 02:42:41 +00:00
Nicholas Wallace eb0f5b2e1b Update: podcast opml endpoints 2024-07-20 02:38:56 +00:00
advplyr 7af02ad2e2 Fix:Series bookshelf row padding when using ignore prefixes setting #3169 2024-07-19 17:12:12 -05:00
advplyr 8330dabc46 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2024-07-18 16:43:10 -05:00
advplyr dbc7ad0b3b Fix:Podcast episode match not properly encoding search query #3177 2024-07-18 16:43:00 -05:00
advplyr c0fd24770e Merge pull request #3170 from ajvgwu/nfo-metadata-language
Parse book language from NFO metadata source
2024-07-18 16:14:48 -05:00
advplyr 4289fe4990 Update Nfo scanner and auto-formatting 2024-07-18 16:09:40 -05:00
advplyr e925e9b23f Update:Increase size of more menu icon on items page 2024-07-18 16:06:37 -05:00
Alex 71cd86fdd5 Merge branch 'advplyr:master' into nfo-metadata-language 2024-07-18 13:26:05 -04:00
advplyr 03be947ad6 Merge pull request #3163 from ajyey/feature/disable-max-backup-size
Adds support for allowing backups of unlimited size
2024-07-17 17:09:37 -05:00
advplyr 96f9084f2e Update:Disable axios progress indicator for sync requests 2024-07-17 17:11:57 -05:00
advplyr bbccfcbd12 Merge pull request #3165 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2024-07-17 16:57:52 -05:00
Alex 9a697f48db feat: parse language from NFO metadata source 2024-07-17 11:50:27 -04:00
advplyr 37ad1cced2 Fix:Large OPML import timeouts #3118
- Added OPML Api endpoints for /parse and /create, removed old
- Show task for OPML import and create failed tasks for failed feeds
2024-07-16 17:05:52 -05:00
Mario 26db20f63d Translated using Weblate (German)
Currently translated at 100.0% (848 of 848 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 16:47:20 +02:00
Andrew Leonard ff788e3591 feat: adds better conditional for max backup size 2024-07-16 01:31:12 -04:00
Andrew Leonard 4b482488de feat: remember setting of 0 on server side 2024-07-16 01:30:00 -04:00
Andrew Leonard e230b6640f feat: adds unlimited text to text label 2024-07-16 01:11:20 -04:00
Andrew Leonard 2bc949fae3 feat: adds support for allowing backups of unlimited size 2024-07-15 23:58:05 -04:00
advplyr b1bc472205 Update:Incrase icon font size for more context menu and player loading 2024-07-15 17:38:13 -05:00
gallegonovato 5c7a38c292 Translated using Weblate (Spanish)
Currently translated at 100.0% (848 of 848 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Petteri Hjort bbd6c51eb6 Translated using Weblate (Finnish)
Currently translated at 21.4% (182 of 848 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-07-16 00:35:43 +02:00
Hosted Weblate d17f9b0687 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/
2024-07-16 00:35:43 +02:00
Marcin Martela 4d2bdb6eee Translated using Weblate (Polish)
Currently translated at 90.2% (763 of 845 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-16 00:35:43 +02:00
Vito0912 b6a1014c72 Translated using Weblate (German)
Currently translated at 100.0% (845 of 845 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
gallegonovato b99885c806 Translated using Weblate (Spanish)
Currently translated at 100.0% (845 of 845 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
tonttula f422c9b820 Translated using Weblate (Finnish)
Currently translated at 21.4% (181 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2024-07-16 00:35:43 +02:00
Ori Z 0befe91360 Translated using Weblate (Hebrew)
Currently translated at 92.2% (779 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2024-07-16 00:35:43 +02:00
gallegonovato da671e3fd5 Translated using Weblate (Spanish)
Currently translated at 100.0% (844 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Mario fec94c18aa Translated using Weblate (German)
Currently translated at 100.0% (844 of 844 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
gallegonovato 11c6fc7d90 Translated using Weblate (Spanish)
Currently translated at 100.0% (838 of 838 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Michał Pomarański 7ea5e7dc95 Translated using Weblate (Polish)
Currently translated at 90.4% (758 of 838 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-16 00:35:43 +02:00
Mario 2a98e2c361 Translated using Weblate (German)
Currently translated at 100.0% (838 of 838 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Charlie 7fb499b301 Translated using Weblate (French)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2024-07-16 00:35:43 +02:00
Lode Smets af9aee76cf Translated using Weblate (Dutch)
Currently translated at 86.7% (722 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2024-07-16 00:35:43 +02:00
gallegonovato 075ec15f02 Translated using Weblate (Spanish)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2024-07-16 00:35:43 +02:00
Valentin 1c650473f8 Translated using Weblate (German)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Valentin 0efdf50821 Translated using Weblate (German)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Mario df65ef2191 Translated using Weblate (German)
Currently translated at 100.0% (832 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2024-07-16 00:35:43 +02:00
Ahetek bc3b1d9565 Translated using Weblate (Polish)
Currently translated at 90.8% (756 of 832 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2024-07-16 00:35:43 +02:00
advplyr 2998d3ba6a Merge pull request #3160 from glorenzen/home-page-ultrawide-support
Add ultrawide support for home page
2024-07-15 16:30:44 -05:00
Greg Lorenzen ea11153032 Update responsive limit for displayed items on personalized shelves on home page 2024-07-15 15:15:06 +00:00
Greg Lorenzen 733f61075f WIP: Add "End of chapter" option for sleep timer (#3151)
* Add SleepTimerTypes for countdown and chapter

* Add functionality for 'end of chapter' sleep timer

* Fix custom time for sleep timer

* Include end of chapter string for sleep timer

* Increase chapter end tolerance to 0.75

* Show sleep time options in modal when timer is active

* Add SleepTimerTypes for countdown and chapter

* Add functionality for 'end of chapter' sleep timer

* Fix custom time for sleep timer

* Include end of chapter string for sleep timer

* Increase chapter end tolerance to 0.75

* Show sleep time options in modal when timer is active

* Sleep timer cleanup

* Localization for sleep timer modal, UI updates

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2024-07-14 13:56:48 -05:00
Greg Lorenzen 618e69775c Add responsive limit for displayed items on personalized shelves on home page 2024-07-13 22:32:44 -07:00
advplyr eabfa90121 Update:Move library stats page to SideRail #3134 2024-07-13 15:26:07 -05:00
Greg Lorenzen 43b7ccd61a WIP: Add adjustable skip amount (#3113)
* Add playback settings string to en-us

* Add playback settings UI for jump forwards and jump backwards

* Remove jump forwards and jump backwards settings

* Remove jump forwards and jump backwards en-us strings

* Update player UI to include player settings button

* Add label view player settings string

* Add PlayerSettingsModal component

Includes a toggle switch for enabling/disabling the chapter track feature.

* Add player settings modal component to MediaPlayerContainer

* Handle useChapterTrack changes in PlayerUI

* Add jump forwards and jump backwards settings to user store

* Add jump forwards and jump backwards label strings

* Add jump forwards and jump backwards settings to PlayerSettingsModal

* Update jump forwards and jump backwards to handle user state values in PlayerHandler

* Update jump backwards icon in PlayerPlaybackControls

* Add playback settings string to en-us

* Add playback settings UI for jump forwards and jump backwards

* Remove jump forwards and jump backwards settings

* Remove jump forwards and jump backwards en-us strings

* Update player UI to include player settings button

* Add label view player settings string

* Add PlayerSettingsModal component

Includes a toggle switch for enabling/disabling the chapter track feature.

* Add player settings modal component to MediaPlayerContainer

* Handle useChapterTrack changes in PlayerUI

* Add jump forwards and jump backwards settings to user store

* Add jump forwards and jump backwards label strings

* Add jump forwards and jump backwards settings to PlayerSettingsModal

* Update jump forwards and jump backwards to handle user state values in PlayerHandler

* Update jump backwards icon in PlayerPlaybackControls

* Add jump amounts to playback controls tooltips

* Fix merge issues and add new Material Symbols to player ui

* Alphabetize strings in en-us.json

* Update dropdown component with SelectInput to support menu overflowing modal

* Update localization for player settings

* Update en-us strings order

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2024-07-12 17:52:48 -05:00
advplyr b6875a44cf Merge pull request #3152 from mikiher/fix-opf-fetch-series
Fix OPF fetchSeries so it deduplicates found series
2024-07-12 07:59:10 -05:00
mikiher c0004dd532 Fix fetchSeries so it deduplicates returned series 2024-07-12 12:42:42 +03:00
advplyr 0ee3b89760 Fix:Series and collection RSS feeds keeping correct order #3137 2024-07-11 17:49:05 -05:00
Nicholas W c5e60d30e1 Podcast endpoints (#3140)
* Add: `AudioTrack.yaml`

* Fix: audiotrack example

* Initial: podcast schemas and endpoints

* Update schemas

* Add: podcasts tag

* Update bundled spec
2024-07-11 16:29:35 -05:00
advplyr acaf1ac196 Merge pull request #3138 from glorenzen/update-material-icons
Update material icons
2024-07-10 16:41:21 -05:00
advplyr 8dc4538c95 Remove old material-icons files and classes 2024-07-10 16:41:42 -05:00
advplyr e224fd2595 Update edit library folder icon to fill 2024-07-10 16:38:58 -05:00
advplyr f0a1ea4d6d Merge pull request #3139 from nichwall/email_notification_linting_fixes
Email notification linting fixes
2024-07-09 16:25:31 -05:00
advplyr 10cb8ebf3b Merge pull request #3117 from mikiher/show-subtitles
New feature: Show Subtitles
2024-07-09 16:08:36 -05:00
advplyr 8c4afa1866 Fix typo 2024-07-09 16:09:41 -05:00
advplyr eb5af47bbf Merge branch 'master' into show-subtitles 2024-07-09 15:58:34 -05:00
Nicholas Wallace 4fd93ce64c Update bundled spec 2024-07-09 01:55:55 +00:00
Nicholas Wallace 7ba4e9e66d Add: summary to Notification endpoints 2024-07-09 01:55:35 +00:00
Nicholas Wallace e2e5449d25 Fix: schema reference in EmailController 2024-07-09 01:51:39 +00:00
Greg Lorenzen abc76ca155 Replace Material Icons in LaxyBookCard and ShareModal components 2024-07-08 22:29:36 +00:00
Greg Lorenzen 0fc84a8684 Replace material-icons in YearInReview components 2024-07-08 22:29:20 +00:00
Greg Lorenzen a76600e53b Update play_arrow icons with fill CSS class 2024-07-08 22:00:16 +00:00
Greg Lorenzen e55cf30705 Add material-icons fill class to play icon in LazyBookCard.vue 2024-07-08 16:43:50 +00:00
Greg Lorenzen 2c65b8fd2b Replace material-icons class with material-symbols class in components 2024-07-08 09:39:00 -07:00
Greg Lorenzen 20b8e35132 Revert material-icons CSS classes
Add new material-symbols classes for Material Symbols fonts
2024-07-08 09:39:00 -07:00
Greg Lorenzen 8007225a41 Update font names and file paths for Material Symbols 2024-07-08 09:39:00 -07:00
Greg Lorenzen 63a6da6680 Add Material Symbols outlined and rounded woff files 2024-07-08 09:39:00 -07:00
mikiher 31c8cb476a Make menu item localized where they're not 2024-07-06 00:58:25 +03:00
mikiher 79bd6a25d9 Move Collapse Series option to the tollbar's context menu 2024-07-05 00:45:18 +03:00
mikiher 0042604e6d Merge branch 'advplyr:master' into show-subtitles 2024-07-05 00:17:01 +03:00
mikiher 54f2bb1092 Add a Show Subtitles option 2024-07-04 20:35:58 +03:00
mikiher 6b6df619f5 Remove tailwind.compiled.css (auto-generated, added by mistake) 2024-07-04 20:30:29 +03:00
239 changed files with 7056 additions and 7772 deletions
+1 -1
View File
@@ -6,5 +6,5 @@ module.exports.config = {
MetadataPath: Path.resolve('metadata'), MetadataPath: Path.resolve('metadata'),
FFmpegPath: '/usr/bin/ffmpeg', FFmpegPath: '/usr/bin/ffmpeg',
FFProbePath: '/usr/bin/ffprobe', FFProbePath: '/usr/bin/ffprobe',
SkipBinariesCheck: true SkipBinariesCheck: false
} }
-3
View File
@@ -3,6 +3,3 @@ contact_links:
- name: Discord - name: Discord
url: https://discord.gg/HQgCbd6E75 url: https://discord.gg/HQgCbd6E75
about: Ask questions, get help troubleshooting, and join the Abs community here. about: Ask questions, get help troubleshooting, and join the Abs community here.
- name: Matrix
url: https://matrix.to/#/#audiobookshelf:matrix.org
about: Ask questions, get help troubleshooting, and join the Abs community here.
@@ -0,0 +1,20 @@
name: Close fixed issues on release.
on:
release:
types: [published]
permissions:
contents: read
issues: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Close issues marked as fixed upon a release.
uses: gcampbell-msft/fixed-pending-release@7fa1b75a0c04bcd4b375110522878e5f6100cff5
with:
label: 'awaiting release'
removeLabel: true
applyToAll: true
message: Fixed in [${releaseTag}](${releaseUrl}).
+1
View File
@@ -15,6 +15,7 @@
/.nyc_output/ /.nyc_output/
/ffmpeg* /ffmpeg*
/ffprobe* /ffprobe*
/unicode*
sw.* sw.*
.DS_STORE .DS_STORE
+1
View File
@@ -16,6 +16,7 @@ RUN apk update && \
tzdata \ tzdata \
ffmpeg \ ffmpeg \
make \ make \
gcompat \
python3 \ python3 \
g++ \ g++ \
tini tini
-24
View File
@@ -2,7 +2,6 @@
set -e set -e
set -o pipefail set -o pipefail
FFMPEG_INSTALL_DIR="/usr/lib/audiobookshelf-ffmpeg"
DEFAULT_DATA_DIR="/usr/share/audiobookshelf" DEFAULT_DATA_DIR="/usr/share/audiobookshelf"
CONFIG_PATH="/etc/default/audiobookshelf" CONFIG_PATH="/etc/default/audiobookshelf"
DEFAULT_PORT=13378 DEFAULT_PORT=13378
@@ -46,25 +45,6 @@ add_group() {
fi fi
} }
install_ffmpeg() {
echo "Starting FFMPEG Install"
WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz --output-document=ffmpeg-git-amd64-static.tar.xz"
if ! cd "$FFMPEG_INSTALL_DIR"; then
echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR"
mkdir "$FFMPEG_INSTALL_DIR"
chown -R 'audiobookshelf:audiobookshelf' "$FFMPEG_INSTALL_DIR"
cd "$FFMPEG_INSTALL_DIR"
fi
$WGET
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 --no-same-owner
rm ffmpeg-git-amd64-static.tar.xz
echo "Good to go on Ffmpeg... hopefully"
}
setup_config() { setup_config() {
if [ -f "$CONFIG_PATH" ]; then if [ -f "$CONFIG_PATH" ]; then
echo "Existing config found." echo "Existing config found."
@@ -83,8 +63,6 @@ setup_config() {
config_text="METADATA_PATH=$DEFAULT_DATA_DIR/metadata config_text="METADATA_PATH=$DEFAULT_DATA_DIR/metadata
CONFIG_PATH=$DEFAULT_DATA_DIR/config CONFIG_PATH=$DEFAULT_DATA_DIR/config
FFMPEG_PATH=$FFMPEG_INSTALL_DIR/ffmpeg
FFPROBE_PATH=$FFMPEG_INSTALL_DIR/ffprobe
PORT=$DEFAULT_PORT PORT=$DEFAULT_PORT
HOST=$DEFAULT_HOST" HOST=$DEFAULT_HOST"
@@ -101,5 +79,3 @@ add_group 'audiobookshelf' ''
add_user 'audiobookshelf' '' 'audiobookshelf' 'audiobookshelf user-daemon' '/bin/false' add_user 'audiobookshelf' '' 'audiobookshelf' 'audiobookshelf user-daemon' '/bin/false'
setup_config setup_config
install_ffmpeg
+15 -12
View File
@@ -1,19 +1,19 @@
@font-face { @font-face {
font-family: 'Material Icons'; font-family: 'Material Symbols Rounded';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(~static/fonts/MaterialIcons.woff2) format('woff2'); src: url(~static/fonts/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].woff2) format('woff2');
} }
@font-face { @font-face {
font-family: 'Material Icons Outlined'; font-family: 'Material Symbols Outlined';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(~static/fonts/MaterialIconsOutlined.woff2) format('woff2'); src: url(~static/fonts/MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].woff2) format('woff2');
} }
.material-icons { .material-symbols {
font-family: 'Material Icons'; font-family: 'Material Symbols Rounded';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
line-height: 1; line-height: 1;
@@ -27,12 +27,13 @@
vertical-align: top; vertical-align: top;
} }
.material-icons:not([class*="text-"]) { .material-symbols.fill {
font-size: 1.5rem; font-variation-settings:
'FILL' 1
} }
.material-icons-outlined { .material-symbols-outlined {
font-family: 'Material Icons Outlined'; font-family: 'Material Symbols Outlined';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
line-height: 1; line-height: 1;
@@ -43,10 +44,12 @@
word-wrap: normal; word-wrap: normal;
direction: ltr; direction: ltr;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
vertical-align: top;
} }
.material-icons-outlined:not([class*="text-"]) { .material-symbols-outlined.fill {
font-size: 1.5rem; font-variation-settings:
'FILL' 1
} }
/* cyrillic-ext */ /* cyrillic-ext */
+9 -9
View File
@@ -16,7 +16,7 @@
<div class="flex-grow" /> <div class="flex-grow" />
<ui-tooltip v-if="isChromecastInitialized && !isHttps" direction="bottom" text="Casting requires a secure connection" class="flex items-center"> <ui-tooltip v-if="isChromecastInitialized && !isHttps" direction="bottom" text="Casting requires a secure connection" class="flex items-center">
<span class="material-icons-outlined text-2xl text-warning text-opacity-50"> cast </span> <span class="material-symbols-outlined text-2xl text-warning text-opacity-50"> cast </span>
</ui-tooltip> </ui-tooltip>
<div v-if="isChromecastInitialized" class="w-6 min-w-6 h-6 ml-2 mr-1 sm:mx-2 cursor-pointer"> <div v-if="isChromecastInitialized" class="w-6 min-w-6 h-6 ml-2 mr-1 sm:mx-2 cursor-pointer">
<google-cast-launcher></google-cast-launcher> <google-cast-launcher></google-cast-launcher>
@@ -26,19 +26,19 @@
<nuxt-link v-if="currentLibrary" to="/config/stats" class="hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1"> <nuxt-link v-if="currentLibrary" to="/config/stats" class="hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderYourStats" direction="bottom" class="flex items-center"> <ui-tooltip :text="$strings.HeaderYourStats" direction="bottom" class="flex items-center">
<span class="material-icons text-2xl" aria-label="User Stats" role="button">equalizer</span> <span class="material-symbols text-2xl" aria-label="User Stats" role="button">equalizer</span>
</ui-tooltip> </ui-tooltip>
</nuxt-link> </nuxt-link>
<nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1"> <nuxt-link v-if="userCanUpload && currentLibrary" to="/upload" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.ButtonUpload" direction="bottom" class="flex items-center"> <ui-tooltip :text="$strings.ButtonUpload" direction="bottom" class="flex items-center">
<span class="material-icons text-2xl" aria-label="Upload Media" role="button">upload</span> <span class="material-symbols text-2xl" aria-label="Upload Media" role="button">upload</span>
</ui-tooltip> </ui-tooltip>
</nuxt-link> </nuxt-link>
<nuxt-link v-if="userIsAdminOrUp" to="/config" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1"> <nuxt-link v-if="userIsAdminOrUp" to="/config" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderSettings" direction="bottom" class="flex items-center"> <ui-tooltip :text="$strings.HeaderSettings" direction="bottom" class="flex items-center">
<span class="material-icons text-2xl" aria-label="System Settings" role="button">settings</span> <span class="material-symbols text-2xl" aria-label="System Settings" role="button">settings</span>
</ui-tooltip> </ui-tooltip>
</nuxt-link> </nuxt-link>
@@ -47,7 +47,7 @@
<span class="block truncate">{{ username }}</span> <span class="block truncate">{{ username }}</span>
</span> </span>
<span class="h-full md:ml-3 md:absolute inset-y-0 md:right-0 flex items-center justify-center md:pr-2 pointer-events-none"> <span class="h-full md:ml-3 md:absolute inset-y-0 md:right-0 flex items-center justify-center md:pr-2 pointer-events-none">
<span class="material-icons text-xl text-gray-100">person</span> <span class="material-symbols text-xl text-gray-100">person</span>
</span> </span>
</nuxt-link> </nuxt-link>
</div> </div>
@@ -55,7 +55,7 @@
<h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}</h1> <h1 class="text-lg md:text-2xl px-4">{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}</h1>
<div class="flex-grow" /> <div class="flex-grow" />
<ui-btn v-if="!isPodcastLibrary && selectedMediaItemsArePlayable" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems"> <ui-btn v-if="!isPodcastLibrary && selectedMediaItemsArePlayable" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems">
<span class="material-icons text-2xl -ml-2 pr-1 text-white">play_arrow</span> <span class="material-symbols fill text-2xl -ml-2 pr-1 text-white">play_arrow</span>
{{ $strings.ButtonPlay }} {{ $strings.ButtonPlay }}
</ui-btn> </ui-btn>
<ui-tooltip v-if="isBookLibrary" :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom"> <ui-tooltip v-if="isBookLibrary" :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
@@ -76,7 +76,7 @@
<ui-context-menu-dropdown v-if="contextMenuItems.length && !processingBatch" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" /> <ui-context-menu-dropdown v-if="contextMenuItems.length && !processingBatch" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" />
<ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom" class="flex items-center"> <ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom" class="flex items-center">
<span class="material-icons text-3xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatch ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span> <span class="material-symbols text-3xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatch ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
</ui-tooltip> </ui-tooltip>
</div> </div>
</div> </div>
@@ -170,13 +170,13 @@ export default {
if (!this.isPodcastLibrary && this.selectedMediaItemsArePlayable) { if (!this.isPodcastLibrary && this.selectedMediaItemsArePlayable) {
options.push({ options.push({
text: 'Quick Embed Metadata', text: this.$strings.ButtonQuickEmbedMetadata,
action: 'quick-embed' action: 'quick-embed'
}) })
} }
options.push({ options.push({
text: 'Re-Scan', text: this.$strings.ButtonReScan,
action: 'rescan' action: 'rescan'
}) })
+12 -1
View File
@@ -167,8 +167,19 @@ export default {
this.loaded = true this.loaded = true
}, },
async fetchCategories() { async fetchCategories() {
// Sets the limit for the number of items to be displayed based on the viewport width.
const viewportWidth = window.innerWidth
let limit
if (viewportWidth >= 3240) {
limit = 15
} else if (viewportWidth >= 2880 && viewportWidth < 3240) {
limit = 12
}
const limitQuery = limit ? `&limit=${limit}` : ''
const categories = await this.$axios const categories = await this.$axios
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete,share`) .$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete,share${limitQuery}`)
.then((data) => { .then((data) => {
return data return data
}) })
+2 -2
View File
@@ -44,10 +44,10 @@
<div class="bookshelfDividerCategorized h-6e w-full absolute top-0 left-0 right-0 z-20"></div> <div class="bookshelfDividerCategorized h-6e w-full absolute top-0 left-0 right-0 z-20"></div>
</div> </div>
<div v-show="canScrollLeft && !isScrolling" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollLeft"> <div v-show="canScrollLeft && !isScrolling" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollLeft">
<span class="material-icons text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_left</span> <span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_left</span>
</div> </div>
<div v-show="canScrollRight && !isScrolling" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollRight"> <div v-show="canScrollRight && !isScrolling" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-40" @click="scrollRight">
<span class="material-icons text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_right</span> <span class="material-symbols text-white" :style="{ fontSize: 3.75 + 'em' }">chevron_right</span>
</div> </div>
</div> </div>
</template> </template>
+87 -10
View File
@@ -24,11 +24,11 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="flex-grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'"> <nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="flex-grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p> <p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p>
<span v-else class="material-icons-outlined text-lg">queue_music</span> <span v-else class="material-symbols-outlined text-lg">queue_music</span>
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="flex-grow h-full flex justify-center items-center" :class="isCollectionsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'"> <nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="flex-grow h-full flex justify-center items-center" :class="isCollectionsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p v-if="isCollectionsPage" class="text-sm">{{ $strings.ButtonCollections }}</p> <p v-if="isCollectionsPage" class="text-sm">{{ $strings.ButtonCollections }}</p>
<span v-else class="material-icons-outlined text-lg">collections_bookmark</span> <span v-else class="material-symbols-outlined text-lg">collections_bookmark</span>
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="flex-grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'"> <nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="flex-grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p> <p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
@@ -53,7 +53,6 @@
<span class="font-mono">{{ numShowing }}</span> <span class="font-mono">{{ numShowing }}</span>
</div> </div>
<div class="flex-grow" /> <div class="flex-grow" />
<ui-checkbox v-if="!isBatchSelecting" v-model="settings.collapseBookSeries" :label="$strings.LabelCollapseSeries" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseBookSeries" />
<!-- RSS feed --> <!-- RSS feed -->
<ui-tooltip v-if="seriesRssFeed" :text="$strings.LabelOpenRSSFeed" direction="top"> <ui-tooltip v-if="seriesRssFeed" :text="$strings.LabelOpenRSSFeed" direction="top">
@@ -68,9 +67,6 @@
<div class="flex-grow hidden sm:inline-block" /> <div class="flex-grow hidden sm:inline-block" />
<!-- collapse series checkbox -->
<ui-checkbox v-if="isLibraryPage && isBookLibrary && !isBatchSelecting" v-model="settings.collapseSeries" :label="$strings.LabelCollapseSeries" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseSeries" />
<!-- library filter select --> <!-- library filter select -->
<controls-library-filter-select v-if="isLibraryPage && !isBatchSelecting" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" /> <controls-library-filter-select v-if="isLibraryPage && !isBatchSelecting" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" />
@@ -93,14 +89,20 @@
<div class="flex-grow" /> <div class="flex-grow" />
<p>{{ $strings.MessageSearchResultsFor }} "{{ searchQuery }}"</p> <p>{{ $strings.MessageSearchResultsFor }} "{{ searchQuery }}"</p>
<div class="flex-grow" /> <div class="flex-grow" />
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template> </template>
<!-- authors page --> <!-- authors page -->
<template v-else-if="page === 'authors'"> <template v-else-if="page === 'authors'">
<div class="flex-grow" /> <div class="flex-grow" />
<ui-btn v-if="userCanUpdate && authors && authors.length && !isBatchSelecting" :loading="processingAuthors" color="primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn> <ui-btn v-if="userCanUpdate && authors?.length && !isBatchSelecting" :loading="processingAuthors" color="primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn>
<!-- author sort select --> <!-- author sort select -->
<controls-sort-select v-if="authors && authors.length" v-model="settings.authorSortBy" :descending.sync="settings.authorSortDesc" :items="authorSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateAuthorSort" /> <controls-sort-select v-if="authors?.length" v-model="settings.authorSortBy" :descending.sync="settings.authorSortDesc" :items="authorSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateAuthorSort" />
</template>
<!-- home page -->
<template v-else-if="isHome">
<div class="flex-grow" />
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
</template> </template>
</div> </div>
</div> </div>
@@ -151,11 +153,13 @@ export default {
if (this.isSeriesRemovedFromContinueListening) { if (this.isSeriesRemovedFromContinueListening) {
items.push({ items.push({
text: 'Re-Add Series to Continue Listening', text: this.$strings.LabelReAddSeriesToContinueListening,
action: 're-add-to-continue-listening' action: 're-add-to-continue-listening'
}) })
} }
this.addSubtitlesMenuItem(items)
return items return items
}, },
seriesSortItems() { seriesSortItems() {
@@ -183,6 +187,10 @@ export default {
{ {
text: this.$strings.LabelTotalDuration, text: this.$strings.LabelTotalDuration,
value: 'totalDuration' value: 'totalDuration'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
} }
] ]
}, },
@@ -318,11 +326,14 @@ export default {
if (this.isPodcastLibrary && this.isLibraryPage && this.userCanDownload) { if (this.isPodcastLibrary && this.isLibraryPage && this.userCanDownload) {
items.push({ items.push({
text: 'Export OPML', text: this.$strings.LabelExportOPML,
action: 'export-opml' action: 'export-opml'
}) })
} }
this.addSubtitlesMenuItem(items)
this.addCollapseSeriesMenuItem(items)
return items return items
}, },
showPlaylists() { showPlaylists() {
@@ -330,9 +341,70 @@ export default {
} }
}, },
methods: { methods: {
addSubtitlesMenuItem(items) {
if (this.isBookLibrary && (!this.page || this.page === 'search')) {
if (this.settings.showSubtitles) {
items.push({
text: this.$strings.LabelHideSubtitles,
action: 'hide-subtitles'
})
} else {
items.push({
text: this.$strings.LabelShowSubtitles,
action: 'show-subtitles'
})
}
}
},
addCollapseSeriesMenuItem(items) {
if (this.isLibraryPage && this.isBookLibrary && !this.isBatchSelecting) {
if (this.settings.collapseSeries) {
items.push({
text: this.$strings.LabelExpandSeries,
action: 'expand-series'
})
} else {
items.push({
text: this.$strings.LabelCollapseSeries,
action: 'collapse-series'
})
}
}
},
handleSubtitlesAction(action) {
if (action === 'show-subtitles') {
this.settings.showSubtitles = true
this.updateShowSubtitles()
return true
}
if (action === 'hide-subtitles') {
this.settings.showSubtitles = false
this.updateShowSubtitles()
return true
}
return false
},
handleCollapseSeriesAction(action) {
if (action === 'collapse-series') {
this.settings.collapseSeries = true
this.updateCollapseSeries()
return true
}
if (action === 'expand-series') {
this.settings.collapseSeries = false
this.updateCollapseSeries()
return true
}
return false
},
contextMenuAction({ action }) { contextMenuAction({ action }) {
if (action === 'export-opml') { if (action === 'export-opml') {
this.exportOPML() this.exportOPML()
return
} else if (this.handleSubtitlesAction(action)) {
return
} else if (this.handleCollapseSeriesAction(action)) {
return
} }
}, },
exportOPML() { exportOPML() {
@@ -353,6 +425,8 @@ export default {
return return
} }
this.markSeriesFinished() this.markSeriesFinished()
} else if (this.handleSubtitlesAction(action)) {
return
} }
}, },
showOpenSeriesRSSFeed() { showOpenSeriesRSSFeed() {
@@ -482,6 +556,9 @@ export default {
updateCollapseBookSeries() { updateCollapseBookSeries() {
this.saveSettings() this.saveSettings()
}, },
updateShowSubtitles() {
this.saveSettings()
},
updateAuthorSort() { updateAuthorSort() {
this.saveSettings() this.saveSettings()
}, },
+5 -11
View File
@@ -2,7 +2,7 @@
<div> <div>
<div class="w-44 fixed left-0 top-16 bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform mb-12 overflow-y-auto" :class="wrapperClass + ' ' + (streamLibraryItem ? 'h-[calc(100%-270px)]' : 'h-[calc(100%-110px)]')" v-click-outside="clickOutside"> <div class="w-44 fixed left-0 top-16 bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform mb-12 overflow-y-auto" :class="wrapperClass + ' ' + (streamLibraryItem ? 'h-[calc(100%-270px)]' : 'h-[calc(100%-110px)]')" v-click-outside="clickOutside">
<div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer"> <div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer">
<span class="material-icons text-2xl">arrow_back</span> <span class="material-symbols text-2xl">arrow_back</span>
</div> </div>
<nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-3 h-12 border-b border-primary border-opacity-30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary bg-opacity-70' : 'hover:bg-primary hover:bg-opacity-30'"> <nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-3 h-12 border-b border-primary border-opacity-30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary bg-opacity-70' : 'hover:bg-primary hover:bg-opacity-30'">
@@ -10,7 +10,7 @@
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" /> <div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link> </nuxt-link>
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" /> <modals-changelog-view-modal v-model="showChangelogModal" :versionData="versionData" />
</div> </div>
<div class="w-44 h-12 px-4 border-t bg-bg border-black border-opacity-20 fixed left-0 flex flex-col justify-center" :class="wrapperClass" :style="{ bottom: streamLibraryItem ? '160px' : '0px' }"> <div class="w-44 h-12 px-4 border-t bg-bg border-black border-opacity-20 fixed left-0 flex flex-col justify-center" :class="wrapperClass" :style="{ bottom: streamLibraryItem ? '160px' : '0px' }">
@@ -19,7 +19,7 @@
<p class="text-xs text-gray-300 italic">{{ Source }}</p> <p class="text-xs text-gray-300 italic">{{ Source }}</p>
</div> </div>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ latestVersion }}</a> <a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ $config.version }}</a>
</div> </div>
</div> </div>
</template> </template>
@@ -114,9 +114,9 @@ export default {
if (this.currentLibraryId) { if (this.currentLibraryId) {
configRoutes.push({ configRoutes.push({
id: 'config-library-stats', id: 'library-stats',
title: this.$strings.HeaderLibraryStats, title: this.$strings.HeaderLibraryStats,
path: '/config/library-stats' path: `/library/${this.currentLibraryId}/stats`
}) })
configRoutes.push({ configRoutes.push({
id: 'config-stats', id: 'config-stats',
@@ -156,15 +156,9 @@ export default {
hasUpdate() { hasUpdate() {
return !!this.versionData.hasUpdate return !!this.versionData.hasUpdate
}, },
latestVersion() {
return this.versionData.latestVersion
},
githubTagUrl() { githubTagUrl() {
return this.versionData.githubTagUrl return this.versionData.githubTagUrl
}, },
currentVersionChangelog() {
return this.versionData.currentVersionChangelog || 'No Changelog Available'
},
streamLibraryItem() { streamLibraryItem() {
return this.$store.state.streamLibraryItem return this.$store.state.streamLibraryItem
} }
+42 -13
View File
@@ -13,7 +13,7 @@
<widgets-explicit-indicator v-if="isExplicit" /> <widgets-explicit-indicator v-if="isExplicit" />
</div> </div>
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5"> <div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
<span class="material-icons text-sm">person</span> <span class="material-symbols text-sm">person</span>
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div> <div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
<div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div> <div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div>
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate"> <div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
@@ -23,23 +23,25 @@
</div> </div>
<div class="text-gray-400 flex items-center"> <div class="text-gray-400 flex items-center">
<span class="material-icons text-xs">schedule</span> <span class="material-symbols text-xs">schedule</span>
<p class="font-mono text-xs sm:text-sm pl-1 sm:pl-1.5 pb-px">{{ totalDurationPretty }}</p> <p class="font-mono text-xs sm:text-sm pl-1 sm:pl-1.5 pb-px">{{ totalDurationPretty }}</p>
</div> </div>
</div> </div>
<div class="flex-grow" /> <div class="flex-grow" />
<ui-tooltip direction="top" :text="$strings.LabelClosePlayer"> <ui-tooltip direction="top" :text="$strings.LabelClosePlayer">
<button :aria-label="$strings.LabelClosePlayer" class="material-icons sm:px-2 py-1 lg:p-4 cursor-pointer text-xl sm:text-2xl" @click="closePlayer">close</button> <button :aria-label="$strings.LabelClosePlayer" class="material-symbols sm:px-2 py-1 lg:p-4 cursor-pointer text-xl sm:text-2xl" @click="closePlayer">close</button>
</ui-tooltip> </ui-tooltip>
</div> </div>
<player-ui <player-ui
ref="audioPlayer" ref="audioPlayer"
:chapters="chapters" :chapters="chapters"
:current-chapter="currentChapter"
:paused="!isPlaying" :paused="!isPlaying"
:loading="playerLoading" :loading="playerLoading"
:bookmarks="bookmarks" :bookmarks="bookmarks"
:sleep-timer-set="sleepTimerSet" :sleep-timer-set="sleepTimerSet"
:sleep-timer-remaining="sleepTimerRemaining" :sleep-timer-remaining="sleepTimerRemaining"
:sleep-timer-type="sleepTimerType"
:is-podcast="isPodcast" :is-podcast="isPodcast"
@playPause="playPause" @playPause="playPause"
@jumpForward="jumpForward" @jumpForward="jumpForward"
@@ -51,13 +53,16 @@
@showBookmarks="showBookmarks" @showBookmarks="showBookmarks"
@showSleepTimer="showSleepTimerModal = true" @showSleepTimer="showSleepTimerModal = true"
@showPlayerQueueItems="showPlayerQueueItemsModal = true" @showPlayerQueueItems="showPlayerQueueItemsModal = true"
@showPlayerSettings="showPlayerSettingsModal = true"
/> />
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :library-item-id="libraryItemId" @select="selectBookmark" /> <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="bookmarkCurrentTime" :library-item-id="libraryItemId" @select="selectBookmark" />
<modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-time="sleepTimerTime" :remaining="sleepTimerRemaining" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" /> <modals-sleep-timer-modal v-model="showSleepTimerModal" :timer-set="sleepTimerSet" :timer-type="sleepTimerType" :remaining="sleepTimerRemaining" :has-chapters="!!chapters.length" @set="setSleepTimer" @cancel="cancelSleepTimer" @increment="incrementSleepTimer" @decrement="decrementSleepTimer" />
<modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" :library-item-id="libraryItemId" /> <modals-player-queue-items-modal v-model="showPlayerQueueItemsModal" :library-item-id="libraryItemId" />
<modals-player-settings-modal v-model="showPlayerSettingsModal" />
</div> </div>
</template> </template>
@@ -76,9 +81,10 @@ export default {
currentTime: 0, currentTime: 0,
showSleepTimerModal: false, showSleepTimerModal: false,
showPlayerQueueItemsModal: false, showPlayerQueueItemsModal: false,
showPlayerSettingsModal: false,
sleepTimerSet: false, sleepTimerSet: false,
sleepTimerTime: 0,
sleepTimerRemaining: 0, sleepTimerRemaining: 0,
sleepTimerType: null,
sleepTimer: null, sleepTimer: null,
displayTitle: null, displayTitle: null,
currentPlaybackRate: 1, currentPlaybackRate: 1,
@@ -145,6 +151,9 @@ export default {
if (this.streamEpisode) return this.streamEpisode.chapters || [] if (this.streamEpisode) return this.streamEpisode.chapters || []
return this.media.chapters || [] return this.media.chapters || []
}, },
currentChapter() {
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
},
title() { title() {
if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle if (this.playerHandler.displayTitle) return this.playerHandler.displayTitle
return this.mediaMetadata.title || 'No Title' return this.mediaMetadata.title || 'No Title'
@@ -204,14 +213,18 @@ export default {
this.$store.commit('setIsPlaying', isPlaying) this.$store.commit('setIsPlaying', isPlaying)
this.updateMediaSessionPlaybackState() this.updateMediaSessionPlaybackState()
}, },
setSleepTimer(seconds) { setSleepTimer(time) {
this.sleepTimerSet = true this.sleepTimerSet = true
this.sleepTimerTime = seconds
this.sleepTimerRemaining = seconds
this.runSleepTimer()
this.showSleepTimerModal = false this.showSleepTimerModal = false
this.sleepTimerType = time.timerType
if (this.sleepTimerType === this.$constants.SleepTimerTypes.COUNTDOWN) {
this.runSleepTimer(time)
}
}, },
runSleepTimer() { runSleepTimer(time) {
this.sleepTimerRemaining = time.seconds
var lastTick = Date.now() var lastTick = Date.now()
clearInterval(this.sleepTimer) clearInterval(this.sleepTimer)
this.sleepTimer = setInterval(() => { this.sleepTimer = setInterval(() => {
@@ -220,12 +233,23 @@ export default {
this.sleepTimerRemaining -= elapsed / 1000 this.sleepTimerRemaining -= elapsed / 1000
if (this.sleepTimerRemaining <= 0) { if (this.sleepTimerRemaining <= 0) {
this.clearSleepTimer() this.sleepTimerEnd()
this.playerHandler.pause()
this.$toast.info('Sleep Timer Done.. zZzzZz')
} }
}, 1000) }, 1000)
}, },
checkChapterEnd(time) {
if (!this.currentChapter) return
const chapterEndTime = this.currentChapter.end
const tolerance = 0.75
if (time >= chapterEndTime - tolerance) {
this.sleepTimerEnd()
}
},
sleepTimerEnd() {
this.clearSleepTimer()
this.playerHandler.pause()
this.$toast.info('Sleep Timer Done.. zZzzZz')
},
cancelSleepTimer() { cancelSleepTimer() {
this.showSleepTimerModal = false this.showSleepTimerModal = false
this.clearSleepTimer() this.clearSleepTimer()
@@ -235,6 +259,7 @@ export default {
this.sleepTimerRemaining = 0 this.sleepTimerRemaining = 0
this.sleepTimer = null this.sleepTimer = null
this.sleepTimerSet = false this.sleepTimerSet = false
this.sleepTimerType = null
}, },
incrementSleepTimer(amount) { incrementSleepTimer(amount) {
if (!this.sleepTimerSet) return if (!this.sleepTimerSet) return
@@ -275,6 +300,10 @@ export default {
if (this.$refs.audioPlayer) { if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.setCurrentTime(time) this.$refs.audioPlayer.setCurrentTime(time)
} }
if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER && this.sleepTimerSet) {
this.checkChapterEnd(time)
}
}, },
setDuration(duration) { setDuration(duration) {
this.totalDuration = duration this.totalDuration = duration
+19 -8
View File
@@ -15,7 +15,7 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastLatestPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastLatestPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">format_list_bulleted</span> <span class="material-symbols text-2xl">format_list_bulleted</span>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLatest }}</p> <p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLatest }}</p>
@@ -43,7 +43,7 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons-outlined text-2xl">collections_bookmark</span> <span class="material-symbols-outlined text-2xl">collections_bookmark</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonCollections }}</p> <p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonCollections }}</p>
@@ -51,7 +51,7 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2.5xl">queue_music</span> <span class="material-symbols text-2.5xl">queue_music</span>
<p class="pt-0.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonPlaylists }}</p> <p class="pt-0.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonPlaylists }}</p>
@@ -72,13 +72,21 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/narrators`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isNarratorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/narrators`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isNarratorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">record_voice_over</span> <span class="material-symbols text-2xl">record_voice_over</span>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.LabelNarrators }}</p> <p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.LabelNarrators }}</p>
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" /> <div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isBookLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/stats`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isStatsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-symbols text-2xl">monitoring</span>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonStats }}</p>
<div v-show="isStatsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="abs-icons icon-podcast text-xl"></span> <span class="abs-icons icon-podcast text-xl"></span>
@@ -88,7 +96,7 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons-outlined text-xl">album</span> <span class="material-symbols-outlined text-xl">album</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p> <p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
@@ -96,15 +104,15 @@
</nuxt-link> </nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> <nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<span class="material-icons text-2xl">file_download</span> <span class="material-symbols text-2xl">file_download</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonDownloadQueue }}</p> <p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonDownloadQueue }}</p>
<div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" /> <div v-show="isPodcastDownloadQueuePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link> </nuxt-link>
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : ' bg-error bg-opacity-20'"> <nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : 'bg-error bg-opacity-20'">
<span class="material-icons text-2xl">warning</span> <span class="material-symbols text-2xl">warning</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p> <p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
@@ -194,6 +202,9 @@ export default {
isPlaylistsPage() { isPlaylistsPage() {
return this.paramId === 'playlists' return this.paramId === 'playlists'
}, },
isStatsPage() {
return this.$route.name === 'library-library-stats'
},
libraryBookshelfPage() { libraryBookshelfPage() {
return this.$route.name === 'library-library-bookshelf-id' return this.$route.name === 'library-library-bookshelf-id'
}, },
+2 -2
View File
@@ -15,12 +15,12 @@
<!-- Search icon btn --> <!-- Search icon btn -->
<div cy-id="match" v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2e cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor"> <div cy-id="match" v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2e cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom"> <ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
<span class="material-icons" :style="{ fontSize: 1.125 + 'em' }">search</span> <span class="material-symbols" :style="{ fontSize: 1.125 + 'em' }">search</span>
</ui-tooltip> </ui-tooltip>
</div> </div>
<div cy-id="edit" v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2e cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)"> <div cy-id="edit" v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2e cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
<ui-tooltip :text="$strings.LabelEdit" direction="bottom"> <ui-tooltip :text="$strings.LabelEdit" direction="bottom">
<span class="material-icons" :style="{ fontSize: 1.125 + 'em' }">edit</span> <span class="material-symbols" :style="{ fontSize: 1.125 + 'em' }">edit</span>
</ui-tooltip> </ui-tooltip>
</div> </div>
+5 -1
View File
@@ -5,6 +5,7 @@
</div> </div>
<div class="flex-grow px-2 authorSearchCardContent h-full"> <div class="flex-grow px-2 authorSearchCardContent h-full">
<p class="truncate text-sm">{{ name }}</p> <p class="truncate text-sm">{{ name }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXBooks', [numBooks]) }}</p>
</div> </div>
</div> </div>
</template> </template>
@@ -23,6 +24,9 @@ export default {
computed: { computed: {
name() { name() {
return this.author.name return this.author.name
},
numBooks() {
return this.author.numBooks
} }
}, },
methods: {}, methods: {},
@@ -33,7 +37,7 @@ export default {
<style> <style>
.authorSearchCardContent { .authorSearchCardContent {
width: calc(100% - 80px); width: calc(100% - 80px);
height: 40px; height: 44px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
-254
View File
@@ -1,254 +0,0 @@
<template>
<div ref="wrapper" class="relative pointer-events-none" :style="{ width: standardWidth * 0.8 * 1.1 * scale + 'px', height: standardHeight * 1.1 * scale + 'px', marginBottom: 20 + 'px', marginTop: 15 + 'px' }">
<div ref="card" class="wrap absolute origin-center transform duration-200" :style="{ transform: `scale(${scale * scaleMultiplier}) translateY(${hover2 ? '-40%' : '-50%'})` }">
<div class="perspective">
<div class="book-wrap transform duration-100 pointer-events-auto" :class="hover2 ? 'z-80' : 'rotate'" @mouseover="hover = true" @mouseout="hover = false">
<div class="book book-1 box-shadow-book3d" ref="front"></div>
<div class="title book-1 pointer-events-none" ref="left"></div>
<div class="bottom book-1 pointer-events-none" ref="bottom"></div>
<div class="book-back book-1 pointer-events-none">
<div class="text pointer-events-none">
<h3 class="mb-4">Book Back</h3>
<p>
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt earum doloremque aliquam culpa dolor nostrum consequatur quas dicta? Molestias repellendus minima pariatur libero vel, reiciendis optio magnam rerum, labore corporis.</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
src: String,
width: {
type: Number,
default: 200
}
},
data() {
return {
hover: false,
hover2: false,
standardWidth: 200,
standardHeight: 320,
isAttached: true,
pageX: 0,
pageY: 0
}
},
watch: {
src(newVal) {
this.setCover()
},
width(newVal) {
this.init()
},
hover(newVal) {
if (newVal) {
this.unattach()
} else {
this.attach()
}
setTimeout(() => {
this.hover2 = newVal
}, 100)
}
},
computed: {
scaleMultiplier() {
return this.hover2 ? 1.25 : 1
},
scale() {
var scale = this.width / this.standardWidth
return scale
}
},
methods: {
unattach() {
if (this.$refs.card && this.isAttached) {
var bookshelf = document.getElementById('bookshelf')
if (bookshelf) {
var pos = this.$refs.wrapper.getBoundingClientRect()
this.pageX = pos.x
this.pageY = pos.y
document.body.appendChild(this.$refs.card)
this.$refs.card.style.left = this.pageX + 'px'
this.$refs.card.style.top = this.pageY + 'px'
this.$refs.card.style.zIndex = 50
this.isAttached = false
} else if (bookshelf) {
console.log(this.pageX, this.pageY)
this.isAttached = false
}
}
},
attach() {
if (this.$refs.card && !this.isAttached) {
if (this.$refs.wrapper) {
this.isAttached = true
this.$refs.wrapper.appendChild(this.$refs.card)
this.$refs.card.style.left = '0px'
this.$refs.card.style.top = '0px'
}
} else {
console.log('Is attached already', this.isAttached)
}
},
init() {
var standardWidth = this.standardWidth
document.documentElement.style.setProperty('--book-w', standardWidth + 'px')
document.documentElement.style.setProperty('--book-wx', standardWidth + 1 + 'px')
document.documentElement.style.setProperty('--book-h', standardWidth * 1.6 + 'px')
document.documentElement.style.setProperty('--book-d', 40 + 'px')
},
setElBg(el) {
el.style.backgroundImage = `url("${this.src}")`
el.style.backgroundSize = 'cover'
el.style.backgroundPosition = 'center center'
el.style.backgroundRepeat = 'no-repeat'
},
setCover() {
if (this.$refs.front) {
this.setElBg(this.$refs.front)
}
if (this.$refs.bottom) {
this.setElBg(this.$refs.bottom)
this.$refs.bottom.style.backgroundSize = '2000%'
this.$refs.bottom.style.filter = 'blur(1px)'
}
if (this.$refs.left) {
this.setElBg(this.$refs.left)
this.$refs.left.style.backgroundSize = '2000%'
this.$refs.left.style.filter = 'blur(1px)'
}
}
},
mounted() {
this.setCover()
this.init()
}
}
</script>
<style>
/* :root {
--book-w: 200px;
--book-h: 320px;
--book-d: 30px;
--book-wx: 201px;
} */
/*
.wrap {
width: calc(1.1 * var(--book-w));
height: calc(1.1 * var(--book-h));
margin: 0 auto;
}
.perspective {
position: relative;
width: 100%;
height: 100%;
perspective: 600px;
transform-style: preserve-3d;
overflow: hidden;
}
.book-wrap {
height: 100%;
width: 100%;
transform-style: preserve-3d;
transition: 'all ease-out 0.6s';
}
.book {
width: var(--book-w);
height: var(--book-h);
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
background-size: cover;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
cursor: pointer;
}
.title {
content: '';
height: var(--book-h);
width: var(--book-d);
position: absolute;
right: 0;
left: calc(var(--book-wx) * -1);
top: 0;
bottom: 0;
margin: auto;
background: #444;
transform: rotateY(-80deg) translateX(-14px);
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
background-size: 5000%;
filter: blur(1px);
}
.bottom {
content: '';
height: var(--book-d);
width: var(--book-w);
position: absolute;
right: 0;
bottom: var(--book-h);
top: 0;
left: 0;
margin: auto;
background: #444;
transform: rotateY(0deg) rotateX(90deg) translateY(-15px) translateX(-2.5px) skewX(10deg);
background: url(https://covers.openlibrary.org/b/id/8303020-L.jpg) no-repeat center center;
background-size: 5000%;
filter: blur(1px);
}
.book-back {
width: var(--book-w);
height: var(--book-h);
background-color: #444;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
cursor: pointer;
transform: rotate(180deg) translateZ(-30px) translateX(5px);
}
.book-back .text {
transform: rotateX(180deg);
position: absolute;
bottom: 0px;
padding: 20px;
text-align: left;
font-size: 12px;
}
.book-back .text h3 {
color: #fff;
}
.book-back .text span {
display: block;
margin-bottom: 20px;
color: #fff;
}
.book-wrap.rotate {
transform: rotateY(30deg) rotateX(0deg);
}
.book-wrap.flip {
transform: rotateY(180deg);
} */
</style>
@@ -0,0 +1,36 @@
<template>
<div class="flex h-full px-1 overflow-hidden">
<div class="w-10 h-10 flex items-center justify-center">
<span class="material-symbols text-2xl text-gray-200">category</span>
</div>
<div class="flex-grow px-2 tagSearchCardContent h-full">
<p class="truncate text-sm">{{ genre }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXItems', [numItems]) }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
genre: String,
numItems: Number
},
data() {
return {}
},
computed: {},
methods: {},
mounted() {}
}
</script>
<style>
.tagSearchCardContent {
width: calc(100% - 40px);
height: 44px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
+4 -30
View File
@@ -2,15 +2,9 @@
<div class="flex items-center h-full px-1 overflow-hidden"> <div class="flex items-center h-full px-1 overflow-hidden">
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div class="flex-grow px-2 audiobookSearchCardContent"> <div class="flex-grow px-2 audiobookSearchCardContent">
<p v-if="matchKey !== 'title'" class="truncate text-sm">{{ title }}</p> <p class="truncate text-sm">{{ title }}</p>
<p v-else class="truncate text-sm" v-html="matchHtml" /> <p v-if="subtitle" class="truncate text-xs text-gray-300">{{ subtitle }}</p>
<p class="text-xs text-gray-200 truncate">{{ $getString('LabelByAuthor', [authorName]) }}</p>
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300" v-html="matchHtml" />
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">{{ $getString('LabelByAuthor', [authorName]) }}</p>
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
<div v-if="matchKey === 'series' || matchKey === 'tags' || matchKey === 'isbn' || matchKey === 'asin' || matchKey === 'episode' || matchKey === 'narrators'" class="m-0 p-0 truncate text-xs" v-html="matchHtml" />
</div> </div>
</div> </div>
</template> </template>
@@ -21,10 +15,7 @@ export default {
libraryItem: { libraryItem: {
type: Object, type: Object,
default: () => {} default: () => {}
}, }
search: String,
matchKey: String,
matchText: String
}, },
data() { data() {
return {} return {}
@@ -58,23 +49,6 @@ export default {
authorName() { authorName() {
if (this.isPodcast) return this.mediaMetadata.author || 'Unknown' if (this.isPodcast) return this.mediaMetadata.author || 'Unknown'
return this.mediaMetadata.authorName || 'Unknown' return this.mediaMetadata.authorName || 'Unknown'
},
matchHtml() {
if (!this.matchText || !this.search) return ''
// This used to highlight the part of the search found
// but with removing commas periods etc this is no longer plausible
const html = this.matchText
if (this.matchKey === 'episode') return `<p class="truncate">${this.$strings.LabelEpisode}: ${html}</p>`
if (this.matchKey === 'tags') return `<p class="truncate">${this.$strings.LabelTags}: ${html}</p>`
if (this.matchKey === 'subtitle') return `<p class="truncate">${html}</p>`
if (this.matchKey === 'authors') this.$getString('LabelByAuthor', [html])
if (this.matchKey === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${html}</p>`
if (this.matchKey === 'series') return `<p class="truncate">${this.$strings.LabelSeries}: ${html}</p>`
if (this.matchKey === 'narrators') return `<p class="truncate">${this.$strings.LabelNarrator}: ${html}</p>`
return `${html}`
} }
}, },
methods: {}, methods: {},
@@ -1,7 +1,7 @@
<template> <template>
<div class="flex items-center px-1 overflow-hidden"> <div class="flex items-center px-1 overflow-hidden">
<div class="w-8 flex items-center justify-center"> <div class="w-8 flex items-center justify-center">
<span v-if="isFinished" :class="taskIconStatus" class="material-icons text-base">{{ actionIcon }}</span> <span v-if="isFinished" :class="taskIconStatus" class="material-symbols text-base">{{ actionIcon }}</span>
<widgets-loading-spinner v-else /> <widgets-loading-spinner v-else />
</div> </div>
<div class="flex-grow px-2 taskRunningCardContent"> <div class="flex-grow px-2 taskRunningCardContent">
+2 -2
View File
@@ -5,7 +5,7 @@
</div> </div>
<div v-if="!processing && !uploadFailed && !uploadSuccess" class="absolute -top-3 -right-3 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-error cursor-pointer" @click="$emit('remove')"> <div v-if="!processing && !uploadFailed && !uploadSuccess" class="absolute -top-3 -right-3 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-error cursor-pointer" @click="$emit('remove')">
<span class="text-base text-white text-opacity-80 font-mono material-icons">close</span> <span class="text-base text-white text-opacity-80 font-mono material-symbols">close</span>
</div> </div>
<template v-if="!uploadSuccess && !uploadFailed"> <template v-if="!uploadSuccess && !uploadFailed">
@@ -22,7 +22,7 @@
<ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" /> <ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
<ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp"> <ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp">
<div class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer" @click="fetchMetadata"> <div class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer" @click="fetchMetadata">
<span class="text-base text-white text-opacity-80 font-mono material-icons">sync</span> <span class="text-base text-white text-opacity-80 font-mono material-symbols">sync</span>
</div> </div>
</ui-tooltip> </ui-tooltip>
</div> </div>
+26 -9
View File
@@ -45,28 +45,28 @@
<div cy-id="overlay" v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded md:block" :class="overlayWrapperClasslist"> <div cy-id="overlay" v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded md:block" :class="overlayWrapperClasslist">
<div cy-id="playButton" v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none"> <div cy-id="playButton" v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play"> <div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'em' }">play_circle_filled</span> <span class="material-symbols fill" :style="{ fontSize: playIconFontSize + 'em' }">play_arrow</span>
</div> </div>
</div> </div>
<div cy-id="readButton" v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none"> <div cy-id="readButton" v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook"> <div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'em' }">auto_stories</span> <span class="material-symbols" :style="{ fontSize: playIconFontSize + 'em' }">auto_stories</span>
</div> </div>
</div> </div>
<div cy-id="editButton" v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 + 'em' }" @click.stop.prevent="editClick"> <div cy-id="editButton" v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 + 'em' }" @click.stop.prevent="editClick">
<span class="material-icons" :style="{ fontSize: 1 + 'em' }">edit</span> <span class="material-symbols" :style="{ fontSize: 1 + 'em' }">edit</span>
</div> </div>
<!-- Radio button --> <!-- Radio button -->
<div cy-id="selectedRadioButton" v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 + 'em', left: 0.375 + 'em' }" @click.stop.prevent="selectBtnClick"> <div cy-id="selectedRadioButton" v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 + 'em', left: 0.375 + 'em' }" @click.stop.prevent="selectBtnClick">
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 + 'em' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span> <span class="material-symbols" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 + 'em' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
</div> </div>
<!-- More Menu Icon --> <!-- More Menu Icon -->
<div cy-id="moreButton" ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 + 'em', right: 0.375 + 'em' }" @click.stop.prevent="clickShowMore"> <div cy-id="moreButton" ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 + 'em', right: 0.375 + 'em' }" @click.stop.prevent="clickShowMore">
<span class="material-icons" :style="{ fontSize: 1.2 + 'em' }">more_vert</span> <span class="material-symbols" :style="{ fontSize: 1.2 + 'em' }">more_vert</span>
</div> </div>
<div cy-id="ebookFormat" v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 + 'em', left: 0.375 + 'em' }"> <div cy-id="ebookFormat" v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 + 'em', left: 0.375 + 'em' }">
@@ -87,17 +87,17 @@
<!-- Error widget --> <!-- Error widget -->
<ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" class="absolute bottom-4e left-0 z-10"> <ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" class="absolute bottom-4e left-0 z-10">
<div :style="{ height: 1.5 + 'em', width: 2.5 + 'em' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300"> <div :style="{ height: 1.5 + 'em', width: 2.5 + 'em' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
<span class="material-icons text-red-100 pr-1e" :style="{ fontSize: 0.875 + 'em' }">priority_high</span> <span class="material-symbols text-red-100 pr-1e" :style="{ fontSize: 0.875 + 'em' }">priority_high</span>
</div> </div>
</ui-tooltip> </ui-tooltip>
<!-- rss feed icon --> <!-- rss feed icon -->
<div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 + 'em' }"> <div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 + 'em' }">
<span class="material-icons" :style="{ fontSize: 1.5 + 'em' }">rss_feed</span> <span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">rss_feed</span>
</div> </div>
<!-- media item shared icon --> <!-- media item shared icon -->
<div cy-id="mediaItemShare" v-if="mediaItemShare && !isSelectionMode && !isHovering" class="absolute text-success left-0 z-10" :style="{ padding: 0.375 + 'em', top: rssFeed ? '2em' : '0px' }"> <div cy-id="mediaItemShare" v-if="mediaItemShare && !isSelectionMode && !isHovering" class="absolute text-success left-0 z-10" :style="{ padding: 0.375 + 'em', top: rssFeed ? '2em' : '0px' }">
<span class="material-icons" :style="{ fontSize: 1.5 + 'em' }">public</span> <span class="material-symbols" :style="{ fontSize: 1.5 + 'em' }">public</span>
</div> </div>
<!-- Series sequence --> <!-- Series sequence -->
@@ -132,6 +132,9 @@
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" /> <widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
</ui-tooltip> </ui-tooltip>
</div> </div>
<ui-tooltip v-if="showSubtitles" :text="displaySubtitle" :disabled="!displaySubtitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p cy-id="subtitle" class="truncate" ref="displaySubtitle" :style="{ fontSize: 0.6 + 'em' }">{{ displaySubtitle }}</p>
</ui-tooltip>
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displayLineTwo || '&nbsp;' }}</p> <p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displayLineTwo || '&nbsp;' }}</p>
<p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p> <p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displaySortLine }}</p>
</div> </div>
@@ -171,6 +174,7 @@ export default {
selected: false, selected: false,
isSelectionMode: false, isSelectionMode: false,
displayTitleTruncated: false, displayTitleTruncated: false,
displaySubtitleTruncated: false,
showCoverBg: false showCoverBg: false
} }
}, },
@@ -237,7 +241,7 @@ export default {
return this._libraryItem.mediaType return this._libraryItem.mediaType
}, },
isPodcast() { isPodcast() {
return this.mediaType === 'podcast' return this.mediaType === 'podcast' || this.store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
}, },
isMusic() { isMusic() {
return this.mediaType === 'music' return this.mediaType === 'music'
@@ -339,6 +343,13 @@ export default {
if (this.collapsedSeries) return ignorePrefix ? this.collapsedSeries.nameIgnorePrefix : this.collapsedSeries.name if (this.collapsedSeries) return ignorePrefix ? this.collapsedSeries.nameIgnorePrefix : this.collapsedSeries.name
return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix || '\u00A0' : this.title || '\u00A0' return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix || '\u00A0' : this.title || '\u00A0'
}, },
displaySubtitle() {
if (!this.libraryItem) return '\u00A0'
if (this.collapsedSeries) return this.collapsedSeries.numBooks === 1 ? '1 book' : `${this.collapsedSeries.numBooks} books`
if (this.mediaMetadata.subtitle) return this.mediaMetadata.subtitle
if (this.mediaMetadata.seriesName) return this.mediaMetadata.seriesName
return ''
},
displayLineTwo() { displayLineTwo() {
if (this.recentEpisode) return this.title if (this.recentEpisode) return this.title
if (this.isPodcast) return this.author if (this.isPodcast) return this.author
@@ -644,6 +655,9 @@ export default {
}, },
mediaItemShare() { mediaItemShare() {
return this._libraryItem.mediaItemShare || null return this._libraryItem.mediaItemShare || null
},
showSubtitles() {
return !this.isPodcast && this.store.getters['user/getUserSetting']('showSubtitles')
} }
}, },
methods: { methods: {
@@ -685,6 +699,9 @@ export default {
if (this.$refs.displayTitle) { if (this.$refs.displayTitle) {
this.displayTitleTruncated = this.$refs.displayTitle.scrollWidth > this.$refs.displayTitle.clientWidth this.displayTitleTruncated = this.$refs.displayTitle.scrollWidth > this.$refs.displayTitle.clientWidth
} }
if (this.$refs.displaySubtitle) {
this.displaySubtitleTruncated = this.$refs.displaySubtitle.scrollWidth > this.$refs.displaySubtitle.clientWidth
}
}) })
}, },
clickCard(e) { clickCard(e) {
@@ -7,11 +7,11 @@
</div> </div>
<div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none"> <div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<div class="absolute pointer-events-auto" :style="{ top: 0.5 + 'em', right: 0.5 + 'em' }" @click.stop.prevent="clickEdit"> <div class="absolute pointer-events-auto" :style="{ top: 0.5 + 'em', right: 0.5 + 'em' }" @click.stop.prevent="clickEdit">
<span class="material-icons text-white text-opacity-75 hover:text-opacity-100" :style="{ fontSize: 1.25 + 'em' }">edit</span> <span class="material-symbols text-white text-opacity-75 hover:text-opacity-100" :style="{ fontSize: 1.25 + 'em' }">edit</span>
</div> </div>
</div> </div>
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 + 'em', left: 0.5 + 'em', fontSize: 1.5 + 'em' }">rss_feed</span> <span v-if="!isHovering && rssFeed" class="absolute z-10 material-symbols text-success" :style="{ top: 0.5 + 'em', left: 0.5 + 'em', fontSize: 1.5 + 'em' }">rss_feed</span>
</div> </div>
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }"> <div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
+1 -1
View File
@@ -7,7 +7,7 @@
</div> </div>
<div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none"> <div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<div class="absolute pointer-events-auto" :style="{ top: 0.5 + 'em', right: 0.5 + 'em' }" @click.stop.prevent="clickEdit"> <div class="absolute pointer-events-auto" :style="{ top: 0.5 + 'em', right: 0.5 + 'em' }" @click.stop.prevent="clickEdit">
<span class="material-icons text-white text-opacity-75 hover:text-opacity-100" :style="{ fontSize: 1.25 + 'em' }">edit</span> <span class="material-symbols text-white text-opacity-75 hover:text-opacity-100" :style="{ fontSize: 1.25 + 'em' }">edit</span>
</div> </div>
</div> </div>
</div> </div>
+9 -9
View File
@@ -16,7 +16,7 @@
<p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p> <p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p>
</div> </div>
<span cy-id="rssFeedMarker" v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 + 'em', left: 0.5 + 'em', fontSize: 1.5 + 'em' }">rss_feed</span> <span cy-id="rssFeedMarker" v-if="!isHovering && rssFeed" class="absolute z-10 material-symbols text-success" :style="{ top: 0.5 + 'em', left: 0.5 + 'em', fontSize: 1.5 + 'em' }">rss_feed</span>
</div> </div>
<div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }"> <div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
@@ -81,16 +81,16 @@ export default {
return this.store.getters['user/getSizeMultiplier'] return this.store.getters['user/getSizeMultiplier']
}, },
seriesId() { seriesId() {
return this.series ? this.series.id : '' return this.series?.id || ''
}, },
title() { title() {
return this.series ? this.series.name : '' return this.series?.name || ''
}, },
nameIgnorePrefix() { nameIgnorePrefix() {
return this.series ? this.series.nameIgnorePrefix : '' return this.series?.nameIgnorePrefix || ''
}, },
displayTitle() { displayTitle() {
if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title || '\u00A0'
return this.title || '\u00A0' return this.title || '\u00A0'
}, },
displaySortLine() { displaySortLine() {
@@ -110,13 +110,13 @@ export default {
} }
}, },
books() { books() {
return this.series ? this.series.books || [] : [] return this.series?.books || []
}, },
addedAt() { addedAt() {
return this.series ? this.series.addedAt : 0 return this.series?.addedAt || 0
}, },
totalDuration() { totalDuration() {
return this.series ? this.series.totalDuration : 0 return this.series?.totalDuration || 0
}, },
seriesBookProgress() { seriesBookProgress() {
return this.books return this.books
@@ -161,7 +161,7 @@ export default {
return this.bookshelfView == constants.BookshelfView.DETAIL return this.bookshelfView == constants.BookshelfView.DETAIL
}, },
rssFeed() { rssFeed() {
return this.series ? this.series.rssFeed : null return this.series?.rssFeed
} }
}, },
methods: { methods: {
+1 -1
View File
@@ -3,7 +3,7 @@
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`"> <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`">
<div cy-id="card" :style="{ width: cardWidth + 'px', height: cardHeight + 'px', fontSize: sizeMultiplier + 'rem' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden"> <div cy-id="card" :style="{ width: cardWidth + 'px', height: cardHeight + 'px', fontSize: sizeMultiplier + 'rem' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
<div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40"> <div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40">
<span class="material-icons-outlined text-[10em]">record_voice_over</span> <span class="material-symbols-outlined text-[10em]">record_voice_over</span>
</div> </div>
<!-- Narrator name & num books overlay --> <!-- Narrator name & num books overlay -->
@@ -1,10 +1,11 @@
<template> <template>
<div class="flex h-full px-1 overflow-hidden"> <div class="flex h-full px-1 overflow-hidden">
<div class="w-10 h-10 flex items-center justify-center"> <div class="w-10 h-10 flex items-center justify-center">
<span class="material-icons text-2xl text-gray-200">record_voice_over</span> <span class="material-symbols text-2xl text-gray-200">record_voice_over</span>
</div> </div>
<div class="flex-grow px-2 narratorSearchCardContent h-full"> <div class="flex-grow px-2 narratorSearchCardContent h-full">
<p class="truncate text-sm">{{ narrator }}</p> <p class="truncate text-sm">{{ narrator }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXBooks', [numBooks]) }}</p>
</div> </div>
</div> </div>
</template> </template>
@@ -12,7 +13,8 @@
<script> <script>
export default { export default {
props: { props: {
narrator: String narrator: String,
numBooks: Number
}, },
data() { data() {
return {} return {}
@@ -26,7 +28,7 @@ export default {
<style scoped> <style scoped>
.narratorSearchCardContent { .narratorSearchCardContent {
width: calc(100% - 40px); width: calc(100% - 40px);
height: 40px; height: 44px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
+5 -3
View File
@@ -1,10 +1,11 @@
<template> <template>
<div class="flex h-full px-1 overflow-hidden"> <div class="flex h-full px-1 overflow-hidden">
<div class="w-10 h-10 flex items-center justify-center"> <div class="w-10 h-10 flex items-center justify-center">
<span class="material-icons text-2xl text-gray-200">local_offer</span> <span class="material-symbols text-2xl text-gray-200">local_offer</span>
</div> </div>
<div class="flex-grow px-2 tagSearchCardContent h-full"> <div class="flex-grow px-2 tagSearchCardContent h-full">
<p class="truncate text-sm">{{ tag }}</p> <p class="truncate text-sm">{{ tag }}</p>
<p class="text-xs text-gray-400">{{ $getString('LabelXItems', [numItems]) }}</p>
</div> </div>
</div> </div>
</template> </template>
@@ -12,7 +13,8 @@
<script> <script>
export default { export default {
props: { props: {
tag: String tag: String,
numItems: Number
}, },
data() { data() {
return {} return {}
@@ -26,7 +28,7 @@ export default {
<style> <style>
.tagSearchCardContent { .tagSearchCardContent {
width: calc(100% - 40px); width: calc(100% - 40px);
height: 40px; height: 44px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
+2 -2
View File
@@ -10,7 +10,7 @@
</svg> </svg>
</span> </span>
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-300" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected"> <div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-300" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
<span class="material-icons" style="font-size: 1.1rem">close</span> <span class="material-symbols" style="font-size: 1.1rem">close</span>
</div> </div>
</button> </button>
@@ -24,7 +24,7 @@
<!-- selected checkmark icon --> <!-- selected checkmark icon -->
<div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none"> <div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
<span class="material-icons text-base text-yellow-400">check</span> <span class="material-symbols text-base text-yellow-400">check</span>
</div> </div>
</li> </li>
</template> </template>
+22 -10
View File
@@ -5,8 +5,8 @@
<ui-text-input ref="input" v-model="search" :placeholder="$strings.PlaceholderSearch" @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" /> <ui-text-input ref="input" v-model="search" :placeholder="$strings.PlaceholderSearch" @input="inputUpdate" @focus="focussed" @blur="blurred" class="w-full h-8 text-sm" />
</form> </form>
<div class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear"> <div class="absolute top-0 right-0 bottom-0 h-full flex items-center px-2 text-gray-400 cursor-pointer" @click="clickClear">
<span v-if="!search" class="material-icons" style="font-size: 1.2rem">search</span> <span v-if="!search" class="material-symbols" style="font-size: 1.2rem">search</span>
<span v-else class="material-icons" style="font-size: 1.2rem">close</span> <span v-else class="material-symbols" style="font-size: 1.2rem">close</span>
</div> </div>
</div> </div>
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full max-w-64 sm:max-w-80 sm:w-80 bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu"> <div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full max-w-64 sm:max-w-80 sm:w-80 bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalSearchMenu">
@@ -25,7 +25,7 @@
<template v-for="item in bookResults"> <template v-for="item in bookResults">
<li :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> <li :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/item/${item.libraryItem.id}`"> <nuxt-link :to="`/item/${item.libraryItem.id}`">
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" /> <cards-item-search-card :library-item="item.libraryItem" />
</nuxt-link> </nuxt-link>
</li> </li>
</template> </template>
@@ -34,7 +34,7 @@
<template v-for="item in podcastResults"> <template v-for="item in podcastResults">
<li :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> <li :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/item/${item.libraryItem.id}`"> <nuxt-link :to="`/item/${item.libraryItem.id}`">
<cards-item-search-card :library-item="item.libraryItem" :match-key="item.matchKey" :match-text="item.matchText" :search="lastSearch" /> <cards-item-search-card :library-item="item.libraryItem" />
</nuxt-link> </nuxt-link>
</li> </li>
</template> </template>
@@ -59,9 +59,18 @@
<p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p> <p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p>
<template v-for="item in tagResults"> <template v-for="item in tagResults">
<li :key="item.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> <li :key="`tag.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`"> <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`">
<cards-tag-search-card :tag="item.name" /> <cards-tag-search-card :tag="item.name" :num-items="item.numItems" />
</nuxt-link>
</li>
</template>
<p v-if="genreResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelGenres }}</p>
<template v-for="item in genreResults">
<li :key="`genre.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(item.name)}`">
<cards-genre-search-card :genre="item.name" :num-items="item.numItems" />
</nuxt-link> </nuxt-link>
</li> </li>
</template> </template>
@@ -70,7 +79,7 @@
<template v-for="narrator in narratorResults"> <template v-for="narrator in narratorResults">
<li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> <li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`"> <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
<cards-narrator-search-card :narrator="narrator.name" /> <cards-narrator-search-card :narrator="narrator.name" :num-books="narrator.numBooks" />
</nuxt-link> </nuxt-link>
</li> </li>
</template> </template>
@@ -95,6 +104,7 @@ export default {
authorResults: [], authorResults: [],
seriesResults: [], seriesResults: [],
tagResults: [], tagResults: [],
genreResults: [],
narratorResults: [], narratorResults: [],
searchTimeout: null, searchTimeout: null,
lastSearch: null lastSearch: null
@@ -105,7 +115,7 @@ export default {
return this.$store.state.libraries.currentLibraryId return this.$store.state.libraries.currentLibraryId
}, },
totalResults() { totalResults() {
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length + this.narratorResults.length return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length
} }
}, },
methods: { methods: {
@@ -116,7 +126,7 @@ export default {
if (!this.search) return if (!this.search) return
var search = this.search var search = this.search
this.clearResults() this.clearResults()
this.$router.push(`/library/${this.currentLibraryId}/search?q=${search}`) this.$router.push(`/library/${this.currentLibraryId}/search?q=${encodeURIComponent(search)}`)
}, },
clearResults() { clearResults() {
this.search = null this.search = null
@@ -126,6 +136,7 @@ export default {
this.authorResults = [] this.authorResults = []
this.seriesResults = [] this.seriesResults = []
this.tagResults = [] this.tagResults = []
this.genreResults = []
this.narratorResults = [] this.narratorResults = []
this.showMenu = false this.showMenu = false
this.isFetching = false this.isFetching = false
@@ -155,7 +166,7 @@ export default {
} }
this.isFetching = true this.isFetching = true
const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=3`).catch((error) => { const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${encodeURIComponent(value)}&limit=3`).catch((error) => {
console.error('Search error', error) console.error('Search error', error)
return [] return []
}) })
@@ -168,6 +179,7 @@ export default {
this.authorResults = searchResults.authors || [] this.authorResults = searchResults.authors || []
this.seriesResults = searchResults.series || [] this.seriesResults = searchResults.series || []
this.tagResults = searchResults.tags || [] this.tagResults = searchResults.tags || []
this.genreResults = searchResults.genres || []
this.narratorResults = searchResults.narrators || [] this.narratorResults = searchResults.narrators || []
this.isFetching = false this.isFetching = false
@@ -10,7 +10,7 @@
</svg> </svg>
</span> </span>
<div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-200" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected"> <div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-200" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected">
<span class="material-icons" style="font-size: 1.1rem">close</span> <span class="material-symbols" style="font-size: 1.1rem">close</span>
</div> </div>
</button> </button>
@@ -22,11 +22,11 @@
<span class="font-normal ml-3 block truncate text-sm">{{ item.text }}</span> <span class="font-normal ml-3 block truncate text-sm">{{ item.text }}</span>
</div> </div>
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center"> <div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
<span class="material-icons text-2xl">arrow_right</span> <span class="material-symbols text-2xl">arrow_right</span>
</div> </div>
<!-- selected checkmark icon --> <!-- selected checkmark icon -->
<div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none"> <div v-if="item.value === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
<span class="material-icons text-base text-yellow-400">check</span> <span class="material-symbols text-base text-yellow-400">check</span>
</div> </div>
</li> </li>
</template> </template>
@@ -34,7 +34,7 @@
<ul v-show="sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label"> <ul v-show="sublist" class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
<li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-white/5" role="option" @click="sublist = null"> <li class="text-gray-50 select-none relative py-2 pl-9 cursor-pointer hover:bg-white/5" role="option" @click="sublist = null">
<div class="absolute left-1 top-0 bottom-0 h-full flex items-center"> <div class="absolute left-1 top-0 bottom-0 h-full flex items-center">
<span class="material-icons text-2xl">arrow_left</span> <span class="material-symbols text-2xl">arrow_left</span>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="font-normal block truncate">{{ $strings.ButtonBack }}</span> <span class="font-normal block truncate">{{ $strings.ButtonBack }}</span>
@@ -52,7 +52,7 @@
</div> </div>
<!-- selected checkmark icon --> <!-- selected checkmark icon -->
<div v-if="`${sublist}.${item.value}` === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none"> <div v-if="`${sublist}.${item.value}` === selected" class="absolute inset-y-0 right-2 h-full flex items-center pointer-events-none">
<span class="material-icons text-base text-yellow-400">check</span> <span class="material-symbols text-base text-yellow-400">check</span>
</div> </div>
</li> </li>
</template> </template>
@@ -3,7 +3,7 @@
<button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu"> <button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
<span class="flex items-center justify-between"> <span class="flex items-center justify-between">
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span> <span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
<span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span> <span class="material-symbols text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span> </span>
</button> </button>
@@ -14,7 +14,7 @@
<span class="font-normal ml-3 block truncate">{{ item.text }}</span> <span class="font-normal ml-3 block truncate">{{ item.text }}</span>
</div> </div>
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4"> <span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
<span class="material-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span> <span class="material-symbols text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span> </span>
</li> </li>
</template> </template>
@@ -88,6 +88,10 @@ export default {
{ {
text: this.$strings.LabelFileModified, text: this.$strings.LabelFileModified,
value: 'mtimeMs' value: 'mtimeMs'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
} }
] ]
}, },
@@ -128,6 +132,10 @@ export default {
{ {
text: this.$strings.LabelFileModified, text: this.$strings.LabelFileModified,
value: 'mtimeMs' value: 'mtimeMs'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
} }
] ]
}, },
+2 -2
View File
@@ -3,7 +3,7 @@
<button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu"> <button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu">
<span class="flex items-center justify-between"> <span class="flex items-center justify-between">
<span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span> <span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span>
<span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span> <span class="material-symbols text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span> </span>
</button> </button>
@@ -14,7 +14,7 @@
<span class="font-normal ml-3 block truncate">{{ item.text }}</span> <span class="font-normal ml-3 block truncate">{{ item.text }}</span>
</div> </div>
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4"> <span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
<span class="material-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span> <span class="material-symbols text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
</span> </span>
</li> </li>
</template> </template>
+1 -1
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="relative" v-click-outside="clickOutside" @mouseover="mouseover" @mouseleave="mouseleave"> <div class="relative" v-click-outside="clickOutside" @mouseover="mouseover" @mouseleave="mouseleave">
<button :aria-label="$strings.LabelVolume" class="text-gray-300 hover:text-white" @mousedown.prevent @mouseup.prevent @click="clickVolumeIcon"> <button :aria-label="$strings.LabelVolume" class="text-gray-300 hover:text-white" @mousedown.prevent @mouseup.prevent @click="clickVolumeIcon">
<span class="material-icons text-2xl sm:text-3xl">{{ volumeIcon }}</span> <span class="material-symbols text-2xl sm:text-3xl">{{ volumeIcon }}</span>
</button> </button>
<transition name="menux"> <transition name="menux">
<div v-show="isOpen" class="volumeMenu h-6 absolute bottom-2 w-28 px-2 bg-bg shadow-sm rounded-lg" style="left: -116px"> <div v-show="isOpen" class="volumeMenu h-6 absolute bottom-2 w-28 px-2 bg-bg shadow-sm rounded-lg" style="left: -116px">
+1 -1
View File
@@ -7,7 +7,7 @@
<img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-fill'" /> <img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-fill'" />
<a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }"> <a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }">
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span> <span class="material-symbols" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span>
</a> </a>
</div> </div>
@@ -91,7 +91,7 @@
<ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" /> <ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" />
<button class="absolute top-4 right-4" :class="copiedToClipboard ? 'text-success' : 'text-white/50 hover:text-white/80'" @click.stop="copyFfprobeData"> <button class="absolute top-4 right-4" :class="copiedToClipboard ? 'text-success' : 'text-white/50 hover:text-white/80'" @click.stop="copyFfprobeData">
<span class="material-icons">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span> <span class="material-symbols">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -19,7 +19,7 @@
<ui-tooltip :text="$strings.LabelUpdateCoverHelp"> <ui-tooltip :text="$strings.LabelUpdateCoverHelp">
<p class="pl-4"> <p class="pl-4">
{{ $strings.LabelUpdateCover }} {{ $strings.LabelUpdateCover }}
<span class="material-icons icon-text">info_outlined</span> <span class="material-symbols icon-text">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -28,7 +28,7 @@
<ui-tooltip :text="$strings.LabelUpdateDetailsHelp"> <ui-tooltip :text="$strings.LabelUpdateDetailsHelp">
<p class="pl-4"> <p class="pl-4">
{{ $strings.LabelUpdateDetails }} {{ $strings.LabelUpdateDetails }}
<span class="material-icons icon-text">info_outlined</span> <span class="material-symbols icon-text">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
+1 -1
View File
@@ -24,7 +24,7 @@
<div class="flex-grow px-2"> <div class="flex-grow px-2">
<ui-text-input v-model="newBookmarkTitle" placeholder="Note" class="w-full" /> <ui-text-input v-model="newBookmarkTitle" placeholder="Note" class="w-full" />
</div> </div>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-icons text-2xl -mt-px">add</span></ui-btn> <ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-symbols text-2xl -mt-px">add</span></ui-btn>
</div> </div>
</form> </form>
</div> </div>
+2 -2
View File
@@ -1,8 +1,8 @@
<template> <template>
<modals-modal v-model="show" name="chapters" :width="600" :height="'unset'"> <modals-modal v-model="show" name="chapters" :width="600" :height="'unset'">
<div id="chapter-modal-wrapper" ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> <div id="chapter-modal-wrapper" ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<template v-for="chap in chapters"> <template v-for="chap in chapters">
<div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer hover:bg-bg relative" :class="chap.id === currentChapterId ? 'bg-yellow-400 bg-opacity-10' : chap.end / _playbackRate <= currentChapterStart ? 'bg-success bg-opacity-5' : 'bg-opacity-20'" @click="clickChapter(chap)"> <div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer relative" :class="chap.id === currentChapterId ? 'bg-yellow-400/20 hover:bg-yellow-400/10' : chap.end / _playbackRate <= currentChapterStart ? 'bg-success/10 hover:bg-success/5' : 'hover:bg-primary/10'" @click="clickChapter(chap)">
<p class="chapter-title truncate text-sm md:text-base"> <p class="chapter-title truncate text-sm md:text-base">
{{ chap.title }} {{ chap.title }}
</p> </p>
@@ -1,7 +1,7 @@
<template> <template>
<div ref="wrapper" class="hidden absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 rounded-lg items-center justify-center" style="z-index: 61" @click="clickClose"> <div ref="wrapper" class="hidden absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 rounded-lg items-center justify-center" style="z-index: 61" @click="clickClose">
<div class="absolute top-3 right-3 md:top-5 md:right-5 h-8 w-8 md:h-12 md:w-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300"> <div class="absolute top-3 right-3 md:top-5 md:right-5 h-8 w-8 md:h-12 md:w-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300">
<span class="material-icons text-2xl md:text-4xl">close</span> <span class="material-symbols text-2xl md:text-4xl">close</span>
</div> </div>
<div ref="content" class="text-white"> <div ref="content" class="text-white">
<form v-if="selectedSeries" @submit.prevent="submitSeriesForm"> <form v-if="selectedSeries" @submit.prevent="submitSeriesForm">
+1 -1
View File
@@ -3,7 +3,7 @@
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" /> <div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
<button class="absolute top-4 right-4 landscape:top-4 landscape:right-4 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 inline-flex text-gray-200 hover:text-white" aria-label="Close modal" @click="clickClose"> <button class="absolute top-4 right-4 landscape:top-4 landscape:right-4 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 inline-flex text-gray-200 hover:text-white" aria-label="Close modal" @click="clickClose">
<span class="material-icons text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span> <span class="material-symbols text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
</button> </button>
<slot name="outer" /> <slot name="outer" />
<div ref="content" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white" aria-modal="true" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg"> <div ref="content" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white" aria-modal="true" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg">
@@ -0,0 +1,70 @@
<template>
<modals-modal v-model="show" name="player-settings" :width="500" :height="'unset'">
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-4" style="max-height: 80vh; min-height: 40vh">
<h3 class="text-xl font-semibold mb-8">{{ $strings.HeaderPlayerSettings }}</h3>
<div class="flex items-center mb-4">
<ui-toggle-switch v-model="useChapterTrack" @input="setUseChapterTrack" />
<div class="pl-4">
<span>{{ $strings.LabelUseChapterTrack }}</span>
</div>
</div>
<div class="flex items-center mb-4">
<ui-select-input v-model="jumpForwardAmount" :label="$strings.LabelJumpForwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpForwardAmount" />
</div>
<div class="flex items-center">
<ui-select-input v-model="jumpBackwardAmount" :label="$strings.LabelJumpBackwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpBackwardAmount" />
</div>
</div>
</modals-modal>
</template>
<script>
export default {
props: {
value: Boolean
},
data() {
return {
useChapterTrack: false,
jumpValues: [
{ text: this.$getString('LabelTimeDurationXSeconds', ['10']), value: 10 },
{ text: this.$getString('LabelTimeDurationXSeconds', ['15']), value: 15 },
{ text: this.$getString('LabelTimeDurationXSeconds', ['30']), value: 30 },
{ text: this.$getString('LabelTimeDurationXSeconds', ['60']), value: 60 },
{ text: this.$getString('LabelTimeDurationXMinutes', ['2']), value: 120 },
{ text: this.$getString('LabelTimeDurationXMinutes', ['5']), value: 300 }
],
jumpForwardAmount: 10,
jumpBackwardAmount: 10
}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
}
},
methods: {
setUseChapterTrack() {
this.$store.dispatch('user/updateUserSettings', { useChapterTrack: this.useChapterTrack })
},
setJumpForwardAmount(val) {
this.jumpForwardAmount = val
this.$store.dispatch('user/updateUserSettings', { jumpForwardAmount: val })
},
setJumpBackwardAmount(val) {
this.jumpBackwardAmount = val
this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val })
}
},
mounted() {
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
}
}
</script>
+1 -1
View File
@@ -9,7 +9,7 @@
<div class="absolute top-0 right-0 p-4"> <div class="absolute top-0 right-0 p-4">
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2"> <ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
<a href="https://www.audiobookshelf.org/guides/media-item-shares" target="_blank" class="inline-flex"> <a href="https://www.audiobookshelf.org/guides/media-item-shares" target="_blank" class="inline-flex">
<span class="material-icons text-xl w-5 text-gray-200">help_outline</span> <span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
</a> </a>
</ui-tooltip> </ui-tooltip>
</div> </div>
+77 -62
View File
@@ -6,34 +6,36 @@
</div> </div>
</template> </template>
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div v-if="!timerSet" class="w-full"> <div class="w-full">
<template v-for="time in sleepTimes"> <template v-for="time in sleepTimes">
<div :key="time.text" class="flex items-center px-6 py-3 justify-center cursor-pointer hover:bg-bg relative" @click="setTime(time.seconds)"> <div :key="time.text" class="flex items-center px-6 py-3 justify-center cursor-pointer hover:bg-primary/25 relative" @click="setTime(time)">
<p class="text-xl text-center">{{ time.text }}</p> <p class="text-lg text-center">{{ time.text }}</p>
</div> </div>
</template> </template>
<form class="flex items-center justify-center px-6 py-3" @submit.prevent="submitCustomTime"> <form class="flex items-center justify-center px-6 py-3" @submit.prevent="submitCustomTime">
<ui-text-input v-model="customTime" type="number" step="any" min="0.1" placeholder="Time in minutes" class="w-48" /> <ui-text-input v-model="customTime" type="number" step="any" min="0.1" :placeholder="$strings.LabelTimeInMinutes" class="w-48" />
<ui-btn color="success" type="submit" :padding-x="0" class="h-9 w-12 flex items-center justify-center ml-1">Set</ui-btn> <ui-btn color="success" type="submit" :padding-x="0" class="h-9 w-12 flex items-center justify-center ml-1">Set</ui-btn>
</form> </form>
</div> </div>
<div v-else class="w-full p-4"> <div v-if="timerSet" class="w-full p-4">
<div class="mb-4 flex items-center justify-center"> <div class="mb-4 h-px w-full bg-white/10" />
<ui-btn :padding-x="2" small :disabled="remaining < 30 * 60" class="flex items-center mr-4" @click="decrement(30 * 60)">
<span class="material-icons text-lg">remove</span> <div v-if="timerType === $constants.SleepTimerTypes.COUNTDOWN" class="mb-4 flex items-center justify-center space-x-4">
<span class="pl-1 text-base font-mono">30m</span> <ui-btn :padding-x="2" small :disabled="remaining < 30 * 60" class="flex items-center h-9" @click="decrement(30 * 60)">
<span class="material-symbols text-lg">remove</span>
<span class="pl-1 text-sm">30m</span>
</ui-btn> </ui-btn>
<ui-icon-btn icon="remove" @click="decrement(60 * 5)" /> <ui-icon-btn icon="remove" class="min-w-9" @click="decrement(60 * 5)" />
<p class="mx-6 text-2xl font-mono">{{ $secondsToTimestamp(remaining) }}</p> <p class="text-2xl font-mono">{{ $secondsToTimestamp(remaining) }}</p>
<ui-icon-btn icon="add" @click="increment(60 * 5)" /> <ui-icon-btn icon="add" class="min-w-9" @click="increment(60 * 5)" />
<ui-btn :padding-x="2" small class="flex items-center ml-4" @click="increment(30 * 60)"> <ui-btn :padding-x="2" small class="flex items-center h-9" @click="increment(30 * 60)">
<span class="material-icons text-lg">add</span> <span class="material-symbols text-lg">add</span>
<span class="pl-1 text-base font-mono">30m</span> <span class="pl-1 text-sm">30m</span>
</ui-btn> </ui-btn>
</div> </div>
<ui-btn class="w-full" @click="$emit('cancel')">{{ $strings.ButtonCancel }}</ui-btn> <ui-btn class="w-full" @click="$emit('cancel')">{{ $strings.ButtonCancel }}</ui-btn>
@@ -47,52 +49,13 @@ export default {
props: { props: {
value: Boolean, value: Boolean,
timerSet: Boolean, timerSet: Boolean,
timerTime: Number, timerType: String,
remaining: Number remaining: Number,
hasChapters: Boolean
}, },
data() { data() {
return { return {
customTime: null, customTime: null
sleepTimes: [
{
seconds: 60 * 5,
text: '5 minutes'
},
{
seconds: 60 * 15,
text: '15 minutes'
},
{
seconds: 60 * 20,
text: '20 minutes'
},
{
seconds: 60 * 30,
text: '30 minutes'
},
{
seconds: 60 * 45,
text: '45 minutes'
},
{
seconds: 60 * 60,
text: '60 minutes'
},
{
seconds: 60 * 90,
text: '90 minutes'
},
{
seconds: 60 * 120,
text: '2 hours'
}
]
}
},
watch: {
show(newVal) {
if (newVal) {
}
} }
}, },
computed: { computed: {
@@ -103,6 +66,54 @@ export default {
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} }
},
sleepTimes() {
const times = [
{
seconds: 60 * 5,
text: this.$getString('LabelTimeDurationXMinutes', ['5']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 15,
text: this.$getString('LabelTimeDurationXMinutes', ['15']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 20,
text: this.$getString('LabelTimeDurationXMinutes', ['20']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 30,
text: this.$getString('LabelTimeDurationXMinutes', ['30']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 45,
text: this.$getString('LabelTimeDurationXMinutes', ['45']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 60,
text: this.$getString('LabelTimeDurationXMinutes', ['60']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 90,
text: this.$getString('LabelTimeDurationXMinutes', ['90']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
},
{
seconds: 60 * 120,
text: this.$getString('LabelTimeDurationXHours', ['2']),
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
}
]
if (this.hasChapters) {
times.push({ seconds: -1, text: this.$strings.LabelEndOfChapter, timerType: this.$constants.SleepTimerTypes.CHAPTER })
}
return times
} }
}, },
methods: { methods: {
@@ -113,10 +124,14 @@ export default {
} }
const timeInSeconds = Math.round(Number(this.customTime) * 60) const timeInSeconds = Math.round(Number(this.customTime) * 60)
this.setTime(timeInSeconds) const time = {
seconds: timeInSeconds,
timerType: this.$constants.SleepTimerTypes.COUNTDOWN
}
this.setTime(time)
}, },
setTime(seconds) { setTime(time) {
this.$emit('set', seconds) this.$emit('set', time)
}, },
increment(amount) { increment(amount) {
this.$emit('increment', amount) this.$emit('increment', amount)
@@ -12,7 +12,7 @@
</div> </div>
<div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8"> <div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8">
<p class="text-lg">Preview Cover</p> <p class="text-lg">Preview Cover</p>
<span class="absolute top-4 right-4 material-icons text-2xl cursor-pointer" @click="resetCoverPreview">close</span> <span class="absolute top-4 right-4 material-symbols text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<div class="flex justify-center py-4"> <div class="flex justify-center py-4">
<covers-preview-cover :src="previewUpload" :width="240" /> <covers-preview-cover :src="previewUpload" :width="240" />
</div> </div>
@@ -11,7 +11,7 @@
<div class="w-full h-45 relative"> <div class="w-full h-45 relative">
<covers-author-image :author="authorCopy" /> <covers-author-image :author="authorCopy" />
<div v-if="userCanDelete && !processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100"> <div v-if="userCanDelete && !processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100">
<span class="absolute top-2 right-2 material-icons text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span> <span class="absolute top-2 right-2 material-symbols text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span>
</div> </div>
</div> </div>
</div> </div>
@@ -12,9 +12,9 @@
<div class="flex-grow pr-2"> <div class="flex-grow pr-2">
<ui-text-input v-model="newBookmarkTitle" placeholder="Note" class="w-full" /> <ui-text-input v-model="newBookmarkTitle" placeholder="Note" class="w-full" />
</div> </div>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-icons text-2xl -mt-px">forward</span></ui-btn> <ui-btn type="submit" color="success" :padding-x="4" class="h-10"><span class="material-symbols text-2xl -mt-px">forward</span></ui-btn>
<div class="pl-2 flex items-center"> <div class="pl-2 flex items-center">
<span class="material-icons text-3xl text-white text-opacity-70 hover:text-opacity-95 cursor-pointer" @click.stop.prevent="cancelEditing">close</span> <span class="material-symbols text-3xl text-white text-opacity-70 hover:text-opacity-95 cursor-pointer" @click.stop.prevent="cancelEditing">close</span>
</div> </div>
</div> </div>
</form> </form>
@@ -22,8 +22,8 @@
<p v-else class="pl-2 pr-2 truncate">{{ bookmark.title }}</p> <p v-else class="pl-2 pr-2 truncate">{{ bookmark.title }}</p>
</div> </div>
<div v-if="!isEditing" class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'"> <div v-if="!isEditing" class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'">
<span class="material-icons text-xl mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span> <span class="material-symbols text-xl mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
<span class="material-icons text-xl text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span> <span class="material-symbols text-xl text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span>
</div> </div>
</div> </div>
</template> </template>
@@ -6,10 +6,15 @@
</div> </div>
</template> </template>
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh"> <div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
<p class="text-xl font-bold pb-4"> <template v-for="release in releasesToShow">
Changelog <a :href="currentTagUrl" target="_blank" class="hover:underline">v{{ currentVersionNumber }}</a> ({{ currentVersionPubDate }}) <div :key="release.name">
</p> <p class="text-xl font-bold pb-4">
<div class="custom-text" v-html="compiledMarkedown" /> Changelog <a :href="`https://github.com/advplyr/audiobookshelf/releases/tag/${release.name}`" target="_blank" class="hover:underline">{{ release.name }}</a> ({{ $formatDate(release.pubdate, dateFormat) }})
</p>
<div class="custom-text" v-html="getChangelog(release)" />
</div>
<div v-if="release !== releasesToShow[releasesToShow.length - 1]" class="border-b border-black-300 my-8" />
</template>
</div> </div>
</modals-modal> </modals-modal>
</template> </template>
@@ -37,24 +42,15 @@ export default {
dateFormat() { dateFormat() {
return this.$store.state.serverSettings.dateFormat return this.$store.state.serverSettings.dateFormat
}, },
changelog() { releasesToShow() {
return this.versionData?.currentVersionChangelog || 'No Changelog Available' return this.versionData?.releasesToShow || []
}, }
compiledMarkedown() { },
return marked.parse(this.changelog, { gfm: true, breaks: true }) methods: {
}, getChangelog(release) {
currentVersionPubDate() { return marked.parse(release.changelog || 'No Changelog Available', { gfm: true, breaks: true })
if (!this.versionData?.currentVersionPubDate) return 'Unknown release date'
return `${this.$formatDate(this.versionData.currentVersionPubDate, this.dateFormat)}`
},
currentTagUrl() {
return this.versionData?.currentTagUrl
},
currentVersionNumber() {
return this.$config.version
} }
}, },
methods: {},
mounted() {} mounted() {}
} }
</script> </script>
@@ -8,8 +8,8 @@
<nuxt-link :to="`/collection/${collection.id}`" class="pl-2 pr-2 truncate hover:underline cursor-pointer" @click.native="clickNuxtLink">{{ collection.name }}</nuxt-link> <nuxt-link :to="`/collection/${collection.id}`" class="pl-2 pr-2 truncate hover:underline cursor-pointer" @click.native="clickNuxtLink">{{ collection.name }}</nuxt-link>
</div> </div>
<div class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'"> <div class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'">
<ui-btn v-if="!isBookIncluded" color="success" :padding-x="3" small class="h-9" @click.stop="clickAdd"><span class="material-icons text-2xl pt-px">add</span></ui-btn> <ui-btn v-if="!isBookIncluded" color="success" :padding-x="3" small class="h-9" @click.stop="clickAdd"><span class="material-symbols text-2xl pt-px">add</span></ui-btn>
<ui-btn v-else color="error" :padding-x="3" class="h-9" small @click.stop="clickRem"><span class="material-icons text-2xl pt-px">remove</span></ui-btn> <ui-btn v-else color="error" :padding-x="3" class="h-9" small @click.stop="clickRem"><span class="material-symbols text-2xl pt-px">remove</span></ui-btn>
</div> </div>
</div> </div>
</template> </template>
@@ -28,7 +28,7 @@
<template v-else> <template v-else>
<div class="flex items-center mb-3"> <div class="flex items-center mb-3">
<div class="hover:bg-white hover:bg-opacity-10 cursor-pointer h-11 w-11 flex items-center justify-center rounded-full" @click="showImageUploader = false"> <div class="hover:bg-white hover:bg-opacity-10 cursor-pointer h-11 w-11 flex items-center justify-center rounded-full" @click="showImageUploader = false">
<span class="material-icons text-4xl">arrow_back</span> <span class="material-symbols text-4xl">arrow_back</span>
</div> </div>
<p class="ml-2 text-xl mb-1">Collection Cover Image</p> <p class="ml-2 text-xl mb-1">Collection Cover Image</p>
</div> </div>
+2 -2
View File
@@ -12,10 +12,10 @@
</div> </div>
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6"> <div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevBook" @mousedown.prevent>arrow_back_ios</div> <div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevBook" @mousedown.prevent>arrow_back_ios</div>
</div> </div>
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6"> <div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</div> <div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextBook" @mousedown.prevent>arrow_forward_ios</div>
</div> </div>
<div class="w-full h-full max-h-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative"> <div class="w-full h-full max-h-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative">
+3 -3
View File
@@ -9,7 +9,7 @@
<div class="absolute top-0 left-0 w-full h-16 bg-gradient-to-b from-black-600 to-transparent" /> <div class="absolute top-0 left-0 w-full h-16 bg-gradient-to-b from-black-600 to-transparent" />
<div v-if="userCanDelete" class="p-1 absolute top-1 right-1 text-red-500 rounded-full w-8 h-8 cursor-pointer hover:text-red-400 shadow-sm" @click="removeCover"> <div v-if="userCanDelete" class="p-1 absolute top-1 right-1 text-red-500 rounded-full w-8 h-8 cursor-pointer hover:text-red-400 shadow-sm" @click="removeCover">
<ui-tooltip direction="top" :text="$strings.LabelRemoveCover"> <ui-tooltip direction="top" :text="$strings.LabelRemoveCover">
<span class="material-icons text-2xl">delete</span> <span class="material-symbols text-2xl">delete</span>
</ui-tooltip> </ui-tooltip>
</div> </div>
</div> </div>
@@ -19,7 +19,7 @@
<div v-if="userCanUpload" class="w-10 md:w-40 pr-2 md:min-w-32"> <div v-if="userCanUpload" class="w-10 md:w-40 pr-2 md:min-w-32">
<ui-file-input ref="fileInput" @change="fileUploadSelected"> <ui-file-input ref="fileInput" @change="fileUploadSelected">
<span class="hidden md:inline-block">{{ $strings.ButtonUploadCover }}</span> <span class="hidden md:inline-block">{{ $strings.ButtonUploadCover }}</span>
<span class="material-icons text-2xl inline-block md:!hidden">upload</span> <span class="material-symbols text-2xl inline-block md:!hidden">upload</span>
</ui-file-input> </ui-file-input>
</div> </div>
@@ -73,7 +73,7 @@
<div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8"> <div v-if="previewUpload" class="absolute top-0 left-0 w-full h-full z-10 bg-bg p-8">
<p class="text-lg">{{ $strings.HeaderPreviewCover }}</p> <p class="text-lg">{{ $strings.HeaderPreviewCover }}</p>
<span class="absolute top-4 right-4 material-icons text-2xl cursor-pointer" @click="resetCoverPreview">close</span> <span class="absolute top-4 right-4 material-symbols text-2xl cursor-pointer" @click="resetCoverPreview">close</span>
<div class="flex justify-center py-4"> <div class="flex justify-center py-4">
<covers-preview-cover :src="previewUpload" :width="240" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-preview-cover :src="previewUpload" :width="240" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div> </div>
@@ -7,7 +7,7 @@
<div class="flex -mb-0.5"> <div class="flex -mb-0.5">
<p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': checkingNewEpisodes }">{{ $strings.LabelLimit }}</p> <p class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': checkingNewEpisodes }">{{ $strings.LabelLimit }}</p>
<ui-tooltip direction="top" text="Max # of episodes to download. Use 0 for unlimited."> <ui-tooltip direction="top" text="Max # of episodes to download. Use 0 for unlimited.">
<span class="material-icons text-base">info_outlined</span> <span class="material-symbols text-base">info</span>
</ui-tooltip> </ui-tooltip>
</div> </div>
</ui-text-input-with-label> </ui-text-input-with-label>
+61 -20
View File
@@ -28,7 +28,7 @@
<div v-if="selectedMatchOrig" class="absolute top-0 left-0 w-full bg-bg h-full px-2 py-6 md:p-8 max-h-full overflow-y-auto overflow-x-hidden"> <div v-if="selectedMatchOrig" class="absolute top-0 left-0 w-full bg-bg h-full px-2 py-6 md:p-8 max-h-full overflow-y-auto overflow-x-hidden">
<div class="flex mb-4"> <div class="flex mb-4">
<div class="w-8 h-8 rounded-full hover:bg-white hover:bg-opacity-10 flex items-center justify-center cursor-pointer" @click="clearSelectedMatch"> <div class="w-8 h-8 rounded-full hover:bg-white hover:bg-opacity-10 flex items-center justify-center cursor-pointer" @click="clearSelectedMatch">
<span class="material-icons text-3xl">arrow_back</span> <span class="material-symbols text-3xl">arrow_back</span>
</div> </div>
<p class="text-xl pl-3">{{ $strings.HeaderUpdateDetails }}</p> <p class="text-xl pl-3">{{ $strings.HeaderUpdateDetails }}</p>
</div> </div>
@@ -59,49 +59,63 @@
<ui-checkbox v-model="selectedMatchUsage.title" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.title" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.title" :disabled="!selectedMatchUsage.title" :label="$strings.LabelTitle" /> <ui-text-input-with-label v-model="selectedMatch.title" :disabled="!selectedMatchUsage.title" :label="$strings.LabelTitle" />
<p v-if="mediaMetadata.title" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.title || '' }}</p> <p v-if="mediaMetadata.title" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('title', mediaMetadata.title)">{{ mediaMetadata.title || '' }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.subtitle" class="flex items-center py-2"> <div v-if="selectedMatchOrig.subtitle" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.subtitle" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.subtitle" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.subtitle" :disabled="!selectedMatchUsage.subtitle" :label="$strings.LabelSubtitle" /> <ui-text-input-with-label v-model="selectedMatch.subtitle" :disabled="!selectedMatchUsage.subtitle" :label="$strings.LabelSubtitle" />
<p v-if="mediaMetadata.subtitle" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.subtitle || '' }}</p> <p v-if="mediaMetadata.subtitle" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('subtitle', mediaMetadata.subtitle)">{{ mediaMetadata.subtitle }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.author" class="flex items-center py-2"> <div v-if="selectedMatchOrig.author" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.author" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.author" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.author" :disabled="!selectedMatchUsage.author" :label="$strings.LabelAuthor" /> <ui-text-input-with-label v-model="selectedMatch.author" :disabled="!selectedMatchUsage.author" :label="$strings.LabelAuthor" />
<p v-if="mediaMetadata.authorName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.authorName || '' }}</p> <p v-if="mediaMetadata.authorName" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', mediaMetadata.authorName)">{{ mediaMetadata.authorName }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.narrator" class="flex items-center py-2"> <div v-if="selectedMatchOrig.narrator" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.narrator" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.narrator" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" /> <ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName || '' }}</p> <p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('narrator', mediaMetadata.narrators)">{{ mediaMetadata.narratorName }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.description" class="flex items-center py-2"> <div v-if="selectedMatchOrig.description" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" /> <ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" />
<p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</p> <p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.publisher" class="flex items-center py-2"> <div v-if="selectedMatchOrig.publisher" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.publisher" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.publisher" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.publisher" :disabled="!selectedMatchUsage.publisher" :label="$strings.LabelPublisher" /> <ui-text-input-with-label v-model="selectedMatch.publisher" :disabled="!selectedMatchUsage.publisher" :label="$strings.LabelPublisher" />
<p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.publisher || '' }}</p> <p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publisher', mediaMetadata.publisher)">{{ mediaMetadata.publisher }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.publishedYear" class="flex items-center py-2"> <div v-if="selectedMatchOrig.publishedYear" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.publishedYear" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.publishedYear" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.publishedYear" :disabled="!selectedMatchUsage.publishedYear" :label="$strings.LabelPublishYear" /> <ui-text-input-with-label v-model="selectedMatch.publishedYear" :disabled="!selectedMatchUsage.publishedYear" :label="$strings.LabelPublishYear" />
<p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear || '' }}</p> <p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publishedYear', mediaMetadata.publishedYear)">{{ mediaMetadata.publishedYear }}</a>
</p>
</div> </div>
</div> </div>
@@ -109,42 +123,54 @@
<ui-checkbox v-model="selectedMatchUsage.series" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.series" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<widgets-series-input-widget v-model="selectedMatch.series" :disabled="!selectedMatchUsage.series" /> <widgets-series-input-widget v-model="selectedMatch.series" :disabled="!selectedMatchUsage.series" />
<p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.seriesName || '' }}</p> <p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('series', mediaMetadata.series)">{{ mediaMetadata.seriesName }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.genres && selectedMatchOrig.genres.length" class="flex items-center py-2"> <div v-if="selectedMatchOrig.genres?.length" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.genres" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.genres" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-multi-select v-model="selectedMatch.genres" :items="genres" :disabled="!selectedMatchUsage.genres" :label="$strings.LabelGenres" /> <ui-multi-select v-model="selectedMatch.genres" :items="genres" :disabled="!selectedMatchUsage.genres" :label="$strings.LabelGenres" />
<p v-if="mediaMetadata.genres" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }}</p> <p v-if="mediaMetadata.genres?.length" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('genres', mediaMetadata.genres)">{{ mediaMetadata.genres.join(', ') }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.tags" class="flex items-center py-2"> <div v-if="selectedMatchOrig.tags" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.tags" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.tags" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-multi-select v-model="selectedMatch.tags" :items="tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" /> <ui-multi-select v-model="selectedMatch.tags" :items="tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
<p v-if="media.tags" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ media.tags.join(', ') }}</p> <p v-if="media.tags?.length" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('tags', media.tags)">{{ media.tags.join(', ') }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.language" class="flex items-center py-2"> <div v-if="selectedMatchOrig.language" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.language" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.language" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.language" :disabled="!selectedMatchUsage.language" :label="$strings.LabelLanguage" /> <ui-text-input-with-label v-model="selectedMatch.language" :disabled="!selectedMatchUsage.language" :label="$strings.LabelLanguage" />
<p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.language || '' }}</p> <p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('language', mediaMetadata.language)">{{ mediaMetadata.language }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.isbn" class="flex items-center py-2"> <div v-if="selectedMatchOrig.isbn" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.isbn" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.isbn" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.isbn" :disabled="!selectedMatchUsage.isbn" label="ISBN" /> <ui-text-input-with-label v-model="selectedMatch.isbn" :disabled="!selectedMatchUsage.isbn" label="ISBN" />
<p v-if="mediaMetadata.isbn" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.isbn || '' }}</p> <p v-if="mediaMetadata.isbn" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('isbn', mediaMetadata.isbn)">{{ mediaMetadata.isbn }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.asin" class="flex items-center py-2"> <div v-if="selectedMatchOrig.asin" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.asin" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.asin" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.asin" :disabled="!selectedMatchUsage.asin" label="ASIN" /> <ui-text-input-with-label v-model="selectedMatch.asin" :disabled="!selectedMatchUsage.asin" label="ASIN" />
<p v-if="mediaMetadata.asin" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.asin || '' }}</p> <p v-if="mediaMetadata.asin" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('asin', mediaMetadata.asin)">{{ mediaMetadata.asin }}</a>
</p>
</div> </div>
</div> </div>
@@ -152,28 +178,36 @@
<ui-checkbox v-model="selectedMatchUsage.itunesId" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.itunesId" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.itunesId" type="number" :disabled="!selectedMatchUsage.itunesId" label="iTunes ID" /> <ui-text-input-with-label v-model="selectedMatch.itunesId" type="number" :disabled="!selectedMatchUsage.itunesId" label="iTunes ID" />
<p v-if="mediaMetadata.itunesId" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.itunesId || '' }}</p> <p v-if="mediaMetadata.itunesId" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesId', mediaMetadata.itunesId)">{{ mediaMetadata.itunesId }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.feedUrl" class="flex items-center py-2"> <div v-if="selectedMatchOrig.feedUrl" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.feedUrl" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.feedUrl" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.feedUrl" :disabled="!selectedMatchUsage.feedUrl" label="RSS Feed URL" /> <ui-text-input-with-label v-model="selectedMatch.feedUrl" :disabled="!selectedMatchUsage.feedUrl" label="RSS Feed URL" />
<p v-if="mediaMetadata.feedUrl" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.feedUrl || '' }}</p> <p v-if="mediaMetadata.feedUrl" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('feedUrl', mediaMetadata.feedUrl)">{{ mediaMetadata.feedUrl }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.itunesPageUrl" class="flex items-center py-2"> <div v-if="selectedMatchOrig.itunesPageUrl" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.itunesPageUrl" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.itunesPageUrl" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.itunesPageUrl" :disabled="!selectedMatchUsage.itunesPageUrl" label="iTunes Page URL" /> <ui-text-input-with-label v-model="selectedMatch.itunesPageUrl" :disabled="!selectedMatchUsage.itunesPageUrl" label="iTunes Page URL" />
<p v-if="mediaMetadata.itunesPageUrl" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.itunesPageUrl || '' }}</p> <p v-if="mediaMetadata.itunesPageUrl" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('itunesPageUrl', mediaMetadata.itunesPageUrl)">{{ mediaMetadata.itunesPageUrl }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.releaseDate" class="flex items-center py-2"> <div v-if="selectedMatchOrig.releaseDate" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.releaseDate" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.releaseDate" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.releaseDate" :disabled="!selectedMatchUsage.releaseDate" :label="$strings.LabelReleaseDate" /> <ui-text-input-with-label v-model="selectedMatch.releaseDate" :disabled="!selectedMatchUsage.releaseDate" :label="$strings.LabelReleaseDate" />
<p v-if="mediaMetadata.releaseDate" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.releaseDate || '' }}</p> <p v-if="mediaMetadata.releaseDate" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="Click to use current value" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('releaseDate', mediaMetadata.releaseDate)">{{ mediaMetadata.releaseDate }}</a>
</p>
</div> </div>
</div> </div>
<div v-if="selectedMatchOrig.explicit != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.explicit == null }"> <div v-if="selectedMatchOrig.explicit != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.explicit == null }">
@@ -281,7 +315,7 @@ export default {
return this.$store.getters['libraries/getBookCoverAspectRatio'] return this.$store.getters['libraries/getBookCoverAspectRatio']
}, },
filterData() { filterData() {
return this.$store.state.libraries.filterData return this.$store.state.libraries.filterData || {}
}, },
providers() { providers() {
if (this.isPodcast) return this.$store.state.scanners.podcastProviders if (this.isPodcast) return this.$store.state.scanners.podcastProviders
@@ -321,6 +355,13 @@ export default {
} }
}, },
methods: { methods: {
setMatchFieldValue(field, value) {
if (Array.isArray(value)) {
this.selectedMatch[field] = [...value]
} else {
this.selectedMatch[field] = value
}
},
selectAllToggled(val) { selectAllToggled(val) {
for (const key in this.selectedMatchUsage) { for (const key in this.selectedMatchUsage) {
this.selectedMatchUsage[key] = val this.selectedMatchUsage[key] = val
@@ -15,7 +15,7 @@
<ui-tooltip text="Value of 0 sets no max limit. After a new episode is auto-downloaded this will delete the oldest episode if you have more than X episodes. <br>This will only delete 1 episode per new download."> <ui-tooltip text="Value of 0 sets no max limit. After a new episode is auto-downloaded this will delete the oldest episode if you have more than X episodes. <br>This will only delete 1 episode per new download.">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
Max episodes to keep Max episodes to keep
<span class="material-icons icon-text">info_outlined</span> <span class="material-symbols icon-text">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -24,7 +24,7 @@
<ui-tooltip text="Value of 0 sets no max limit. When checking for new episodes this is the max number of episodes that will be downloaded."> <ui-tooltip text="Value of 0 sets no max limit. When checking for new episodes this is the max number of episodes that will be downloaded.">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
Max new episodes to download per check Max new episodes to download per check
<span class="material-icons icon-text">info_outlined</span> <span class="material-symbols icon-text">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
+2 -2
View File
@@ -13,7 +13,7 @@
<div> <div>
<ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=m4b`" class="flex items-center" <ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=m4b`" class="flex items-center"
>{{ $strings.ButtonOpenManager }} >{{ $strings.ButtonOpenManager }}
<span class="material-icons text-lg ml-2">launch</span> <span class="material-symbols text-lg ml-2">launch</span>
</ui-btn> </ui-btn>
</div> </div>
</div> </div>
@@ -30,7 +30,7 @@
<div> <div>
<ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=embed`" class="flex items-center" <ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=embed`" class="flex items-center"
>{{ $strings.ButtonOpenManager }} >{{ $strings.ButtonOpenManager }}
<span class="material-icons text-lg ml-2">launch</span> <span class="material-symbols text-lg ml-2">launch</span>
</ui-btn> </ui-btn>
<ui-btn v-if="!isMetadataEmbedQueued && !isEmbedTaskRunning" class="w-full mt-4" small @click.stop="quickEmbed">Quick Embed</ui-btn> <ui-btn v-if="!isMetadataEmbedQueued && !isEmbedTaskRunning" class="w-full mt-4" small @click.stop="quickEmbed">Quick Embed</ui-btn>
@@ -19,12 +19,12 @@
<div class="folders-container overflow-y-auto w-full py-2 mb-2"> <div class="folders-container overflow-y-auto w-full py-2 mb-2">
<p class="px-1 text-sm font-semibold">{{ $strings.LabelFolders }}</p> <p class="px-1 text-sm font-semibold">{{ $strings.LabelFolders }}</p>
<div v-for="(folder, index) in folders" :key="index" class="w-full flex items-center py-1 px-2"> <div v-for="(folder, index) in folders" :key="index" class="w-full flex items-center py-1 px-2">
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span> <span class="material-symbols fill bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
<ui-editable-text ref="folderInput" v-model="folder.fullPath" :readonly="!!folder.id" type="text" class="w-full" @blur="existingFolderInputBlurred(folder)" /> <ui-editable-text ref="folderInput" v-model="folder.fullPath" :readonly="!!folder.id" type="text" class="w-full" @blur="existingFolderInputBlurred(folder)" />
<span v-show="folders.length > 1" class="material-icons text-2xl ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span> <span v-show="folders.length > 1" class="material-symbols text-2xl ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
</div> </div>
<div class="flex py-1 px-2 items-center w-full"> <div class="flex py-1 px-2 items-center w-full">
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span> <span class="material-symbols fill bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
<ui-editable-text ref="newFolderInput" v-model="newFolderPath" :placeholder="$strings.PlaceholderNewFolderPath" type="text" class="w-full" @blur="newFolderInputBlurred" /> <ui-editable-text ref="newFolderInput" v-model="newFolderPath" :placeholder="$strings.PlaceholderNewFolderPath" type="text" class="w-full" @blur="newFolderInputBlurred" />
</div> </div>
@@ -1,7 +1,7 @@
<template> <template>
<div class="w-full h-full bg-bg absolute top-0 left-0 px-4 py-4 z-10"> <div class="w-full h-full bg-bg absolute top-0 left-0 px-4 py-4 z-10">
<div class="flex items-center py-1 mb-2"> <div class="flex items-center py-1 mb-2">
<span class="material-icons text-3xl cursor-pointer hover:text-gray-300" @click="$emit('back')">arrow_back</span> <span class="material-symbols text-3xl cursor-pointer hover:text-gray-300" @click="$emit('back')">arrow_back</span>
<p class="px-4 text-xl">{{ $strings.HeaderChooseAFolder }}</p> <p class="px-4 text-xl">{{ $strings.HeaderChooseAFolder }}</p>
</div> </div>
<div v-if="rootDirs.length" class="w-full bg-primary bg-opacity-70 py-1 px-4 mb-2"> <div v-if="rootDirs.length" class="w-full bg-primary bg-opacity-70 py-1 px-4 mb-2">
@@ -10,18 +10,18 @@
<div v-if="rootDirs.length" class="relative flex bg-primary bg-opacity-50 p-4 folder-container"> <div v-if="rootDirs.length" class="relative flex bg-primary bg-opacity-50 p-4 folder-container">
<div class="w-1/2 border-r border-bg h-full overflow-y-auto"> <div class="w-1/2 border-r border-bg h-full overflow-y-auto">
<div v-if="level > 0" class="w-full p-1 cursor-pointer flex items-center hover:bg-white/10" @click="goBack"> <div v-if="level > 0" class="w-full p-1 cursor-pointer flex items-center hover:bg-white/10" @click="goBack">
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span> <span class="material-symbols fill bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<p class="text-base font-mono px-2">..</p> <p class="text-base font-mono px-2">..</p>
</div> </div>
<div v-for="dir in _directories" :key="dir.path" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200 hover:bg-white/10" :class="dir.className" @click="selectDir(dir)"> <div v-for="dir in _directories" :key="dir.path" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200 hover:bg-white/10" :class="dir.className" @click="selectDir(dir)">
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span> <span class="material-symbols fill bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p> <p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p>
<span v-if="dir.path === selectedPath" class="material-icons" style="font-size: 1.1rem">arrow_right</span> <span v-if="dir.path === selectedPath" class="material-symbols" style="font-size: 1.1rem">arrow_right</span>
</div> </div>
</div> </div>
<div class="w-1/2 h-full overflow-y-auto"> <div class="w-1/2 h-full overflow-y-auto">
<div v-for="dir in _subdirs" :key="dir.path" :class="dir.className" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200 hover:bg-white/10" @click="selectSubDir(dir)"> <div v-for="dir in _subdirs" :key="dir.path" :class="dir.className" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200 hover:bg-white/10" @click="selectSubDir(dir)">
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span> <span class="material-symbols fill bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
<p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p> <p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p>
</div> </div>
</div> </div>
@@ -9,7 +9,7 @@
<p class="text-sm text-gray-300 pr-2">{{ $strings.LabelMetadataOrderOfPrecedenceDescription }}</p> <p class="text-sm text-gray-300 pr-2">{{ $strings.LabelMetadataOrderOfPrecedenceDescription }}</p>
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex"> <ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex">
<a href="https://www.audiobookshelf.org/guides/book-scanner" target="_blank" class="inline-flex"> <a href="https://www.audiobookshelf.org/guides/book-scanner" target="_blank" class="inline-flex">
<span class="material-icons text-xl w-5">help_outline</span> <span class="material-symbols text-xl w-5">help_outline</span>
</a> </a>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -17,7 +17,7 @@
<draggable v-model="metadataSourceMapped" v-bind="dragOptions" class="list-group" draggable=".item" handle=".drag-handle" tag="ul" @start="drag = true" @end="drag = false" @update="draggableUpdate"> <draggable v-model="metadataSourceMapped" v-bind="dragOptions" class="list-group" draggable=".item" handle=".drag-handle" tag="ul" @start="drag = true" @end="drag = false" @update="draggableUpdate">
<transition-group type="transition" :name="!drag ? 'flip-list' : null"> <transition-group type="transition" :name="!drag ? 'flip-list' : null">
<li v-for="(source, index) in metadataSourceMapped" :key="source.id" :class="source.include ? 'item' : 'opacity-50'" class="w-full px-2 flex items-center relative border border-white/10"> <li v-for="(source, index) in metadataSourceMapped" :key="source.id" :class="source.include ? 'item' : 'opacity-50'" class="w-full px-2 flex items-center relative border border-white/10">
<span class="material-icons drag-handle text-xl text-gray-400 hover:text-gray-50 mr-2 md:mr-4">reorder</span> <span class="material-symbols drag-handle text-xl text-gray-400 hover:text-gray-50 mr-2 md:mr-4">reorder</span>
<div class="text-center py-1 w-8 min-w-8"> <div class="text-center py-1 w-8 min-w-8">
{{ source.include ? getSourceIndex(source.id) : '' }} {{ source.include ? getSourceIndex(source.id) : '' }}
</div> </div>
@@ -5,7 +5,7 @@
<ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp"> <ui-tooltip :text="$strings.LabelSettingsSquareBookCoversHelp">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
{{ $strings.LabelSettingsSquareBookCovers }} {{ $strings.LabelSettingsSquareBookCovers }}
<span class="material-icons icon-text text-sm">info_outlined</span> <span class="material-symbols icon-text text-sm">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -22,7 +22,7 @@
<ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp"> <ui-tooltip :text="$strings.LabelSettingsAudiobooksOnlyHelp">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
{{ $strings.LabelSettingsAudiobooksOnly }} {{ $strings.LabelSettingsAudiobooksOnly }}
<span class="material-icons icon-text text-sm">info_outlined</span> <span class="material-symbols icon-text text-sm">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -44,7 +44,7 @@
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp"> <ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
{{ $strings.LabelSettingsHideSingleBookSeries }} {{ $strings.LabelSettingsHideSingleBookSeries }}
<span class="material-icons icon-text text-sm">info_outlined</span> <span class="material-symbols icon-text text-sm">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -55,7 +55,7 @@
<ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp"> <ui-tooltip :text="$strings.LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
{{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }} {{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }}
<span class="material-icons icon-text text-sm">info_outlined</span> <span class="material-symbols icon-text text-sm">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -66,7 +66,7 @@
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp"> <ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
<p class="pl-4 text-base"> <p class="pl-4 text-base">
{{ $strings.LabelSettingsEpubsAllowScriptedContent }} {{ $strings.LabelSettingsEpubsAllowScriptedContent }}
<span class="material-icons icon-text text-sm">info_outlined</span> <span class="material-symbols icon-text text-sm">info</span>
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -10,10 +10,10 @@
<p v-if="isOpenInPlayer" class="text-sm text-right text-gray-400">{{ $strings.ButtonPlaying }}</p> <p v-if="isOpenInPlayer" class="text-sm text-right text-gray-400">{{ $strings.ButtonPlaying }}</p>
<div v-else-if="isHovering" class="flex items-center justify-end -mx-1"> <div v-else-if="isHovering" class="flex items-center justify-end -mx-1">
<button class="outline-none mx-1 flex items-center" @click.stop="playClick"> <button class="outline-none mx-1 flex items-center" @click.stop="playClick">
<span class="material-icons text-2xl text-success">play_arrow</span> <span class="material-symbols fill text-2xl text-success">play_arrow</span>
</button> </button>
<button class="outline-none mx-1 flex items-center" @click.stop="removeClick"> <button class="outline-none mx-1 flex items-center" @click.stop="removeClick">
<span class="material-icons text-2xl text-error">close</span> <span class="material-symbols text-2xl text-error">close</span>
</button> </button>
</div> </div>
<p v-else class="text-gray-400 text-sm text-right">{{ durationPretty }}</p> <p v-else class="text-gray-400 text-sm text-right">{{ durationPretty }}</p>
@@ -8,8 +8,8 @@
<nuxt-link :to="`/playlist/${playlist.id}`" class="pl-2 pr-2 truncate hover:underline cursor-pointer" @click.native="clickNuxtLink">{{ playlist.name }}</nuxt-link> <nuxt-link :to="`/playlist/${playlist.id}`" class="pl-2 pr-2 truncate hover:underline cursor-pointer" @click.native="clickNuxtLink">{{ playlist.name }}</nuxt-link>
</div> </div>
<div class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'"> <div class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'">
<ui-btn v-if="!isItemIncluded" color="success" :padding-x="3" small class="h-9" @click.stop="clickAdd"><span class="material-icons text-2xl pt-px">add</span></ui-btn> <ui-btn v-if="!isItemIncluded" color="success" :padding-x="3" small class="h-9" @click.stop="clickAdd"><span class="material-symbols text-2xl pt-px">add</span></ui-btn>
<ui-btn v-else color="error" :padding-x="3" class="h-9" small @click.stop="clickRem"><span class="material-icons text-2xl pt-px">remove</span></ui-btn> <ui-btn v-else color="error" :padding-x="3" class="h-9" small @click.stop="clickRem"><span class="material-symbols text-2xl pt-px">remove</span></ui-btn>
</div> </div>
</div> </div>
</template> </template>
@@ -12,10 +12,10 @@
</div> </div>
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6"> <div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevEpisode" @mousedown.prevent>arrow_back_ios</div> <div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevEpisode" @mousedown.prevent>arrow_back_ios</div>
</div> </div>
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6"> <div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextEpisode" @mousedown.prevent>arrow_forward_ios</div> <div class="material-symbols text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextEpisode" @mousedown.prevent>arrow_forward_ios</div>
</div> </div>
<div ref="wrapper" class="p-4 w-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-y-auto" style="max-height: 80vh"> <div ref="wrapper" class="p-4 w-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-y-auto" style="max-height: 80vh">
@@ -20,7 +20,7 @@
@click="toggleSelectEpisode(episode)" @click="toggleSelectEpisode(episode)"
> >
<div class="absolute top-0 left-0 h-full flex items-center p-2"> <div class="absolute top-0 left-0 h-full flex items-center p-2">
<span v-if="getIsEpisodeDownloaded(episode)" class="material-icons text-success text-xl">download_done</span> <span v-if="getIsEpisodeDownloaded(episode)" class="material-symbols text-success text-xl">download_done</span>
<ui-checkbox v-else v-model="selectedEpisodes[episode.cleanUrl]" small checkbox-bg="primary" border-color="gray-600" /> <ui-checkbox v-else v-model="selectedEpisodes[episode.cleanUrl]" small checkbox-bg="primary" border-color="gray-600" />
</div> </div>
<div class="px-8 py-2"> <div class="px-8 py-2">
@@ -16,11 +16,18 @@
</div> </div>
</div> </div>
<p class="text-lg font-semibold mb-2">{{ $strings.HeaderPodcastsToAdd }}</p> <p class="text-lg font-semibold mb-1">{{ $strings.HeaderPodcastsToAdd }}</p>
<p class="text-sm text-gray-300 mb-4">{{ $strings.MessageOpmlPreviewNote }}</p>
<div class="w-full overflow-y-auto" style="max-height: 50vh"> <div class="w-full overflow-y-auto" style="max-height: 50vh">
<template v-for="(feed, index) in feedMetadata"> <template v-for="(feed, index) in feeds">
<cards-podcast-feed-summary-card :key="index" :feed="feed" :library-folder-path="selectedFolderPath" class="my-1" /> <div :key="index" class="py-1 flex items-center">
<p class="text-lg font-semibold">{{ index + 1 }}.</p>
<div class="pl-2">
<p v-if="feed.title" class="text-sm font-semibold">{{ feed.title }}</p>
<p class="text-xs text-gray-400">{{ feed.feedUrl }}</p>
</div>
</div>
</template> </template>
</div> </div>
</div> </div>
@@ -45,9 +52,7 @@ export default {
return { return {
processing: false, processing: false,
selectedFolderId: null, selectedFolderId: null,
fullPath: null, autoDownloadEpisodes: false
autoDownloadEpisodes: false,
feedMetadata: []
} }
}, },
watch: { watch: {
@@ -96,73 +101,36 @@ export default {
} }
}, },
methods: { methods: {
toFeedMetadata(feed) {
const metadata = feed.metadata
return {
title: metadata.title,
author: metadata.author,
description: metadata.description,
releaseDate: '',
genres: [...metadata.categories],
feedUrl: metadata.feedUrl,
imageUrl: metadata.image,
itunesPageUrl: '',
itunesId: '',
itunesArtistId: '',
language: '',
numEpisodes: feed.numEpisodes
}
},
init() { init() {
this.feedMetadata = this.feeds.map(this.toFeedMetadata)
if (this.folderItems[0]) { if (this.folderItems[0]) {
this.selectedFolderId = this.folderItems[0].value this.selectedFolderId = this.folderItems[0].value
} }
}, },
async submit() { async submit() {
this.processing = true this.processing = true
const newFeedPayloads = this.feedMetadata.map((metadata) => {
return {
path: `${this.selectedFolderPath}/${this.$sanitizeFilename(metadata.title)}`,
folderId: this.selectedFolderId,
libraryId: this.currentLibrary.id,
media: {
metadata: {
...metadata
},
autoDownloadEpisodes: this.autoDownloadEpisodes
}
}
})
console.log('New feed payloads', newFeedPayloads)
for (const podcastPayload of newFeedPayloads) { const payload = {
await this.$axios feeds: this.feeds.map((f) => f.feedUrl),
.$post('/api/podcasts', podcastPayload) folderId: this.selectedFolderId,
.then(() => { libraryId: this.currentLibrary.id,
this.$toast.success(`${podcastPayload.media.metadata.title}: ${this.$strings.ToastPodcastCreateSuccess}`) autoDownloadEpisodes: this.autoDownloadEpisodes
})
.catch((error) => {
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastPodcastCreateFailed
console.error('Failed to create podcast', podcastPayload, error)
this.$toast.error(`${podcastPayload.media.metadata.title}: ${errorMsg}`)
})
} }
this.processing = false this.$axios
this.show = false .$post('/api/podcasts/opml/create', payload)
.then(() => {
this.show = false
})
.catch((error) => {
const errorMsg = error.response?.data || this.$strings.ToastPodcastCreateFailed
console.error('Failed to create podcast', payload, error)
this.$toast.error(errorMsg)
})
.finally(() => {
this.processing = false
})
} }
}, },
mounted() {} mounted() {}
} }
</script> </script>
<style scoped>
#podcast-wrapper {
min-height: 400px;
max-height: 80vh;
}
#episodes-scroll {
max-height: calc(80vh - 200px);
}
</style>
@@ -132,7 +132,7 @@ export default {
this.searchedTitle = this.episodeTitle this.searchedTitle = this.episodeTitle
this.isProcessing = true this.isProcessing = true
this.$axios this.$axios
.$get(`/api/podcasts/${this.libraryItem.id}/search-episode?title=${this.$encodeUriPath(this.episodeTitle)}`) .$get(`/api/podcasts/${this.libraryItem.id}/search-episode?title=${encodeURIComponent(this.episodeTitle)}`)
.then((results) => { .then((results) => {
this.episodesFound = results.episodes.map((ep) => ep.episode) this.episodesFound = results.episodes.map((ep) => ep.episode)
console.log('Episodes found', this.episodesFound) console.log('Episodes found', this.episodesFound)
@@ -12,7 +12,7 @@
<div class="w-full relative"> <div class="w-full relative">
<ui-text-input v-model="currentFeed.feedUrl" readonly /> <ui-text-input v-model="currentFeed.feedUrl" readonly />
<span class="material-icons absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(currentFeed.feedUrl)">content_copy</span> <span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(currentFeed.feedUrl)">content_copy</span>
</div> </div>
<div v-if="currentFeed.meta" class="mt-5"> <div v-if="currentFeed.meta" class="mt-5">
@@ -6,7 +6,7 @@
<div class="w-full relative"> <div class="w-full relative">
<ui-text-input v-model="feed.feedUrl" readonly /> <ui-text-input v-model="feed.feedUrl" readonly />
<span class="material-icons absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feed.feedUrl)">content_copy</span> <span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feed.feedUrl)">content_copy</span>
</div> </div>
<div v-if="feed.meta" class="mt-5"> <div v-if="feed.meta" class="mt-5">
@@ -4,32 +4,32 @@
<template v-if="!loading"> <template v-if="!loading">
<ui-tooltip direction="top" :text="$strings.ButtonPreviousChapter" class="mr-4 lg:mr-8"> <ui-tooltip direction="top" :text="$strings.ButtonPreviousChapter" class="mr-4 lg:mr-8">
<button :aria-label="$strings.ButtonPreviousChapter" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="prevChapter"> <button :aria-label="$strings.ButtonPreviousChapter" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="prevChapter">
<span class="material-icons text-2xl sm:text-3xl">first_page</span> <span class="material-symbols text-2xl sm:text-3xl">first_page</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<ui-tooltip direction="top" :text="$strings.ButtonJumpBackward"> <ui-tooltip direction="top" :text="jumpBackwardText">
<button :aria-label="$strings.ButtonJumpBackward" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpBackward"> <button :aria-label="jumpForwardText" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpBackward">
<span class="material-icons text-2xl sm:text-3xl">replay_10</span> <span class="material-symbols text-2xl sm:text-3xl">replay</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<button :aria-label="paused ? $strings.ButtonPlay : $strings.ButtonPause" class="p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-4 lg:mx-8" :class="seekLoading ? 'animate-spin' : ''" @mousedown.prevent @mouseup.prevent @click.stop="playPause"> <button :aria-label="paused ? $strings.ButtonPlay : $strings.ButtonPause" class="p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-4 lg:mx-8" :class="seekLoading ? 'animate-spin' : ''" @mousedown.prevent @mouseup.prevent @click.stop="playPause">
<span class="material-icons text-2xl">{{ seekLoading ? 'autorenew' : paused ? 'play_arrow' : 'pause' }}</span> <span class="material-symbols fill text-2xl">{{ seekLoading ? 'autorenew' : paused ? 'play_arrow' : 'pause' }}</span>
</button> </button>
<ui-tooltip direction="top" :text="$strings.ButtonJumpForward"> <ui-tooltip direction="top" :text="jumpForwardText">
<button :aria-label="$strings.ButtonJumpForward" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpForward"> <button :aria-label="jumpForwardText" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpForward">
<span class="material-icons text-2xl sm:text-3xl">forward_10</span> <span class="material-symbols text-2xl sm:text-3xl">forward_media</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<ui-tooltip direction="top" :text="$strings.ButtonNextChapter" class="ml-4 lg:ml-8"> <ui-tooltip direction="top" :text="$strings.ButtonNextChapter" class="ml-4 lg:ml-8">
<button :aria-label="$strings.ButtonNextChapter" :disabled="!hasNextChapter" :class="hasNextChapter ? 'text-gray-300' : 'text-gray-500'" @mousedown.prevent @mouseup.prevent @click.stop="nextChapter"> <button :aria-label="$strings.ButtonNextChapter" :disabled="!hasNextChapter" :class="hasNextChapter ? 'text-gray-300' : 'text-gray-500'" @mousedown.prevent @mouseup.prevent @click.stop="nextChapter">
<span class="material-icons text-2xl sm:text-3xl">last_page</span> <span class="material-symbols text-2xl sm:text-3xl">last_page</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<controls-playback-speed-control v-model="playbackRateInput" @input="playbackRateUpdated" @change="playbackRateChanged" /> <controls-playback-speed-control v-model="playbackRateInput" @input="playbackRateUpdated" @change="playbackRateChanged" />
</template> </template>
<template v-else> <template v-else>
<div class="cursor-pointer p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-8 animate-spin"> <div class="cursor-pointer p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-8 animate-spin">
<span class="material-icons">autorenew</span> <span class="material-symbols text-2xl">autorenew</span>
</div> </div>
</template> </template>
<div class="flex-grow" /> <div class="flex-grow" />
@@ -56,6 +56,12 @@ export default {
set(val) { set(val) {
this.$emit('update:playbackRate', val) this.$emit('update:playbackRate', val)
} }
},
jumpForwardText() {
return this.getJumpText('jumpForwardAmount', this.$strings.ButtonJumpForward)
},
jumpBackwardText() {
return this.getJumpText('jumpBackwardAmount', this.$strings.ButtonJumpBackward)
} }
}, },
methods: { methods: {
@@ -83,6 +89,20 @@ export default {
this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => { this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => {
console.error('Failed to update settings', err) console.error('Failed to update settings', err)
}) })
},
getJumpText(setting, prefix) {
const amount = this.$store.getters['user/getUserSetting'](setting)
if (!amount) return prefix
let formattedTime = ''
if (amount <= 60) {
formattedTime = this.$getString('LabelTimeDurationXSeconds', [amount])
} else {
const minutes = Math.floor(amount / 60)
formattedTime = this.$getString('LabelTimeDurationXMinutes', [minutes])
}
return `${prefix} - ${formattedTime}`
} }
}, },
mounted() {} mounted() {}
+34 -27
View File
@@ -2,7 +2,7 @@
<div class="w-full -mt-6"> <div class="w-full -mt-6">
<div class="w-full relative mb-1"> <div class="w-full relative mb-1">
<div class="absolute -top-10 lg:top-0 right-0 lg:right-2 flex items-center h-full"> <div class="absolute -top-10 lg:top-0 right-0 lg:right-2 flex items-center h-full">
<!-- <span class="material-icons text-2xl cursor-pointer" @click="toggleFullscreen(true)">expand_less</span> --> <!-- <span class="material-symbols text-2xl cursor-pointer" @click="toggleFullscreen(true)">expand_less</span> -->
<ui-tooltip direction="top" :text="$strings.LabelVolume"> <ui-tooltip direction="top" :text="$strings.LabelVolume">
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" /> <controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" />
@@ -10,35 +10,35 @@
<ui-tooltip v-if="!hideSleepTimer" direction="top" :text="$strings.LabelSleepTimer"> <ui-tooltip v-if="!hideSleepTimer" direction="top" :text="$strings.LabelSleepTimer">
<button :aria-label="$strings.LabelSleepTimer" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')"> <button :aria-label="$strings.LabelSleepTimer" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showSleepTimer')">
<span v-if="!sleepTimerSet" class="material-icons text-2xl">snooze</span> <span v-if="!sleepTimerSet" class="material-symbols text-2xl">snooze</span>
<div v-else class="flex items-center"> <div v-else class="flex items-center">
<span class="material-icons text-lg text-warning">snooze</span> <span class="material-symbols text-lg text-warning">snooze</span>
<p class="text-xl text-warning font-mono font-semibold text-center px-0.5 pb-0.5" style="min-width: 30px">{{ sleepTimerRemainingString }}</p> <p class="text-sm sm:text-lg text-warning font-mono font-semibold text-center px-0.5 sm:pb-0.5 sm:min-w-8">{{ sleepTimerRemainingString }}</p>
</div> </div>
</button> </button>
</ui-tooltip> </ui-tooltip>
<ui-tooltip v-if="!isPodcast && !hideBookmarks" direction="top" :text="$strings.LabelViewBookmarks"> <ui-tooltip v-if="!isPodcast && !hideBookmarks" direction="top" :text="$strings.LabelViewBookmarks">
<button :aria-label="$strings.LabelViewBookmarks" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')"> <button :aria-label="$strings.LabelViewBookmarks" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showBookmarks')">
<span class="material-icons text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span> <span class="material-symbols text-2xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<ui-tooltip v-if="chapters.length" direction="top" :text="$strings.LabelViewChapters"> <ui-tooltip v-if="chapters.length" direction="top" :text="$strings.LabelViewChapters">
<button :aria-label="$strings.LabelViewChapters" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters"> <button :aria-label="$strings.LabelViewChapters" class="text-gray-300 hover:text-white mx-1 lg:mx-2" @mousedown.prevent @mouseup.prevent @click.stop="showChapters">
<span class="material-icons text-2xl">format_list_bulleted</span> <span class="material-symbols text-2xl">format_list_bulleted</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<ui-tooltip v-if="playerQueueItems.length" direction="top" :text="$strings.LabelViewQueue"> <ui-tooltip v-if="playerQueueItems.length" direction="top" :text="$strings.LabelViewQueue">
<button :aria-label="$strings.LabelViewQueue" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')"> <button :aria-label="$strings.LabelViewQueue" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerQueueItems')">
<span class="material-icons text-2.5xl sm:text-3xl">playlist_play</span> <span class="material-symbols text-2.5xl sm:text-3xl">playlist_play</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
<ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack"> <ui-tooltip direction="top" :text="$strings.LabelViewPlayerSettings">
<button :aria-label="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack" class="text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="setUseChapterTrack"> <button :aria-label="$strings.LabelViewPlayerSettings" class="outline-none text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="$emit('showPlayerSettings')">
<span class="material-icons text-2xl sm:text-3xl transform transition-transform" :class="useChapterTrack ? 'rotate-180' : ''">timelapse</span> <span class="material-symbols text-2xl sm:text-2.5xl">settings_slow_motion</span>
</button> </button>
</ui-tooltip> </ui-tooltip>
</div> </div>
@@ -72,12 +72,14 @@ export default {
type: Array, type: Array,
default: () => [] default: () => []
}, },
currentChapter: Object,
bookmarks: { bookmarks: {
type: Array, type: Array,
default: () => [] default: () => []
}, },
sleepTimerSet: Boolean, sleepTimerSet: Boolean,
sleepTimerRemaining: Number, sleepTimerRemaining: Number,
sleepTimerType: String,
isPodcast: Boolean, isPodcast: Boolean,
hideBookmarks: Boolean, hideBookmarks: Boolean,
hideSleepTimer: Boolean hideSleepTimer: Boolean
@@ -90,27 +92,34 @@ export default {
seekLoading: false, seekLoading: false,
showChaptersModal: false, showChaptersModal: false,
currentTime: 0, currentTime: 0,
duration: 0, duration: 0
useChapterTrack: false
} }
}, },
watch: { watch: {
playbackRate() { playbackRate() {
this.updateTimestamp() this.updateTimestamp()
},
useChapterTrack() {
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
this.updateTimestamp()
} }
}, },
computed: { computed: {
sleepTimerRemainingString() { sleepTimerRemainingString() {
var rounded = Math.round(this.sleepTimerRemaining) if (this.sleepTimerType === this.$constants.SleepTimerTypes.CHAPTER) {
if (rounded < 90) { return 'EoC'
return `${rounded}s` } else {
var rounded = Math.round(this.sleepTimerRemaining)
if (rounded < 90) {
return `${rounded}s`
}
var minutesRounded = Math.round(rounded / 60)
if (minutesRounded <= 90) {
return `${minutesRounded}m`
}
var hoursRounded = Math.round(minutesRounded / 60)
return `${hoursRounded}h`
} }
var minutesRounded = Math.round(rounded / 60)
if (minutesRounded < 90) {
return `${minutesRounded}m`
}
var hoursRounded = Math.round(minutesRounded / 60)
return `${hoursRounded}h`
}, },
token() { token() {
return this.$store.getters['user/getToken'] return this.$store.getters['user/getToken']
@@ -135,9 +144,6 @@ export default {
if (!duration) return 0 if (!duration) return 0
return Math.round((100 * time) / duration) return Math.round((100 * time) / duration)
}, },
currentChapter() {
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
},
currentChapterName() { currentChapterName() {
return this.currentChapter ? this.currentChapter.title : '' return this.currentChapter ? this.currentChapter.title : ''
}, },
@@ -162,6 +168,10 @@ export default {
}, },
playerQueueItems() { playerQueueItems() {
return this.$store.state.playerQueueItems || [] return this.$store.state.playerQueueItems || []
},
useChapterTrack() {
const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
return this.chapters.length ? _useChapterTrack : false
} }
}, },
methods: { methods: {
@@ -310,9 +320,6 @@ export default {
init() { init() {
this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1 this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1
const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
this.useChapterTrack = this.chapters.length ? _useChapterTrack : false
if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack) if (this.$refs.trackbar) this.$refs.trackbar.setUseChapterTrack(this.useChapterTrack)
this.setPlaybackRate(this.playbackRate) this.setPlaybackRate(this.playbackRate)
}, },
+5 -5
View File
@@ -15,13 +15,13 @@
</div> </div>
<div v-if="numPages" class="absolute top-0 left-4 sm:left-8 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="clickShowPageMenu"> <div v-if="numPages" class="absolute top-0 left-4 sm:left-8 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="clickShowPageMenu">
<span class="material-icons text-xl">menu</span> <span class="material-symbols text-xl">menu</span>
</div> </div>
<div v-if="comicMetadata" class="absolute top-0 left-16 sm:left-20 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="clickShowInfoMenu"> <div v-if="comicMetadata" class="absolute top-0 left-16 sm:left-20 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" @mousedown.prevent @click.stop.prevent="clickShowInfoMenu">
<span class="material-icons text-xl">more</span> <span class="material-symbols text-xl">more</span>
</div> </div>
<a v-if="pages && numPages" :href="mainImg" :download="pages[page - 1]" class="absolute top-0 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" :class="comicMetadata ? 'left-28 sm:left-32' : 'left-16 sm:left-20'"> <a v-if="pages && numPages" :href="mainImg" :download="pages[page - 1]" class="absolute top-0 bg-bg text-gray-100 border-b border-l border-r border-gray-400 hover:bg-black-200 cursor-pointer rounded-b-md w-10 h-9 flex items-center justify-center text-center z-20" :class="comicMetadata ? 'left-28 sm:left-32' : 'left-16 sm:left-20'">
<span class="material-icons text-xl">download</span> <span class="material-symbols text-xl">download</span>
</a> </a>
<div v-if="numPages" class="absolute top-0 right-14 sm:right-16 bg-bg text-gray-100 border-b border-l border-r border-gray-400 rounded-b-md px-2 h-9 flex items-center text-center z-20"> <div v-if="numPages" class="absolute top-0 right-14 sm:right-16 bg-bg text-gray-100 border-b border-l border-r border-gray-400 rounded-b-md px-2 h-9 flex items-center text-center z-20">
@@ -35,12 +35,12 @@
<div class="w-full h-full relative"> <div class="w-full h-full relative">
<div v-show="canGoPrev" ref="prevButton" class="absolute top-0 left-0 h-full w-1/2 lg:w-1/3 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="prev" @mousedown.prevent> <div v-show="canGoPrev" ref="prevButton" class="absolute top-0 left-0 h-full w-1/2 lg:w-1/3 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="prev" @mousedown.prevent>
<div class="flex items-center justify-center h-full w-1/2"> <div class="flex items-center justify-center h-full w-1/2">
<span v-show="loadedFirstPage" class="material-icons text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_back_ios</span> <span v-show="loadedFirstPage" class="material-symbols text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_back_ios</span>
</div> </div>
</div> </div>
<div v-show="canGoNext" ref="nextButton" class="absolute top-0 right-0 h-full w-1/2 lg:w-1/3 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="next" @mousedown.prevent> <div v-show="canGoNext" ref="nextButton" class="absolute top-0 right-0 h-full w-1/2 lg:w-1/3 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="next" @mousedown.prevent>
<div class="flex items-center justify-center h-full w-1/2 ml-auto"> <div class="flex items-center justify-center h-full w-1/2 ml-auto">
<span v-show="loadedFirstPage" class="material-icons text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_forward_ios</span> <span v-show="loadedFirstPage" class="material-symbols text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_forward_ios</span>
</div> </div>
</div> </div>
<div ref="imageContainer" class="w-full h-full relative overflow-auto"> <div ref="imageContainer" class="w-full h-full relative overflow-auto">
+2 -2
View File
@@ -2,13 +2,13 @@
<div id="epub-reader" class="h-full w-full"> <div id="epub-reader" class="h-full w-full">
<div class="h-full flex items-center justify-center"> <div class="h-full flex items-center justify-center">
<button type="button" aria-label="Previous page" class="w-24 max-w-24 h-full hidden sm:flex items-center overflow-x-hidden justify-center opacity-50 hover:opacity-100"> <button type="button" aria-label="Previous page" class="w-24 max-w-24 h-full hidden sm:flex items-center overflow-x-hidden justify-center opacity-50 hover:opacity-100">
<span v-if="hasPrev" class="material-icons text-6xl" @mousedown.prevent @click="prev">chevron_left</span> <span v-if="hasPrev" class="material-symbols text-6xl" @mousedown.prevent @click="prev">chevron_left</span>
</button> </button>
<div id="frame" class="w-full" style="height: 80%"> <div id="frame" class="w-full" style="height: 80%">
<div id="viewer"></div> <div id="viewer"></div>
</div> </div>
<button type="button" aria-label="Next page" class="w-24 max-w-24 h-full hidden sm:flex items-center justify-center overflow-x-hidden opacity-50 hover:opacity-100"> <button type="button" aria-label="Next page" class="w-24 max-w-24 h-full hidden sm:flex items-center justify-center overflow-x-hidden opacity-50 hover:opacity-100">
<span v-if="hasNext" class="material-icons text-6xl" @mousedown.prevent @click="next">chevron_right</span> <span v-if="hasNext" class="material-symbols text-6xl" @mousedown.prevent @click="next">chevron_right</span>
</button> </button>
</div> </div>
</div> </div>
+2 -2
View File
@@ -2,12 +2,12 @@
<div class="w-full h-full pt-20 relative"> <div class="w-full h-full pt-20 relative">
<div v-show="canGoPrev" class="absolute top-0 left-0 h-full w-1/2 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="prev" @mousedown.prevent> <div v-show="canGoPrev" class="absolute top-0 left-0 h-full w-1/2 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="prev" @mousedown.prevent>
<div class="flex items-center justify-center h-full w-1/2"> <div class="flex items-center justify-center h-full w-1/2">
<span class="material-icons text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_back_ios</span> <span class="material-symbols text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_back_ios</span>
</div> </div>
</div> </div>
<div v-show="canGoNext" class="absolute top-0 right-0 h-full w-1/2 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="next" @mousedown.prevent> <div v-show="canGoNext" class="absolute top-0 right-0 h-full w-1/2 hover:opacity-100 opacity-0 z-10 cursor-pointer" @click.stop.prevent="next" @mousedown.prevent>
<div class="flex items-center justify-center h-full w-1/2 ml-auto"> <div class="flex items-center justify-center h-full w-1/2 ml-auto">
<span class="material-icons text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_forward_ios</span> <span class="material-symbols text-5xl text-white cursor-pointer text-opacity-30 hover:text-opacity-90">arrow_forward_ios</span>
</div> </div>
</div> </div>
+4 -4
View File
@@ -2,10 +2,10 @@
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': !!streamLibraryItem }"> <div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': !!streamLibraryItem }">
<div class="absolute top-4 left-4 z-20 flex items-center"> <div class="absolute top-4 left-4 z-20 flex items-center">
<button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100"> <button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100">
<span class="material-icons text-2xl">menu</span> <span class="material-symbols text-2xl">menu</span>
</button> </button>
<button v-if="hasSettings" @click="openSettings" type="button" aria-label="Ereader settings" class="mx-4 inline-flex opacity-80 hover:opacity-100"> <button v-if="hasSettings" @click="openSettings" type="button" aria-label="Ereader settings" class="mx-4 inline-flex opacity-80 hover:opacity-100">
<span class="material-icons text-1.5xl">settings</span> <span class="material-symbols text-1.5xl">settings</span>
</button> </button>
</div> </div>
@@ -19,7 +19,7 @@
<div class="absolute top-4 right-4 z-20"> <div class="absolute top-4 right-4 z-20">
<button @click="close" type="button" aria-label="Close ereader" class="inline-flex opacity-80 hover:opacity-100"> <button @click="close" type="button" aria-label="Close ereader" class="inline-flex opacity-80 hover:opacity-100">
<span class="material-icons text-2xl">close</span> <span class="material-symbols text-2xl">close</span>
</button> </button>
</div> </div>
@@ -31,7 +31,7 @@
<div class="flex flex-col p-4 h-full"> <div class="flex flex-col p-4 h-full">
<div class="flex items-center mb-2"> <div class="flex items-center mb-2">
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100"> <button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
<span class="material-icons text-2xl">arrow_back</span> <span class="material-symbols text-2xl">arrow_back</span>
</button> </button>
<p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p> <p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p>
+3 -3
View File
@@ -11,7 +11,7 @@
</div> </div>
<div class="flex p-2"> <div class="flex p-2">
<span class="material-icons text-5xl py-1">show_chart</span> <span class="material-symbols text-5xl py-1">show_chart</span>
<div class="px-1"> <div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalTime) }}</p> <p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalTime) }}</p>
<p class="text-xs md:text-sm text-white text-opacity-80">{{ useOverallHours ? $strings.LabelStatsOverallHours : $strings.LabelStatsOverallDays }}</p> <p class="text-xs md:text-sm text-white text-opacity-80">{{ useOverallHours ? $strings.LabelStatsOverallHours : $strings.LabelStatsOverallDays }}</p>
@@ -29,7 +29,7 @@
</div> </div>
<div class="flex p-2"> <div class="flex p-2">
<span class="material-icons-outlined text-5xl pt-1">insert_drive_file</span> <span class="material-symbols-outlined text-5xl pt-1">insert_drive_file</span>
<div class="px-1"> <div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalSizeNum) }}</p> <p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalSizeNum) }}</p>
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelSize }} ({{ totalSizeMod }})</p> <p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelSize }} ({{ totalSizeMod }})</p>
@@ -37,7 +37,7 @@
</div> </div>
<div class="flex p-2"> <div class="flex p-2">
<span class="material-icons-outlined text-5xl pt-1">audio_file</span> <span class="material-symbols-outlined text-5xl pt-1">audio_file</span>
<div class="px-1"> <div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(numAudioTracks) }}</p> <p class="text-4.5xl leading-none font-bold">{{ $formatNumber(numAudioTracks) }}</p>
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsAudioTracks }}</p> <p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsAudioTracks }}</p>
+16 -14
View File
@@ -73,7 +73,7 @@ export default {
const addIcon = (icon, color, fontSize, x, y) => { const addIcon = (icon, color, fontSize, x, y) => {
ctx.fillStyle = color ctx.fillStyle = color
ctx.font = `${fontSize} Material Icons Outlined` ctx.font = `${fontSize} Material Symbols Outlined`
ctx.fillText(icon, x, y) ctx.fillText(icon, x, y)
} }
@@ -132,6 +132,8 @@ export default {
ctx.restore() ctx.restore()
} }
const twoColumnWidth = 210
ctx.globalAlpha = 1 ctx.globalAlpha = 1
ctx.textBaseline = 'middle' ctx.textBaseline = 'middle'
@@ -150,12 +152,12 @@ export default {
// Top text // Top text
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28) addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
addText(`${this.year} YEAR IN REVIEW`, '18px', 'bold', 'white', '1px', 65, 51) addText(`${this.year} ${this.$strings.StatsYearInReview}`, '18px', 'bold', 'white', '1px', 65, 51,)
// Top left box // Top left box
createRoundedRect(50, 100, 340, 160) createRoundedRect(50, 100, 340, 160)
addText(this.yearStats.numBooksFinished, '64px', 'bold', 'white', '0px', 160, 165) addText(this.yearStats.numBooksFinished, '64px', 'bold', 'white', '0px', 160, 165)
addText('books finished', '28px', 'normal', tanColor, '0px', 160, 210) addText(this.$strings.StatsBooksFinished, '28px', 'normal', tanColor, '0px', 160, 210, twoColumnWidth)
const readIconPath = new Path2D() const readIconPath = new Path2D()
readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 2, d: 2, e: 100, f: 160 }) readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 2, d: 2, e: 100, f: 160 })
ctx.fillStyle = '#ffffff' ctx.fillStyle = '#ffffff'
@@ -164,40 +166,40 @@ export default {
// Box top right // Box top right
createRoundedRect(410, 100, 340, 160) createRoundedRect(410, 100, 340, 160)
addText(this.$elapsedPrettyExtended(this.yearStats.totalListeningTime, true, false), '40px', 'bold', 'white', '0px', 500, 165) addText(this.$elapsedPrettyExtended(this.yearStats.totalListeningTime, true, false), '40px', 'bold', 'white', '0px', 500, 165)
addText('spent listening', '28px', 'normal', tanColor, '0px', 500, 205) addText(this.$strings.StatsSpentListening, '28px', 'normal', tanColor, '0px', 500, 205, twoColumnWidth)
addIcon('watch_later', 'white', '52px', 440, 180) addIcon('watch_later', 'white', '52px', 440, 180)
// Box bottom left // Box bottom left
createRoundedRect(50, 280, 340, 160) createRoundedRect(50, 280, 340, 160)
addText(this.yearStats.totalListeningSessions, '64px', 'bold', 'white', '0px', 160, 345) addText(this.yearStats.totalListeningSessions, '64px', 'bold', 'white', '0px', 160, 345)
addText('sessions', '28px', 'normal', tanColor, '1px', 160, 390) addText(this.$strings.StatsSessions, '28px', 'normal', tanColor, '1px', 160, 390, twoColumnWidth)
addIcon('headphones', 'white', '52px', 95, 360) addIcon('headphones', 'white', '52px', 95, 360)
// Box bottom right // Box bottom right
createRoundedRect(410, 280, 340, 160) createRoundedRect(410, 280, 340, 160)
addText(this.yearStats.numBooksListened, '64px', 'bold', 'white', '0px', 500, 345) addText(this.yearStats.numBooksListened, '64px', 'bold', 'white', '0px', 500, 345)
addText('books listened to', '28px', 'normal', tanColor, '0px', 500, 390) addText(this.$strings.StatsBooksListenedTo, '28px', 'normal', tanColor, '0px', 500, 390, twoColumnWidth)
addIcon('local_library', 'white', '52px', 440, 360) addIcon('local_library', 'white', '52px', 440, 360)
if (!this.variant) { if (!this.variant) {
// Text stats // Text stats
const topNarrator = this.yearStats.mostListenedNarrator const topNarrator = this.yearStats.mostListenedNarrator
if (topNarrator) { if (topNarrator) {
addText('TOP NARRATOR', '24px', 'normal', tanColor, '1px', 70, 520) addText(this.$strings.StatsTopNarrator, '24px', 'normal', tanColor, '1px', 70, 520, 330)
addText(topNarrator.name, '36px', 'bolder', 'white', '0px', 70, 564, 330) addText(topNarrator.name, '36px', 'bolder', 'white', '0px', 70, 564, 330)
addText(this.$elapsedPrettyExtended(topNarrator.time, true, false), '24px', 'lighter', 'white', '1px', 70, 599) addText(this.$elapsedPrettyExtended(topNarrator.time, true, false), '24px', 'lighter', 'white', '1px', 70, 599)
} }
const topGenre = this.yearStats.topGenres[0] const topGenre = this.yearStats.topGenres[0]
if (topGenre) { if (topGenre) {
addText('TOP GENRE', '24px', 'normal', tanColor, '1px', 430, 520) addText(this.$strings.StatsTopGenre, '24px', 'normal', tanColor, '1px', 430, 520, 330)
addText(topGenre.genre, '36px', 'bolder', 'white', '0px', 430, 564, 330) addText(topGenre.genre, '36px', 'bolder', 'white', '0px', 430, 564, 330)
addText(this.$elapsedPrettyExtended(topGenre.time, true, false), '24px', 'lighter', 'white', '1px', 430, 599) addText(this.$elapsedPrettyExtended(topGenre.time, true, false), '24px', 'lighter', 'white', '1px', 430, 599)
} }
const topAuthor = this.yearStats.topAuthors[0] const topAuthor = this.yearStats.topAuthors[0]
if (topAuthor) { if (topAuthor) {
addText('TOP AUTHOR', '24px', 'normal', tanColor, '1px', 70, 670) addText(this.$strings.StatsTopAuthor, '24px', 'normal', tanColor, '1px', 70, 670, 330)
addText(topAuthor.name, '36px', 'bolder', 'white', '0px', 70, 714, 330) addText(topAuthor.name, '36px', 'bolder', 'white', '0px', 70, 714, 330)
addText(this.$elapsedPrettyExtended(topAuthor.time, true, false), '24px', 'lighter', 'white', '1px', 70, 749) addText(this.$elapsedPrettyExtended(topAuthor.time, true, false), '24px', 'lighter', 'white', '1px', 70, 749)
} }
@@ -205,7 +207,7 @@ export default {
if (this.yearStats.mostListenedMonth?.time) { if (this.yearStats.mostListenedMonth?.time) {
const jsdate = new Date(this.year, this.yearStats.mostListenedMonth.month, 1) const jsdate = new Date(this.year, this.yearStats.mostListenedMonth.month, 1)
const monthName = this.$formatJsDate(jsdate, 'LLLL') const monthName = this.$formatJsDate(jsdate, 'LLLL')
addText('TOP MONTH', '24px', 'normal', tanColor, '1px', 430, 670) addText(this.$strings.StatsTopMonth, '24px', 'normal', tanColor, '1px', 430, 670, 330)
addText(monthName, '36px', 'bolder', 'white', '0px', 430, 714, 330) addText(monthName, '36px', 'bolder', 'white', '0px', 430, 714, 330)
addText(this.$elapsedPrettyExtended(this.yearStats.mostListenedMonth.time, true, false), '24px', 'lighter', 'white', '1px', 430, 749) addText(this.$elapsedPrettyExtended(this.yearStats.mostListenedMonth.time, true, false), '24px', 'lighter', 'white', '1px', 430, 749)
} }
@@ -214,7 +216,7 @@ export default {
finishedBookCoverImgs = Object.values(finishedBookCoverImgs) finishedBookCoverImgs = Object.values(finishedBookCoverImgs)
if (finishedBookCoverImgs.length > 0) { if (finishedBookCoverImgs.length > 0) {
ctx.textAlign = 'center' ctx.textAlign = 'center'
addText('Some books finished this year...', '28px', 'normal', tanColor, '0px', canvas.width / 2, 530) addText(this.$strings.StatsBooksFinishedThisYear, '28px', 'normal', tanColor, '0px', canvas.width / 2, 530)
for (let i = 0; i < Math.min(5, finishedBookCoverImgs.length); i++) { for (let i = 0; i < Math.min(5, finishedBookCoverImgs.length); i++) {
let imgToAdd = finishedBookCoverImgs[i] let imgToAdd = finishedBookCoverImgs[i]
@@ -224,14 +226,14 @@ export default {
} else if (this.variant === 2) { } else if (this.variant === 2) {
// Text stats // Text stats
if (this.yearStats.topAuthors.length) { if (this.yearStats.topAuthors.length) {
addText('TOP AUTHORS', '24px', 'normal', tanColor, '1px', 70, 524) addText(this.$strings.StatsTopAuthors, '24px', 'normal', tanColor, '1px', 70, 524)
for (let i = 0; i < this.yearStats.topAuthors.length; i++) { for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 584 + i * 60, 330) addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 584 + i * 60, 330)
} }
} }
if (this.yearStats.topGenres.length) { if (this.yearStats.topGenres.length) {
addText('TOP GENRES', '24px', 'normal', tanColor, '1px', 430, 524) addText(this.$strings.StatsTopGenres, '24px', 'normal', tanColor, '1px', 430, 524)
for (let i = 0; i < this.yearStats.topGenres.length; i++) { for (let i = 0; i < this.yearStats.topGenres.length; i++) {
addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 584 + i * 60, 330) addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 584 + i * 60, 330)
} }
@@ -263,7 +265,7 @@ export default {
} }
}) })
} else { } else {
this.$toast.error('Cannot share natively on this device') this.$toast.error(this.$strings.ToastErrorCannotShare)
} }
}) })
}, },
+11 -16
View File
@@ -2,15 +2,14 @@
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-1 sm:p-4 mb-4"> <div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-1 sm:p-4 mb-4">
<!-- hack to get icon fonts loaded on init --> <!-- hack to get icon fonts loaded on init -->
<div class="h-0 w-0 overflow-hidden opacity-0"> <div class="h-0 w-0 overflow-hidden opacity-0">
<span class="material-icons-outlined">close</span> <span class="material-symbols-outlined">close</span>
<span class="abs-icons icon-audiobookshelf" /> <span class="abs-icons icon-audiobookshelf" />
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<p class="hidden md:block text-xl font-semibold">{{ $getString('HeaderYearReview', [yearInReviewYear]) }}</p> <p class="hidden md:block text-xl font-semibold">{{ $getString('HeaderYearReview', [yearInReviewYear]) }}</p>
<div class="hidden md:block flex-grow" /> <div class="hidden md:block flex-grow" />
<ui-btn class="w-full md:w-auto" @click.stop="clickShowYearInReview">{{ showYearInReview ? $strings.LabelYearReviewHide : <ui-btn class="w-full md:w-auto" @click.stop="clickShowYearInReview">{{ showYearInReview ? $strings.LabelYearReviewHide : $strings.LabelYearReviewShow }}</ui-btn>
$strings.LabelYearReviewShow }}</ui-btn>
</div> </div>
<!-- your year in review --> <!-- your year in review -->
@@ -20,29 +19,26 @@
<div class="flex items-center justify-center mb-2 max-w-[800px] mx-auto"> <div class="flex items-center justify-center mb-2 max-w-[800px] mx-auto">
<!-- previous button --> <!-- previous button -->
<ui-btn small :disabled="!yearInReviewVariant || processingYearInReview" class="inline-flex items-center font-semibold" @click="yearInReviewVariant--"> <ui-btn small :disabled="!yearInReviewVariant || processingYearInReview" class="inline-flex items-center font-semibold" @click="yearInReviewVariant--">
<span class="material-icons text-lg sm:pr-1 py-px sm:py-0">chevron_left</span> <span class="material-symbols text-lg sm:pr-1 py-px sm:py-0">chevron_left</span>
<span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span> <span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span>
</ui-btn> </ui-btn>
<!-- share button --> <!-- share button -->
<ui-btn v-if="showShareButton" small :disabled="processingYearInReview" class="inline-flex sm:hidden items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReview">{{ <ui-btn v-if="showShareButton" small :disabled="processingYearInReview" class="inline-flex sm:hidden items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReview">{{ $strings.ButtonShare }} </ui-btn>
$strings.ButtonShare }}
</ui-btn>
<div class="flex-grow" /> <div class="flex-grow" />
<p class="hidden sm:block text-lg font-semibold">{{ $getString('LabelPersonalYearReview', [yearInReviewVariant + 1]) }} <p class="hidden sm:block text-lg font-semibold">{{ $getString('LabelPersonalYearReview', [yearInReviewVariant + 1]) }}</p>
</p>
<p class="block sm:hidden text-lg font-semibold">{{ yearInReviewVariant + 1 }}</p> <p class="block sm:hidden text-lg font-semibold">{{ yearInReviewVariant + 1 }}</p>
<div class="flex-grow" /> <div class="flex-grow" />
<!-- refresh button --> <!-- refresh button -->
<ui-btn small :disabled="processingYearInReview" class="inline-flex items-center font-semibold mr-1 sm:mr-2" @click="refreshYearInReview"> <ui-btn small :disabled="processingYearInReview" class="inline-flex items-center font-semibold mr-1 sm:mr-2" @click="refreshYearInReview">
<span class="hidden sm:inline-block">{{ $strings.ButtonRefresh }}</span> <span class="hidden sm:inline-block">{{ $strings.ButtonRefresh }}</span>
<span class="material-icons sm:!hidden text-lg py-px">refresh</span> <span class="material-symbols sm:!hidden text-lg py-px">refresh</span>
</ui-btn> </ui-btn>
<!-- next button --> <!-- next button -->
<ui-btn small :disabled="yearInReviewVariant >= 2 || processingYearInReview" class="inline-flex items-center font-semibold" @click="yearInReviewVariant++"> <ui-btn small :disabled="yearInReviewVariant >= 2 || processingYearInReview" class="inline-flex items-center font-semibold" @click="yearInReviewVariant++">
<span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span> <span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span>
<span class="material-icons-outlined text-lg sm:pl-1 py-px sm:py-0">chevron_right</span> <span class="material-symbols-outlined text-lg sm:pl-1 py-px sm:py-0">chevron_right</span>
</ui-btn> </ui-btn>
</div> </div>
<stats-year-in-review ref="yearInReview" :variant="yearInReviewVariant" :year="yearInReviewYear" :processing.sync="processingYearInReview" /> <stats-year-in-review ref="yearInReview" :variant="yearInReviewVariant" :year="yearInReviewYear" :processing.sync="processingYearInReview" />
@@ -59,12 +55,11 @@
<div class="flex items-center justify-center mb-2"> <div class="flex items-center justify-center mb-2">
<!-- previous button --> <!-- previous button -->
<ui-btn small :disabled="!yearInReviewServerVariant || processingYearInReviewServer" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant--"> <ui-btn small :disabled="!yearInReviewServerVariant || processingYearInReviewServer" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant--">
<span class="material-icons text-lg sm:pr-1 py-px sm:py-0">chevron_left</span> <span class="material-symbols text-lg sm:pr-1 py-px sm:py-0">chevron_left</span>
<span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span> <span class="hidden sm:inline-block pr-2">{{ $strings.ButtonPrevious }}</span>
</ui-btn> </ui-btn>
<!-- share button --> <!-- share button -->
<ui-btn v-if="showShareButton" small :disabled="processingYearInReviewServer" class="inline-flex sm:hidden items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReviewServer">{{ $strings.ButtonShare }} <ui-btn v-if="showShareButton" small :disabled="processingYearInReviewServer" class="inline-flex sm:hidden items-center font-semibold ml-1 sm:ml-2" @click="shareYearInReviewServer">{{ $strings.ButtonShare }} </ui-btn>
</ui-btn>
<div class="flex-grow" /> <div class="flex-grow" />
<p class="hidden sm:block text-lg font-semibold">{{ $getString('LabelServerYearReview', [yearInReviewServerVariant + 1]) }}</p> <p class="hidden sm:block text-lg font-semibold">{{ $getString('LabelServerYearReview', [yearInReviewServerVariant + 1]) }}</p>
@@ -74,12 +69,12 @@
<!-- refresh button --> <!-- refresh button -->
<ui-btn small :disabled="processingYearInReviewServer" class="inline-flex items-center font-semibold mr-1 sm:mr-2" @click="refreshYearInReviewServer"> <ui-btn small :disabled="processingYearInReviewServer" class="inline-flex items-center font-semibold mr-1 sm:mr-2" @click="refreshYearInReviewServer">
<span class="hidden sm:inline-block">{{ $strings.ButtonRefresh }}</span> <span class="hidden sm:inline-block">{{ $strings.ButtonRefresh }}</span>
<span class="material-icons sm:!hidden text-lg py-px">refresh</span> <span class="material-symbols sm:!hidden text-lg py-px">refresh</span>
</ui-btn> </ui-btn>
<!-- next button --> <!-- next button -->
<ui-btn small :disabled="yearInReviewServerVariant >= 2 || processingYearInReviewServer" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant++"> <ui-btn small :disabled="yearInReviewServerVariant >= 2 || processingYearInReviewServer" class="inline-flex items-center font-semibold" @click="yearInReviewServerVariant++">
<span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span> <span class="hidden sm:inline-block pl-2">{{ $strings.ButtonNext }}</span>
<span class="material-icons-outlined text-lg sm:pl-1 py-px sm:py-0">chevron_right</span> <span class="material-symbols-outlined text-lg sm:pl-1 py-px sm:py-0">chevron_right</span>
</ui-btn> </ui-btn>
</div> </div>
</div> </div>
+14 -12
View File
@@ -123,6 +123,8 @@ export default {
ctx.restore() ctx.restore()
} }
const threeColumnTextWidth = 200
ctx.globalAlpha = 1 ctx.globalAlpha = 1
ctx.textBaseline = 'middle' ctx.textBaseline = 'middle'
@@ -141,33 +143,33 @@ export default {
// Top text // Top text
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28) addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
addText(`${this.year} YEAR IN REVIEW`, '18px', 'bold', 'white', '1px', 65, 51) addText(`${this.year} ${this.$strings.StatsYearInReview}`, '18px', 'bold', 'white', '1px', 65, 51)
// Top left box // Top left box
createRoundedRect(40, 100, 230, 100) createRoundedRect(40, 100, 230, 100)
ctx.textAlign = 'center' ctx.textAlign = 'center'
addText(this.yearStats.numBooksAdded, '48px', 'bold', 'white', '0px', 155, 140) addText(this.yearStats.numBooksAdded, '48px', 'bold', 'white', '0px', 155, 140)
addText('books added', '18px', 'normal', tanColor, '0px', 155, 170) addText(this.$strings.StatsBooksAdded, '18px', 'normal', tanColor, '0px', 155, 170, threeColumnTextWidth)
// Box top right // Box top right
createRoundedRect(285, 100, 230, 100) createRoundedRect(285, 100, 230, 100)
addText(this.yearStats.numAuthorsAdded, '48px', 'bold', 'white', '0px', 400, 140) addText(this.yearStats.numAuthorsAdded, '48px', 'bold', 'white', '0px', 400, 140)
addText('authors added', '18px', 'normal', tanColor, '0px', 400, 170) addText(this.$strings.StatsAuthorsAdded, '18px', 'normal', tanColor, '0px', 400, 170, threeColumnTextWidth)
// Box bottom left // Box bottom left
createRoundedRect(530, 100, 230, 100) createRoundedRect(530, 100, 230, 100)
addText(this.yearStats.numListeningSessions, '48px', 'bold', 'white', '0px', 645, 140) addText(this.yearStats.numListeningSessions, '48px', 'bold', 'white', '0px', 645, 140)
addText('sessions', '18px', 'normal', tanColor, '1px', 645, 170) addText(this.$strings.StatsSessions, '18px', 'normal', tanColor, '1px', 645, 170, threeColumnTextWidth)
// Text stats // Text stats
if (this.yearStats.totalBooksAddedSize) { if (this.yearStats.totalBooksAddedSize) {
addText('Your book collection grew to...', '24px', 'normal', tanColor, '0px', canvas.width / 2, 260) addText(this.$strings.StatsCollectionGrewTo, '24px', 'normal', tanColor, '0px', canvas.width / 2, 260)
addText(this.$bytesPretty(this.yearStats.totalBooksSize), '36px', 'bolder', 'white', '0px', canvas.width / 2, 300) addText(this.$bytesPretty(this.yearStats.totalBooksSize), '36px', 'bolder', 'white', '0px', canvas.width / 2, 300)
addText('+' + this.$bytesPretty(this.yearStats.totalBooksAddedSize), '20px', 'lighter', 'white', '0px', canvas.width / 2, 330) addText('+' + this.$bytesPretty(this.yearStats.totalBooksAddedSize), '20px', 'lighter', 'white', '0px', canvas.width / 2, 330)
} }
if (this.yearStats.totalBooksAddedDuration) { if (this.yearStats.totalBooksAddedDuration) {
addText('With a total duration of...', '24px', 'normal', tanColor, '0px', canvas.width / 2, 400) addText(this.$strings.StatsTotalDuration, '24px', 'normal', tanColor, '0px', canvas.width / 2, 400)
addText(this.$elapsedPrettyExtended(this.yearStats.totalBooksDuration, true, false), '36px', 'bolder', 'white', '0px', canvas.width / 2, 440) addText(this.$elapsedPrettyExtended(this.yearStats.totalBooksDuration, true, false), '36px', 'bolder', 'white', '0px', canvas.width / 2, 440)
addText('+' + this.$elapsedPrettyExtended(this.yearStats.totalBooksAddedDuration, true, false), '20px', 'lighter', 'white', '0px', canvas.width / 2, 470) addText('+' + this.$elapsedPrettyExtended(this.yearStats.totalBooksAddedDuration, true, false), '20px', 'lighter', 'white', '0px', canvas.width / 2, 470)
} }
@@ -176,7 +178,7 @@ export default {
// Bottom images // Bottom images
imgsToAdd = Object.values(imgsToAdd) imgsToAdd = Object.values(imgsToAdd)
if (imgsToAdd.length > 0) { if (imgsToAdd.length > 0) {
addText('Some additions include...', '24px', 'normal', tanColor, '0px', canvas.width / 2, 540) addText(this.$strings.StatsBooksAdditional, '24px', 'normal', tanColor, '0px', canvas.width / 2, 540)
for (let i = 0; i < Math.min(5, imgsToAdd.length); i++) { for (let i = 0; i < Math.min(5, imgsToAdd.length); i++) {
let imgToAdd = imgsToAdd[i] let imgToAdd = imgsToAdd[i]
@@ -187,14 +189,14 @@ export default {
// Text stats // Text stats
ctx.textAlign = 'left' ctx.textAlign = 'left'
if (this.yearStats.topAuthors.length) { if (this.yearStats.topAuthors.length) {
addText('TOP AUTHORS', '24px', 'normal', tanColor, '1px', 70, 549) addText(this.$strings.StatsTopAuthors, '24px', 'normal', tanColor, '1px', 70, 549, 330)
for (let i = 0; i < this.yearStats.topAuthors.length; i++) { for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330) addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330)
} }
} }
if (this.yearStats.topNarrators.length) { if (this.yearStats.topNarrators.length) {
addText('TOP NARRATORS', '24px', 'normal', tanColor, '1px', 430, 549) addText(this.$strings.StatsTopNarrators, '24px', 'normal', tanColor, '1px', 430, 549)
for (let i = 0; i < this.yearStats.topNarrators.length; i++) { for (let i = 0; i < this.yearStats.topNarrators.length; i++) {
addText(this.yearStats.topNarrators[i].name, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330) addText(this.yearStats.topNarrators[i].name, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330)
} }
@@ -203,14 +205,14 @@ export default {
// Text stats // Text stats
ctx.textAlign = 'left' ctx.textAlign = 'left'
if (this.yearStats.topAuthors.length) { if (this.yearStats.topAuthors.length) {
addText('TOP AUTHORS', '24px', 'normal', tanColor, '1px', 70, 549) addText(this.$strings.StatsTopAuthors, '24px', 'normal', tanColor, '1px', 70, 549, 330)
for (let i = 0; i < this.yearStats.topAuthors.length; i++) { for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330) addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330)
} }
} }
if (this.yearStats.topGenres.length) { if (this.yearStats.topGenres.length) {
addText('TOP GENRES', '24px', 'normal', tanColor, '1px', 430, 549) addText(this.$strings.StatsTopGenres, '24px', 'normal', tanColor, '1px', 430, 549)
for (let i = 0; i < this.yearStats.topGenres.length; i++) { for (let i = 0; i < this.yearStats.topGenres.length; i++) {
addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330) addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330)
} }
@@ -239,7 +241,7 @@ export default {
} }
}) })
} else { } else {
this.$toast.error('Cannot share natively on this device') this.$toast.error(this.$strings.ToastErrorCannotShare)
} }
}) })
}, },
@@ -64,7 +64,7 @@ export default {
const addIcon = (icon, color, fontSize, x, y) => { const addIcon = (icon, color, fontSize, x, y) => {
ctx.fillStyle = color ctx.fillStyle = color
ctx.font = `${fontSize} Material Icons Outlined` ctx.font = `${fontSize} Material Symbols Outlined`
ctx.fillText(icon, x, y) ctx.fillText(icon, x, y)
} }
@@ -113,6 +113,8 @@ export default {
ctx.restore() ctx.restore()
} }
const twoColumnWidth = 180
ctx.globalAlpha = 1 ctx.globalAlpha = 1
ctx.textBaseline = 'middle' ctx.textBaseline = 'middle'
@@ -131,12 +133,12 @@ export default {
// Top text // Top text
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28) addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
addText(`${this.year} YEAR IN REVIEW`, '18px', 'bold', 'white', '1px', 65, 51) addText(`${this.year} ${this.$strings.StatsYearInReview}`, '18px', 'bold', 'white', '1px', 65, 51)
// Top left box // Top left box
createRoundedRect(15, 75, 280, 110) createRoundedRect(15, 75, 280, 110)
addText(this.yearStats.numBooksFinished, '48px', 'bold', 'white', '0px', 105, 120) addText(this.yearStats.numBooksFinished, '48px', 'bold', 'white', '0px', 105, 120)
addText('books finished', '20px', 'normal', tanColor, '0px', 105, 155) addText(this.$strings.StatsBooksFinished, '20px', 'normal', tanColor, '0px', 105, 155, twoColumnWidth)
const readIconPath = new Path2D() const readIconPath = new Path2D()
readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 1.5, d: 1.5, e: 55, f: 115 }) readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 1.5, d: 1.5, e: 55, f: 115 })
ctx.fillStyle = '#ffffff' ctx.fillStyle = '#ffffff'
@@ -144,7 +146,7 @@ export default {
createRoundedRect(305, 75, 280, 110) createRoundedRect(305, 75, 280, 110)
addText(this.yearStats.numBooksListened, '48px', 'bold', 'white', '0px', 400, 120) addText(this.yearStats.numBooksListened, '48px', 'bold', 'white', '0px', 400, 120)
addText('books listened to', '20px', 'normal', tanColor, '0px', 400, 155) addText(this.$strings.StatsBooksListenedTo, '20px', 'normal', tanColor, '0px', 400, 155, twoColumnWidth)
addIcon('local_library', 'white', '42px', 345, 130) addIcon('local_library', 'white', '42px', 345, 130)
this.canvas = canvas this.canvas = canvas
@@ -169,7 +171,7 @@ export default {
} }
}) })
} else { } else {
this.$toast.error('Cannot share natively on this device') this.$toast.error(this.$strings.ToastErrorCannotShare)
} }
}) })
}, },
+3 -3
View File
@@ -23,12 +23,12 @@
<div class="w-full flex flex-row items-center justify-center"> <div class="w-full flex flex-row items-center justify-center">
<ui-btn v-if="backup.serverVersion && backup.key" small color="primary" @click="applyBackup(backup)">{{ $strings.ButtonRestore }}</ui-btn> <ui-btn v-if="backup.serverVersion && backup.key" small color="primary" @click="applyBackup(backup)">{{ $strings.ButtonRestore }}</ui-btn>
<ui-tooltip v-else text="This backup was created with an old version of audiobookshelf no longer supported" direction="bottom" class="mx-2 flex items-center"> <ui-tooltip v-else text="This backup was created with an old version of audiobookshelf no longer supported" direction="bottom" class="mx-2 flex items-center">
<span class="material-icons-outlined text-2xl text-error">error_outline</span> <span class="material-symbols-outlined text-2xl text-error">error_outline</span>
</ui-tooltip> </ui-tooltip>
<button aria-label="Download Backup" class="inline-flex material-icons text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button> <button aria-label="Download Backup" class="inline-flex material-symbols text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button>
<button aria-label="Delete Backup" class="inline-flex material-icons text-xl mx-1 text-white/70 hover:text-error" @click="deleteBackupClick(backup)">delete</button> <button aria-label="Delete Backup" class="inline-flex material-symbols text-xl mx-1 text-white/70 hover:text-error" @click="deleteBackupClick(backup)">delete</button>
</div> </div>
</td> </td>
</tr> </tr>
+1 -1
View File
@@ -6,7 +6,7 @@
<div class="flex-grow" /> <div class="flex-grow" />
<ui-btn v-if="userCanUpdate" small :to="`/audiobook/${libraryItemId}/chapters`" color="primary" class="mr-2" @click="clickEditChapters">{{ $strings.ButtonEditChapters }}</ui-btn> <ui-btn v-if="userCanUpdate" small :to="`/audiobook/${libraryItemId}/chapters`" color="primary" class="mr-2" @click="clickEditChapters">{{ $strings.ButtonEditChapters }}</ui-btn>
<div v-if="!keepOpen" class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="expanded ? 'transform rotate-180' : ''"> <div v-if="!keepOpen" class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="expanded ? 'transform rotate-180' : ''">
<span class="material-icons text-4xl">expand_more</span> <span class="material-symbols text-4xl">expand_more</span>
</div> </div>
</div> </div>
<transition name="slide"> <transition name="slide">
@@ -15,7 +15,7 @@
</td> </td>
<td class="py-0"> <td class="py-0">
<div class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="removeProvider(provider)"> <div class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="removeProvider(provider)">
<button type="button" :aria-label="$strings.ButtonDelete" class="material-icons text-base">delete</button> <button type="button" :aria-label="$strings.ButtonDelete" class="material-symbols text-base">delete</button>
</div> </div>
</td> </td>
</tr> </tr>
+2 -2
View File
@@ -8,7 +8,7 @@
<div class="flex-grow" /> <div class="flex-grow" />
<ui-btn v-if="userIsAdmin" small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="toggleFullPath">{{ $strings.ButtonFullPath }}</ui-btn> <ui-btn v-if="userIsAdmin" small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="toggleFullPath">{{ $strings.ButtonFullPath }}</ui-btn>
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''"> <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''">
<span class="material-icons text-4xl">expand_more</span> <span class="material-symbols text-4xl">expand_more</span>
</div> </div>
</div> </div>
<transition name="slide"> <transition name="slide">
@@ -18,7 +18,7 @@
<th class="text-left px-4">{{ $strings.LabelPath }}</th> <th class="text-left px-4">{{ $strings.LabelPath }}</th>
<th class="text-left w-24 min-w-24">{{ $strings.LabelSize }}</th> <th class="text-left w-24 min-w-24">{{ $strings.LabelSize }}</th>
<th class="text-left px-4 w-24"> <th class="text-left px-4 w-24">
{{ $strings.LabelRead }} <ui-tooltip :text="$strings.LabelReadEbookWithoutProgress" direction="top" class="inline-block"><span class="material-icons-outlined text-sm align-middle">info</span></ui-tooltip> {{ $strings.LabelRead }} <ui-tooltip :text="$strings.LabelReadEbookWithoutProgress" direction="top" class="inline-block"><span class="material-symbols-outlined text-sm align-middle">info</span></ui-tooltip>
</th> </th>
<th v-if="showMoreColumn" class="text-center w-16"></th> <th v-if="showMoreColumn" class="text-center w-16"></th>
</tr> </tr>
@@ -1,7 +1,7 @@
<template> <template>
<tr> <tr>
<td class="px-4"> <td class="px-4">
{{ showFullPath ? file.metadata.path : file.metadata.relPath }} <ui-tooltip :text="$strings.LabelPrimaryEbook" class="inline-block"><span v-if="isPrimary" class="material-icons-outlined text-success align-text-bottom">check_circle</span></ui-tooltip> {{ showFullPath ? file.metadata.path : file.metadata.relPath }} <ui-tooltip :text="$strings.LabelPrimaryEbook" class="inline-block"><span v-if="isPrimary" class="material-symbols-outlined text-success align-text-bottom">check_circle</span></ui-tooltip>
</td> </td>
<td> <td>
{{ $bytesPretty(file.metadata.size) }} {{ $bytesPretty(file.metadata.size) }}
@@ -8,7 +8,7 @@
<div class="flex-grow" /> <div class="flex-grow" />
<ui-btn v-if="userIsAdmin" small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="toggleFullPath">{{ $strings.ButtonFullPath }}</ui-btn> <ui-btn v-if="userIsAdmin" small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="toggleFullPath">{{ $strings.ButtonFullPath }}</ui-btn>
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''"> <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''">
<span class="material-icons text-4xl">expand_more</span> <span class="material-symbols text-4xl">expand_more</span>
</div> </div>
</div> </div>
<transition name="slide"> <transition name="slide">
+1 -1
View File
@@ -11,7 +11,7 @@
<ui-btn small color="primary">{{ $strings.ButtonManageTracks }}</ui-btn> <ui-btn small color="primary">{{ $strings.ButtonManageTracks }}</ui-btn>
</nuxt-link> </nuxt-link>
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''"> <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
<span class="material-icons text-4xl">expand_more</span> <span class="material-symbols text-4xl">expand_more</span>
</div> </div>
</div> </div>
<transition name="slide"> <transition name="slide">
@@ -5,7 +5,7 @@
<span class="bg-black-400 rounded-xl py-0.5 px-2 text-sm font-mono">{{ files.length }}</span> <span class="bg-black-400 rounded-xl py-0.5 px-2 text-sm font-mono">{{ files.length }}</span>
<div class="flex-grow" /> <div class="flex-grow" />
<div class="cursor-pointer h-9 w-9 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="expand ? 'transform rotate-180' : ''"> <div class="cursor-pointer h-9 w-9 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="expand ? 'transform rotate-180' : ''">
<span class="material-icons text-3xl">expand_more</span> <span class="material-symbols text-3xl">expand_more</span>
</div> </div>
</div> </div>
<transition name="slide"> <transition name="slide">
+2 -2
View File
@@ -42,10 +42,10 @@
<div class="w-full flex justify-left"> <div class="w-full flex justify-left">
<!-- Dont show edit for non-root users --> <!-- Dont show edit for non-root users -->
<div v-if="user.type !== 'root' || userIsRoot" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-opacity-100 cursor-pointer" @click.stop="editUser(user)"> <div v-if="user.type !== 'root' || userIsRoot" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-opacity-100 cursor-pointer" @click.stop="editUser(user)">
<button type="button" :aria-label="$getString('ButtonUserEdit', [user.username])" class="material-icons text-base">edit</button> <button type="button" :aria-label="$getString('ButtonUserEdit', [user.username])" class="material-symbols text-base">edit</button>
</div> </div>
<div v-show="user.type !== 'root'" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="deleteUserClick(user)"> <div v-show="user.type !== 'root'" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="deleteUserClick(user)">
<button type="button" :aria-label="$getString('ButtonUserDelete', [user.username])" class="material-icons text-base">delete</button> <button type="button" :aria-label="$getString('ButtonUserDelete', [user.username])" class="material-symbols text-base">delete</button>
</div> </div>
</div> </div>
</td> </td>
@@ -3,7 +3,7 @@
<div v-if="book" class="flex h-18 md:h-[5.5rem]"> <div v-if="book" class="flex h-18 md:h-[5.5rem]">
<div class="w-10 min-w-10 md:w-16 md:max-w-16 h-full"> <div class="w-10 min-w-10 md:w-16 md:max-w-16 h-full">
<div class="flex h-full items-center justify-center"> <div class="flex h-full items-center justify-center">
<span class="material-icons drag-handle text-lg md:text-xl">menu</span> <span class="material-symbols drag-handle text-lg md:text-xl">menu</span>
</div> </div>
</div> </div>
<div class="h-full flex items-center" :style="{ width: coverWidth + 'px', minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }"> <div class="h-full flex items-center" :style="{ width: coverWidth + 'px', minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }">
@@ -11,7 +11,7 @@
<covers-book-cover :library-item="book" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-book-cover :library-item="book" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div class="absolute top-0 left-0 flex items-center justify-center bg-black bg-opacity-50 h-full w-full z-10" v-show="isHovering && showPlayBtn"> <div class="absolute top-0 left-0 flex items-center justify-center bg-black bg-opacity-50 h-full w-full z-10" v-show="isHovering && showPlayBtn">
<div class="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-40 cursor-pointer" @click="playClick"> <div class="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-40 cursor-pointer" @click="playClick">
<span class="material-icons text-2xl">play_arrow</span> <span class="material-symbols fill text-2xl">play_arrow</span>
</div> </div>
</div> </div>
</div> </div>
@@ -16,14 +16,14 @@
<ui-context-menu-dropdown v-if="!isScanning && !isDeleting" :items="contextMenuItems" :icon-class="`text-1.5xl text-gray-${isHovering ? 50 : 400}`" class="!hidden md:!block" @action="contextMenuAction" /> <ui-context-menu-dropdown v-if="!isScanning && !isDeleting" :items="contextMenuItems" :icon-class="`text-1.5xl text-gray-${isHovering ? 50 : 400}`" class="!hidden md:!block" @action="contextMenuAction" />
<!-- Mobile context menu icon --> <!-- Mobile context menu icon -->
<span v-if="!isScanning && !isDeleting" class="!block md:!hidden material-icons text-xl text-gray-300 ml-3 cursor-pointer" @click.stop="showMenu">more_vert</span> <span v-if="!isScanning && !isDeleting" class="!block md:!hidden material-symbols text-xl text-gray-300 ml-3 cursor-pointer" @click.stop="showMenu">more_vert</span>
<div v-show="isDeleting" class="text-xl text-gray-300 ml-3 animate-spin"> <div v-show="isDeleting" class="text-xl text-gray-300 ml-3 animate-spin">
<svg viewBox="0 0 24 24" class="w-6 h-6"> <svg viewBox="0 0 24 24" class="w-6 h-6">
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" /> <path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
</svg> </svg>
</div> </div>
<span class="material-icons drag-handle text-xl text-gray-400 hover:text-gray-50 ml-2 md:ml-4">reorder</span> <span class="material-symbols drag-handle text-xl text-gray-400 hover:text-gray-50 ml-2 md:ml-4">reorder</span>
<!-- For mobile --> <!-- For mobile -->
<modals-dialog v-model="showMobileMenu" :title="menuTitle" :items="contextMenuItems" @action="contextMenuAction" /> <modals-dialog v-model="showMobileMenu" :title="menuTitle" :items="contextMenuItems" @action="contextMenuAction" />
@@ -3,14 +3,14 @@
<div v-if="item" class="flex h-16 md:h-20"> <div v-if="item" class="flex h-16 md:h-20">
<div class="w-10 min-w-10 md:w-16 md:max-w-16 h-full"> <div class="w-10 min-w-10 md:w-16 md:max-w-16 h-full">
<div class="flex h-full items-center justify-center"> <div class="flex h-full items-center justify-center">
<span class="material-icons drag-handle text-lg md:text-xl">menu</span> <span class="material-symbols drag-handle text-lg md:text-xl">menu</span>
</div> </div>
</div> </div>
<div class="h-full relative flex items-center" :style="{ width: coverWidth + 'px', minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }"> <div class="h-full relative flex items-center" :style="{ width: coverWidth + 'px', minWidth: coverWidth + 'px', maxWidth: coverWidth + 'px' }">
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div class="absolute top-0 left-0 bg-black bg-opacity-50 flex items-center justify-center h-full w-full z-10" v-show="isHovering && showPlayBtn"> <div class="absolute top-0 left-0 bg-black bg-opacity-50 flex items-center justify-center h-full w-full z-10" v-show="isHovering && showPlayBtn">
<div class="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-40 cursor-pointer" @click="playClick"> <div class="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-40 cursor-pointer" @click="playClick">
<span class="material-icons text-2xl">play_arrow</span> <span class="material-symbols fill text-2xl">play_arrow</span>
</div> </div>
</div> </div>
</div> </div>
@@ -21,7 +21,7 @@
<div class="flex items-center pt-2"> <div class="flex items-center pt-2">
<button class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick"> <button class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer focus:outline-none" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click.stop="playClick">
<span class="material-icons text-2xl" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span> <span class="material-symbols fill text-2xl" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
<p class="pl-2 pr-1 text-sm font-semibold">{{ timeRemaining }}</p> <p class="pl-2 pr-1 text-sm font-semibold">{{ timeRemaining }}</p>
</button> </button>
+4 -2
View File
@@ -10,7 +10,8 @@
<button v-else class="abs-btn outline-none rounded-md shadow-md relative border border-gray-600" :disabled="disabled || loading" :type="type" :class="classList" @mousedown.prevent @click="click"> <button v-else class="abs-btn outline-none rounded-md shadow-md relative border border-gray-600" :disabled="disabled || loading" :type="type" :class="classList" @mousedown.prevent @click="click">
<slot /> <slot />
<div v-if="loading" class="text-white absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100"> <div v-if="loading" class="text-white absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100">
<svg class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24"> <span v-if="progress">{{ progress }}</span>
<svg v-else class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" /> <path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
</svg> </svg>
</div> </div>
@@ -33,7 +34,8 @@ export default {
paddingY: Number, paddingY: Number,
small: Boolean, small: Boolean,
loading: Boolean, loading: Boolean,
disabled: Boolean disabled: Boolean,
progress: String
}, },
data() { data() {
return {} return {}
+1 -1
View File
@@ -2,7 +2,7 @@
<label class="flex justify-start items-center" :class="!disabled ? 'cursor-pointer' : ''"> <label class="flex justify-start items-center" :class="!disabled ? 'cursor-pointer' : ''">
<div class="border-2 rounded flex flex-shrink-0 justify-center items-center" :class="wrapperClass"> <div class="border-2 rounded flex flex-shrink-0 justify-center items-center" :class="wrapperClass">
<input v-model="selected" :disabled="disabled" type="checkbox" class="opacity-0 absolute" :class="!disabled ? 'cursor-pointer' : ''" /> <input v-model="selected" :disabled="disabled" type="checkbox" class="opacity-0 absolute" :class="!disabled ? 'cursor-pointer' : ''" />
<span v-if="partial" class="material-icons text-base leading-none text-gray-400">remove</span> <span v-if="partial" class="material-symbols text-base leading-none text-gray-400">remove</span>
<svg v-else-if="selected" class="fill-current pointer-events-none" :class="svgClass" viewBox="0 0 20 20"><path d="M0 11l2-2 5 5L18 3l2 2L7 18z" /></svg> <svg v-else-if="selected" class="fill-current pointer-events-none" :class="svgClass" viewBox="0 0 20 20"><path d="M0 11l2-2 5 5L18 3l2 2L7 18z" /></svg>
</div> </div>
<div v-if="label" class="select-none" :class="[labelClassname, disabled ? 'text-gray-400' : 'text-gray-100']">{{ label }}</div> <div v-if="label" class="select-none" :class="[labelClassname, disabled ? 'text-gray-400' : 'text-gray-100']">{{ label }}</div>
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="relative h-9 w-9" v-click-outside="clickOutsideObj"> <div class="relative h-9 w-9" v-click-outside="clickOutsideObj">
<slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu" :processing="processing"> <slot :disabled="disabled" :showMenu="showMenu" :clickShowMenu="clickShowMenu" :processing="processing">
<button v-if="!processing" type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu"> <button v-if="!processing" type="button" :disabled="disabled" class="relative h-full w-full flex items-center justify-center shadow-sm pl-3 pr-3 text-left focus:outline-none cursor-pointer text-gray-100 hover:text-gray-200 rounded-full hover:bg-white/5" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
<span class="material-icons" :class="iconClass">more_vert</span> <span class="material-symbols text-2xl" :class="iconClass">more_vert</span>
</button> </button>
<div v-else class="h-full w-full flex items-center justify-center"> <div v-else class="h-full w-full flex items-center justify-center">
<widgets-loading-spinner /> <widgets-loading-spinner />
+1 -1
View File
@@ -8,7 +8,7 @@
<span v-if="selectedSubtext" class="font-normal block truncate font-sans text-sm text-gray-400">{{ selectedSubtext }}</span> <span v-if="selectedSubtext" class="font-normal block truncate font-sans text-sm text-gray-400">{{ selectedSubtext }}</span>
</span> </span>
<span class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> <span class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<span class="material-icons text-2xl">expand_more</span> <span class="material-symbols text-2xl">expand_more</span>
</span> </span>
</button> </button>
+1 -1
View File
@@ -5,7 +5,7 @@
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" /> <path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
</svg> </svg>
</div> </div>
<span v-else :class="outlined ? 'material-icons-outlined' : 'material-icons'" :style="{ fontSize }">{{ icon }}</span> <span v-else :class="outlined ? 'material-symbols-outlined' : 'material-symbols'" :style="{ fontSize }">{{ icon }}</span>
</button> </button>
</template> </template>
+1 -1
View File
@@ -15,7 +15,7 @@
<span class="font-normal ml-3 block truncate">{{ item }}</span> <span class="font-normal ml-3 block truncate">{{ item }}</span>
</div> </div>
<span v-if="input === item" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4"> <span v-if="input === item" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
<span class="material-icons text-xl">checkmark</span> <span class="material-symbols text-xl">check</span>
</span> </span>
</li> </li>
</template> </template>
+4 -3
View File
@@ -92,12 +92,13 @@ export default {
if (this.$route.name.startsWith('config')) { if (this.$route.name.startsWith('config')) {
// No need to refresh // No need to refresh
} else if (this.$route.name.startsWith('library') && this.$route.name !== 'library-library-series-id') {
const newRoute = this.$route.path.replace(currLibraryId, library.id)
this.$router.push(newRoute)
} else if (this.$route.name === 'library-library-series-id' && library.mediaType === 'book') { } else if (this.$route.name === 'library-library-series-id' && library.mediaType === 'book') {
// For series item page redirect to root series page // For series item page redirect to root series page
this.$router.push(`/library/${library.id}/bookshelf/series`) this.$router.push(`/library/${library.id}/bookshelf/series`)
} else if (this.$route.name === 'library-library-search') {
this.$router.push(this.$route.fullPath.replace(currLibraryId, library.id))
} else if (this.$route.name.startsWith('library')) {
this.$router.push(this.$route.path.replace(currLibraryId, library.id))
} else { } else {
this.$router.push(`/library/${library.id}`) this.$router.push(`/library/${library.id}`)
} }
+3 -3
View File
@@ -6,8 +6,8 @@
<div ref="inputWrapper" style="min-height: 36px" class="flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-1" :class="wrapperClass" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent> <div ref="inputWrapper" style="min-height: 36px" class="flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-1" :class="wrapperClass" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
<div v-for="item in selected" :key="item" class="rounded-full px-2 py-1 mx-0.5 my-0.5 text-xs bg-bg flex flex-nowrap break-all items-center relative"> <div v-for="item in selected" :key="item" class="rounded-full px-2 py-1 mx-0.5 my-0.5 text-xs bg-bg flex flex-nowrap break-all items-center relative">
<div v-if="!disabled" class="w-full h-full rounded-full absolute top-0 left-0 px-1 bg-bg bg-opacity-75 flex items-center justify-end opacity-0 hover:opacity-100"> <div v-if="!disabled" class="w-full h-full rounded-full absolute top-0 left-0 px-1 bg-bg bg-opacity-75 flex items-center justify-end opacity-0 hover:opacity-100">
<span v-if="showEdit" class="material-icons text-white hover:text-warning cursor-pointer" style="font-size: 1.1rem" @click.stop="editItem(item)">edit</span> <span v-if="showEdit" class="material-symbols text-white hover:text-warning cursor-pointer" style="font-size: 1.1rem" @click.stop="editItem(item)">edit</span>
<span class="material-icons text-white hover:text-error cursor-pointer" style="font-size: 1.1rem" @click.stop="removeItem(item)">close</span> <span class="material-symbols text-white hover:text-error cursor-pointer" style="font-size: 1.1rem" @click.stop="removeItem(item)">close</span>
</div> </div>
{{ item }} {{ item }}
</div> </div>
@@ -22,7 +22,7 @@
<span class="font-normal ml-3 block truncate">{{ item }}</span> <span class="font-normal ml-3 block truncate">{{ item }}</span>
</div> </div>
<span v-if="selected.includes(item)" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4"> <span v-if="selected.includes(item)" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
<span class="material-icons text-xl">checkmark</span> <span class="material-symbols text-xl">check</span>
</span> </span>
</li> </li>
</template> </template>

Some files were not shown because too many files have changed in this diff Show More