Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add input fields to sliders #674

Merged
merged 27 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0dc1473
fix: make vuetify touch directive work again
dw-0 Feb 9, 2022
cfe9445
feat: add input fields to sliders
dw-0 Feb 13, 2022
b6e0939
style: add prettier as default formatter (#614)
pataar Feb 13, 2022
3455069
fix(env): parse environment variable as string (#632)
pataar Feb 13, 2022
40e609b
fix: console error regarding touch directive (#633)
dw-0 Feb 13, 2022
64efed4
feat: add input fields to sliders
dw-0 Feb 13, 2022
115315d
Merge remote-tracking branch 'origin/feat/add-slider-inputs' into fea…
dw-0 Feb 14, 2022
522d6ae
fix: return type error
dw-0 Feb 14, 2022
649a8fe
chore: run format
dw-0 Feb 14, 2022
bb55d14
feat: add input fields to sliders
dw-0 Feb 13, 2022
ce16f47
fix: return type error
dw-0 Feb 14, 2022
2acafd1
chore: run format
dw-0 Feb 14, 2022
4dcfa0b
Merge remote-tracking branch 'origin/feat/add-slider-inputs' into fea…
dw-0 Feb 19, 2022
e7f5ee1
fix: formatting
dw-0 Feb 19, 2022
9e64b2a
fix: use correct timeout return type
dw-0 Feb 19, 2022
dbd17eb
feat: add input validation to slider input fields
dw-0 Feb 19, 2022
97bf624
refactor: full visual rework
dw-0 Feb 20, 2022
e361cf0
merge: develop
dw-0 Feb 20, 2022
64692a0
fix: padding of error messages
dw-0 Feb 21, 2022
e81e83c
fix: alignment of RPM
dw-0 Feb 21, 2022
7aeb0e9
fix: rounding issue
dw-0 Feb 22, 2022
0dd6222
Merge branch 'develop' into feat/add-slider-inputs
dw-0 Feb 22, 2022
44a6c44
chore: rework slider behavior for `off_below`
dw-0 Feb 23, 2022
75cc3f8
chore: reformat with respect to style guide
dw-0 Feb 23, 2022
d077526
chore: simplify input validation
dw-0 Feb 23, 2022
7bee37a
fix: disable slider increment/decrement buttons accordingly
dw-0 Feb 23, 2022
056912f
Merge branch 'develop' into feat/add-slider-inputs
dw-0 Feb 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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