Add: Experimental bookmarks edit and delete #115

This commit is contained in:
advplyr 2021-10-26 20:09:04 -05:00
parent fffa02e7e8
commit 9f66054a72
10 changed files with 234 additions and 39 deletions

View File

@ -20,7 +20,7 @@
-webkit-font-feature-settings: 'liga'; -webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
.material-icons:not(.text-sm):not(.text-md):not(.text-base):not(.text-lg):not(.text-xl):not(.text-2xl):not(.text-3xl):not(.text-4xl):not(.text-5xl):not(.text-6xl) { .material-icons:not(.text-xs):not(.text-sm):not(.text-md):not(.text-base):not(.text-lg):not(.text-xl):not(.text-2xl):not(.text-3xl):not(.text-4xl):not(.text-5xl):not(.text-6xl) {
font-size: 1.5rem; font-size: 1.5rem;
} }

View File

@ -15,7 +15,7 @@
</div> </div>
<div v-if="showExperimentalFeatures" class="absolute top-0 bottom-0 h-full flex items-end" :class="chapters.length ? ' right-32' : 'right-20'"> <div v-if="showExperimentalFeatures" class="absolute top-0 bottom-0 h-full flex items-end" :class="chapters.length ? ' right-32' : 'right-20'">
<div class="cursor-pointer text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="showBookmarks"> <div class="cursor-pointer text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="showBookmarks">
<span class="material-icons text-3xl">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span> <span class="material-icons" style="font-size: 1.7rem">{{ bookmarks.length ? 'bookmarks' : 'bookmark_border' }}</span>
</div> </div>
</div> </div>
<div class="absolute top-0 bottom-0 h-full flex items-end" :class="!showExperimentalFeatures ? (chapters.length ? ' right-32' : 'right-20') : chapters.length ? ' right-44' : 'right-32'"> <div class="absolute top-0 bottom-0 h-full flex items-end" :class="!showExperimentalFeatures ? (chapters.length ? ' right-32' : 'right-20') : chapters.length ? ' right-44' : 'right-32'">

View File

@ -24,7 +24,7 @@
<audio-player ref="audioPlayer" :chapters="chapters" :loading="isLoading" :bookmarks="bookmarks" @close="cancelStream" @updateTime="updateTime" @loaded="(d) => (totalDuration = d)" @showBookmarks="showBookmarks" @hook:mounted="audioPlayerMounted" /> <audio-player ref="audioPlayer" :chapters="chapters" :loading="isLoading" :bookmarks="bookmarks" @close="cancelStream" @updateTime="updateTime" @loaded="(d) => (totalDuration = d)" @showBookmarks="showBookmarks" @hook:mounted="audioPlayerMounted" />
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :audiobook-id="bookmarkAudiobookId" :current-time="bookmarkCurrentTime" @select="selectBookmark" @create="createBookmark" /> <modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :audiobook-id="bookmarkAudiobookId" :current-time="bookmarkCurrentTime" @select="selectBookmark" @create="createBookmark" @update="updateBookmark" @delete="deleteBookmark" />
</div> </div>
</template> </template>
@ -38,8 +38,7 @@ export default {
totalDuration: 0, totalDuration: 0,
showBookmarksModal: false, showBookmarksModal: false,
bookmarkCurrentTime: 0, bookmarkCurrentTime: 0,
bookmarkAudiobookId: null, bookmarkAudiobookId: null
bookmarkTimeCreating: 0
} }
}, },
computed: { computed: {
@ -103,24 +102,38 @@ export default {
this.bookmarkCurrentTime = currentTime this.bookmarkCurrentTime = currentTime
this.showBookmarksModal = true this.showBookmarksModal = true
}, },
bookmarkCreated(time) { // bookmarkCreated(time) {
if (time === this.bookmarkTimeCreating) { // if (time === this.bookmarkTimeProcessing) {
this.bookmarkTimeCreating = 0 // this.bookmarkTimeProcessing = 0
this.$toast.success(`${this.$secondsToTimestamp(time)} Bookmarked`) // this.$toast.success(`${this.$secondsToTimestamp(time)} Bookmarked`)
} // }
}, // },
createBookmark(bookmark) { createBookmark(bookmark) {
this.bookmarkTimeCreating = bookmark.time // this.bookmarkTimeProcessing = bookmark.time
this.$root.socket.once('bookmark_created', this.bookmarkCreated)
this.$root.socket.emit('create_bookmark', bookmark) this.$root.socket.emit('create_bookmark', bookmark)
this.showBookmarksModal = false this.showBookmarksModal = false
}, },
// bookmarkUpdated(time) {
// if (time === this.bookmarkTimeProcessing) {
// this.bookmarkTimeProcessing = 0
// this.$toast.success(`Bookmark @${this.$secondsToTimestamp(time)} Updated`)
// }
// },
updateBookmark(bookmark) {
// this.bookmarkTimeProcessing = bookmark.time
this.$root.socket.emit('update_bookmark', bookmark)
this.showBookmarksModal = false
},
selectBookmark(bookmark) { selectBookmark(bookmark) {
if (this.$refs.audioPlayer) { if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.selectBookmark(bookmark) this.$refs.audioPlayer.selectBookmark(bookmark)
} }
this.showBookmarksModal = false this.showBookmarksModal = false
}, },
deleteBookmark(bookmark) {
this.$root.socket.emit('delete_bookmark', bookmark)
this.showBookmarksModal = false
},
filterByAuthor() { filterByAuthor() {
if (this.$route.name !== 'index') { if (this.$route.name !== 'index') {
this.$router.push(`/library/${this.libraryId || this.$store.state.libraries.currentLibraryId}/bookshelf`) this.$router.push(`/library/${this.libraryId || this.$store.state.libraries.currentLibraryId}/bookshelf`)

View File

@ -6,7 +6,7 @@
<div class="w-9 h-9 flex items-center justify-center rounded-full hover:bg-white hover:bg-opacity-10 cursor-pointer" @click="showBookmarkTitleInput = false"> <div class="w-9 h-9 flex items-center justify-center rounded-full hover:bg-white hover:bg-opacity-10 cursor-pointer" @click="showBookmarkTitleInput = false">
<span class="material-icons text-3xl">arrow_back</span> <span class="material-icons text-3xl">arrow_back</span>
</div> </div>
<p class="text-xl pl-2">New Bookmark</p> <p class="text-xl pl-2">{{ selectedBookmark ? 'Edit Bookmark' : 'New Bookmark' }}</p>
<div class="flex-grow" /> <div class="flex-grow" />
<p class="text-xl font-mono"> <p class="text-xl font-mono">
{{ this.$secondsToTimestamp(currentTime) }} {{ this.$secondsToTimestamp(currentTime) }}
@ -15,25 +15,18 @@
<form @submit.prevent="submitBookmark"> <form @submit.prevent="submitBookmark">
<ui-text-input-with-label v-model="newBookmarkTitle" label="Note" /> <ui-text-input-with-label v-model="newBookmarkTitle" label="Note" />
<div class="flex justify-end mt-6"> <div class="flex justify-end mt-6">
<ui-btn color="success" class="w-1/2" type="submit">Create Bookmark</ui-btn> <ui-btn color="success" class="w-1/2" type="submit">{{ selectedBookmark ? 'Update' : 'Create' }} Bookmark</ui-btn>
</div> </div>
</form> </form>
</div> </div>
<div class="w-full h-full" v-show="!showBookmarkTitleInput"> <div class="w-full h-full" v-show="!showBookmarkTitleInput">
<template v-for="bookmark in bookmarks"> <template v-for="bookmark in bookmarks">
<div :key="bookmark.id" :id="`bookmark-row-${bookmark.id}`" class="flex items-center px-4 py-4 justify-start cursor-pointer bg-opacity-20 hover:bg-bg relative" @click="clickBookmark(bookmark)"> <modals-bookmarks-bookmark-item :key="bookmark.id" :highlight="currentTime === bookmark.time" :bookmark="bookmark" @click="clickBookmark" @edit="editBookmark" @delete="deleteBookmark" />
<span class="material-icons text-white text-opacity-60">bookmark_border</span>
<p class="pl-2 pr-16 truncate">{{ bookmark.title }}</p>
<div class="absolute right-0 top-0 h-full flex items-center pr-4">
<span class="font-mono text-sm text-gray-300">{{ $secondsToTimestamp(bookmark.time) }}</span>
</div>
</div>
</template> </template>
<div v-if="!bookmarks.length" class="flex h-32 items-center justify-center"> <div v-if="!bookmarks.length" class="flex h-32 items-center justify-center">
<p class="text-xl">No Bookmarks</p> <p class="text-xl">No Bookmarks</p>
</div> </div>
<div class="flex px-4 py-2 items-center text-center justify-between border-b border-white border-opacity-10 bg-blue-500 bg-opacity-20 cursor-pointer text-white text-opacity-80 hover:bg-opacity-40 hover:text-opacity-100" @click="createBookmark"> <div v-show="canCreateBookmark" class="flex px-4 py-2 items-center text-center justify-between border-b border-white border-opacity-10 bg-blue-500 bg-opacity-20 cursor-pointer text-white text-opacity-80 hover:bg-opacity-40 hover:text-opacity-100" @click="createBookmark">
<span class="material-icons">add</span> <span class="material-icons">add</span>
<p class="text-base pl-2">Create Bookmark</p> <p class="text-base pl-2">Create Bookmark</p>
<p class="text-sm font-mono"> <p class="text-sm font-mono">
@ -61,6 +54,7 @@ export default {
}, },
data() { data() {
return { return {
selectedBookmark: null,
showBookmarkTitleInput: false, showBookmarkTitleInput: false,
newBookmarkTitle: '' newBookmarkTitle: ''
} }
@ -81,22 +75,45 @@ export default {
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} }
},
canCreateBookmark() {
return !this.bookmarks.find((bm) => bm.time === this.currentTime)
} }
}, },
methods: { methods: {
editBookmark(bm) {
this.selectedBookmark = bm
this.newBookmarkTitle = bm.title
this.showBookmarkTitleInput = true
},
deleteBookmark(bm) {
var bookmark = { ...bm, audiobookId: this.audiobookId }
this.$emit('delete', bookmark)
},
clickBookmark(bm) { clickBookmark(bm) {
this.$emit('select', bm) this.$emit('select', bm)
}, },
createBookmark() { createBookmark() {
this.selectedBookmark = null
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
this.showBookmarkTitleInput = true this.showBookmarkTitleInput = true
}, },
submitBookmark() { submitBookmark() {
var bookmark = { if (this.selectedBookmark) {
audiobookId: this.audiobookId, if (this.selectedBookmark.title !== this.newBookmarkTitle) {
title: this.newBookmarkTitle, var bookmark = { ...this.selectedBookmark }
time: this.currentTime bookmark.audiobookId = this.audiobookId
bookmark.title = this.newBookmarkTitle
this.$emit('update', bookmark)
}
} else {
var bookmark = {
audiobookId: this.audiobookId,
title: this.newBookmarkTitle,
time: this.currentTime
}
this.$emit('create', bookmark)
} }
this.$emit('create', bookmark)
this.newBookmarkTitle = '' this.newBookmarkTitle = ''
this.showBookmarkTitleInput = false this.showBookmarkTitleInput = false
} }

View File

@ -0,0 +1,45 @@
<template>
<div :key="bookmark.id" :id="`bookmark-row-${bookmark.id}`" class="flex items-center px-4 py-4 justify-start cursor-pointer hover:bg-bg relative" :class="highlight ? 'bg-bg bg-opacity-60' : ' bg-opacity-20'" @click="click" @mouseover="isHovering = true" @mouseleave="isHovering = false">
<span class="material-icons" :class="highlight ? 'text-success' : 'text-white text-opacity-60'">{{ highlight ? 'bookmark' : 'bookmark_border' }}</span>
<div class="flex-grow overflow-hidden">
<p class="pl-2 pr-2 truncate">{{ bookmark.title }}</p>
</div>
<div class="h-full flex items-center w-16 justify-end">
<span class="font-mono text-sm text-gray-300">{{ $secondsToTimestamp(bookmark.time) }}</span>
</div>
<div class="h-full flex items-center justify-end transform" :class="isHovering ? 'transition-transform translate-0 w-16' : 'translate-x-40 w-0'">
<span class="material-icons text-lg mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
<span class="material-icons text-lg text-gray-200 hover:text-error cursor-pointer" @click.stop="deleteClick">delete</span>
</div>
</div>
</template>
<script>
export default {
props: {
bookmark: {
type: Object,
default: () => {}
},
highlight: Boolean
},
data() {
return {
isHovering: false
}
},
computed: {},
methods: {
click() {
this.$emit('click', this.bookmark)
},
deleteClick() {
this.$emit('delete', this.bookmark)
},
editClick() {
this.$emit('edit', this.bookmark)
}
},
mounted() {}
}
</script>

View File

@ -239,6 +239,12 @@ export default {
download.status = this.$constants.DownloadStatus.EXPIRED download.status = this.$constants.DownloadStatus.EXPIRED
this.$store.commit('downloads/addUpdateDownload', download) this.$store.commit('downloads/addUpdateDownload', download)
}, },
showErrorToast(message) {
this.$toast.error(message)
},
showSuccessToast(message) {
this.$toast.success(message)
},
logEvtReceived(payload) { logEvtReceived(payload) {
this.$store.commit('logs/logEvt', payload) this.$store.commit('logs/logEvt', payload)
}, },
@ -303,6 +309,10 @@ export default {
this.socket.on('download_killed', this.downloadKilled) this.socket.on('download_killed', this.downloadKilled)
this.socket.on('download_expired', this.downloadExpired) this.socket.on('download_expired', this.downloadExpired)
// Toast Listeners
this.socket.on('show_error_toast', this.showErrorToast)
this.socket.on('show_success_toast', this.showSuccessToast)
this.socket.on('log', this.logEvtReceived) this.socket.on('log', this.logEvtReceived)
this.socket.on('backup_applied', this.backupApplied) this.socket.on('backup_applied', this.backupApplied)

View File

@ -11,6 +11,7 @@ const { version } = require('../package.json')
// Utils // Utils
const { ScanResult } = require('./utils/constants') const { ScanResult } = require('./utils/constants')
const filePerms = require('./utils/filePerms') const filePerms = require('./utils/filePerms')
const { secondsToTimestamp } = require('./utils/fileUtils')
const Logger = require('./Logger') const Logger = require('./Logger')
// Classes // Classes
@ -261,6 +262,8 @@ class Server {
// Bookmarks // Bookmarks
socket.on('create_bookmark', (payload) => this.createBookmark(socket, payload)) socket.on('create_bookmark', (payload) => this.createBookmark(socket, payload))
socket.on('update_bookmark', (payload) => this.updateBookmark(socket, payload))
socket.on('delete_bookmark', (payload) => this.deleteBookmark(socket, payload))
socket.on('test', () => { socket.on('test', () => {
socket.emit('test_received', socket.id) socket.emit('test_received', socket.id)
@ -481,15 +484,66 @@ class Server {
return return
} }
var userAudiobook = client.user.createBookmark(payload) var userAudiobook = client.user.createBookmark(payload)
if (userAudiobook) { if (!userAudiobook || userAudiobook.error) {
await this.db.updateEntity('user', client.user) var failMessage = (userAudiobook ? userAudiobook.error : null) || 'Unknown Error'
socket.emit('show_error_toast', `Failed to create Bookmark: ${failMessage}`)
this.clientEmitter(client.user.id, 'bookmark_created', payload.time) return
this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
id: userAudiobook.audiobookId,
data: userAudiobook || null
})
} }
await this.db.updateEntity('user', client.user)
socket.emit('show_success_toast', `${secondsToTimestamp(payload.time)} Bookmarked`)
this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
id: userAudiobook.audiobookId,
data: userAudiobook || null
})
}
async updateBookmark(socket, payload) {
var client = socket.sheepClient
if (!client || !client.user) {
Logger.error('[Server] updateBookmark invalid socket client')
return
}
var userAudiobook = client.user.updateBookmark(payload)
if (!userAudiobook || userAudiobook.error) {
var failMessage = (userAudiobook ? userAudiobook.error : null) || 'Unknown Error'
socket.emit('show_error_toast', `Failed to update Bookmark: ${failMessage}`)
return
}
await this.db.updateEntity('user', client.user)
socket.emit('show_success_toast', `Bookmark ${secondsToTimestamp(payload.time)} Updated`)
this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
id: userAudiobook.audiobookId,
data: userAudiobook || null
})
}
async deleteBookmark(socket, payload) {
var client = socket.sheepClient
if (!client || !client.user) {
Logger.error('[Server] deleteBookmark invalid socket client')
return
}
var userAudiobook = client.user.deleteBookmark(payload)
if (!userAudiobook || userAudiobook.error) {
var failMessage = (userAudiobook ? userAudiobook.error : null) || 'Unknown Error'
socket.emit('show_error_toast', `Failed to delete Bookmark: ${failMessage}`)
return
}
await this.db.updateEntity('user', client.user)
socket.emit('show_success_toast', `Bookmark ${secondsToTimestamp(payload.time)} Removed`)
this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
id: userAudiobook.audiobookId,
data: userAudiobook || null
})
} }
async authenticateSocket(socket, token) { async authenticateSocket(socket, token) {

View File

@ -107,11 +107,26 @@ class AudiobookProgress {
return hasUpdates return hasUpdates
} }
checkBookmarkExists(time) {
return this.bookmarks.find(bm => bm.time === time)
}
createBookmark(time, title) { createBookmark(time, title) {
var newBookmark = new AudioBookmark() var newBookmark = new AudioBookmark()
newBookmark.setData(time, title) newBookmark.setData(time, title)
this.bookmarks.push(newBookmark) this.bookmarks.push(newBookmark)
return newBookmark return newBookmark
} }
updateBookmark(time, title) {
var bookmark = this.bookmarks.find(bm => bm.time === time)
if (!bookmark) return false
bookmark.title = title
return bookmark
}
deleteBookmark(time) {
this.bookmarks = this.bookmarks.filter(bm => bm.time !== time)
}
} }
module.exports = AudiobookProgress module.exports = AudiobookProgress

View File

@ -281,11 +281,52 @@ class User {
createBookmark({ audiobookId, time, title }) { createBookmark({ audiobookId, time, title }) {
if (!this.audiobooks || !this.audiobooks[audiobookId]) { if (!this.audiobooks || !this.audiobooks[audiobookId]) {
return false return {
error: 'Invalid Audiobook'
}
} }
if (this.audiobooks[audiobookId].checkBookmarkExists(time)) {
return {
error: 'Bookmark already exists'
}
}
var success = this.audiobooks[audiobookId].createBookmark(time, title) var success = this.audiobooks[audiobookId].createBookmark(time, title)
if (success) return this.audiobooks[audiobookId] if (success) return this.audiobooks[audiobookId]
return null return null
} }
updateBookmark({ audiobookId, time, title }) {
if (!this.audiobooks || !this.audiobooks[audiobookId]) {
return {
error: 'Invalid Audiobook'
}
}
if (!this.audiobooks[audiobookId].checkBookmarkExists(time)) {
return {
error: 'Bookmark does not exist'
}
}
var success = this.audiobooks[audiobookId].updateBookmark(time, title)
if (success) return this.audiobooks[audiobookId]
return null
}
deleteBookmark({ audiobookId, time }) {
if (!this.audiobooks || !this.audiobooks[audiobookId]) {
return {
error: 'Invalid Audiobook'
}
}
if (!this.audiobooks[audiobookId].checkBookmarkExists(time)) {
return {
error: 'Bookmark does not exist'
}
}
this.audiobooks[audiobookId].deleteBookmark(time)
return this.audiobooks[audiobookId]
}
} }
module.exports = User module.exports = User

View File

@ -69,7 +69,7 @@ function secondsToTimestamp(seconds) {
_seconds -= _minutes * 60 _seconds -= _minutes * 60
var _hours = Math.floor(_minutes / 60) var _hours = Math.floor(_minutes / 60)
_minutes -= _hours * 60 _minutes -= _hours * 60
_seconds = Math.round(_seconds) _seconds = Math.floor(_seconds)
if (!_hours) { if (!_hours) {
return `${_minutes}:${_seconds.toString().padStart(2, '0')}` return `${_minutes}:${_seconds.toString().padStart(2, '0')}`
} }