mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-02 12:09:11 +01:00
Merge branch 'advplyr:master' into master
This commit is contained in:
commit
a7163f7a00
@ -45,17 +45,16 @@
|
|||||||
</span>
|
</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="numLibraryItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
|
<div v-show="numLibraryItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center">
|
||||||
<h1 class="text-2xl px-4">{{ numLibraryItemsSelected }} Selected</h1>
|
<h1 class="text-2xl px-4">{{ $getString('MessageItemsSelected', [numLibraryItemsSelected]) }}</h1>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-tooltip v-if="userIsAdminOrUp && !isPodcastLibrary" text="Quick Match Selected" direction="bottom">
|
<ui-tooltip v-if="userIsAdminOrUp && !isPodcastLibrary" :text="$strings.ButtonQuickMatch" direction="bottom">
|
||||||
<ui-icon-btn :disabled="processingBatch" icon="auto_awesome" @click="batchAutoMatchClick" class="mx-1.5" />
|
<ui-icon-btn :disabled="processingBatch" icon="auto_awesome" @click="batchAutoMatchClick" class="mx-1.5" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<ui-tooltip v-if="!isPodcastLibrary" :text="`Mark as ${selectedIsFinished ? 'Not Finished' : 'Finished'}`" direction="bottom">
|
<ui-tooltip v-if="!isPodcastLibrary" :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
||||||
<ui-read-icon-btn :disabled="processingBatch" :is-read="selectedIsFinished" @click="toggleBatchRead" class="mx-1.5" />
|
<ui-read-icon-btn :disabled="processingBatch" :is-read="selectedIsFinished" @click="toggleBatchRead" class="mx-1.5" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<ui-tooltip v-if="userCanUpdate && !isPodcastLibrary" text="Add to Collection" direction="bottom">
|
<ui-tooltip v-if="userCanUpdate && !isPodcastLibrary" :text="$strings.LabelAddToCollection" direction="bottom">
|
||||||
<ui-icon-btn :disabled="processingBatch" icon="collections_bookmark" @click="batchAddToCollectionClick" class="mx-1.5" />
|
<ui-icon-btn :disabled="processingBatch" icon="collections_bookmark" @click="batchAddToCollectionClick" class="mx-1.5" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<template v-if="userCanUpdate && numLibraryItemsSelected < 50">
|
<template v-if="userCanUpdate && numLibraryItemsSelected < 50">
|
||||||
@ -63,10 +62,10 @@
|
|||||||
<ui-icon-btn v-show="!processingBatchDelete" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" />
|
<ui-icon-btn v-show="!processingBatchDelete" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<ui-tooltip v-if="userCanDelete" text="Delete" direction="bottom">
|
<ui-tooltip v-if="userCanDelete" :text="$strings.ButtonRemove" direction="bottom">
|
||||||
<ui-icon-btn :disabled="processingBatchDelete" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" />
|
<ui-icon-btn :disabled="processingBatchDelete" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<ui-tooltip text="Deselect All" direction="bottom">
|
<ui-tooltip :text="$strings.LabelDeselectAll" direction="bottom">
|
||||||
<span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
|
<span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,16 +17,16 @@
|
|||||||
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24">
|
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24">
|
||||||
<template v-for="(shelf, index) in shelves">
|
<template v-for="(shelf, index) in shelves">
|
||||||
<widgets-item-slider v-if="shelf.type === 'book' || shelf.type === 'podcast'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
|
<widgets-item-slider v-if="shelf.type === 'book' || shelf.type === 'podcast'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
|
||||||
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p>
|
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
|
||||||
</widgets-item-slider>
|
</widgets-item-slider>
|
||||||
<widgets-episode-slider v-else-if="shelf.type === 'episode'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
|
<widgets-episode-slider v-else-if="shelf.type === 'episode'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
|
||||||
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p>
|
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
|
||||||
</widgets-episode-slider>
|
</widgets-episode-slider>
|
||||||
<widgets-series-slider v-else-if="shelf.type === 'series'" :key="index + '.'" :items="shelf.entities" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
|
<widgets-series-slider v-else-if="shelf.type === 'series'" :key="index + '.'" :items="shelf.entities" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
|
||||||
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p>
|
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
|
||||||
</widgets-series-slider>
|
</widgets-series-slider>
|
||||||
<widgets-authors-slider v-else-if="shelf.type === 'authors'" :key="index + '.'" :items="shelf.entities" :height="192 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
|
<widgets-authors-slider v-else-if="shelf.type === 'authors'" :key="index + '.'" :items="shelf.entities" :height="192 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
|
||||||
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p>
|
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ $strings[shelf.labelStringKey] }}</p>
|
||||||
</widgets-authors-slider>
|
</widgets-authors-slider>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -180,6 +180,7 @@ export default {
|
|||||||
shelves.push({
|
shelves.push({
|
||||||
id: 'books',
|
id: 'books',
|
||||||
label: 'Books',
|
label: 'Books',
|
||||||
|
labelStringKey: 'LabelBooks',
|
||||||
type: 'book',
|
type: 'book',
|
||||||
entities: this.results.books.map((res) => res.libraryItem)
|
entities: this.results.books.map((res) => res.libraryItem)
|
||||||
})
|
})
|
||||||
@ -189,6 +190,7 @@ export default {
|
|||||||
shelves.push({
|
shelves.push({
|
||||||
id: 'podcasts',
|
id: 'podcasts',
|
||||||
label: 'Podcasts',
|
label: 'Podcasts',
|
||||||
|
labelStringKey: 'LabelPodcasts',
|
||||||
type: 'podcast',
|
type: 'podcast',
|
||||||
entities: this.results.podcasts.map((res) => res.libraryItem)
|
entities: this.results.podcasts.map((res) => res.libraryItem)
|
||||||
})
|
})
|
||||||
@ -198,6 +200,7 @@ export default {
|
|||||||
shelves.push({
|
shelves.push({
|
||||||
id: 'series',
|
id: 'series',
|
||||||
label: 'Series',
|
label: 'Series',
|
||||||
|
labelStringKey: 'LabelSeries',
|
||||||
type: 'series',
|
type: 'series',
|
||||||
entities: this.results.series.map((seriesObj) => {
|
entities: this.results.series.map((seriesObj) => {
|
||||||
return {
|
return {
|
||||||
@ -212,6 +215,7 @@ export default {
|
|||||||
shelves.push({
|
shelves.push({
|
||||||
id: 'tags',
|
id: 'tags',
|
||||||
label: 'Tags',
|
label: 'Tags',
|
||||||
|
labelStringKey: 'LabelTags',
|
||||||
type: 'tags',
|
type: 'tags',
|
||||||
entities: this.results.tags.map((tagObj) => {
|
entities: this.results.tags.map((tagObj) => {
|
||||||
return {
|
return {
|
||||||
@ -226,6 +230,7 @@ export default {
|
|||||||
shelves.push({
|
shelves.push({
|
||||||
id: 'authors',
|
id: 'authors',
|
||||||
label: 'Authors',
|
label: 'Authors',
|
||||||
|
labelStringKey: 'LabelAuthors',
|
||||||
type: 'authors',
|
type: 'authors',
|
||||||
entities: this.results.authors.map((a) => {
|
entities: this.results.authors.map((a) => {
|
||||||
return {
|
return {
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
<div class="absolute text-center categoryPlacard font-book transform z-30 bottom-px left-4 md:left-8 w-44 rounded-md" style="height: 22px">
|
<div class="absolute text-center categoryPlacard font-book transform z-30 bottom-px left-4 md:left-8 w-44 rounded-md" style="height: 22px">
|
||||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border">
|
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border">
|
||||||
<p class="transform text-sm">{{ shelf.label }}</p>
|
<p class="transform text-sm">{{ $strings[shelf.labelStringKey] }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<p v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base">
|
<p v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base">
|
||||||
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">Unknown</p>
|
<p v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-gray-400 flex items-center">
|
<div class="text-gray-400 flex items-center">
|
||||||
|
@ -1,164 +1,113 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="card" :id="`book-card-${index}`"
|
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
:style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }"
|
|
||||||
class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent
|
|
||||||
@mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
|
||||||
<!-- When cover image does not fill -->
|
<!-- When cover image does not fill -->
|
||||||
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
||||||
<div class="absolute cover-bg" ref="coverBg" />
|
<div class="absolute cover-bg" ref="coverBg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Alternative bookshelf title/author/sort -->
|
<!-- Alternative bookshelf title/author/sort -->
|
||||||
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" class="absolute left-0 z-50 w-full"
|
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||||
:style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
|
||||||
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||||
{{ displayTitle }}
|
{{ displayTitle }}
|
||||||
</p>
|
</p>
|
||||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo ||
|
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || ' ' }}</p>
|
||||||
' '
|
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||||
}}</p>
|
|
||||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{
|
|
||||||
displaySortLine
|
|
||||||
}}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right"
|
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #78350f">
|
||||||
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }"
|
|
||||||
style="background-color: #78350f">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20"
|
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #cd9d49dd">
|
||||||
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }"
|
|
||||||
style="background-color: #cd9d49dd">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||||
<div v-show="libraryItem && !imageReady"
|
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||||
class="absolute top-0 left-0 w-full h-full flex items-center justify-center"
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
||||||
:style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cover Image -->
|
<!-- Cover Image -->
|
||||||
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300"
|
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||||
:class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded"
|
|
||||||
:style="{ opacity: imageReady ? 1 : 0 }" />
|
|
||||||
|
|
||||||
<!-- Placeholder Cover Title & Author -->
|
<!-- Placeholder Cover Title & Author -->
|
||||||
<div v-if="!hasCover"
|
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||||
class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center"
|
|
||||||
:style="{ padding: placeholderCoverPadding + 'rem' }">
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
|
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
|
||||||
{{ titleCleaned }}</p>
|
{{ titleCleaned }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center"
|
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
||||||
:style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75"
|
|
||||||
:style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
|
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
|
||||||
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)"
|
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||||
class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b"
|
|
||||||
:class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }">
|
|
||||||
</div>
|
|
||||||
<!-- Finished progress bar for collapsed series -->
|
<!-- Finished progress bar for collapsed series -->
|
||||||
<div v-else-if="booksInSeries && seriesIsFinished"
|
<div v-else-if="booksInSeries && seriesIsFinished" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||||
class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success"
|
|
||||||
:style="{ width: width * userProgressPercent + 'px' }"></div>
|
|
||||||
|
|
||||||
<!-- Overlay is not shown if collapsing series in library -->
|
<!-- Overlay is not shown if collapsing series in library -->
|
||||||
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing"
|
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
|
||||||
class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block"
|
|
||||||
:class="overlayWrapperClasslist">
|
|
||||||
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
|
<div 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"
|
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
|
||||||
@click.stop.prevent="play">
|
|
||||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
|
<div 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"
|
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
|
||||||
@click.stop.prevent="clickReadEBook">
|
|
||||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
|
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="userCanUpdate" v-show="!isSelectionMode"
|
<div 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 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
||||||
class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0"
|
|
||||||
:style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
|
||||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100"
|
<div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
|
||||||
:style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }"
|
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
|
||||||
@click.stop.prevent="selectBtnClick">
|
|
||||||
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''"
|
|
||||||
:style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' :
|
|
||||||
'radio_button_unchecked'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- More Menu Icon -->
|
<!-- More Menu Icon -->
|
||||||
<div ref="moreIcon" v-show="!isSelectionMode"
|
<div ref="moreIcon" v-show="!isSelectionMode" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
|
||||||
class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150"
|
|
||||||
:style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }"
|
|
||||||
@click.stop.prevent="clickShowMore">
|
|
||||||
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
|
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Processing/loading spinner overlay -->
|
<!-- Processing/loading spinner overlay -->
|
||||||
<div v-if="processing"
|
<div v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
|
||||||
class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
|
|
||||||
<widgets-loading-spinner size="la-lg" />
|
<widgets-loading-spinner size="la-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Series name overlay -->
|
<!-- Series name overlay -->
|
||||||
<div v-if="booksInSeries && libraryItem && isHovering"
|
<div v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
|
||||||
class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center"
|
|
||||||
:style="{ padding: sizeMultiplier + 'rem' }">
|
|
||||||
<p class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ series }}</p>
|
<p class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ series }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error widget -->
|
<!-- Error widget -->
|
||||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
||||||
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }"
|
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
||||||
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-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
||||||
<span class="material-icons text-red-100 pr-1"
|
|
||||||
:style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
|
||||||
</div>
|
</div>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10"
|
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
|
||||||
:style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
|
|
||||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
|
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Series sequence -->
|
<!-- Series sequence -->
|
||||||
<div v-if="seriesSequence && !isHovering && !isSelectionMode"
|
<div v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||||
class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10"
|
|
||||||
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Episode # -->
|
<!-- Podcast Episode # -->
|
||||||
<div v-if="recentEpisodeNumber && !isHovering && !isSelectionMode && !processing"
|
<div v-if="recentEpisodeNumber && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||||
class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10"
|
|
||||||
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">Episode #{{ recentEpisodeNumber }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">Episode #{{ recentEpisodeNumber }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
<div v-else-if="numEpisodes && !isHovering && !isSelectionMode"
|
<div v-else-if="numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center"
|
|
||||||
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -446,17 +395,17 @@ export default {
|
|||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
func: 'editPodcast',
|
func: 'editPodcast',
|
||||||
text: 'Edit Podcast'
|
text: this.$strings.ButtonEditPodcast
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
func: 'toggleFinished',
|
func: 'toggleFinished',
|
||||||
text: `Mark as ${this.itemIsFinished ? 'Not Finished' : 'Finished'}`
|
text: this.itemIsFinished ? this.$strings.MessageMarkAsNotFinished : this.$strings.MessageMarkAsFinished
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (this.continueListeningShelf) {
|
if (this.continueListeningShelf) {
|
||||||
items.push({
|
items.push({
|
||||||
func: 'removeFromContinueListening',
|
func: 'removeFromContinueListening',
|
||||||
text: 'Remove from Continue Listening'
|
text: this.$strings.ButtonRemoveFromContinueListening
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
@ -467,42 +416,42 @@ export default {
|
|||||||
items = [
|
items = [
|
||||||
{
|
{
|
||||||
func: 'toggleFinished',
|
func: 'toggleFinished',
|
||||||
text: `Mark as ${this.itemIsFinished ? 'Not Finished' : 'Finished'}`
|
text: this.itemIsFinished ? this.$strings.MessageMarkAsNotFinished : this.$strings.MessageMarkAsFinished
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (this.userCanUpdate) {
|
if (this.userCanUpdate) {
|
||||||
items.push({
|
items.push({
|
||||||
func: 'openCollections',
|
func: 'openCollections',
|
||||||
text: 'Add to Collection'
|
text: this.$strings.LabelAddToCollection
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.userCanUpdate) {
|
if (this.userCanUpdate) {
|
||||||
items.push({
|
items.push({
|
||||||
func: 'showEditModalFiles',
|
func: 'showEditModalFiles',
|
||||||
text: 'Files'
|
text: this.$strings.HeaderFiles
|
||||||
})
|
})
|
||||||
items.push({
|
items.push({
|
||||||
func: 'showEditModalMatch',
|
func: 'showEditModalMatch',
|
||||||
text: 'Match'
|
text: this.$strings.HeaderMatch
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.userIsAdminOrUp && !this.isFile) {
|
if (this.userIsAdminOrUp && !this.isFile) {
|
||||||
items.push({
|
items.push({
|
||||||
func: 'rescan',
|
func: 'rescan',
|
||||||
text: 'Re-Scan'
|
text: this.$strings.ButtonReScan
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.series && this.bookMount) {
|
if (this.series && this.bookMount) {
|
||||||
items.push({
|
items.push({
|
||||||
func: 'removeSeriesFromContinueListening',
|
func: 'removeSeriesFromContinueListening',
|
||||||
text: 'Remove Series from Continue Series'
|
text: this.$strings.ButtonRemoveSeriesFromContinueSeries
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.continueListeningShelf) {
|
if (this.continueListeningShelf) {
|
||||||
items.push({
|
items.push({
|
||||||
func: 'removeFromContinueListening',
|
func: 'removeFromContinueListening',
|
||||||
text: 'Remove from Continue Listening'
|
text: this.$strings.ButtonRemoveFromContinueListening
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
@ -594,7 +543,7 @@ export default {
|
|||||||
if (this.isSelectionMode) {
|
if (this.isSelectionMode) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.selectBtnClick()
|
this.selectBtnClick(e)
|
||||||
} else {
|
} else {
|
||||||
var router = this.$router || this.$nuxt.$router
|
var router = this.$router || this.$nuxt.$router
|
||||||
if (router) {
|
if (router) {
|
||||||
@ -638,12 +587,12 @@ export default {
|
|||||||
.$patch(apiEndpoint, updatePayload)
|
.$patch(apiEndpoint, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
toast.success(`Item marked as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
toast.error(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedFailed : this.$strings.ToastItemMarkedAsNotFinishedFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
editPodcast() {
|
editPodcast() {
|
||||||
|
@ -5,14 +5,14 @@
|
|||||||
<div class="w-full h-16 bg-primary">
|
<div class="w-full h-16 bg-primary">
|
||||||
<img v-if="image" :src="image" class="w-full h-full object-cover" />
|
<img v-if="image" :src="image" class="w-full h-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 text-xxs pt-1 text-center">{{ numEpisodes }} Episodes</p>
|
<p class="text-gray-400 text-xxs pt-1 text-center">{{ numEpisodes }} {{ $strings.HeaderEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow pl-2" :style="{ maxWidth: detailsWidth + 'px' }">
|
<div class="flex-grow pl-2" :style="{ maxWidth: detailsWidth + 'px' }">
|
||||||
<p class="mb-1">{{ title }}</p>
|
<p class="mb-1">{{ title }}</p>
|
||||||
<p class="text-xs mb-1 text-gray-300">{{ author }}</p>
|
<p class="text-xs mb-1 text-gray-300">{{ author }}</p>
|
||||||
<p class="text-xs mb-2 text-gray-200">{{ description }}</p>
|
<p class="text-xs mb-2 text-gray-200">{{ description }}</p>
|
||||||
<p class="text-xs truncate text-blue-200">
|
<p class="text-xs truncate text-blue-200">
|
||||||
Folder: <span class="font-mono">{{ folderPath }}</span>
|
{{ $strings.LabelFolder }}: <span class="font-mono">{{ folderPath }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,16 +10,16 @@
|
|||||||
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-40 sm:w-full 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-40 sm:w-full 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">
|
||||||
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
<li v-if="isTyping" class="py-2 px-2">
|
<li v-if="isTyping" class="py-2 px-2">
|
||||||
<p>Thinking...</p>
|
<p>{{ $strings.MessageThinking }}</p>
|
||||||
</li>
|
</li>
|
||||||
<li v-else-if="isFetching" class="py-2 px-2">
|
<li v-else-if="isFetching" class="py-2 px-2">
|
||||||
<p>Fetching...</p>
|
<p>{{ $strings.MessageFetching }}</p>
|
||||||
</li>
|
</li>
|
||||||
<li v-else-if="!totalResults" class="py-2 px-2">
|
<li v-else-if="!totalResults" class="py-2 px-2">
|
||||||
<p>No Results</p>
|
<p>{{ $strings.MessageNoResults }}</p>
|
||||||
</li>
|
</li>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p v-if="bookResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">Books</p>
|
<p v-if="bookResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelBooks }}</p>
|
||||||
<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}`">
|
||||||
@ -28,7 +28,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="podcastResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">Podcasts</p>
|
<p v-if="podcastResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelPodcasts }}</p>
|
||||||
<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}`">
|
||||||
@ -37,7 +37,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">Authors</p>
|
<p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelAuthors }}</p>
|
||||||
<template v-for="item in authorResults">
|
<template v-for="item in authorResults">
|
||||||
<li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
<li :key="item.id" 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=authors.${$encode(item.id)}`">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(item.id)}`">
|
||||||
@ -46,7 +46,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="seriesResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">Series</p>
|
<p v-if="seriesResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelSeries }}</p>
|
||||||
<template v-for="item in seriesResults">
|
<template v-for="item in seriesResults">
|
||||||
<li :key="item.series.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
<li :key="item.series.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/series/${item.series.id}`">
|
<nuxt-link :to="`/library/${currentLibraryId}/series/${item.series.id}`">
|
||||||
@ -55,7 +55,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">Tags</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="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)}`">
|
||||||
|
@ -1,27 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
<div ref="wrapper" class="relative" v-click-outside="clickOutside">
|
||||||
<button type="button"
|
<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">
|
||||||
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-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul v-show="showMenu"
|
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" role="listbox" aria-labelledby="listbox-label">
|
||||||
class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
|
|
||||||
role="listbox" aria-labelledby="listbox-label">
|
|
||||||
<template v-for="item in selectItems">
|
<template v-for="item in selectItems">
|
||||||
<li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400"
|
<li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" :class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedOption(item.value)">
|
||||||
:class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option"
|
|
||||||
@click="clickedOption(item.value)">
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="font-normal ml-3 block truncate text-xs">{{ item.text }}</span>
|
<span class="font-normal ml-3 block truncate text-xs">{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="item.value === selected"
|
<span v-if="item.value === selected" class="text-yellow-400 absolute inset-y-0 right-0 flex items-center pr-4">
|
||||||
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-icons text-xl">{{ descending ? 'expand_more' : 'expand_less' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@ -37,55 +29,32 @@ export default {
|
|||||||
descending: Boolean
|
descending: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const bookItems = [
|
|
||||||
{
|
|
||||||
text: 'Title',
|
|
||||||
value: 'media.metadata.title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Author (First Last)',
|
|
||||||
value: 'media.metadata.authorName'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Author (Last, First)',
|
|
||||||
value: 'media.metadata.authorNameLF'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Published Year',
|
|
||||||
value: 'media.metadata.publishedYear'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Added At',
|
|
||||||
value: 'addedAt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Size',
|
|
||||||
value: 'size'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Duration',
|
|
||||||
value: 'media.duration'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'File Birthtime',
|
|
||||||
value: 'birthtimeMs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'File Modified',
|
|
||||||
value: 'mtimeMs'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const seriesItems = [...bookItems, {
|
|
||||||
text: 'Sequence',
|
|
||||||
value: 'sequence'
|
|
||||||
}]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showMenu: false,
|
showMenu: false
|
||||||
bookItems: bookItems,
|
}
|
||||||
seriesItems: seriesItems,
|
},
|
||||||
podcastItems: [
|
computed: {
|
||||||
|
selected: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedDesc: {
|
||||||
|
get() {
|
||||||
|
return this.descending
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('update:descending', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isPodcast() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast'
|
||||||
|
},
|
||||||
|
podcastItems() {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
text: 'Title',
|
text: 'Title',
|
||||||
value: 'media.metadata.title'
|
value: 'media.metadata.title'
|
||||||
@ -115,27 +84,55 @@ export default {
|
|||||||
value: 'mtimeMs'
|
value: 'mtimeMs'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
selected: {
|
|
||||||
get() {
|
|
||||||
return this.value
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.$emit('input', val)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
selectedDesc: {
|
bookItems() {
|
||||||
get() {
|
return [
|
||||||
return this.descending
|
{
|
||||||
},
|
text: this.$strings.LabelTitle,
|
||||||
set(val) {
|
value: 'media.metadata.title'
|
||||||
this.$emit('update:descending', val)
|
},
|
||||||
}
|
{
|
||||||
|
text: 'Author (First Last)',
|
||||||
|
value: 'media.metadata.authorName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Author (Last, First)',
|
||||||
|
value: 'media.metadata.authorNameLF'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Published Year',
|
||||||
|
value: 'media.metadata.publishedYear'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Added At',
|
||||||
|
value: 'addedAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Size',
|
||||||
|
value: 'size'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Duration',
|
||||||
|
value: 'media.duration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'File Birthtime',
|
||||||
|
value: 'birthtimeMs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'File Modified',
|
||||||
|
value: 'mtimeMs'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
isPodcast() {
|
seriesItems() {
|
||||||
return this.$store.getters['libraries/getCurrentLibraryMediaType'] == 'podcast'
|
return [
|
||||||
|
...this.bookItems,
|
||||||
|
{
|
||||||
|
text: 'Sequence',
|
||||||
|
value: 'sequence'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
selectItems() {
|
selectItems() {
|
||||||
let items = null
|
let items = null
|
||||||
@ -147,7 +144,7 @@ export default {
|
|||||||
items = this.bookItems
|
items = this.bookItems
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!items.some(i => i.value === this.selected)) {
|
if (!items.some((i) => i.value === this.selected)) {
|
||||||
this.selected = items[0].value
|
this.selected = items[0].value
|
||||||
this.selectedDesc = !this.defaultsToAsc(items[0].value)
|
this.selectedDesc = !this.defaultsToAsc(items[0].value)
|
||||||
}
|
}
|
||||||
@ -178,13 +175,7 @@ export default {
|
|||||||
this.$nextTick(() => this.$emit('change', val))
|
this.$nextTick(() => this.$emit('change', val))
|
||||||
},
|
},
|
||||||
defaultsToAsc(val) {
|
defaultsToAsc(val) {
|
||||||
return (
|
return val == 'media.metadata.title' || val == 'media.metadata.author' || val == 'media.metadata.authorName' || val == 'media.metadata.authorNameLF' || val == 'sequence'
|
||||||
val == 'media.metadata.title' ||
|
|
||||||
val == 'media.metadata.author' ||
|
|
||||||
val == 'media.metadata.authorName' ||
|
|
||||||
val == 'media.metadata.authorNameLF' ||
|
|
||||||
val == 'sequence'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,20 +125,6 @@ export default {
|
|||||||
processing: false,
|
processing: false,
|
||||||
newUser: {},
|
newUser: {},
|
||||||
isNew: true,
|
isNew: true,
|
||||||
accountTypes: [
|
|
||||||
{
|
|
||||||
text: 'Guest',
|
|
||||||
value: 'guest'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'User',
|
|
||||||
value: 'user'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Admin',
|
|
||||||
value: 'admin'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
tags: [],
|
tags: [],
|
||||||
loadingTags: false
|
loadingTags: false
|
||||||
}
|
}
|
||||||
@ -161,6 +147,22 @@ export default {
|
|||||||
this.$emit('input', val)
|
this.$emit('input', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
accountTypes() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAccountTypeGuest,
|
||||||
|
value: 'guest'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAccountTypeUser,
|
||||||
|
value: 'user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAccountTypeAdmin,
|
||||||
|
value: 'admin'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
@ -249,7 +251,7 @@ export default {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
this.$toast.error(`Failed to update account: ${data.error}`)
|
this.$toast.error(`${this.$strings.ToastAccountUpdateFailed}: ${data.error}`)
|
||||||
} else {
|
} else {
|
||||||
console.log('Account updated', data.user)
|
console.log('Account updated', data.user)
|
||||||
|
|
||||||
@ -258,7 +260,7 @@ export default {
|
|||||||
this.$store.commit('user/setUserToken', data.user.token)
|
this.$store.commit('user/setUserToken', data.user.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$toast.success('Account updated')
|
this.$toast.success(this.$strings.ToastAccountUpdateSuccess)
|
||||||
this.show = false
|
this.show = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
<modals-modal v-model="show" name="backup-scheduler" :width="700" :height="'unset'" :processing="processing">
|
<modals-modal v-model="show" name="backup-scheduler" :width="700" :height="'unset'" :processing="processing">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="font-book text-3xl text-white truncate">Set Backup Schedule</p>
|
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderSetBackupSchedule }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="show && newCronExpression" class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
<div v-if="show && newCronExpression" class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||||
<widgets-cron-expression-builder ref="expressionBuilder" v-model="newCronExpression" @input="expressionUpdated" />
|
<widgets-cron-expression-builder ref="expressionBuilder" v-model="newCronExpression" @input="expressionUpdated" />
|
||||||
|
|
||||||
<div class="flex items-center justify-end">
|
<div class="flex items-center justify-end">
|
||||||
<ui-btn :disabled="!isUpdated" @click="submit">{{ isUpdated ? 'Save Backup Schedule' : 'No update necessary' }}</ui-btn>
|
<ui-btn :disabled="!isUpdated" @click="submit">{{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdateNecessary }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
|
@ -7,40 +7,36 @@
|
|||||||
</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-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||||
<div v-if="show" class="w-full h-full">
|
<div v-if="show" class="w-full h-full py-4">
|
||||||
<div class="py-4 px-4">
|
|
||||||
<h1 class="text-2xl">Quick Match {{ selectedBookIds.length }} Books</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
|
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
|
||||||
<div class="flex px-8 items-center py-2">
|
<div class="flex px-8 items-center py-2">
|
||||||
<p class="pr-4">Provider</p>
|
<p class="pr-4">{{ $strings.LabelProvider }}</p>
|
||||||
<ui-dropdown v-model="options.provider" :items="providers" small />
|
<ui-dropdown v-model="options.provider" :items="providers" small />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-base px-8 py-2">Quick Match will attempt to add missing covers and metadata for the selected books. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.</p>
|
<p class="text-base px-8 py-2">{{ $strings.MessageBatchQuickMatchDescription }}</p>
|
||||||
<div class="flex px-8 items-end py-2">
|
<div class="flex px-8 items-end py-2">
|
||||||
<ui-toggle-switch v-model="options.overrideCover"/>
|
<ui-toggle-switch v-model="options.overrideCover" />
|
||||||
<ui-tooltip :text="tooltips.updateCovers">
|
<ui-tooltip :text="$strings.LabelUpdateCoverHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
Update Covers
|
{{ $strings.LabelUpdateCover }}
|
||||||
<span class="material-icons icon-text text-sm">info_outlined</span>
|
<span class="material-icons icon-text text-sm">info_outlined</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex px-8 items-end py-2">
|
<div class="flex px-8 items-end py-2">
|
||||||
<ui-toggle-switch v-model="options.overrideDetails"/>
|
<ui-toggle-switch v-model="options.overrideDetails" />
|
||||||
<ui-tooltip :text="tooltips.updateDetails">
|
<ui-tooltip :text="$strings.LabelUpdateDetailsHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
Update Details
|
{{ $strings.LabelUpdateDetails }}
|
||||||
<span class="material-icons icon-text text-sm">info_outlined</span>
|
<span class="material-icons icon-text text-sm">info_outlined</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 py-4 border-b border-white border-opacity-10 text-white text-opacity-80 border-t border-white border-opacity-5">
|
<div class="mt-4 pt-4 text-white text-opacity-80 border-t border-white border-opacity-5">
|
||||||
<div class="flex items-center px-4">
|
<div class="flex items-center px-4">
|
||||||
<ui-btn type="button" @click="show = false">Cancel</ui-btn>
|
<ui-btn type="button" @click="show = false">{{ $strings.ButtonCancel }}</ui-btn>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="success" @click="doBatchQuickMatch">Continue</ui-btn>
|
<ui-btn color="success" @click="doBatchQuickMatch">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,10 +56,6 @@ export default {
|
|||||||
overrideDetails: true,
|
overrideDetails: true,
|
||||||
overrideCover: true,
|
overrideCover: true,
|
||||||
overrideDefaults: true
|
overrideDefaults: true
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
updateCovers: 'Allow overwriting of existing covers for the selected books when a match is located.',
|
|
||||||
updateDetails: 'Allow overwriting of existing details for the selected books when a match is located.'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -84,7 +76,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return `${this.selectedBookIds.length} Items Selected`
|
return this.$getString('MessageItemsSelected', [this.selectedBookIds.length])
|
||||||
},
|
},
|
||||||
showBatchQuickMatchModal() {
|
showBatchQuickMatchModal() {
|
||||||
return this.$store.state.globals.showBatchQuickMatchModal
|
return this.$store.state.globals.showBatchQuickMatchModal
|
||||||
@ -107,7 +99,7 @@ export default {
|
|||||||
init() {
|
init() {
|
||||||
// If we don't have a set provider (first open of dialog) or we've switched library, set
|
// If we don't have a set provider (first open of dialog) or we've switched library, set
|
||||||
// the selected provider to the current library default provider
|
// the selected provider to the current library default provider
|
||||||
if (!this.options.provider || (this.options.lastUsedLibrary != this.currentLibraryId)) {
|
if (!this.options.provider || this.options.lastUsedLibrary != this.currentLibraryId) {
|
||||||
this.options.lastUsedLibrary = this.currentLibraryId
|
this.options.lastUsedLibrary = this.currentLibraryId
|
||||||
this.options.provider = this.libraryProvider
|
this.options.provider = this.libraryProvider
|
||||||
}
|
}
|
||||||
@ -115,7 +107,7 @@ export default {
|
|||||||
doBatchQuickMatch() {
|
doBatchQuickMatch() {
|
||||||
if (!this.selectedBookIds.length) return
|
if (!this.selectedBookIds.length) return
|
||||||
if (this.processing) return
|
if (this.processing) return
|
||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$store.commit('setProcessingBatch', true)
|
this.$store.commit('setProcessingBatch', true)
|
||||||
this.$axios
|
this.$axios
|
||||||
@ -125,10 +117,12 @@ export default {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.info('Batch quick match of ' + this.selectedBookIds.length + ' books started!')
|
this.$toast.info('Batch quick match of ' + this.selectedBookIds.length + ' books started!')
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
this.$toast.error('Batch quick match failed')
|
this.$toast.error('Batch quick match failed')
|
||||||
console.error('Failed to batch quick match', error)
|
console.error('Failed to batch quick match', error)
|
||||||
}).finally(() => {
|
})
|
||||||
|
.finally(() => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
this.show = false
|
this.show = false
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modals-modal v-model="show" name="bookmarks" :width="500" :height="'unset'">
|
<modals-modal v-model="show" name="bookmarks" :width="500" :height="'unset'">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="font-book text-3xl text-white truncate">Your Bookmarks</p>
|
<p class="font-book text-3xl text-white truncate">{{ $strings.LabelYourBookmarks }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div ref="container" class="w-full rounded-lg bg-bg 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">
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<modals-bookmarks-bookmark-item :key="bookmark.id" :highlight="currentTime === bookmark.time" :bookmark="bookmark" @click="clickBookmark" @update="submitUpdateBookmark" @delete="deleteBookmark" />
|
<modals-bookmarks-bookmark-item :key="bookmark.id" :highlight="currentTime === bookmark.time" :bookmark="bookmark" @click="clickBookmark" @update="submitUpdateBookmark" @delete="deleteBookmark" />
|
||||||
</template>
|
</template>
|
||||||
<div v-if="!bookmarks.length" class="flex h-32 items-center justify-center">
|
<div v-if="!bookmarks.length" class="flex h-32 items-center justify-center">
|
||||||
<p class="text-xl">No Bookmarks</p>
|
<p class="text-xl">{{ $strings.MessageNoBookmarks }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hideCreate" class="w-full h-px bg-white bg-opacity-10" />
|
<div v-if="!hideCreate" class="w-full h-px bg-white bg-opacity-10" />
|
||||||
<form v-if="!hideCreate" @submit.prevent="submitCreateBookmark">
|
<form v-if="!hideCreate" @submit.prevent="submitCreateBookmark">
|
||||||
@ -85,10 +85,10 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.$delete(`/api/me/item/${this.libraryItemId}/bookmark/${bm.time}`)
|
.$delete(`/api/me/item/${this.libraryItemId}/bookmark/${bm.time}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Bookmark removed')
|
this.$toast.success(this.$strings.ToastBookmarkRemoveSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error(`Failed to remove bookmark`)
|
this.$toast.error(this.$strings.ToastBookmarkRemoveFailed)
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
this.show = false
|
this.show = false
|
||||||
@ -101,10 +101,10 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.$patch(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
.$patch(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Bookmark updated')
|
this.$toast.success(this.$strings.ToastBookmarkUpdateSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error(`Failed to update bookmark`)
|
this.$toast.error(this.$strings.ToastBookmarkUpdateFailed)
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
this.show = false
|
this.show = false
|
||||||
@ -120,10 +120,10 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
.$post(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Bookmark added')
|
this.$toast.success(this.$strings.ToastBookmarkCreateSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error(`Failed to create bookmark`)
|
this.$toast.error(this.$strings.ToastBookmarkCreateFailed)
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -96,20 +96,19 @@ export default {
|
|||||||
this.newCollectionDescription = this.collection.description || ''
|
this.newCollectionDescription = this.collection.description || ''
|
||||||
},
|
},
|
||||||
removeClick() {
|
removeClick() {
|
||||||
if (confirm(`Are you sure you want to remove collection "${this.collectionName}"?`)) {
|
if (confirm(this.$getString('MessageConfirmRemoveCollection', [this.collectionName]))) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
var collectionName = this.collectionName
|
|
||||||
this.$axios
|
this.$axios
|
||||||
.$delete(`/api/collections/${this.collection.id}`)
|
.$delete(`/api/collections/${this.collection.id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.show = false
|
this.show = false
|
||||||
this.$toast.success(`Collection "${collectionName}" Removed`)
|
this.$toast.success(this.$strings.ToastCollectionRemoveSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to remove collection', error)
|
console.error('Failed to remove collection', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.error(`Failed to remove collection`)
|
this.$toast.error(this.$strings.ToastCollectionRemoveFailed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -133,12 +132,12 @@ export default {
|
|||||||
console.log('Collection Updated', collection)
|
console.log('Collection Updated', collection)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.show = false
|
this.show = false
|
||||||
this.$toast.success(`Collection "${collection.name}" Updated`)
|
this.$toast.success(this.$strings.ToastCollectionUpdateSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to update collection', error)
|
console.error('Failed to update collection', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.error(`Failed to update collection`)
|
this.$toast.error(this.$strings.ToastCollectionUpdateFailed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modals-modal v-model="show" name="listening-session-modal" :processing="processing" :width="700" :height="'unset'">
|
<modals-modal v-model="show" name="listening-session-modal" :processing="processing" :width="700" :height="'unset'">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="font-book text-3xl text-white truncate">Session {{ _session.id }}</p>
|
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderSession }} {{ _session.id }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
||||||
@ -15,90 +15,90 @@
|
|||||||
|
|
||||||
<div class="flex flex-wrap mb-4">
|
<div class="flex flex-wrap mb-4">
|
||||||
<div class="w-full md:w-2/3">
|
<div class="w-full md:w-2/3">
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2">Details</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2">{{ $strings.HeaderDetails }}</p>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Started At</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelStartedAt }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ $formatDate(_session.startedAt, 'MMMM do, yyyy HH:mm') }}
|
{{ $formatDate(_session.startedAt, 'MMMM do, yyyy HH:mm') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Updated At</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelUpdatedAt }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ $formatDate(_session.updatedAt, 'MMMM do, yyyy HH:mm') }}
|
{{ $formatDate(_session.updatedAt, 'MMMM do, yyyy HH:mm') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Listened for</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelTimeListened }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ $elapsedPrettyExtended(_session.timeListening) }}
|
{{ $elapsedPrettyExtended(_session.timeListening) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Start Time</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelStartTime }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ $secondsToTimestamp(_session.startTime) }}
|
{{ $secondsToTimestamp(_session.startTime) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Last Time</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLastTime }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ $secondsToTimestamp(_session.currentTime) }}
|
{{ $secondsToTimestamp(_session.currentTime) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">Item</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelItem }}</p>
|
||||||
<div v-if="_session.libraryId" class="flex items-center -mx-1 mb-1">
|
<div v-if="_session.libraryId" class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Library Id</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLibrary }} Id</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ _session.libraryId }}
|
{{ _session.libraryId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Library Item Id</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLibraryItem }} Id</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ _session.libraryItemId }}
|
{{ _session.libraryItemId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="_session.episodeId" class="flex items-center -mx-1 mb-1">
|
<div v-if="_session.episodeId" class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Episode Id</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelEpisode }} Id</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ _session.episodeId }}
|
{{ _session.episodeId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Media Type</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelMediaType }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ _session.mediaType }}
|
{{ _session.mediaType }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">Duration</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelDuration }}</div>
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
{{ $elapsedPretty(_session.duration) }}
|
{{ $elapsedPretty(_session.duration) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/3">
|
<div class="w-full md:w-1/3">
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">User</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
||||||
<p class="mb-1">{{ _session.userId }}</p>
|
<p class="mb-1">{{ _session.userId }}</p>
|
||||||
|
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">Media Player</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
|
||||||
<p class="mb-1">{{ playMethodName }}</p>
|
<p class="mb-1">{{ playMethodName }}</p>
|
||||||
<p class="mb-1">{{ _session.mediaPlayer }}</p>
|
<p class="mb-1">{{ _session.mediaPlayer }}</p>
|
||||||
|
|
||||||
<p v-if="hasDeviceInfo" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">Device</p>
|
<p v-if="hasDeviceInfo" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelDevice }}</p>
|
||||||
<p v-if="deviceInfo.ipAddress" class="mb-1">{{ deviceInfo.ipAddress }}</p>
|
<p v-if="deviceInfo.ipAddress" class="mb-1">{{ deviceInfo.ipAddress }}</p>
|
||||||
<p v-if="osDisplayName" class="mb-1">{{ osDisplayName }}</p>
|
<p v-if="osDisplayName" class="mb-1">{{ osDisplayName }}</p>
|
||||||
<p v-if="deviceInfo.browserName" class="mb-1">{{ deviceInfo.browserName }}</p>
|
<p v-if="deviceInfo.browserName" class="mb-1">{{ deviceInfo.browserName }}</p>
|
||||||
<p v-if="clientDisplayName" class="mb-1">{{ clientDisplayName }}</p>
|
<p v-if="clientDisplayName" class="mb-1">{{ clientDisplayName }}</p>
|
||||||
<p v-if="deviceInfo.sdkVersion" class="mb-1">SDK Version: {{ deviceInfo.sdkVersion }}</p>
|
<p v-if="deviceInfo.sdkVersion" class="mb-1">SDK {{ $strings.LabelVersion }}: {{ deviceInfo.sdkVersion }}</p>
|
||||||
<p v-if="deviceInfo.deviceType" class="mb-1">Type: {{ deviceInfo.deviceType }}</p>
|
<p v-if="deviceInfo.deviceType" class="mb-1">{{ $strings.LabelType }}: {{ deviceInfo.deviceType }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-btn small color="error" @click.stop="deleteSessionClick">Delete</ui-btn>
|
<ui-btn small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@ -156,7 +156,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
deleteSessionClick() {
|
deleteSessionClick() {
|
||||||
const payload = {
|
const payload = {
|
||||||
message: `Are you sure you want to delete this session?`,
|
message: this.$strings.MessageConfirmDeleteSession,
|
||||||
callback: (confirmed) => {
|
callback: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.deleteSession()
|
this.deleteSession()
|
||||||
@ -172,7 +172,7 @@ export default {
|
|||||||
.$delete(`/api/sessions/${this._session.id}`)
|
.$delete(`/api/sessions/${this._session.id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.success('Session deleted successfully')
|
this.$toast.success(this.$strings.ToastSessionDeleteSuccess)
|
||||||
this.$emit('removedSession')
|
this.$emit('removedSession')
|
||||||
this.show = false
|
this.show = false
|
||||||
})
|
})
|
||||||
@ -180,7 +180,7 @@ export default {
|
|||||||
this.processing = false
|
this.processing = false
|
||||||
console.error('Failed to delete session', error)
|
console.error('Failed to delete session', error)
|
||||||
var errMsg = error.response ? error.response.data || '' : ''
|
var errMsg = error.response ? error.response.data || '' : ''
|
||||||
this.$toast.error(errMsg || 'Failed to delete session')
|
this.$toast.error(errMsg || this.$strings.ToastSessionDeleteFailed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modals-modal v-model="show" name="sleep-timer" :width="350" :height="'unset'">
|
<modals-modal v-model="show" name="sleep-timer" :width="350" :height="'unset'">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||||
<p class="font-book text-3xl text-white truncate pointer-events-none">Sleep Timer</p>
|
<p class="font-book text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderSleepTimer }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<span class="pl-1 text-base font-mono">30m</span>
|
<span class="pl-1 text-base font-mono">30m</span>
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
<ui-btn class="w-full" @click="$emit('cancel')">Cancel</ui-btn>
|
<ui-btn class="w-full" @click="$emit('cancel')">{{ $strings.ButtonCancel }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<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-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||||
<div v-if="show" class="w-full h-full">
|
<div v-if="show" class="w-full h-full">
|
||||||
<div class="py-4 px-4">
|
<div class="py-4 px-4">
|
||||||
<h1 v-if="!showBatchUserCollectionModal" class="text-2xl">Add to Collection</h1>
|
<h1 v-if="!showBatchUserCollectionModal" class="text-2xl">{{ $strings.LabelAddToCollection }}</h1>
|
||||||
<h1 v-else class="text-2xl">Add {{ selectedBookIds.length }} Books to Collection</h1>
|
<h1 v-else class="text-2xl">{{ $getString('LabelAddToCollectionBatch', [selectedBookIds.length]) }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
|
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
|
||||||
<transition-group name="list-complete" tag="div">
|
<transition-group name="list-complete" tag="div">
|
||||||
@ -20,15 +20,15 @@
|
|||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!collections.length" class="flex h-32 items-center justify-center">
|
<div v-if="!collections.length" class="flex h-32 items-center justify-center">
|
||||||
<p class="text-xl">No Collections</p>
|
<p class="text-xl">{{ $strings.MessageNoCollections }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-px bg-white bg-opacity-10" />
|
<div class="w-full h-px bg-white bg-opacity-10" />
|
||||||
<form @submit.prevent="submitCreateCollection">
|
<form @submit.prevent="submitCreateCollection">
|
||||||
<div class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80">
|
<div class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80">
|
||||||
<div class="flex-grow px-2">
|
<div class="flex-grow px-2">
|
||||||
<ui-text-input v-model="newCollectionName" placeholder="New Collection" class="w-full" />
|
<ui-text-input v-model="newCollectionName" :placeholder="$strings.PlaceholderNewCollection" class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<ui-btn type="submit" color="success" :padding-x="4" class="h-10">Create</ui-btn>
|
<ui-btn type="submit" color="success" :padding-x="4" class="h-10">{{ $strings.ButtonCreate }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -65,7 +65,7 @@ export default {
|
|||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
if (this.showBatchUserCollectionModal) {
|
if (this.showBatchUserCollectionModal) {
|
||||||
return `${this.selectedBookIds.length} Items Selected`
|
return this.$getString('MessageItemsSelected', [this.selectedBookIds.length])
|
||||||
}
|
}
|
||||||
return this.selectedLibraryItem ? this.selectedLibraryItem.media.metadata.title : ''
|
return this.selectedLibraryItem ? this.selectedLibraryItem.media.metadata.title : ''
|
||||||
},
|
},
|
||||||
@ -124,12 +124,12 @@ export default {
|
|||||||
.$post(`/api/collections/${collection.id}/batch/remove`, { books: this.selectedBookIds })
|
.$post(`/api/collections/${collection.id}/batch/remove`, { books: this.selectedBookIds })
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Books removed from collection`, updatedCollection)
|
console.log(`Books removed from collection`, updatedCollection)
|
||||||
this.$toast.success('Books removed from collection')
|
this.$toast.success(this.$strings.ToastCollectionItemsRemoveSuccess)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to remove books from collection', error)
|
console.error('Failed to remove books from collection', error)
|
||||||
this.$toast.error('Failed to remove books from collection')
|
this.$toast.error(this.$strings.ToastCollectionItemsRemoveFailed)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -138,12 +138,12 @@ export default {
|
|||||||
.$delete(`/api/collections/${collection.id}/book/${this.selectedLibraryItemId}`)
|
.$delete(`/api/collections/${collection.id}/book/${this.selectedLibraryItemId}`)
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Book removed from collection`, updatedCollection)
|
console.log(`Book removed from collection`, updatedCollection)
|
||||||
this.$toast.success('Book removed from collection')
|
this.$toast.success(this.$strings.ToastCollectionItemsRemoveSuccess)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to remove book from collection', error)
|
console.error('Failed to remove book from collection', error)
|
||||||
this.$toast.error('Failed to remove book from collection')
|
this.$toast.error(this.$strings.ToastCollectionItemsRemoveFailed)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -19,23 +19,23 @@
|
|||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-3/4 p-2">
|
<div class="w-3/4 p-2">
|
||||||
<ui-text-input-with-label v-model="authorCopy.name" :disabled="processing" label="Name" />
|
<ui-text-input-with-label v-model="authorCopy.name" :disabled="processing" :label="$strings.LabelName" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow p-2">
|
<div class="flex-grow p-2">
|
||||||
<ui-text-input-with-label v-model="authorCopy.asin" :disabled="processing" label="ASIN" />
|
<ui-text-input-with-label v-model="authorCopy.asin" :disabled="processing" label="ASIN" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<ui-text-input-with-label v-model="authorCopy.imagePath" :disabled="processing" label="Photo Path/URL" />
|
<ui-text-input-with-label v-model="authorCopy.imagePath" :disabled="processing" :label="$strings.LabelPhotoPathURL" />
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<ui-textarea-with-label v-model="authorCopy.description" :disabled="processing" label="Description" :rows="8" />
|
<ui-textarea-with-label v-model="authorCopy.description" :disabled="processing" :label="$strings.LabelDescription" :rows="8" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex pt-2 px-2">
|
<div class="flex pt-2 px-2">
|
||||||
<ui-btn type="button" @click="searchAuthor">Quick Match</ui-btn>
|
<ui-btn type="button" @click="searchAuthor">{{ $strings.ButtonQuickMatch }}</ui-btn>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn type="submit">Submit</ui-btn>
|
<ui-btn type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -84,7 +84,7 @@ export default {
|
|||||||
return this.author.id
|
return this.author.id
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return 'Edit Author'
|
return this.$strings.HeaderUpdateAuthor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -103,23 +103,23 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!Object.keys(updatePayload).length) {
|
if (!Object.keys(updatePayload).length) {
|
||||||
this.$toast.info('No updates are necessary')
|
this.$toast.info(this.$strings.MessageNoUpdateNecessary)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.processing = true
|
this.processing = true
|
||||||
var result = await this.$axios.$patch(`/api/authors/${this.authorId}`, updatePayload).catch((error) => {
|
var result = await this.$axios.$patch(`/api/authors/${this.authorId}`, updatePayload).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error('Failed to update author')
|
this.$toast.error(this.$strings.ToastAuthorUpdateFailed)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
this.$toast.success('Author updated')
|
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
|
||||||
this.show = false
|
this.show = false
|
||||||
} else if (result.merged) {
|
} else if (result.merged) {
|
||||||
this.$toast.success('Author merged')
|
this.$toast.success(this.$strings.ToastAuthorUpdateMerged)
|
||||||
this.show = false
|
this.show = false
|
||||||
} else this.$toast.info('No updates were needed')
|
} else this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
}
|
}
|
||||||
this.processing = false
|
this.processing = false
|
||||||
},
|
},
|
||||||
@ -131,11 +131,11 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
var result = await this.$axios.$patch(`/api/authors/${this.authorId}`, updatePayload).catch((error) => {
|
var result = await this.$axios.$patch(`/api/authors/${this.authorId}`, updatePayload).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error('Failed to remove image')
|
this.$toast.error(this.$strings.ToastAuthorImageRemoveFailed)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
if (result && result.updated) {
|
if (result && result.updated) {
|
||||||
this.$toast.success('Author image removed')
|
this.$toast.success(this.$strings.ToastAuthorImageRemoveSuccess)
|
||||||
}
|
}
|
||||||
this.processing = false
|
this.processing = false
|
||||||
},
|
},
|
||||||
@ -157,8 +157,8 @@ export default {
|
|||||||
if (!response) {
|
if (!response) {
|
||||||
this.$toast.error('Author not found')
|
this.$toast.error('Author not found')
|
||||||
} else if (response.updated) {
|
} else if (response.updated) {
|
||||||
if (response.author.imagePath) this.$toast.success('Author was updated')
|
if (response.author.imagePath) this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
|
||||||
else this.$toast.success('Author was updated (no image found)')
|
else this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info('No updates were made for Author')
|
this.$toast.info('No updates were made for Author')
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ export default {
|
|||||||
this.$toast.success('Item details updated')
|
this.$toast.success('Item details updated')
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info('No updates were necessary')
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -2,29 +2,29 @@
|
|||||||
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
||||||
<div class="w-full mb-4">
|
<div class="w-full mb-4">
|
||||||
<div v-if="userIsAdminOrUp" class="flex items-end justify-end mb-4">
|
<div v-if="userIsAdminOrUp" class="flex items-end justify-end mb-4">
|
||||||
<ui-text-input-with-label ref="lastCheckInput" v-model="lastEpisodeCheckInput" :disabled="checkingNewEpisodes" type="datetime-local" label="Look for new episodes after this date" class="max-w-xs mr-2" />
|
<ui-text-input-with-label ref="lastCheckInput" v-model="lastEpisodeCheckInput" :disabled="checkingNewEpisodes" type="datetime-local" :label="$strings.LabelLookForNewEpisodesAfterDate" class="max-w-xs mr-2" />
|
||||||
<ui-text-input-with-label ref="maxEpisodesInput" v-model="maxEpisodesToDownload" :disabled="checkingNewEpisodes" type="number" label="Max episodes" class="w-16 mr-2" input-class="h-10">
|
<ui-text-input-with-label ref="maxEpisodesInput" v-model="maxEpisodesToDownload" :disabled="checkingNewEpisodes" type="number" :label="$strings.LabelLimit" class="w-16 mr-2" input-class="h-10">
|
||||||
<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 }">Limit</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-icons text-base">info_outlined</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</ui-text-input-with-label>
|
</ui-text-input-with-label>
|
||||||
<ui-btn :loading="checkingNewEpisodes" @click="checkForNewEpisodes">Check & Download New Episodes</ui-btn>
|
<ui-btn :loading="checkingNewEpisodes" @click="checkForNewEpisodes">{{ $strings.ButtonCheckAndDownloadNewEpisodes }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="episodes.length" class="w-full p-4 bg-primary">
|
<div v-if="episodes.length" class="w-full p-4 bg-primary">
|
||||||
<p>Podcast Episodes</p>
|
<p>{{ $strings.HeaderEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!episodes.length" class="flex my-4 text-center justify-center text-xl">No Episodes</div>
|
<div v-if="!episodes.length" class="flex my-4 text-center justify-center text-xl">{{ $strings.MessageNoEpisodes }}</div>
|
||||||
<table v-else class="text-sm tracksTable">
|
<table v-else class="text-sm tracksTable">
|
||||||
<tr class="font-book">
|
<tr class="font-book">
|
||||||
<th class="text-left">Sort #</th>
|
<th class="text-left">Sort #</th>
|
||||||
<th class="text-left whitespace-nowrap">Episode #</th>
|
<th class="text-left whitespace-nowrap">{{ $strings.LabelEpisode }}</th>
|
||||||
<th class="text-left">Title</th>
|
<th class="text-left">{{ $strings.EpisodeTitle }}</th>
|
||||||
<th class="text-center w-28">Duration</th>
|
<th class="text-center w-28">{{ $strings.EpisodeDuration }}</th>
|
||||||
<th class="text-center w-28">Size</th>
|
<th class="text-center w-28">{{ $strings.EpisodeSize }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="episode in episodes" :key="episode.id">
|
<tr v-for="episode in episodes" :key="episode.id">
|
||||||
<td class="text-left">
|
<td class="text-left">
|
||||||
|
@ -474,9 +474,9 @@ export default {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if (success) {
|
if (success) {
|
||||||
this.$toast.success('Item Cover Updated')
|
this.$toast.success(this.$strings.ToastItemCoverUpdateSuccess)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Item Cover Failed to Update')
|
this.$toast.error(this.$strings.ToastItemCoverUpdateFailed)
|
||||||
}
|
}
|
||||||
console.log('Updated cover')
|
console.log('Updated cover')
|
||||||
delete updatePayload.metadata.cover
|
delete updatePayload.metadata.cover
|
||||||
@ -490,14 +490,14 @@ export default {
|
|||||||
})
|
})
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
if (updateResult.updated) {
|
if (updateResult.updated) {
|
||||||
this.$toast.success('Item details updated')
|
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info('No detail updates were necessary')
|
this.$toast.info(this.$strings.ToastItemDetailsUpdateUnneeded)
|
||||||
}
|
}
|
||||||
this.clearSelectedMatch()
|
this.clearSelectedMatch()
|
||||||
this.$emit('selectTab', 'details')
|
this.$emit('selectTab', 'details')
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Item Details Failed to Update')
|
this.$toast.error(this.$strings.ToastItemDetailsUpdateFailed)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.clearSelectedMatch()
|
this.clearSelectedMatch()
|
||||||
|
@ -153,7 +153,7 @@ export default {
|
|||||||
this.$toast.success('Item details updated')
|
this.$toast.success('Item details updated')
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info('No updates were necessary')
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="!mediaTracks.length" class="text-lg text-center my-8">No audio tracks</p>
|
<p v-if="!mediaTracks.length" class="text-lg text-center my-8">{{ $strings.MessageNoAudioTracks }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -53,20 +53,22 @@ export default {
|
|||||||
folders: [],
|
folders: [],
|
||||||
showDirectoryPicker: false,
|
showDirectoryPicker: false,
|
||||||
newFolderPath: '',
|
newFolderPath: '',
|
||||||
mediaType: null,
|
mediaType: null
|
||||||
mediaTypes: [
|
|
||||||
{
|
|
||||||
value: 'book',
|
|
||||||
text: 'Books'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'podcast',
|
|
||||||
text: 'Podcasts'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
mediaTypes() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: 'book',
|
||||||
|
text: this.$strings.LabelBooks
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'podcast',
|
||||||
|
text: this.$strings.LabelPodcasts
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
folderPaths() {
|
folderPaths() {
|
||||||
return this.folders.map((f) => f.fullPath)
|
return this.folders.map((f) => f.fullPath)
|
||||||
},
|
},
|
||||||
|
@ -192,14 +192,14 @@ export default {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.show = false
|
this.show = false
|
||||||
this.$toast.success(`Library "${res.name}" updated successfully`)
|
this.$toast.success(this.$getString('ToastLibraryUpdateSuccess', [res.name]))
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
if (error.response && error.response.data) {
|
if (error.response && error.response.data) {
|
||||||
this.$toast.error(error.response.data)
|
this.$toast.error(error.response.data)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Failed to update library')
|
this.$toast.error(this.$strings.ToastLibraryUpdateFailed)
|
||||||
}
|
}
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
@ -211,7 +211,7 @@ export default {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.show = false
|
this.show = false
|
||||||
this.$toast.success(`Library "${res.name}" created successfully`)
|
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)
|
console.log('Setting initially library id', res.id)
|
||||||
// First library added
|
// First library added
|
||||||
@ -223,7 +223,7 @@ export default {
|
|||||||
if (error.response && error.response.data) {
|
if (error.response && error.response.data) {
|
||||||
this.$toast.error(error.response.data)
|
this.$toast.error(error.response.data)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Failed to create library')
|
this.$toast.error(this.$strings.ToastLibraryCreateFailed)
|
||||||
}
|
}
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
|
@ -26,12 +26,12 @@ export default {
|
|||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
id: 'details',
|
id: 'details',
|
||||||
title: 'Details',
|
title: this.$strings.HeaderDetails,
|
||||||
component: 'modals-podcast-tabs-episode-details'
|
component: 'modals-podcast-tabs-episode-details'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'match',
|
id: 'match',
|
||||||
title: 'Match',
|
title: this.$strings.HeaderMatch,
|
||||||
component: 'modals-podcast-tabs-episode-match'
|
component: 'modals-podcast-tabs-episode-match'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -7,45 +7,45 @@
|
|||||||
</template>
|
</template>
|
||||||
<div ref="wrapper" id="podcast-wrapper" class="p-2 md:p-8 w-full text-sm py-2 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-x-hidden overflow-y-auto" style="max-height: 80vh">
|
<div ref="wrapper" id="podcast-wrapper" class="p-2 md:p-8 w-full text-sm py-2 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-x-hidden overflow-y-auto" style="max-height: 80vh">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<p class="text-lg font-semibold mb-2 px-2">Details</p>
|
<p class="text-lg font-semibold mb-2 px-2">{{ $strings.HeaderDetails }}</p>
|
||||||
|
|
||||||
<div v-if="podcast.imageUrl" class="p-2 w-full">
|
<div v-if="podcast.imageUrl" class="p-2 w-full">
|
||||||
<img :src="podcast.imageUrl" class="h-16 w-16 object-contain" />
|
<img :src="podcast.imageUrl" class="h-16 w-16 object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ui-text-input-with-label v-model="podcast.title" label="Title" @input="titleUpdated" />
|
<ui-text-input-with-label v-model="podcast.title" :label="$strings.LabelTitle" @input="titleUpdated" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ui-text-input-with-label v-model="podcast.author" label="Author" />
|
<ui-text-input-with-label v-model="podcast.author" :label="$strings.LabelAuthor" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ui-text-input-with-label v-model="podcast.feedUrl" label="Feed URL" readonly />
|
<ui-text-input-with-label v-model="podcast.feedUrl" :label="$strings.LabelFeedURL" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ui-multi-select v-model="podcast.genres" :items="podcast.genres" label="Genres" />
|
<ui-multi-select v-model="podcast.genres" :items="podcast.genres" :label="$strings.LabelGenres" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2 w-full">
|
<div class="p-2 w-full">
|
||||||
<ui-textarea-with-label v-model="podcast.description" label="Description" :rows="3" />
|
<ui-textarea-with-label v-model="podcast.description" :label="$strings.LabelDescription" :rows="3" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="processing" label="Folder" @input="folderUpdated" />
|
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="processing" :label="$strings.LabelFolder" @input="folderUpdated" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ui-text-input-with-label v-model="fullPath" label="Podcast Path" input-class="h-10" readonly />
|
<ui-text-input-with-label v-model="fullPath" :label="`${$strings.LabelPodcast} ${$strings.LabelPath}`" input-class="h-10" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center py-4 px-2">
|
<div class="flex items-center py-4 px-2">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<div class="px-4">
|
<div class="px-4">
|
||||||
<ui-checkbox v-model="podcast.autoDownloadEpisodes" label="Auto Download Episodes" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-sm md:text-base font-semibold" />
|
<ui-checkbox v-model="podcast.autoDownloadEpisodes" :label="$strings.LabelAutoDownloadEpisodes" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-sm md:text-base font-semibold" />
|
||||||
</div>
|
</div>
|
||||||
<ui-btn color="success" @click="submit">Add Podcast</ui-btn>
|
<ui-btn color="success" @click="submit">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@ -182,12 +182,12 @@ export default {
|
|||||||
.$post('/api/podcasts', podcastPayload)
|
.$post('/api/podcasts', podcastPayload)
|
||||||
.then((libraryItem) => {
|
.then((libraryItem) => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.success('Podcast created successfully')
|
this.$toast.success(this.$strings.ToastPodcastCreateSuccess)
|
||||||
this.show = false
|
this.show = false
|
||||||
this.$router.push(`/item/${libraryItem.id}`)
|
this.$router.push(`/item/${libraryItem.id}`)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to create podcast'
|
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastPodcastCreateFailed
|
||||||
console.error('Failed to create podcast', error)
|
console.error('Failed to create podcast', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.error(errorMsg)
|
this.$toast.error(errorMsg)
|
||||||
|
@ -9,14 +9,14 @@
|
|||||||
<div class="w-full p-4">
|
<div class="w-full p-4">
|
||||||
<div class="flex items-center -mx-2 mb-2">
|
<div class="flex items-center -mx-2 mb-2">
|
||||||
<div class="w-full md:w-2/3 p-2">
|
<div class="w-full md:w-2/3 p-2">
|
||||||
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="processing" label="Folder" />
|
<ui-dropdown v-model="selectedFolderId" :items="folderItems" :disabled="processing" :label="$strings.LabelFolder" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/3 p-2 pt-6">
|
<div class="w-full md:w-1/3 p-2 pt-6">
|
||||||
<ui-checkbox v-model="autoDownloadEpisodes" label="Auto Download New Episodes" checkbox-bg="primary" border-color="gray-600" label-class="text-sm font-semibold pl-2" />
|
<ui-checkbox v-model="autoDownloadEpisodes" :label="$strings.LabelAutoDownloadEpisodes" checkbox-bg="primary" border-color="gray-600" label-class="text-sm font-semibold pl-2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-lg font-semibold mb-2">Podcasts to Add</p>
|
<p class="text-lg font-semibold mb-2">{{ $strings.HeaderPodcastsToAdd }}</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 feedMetadata">
|
||||||
@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center py-4">
|
<div class="flex items-center py-4">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="success" @click="submit">Add Podcasts</ui-btn>
|
<ui-btn color="success" @click="submit">{{ $strings.ButtonAddPodcasts }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@ -141,10 +141,10 @@ export default {
|
|||||||
await this.$axios
|
await this.$axios
|
||||||
.$post('/api/podcasts', podcastPayload)
|
.$post('/api/podcasts', podcastPayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success(`${podcastPayload.media.metadata.title}: Podcast created successfully`)
|
this.$toast.success(`${podcastPayload.media.metadata.title}: ${this.$strings.ToastPodcastCreateSuccess}`)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to create podcast'
|
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastPodcastCreateFailed
|
||||||
console.error('Failed to create podcast', podcastPayload, error)
|
console.error('Failed to create podcast', podcastPayload, error)
|
||||||
this.$toast.error(`${podcastPayload.media.metadata.title}: ${errorMsg}`)
|
this.$toast.error(`${podcastPayload.media.metadata.title}: ${errorMsg}`)
|
||||||
})
|
})
|
||||||
|
@ -8,14 +8,13 @@
|
|||||||
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p v-if="episode" class="text-lg text-gray-200 mb-4">
|
<p v-if="episode" class="text-lg text-gray-200 mb-4">
|
||||||
Are you sure you want to remove episode<br /><span class="text-base">{{ episodeTitle }}</span
|
{{ $getString('MessageConfirmRemoveEpisode', [episodeTitle]) }}
|
||||||
>?
|
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-lg text-gray-200 mb-4">Are you sure you want to remove {{ episodes.length }} episodes?</p>
|
<p v-else class="text-lg text-gray-200 mb-4">{{ $getString('MessageConfirmRemoveEpisodes', [episodes.length]) }}</p>
|
||||||
<p class="text-xs font-semibold text-warning text-opacity-90">Note: This does not delete the audio file unless toggling "Hard delete file"</p>
|
<p class="text-xs font-semibold text-warning text-opacity-90">Note: This does not delete the audio file unless toggling "Hard delete file"</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center pt-4">
|
<div class="flex justify-between items-center pt-4">
|
||||||
<ui-checkbox v-model="hardDeleteFile" label="Hard delete file" check-color="error" checkbox-bg="bg" small label-class="text-base text-gray-200 pl-3" />
|
<ui-checkbox v-model="hardDeleteFile" :label="$strings.LabelHardDeleteFile" check-color="error" checkbox-bg="bg" small label-class="text-base text-gray-200 pl-3" />
|
||||||
|
|
||||||
<ui-btn @click="submit">{{ btnText }}</ui-btn>
|
<ui-btn @click="submit">{{ btnText }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
@ -61,12 +60,11 @@ export default {
|
|||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
if (this.episodes.length > 1) return `Remove ${this.episodes.length} episodes`
|
if (this.episodes.length > 1) return this.$getString('HeaderRemoveEpisodes', [this.episodes.length])
|
||||||
return 'Remove Episode'
|
return this.$strings.HeaderRemoveEpisode
|
||||||
},
|
},
|
||||||
btnText() {
|
btnText() {
|
||||||
if (this.episodes.length > 1) return this.hardDeleteFile ? `Delete ${this.episodes.length} episodes` : `Remove ${this.episodes.length} episodes`
|
return this.hardDeleteFile ? this.$strings.ButtonDelete : this.$strings.ButtonRemove
|
||||||
return this.hardDeleteFile ? 'Delete episode' : 'Remove episode'
|
|
||||||
},
|
},
|
||||||
episodeTitle() {
|
episodeTitle() {
|
||||||
return this.episode ? this.episode.title : null
|
return this.episode ? this.episode.title : null
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modals-modal v-model="show" name="podcast-episode-view-modal" :width="800" :height="'unset'" :processing="processing">
|
<modals-modal v-model="show" name="podcast-episode-view-modal" :width="800" :height="'unset'" :processing="processing">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="font-book text-3xl text-white truncate">Episode</p>
|
<p class="font-book text-3xl text-white truncate">{{ $strings.LabelEpisode }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div ref="wrapper" class="p-4 w-full text-sm rounded-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-lg bg-bg shadow-lg border border-black-300 relative overflow-y-auto" style="max-height: 80vh">
|
||||||
@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="text-lg font-semibold mb-6">{{ title }}</p>
|
<p class="text-lg font-semibold mb-6">{{ title }}</p>
|
||||||
<div v-if="description" class="default-style" v-html="description" />
|
<div v-if="description" class="default-style" v-html="description" />
|
||||||
<p v-else class="mb-2">No description</p>
|
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,29 +2,29 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="w-1/5 p-1">
|
<div class="w-1/5 p-1">
|
||||||
<ui-text-input-with-label v-model="newEpisode.season" label="Season" />
|
<ui-text-input-with-label v-model="newEpisode.season" :label="$strings.LabelSeason" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/5 p-1">
|
<div class="w-1/5 p-1">
|
||||||
<ui-text-input-with-label v-model="newEpisode.episode" label="Episode" />
|
<ui-text-input-with-label v-model="newEpisode.episode" :label="$strings.LabelEpisode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/5 p-1">
|
<div class="w-1/5 p-1">
|
||||||
<ui-text-input-with-label v-model="newEpisode.episodeType" label="Episode Type" />
|
<ui-text-input-with-label v-model="newEpisode.episodeType" :label="$strings.LabelEpisodeType" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/5 p-1">
|
<div class="w-2/5 p-1">
|
||||||
<ui-text-input-with-label v-model="pubDateInput" @input="updatePubDate" type="datetime-local" label="Pub Date" />
|
<ui-text-input-with-label v-model="pubDateInput" @input="updatePubDate" type="datetime-local" :label="$strings.LabelPubDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full p-1">
|
<div class="w-full p-1">
|
||||||
<ui-text-input-with-label v-model="newEpisode.title" label="Title" />
|
<ui-text-input-with-label v-model="newEpisode.title" :label="$strings.LabelTitle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full p-1">
|
<div class="w-full p-1">
|
||||||
<ui-textarea-with-label v-model="newEpisode.subtitle" label="Subtitle" :rows="3" />
|
<ui-textarea-with-label v-model="newEpisode.subtitle" :label="$strings.LabelSubtitle" :rows="3" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full p-1 default-style">
|
<div class="w-full p-1 default-style">
|
||||||
<ui-rich-text-editor label="Description" v-model="newEpisode.description" />
|
<ui-rich-text-editor :label="$strings.LabelDescription" v-model="newEpisode.description" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-end pt-4">
|
<div class="flex items-center justify-end pt-4">
|
||||||
<ui-btn @click="submit">Submit</ui-btn>
|
<ui-btn @click="submit">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="enclosureUrl" class="py-4">
|
<div v-if="enclosureUrl" class="py-4">
|
||||||
<p class="text-xs text-gray-300 font-semibold">Episode URL from RSS feed</p>
|
<p class="text-xs text-gray-300 font-semibold">Episode URL from RSS feed</p>
|
||||||
|
@ -2,18 +2,18 @@
|
|||||||
<div style="min-height: 200px">
|
<div style="min-height: 200px">
|
||||||
<template v-if="!podcastFeedUrl">
|
<template v-if="!podcastFeedUrl">
|
||||||
<div class="py-8">
|
<div class="py-8">
|
||||||
<widgets-alert type="error">Podcast has no RSS feed url to use for matching</widgets-alert>
|
<widgets-alert type="error">{{ $strings.MessagePodcastHasNoRSSFeedForMatching }}</widgets-alert>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<form @submit.prevent="submitForm">
|
<form @submit.prevent="submitForm">
|
||||||
<div class="flex mb-2">
|
<div class="flex mb-2">
|
||||||
<ui-text-input-with-label v-model="episodeTitle" :disabled="isProcessing" label="Episode Title" class="pr-1" />
|
<ui-text-input-with-label v-model="episodeTitle" :disabled="isProcessing" :label="$strings.LabelEpisodeTitle" class="pr-1" />
|
||||||
<ui-btn class="mt-5 ml-1" :loading="isProcessing" type="submit">Search</ui-btn>
|
<ui-btn class="mt-5 ml-1" :loading="isProcessing" type="submit">{{ $strings.ButtonSearch }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div v-if="!isProcessing && searchedTitle && !episodesFound.length" class="w-full py-8">
|
<div v-if="!isProcessing && searchedTitle && !episodesFound.length" class="w-full py-8">
|
||||||
<p class="text-center text-lg">No episode matches found</p>
|
<p class="text-center text-lg">{{ $strings.MessageNoEpisodeMatchesFound }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(episode, index) in episodesFound" :key="index" class="w-full py-4 border-b border-white border-opacity-5 hover:bg-gray-300 hover:bg-opacity-10 cursor-pointer px-2" @click.stop="selectEpisode(episode)">
|
<div v-for="(episode, index) in episodesFound" :key="index" class="w-full py-4 border-b border-white border-opacity-5 hover:bg-gray-300 hover:bg-opacity-10 cursor-pointer px-2" @click.stop="selectEpisode(episode)">
|
||||||
<p v-if="episode.episode" class="font-semibold text-gray-200">#{{ episode.episode }}</p>
|
<p v-if="episode.episode" class="font-semibold text-gray-200">#{{ episode.episode }}</p>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||||
<div v-if="currentFeedUrl" class="w-full">
|
<div v-if="currentFeedUrl" class="w-full">
|
||||||
<p class="text-lg font-semibold mb-4">Podcast RSS Feed is Open</p>
|
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
|
||||||
|
|
||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<ui-text-input v-model="currentFeedUrl" readonly />
|
<ui-text-input v-model="currentFeedUrl" readonly />
|
||||||
@ -16,20 +16,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full">
|
<div v-else class="w-full">
|
||||||
<p class="text-lg font-semibold mb-4">Open RSS Feed</p>
|
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderOpenRSSFeed }}</p>
|
||||||
|
|
||||||
<div class="w-full relative mb-2">
|
<div class="w-full relative mb-2">
|
||||||
<ui-text-input-with-label v-model="newFeedSlug" label="RSS Feed Slug" />
|
<ui-text-input-with-label v-model="newFeedSlug" :label="$strings.LabelRSSFeedSlug" />
|
||||||
<p class="text-xs text-gray-400 py-0.5 px-1">Feed will be {{ demoFeedUrl }}</p>
|
<p class="text-xs text-gray-400 py-0.5 px-1">{{ $getString('MessageFeedURLWillBe', [demoFeedUrl]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="isHttp" class="w-full pt-2 text-warning text-xs">Warning: Most podcast apps will require the RSS feed URL is using HTTPS</p>
|
<p v-if="isHttp" class="w-full pt-2 text-warning text-xs">{{ $strings.NoteRSSFeedPodcastAppsHttps }}</p>
|
||||||
<p v-if="hasEpisodesWithoutPubDate" class="w-full pt-2 text-warning text-xs">Warning: 1 or more of your episodes do not have a Pub Date. Some podcast apps require this.</p>
|
<p v-if="hasEpisodesWithoutPubDate" class="w-full pt-2 text-warning text-xs">{{ $strings.NoteRSSFeedPodcastAppsPubDate }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="userIsAdminOrUp" class="flex items-center pt-6">
|
<div v-show="userIsAdminOrUp" class="flex items-center pt-6">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn v-if="currentFeedUrl" color="error" small @click="closeFeed">Close RSS Feed</ui-btn>
|
<ui-btn v-if="currentFeedUrl" color="error" small @click="closeFeed">{{ $strings.ButtonCloseFeed }}</ui-btn>
|
||||||
<ui-btn v-else color="success" small @click="openFeed">Open RSS Feed</ui-btn>
|
<ui-btn v-else color="success" small @click="openFeed">{{ $strings.ButtonOpenFeed }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@ -144,14 +144,14 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/items/${this.libraryItem.id}/close-feed`)
|
.$post(`/api/items/${this.libraryItem.id}/close-feed`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('RSS Feed Closed')
|
this.$toast.success(this.$strings.ToastRSSFeedCloseSuccess)
|
||||||
this.show = false
|
this.show = false
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to close RSS feed', error)
|
console.error('Failed to close RSS feed', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.error()
|
this.$toast.error(this.$strings.ToastRSSFeedCloseFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<span class="material-icons text-2xl sm:text-3xl">format_list_bulleted</span>
|
<span class="material-icons text-2xl sm:text-3xl">format_list_bulleted</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? 'Use full track' : 'Use chapter track'">
|
<ui-tooltip v-if="chapters.length" direction="top" :text="useChapterTrack ? $strings.LabelUseFullTrack : $strings.LabelUseChapterTrack">
|
||||||
<div class="cursor-pointer text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="setUseChapterTrack">
|
<div class="cursor-pointer text-gray-300 mx-1 lg:mx-2 hover:text-white" @mousedown.prevent @mouseup.prevent @click.stop="setUseChapterTrack">
|
||||||
<span class="material-icons text-2xl sm:text-3xl transform transition-transform" :class="useChapterTrack ? 'rotate-180' : ''">timelapse</span>
|
<span class="material-icons text-2xl sm:text-3xl transform transition-transform" :class="useChapterTrack ? 'rotate-180' : ''">timelapse</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="heatmap" class="w-full">
|
<div id="heatmap" class="w-full">
|
||||||
<div class="mx-auto" :style="{ height: innerHeight + 160 + 'px', width: innerWidth + 52 + 'px' }" style="background-color: rgba(13, 17, 23, 0)">
|
<div class="mx-auto" :style="{ height: innerHeight + 160 + 'px', width: innerWidth + 52 + 'px' }" style="background-color: rgba(13, 17, 23, 0)">
|
||||||
<p class="mb-2 px-1 text-sm text-gray-200">{{ Object.values(daysListening).length }} listening sessions in the last year</p>
|
<p class="mb-2 px-1 text-sm text-gray-200">{{ $getString('MessageListeningSessionsInTheLastYear', [Object.values(daysListening).length]) }}</p>
|
||||||
<div class="border border-white border-opacity-25 rounded py-2 w-full" style="background-color: #232323" :style="{ height: innerHeight + 80 + 'px' }">
|
<div class="border border-white border-opacity-25 rounded py-2 w-full" style="background-color: #232323" :style="{ height: innerHeight + 80 + 'px' }">
|
||||||
<div :style="{ width: innerWidth + 'px', height: innerHeight + 'px' }" class="ml-10 mt-5 absolute" @mouseover="mouseover" @mouseout="mouseout">
|
<div :style="{ width: innerWidth + 'px', height: innerHeight + 'px' }" class="ml-10 mt-5 absolute" @mouseover="mouseover" @mouseout="mouseout">
|
||||||
<div v-for="dayLabel in dayLabels" :key="dayLabel.label" :style="dayLabel.style" class="absolute top-0 left-0 text-gray-300">{{ dayLabel.label }}</div>
|
<div v-for="dayLabel in dayLabels" :key="dayLabel.label" :style="dayLabel.style" class="absolute top-0 left-0 text-gray-300">{{ dayLabel.label }}</div>
|
||||||
@ -12,9 +12,9 @@
|
|||||||
|
|
||||||
<div class="flex py-2 px-4" :style="{ marginTop: innerHeight + 'px' }">
|
<div class="flex py-2 px-4" :style="{ marginTop: innerHeight + 'px' }">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<p style="font-size: 10px; line-height: 10px" class="text-gray-400 px-1">Less</p>
|
<p style="font-size: 10px; line-height: 10px" class="text-gray-400 px-1">{{ $strings.LabelLess }}</p>
|
||||||
<div v-for="block in legendBlocks" :key="block.id" :style="block.style" class="h-2.5 w-2.5 rounded-sm" style="margin-left: 1.5px; margin-right: 1.5px" />
|
<div v-for="block in legendBlocks" :key="block.id" :style="block.style" class="h-2.5 w-2.5 rounded-sm" style="margin-left: 1.5px; margin-right: 1.5px" />
|
||||||
<p style="font-size: 10px; line-height: 10px" class="text-gray-400 px-1">More</p>
|
<p style="font-size: 10px; line-height: 10px" class="text-gray-400 px-1">{{ $strings.LabelMore }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="w-full my-2">
|
|
||||||
<div class="w-full bg-primary px-6 py-2 flex items-center cursor-pointer" @click.stop="clickBar">
|
|
||||||
<p class="pr-4">Other Audio Files</p>
|
|
||||||
<span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ files.length }}</span>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
|
|
||||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
|
||||||
</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' : ''">
|
|
||||||
<span class="material-icons text-4xl">expand_more</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<transition name="slide">
|
|
||||||
<div class="w-full" v-show="showTracks">
|
|
||||||
<table class="text-sm tracksTable">
|
|
||||||
<tr class="font-book">
|
|
||||||
<th class="text-left">Filename</th>
|
|
||||||
<th class="text-left">Size</th>
|
|
||||||
<th class="text-left">Duration</th>
|
|
||||||
<th class="text-left">Notes</th>
|
|
||||||
</tr>
|
|
||||||
<template v-for="track in files">
|
|
||||||
<tr :key="track.path">
|
|
||||||
<td class="font-book pl-2">
|
|
||||||
{{ track.filename }}
|
|
||||||
</td>
|
|
||||||
<td class="font-mono">
|
|
||||||
{{ $bytesPretty(track.size) }}
|
|
||||||
</td>
|
|
||||||
<td class="font-mono">
|
|
||||||
{{ $secondsToTimestamp(track.duration) }}
|
|
||||||
</td>
|
|
||||||
<td class="text-xs">
|
|
||||||
<p>{{ track.error || '' }}</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
files: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
audiobookId: String
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showTracks: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
userCanUpdate() {
|
|
||||||
return this.$store.getters['user/getUserCanUpdate']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
clickBar() {
|
|
||||||
this.showTracks = !this.showTracks
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -33,7 +33,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="!backups.length" class="staticrow">
|
<tr v-if="!backups.length" class="staticrow">
|
||||||
<td colspan="4" class="text-lg">No Backups</td>
|
<td colspan="4" class="text-lg">{{ $strings.MessageNoBackups }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<div v-show="processing" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-25 flex items-center justify-center">
|
<div v-show="processing" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-25 flex items-center justify-center">
|
||||||
@ -88,23 +88,23 @@ export default {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.isBackingUp = false
|
this.isBackingUp = false
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error('Failed to apply backup')
|
this.$toast.error(this.$strings.ToastBackupRestoreFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteBackupClick(backup) {
|
deleteBackupClick(backup) {
|
||||||
if (confirm(`Are you sure you want to delete backup for ${backup.datePretty}?`)) {
|
if (confirm(this.$getString('MessageConfirmDeleteBackup', [backup.datePretty]))) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$axios
|
||||||
.$delete(`/api/backups/${backup.id}`)
|
.$delete(`/api/backups/${backup.id}`)
|
||||||
.then((backups) => {
|
.then((backups) => {
|
||||||
console.log('Backup deleted', backups)
|
console.log('Backup deleted', backups)
|
||||||
this.$store.commit('setBackups', backups)
|
this.$store.commit('setBackups', backups)
|
||||||
this.$toast.success(`Backup deleted`)
|
this.$toast.success(this.$strings.ToastBackupDeleteSuccess)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
this.$toast.error('Failed to delete backup')
|
this.$toast.error(this.$strings.ToastBackupDeleteFailed)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -119,13 +119,13 @@ export default {
|
|||||||
.$post('/api/backups')
|
.$post('/api/backups')
|
||||||
.then((backups) => {
|
.then((backups) => {
|
||||||
this.isBackingUp = false
|
this.isBackingUp = false
|
||||||
this.$toast.success('Backup Successful')
|
this.$toast.success(this.$strings.ToastBackupCreateSuccess)
|
||||||
this.$store.commit('setBackups', backups)
|
this.$store.commit('setBackups', backups)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.isBackingUp = false
|
this.isBackingUp = false
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.$toast.error('Backup Failed')
|
this.$toast.error(this.$strings.ToastBackupCreateFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
backupUploaded(file) {
|
backupUploaded(file) {
|
||||||
@ -139,12 +139,12 @@ export default {
|
|||||||
.then((result) => {
|
.then((result) => {
|
||||||
console.log('Upload backup result', result)
|
console.log('Upload backup result', result)
|
||||||
this.$store.commit('setBackups', result)
|
this.$store.commit('setBackups', result)
|
||||||
this.$toast.success('Backup upload success')
|
this.$toast.success(this.$strings.ToastBackupUploadSuccess)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
var errorMessage = error.response && error.response.data ? error.response.data : 'Failed to upload backup'
|
var errorMessage = error.response && error.response.data ? error.response.data : this.$strings.ToastBackupUploadFailed
|
||||||
this.$toast.error(errorMessage)
|
this.$toast.error(errorMessage)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full bg-primary bg-opacity-40">
|
<div class="w-full bg-primary bg-opacity-40">
|
||||||
<div class="w-full h-14 flex items-center px-4 md:px-6 py-2 bg-primary">
|
<div class="w-full h-14 flex items-center px-4 md:px-6 py-2 bg-primary">
|
||||||
<p class="pr-4">Collection List</p>
|
<p class="pr-4">{{ $strings.HeaderCollectionItems }}</p>
|
||||||
|
|
||||||
<div class="w-6 h-6 md:w-7 md:h-7 bg-white bg-opacity-10 rounded-full flex items-center justify-center">
|
<div class="w-6 h-6 md:w-7 md:h-7 bg-white bg-opacity-10 rounded-full flex items-center justify-center">
|
||||||
<span class="text-xs md:text-sm font-mono leading-none">{{ books.length }}</span>
|
<span class="text-xs md:text-sm font-mono leading-none">{{ books.length }}</span>
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span> -->
|
<!-- <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span> -->
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn>
|
<ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">{{ $strings.ButtonFullPath }}</ui-btn>
|
||||||
<nuxt-link v-if="userCanUpdate && !isFile" :to="`/audiobook/${libraryItemId}/edit`" class="mr-2 md:mr-4" @mousedown.prevent>
|
<nuxt-link v-if="userCanUpdate && !isFile" :to="`/audiobook/${libraryItemId}/edit`" class="mr-2 md:mr-4" @mousedown.prevent>
|
||||||
<ui-btn small color="primary">Manage Tracks</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-icons text-4xl">expand_more</span>
|
||||||
@ -20,10 +20,10 @@
|
|||||||
<table class="text-sm tracksTable">
|
<table class="text-sm tracksTable">
|
||||||
<tr class="font-book">
|
<tr class="font-book">
|
||||||
<th class="w-10">#</th>
|
<th class="w-10">#</th>
|
||||||
<th class="text-left">Filename</th>
|
<th class="text-left">{{ $strings.LabelFilename }}</th>
|
||||||
<th class="text-left w-20">Size</th>
|
<th class="text-left w-20">{{ $strings.LabelSize }}</th>
|
||||||
<th class="text-left w-20">Duration</th>
|
<th class="text-left w-20">{{ $strings.LabelDuration }}</th>
|
||||||
<th v-if="userCanDownload" class="text-center w-20">Download</th>
|
<th v-if="userCanDownload" class="text-center w-20">{{ $strings.LabelDownload }}</th>
|
||||||
<th v-if="showExperimentalFeatures" class="text-center w-20">
|
<th v-if="showExperimentalFeatures" class="text-center w-20">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p>Tone</p>
|
<p>Tone</p>
|
||||||
|
@ -97,12 +97,12 @@ export default {
|
|||||||
if (data.error) {
|
if (data.error) {
|
||||||
this.$toast.error(data.error)
|
this.$toast.error(data.error)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.success('User deleted')
|
this.$toast.success(this.$strings.ToastUserDeleteSuccess)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to delete user', error)
|
console.error('Failed to delete user', error)
|
||||||
this.$toast.error('Failed to delete user')
|
this.$toast.error(this.$strings.ToastUserDeleteFailed)
|
||||||
this.isDeletingUser = false
|
this.isDeletingUser = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-40 absolute top-0 -right-24 h-full transform transition-transform" :class="!isHovering ? 'translate-x-0' : translateDistance">
|
<div class="w-40 absolute top-0 -right-24 h-full transform transition-transform" :class="!isHovering ? 'translate-x-0' : translateDistance">
|
||||||
<div class="flex h-full items-center">
|
<div class="flex h-full items-center">
|
||||||
<ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
|
<ui-tooltip :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<div v-if="userCanUpdate" class="mx-1" :class="isHovering ? '' : 'ml-6'">
|
<div v-if="userCanUpdate" class="mx-1" :class="isHovering ? '' : 'ml-6'">
|
||||||
@ -153,12 +153,12 @@ export default {
|
|||||||
.$patch(`/api/me/progress/${this.book.id}`, updatePayload)
|
.$patch(`/api/me/progress/${this.book.id}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(`Item marked as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedFailed : this.$strings.ToastItemMarkedAsNotFinishedFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeClick() {
|
removeClick() {
|
||||||
@ -168,12 +168,12 @@ export default {
|
|||||||
.$delete(`/api/collections/${this.collectionId}/book/${this.book.id}`)
|
.$delete(`/api/collections/${this.collectionId}/book/${this.book.id}`)
|
||||||
.then((updatedCollection) => {
|
.then((updatedCollection) => {
|
||||||
console.log(`Book removed from collection`, updatedCollection)
|
console.log(`Book removed from collection`, updatedCollection)
|
||||||
this.$toast.success('Book removed from collection')
|
this.$toast.success(this.$strings.ToastRemoveItemFromCollectionSuccess)
|
||||||
this.processingRemove = false
|
this.processingRemove = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to remove book from collection', error)
|
console.error('Failed to remove book from collection', error)
|
||||||
this.$toast.error('Failed to remove book from collection')
|
this.$toast.error(this.$strings.ToastRemoveItemFromCollectionFailed)
|
||||||
this.processingRemove = false
|
this.processingRemove = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -66,22 +66,22 @@ export default {
|
|||||||
mobileMenuItems() {
|
mobileMenuItems() {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
text: 'Scan',
|
text: this.$strings.ButtonScan,
|
||||||
value: 'scan'
|
value: 'scan'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Force Re-Scan',
|
text: this.$strings.ButtonForceReScan,
|
||||||
value: 'force-scan'
|
value: 'force-scan'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (this.isBookLibrary) {
|
if (this.isBookLibrary) {
|
||||||
items.push({
|
items.push({
|
||||||
text: 'Match Books',
|
text: this.$strings.ButtonMatchBooks,
|
||||||
value: 'match-books'
|
value: 'match-books'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
items.push({
|
items.push({
|
||||||
text: 'Delete',
|
text: this.$strings.ButtonDelete,
|
||||||
value: 'delete'
|
value: 'delete'
|
||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
@ -122,28 +122,28 @@ export default {
|
|||||||
this.$store
|
this.$store
|
||||||
.dispatch('libraries/requestLibraryScan', { libraryId: this.library.id })
|
.dispatch('libraries/requestLibraryScan', { libraryId: this.library.id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Library scan started')
|
this.$toast.success(this.$strings.ToastLibraryScanStarted)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to start scan', error)
|
console.error('Failed to start scan', error)
|
||||||
this.$toast.error('Failed to start scan')
|
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
forceScan() {
|
forceScan() {
|
||||||
if (confirm(`Force Re-Scan will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed to be used for the library item.\n\nAre you sure you want to force re-scan?`)) {
|
if (confirm(this.$strings.MessageConfirmForceReScan)) {
|
||||||
this.$store
|
this.$store
|
||||||
.dispatch('libraries/requestLibraryScan', { libraryId: this.library.id, force: 1 })
|
.dispatch('libraries/requestLibraryScan', { libraryId: this.library.id, force: 1 })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Library scan started')
|
this.$toast.success(this.$strings.ToastLibraryScanStarted)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to start scan', error)
|
console.error('Failed to start scan', error)
|
||||||
this.$toast.error('Failed to start scan')
|
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteClick() {
|
deleteClick() {
|
||||||
if (confirm(`Are you sure you want to permanently delete library "${this.library.name}"?`)) {
|
if (confirm(this.$getString('MessageConfirmDeleteLibrary', [this.library.name]))) {
|
||||||
this.isDeleting = true
|
this.isDeleting = true
|
||||||
this.$axios
|
this.$axios
|
||||||
.$delete(`/api/libraries/${this.library.id}`)
|
.$delete(`/api/libraries/${this.library.id}`)
|
||||||
@ -152,12 +152,12 @@ export default {
|
|||||||
if (data.error) {
|
if (data.error) {
|
||||||
this.$toast.error(data.error)
|
this.$toast.error(data.error)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.success('Library deleted')
|
this.$toast.success(this.$strings.ToastLibraryDeleteSuccess)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to delete library', error)
|
console.error('Failed to delete library', error)
|
||||||
this.$toast.error('Failed to delete library')
|
this.$toast.error(this.$strings.ToastLibraryDeleteFailed)
|
||||||
this.isDeleting = false
|
this.isDeleting = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
|
<ui-tooltip :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
@ -159,12 +159,12 @@ export default {
|
|||||||
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(`Item marked as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedFailed : this.$strings.ToastItemMarkedAsNotFinishedFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeClick() {
|
removeClick() {
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full py-6">
|
<div class="w-full py-6">
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<p class="text-lg mb-0 font-semibold">Episodes</p>
|
<p class="text-lg mb-0 font-semibold">{{ $strings.HeaderEpisodes }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<template v-if="isSelectionMode">
|
<template v-if="isSelectionMode">
|
||||||
<ui-tooltip :text="`Mark as ${selectedIsFinished ? 'Not Finished' : 'Finished'}`" direction="bottom">
|
<ui-tooltip :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
||||||
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
<ui-btn color="error" :disabled="processing" small class="h-9" @click="removeSelectedEpisodes">Remove {{ selectedEpisodes.length }} episode{{ selectedEpisodes.length > 1 ? 's' : '' }}</ui-btn>
|
<ui-btn color="error" :disabled="processing" small class="h-9" @click="removeSelectedEpisodes">{{ $getString('MessageRemoveEpisodes', [selectedEpisodes.length]) }}</ui-btn>
|
||||||
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">Cancel</ui-btn>
|
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">{{ $strings.ButtonCancel }}</ui-btn>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-32 md:w-36 h-9 ml-1 sm:ml-4" />
|
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-32 md:w-36 h-9 ml-1 sm:ml-4" />
|
||||||
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-32 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-32 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!episodes.length" class="py-4 text-center text-lg">No Episodes</p>
|
<p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p>
|
||||||
<template v-for="episode in episodesSorted">
|
<template v-for="episode in episodesSorted">
|
||||||
<tables-podcast-episode-table-row ref="episodeRow" :key="episode.id" :episode="episode" :library-item-id="libraryItem.id" :selection-mode="isSelectionMode" class="item" @play="playEpisode" @remove="removeEpisode" @edit="editEpisode" @view="viewEpisode" @selected="episodeSelected" />
|
<tables-podcast-episode-table-row ref="episodeRow" :key="episode.id" :episode="episode" :library-item-id="libraryItem.id" :selection-mode="isSelectionMode" class="item" @play="playEpisode" @remove="removeEpisode" @edit="editEpisode" @view="viewEpisode" @selected="episodeSelected" />
|
||||||
</template>
|
</template>
|
||||||
@ -42,43 +42,7 @@ export default {
|
|||||||
showPodcastRemoveModal: false,
|
showPodcastRemoveModal: false,
|
||||||
selectedEpisodes: [],
|
selectedEpisodes: [],
|
||||||
episodesToRemove: [],
|
episodesToRemove: [],
|
||||||
processing: false,
|
processing: false
|
||||||
sortItems: [
|
|
||||||
{
|
|
||||||
text: 'Pub Date',
|
|
||||||
value: 'publishedAt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Title',
|
|
||||||
value: 'title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Season',
|
|
||||||
value: 'season'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Episode',
|
|
||||||
value: 'episode'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
filterItems: [
|
|
||||||
{
|
|
||||||
value: 'all',
|
|
||||||
text: 'Show All'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'incomplete',
|
|
||||||
text: 'Incomplete'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'complete',
|
|
||||||
text: 'Complete'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'in_progress',
|
|
||||||
text: 'In Progress'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -87,6 +51,46 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
sortItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelPubDate,
|
||||||
|
value: 'publishedAt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelTitle,
|
||||||
|
value: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelSeason,
|
||||||
|
value: 'season'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelEpisode,
|
||||||
|
value: 'episode'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
filterItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: 'all',
|
||||||
|
text: this.$strings.LabelShowAll
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'incomplete',
|
||||||
|
text: this.$strings.LabelIncomplete
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'complete',
|
||||||
|
text: this.$strings.LabelComplete
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'in_progress',
|
||||||
|
text: this.$strings.LabelInProgress
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
isSelectionMode() {
|
isSelectionMode() {
|
||||||
return this.selectedEpisodes.length > 0
|
return this.selectedEpisodes.length > 0
|
||||||
},
|
},
|
||||||
@ -141,12 +145,12 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
|
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Batch update success!')
|
this.$toast.success(this.$strings.ToastBatchUpdateSuccess)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.clearSelected()
|
this.clearSelected()
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error('Batch update failed')
|
this.$toast.error(this.$strings.ToastBatchUpdateFailed)
|
||||||
console.error('Failed to batch update read/not read', error)
|
console.error('Failed to batch update read/not read', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
})
|
})
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<li v-if="!itemsToShow.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
<li v-if="!itemsToShow.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="font-normal">No items</span>
|
<span class="font-normal">{{ $strings.MessageNoItems }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<li v-if="!items.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
<li v-if="!items.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="font-normal">No items</span>
|
<span class="font-normal">{{ $strings.MessageNoItems }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<li v-if="!itemsToShow.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
<li v-if="!itemsToShow.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="font-normal">No items</span>
|
<span class="font-normal">{{ $strings.MessageNoItems }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<li v-if="!items.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
<li v-if="!items.length" class="text-gray-50 select-none relative py-2 pr-9" role="option">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="font-normal">No items</span>
|
<span class="font-normal">{{ $strings.MessageNoItems }}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -74,7 +74,7 @@ export default {
|
|||||||
if (this.searching) return
|
if (this.searching) return
|
||||||
this.currentSearch = this.textInput
|
this.currentSearch = this.textInput
|
||||||
this.searching = true
|
this.searching = true
|
||||||
var results = await this.$axios.$get(`/api/${this.endpoint}?q=${this.currentSearch}&limit=15`).catch((error) => {
|
var results = await this.$axios.$gest(`/api/${this.endpoint}?q=${this.currentSearch}&limit=15`).catch((error) => {
|
||||||
console.error('Failed to get search results', error)
|
console.error('Failed to get search results', error)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div v-if="missingParts.length" class="bg-error border-red-800 shadow-md p-4">
|
<div v-if="missingParts.length" class="bg-error border-red-800 shadow-md p-4">
|
||||||
<p class="text-sm mb-2">
|
<p class="text-sm mb-2">
|
||||||
Missing Parts <span class="text-sm">({{ missingParts.length }})</span>
|
{{ $strings.LabelMissingParts }} <span class="text-sm">({{ missingParts.length }})</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm font-mono">{{ missingPartChunks.join(', ') }}</p>
|
<p class="text-sm font-mono">{{ missingPartChunks.join(', ') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="invalidParts.length" class="bg-error border-red-800 shadow-md p-4">
|
<div v-if="invalidParts.length" class="bg-error border-red-800 shadow-md p-4">
|
||||||
<p class="text-sm mb-2">
|
<p class="text-sm mb-2">
|
||||||
Invalid Parts <span class="text-sm">({{ invalidParts.length }})</span>
|
{{ $strings.LabelInvalidParts }} <span class="text-sm">({{ invalidParts.length }})</span>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p v-for="part in invalidParts" :key="part.filename" class="text-sm font-mono">{{ part.filename }}: {{ part.error }}</p>
|
<p v-for="part in invalidParts" :key="part.filename" class="text-sm font-mono">{{ part.filename }}: {{ part.error }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tables-tracks-table :title="`Audiobook Tracks`" :tracks="media.tracks" :is-file="isFile" :library-item-id="libraryItemId" class="mt-6" />
|
<tables-tracks-table :title="$strings.LabelStatsAudioTracks" :tracks="media.tracks" :is-file="isFile" :library-item-id="libraryItemId" class="mt-6" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<template v-if="!showAdvancedView">
|
<template v-if="!showAdvancedView">
|
||||||
<ui-dropdown v-model="selectedInterval" @input="updateCron" :label="$strings.LabelInterval" :items="intervalOptions" class="mb-2" />
|
<ui-dropdown v-model="selectedInterval" @input="updateCron" :label="$strings.LabelInterval" :items="intervalOptions" class="mb-2" />
|
||||||
|
|
||||||
<ui-multi-select-dropdown v-if="selectedInterval === 'custom'" v-model="selectedWeekdays" @input="updateCron" :label="$string.LabelWeekdaysToRun" :items="weekdays" />
|
<ui-multi-select-dropdown v-if="selectedInterval === 'custom'" v-model="selectedWeekdays" @input="updateCron" :label="$strings.LabelWeekdaysToRun" :items="weekdays" />
|
||||||
|
|
||||||
<div v-if="(selectedWeekdays.length && selectedInterval === 'custom') || selectedInterval === 'daily'" class="flex items-center py-2">
|
<div v-if="(selectedWeekdays.length && selectedInterval === 'custom') || selectedInterval === 'daily'" class="flex items-center py-2">
|
||||||
<ui-text-input-with-label v-model="selectedHour" @input="updateCron" @blur="hourBlur" type="number" :label="$strings.LabelHour" class="max-w-20" />
|
<ui-text-input-with-label v-model="selectedHour" @input="updateCron" @blur="hourBlur" type="number" :label="$strings.LabelHour" class="max-w-20" />
|
||||||
|
@ -3,39 +3,39 @@
|
|||||||
<form class="w-full h-full px-4 py-6" @submit.prevent="submitForm">
|
<form class="w-full h-full px-4 py-6" @submit.prevent="submitForm">
|
||||||
<div class="flex -mx-1">
|
<div class="flex -mx-1">
|
||||||
<div class="w-1/2 px-1">
|
<div class="w-1/2 px-1">
|
||||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" label="Title" />
|
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<ui-text-input-with-label ref="authorInput" v-model="details.author" label="Author" />
|
<ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" label="RSS Feed URL" class="mt-2" />
|
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" class="mt-2" />
|
||||||
|
|
||||||
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" label="Description" class="mt-2" />
|
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" />
|
||||||
|
|
||||||
<div class="flex mt-2 -mx-1">
|
<div class="flex mt-2 -mx-1">
|
||||||
<div class="w-1/2 px-1">
|
<div class="w-1/2 px-1">
|
||||||
<ui-multi-select ref="genresSelect" v-model="details.genres" label="Genres" :items="genres" />
|
<ui-multi-select ref="genresSelect" v-model="details.genres" :label="$strings.LabelGenres" :items="genres" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<ui-multi-select ref="tagsSelect" v-model="newTags" label="Tags" :items="tags" />
|
<ui-multi-select ref="tagsSelect" v-model="newTags" :label="$strings.LabelTags" :items="tags" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex mt-2 -mx-1">
|
<div class="flex mt-2 -mx-1">
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" label="Release Date" />
|
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" />
|
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" label="Language" />
|
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 pt-6">
|
<div class="flex-grow px-1 pt-6">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<ui-checkbox v-model="details.explicit" label="Explicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<div class="flex items-center py-2">
|
<div class="flex items-center py-2">
|
||||||
<p v-if="isRoot" class="text-error py-2 text-xs">* {{ $strings.NoteChangeRootPassword }}</p>
|
<p v-if="isRoot" class="text-error py-2 text-xs">* {{ $strings.NoteChangeRootPassword }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn v-show="(password && newPassword && confirmPassword) || isRoot" type="submit" :loading="changingPassword" color="success">{{ $strings.ButtonChangePasswordSubmit }}</ui-btn>
|
<ui-btn v-show="(password && newPassword && confirmPassword) || isRoot" type="submit" :loading="changingPassword" color="success">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -134,18 +134,18 @@
|
|||||||
<div v-else class="w-full p-4">
|
<div v-else class="w-full p-4">
|
||||||
<div class="flex justify-between mb-4">
|
<div class="flex justify-between mb-4">
|
||||||
<p>
|
<p>
|
||||||
Duration found: <span class="font-semibold">{{ $secondsToTimestamp(chapterData.runtimeLengthSec) }}</span
|
{{ $strings.LabelDurationFound }} <span class="font-semibold">{{ $secondsToTimestamp(chapterData.runtimeLengthSec) }}</span
|
||||||
><br />
|
><br />
|
||||||
<span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapterData.chapters.length }}</span> chapters found
|
<span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapterData.chapters.length }}</span> {{ $strings.LabelChaptersFound }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Your audiobook duration: <span class="font-semibold">{{ $secondsToTimestamp(mediaDurationRounded) }}</span
|
{{ $strings.LabelYourAudiobookDuration }}: <span class="font-semibold">{{ $secondsToTimestamp(mediaDurationRounded) }}</span
|
||||||
><br />
|
><br />
|
||||||
Your audiobook has <span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapters.length }}</span> chapters
|
Your audiobook has <span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapters.length }}</span> chapters
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<widgets-alert v-if="chapterData.runtimeLengthSec > mediaDurationRounded" type="warning" class="mb-2"> Your audiobook duration is shorter than duration found </widgets-alert>
|
<widgets-alert v-if="chapterData.runtimeLengthSec > mediaDurationRounded" type="warning" class="mb-2"> {{ $strings.MessageYourAudiobookDurationIsShorter }} </widgets-alert>
|
||||||
<widgets-alert v-else-if="chapterData.runtimeLengthSec < mediaDurationRounded" type="warning" class="mb-2"> Your audiobook duration is longer than the duration found </widgets-alert>
|
<widgets-alert v-else-if="chapterData.runtimeLengthSec < mediaDurationRounded" type="warning" class="mb-2"> {{ $strings.MessageYourAudiobookDurationIsLonger }} </widgets-alert>
|
||||||
|
|
||||||
<div class="flex py-0.5 text-xs font-semibold uppercase text-gray-300 mb-1">
|
<div class="flex py-0.5 text-xs font-semibold uppercase text-gray-300 mb-1">
|
||||||
<div class="w-24 px-2">{{ $strings.LabelStart }}</div>
|
<div class="w-24 px-2">{{ $strings.LabelStart }}</div>
|
||||||
@ -164,11 +164,11 @@
|
|||||||
<div v-if="chapterData.runtimeLengthSec > mediaDurationRounded" class="w-full pt-2">
|
<div v-if="chapterData.runtimeLengthSec > mediaDurationRounded" class="w-full pt-2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="w-2 h-2 bg-warning bg-opacity-50" />
|
<div class="w-2 h-2 bg-warning bg-opacity-50" />
|
||||||
<p class="pl-2">Chapter end is after the end of your audiobook</p>
|
<p class="pl-2">{{ $strings.MessageChapterEndIsAfter }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="w-2 h-2 bg-error bg-opacity-50" />
|
<div class="w-2 h-2 bg-error bg-opacity-50" />
|
||||||
<p class="pl-2">Chapter start is after the end of your audiobook</p>
|
<p class="pl-2">{{ $strings.MessageChapterStartIsAfter }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center pt-2">
|
<div class="flex items-center pt-2">
|
||||||
@ -442,7 +442,7 @@ export default {
|
|||||||
this.$router.push(`/item/${this.libraryItem.id}`)
|
this.$router.push(`/item/${this.libraryItem.id}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info('No changes needed updating')
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -5,73 +5,68 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-full h-full overflow-y-auto p-8">
|
<div class="w-full h-full overflow-y-auto p-8">
|
||||||
<div class="w-full flex justify-between items-center pb-6 pt-2">
|
<div class="w-full flex justify-between items-center pb-6 pt-2">
|
||||||
<p class="text-lg">Drag files into correct track order</p>
|
<p class="text-lg">{{ $strings.MessageDragFilesIntoTrackOrder }}</p>
|
||||||
<ui-btn color="success" @click="saveTracklist">Save Tracklist</ui-btn>
|
<ui-btn color="success" @click="saveTracklist">{{ $strings.ButtonSaveTracklist }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex items-center text-sm py-4 bg-primary border-l border-r border-t border-gray-600">
|
<div class="w-full flex items-center text-sm py-4 bg-primary border-l border-r border-t border-gray-600">
|
||||||
<div class="font-book text-center px-4 w-12">New</div>
|
<div class="text-center px-4 w-12">{{ $strings.LabelNew }}</div>
|
||||||
<div class="font-book text-center px-4 w-24 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByCurrent" @mousedown.prevent>
|
<div class="text-center px-4 w-24 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByCurrent" @mousedown.prevent>
|
||||||
<span class="text-white">Current</span>
|
<span class="text-white">{{ $strings.LabelCurrent }}</span>
|
||||||
<span class="material-icons ml-1" :class="currentSort === 'current' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'current' ? 'expand_more' : 'unfold_more' }}</span>
|
<span class="material-icons ml-1" :class="currentSort === 'current' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'current' ? 'expand_more' : 'unfold_more' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book text-center px-4 w-32 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByFilenameTrack" @mousedown.prevent>
|
<div class="text-center px-4 w-32 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByFilenameTrack" @mousedown.prevent>
|
||||||
<span class="text-white">Track From Filename</span>
|
<span class="text-white">{{ $strings.LabelTrackFromFilename }}</span>
|
||||||
<span class="material-icons ml-1" :class="currentSort === 'track-filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'track-filename' ? 'expand_more' : 'unfold_more' }}</span>
|
<span class="material-icons ml-1" :class="currentSort === 'track-filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'track-filename' ? 'expand_more' : 'unfold_more' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book text-center px-4 w-32 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByMetadataTrack" @mousedown.prevent>
|
<div class="text-center px-4 w-32 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByMetadataTrack" @mousedown.prevent>
|
||||||
<span class="text-white">Track From Metadata</span>
|
<span class="text-white">{{ $strings.LabelTrackFromMetadata }}</span>
|
||||||
<span class="material-icons ml-1" :class="currentSort === 'metadata' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'metadata' ? 'expand_more' : 'unfold_more' }}</span>
|
<span class="material-icons ml-1" :class="currentSort === 'metadata' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'metadata' ? 'expand_more' : 'unfold_more' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono w-20 text-center">Disc From Filename</div>
|
<div class="w-20 text-center">{{ $strings.LabelDiscFromFilename }}</div>
|
||||||
<div class="font-mono w-20 text-center">Disc From Metadata</div>
|
<div class="w-20 text-center">{{ $strings.LabelDiscFromMetadata }}</div>
|
||||||
<div class="font-book text-center px-4 flex-grow flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByFilename" @mousedown.prevent>
|
<div class="text-center px-4 flex-grow flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByFilename" @mousedown.prevent>
|
||||||
<span class="text-white">Filename</span>
|
<span class="text-white">{{ $strings.LabelFilename }}</span>
|
||||||
<span class="material-icons ml-1" :class="currentSort === 'filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'filename' ? 'expand_more' : 'unfold_more' }}</span>
|
<span class="material-icons ml-1" :class="currentSort === 'filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'filename' ? 'expand_more' : 'unfold_more' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="font-book truncate px-4 flex-grow">Filename</div> -->
|
|
||||||
|
|
||||||
<div class="font-mono w-20 text-center">Size</div>
|
<div class="w-20 text-center">{{ $strings.LabelSize }}</div>
|
||||||
<div class="font-mono w-20 text-center">Duration</div>
|
<div class="w-20 text-center">{{ $strings.LabelDuration }}</div>
|
||||||
<div class="font-mono text-center w-20">Status</div>
|
<div class="w-56">{{ $strings.LabelNotes }}</div>
|
||||||
<div class="font-mono w-56">Notes</div>
|
<div class="w-40">{{ $strings.LabelIncludeInTracklist }}</div>
|
||||||
<div class="font-book w-40">Include in Tracklist</div>
|
|
||||||
</div>
|
</div>
|
||||||
<draggable v-model="files" v-bind="dragOptions" class="list-group border border-gray-600" draggable=".item" tag="ul" @start="drag = true" @end="drag = false" @update="draggableUpdate">
|
<draggable v-model="files" v-bind="dragOptions" class="list-group border border-gray-600" draggable=".item" 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="(audio, index) in files" :key="audio.ino" :class="audio.include ? 'item' : 'exclude'" class="w-full list-group-item flex items-center relative">
|
<li v-for="(audio, index) in files" :key="audio.ino" :class="audio.include ? 'item' : 'exclude'" class="w-full list-group-item flex items-center relative">
|
||||||
<div class="font-book text-center px-4 py-1 w-12">
|
<div class="font-book text-center px-4 py-1 w-12 min-w-12">
|
||||||
{{ audio.include ? index - numExcluded + 1 : -1 }}
|
{{ audio.include ? index - numExcluded + 1 : -1 }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book text-center px-4 w-24">{{ audio.index }}</div>
|
<div class="font-book text-center px-4 w-24 min-w-24">{{ audio.index }}</div>
|
||||||
<div class="font-book text-center px-2 w-32">
|
<div class="font-book text-center px-2 w-32 min-w-32">
|
||||||
{{ audio.trackNumFromFilename }}
|
{{ audio.trackNumFromFilename }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book text-center w-32">
|
<div class="font-book text-center w-32 min-w-32">
|
||||||
{{ audio.trackNumFromMeta }}
|
{{ audio.trackNumFromMeta }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book truncate px-4 w-20">
|
<div class="font-book truncate px-4 w-20 min-w-20">
|
||||||
{{ audio.discNumFromFilename }}
|
{{ audio.discNumFromFilename }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book truncate px-4 w-20">
|
<div class="font-book truncate px-4 w-20 min-w-20">
|
||||||
{{ audio.discNumFromMeta }}
|
{{ audio.discNumFromMeta }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-book truncate px-4 flex-grow">
|
<div class="font-book truncate px-4 flex-grow">
|
||||||
{{ audio.metadata.filename }}
|
{{ audio.metadata.filename }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="font-mono w-20 text-center">
|
<div class="font-mono w-20 min-w-20 text-center text-xs">
|
||||||
{{ $bytesPretty(audio.metadata.size) }}
|
{{ $bytesPretty(audio.metadata.size) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono w-20">
|
<div class="font-mono w-20 min-w-20 text-center text-xs">
|
||||||
{{ $secondsToTimestamp(audio.duration) }}
|
{{ $secondsToTimestamp(audio.duration) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-mono text-center w-20">
|
<div class="font-sans text-xs font-normal w-56 min-w-[224px]">
|
||||||
<span class="material-icons text-sm" :class="audio.invalid ? 'text-error' : 'text-success'">{{ getStatusIcon(audio) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="font-sans text-xs font-normal w-56">
|
|
||||||
{{ audio.error }}
|
{{ audio.error }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-sans text-xs font-normal w-40 flex items-center justify-center">
|
<div class="font-sans text-xs font-normal w-40 min-w-[160px] flex items-center justify-center">
|
||||||
<ui-toggle-switch v-model="audio.include" :off-color="'error'" @input="includeToggled(audio)" />
|
<ui-toggle-switch v-model="audio.include" :off-color="'error'" @input="includeToggled(audio)" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -229,13 +224,6 @@ export default {
|
|||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.saving = false
|
this.saving = false
|
||||||
})
|
})
|
||||||
},
|
|
||||||
getStatusIcon(audio) {
|
|
||||||
if (audio.invalid) {
|
|
||||||
return 'error_outline'
|
|
||||||
} else {
|
|
||||||
return 'check_circle'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="w-full max-w-2xl">
|
<div class="w-full max-w-2xl">
|
||||||
<p class="text-xl mb-1">Metadata to embed</p>
|
<p class="text-xl mb-1">{{ $strings.HeaderMetadataToEmbed }}</p>
|
||||||
<p class="mb-2 text-base text-gray-300">audiobookshelf uses <a href="https://github.com/sandreas/tone" target="_blank" class="hover:underline text-blue-400 hover:text-blue-300">tone</a> to write metadata.</p>
|
<p class="mb-2 text-base text-gray-300">audiobookshelf uses <a href="https://github.com/sandreas/tone" target="_blank" class="hover:underline text-blue-400 hover:text-blue-300">tone</a> to write metadata.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-w-2xl"></div>
|
<div class="w-full max-w-2xl"></div>
|
||||||
@ -22,8 +22,8 @@
|
|||||||
<div class="flex justify-center flex-wrap">
|
<div class="flex justify-center flex-wrap">
|
||||||
<div class="w-full max-w-2xl border border-white border-opacity-10 bg-bg mx-2">
|
<div class="w-full max-w-2xl border border-white border-opacity-10 bg-bg mx-2">
|
||||||
<div class="flex py-2 px-4">
|
<div class="flex py-2 px-4">
|
||||||
<div class="w-1/3 text-xs font-semibold uppercase text-gray-200">Meta Tag</div>
|
<div class="w-1/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelMetaTag }}</div>
|
||||||
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">Value</div>
|
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-h-72 overflow-auto">
|
<div class="w-full max-h-72 overflow-auto">
|
||||||
<template v-for="(value, key, index) in toneObject">
|
<template v-for="(value, key, index) in toneObject">
|
||||||
@ -38,12 +38,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-full max-w-2xl border border-white border-opacity-10 bg-bg mx-2">
|
<div class="w-full max-w-2xl border border-white border-opacity-10 bg-bg mx-2">
|
||||||
<div class="flex py-2 px-4 bg-primary bg-opacity-25">
|
<div class="flex py-2 px-4 bg-primary bg-opacity-25">
|
||||||
<div class="flex-grow text-xs font-semibold uppercase text-gray-200">Chapter Title</div>
|
<div class="flex-grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelChapterTitle }}</div>
|
||||||
<div class="w-24 text-xs font-semibold uppercase text-gray-200">Start</div>
|
<div class="w-24 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelStart }}</div>
|
||||||
<div class="w-24 text-xs font-semibold uppercase text-gray-200">End</div>
|
<div class="w-24 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelEnd }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-h-72 overflow-auto">
|
<div class="w-full max-h-72 overflow-auto">
|
||||||
<p v-if="!metadataChapters.length" class="py-5 text-center text-gray-200">No chapters</p>
|
<p v-if="!metadataChapters.length" class="py-5 text-center text-gray-200">{{ $strings.MessageNoChapters }}</p>
|
||||||
<template v-for="(chapter, index) in metadataChapters">
|
<template v-for="(chapter, index) in metadataChapters">
|
||||||
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 1 ? 'bg-primary bg-opacity-25' : ''">
|
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 1 ? 'bg-primary bg-opacity-25' : ''">
|
||||||
<div class="flex-grow font-semibold">{{ chapter.title }}</div>
|
<div class="flex-grow font-semibold">{{ chapter.title }}</div>
|
||||||
@ -63,20 +63,20 @@
|
|||||||
|
|
||||||
<div class="w-full max-w-4xl mx-auto">
|
<div class="w-full max-w-4xl mx-auto">
|
||||||
<div v-if="selectedTool === 'embed'" class="w-full flex justify-end items-center mb-4">
|
<div v-if="selectedTool === 'embed'" class="w-full flex justify-end items-center mb-4">
|
||||||
<ui-btn v-if="!isFinished" color="primary" :loading="processing" @click.stop="embedClick">Start Metadata Embed</ui-btn>
|
<ui-btn v-if="!isFinished" color="primary" :loading="processing" @click.stop="embedClick">{{ $strings.ButtonStartMetadataEmbed }}</ui-btn>
|
||||||
<p v-else class="text-success text-lg font-semibold">Embed Finished!</p>
|
<p v-else class="text-success text-lg font-semibold">{{ $strings.MessageEmbedFinished }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full flex justify-end items-center mb-4">
|
<div v-else class="w-full flex justify-end items-center mb-4">
|
||||||
<ui-btn v-if="!isTaskFinished && processing" color="error" :loading="isCancelingEncode" class="mr-2" @click.stop="cancelEncodeClick">Cancel Encode</ui-btn>
|
<ui-btn v-if="!isTaskFinished && processing" color="error" :loading="isCancelingEncode" class="mr-2" @click.stop="cancelEncodeClick">{{ $strings.ButtonCancelEncode }}</ui-btn>
|
||||||
<ui-btn v-if="!isTaskFinished" color="primary" :loading="processing" @click.stop="encodeM4bClick">Start M4B Encode</ui-btn>
|
<ui-btn v-if="!isTaskFinished" color="primary" :loading="processing" @click.stop="encodeM4bClick">{{ $strings.ButtonStartM4BEncode }}</ui-btn>
|
||||||
<p v-else-if="taskFailed" class="text-error text-lg font-semibold">M4B Failed! {{ taskError }}</p>
|
<p v-else-if="taskFailed" class="text-error text-lg font-semibold">{{ $strings.MessageM4BFailed }} {{ taskError }}</p>
|
||||||
<p v-else class="text-success text-lg font-semibold">M4B Finished!</p>
|
<p v-else class="text-success text-lg font-semibold">{{ $strings.MessageM4BFinished }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div v-if="selectedTool === 'embed'" class="flex items-start mb-2">
|
<div v-if="selectedTool === 'embed'" class="flex items-start mb-2">
|
||||||
<span class="material-icons text-base text-warning pt-1">star</span>
|
<span class="material-icons text-base text-warning pt-1">star</span>
|
||||||
<p class="text-gray-200 ml-2">Metadata will be embedded on the audio tracks inside your audiobook folder.</p>
|
<p class="text-gray-200 ml-2">Metadata will be embedded in the audio tracks inside your audiobook folder.</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex items-start mb-2">
|
<div v-else class="flex items-start mb-2">
|
||||||
<span class="material-icons text-base text-warning pt-1">star</span>
|
<span class="material-icons text-base text-warning pt-1">star</span>
|
||||||
@ -111,12 +111,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full max-w-4xl mx-auto">
|
<div class="w-full max-w-4xl mx-auto">
|
||||||
<p class="mb-2 font-semibold">Audio Tracks</p>
|
<p class="mb-2 font-semibold">{{ $strings.HeaderAudioTracks }}</p>
|
||||||
<div class="w-full mx-auto border border-white border-opacity-10 bg-bg">
|
<div class="w-full mx-auto border border-white border-opacity-10 bg-bg">
|
||||||
<div class="flex py-2 px-4 bg-primary bg-opacity-25">
|
<div class="flex py-2 px-4 bg-primary bg-opacity-25">
|
||||||
<div class="w-10 text-xs font-semibold text-gray-200">#</div>
|
<div class="w-10 text-xs font-semibold text-gray-200">#</div>
|
||||||
<div class="flex-grow text-xs font-semibold uppercase text-gray-200">Filename</div>
|
<div class="flex-grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelFilename }}</div>
|
||||||
<div class="w-16 text-xs font-semibold uppercase text-gray-200">Size</div>
|
<div class="w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelSize }}</div>
|
||||||
<div class="w-24"></div>
|
<div class="w-24"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="file in audioFiles">
|
<template v-for="file in audioFiles">
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="author.description" class="text-white text-opacity-60 uppercase text-xs mb-2">Description</p>
|
<p v-if="author.description" class="text-white text-opacity-60 uppercase text-xs mb-2">{{ $strings.LabelDescription }}</p>
|
||||||
<p class="text-white max-w-3xl text-sm leading-5">{{ author.description }}</p>
|
<p class="text-white max-w-3xl text-sm leading-5">{{ author.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<div class="py-4">
|
<div class="py-4">
|
||||||
<widgets-item-slider :items="libraryItems" :bookshelf-view="$constants.BookshelfView.AUTHOR">
|
<widgets-item-slider :items="libraryItems" :bookshelf-view="$constants.BookshelfView.AUTHOR">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">
|
||||||
<h2 class="text-lg">{{ libraryItems.length }} Books</h2>
|
<h2 class="text-lg">{{ libraryItems.length }} {{ $strings.LabelBooks }}</h2>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</widgets-item-slider>
|
</widgets-item-slider>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<nuxt-link :to="`/library/${currentLibraryId}/series/${series.id}`" class="hover:underline">
|
<nuxt-link :to="`/library/${currentLibraryId}/series/${series.id}`" class="hover:underline">
|
||||||
<h2 class="text-lg">{{ series.name }}</h2>
|
<h2 class="text-lg">{{ series.name }}</h2>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<p class="text-white text-opacity-40 text-base px-2">Series</p>
|
<p class="text-white text-opacity-40 text-base px-2">{{ $strings.LabelSeries }}</p>
|
||||||
</widgets-item-slider>
|
</widgets-item-slider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,47 +11,47 @@
|
|||||||
<div v-if="openMapOptions" class="flex flex-wrap">
|
<div v-if="openMapOptions" class="flex flex-wrap">
|
||||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.subtitle" />
|
<ui-checkbox v-model="selectedBatchUsage.subtitle" />
|
||||||
<ui-text-input-with-label ref="subtitleInput" v-model="batchDetails.subtitle" :disabled="!selectedBatchUsage.subtitle" label="Subtitle" class="mb-4 ml-4" />
|
<ui-text-input-with-label ref="subtitleInput" v-model="batchDetails.subtitle" :disabled="!selectedBatchUsage.subtitle" :label="$strings.LabelSubtitle" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.authors" />
|
<ui-checkbox v-model="selectedBatchUsage.authors" />
|
||||||
<!-- Authors filter only contains authors in this library, use query input to query all authors -->
|
<!-- Authors filter only contains authors in this library, use query input to query all authors -->
|
||||||
<ui-multi-select-query-input ref="authorsSelect" v-model="batchDetails.authors" :disabled="!selectedBatchUsage.authors" label="Authors" endpoint="authors/search" class="mb-4 ml-4" />
|
<ui-multi-select-query-input ref="authorsSelect" v-model="batchDetails.authors" :disabled="!selectedBatchUsage.authors" :label="$strings.LabelAuthors" endpoint="authors/search" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.publishedYear" />
|
<ui-checkbox v-model="selectedBatchUsage.publishedYear" />
|
||||||
<ui-text-input-with-label ref="publishedYearInput" v-model="batchDetails.publishedYear" :disabled="!selectedBatchUsage.publishedYear" label="Publish Year" class="mb-4 ml-4" />
|
<ui-text-input-with-label ref="publishedYearInput" v-model="batchDetails.publishedYear" :disabled="!selectedBatchUsage.publishedYear" :label="$strings.LabelPublishYear" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.series" />
|
<ui-checkbox v-model="selectedBatchUsage.series" />
|
||||||
<ui-multi-select ref="seriesSelect" v-model="batchDetails.series" :disabled="!selectedBatchUsage.series" label="Series" :items="seriesItems" @newItem="newSeriesItem" @removedItem="removedSeriesItem" class="mb-4 ml-4" />
|
<ui-multi-select ref="seriesSelect" v-model="batchDetails.series" :disabled="!selectedBatchUsage.series" :label="$strings.LabelSeries" :items="seriesItems" @newItem="newSeriesItem" @removedItem="removedSeriesItem" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-4 w-1/2">
|
<div class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.genres" />
|
<ui-checkbox v-model="selectedBatchUsage.genres" />
|
||||||
<ui-multi-select ref="genresSelect" v-model="batchDetails.genres" :disabled="!selectedBatchUsage.genres" label="Genres" :items="genreItems" @newItem="newGenreItem" @removedItem="removedGenreItem" class="mb-4 ml-4" />
|
<ui-multi-select ref="genresSelect" v-model="batchDetails.genres" :disabled="!selectedBatchUsage.genres" :label="$strings.LabelGenres" :items="genreItems" @newItem="newGenreItem" @removedItem="removedGenreItem" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-4 w-1/2">
|
<div class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.tags" />
|
<ui-checkbox v-model="selectedBatchUsage.tags" />
|
||||||
<ui-multi-select ref="tagsSelect" v-model="batchDetails.tags" label="Tags" :disabled="!selectedBatchUsage.tags" :items="tagItems" @newItem="newTagItem" @removedItem="removedTagItem" class="mb-4 ml-4" />
|
<ui-multi-select ref="tagsSelect" v-model="batchDetails.tags" :label="$strings.LabelTags" :disabled="!selectedBatchUsage.tags" :items="tagItems" @newItem="newTagItem" @removedItem="removedTagItem" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.narrators" />
|
<ui-checkbox v-model="selectedBatchUsage.narrators" />
|
||||||
<ui-multi-select ref="narratorsSelect" v-model="batchDetails.narrators" :disabled="!selectedBatchUsage.narrators" label="Narrators" :items="narratorItems" @newItem="newNarratorItem" @removedItem="removedNarratorItem" class="mb-4 ml-4" />
|
<ui-multi-select ref="narratorsSelect" v-model="batchDetails.narrators" :disabled="!selectedBatchUsage.narrators" :label="$strings.LabelNarrators" :items="narratorItems" @newItem="newNarratorItem" @removedItem="removedNarratorItem" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
<div v-if="!isPodcastLibrary" class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.publisher" />
|
<ui-checkbox v-model="selectedBatchUsage.publisher" />
|
||||||
<ui-text-input-with-label ref="publisherInput" v-model="batchDetails.publisher" :disabled="!selectedBatchUsage.publisher" label="Publisher" class="mb-4 ml-4" />
|
<ui-text-input-with-label ref="publisherInput" v-model="batchDetails.publisher" :disabled="!selectedBatchUsage.publisher" :label="$strings.LabelPublisher" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-4 w-1/2">
|
<div class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.language" />
|
<ui-checkbox v-model="selectedBatchUsage.language" />
|
||||||
<ui-text-input-with-label ref="languageInput" v-model="batchDetails.language" :disabled="!selectedBatchUsage.language" label="Language" class="mb-4 ml-4" />
|
<ui-text-input-with-label ref="languageInput" v-model="batchDetails.language" :disabled="!selectedBatchUsage.language" :label="$strings.LabelLanguage" class="mb-4 ml-4" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-4 w-1/2">
|
<div class="flex items-center px-4 w-1/2">
|
||||||
<ui-checkbox v-model="selectedBatchUsage.explicit" />
|
<ui-checkbox v-model="selectedBatchUsage.explicit" />
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<ui-checkbox
|
<ui-checkbox
|
||||||
v-model="batchDetails.explicit"
|
v-model="batchDetails.explicit"
|
||||||
label="Explicit"
|
:label="$strings.LabelExplicit"
|
||||||
:disabled="!selectedBatchUsage.explicit"
|
:disabled="!selectedBatchUsage.explicit"
|
||||||
:checkbox-bg="!selectedBatchUsage.explicit ? 'bg' : 'primary'"
|
:checkbox-bg="!selectedBatchUsage.explicit ? 'bg' : 'primary'"
|
||||||
:check-color="!selectedBatchUsage.explicit ? 'gray-600' : 'green-500'"
|
:check-color="!selectedBatchUsage.explicit ? 'gray-600' : 'green-500'"
|
||||||
@ -62,7 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex items-center justify-end p-4">
|
<div class="w-full flex items-center justify-end p-4">
|
||||||
<ui-btn color="success" :disabled="!hasSelectedBatchUsage" :padding-x="8" small class="text-base" :loading="isProcessing" @click="mapBatchDetails">Apply</ui-btn>
|
<ui-btn color="success" :disabled="!hasSelectedBatchUsage" :padding-x="8" small class="text-base" :loading="isProcessing" @click="mapBatchDetails">{{ $strings.ButtonApply }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
<div :class="isScrollable ? 'fixed left-0 box-shadow-lg-up bg-primary' : ''" class="w-full h-20 px-4 flex items-center border-t border-bg z-40" :style="{ bottom: streamLibraryItem ? '165px' : '0px' }">
|
<div :class="isScrollable ? 'fixed left-0 box-shadow-lg-up bg-primary' : ''" class="w-full h-20 px-4 flex items-center border-t border-bg z-40" :style="{ bottom: streamLibraryItem ? '165px' : '0px' }">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="success" :padding-x="8" class="text-lg" :loading="isProcessing" @click.prevent="saveClick">Save</ui-btn>
|
<ui-btn color="success" :padding-x="8" class="text-lg" :loading="isProcessing" @click.prevent="saveClick">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -342,7 +342,7 @@ export default {
|
|||||||
this.$toast.success(`Successfully updated ${data.updates} items`)
|
this.$toast.success(`Successfully updated ${data.updates} items`)
|
||||||
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf`)
|
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf`)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.warning('No updates were necessary')
|
this.$toast.warning(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="clickPlay">
|
<ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="clickPlay">
|
||||||
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
|
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
|
||||||
{{ streaming ? 'Streaming' : 'Play' }}
|
{{ streaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
<ui-icon-btn v-if="userCanUpdate" icon="edit" class="mx-0.5" @click="editClick" />
|
<ui-icon-btn v-if="userCanUpdate" icon="edit" class="mx-0.5" @click="editClick" />
|
||||||
|
@ -72,9 +72,14 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div class="py-2">
|
||||||
<p class="pr-4">{{ $strings.LabelSettingsDateFormat }}</p>
|
<p class="px-1 text-sm font-semibold">{{ $strings.LabelSettingsDateFormat }}</p>
|
||||||
<ui-dropdown v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-40" @input="(val) => updateSettingsKey('dateFormat', val)" />
|
<ui-dropdown v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-48" @input="(val) => updateSettingsKey('dateFormat', val)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-2">
|
||||||
|
<p class="px-1 text-sm font-semibold">{{ $strings.LabelLanguageDefaultServer }}</p>
|
||||||
|
<ui-dropdown v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-48" @input="updateServerLanguage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -321,6 +326,10 @@ export default {
|
|||||||
bookshelfView: !val ? this.$constants.BookshelfView.DETAIL : this.$constants.BookshelfView.STANDARD
|
bookshelfView: !val ? this.$constants.BookshelfView.DETAIL : this.$constants.BookshelfView.STANDARD
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
updateServerLanguage(val) {
|
||||||
|
this.$setLanguageCode(val)
|
||||||
|
this.updateSettingsKey('language', val)
|
||||||
|
},
|
||||||
updateSettingsKey(key, val) {
|
updateSettingsKey(key, val) {
|
||||||
this.updateServerSettings({
|
this.updateServerSettings({
|
||||||
[key]: val
|
[key]: val
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<div v-if="narrator" class="flex py-0.5 mt-4">
|
<div v-if="narrator" class="flex py-0.5 mt-4">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Narrated By</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNarrators }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<template v-for="(narrator, index) in narrators">
|
<template v-for="(narrator, index) in narrators">
|
||||||
@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="publishedYear" class="flex py-0.5">
|
<div v-if="publishedYear" class="flex py-0.5">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Publish Year</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPublishYear }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ publishedYear }}
|
{{ publishedYear }}
|
||||||
@ -61,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex py-0.5" v-if="genres.length">
|
<div class="flex py-0.5" v-if="genres.length">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Genres</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelGenres }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<template v-for="(genre, index) in genres">
|
<template v-for="(genre, index) in genres">
|
||||||
@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="tracks.length" class="flex py-0.5">
|
<div v-if="tracks.length" class="flex py-0.5">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Duration</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ durationPretty }}
|
{{ durationPretty }}
|
||||||
@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex py-0.5">
|
<div class="flex py-0.5">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Size</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelSize }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ sizePretty }}
|
{{ sizePretty }}
|
||||||
@ -100,7 +100,7 @@
|
|||||||
<!-- Podcast episode downloads queue -->
|
<!-- Podcast episode downloads queue -->
|
||||||
<div v-if="episodeDownloadsQueued.length" class="px-4 py-2 mt-4 bg-info bg-opacity-40 text-sm font-semibold rounded-md text-gray-100 relative max-w-max mx-auto md:mx-0">
|
<div v-if="episodeDownloadsQueued.length" class="px-4 py-2 mt-4 bg-info bg-opacity-40 text-sm font-semibold rounded-md text-gray-100 relative max-w-max mx-auto md:mx-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-sm py-1">{{ episodeDownloadsQueued.length }} Episode{{ episodeDownloadsQueued.length === 1 ? '' : 's' }} queued for download</p>
|
<p class="text-sm py-1">{{ $getString('MessageEpisodesQueuedForDownload', [episodeDownloadsQueued.length]) }}</p>
|
||||||
|
|
||||||
<span v-if="userIsAdminOrUp" class="material-icons hover:text-error text-xl ml-3 cursor-pointer" @click="clearDownloadQueue">close</span>
|
<span v-if="userIsAdminOrUp" class="material-icons hover:text-error text-xl ml-3 cursor-pointer" @click="clearDownloadQueue">close</span>
|
||||||
</div>
|
</div>
|
||||||
@ -110,16 +110,16 @@
|
|||||||
<div v-if="episodesDownloading.length" class="px-4 py-2 mt-4 bg-success bg-opacity-20 text-sm font-semibold rounded-md text-gray-100 relative max-w-max mx-auto md:mx-0">
|
<div v-if="episodesDownloading.length" class="px-4 py-2 mt-4 bg-success bg-opacity-20 text-sm font-semibold rounded-md text-gray-100 relative max-w-max mx-auto md:mx-0">
|
||||||
<div v-for="episode in episodesDownloading" :key="episode.id" class="flex items-center">
|
<div v-for="episode in episodesDownloading" :key="episode.id" class="flex items-center">
|
||||||
<widgets-loading-spinner />
|
<widgets-loading-spinner />
|
||||||
<p class="text-sm py-1 pl-4">Downloading episode "{{ episode.episodeDisplayTitle }}"</p>
|
<p class="text-sm py-1 pl-4">{{ $strings.MessageDownloadingEpisode }} "{{ episode.episodeDisplayTitle }}"</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Progress -->
|
<!-- Progress -->
|
||||||
<div v-if="!isPodcast && progressPercent > 0" class="px-4 py-2 mt-4 bg-primary text-sm font-semibold rounded-md text-gray-100 relative max-w-max mx-auto md:mx-0" :class="resettingProgress ? 'opacity-25' : ''">
|
<div v-if="!isPodcast && progressPercent > 0" class="px-4 py-2 mt-4 bg-primary text-sm font-semibold rounded-md text-gray-100 relative max-w-max mx-auto md:mx-0" :class="resettingProgress ? 'opacity-25' : ''">
|
||||||
<p v-if="progressPercent < 1" class="leading-6">Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
|
<p v-if="progressPercent < 1" class="leading-6">{{ $strings.LabelYourProgress }}: {{ Math.round(progressPercent * 100) }}%</p>
|
||||||
<p v-else class="text-xs">Finished {{ $formatDate(userProgressFinishedAt, dateFormat) }}</p>
|
<p v-else class="text-xs">{{ $strings.LabelFinished }} {{ $formatDate(userProgressFinishedAt, dateFormat) }}</p>
|
||||||
<p v-if="progressPercent < 1" class="text-gray-200 text-xs">{{ $elapsedPretty(userTimeRemaining) }} remaining</p>
|
<p v-if="progressPercent < 1" class="text-gray-200 text-xs">{{ $getString('LabelTimeRemaining', [$elapsedPretty(userTimeRemaining)]) }}</p>
|
||||||
<p class="text-gray-400 text-xs pt-1">Started {{ $formatDate(userProgressStartedAt, dateFormat) }}</p>
|
<p class="text-gray-400 text-xs pt-1">{{ $strings.LabelStarted }} {{ $formatDate(userProgressStartedAt, dateFormat) }}</p>
|
||||||
|
|
||||||
<div v-if="!resettingProgress" class="absolute -top-1.5 -right-1.5 p-1 w-5 h-5 rounded-full bg-bg hover:bg-error border border-primary flex items-center justify-center cursor-pointer" @click.stop="clearProgressClick">
|
<div v-if="!resettingProgress" class="absolute -top-1.5 -right-1.5 p-1 w-5 h-5 rounded-full bg-bg hover:bg-error border border-primary flex items-center justify-center cursor-pointer" @click.stop="clearProgressClick">
|
||||||
<span class="material-icons text-sm">close</span>
|
<span class="material-icons text-sm">close</span>
|
||||||
@ -130,41 +130,41 @@
|
|||||||
<div class="flex items-center justify-center md:justify-start pt-4">
|
<div class="flex items-center justify-center md:justify-start pt-4">
|
||||||
<ui-btn v-if="showPlayButton" :disabled="isStreaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playItem">
|
<ui-btn v-if="showPlayButton" :disabled="isStreaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playItem">
|
||||||
<span v-show="!isStreaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
|
<span v-show="!isStreaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
|
||||||
{{ isStreaming ? 'Playing' : 'Play' }}
|
{{ isStreaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
<ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
|
<ui-btn v-else-if="isMissing || isInvalid" color="error" :padding-x="4" small class="flex items-center h-9 mr-2">
|
||||||
<span v-show="!isStreaming" class="material-icons -ml-2 pr-1 text-white">error</span>
|
<span v-show="!isStreaming" class="material-icons -ml-2 pr-1 text-white">error</span>
|
||||||
{{ isMissing ? 'Missing' : 'Incomplete' }}
|
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
<ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
<ui-btn v-if="showReadButton" color="info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
|
||||||
<span class="material-icons -ml-2 pr-2 text-white">auto_stories</span>
|
<span class="material-icons -ml-2 pr-2 text-white">auto_stories</span>
|
||||||
Read
|
{{ $strings.ButtonRead }}
|
||||||
</ui-btn>
|
</ui-btn>
|
||||||
|
|
||||||
<ui-tooltip v-if="userCanUpdate" text="Edit" direction="top">
|
<ui-tooltip v-if="userCanUpdate" text="Edit" direction="top">
|
||||||
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
|
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
|
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast && userCanUpdate" text="Collections" direction="top">
|
<ui-tooltip v-if="!isPodcast && userCanUpdate" :text="$strings.LabelCollections" direction="top">
|
||||||
<ui-icon-btn icon="collections_bookmark" class="mx-0.5" outlined @click="collectionsClick" />
|
<ui-icon-btn icon="collections_bookmark" class="mx-0.5" outlined @click="collectionsClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<!-- Only admin or root user can download new episodes -->
|
<!-- Only admin or root user can download new episodes -->
|
||||||
<ui-tooltip v-if="isPodcast && userIsAdminOrUp" text="Find Episodes" direction="top">
|
<ui-tooltip v-if="isPodcast && userIsAdminOrUp" :text="$strings.LabelFindEpisodes" direction="top">
|
||||||
<ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
|
<ui-icon-btn icon="search" class="mx-0.5" :loading="fetchingRSSFeed" outlined @click="findEpisodesClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="bookmarks.length" text="Your Bookmarks" direction="top">
|
<ui-tooltip v-if="bookmarks.length" :text="$strings.LabelYourBookmarks" direction="top">
|
||||||
<ui-icon-btn :icon="bookmarks.length ? 'bookmarks' : 'bookmark_border'" class="mx-0.5" @click="clickBookmarksBtn" />
|
<ui-icon-btn :icon="bookmarks.length ? 'bookmarks' : 'bookmark_border'" class="mx-0.5" @click="clickBookmarksBtn" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<!-- RSS feed -->
|
<!-- RSS feed -->
|
||||||
<ui-tooltip v-if="showRssFeedBtn" text="Open RSS Feed" direction="top">
|
<ui-tooltip v-if="showRssFeedBtn" :text="$strings.LabelOpenRSSFeed" direction="top">
|
||||||
<ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeedUrl ? 'success' : 'primary'" outlined @click="clickRSSFeed" />
|
<ui-icon-btn icon="rss_feed" class="mx-0.5" :bg-color="rssFeedUrl ? 'success' : 'primary'" outlined @click="clickRSSFeed" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -507,12 +507,12 @@ export default {
|
|||||||
.$patch(`/api/me/progress/${this.libraryItemId}`, updatePayload)
|
.$patch(`/api/me/progress/${this.libraryItemId}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(`Item marked as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedFailed : this.$strings.ToastItemMarkedAsNotFinishedFailed)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
playItem(startTime = null) {
|
playItem(startTime = null) {
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
<div id="bookshelf" class="w-full overflow-y-auto px-2 py-6 sm:px-4 md:p-12 relative">
|
<div id="bookshelf" class="w-full overflow-y-auto px-2 py-6 sm:px-4 md:p-12 relative">
|
||||||
<div class="w-full max-w-3xl mx-auto py-4">
|
<div class="w-full max-w-3xl mx-auto py-4">
|
||||||
<p class="text-xl mb-2 font-semibold">Latest episodes</p>
|
<p class="text-xl mb-2 font-semibold">{{ $strings.HeaderLatestEpisodes }}</p>
|
||||||
<p v-if="!recentEpisodes.length && !processing" class="text-center text-xl">No podcasts found</p>
|
<p v-if="!recentEpisodes.length && !processing" class="text-center text-xl">{{ $strings.MessageNoEpisodes }}</p>
|
||||||
<template v-for="(episode, index) in episodesMapped">
|
<template v-for="(episode, index) in episodesMapped">
|
||||||
<div :key="episode.id" class="flex py-5 cursor-pointer relative" @click.stop="clickEpisode(episode)">
|
<div :key="episode.id" class="flex py-5 cursor-pointer relative" @click.stop="clickEpisode(episode)">
|
||||||
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](episode.libraryItemId)" :width="96" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" />
|
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](episode.libraryItemId)" :width="96" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" />
|
||||||
@ -25,7 +25,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="episode.progress" class="absolute bottom-0 left-0 h-0.5 pointer-events-none bg-warning" :style="{ width: episode.progress.progress * 100 + '%' }" />
|
<div v-if="episode.progress" class="absolute bottom-0 left-0 h-0.5 pointer-events-none bg-warning" :style="{ width: episode.progress.progress * 100 + '%' }" />
|
||||||
</div>
|
</div>
|
||||||
<div :key="index" v-if="index !== recentEpisodes.length" class="w-full h-px bg-white bg-opacity-10" />
|
<div :key="index" v-if="index !== recentEpisodes.length" class="w-full h-px bg-white bg-opacity-10" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
<div class="w-full max-w-4xl mx-auto flex">
|
<div class="w-full max-w-4xl mx-auto flex">
|
||||||
<form @submit.prevent="submit" class="flex flex-grow">
|
<form @submit.prevent="submit" class="flex flex-grow">
|
||||||
<ui-text-input v-model="searchInput" :disabled="processing" placeholder="Enter search term or RSS feed URL" class="flex-grow mr-2 text-sm md:text-base" />
|
<ui-text-input v-model="searchInput" :disabled="processing" placeholder="Enter search term or RSS feed URL" class="flex-grow mr-2 text-sm md:text-base" />
|
||||||
<ui-btn type="submit" :disabled="processing" class="hidden md:block">Submit</ui-btn>
|
<ui-btn type="submit" :disabled="processing" class="hidden md:block">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
<ui-btn type="submit" :disabled="processing" class="block md:hidden" small>Submit</ui-btn>
|
<ui-btn type="submit" :disabled="processing" class="block md:hidden" small>{{ $strings.ButtonSubmit }}</ui-btn>
|
||||||
</form>
|
</form>
|
||||||
<ui-file-input ref="fileInput" :accept="'.opml, .txt'" class="ml-2" @change="opmlFileUpload"> Upload OPML File </ui-file-input>
|
<ui-file-input ref="fileInput" :accept="'.opml, .txt'" class="ml-2" @change="opmlFileUpload">{{ $strings.ButtonUploadOPMLFile }}</ui-file-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full max-w-3xl mx-auto py-4">
|
<div class="w-full max-w-3xl mx-auto py-4">
|
||||||
<p v-if="termSearched && !results.length && !processing" class="text-center text-xl">No podcasts found</p>
|
<p v-if="termSearched && !results.length && !processing" class="text-center text-xl">{{ $strings.MessageNoPodcastsFound }}</p>
|
||||||
<template v-for="podcast in results">
|
<template v-for="podcast in results">
|
||||||
<div :key="podcast.id" class="flex p-1 hover:bg-primary hover:bg-opacity-25 cursor-pointer" @click="selectPodcast(podcast)">
|
<div :key="podcast.id" class="flex p-1 hover:bg-primary hover:bg-opacity-25 cursor-pointer" @click="selectPodcast(podcast)">
|
||||||
<div class="w-20 min-w-20 h-20 md:w-24 md:min-w-24 md:h-24 bg-primary">
|
<div class="w-20 min-w-20 h-20 md:w-24 md:min-w-24 md:h-24 bg-primary">
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<a :href="podcast.pageUrl" class="text-base md:text-lg text-gray-200 hover:underline" target="_blank" @click.stop>{{ podcast.title }}</a>
|
<a :href="podcast.pageUrl" class="text-base md:text-lg text-gray-200 hover:underline" target="_blank" @click.stop>{{ podcast.title }}</a>
|
||||||
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">by {{ podcast.artistName }}</p>
|
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">by {{ podcast.artistName }}</p>
|
||||||
<p class="text-xs text-gray-400 leading-5">{{ podcast.genres.join(', ') }}</p>
|
<p class="text-xs text-gray-400 leading-5">{{ podcast.genres.join(', ') }}</p>
|
||||||
<p class="text-xs text-gray-400 leading-5">{{ podcast.trackCount }} Episodes</p>
|
<p class="text-xs text-gray-400 leading-5">{{ podcast.trackCount }} {{ $strings.HeaderEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<app-book-shelf-toolbar is-home page="search" :search-query="query" />
|
<app-book-shelf-toolbar is-home page="search" :search-query="query" />
|
||||||
<app-book-shelf-categorized v-if="hasResults" ref="bookshelf" search :results="results" />
|
<app-book-shelf-categorized v-if="hasResults" ref="bookshelf" search :results="results" />
|
||||||
<div v-else class="w-full py-16">
|
<div v-else class="w-full py-16">
|
||||||
<p class="text-xl text-center">No Search results for "{{ query }}"</p>
|
<p class="text-xl text-center">{{ $getString('MessageNoSearchResultsFor', [query]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="w-full h-screen bg-bg">
|
<div class="w-full h-screen bg-bg">
|
||||||
<div class="w-full flex h-full items-center justify-center">
|
<div class="w-full flex h-full items-center justify-center">
|
||||||
<div v-if="criticalError" class="w-full max-w-md rounded border border-error border-opacity-25 bg-error bg-opacity-10 p-4">
|
<div v-if="criticalError" class="w-full max-w-md rounded border border-error border-opacity-25 bg-error bg-opacity-10 p-4">
|
||||||
<p class="text-center text-lg font-semibold">Server could not be reached</p>
|
<p class="text-center text-lg font-semibold">{{ $strings.MessageServerCouldNotBeReached }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="showInitScreen" class="w-full max-w-lg px-4 md:px-8 pb-8 pt-4">
|
<div v-else-if="showInitScreen" class="w-full max-w-lg px-4 md:px-8 pb-8 pt-4">
|
||||||
<p class="text-3xl text-white text-center mb-4">Initial Server Setup</p>
|
<p class="text-3xl text-white text-center mb-4">Initial Server Setup</p>
|
||||||
@ -19,22 +19,22 @@
|
|||||||
<ui-text-input-with-label v-model="MetadataPath" label="Metadata Path" disabled class="w-full mb-3 text-sm" />
|
<ui-text-input-with-label v-model="MetadataPath" label="Metadata Path" disabled class="w-full mb-3 text-sm" />
|
||||||
|
|
||||||
<div class="w-full flex justify-end py-3">
|
<div class="w-full flex justify-end py-3">
|
||||||
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Initializing...' : 'Submit' }}</ui-btn>
|
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Initializing...' : $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
|
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
|
||||||
<p class="text-3xl text-white text-center mb-4">Login</p>
|
<p class="text-3xl text-white text-center mb-4">{{ $strings.HeaderLogin }}</p>
|
||||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
||||||
<form @submit.prevent="submitForm">
|
<form @submit.prevent="submitForm">
|
||||||
<label class="text-xs text-gray-300 uppercase">Username</label>
|
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelUsername }}</label>
|
||||||
<ui-text-input v-model="username" :disabled="processing" class="mb-3 w-full" />
|
<ui-text-input v-model="username" :disabled="processing" class="mb-3 w-full" />
|
||||||
|
|
||||||
<label class="text-xs text-gray-300 uppercase">Password</label>
|
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelPassword }}</label>
|
||||||
<ui-text-input v-model="password" type="password" :disabled="processing" class="w-full mb-3" />
|
<ui-text-input v-model="password" type="password" :disabled="processing" class="w-full mb-3" />
|
||||||
<div class="w-full flex justify-end py-3">
|
<div class="w-full flex justify-end py-3">
|
||||||
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : 'Submit' }}</ui-btn>
|
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -128,6 +128,7 @@ export default {
|
|||||||
this.$store.commit('setServerSettings', serverSettings)
|
this.$store.commit('setServerSettings', serverSettings)
|
||||||
this.$store.commit('setSource', Source)
|
this.$store.commit('setSource', Source)
|
||||||
this.$store.commit('feeds/setFeeds', feeds)
|
this.$store.commit('feeds/setFeeds', feeds)
|
||||||
|
this.$setServerLanguageCode(serverSettings.language)
|
||||||
|
|
||||||
if (serverSettings.chromecastEnabled) {
|
if (serverSettings.chromecastEnabled) {
|
||||||
console.log('Chromecast enabled import script')
|
console.log('Chromecast enabled import script')
|
||||||
@ -189,6 +190,7 @@ export default {
|
|||||||
this.processing = false
|
this.processing = false
|
||||||
this.isInit = res.isInit
|
this.isInit = res.isInit
|
||||||
this.showInitScreen = !res.isInit
|
this.showInitScreen = !res.isInit
|
||||||
|
this.$setServerLanguageCode(res.language)
|
||||||
if (this.showInitScreen) {
|
if (this.showInitScreen) {
|
||||||
this.ConfigPath = res.ConfigPath || ''
|
this.ConfigPath = res.ConfigPath || ''
|
||||||
this.MetadataPath = res.MetadataPath || ''
|
this.MetadataPath = res.MetadataPath || ''
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
import Vue from "vue"
|
import Vue from "vue"
|
||||||
|
import enUsStrings from '../strings/en-us.json'
|
||||||
|
import { supplant } from './utils'
|
||||||
|
|
||||||
const defaultCode = 'en-us'
|
const defaultCode = 'en-us'
|
||||||
|
|
||||||
function supplant(str, subs) {
|
const languageCodeMap = {
|
||||||
// source: http://crockford.com/javascript/remedial.html
|
'en-us': 'English',
|
||||||
return str.replace(/{([^{}]*)}/g,
|
'es': 'Español',
|
||||||
function (a, b) {
|
'it': 'Italiano',
|
||||||
var r = subs[b]
|
'pl': 'Polski',
|
||||||
return typeof r === 'string' || typeof r === 'number' ? r : a
|
'zh-cn': '汉语 (简化字)'
|
||||||
}
|
}
|
||||||
)
|
Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map(code => {
|
||||||
|
return {
|
||||||
|
text: languageCodeMap[code],
|
||||||
|
value: code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Vue.prototype.$languageCodes = {
|
||||||
|
default: defaultCode,
|
||||||
|
current: defaultCode,
|
||||||
|
local: null,
|
||||||
|
server: null
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.prototype.$i18nCode = ''
|
Vue.prototype.$strings = { ...enUsStrings }
|
||||||
Vue.prototype.$strings = {}
|
|
||||||
Vue.prototype.$getString = (key, subs) => {
|
Vue.prototype.$getString = (key, subs) => {
|
||||||
if (!Vue.prototype.$strings[key]) return ''
|
if (!Vue.prototype.$strings[key]) return ''
|
||||||
if (subs && Array.isArray(subs) && subs.length) {
|
if (subs && Array.isArray(subs) && subs.length) {
|
||||||
@ -22,7 +34,9 @@ Vue.prototype.$getString = (key, subs) => {
|
|||||||
return Vue.prototype.$strings[key]
|
return Vue.prototype.$strings[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
var translations = {}
|
var translations = {
|
||||||
|
[defaultCode]: enUsStrings
|
||||||
|
}
|
||||||
|
|
||||||
function loadTranslationStrings(code) {
|
function loadTranslationStrings(code) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@ -30,35 +44,64 @@ function loadTranslationStrings(code) {
|
|||||||
resolve(fileContents.default)
|
resolve(fileContents.default)
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('Failed to load i18n strings', code, error)
|
console.error('Failed to load i18n strings', code, error)
|
||||||
resolve({})
|
resolve(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadi18n(code) {
|
async function loadi18n(code) {
|
||||||
if (Vue.prototype.$i18nCode == code) {
|
if (!code) return false
|
||||||
|
if (Vue.prototype.$languageCodes.current == code) {
|
||||||
// already set
|
// already set
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentCode = Vue.prototype.$i18nCode
|
|
||||||
const strings = translations[code] || await loadTranslationStrings(code)
|
const strings = translations[code] || await loadTranslationStrings(code)
|
||||||
|
if (!strings) {
|
||||||
|
console.warn(`Invalid lang code ${code}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
translations[code] = strings
|
translations[code] = strings
|
||||||
Vue.prototype.$i18nCode = code
|
Vue.prototype.$languageCodes.current = code
|
||||||
|
localStorage.setItem('lang', code)
|
||||||
if (!currentCode) {
|
|
||||||
// first load
|
|
||||||
Vue.prototype.$strings = strings
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in Vue.prototype.$strings) {
|
for (const key in Vue.prototype.$strings) {
|
||||||
Vue.prototype.$strings[key] = strings[key] || translations[defaultCode][key]
|
Vue.prototype.$strings[key] = strings[key] || translations[defaultCode][key]
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('i18n strings=', Vue.prototype.$strings)
|
console.log('i18n strings=', Vue.prototype.$strings)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.prototype.$i18nUpdate = loadi18n
|
Vue.prototype.$setLanguageCode = loadi18n
|
||||||
|
|
||||||
|
// Set the servers default language code, does not override users local language code
|
||||||
|
Vue.prototype.$setServerLanguageCode = (code) => {
|
||||||
|
if (!code) return
|
||||||
|
|
||||||
|
if (!languageCodeMap[code]) {
|
||||||
|
console.warn('invalid server language in', code)
|
||||||
|
} else {
|
||||||
|
Vue.prototype.$languageCodes.server = code
|
||||||
|
if (!Vue.prototype.$languageCodes.local && code !== defaultCode) {
|
||||||
|
loadi18n(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with language code in localStorage if valid
|
||||||
|
async function initialize() {
|
||||||
|
const localLanguage = localStorage.getItem('lang')
|
||||||
|
if (!localLanguage) return
|
||||||
|
|
||||||
|
if (!languageCodeMap[localLanguage]) {
|
||||||
|
console.warn('Invalid local language code', localLanguage)
|
||||||
|
localStorage.setItem('lang', defaultCode)
|
||||||
|
} else if (localLanguage !== defaultCode) {
|
||||||
|
Vue.prototype.$languageCodes.local = localLanguage
|
||||||
|
loadi18n(localLanguage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialize()
|
||||||
|
|
||||||
loadi18n(defaultCode)
|
|
@ -134,4 +134,14 @@ Vue.prototype.$parseCronExpression = (expression) => {
|
|||||||
return {
|
return {
|
||||||
description: `Run every ${weekdayText} at ${pieces[1]}:${pieces[0].padStart(2, '0')}`
|
description: `Run every ${weekdayText} at ${pieces[1]}:${pieces[0].padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function supplant(str, subs) {
|
||||||
|
// source: http://crockford.com/javascript/remedial.html
|
||||||
|
return str.replace(/{([^{}]*)}/g,
|
||||||
|
function (a, b) {
|
||||||
|
var r = subs[b]
|
||||||
|
return typeof r === 'string' || typeof r === 'number' ? r : a
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,19 +1,25 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Add",
|
"ButtonAdd": "Add",
|
||||||
"ButtonAddChapters": "Add Chapters",
|
"ButtonAddChapters": "Add Chapters",
|
||||||
|
"ButtonAddPodcasts": "Add Podcasts",
|
||||||
"ButtonAddYourFirstLibrary": "Add your first library",
|
"ButtonAddYourFirstLibrary": "Add your first library",
|
||||||
|
"ButtonApply": "Apply",
|
||||||
"ButtonApplyChapters": "Apply Chapters",
|
"ButtonApplyChapters": "Apply Chapters",
|
||||||
"ButtonAuthors": "Authors",
|
"ButtonAuthors": "Authors",
|
||||||
"ButtonBrowseForFolder": "Browse for Folder",
|
"ButtonBrowseForFolder": "Browse for Folder",
|
||||||
"ButtonCancel": "Cancel",
|
"ButtonCancel": "Cancel",
|
||||||
"ButtonChangePasswordSubmit": "Submit",
|
"ButtonCancelEncode": "Cancel Encode",
|
||||||
"ButtonChangeRootPassword": "Change Root Password",
|
"ButtonChangeRootPassword": "Change Root Password",
|
||||||
|
"ButtonCheckAndDownloadNewEpisodes": "Check & Download New Episodes",
|
||||||
"ButtonChooseAFolder": "Choose a folder",
|
"ButtonChooseAFolder": "Choose a folder",
|
||||||
"ButtonChooseFiles": "Choose files",
|
"ButtonChooseFiles": "Choose files",
|
||||||
|
"ButtonCloseFeed": "Close Feed",
|
||||||
"ButtonCollections": "Collections",
|
"ButtonCollections": "Collections",
|
||||||
"ButtonCreate": "Create",
|
"ButtonCreate": "Create",
|
||||||
"ButtonCreateBackup": "Create Backup",
|
"ButtonCreateBackup": "Create Backup",
|
||||||
|
"ButtonDelete": "Delete",
|
||||||
"ButtonEditChapters": "Edit Chapters",
|
"ButtonEditChapters": "Edit Chapters",
|
||||||
|
"ButtonEditPodcast": "Edit Podcast",
|
||||||
"ButtonForceReScan": "Force Re-Scan",
|
"ButtonForceReScan": "Force Re-Scan",
|
||||||
"ButtonFullPath": "Full Path",
|
"ButtonFullPath": "Full Path",
|
||||||
"ButtonHide": "Hide",
|
"ButtonHide": "Hide",
|
||||||
@ -23,34 +29,45 @@
|
|||||||
"ButtonLogout": "Logout",
|
"ButtonLogout": "Logout",
|
||||||
"ButtonLookup": "Lookup",
|
"ButtonLookup": "Lookup",
|
||||||
"ButtonLibrary": "Library",
|
"ButtonLibrary": "Library",
|
||||||
|
"ButtonManageTracks": "Manage Tracks",
|
||||||
|
"ButtonMapChapterTitles": "Map Chapter Titles",
|
||||||
"ButtonMatchAllAuthors": "Match All Authors",
|
"ButtonMatchAllAuthors": "Match All Authors",
|
||||||
"ButtonMatchBooks": "Match Books",
|
"ButtonMatchBooks": "Match Books",
|
||||||
"ButtonMapChapterTitles": "Map Chapter Titles",
|
|
||||||
"ButtonNevermind": "Nevermind",
|
"ButtonNevermind": "Nevermind",
|
||||||
"ButtonOk": "Ok",
|
"ButtonOk": "Ok",
|
||||||
|
"ButtonOpenFeed": "Open Feed",
|
||||||
"ButtonOpenManager": "Open Manager",
|
"ButtonOpenManager": "Open Manager",
|
||||||
|
"ButtonPlay": "Play",
|
||||||
|
"ButtonPlaying": "Playing",
|
||||||
"ButtonPurgeAllCache": "Purge All Cache",
|
"ButtonPurgeAllCache": "Purge All Cache",
|
||||||
"ButtonPurgeItemsCache": "Purge Items Cache",
|
"ButtonPurgeItemsCache": "Purge Items Cache",
|
||||||
"ButtonPurgeMediaProgress": "Purge Media Progress",
|
"ButtonPurgeMediaProgress": "Purge Media Progress",
|
||||||
"ButtonQuickMatch": "Quick Match",
|
"ButtonQuickMatch": "Quick Match",
|
||||||
|
"ButtonRead": "Read",
|
||||||
"ButtonRemove": "Remove",
|
"ButtonRemove": "Remove",
|
||||||
"ButtonRemoveAll": "Remove All",
|
"ButtonRemoveAll": "Remove All",
|
||||||
"ButtonRemoveAllLibraryItems": "Remove All Library Items",
|
"ButtonRemoveAllLibraryItems": "Remove All Library Items",
|
||||||
|
"ButtonRemoveFromContinueListening": "Remove from Continue Listening",
|
||||||
|
"ButtonRemoveSeriesFromContinueSeries": "Remove Series from Continue Series",
|
||||||
"ButtonReScan": "Re-Scan",
|
"ButtonReScan": "Re-Scan",
|
||||||
"ButtonReset": "Reset",
|
"ButtonReset": "Reset",
|
||||||
"ButtonRestore": "Restore",
|
"ButtonRestore": "Restore",
|
||||||
"ButtonSave": "Save",
|
"ButtonSave": "Save",
|
||||||
"ButtonSaveAndClose": "Save & Close",
|
"ButtonSaveAndClose": "Save & Close",
|
||||||
|
"ButtonSaveTracklist": "Save Tracklist",
|
||||||
"ButtonScan": "Scan",
|
"ButtonScan": "Scan",
|
||||||
"ButtonSearch": "Search",
|
"ButtonSearch": "Search",
|
||||||
"ButtonSelectFolderPath": "Select Folder Path",
|
"ButtonSelectFolderPath": "Select Folder Path",
|
||||||
"ButtonSeries": "Series",
|
"ButtonSeries": "Series",
|
||||||
"ButtonShiftTimes": "Shift Times",
|
"ButtonShiftTimes": "Shift Times",
|
||||||
"ButtonShow": "Show",
|
"ButtonShow": "Show",
|
||||||
|
"ButtonStartM4BEncode": "Start M4B Encode",
|
||||||
|
"ButtonStartMetadataEmbed": "Start Metadata Embed",
|
||||||
"ButtonSubmit": "Submit",
|
"ButtonSubmit": "Submit",
|
||||||
"ButtonUpload": "Upload",
|
"ButtonUpload": "Upload",
|
||||||
"ButtonUploadBackup": "Upload Backup",
|
"ButtonUploadBackup": "Upload Backup",
|
||||||
"ButtonUploadCover": "Upload Cover",
|
"ButtonUploadCover": "Upload Cover",
|
||||||
|
"ButtonUploadOPMLFile": "Upload OPML File",
|
||||||
"ButtonViewAll": "View All",
|
"ButtonViewAll": "View All",
|
||||||
"ButtonYes": "Yes",
|
"ButtonYes": "Yes",
|
||||||
"HeaderAccount": "Account",
|
"HeaderAccount": "Account",
|
||||||
@ -63,6 +80,7 @@
|
|||||||
"HeaderChapters": "Chapters",
|
"HeaderChapters": "Chapters",
|
||||||
"HeaderChooseAFolder": "Choose a Folder",
|
"HeaderChooseAFolder": "Choose a Folder",
|
||||||
"HeaderCollection": "Collection",
|
"HeaderCollection": "Collection",
|
||||||
|
"HeaderCollectionItems": "Collection Items",
|
||||||
"HeaderCover": "Cover",
|
"HeaderCover": "Cover",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
@ -71,27 +89,38 @@
|
|||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
"HeaderItemFiles": "Item Files",
|
"HeaderItemFiles": "Item Files",
|
||||||
"HeaderLastListeningSession": "Last Listening Session",
|
"HeaderLastListeningSession": "Last Listening Session",
|
||||||
|
"HeaderLatestEpisodes": "Latest episodes",
|
||||||
"HeaderLibraries": "Libraries",
|
"HeaderLibraries": "Libraries",
|
||||||
"HeaderLibraryFiles": "Library Files",
|
"HeaderLibraryFiles": "Library Files",
|
||||||
"HeaderLibraryStats": "Library Stats",
|
"HeaderLibraryStats": "Library Stats",
|
||||||
"HeaderListeningSessions": "Listening Sessions",
|
"HeaderListeningSessions": "Listening Sessions",
|
||||||
"HeaderListeningStats": "Listening Stats",
|
"HeaderListeningStats": "Listening Stats",
|
||||||
|
"HeaderLogin": "Login",
|
||||||
"HeaderLogs": "Logs",
|
"HeaderLogs": "Logs",
|
||||||
"HeaderMatch": "Match",
|
"HeaderMatch": "Match",
|
||||||
|
"HeaderMetadataToEmbed": "Metadata to embed",
|
||||||
"HeaderNewAccount": "New Account",
|
"HeaderNewAccount": "New Account",
|
||||||
"HeaderNewLibrary": "New Library",
|
"HeaderNewLibrary": "New Library",
|
||||||
"HeaderNotifications": "Notifications",
|
"HeaderNotifications": "Notifications",
|
||||||
"HeaderOtherFiles": "Other Files",
|
"HeaderOtherFiles": "Other Files",
|
||||||
|
"HeaderOpenRSSFeed": "Open RSS Feed",
|
||||||
"HeaderPermissions": "Permissions",
|
"HeaderPermissions": "Permissions",
|
||||||
|
"HeaderPodcastsToAdd": "Podcasts to Add",
|
||||||
"HeaderPreviewCover": "Preview Cover",
|
"HeaderPreviewCover": "Preview Cover",
|
||||||
|
"HeaderRemoveEpisode": "Remove Episode",
|
||||||
|
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
||||||
|
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
||||||
"HeaderSavedMediaProgress": "Saved Media Progress",
|
"HeaderSavedMediaProgress": "Saved Media Progress",
|
||||||
"HeaderSchedule": "Schedule",
|
"HeaderSchedule": "Schedule",
|
||||||
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
||||||
|
"HeaderSession": "Session",
|
||||||
|
"HeaderSetBackupSchedule": "Set Backup Schedule",
|
||||||
"HeaderSettings": "Settings",
|
"HeaderSettings": "Settings",
|
||||||
"HeaderSettingsDisplay": "Display",
|
"HeaderSettingsDisplay": "Display",
|
||||||
"HeaderSettingsExperimental": "Experimental Features",
|
"HeaderSettingsExperimental": "Experimental Features",
|
||||||
"HeaderSettingsGeneral": "General",
|
"HeaderSettingsGeneral": "General",
|
||||||
"HeaderSettingsScanner": "Scanner",
|
"HeaderSettingsScanner": "Scanner",
|
||||||
|
"HeaderSleepTimer": "Sleep Timer",
|
||||||
"HeaderStatsLongestItems": "Longest Items (hrs)",
|
"HeaderStatsLongestItems": "Longest Items (hrs)",
|
||||||
"HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)",
|
"HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)",
|
||||||
"HeaderStatsRecentSessions": "Recent Sessions",
|
"HeaderStatsRecentSessions": "Recent Sessions",
|
||||||
@ -99,15 +128,22 @@
|
|||||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||||
"HeaderTools": "Tools",
|
"HeaderTools": "Tools",
|
||||||
"HeaderUpdateAccount": "Update Account",
|
"HeaderUpdateAccount": "Update Account",
|
||||||
|
"HeaderUpdateAuthor": "Update Author",
|
||||||
"HeaderUpdateDetails": "Update Details",
|
"HeaderUpdateDetails": "Update Details",
|
||||||
"HeaderUpdateLibrary": "Update Library",
|
"HeaderUpdateLibrary": "Update Library",
|
||||||
"HeaderUsers": "Users",
|
"HeaderUsers": "Users",
|
||||||
"HeaderYourStats": "Your Stats",
|
"HeaderYourStats": "Your Stats",
|
||||||
"LabelAccountType": "Account Type",
|
"LabelAccountType": "Account Type",
|
||||||
|
"LabelAccountTypeAdmin": "Admin",
|
||||||
|
"LabelAccountTypeGuest": "Guest",
|
||||||
|
"LabelAccountTypeUser": "User",
|
||||||
"LabelActivity": "Activity",
|
"LabelActivity": "Activity",
|
||||||
|
"LabelAddToCollection": "Add to Collection",
|
||||||
|
"LabelAddToCollectionBatch": "Add {0} Books to Collection",
|
||||||
"LabelAllUsers": "All Users",
|
"LabelAllUsers": "All Users",
|
||||||
"LabelAuthor": "Author",
|
"LabelAuthor": "Author",
|
||||||
"LabelAuthors": "Authors",
|
"LabelAuthors": "Authors",
|
||||||
|
"LabelAutoDownloadEpisodes": "Auto Download Episodes",
|
||||||
"LabelBackToUser": "Back to User",
|
"LabelBackToUser": "Back to User",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Enable automatic backups",
|
"LabelBackupsEnableAutomaticBackups": "Enable automatic backups",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Backups saved in /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Backups saved in /metadata/backups",
|
||||||
@ -117,48 +153,85 @@
|
|||||||
"LabelBackupsNumberToKeepHelp": "Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.",
|
"LabelBackupsNumberToKeepHelp": "Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.",
|
||||||
"LabelBooks": "Books",
|
"LabelBooks": "Books",
|
||||||
"LabelChangePassword": "Change Password",
|
"LabelChangePassword": "Change Password",
|
||||||
|
"LabelChaptersFound": "chapters found",
|
||||||
|
"LabelChapterTitle": "Chapter Title",
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
"LabelCollapseSeries": "Collapse Series",
|
||||||
"LabelCollections": "Collections",
|
"LabelCollections": "Collections",
|
||||||
|
"LabelComplete": "Complete",
|
||||||
"LabelConfirmPassword": "Confirm Password",
|
"LabelConfirmPassword": "Confirm Password",
|
||||||
|
"LabelContinueListening": "Continue Listening",
|
||||||
|
"LabelContinueSeries": "Continue Series",
|
||||||
"LabelCover": "Cover",
|
"LabelCover": "Cover",
|
||||||
"LabelCoverImageURL": "Cover Image URL",
|
"LabelCoverImageURL": "Cover Image URL",
|
||||||
"LabelCreatedAt": "Created At",
|
"LabelCreatedAt": "Created At",
|
||||||
"LabelCronExpression": "Cron Expression",
|
"LabelCronExpression": "Cron Expression",
|
||||||
|
"LabelCurrent": "Current",
|
||||||
"LabelCurrently": "Currently:",
|
"LabelCurrently": "Currently:",
|
||||||
"LabelDatetime": "Datetime",
|
"LabelDatetime": "Datetime",
|
||||||
"LabelDescription": "Description",
|
"LabelDescription": "Description",
|
||||||
|
"LabelDeselectAll": "Deselect All",
|
||||||
|
"LabelDevice": "Device",
|
||||||
"LabelDeviceInfo": "Device Info",
|
"LabelDeviceInfo": "Device Info",
|
||||||
"LabelDirectory": "Directory",
|
"LabelDirectory": "Directory",
|
||||||
|
"LabelDiscFromFilename": "Disc from Filename",
|
||||||
|
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
|
"LabelDurationFound": "Duration found:",
|
||||||
|
"LabelEdit": "Edit",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
|
"LabelEpisode": "Episode",
|
||||||
|
"LabelEpisodeTitle": "Episode Title",
|
||||||
|
"LabelEpisodeType": "Episode Type",
|
||||||
"LabelExplicit": "Explicit",
|
"LabelExplicit": "Explicit",
|
||||||
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFile": "File",
|
"LabelFile": "File",
|
||||||
"LabelFilename": "Filename",
|
"LabelFilename": "Filename",
|
||||||
"LabelFilterByUser": "Filter by User",
|
"LabelFilterByUser": "Filter by User",
|
||||||
|
"LabelFindEpisodes": "Find Episodes",
|
||||||
"LabelFinished": "Finished",
|
"LabelFinished": "Finished",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
"LabelFolders": "Folders",
|
"LabelFolders": "Folders",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
|
"LabelHardDeleteFile": "Hard delete file",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
|
"LabelIncludeInTracklist": "Include in Tracklist",
|
||||||
|
"LabelIncomplete": "Incomplete",
|
||||||
|
"LabelInProgress": "In Progress",
|
||||||
"LabelInterval": "Interval",
|
"LabelInterval": "Interval",
|
||||||
|
"LabelInvalidParts": "Invalid Parts",
|
||||||
"LabelItem": "Item",
|
"LabelItem": "Item",
|
||||||
"LabelLanguage": "Language",
|
"LabelLanguage": "Language",
|
||||||
|
"LabelLanguageDefaultServer": "Default Server Language",
|
||||||
"LabelLastSeen": "Last Seen",
|
"LabelLastSeen": "Last Seen",
|
||||||
"LabelLastTime": "Last Time",
|
"LabelLastTime": "Last Time",
|
||||||
"LabelLastUpdate": "Last Update",
|
"LabelLastUpdate": "Last Update",
|
||||||
|
"LabelLess": "Less",
|
||||||
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
||||||
"LabelLibrary": "Library",
|
"LabelLibrary": "Library",
|
||||||
|
"LabelLibraryItem": "Library Item",
|
||||||
"LabelLibraryName": "Library Name",
|
"LabelLibraryName": "Library Name",
|
||||||
|
"LabelLimit": "Limit",
|
||||||
|
"LabelListenAgain": "Listen Again",
|
||||||
|
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
||||||
"LabelMarkSeries": "Mark Series",
|
"LabelMarkSeries": "Mark Series",
|
||||||
|
"LabelMediaPlayer": "Media Player",
|
||||||
"LabelMediaType": "Media Type",
|
"LabelMediaType": "Media Type",
|
||||||
"LabelMetadataProvider": "Metadata Provider",
|
"LabelMetadataProvider": "Metadata Provider",
|
||||||
|
"LabelMetaTag": "Meta Tag",
|
||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
|
"LabelMissing": "Missing",
|
||||||
|
"LabelMissingParts": "Missing Parts",
|
||||||
|
"LabelMore": "More",
|
||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
"LabelNarrators": "Narrators",
|
"LabelNarrators": "Narrators",
|
||||||
|
"LabelNew": "New",
|
||||||
|
"LabelNewestAuthors": "Newest Authors",
|
||||||
|
"LabelNewestEpisodes": "Newest Episodes",
|
||||||
"LabelNewPassword": "New Password",
|
"LabelNewPassword": "New Password",
|
||||||
|
"LabelNotes": "Notes",
|
||||||
"LabelNotFinished": "Not Finished",
|
"LabelNotFinished": "Not Finished",
|
||||||
"LabelNotificationEvent": "Notification Event",
|
"LabelNotificationEvent": "Notification Event",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@ -169,6 +242,7 @@
|
|||||||
"LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times",
|
"LabelNotificationsMaxFailedAttemptsHelp": "Notifications are disabled once they fail to send this many times",
|
||||||
"LabelNotificationsMaxQueueSize": "Max queue size for notification events",
|
"LabelNotificationsMaxQueueSize": "Max queue size for notification events",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
|
"LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
|
||||||
|
"LabelOpenRSSFeed": "Open RSS Feed",
|
||||||
"LabelPassword": "Password",
|
"LabelPassword": "Password",
|
||||||
"LabelPath": "Path",
|
"LabelPath": "Path",
|
||||||
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
|
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
|
||||||
@ -178,18 +252,26 @@
|
|||||||
"LabelPermissionsDownload": "Can Download",
|
"LabelPermissionsDownload": "Can Download",
|
||||||
"LabelPermissionsUpdate": "Can Update",
|
"LabelPermissionsUpdate": "Can Update",
|
||||||
"LabelPermissionsUpload": "Can Upload",
|
"LabelPermissionsUpload": "Can Upload",
|
||||||
|
"LabelPhotoPathURL": "Photo Path/URL",
|
||||||
"LabelPlayMethod": "Play Method",
|
"LabelPlayMethod": "Play Method",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
"LabelPrefixesToIgnore": "Prefixes to Ignore (case insensitive)",
|
||||||
"LabelProgress": "Progress",
|
"LabelProgress": "Progress",
|
||||||
"LabelProvider": "Provider",
|
"LabelProvider": "Provider",
|
||||||
|
"LabelPubDate": "Pub Date",
|
||||||
"LabelPublisher": "Publisher",
|
"LabelPublisher": "Publisher",
|
||||||
"LabelPublishYear": "Publish Year",
|
"LabelPublishYear": "Publish Year",
|
||||||
|
"LabelRecentlyAdded": "Recently Added",
|
||||||
|
"LabelRecentSeries": "Recent Series",
|
||||||
"LabelRegion": "Region",
|
"LabelRegion": "Region",
|
||||||
"LabelReleaseDate": "Release Date",
|
"LabelReleaseDate": "Release Date",
|
||||||
|
"LabelRSSFeedSlug": "RSS Feed Slug",
|
||||||
|
"LabelRSSFeedURL": "RSS Feed URL",
|
||||||
"LabelSearchTerm": "Search Term",
|
"LabelSearchTerm": "Search Term",
|
||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
|
"LabelSeason": "Season",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
"LabelSeriesName": "Series Name",
|
"LabelSeriesName": "Series Name",
|
||||||
@ -227,9 +309,12 @@
|
|||||||
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders. Uses .abs file extension",
|
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders. Uses .abs file extension",
|
||||||
"LabelSettingsSquareBookCovers": "User square book covers",
|
"LabelSettingsSquareBookCovers": "User square book covers",
|
||||||
"LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
|
"LabelSettingsSquareBookCoversHelp": "Prefer to use square covers over standard 1.6:1 book covers",
|
||||||
|
"LabelShowAll": "Show All",
|
||||||
"LabelSize": "Size",
|
"LabelSize": "Size",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
|
"LabelStarted": "Started",
|
||||||
"LabelStartedAt": "Started At",
|
"LabelStartedAt": "Started At",
|
||||||
|
"LabelStartTime": "Start Time",
|
||||||
"LabelStatsAudioTracks": "Audio Tracks",
|
"LabelStatsAudioTracks": "Audio Tracks",
|
||||||
"LabelStatsAuthors": "Authors",
|
"LabelStatsAuthors": "Authors",
|
||||||
"LabelStatsBestDay": "Best Day",
|
"LabelStatsBestDay": "Best Day",
|
||||||
@ -250,29 +335,74 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
||||||
"LabelTimeListened": "Time Listened",
|
"LabelTimeListened": "Time Listened",
|
||||||
"LabelTimeListenedToday": "Time Listened Today",
|
"LabelTimeListenedToday": "Time Listened Today",
|
||||||
|
"LabelTimeRemaining": "{0} remaining",
|
||||||
"LabelTimeToShift": "Time to shift in seconds",
|
"LabelTimeToShift": "Time to shift in seconds",
|
||||||
"LabelTitle": "Title",
|
"LabelTitle": "Title",
|
||||||
"LabelTotalTimeListened": "Total Time Listened",
|
"LabelTotalTimeListened": "Total Time Listened",
|
||||||
|
"LabelTrackFromFilename": "Track from Filename",
|
||||||
|
"LabelTrackFromMetadata": "Track from Metadata",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
|
"LabelUnknown": "Unknown",
|
||||||
|
"LabelUpdateCover": "Update Cover",
|
||||||
|
"LabelUpdateCoverHelp": "Allow overwriting of existing covers for the selected books when a match is located",
|
||||||
|
"LabelUpdatedAt": "Updated At",
|
||||||
|
"LabelUpdateDetails": "Update Details",
|
||||||
|
"LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
|
||||||
"LabelUploaderDragAndDrop": "Drag & drop files or folders",
|
"LabelUploaderDragAndDrop": "Drag & drop files or folders",
|
||||||
"LabelUploaderDropFiles": "Drop files",
|
"LabelUploaderDropFiles": "Drop files",
|
||||||
|
"LabelUseChapterTrack": "Use chapter track",
|
||||||
|
"LabelUseFullTrack": "Use full track",
|
||||||
"LabelUser": "User",
|
"LabelUser": "User",
|
||||||
"LabelUsername": "Username",
|
"LabelUsername": "Username",
|
||||||
|
"LabelValue": "Value",
|
||||||
|
"LabelVersion": "Version",
|
||||||
"LabelWeekdaysToRun": "Weekdays to run",
|
"LabelWeekdaysToRun": "Weekdays to run",
|
||||||
|
"LabelYourAudiobookDuration": "Your audiobook duration",
|
||||||
|
"LabelYourBookmarks": "Your Bookmarks",
|
||||||
|
"LabelYourProgress": "Your Progress",
|
||||||
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in",
|
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in",
|
||||||
"MessageBackupsNote": "Backups do not include any files stored in your library folders.",
|
"MessageBackupsNote": "Backups do not include any files stored in your library folders.",
|
||||||
|
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
|
||||||
|
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
||||||
|
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||||
"MessageCheckingCron": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
|
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||||
|
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
||||||
|
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
||||||
|
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
||||||
|
"MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
|
||||||
|
"MessageDownloadingEpisode": "Downloading episode",
|
||||||
|
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||||
|
"MessageEmbedFinished": "Embed Finished!",
|
||||||
|
"MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
|
||||||
|
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
||||||
|
"MessageFetching": "Fetching...",
|
||||||
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
||||||
"MessageImportantNotice": "Important Notice!",
|
"MessageImportantNotice": "Important Notice!",
|
||||||
"MessageInsertChapterBelow": "Insert chapter below",
|
"MessageInsertChapterBelow": "Insert chapter below",
|
||||||
|
"MessageItemsSelected": "{0} Items Selected",
|
||||||
"MessageJoinUsOn": "Join us on",
|
"MessageJoinUsOn": "Join us on",
|
||||||
|
"MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year",
|
||||||
"MessageLoading": "Loading...",
|
"MessageLoading": "Loading...",
|
||||||
"MessageLoadingFolders": "Loading folders...",
|
"MessageLoadingFolders": "Loading folders...",
|
||||||
|
"MessageM4BFailed": "M4B Failed!",
|
||||||
|
"MessageM4BFinished": "M4B Finished!",
|
||||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||||
|
"MessageMarkAsFinished": "Mark as Finished",
|
||||||
|
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
||||||
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
||||||
|
"MessageNoAudioTracks": "No audio tracks",
|
||||||
"MessageNoAuthors": "No Authors",
|
"MessageNoAuthors": "No Authors",
|
||||||
|
"MessageNoBackups": "No Backups",
|
||||||
|
"MessageNoBookmarks": "No Bookmarks",
|
||||||
"MessageNoChapters": "No Chapters",
|
"MessageNoChapters": "No Chapters",
|
||||||
|
"MessageNoCollections": "No Collections",
|
||||||
"MessageNoCoversFound": "No Covers Found",
|
"MessageNoCoversFound": "No Covers Found",
|
||||||
|
"MessageNoDescription": "No description",
|
||||||
|
"MessageNoEpisodeMatchesFound": "No episode matches found",
|
||||||
|
"MessageNoEpisodes": "No Episodes",
|
||||||
"MessageNoFoldersAvailable": "No Folders Available",
|
"MessageNoFoldersAvailable": "No Folders Available",
|
||||||
"MessageNoGenres": "No Genres",
|
"MessageNoGenres": "No Genres",
|
||||||
"MessageNoItems": "No Items",
|
"MessageNoItems": "No Items",
|
||||||
@ -281,29 +411,98 @@
|
|||||||
"MessageNoLogs": "No Logs",
|
"MessageNoLogs": "No Logs",
|
||||||
"MessageNoMediaProgress": "No Media Progress",
|
"MessageNoMediaProgress": "No Media Progress",
|
||||||
"MessageNoNotifications": "No Notifications",
|
"MessageNoNotifications": "No Notifications",
|
||||||
|
"MessageNoPodcastsFound": "No podcasts found",
|
||||||
"MessageNoResults": "No Results",
|
"MessageNoResults": "No Results",
|
||||||
|
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
|
||||||
|
"MessageNoUpdateNecessary": "No update necessary",
|
||||||
|
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
||||||
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
||||||
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
||||||
"MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
|
"MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
|
||||||
|
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
||||||
"MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
|
"MessageRemoveUserWarning": "Are you sure you want to permanently delete user \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
||||||
"MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
|
"MessageRestoreBackupConfirm": "Are you sure you want to restore the backup created on",
|
||||||
"MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.<br /><br />Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.<br /><br />All clients using your server will be automatically refreshed.",
|
"MessageRestoreBackupWarning": "Restoring a backup will overwrite the entire database located at /config and cover images in /metadata/items & /metadata/authors.<br /><br />Backups do not modify any files in your library folders. If you have enabled server settings to store cover art and metadata in your library folders then those are not backed up or overwritten.<br /><br />All clients using your server will be automatically refreshed.",
|
||||||
"MessageSearchResultsFor": "Search results for",
|
"MessageSearchResultsFor": "Search results for",
|
||||||
|
"MessageServerCouldNotBeReached": "Server could not be reached",
|
||||||
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
||||||
|
"MessageThinking": "Thinking...",
|
||||||
"MessageUploaderItemFailed": "Failed to upload",
|
"MessageUploaderItemFailed": "Failed to upload",
|
||||||
"MessageUploaderItemSuccess": "Successfully Uploaded!",
|
"MessageUploaderItemSuccess": "Successfully Uploaded!",
|
||||||
"MessageUploading": "Uploading...",
|
"MessageUploading": "Uploading...",
|
||||||
"MessageValidCronExpression": "Valid cron expression",
|
"MessageValidCronExpression": "Valid cron expression",
|
||||||
"MessageWatcherIsDisabledGlobally": "Watcher is disabled globally in server settings",
|
"MessageWatcherIsDisabledGlobally": "Watcher is disabled globally in server settings",
|
||||||
|
"MessageYourAudiobookDurationIsLonger": "Your audiobook duration is longer than the duration found",
|
||||||
|
"MessageYourAudiobookDurationIsShorter": "Your audiobook duration is shorter than duration found",
|
||||||
"NoteChangeRootPassword": "Root user is the only user that can have an empty password",
|
"NoteChangeRootPassword": "Root user is the only user that can have an empty password",
|
||||||
"NoteChapterEditorTimes": "Note: First chapter start time must remain at 0:00 and the last chapter start time cannot exceed this audiobooks duration.",
|
"NoteChapterEditorTimes": "Note: First chapter start time must remain at 0:00 and the last chapter start time cannot exceed this audiobooks duration.",
|
||||||
"NoteFolderPicker": "Note: folders already mapped will not be shown",
|
"NoteFolderPicker": "Note: folders already mapped will not be shown",
|
||||||
"NoteFolderPickerDebian": "Note: Folder picker for the debian install is not fully implemented. You should enter the path to your library directly.",
|
"NoteFolderPickerDebian": "Note: Folder picker for the debian install is not fully implemented. You should enter the path to your library directly.",
|
||||||
|
"NoteRSSFeedPodcastAppsHttps": "Warning: Most podcast apps will require the RSS feed URL is using HTTPS",
|
||||||
|
"NoteRSSFeedPodcastAppsPubDate": "Warning: 1 or more of your episodes do not have a Pub Date. Some podcast apps require this.",
|
||||||
"NoteUploaderFoldersWithMediaFiles": "Folders with media files will be handled as separate library items.",
|
"NoteUploaderFoldersWithMediaFiles": "Folders with media files will be handled as separate library items.",
|
||||||
"NoteUploaderOnlyAudioFiles": "If uploading only audio files then each audio file will be handled as a separate audiobook.",
|
"NoteUploaderOnlyAudioFiles": "If uploading only audio files then each audio file will be handled as a separate audiobook.",
|
||||||
"NoteUploaderUnsupportedFiles": "Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.",
|
"NoteUploaderUnsupportedFiles": "Unsupported files are ignored. When choosing or dropping a folder, other files that are not in an item folder are ignored.",
|
||||||
|
"PlaceholderNewCollection": "New collection name",
|
||||||
"PlaceholderNewFolderPath": "New folder path",
|
"PlaceholderNewFolderPath": "New folder path",
|
||||||
"PlaceholderSearch": "Search..",
|
"PlaceholderSearch": "Search..",
|
||||||
|
"ToastAccountUpdateFailed": "Failed to update account",
|
||||||
|
"ToastAccountUpdateSuccess": "Account updated",
|
||||||
|
"ToastAuthorImageRemoveFailed": "Failed to remove image",
|
||||||
|
"ToastAuthorImageRemoveSuccess": "Author image removed",
|
||||||
|
"ToastAuthorUpdateFailed": "Failed to update author",
|
||||||
|
"ToastAuthorUpdateMerged": "Author merged",
|
||||||
|
"ToastAuthorUpdateSuccess": "Author updated",
|
||||||
|
"ToastAuthorUpdateSuccessNoImageFound": "Author updated (no image found)",
|
||||||
|
"ToastBackupCreateFailed": "Failed to create backup",
|
||||||
|
"ToastBackupCreateSuccess": "Backup created",
|
||||||
|
"ToastBackupDeleteFailed": "Failed to delete backup",
|
||||||
|
"ToastBackupDeleteSuccess": "Backup deleted",
|
||||||
|
"ToastBackupRestoreFailed": "Failed to restore backup",
|
||||||
|
"ToastBackupUploadFailed": "Failed to upload backup",
|
||||||
|
"ToastBackupUploadSuccess": "Backup uploaded",
|
||||||
|
"ToastBatchUpdateFailed": "Batch update failed",
|
||||||
|
"ToastBatchUpdateSuccess": "Batch update success",
|
||||||
|
"ToastBookmarkCreateFailed": "Failed to create bookmark",
|
||||||
|
"ToastBookmarkCreateSuccess": "Bookmark added",
|
||||||
|
"ToastBookmarkRemoveFailed": "Failed to remove bookmark",
|
||||||
|
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||||
|
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||||
|
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
||||||
|
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
||||||
|
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
|
||||||
|
"ToastCollectionRemoveFailed": "Failed to remove collection",
|
||||||
|
"ToastCollectionRemoveSuccess": "Collection removed",
|
||||||
|
"ToastCollectionUpdateFailed": "Failed to update collection",
|
||||||
|
"ToastCollectionUpdateSuccess": "Collection updated",
|
||||||
|
"ToastItemCoverUpdateFailed": "Failed to update item cover",
|
||||||
|
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||||
|
"ToastItemDetailsUpdateFailed": "Failed to update item details",
|
||||||
|
"ToastItemDetailsUpdateSuccess": "Item details updated",
|
||||||
|
"ToastItemDetailsUpdateUnneeded": "No updates needed for item details",
|
||||||
|
"ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
|
||||||
|
"ToastItemMarkedAsFinishedSuccess": "Item marked as Finished",
|
||||||
|
"ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
|
||||||
|
"ToastItemMarkedAsNotFinishedSuccess": "Item marked as Not Finished",
|
||||||
|
"ToastLibraryCreateFailed": "Failed to create library",
|
||||||
|
"ToastLibraryCreateSuccess": "Library \"{0}\" created",
|
||||||
|
"ToastLibraryDeleteFailed": "Failed to delete library",
|
||||||
|
"ToastLibraryDeleteSuccess": "Library deleted",
|
||||||
|
"ToastLibraryScanFailedToStart": "Failed to start scan",
|
||||||
|
"ToastLibraryScanStarted": "Library scan started",
|
||||||
|
"ToastLibraryUpdateFailed": "Failed to update library",
|
||||||
|
"ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
|
||||||
|
"ToastPodcastCreateFailed": "Failed to create podcast",
|
||||||
|
"ToastPodcastCreateSuccess": "Podcast created successfully",
|
||||||
|
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
|
||||||
|
"ToastRemoveItemFromCollectionFailed": "Failed to remove item from collection",
|
||||||
|
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
||||||
|
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
||||||
|
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||||
|
"ToastSessionDeleteSuccess": "Session deleted",
|
||||||
|
"ToastUserDeleteFailed": "Failed to delete user",
|
||||||
|
"ToastUserDeleteSuccess": "User deleted",
|
||||||
"WeekdayFriday": "Friday",
|
"WeekdayFriday": "Friday",
|
||||||
"WeekdayMonday": "Monday",
|
"WeekdayMonday": "Monday",
|
||||||
"WeekdaySaturday": "Saturday",
|
"WeekdaySaturday": "Saturday",
|
||||||
|
@ -51,4 +51,4 @@
|
|||||||
"MessageReportBugsAndContribute": "报告错误, 请求新功能, 做贡献请点击",
|
"MessageReportBugsAndContribute": "报告错误, 请求新功能, 做贡献请点击",
|
||||||
"NoteChangeRootPassword": "Root 用户是唯一可以有空密码的用户",
|
"NoteChangeRootPassword": "Root 用户是唯一可以有空密码的用户",
|
||||||
"SearchPlaceholder": "搜索.."
|
"SearchPlaceholder": "搜索.."
|
||||||
}
|
}
|
@ -254,7 +254,8 @@ class Server {
|
|||||||
// status check for client to see if server has been initialized
|
// status check for client to see if server has been initialized
|
||||||
// server has been initialized if a root user exists
|
// server has been initialized if a root user exists
|
||||||
const payload = {
|
const payload = {
|
||||||
isInit: this.db.hasRootUser
|
isInit: this.db.hasRootUser,
|
||||||
|
language: this.db.serverSettings.language
|
||||||
}
|
}
|
||||||
if (!payload.isInit) {
|
if (!payload.isInit) {
|
||||||
payload.ConfigPath = global.ConfigPath
|
payload.ConfigPath = global.ConfigPath
|
||||||
|
@ -53,6 +53,7 @@ class ServerSettings {
|
|||||||
this.chromecastEnabled = false
|
this.chromecastEnabled = false
|
||||||
this.enableEReader = false
|
this.enableEReader = false
|
||||||
this.dateFormat = 'MM/dd/yyyy'
|
this.dateFormat = 'MM/dd/yyyy'
|
||||||
|
this.language = 'en-us'
|
||||||
|
|
||||||
this.logLevel = Logger.logLevel
|
this.logLevel = Logger.logLevel
|
||||||
|
|
||||||
@ -102,6 +103,7 @@ class ServerSettings {
|
|||||||
this.chromecastEnabled = !!settings.chromecastEnabled
|
this.chromecastEnabled = !!settings.chromecastEnabled
|
||||||
this.enableEReader = !!settings.enableEReader
|
this.enableEReader = !!settings.enableEReader
|
||||||
this.dateFormat = settings.dateFormat || 'MM/dd/yyyy'
|
this.dateFormat = settings.dateFormat || 'MM/dd/yyyy'
|
||||||
|
this.language = settings.language || 'en-us'
|
||||||
this.logLevel = settings.logLevel || Logger.logLevel
|
this.logLevel = settings.logLevel || Logger.logLevel
|
||||||
this.version = settings.version || null
|
this.version = settings.version || null
|
||||||
|
|
||||||
@ -153,6 +155,7 @@ class ServerSettings {
|
|||||||
chromecastEnabled: this.chromecastEnabled,
|
chromecastEnabled: this.chromecastEnabled,
|
||||||
enableEReader: this.enableEReader,
|
enableEReader: this.enableEReader,
|
||||||
dateFormat: this.dateFormat,
|
dateFormat: this.dateFormat,
|
||||||
|
language: this.language,
|
||||||
logLevel: this.logLevel,
|
logLevel: this.logLevel,
|
||||||
version: this.version
|
version: this.version
|
||||||
}
|
}
|
||||||
|
@ -320,6 +320,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'continue-listening',
|
id: 'continue-listening',
|
||||||
label: 'Continue Listening',
|
label: 'Continue Listening',
|
||||||
|
labelStringKey: 'LabelContinueListening',
|
||||||
type: isPodcastLibrary ? 'episode' : mediaType,
|
type: isPodcastLibrary ? 'episode' : mediaType,
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'recentlyListened'
|
category: 'recentlyListened'
|
||||||
@ -327,6 +328,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'continue-series',
|
id: 'continue-series',
|
||||||
label: 'Continue Series',
|
label: 'Continue Series',
|
||||||
|
labelStringKey: 'LabelContinueSeries',
|
||||||
type: mediaType,
|
type: mediaType,
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'continueSeries'
|
category: 'continueSeries'
|
||||||
@ -334,6 +336,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'recently-added',
|
id: 'recently-added',
|
||||||
label: 'Recently Added',
|
label: 'Recently Added',
|
||||||
|
labelStringKey: 'LabelRecentlyAdded',
|
||||||
type: mediaType,
|
type: mediaType,
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'newestItems'
|
category: 'newestItems'
|
||||||
@ -341,6 +344,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'listen-again',
|
id: 'listen-again',
|
||||||
label: 'Listen Again',
|
label: 'Listen Again',
|
||||||
|
labelStringKey: 'LabelListenAgain',
|
||||||
type: isPodcastLibrary ? 'episode' : mediaType,
|
type: isPodcastLibrary ? 'episode' : mediaType,
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'recentlyFinished'
|
category: 'recentlyFinished'
|
||||||
@ -348,6 +352,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'recent-series',
|
id: 'recent-series',
|
||||||
label: 'Recent Series',
|
label: 'Recent Series',
|
||||||
|
labelStringKey: 'LabelRecentSeries',
|
||||||
type: 'series',
|
type: 'series',
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'newestSeries'
|
category: 'newestSeries'
|
||||||
@ -355,6 +360,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'newest-authors',
|
id: 'newest-authors',
|
||||||
label: 'Newest Authors',
|
label: 'Newest Authors',
|
||||||
|
labelStringKey: 'LabelNewestAuthors',
|
||||||
type: 'authors',
|
type: 'authors',
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'newestAuthors'
|
category: 'newestAuthors'
|
||||||
@ -362,6 +368,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
id: 'episodes-recently-added',
|
id: 'episodes-recently-added',
|
||||||
label: 'Newest Episodes',
|
label: 'Newest Episodes',
|
||||||
|
labelStringKey: 'LabelNewestEpisodes',
|
||||||
type: 'episode',
|
type: 'episode',
|
||||||
entities: [],
|
entities: [],
|
||||||
category: 'newestEpisodes'
|
category: 'newestEpisodes'
|
||||||
|
Loading…
Reference in New Issue
Block a user