Fix ebook url #75, download other files #75, fix book icon disappearing #88, backups #87

This commit is contained in:
advplyr 2021-10-08 17:30:20 -05:00
parent f752c19418
commit e80ec10e8a
32 changed files with 954 additions and 74 deletions

View file

@ -51,6 +51,19 @@
opacity: 0;
}
/* Chrome, Safari, Edge, Opera */
.no-spinner::-webkit-outer-spin-button,
.no-spinner::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
.tracksTable {
border-collapse: collapse;
width: 100%;

View file

@ -1,6 +1,6 @@
<template>
<div class="w-full h-10 relative">
<div id="toolbar" class="absolute top-0 left-0 w-full h-full z-40 flex items-center px-8">
<div id="toolbar" class="absolute top-0 left-0 w-full h-full z-20 flex items-center px-8">
<template v-if="page !== 'search' && !isHome">
<p v-if="!selectedSeries" class="font-book">{{ numShowing }} {{ entityName }}</p>
<div v-else class="flex items-center">

View file

@ -15,7 +15,7 @@
</div>
<div class="h-full flex items-center">
<div style="width: 100px; max-width: 100px" class="h-full flex items-center overflow-x-hidden">
<span v-show="hasPrev" class="material-icons text-black text-opacity-30 hover:text-opacity-80 cursor-pointer text-8xl" @click="pageLeft">chevron_left</span>
<span v-show="hasPrev" class="material-icons text-black text-opacity-30 hover:text-opacity-80 cursor-pointer text-8xl" @mousedown.prevent @click="pageLeft">chevron_left</span>
</div>
<div id="frame" class="w-full" style="height: 650px">
<div id="viewer" class="spreads"></div>
@ -25,7 +25,7 @@
</div>
</div>
<div style="width: 100px; max-width: 100px" class="h-full flex items-center overflow-x-hidden">
<span v-show="hasNext" class="material-icons text-black text-opacity-30 hover:text-opacity-80 cursor-pointer text-8xl" @click="pageRight">chevron_right</span>
<span v-show="hasNext" class="material-icons text-black text-opacity-30 hover:text-opacity-80 cursor-pointer text-8xl" @mousedown.prevent @click="pageRight">chevron_right</span>
</div>
</div>
</div>
@ -69,10 +69,13 @@ export default {
this.$emit('input', val)
}
},
fullUrl() {
var serverUrl = process.env.serverUrl || '/local'
return `${serverUrl}/${this.url}`
userToken() {
return this.$store.getters['user/getToken']
}
// fullUrl() {
// var serverUrl = process.env.serverUrl || `/s/book/${this.audiobookId}`
// return `${serverUrl}/${this.url}`
// }
},
methods: {
changedChapter() {
@ -113,7 +116,13 @@ export default {
init() {
this.registerListeners()
var book = ePub(this.fullUrl)
console.log('epub', this.url)
// var book = ePub(this.url, {
// requestHeaders: {
// Authorization: `Bearer ${this.userToken}`
// }
// })
var book = ePub(this.url)
this.book = book
this.rendition = book.renderTo('viewer', {

View file

@ -33,6 +33,7 @@
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ volumeNumber }}</p>
</div>
<!-- EBook Icon -->
<div v-if="showExperimentalFeatures && hasEbook" class="absolute rounded-full bg-blue-500 w-6 h-6 flex items-center justify-center bg-opacity-90" :style="{ bottom: 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' }">EBook</p> -->
<span class="material-icons text-white text-base">auto_stories</span>

View file

@ -53,9 +53,6 @@ export default {
book() {
return this.audiobook.book || {}
},
bookLastUpdate() {
return this.book.lastUpdate || Date.now()
},
title() {
return this.book.title || 'No Title'
},

View file

@ -7,7 +7,7 @@
<span v-if="!search" class="material-icons" style="font-size: 1.2rem">search</span>
<span v-else class="material-icons" style="font-size: 1.2rem">close</span>
</div>
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-10 -mt-px w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
<div v-show="showMenu && (lastSearch || isTyping)" class="absolute z-40 -mt-px w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
<li v-if="isTyping" class="py-2 px-2">
<p>Typing...</p>

View file

@ -13,8 +13,8 @@
</div>
<div class="flex-grow pl-6 pr-2">
<div class="flex items-center">
<div v-if="userCanUpload" class="w-40 pr-2" style="min-width: 160px">
<ui-file-input ref="fileInput" @change="fileUploadSelected" />
<div v-if="userCanUpload" class="w-40 pr-2 pt-4" style="min-width: 160px">
<ui-file-input ref="fileInput" @change="fileUploadSelected">Upload Cover</ui-file-input>
</div>
<form @submit.prevent="submitForm" class="flex flex-grow">
<ui-text-input-with-label v-model="imageUrl" label="Cover Image URL" />
@ -24,7 +24,7 @@
<div v-if="localCovers.length" class="mb-4 mt-6 border-t border-b border-primary">
<div class="flex items-center justify-center py-2">
<p>{{ localCovers.length }} local image(s)</p>
<p>{{ localCovers.length }} local image{{ localCovers.length !== 1 ? 's' : '' }}</p>
<div class="flex-grow" />
<ui-btn small @click="showLocalCovers = !showLocalCovers">{{ showLocalCovers ? 'Hide' : 'Show' }}</ui-btn>
</div>

View file

@ -73,7 +73,10 @@ export default {
return {
...track,
relativePath: trackPath.replace(audiobookPath, '').replace(/%/g, '%25').replace(/#/g, '%23')
relativePath: trackPath
.replace(audiobookPath + '/', '')
.replace(/%/g, '%25')
.replace(/#/g, '%23')
}
})
},

View file

@ -0,0 +1,85 @@
<template>
<div ref="wrapper" class="modal modal-bg w-full h-full fixed top-0 left-0 bg-primary bg-opacity-75 flex items-center justify-center z-40 opacity-0">
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
<div ref="content" style="min-width: 400px; min-height: 200px" class="relative text-white" :style="{ height: modalHeight, width: modalWidth }">
<slot />
</div>
</div>
</template>
<script>
export default {
props: {
value: Boolean,
persistent: {
type: Boolean,
default: true
},
width: {
type: [String, Number],
default: 500
},
height: {
type: [String, Number],
default: 'unset'
}
},
data() {
return {
el: null,
content: null
}
},
watch: {
show(newVal) {
if (newVal) {
this.setShow()
} else {
this.setHide()
}
}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
modalHeight() {
if (typeof this.height === 'string') {
return this.height
} else {
return this.height + 'px'
}
},
modalWidth() {
return typeof this.width === 'string' ? this.width : this.width + 'px'
}
},
methods: {
setShow() {
document.body.appendChild(this.el)
setTimeout(() => {
this.content.style.transform = 'scale(1)'
}, 10)
document.documentElement.classList.add('modal-open')
},
setHide() {
this.content.style.transform = 'scale(0)'
this.el.remove()
document.documentElement.classList.remove('modal-open')
}
},
mounted() {
this.el = this.$refs.wrapper
this.content = this.$refs.content
this.content.style.transform = 'scale(0)'
this.content.style.transition = 'transform 0.25s cubic-bezier(0.16, 1, 0.3, 1)'
this.el.style.opacity = 1
this.el.remove()
}
}
</script>

View file

@ -0,0 +1,190 @@
<template>
<div class="text-center mt-4">
<div class="flex py-4">
<ui-file-input ref="fileInput" class="mr-2" accept=".audiobookshelf" @change="backupUploaded">Upload Backup</ui-file-input>
<div class="flex-grow" />
<ui-btn :loading="isBackingUp" @click="clickCreateBackup">Create Backup</ui-btn>
</div>
<div class="relative">
<table id="backups">
<tr>
<th>File</th>
<th class="w-56">Datetime</th>
<th class="w-28">Size</th>
<th class="w-36"></th>
</tr>
<tr v-for="backup in backups" :key="backup.id">
<td>
<p class="truncate">/{{ backup.path.replace(/\\/g, '/') }}</p>
</td>
<td class="font-sans">{{ backup.datePretty }}</td>
<td class="font-mono">{{ $bytesPretty(backup.fileSize) }}</td>
<td>
<div class="w-full flex items-center justify-center">
<ui-btn small color="primary" @click="applyBackup(backup)">Apply</ui-btn>
<a :href="`/metadata/${backup.path.replace(/%/g, '%25').replace(/#/g, '%23')}?token=${userToken}`" class="mx-1 pt-0.5 hover:text-opacity-100 text-opacity-70 text-white" download><span class="material-icons text-xl">download</span></a>
<!-- <span class="material-icons text-xl hover:text-opacity-100 text-opacity-70 text-white cursor-pointer mx-1" @click="downloadBackup">download</span> -->
<span class="material-icons text-xl hover:text-error hover:text-opacity-100 text-opacity-70 text-white cursor-pointer mx-1" @click="deleteBackupClick(backup)">delete</span>
</div>
</td>
</tr>
<tr v-if="!backups.length" class="staticrow">
<td colspan="4" class="text-lg">No Backups</td>
</tr>
</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">
<ui-loading-indicator />
</div>
</div>
<prompt-dialog v-model="showConfirmApply" :width="675">
<div v-if="selectedBackup" class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300">
<p class="text-error text-lg font-semibold">Important Notice!</p>
<p class="text-base py-1">Applying a backup will overwrite users, user progress, book details, settings, and covers stored in metadata with the backed up data.</p>
<p class="text-base py-1">Backups <strong>do not</strong> modify any files in your library folders, only data in the audiobookshelf created <span class="font-mono">/config</span> and <span class="font-mono">/metadata</span> directories.</p>
<p class="text-base py-1">All clients using your server will be automatically refreshed.</p>
<p class="text-lg text-center my-8">Are you sure you want to apply the backup created on {{ selectedBackup.datePretty }}?</p>
<div class="flex px-1 items-center">
<ui-btn color="primary" @click="showConfirmApply = false">Nevermind</ui-btn>
<div class="flex-grow" />
<ui-btn color="success" @click="confirm">Apply Backup</ui-btn>
</div>
</div>
</prompt-dialog>
</div>
</template>
<script>
export default {
data() {
return {
showConfirmApply: false,
selectedBackup: null,
isBackingUp: false,
processing: false
}
},
computed: {
backups() {
return this.$store.state.backups || []
},
userToken() {
return this.$store.getters['user/getToken']
}
},
methods: {
confirm() {
this.showConfirmApply = false
this.$root.socket.once('apply_backup_complete', this.applyBackupComplete)
this.$root.socket.emit('apply_backup', this.selectedBackup.id)
},
deleteBackupClick(backup) {
if (confirm(`Are you sure you want to delete backup for ${backup.datePretty}?`)) {
this.processing = true
this.$axios
.$delete(`/api/backup/${backup.id}`)
.then((backups) => {
console.log('Backup deleted', backups)
this.$store.commit('setBackups', backups)
this.$toast.success(`Backup deleted`)
this.processing = false
})
.catch((error) => {
console.error(error)
this.$toast.error('Failed to delete backup')
this.processing = false
})
}
},
applyBackupComplete(success) {
if (success) {
// this.$toast.success('Backup Applied, refresh the page')
location.replace('/config?backup=1')
} else {
this.$toast.error('Failed to apply backup')
}
},
applyBackup(backup) {
this.selectedBackup = backup
this.showConfirmApply = true
},
backupComplete(backups) {
this.isBackingUp = false
if (backups) {
this.$toast.success('Backup Successful')
this.$store.commit('setBackups', backups)
} else this.$toast.error('Backup Failed')
},
clickCreateBackup() {
this.isBackingUp = true
this.$root.socket.once('backup_complete', this.backupComplete)
this.$root.socket.emit('create_backup')
},
backupUploaded(file) {
var form = new FormData()
form.set('file', file)
this.processing = true
this.$axios
.$post('/api/backup/upload', form)
.then((result) => {
console.log('Upload backup result', result)
this.$store.commit('setBackups', result)
this.$toast.success('Backup upload success')
this.processing = false
})
.catch((error) => {
console.error(error)
var errorMessage = error.response && error.response.data ? error.response.data : 'Failed to upload backup'
this.$toast.error(errorMessage)
this.processing = false
})
}
},
mounted() {
if (this.$route.query.backup) {
this.$toast.success('Backup applied successfully')
this.$router.replace('/config')
}
}
}
</script>
<style>
#backups {
table-layout: fixed;
border-collapse: collapse;
width: 100%;
}
#backups td,
#backups th {
border: 1px solid #2e2e2e;
padding: 8px 8px;
text-align: left;
}
#backups tr.staticrow td {
text-align: center;
}
#backups tr:nth-child(even) {
background-color: #3a3a3a;
}
#backups tr:not(.staticrow):hover {
background-color: #444;
}
#backups th {
font-size: 0.8rem;
font-weight: 600;
padding-top: 5px;
padding-bottom: 5px;
background-color: #333;
}
</style>

View file

@ -19,9 +19,10 @@
<table class="text-sm tracksTable">
<tr class="font-book">
<th class="text-left px-4">Path</th>
<th class="text-left px-4">Filetype</th>
<th class="text-left px-4 w-24">Filetype</th>
<th v-if="userCanDownload" class="text-center w-20">Download</th>
</tr>
<template v-for="file in files">
<template v-for="file in otherFilesCleaned">
<tr :key="file.path">
<td class="font-book pl-2">
{{ showFullPath ? file.fullPath : file.path }}
@ -29,6 +30,9 @@
<td class="text-xs">
<p>{{ file.filetype }}</p>
</td>
<td v-if="userCanDownload" class="text-center">
<a :href="`/s/book/${audiobookId}/${file.relativePath}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
</td>
</tr>
</template>
</table>
@ -44,7 +48,10 @@ export default {
type: Array,
default: () => []
},
audiobookId: String
audiobook: {
type: Object,
default: () => null
}
},
data() {
return {
@ -52,7 +59,34 @@ export default {
showFullPath: false
}
},
computed: {},
computed: {
audiobookId() {
return this.audiobook.id
},
audiobookPath() {
return this.audiobook.path
},
otherFilesCleaned() {
return this.files.map((file) => {
var filePath = file.path.replace(/\\/g, '/')
var audiobookPath = this.audiobookPath.replace(/\\/g, '/')
return {
...file,
relativePath: filePath
.replace(audiobookPath + '/', '')
.replace(/%/g, '%25')
.replace(/#/g, '%23')
}
})
},
userToken() {
return this.$store.getters['user/getToken']
},
userCanDownload() {
return this.$store.getters['user/getUserCanDownload']
}
},
methods: {
clickBar() {
this.showFiles = !this.showFiles

View file

@ -80,7 +80,10 @@ export default {
return {
...track,
relativePath: trackPath.replace(audiobookPath, '').replace(/%/g, '%25').replace(/#/g, '%23')
relativePath: trackPath
.replace(audiobookPath + '/', '')
.replace(/%/g, '%25')
.replace(/#/g, '%23')
}
})
},

View file

@ -127,4 +127,35 @@ export default {
}
}
}
</script>
</script>
<style>
#accounts {
table-layout: fixed;
border-collapse: collapse;
width: 100%;
}
#accounts td,
#accounts th {
border: 1px solid #2e2e2e;
padding: 8px 8px;
text-align: left;
}
#accounts tr:nth-child(even) {
background-color: #3a3a3a;
}
#accounts tr:hover {
background-color: #444;
}
#accounts th {
font-size: 0.8rem;
font-weight: 600;
padding-top: 5px;
padding-bottom: 5px;
background-color: #333;
}
</style>

View file

@ -1,17 +1,21 @@
<template>
<div>
<input ref="fileInput" id="hidden-input" type="file" :accept="inputAccept" class="hidden" @change="inputChanged" />
<ui-btn @click="clickUpload" color="primary" type="text">Upload Cover</ui-btn>
<input ref="fileInput" id="hidden-input" type="file" :accept="accept" class="hidden" @change="inputChanged" />
<ui-btn @click="clickUpload" color="primary" type="text"><slot /></ui-btn>
</div>
</template>
<script>
export default {
data() {
return {
inputAccept: '.png, .jpg, .jpeg, .webp'
props: {
accept: {
type: String,
default: '.png, .jpg, .jpeg, .webp'
}
},
data() {
return {}
},
computed: {},
methods: {
reset() {

View file

@ -26,6 +26,8 @@ export default {
type: Number,
default: 3
},
noSpinner: Boolean,
textCenter: Boolean,
clearable: Boolean
},
data() {
@ -44,6 +46,8 @@ export default {
var _list = []
_list.push(`px-${this.paddingX}`)
_list.push(`py-${this.paddingY}`)
if (this.noSpinner) _list.push('no-spinner')
if (this.textCenter) _list.push('text-center')
return _list.join(' ')
}
},

View file

@ -98,6 +98,7 @@ export default {
if (!this.$refs.box) return // Ensure element is not destroyed
try {
document.body.appendChild(this.tooltip)
this.setTooltipPosition(this.tooltip)
} catch (error) {
console.error(error)
}

View file

@ -72,6 +72,9 @@ export default {
this.scanStart(libraryScan)
})
}
if (payload.backups && payload.backups.length) {
this.$store.commit('setBackups', payload.backups)
}
},
streamOpen(stream) {
if (this.$refs.streamContainer) this.$refs.streamContainer.streamOpen(stream)
@ -220,6 +223,10 @@ export default {
logEvtReceived(payload) {
this.$store.commit('logs/logEvt', payload)
},
backupApplied() {
// Force refresh
location.reload()
},
initializeSocket() {
this.socket = this.$nuxtSocket({
name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
@ -274,6 +281,8 @@ export default {
this.socket.on('download_expired', this.downloadExpired)
this.socket.on('log', this.logEvtReceived)
this.socket.on('backup_applied', this.backupApplied)
},
showUpdateToast(versionData) {
var ignoreVersion = localStorage.getItem('ignoreVersion')

View file

@ -77,6 +77,7 @@ module.exports = {
'/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } },
'/local/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
'/lib/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
'/ebook/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
'/s/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
'/metadata/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' }
},

View file

@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "1.4.1",
"version": "1.4.2",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {

View file

@ -145,7 +145,7 @@
<tables-audio-files-table v-if="otherAudioFiles.length" :audiobook-id="audiobook.id" :files="otherAudioFiles" class="mt-6" />
<tables-other-files-table v-if="otherFiles.length" :audiobook-id="audiobook.id" :files="otherFiles" class="mt-6" />
<tables-other-files-table v-if="otherFiles.length" :audiobook="audiobook" :files="otherFiles" class="mt-6" />
</div>
</div>
</div>
@ -239,6 +239,9 @@ export default {
libraryId() {
return this.audiobook.libraryId
},
folderId() {
return this.audiobook.folderId
},
audiobookId() {
return this.audiobook.id
},
@ -313,9 +316,16 @@ export default {
epubEbook() {
return this.audiobook.ebooks.find((eb) => eb.ext === '.epub')
},
epubUrl() {
epubPath() {
return this.epubEbook ? this.epubEbook.path : null
},
epubUrl() {
if (!this.epubPath) return null
return `/ebook/${this.libraryId}/${this.folderId}/${this.epubPath}`
},
userToken() {
return this.$store.getters['user/getToken']
},
description() {
return this.book.description || ''
},

View file

@ -35,6 +35,31 @@
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-5 p-4 mb-8">
<div class="flex items-center mb-2">
<h1 class="text-xl">Backups</h1>
</div>
<p class="text-base mb-4 text-gray-300">Backups include users, user progress, book details, server settings and covers stored in <span class="font-mono text-gray-100">/metadata/books</span>. <br />Backups <strong>do not</strong> include any files stored in your library folders.</p>
<div class="flex items-center py-2">
<ui-toggle-switch v-model="dailyBackups" small :disabled="updatingServerSettings" @input="updateBackupsSettings" />
<ui-tooltip :text="dailyBackupsTooltip">
<p class="pl-4 text-lg">Run daily backups <span class="material-icons icon-text">info_outlined</span></p>
</ui-tooltip>
</div>
<div class="flex items-center py-2">
<ui-text-input type="number" v-model="backupsToKeep" no-spinner :disabled="updatingServerSettings" :padding-x="1" text-center class="w-10" @change="updateBackupsSettings" />
<p class="pl-4 text-lg">Number of backups to keep</p>
</div>
<tables-backups-table />
</div>
<div class="h-0.5 bg-primary bg-opacity-50 w-full" />
<div class="flex items-center py-4">
<ui-btn color="bg" small :padding-x="4" :loading="isResettingAudiobooks" @click="resetAudiobooks">Reset All Audiobooks</ui-btn>
<div class="flex-grow" />
@ -92,14 +117,16 @@ export default {
storeCoversInAudiobookDir: false,
isResettingAudiobooks: false,
newServerSettings: {},
updatingServerSettings: false
updatingServerSettings: false,
dailyBackups: true,
backupsToKeep: 2
}
},
watch: {
serverSettings(newVal, oldVal) {
if (newVal && !oldVal) {
this.newServerSettings = { ...this.serverSettings }
this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK
this.initServerSettings()
}
}
},
@ -119,6 +146,12 @@ export default {
experimentalFeaturesTooltip() {
return 'Features in development that could use your feedback and help testing.'
},
dailyBackupsTooltip() {
return 'Runs at 1am every day (your server time). Saved in /metadata/backups.'
},
backupsToKeepTooltip() {
return ''
},
serverSettings() {
return this.$store.state.serverSettings
},
@ -141,10 +174,17 @@ export default {
}
},
methods: {
// toggleShowExperimentalFeatures() {
// var newExperimentalValue = !this.showExperimentalFeatures
// this.$store.commit('setExperimentalFeatures', newExperimentalValue)
// },
updateBackupsSettings() {
if (isNaN(this.backupsToKeep) || this.backupsToKeep <= 0 || this.backupsToKeep > 99) {
this.$toast.error('Invalid number of backups to keep')
return
}
var updatePayload = {
backupSchedule: this.dailyBackups ? '0 1 * * *' : false,
backupsToKeep: Number(this.backupsToKeep)
}
this.updateServerSettings(updatePayload)
},
updateScannerFindCovers(val) {
this.updateServerSettings({
scannerFindCovers: !!val
@ -211,7 +251,13 @@ export default {
},
init() {
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
this.initServerSettings()
},
initServerSettings() {
this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK
this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK
this.backupsToKeep = this.newServerSettings.backupsToKeep || 2
this.dailyBackups = !!this.newServerSettings.backupSchedule
}
},
mounted() {
@ -219,34 +265,3 @@ export default {
}
}
</script>
<style>
#accounts {
table-layout: fixed;
border-collapse: collapse;
width: 100%;
}
#accounts td,
#accounts th {
border: 1px solid #2e2e2e;
padding: 8px 8px;
text-align: left;
}
#accounts tr:nth-child(even) {
background-color: #3a3a3a;
}
#accounts tr:hover {
background-color: #444;
}
#accounts th {
font-size: 0.8rem;
font-weight: 600;
padding-top: 5px;
padding-bottom: 5px;
background-color: #333;
}
</style>

View file

@ -14,7 +14,8 @@ export const state = () => ({
processingBatch: false,
previousPath: '/',
routeHistory: [],
showExperimentalFeatures: false
showExperimentalFeatures: false,
backups: []
})
export const getters = {
@ -130,5 +131,8 @@ export const mutations = {
setExperimentalFeatures(state, val) {
state.showExperimentalFeatures = val
localStorage.setItem('experimental', val ? 1 : 0)
},
setBackups(state, val) {
state.backups = val.sort((a, b) => b.createdAt - a.createdAt)
}
}