Add:Library settings for mark as finished when time remaining or percent complete #837

This commit is contained in:
advplyr 2024-10-24 17:19:51 -05:00
parent 6ca277a21d
commit 91aea4f754
8 changed files with 99 additions and 18 deletions

View File

@ -111,7 +111,6 @@ export default {
},
updateLibrary(library) {
this.mapLibraryToCopy(library)
console.log('Updated library', this.libraryCopy)
},
getNewLibraryData() {
return {
@ -128,7 +127,9 @@ export default {
autoScanCronExpression: null,
hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false,
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'],
markAsFinishedPercentComplete: null,
markAsFinishedTimeRemaining: 10
}
}
},
@ -236,7 +237,6 @@ export default {
this.show = false
this.$toast.success(this.$getString('ToastLibraryCreateSuccess', [res.name]))
if (!this.$store.state.libraries.currentLibraryId) {
console.log('Setting initially library id', res.id)
// First library added
this.$store.dispatch('libraries/fetch', res.id)
}

View File

@ -75,6 +75,20 @@
<div v-if="isPodcastLibrary" class="p-2 w-full md:w-1/2">
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
</div>
<div class="p-2 w-full flex items-center space-x-2 flex-wrap">
<div>
<ui-dropdown v-model="markAsFinishedWhen" :items="maskAsFinishedWhenItems" :label="$strings.LabelSettingsLibraryMarkAsFinishedWhen" small class="w-72 min-w-72 text-sm" menu-max-height="200px" @input="markAsFinishedWhenChanged" />
</div>
<div class="w-16">
<div>
<label class="px-1 text-sm font-semibold"></label>
<div class="relative">
<ui-text-input v-model="markAsFinishedValue" type="number" label="" no-spinner custom-input-class="pr-5" @input="markAsFinishedChanged" />
<div class="absolute top-0 bottom-0 right-4 flex items-center">{{ markAsFinishedWhen === 'timeRemaining' ? '' : '%' }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@ -99,7 +113,9 @@ export default {
epubsAllowScriptedContent: false,
hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false,
podcastSearchRegion: 'us'
podcastSearchRegion: 'us',
markAsFinishedWhen: 'timeRemaining',
markAsFinishedValue: 10
}
},
computed: {
@ -121,10 +137,34 @@ export default {
providers() {
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
return this.$store.state.scanners.providers
},
maskAsFinishedWhenItems() {
return [
{
text: this.$strings.LabelSettingsLibraryMarkAsFinishedTimeRemaining,
value: 'timeRemaining'
},
{
text: this.$strings.LabelSettingsLibraryMarkAsFinishedPercentComplete,
value: 'percentComplete'
}
]
}
},
methods: {
markAsFinishedWhenChanged(val) {
if (val === 'percentComplete' && this.markAsFinishedValue > 100) {
this.markAsFinishedValue = 100
}
this.formUpdated()
},
markAsFinishedChanged(val) {
this.formUpdated()
},
getLibraryData() {
let markAsFinishedTimeRemaining = this.markAsFinishedWhen === 'timeRemaining' ? Number(this.markAsFinishedValue) : null
let markAsFinishedPercentComplete = this.markAsFinishedWhen === 'percentComplete' ? Number(this.markAsFinishedValue) : null
return {
settings: {
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
@ -135,7 +175,9 @@ export default {
epubsAllowScriptedContent: !!this.epubsAllowScriptedContent,
hideSingleBookSeries: !!this.hideSingleBookSeries,
onlyShowLaterBooksInContinueSeries: !!this.onlyShowLaterBooksInContinueSeries,
podcastSearchRegion: this.podcastSearchRegion
podcastSearchRegion: this.podcastSearchRegion,
markAsFinishedTimeRemaining: markAsFinishedTimeRemaining,
markAsFinishedPercentComplete: markAsFinishedPercentComplete
}
}
},
@ -152,6 +194,11 @@ export default {
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries
this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us'
this.markAsFinishedWhen = this.librarySettings.markAsFinishedTimeRemaining ? 'timeRemaining' : 'percentComplete'
if (!this.librarySettings.markAsFinishedTimeRemaining && !this.librarySettings.markAsFinishedPercentComplete) {
this.markAsFinishedWhen = 'timeRemaining'
}
this.markAsFinishedValue = this.librarySettings.markAsFinishedTimeRemaining || this.librarySettings.markAsFinishedPercentComplete || 10
}
},
mounted() {

View File

@ -562,6 +562,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "Percent complete is greater than",
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Time remaining is less than (seconds)",
"LabelSettingsLibraryMarkAsFinishedWhen": "Mark media item as finished when",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
"LabelSettingsParseSubtitles": "Parse subtitles",

View File

@ -255,15 +255,18 @@ class LibraryController {
}
// Validate settings
const defaultLibrarySettings = Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType)
const updatedSettings = {
...(req.library.settings || Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType))
...(req.library.settings || defaultLibrarySettings)
}
let hasUpdates = false
let hasUpdatedDisableWatcher = false
let hasUpdatedScanCron = false
if (req.body.settings) {
for (const key in req.body.settings) {
if (updatedSettings[key] === undefined) continue
if (!Object.keys(defaultLibrarySettings).includes(key)) {
continue
}
if (key === 'metadataPrecedence') {
if (!Array.isArray(req.body.settings[key])) {
@ -285,6 +288,28 @@ class LibraryController {
updatedSettings[key] = req.body.settings[key]
Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
}
} else if (key === 'markAsFinishedPercentComplete') {
if (req.body.settings[key] !== null && isNaN(req.body.settings[key])) {
return res.status(400).send(`Invalid request. Setting "${key}" must be a number`)
} else if (req.body.settings[key] !== null && (Number(req.body.settings[key]) < 0 || Number(req.body.settings[key]) > 100)) {
return res.status(400).send(`Invalid request. Setting "${key}" must be between 0 and 100`)
}
if (req.body.settings[key] !== updatedSettings[key]) {
hasUpdates = true
updatedSettings[key] = Number(req.body.settings[key])
Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
}
} else if (key === 'markAsFinishedTimeRemaining') {
if (req.body.settings[key] !== null && isNaN(req.body.settings[key])) {
return res.status(400).send(`Invalid request. Setting "${key}" must be a number`)
} else if (req.body.settings[key] !== null && Number(req.body.settings[key]) < 0) {
return res.status(400).send(`Invalid request. Setting "${key}" must be greater than or equal to 0`)
}
if (req.body.settings[key] !== updatedSettings[key]) {
hasUpdates = true
updatedSettings[key] = Number(req.body.settings[key])
Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
}
} else {
if (typeof req.body.settings[key] !== typeof updatedSettings[key]) {
return res.status(400).send(`Invalid request. Setting "${key}" must be of type ${typeof updatedSettings[key]}`)

View File

@ -349,7 +349,7 @@ class PlaybackSessionManager {
progress: session.progress
// TODO: Add support for passing in these values from library settings
// markAsFinishedTimeRemaining: 5,
// markAsFinishedPercentageComplete: 95
// markAsFinishedPercentComplete: 95
})
if (updateResponse.mediaProgress) {
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {

View File

@ -12,6 +12,8 @@ const Logger = require('../Logger')
* @property {boolean} hideSingleBookSeries Do not show series that only have 1 book
* @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read
* @property {string[]} metadataPrecedence
* @property {number} markAsFinishedTimeRemaining Time remaining in seconds to mark as finished. (defaults to 10s)
* @property {number} markAsFinishedPercentComplete Percent complete to mark as finished (0-100). If this is set it will be used over markAsFinishedTimeRemaining.
*/
class Library extends Model {
@ -57,7 +59,9 @@ class Library extends Model {
coverAspectRatio: 1, // Square
disableWatcher: false,
autoScanCronExpression: null,
podcastSearchRegion: 'us'
podcastSearchRegion: 'us',
markAsFinishedPercentComplete: null,
markAsFinishedTimeRemaining: 10
}
} else {
return {
@ -70,7 +74,9 @@ class Library extends Model {
epubsAllowScriptedContent: false,
hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false,
metadataPrecedence: this.defaultMetadataPrecedence
metadataPrecedence: this.defaultMetadataPrecedence,
markAsFinishedPercentComplete: null,
markAsFinishedTimeRemaining: 10
}
}
}

View File

@ -229,18 +229,18 @@ class MediaProgress extends Model {
const timeRemaining = this.duration - this.currentTime
// Check if progress is far enough to mark as finished
// - If markAsFinishedPercentageComplete is provided, use that otherwise use markAsFinishedTimeRemaining (default 5 seconds)
// - If markAsFinishedPercentComplete is provided, use that otherwise use markAsFinishedTimeRemaining (default 10 seconds)
let shouldMarkAsFinished = false
if (!this.isFinished && this.duration) {
if (!isNullOrNaN(progressPayload.markAsFinishedPercentageComplete)) {
const markAsFinishedPercentageComplete = Number(progressPayload.markAsFinishedPercentageComplete) / 100
shouldMarkAsFinished = markAsFinishedPercentageComplete <= this.progress
if (!isNullOrNaN(progressPayload.markAsFinishedPercentComplete)) {
const markAsFinishedPercentComplete = Number(progressPayload.markAsFinishedPercentComplete) / 100
shouldMarkAsFinished = markAsFinishedPercentComplete < this.progress
if (shouldMarkAsFinished) {
Logger.debug(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentageComplete}`)
Logger.debug(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentComplete}`)
}
} else {
const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 5 : Number(progressPayload.markAsFinishedTimeRemaining)
shouldMarkAsFinished = timeRemaining <= markAsFinishedTimeRemaining
const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 10 : Number(progressPayload.markAsFinishedTimeRemaining)
shouldMarkAsFinished = timeRemaining < markAsFinishedTimeRemaining
if (shouldMarkAsFinished) {
Logger.debug(`[MediaProgress] Marking media progress as finished because time remaining (${timeRemaining}) is less than ${markAsFinishedTimeRemaining} seconds`)
}

View File

@ -28,7 +28,7 @@ const { DataTypes, Model } = sequelize
* @property {string} [finishedAt]
* @property {number} [lastUpdate]
* @property {number} [markAsFinishedTimeRemaining]
* @property {number} [markAsFinishedPercentageComplete]
* @property {number} [markAsFinishedPercentComplete]
*/
class User extends Model {