UPD: Split store into app and device
This commit is contained in:
@@ -2,7 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron'
|
|||||||
|
|
||||||
// expose an API to choose available devices
|
// expose an API to choose available devices
|
||||||
contextBridge.exposeInMainWorld('nanoSerialApi', {
|
contextBridge.exposeInMainWorld('nanoSerialApi', {
|
||||||
list_devices() {
|
listConnectedDevices() {
|
||||||
return ipcRenderer.invoke('nanoSerialApi:list_devices')
|
return ipcRenderer.invoke('nanoSerialApi:list_devices')
|
||||||
},
|
},
|
||||||
connect(deviceid) {
|
connect(deviceid) {
|
||||||
@@ -11,20 +11,13 @@ contextBridge.exposeInMainWorld('nanoSerialApi', {
|
|||||||
disconnect(deviceid) {
|
disconnect(deviceid) {
|
||||||
return ipcRenderer.invoke('nanoSerialApi:disconnect', deviceid)
|
return ipcRenderer.invoke('nanoSerialApi:disconnect', deviceid)
|
||||||
},
|
},
|
||||||
on_event(eventid_filter, callback) {
|
on(callback) {
|
||||||
//console.log('attaching filter for ', eventid_filter)
|
|
||||||
ipcRenderer.on('nanoSerialApi:event', (_event, eventid, deviceid, ...data) => {
|
ipcRenderer.on('nanoSerialApi:event', (_event, eventid, deviceid, ...data) => {
|
||||||
//console.log('Event in ipcRenderer ', eventid, deviceid, data)
|
callback(eventid, deviceid, ...data)
|
||||||
if (eventid_filter == '*' || eventid_filter == eventid) {
|
|
||||||
callback(eventid, deviceid, ...data)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
send(deviceid, obj) {
|
send(deviceid, obj) {
|
||||||
return ipcRenderer.invoke('nanoSerialApi:send', deviceid, JSON.stringify(obj))
|
return ipcRenderer.invoke('nanoSerialApi:send', deviceid, JSON.stringify(obj))
|
||||||
},
|
|
||||||
save(deviceid) {
|
|
||||||
return ipcRenderer.invoke('nanoSerialApi:send', deviceid, JSON.stringify({ save: true }))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,41 +3,39 @@ import ProfileManager from '@renderer/components/profile/ProfileManager.vue'
|
|||||||
import DevicePreview from '@renderer/components/device/DevicePreview.vue'
|
import DevicePreview from '@renderer/components/device/DevicePreview.vue'
|
||||||
import ConfigPane from '@renderer/components/config/ConfigPane.vue'
|
import ConfigPane from '@renderer/components/config/ConfigPane.vue'
|
||||||
import Navbar from '@renderer/components/navbar/Navbar.vue'
|
import Navbar from '@renderer/components/navbar/Navbar.vue'
|
||||||
import { useStore } from '@renderer/store'
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
import { useMessageHandlers } from '@renderer/device'
|
|
||||||
|
|
||||||
const { electronApi, nanoSerialApi } = window
|
const deviceStore = useDeviceStore()
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const menuActions = {
|
// const menuActions = {
|
||||||
connect: () => store.setConnected(!store.connected),
|
// connect: () => store.setConnected(!store.connected),
|
||||||
orientation: () => store.cycleScreenOrientation(),
|
// orientation: () => store.cycleScreenOrientation(),
|
||||||
skin: () => store.switchPreviewDeviceModel()
|
// skin: () => store.switchPreviewDeviceModel()
|
||||||
}
|
// }
|
||||||
|
|
||||||
electronApi.onMenu((key) => {
|
// electronApi.onMenu((key) => {
|
||||||
console.log('menu', key)
|
// console.log('menu', key)
|
||||||
if (menuActions[key]) {
|
// if (menuActions[key]) {
|
||||||
menuActions[key]()
|
// menuActions[key]()
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
store.fetchProfiles() // TODO remove me!
|
// store.fetchProfiles() // TODO remove me!
|
||||||
|
|
||||||
// handle device events
|
// handle device events
|
||||||
const handlers = useMessageHandlers(store)
|
// const handlers = useMessageHandlers(store)
|
||||||
nanoSerialApi.on_event('device-attached', (evt, deviceid, data) => store.device_attached(deviceid))
|
// nanoSerialApi.on_event('device-attached', (evt, deviceid, data) => store.device_attached(deviceid))
|
||||||
nanoSerialApi.on_event('device-detached', (evt, deviceid, data) => store.device_detached(deviceid))
|
// nanoSerialApi.on_event('device-detached', (evt, deviceid, data) => store.device_detached(deviceid))
|
||||||
nanoSerialApi.on_event('device-error', (evt, deviceid, data) => {
|
// nanoSerialApi.on_event('device-error', (evt, deviceid, data) => {
|
||||||
/* TODO handle connection errors */
|
// /* TODO handle connection errors */
|
||||||
})
|
// })
|
||||||
nanoSerialApi.on_event('connected', (evt, deviceid, data) => store.device_connected(deviceid))
|
// nanoSerialApi.on_event('connected', (evt, deviceid, data) => store.device_connected(deviceid))
|
||||||
nanoSerialApi.on_event('disconnected', (evt, deviceid, data) => store.device_disconnected(deviceid))
|
// nanoSerialApi.on_event('disconnected', (evt, deviceid, data) => store.device_disconnected(deviceid))
|
||||||
nanoSerialApi.on_event('update', (evt, deviceid, data) => {
|
// nanoSerialApi.on_event('update', (evt, deviceid, data) => {
|
||||||
handlers.handle_message(data)
|
// handlers.handle_message(data)
|
||||||
})
|
// })
|
||||||
// get list of the currently attached devices
|
// // get list of the currently attached devices
|
||||||
nanoSerialApi.list_devices().then((devs) => store.init_devices(devs))
|
// nanoSerialApi.list_devices().then((devs) => store.init_devices(devs))
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<main class="flex h-screen w-screen select-none flex-col">
|
<main class="flex h-screen w-screen select-none flex-col">
|
||||||
@@ -46,7 +44,7 @@ nanoSerialApi.list_devices().then((devs) => store.init_devices(devs))
|
|||||||
<div class="flex min-w-60 flex-1 basis-1/3 overflow-hidden">
|
<div class="flex min-w-60 flex-1 basis-1/3 overflow-hidden">
|
||||||
<Transition name="slide-left">
|
<Transition name="slide-left">
|
||||||
<ProfileManager
|
<ProfileManager
|
||||||
v-if="store.connected"
|
v-if="deviceStore.connected"
|
||||||
class="flex max-w-full flex-1 flex-col border-0 border-r border-solid bg-zinc-900/50"
|
class="flex max-w-full flex-1 flex-col border-0 border-r border-solid bg-zinc-900/50"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -55,7 +53,7 @@ nanoSerialApi.list_devices().then((devs) => store.init_devices(devs))
|
|||||||
<div class="flex flex-1 basis-2/5 overflow-hidden">
|
<div class="flex flex-1 basis-2/5 overflow-hidden">
|
||||||
<Transition name="slide-right">
|
<Transition name="slide-right">
|
||||||
<ConfigPane
|
<ConfigPane
|
||||||
v-if="store.connected"
|
v-if="deviceStore.connected"
|
||||||
class="flex max-w-full flex-1 flex-col border-0 border-l border-solid bg-zinc-900/50"
|
class="flex max-w-full flex-1 flex-col border-0 border-l border-solid bg-zinc-900/50"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
271
src/renderer/src/appStore.ts
Normal file
271
src/renderer/src/appStore.ts
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import WIP from '@renderer/components/WIP.vue'
|
||||||
|
import KnobFeedbackConfig from '@renderer/components/config/knob/KnobFeedbackConfig.vue'
|
||||||
|
import KnobLightConfig from '@renderer/components/config/knob/KnobLightConfig.vue'
|
||||||
|
import KeyLightConfig from '@renderer/components/config/keys/KeyLightConfig.vue'
|
||||||
|
import KnobMappingConfig from '@renderer/components/config/knob/KnobMappingConfig.vue'
|
||||||
|
import KeyMappingConfig from '@renderer/components/config/keys/KeyMappingConfig.vue'
|
||||||
|
import { shallowRef } from 'vue'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
selectedFeature: 'knob',
|
||||||
|
selectedKey: 'a',
|
||||||
|
currentConfigPage: 'mapping',
|
||||||
|
configPages: {
|
||||||
|
knob: {
|
||||||
|
mapping: {
|
||||||
|
titleKey: 'config_options.mapping_configuration.title',
|
||||||
|
component: shallowRef(KnobMappingConfig)
|
||||||
|
},
|
||||||
|
feedback: {
|
||||||
|
titleKey: 'config_options.feedback_designer.title',
|
||||||
|
component: shallowRef(KnobFeedbackConfig)
|
||||||
|
},
|
||||||
|
lighting: {
|
||||||
|
titleKey: 'config_options.light_designer.title',
|
||||||
|
component: shallowRef(KnobLightConfig)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
mapping: {
|
||||||
|
titleKey: 'config_options.mapping_configuration.title',
|
||||||
|
component: shallowRef(KeyMappingConfig)
|
||||||
|
},
|
||||||
|
lighting: {
|
||||||
|
titleKey: 'config_options.light_designer.title',
|
||||||
|
component: shallowRef(KeyLightConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
previewDeviceModel: localStorage.getItem('previewDeviceModel') || 'nanoOne'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
// profiles: (state) => state.profileCategories.flatMap((c) => c.profiles),
|
||||||
|
// profileIds: (state) => state.profiles.map((p) => p.id),
|
||||||
|
// selectedProfileCategory: (state) =>
|
||||||
|
// state.profileCategories.find((c) => c.profiles.find((p) => p.id === state.selectedProfileId)),
|
||||||
|
// selectedProfile: (state) => state.profiles.find((p) => p.id === state.selectedProfileId),
|
||||||
|
currentConfigComponent: (state) =>
|
||||||
|
state.configPages[state.selectedFeature][state.currentConfigPage]?.component || WIP,
|
||||||
|
currentConfigPages: (state) => state.configPages[state.selectedFeature] || {}
|
||||||
|
// multipleDevicesConnected: (state) => state.connectedDevices.length > 1,
|
||||||
|
// numAttachedDevices: (state) => Object.keys(state.devices).length
|
||||||
|
// connected: (state) => state.connectedId !== null,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// selectProfile(id) {
|
||||||
|
// if (!this.profileIds.includes(id)) return false
|
||||||
|
// this.selectedProfileId = id
|
||||||
|
// return true
|
||||||
|
// },
|
||||||
|
// addProfile(profile, categoryIndex, newIndex) {
|
||||||
|
// const category = this.profileCategories[categoryIndex]
|
||||||
|
// category.profiles.splice(newIndex, 0, profile)
|
||||||
|
// },
|
||||||
|
// removeProfile(profileId) {
|
||||||
|
// const category = this.profileCategories.find((c) =>
|
||||||
|
// c.profiles.find((p) => p.id === profileId)
|
||||||
|
// )
|
||||||
|
// const index = category.profiles.findIndex((p) => p.id === profileId)
|
||||||
|
// category.profiles.splice(index, 1)
|
||||||
|
// },
|
||||||
|
// duplicateProfile(profileId) {
|
||||||
|
// const profile = this.profiles.find((p) => p.id === profileId)
|
||||||
|
// const newProfile = JSON.parse(JSON.stringify(profile))
|
||||||
|
// newProfile.id = this.newProfileId(profile.id)
|
||||||
|
// newProfile.name = this.newProfileName(profile.name)
|
||||||
|
// const category = this.profileCategories.find((c) =>
|
||||||
|
// c.profiles.find((p) => p.id === profileId)
|
||||||
|
// )
|
||||||
|
// const index = category.profiles.findIndex((p) => p.id === profileId)
|
||||||
|
// category.profiles.splice(index + 1, 0, newProfile)
|
||||||
|
// this.selectProfile(newProfile.id)
|
||||||
|
// },
|
||||||
|
// moveProfile(profileId, oldIndex, newIndex) {
|
||||||
|
// // Find the profile category, then swap the profiles at the old and new indices
|
||||||
|
// const category = this.profileCategories.find((c) =>
|
||||||
|
// c.profiles.find((p) => p.id === profileId)
|
||||||
|
// )
|
||||||
|
// const tmpProfile = category.profiles[newIndex]
|
||||||
|
// category.profiles[newIndex] = category.profiles[oldIndex]
|
||||||
|
// category.profiles[newIndex] = tmpProfile
|
||||||
|
// },
|
||||||
|
// moveProfileCategory(categoryName, oldIndex, newIndex) {
|
||||||
|
// const tmpCategory = this.profileCategories[newIndex]
|
||||||
|
// this.profileCategories[newIndex] = this.profileCategories[oldIndex]
|
||||||
|
// this.profileCategories[newIndex] = tmpCategory
|
||||||
|
// },
|
||||||
|
// changeProfileCategory(profileId, newCategoryIndex, newIndex) {
|
||||||
|
// const profile = this.profiles.find((p) => p.id === profileId)
|
||||||
|
// const oldCategory = this.profileCategories.find((c) =>
|
||||||
|
// c.profiles.find((p) => p.id === profileId)
|
||||||
|
// )
|
||||||
|
// const newCategory = this.profileCategories[newCategoryIndex]
|
||||||
|
// oldCategory.profiles = oldCategory.profiles.filter((p) => p.id !== profileId)
|
||||||
|
// newCategory.profiles.splice(newIndex, 0, profile)
|
||||||
|
// },
|
||||||
|
// renameProfile(profileId, newName) {
|
||||||
|
// const profile = this.profiles.find((p) => p.id === profileId)
|
||||||
|
// profile.name = newName
|
||||||
|
// },
|
||||||
|
// fetchProfiles() {
|
||||||
|
// const categories = mockData.categories
|
||||||
|
// console.log(categories)
|
||||||
|
// const ids = new Set()
|
||||||
|
// // const validate = ajv.compile(schema) // see below
|
||||||
|
// this.$patch({
|
||||||
|
// profileCategories: categories.map((category) => ({
|
||||||
|
// name: category.name,
|
||||||
|
// profiles: category.profiles.filter((profile) => {
|
||||||
|
// // Ajv validation requires unsafe-eval CSP, let's not do that
|
||||||
|
// // TODO: Remove ajv validation completely or compile schema at build time
|
||||||
|
// // if (!validate(profile)) {
|
||||||
|
// // console.error('Failed to validate profile: ' + profile.name, validate.errors)
|
||||||
|
// // return false
|
||||||
|
// // }
|
||||||
|
// if (ids.has(profile.id)) {
|
||||||
|
// console.error('Duplicate profile id: ' + profile.id + ' for profile: ' + profile.name)
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// ids.add(profile.id)
|
||||||
|
// return true
|
||||||
|
// })
|
||||||
|
// })),
|
||||||
|
// selectedProfileId: categories[0]?.profiles[0]?.id || null
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// newProfileName(originalName = '') {
|
||||||
|
// let name = originalName
|
||||||
|
// let i = 1
|
||||||
|
// while (this.profiles.find((p) => p.name === name)) {
|
||||||
|
// name = `${originalName} (${i++})`
|
||||||
|
// }
|
||||||
|
// return name
|
||||||
|
// },
|
||||||
|
// newProfileId(originalId = '') {
|
||||||
|
// let id = originalId
|
||||||
|
// if (originalId) {
|
||||||
|
// do {
|
||||||
|
// id = Math.floor((parseInt(id) + 1) % 9999)
|
||||||
|
// .toString()
|
||||||
|
// .padStart(4, '0')
|
||||||
|
// } while (this.profileIds.includes(id))
|
||||||
|
// } else {
|
||||||
|
// do {
|
||||||
|
// id = Math.floor(Math.random() * 9999)
|
||||||
|
// .toString()
|
||||||
|
// .padStart(4, '0')
|
||||||
|
// } while (this.profileIds.includes(id))
|
||||||
|
// }
|
||||||
|
// return id
|
||||||
|
// },
|
||||||
|
selectConfigFeature(feature) {
|
||||||
|
this.selectedFeature = feature
|
||||||
|
if (!this.currentConfigPages[this.currentConfigPage]) this.setCurrentConfigPage('mapping')
|
||||||
|
},
|
||||||
|
selectKey(key) {
|
||||||
|
this.selectedKey = key
|
||||||
|
this.selectConfigFeature('key')
|
||||||
|
},
|
||||||
|
setCurrentConfigPage(page) {
|
||||||
|
this.currentConfigPage = page
|
||||||
|
},
|
||||||
|
// setConnected(connected) {
|
||||||
|
// this.connected = connected
|
||||||
|
// },
|
||||||
|
switchPreviewDeviceModel() {
|
||||||
|
this.previewDeviceModel = this.previewDeviceModel === 'nanoOne' ? 'nanoZero' : 'nanoOne'
|
||||||
|
localStorage.setItem('previewDeviceModel', this.previewDeviceModel)
|
||||||
|
}
|
||||||
|
// cycleScreenOrientation() {
|
||||||
|
// this.screenOrientation = (this.screenOrientation + 90) % 360
|
||||||
|
// },
|
||||||
|
// setKeyDefaultColor(color) {
|
||||||
|
// // this.selectedProfile.keys[this.selectedKey].default = color
|
||||||
|
// const props = {}
|
||||||
|
// props[`button${this.selectedKey.toUpperCase()}Idle`] = color.rgbNumber()
|
||||||
|
// nanoSerialApi.send(this.connectedId, { p: { name: 'Default Profile', ...props } })
|
||||||
|
// },
|
||||||
|
// setKeyPressedColor(color) {
|
||||||
|
// // this.selectedProfile.keys[this.selectedKey].pressed = color
|
||||||
|
// const props = {}
|
||||||
|
// props[`button${this.selectedKey.toUpperCase()}Press`] = color.rgbNumber()
|
||||||
|
// nanoSerialApi.send(this.connectedId, { p: { name: 'Default Profile', ...props } })
|
||||||
|
// },
|
||||||
|
|
||||||
|
// // devices, device attachment, connection, and disconnection
|
||||||
|
// init_devices(ids) {
|
||||||
|
// console.log('Initializing devices: ', ids)
|
||||||
|
// for (const id of ids) this.update_devices(id, true)
|
||||||
|
// if (Object.keys(this.devices).length == 1) {
|
||||||
|
// // TODO auto-connect to the device
|
||||||
|
// const deviceid = Object.keys(this.devices)[0]
|
||||||
|
// console.log('Auto-connecting to device ', deviceid)
|
||||||
|
// window.nanoSerialApi.connect(deviceid)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// update_devices(deviceid, attached) {
|
||||||
|
// if (attached) {
|
||||||
|
// if (!this.devices.hasOwnProperty(deviceid))
|
||||||
|
// this.devices[deviceid] = { serialNumber: deviceid, connected: false }
|
||||||
|
// } else {
|
||||||
|
// if (this.devices.hasOwnProperty(deviceid)) delete this.devices[deviceid] // TODO maybe mark as detached instead of deleting? then we can remember its name, etc...
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// device_attached(deviceid) {
|
||||||
|
// this.update_devices(deviceid, true)
|
||||||
|
// if (Object.keys(this.devices).length == 1) {
|
||||||
|
// // TODO auto-connect to the device
|
||||||
|
// console.log('Auto-connecting to device ', deviceid)
|
||||||
|
// window.nanoSerialApi.connect(deviceid)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// device_detached(deviceid) {
|
||||||
|
// if (this.devices[deviceid].connected) {
|
||||||
|
// // detached event arrived before disconnected event?
|
||||||
|
// this.devices[deviceid].connected = false
|
||||||
|
// this.connected = false
|
||||||
|
// }
|
||||||
|
// this.update_devices(deviceid, false)
|
||||||
|
// },
|
||||||
|
// device_connected(deviceid) {
|
||||||
|
// this.devices[deviceid].connected = true
|
||||||
|
// this.connected = true
|
||||||
|
// this.connectedId = deviceid
|
||||||
|
// // TODO load profiles from device
|
||||||
|
// // nanoSerialApi.send(deviceid, { profiles: "#all" }) // request profiles
|
||||||
|
// // "Default Profile", for now, is the only profile after the device
|
||||||
|
// // starts up, so it is also the current (eg. 'selected') profile
|
||||||
|
// // nanoSerialApi.send(deviceid, { p: "Default Profile" }) // request Default Profile
|
||||||
|
|
||||||
|
// // TODO maybe you want to request all the profiles right now?
|
||||||
|
// // or only on demand?
|
||||||
|
// },
|
||||||
|
// device_disconnected(deviceid) {
|
||||||
|
// this.devices[deviceid].connected = false
|
||||||
|
// this.connected = false
|
||||||
|
// this.connectedId = null
|
||||||
|
// // TODO switch UI to disconnected state
|
||||||
|
// },
|
||||||
|
|
||||||
|
// // device events
|
||||||
|
// update_knob_position(turns, angle, velocity) {
|
||||||
|
// this.turns = turns
|
||||||
|
// this.angle = angle
|
||||||
|
// this.velocity = velocity
|
||||||
|
// this.last_event = Date.now()
|
||||||
|
// },
|
||||||
|
// update_keystate(keystate) {
|
||||||
|
// this.keyState = keystate
|
||||||
|
// this.last_event = Date.now()
|
||||||
|
// },
|
||||||
|
|
||||||
|
// // settings changes
|
||||||
|
// update_device_name(name) {
|
||||||
|
// this.devices[this.connectedId].name = name
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template v-if="store.selectedProfile">
|
<template v-if="deviceStore.currentProfile">
|
||||||
<TabSelect
|
<TabSelect
|
||||||
v-if="showTabs"
|
v-if="showTabs"
|
||||||
v-model="configPage"
|
v-model="configPage"
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</TabSelect>
|
</TabSelect>
|
||||||
<div class="grow overflow-y-auto">
|
<div class="grow overflow-y-auto">
|
||||||
<component :is="store.currentConfigComponent" />
|
<component :is="appStore.currentConfigComponent" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -29,18 +29,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useStore } from '@renderer/store'
|
import { useAppStore } from '@renderer/appStore'
|
||||||
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
import TabSelect from '@renderer/components/common/TabSelect.vue'
|
import TabSelect from '@renderer/components/common/TabSelect.vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||||
import { ChevronLeft } from 'lucide-vue-next'
|
import { ChevronLeft } from 'lucide-vue-next'
|
||||||
|
|
||||||
const store = useStore()
|
const appStore = useAppStore()
|
||||||
|
const deviceStore = useDeviceStore()
|
||||||
|
|
||||||
const configPages = computed(() => store.currentConfigPages)
|
const configPages = computed(() => appStore.currentConfigPages)
|
||||||
const configPage = computed({
|
const configPage = computed({
|
||||||
get: () => store.currentConfigPage,
|
get: () => appStore.currentConfigPage,
|
||||||
set: (value) => store.setCurrentConfigPage(value)
|
set: (value) => appStore.setCurrentConfigPage(value)
|
||||||
})
|
})
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ 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 { ref, watch } from 'vue'
|
||||||
import { useStore } from '@renderer/store'
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
|
|
||||||
const store = useStore()
|
const deviceStore = useDeviceStore()
|
||||||
|
|
||||||
const keyColors = ref({
|
const keyColors = ref({
|
||||||
default: {
|
default: {
|
||||||
@@ -27,8 +27,8 @@ const keyColors = ref({
|
|||||||
watch(
|
watch(
|
||||||
keyColors,
|
keyColors,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
store.setKeyDefaultColor(newVal.default.color)
|
// store.setKeyDefaultColor(newVal.default.color)
|
||||||
store.setKeyPressedColor(newVal.pressed.color)
|
// store.setKeyPressedColor(newVal.pressed.color)
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<ConfigSection :title="`${store.selectedKey} Pressed`" :icon-component="PanelBottomClose">
|
<ConfigSection :title="`${appStore.selectedKey} Pressed`" :icon-component="PanelBottomClose">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="text-zinc-500"> ({{ actionsPressed.length }})</span></template
|
<span class="text-zinc-500"> ({{ actionsPressed.length }})</span></template
|
||||||
>
|
>
|
||||||
<ActionGroup :actions="actionsPressed" class="p-2" />
|
<ActionGroup :actions="actionsPressed" class="p-2" />
|
||||||
</ConfigSection>
|
</ConfigSection>
|
||||||
<ConfigSection :title="`${store.selectedKey} Released`" :icon-component="PanelBottomOpen">
|
<ConfigSection :title="`${appStore.selectedKey} Released`" :icon-component="PanelBottomOpen">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="text-zinc-500"> ({{ actionsReleased.length }})</span></template
|
<span class="text-zinc-500"> ({{ actionsReleased.length }})</span></template
|
||||||
>
|
>
|
||||||
<ActionGroup :actions="actionsReleased" class="p-2" />
|
<ActionGroup :actions="actionsReleased" class="p-2" />
|
||||||
</ConfigSection>
|
</ConfigSection>
|
||||||
<ConfigSection :title="`${store.selectedKey} Held`" :icon-component="Clock2">
|
<ConfigSection :title="`${appStore.selectedKey} Held`" :icon-component="Clock2">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="text-zinc-500"> ({{ actionsHeld.length }})</span></template
|
<span class="text-zinc-500"> ({{ actionsHeld.length }})</span></template
|
||||||
>
|
>
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { PanelBottomClose, PanelBottomOpen, Clock2 } from 'lucide-vue-next'
|
import { PanelBottomClose, PanelBottomOpen, Clock2 } from 'lucide-vue-next'
|
||||||
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
import ConfigSection from '@renderer/components/common/ConfigSection.vue'
|
||||||
import { useStore } from '@renderer/store'
|
import { useAppStore } from '@renderer/appStore'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import ActionGroup from '@renderer/components/config/actions/ActionGroup.vue'
|
import ActionGroup from '@renderer/components/config/actions/ActionGroup.vue'
|
||||||
|
|
||||||
const store = useStore()
|
const appStore = useAppStore()
|
||||||
const actionsPressed = ref([
|
const actionsPressed = ref([
|
||||||
{
|
{
|
||||||
id: '1'
|
id: '1'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<div v-if="store.connected" class="flex h-12 items-center justify-between px-10">
|
<div v-if="deviceStore.connected" class="flex h-12 items-center justify-between px-10">
|
||||||
<h2>
|
<h2>
|
||||||
<ScrambleText
|
<ScrambleText
|
||||||
:delay="100"
|
:delay="100"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</Transition>
|
</Transition>
|
||||||
<Transition name="fade-delayed">
|
<Transition name="fade-delayed">
|
||||||
<DeviceLEDRing
|
<DeviceLEDRing
|
||||||
v-if="store.connected"
|
v-if="deviceStore.connected"
|
||||||
:value="barValue"
|
:value="barValue"
|
||||||
class="absolute inset-x-0 top-[12.5%] mx-auto h-[66%]"
|
class="absolute inset-x-0 top-[12.5%] mx-auto h-[66%]"
|
||||||
/>
|
/>
|
||||||
@@ -44,11 +44,13 @@
|
|||||||
>
|
>
|
||||||
<TransitionGroup name="fade-display">
|
<TransitionGroup name="fade-display">
|
||||||
<div
|
<div
|
||||||
v-if="store.connected"
|
v-if="deviceStore.connected"
|
||||||
class="absolute flex flex-col items-center pb-2 text-center mix-blend-screen"
|
class="absolute flex flex-col items-center pb-2 text-center mix-blend-screen"
|
||||||
>
|
>
|
||||||
<img :src="LogoMidi" alt="midi-logo" class="h-4 opacity-50" />
|
<img :src="LogoMidi" alt="midi-logo" class="h-4 opacity-50" />
|
||||||
<h2 class="font-pixellg text-5xl">{{ parseInt(barValue - store.turns * 100) }}</h2>
|
<h2 class="font-pixellg text-5xl">
|
||||||
|
{{ parseInt(barValue - deviceStore.turns * 100) }}
|
||||||
|
</h2>
|
||||||
<div class="font-pixelsm text-md">HIGH PASS</div>
|
<div class="font-pixelsm text-md">HIGH PASS</div>
|
||||||
<DeviceBar :value="barValue" :count="30" :width="120" />
|
<DeviceBar :value="barValue" :count="30" :width="120" />
|
||||||
<span class="font-pixelsm w-36 text-[7pt] uppercase text-muted-foreground">
|
<span class="font-pixelsm w-36 text-[7pt] uppercase text-muted-foreground">
|
||||||
@@ -71,21 +73,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<Transition name="fade-delayed">
|
<Transition name="fade-delayed">
|
||||||
<button
|
<button
|
||||||
v-if="store.connected"
|
v-if="deviceStore.connected"
|
||||||
class="absolute inset-x-0 top-[24.5%] mx-auto aspect-square h-[41.5%] rounded-full outline-2 transition-all"
|
class="absolute inset-x-0 top-[24.5%] mx-auto aspect-square h-[41.5%] rounded-full outline-2 transition-all"
|
||||||
:class="{
|
:class="{
|
||||||
'outline outline-white': store.selectedFeature === 'knob',
|
'outline outline-white': appStore.selectedFeature === 'knob',
|
||||||
'outline-zinc-400 hover:outline': store.selectedFeature !== 'knob'
|
'outline-zinc-400 hover:outline': appStore.selectedFeature !== 'knob'
|
||||||
}"
|
}"
|
||||||
@click="store.selectConfigFeature('knob')"
|
@click="appStore.selectConfigFeature('knob')"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
<Transition name="fade-delayed">
|
<Transition name="fade-delayed">
|
||||||
<DeviceKeys
|
<DeviceKeys
|
||||||
v-if="store.connected"
|
v-if="deviceStore.connected"
|
||||||
class="absolute inset-x-0 top-[77.5%] mx-auto w-[72.7%] gap-[2.2%]"
|
class="absolute inset-x-0 top-[77.5%] mx-auto w-[72.7%] gap-[2.2%]"
|
||||||
:selected="store.selectedFeature === 'key' ? store.selectedKey : ''"
|
:selected="appStore.selectedFeature === 'key' ? appStore.selectedKey : ''"
|
||||||
@select="store.selectKey"
|
@select="appStore.selectKey"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,15 +98,17 @@ import RenderNanoOne from '@renderer/assets/images/renderNanoOneTransparent.png'
|
|||||||
import RenderNanoZero from '@renderer/assets/images/renderNanoZeroTransparent.png'
|
import RenderNanoZero from '@renderer/assets/images/renderNanoZeroTransparent.png'
|
||||||
import LogoMidi from '@renderer/assets/logos/logoMidi.svg'
|
import LogoMidi from '@renderer/assets/logos/logoMidi.svg'
|
||||||
import DeviceBar from '@renderer/components/device/DeviceBar.vue'
|
import DeviceBar from '@renderer/components/device/DeviceBar.vue'
|
||||||
import { useStore } from '@renderer/store'
|
import { useAppStore } from '@renderer/appStore'
|
||||||
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import DeviceLEDRing from '@renderer/components/device/DeviceLEDRing.vue'
|
import DeviceLEDRing from '@renderer/components/device/DeviceLEDRing.vue'
|
||||||
import DeviceKeys from '@renderer/components/device/DeviceKeys.vue'
|
import DeviceKeys from '@renderer/components/device/DeviceKeys.vue'
|
||||||
|
|
||||||
const store = useStore()
|
const appStore = useAppStore()
|
||||||
|
const deviceStore = useDeviceStore()
|
||||||
|
|
||||||
const barValue = computed(() => 100 - (store.angle / Math.PI / 2) * 100)
|
const barValue = computed(() => 100 - (deviceStore.angle / Math.PI / 2) * 100)
|
||||||
|
|
||||||
const previewDeviceImages = {
|
const previewDeviceImages = {
|
||||||
nanoOne: RenderNanoOne,
|
nanoOne: RenderNanoOne,
|
||||||
@@ -112,7 +116,7 @@ const previewDeviceImages = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const previewDeviceImage = computed(
|
const previewDeviceImage = computed(
|
||||||
() => previewDeviceImages[store.previewDeviceModel || 'nanoOne']
|
() => previewDeviceImages[appStore.previewDeviceModel || 'nanoOne']
|
||||||
)
|
)
|
||||||
|
|
||||||
const offlineText = ref('NO DEVICE CONNECTED')
|
const offlineText = ref('NO DEVICE CONNECTED')
|
||||||
|
|||||||
@@ -41,31 +41,35 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger class="app-titlebar-button">
|
<MenubarTrigger class="app-titlebar-button">
|
||||||
<template v-if="store.numAttachedDevices !== 1">
|
<template v-if="deviceStore.attachedDeviceIds.length !== 1">
|
||||||
Devices<span class="text-zinc-500"> ({{ '' + store.numAttachedDevices }})</span>
|
Devices<span class="text-zinc-500"
|
||||||
|
> ({{ '' + deviceStore.attachedDeviceIds.length }})</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-else> Device </template>
|
<template v-else> Device </template>
|
||||||
</MenubarTrigger>
|
</MenubarTrigger>
|
||||||
<MenubarContent>
|
<MenubarContent>
|
||||||
<!-- TODO: Switch keyboard shortcut icons based on platform -->
|
<!-- TODO: Switch keyboard shortcut icons based on platform -->
|
||||||
<MenubarItem @click="store.setConnected(!store.connected)">
|
<MenubarItem @click="deviceStore.setConnected(!deviceStore.connected)">
|
||||||
{{ store.connected ? $t('navbar.device.disconnect') : $t('navbar.device.connect') }}
|
{{
|
||||||
|
deviceStore.connected ? $t('navbar.device.disconnect') : $t('navbar.device.connect')
|
||||||
|
}}
|
||||||
<MenubarShortcut>⌘D</MenubarShortcut>
|
<MenubarShortcut>⌘D</MenubarShortcut>
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarItem v-if="store.multipleDevicesConnected"
|
<MenubarItem v-if="deviceStore.attachedDeviceIds.length > 1"
|
||||||
>Next Device
|
>Next Device
|
||||||
<MenubarShortcut>⌘N</MenubarShortcut>
|
<MenubarShortcut>⌘N</MenubarShortcut>
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem class="flex justify-between" @click="store.cycleScreenOrientation">
|
<MenubarItem class="flex justify-between" @click="deviceStore.cycleOrientation">
|
||||||
<p>Orientation: </p>
|
<p>Orientation: </p>
|
||||||
<p>{{ store.screenOrientation }}°</p>
|
<p>{{ deviceStore.orientation }}°</p>
|
||||||
<MenubarShortcut>⌘R</MenubarShortcut>
|
<MenubarShortcut>⌘R</MenubarShortcut>
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem class="flex justify-between" @click="store.switchPreviewDeviceModel">
|
<MenubarItem class="flex justify-between" @click="appStore.switchPreviewDeviceModel">
|
||||||
<p>Skin: </p>
|
<p>Skin: </p>
|
||||||
<p>{{ previewDeviceNames[store.previewDeviceModel || 'nanoOne'] }}</p>
|
<p>{{ previewDeviceNames[appStore.previewDeviceModel || 'nanoOne'] }}</p>
|
||||||
<MenubarShortcut>⌘S</MenubarShortcut>
|
<MenubarShortcut>⌘S</MenubarShortcut>
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
@@ -133,7 +137,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grow" />
|
<div class="grow" />
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<div v-if="store.connected" class="flex items-center gap-2 px-2">
|
<div v-if="deviceStore.connected" class="flex items-center gap-2 px-2">
|
||||||
<div v-if="numberOfChanges" class="text-sm">
|
<div v-if="numberOfChanges" class="text-sm">
|
||||||
<PenLine class="inline-block h-4" />{{ numberOfChanges }} Changes
|
<PenLine class="inline-block h-4" />{{ numberOfChanges }} Changes
|
||||||
</div>
|
</div>
|
||||||
@@ -145,7 +149,7 @@
|
|||||||
: 'border-2'
|
: 'border-2'
|
||||||
"
|
"
|
||||||
class="app-titlebar-button"
|
class="app-titlebar-button"
|
||||||
@click="nanoSerialApi.save(store.connectedId)"
|
@click="console.log('Save not implemented!')"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</MenubarButton>
|
</MenubarButton>
|
||||||
@@ -154,9 +158,9 @@
|
|||||||
<MenubarButton
|
<MenubarButton
|
||||||
v-if="showDisconnectButton"
|
v-if="showDisconnectButton"
|
||||||
class="app-titlebar-button border-2"
|
class="app-titlebar-button border-2"
|
||||||
@click="store.setConnected(!store.connected)"
|
@click="deviceStore.setConnected(!deviceStore.connected)"
|
||||||
>
|
>
|
||||||
{{ store.connected ? 'Disconnect' : 'Connect' }}
|
{{ deviceStore.connected ? 'Disconnect' : 'Connect' }}
|
||||||
</MenubarButton>
|
</MenubarButton>
|
||||||
<div v-if="!isMacOS" class="flex h-full">
|
<div v-if="!isMacOS" class="flex h-full">
|
||||||
<button
|
<button
|
||||||
@@ -198,10 +202,12 @@ import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
|||||||
import { Minus, Square, Copy, X, PenLine } from 'lucide-vue-next'
|
import { Minus, Square, Copy, X, PenLine } from 'lucide-vue-next'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { Separator } from '@renderer/components/ui/separator'
|
import { Separator } from '@renderer/components/ui/separator'
|
||||||
import { useStore } from '@renderer/store'
|
|
||||||
import MenubarButton from '@renderer/components/navbar/MenubarButton.vue'
|
import MenubarButton from '@renderer/components/navbar/MenubarButton.vue'
|
||||||
|
import { useAppStore } from '@renderer/appStore'
|
||||||
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
|
|
||||||
const store = useStore()
|
const appStore = useAppStore()
|
||||||
|
const deviceStore = useDeviceStore()
|
||||||
|
|
||||||
const minimizable = ref(true)
|
const minimizable = ref(true)
|
||||||
const maximizable = ref(true)
|
const maximizable = ref(true)
|
||||||
@@ -209,7 +215,7 @@ const showDisconnectButton = ref(false)
|
|||||||
|
|
||||||
const isMaximized = ref(false)
|
const isMaximized = ref(false)
|
||||||
|
|
||||||
const { electronApi, nanoSerialApi } = window
|
const { electronApi } = window
|
||||||
|
|
||||||
const isMacOS = electronApi.platform === 'darwin'
|
const isMacOS = electronApi.platform === 'darwin'
|
||||||
const zoomFactor = ref(1)
|
const zoomFactor = ref(1)
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
:class="{ 'bg-zinc-300': selected }"
|
:class="{ 'bg-zinc-300': selected }"
|
||||||
@submit.prevent="
|
@submit.prevent="
|
||||||
() => {
|
() => {
|
||||||
store.renameProfile(profile.id, nameInput)
|
// store.renameProfile(profile.id, nameInput)
|
||||||
|
console.log('Renaming profile to:', nameInput)
|
||||||
|
console.log('NOT IMPLEMENTED YET!')
|
||||||
|
// TODO: Implement deviceStore.renameProfile
|
||||||
editing = false
|
editing = false
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -136,9 +139,6 @@
|
|||||||
import { Check, Copy, PenLine, Trash2, X, GripHorizontal } from 'lucide-vue-next'
|
import { Check, Copy, PenLine, Trash2, X, GripHorizontal } from 'lucide-vue-next'
|
||||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||||
import { nextTick, ref } from 'vue'
|
import { nextTick, ref } from 'vue'
|
||||||
import { useStore } from '@renderer/store'
|
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
defineEmits(['select', 'duplicate', 'delete'])
|
defineEmits(['select', 'duplicate', 'delete'])
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="font-heading flex h-full min-w-0 flex-1 items-center"
|
class="font-heading flex h-full min-w-0 flex-1 items-center"
|
||||||
@click="showProfileConfig = store.selectedProfile && !showProfileConfig"
|
@click="showProfileConfig = deviceStore.currentProfileName && !showProfileConfig"
|
||||||
>
|
>
|
||||||
<component :is="showProfileConfig ? ArrowLeft : List" class="mr-1 h-full w-5 shrink-0" />
|
<component :is="showProfileConfig ? ArrowLeft : List" class="mr-1 h-full w-5 shrink-0" />
|
||||||
<ScrambleText
|
<ScrambleText
|
||||||
:text="showProfileConfig ? store.selectedProfile?.name : $t('profiles.title')"
|
:text="showProfileConfig ? deviceStore.currentProfileName : $t('profiles.title')"
|
||||||
class="min-w-0 overflow-hidden text-ellipsis"
|
class="min-w-0 overflow-hidden text-ellipsis"
|
||||||
/>
|
/>
|
||||||
<ScrambleText
|
<ScrambleText
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
scramble-on-mount
|
scramble-on-mount
|
||||||
:fill-interval="20"
|
:fill-interval="20"
|
||||||
:delay="500"
|
:delay="500"
|
||||||
:text="`(${store.profiles.length}/${maxProfiles})`"
|
:text="`(${deviceStore.profileNames.length}/${maxProfiles})`"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<button
|
<button
|
||||||
v-if="!showProfileConfig"
|
v-if="!showProfileConfig"
|
||||||
class="flex aspect-square h-8 items-center justify-center rounded-lg border border-zinc-100 bg-zinc-300 text-black hover:bg-zinc-200"
|
class="flex aspect-square h-8 items-center justify-center rounded-lg border border-zinc-100 bg-zinc-300 text-black hover:bg-zinc-200"
|
||||||
@click="store.addProfile"
|
@click="console.log('Add profile not implemented!')"
|
||||||
>
|
>
|
||||||
<Plus class="h-4" />
|
<Plus class="h-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -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="store.profiles.length === 0">
|
<div v-if="deviceStore.profileNames.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
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
key="categoriesDraggable"
|
key="categoriesDraggable"
|
||||||
group="profileCategories"
|
group="profileCategories"
|
||||||
item-key="name"
|
item-key="name"
|
||||||
:list="store.profileCategories"
|
:list="deviceStore.profileCategories"
|
||||||
v-bind="dragOptions"
|
v-bind="dragOptions"
|
||||||
handle=".category-handle"
|
handle=".category-handle"
|
||||||
@start="drag = true"
|
@start="drag = true"
|
||||||
@@ -105,15 +105,15 @@
|
|||||||
<ProfileButton
|
<ProfileButton
|
||||||
:profile="dragProfile.element"
|
:profile="dragProfile.element"
|
||||||
:show-hover-buttons="!drag"
|
:show-hover-buttons="!drag"
|
||||||
:selected="store.selectedProfile?.id === dragProfile.element.id"
|
:selected="deviceStore.currentProfileName === dragProfile.element.name"
|
||||||
@select="
|
@select="
|
||||||
() => {
|
() => {
|
||||||
store.selectProfile(dragProfile.element.id)
|
console.log('Select profile not implemented!')
|
||||||
showProfileConfig = true
|
showProfileConfig = true
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@duplicate="store.duplicateProfile(dragProfile.element.id)"
|
@duplicate="console.log('Duplicate profile not implemented!')"
|
||||||
@delete="store.removeProfile(dragProfile.element.id)"
|
@delete="console.log('Delete profile not implemented!')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -149,7 +149,6 @@ import {
|
|||||||
CollapsibleTrigger
|
CollapsibleTrigger
|
||||||
} from '@renderer/components/ui/collapsible'
|
} from '@renderer/components/ui/collapsible'
|
||||||
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
import ScrambleText from '@renderer/components/common/ScrambleText.vue'
|
||||||
import { useStore } from '@renderer/store'
|
|
||||||
import ProfileButton from '@renderer/components/profile/ProfileButton.vue'
|
import ProfileButton from '@renderer/components/profile/ProfileButton.vue'
|
||||||
import ProfileConfig from '@renderer/components/profile/ProfileConfig.vue'
|
import ProfileConfig from '@renderer/components/profile/ProfileConfig.vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
@@ -159,6 +158,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from '@renderer/components/ui/dropdown-menu'
|
} from '@renderer/components/ui/dropdown-menu'
|
||||||
|
import { useDeviceStore } from '@renderer/deviceStore'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
showFilter: {
|
showFilter: {
|
||||||
@@ -167,6 +167,8 @@ defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deviceStore = useDeviceStore()
|
||||||
|
|
||||||
const dragOptions = ref({
|
const dragOptions = ref({
|
||||||
ghostClass: 'ghost',
|
ghostClass: 'ghost',
|
||||||
animation: 150,
|
animation: 150,
|
||||||
@@ -175,7 +177,6 @@ const dragOptions = ref({
|
|||||||
|
|
||||||
const maxProfiles = 32
|
const maxProfiles = 32
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
const collapse = ref({})
|
const collapse = ref({})
|
||||||
|
|
||||||
const showProfileConfig = ref(false)
|
const showProfileConfig = ref(false)
|
||||||
@@ -228,7 +229,8 @@ const onCategoryDrop = (event) => {
|
|||||||
const category = event.moved.element
|
const category = event.moved.element
|
||||||
const oldIndex = event.moved.oldIndex
|
const oldIndex = event.moved.oldIndex
|
||||||
const newIndex = event.moved.newIndex
|
const newIndex = event.moved.newIndex
|
||||||
store.moveProfileCategory(category.name, oldIndex, newIndex)
|
// store.moveProfileCategory(category.name, oldIndex, newIndex)
|
||||||
|
console.log('Move category not implemented!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,12 +239,14 @@ const onProfileDrop = (event, categoryIndex) => {
|
|||||||
const profile = event.moved.element
|
const profile = event.moved.element
|
||||||
const oldIndex = event.moved.oldIndex
|
const oldIndex = event.moved.oldIndex
|
||||||
const newIndex = event.moved.newIndex
|
const newIndex = event.moved.newIndex
|
||||||
store.moveProfile(profile.id, oldIndex, newIndex)
|
// store.moveProfile(profile.id, oldIndex, newIndex)
|
||||||
|
console.log('Move profile not implemented!')
|
||||||
}
|
}
|
||||||
if (event.added) {
|
if (event.added) {
|
||||||
const profile = event.added.element
|
const profile = event.added.element
|
||||||
const newIndex = event.added.newIndex
|
const newIndex = event.added.newIndex
|
||||||
store.changeProfileCategory(profile.id, categoryIndex, newIndex)
|
// store.changeProfileCategory(profile.id, categoryIndex, newIndex)
|
||||||
|
console.log('Change profile category not implemented!')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
export const useMessageHandlers = function (store) {
|
|
||||||
return {
|
|
||||||
handle_message: (jsonstr) => {
|
|
||||||
const message = JSON.parse(jsonstr)
|
|
||||||
if (message.hasOwnProperty('event')) {
|
|
||||||
this.handle_event_message(message)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('p')) {
|
|
||||||
this.handle_profile_message(message)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('profiles')) {
|
|
||||||
this.handle_profiles_message(message)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('settings')) {
|
|
||||||
this.handle_settings_message(message)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('error')) {
|
|
||||||
this.handle_error_message(message)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('debug')) {
|
|
||||||
console.log('Device: DEBUG: ', message.debug)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('idle')) {
|
|
||||||
// TODO remove
|
|
||||||
console.log('Device present, idle since: ', message.idle)
|
|
||||||
}
|
|
||||||
// Moved these two up from handle_event_message
|
|
||||||
// Event messages don't include the event key atm, so handle_event_message was never called
|
|
||||||
if (message.hasOwnProperty('ks')) {
|
|
||||||
store.update_keystates(message.ks)
|
|
||||||
}
|
|
||||||
if (message.hasOwnProperty('a')) {
|
|
||||||
store.update_knob_position(message.t, message.a, message.v)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handle_event_message: (event) => {},
|
|
||||||
|
|
||||||
handle_profile_message: (profile) => {
|
|
||||||
// TODO update profile
|
|
||||||
},
|
|
||||||
|
|
||||||
handle_profiles_message: (profiles) => {
|
|
||||||
// TODO update profiles
|
|
||||||
},
|
|
||||||
|
|
||||||
handle_settings_message: (settings) => {
|
|
||||||
if (settings.hasOwnProperty('deviceName')) {
|
|
||||||
store.update_device_name(settings.deviceName)
|
|
||||||
}
|
|
||||||
// TODO update other settings - maybe this should be in a separate store? Or in the main store, but move these ifs to a action method in the store?
|
|
||||||
},
|
|
||||||
|
|
||||||
handle_error_message: (error) => {
|
|
||||||
console.error('Device: ERROR: ', error.error)
|
|
||||||
// TODO show/handle error in UI?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
87
src/renderer/src/deviceStore.ts
Normal file
87
src/renderer/src/deviceStore.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
interface Profile {
|
||||||
|
version: number
|
||||||
|
name: string
|
||||||
|
desc: string
|
||||||
|
profileTag: string
|
||||||
|
profileType: number
|
||||||
|
profile_type: number
|
||||||
|
position_num: number
|
||||||
|
attract_distance: number
|
||||||
|
feedback_strength: number
|
||||||
|
bounce_strength: number
|
||||||
|
haptic_click_strength: number
|
||||||
|
output_ramp: number
|
||||||
|
ledEnable: boolean
|
||||||
|
ledBrightness: number
|
||||||
|
ledMode: number
|
||||||
|
pointer: number
|
||||||
|
primary: number
|
||||||
|
secondary: number
|
||||||
|
buttonAIdle: number
|
||||||
|
buttonBIdle: number
|
||||||
|
buttonCIdle: number
|
||||||
|
buttonDIdle: number
|
||||||
|
buttonAPress: number
|
||||||
|
buttonBPress: number
|
||||||
|
buttonCPress: number
|
||||||
|
buttonDPress: number
|
||||||
|
internalMacro: boolean
|
||||||
|
knobMap: string
|
||||||
|
switchA: string
|
||||||
|
switchB: string
|
||||||
|
switchC: string
|
||||||
|
switchD: string
|
||||||
|
guiEnable: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const { nanoSerialApi } = window
|
||||||
|
|
||||||
|
export const useDeviceStore = defineStore('device', {
|
||||||
|
state: () => ({
|
||||||
|
attachedDeviceIds: [] as string[], // list of attached device ids
|
||||||
|
currentDeviceId: null as string | null, // id of the current device
|
||||||
|
profileNames: [] as string[], // list of profile names
|
||||||
|
profiles: {} as Record<string, Profile>, // map of profiles by name
|
||||||
|
currentProfileName: null as string | null, // name of the current profile
|
||||||
|
orientation: 0 as number, // orientation of the device
|
||||||
|
dirtyState: false as boolean, // whether the device state has changed
|
||||||
|
angle: 0 as number, // angle of the knob
|
||||||
|
turns: 0 as number, // number of turns of the knob
|
||||||
|
velocity: 0 as number, // velocity of the knob
|
||||||
|
keyStates: {} as Record<string, boolean> // state of the keys (true if pressed)
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
connected: (state) => state.currentDeviceId !== null,
|
||||||
|
currentProfile: (state) =>
|
||||||
|
state.currentProfileName ? state.profiles[state.currentProfileName] : null
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
setAttachedDeviceIds(deviceIds: string[]) {
|
||||||
|
this.attachedDeviceIds = deviceIds
|
||||||
|
},
|
||||||
|
setConnected(connected: boolean) {
|
||||||
|
// TODO: This is here for compatibility, but it should be removed
|
||||||
|
// Real connect calls would need to know the last device id
|
||||||
|
// Maybe that should be stored here
|
||||||
|
// Then connect connects to the last device id of falls back to the first
|
||||||
|
},
|
||||||
|
setCurrentProfile(profileName: string, updateDevice: boolean = true) {
|
||||||
|
this.currentProfileName = profileName
|
||||||
|
if (updateDevice) {
|
||||||
|
nanoSerialApi.send(this.currentDeviceId!, JSON.stringify({ current: profileName }))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setOrientation(orientation: number, updateDevice: boolean = true) {
|
||||||
|
this.orientation = orientation
|
||||||
|
if (updateDevice) {
|
||||||
|
// TODO: send orientation to device
|
||||||
|
console.log('No orientation API message yet! Orientation:', orientation)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cycleOrientation() {
|
||||||
|
this.setOrientation((this.orientation + 90) % 360)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -4,7 +4,7 @@ import { createApp } from 'vue'
|
|||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
import { pinia } from '@renderer/store'
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
import en from '@renderer/lang/en.json'
|
import en from '@renderer/lang/en.json'
|
||||||
|
|
||||||
@@ -15,14 +15,16 @@ const i18n = createI18n({
|
|||||||
messages: { en: en }
|
messages: { en: en }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
// TODO remove this
|
// TODO remove this
|
||||||
window.nanoSerialApi.on_event('*', (eventid, deviceid, ...data) => {
|
// window.nanoSerialApi.on_event('*', (eventid, deviceid, ...data) => {
|
||||||
console.log('Event on window ', eventid, deviceid, data)
|
// console.log('Event on window ', eventid, deviceid, data)
|
||||||
})
|
// })
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
import { createPinia, defineStore } from 'pinia'
|
|
||||||
// import schema from '@renderer/data/profileSchema.json' // see below
|
|
||||||
// import Ajv from 'ajv' // see below
|
|
||||||
import WIP from '@renderer/components/WIP.vue'
|
|
||||||
import KnobFeedbackConfig from '@renderer/components/config/knob/KnobFeedbackConfig.vue'
|
|
||||||
import KnobLightConfig from '@renderer/components/config/knob/KnobLightConfig.vue'
|
|
||||||
import KeyLightConfig from '@renderer/components/config/keys/KeyLightConfig.vue'
|
|
||||||
import KnobMappingConfig from '@renderer/components/config/knob/KnobMappingConfig.vue'
|
|
||||||
import KeyMappingConfig from '@renderer/components/config/keys/KeyMappingConfig.vue'
|
|
||||||
import { shallowRef } from 'vue'
|
|
||||||
import mockData from '@renderer/data/nanoConfig.json'
|
|
||||||
|
|
||||||
// const ajv = new Ajv() // see below
|
|
||||||
|
|
||||||
const { nanoSerialApi } = window
|
|
||||||
|
|
||||||
// TODO: Define Profile type
|
|
||||||
|
|
||||||
// TODO: Define Device type
|
|
||||||
|
|
||||||
// TODO: Define Key type
|
|
||||||
|
|
||||||
// TODO: Define ProfileCategory type
|
|
||||||
|
|
||||||
export const useStore = defineStore('main', {
|
|
||||||
state: () => {
|
|
||||||
return {
|
|
||||||
devices: {},
|
|
||||||
connected: false, // TODO make into getter
|
|
||||||
connectedId: null,
|
|
||||||
profileCategories: [],
|
|
||||||
selectedProfileId: null,
|
|
||||||
connectedDevices: ['test1', 'test2'],
|
|
||||||
selectedFeature: 'knob',
|
|
||||||
selectedKey: 'a',
|
|
||||||
currentConfigPage: 'mapping',
|
|
||||||
configPages: {
|
|
||||||
knob: {
|
|
||||||
mapping: {
|
|
||||||
titleKey: 'config_options.mapping_configuration.title',
|
|
||||||
component: shallowRef(KnobMappingConfig)
|
|
||||||
},
|
|
||||||
feedback: {
|
|
||||||
titleKey: 'config_options.feedback_designer.title',
|
|
||||||
component: shallowRef(KnobFeedbackConfig)
|
|
||||||
},
|
|
||||||
lighting: {
|
|
||||||
titleKey: 'config_options.light_designer.title',
|
|
||||||
component: shallowRef(KnobLightConfig)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
key: {
|
|
||||||
mapping: {
|
|
||||||
titleKey: 'config_options.mapping_configuration.title',
|
|
||||||
component: shallowRef(KeyMappingConfig)
|
|
||||||
},
|
|
||||||
lighting: {
|
|
||||||
titleKey: 'config_options.light_designer.title',
|
|
||||||
component: shallowRef(KeyLightConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
previewDeviceModel: localStorage.getItem('previewDeviceModel') || 'nanoOne',
|
|
||||||
screenOrientation: 90,
|
|
||||||
|
|
||||||
// device state as received from the device
|
|
||||||
keyState: 'abcd',
|
|
||||||
turns: 0,
|
|
||||||
angle: 0,
|
|
||||||
velocity: 0,
|
|
||||||
last_event: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
profiles: (state) => state.profileCategories.flatMap((c) => c.profiles),
|
|
||||||
profileIds: (state) => state.profiles.map((p) => p.id),
|
|
||||||
selectedProfileCategory: (state) =>
|
|
||||||
state.profileCategories.find((c) => c.profiles.find((p) => p.id === state.selectedProfileId)),
|
|
||||||
selectedProfile: (state) => state.profiles.find((p) => p.id === state.selectedProfileId),
|
|
||||||
currentConfigComponent: (state) =>
|
|
||||||
state.configPages[state.selectedFeature][state.currentConfigPage]?.component || WIP,
|
|
||||||
currentConfigPages: (state) => state.configPages[state.selectedFeature] || {},
|
|
||||||
multipleDevicesConnected: (state) => state.connectedDevices.length > 1,
|
|
||||||
numAttachedDevices: (state) => Object.keys(state.devices).length
|
|
||||||
// connected: (state) => state.connectedId !== null,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
selectProfile(id) {
|
|
||||||
if (!this.profileIds.includes(id)) return false
|
|
||||||
this.selectedProfileId = id
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
addProfile(profile, categoryIndex, newIndex) {
|
|
||||||
const category = this.profileCategories[categoryIndex]
|
|
||||||
category.profiles.splice(newIndex, 0, profile)
|
|
||||||
},
|
|
||||||
removeProfile(profileId) {
|
|
||||||
const category = this.profileCategories.find((c) =>
|
|
||||||
c.profiles.find((p) => p.id === profileId)
|
|
||||||
)
|
|
||||||
const index = category.profiles.findIndex((p) => p.id === profileId)
|
|
||||||
category.profiles.splice(index, 1)
|
|
||||||
},
|
|
||||||
duplicateProfile(profileId) {
|
|
||||||
const profile = this.profiles.find((p) => p.id === profileId)
|
|
||||||
const newProfile = JSON.parse(JSON.stringify(profile))
|
|
||||||
newProfile.id = this.newProfileId(profile.id)
|
|
||||||
newProfile.name = this.newProfileName(profile.name)
|
|
||||||
const category = this.profileCategories.find((c) =>
|
|
||||||
c.profiles.find((p) => p.id === profileId)
|
|
||||||
)
|
|
||||||
const index = category.profiles.findIndex((p) => p.id === profileId)
|
|
||||||
category.profiles.splice(index + 1, 0, newProfile)
|
|
||||||
this.selectProfile(newProfile.id)
|
|
||||||
},
|
|
||||||
moveProfile(profileId, oldIndex, newIndex) {
|
|
||||||
// Find the profile category, then swap the profiles at the old and new indices
|
|
||||||
const category = this.profileCategories.find((c) =>
|
|
||||||
c.profiles.find((p) => p.id === profileId)
|
|
||||||
)
|
|
||||||
const tmpProfile = category.profiles[newIndex]
|
|
||||||
category.profiles[newIndex] = category.profiles[oldIndex]
|
|
||||||
category.profiles[newIndex] = tmpProfile
|
|
||||||
},
|
|
||||||
moveProfileCategory(categoryName, oldIndex, newIndex) {
|
|
||||||
const tmpCategory = this.profileCategories[newIndex]
|
|
||||||
this.profileCategories[newIndex] = this.profileCategories[oldIndex]
|
|
||||||
this.profileCategories[newIndex] = tmpCategory
|
|
||||||
},
|
|
||||||
changeProfileCategory(profileId, newCategoryIndex, newIndex) {
|
|
||||||
const profile = this.profiles.find((p) => p.id === profileId)
|
|
||||||
const oldCategory = this.profileCategories.find((c) =>
|
|
||||||
c.profiles.find((p) => p.id === profileId)
|
|
||||||
)
|
|
||||||
const newCategory = this.profileCategories[newCategoryIndex]
|
|
||||||
oldCategory.profiles = oldCategory.profiles.filter((p) => p.id !== profileId)
|
|
||||||
newCategory.profiles.splice(newIndex, 0, profile)
|
|
||||||
},
|
|
||||||
renameProfile(profileId, newName) {
|
|
||||||
const profile = this.profiles.find((p) => p.id === profileId)
|
|
||||||
profile.name = newName
|
|
||||||
},
|
|
||||||
fetchProfiles() {
|
|
||||||
const categories = mockData.categories
|
|
||||||
console.log(categories)
|
|
||||||
const ids = new Set()
|
|
||||||
// const validate = ajv.compile(schema) // see below
|
|
||||||
this.$patch({
|
|
||||||
profileCategories: categories.map((category) => ({
|
|
||||||
name: category.name,
|
|
||||||
profiles: category.profiles.filter((profile) => {
|
|
||||||
// Ajv validation requires unsafe-eval CSP, let's not do that
|
|
||||||
// TODO: Remove ajv validation completely or compile schema at build time
|
|
||||||
// if (!validate(profile)) {
|
|
||||||
// console.error('Failed to validate profile: ' + profile.name, validate.errors)
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
if (ids.has(profile.id)) {
|
|
||||||
console.error('Duplicate profile id: ' + profile.id + ' for profile: ' + profile.name)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ids.add(profile.id)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
selectedProfileId: categories[0]?.profiles[0]?.id || null
|
|
||||||
})
|
|
||||||
},
|
|
||||||
newProfileName(originalName = '') {
|
|
||||||
let name = originalName
|
|
||||||
let i = 1
|
|
||||||
while (this.profiles.find((p) => p.name === name)) {
|
|
||||||
name = `${originalName} (${i++})`
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
},
|
|
||||||
newProfileId(originalId = '') {
|
|
||||||
let id = originalId
|
|
||||||
if (originalId) {
|
|
||||||
do {
|
|
||||||
id = Math.floor((parseInt(id) + 1) % 9999)
|
|
||||||
.toString()
|
|
||||||
.padStart(4, '0')
|
|
||||||
} while (this.profileIds.includes(id))
|
|
||||||
} else {
|
|
||||||
do {
|
|
||||||
id = Math.floor(Math.random() * 9999)
|
|
||||||
.toString()
|
|
||||||
.padStart(4, '0')
|
|
||||||
} while (this.profileIds.includes(id))
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
},
|
|
||||||
selectConfigFeature(feature) {
|
|
||||||
this.selectedFeature = feature
|
|
||||||
if (!this.currentConfigPages[this.currentConfigPage]) this.setCurrentConfigPage('mapping')
|
|
||||||
},
|
|
||||||
selectKey(key) {
|
|
||||||
this.selectedKey = key
|
|
||||||
this.selectConfigFeature('key')
|
|
||||||
},
|
|
||||||
setCurrentConfigPage(page) {
|
|
||||||
this.currentConfigPage = page
|
|
||||||
},
|
|
||||||
setConnected(connected) {
|
|
||||||
this.connected = connected
|
|
||||||
},
|
|
||||||
switchPreviewDeviceModel() {
|
|
||||||
this.previewDeviceModel = this.previewDeviceModel === 'nanoOne' ? 'nanoZero' : 'nanoOne'
|
|
||||||
localStorage.setItem('previewDeviceModel', this.previewDeviceModel)
|
|
||||||
},
|
|
||||||
cycleScreenOrientation() {
|
|
||||||
this.screenOrientation = (this.screenOrientation + 90) % 360
|
|
||||||
},
|
|
||||||
setKeyDefaultColor(color) {
|
|
||||||
// this.selectedProfile.keys[this.selectedKey].default = color
|
|
||||||
const props = {}
|
|
||||||
props[`button${this.selectedKey.toUpperCase()}Idle`] = color.rgbNumber()
|
|
||||||
nanoSerialApi.send(this.connectedId, { p: { name: 'Default Profile', ...props } })
|
|
||||||
},
|
|
||||||
setKeyPressedColor(color) {
|
|
||||||
// this.selectedProfile.keys[this.selectedKey].pressed = color
|
|
||||||
const props = {}
|
|
||||||
props[`button${this.selectedKey.toUpperCase()}Press`] = color.rgbNumber()
|
|
||||||
nanoSerialApi.send(this.connectedId, { p: { name: 'Default Profile', ...props } })
|
|
||||||
},
|
|
||||||
|
|
||||||
// devices, device attachment, connection, and disconnection
|
|
||||||
init_devices(ids) {
|
|
||||||
console.log('Initializing devices: ', ids)
|
|
||||||
for (const id of ids) this.update_devices(id, true)
|
|
||||||
if (Object.keys(this.devices).length == 1) {
|
|
||||||
// TODO auto-connect to the device
|
|
||||||
const deviceid = Object.keys(this.devices)[0]
|
|
||||||
console.log('Auto-connecting to device ', deviceid)
|
|
||||||
window.nanoSerialApi.connect(deviceid)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update_devices(deviceid, attached) {
|
|
||||||
if (attached) {
|
|
||||||
if (!this.devices.hasOwnProperty(deviceid))
|
|
||||||
this.devices[deviceid] = { serialNumber: deviceid, connected: false }
|
|
||||||
} else {
|
|
||||||
if (this.devices.hasOwnProperty(deviceid)) delete this.devices[deviceid] // TODO maybe mark as detached instead of deleting? then we can remember its name, etc...
|
|
||||||
}
|
|
||||||
},
|
|
||||||
device_attached(deviceid) {
|
|
||||||
this.update_devices(deviceid, true)
|
|
||||||
if (Object.keys(this.devices).length == 1) {
|
|
||||||
// TODO auto-connect to the device
|
|
||||||
console.log('Auto-connecting to device ', deviceid)
|
|
||||||
window.nanoSerialApi.connect(deviceid)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
device_detached(deviceid) {
|
|
||||||
if (this.devices[deviceid].connected) {
|
|
||||||
// detached event arrived before disconnected event?
|
|
||||||
this.devices[deviceid].connected = false
|
|
||||||
this.connected = false
|
|
||||||
}
|
|
||||||
this.update_devices(deviceid, false)
|
|
||||||
},
|
|
||||||
device_connected(deviceid) {
|
|
||||||
this.devices[deviceid].connected = true
|
|
||||||
this.connected = true
|
|
||||||
this.connectedId = deviceid
|
|
||||||
// TODO load profiles from device
|
|
||||||
// nanoSerialApi.send(deviceid, { profiles: "#all" }) // request profiles
|
|
||||||
// "Default Profile", for now, is the only profile after the device
|
|
||||||
// starts up, so it is also the current (eg. 'selected') profile
|
|
||||||
// nanoSerialApi.send(deviceid, { p: "Default Profile" }) // request Default Profile
|
|
||||||
|
|
||||||
// TODO maybe you want to request all the profiles right now?
|
|
||||||
// or only on demand?
|
|
||||||
},
|
|
||||||
device_disconnected(deviceid) {
|
|
||||||
this.devices[deviceid].connected = false
|
|
||||||
this.connected = false
|
|
||||||
this.connectedId = null
|
|
||||||
// TODO switch UI to disconnected state
|
|
||||||
},
|
|
||||||
|
|
||||||
// device events
|
|
||||||
update_knob_position(turns, angle, velocity) {
|
|
||||||
this.turns = turns
|
|
||||||
this.angle = angle
|
|
||||||
this.velocity = velocity
|
|
||||||
this.last_event = Date.now()
|
|
||||||
},
|
|
||||||
update_keystate(keystate) {
|
|
||||||
this.keyState = keystate
|
|
||||||
this.last_event = Date.now()
|
|
||||||
},
|
|
||||||
|
|
||||||
// settings changes
|
|
||||||
update_device_name(name) {
|
|
||||||
this.devices[this.connectedId].name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const pinia = createPinia()
|
|
||||||
Reference in New Issue
Block a user