mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-31 12:15:49 -04:00
Add LibrarySettings and update edit library modal to include settings tab
This commit is contained in:
parent
cbde451120
commit
5a26b01ffb
@ -5,7 +5,7 @@
|
|||||||
<p class="font-book text-3xl text-white truncate pointer-events-none">{{ title }}</p>
|
<p class="font-book text-3xl text-white truncate pointer-events-none">{{ title }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="absolute -top-10 left-0 w-full flex">
|
<div class="absolute -top-10 left-0 z-10 w-full flex">
|
||||||
<template v-for="tab in availableTabs">
|
<template v-for="tab in availableTabs">
|
||||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||||
</template>
|
</template>
|
||||||
@ -252,7 +252,7 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.tab {
|
.tab {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full px-4 py-2 mb-4">
|
<div class="w-full h-full px-4 py-2 mb-4">
|
||||||
<div v-show="showDirectoryPicker" class="flex items-center py-1 mb-2">
|
|
||||||
<span class="material-icons text-3xl cursor-pointer hover:text-gray-300" @click="backArrowPress">arrow_back</span>
|
|
||||||
<p class="px-4 text-xl">{{ title }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="!showDirectoryPicker" class="w-full h-full py-4">
|
<div v-if="!showDirectoryPicker" class="w-full h-full py-4">
|
||||||
<div class="flex flex-wrap md:flex-nowrap -mx-1">
|
<div class="flex flex-wrap md:flex-nowrap -mx-1">
|
||||||
<div class="w-2/5 md:w-72 px-1 py-1 md:py-0">
|
<div class="w-2/5 md:w-72 px-1 py-1 md:py-0">
|
||||||
<ui-dropdown v-model="mediaType" :items="mediaTypes" label="Media Type" :disabled="!!library" small @input="changedMediaType" />
|
<ui-dropdown v-model="mediaType" :items="mediaTypes" label="Media Type" :disabled="!isNew" small @input="changedMediaType" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:flex-grow px-1 py-1 md:py-0">
|
<div class="w-full md:flex-grow px-1 py-1 md:py-0">
|
||||||
<ui-text-input-with-label v-model="name" label="Library Name" />
|
<ui-text-input-with-label v-model="name" label="Library Name" @blur="nameBlurred" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/5 md:w-18 px-1 py-1 md:py-0">
|
<div class="w-1/5 md:w-18 px-1 py-1 md:py-0">
|
||||||
<ui-media-icon-picker v-model="icon" />
|
<ui-media-icon-picker v-model="icon" @input="iconChanged" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/5 md:w-72 px-1 py-1 md:py-0">
|
<div class="w-2/5 md:w-72 px-1 py-1 md:py-0">
|
||||||
<ui-dropdown v-model="provider" :items="providers" label="Metadata Provider" small />
|
<ui-dropdown v-model="provider" :items="providers" label="Metadata Provider" small @input="formUpdated" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -27,39 +23,23 @@
|
|||||||
<ui-editable-text v-model="folder.fullPath" readonly type="text" class="w-full" />
|
<ui-editable-text v-model="folder.fullPath" readonly type="text" class="w-full" />
|
||||||
<span v-show="folders.length > 1" class="material-icons ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
|
<span v-show="folders.length > 1" class="material-icons ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p v-if="!folders.length" class="text-sm text-gray-300 px-1 py-2">No folders</p> -->
|
|
||||||
|
|
||||||
<div class="flex py-1 px-2 items-center w-full">
|
<div class="flex py-1 px-2 items-center w-full">
|
||||||
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
||||||
<ui-editable-text v-model="newFolderPath" placeholder="New folder path" type="text" class="w-full" />
|
<ui-editable-text v-model="newFolderPath" placeholder="New folder path" type="text" class="w-full" @blur="newFolderInputBlurred" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-btn class="w-full mt-2" color="primary" @click="showDirectoryPicker = true">Browse for Folder</ui-btn>
|
<ui-btn class="w-full mt-2" color="primary" @click="showDirectoryPicker = true">Browse for Folder</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 left-0 w-full py-4 px-4">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<ui-btn v-show="!disableSubmit" color="success" :disabled="disableSubmit" @click="submit">{{ library ? 'Update Library' : 'Create Library' }}</ui-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modals-libraries-folder-chooser v-else :paths="folderPaths" @select="selectFolder" />
|
<modals-libraries-folder-chooser v-else :paths="folderPaths" @back="showDirectoryPicker = false" @select="selectFolder" />
|
||||||
|
|
||||||
<div v-if="!showDirectoryPicker">
|
|
||||||
<div class="flex items-center pt-2">
|
|
||||||
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="disableWatcher" />
|
|
||||||
<ui-toggle-switch v-else disabled :value="false" />
|
|
||||||
<p class="pl-4 text-lg">Disable folder watcher for library</p>
|
|
||||||
</div>
|
|
||||||
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*Watcher is disabled globally in server settings</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
isNew: Boolean,
|
||||||
library: {
|
library: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => null
|
default: () => null
|
||||||
@ -73,7 +53,6 @@ export default {
|
|||||||
icon: '',
|
icon: '',
|
||||||
folders: [],
|
folders: [],
|
||||||
showDirectoryPicker: false,
|
showDirectoryPicker: false,
|
||||||
disableWatcher: false,
|
|
||||||
newFolderPath: '',
|
newFolderPath: '',
|
||||||
mediaType: null,
|
mediaType: null,
|
||||||
mediaTypes: [
|
mediaTypes: [
|
||||||
@ -89,36 +68,54 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
|
||||||
if (this.showDirectoryPicker) return 'Choose a Folder'
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
folderPaths() {
|
folderPaths() {
|
||||||
return this.folders.map((f) => f.fullPath)
|
return this.folders.map((f) => f.fullPath)
|
||||||
},
|
},
|
||||||
disableSubmit() {
|
|
||||||
if (!this.library) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var newfolderpaths = this.folderPaths.join(',')
|
|
||||||
var origfolderpaths = this.library.folders.map((f) => f.fullPath).join(',')
|
|
||||||
|
|
||||||
return newfolderpaths === origfolderpaths && this.name === this.library.name && this.provider === this.library.provider && this.disableWatcher === this.library.disableWatcher && this.icon === this.library.icon && !this.newFolderPath
|
|
||||||
},
|
|
||||||
providers() {
|
providers() {
|
||||||
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
|
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
|
||||||
return this.$store.state.scanners.providers
|
return this.$store.state.scanners.providers
|
||||||
},
|
|
||||||
globalWatcherDisabled() {
|
|
||||||
return this.$store.getters['getServerSetting']('scannerDisableWatcher')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getLibraryData() {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
provider: this.provider,
|
||||||
|
folders: this.folders,
|
||||||
|
icon: this.icon,
|
||||||
|
mediaType: this.mediaType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formUpdated() {
|
||||||
|
this.$emit('update', this.getLibraryData())
|
||||||
|
},
|
||||||
|
newFolderInputBlurred() {
|
||||||
|
if (this.newFolderPath) {
|
||||||
|
this.folders.push({ fullPath: this.newFolderPath })
|
||||||
|
this.newFolderPath = ''
|
||||||
|
this.formUpdated()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
iconChanged() {
|
||||||
|
this.formUpdated()
|
||||||
|
},
|
||||||
|
nameBlurred() {
|
||||||
|
if (this.name !== this.library.name) {
|
||||||
|
this.formUpdated()
|
||||||
|
}
|
||||||
|
},
|
||||||
changedMediaType() {
|
changedMediaType() {
|
||||||
this.provider = this.providers[0].value
|
this.provider = this.providers[0].value
|
||||||
|
this.formUpdated()
|
||||||
|
},
|
||||||
|
selectFolder(fullPath) {
|
||||||
|
this.folders.push({ fullPath })
|
||||||
|
this.showDirectoryPicker = false
|
||||||
|
this.formUpdated()
|
||||||
},
|
},
|
||||||
removeFolder(folder) {
|
removeFolder(folder) {
|
||||||
this.folders = this.folders.filter((f) => f.fullPath !== folder.fullPath)
|
this.folders = this.folders.filter((f) => f.fullPath !== folder.fullPath)
|
||||||
|
this.formUpdated()
|
||||||
},
|
},
|
||||||
backArrowPress() {
|
backArrowPress() {
|
||||||
if (this.showDirectoryPicker) {
|
if (this.showDirectoryPicker) {
|
||||||
@ -129,95 +126,9 @@ export default {
|
|||||||
this.name = this.library ? this.library.name : ''
|
this.name = this.library ? this.library.name : ''
|
||||||
this.provider = this.library ? this.library.provider : 'google'
|
this.provider = this.library ? this.library.provider : 'google'
|
||||||
this.folders = this.library ? this.library.folders.map((p) => ({ ...p })) : []
|
this.folders = this.library ? this.library.folders.map((p) => ({ ...p })) : []
|
||||||
this.disableWatcher = this.library ? !!this.library.disableWatcher : false
|
|
||||||
this.icon = this.library ? this.library.icon : 'default'
|
this.icon = this.library ? this.library.icon : 'default'
|
||||||
this.mediaType = this.library ? this.library.mediaType : 'book'
|
this.mediaType = this.library ? this.library.mediaType : 'book'
|
||||||
this.showDirectoryPicker = false
|
this.showDirectoryPicker = false
|
||||||
},
|
|
||||||
selectFolder(fullPath) {
|
|
||||||
this.folders.push({ fullPath })
|
|
||||||
this.showDirectoryPicker = false
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
if (this.newFolderPath) {
|
|
||||||
this.folders.push({ fullPath: this.newFolderPath })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.library) {
|
|
||||||
this.updateLibrary()
|
|
||||||
} else {
|
|
||||||
this.createLibrary()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateLibrary() {
|
|
||||||
if (!this.name) {
|
|
||||||
this.$toast.error('Library must have a name')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.folders.length) {
|
|
||||||
this.$toast.error('Library must have at least 1 path')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var newLibraryPayload = {
|
|
||||||
name: this.name,
|
|
||||||
provider: this.provider,
|
|
||||||
folders: this.folders,
|
|
||||||
icon: this.icon,
|
|
||||||
disableWatcher: this.disableWatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('update:processing', true)
|
|
||||||
this.$axios
|
|
||||||
.$patch(`/api/libraries/${this.library.id}`, newLibraryPayload)
|
|
||||||
.then((res) => {
|
|
||||||
this.$emit('update:processing', false)
|
|
||||||
this.$emit('close')
|
|
||||||
this.$toast.success(`Library "${res.name}" updated successfully`)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
if (error.response && error.response.data) {
|
|
||||||
this.$toast.error(error.response.data)
|
|
||||||
} else {
|
|
||||||
this.$toast.error('Failed to update library')
|
|
||||||
}
|
|
||||||
this.$emit('update:processing', false)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
createLibrary() {
|
|
||||||
if (!this.name) {
|
|
||||||
this.$toast.error('Library must have a name')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.folders.length) {
|
|
||||||
this.$toast.error('Library must have at least 1 path')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var newLibraryPayload = {
|
|
||||||
name: this.name,
|
|
||||||
provider: this.provider,
|
|
||||||
folders: this.folders,
|
|
||||||
icon: this.icon,
|
|
||||||
mediaType: this.mediaType,
|
|
||||||
disableWatcher: this.disableWatcher
|
|
||||||
}
|
|
||||||
this.$emit('update:processing', true)
|
|
||||||
this.$axios
|
|
||||||
.$post('/api/libraries', newLibraryPayload)
|
|
||||||
.then((res) => {
|
|
||||||
this.$emit('update:processing', false)
|
|
||||||
this.$emit('close')
|
|
||||||
this.$toast.success(`Library "${res.name}" created successfully`)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
if (error.response && error.response.data) {
|
|
||||||
this.$toast.error(error.response.data)
|
|
||||||
} else {
|
|
||||||
this.$toast.error('Failed to create library')
|
|
||||||
}
|
|
||||||
this.$emit('update:processing', false)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -5,8 +5,20 @@
|
|||||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div 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 class="absolute -top-10 left-0 z-10 w-full flex">
|
||||||
<modals-libraries-edit-library v-if="show" :library="library" :processing.sync="processing" @close="show = false" />
|
<template v-for="tab in tabs">
|
||||||
|
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 w-full text-sm pt-6 pb-20 rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||||
|
<component v-if="libraryCopy && show" :is="tabName" :is-new="!library" :library="libraryCopy" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
|
||||||
|
|
||||||
|
<div class="absolute bottom-0 left-0 w-full px-4 py-4 border-t border-opacity-10">
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<ui-btn @click="submit">{{ buttonText }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
@ -22,7 +34,21 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
processing: false
|
processing: false,
|
||||||
|
selectedTab: 'details',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: 'details',
|
||||||
|
title: 'Details',
|
||||||
|
component: 'modals-libraries-edit-library'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
title: 'Settings',
|
||||||
|
component: 'modals-libraries-library-settings'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
libraryCopy: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -36,10 +62,157 @@ export default {
|
|||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return this.library ? 'Update Library' : 'New Library'
|
return this.library ? 'Update Library' : 'New Library'
|
||||||
|
},
|
||||||
|
buttonText() {
|
||||||
|
return this.library ? 'Update Library' : 'Create New Library'
|
||||||
|
},
|
||||||
|
tabName() {
|
||||||
|
var _tab = this.tabs.find((t) => t.id === this.selectedTab)
|
||||||
|
return _tab ? _tab.component : ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: {
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal) this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectTab(tab) {
|
||||||
|
this.selectedTab = tab
|
||||||
|
},
|
||||||
|
updateLibrary(library) {
|
||||||
|
this.mapLibraryToCopy(library)
|
||||||
|
},
|
||||||
|
getNewLibraryData() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
provider: 'google',
|
||||||
|
folders: [],
|
||||||
|
icon: 'database',
|
||||||
|
mediaType: 'book',
|
||||||
|
settings: {
|
||||||
|
disableWatcher: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.selectedTab = 'details'
|
||||||
|
this.libraryCopy = this.getNewLibraryData()
|
||||||
|
if (this.library) {
|
||||||
|
this.mapLibraryToCopy(this.library)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mapLibraryToCopy(library) {
|
||||||
|
for (const key in this.libraryCopy) {
|
||||||
|
if (library[key] !== undefined) {
|
||||||
|
if (key === 'folders') {
|
||||||
|
this.libraryCopy.folders = library.folders.map((f) => ({ ...f }))
|
||||||
|
} else if (key === 'settings') {
|
||||||
|
this.libraryCopy.settings = { ...library.settings }
|
||||||
|
} else {
|
||||||
|
this.libraryCopy[key] = library[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validate() {
|
||||||
|
if (!this.libraryCopy.name) {
|
||||||
|
this.$toast.error('Library must have a name')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!this.libraryCopy.folders.length) {
|
||||||
|
this.$toast.error('Library must have at least 1 path')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (!this.validate()) return
|
||||||
|
|
||||||
|
if (this.library) {
|
||||||
|
this.submitUpdateLibrary()
|
||||||
|
} else {
|
||||||
|
this.submitCreateLibrary()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getLibraryUpdatePayload() {
|
||||||
|
var updatePayload = {}
|
||||||
|
for (const key in this.libraryCopy) {
|
||||||
|
if (key === 'folders') {
|
||||||
|
if (this.libraryCopy.folders.join(',') !== this.library.folders.join(',')) {
|
||||||
|
updatePayload.folders = [...this.libraryCopy.folders]
|
||||||
|
}
|
||||||
|
} else if (key === 'settings') {
|
||||||
|
for (const settingsKey in this.libraryCopy.settings) {
|
||||||
|
if (this.libraryCopy.settings[settingsKey] !== this.library.settings[settingsKey]) {
|
||||||
|
if (!updatePayload.settings) updatePayload.settings = {}
|
||||||
|
updatePayload.settings[settingsKey] = this.libraryCopy.settings[settingsKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (key !== 'mediaType' && this.libraryCopy[key] !== this.library[key]) {
|
||||||
|
updatePayload[key] = this.libraryCopy[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatePayload
|
||||||
|
},
|
||||||
|
submitUpdateLibrary() {
|
||||||
|
var newLibraryPayload = this.getLibraryUpdatePayload()
|
||||||
|
if (!Object.keys(newLibraryPayload).length) {
|
||||||
|
this.$toast.info('No updates are necessary')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.processing = true
|
||||||
|
this.$axios
|
||||||
|
.$patch(`/api/libraries/${this.library.id}`, newLibraryPayload)
|
||||||
|
.then((res) => {
|
||||||
|
this.processing = false
|
||||||
|
this.show = false
|
||||||
|
this.$toast.success(`Library "${res.name}" updated successfully`)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
this.$toast.error(error.response.data)
|
||||||
|
} else {
|
||||||
|
this.$toast.error('Failed to update library')
|
||||||
|
}
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitCreateLibrary() {
|
||||||
|
this.processing = true
|
||||||
|
this.$axios
|
||||||
|
.$post('/api/libraries', this.libraryCopy)
|
||||||
|
.then((res) => {
|
||||||
|
this.processing = false
|
||||||
|
this.show = false
|
||||||
|
this.$toast.success(`Library "${res.name}" created successfully`)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
this.$toast.error(error.response.data)
|
||||||
|
} else {
|
||||||
|
this.$toast.error('Failed to create library')
|
||||||
|
}
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
|
||||||
mounted() {},
|
mounted() {},
|
||||||
beforeDestroy() {}
|
beforeDestroy() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tab {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.tab.tab-selected {
|
||||||
|
height: 41px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full bg-bg absolute top-0 left-0 px-4 py-4 z-10">
|
||||||
|
<div class="flex items-center py-1 mb-2">
|
||||||
|
<span class="material-icons text-3xl cursor-pointer hover:text-gray-300" @click="$emit('back')">arrow_back</span>
|
||||||
|
<p class="px-4 text-xl">Choose a Folder</p>
|
||||||
|
</div>
|
||||||
<div v-if="allFolders.length" class="w-full bg-primary bg-opacity-70 py-1 px-4 mb-2">
|
<div v-if="allFolders.length" class="w-full bg-primary bg-opacity-70 py-1 px-4 mb-2">
|
||||||
<p class="font-mono truncate">{{ selectedPath || '\\' }}</p>
|
<p class="font-mono truncate">{{ selectedPath || '\\' }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="allFolders.length" class="flex bg-primary bg-opacity-50 p-4">
|
<div v-if="allFolders.length" class="flex bg-primary bg-opacity-50 p-4 folder-container">
|
||||||
<div class="w-1/2 border-r border-bg">
|
<div class="w-1/2 border-r border-bg h-full overflow-y-auto">
|
||||||
<div v-if="level > 0" class="w-full p-1 cursor-pointer flex items-center" @click="goBack">
|
<div v-if="level > 0" class="w-full p-1 cursor-pointer flex items-center" @click="goBack">
|
||||||
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
||||||
<p class="text-base font-mono px-2">..</p>
|
<p class="text-base font-mono px-2">..</p>
|
||||||
@ -15,7 +19,7 @@
|
|||||||
<span v-if="dir.dirs && dir.dirs.length && dir.path === selectedPath" class="material-icons" style="font-size: 1.1rem">arrow_right</span>
|
<span v-if="dir.dirs && dir.dirs.length && dir.path === selectedPath" class="material-icons" style="font-size: 1.1rem">arrow_right</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2">
|
<div class="w-1/2 h-full overflow-y-auto">
|
||||||
<div v-for="dir in _subdirs" :key="dir.path" :class="dir.className" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200" @click="selectSubDir(dir)">
|
<div v-for="dir in _subdirs" :key="dir.path" :class="dir.className" class="dir-item w-full p-1 cursor-pointer flex items-center hover:text-white text-gray-200" @click="selectSubDir(dir)">
|
||||||
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
<span class="material-icons bg-opacity-50 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
||||||
<p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p>
|
<p class="text-base font-mono px-2 truncate">{{ dir.dirname }}</p>
|
||||||
@ -30,12 +34,8 @@
|
|||||||
<p class="text-gray-300">Note: folders already mapped will not be shown</p>
|
<p class="text-gray-300">Note: folders already mapped will not be shown</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute bottom-0 left-0 w-full py-4 px-8">
|
<div class="w-full py-2">
|
||||||
<ui-btn :disabled="!selectedPath" color="primary" class="w-full mt-2" @click="selectFolder">Select Folder Path</ui-btn>
|
<ui-btn :disabled="!selectedPath" color="primary" class="w-full mt-2" @click="selectFolder">Select Folder Path</ui-btn>
|
||||||
<!-- <div class="flex items-center">
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<ui-btn color="success" @click="selectFolder">Select</ui-btn>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -161,4 +161,9 @@ export default {
|
|||||||
.dir-item.dir-used {
|
.dir-item.dir-used {
|
||||||
background-color: rgba(255, 25, 0, 0.1);
|
background-color: rgba(255, 25, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
.folder-container {
|
||||||
|
max-height: calc(100% - 130px);
|
||||||
|
height: calc(100% - 130px);
|
||||||
|
min-height: calc(100% - 130px);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
63
client/components/modals/libraries/LibrarySettings.vue
Normal file
63
client/components/modals/libraries/LibrarySettings.vue
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full h-full px-4 py-1 mb-4">
|
||||||
|
<div class="py-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="disableWatcher" @input="formUpdated" />
|
||||||
|
<ui-toggle-switch v-else disabled :value="false" />
|
||||||
|
<p class="pl-4 text-lg">Disable folder watcher for library</p>
|
||||||
|
</div>
|
||||||
|
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*Watcher is disabled globally in server settings</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
library: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
},
|
||||||
|
processing: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
provider: null,
|
||||||
|
disableWatcher: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
librarySettings() {
|
||||||
|
return this.library.settings || {}
|
||||||
|
},
|
||||||
|
globalWatcherDisabled() {
|
||||||
|
return this.$store.getters['getServerSetting']('scannerDisableWatcher')
|
||||||
|
},
|
||||||
|
mediaType() {
|
||||||
|
return this.library.mediaType
|
||||||
|
},
|
||||||
|
providers() {
|
||||||
|
if (this.mediaType === 'podcast') return this.$store.state.scanners.podcastProviders
|
||||||
|
return this.$store.state.scanners.providers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getLibraryData() {
|
||||||
|
return {
|
||||||
|
settings: {
|
||||||
|
disableWatcher: !!this.disableWatcher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formUpdated() {
|
||||||
|
this.$emit('update', this.getLibraryData())
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.disableWatcher = !!this.librarySettings.disableWatcher
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -40,8 +40,8 @@ export default {
|
|||||||
showMenu: false,
|
showMenu: false,
|
||||||
types: [
|
types: [
|
||||||
{
|
{
|
||||||
id: 'default',
|
id: 'database',
|
||||||
name: 'Default'
|
name: 'Database'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'audiobook',
|
id: 'audiobook',
|
||||||
@ -65,7 +65,7 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
selected: {
|
selected: {
|
||||||
get() {
|
get() {
|
||||||
return this.value || 'default'
|
return this.value || 'database'
|
||||||
},
|
},
|
||||||
set(val) {
|
set(val) {
|
||||||
this.$emit('input', val)
|
this.$emit('input', val)
|
||||||
@ -75,7 +75,7 @@ export default {
|
|||||||
return this.types.find((t) => t.id === this.selected)
|
return this.types.find((t) => t.id === this.selected)
|
||||||
},
|
},
|
||||||
selectedName() {
|
selectedName() {
|
||||||
return this.selectedItem ? this.selectedItem.name : 'Default'
|
return this.selectedItem ? this.selectedItem.name : 'Database'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">
|
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">
|
||||||
{{ label }}<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
|
{{ label }}<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
|
||||||
</p>
|
</p>
|
||||||
<ui-text-input ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" />
|
<ui-text-input ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" @blur="inputBlurred" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -38,6 +38,9 @@ export default {
|
|||||||
if (this.$refs.input && this.$refs.input.blur) {
|
if (this.$refs.input && this.$refs.input.blur) {
|
||||||
this.$refs.input.blur()
|
this.$refs.input.blur()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
inputBlurred() {
|
||||||
|
this.$emit('blur')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
@ -9,7 +9,7 @@ const UserCollection = require('./objects/UserCollection')
|
|||||||
const Library = require('./objects/Library')
|
const Library = require('./objects/Library')
|
||||||
const Author = require('./objects/entities/Author')
|
const Author = require('./objects/entities/Author')
|
||||||
const Series = require('./objects/entities/Series')
|
const Series = require('./objects/entities/Series')
|
||||||
const ServerSettings = require('./objects/ServerSettings')
|
const ServerSettings = require('./objects/settings/ServerSettings')
|
||||||
const PlaybackSession = require('./objects/PlaybackSession')
|
const PlaybackSession = require('./objects/PlaybackSession')
|
||||||
|
|
||||||
class Db {
|
class Db {
|
||||||
|
@ -69,19 +69,19 @@ class FolderWatcher extends EventEmitter {
|
|||||||
|
|
||||||
initWatcher(libraries) {
|
initWatcher(libraries) {
|
||||||
libraries.forEach((lib) => {
|
libraries.forEach((lib) => {
|
||||||
if (!lib.disableWatcher) {
|
if (!lib.settings.disableWatcher) {
|
||||||
this.buildLibraryWatcher(lib)
|
this.buildLibraryWatcher(lib)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addLibrary(library) {
|
addLibrary(library) {
|
||||||
if (this.disabled || library.disableWatcher) return
|
if (this.disabled || library.settings.disableWatcher) return
|
||||||
this.buildLibraryWatcher(library)
|
this.buildLibraryWatcher(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLibrary(library) {
|
updateLibrary(library) {
|
||||||
if (this.disabled || library.disableWatcher) return
|
if (this.disabled || library.settings.disableWatcher) return
|
||||||
var libwatcher = this.libraryWatchers.find(lib => lib.id === library.id)
|
var libwatcher = this.libraryWatchers.find(lib => lib.id === library.id)
|
||||||
if (libwatcher) {
|
if (libwatcher) {
|
||||||
libwatcher.name = library.name
|
libwatcher.name = library.name
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const Folder = require('./Folder')
|
const Folder = require('./Folder')
|
||||||
|
const LibrarySettings = require('./settings/LibrarySettings')
|
||||||
const { getId } = require('../utils/index')
|
const { getId } = require('../utils/index')
|
||||||
|
|
||||||
class Library {
|
class Library {
|
||||||
@ -10,9 +11,9 @@ class Library {
|
|||||||
this.icon = 'database' // database, podcast, book, audiobook, comic
|
this.icon = 'database' // database, podcast, book, audiobook, comic
|
||||||
this.mediaType = 'book' // book, podcast
|
this.mediaType = 'book' // book, podcast
|
||||||
this.provider = 'google'
|
this.provider = 'google'
|
||||||
this.disableWatcher = false
|
|
||||||
|
|
||||||
this.lastScan = 0
|
this.lastScan = 0
|
||||||
|
this.settings = null
|
||||||
|
|
||||||
this.createdAt = null
|
this.createdAt = null
|
||||||
this.lastUpdate = null
|
this.lastUpdate = null
|
||||||
@ -34,7 +35,11 @@ class Library {
|
|||||||
this.icon = library.icon || 'database'
|
this.icon = library.icon || 'database'
|
||||||
this.mediaType = library.mediaType
|
this.mediaType = library.mediaType
|
||||||
this.provider = library.provider || 'google'
|
this.provider = library.provider || 'google'
|
||||||
this.disableWatcher = !!library.disableWatcher
|
|
||||||
|
this.settings = new LibrarySettings(library.settings)
|
||||||
|
if (library.settings === undefined) { // LibrarySettings added in v2, migrate settings
|
||||||
|
this.settings.disableWatcher = !!library.disableWatcher
|
||||||
|
}
|
||||||
|
|
||||||
this.createdAt = library.createdAt
|
this.createdAt = library.createdAt
|
||||||
this.lastUpdate = library.lastUpdate
|
this.lastUpdate = library.lastUpdate
|
||||||
@ -62,7 +67,7 @@ class Library {
|
|||||||
icon: this.icon,
|
icon: this.icon,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
disableWatcher: this.disableWatcher,
|
settings: this.settings.toJSON(),
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
lastUpdate: this.lastUpdate
|
lastUpdate: this.lastUpdate
|
||||||
}
|
}
|
||||||
@ -89,7 +94,7 @@ class Library {
|
|||||||
this.icon = data.icon || 'database'
|
this.icon = data.icon || 'database'
|
||||||
this.mediaType = data.mediaType || 'book'
|
this.mediaType = data.mediaType || 'book'
|
||||||
this.provider = data.provider || 'google'
|
this.provider = data.provider || 'google'
|
||||||
this.disableWatcher = !!data.disableWatcher
|
this.settings = new LibrarySettings(data.settings)
|
||||||
this.createdAt = Date.now()
|
this.createdAt = Date.now()
|
||||||
this.lastUpdate = Date.now()
|
this.lastUpdate = Date.now()
|
||||||
}
|
}
|
||||||
@ -105,10 +110,10 @@ class Library {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (payload.disableWatcher !== this.disableWatcher) {
|
if (payload.settings && this.settings.update(payload.settings)) {
|
||||||
this.disableWatcher = !!payload.disableWatcher
|
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNaN(payload.displayOrder) && payload.displayOrder !== this.displayOrder) {
|
if (!isNaN(payload.displayOrder) && payload.displayOrder !== this.displayOrder) {
|
||||||
this.displayOrder = Number(payload.displayOrder)
|
this.displayOrder = Number(payload.displayOrder)
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
|
34
server/objects/settings/LibrarySettings.js
Normal file
34
server/objects/settings/LibrarySettings.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const { BookCoverAspectRatio } = require('../../utils/constants')
|
||||||
|
const Logger = require('../../Logger')
|
||||||
|
|
||||||
|
class LibrarySettings {
|
||||||
|
constructor(settings) {
|
||||||
|
this.disableWatcher = false
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
this.construct(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
construct(settings) {
|
||||||
|
this.disableWatcher = !!settings.disableWatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
disableWatcher: this.disableWatcher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(payload) {
|
||||||
|
var hasUpdates = false
|
||||||
|
for (const key in payload) {
|
||||||
|
if (this[key] !== payload[key]) {
|
||||||
|
this[key] = payload[key]
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasUpdates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = LibrarySettings
|
@ -1,5 +1,5 @@
|
|||||||
const { BookCoverAspectRatio, BookshelfView } = require('../utils/constants')
|
const { BookCoverAspectRatio, BookshelfView } = require('../../utils/constants')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../../Logger')
|
||||||
|
|
||||||
class ServerSettings {
|
class ServerSettings {
|
||||||
constructor(settings) {
|
constructor(settings) {
|
Loading…
x
Reference in New Issue
Block a user