diff --git a/client/components/ui/MultiSelect.vue b/client/components/ui/MultiSelect.vue index 064aa2a3..71bd9539 100644 --- a/client/components/ui/MultiSelect.vue +++ b/client/components/ui/MultiSelect.vue @@ -57,6 +57,12 @@ export default { menu: null } }, + watch: { + showMenu(newVal) { + if (newVal) this.setListener() + else this.removeListener() + } + }, computed: { selected: { get() { @@ -99,7 +105,18 @@ export default { recalcMenuPos() { if (!this.menu) return var boundingBox = this.$refs.inputWrapper.getBoundingClientRect() - this.menu.style.top = boundingBox.y + boundingBox.height - 4 + 'px' + if (boundingBox.y > window.innerHeight - 8) { + // Input is off the page + return this.forceBlur() + } + var menuHeight = this.menu.clientHeight + var top = boundingBox.y + boundingBox.height - 4 + if (top + menuHeight > window.innerHeight - 20) { + // Reverse menu to open upwards + top = boundingBox.y - menuHeight - 4 + } + + this.menu.style.top = top + 'px' this.menu.style.left = boundingBox.x + 'px' this.menu.style.width = boundingBox.width + 'px' }, @@ -119,7 +136,7 @@ export default { this.unmountMountMenu() } this.isFocused = true - this.recalcMenuPos() + this.$nextTick(this.recalcMenuPos) }, inputBlur() { if (!this.isFocused) return @@ -200,6 +217,15 @@ export default { } else { this.insertNewItem(this.textInput) } + }, + scroll() { + this.recalcMenuPos() + }, + setListener() { + document.addEventListener('scroll', this.scroll, true) + }, + removeListener() { + document.removeEventListener('scroll', this.scroll, true) } }, mounted() {} diff --git a/client/components/ui/MultiSelectDropdown.vue b/client/components/ui/MultiSelectDropdown.vue index f2bcf009..526fa6c2 100644 --- a/client/components/ui/MultiSelectDropdown.vue +++ b/client/components/ui/MultiSelectDropdown.vue @@ -103,9 +103,12 @@ export default { }, closeMenu() { this.showMenu = false + this.removeListener() }, clickWrapper() { this.showMenu = !this.showMenu + if (this.showMenu) this.setListener() + else this.removeListener() }, removeItem(itemValue) { var remaining = this.selected.filter((i) => i !== itemValue) @@ -113,6 +116,15 @@ export default { this.$nextTick(() => { this.recalcMenuPos() }) + }, + scroll() { + this.recalcMenuPos() + }, + setListener() { + document.addEventListener('scroll', this.scroll, true) + }, + removeListener() { + document.removeEventListener('scroll', this.scroll, true) } }, mounted() {} diff --git a/client/pages/batch/index.vue b/client/pages/batch/index.vue index 6a410cd6..4191d629 100644 --- a/client/pages/batch/index.vue +++ b/client/pages/batch/index.vue @@ -55,7 +55,7 @@
- Save + Save
@@ -178,11 +178,55 @@ export default { } }) }, + compareStringArrays(arr1, arr2) { + if (!arr1 || !arr2) return false + return arr1.join(',') !== arr2.join(',') + }, + compareAudiobooks(newAb, origAb) { + const bookKeysToCheck = ['title', 'subtitle', 'narrator', 'author', 'publishYear', 'series', 'volumeNumber', 'description'] + var newBook = newAb.book + var origBook = origAb.book + var diffObj = {} + for (const key in newBook) { + if (bookKeysToCheck.includes(key)) { + if (newBook[key] !== origBook[key]) { + if (!diffObj.book) diffObj.book = {} + diffObj.book[key] = newBook[key] + } + } + if (key === 'genres') { + if (this.compareStringArrays(newBook[key], origBook[key])) { + diffObj[key] = newBook[key] + } + } + } + if (newAb.tags && origAb.tags && newAb.tags.join(',') !== origAb.tags.join(',')) { + diffObj.tags = newAb.tags + } + return diffObj + }, saveClick() { - this.isProcessing = true + var updates = [] + for (let i = 0; i < this.audiobookCopies.length; i++) { + var ab = { ...this.audiobookCopies[i] } + var origAb = ab.originalAudiobook + delete ab.originalAudiobook + var res = this.compareAudiobooks(ab, origAb) + if (res && Object.keys(res).length) { + updates.push({ + id: ab.id, + updates: res + }) + } + } + if (!updates.length) { + return this.$toast.warning('No updates were made') + } + console.log('Pushing updates', updates) + this.isProcessing = true this.$axios - .$post('/api/books/batch/update', this.audiobookCopies) + .$post('/api/books/batch/update', updates) .then((data) => { this.isProcessing = false if (data.updates) { diff --git a/client/plugins/init.client.js b/client/plugins/init.client.js index 2983b58c..61d04852 100644 --- a/client/plugins/init.client.js +++ b/client/plugins/init.client.js @@ -101,42 +101,6 @@ Vue.prototype.$calculateTextSize = (text, styles = {}) => { } } -// function isClickedOutsideEl(clickEvent, elToCheckOutside, ignoreSelectors = [], ignoreElems = []) { -// const isDOMElement = (element) => { -// return element instanceof Element || element instanceof HTMLDocument -// } - -// const clickedEl = clickEvent.srcElement -// const didClickOnIgnoredEl = ignoreElems.filter((el) => el).some((element) => element.contains(clickedEl) || element.isEqualNode(clickedEl)) -// const didClickOnIgnoredSelector = ignoreSelectors.length ? ignoreSelectors.map((selector) => clickedEl.closest(selector)).reduce((curr, accumulator) => curr && accumulator, true) : false - -// if (isDOMElement(elToCheckOutside) && !elToCheckOutside.contains(clickedEl) && !didClickOnIgnoredEl && !didClickOnIgnoredSelector) { -// return true -// } -// return false -// } - -// Vue.directive('click-outside', { -// bind: function (el, binding, vnode) { -// let vm = vnode.context; -// let callback = binding.value; -// if (typeof callback !== 'function') { -// console.error('Invalid callback', binding) -// return -// } -// el['__click_outside__'] = (ev) => { -// if (isClickedOutsideEl(ev, el)) { -// callback.call(vm, ev) -// } -// } -// document.addEventListener('click', el['__click_outside__'], false) -// }, -// unbind: function (el, binding, vnode) { -// document.removeEventListener('click', el['__click_outside__'], false) -// delete el['__click_outside__'] -// } -// }) - Vue.prototype.$sanitizeFilename = (input, replacement = '') => { if (typeof input !== 'string') { return false diff --git a/server/controllers/BookController.js b/server/controllers/BookController.js index 1ba8f4d3..01e264c9 100644 --- a/server/controllers/BookController.js +++ b/server/controllers/BookController.js @@ -99,19 +99,20 @@ class BookController { Logger.warn('User attempted to batch update without permission', req.user) return res.sendStatus(403) } - var audiobooks = req.body - if (!audiobooks || !audiobooks.length) { + var updatePayloads = req.body + if (!updatePayloads || !updatePayloads.length) { return res.sendStatus(500) } var audiobooksUpdated = 0 - audiobooks = audiobooks.map((ab) => { - var _ab = this.db.audiobooks.find(__ab => __ab.id === ab.id) - if (!_ab) return null - var hasUpdated = _ab.update(ab) + var audiobooks = updatePayloads.map((up) => { + var audiobookUpdates = up.updates + var ab = this.db.audiobooks.find(_ab => _ab.id === up.id) + if (!ab) return null + var hasUpdated = ab.update(audiobookUpdates) if (!hasUpdated) return null audiobooksUpdated++ - return _ab + return ab }).filter(ab => ab) if (audiobooksUpdated) {