UPD: Working key LED config!

This commit is contained in:
Robert Kossessa
2024-03-13 00:52:42 +01:00
parent 97088ded94
commit 7aa8b8a40b
8 changed files with 158 additions and 82 deletions

View File

@@ -26,6 +26,7 @@
"@intlify/unplugin-vue-i18n": "^2.0.0", "@intlify/unplugin-vue-i18n": "^2.0.0",
"@radix-icons/vue": "^1.0.0", "@radix-icons/vue": "^1.0.0",
"@serialport/bindings-cpp": "^12.0.1", "@serialport/bindings-cpp": "^12.0.1",
"@types/color": "^3.0.6",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",

19
pnpm-lock.yaml generated
View File

@@ -20,6 +20,9 @@ dependencies:
'@serialport/bindings-cpp': '@serialport/bindings-cpp':
specifier: ^12.0.1 specifier: ^12.0.1
version: 12.0.1 version: 12.0.1
'@types/color':
specifier: ^3.0.6
version: 3.0.6
'@vueuse/core': '@vueuse/core':
specifier: ^10.9.0 specifier: ^10.9.0
version: 10.9.0(vue@3.4.21) version: 10.9.0(vue@3.4.21)
@@ -1204,6 +1207,22 @@ packages:
'@types/node': 18.19.21 '@types/node': 18.19.21
'@types/responselike': 1.0.3 '@types/responselike': 1.0.3
/@types/color-convert@2.0.3:
resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==}
dependencies:
'@types/color-name': 1.1.3
dev: false
/@types/color-name@1.1.3:
resolution: {integrity: sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw==}
dev: false
/@types/color@3.0.6:
resolution: {integrity: sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==}
dependencies:
'@types/color-convert': 2.0.3
dev: false
/@types/debug@4.1.12: /@types/debug@4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
dependencies: dependencies:

View File

@@ -187,13 +187,19 @@ import { SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
import { MoreHorizontal } from 'lucide-vue-next' import { MoreHorizontal } from 'lucide-vue-next'
import { Separator } from '@renderer/components/ui/separator' import { Separator } from '@renderer/components/ui/separator'
defineProps({ const props = defineProps({
color: {
type: Color,
default: () => Color.rgb(255, 0, 0)
},
roundedTop: { roundedTop: {
type: Boolean, type: Boolean,
default: false default: false
} }
}) })
const emit = defineEmits(['input'])
const hueSliderValue = ref(0) const hueSliderValue = ref(0)
const saturationSliderValue = ref(100) const saturationSliderValue = ref(100)
const valueSliderValue = ref(50) const valueSliderValue = ref(50)
@@ -204,7 +210,7 @@ const hueSliderModel = computed({
}, },
set(hue) { set(hue) {
hueSliderValue.value = hue[0] hueSliderValue.value = hue[0]
color.value = color.value.hue(hue[0]) emit('input', props.color.hue(hue[0]))
} }
}) })
@@ -214,7 +220,7 @@ const saturationSliderModel = computed({
}, },
set(saturation) { set(saturation) {
saturationSliderValue.value = saturation[0] saturationSliderValue.value = saturation[0]
color.value = color.value.saturationv(saturation[0]) emit('input', props.color.saturationv(saturation[0]))
} }
}) })
@@ -224,15 +230,10 @@ const valueSliderModel = computed({
}, },
set(value) { set(value) {
valueSliderValue.value = value[0] valueSliderValue.value = value[0]
color.value = color.value.value(value[0]) emit('input', props.color.value(value[0]))
} }
}) })
const color = defineModel({
type: Color,
default: () => Color.rgb(255, 0, 0)
})
const saturationSliderColor = computed(() => { const saturationSliderColor = computed(() => {
return Color.hsv(hueSliderModel.value[0], 100, valueSliderModel.value[0]) return Color.hsv(hueSliderModel.value[0], 100, valueSliderModel.value[0])
}) })
@@ -255,7 +256,7 @@ function onSubmitHexInput() {
input = '#' + input input = '#' + input
} }
if (input.match(/^#[0-9A-F]{6}$/i)) { if (input.match(/^#[0-9A-F]{6}$/i)) {
color.value = Color(input) emit('input', Color(input))
} else shake() } else shake()
} }
@@ -266,10 +267,10 @@ function onSubmitHueInput() {
return return
} }
const newHue = Math.max(0, Math.min(input, 360)) const newHue = Math.max(0, Math.min(input, 360))
if (newHue === color.value.hue()) { if (newHue === props.color.hue()) {
updateInputs() updateInputs()
} }
color.value = color.value.hue(newHue) emit('input', props.color.hue(newHue))
} }
function onSubmitSaturationInput() { function onSubmitSaturationInput() {
@@ -279,10 +280,10 @@ function onSubmitSaturationInput() {
return return
} }
const newSaturation = Math.max(0, Math.min(input, 100)) const newSaturation = Math.max(0, Math.min(input, 100))
if (newSaturation === color.value.saturationv()) { if (newSaturation === props.color.saturationv()) {
updateInputs() updateInputs()
} }
color.value = color.value.saturationv(newSaturation) emit('input', props.color.saturationv(newSaturation))
} }
function onSubmitValueInput() { function onSubmitValueInput() {
@@ -292,10 +293,10 @@ function onSubmitValueInput() {
return return
} }
const newValue = Math.max(0, Math.min(input, 100)) const newValue = Math.max(0, Math.min(input, 100))
if (newValue === color.value.value()) { if (newValue === props.color.value()) {
updateInputs() updateInputs()
} }
color.value = color.value.value(newValue) emit('input', props.color.value(newValue))
} }
function onSubmitRGBInput() { function onSubmitRGBInput() {
@@ -307,26 +308,27 @@ function onSubmitRGBInput() {
return return
} }
const newColor = Color.rgb(r, g, b) const newColor = Color.rgb(r, g, b)
if (newColor.hex() === color.value.hex()) { if (newColor.hex() === props.color.hex()) {
updateInputs() updateInputs()
} }
color.value = newColor emit('input', newColor)
} }
function updateInputs() { function updateInputs() {
hexInput.value = color.value.hex().substring(1, 7) console.log('COLORRR', props.color)
hueInput.value = String(parseInt(color.value.hue())).padStart(3, '0') hexInput.value = props.color.hex().substring(1, 7)
saturationInput.value = String(parseInt(color.value.saturationv())).padStart(3, '0') hueInput.value = String(parseInt(props.color.hue())).padStart(3, '0')
valueInput.value = String(parseInt(color.value.value())).padStart(3, '0') saturationInput.value = String(parseInt(props.color.saturationv())).padStart(3, '0')
rInput.value = String(parseInt(color.value.red())).padStart(3, '0') valueInput.value = String(parseInt(props.color.value())).padStart(3, '0')
gInput.value = String(parseInt(color.value.green())).padStart(3, '0') rInput.value = String(parseInt(props.color.red())).padStart(3, '0')
bInput.value = String(parseInt(color.value.blue())).padStart(3, '0') gInput.value = String(parseInt(props.color.green())).padStart(3, '0')
hueSliderValue.value = color.value.hue() bInput.value = String(parseInt(props.color.blue())).padStart(3, '0')
saturationSliderValue.value = color.value.saturationv() hueSliderValue.value = props.color.hue()
valueSliderValue.value = color.value.value() saturationSliderValue.value = props.color.saturationv()
valueSliderValue.value = props.color.value()
} }
watch(color, updateInputs) watch(props.color, updateInputs)
onBeforeMount(updateInputs) onBeforeMount(updateInputs)
const colorFieldText = ref(null) const colorFieldText = ref(null)

View File

@@ -32,40 +32,45 @@
@click="currentOption = key" @click="currentOption = key"
/> />
</div> </div>
<HSVInput v-model="options[currentOption].color" /> <HSVInput
:color="options[currentOption].color"
@input="(color) => $emit('input', currentOption, color)"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import HSVInput from '@renderer/components/common/HSVInput.vue' import HSVInput from '@renderer/components/common/HSVInput.vue'
import Color from 'color' import Color from 'color'
import { computed, onBeforeMount, reactive, ref } from 'vue' import { computed, onBeforeMount, ref } from 'vue'
defineEmits(['input'])
const currentOption = ref(null) const currentOption = ref(null)
const currentColorHex = computed(() => options[currentOption.value].color.hex()) const currentColorHex = computed(() => props.options[currentOption.value].color.hex())
const model = defineModel({ const props = defineProps({
type: Object, options: {
default: () => ({ type: Object,
one: { default: () => ({
titleKey: 'One', one: {
color: Color('#ff0000') titleKey: 'One',
}, color: Color('#ff0000')
two: { },
titleKey: 'Two', two: {
color: Color('#00ff00') titleKey: 'Two',
}, color: Color('#00ff00')
three: { },
titleKey: 'Three', three: {
color: Color('#0000ff') titleKey: 'Three',
} color: Color('#0000ff')
}) }
})
}
}) })
const options = reactive(model.value)
onBeforeMount(() => { onBeforeMount(() => {
if (currentOption.value === null) currentOption.value = Object.keys(options)[0] if (currentOption.value === null) currentOption.value = Object.keys(props.options)[0]
}) })
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,6 +1,19 @@
<template> <template>
<ConfigSection title="Key Colors" :icon-component="Palette"> <ConfigSection title="Key Colors" :icon-component="Palette">
<PaletteInput v-model="keyColors" /> <PaletteInput
:options="keyColors"
@input="
(optionKey, color) => {
keyColors = {
...keyColors,
[optionKey]: {
...keyColors[optionKey],
color
}
}
}
"
/>
</ConfigSection> </ConfigSection>
</template> </template>
<script setup> <script setup>
@@ -8,28 +21,31 @@ import { Palette } from 'lucide-vue-next'
import ConfigSection from '@renderer/components/common/ConfigSection.vue' import ConfigSection from '@renderer/components/common/ConfigSection.vue'
import PaletteInput from '@renderer/components/common/PaletteInput.vue' import PaletteInput from '@renderer/components/common/PaletteInput.vue'
import Color from 'color' import Color from 'color'
import { ref, watch } from 'vue'
import { useDeviceStore } from '@renderer/deviceStore' import { useDeviceStore } from '@renderer/deviceStore'
import { useAppStore } from '@renderer/appStore'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
const appStore = useAppStore()
const deviceStore = useDeviceStore() const deviceStore = useDeviceStore()
const { keyColor } = storeToRefs(deviceStore)
const keyColors = ref({ const keyColors = computed({
default: { get() {
titleKey: 'default', return {
color: Color('#4f25ef') default: {
titleKey: 'default',
color: Color(keyColor.value(appStore.selectedKey, false))
},
pressed: {
titleKey: 'pressed',
color: Color(keyColor.value(appStore.selectedKey, true))
}
}
}, },
pressed: { set(newValue) {
titleKey: 'pressed', deviceStore.setKeyColor(appStore.selectedKey, false, Color(newValue.default.color).rgbNumber())
color: Color('#d0078f') deviceStore.setKeyColor(appStore.selectedKey, true, Color(newValue.pressed.color).rgbNumber())
} }
}) })
watch(
keyColors,
(newVal) => {
// store.setKeyDefaultColor(newVal.default.color)
// store.setKeyPressedColor(newVal.pressed.color)
},
{ deep: true }
)
</script> </script>

View File

@@ -13,7 +13,7 @@
:class="{ 'bg-zinc-300': selected }" :class="{ 'bg-zinc-300': selected }"
@submit.prevent=" @submit.prevent="
() => { () => {
$emit('rename', { profile: profile.name, name: nameInput }) $emit('rename', profile.name, nameInput)
editing = false editing = false
} }
" "
@@ -90,7 +90,7 @@
'rounded-l-lg': !nameEditable 'rounded-l-lg': !nameEditable
}" }"
class="flex w-0 shrink-0 items-center justify-center rounded-lg transition-all" class="flex w-0 shrink-0 items-center justify-center rounded-lg transition-all"
@click="$emit('duplicate')" @click="$emit('duplicate', profile.name, profile)"
> >
<Copy class="size-4" /> <Copy class="size-4" />
</button> </button>
@@ -114,7 +114,7 @@
'group-focus-within:w-12 group-hover:w-12': !editing 'group-focus-within:w-12 group-hover:w-12': !editing
}" }"
class="flex w-0 shrink-0 items-center justify-center rounded-lg transition-all" class="flex w-0 shrink-0 items-center justify-center rounded-lg transition-all"
@click="$emit('delete', profile.id)" @click="$emit('delete', profile.name)"
> >
<Check class="size-4" /> <Check class="size-4" />
</button> </button>

View File

@@ -19,7 +19,7 @@
scramble-on-mount scramble-on-mount
:fill-interval="20" :fill-interval="20"
:delay="500" :delay="500"
:text="`(${deviceStore.profileNames.length}/${maxProfiles})`" :text="`(${deviceStore.profiles.length}/${maxProfiles})`"
/> />
</button> </button>
<DropdownMenu> <DropdownMenu>
@@ -44,7 +44,7 @@
</div> </div>
<div class="relative grow overflow-y-auto"> <div class="relative grow overflow-y-auto">
<div v-if="renderProfileList" class="absolute w-full"> <div v-if="renderProfileList" class="absolute w-full">
<div v-if="deviceStore.profileNames.length === 0"> <div v-if="deviceStore.profiles.length === 0">
<div class="flex h-32 flex-col items-center justify-center"> <div class="flex h-32 flex-col items-center justify-center">
<ScrambleText <ScrambleText
scramble-on-mount scramble-on-mount
@@ -114,14 +114,18 @@
} }
" "
@rename=" @rename="
(event) => { (oldName, newName) => {
deviceStore.renameProfile(event.profile, event.name) deviceStore.renameProfile(oldName, newName)
if (deviceStore.currentProfileName === event.profile) { if (deviceStore.currentProfileName === oldName) {
deviceStore.selectProfile(event.name) deviceStore.selectProfile(newName)
} }
} }
" "
@duplicate="console.log('Duplicate profile not implemented!')" @duplicate="
(originalName, profile) => {
deviceStore.duplicateProfile(originalName, profile)
}
"
@delete="console.log('Delete profile not implemented!')" @delete="console.log('Delete profile not implemented!')"
/> />
</div> </div>

View File

@@ -67,7 +67,7 @@ export const useDeviceStore = defineStore('device', {
state.currentProfileName state.currentProfileName
? state.profiles.find((profile) => profile.name === state.currentProfileName) ? state.profiles.find((profile) => profile.name === state.currentProfileName)
: null, : null,
profileTags: (state) => state.profiles.map((profile) => profile.profileTag), profileTags: (state) => [...new Set(state.profiles.map((profile) => profile.profileTag))],
profilesByTag: (state) => profilesByTag: (state) =>
state.profiles.reduce((acc, profile) => { state.profiles.reduce((acc, profile) => {
if (!acc[profile.profileTag]) { if (!acc[profile.profileTag]) {
@@ -75,7 +75,11 @@ export const useDeviceStore = defineStore('device', {
} }
acc[profile.profileTag].push(profile) acc[profile.profileTag].push(profile)
return acc return acc
}, {}) }, {}),
keyColor: (state) => (key: string, pressed: boolean) => {
const propertyName = `button${key.toUpperCase()}${pressed ? 'Press' : 'Idle'}`
return state.currentProfile ? state.currentProfile[propertyName] : 0
}
}, },
actions: { actions: {
setAttachedDeviceIds(deviceIds: string[]) { setAttachedDeviceIds(deviceIds: string[]) {
@@ -103,9 +107,13 @@ export const useDeviceStore = defineStore('device', {
this.profiles.push(profile) this.profiles.push(profile)
} }
if (updateDevice) { if (updateDevice) {
const newProfile = JSON.parse(JSON.stringify(profile))
delete newProfile.name
console.log('Sending new profile:', newProfile)
console.log('with name', profile.name)
nanoIpc.send( nanoIpc.send(
this.currentDeviceId!, this.currentDeviceId!,
JSON.stringify({ profile: profile.name, updates: profile }) JSON.stringify({ profile: profile.name, updates: newProfile })
) )
} }
}, },
@@ -124,6 +132,17 @@ export const useDeviceStore = defineStore('device', {
} }
} }
}, },
duplicateProfile(profileName: string, updateDevice: boolean = true) {
const profile = this.profiles.find((p) => p.name === profileName)
if (profile) {
const newProfile = JSON.parse(JSON.stringify(profile))
newProfile.name = profileName + ' Copy'
this.addProfile(newProfile, updateDevice)
if (this.currentProfileName === profileName) {
this.selectProfile(newProfile.name, updateDevice)
}
}
},
detachDevice(deviceId: string) { detachDevice(deviceId: string) {
const index = this.attachedDeviceIds.indexOf(deviceId) const index = this.attachedDeviceIds.indexOf(deviceId)
if (index !== -1) { if (index !== -1) {
@@ -170,6 +189,16 @@ export const useDeviceStore = defineStore('device', {
}, },
setAngle(angle: number) { setAngle(angle: number) {
this.angle = angle this.angle = angle
},
setKeyColor(key: string, pressed: boolean, color: number, updateDevice: boolean = true) {
const propertyName = `button${key.toUpperCase()}${pressed ? 'Press' : 'Idle'}`
this.currentProfile![propertyName] = color
if (updateDevice) {
nanoIpc.send(
this.currentDeviceId!,
JSON.stringify({ profile: this.currentProfileName, updates: { [propertyName]: color } })
)
}
} }
} }
}) })