+
+
0:00
-
{{ currentChapterTitle }}
-
{{ timeRemainingPretty }}
-
@@ -151,8 +148,10 @@ export default {
useTotalTrack: true,
lockUi: false,
isLoading: false,
- touchTrackStart: false,
- dragPercent: 0,
+ isDraggingCursor: false,
+ draggingTouchStartX: 0,
+ draggingTouchStartTime: 0,
+ draggingCurrentTime: 0,
syncStatus: 0,
showMoreMenuDialog: false,
coverRgb: 'rgb(55, 56, 56)',
@@ -287,6 +286,7 @@ export default {
return this.playMethod == this.$constants.PlayMethod.DIRECTPLAY
},
title() {
+ if (this.currentChapterTitle && this.showFullscreen) return this.currentChapterTitle
if (this.playbackSession) return this.playbackSession.displayTitle
return this.mediaMetadata ? this.mediaMetadata.title : 'Title'
},
@@ -318,17 +318,20 @@ export default {
return this.$secondsToTimestamp(this.totalDuration)
},
currentTimePretty() {
- return this.$secondsToTimestamp(this.currentTime / this.currentPlaybackRate)
+ let currentTimeToUse = this.isDraggingCursor ? this.draggingCurrentTime : this.currentTime
+ return this.$secondsToTimestamp(currentTimeToUse / this.currentPlaybackRate)
},
timeRemaining() {
+ let currentTimeToUse = this.isDraggingCursor ? this.draggingCurrentTime : this.currentTime
if (this.useChapterTrack && this.currentChapter) {
- var currChapTime = this.currentTime - this.currentChapter.start
+ var currChapTime = currentTimeToUse - this.currentChapter.start
return (this.currentChapterDuration - currChapTime) / this.currentPlaybackRate
}
return this.totalTimeRemaining
},
totalTimeRemaining() {
- return (this.totalDuration - this.currentTime) / this.currentPlaybackRate
+ let currentTimeToUse = this.isDraggingCursor ? this.draggingCurrentTime : this.currentTime
+ return (this.totalDuration - currentTimeToUse) / this.currentPlaybackRate
},
totalTimeRemainingPretty() {
if (this.totalTimeRemaining < 0) {
@@ -342,10 +345,6 @@ export default {
}
return '-' + this.$secondsToTimestamp(this.timeRemaining)
},
- timeLeftInChapter() {
- if (!this.currentChapter) return 0
- return this.currentChapter.end - this.currentTime
- },
sleepTimeRemainingPretty() {
if (!this.sleepTimeRemaining) return '0s'
var secondsRemaining = Math.round(this.sleepTimeRemaining)
@@ -392,11 +391,6 @@ export default {
this.showFullscreen = false
}
},
- async touchstartTrack(e) {
- await this.$hapticsImpact()
- if (!e || !e.touches || !this.$refs.track || !this.showFullscreen || this.lockUi) return
- this.touchTrackStart = true
- },
async selectChapter(chapter) {
await this.$hapticsImpact()
this.seek(chapter.start)
@@ -450,7 +444,6 @@ export default {
this.$emit('showSleepTimer')
},
async setPlaybackSpeed(speed) {
- await this.$hapticsImpact()
console.log(`[AudioPlayer] Set Playback Rate: ${speed}`)
this.currentPlaybackRate = speed
this.updateTimestamp()
@@ -509,12 +502,13 @@ export default {
console.error('No timestamp el')
return
}
- let currentTime = this.currentTime / this.currentPlaybackRate
+
+ let currentTime = this.isDraggingCursor ? this.draggingCurrentTime : this.currentTime
if (this.useChapterTrack && this.currentChapter) {
- const currChapTime = Math.max(0, this.currentTime - this.currentChapter.start)
- currentTime = currChapTime / this.currentPlaybackRate
+ currentTime = Math.max(0, currentTime - this.currentChapter.start)
}
- ts.innerText = this.$secondsToTimestamp(currentTime)
+
+ ts.innerText = this.$secondsToTimestamp(currentTime / this.currentPlaybackRate)
},
timeupdate() {
if (!this.$refs.playedTrack) {
@@ -536,22 +530,24 @@ export default {
},
updateTrack() {
// Update progress track UI
- let percentDone = this.currentTime / this.totalDuration
+ let currentTimeToUse = this.isDraggingCursor ? this.draggingCurrentTime : this.currentTime
+ let percentDone = currentTimeToUse / this.totalDuration
const totalPercentDone = percentDone
let bufferedPercent = this.bufferedTime / this.totalDuration
const totalBufferedPercent = bufferedPercent
if (this.useChapterTrack && this.currentChapter) {
- const currChapTime = this.currentTime - this.currentChapter.start
+ const currChapTime = currentTimeToUse - this.currentChapter.start
percentDone = currChapTime / this.currentChapterDuration
bufferedPercent = Math.max(0, Math.min(1, (this.bufferedTime - this.currentChapter.start) / this.currentChapterDuration))
}
+
const ptWidth = Math.round(percentDone * this.trackWidth)
this.$refs.playedTrack.style.width = ptWidth + 'px'
this.$refs.bufferedTrack.style.width = Math.round(bufferedPercent * this.trackWidth) + 'px'
if (this.$refs.trackCursor) {
- this.$refs.trackCursor.style.left = ptWidth - 8 + 'px'
+ this.$refs.trackCursor.style.left = ptWidth - 7 + 'px'
}
if (this.useChapterTrack) {
@@ -580,27 +576,15 @@ export default {
this.$refs.playedTrack.classList.add('bg-yellow-300')
}
},
- clickTrack(e) {
- if (this.isLoading || this.lockUi) return
- if (!this.showFullscreen) {
- // Track not clickable on mini-player
- return
- }
- if (e) e.stopPropagation()
+ async touchstartCursor(e) {
+ if (!e || !e.touches || !this.$refs.track || !this.showFullscreen || this.lockUi) return
- var offsetX = e.offsetX
- var perc = offsetX / this.trackWidth
- var time = 0
- if (this.useChapterTrack && this.currentChapter) {
- time = perc * this.currentChapterDuration + this.currentChapter.start
- } else {
- time = perc * this.totalDuration
- }
- if (isNaN(time) || time === null) {
- console.error('Invalid time', perc, time)
- return
- }
- this.seek(time)
+ await this.$hapticsImpact()
+ this.isDraggingCursor = true
+ this.draggingTouchStartX = e.touches[0].pageX
+ this.draggingTouchStartTime = this.currentTime
+ this.draggingCurrentTime = this.currentTime
+ this.updateTrack()
},
async playPauseClick() {
await this.$hapticsImpact()
@@ -653,24 +637,11 @@ export default {
touchend(e) {
if (!e.changedTouches) return
- if (this.touchTrackStart) {
- var touch = e.changedTouches[0]
- const touchOnTrackPos = touch.pageX - 12
- const dragPercent = Math.max(0, Math.min(1, touchOnTrackPos / this.trackWidth))
-
- var seekToTime = 0
- if (this.useChapterTrack && this.currentChapter) {
- const currChapTime = dragPercent * this.currentChapterDuration
- seekToTime = this.currentChapter.start + currChapTime
- } else {
- seekToTime = dragPercent * this.totalDuration
+ if (this.isDraggingCursor) {
+ if (this.draggingCurrentTime !== this.currentTime) {
+ this.seek(this.draggingCurrentTime)
}
- this.seek(seekToTime)
-
- if (this.$refs.draggingTrack) {
- this.$refs.draggingTrack.style.width = '0px'
- }
- this.touchTrackStart = false
+ this.isDraggingCursor = false
} else if (this.showFullscreen) {
this.touchEndY = e.changedTouches[0].screenY
var touchDuration = Date.now() - this.touchStartTime
@@ -682,29 +653,24 @@ export default {
}
},
touchmove(e) {
- if (!this.touchTrackStart) return
+ if (!this.isDraggingCursor || !e.touches) return
- var touch = e.touches[0]
- const touchOnTrackPos = touch.pageX - 12
- const dragPercent = Math.max(0, Math.min(1, touchOnTrackPos / this.trackWidth))
- this.dragPercent = dragPercent
-
- if (this.$refs.draggingTrack) {
- this.$refs.draggingTrack.style.width = this.dragPercent * this.trackWidth + 'px'
+ const distanceMoved = e.touches[0].pageX - this.draggingTouchStartX
+ let duration = this.totalDuration
+ let minTime = 0
+ let maxTime = duration
+ if (this.useChapterTrack && this.currentChapter) {
+ duration = this.currentChapterDuration
+ minTime = this.currentChapter.start
+ maxTime = minTime + duration
}
- var ts = this.$refs.currentTimestamp
- if (ts) {
- var currTimeStr = ''
- if (this.useChapterTrack && this.currentChapter) {
- const currChapTime = dragPercent * this.currentChapterDuration
- currTimeStr = this.$secondsToTimestamp(currChapTime)
- } else {
- const dragTime = dragPercent * this.totalDuration
- currTimeStr = this.$secondsToTimestamp(dragTime)
- }
- ts.innerText = currTimeStr
- }
+ const timePerPixel = duration / this.trackWidth
+ const newTime = this.draggingTouchStartTime + timePerPixel * distanceMoved
+ this.draggingCurrentTime = Math.min(maxTime, Math.max(minTime, newTime))
+
+ this.updateTimestamp()
+ this.updateTrack()
},
async clickMenuAction(action) {
await this.$hapticsImpact()
@@ -850,7 +816,7 @@ export default {
document.documentElement.style.setProperty('--cover-image-height', coverHeight + 'px')
document.documentElement.style.setProperty('--cover-image-width-collapsed', coverImageWidthCollapsed + 'px')
document.documentElement.style.setProperty('--cover-image-height-collapsed', 46 + 'px')
- document.documentElement.style.setProperty('--title-author-left-offset-collapsed', 24 + coverImageWidthCollapsed + 'px')
+ document.documentElement.style.setProperty('--title-author-left-offset-collapsed', 30 + coverImageWidthCollapsed + 'px')
},
minimizePlayerEvt() {
this.collapseFullscreen()
@@ -917,7 +883,7 @@ export default {
--cover-image-height: 0px;
--cover-image-width-collapsed: 46px;
--cover-image-height-collapsed: 46px;
- --title-author-left-offset-collapsed: 70px;
+ --title-author-left-offset-collapsed: 80px;
}
.playerContainer {
@@ -944,12 +910,14 @@ export default {
.cover-wrapper {
bottom: 68px;
- left: 12px;
+ left: 24px;
height: var(--cover-image-height-collapsed);
width: var(--cover-image-width-collapsed);
transition: all 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
transition-property: left, bottom, width, height;
transform-origin: left bottom;
+ border-radius: 3px;
+ overflow: hidden;
}
.total-track {
@@ -990,19 +958,17 @@ export default {
pointer-events: auto;
}
.fullscreen .title-author-texts .title-text {
- font-size: clamp(0.8rem, calc(var(--cover-image-height) / 260 * 20), 1.5rem);
+ font-size: clamp(0.8rem, calc(var(--cover-image-height) / 260 * 20), 1.3rem);
}
.fullscreen .title-author-texts .author-text {
- font-size: clamp(0.6rem, calc(var(--cover-image-height) / 260 * 16), 1.1rem);
+ font-size: clamp(0.6rem, calc(var(--cover-image-height) / 260 * 16), 1rem);
}
#playerControls {
transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1);
transition-property: width, bottom;
- height: 48px;
- width: 140px;
- padding-left: 12px;
- padding-right: 12px;
+ width: 128px;
+ padding-right: 24px;
bottom: 70px;
}
#playerControls .jump-icon {
@@ -1020,7 +986,7 @@ export default {
width: 40px;
min-width: 40px;
min-height: 40px;
- margin: 0px 14px;
+ margin: 0px 7px;
}
#playerControls .play-btn .material-icons {
transition: all 0.15s cubic-bezier(0.39, 0.575, 0.565, 1);
@@ -1035,18 +1001,21 @@ export default {
width: var(--cover-image-width);
left: calc(50% - (calc(var(--cover-image-width)) / 2));
bottom: calc(50% + 120px - (calc(var(--cover-image-height)) / 2));
+ border-radius: 16px;
+ overflow: hidden;
}
.fullscreen #playerControls {
width: 100%;
- bottom: 94px;
+ padding-left: 24px;
+ padding-right: 24px;
+ bottom: 78px;
+ left: 0;
}
.fullscreen #playerControls .jump-icon {
- margin: 0px 18px;
font-size: 2.4rem;
}
.fullscreen #playerControls .next-icon {
- margin: 0px 20px;
font-size: 2rem;
}
.fullscreen #playerControls .play-btn {
@@ -1054,7 +1023,6 @@ export default {
width: 65px;
min-width: 65px;
min-height: 65px;
- margin: 0px 26px;
}
.fullscreen #playerControls .play-btn .material-icons {
font-size: 2.1rem;
diff --git a/components/bookshelf/LazyBookshelf.vue b/components/bookshelf/LazyBookshelf.vue
index 2eede182..4309238a 100644
--- a/components/bookshelf/LazyBookshelf.vue
+++ b/components/bookshelf/LazyBookshelf.vue
@@ -50,6 +50,9 @@ export default {
watch: {
showBookshelfListView(newVal) {
this.resetEntities()
+ },
+ seriesId() {
+ this.resetEntities()
}
},
computed: {
@@ -85,6 +88,12 @@ export default {
filterBy() {
return this.$store.getters['user/getUserSetting']('mobileFilterBy')
},
+ collapseSeries() {
+ return this.$store.getters['user/getUserSetting']('collapseSeries')
+ },
+ collapseBookSeries() {
+ return this.$store.getters['user/getUserSetting']('collapseBookSeries')
+ },
isCoverSquareAspectRatio() {
return this.bookCoverAspectRatio === 1
},
@@ -356,6 +365,9 @@ export default {
let searchParams = new URLSearchParams()
if (this.page === 'series-books') {
searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
+ if (this.collapseBookSeries) {
+ searchParams.set('collapseseries', 1)
+ }
} else {
if (this.filterBy && this.filterBy !== 'all') {
searchParams.set('filter', this.filterBy)
diff --git a/components/cards/LazyBookCard.vue b/components/cards/LazyBookCard.vue
index 0ff4f236..65b763d0 100644
--- a/components/cards/LazyBookCard.vue
+++ b/components/cards/LazyBookCard.vue
@@ -10,11 +10,16 @@
{{ displayTitle }}
-
{{ displayAuthor || ' ' }}
+
{{ displayLineTwo || ' ' }}
{{ displaySortLine }}
+
#{{ seriesSequenceList }}
+
@@ -226,22 +231,36 @@ export default {
// Only added to item object when collapseSeries is enabled
return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
},
- displayTitle() {
- if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) {
- return this.title.substr(4) + ', The'
- }
- return this.title
+ seriesSequenceList() {
+ return this.collapsedSeries ? this.collapsedSeries.seriesSequenceList : null
},
- displayAuthor() {
+ libraryItemIdsInSeries() {
+ // Only added to item object when collapseSeries is enabled
+ return this.collapsedSeries ? this.collapsedSeries.libraryItemIds || [] : []
+ },
+ displayTitle() {
+ if (this.recentEpisode) return this.recentEpisode.title
+
+ const ignorePrefix = this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix
+ if (this.collapsedSeries) return ignorePrefix ? this.collapsedSeries.nameIgnorePrefix : this.collapsedSeries.name
+ return ignorePrefix ? this.mediaMetadata.titleIgnorePrefix : this.title
+ },
+ displayLineTwo() {
+ if (this.recentEpisode) return this.title
+ if (this.collapsedSeries) return ''
+ if (this.isPodcast) return this.author
+
if (this.orderBy === 'media.metadata.authorNameLF') return this.authorLF
return this.author
},
displaySortLine() {
+ if (this.collapsedSeries) return null
if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._libraryItem.mtimeMs)
if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._libraryItem.birthtimeMs)
if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._libraryItem.addedAt)
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
+ if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
return null
},
episodeProgress() {
@@ -434,7 +453,7 @@ export default {
const router = this.$router || this.$nuxt.$router
if (router) {
if (this.recentEpisode) router.push(`/item/${this.libraryItemId}/${this.recentEpisode.id}`)
- else if (this.collapsedSeries) router.push(`/library/${this.libraryId}/series/${this.collapsedSeries.id}`)
+ else if (this.collapsedSeries) router.push(`/bookshelf/series/${this.collapsedSeries.id}`)
else router.push(`/item/${this.libraryItemId}`)
}
}
diff --git a/components/home/BookshelfNavBar.vue b/components/home/BookshelfNavBar.vue
index 2508120b..06294c1e 100644
--- a/components/home/BookshelfNavBar.vue
+++ b/components/home/BookshelfNavBar.vue
@@ -101,15 +101,15 @@ export default {
icon: 'collections_bookmark',
iconClass: 'text-xl',
text: 'Collections'
+ },
+ {
+ to: '/bookshelf/authors',
+ routeName: 'bookshelf-authors',
+ iconPack: 'abs-icons',
+ icon: 'authors',
+ iconClass: 'text-2xl',
+ text: 'Authors'
}
- // {
- // to: '/bookshelf/authors',
- // routeName: 'bookshelf-authors',
- // iconPack: 'abs-icons',
- // icon: 'authors',
- // iconClass: 'text-2xl pb-px',
- // text: 'Authors'
- // }
]
}
diff --git a/components/home/BookshelfToolbar.vue b/components/home/BookshelfToolbar.vue
index 148bc71a..c6b09b4e 100644
--- a/components/home/BookshelfToolbar.vue
+++ b/components/home/BookshelfToolbar.vue
@@ -2,11 +2,8 @@
+
@@ -31,7 +30,8 @@ export default {
showSortModal: false,
showFilterModal: false,
settings: {},
- totalEntities: 0
+ totalEntities: 0,
+ showMoreMenuDialog: false
}
},
computed: {
@@ -44,6 +44,12 @@ export default {
this.$store.commit('globals/setBookshelfListView', val)
}
},
+ currentLibraryMediaType() {
+ return this.$store.getters['libraries/getCurrentLibraryMediaType']
+ },
+ isBookLibrary() {
+ return this.currentLibraryMediaType === 'book'
+ },
hasFilters() {
return this.$store.getters['user/getUserSetting']('mobileFilterBy') !== 'all'
},
@@ -66,6 +72,8 @@ export default {
return 'Collections'
} else if (this.page === 'playlists') {
return 'Playlists'
+ } else if (this.page === 'authors') {
+ return 'Authors'
}
return ''
},
@@ -77,9 +85,40 @@ export default {
},
isPodcast() {
return this.$store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
+ },
+ menuItems() {
+ if (!this.isBookLibrary) return []
+
+ if (this.seriesBookPage) {
+ return [
+ {
+ text: 'Collapse Sub-Series',
+ value: 'collapse_subseries',
+ icon: this.settings.collapseBookSeries ? 'check_box' : 'check_box_outline_blank'
+ }
+ ]
+ } else {
+ return [
+ {
+ text: 'Collapse Series',
+ value: 'collapse_series',
+ icon: this.settings.collapseSeries ? 'check_box' : 'check_box_outline_blank'
+ }
+ ]
+ }
}
},
methods: {
+ clickMenuAction(action) {
+ this.showMoreMenuDialog = false
+ if (action === 'collapse_series') {
+ this.settings.collapseSeries = !this.settings.collapseSeries
+ this.saveSettings()
+ } else if (action === 'collapse_subseries') {
+ this.settings.collapseBookSeries = !this.settings.collapseBookSeries
+ this.saveSettings()
+ }
+ },
updateOrder() {
this.saveSettings()
},
diff --git a/components/modals/BookmarksModal.vue b/components/modals/BookmarksModal.vue
index 5c7fc805..675758cf 100644
--- a/components/modals/BookmarksModal.vue
+++ b/components/modals/BookmarksModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/ChaptersModal.vue b/components/modals/ChaptersModal.vue
index 1e4ca318..843e11fd 100644
--- a/components/modals/ChaptersModal.vue
+++ b/components/modals/ChaptersModal.vue
@@ -1,7 +1,7 @@
-
+
Current: {{ currentChapterTitle }}
diff --git a/components/modals/Dialog.vue b/components/modals/Dialog.vue
index 99c7e2f0..eb1d1209 100644
--- a/components/modals/Dialog.vue
+++ b/components/modals/Dialog.vue
@@ -1,8 +1,8 @@
-
-
{{ title }}
+
diff --git a/components/modals/FilterModal.vue b/components/modals/FilterModal.vue
index 114f86a9..9544f6f1 100644
--- a/components/modals/FilterModal.vue
+++ b/components/modals/FilterModal.vue
@@ -1,7 +1,7 @@
-
+
Clear
diff --git a/components/modals/ItemDetailsModal.vue b/components/modals/ItemDetailsModal.vue
index fa471ceb..77813724 100644
--- a/components/modals/ItemDetailsModal.vue
+++ b/components/modals/ItemDetailsModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/LibrariesModal.vue b/components/modals/LibrariesModal.vue
index 390bca17..83a23f12 100644
--- a/components/modals/LibrariesModal.vue
+++ b/components/modals/LibrariesModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/Modal.vue b/components/modals/Modal.vue
index fbc56914..09a67d72 100644
--- a/components/modals/Modal.vue
+++ b/components/modals/Modal.vue
@@ -2,7 +2,7 @@
-
+
close
@@ -70,9 +70,9 @@ export default {
}
},
methods: {
- clickBg(vm, ev) {
+ clickBg(ev) {
if (this.processing && this.persistent) return
- if (vm.srcElement.classList.contains('modal-bg')) {
+ if (ev && ev.srcElement && ev.srcElement.classList && ev.srcElement.classList.contains('modal-bg')) {
this.show = false
}
},
diff --git a/components/modals/PlaybackSpeedModal.vue b/components/modals/PlaybackSpeedModal.vue
index 79050c02..82f557f7 100644
--- a/components/modals/PlaybackSpeedModal.vue
+++ b/components/modals/PlaybackSpeedModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/PodcastEpisodesFeedModal.vue b/components/modals/PodcastEpisodesFeedModal.vue
index 502f5554..8c55a074 100644
--- a/components/modals/PodcastEpisodesFeedModal.vue
+++ b/components/modals/PodcastEpisodesFeedModal.vue
@@ -1,7 +1,7 @@
-
-
+
{{ episodesSelected.length ? `Add ${episodesSelected.length} Episode(s) to Server` : 'No Episodes Selected' }}
@@ -130,8 +130,8 @@ export default {
\ No newline at end of file
diff --git a/components/modals/SelectLocalFolderModal.vue b/components/modals/SelectLocalFolderModal.vue
index 36cc2b17..bc326bd9 100644
--- a/components/modals/SelectLocalFolderModal.vue
+++ b/components/modals/SelectLocalFolderModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/SleepTimerLengthModal.vue b/components/modals/SleepTimerLengthModal.vue
index 9785e10e..3f94454e 100644
--- a/components/modals/SleepTimerLengthModal.vue
+++ b/components/modals/SleepTimerLengthModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/SleepTimerModal.vue b/components/modals/SleepTimerModal.vue
index edb45465..cd8ca41f 100644
--- a/components/modals/SleepTimerModal.vue
+++ b/components/modals/SleepTimerModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/modals/playlists/AddCreateModal.vue b/components/modals/playlists/AddCreateModal.vue
index f74d3177..f8ea4763 100644
--- a/components/modals/playlists/AddCreateModal.vue
+++ b/components/modals/playlists/AddCreateModal.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/components/readers/ComicReader.vue b/components/readers/ComicReader.vue
index 58cf2b1c..0f7518d5 100644
--- a/components/readers/ComicReader.vue
+++ b/components/readers/ComicReader.vue
@@ -1,11 +1,11 @@
-
-
-
+
+
-
+
{{ key }}
@@ -14,13 +14,13 @@
-
+
more
-
+
menu
-
+
{{ page + 1 }} / {{ numPages }}
@@ -61,7 +61,12 @@ export default {
showInfoMenu: false,
loadTimeout: null,
loadedFirstPage: false,
- comicMetadata: null
+ comicMetadata: null,
+ clickOutsideObj: {
+ handler: this.clickedOutside,
+ events: ['mousedown'],
+ isActive: true
+ }
}
},
watch: {
@@ -84,9 +89,19 @@ export default {
}
},
methods: {
- clickOutside() {
- if (this.showPageMenu) this.showPageMenu = false
- if (this.showInfoMenu) this.showInfoMenu = false
+ clickShowInfoMenu() {
+ this.showInfoMenu = !this.showInfoMenu
+ this.showPageMenu = false
+ },
+ clickShowPageMenu() {
+ this.showPageMenu = !this.showPageMenu
+ this.showInfoMenu = false
+ },
+ clickedOutside() {
+ this.showPageMenu = false
+ },
+ clickedOutsideInfoMenu() {
+ this.showInfoMenu = false
},
next() {
if (!this.canGoNext) return
@@ -100,7 +115,8 @@ export default {
if (index < 0 || index > this.numPages - 1) {
return
}
- var filename = this.pages[index]
+ this.showPageMenu = false
+ const filename = this.pages[index]
this.page = index
return this.extractFile(filename)
},
@@ -235,8 +251,16 @@ export default {
\ No newline at end of file
diff --git a/components/readers/PdfReader.vue b/components/readers/PdfReader.vue
index a8c69dac..d8d78c09 100644
--- a/components/readers/PdfReader.vue
+++ b/components/readers/PdfReader.vue
@@ -1,5 +1,5 @@
-
+
arrow_back_ios
diff --git a/components/readers/Reader.vue b/components/readers/Reader.vue
index 98dedbf8..5c7e68f4 100644
--- a/components/readers/Reader.vue
+++ b/components/readers/Reader.vue
@@ -1,6 +1,6 @@
-
-
+
+
{{ title }}
close
@@ -132,7 +132,9 @@ export default {
const touchDistanceX = Math.abs(this.touchendX - this.touchstartX)
const touchDistanceY = Math.abs(this.touchendY - this.touchstartY)
- if (touchDistanceX < 100 || touchDistanceY > touchDistanceX) return
+ if (touchDistanceX < 60 || touchDistanceY > touchDistanceX) {
+ return
+ }
if (this.touchendX < this.touchstartX) {
console.log('swiped left')
@@ -144,8 +146,8 @@ export default {
}
},
touchstart(e) {
- this.touchstartX = e.changedTouches[0].screenX
- this.touchstartY = e.changedTouches[0].screenY
+ this.touchstartX = e.touches[0].screenX
+ this.touchstartY = e.touches[0].screenY
this.touchstartTime = Date.now()
},
touchend(e) {
diff --git a/ios/App/App/plugins/AbsDownloader.swift b/ios/App/App/plugins/AbsDownloader.swift
index b208f569..186b0e24 100644
--- a/ios/App/App/plugins/AbsDownloader.swift
+++ b/ios/App/App/plugins/AbsDownloader.swift
@@ -348,8 +348,9 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
private func urlForTrack(item: LibraryItem, track: AudioTrack) -> URL {
// filename needs to be encoded otherwise would just use contentUrl
- let filenameEncoded = track.metadata?.filename.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
- let urlstr = "\(Store.serverConfig!.address)/s/item/\(item.id)/\(filenameEncoded ?? "")?token=\(Store.serverConfig!.token)"
+ let relPath = track.metadata?.relPath ?? ""
+ let filepathEncoded = relPath.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
+ let urlstr = "\(Store.serverConfig!.address)/s/item/\(item.id)/\(filepathEncoded ?? "")?token=\(Store.serverConfig!.token)"
return URL(string: urlstr)!
}
diff --git a/layouts/default.vue b/layouts/default.vue
index a8fed1da..5595d11c 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -1,5 +1,5 @@
-
+
@@ -101,7 +101,7 @@ export default {
const deviceData = await this.$db.getDeviceData()
let serverConfig = null
if (deviceData) {
- this.$store.commit('globals/setHapticFeedback', deviceData.hapticFeedback)
+ this.$store.commit('globals/setHapticFeedback', deviceData.deviceSettings?.hapticFeedback)
if (deviceData.lastServerConnectionConfigId && deviceData.serverConnectionConfigs.length) {
serverConfig = deviceData.serverConnectionConfigs.find((scc) => scc.id == deviceData.lastServerConnectionConfigId)
diff --git a/package-lock.json b/package-lock.json
index 97506f06..5f49d2b9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
"libarchive.js": "^1.3.0",
"nuxt": "^2.15.7",
"socket.io-client": "^4.1.3",
+ "v-click-outside": "^3.2.0",
"vue-pdf": "^4.2.0",
"vue-toastification": "^1.7.11",
"vuedraggable": "^2.24.3"
@@ -17192,6 +17193,14 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/v-click-outside": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/v-click-outside/-/v-click-outside-3.2.0.tgz",
+ "integrity": "sha512-QD0bDy38SHJXQBjgnllmkI/rbdiwmq9RC+/+pvrFjYJKTn8dtp7Penf9q1lLBta280fYG2q53mgLhQ+3l3z74w==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -31992,6 +32001,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
+ "v-click-outside": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/v-click-outside/-/v-click-outside-3.2.0.tgz",
+ "integrity": "sha512-QD0bDy38SHJXQBjgnllmkI/rbdiwmq9RC+/+pvrFjYJKTn8dtp7Penf9q1lLBta280fYG2q53mgLhQ+3l3z74w=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -33339,4 +33353,4 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
}
}
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 35cadcff..6a2e8a33 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"libarchive.js": "^1.3.0",
"nuxt": "^2.15.7",
"socket.io-client": "^4.1.3",
+ "v-click-outside": "^3.2.0",
"vue-pdf": "^4.2.0",
"vue-toastification": "^1.7.11",
"vuedraggable": "^2.24.3"
@@ -41,4 +42,4 @@
"@nuxtjs/tailwindcss": "^4.2.0",
"postcss": "^8.3.5"
}
-}
\ No newline at end of file
+}
diff --git a/pages/bookshelf/authors.vue b/pages/bookshelf/authors.vue
index 280edfcb..8b8202c3 100644
--- a/pages/bookshelf/authors.vue
+++ b/pages/bookshelf/authors.vue
@@ -1,14 +1,83 @@
- Authors
+
\ No newline at end of file
diff --git a/pages/bookshelf/index.vue b/pages/bookshelf/index.vue
index 0ea6729e..01655d0b 100644
--- a/pages/bookshelf/index.vue
+++ b/pages/bookshelf/index.vue
@@ -37,6 +37,7 @@ export default {
data() {
return {
shelves: [],
+ isSettingsLoaded: false,
isFirstNetworkConnection: true,
lastServerFetch: 0,
lastServerFetchLibraryId: null,
@@ -276,12 +277,25 @@ export default {
this.isLoading = false
},
- openMediaPlayerWithMostRecentListening() {
+ async waitForSettings() {
+ // Wait up to 3 seconds
+ for (let i = 0; i < 6; i++) {
+ if (this.isSettingsLoaded) return true
+ await new Promise((resolve) => setTimeout(resolve, 500))
+ }
+ return false
+ },
+ async openMediaPlayerWithMostRecentListening() {
// If we don't already have a player open
// Try opening the first book from continue-listening without playing it
if (this.$store.state.playerLibraryItemId || !this.$store.state.isFirstAudioLoad) return
this.$store.commit('setIsFirstAudioLoad', false) // Only run this once on app launch
+ // Wait for settings to load to prevent race condition when setting playback speed.
+ if (!this.isSettingsLoaded) {
+ await this.waitForSettings()
+ }
+
const continueListeningShelf = this.shelves.find((cat) => cat.id === 'continue-listening')
const mostRecentEntity = continueListeningShelf?.entities?.[0]
if (mostRecentEntity) {
@@ -356,11 +370,16 @@ export default {
}
})
},
+ settingsUpdated() {
+ this.isSettingsLoaded = true
+ },
initListeners() {
this.$eventBus.$on('library-changed', this.libraryChanged)
+ this.$eventBus.$on('user-settings', this.settingsUpdated)
},
removeListeners() {
this.$eventBus.$off('library-changed', this.libraryChanged)
+ this.$eventBus.$off('user-settings', this.settingsUpdated)
}
},
mounted() {
diff --git a/pages/bookshelf/latest.vue b/pages/bookshelf/latest.vue
index e9871040..7b783dd2 100644
--- a/pages/bookshelf/latest.vue
+++ b/pages/bookshelf/latest.vue
@@ -17,7 +17,8 @@ export default {
totalEpisodes: 0,
currentPage: 0,
localEpisodeMap: {},
- isLocal: false
+ isLocal: false,
+ loadedLibraryId: null
}
},
watch: {},
@@ -39,6 +40,7 @@ export default {
this.$store.commit('globals/setShowPlaylistsAddCreateModal', true)
},
async loadRecentEpisodes(page = 0) {
+ this.loadedLibraryId = this.currentLibraryId
this.processing = true
const episodePayload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
console.error('Failed to get recent episodes', error)
@@ -50,10 +52,23 @@ export default {
this.recentEpisodes = episodePayload.episodes || []
this.totalEpisodes = episodePayload.total
this.currentPage = page
+ },
+ libraryChanged(libraryId) {
+ if (libraryId !== this.loadedLibraryId) {
+ if (this.$store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast') {
+ this.loadRecentEpisodes()
+ } else {
+ this.$router.replace('/bookshelf')
+ }
+ }
}
},
mounted() {
this.loadRecentEpisodes()
+ this.$eventBus.$on('library-changed', this.libraryChanged)
+ },
+ beforeDestroy() {
+ this.$eventBus.$off('library-changed', this.libraryChanged)
}
}
\ No newline at end of file
diff --git a/pages/bookshelf/library.vue b/pages/bookshelf/library.vue
index 9a35f603..1e58d8ef 100644
--- a/pages/bookshelf/library.vue
+++ b/pages/bookshelf/library.vue
@@ -7,7 +7,6 @@ export default {
async asyncData({ store, params, query }) {
// Set filter by
if (query.filter) {
- store.commit('user/setSettings', { mobileFilterBy: query.filter })
await store.dispatch('user/updateUserSettings', { mobileFilterBy: query.filter })
}
}
diff --git a/pages/item/_id/index.vue b/pages/item/_id/index.vue
index 1ea27d2d..1206303e 100644
--- a/pages/item/_id/index.vue
+++ b/pages/item/_id/index.vue
@@ -1,5 +1,5 @@
-
+
diff --git a/pages/localMedia/folders/_id.vue b/pages/localMedia/folders/_id.vue
index 22645149..8c17fa4b 100644
--- a/pages/localMedia/folders/_id.vue
+++ b/pages/localMedia/folders/_id.vue
@@ -189,7 +189,11 @@ export default {
\ No newline at end of file
diff --git a/pages/localMedia/item/_id.vue b/pages/localMedia/item/_id.vue
index bd2313e8..97c35827 100644
--- a/pages/localMedia/item/_id.vue
+++ b/pages/localMedia/item/_id.vue
@@ -390,7 +390,7 @@ export default {
}
-