mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Moving settings to be user specific, adding playbackRate setting, update playbackRate picker to go up to 3x
This commit is contained in:
		
							parent
							
								
									2548aba840
								
							
						
					
					
						commit
						f83c5dd440
					
				| @ -27,7 +27,7 @@ | |||||||
|           <div class="cursor-pointer flex items-center justify-center text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="forward10"> |           <div class="cursor-pointer flex items-center justify-center text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="forward10"> | ||||||
|             <span class="material-icons text-3xl">forward_10</span> |             <span class="material-icons text-3xl">forward_10</span> | ||||||
|           </div> |           </div> | ||||||
|           <controls-playback-speed-control v-model="playbackRate" @change="updatePlaybackRate" /> |           <controls-playback-speed-control v-model="playbackRate" @change="playbackRateChanged" /> | ||||||
|         </template> |         </template> | ||||||
|         <template v-else> |         <template v-else> | ||||||
|           <div class="cursor-pointer p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-8 animate-spin"> |           <div class="cursor-pointer p-2 shadow-sm bg-accent flex items-center justify-center rounded-full text-primary mx-8 animate-spin"> | ||||||
| @ -89,7 +89,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     token() { |     token() { | ||||||
|       return this.$store.getters.getToken |       return this.$store.getters['user/getToken'] | ||||||
|     }, |     }, | ||||||
|     totalDurationPretty() { |     totalDurationPretty() { | ||||||
|       return this.$secondsToTimestamp(this.totalDuration) |       return this.$secondsToTimestamp(this.totalDuration) | ||||||
| @ -130,12 +130,22 @@ export default { | |||||||
|     }, |     }, | ||||||
|     updatePlaybackRate(playbackRate) { |     updatePlaybackRate(playbackRate) { | ||||||
|       if (this.audioEl) { |       if (this.audioEl) { | ||||||
|         console.log('UpdatePlaybackRate', playbackRate) |         try { | ||||||
|           this.audioEl.playbackRate = playbackRate |           this.audioEl.playbackRate = playbackRate | ||||||
|  |           this.audioEl.defaultPlaybackRate = playbackRate | ||||||
|  |         } catch (error) { | ||||||
|  |           console.error('Update playback rate failed', error) | ||||||
|  |         } | ||||||
|       } else { |       } else { | ||||||
|         console.error('No Audio El updatePlaybackRate') |         console.error('No Audio El updatePlaybackRate') | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     playbackRateChanged(playbackRate) { | ||||||
|  |       this.updatePlaybackRate(playbackRate) | ||||||
|  |       this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => { | ||||||
|  |         console.error('Failed to update settings', err) | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|     mousemoveTrack(e) { |     mousemoveTrack(e) { | ||||||
|       var offsetX = e.offsetX |       var offsetX = e.offsetX | ||||||
|       var time = (offsetX / this.trackWidth) * this.totalDuration |       var time = (offsetX / this.trackWidth) * this.totalDuration | ||||||
| @ -355,7 +365,8 @@ export default { | |||||||
|       this.hlsInstance = new Hls(hlsOptions) |       this.hlsInstance = new Hls(hlsOptions) | ||||||
|       var audio = this.$refs.audio |       var audio = this.$refs.audio | ||||||
|       audio.volume = this.volume |       audio.volume = this.volume | ||||||
|       audio.playbackRate = this.playbackRate |       audio.defaultPlaybackRate = this.playbackRate | ||||||
|  | 
 | ||||||
|       this.hlsInstance.attachMedia(audio) |       this.hlsInstance.attachMedia(audio) | ||||||
|       this.hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => { |       this.hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => { | ||||||
|         // console.log('[HLS] MEDIA ATTACHED') |         // console.log('[HLS] MEDIA ATTACHED') | ||||||
| @ -410,17 +421,27 @@ export default { | |||||||
|       this.set(this.url, startTime, true) |       this.set(this.url, startTime, true) | ||||||
|     }, |     }, | ||||||
|     init() { |     init() { | ||||||
|  |       this.playbackRate = this.$store.getters['user/getUserSetting']('playbackRate') || 1 | ||||||
|  | 
 | ||||||
|       this.audioEl = this.$refs.audio |       this.audioEl = this.$refs.audio | ||||||
|       if (this.$refs.track) { |       if (this.$refs.track) { | ||||||
|         this.trackWidth = this.$refs.track.clientWidth |         this.trackWidth = this.$refs.track.clientWidth | ||||||
|       } else { |       } else { | ||||||
|         console.error('Track not loaded', this.$refs) |         console.error('Track not loaded', this.$refs) | ||||||
|       } |       } | ||||||
|  |     }, | ||||||
|  |     settingsUpdated(settings) { | ||||||
|  |       if (settings.playbackRate && this.playbackRate !== settings.playbackRate) { | ||||||
|  |         this.updatePlaybackRate(settings.playbackRate) | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     // this.$nextTick(this.init) |     this.$store.commit('user/addSettingsListener', { id: 'audioplayer', meth: this.settingsUpdated }) | ||||||
|     this.init() |     this.init() | ||||||
|  |   }, | ||||||
|  |   beforeDestroy() { | ||||||
|  |     this.$store.commit('user/removeSettingsListener', 'audioplayer') | ||||||
|   } |   } | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ export default { | |||||||
|       return this.$route.name !== 'index' |       return this.$route.name !== 'index' | ||||||
|     }, |     }, | ||||||
|     user() { |     user() { | ||||||
|       return this.$store.state.user |       return this.$store.state.user.user | ||||||
|     }, |     }, | ||||||
|     username() { |     username() { | ||||||
|       return this.user ? this.user.username : 'err' |       return this.user ? this.user.username : 'err' | ||||||
|  | |||||||
| @ -32,13 +32,13 @@ export default { | |||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     userAudiobooks() { |     userAudiobooks() { | ||||||
|       return this.$store.state.user ? this.$store.state.user.audiobooks || {} : {} |       return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {} | ||||||
|     }, |     }, | ||||||
|     audiobooks() { |     audiobooks() { | ||||||
|       return this.$store.state.audiobooks.audiobooks |       return this.$store.state.audiobooks.audiobooks | ||||||
|     }, |     }, | ||||||
|     filterOrderKey() { |     filterOrderKey() { | ||||||
|       return this.$store.getters['settings/getFilterOrderKey'] |       return this.$store.getters['user/getFilterOrderKey'] | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @ -100,7 +100,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated }) |     this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated }) | ||||||
|     this.$store.commit('settings/addListener', { id: 'bookshelf', meth: this.settingsUpdated }) |     this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated }) | ||||||
| 
 | 
 | ||||||
|     this.$store.dispatch('audiobooks/load') |     this.$store.dispatch('audiobooks/load') | ||||||
|     this.init() |     this.init() | ||||||
| @ -108,7 +108,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   beforeDestroy() { |   beforeDestroy() { | ||||||
|     this.$store.commit('audiobooks/removeListener', 'bookshelf') |     this.$store.commit('audiobooks/removeListener', 'bookshelf') | ||||||
|     this.$store.commit('settings/removeListener', 'bookshelf') |     this.$store.commit('user/removeSettingsListener', 'bookshelf') | ||||||
|     window.removeEventListener('resize', this.resize) |     window.removeEventListener('resize', this.resize) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,7 +14,8 @@ | |||||||
| export default { | export default { | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       settings: {} |       settings: {}, | ||||||
|  |       hasInit: false | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @ -30,15 +31,24 @@ export default { | |||||||
|       this.saveSettings() |       this.saveSettings() | ||||||
|     }, |     }, | ||||||
|     saveSettings() { |     saveSettings() { | ||||||
|       // Send to server |       this.$store.commit('user/setSettings', this.settings) // Immediate update | ||||||
|       this.$store.commit('settings/setSettings', this.settings) |       this.$store.dispatch('user/updateUserSettings', this.settings) | ||||||
|     }, |     }, | ||||||
|     init() { |     init() { | ||||||
|       this.settings = { ...this.$store.state.settings.settings } |       this.settings = { ...this.$store.state.user.settings } | ||||||
|  |     }, | ||||||
|  |     settingsUpdated(settings) { | ||||||
|  |       for (const key in settings) { | ||||||
|  |         this.settings[key] = settings[key] | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.init() |     this.init() | ||||||
|  |     this.$store.commit('user/addSettingsListener', { id: 'bookshelftoolbar', meth: this.settingsUpdated }) | ||||||
|  |   }, | ||||||
|  |   beforeDestroy() { | ||||||
|  |     this.$store.commit('user/removeSettingsListener', 'bookshelftoolbar') | ||||||
|   } |   } | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ | |||||||
| export default { | export default { | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  |       audioPlayerReady: false, | ||||||
|       lastServerUpdateSentSeconds: 0, |       lastServerUpdateSentSeconds: 0, | ||||||
|       stream: null |       stream: null | ||||||
|     } |     } | ||||||
| @ -32,7 +33,7 @@ export default { | |||||||
|       return 'Logo.png' |       return 'Logo.png' | ||||||
|     }, |     }, | ||||||
|     user() { |     user() { | ||||||
|       return this.$store.state.user |       return this.$store.state.user.user | ||||||
|     }, |     }, | ||||||
|     isLoading() { |     isLoading() { | ||||||
|       if (!this.streamAudiobook) return false |       if (!this.streamAudiobook) return false | ||||||
| @ -63,6 +64,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     audioPlayerMounted() { |     audioPlayerMounted() { | ||||||
|  |       this.audioPlayerReady = true | ||||||
|       if (this.stream) { |       if (this.stream) { | ||||||
|         console.log('[STREAM-CONTAINER] audioPlayerMounted w/ Stream', this.stream) |         console.log('[STREAM-CONTAINER] audioPlayerMounted w/ Stream', this.stream) | ||||||
|         this.openStream() |         this.openStream() | ||||||
| @ -104,7 +106,7 @@ export default { | |||||||
|       if (this.$refs.audioPlayer) { |       if (this.$refs.audioPlayer) { | ||||||
|         console.log('[STREAM-CONTAINER] streamOpen', stream) |         console.log('[STREAM-CONTAINER] streamOpen', stream) | ||||||
|         this.openStream() |         this.openStream() | ||||||
|       } else { |       } else if (this.audioPlayerReady) { | ||||||
|         console.error('No Audio Ref') |         console.error('No Audio Ref') | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -8,14 +8,26 @@ | |||||||
|         <div class="arrow-down" /> |         <div class="arrow-down" /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div class="w-full h-full no-scroll flex"> |       <div class="w-full h-full no-scroll flex px-7 relative overflow-hidden"> | ||||||
|         <template v-for="(rate, index) in rates"> |         <div class="absolute left-0 top-0 h-full w-7 border-r border-black-300 bg-black-300 rounded-l-lg flex items-center justify-center cursor-pointer" :class="rateIndex === 0 ? 'bg-black-400 text-gray-400' : 'hover:bg-black-200'" @mousedown.prevent @mouseup.prevent @click="leftArrowClick"> | ||||||
|           <div :key="rate" class="flex items-center justify-center border-black-300 w-11 hover:bg-black hover:bg-opacity-10 cursor-pointer" :class="index < rates.length - 1 ? 'border-r' : ''" style="min-width: 44px; max-width: 44px" @click="set(rate)"> |           <span class="material-icons" style="font-size: 1.2rem">chevron_left</span> | ||||||
|             <p class="text-xs text-center font-mono">{{ rate.toFixed(1) }}<span class="text-sm">⨯</span></p> |         </div> | ||||||
|  |         <div class="overflow-hidden relative" style="width: 220px"> | ||||||
|  |           <div class="flex items-center h-full absolute top-0 left-0 transition-transform duration-100" :style="{ transform: `translateX(${xPos}px)` }"> | ||||||
|  |             <template v-for="rate in rates"> | ||||||
|  |               <div :key="rate" class="h-full border-black-300 w-11 cursor-pointer border-r" :class="value === rate ? 'bg-black-100' : 'hover:bg-black hover:bg-opacity-10'" style="min-width: 44px; max-width: 44px" @click="set(rate)"> | ||||||
|  |                 <div class="w-full h-full flex justify-center items-center"> | ||||||
|  |                   <p class="text-xs text-center font-mono">{{ rate }}<span class="text-sm">⨯</span></p> | ||||||
|  |                 </div> | ||||||
|               </div> |               </div> | ||||||
|             </template> |             </template> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |         <div class="absolute top-0 right-0 h-full w-7 bg-black-300 rounded-r-lg flex items-center justify-center cursor-pointer" :class="rateIndex === rates.length - numVisible ? 'bg-black-400 text-gray-400' : 'hover:bg-black-200'" @mousedown.prevent @mouseup.prevent @click="rightArrowClick"> | ||||||
|  |           <span class="material-icons" style="font-size: 1.2rem">chevron_right</span> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| @ -29,7 +41,9 @@ export default { | |||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       showMenu: false |       showMenu: false, | ||||||
|  |       rateIndex: 1, | ||||||
|  |       numVisible: 5 | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @ -42,7 +56,10 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     rates() { |     rates() { | ||||||
|       return [0.5, 0.8, 1.0, 1.3, 1.5, 2.0] |       return [0.25, 0.5, 0.8, 1, 1.3, 1.5, 2, 2.5, 3] | ||||||
|  |     }, | ||||||
|  |     xPos() { | ||||||
|  |       return -1 * this.rateIndex * 44 | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @ -55,6 +72,12 @@ export default { | |||||||
|       this.playbackRate = newPlaybackRate |       this.playbackRate = newPlaybackRate | ||||||
|       if (hasChanged) this.$emit('change', newPlaybackRate) |       if (hasChanged) this.$emit('change', newPlaybackRate) | ||||||
|       this.showMenu = false |       this.showMenu = false | ||||||
|  |     }, | ||||||
|  |     leftArrowClick() { | ||||||
|  |       this.rateIndex = Math.max(0, this.rateIndex - 4) | ||||||
|  |     }, | ||||||
|  |     rightArrowClick() { | ||||||
|  |       this.rateIndex = Math.min(this.rates.length - this.numVisible, this.rateIndex + 4) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   mounted() {} |   mounted() {} | ||||||
|  | |||||||
| @ -92,7 +92,7 @@ export default { | |||||||
|       return this.audiobook ? this.audiobook.book || {} : {} |       return this.audiobook ? this.audiobook.book || {} : {} | ||||||
|     }, |     }, | ||||||
|     userAudiobook() { |     userAudiobook() { | ||||||
|       return this.$store.getters['getUserAudiobook'](this.audiobookId) |       return this.$store.getters['user/getUserAudiobook'](this.audiobookId) | ||||||
|     }, |     }, | ||||||
|     userProgress() { |     userProgress() { | ||||||
|       return this.userAudiobook ? this.userAudiobook.progress : 0 |       return this.userAudiobook ? this.userAudiobook.progress : 0 | ||||||
|  | |||||||
| @ -24,13 +24,13 @@ export default { | |||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     user() { |     user() { | ||||||
|       return this.$store.state.user |       return this.$store.state.user.user | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     connect() { |     connect() { | ||||||
|       console.log('[SOCKET] Connected') |       console.log('[SOCKET] Connected') | ||||||
|       var token = this.$store.getters.getToken |       var token = this.$store.getters['user/getToken'] | ||||||
|       this.socket.emit('auth', token) |       this.socket.emit('auth', token) | ||||||
|     }, |     }, | ||||||
|     connectError() {}, |     connectError() {}, | ||||||
| @ -49,7 +49,8 @@ export default { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       if (payload.user) { |       if (payload.user) { | ||||||
|         this.$store.commit('setUser', payload.user) |         this.$store.commit('user/setUser', payload.user) | ||||||
|  |         this.$store.commit('user/setSettings', payload.user.settings) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     streamOpen(stream) { |     streamOpen(stream) { | ||||||
| @ -92,8 +93,9 @@ export default { | |||||||
|       this.$store.commit('setScanProgress', progress) |       this.$store.commit('setScanProgress', progress) | ||||||
|     }, |     }, | ||||||
|     userUpdated(user) { |     userUpdated(user) { | ||||||
|       if (this.$store.state.user.id === user.id) { |       if (this.$store.state.user.user.id === user.id) { | ||||||
|         this.$store.commit('setUser', user) |         this.$store.commit('user/setUser', user) | ||||||
|  |         this.$store.commit('user/setSettings', user.settings) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     initializeSocket() { |     initializeSocket() { | ||||||
| @ -139,7 +141,7 @@ export default { | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   beforeMount() { |   beforeMount() { | ||||||
|     if (!this.$store.state.user) { |     if (!this.$store.state.user.user) { | ||||||
|       this.$router.replace(`/login?redirect=${this.$route.path}`) |       this.$router.replace(`/login?redirect=${this.$route.path}`) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf-client", |   "name": "audiobookshelf-client", | ||||||
|   "version": "0.9.65-beta", |   "version": "0.9.7-beta", | ||||||
|   "description": "Audiobook manager and player", |   "description": "Audiobook manager and player", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     user() { |     user() { | ||||||
|       return this.$store.state.user || null |       return this.$store.state.user.user || null | ||||||
|     }, |     }, | ||||||
|     username() { |     username() { | ||||||
|       return this.user.username |       return this.user.username | ||||||
|  | |||||||
| @ -66,7 +66,7 @@ export default { | |||||||
|     draggable |     draggable | ||||||
|   }, |   }, | ||||||
|   async asyncData({ store, params, app, redirect, route }) { |   async asyncData({ store, params, app, redirect, route }) { | ||||||
|     if (!store.state.user) { |     if (!store.state.user.user) { | ||||||
|       return redirect(`/login?redirect=${route.path}`) |       return redirect(`/login?redirect=${route.path}`) | ||||||
|     } |     } | ||||||
|     var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => { |     var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => { | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ | |||||||
| <script> | <script> | ||||||
| export default { | export default { | ||||||
|   async asyncData({ store, params, app, redirect, route }) { |   async asyncData({ store, params, app, redirect, route }) { | ||||||
|     if (!store.state.user) { |     if (!store.state.user.user) { | ||||||
|       return redirect(`/login?redirect=${route.path}`) |       return redirect(`/login?redirect=${route.path}`) | ||||||
|     } |     } | ||||||
|     var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => { |     var audiobook = await app.$axios.$get(`/api/audiobook/${params.id}`).catch((error) => { | ||||||
| @ -163,7 +163,7 @@ export default { | |||||||
|       return this.book.description || 'No Description' |       return this.book.description || 'No Description' | ||||||
|     }, |     }, | ||||||
|     userAudiobooks() { |     userAudiobooks() { | ||||||
|       return this.$store.state.user ? this.$store.state.user.audiobooks || {} : {} |       return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {} | ||||||
|     }, |     }, | ||||||
|     userAudiobook() { |     userAudiobook() { | ||||||
|       return this.userAudiobooks[this.audiobookId] || null |       return this.userAudiobooks[this.audiobookId] || null | ||||||
|  | |||||||
| @ -49,7 +49,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     user() { |     user() { | ||||||
|       return this.$store.state.user |       return this.$store.state.user.user | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @ -71,7 +71,7 @@ export default { | |||||||
|       } else if (authRes.error) { |       } else if (authRes.error) { | ||||||
|         this.error = authRes.error |         this.error = authRes.error | ||||||
|       } else { |       } else { | ||||||
|         this.$store.commit('setUser', authRes.user) |         this.$store.commit('user/setUser', authRes.user) | ||||||
|       } |       } | ||||||
|       this.processing = false |       this.processing = false | ||||||
|     }, |     }, | ||||||
| @ -90,7 +90,7 @@ export default { | |||||||
|               } |               } | ||||||
|             }) |             }) | ||||||
|             .then((res) => { |             .then((res) => { | ||||||
|               this.$store.commit('setUser', res.user) |               this.$store.commit('user/setUser', res.user) | ||||||
|               this.processing = false |               this.processing = false | ||||||
|             }) |             }) | ||||||
|             .catch((error) => { |             .catch((error) => { | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ export default function ({ $axios, store }) { | |||||||
|     if (config.url.startsWith('http:') || config.url.startsWith('https:')) { |     if (config.url.startsWith('http:') || config.url.startsWith('https:')) { | ||||||
|       return |       return | ||||||
|     } |     } | ||||||
|     var bearerToken = store.state.user ? store.state.user.token : null |     var bearerToken = store.state.user.user ? store.state.user.user.token : null | ||||||
|     // console.log('Bearer token', bearerToken)
 |     // console.log('Bearer token', bearerToken)
 | ||||||
|     if (bearerToken) { |     if (bearerToken) { | ||||||
|       config.headers.common['Authorization'] = `Bearer ${bearerToken}` |       config.headers.common['Authorization'] = `Bearer ${bearerToken}` | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ export const state = () => ({ | |||||||
| export const getters = { | export const getters = { | ||||||
|   getFiltered: (state, getters, rootState) => () => { |   getFiltered: (state, getters, rootState) => () => { | ||||||
|     var filtered = state.audiobooks |     var filtered = state.audiobooks | ||||||
|     var settings = rootState.settings.settings || {} |     var settings = rootState.user.settings || {} | ||||||
|     var filterBy = settings.filterBy || '' |     var filterBy = settings.filterBy || '' | ||||||
| 
 | 
 | ||||||
|     var searchGroups = ['genres', 'tags', 'series'] |     var searchGroups = ['genres', 'tags', 'series'] | ||||||
| @ -27,7 +27,7 @@ export const getters = { | |||||||
|     return filtered |     return filtered | ||||||
|   }, |   }, | ||||||
|   getFilteredAndSorted: (state, getters, rootState) => () => { |   getFilteredAndSorted: (state, getters, rootState) => () => { | ||||||
|     var settings = rootState.settings.settings |     var settings = rootState.user.settings | ||||||
|     var direction = settings.orderDesc ? 'desc' : 'asc' |     var direction = settings.orderDesc ? 'desc' : 'asc' | ||||||
| 
 | 
 | ||||||
|     var filtered = getters.getFiltered() |     var filtered = getters.getFiltered() | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| 
 | 
 | ||||||
| export const state = () => ({ | export const state = () => ({ | ||||||
|   user: null, |  | ||||||
|   streamAudiobook: null, |   streamAudiobook: null, | ||||||
|   showEditModal: false, |   showEditModal: false, | ||||||
|   selectedAudiobook: null, |   selectedAudiobook: null, | ||||||
| @ -10,26 +9,11 @@ export const state = () => ({ | |||||||
|   developerMode: false |   developerMode: false | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| export const getters = { | export const getters = {} | ||||||
|   getToken: (state) => { |  | ||||||
|     return state.user ? state.user.token : null |  | ||||||
|   }, |  | ||||||
|   getUserAudiobook: (state) => (audiobookId) => { |  | ||||||
|     return state.user && state.user.audiobooks ? state.user.audiobooks[audiobookId] || null : null |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export const actions = { | export const actions = {} | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export const mutations = { | export const mutations = { | ||||||
|   setUser(state, user) { |  | ||||||
|     state.user = user |  | ||||||
|     if (user.token) { |  | ||||||
|       localStorage.setItem('token', user.token) |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   setStreamAudiobook(state, audiobook) { |   setStreamAudiobook(state, audiobook) { | ||||||
|     state.playOnLoad = true |     state.playOnLoad = true | ||||||
|     state.streamAudiobook = audiobook |     state.streamAudiobook = audiobook | ||||||
|  | |||||||
| @ -1,39 +0,0 @@ | |||||||
| 
 |  | ||||||
| export const state = () => ({ |  | ||||||
|   settings: { |  | ||||||
|     orderBy: 'book.title', |  | ||||||
|     orderDesc: false, |  | ||||||
|     filterBy: 'all' |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   listeners: [] |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| export const getters = { |  | ||||||
|   getFilterOrderKey: (state) => { |  | ||||||
|     return Object.values(state.settings).join('-') |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const actions = { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const mutations = { |  | ||||||
|   setSettings(state, settings) { |  | ||||||
|     state.settings = { |  | ||||||
|       ...settings |  | ||||||
|     } |  | ||||||
|     state.listeners.forEach((listener) => { |  | ||||||
|       listener.meth() |  | ||||||
|     }) |  | ||||||
|   }, |  | ||||||
|   addListener(state, listener) { |  | ||||||
|     var index = state.listeners.findIndex(l => l.id === listener.id) |  | ||||||
|     if (index >= 0) state.listeners.splice(index, 1, listener) |  | ||||||
|     else state.listeners.push(listener) |  | ||||||
|   }, |  | ||||||
|   removeListener(state, listenerId) { |  | ||||||
|     state.listeners = state.listeners.filter(l => l.id !== listenerId) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										81
									
								
								client/store/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								client/store/user.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | |||||||
|  | 
 | ||||||
|  | export const state = () => ({ | ||||||
|  |   user: null, | ||||||
|  |   settings: { | ||||||
|  |     orderBy: 'book.title', | ||||||
|  |     orderDesc: false, | ||||||
|  |     filterBy: 'all', | ||||||
|  |     playbackRate: 1 | ||||||
|  |   }, | ||||||
|  |   settingsListeners: [] | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | export const getters = { | ||||||
|  |   getToken: (state) => { | ||||||
|  |     return state.user ? state.user.token : null | ||||||
|  |   }, | ||||||
|  |   getUserAudiobook: (state) => (audiobookId) => { | ||||||
|  |     return state.user && state.user.audiobooks ? state.user.audiobooks[audiobookId] || null : null | ||||||
|  |   }, | ||||||
|  |   getUserSetting: (state) => (key) => { | ||||||
|  |     return state.settings ? state.settings[key] || null : null | ||||||
|  |   }, | ||||||
|  |   getFilterOrderKey: (state) => { | ||||||
|  |     return Object.values(state.settings).join('-') | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const actions = { | ||||||
|  |   updateUserSettings({ commit }, payload) { | ||||||
|  |     var updatePayload = { | ||||||
|  |       ...payload | ||||||
|  |     } | ||||||
|  |     return this.$axios.$patch('/api/user/settings', updatePayload).then((result) => { | ||||||
|  |       if (result.success) { | ||||||
|  |         commit('setSettings', result.settings) | ||||||
|  |         console.log('Settings updated', result.settings) | ||||||
|  |         return true | ||||||
|  |       } else { | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|  |     }).catch((error) => { | ||||||
|  |       console.error('Failed to update settings', error) | ||||||
|  |       return false | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const mutations = { | ||||||
|  |   setUser(state, user) { | ||||||
|  |     state.user = user | ||||||
|  |     if (user && user.token) { | ||||||
|  |       localStorage.setItem('token', user.token) | ||||||
|  |     } else if (user) { | ||||||
|  |       localStorage.removeItem('token') | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   setSettings(state, settings) { | ||||||
|  |     if (!settings) return | ||||||
|  | 
 | ||||||
|  |     var hasChanges = false | ||||||
|  |     for (const key in settings) { | ||||||
|  |       if (state.settings[key] !== settings[key]) { | ||||||
|  |         hasChanges = true | ||||||
|  |         state.settings[key] = settings[key] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (hasChanges) { | ||||||
|  |       state.settingsListeners.forEach((listener) => { | ||||||
|  |         listener.meth(state.settings) | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   addSettingsListener(state, listener) { | ||||||
|  |     var index = state.settingsListeners.findIndex(l => l.id === listener.id) | ||||||
|  |     if (index >= 0) state.settingsListeners.splice(index, 1, listener) | ||||||
|  |     else state.settingsListeners.push(listener) | ||||||
|  |   }, | ||||||
|  |   removeSettingsListener(state, listenerId) { | ||||||
|  |     state.settingsListeners = state.settingsListeners.filter(l => l.id !== listenerId) | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf", |   "name": "audiobookshelf", | ||||||
|   "version": "0.9.65-beta", |   "version": "0.9.7-beta", | ||||||
|   "description": "Self-hosted audiobook server for managing and playing audiobooks.", |   "description": "Self-hosted audiobook server for managing and playing audiobooks.", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| const express = require('express') | const express = require('express') | ||||||
| const Logger = require('./Logger') | const Logger = require('./Logger') | ||||||
|  | const { isObject } = require('./utils/index') | ||||||
| 
 | 
 | ||||||
| class ApiController { | class ApiController { | ||||||
|   constructor(db, scanner, auth, streamManager, rssFeeds, emitter) { |   constructor(db, scanner, auth, streamManager, rssFeeds, emitter) { | ||||||
| @ -32,6 +33,7 @@ class ApiController { | |||||||
|     this.router.get('/users', this.getUsers.bind(this)) |     this.router.get('/users', this.getUsers.bind(this)) | ||||||
|     this.router.delete('/user/audiobook/:id', this.resetUserAudiobookProgress.bind(this)) |     this.router.delete('/user/audiobook/:id', this.resetUserAudiobookProgress.bind(this)) | ||||||
|     this.router.patch('/user/password', this.userChangePassword.bind(this)) |     this.router.patch('/user/password', this.userChangePassword.bind(this)) | ||||||
|  |     this.router.patch('/user/settings', this.userUpdateSettings.bind(this)) | ||||||
| 
 | 
 | ||||||
|     this.router.post('/authorize', this.authorize.bind(this)) |     this.router.post('/authorize', this.authorize.bind(this)) | ||||||
| 
 | 
 | ||||||
| @ -185,6 +187,21 @@ class ApiController { | |||||||
|     res.json(feed) |     res.json(feed) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async userUpdateSettings(req, res) { | ||||||
|  |     var settingsUpdate = req.body | ||||||
|  |     if (!settingsUpdate || !isObject(settingsUpdate)) { | ||||||
|  |       return res.sendStatus(500) | ||||||
|  |     } | ||||||
|  |     var madeUpdates = req.user.updateSettings(settingsUpdate) | ||||||
|  |     if (madeUpdates) { | ||||||
|  |       await this.db.updateEntity('user', req.user) | ||||||
|  |     } | ||||||
|  |     return res.json({ | ||||||
|  |       success: true, | ||||||
|  |       settings: req.user.settings | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getGenres(req, res) { |   getGenres(req, res) { | ||||||
|     res.json({ |     res.json({ | ||||||
|       genres: this.db.getGenres() |       genres: this.db.getGenres() | ||||||
|  | |||||||
| @ -8,12 +8,22 @@ class User { | |||||||
|     this.token = null |     this.token = null | ||||||
|     this.createdAt = null |     this.createdAt = null | ||||||
|     this.audiobooks = null |     this.audiobooks = null | ||||||
|  |     this.settings = {} | ||||||
| 
 | 
 | ||||||
|     if (user) { |     if (user) { | ||||||
|       this.construct(user) |       this.construct(user) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   getDefaultUserSettings() { | ||||||
|  |     return { | ||||||
|  |       orderBy: 'book.title', | ||||||
|  |       orderDesc: false, | ||||||
|  |       filterBy: 'all', | ||||||
|  |       playbackRate: 1 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   toJSON() { |   toJSON() { | ||||||
|     return { |     return { | ||||||
|       id: this.id, |       id: this.id, | ||||||
| @ -23,7 +33,8 @@ class User { | |||||||
|       stream: this.stream, |       stream: this.stream, | ||||||
|       token: this.token, |       token: this.token, | ||||||
|       audiobooks: this.audiobooks, |       audiobooks: this.audiobooks, | ||||||
|       createdAt: this.createdAt |       createdAt: this.createdAt, | ||||||
|  |       settings: this.settings | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -35,7 +46,8 @@ class User { | |||||||
|       stream: this.stream, |       stream: this.stream, | ||||||
|       token: this.token, |       token: this.token, | ||||||
|       audiobooks: this.audiobooks, |       audiobooks: this.audiobooks, | ||||||
|       createdAt: this.createdAt |       createdAt: this.createdAt, | ||||||
|  |       settings: this.settings | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -48,6 +60,7 @@ class User { | |||||||
|     this.token = user.token |     this.token = user.token | ||||||
|     this.audiobooks = user.audiobooks || null |     this.audiobooks = user.audiobooks || null | ||||||
|     this.createdAt = user.createdAt |     this.createdAt = user.createdAt | ||||||
|  |     this.settings = user.settings || this.getDefaultUserSettings() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updateAudiobookProgress(stream) { |   updateAudiobookProgress(stream) { | ||||||
| @ -64,6 +77,32 @@ class User { | |||||||
|     this.audiobooks[stream.audiobookId].currentTime = stream.clientCurrentTime |     this.audiobooks[stream.audiobookId].currentTime = stream.clientCurrentTime | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // Returns Boolean If update was made
 | ||||||
|  |   updateSettings(settings) { | ||||||
|  |     if (!this.settings) { | ||||||
|  |       this.settings = { ...settings } | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |     var madeUpdates = false | ||||||
|  | 
 | ||||||
|  |     for (const key in this.settings) { | ||||||
|  |       if (settings[key] !== undefined && this.settings[key] !== settings[key]) { | ||||||
|  |         this.settings[key] = settings[key] | ||||||
|  |         madeUpdates = true | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Check if new settings update has keys not currently in user settings
 | ||||||
|  |     for (const key in settings) { | ||||||
|  |       if (settings[key] !== undefined && this.settings[key] === undefined) { | ||||||
|  |         this.settings[key] = settings[key] | ||||||
|  |         madeUpdates = true | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return madeUpdates | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   resetAudiobookProgress(audiobookId) { |   resetAudiobookProgress(audiobookId) { | ||||||
|     if (!this.audiobooks || !this.audiobooks[audiobookId]) { |     if (!this.audiobooks || !this.audiobooks[audiobookId]) { | ||||||
|       return false |       return false | ||||||
|  | |||||||
| @ -41,3 +41,7 @@ const cleanString = (str) => { | |||||||
|   return cleaned |   return cleaned | ||||||
| } | } | ||||||
| module.exports.cleanString = cleanString | module.exports.cleanString = cleanString | ||||||
|  | 
 | ||||||
|  | module.exports.isObject = (val) => { | ||||||
|  |   return val !== null && typeof val === 'object' | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user