UPD: Working key LED config! ✨
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@serialport/bindings-cpp": "^12.0.1",
|
||||
"@types/color": "^3.0.6",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"ajv": "^8.12.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ dependencies:
|
||||
'@serialport/bindings-cpp':
|
||||
specifier: ^12.0.1
|
||||
version: 12.0.1
|
||||
'@types/color':
|
||||
specifier: ^3.0.6
|
||||
version: 3.0.6
|
||||
'@vueuse/core':
|
||||
specifier: ^10.9.0
|
||||
version: 10.9.0(vue@3.4.21)
|
||||
@@ -1204,6 +1207,22 @@ packages:
|
||||
'@types/node': 18.19.21
|
||||
'@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:
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
dependencies:
|
||||
|
||||
@@ -187,13 +187,19 @@ import { SliderRoot, SliderThumb, SliderTrack } from 'radix-vue'
|
||||
import { MoreHorizontal } from 'lucide-vue-next'
|
||||
import { Separator } from '@renderer/components/ui/separator'
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
color: {
|
||||
type: Color,
|
||||
default: () => Color.rgb(255, 0, 0)
|
||||
},
|
||||
roundedTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['input'])
|
||||
|
||||
const hueSliderValue = ref(0)
|
||||
const saturationSliderValue = ref(100)
|
||||
const valueSliderValue = ref(50)
|
||||
@@ -204,7 +210,7 @@ const hueSliderModel = computed({
|
||||
},
|
||||
set(hue) {
|
||||
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) {
|
||||
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) {
|
||||
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(() => {
|
||||
return Color.hsv(hueSliderModel.value[0], 100, valueSliderModel.value[0])
|
||||
})
|
||||
@@ -255,7 +256,7 @@ function onSubmitHexInput() {
|
||||
input = '#' + input
|
||||
}
|
||||
if (input.match(/^#[0-9A-F]{6}$/i)) {
|
||||
color.value = Color(input)
|
||||
emit('input', Color(input))
|
||||
} else shake()
|
||||
}
|
||||
|
||||
@@ -266,10 +267,10 @@ function onSubmitHueInput() {
|
||||
return
|
||||
}
|
||||
const newHue = Math.max(0, Math.min(input, 360))
|
||||
if (newHue === color.value.hue()) {
|
||||
if (newHue === props.color.hue()) {
|
||||
updateInputs()
|
||||
}
|
||||
color.value = color.value.hue(newHue)
|
||||
emit('input', props.color.hue(newHue))
|
||||
}
|
||||
|
||||
function onSubmitSaturationInput() {
|
||||
@@ -279,10 +280,10 @@ function onSubmitSaturationInput() {
|
||||
return
|
||||
}
|
||||
const newSaturation = Math.max(0, Math.min(input, 100))
|
||||
if (newSaturation === color.value.saturationv()) {
|
||||
if (newSaturation === props.color.saturationv()) {
|
||||
updateInputs()
|
||||
}
|
||||
color.value = color.value.saturationv(newSaturation)
|
||||
emit('input', props.color.saturationv(newSaturation))
|
||||
}
|
||||
|
||||
function onSubmitValueInput() {
|
||||
@@ -292,10 +293,10 @@ function onSubmitValueInput() {
|
||||
return
|
||||
}
|
||||
const newValue = Math.max(0, Math.min(input, 100))
|
||||
if (newValue === color.value.value()) {
|
||||
if (newValue === props.color.value()) {
|
||||
updateInputs()
|
||||
}
|
||||
color.value = color.value.value(newValue)
|
||||
emit('input', props.color.value(newValue))
|
||||
}
|
||||
|
||||
function onSubmitRGBInput() {
|
||||
@@ -307,26 +308,27 @@ function onSubmitRGBInput() {
|
||||
return
|
||||
}
|
||||
const newColor = Color.rgb(r, g, b)
|
||||
if (newColor.hex() === color.value.hex()) {
|
||||
if (newColor.hex() === props.color.hex()) {
|
||||
updateInputs()
|
||||
}
|
||||
color.value = newColor
|
||||
emit('input', newColor)
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
hexInput.value = color.value.hex().substring(1, 7)
|
||||
hueInput.value = String(parseInt(color.value.hue())).padStart(3, '0')
|
||||
saturationInput.value = String(parseInt(color.value.saturationv())).padStart(3, '0')
|
||||
valueInput.value = String(parseInt(color.value.value())).padStart(3, '0')
|
||||
rInput.value = String(parseInt(color.value.red())).padStart(3, '0')
|
||||
gInput.value = String(parseInt(color.value.green())).padStart(3, '0')
|
||||
bInput.value = String(parseInt(color.value.blue())).padStart(3, '0')
|
||||
hueSliderValue.value = color.value.hue()
|
||||
saturationSliderValue.value = color.value.saturationv()
|
||||
valueSliderValue.value = color.value.value()
|
||||
console.log('COLORRR', props.color)
|
||||
hexInput.value = props.color.hex().substring(1, 7)
|
||||
hueInput.value = String(parseInt(props.color.hue())).padStart(3, '0')
|
||||
saturationInput.value = String(parseInt(props.color.saturationv())).padStart(3, '0')
|
||||
valueInput.value = String(parseInt(props.color.value())).padStart(3, '0')
|
||||
rInput.value = String(parseInt(props.color.red())).padStart(3, '0')
|
||||
gInput.value = String(parseInt(props.color.green())).padStart(3, '0')
|
||||
bInput.value = String(parseInt(props.color.blue())).padStart(3, '0')
|
||||
hueSliderValue.value = props.color.hue()
|
||||
saturationSliderValue.value = props.color.saturationv()
|
||||
valueSliderValue.value = props.color.value()
|
||||
}
|
||||
|
||||
watch(color, updateInputs)
|
||||
watch(props.color, updateInputs)
|
||||
onBeforeMount(updateInputs)
|
||||
|
||||
const colorFieldText = ref(null)
|
||||
|
||||
@@ -32,40 +32,45 @@
|
||||
@click="currentOption = key"
|
||||
/>
|
||||
</div>
|
||||
<HSVInput v-model="options[currentOption].color" />
|
||||
<HSVInput
|
||||
:color="options[currentOption].color"
|
||||
@input="(color) => $emit('input', currentOption, color)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import HSVInput from '@renderer/components/common/HSVInput.vue'
|
||||
import Color from 'color'
|
||||
import { computed, onBeforeMount, reactive, ref } from 'vue'
|
||||
import { computed, onBeforeMount, ref } from 'vue'
|
||||
|
||||
defineEmits(['input'])
|
||||
|
||||
const currentOption = ref(null)
|
||||
|
||||
const currentColorHex = computed(() => options[currentOption.value].color.hex())
|
||||
const currentColorHex = computed(() => props.options[currentOption.value].color.hex())
|
||||
|
||||
const model = defineModel({
|
||||
type: Object,
|
||||
default: () => ({
|
||||
one: {
|
||||
titleKey: 'One',
|
||||
color: Color('#ff0000')
|
||||
},
|
||||
two: {
|
||||
titleKey: 'Two',
|
||||
color: Color('#00ff00')
|
||||
},
|
||||
three: {
|
||||
titleKey: 'Three',
|
||||
color: Color('#0000ff')
|
||||
}
|
||||
})
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
one: {
|
||||
titleKey: 'One',
|
||||
color: Color('#ff0000')
|
||||
},
|
||||
two: {
|
||||
titleKey: 'Two',
|
||||
color: Color('#00ff00')
|
||||
},
|
||||
three: {
|
||||
titleKey: 'Three',
|
||||
color: Color('#0000ff')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const options = reactive(model.value)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (currentOption.value === null) currentOption.value = Object.keys(options)[0]
|
||||
if (currentOption.value === null) currentOption.value = Object.keys(props.options)[0]
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
<template>
|
||||
<ConfigSection title="Key Colors" :icon-component="Palette">
|
||||
<PaletteInput v-model="keyColors" />
|
||||
<PaletteInput
|
||||
:options="keyColors"
|
||||
@input="
|
||||
(optionKey, color) => {
|
||||
keyColors = {
|
||||
...keyColors,
|
||||
[optionKey]: {
|
||||
...keyColors[optionKey],
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
/>
|
||||
</ConfigSection>
|
||||
</template>
|
||||
<script setup>
|
||||
@@ -8,28 +21,31 @@ import { Palette } from 'lucide-vue-next'
|
||||
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
||||
import PaletteInput from '@renderer/components/common/PaletteInput.vue'
|
||||
import Color from 'color'
|
||||
import { ref, watch } from 'vue'
|
||||
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 { keyColor } = storeToRefs(deviceStore)
|
||||
|
||||
const keyColors = ref({
|
||||
default: {
|
||||
titleKey: 'default',
|
||||
color: Color('#4f25ef')
|
||||
const keyColors = computed({
|
||||
get() {
|
||||
return {
|
||||
default: {
|
||||
titleKey: 'default',
|
||||
color: Color(keyColor.value(appStore.selectedKey, false))
|
||||
},
|
||||
pressed: {
|
||||
titleKey: 'pressed',
|
||||
color: Color(keyColor.value(appStore.selectedKey, true))
|
||||
}
|
||||
}
|
||||
},
|
||||
pressed: {
|
||||
titleKey: 'pressed',
|
||||
color: Color('#d0078f')
|
||||
set(newValue) {
|
||||
deviceStore.setKeyColor(appStore.selectedKey, false, Color(newValue.default.color).rgbNumber())
|
||||
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>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
:class="{ 'bg-zinc-300': selected }"
|
||||
@submit.prevent="
|
||||
() => {
|
||||
$emit('rename', { profile: profile.name, name: nameInput })
|
||||
$emit('rename', profile.name, nameInput)
|
||||
editing = false
|
||||
}
|
||||
"
|
||||
@@ -90,7 +90,7 @@
|
||||
'rounded-l-lg': !nameEditable
|
||||
}"
|
||||
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" />
|
||||
</button>
|
||||
@@ -114,7 +114,7 @@
|
||||
'group-focus-within:w-12 group-hover:w-12': !editing
|
||||
}"
|
||||
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" />
|
||||
</button>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
scramble-on-mount
|
||||
:fill-interval="20"
|
||||
:delay="500"
|
||||
:text="`(${deviceStore.profileNames.length}/${maxProfiles})`"
|
||||
:text="`(${deviceStore.profiles.length}/${maxProfiles})`"
|
||||
/>
|
||||
</button>
|
||||
<DropdownMenu>
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
<div class="relative grow overflow-y-auto">
|
||||
<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">
|
||||
<ScrambleText
|
||||
scramble-on-mount
|
||||
@@ -114,14 +114,18 @@
|
||||
}
|
||||
"
|
||||
@rename="
|
||||
(event) => {
|
||||
deviceStore.renameProfile(event.profile, event.name)
|
||||
if (deviceStore.currentProfileName === event.profile) {
|
||||
deviceStore.selectProfile(event.name)
|
||||
(oldName, newName) => {
|
||||
deviceStore.renameProfile(oldName, newName)
|
||||
if (deviceStore.currentProfileName === oldName) {
|
||||
deviceStore.selectProfile(newName)
|
||||
}
|
||||
}
|
||||
"
|
||||
@duplicate="console.log('Duplicate profile not implemented!')"
|
||||
@duplicate="
|
||||
(originalName, profile) => {
|
||||
deviceStore.duplicateProfile(originalName, profile)
|
||||
}
|
||||
"
|
||||
@delete="console.log('Delete profile not implemented!')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@ export const useDeviceStore = defineStore('device', {
|
||||
state.currentProfileName
|
||||
? state.profiles.find((profile) => profile.name === state.currentProfileName)
|
||||
: null,
|
||||
profileTags: (state) => state.profiles.map((profile) => profile.profileTag),
|
||||
profileTags: (state) => [...new Set(state.profiles.map((profile) => profile.profileTag))],
|
||||
profilesByTag: (state) =>
|
||||
state.profiles.reduce((acc, profile) => {
|
||||
if (!acc[profile.profileTag]) {
|
||||
@@ -75,7 +75,11 @@ export const useDeviceStore = defineStore('device', {
|
||||
}
|
||||
acc[profile.profileTag].push(profile)
|
||||
return acc
|
||||
}, {})
|
||||
}, {}),
|
||||
keyColor: (state) => (key: string, pressed: boolean) => {
|
||||
const propertyName = `button${key.toUpperCase()}${pressed ? 'Press' : 'Idle'}`
|
||||
return state.currentProfile ? state.currentProfile[propertyName] : 0
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setAttachedDeviceIds(deviceIds: string[]) {
|
||||
@@ -103,9 +107,13 @@ export const useDeviceStore = defineStore('device', {
|
||||
this.profiles.push(profile)
|
||||
}
|
||||
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(
|
||||
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) {
|
||||
const index = this.attachedDeviceIds.indexOf(deviceId)
|
||||
if (index !== -1) {
|
||||
@@ -170,6 +189,16 @@ export const useDeviceStore = defineStore('device', {
|
||||
},
|
||||
setAngle(angle: number) {
|
||||
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 } })
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user