audiobookshelf/client/components/ui/TimePicker.vue

213 lines
6.8 KiB
Vue

<template>
<div tabindex="0" @focus="focusDigit('second0')" class="relative">
<div class="rounded text-gray-200 border w-full px-3 py-2" :class="focusedDigit ? 'bg-primary bg-opacity-50 border-gray-300' : 'bg-primary border-gray-600'" @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],
showThreeDigitHour: Boolean
},
data() {
return {
clickOutsideObj: {
handler: this.clickOutside,
events: ['mousedown'],
isActive: true
},
digitDisplay: ['hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0'],
focusedDigit: null,
digits: {
hour2: 0,
hour1: 0,
hour0: 0,
minute1: 0,
minute0: 0,
second1: 0,
second0: 0
},
isOver99Hours: false
}
},
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])
if (hours > 99) {
this.digits.hour2 = Number(String(hours)[0])
this.digits.hour1 = Number(String(hours)[1])
this.digits.hour0 = Number(String(hours)[2])
this.isOver99Hours = true
} else {
this.digits.hour1 = hours <= 9 ? 0 : Number(String(hours)[0])
this.digits.hour0 = hours <= 9 ? hours : Number(String(hours)[1])
this.isOver99Hours = this.showThreeDigitHour
}
if (this.isOver99Hours) {
this.digitDisplay = ['hour2', 'hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0']
} else {
this.digitDisplay = ['hour1', 'hour0', ':', 'minute1', 'minute0', ':', 'second1', 'second0']
}
},
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 (this.isOver99Hours) seconds += this.digits.hour2 * 360000
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
if (this.focusedDigit.endsWith('2')) 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'
} else if (this.isOver99Hours && this.focusedDigit.startsWith('hour')) {
this.focusedDigit = 'hour2'
}
},
shiftFocusRight() {
if (!this.focusedDigit) return
if (this.focusedDigit.endsWith('2')) {
// Must be hour2
this.focusedDigit = 'hour1'
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' || evt.key === 'Tab') {
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: #555;
}
</style>