Skip to content

Commit

Permalink
feat: add input fields to sliders (#674)
Browse files Browse the repository at this point in the history
Co-authored-by: pataar <pietering1@gmail.com>
  • Loading branch information
dw-0 and pataar authored Feb 28, 2022
1 parent fef6ba3 commit 91197cc
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 75 deletions.
210 changes: 174 additions & 36 deletions src/components/inputs/MiscellaneousSlider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,58 @@
._fan-slider-subheader {
height: auto;
}
._lock-button {
margin-left: -6px;
}
._rpm {
padding-top: 0.2rem !important;
margin: 0 !important;
}
._error-msg {
color: #ff5252;
font-size: 12px;
padding: 4px 16px 2px 0;
}
.fade-enter-active {
animation: slide-in 0.15s reverse;
opacity: 1;
}
.fade-leave-active {
animation: slide-in 0.15s;
opacity: 1;
}
@keyframes slide-in {
100% {
transform: translateY(-5px);
}
}
._slider-input {
font-size: 0.875rem;
max-width: 5.4rem;
margin-left: 12px;
}
._slider-input >>> .v-input__slot {
min-height: 1rem !important;
}
._slider-input >>> .v-text-field__slot input {
padding: 4px 0 4px;
}
</style>

<template>
<v-container class="px-0 py-2">
<v-row>
<v-col :class="pwm ? 'pb-1' : 'pb-3'">
<v-subheader class="_fan-slider-subheader">
<v-btn v-if="lockSliders && isTouchDevice && pwm" plain small icon @click="isLocked = !isLocked">
<v-icon small :color="isLocked ? 'red' : ''">
{{ isLocked ? 'mdi-lock-outline' : 'mdi-lock-open-variant-outline' }}
</v-icon>
</v-btn>
<v-icon
v-if="type !== 'output_pin'"
small
Expand All @@ -22,33 +62,68 @@
</v-icon>
<span>{{ convertName(name) }}</span>
<v-spacer></v-spacer>
<small v-if="rpm || rpm === 0" :class="'mr-3 ' + (rpm === 0 && value > 0 ? 'red--text' : '')">
<small
v-if="rpm || rpm === 0"
:class="`mr-3 ${controllable && pwm ? '_rpm' : ''}
${rpm === 0 && value > 0 ? 'red--text' : ''}`">
{{ Math.round(rpm) }} RPM
</small>
<span v-if="!controllable || (controllable && pwm)" class="font-weight-bold">
<span v-if="!controllable" class="font-weight-bold">
{{ Math.round(parseFloat(value) * 100) }} %
</span>
<v-icon v-if="controllable && !pwm" @click="switchOutputPin">
{{ value ? 'mdi-toggle-switch' : 'mdi-toggle-switch-off-outline' }}
</v-icon>
<v-text-field
v-if="controllable && pwm"
v-model="inputValue"
:error="errors().length > 0"
suffix="%"
type="number"
hide-spin-buttons
hide-details
outlined
dense
class="_slider-input pt-1"
@blur="inputValue = Math.round(parseFloat(sliderValue) * 100)"
@focus="$event.target.select()"
@keydown="checkInvalidChars"
@keyup.enter="submitInput"></v-text-field>
</v-subheader>
<v-card-text v-if="controllable && pwm" class="py-0">
<transition name="fade">
<!-- display errors -->
<div v-show="errors().length > 0 && controllable && pwm" class="_error-msg d-flex justify-end">
{{ errors()[0] }}
</div>
</transition>
<v-card-text v-if="controllable && pwm" class="py-0 pb-2 d-flex align-center">
<v-btn
v-if="lockSliders && isTouchDevice && pwm"
plain
small
icon
class="_lock-button"
@click="isLocked = !isLocked">
<v-icon small :color="isLocked ? 'red' : ''">
{{ isLocked ? 'mdi-lock-outline' : 'mdi-lock-open-variant-outline' }}
</v-icon>
</v-btn>
<v-slider
v-model="value"
v-model="sliderValue"
v-touch="{ start: resetLockTimer }"
:disabled="isLocked"
:min="0.0"
:max="1.0"
:step="0.01"
:color="value < off_below && value > 0 ? 'red' : undefined"
:color="sliderValue < off_below && sliderValue > 0 ? 'red' : undefined"
hide-details
@change="changeSlider">
@change="changeSliderValue">
<template #prepend>
<v-icon :disabled="isLocked" @click="decrement">mdi-minus</v-icon>
<v-icon :disabled="isLocked || sliderValue <= min" @click="decrement">mdi-minus</v-icon>
</template>

<template #append>
<v-icon :disabled="isLocked" @click="increment">mdi-plus</v-icon>
<v-icon :disabled="isLocked || sliderValue >= max" @click="increment">mdi-plus</v-icon>
</template>
</v-slider>
</v-card-text>
Expand All @@ -60,16 +135,20 @@
<script lang="ts">
import { convertName } from '@/plugins/helpers'
import { Component, Mixins, Prop, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { Debounce } from 'vue-debounce-decorator'
import BaseMixin from '@/components/mixins/base'
@Component
export default class MiscellaneousSlider extends Mixins(BaseMixin) {
convertName = convertName
private timeout: number | undefined
private isLocked = false
private declare timeout: ReturnType<typeof setTimeout>
private isLocked: boolean = false
private invalidChars: string[] = ['e', 'E', '+']
private min = 0
private value = 0
private inputValue = 0
private sliderValue = 0
@Prop({ type: Number, required: true }) declare target: number
@Prop({ type: Number, default: 1 }) declare max: number
Expand All @@ -82,44 +161,53 @@ export default class MiscellaneousSlider extends Mixins(BaseMixin) {
@Prop({ type: Number, default: 0 }) declare off_below: number
@Watch('lockSliders', { immediate: true })
lockSlidersChanged() {
lockSlidersChanged(): void {
this.isLocked = this.lockSliders && this.isTouchDevice
}
startLockTimer() {
startLockTimer(): void {
let t = this.lockSlidersDelay
if (!this.isTouchDevice || !this.lockSliders || t <= 0) return
this.timeout = setTimeout(() => (this.isLocked = true), t * 1000)
}
resetLockTimer() {
resetLockTimer(): void {
clearTimeout(this.timeout)
}
get lockSliders() {
get lockSliders(): boolean {
return this.$store.state.gui.uiSettings.lockSlidersOnTouchDevices
}
get lockSlidersDelay() {
get lockSlidersDelay(): number {
return this.$store.state.gui.uiSettings.lockSlidersDelay
}
@Debounce(500)
changeSlider() {
changeSliderValue(): void {
if (this.value === this.sliderValue) return
/**
* snap slider handle to 0 if dragging from above 'off_below' to below 'off_below'
* snap slider handle to 'off_below' if dragging from 0 to below 'off_below'
*/
if (this.sliderValue < this.value && this.sliderValue < this.off_below) {
this.sliderValue = 0
} else if (this.sliderValue > this.value && this.sliderValue < this.off_below) {
this.sliderValue = this.off_below
}
this.value = this.sliderValue
this.sendCmd()
}
sendCmd() {
let gcode = ''
if (this.value < this.min) this.value = 0
sendCmd(): void {
if (this.target === this.value) return
let gcode = ''
if (this.value < this.min) this.value = 0
const l_value = this.value * this.multi
if (this.type === 'fan') gcode = 'M106 S' + l_value.toFixed(0)
if (this.type === 'fan_generic') gcode = 'SET_FAN_SPEED FAN=' + this.name + ' SPEED=' + l_value
if (this.type === 'output_pin') gcode = 'SET_PIN PIN=' + this.name + ' VALUE=' + l_value.toFixed(2)
if (this.type === 'fan') gcode = `M106 S${l_value.toFixed(0)}`
if (this.type === 'fan_generic') gcode = `SET_FAN_SPEED FAN=${this.name} SPEED=${l_value}`
if (this.type === 'output_pin') gcode = `SET_PIN PIN=${this.name} VALUE=${l_value.toFixed(2)}`
if (gcode !== '') {
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
Expand All @@ -129,31 +217,81 @@ export default class MiscellaneousSlider extends Mixins(BaseMixin) {
this.startLockTimer()
}
switchOutputPin() {
switchOutputPin(): void {
this.value = this.value ? 0 : 1
const gcode = 'SET_PIN PIN=' + this.name + ' VALUE=' + (this.value * this.multi).toFixed(2)
const gcode = `SET_PIN PIN=${this.name} VALUE=${(this.value * this.multi).toFixed(2)}`
this.$store.dispatch('server/addEvent', { message: gcode, type: 'command' })
this.$socket.emit('printer.gcode.script', { script: gcode })
}
decrement() {
decrement(): void {
this.value = this.value > 0 ? Math.round((this.value - 0.01) * 100) / 100 : 0
if (this.value < this.off_below) this.value = 0
this.sendCmd()
}
increment() {
increment(): void {
this.value = this.value < 1.0 ? Math.round((this.value + 0.01) * 100) / 100 : 1.0
if (this.value < this.off_below) this.value = this.off_below
this.sendCmd()
}
mounted() {
mounted(): void {
this.value = this.target
}
@Watch('target')
targetChanged(newVal: number) {
targetChanged(newVal: number): void {
this.value = newVal / this.max
}
@Watch('value')
valueChanged(newVal: number): void {
this.sliderValue = newVal
}
@Watch('sliderValue', { immediate: true })
sliderValueChanged(newVal: number): void {
this.inputValue = Math.round(newVal * 100)
}
// input validation //
checkInvalidChars(event: any): void {
// add '-' to invalid characters if no negative input is allowed
if (this.min >= 0) this.invalidChars.push('-')
if (this.invalidChars.includes(event.key)) event.preventDefault()
}
errors() {
const errors = []
const input = this.inputValue / 100
if (this.inputValue.toString() === '') {
// "Input must not be empty!"
errors.push(this.$t('App.NumberInput.NoEmptyAllowedError'))
}
if (input < this.min) {
// "Must be grater or equal than {min}!"
errors.push(this.$t('App.NumberInput.GreaterOrEqualError', { min: this.min * 100 }))
}
if (input > this.max || input < this.min) {
// "Must be between {min} and {max}!"
errors.push(this.$t('App.NumberInput.MustBeBetweenError', { min: this.min * 100, max: this.max * 100 }))
}
return errors
}
submitInput(): void {
if (this.errors().length > 0) return
const input = this.inputValue / 100
if (input > this.max) {
this.value = this.max
} else if (input < this.off_below) {
this.value = 0
this.inputValue = 0
} else this.value = input
this.sendCmd()
}
}
</script>
Loading

0 comments on commit 91197cc

Please sign in to comment.