mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-19 00:18:56 +01:00
Update:Chapter editor to use timestamp input for chapter start time with toggle to show seconds #657
This commit is contained in:
parent
9d7ffdfcd0
commit
c453d3e8c7
185
client/components/ui/TimePicker.vue
Normal file
185
client/components/ui/TimePicker.vue
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 w-full px-3 py-2" @click="clickInput" v-click-outside="clickOutsideObj">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<template v-for="(digit, index) in digitDisplay">
|
||||||
|
<div v-if="digit == ':'" :key="index" class="px-px" @click.stop="clickMedian(index)">:</div>
|
||||||
|
<div v-else :key="index" class="px-px" :class="{ 'digit-focused': focusedDigit == digit }" @click.stop="focusDigit(digit)">{{ digits[digit] }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: [String, Number]
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
clickOutsideObj: {
|
||||||
|
handler: this.clickOutside,
|
||||||
|
events: ['mousedown'],
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
digitDisplay: ['hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0'],
|
||||||
|
focusedDigit: null,
|
||||||
|
digits: {
|
||||||
|
hour1: 0,
|
||||||
|
hour0: 0,
|
||||||
|
minute1: 0,
|
||||||
|
minute0: 0,
|
||||||
|
second1: 0,
|
||||||
|
second0: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler() {
|
||||||
|
this.initDigits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
initDigits() {
|
||||||
|
var totalSeconds = !this.value || isNaN(this.value) ? 0 : Number(this.value)
|
||||||
|
totalSeconds = Math.round(totalSeconds)
|
||||||
|
|
||||||
|
var minutes = Math.floor(totalSeconds / 60)
|
||||||
|
var seconds = totalSeconds - minutes * 60
|
||||||
|
var hours = Math.floor(minutes / 60)
|
||||||
|
minutes -= hours * 60
|
||||||
|
|
||||||
|
this.digits.second1 = seconds <= 9 ? 0 : Number(String(seconds)[0])
|
||||||
|
this.digits.second0 = seconds <= 9 ? seconds : Number(String(seconds)[1])
|
||||||
|
|
||||||
|
this.digits.minute1 = minutes <= 9 ? 0 : Number(String(minutes)[0])
|
||||||
|
this.digits.minute0 = minutes <= 9 ? minutes : Number(String(minutes)[1])
|
||||||
|
|
||||||
|
this.digits.hour1 = hours <= 9 ? 0 : Number(String(hours)[0])
|
||||||
|
this.digits.hour0 = hours <= 9 ? hours : Number(String(hours)[1])
|
||||||
|
},
|
||||||
|
updateSeconds() {
|
||||||
|
var seconds = this.digits.second0 + this.digits.second1 * 10
|
||||||
|
seconds += this.digits.minute0 * 60 + this.digits.minute1 * 600
|
||||||
|
seconds += this.digits.hour0 * 3600 + this.digits.hour1 * 36000
|
||||||
|
|
||||||
|
if (Number(this.value) !== seconds) {
|
||||||
|
this.$emit('input', seconds)
|
||||||
|
this.$emit('change', seconds)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickMedian(index) {
|
||||||
|
// Click colon select digit to right
|
||||||
|
if (index >= 5) {
|
||||||
|
this.focusedDigit = 'second1'
|
||||||
|
} else {
|
||||||
|
this.focusedDigit = 'minute1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickOutside() {
|
||||||
|
this.removeFocus()
|
||||||
|
},
|
||||||
|
removeFocus() {
|
||||||
|
this.focusedDigit = null
|
||||||
|
this.removeListeners()
|
||||||
|
},
|
||||||
|
focusDigit(digit) {
|
||||||
|
if (this.focusedDigit == null || isNaN(this.focusedDigit)) this.initListeners()
|
||||||
|
this.focusedDigit = digit
|
||||||
|
},
|
||||||
|
clickInput() {
|
||||||
|
if (this.focusedDigit) return
|
||||||
|
this.focusDigit('second0')
|
||||||
|
},
|
||||||
|
shiftFocusLeft() {
|
||||||
|
if (!this.focusedDigit) return
|
||||||
|
const isDigit1 = this.focusedDigit.endsWith('1')
|
||||||
|
if (!isDigit1) {
|
||||||
|
const digit1Key = this.focusedDigit.replace('0', '1')
|
||||||
|
this.focusedDigit = digit1Key
|
||||||
|
} else if (this.focusedDigit.startsWith('second')) {
|
||||||
|
this.focusedDigit = 'minute0'
|
||||||
|
} else if (this.focusedDigit.startsWith('minute')) {
|
||||||
|
this.focusedDigit = 'hour0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shiftFocusRight() {
|
||||||
|
if (!this.focusedDigit) return
|
||||||
|
const isDigit1 = this.focusedDigit.endsWith('1')
|
||||||
|
if (isDigit1) {
|
||||||
|
const digit0Key = this.focusedDigit.replace('1', '0')
|
||||||
|
this.focusedDigit = digit0Key
|
||||||
|
} else if (this.focusedDigit.startsWith('hour')) {
|
||||||
|
this.focusedDigit = 'minute1'
|
||||||
|
} else if (this.focusedDigit.startsWith('minute')) {
|
||||||
|
this.focusedDigit = 'second1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
increaseFocused() {
|
||||||
|
if (!this.focusedDigit) return
|
||||||
|
const isDigit1 = this.focusedDigit.endsWith('1')
|
||||||
|
const digit = Number(this.digits[this.focusedDigit])
|
||||||
|
if (isDigit1 && !this.focusedDigit.startsWith('hour')) this.digits[this.focusedDigit] = (digit + 1) % 6
|
||||||
|
else this.digits[this.focusedDigit] = (digit + 1) % 10
|
||||||
|
this.updateSeconds()
|
||||||
|
},
|
||||||
|
decreaseFocused() {
|
||||||
|
if (!this.focusedDigit) return
|
||||||
|
const isDigit1 = this.focusedDigit.endsWith('1')
|
||||||
|
const digit = Number(this.digits[this.focusedDigit])
|
||||||
|
if (isDigit1 && !this.focusedDigit.startsWith('hour')) this.digits[this.focusedDigit] = digit - 1 < 0 ? 5 : digit - 1
|
||||||
|
else this.digits[this.focusedDigit] = digit - 1 < 0 ? 9 : digit - 1
|
||||||
|
this.updateSeconds()
|
||||||
|
},
|
||||||
|
keydown(evt) {
|
||||||
|
if (!this.focusedDigit || !evt.key) return
|
||||||
|
|
||||||
|
if (evt.key === 'ArrowLeft') {
|
||||||
|
return this.shiftFocusLeft()
|
||||||
|
} else if (evt.key === 'ArrowRight') {
|
||||||
|
return this.shiftFocusRight()
|
||||||
|
} else if (evt.key === 'ArrowUp') {
|
||||||
|
return this.increaseFocused()
|
||||||
|
} else if (evt.key === 'ArrowDown') {
|
||||||
|
return this.decreaseFocused()
|
||||||
|
} else if (evt.key === 'Enter' || evt.key === 'Escape') {
|
||||||
|
return this.removeFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(evt.key)) return
|
||||||
|
|
||||||
|
var digit = Number(evt.key)
|
||||||
|
const isDigit1 = this.focusedDigit.endsWith('1')
|
||||||
|
if (isDigit1 && !this.focusedDigit.startsWith('hour') && digit >= 6) {
|
||||||
|
digit = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
this.digits[this.focusedDigit] = digit
|
||||||
|
|
||||||
|
this.updateSeconds()
|
||||||
|
this.shiftFocusRight()
|
||||||
|
},
|
||||||
|
initListeners() {
|
||||||
|
window.addEventListener('keydown', this.keydown)
|
||||||
|
},
|
||||||
|
removeListeners() {
|
||||||
|
window.removeEventListener('keydown', this.keydown)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.removeListeners()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.digit-focused {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
@ -17,6 +17,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-lg mb-4 font-semibold">Audiobook Chapters</p>
|
<p class="text-lg mb-4 font-semibold">Audiobook Chapters</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" label="Show seconds" class="mx-2" />
|
||||||
<ui-btn color="primary" small class="mx-2" @click="showFindChaptersModal = true">Lookup</ui-btn>
|
<ui-btn color="primary" small class="mx-2" @click="showFindChaptersModal = true">Lookup</ui-btn>
|
||||||
<ui-btn color="success" small @click="saveChapters">Save</ui-btn>
|
<ui-btn color="success" small @click="saveChapters">Save</ui-btn>
|
||||||
<div class="w-40" />
|
<div class="w-40" />
|
||||||
@ -32,7 +33,8 @@
|
|||||||
<div :key="chapter.id" class="flex py-1">
|
<div :key="chapter.id" class="flex py-1">
|
||||||
<div class="w-12">#{{ chapter.id + 1 }}</div>
|
<div class="w-12">#{{ chapter.id + 1 }}</div>
|
||||||
<div class="w-32 px-1">
|
<div class="w-32 px-1">
|
||||||
<ui-text-input v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
|
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
|
||||||
|
<ui-time-picker v-else class="text-xs" v-model="chapter.start" @change="checkChapters" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<ui-text-input v-model="chapter.title" class="text-xs" />
|
<ui-text-input v-model="chapter.title" class="text-xs" />
|
||||||
@ -168,7 +170,8 @@ export default {
|
|||||||
asinInput: null,
|
asinInput: null,
|
||||||
findingChapters: false,
|
findingChapters: false,
|
||||||
showFindChaptersModal: false,
|
showFindChaptersModal: false,
|
||||||
chapterData: null
|
chapterData: null,
|
||||||
|
showSecondInputs: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
Loading…
Reference in New Issue
Block a user