Merge remote-tracking branch 'remotes/upstream/master'

This commit is contained in:
Toni Barth 2024-10-27 20:51:40 +01:00
commit 83199f0cda
77 changed files with 1528 additions and 418 deletions

View file

@ -32,9 +32,48 @@
</form>
</div>
<div v-if="showEreaderTable">
<div class="w-full h-px bg-white/10 my-4" />
<app-settings-content :header-text="$strings.HeaderEreaderDevices">
<template #header-items>
<div class="flex-grow" />
<ui-btn color="primary" small @click="addNewDeviceClick">{{ $strings.ButtonAddDevice }}</ui-btn>
</template>
<table v-if="ereaderDevices.length" class="tracksTable mt-4">
<tr>
<th class="text-left">{{ $strings.LabelName }}</th>
<th class="text-left">{{ $strings.LabelEmail }}</th>
<th class="w-40"></th>
</tr>
<tr v-for="device in ereaderDevices" :key="device.name">
<td>
<p class="text-sm md:text-base text-gray-100">{{ device.name }}</p>
</td>
<td class="text-left">
<p class="text-sm md:text-base text-gray-100">{{ device.email }}</p>
</td>
<td class="w-40">
<div class="flex justify-end items-center h-10">
<ui-icon-btn icon="edit" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name || device.users?.length !== 1" class="mx-1" @click="editDeviceClick(device)" />
<ui-icon-btn icon="delete" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name || device.users?.length !== 1" @click="deleteDeviceClick(device)" />
</div>
</td>
</tr>
</table>
<div v-else-if="!loading" class="text-center py-4">
<p class="text-lg text-gray-100">{{ $strings.MessageNoDevices }}</p>
</div>
</app-settings-content>
</div>
<div class="py-4 mt-8 flex">
<ui-btn color="primary flex items-center text-lg" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
</div>
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
</div>
</div>
</template>
@ -43,11 +82,20 @@
export default {
data() {
return {
loading: false,
password: null,
newPassword: null,
confirmPassword: null,
changingPassword: false,
selectedLanguage: ''
selectedLanguage: '',
newEReaderDevice: {
name: '',
email: ''
},
ereaderDevices: [],
deletingDeviceName: null,
selectedEReaderDevice: null,
showEReaderDeviceModal: false
}
},
computed: {
@ -75,6 +123,12 @@ export default {
},
showChangePasswordForm() {
return !this.isGuest && this.isPasswordAuthEnabled
},
showEreaderTable() {
return this.usertype !== 'root' && this.usertype !== 'admin' && this.user.permissions?.createEreader
},
revisedEreaderDevices() {
return this.ereaderDevices.filter((device) => device.users?.length === 1)
}
},
methods: {
@ -142,10 +196,52 @@ export default {
this.$toast.error(this.$strings.ToastUnknownError)
this.changingPassword = false
})
},
addNewDeviceClick() {
this.selectedEReaderDevice = null
this.showEReaderDeviceModal = true
},
editDeviceClick(device) {
this.selectedEReaderDevice = device
this.showEReaderDeviceModal = true
},
deleteDeviceClick(device) {
const payload = {
message: this.$getString('MessageConfirmDeleteDevice', [device.name]),
callback: (confirmed) => {
if (confirmed) {
this.deleteDevice(device)
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
deleteDevice(device) {
const payload = {
ereaderDevices: this.revisedEreaderDevices.filter((d) => d.name !== device.name)
}
this.deletingDeviceName = device.name
this.$axios
.$post(`/api/me/ereader-devices`, payload)
.then((data) => {
this.ereaderDevicesUpdated(data.ereaderDevices)
})
.catch((error) => {
console.error('Failed to delete device', error)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.deletingDeviceName = null
})
},
ereaderDevicesUpdated(ereaderDevices) {
this.ereaderDevices = ereaderDevices
}
},
mounted() {
this.selectedLanguage = this.$languageCodes.current
this.ereaderDevices = this.$store.state.libraries.ereaderDevices || []
}
}
</script>

View file

@ -415,7 +415,7 @@ export default {
const audioEl = this.audioEl || document.createElement('audio')
var src = audioTrack.contentUrl + `?token=${this.userToken}`
if (this.$isDev) {
src = `http://localhost:3333${this.$config.routerBasePath}${src}`
src = `${process.env.serverUrl}${src}`
}
audioEl.src = src
@ -486,7 +486,7 @@ export default {
.then((data) => {
this.saving = false
if (data.updated) {
this.$toast.success('Chapters updated')
this.$toast.success(this.$strings.ToastChaptersUpdated)
if (this.previousRoute) {
this.$router.push(this.previousRoute)
} else {
@ -533,7 +533,7 @@ export default {
},
findChapters() {
if (!this.asinInput) {
this.$toast.error('Must input an ASIN')
this.$toast.error(this.$strings.ToastAsinRequired)
return
}

View file

@ -88,7 +88,7 @@
<ui-dropdown v-model="itemsPerPage" :items="itemsPerPageOptions" small class="w-24 mx-2" @input="updatedItemsPerPage" />
</div>
<div class="inline-flex items-center">
<p class="text-sm mx-2">Page {{ currentPage + 1 }} of {{ numPages }}</p>
<p class="text-sm mx-2">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
<ui-icon-btn icon="arrow_back_ios_new" :size="9" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
<ui-icon-btn icon="arrow_forward_ios" :size="9" icon-font-size="1rem" class="mx-1" :disabled="currentPage >= numPages - 1" @click="nextPage" />
</div>
@ -103,7 +103,7 @@
<div v-if="openListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
<!-- open listening sessions table -->
<p v-if="openListeningSessions.length" class="text-lg my-4">Open Listening Sessions</p>
<p v-if="openListeningSessions.length" class="text-lg my-4">{{ $strings.HeaderOpenListeningSessions }}</p>
<div v-if="openListeningSessions.length" class="block max-w-full">
<table class="userSessionsTable">
<tr class="bg-primary bg-opacity-40">

View file

@ -14,7 +14,7 @@
<h1 class="text-xl pl-2">{{ username }}</h1>
</div>
<div v-if="userToken" class="flex text-xs mt-4">
<ui-text-input-with-label label="API Token" :value="userToken" readonly />
<ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly />
<div class="px-1 mt-8 cursor-pointer" @click="copyToClipboard(userToken)">
<span class="material-symbols pl-2 text-base">content_copy</span>

View file

@ -54,7 +54,7 @@
</table>
<div class="flex items-center justify-end py-1">
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
<p class="text-sm mx-1">Page {{ currentPage + 1 }} of {{ numPages }}</p>
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
<ui-icon-btn icon="arrow_forward_ios" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage >= numPages - 1" @click="nextPage" />
</div>
</div>

View file

@ -120,7 +120,7 @@ export default {
})
.catch((error) => {
console.error('Failed to updated narrator', error)
this.$toast.error('Failed to update narrator')
this.$toast.error(this.$strings.ToastFailedToUpdate)
this.loading = false
})
},

View file

@ -10,7 +10,7 @@
<p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-lg lg:text-xl text-slate-400 font-semibold text-center mb-1 truncate">{{ mediaItemShare.playbackSession.displayAuthor }}</p>
<div class="w-full pt-16">
<player-ui ref="audioPlayer" :chapters="chapters" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
<player-ui ref="audioPlayer" :chapters="chapters" :current-chapter="currentChapter" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
</div>
</div>
</div>
@ -51,7 +51,8 @@ export default {
windowHeight: 0,
listeningTimeSinceSync: 0,
coverRgb: null,
coverBgIsLight: false
coverBgIsLight: false,
currentTime: 0
}
},
computed: {
@ -60,16 +61,10 @@ export default {
},
coverUrl() {
if (!this.playbackSession.coverPath) return `${this.$config.routerBasePath}/book_placeholder.jpg`
if (process.env.NODE_ENV === 'development') {
return `http://localhost:3333/public/share/${this.mediaItemShare.slug}/cover`
}
return `/public/share/${this.mediaItemShare.slug}/cover`
return `${this.$config.routerBasePath}/public/share/${this.mediaItemShare.slug}/cover`
},
audioTracks() {
return (this.playbackSession.audioTracks || []).map((track) => {
if (process.env.NODE_ENV === 'development') {
track.contentUrl = `${process.env.serverUrl}${track.contentUrl}`
}
track.relativeContentUrl = track.contentUrl
return track
})
@ -83,6 +78,9 @@ export default {
chapters() {
return this.playbackSession.chapters || []
},
currentChapter() {
return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end)
},
coverAspectRatio() {
const coverAspectRatio = this.playbackSession.coverAspectRatio
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
@ -154,6 +152,7 @@ export default {
// Update UI
this.$refs.audioPlayer.setCurrentTime(time)
this.currentTime = time
},
setDuration() {
if (!this.localAudioPlayer) return