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) { updateLibrary(library) {
this.mapLibraryToCopy(library) this.mapLibraryToCopy(library)
console.log('Updated library', this.libraryCopy)
}, },
getNewLibraryData() { getNewLibraryData() {
return { return {
@ -128,7 +127,9 @@ export default {
autoScanCronExpression: null, autoScanCronExpression: null,
hideSingleBookSeries: false, hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: 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.show = false
this.$toast.success(this.$getString('ToastLibraryCreateSuccess', [res.name])) this.$toast.success(this.$getString('ToastLibraryCreateSuccess', [res.name]))
if (!this.$store.state.libraries.currentLibraryId) { if (!this.$store.state.libraries.currentLibraryId) {
console.log('Setting initially library id', res.id)
// First library added // First library added
this.$store.dispatch('libraries/fetch', res.id) 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"> <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" /> <ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
</div> </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>
</div> </div>
</template> </template>
@ -99,7 +113,9 @@ export default {
epubsAllowScriptedContent: false, epubsAllowScriptedContent: false,
hideSingleBookSeries: false, hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false, onlyShowLaterBooksInContinueSeries: false,
podcastSearchRegion: 'us' podcastSearchRegion: 'us',
markAsFinishedWhen: 'timeRemaining',
markAsFinishedValue: 10
} }
}, },
computed: { computed: {
@ -121,10 +137,34 @@ export default {
providers() { providers() {
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
return this.$store.state.scanners.providers return this.$store.state.scanners.providers
},
maskAsFinishedWhenItems() {
return [
{
text: this.$strings.LabelSettingsLibraryMarkAsFinishedTimeRemaining,
value: 'timeRemaining'
},
{
text: this.$strings.LabelSettingsLibraryMarkAsFinishedPercentComplete,
value: 'percentComplete'
}
]
} }
}, },
methods: { methods: {
markAsFinishedWhenChanged(val) {
if (val === 'percentComplete' && this.markAsFinishedValue > 100) {
this.markAsFinishedValue = 100
}
this.formUpdated()
},
markAsFinishedChanged(val) {
this.formUpdated()
},
getLibraryData() { getLibraryData() {
let markAsFinishedTimeRemaining = this.markAsFinishedWhen === 'timeRemaining' ? Number(this.markAsFinishedValue) : null
let markAsFinishedPercentComplete = this.markAsFinishedWhen === 'percentComplete' ? Number(this.markAsFinishedValue) : null
return { return {
settings: { settings: {
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD, coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
@ -135,7 +175,9 @@ export default {
epubsAllowScriptedContent: !!this.epubsAllowScriptedContent, epubsAllowScriptedContent: !!this.epubsAllowScriptedContent,
hideSingleBookSeries: !!this.hideSingleBookSeries, hideSingleBookSeries: !!this.hideSingleBookSeries,
onlyShowLaterBooksInContinueSeries: !!this.onlyShowLaterBooksInContinueSeries, 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.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries
this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us' 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() { 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.", "LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view", "LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
"LabelSettingsLibraryBookshelfView": "Library 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", "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.", "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", "LabelSettingsParseSubtitles": "Parse subtitles",

View File

@ -255,15 +255,18 @@ class LibraryController {
} }
// Validate settings // Validate settings
const defaultLibrarySettings = Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType)
const updatedSettings = { const updatedSettings = {
...(req.library.settings || Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType)) ...(req.library.settings || defaultLibrarySettings)
} }
let hasUpdates = false let hasUpdates = false
let hasUpdatedDisableWatcher = false let hasUpdatedDisableWatcher = false
let hasUpdatedScanCron = false let hasUpdatedScanCron = false
if (req.body.settings) { if (req.body.settings) {
for (const key in 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 (key === 'metadataPrecedence') {
if (!Array.isArray(req.body.settings[key])) { if (!Array.isArray(req.body.settings[key])) {
@ -285,6 +288,28 @@ class LibraryController {
updatedSettings[key] = req.body.settings[key] updatedSettings[key] = req.body.settings[key]
Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[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 { } else {
if (typeof req.body.settings[key] !== typeof updatedSettings[key]) { 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]}`) 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 progress: session.progress
// TODO: Add support for passing in these values from library settings // TODO: Add support for passing in these values from library settings
// markAsFinishedTimeRemaining: 5, // markAsFinishedTimeRemaining: 5,
// markAsFinishedPercentageComplete: 95 // markAsFinishedPercentComplete: 95
}) })
if (updateResponse.mediaProgress) { if (updateResponse.mediaProgress) {
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { 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} 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 {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read
* @property {string[]} metadataPrecedence * @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 { class Library extends Model {
@ -57,7 +59,9 @@ class Library extends Model {
coverAspectRatio: 1, // Square coverAspectRatio: 1, // Square
disableWatcher: false, disableWatcher: false,
autoScanCronExpression: null, autoScanCronExpression: null,
podcastSearchRegion: 'us' podcastSearchRegion: 'us',
markAsFinishedPercentComplete: null,
markAsFinishedTimeRemaining: 10
} }
} else { } else {
return { return {
@ -70,7 +74,9 @@ class Library extends Model {
epubsAllowScriptedContent: false, epubsAllowScriptedContent: false,
hideSingleBookSeries: false, hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: 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 const timeRemaining = this.duration - this.currentTime
// Check if progress is far enough to mark as finished // 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 let shouldMarkAsFinished = false
if (!this.isFinished && this.duration) { if (!this.isFinished && this.duration) {
if (!isNullOrNaN(progressPayload.markAsFinishedPercentageComplete)) { if (!isNullOrNaN(progressPayload.markAsFinishedPercentComplete)) {
const markAsFinishedPercentageComplete = Number(progressPayload.markAsFinishedPercentageComplete) / 100 const markAsFinishedPercentComplete = Number(progressPayload.markAsFinishedPercentComplete) / 100
shouldMarkAsFinished = markAsFinishedPercentageComplete <= this.progress shouldMarkAsFinished = markAsFinishedPercentComplete < this.progress
if (shouldMarkAsFinished) { 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 { } else {
const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 5 : Number(progressPayload.markAsFinishedTimeRemaining) const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 10 : Number(progressPayload.markAsFinishedTimeRemaining)
shouldMarkAsFinished = timeRemaining <= markAsFinishedTimeRemaining shouldMarkAsFinished = timeRemaining < markAsFinishedTimeRemaining
if (shouldMarkAsFinished) { if (shouldMarkAsFinished) {
Logger.debug(`[MediaProgress] Marking media progress as finished because time remaining (${timeRemaining}) is less than ${markAsFinishedTimeRemaining} seconds`) 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 {string} [finishedAt]
* @property {number} [lastUpdate] * @property {number} [lastUpdate]
* @property {number} [markAsFinishedTimeRemaining] * @property {number} [markAsFinishedTimeRemaining]
* @property {number} [markAsFinishedPercentageComplete] * @property {number} [markAsFinishedPercentComplete]
*/ */
class User extends Model { class User extends Model {