loupedeck-commander 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/VERSION.md +33 -0
- package/common/BaseLoupeDeckHandler.mjs +8 -8
- package/common/touchbuttons.mjs +173 -273
- package/{ExampleDeviceHandler.mjs → example/ExampleDeviceHandler.mjs} +1 -1
- package/icons/bulb_filled.png +0 -0
- package/package.json +1 -1
- package/profile-1.json +36 -99
- /package/{example.mjs → example/example.mjs} +0 -0
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
The loupedeck-commander is helping you using your Loupedeck device more easily, and connect it with custom commands you define with script files.
|
|
6
6
|
|
|
7
7
|
Features:
|
|
8
|
+
|
|
8
9
|
- Reference image files for every state of your button in the touch-display fields
|
|
9
10
|
- Connect commands to your state transitions
|
|
10
11
|
- Tested with following LoupeDeck devices:
|
|
@@ -12,14 +13,17 @@ Features:
|
|
|
12
13
|
- [LoupeDeck CT](https://loupedeck.com/products/loupedeck-ct/)
|
|
13
14
|
|
|
14
15
|
Runs on:
|
|
16
|
+
|
|
15
17
|
- Linux (Tested with Ubuntu 22.04 and Debian 11/12 on x64 & arm32/arm64)
|
|
16
18
|
- Windows (Tested with Windows 10/11 on x64)
|
|
17
19
|
|
|
18
|
-
Small footprint
|
|
20
|
+
Small footprint
|
|
21
|
+
|
|
19
22
|
- Raspberry PI Zero is suitable to run this
|
|
20
23
|
|
|
21
24
|
Please take care about the following:
|
|
22
|
-
|
|
25
|
+
|
|
26
|
+
- LoupeDeck devices after `version 0.2.x` use a serial interface instead of WebSocket.
|
|
23
27
|
When using this library please upgrade your firmware using the [LoupeDeck Software](https://loupedeck.com/downloads/)
|
|
24
28
|
Tested with Firmware version [`version 0.2.5`](https://support.loupedeck.com/f-a-q-support#firmware-connectivity-issues)
|
|
25
29
|
|
|
@@ -27,7 +31,7 @@ Please take care about the following:
|
|
|
27
31
|
|
|
28
32
|
Do the following steps:
|
|
29
33
|
|
|
30
|
-
```
|
|
34
|
+
```bash
|
|
31
35
|
# init your new node package
|
|
32
36
|
npm init
|
|
33
37
|
|
|
@@ -38,10 +42,11 @@ npm install -s loupedeck-commander
|
|
|
38
42
|
Create a new configuration file with at least one profile or copy from here,
|
|
39
43
|
and replace the image references with your own icons (90x90px size):
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
- [config.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/config.json)
|
|
46
|
+
- [profile-1.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/profile-1.json)
|
|
43
47
|
|
|
44
48
|
Create a `index.mjs` file to open up your loupedeck connection:
|
|
49
|
+
|
|
45
50
|
```javascript
|
|
46
51
|
# save as index.mjs
|
|
47
52
|
import { BaseLoupeDeckHandler } from 'loupedeck-commander'
|
|
@@ -85,9 +90,9 @@ sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev l
|
|
|
85
90
|
|
|
86
91
|
The buttons images shown on the different buttons should be prepared with specific resolutions:
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
- Main/Center Touch-Area: 90px x 90px per Button
|
|
94
|
+
- Left/Right Touch-Area: 90px x 270px per Button
|
|
95
|
+
- LoupeDeck CT Knob Touch-Area: 240px x 240px Button
|
|
91
96
|
|
|
92
97
|
if you already have suitable icons with the right aspect ratio, you could resize them with imagemagick/mogrify:
|
|
93
98
|
|
package/VERSION.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Versions
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
1. Updated button rendering strategy:
|
|
6
|
+
- Render Background Color - per Button State
|
|
7
|
+
- Render Image on Top (if available), works well with transparent images
|
|
8
|
+
|
|
9
|
+
2. Updated configuration strategy:
|
|
10
|
+
- Keep all state related attributes per button-state also in config:
|
|
11
|
+
example:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
"states": {
|
|
15
|
+
"off": {
|
|
16
|
+
"color": "#aaaaaa",
|
|
17
|
+
"image": "icons/bulb.png",
|
|
18
|
+
"cmd": "echo \"{id} {state}\""
|
|
19
|
+
},
|
|
20
|
+
"on": {
|
|
21
|
+
"color": "#11ff11",
|
|
22
|
+
"image": "icons/bulb.png",
|
|
23
|
+
"cmd": "echo \"{id} {state}\""
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 1.0.1
|
|
28
|
+
|
|
29
|
+
bugfixes and documentation
|
|
30
|
+
|
|
31
|
+
## 1.0.0
|
|
32
|
+
|
|
33
|
+
first public release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pkg from 'loupedeck'
|
|
2
2
|
import { ApplicationConfig } from './ApplicationConfig.mjs'
|
|
3
|
-
import {
|
|
3
|
+
import { ButtonField } from './touchbuttons.mjs'
|
|
4
4
|
const { discover, HAPTIC } = pkg
|
|
5
5
|
|
|
6
6
|
export class BaseLoupeDeckHandler {
|
|
@@ -76,15 +76,15 @@ export class BaseLoupeDeckHandler {
|
|
|
76
76
|
const dKnob = this.device.displays.knob
|
|
77
77
|
|
|
78
78
|
const profile = this.getCurrentProfile()
|
|
79
|
-
this.screens.center = new
|
|
80
|
-
this.screens.left = new
|
|
81
|
-
this.screens.right = new
|
|
79
|
+
this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center)
|
|
80
|
+
this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left)
|
|
81
|
+
this.screens.right = new ButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right)
|
|
82
82
|
// knob Display is only available in the CT-version - not with live:
|
|
83
83
|
if (dKnob) {
|
|
84
|
-
this.screens.knob = new
|
|
84
|
+
this.screens.knob = new ButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob)
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
this.buttons = new
|
|
87
|
+
this.buttons = new ButtonField('buttons', 1, 1, 0, 0, profile.buttons)
|
|
88
88
|
|
|
89
89
|
await this.screens.center.load()
|
|
90
90
|
await this.screens.right.load()
|
|
@@ -155,7 +155,7 @@ export class BaseLoupeDeckHandler {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
async onTouchMove (event) {
|
|
158
|
-
|
|
158
|
+
let ok = false
|
|
159
159
|
const changedTouches = event.changedTouches
|
|
160
160
|
for (let i = 0; i < changedTouches.length; i++) {
|
|
161
161
|
const x = changedTouches[i].x
|
|
@@ -165,7 +165,7 @@ export class BaseLoupeDeckHandler {
|
|
|
165
165
|
if (id === undefined) {
|
|
166
166
|
id = 0 // for left/right
|
|
167
167
|
}
|
|
168
|
-
|
|
168
|
+
ok = await this.screens[screen].touchmove(id, x, y)
|
|
169
169
|
}
|
|
170
170
|
return ok
|
|
171
171
|
}
|
package/common/touchbuttons.mjs
CHANGED
|
@@ -3,14 +3,6 @@ import { sh } from './cmd-executer.mjs'
|
|
|
3
3
|
import format from 'string-template'
|
|
4
4
|
import { calcDelta } from './utils.mjs'
|
|
5
5
|
|
|
6
|
-
const ButtonStyle = {
|
|
7
|
-
NONE: 0,
|
|
8
|
-
IMAGE: 1,
|
|
9
|
-
COLOR: 2,
|
|
10
|
-
TEXT: 3,
|
|
11
|
-
PHYSICAL: 3
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
export const ButtonIndex = {
|
|
15
7
|
BUTN_0: 0,
|
|
16
8
|
BUTN_1: 1,
|
|
@@ -29,35 +21,58 @@ const ButtonType = {
|
|
|
29
21
|
PUSH: 'PUSH'
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
export class
|
|
33
|
-
#buttons =
|
|
24
|
+
export class ButtonField {
|
|
25
|
+
#buttons = {}
|
|
26
|
+
#screen
|
|
34
27
|
width = 0
|
|
35
28
|
height = 0
|
|
36
29
|
#rows = 0
|
|
37
30
|
#columns = 0
|
|
31
|
+
#keys = []
|
|
32
|
+
#type
|
|
38
33
|
#name
|
|
39
34
|
constructor (name, rows, columns, width, height, data) {
|
|
40
|
-
console.info(`
|
|
35
|
+
console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
|
|
41
36
|
this.#name = name
|
|
42
37
|
this.width = width
|
|
43
38
|
this.height = height
|
|
44
39
|
this.#rows = rows
|
|
45
40
|
this.#columns = columns
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
this.#screen = this.width > 0 && this.height > 0
|
|
42
|
+
this.#type = 'button'
|
|
43
|
+
if (this.#screen) { this.#type = 'touch' }
|
|
44
|
+
|
|
45
|
+
const keys = Object.keys(data)
|
|
46
|
+
for (let i = 0; i < keys.length; i++) {
|
|
47
|
+
const key = keys[i]
|
|
48
|
+
const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key])
|
|
49
|
+
this.#buttons[key] = tb
|
|
49
50
|
}
|
|
51
|
+
|
|
52
|
+
this.#keys = keys
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
async draw (device) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
this.#buttons[i].draw(row, column, ctx)
|
|
56
|
+
if (!this.#screen) {
|
|
57
|
+
// physical buttons:
|
|
58
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
59
|
+
const key = this.#keys[i]
|
|
60
|
+
this.#buttons[key].drawPhysical(device, key)
|
|
59
61
|
}
|
|
60
|
-
}
|
|
62
|
+
} else {
|
|
63
|
+
// screen:
|
|
64
|
+
device.drawScreen(this.#name, ctx => {
|
|
65
|
+
ctx.globalCompositeOperation = 'source-atop'
|
|
66
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
67
|
+
const key = this.#keys[i]
|
|
68
|
+
const iValue = parseInt(key, 10)
|
|
69
|
+
const row = Math.floor(iValue / device.columns)
|
|
70
|
+
const column = iValue % device.columns
|
|
71
|
+
|
|
72
|
+
this.#buttons[key].draw(row, column, ctx)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
setIntState (id, val) {
|
|
@@ -65,127 +80,94 @@ export class TouchButtonField {
|
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
async load () {
|
|
68
|
-
for (let i = 0; i < this.#
|
|
69
|
-
|
|
83
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
84
|
+
const key = this.#keys[i]
|
|
85
|
+
if (isNaN(key)) {
|
|
86
|
+
await this.#buttons[key].load()
|
|
87
|
+
} else {
|
|
88
|
+
const iVal = parseInt(key, 10)
|
|
89
|
+
await this.#buttons[iVal].load()
|
|
90
|
+
}
|
|
70
91
|
}
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
async pressed (id) {
|
|
74
|
-
|
|
75
|
-
await this.#buttons[id].pressed()
|
|
95
|
+
this.checkAndCreateButton(id)
|
|
96
|
+
const result = await this.#buttons[id].pressed()
|
|
97
|
+
if (!result) {
|
|
98
|
+
console.info(`pressed ${this.#type} ${id}`)
|
|
99
|
+
}
|
|
100
|
+
return result
|
|
76
101
|
}
|
|
77
102
|
|
|
78
103
|
async released (id) {
|
|
79
|
-
// console.info(`released ${id}`)
|
|
80
104
|
const result = await this.#buttons[id].released()
|
|
81
105
|
if (result) {
|
|
82
106
|
// disable all other buttons of the group, if this one had been activated:
|
|
83
|
-
for (let i = 0; i < this.#
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
107
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
108
|
+
let key = this.#keys[i]
|
|
109
|
+
if (!isNaN(key)) { key = parseInt(key, 10) }
|
|
110
|
+
if (id === key) { continue }
|
|
111
|
+
if (this.#buttons[key].group === this.#buttons[id].group) {
|
|
112
|
+
this.#buttons[key].setState(0)
|
|
87
113
|
}
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export class PhysicalButtonField {
|
|
95
|
-
#buttons = {}
|
|
96
|
-
#name
|
|
97
|
-
constructor (name, data) {
|
|
98
|
-
console.info(`PhysicalButtonField ${name.padEnd(10, ' ')} `)
|
|
99
|
-
this.#name = name
|
|
100
|
-
const keys = Object.keys(data)
|
|
101
|
-
for (let i = 0; i < keys.length; i++) {
|
|
102
|
-
const key = keys[i]
|
|
103
|
-
const tb = new TouchButton(key, 1, 1, data[key])
|
|
104
|
-
this.#buttons[key] = tb
|
|
116
|
+
if (!result) {
|
|
117
|
+
console.info(`released ${this.#type} ${id}`)
|
|
105
118
|
}
|
|
119
|
+
return result
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
async
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
122
|
+
async rotated (id, delta) {
|
|
123
|
+
this.checkAndCreateButton(id)
|
|
124
|
+
const result = await this.#buttons[id].rotated(delta)
|
|
125
|
+
if (!result) { console.info(`rotated ${this.#type} ${id} ${delta}`) }
|
|
126
|
+
return result
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
async
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
129
|
+
async touchmove (id, x, y) {
|
|
130
|
+
this.checkAndCreateButton(id)
|
|
131
|
+
const result = await this.#buttons[id].touchmove(x, y)
|
|
132
|
+
if (!result) { console.info(`touchmove ${id} ${x} ${y}`) }
|
|
133
|
+
return result
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
checkAndCreateButton (id) {
|
|
125
137
|
if (!(id in this.#buttons)) {
|
|
126
|
-
const tb = new
|
|
138
|
+
const tb = new Button(id, 1, 1, id)
|
|
127
139
|
this.#buttons[id] = tb
|
|
128
140
|
}
|
|
129
141
|
}
|
|
130
|
-
|
|
131
|
-
setIntState (id, val) {
|
|
132
|
-
this.checkAndCreateButton(id)
|
|
133
|
-
this.#buttons[id].setIntState(val)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async pressed (id) {
|
|
137
|
-
// console.info(`pressed ${id}`)
|
|
138
|
-
this.checkAndCreateButton(id)
|
|
139
|
-
return await this.#buttons[id].pressed()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async released (id) {
|
|
143
|
-
// console.info(`released ${id}`)
|
|
144
|
-
this.checkAndCreateButton(id)
|
|
145
|
-
return await this.#buttons[id].released()
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async rotated (id, delta) {
|
|
149
|
-
// console.info(`rotated ${id} ${delta}`)
|
|
150
|
-
this.checkAndCreateButton(id)
|
|
151
|
-
return await this.#buttons[id].rotated(delta)
|
|
152
|
-
}
|
|
153
142
|
}
|
|
154
143
|
|
|
155
|
-
export class
|
|
144
|
+
export class Button {
|
|
156
145
|
width = 0
|
|
157
146
|
height = 0
|
|
158
|
-
|
|
159
|
-
#type
|
|
160
|
-
|
|
161
|
-
#states = []
|
|
147
|
+
|
|
148
|
+
#type = ButtonType.TOGGLE
|
|
149
|
+
|
|
162
150
|
#min = 0
|
|
163
151
|
#max = 100
|
|
164
152
|
#value = 50
|
|
165
|
-
#data
|
|
166
153
|
#name = undefined
|
|
167
154
|
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
// on: '#110011'
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
#rgbs = {
|
|
175
|
-
off: [0, 0, 0]
|
|
176
|
-
// on: [0, 255, 0]
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
#commands = {
|
|
180
|
-
off: '',
|
|
181
|
-
on: ''
|
|
182
|
-
}
|
|
155
|
+
#index = 0
|
|
156
|
+
#keys
|
|
157
|
+
#states
|
|
183
158
|
|
|
184
159
|
group = ''
|
|
185
160
|
|
|
186
161
|
text = ''
|
|
187
162
|
font = '16px Arial'
|
|
188
163
|
|
|
164
|
+
#x
|
|
165
|
+
#y
|
|
166
|
+
#moveLeft
|
|
167
|
+
#moveRight
|
|
168
|
+
#moveUp
|
|
169
|
+
#moveDown
|
|
170
|
+
|
|
189
171
|
// Timestamp when button was pressed
|
|
190
172
|
timeStampPressed
|
|
191
173
|
// Timestamp when button was released
|
|
@@ -200,29 +182,16 @@ export class TouchButton {
|
|
|
200
182
|
this.width = width
|
|
201
183
|
this.height = height
|
|
202
184
|
this.#index = 0
|
|
203
|
-
this.#style = ButtonStyle.NONE
|
|
204
|
-
this.#type = ButtonType.TOGGLE
|
|
205
|
-
this.#states = Object.keys(this.#rgbs)
|
|
206
185
|
|
|
207
|
-
if (data) {
|
|
186
|
+
if (data && data.states) {
|
|
208
187
|
this.group = data.group
|
|
209
|
-
this.#style = ButtonStyle.PHYSICAL
|
|
210
|
-
if (data.images && Object.keys(data.images).length > 0) {
|
|
211
|
-
this.#style = ButtonStyle.IMAGE
|
|
212
|
-
this.#states = Object.keys(data.images)
|
|
213
|
-
} else if (data.colors && Object.keys(data.colors).length > 0) {
|
|
214
|
-
this.#style = ButtonStyle.COLOR
|
|
215
|
-
this.#states = Object.keys(data.colors)
|
|
216
|
-
} else if (data.rgb && Object.keys(data.rgb).length > 0) {
|
|
217
|
-
this.#states = Object.keys(data.rgb)
|
|
218
|
-
}
|
|
219
|
-
if (data.commands) {
|
|
220
|
-
this.#commands = data.commands
|
|
221
|
-
}
|
|
222
188
|
|
|
189
|
+
this.#states = data.states
|
|
190
|
+
this.#keys = Object.keys(this.#states)
|
|
223
191
|
if (data.type) {
|
|
224
192
|
this.#type = data.type.toUpperCase()
|
|
225
193
|
}
|
|
194
|
+
|
|
226
195
|
if (data.minPressed) {
|
|
227
196
|
this.minPressed = data.minPressed
|
|
228
197
|
}
|
|
@@ -230,12 +199,11 @@ export class TouchButton {
|
|
|
230
199
|
if (data.text) {
|
|
231
200
|
this.text = data.text
|
|
232
201
|
}
|
|
233
|
-
this.#data = data
|
|
234
202
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
203
|
+
if (this.#states === undefined) {
|
|
204
|
+
this.#states = {}
|
|
205
|
+
this.#keys = []
|
|
206
|
+
}
|
|
239
207
|
}
|
|
240
208
|
|
|
241
209
|
setState (index = 0) {
|
|
@@ -243,10 +211,17 @@ export class TouchButton {
|
|
|
243
211
|
}
|
|
244
212
|
|
|
245
213
|
async drawPhysical (device, id) {
|
|
214
|
+
const elem = this.getCurrentElement()
|
|
215
|
+
if (!elem || !elem.color) { return }
|
|
216
|
+
|
|
217
|
+
const r = parseInt(elem.color.slice(1, 3), 16)
|
|
218
|
+
const g = parseInt(elem.color.slice(3, 5), 16)
|
|
219
|
+
const b = parseInt(elem.color.slice(5, 7), 16)
|
|
220
|
+
|
|
246
221
|
try {
|
|
247
222
|
const val = {
|
|
248
223
|
id,
|
|
249
|
-
color:
|
|
224
|
+
color: `rgba(${r}, ${g}, ${b})`
|
|
250
225
|
}
|
|
251
226
|
device.setButtonColor(val)
|
|
252
227
|
} catch (error) {
|
|
@@ -257,160 +232,49 @@ export class TouchButton {
|
|
|
257
232
|
async draw (row, column, ctx) {
|
|
258
233
|
const x = column * this.width
|
|
259
234
|
const y = row * this.height
|
|
260
|
-
switch (this.#style) {
|
|
261
|
-
case ButtonStyle.IMAGE:
|
|
262
|
-
ctx.drawImage(this.getCurrentStateImage(), x, y, this.width, this.height)
|
|
263
|
-
break
|
|
264
|
-
case ButtonStyle.COLOR:
|
|
265
|
-
// Draw a Colored Rectangle
|
|
266
|
-
ctx.fillStyle = this.getCurrentStateFill()
|
|
267
|
-
ctx.fillRect(x, y, this.width, this.height)
|
|
268
|
-
break
|
|
269
|
-
case ButtonStyle.NONE:
|
|
270
|
-
// Draw a Colored Rectangle
|
|
271
|
-
ctx.fillStyle = this.getCurrentStateFill(0)
|
|
272
|
-
ctx.fillRect(x, y, this.width, this.height)
|
|
273
|
-
break
|
|
274
|
-
}
|
|
275
235
|
|
|
276
|
-
|
|
277
|
-
ctx.font = this.font
|
|
278
|
-
ctx.fillText(this.text, x + 10, y - 10)
|
|
279
|
-
}
|
|
236
|
+
const elem = this.getCurrentElement()
|
|
280
237
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
case ButtonStyle.COLOR:
|
|
289
|
-
bLoaded = await this.loadAsColor()
|
|
290
|
-
break
|
|
291
|
-
case ButtonStyle.PHYSICAL:
|
|
292
|
-
bLoaded = await this.loadAsRGB()
|
|
293
|
-
break
|
|
294
|
-
default:
|
|
295
|
-
bLoaded = true
|
|
238
|
+
if (elem) {
|
|
239
|
+
if (elem.color) {
|
|
240
|
+
ctx.fillStyle = elem.color
|
|
241
|
+
ctx.fillRect(x, y, this.width, this.height)
|
|
242
|
+
}
|
|
243
|
+
if (elem.imgBuffer) {
|
|
244
|
+
ctx.drawImage(elem.imgBuffer, x, y, this.width, this.height)
|
|
296
245
|
}
|
|
297
246
|
}
|
|
298
|
-
}
|
|
299
247
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return false
|
|
305
|
-
}
|
|
248
|
+
// ctx.fillStyle = '#000000'
|
|
249
|
+
// ctx.font = this.font
|
|
250
|
+
// ctx.fillText(this.text, x + 10, y - 10)
|
|
251
|
+
}
|
|
306
252
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
253
|
+
async load () {
|
|
254
|
+
for (let i = 0; i < this.#keys.length; i++) {
|
|
255
|
+
const key = this.#keys[i]
|
|
256
|
+
const elem = this.#states[key]
|
|
257
|
+
const file = elem.image
|
|
310
258
|
if (file !== undefined && file !== '') {
|
|
311
259
|
try {
|
|
312
|
-
this.#
|
|
260
|
+
this.#states[key].imgBuffer = await loadImage(file)
|
|
313
261
|
} catch (e) {
|
|
314
262
|
console.error('No such image', file)
|
|
315
|
-
this.#style = ButtonStyle.COLOR
|
|
316
|
-
delete this.#images[key]
|
|
317
263
|
return false
|
|
318
264
|
}
|
|
319
|
-
} else {
|
|
320
|
-
// this is not an image type
|
|
321
265
|
}
|
|
322
266
|
}
|
|
323
|
-
return true
|
|
324
267
|
}
|
|
325
268
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
this.#states = Object.keys(this.#colors)
|
|
330
|
-
return true
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
for (let i = 0; i < this.#states.length; i++) {
|
|
334
|
-
const key = this.#states[i]
|
|
335
|
-
if (this.#data.colors && key in this.#data.colors) {
|
|
336
|
-
const color = this.#data.colors[key]
|
|
337
|
-
if (color !== undefined && color !== '') {
|
|
338
|
-
this.#colors[key] = color
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return true
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async loadAsRGB () {
|
|
346
|
-
if (this.#states.length <= 0) {
|
|
347
|
-
// use default colors
|
|
348
|
-
this.#states = Object.keys(this.#colors)
|
|
349
|
-
return true
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (this.#data.rgb) {
|
|
353
|
-
const keys = Object.keys(this.#data.rgb)
|
|
354
|
-
for (let i = 0; i < keys.length; i++) {
|
|
355
|
-
const key = this.#states[i]
|
|
356
|
-
const rgb = this.#data.rgb[key]
|
|
357
|
-
if (rgb !== undefined && rgb !== '') {
|
|
358
|
-
this.#rgbs[key] = rgb
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return true
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
getCurrentStateImage () {
|
|
367
|
-
const key = this.#states[this.#index]
|
|
368
|
-
return this.#images[key]
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
getCurrentStateFill (forceIndex = -1) {
|
|
372
|
-
let key
|
|
373
|
-
if (forceIndex >= 0) {
|
|
374
|
-
key = this.#states[forceIndex]
|
|
375
|
-
} else {
|
|
376
|
-
key = this.#states[this.#index]
|
|
377
|
-
}
|
|
378
|
-
return this.#colors[key]
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
getCurrentRGB () {
|
|
382
|
-
const key = this.#states[this.#index]
|
|
383
|
-
const rgb = this.#rgbs[key]
|
|
384
|
-
|
|
385
|
-
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
|
|
269
|
+
getCurrentElement () {
|
|
270
|
+
const key = this.#keys[this.#index]
|
|
271
|
+
return this.#states[key]
|
|
386
272
|
}
|
|
387
273
|
|
|
388
274
|
getCurrentText () {
|
|
389
275
|
return this.text
|
|
390
276
|
}
|
|
391
277
|
|
|
392
|
-
getCurrentCommand () {
|
|
393
|
-
const key = this.#states[this.#index]
|
|
394
|
-
const cmd = this.#commands[key]
|
|
395
|
-
|
|
396
|
-
if (!cmd) { return '' }
|
|
397
|
-
// Call an action
|
|
398
|
-
const params = {
|
|
399
|
-
id: this.id,
|
|
400
|
-
state: key,
|
|
401
|
-
min: this.#min,
|
|
402
|
-
max: this.#max,
|
|
403
|
-
value: this.#value,
|
|
404
|
-
text: this.getCurrentText()
|
|
405
|
-
}
|
|
406
|
-
/* const keys = Object.keys(this.#data.config)
|
|
407
|
-
keys.forEach(key => {
|
|
408
|
-
params[key] = this.#data.config[key]
|
|
409
|
-
}) */
|
|
410
|
-
|
|
411
|
-
return format(cmd, params)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
278
|
setIntState (val) {
|
|
415
279
|
this.#index = val
|
|
416
280
|
}
|
|
@@ -418,27 +282,21 @@ export class TouchButton {
|
|
|
418
282
|
pressed () {
|
|
419
283
|
this.timeStampPressed = Date.now()
|
|
420
284
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
break
|
|
424
|
-
default:
|
|
425
|
-
this.#index++
|
|
426
|
-
this.#index %= this.#states.length
|
|
427
|
-
}
|
|
285
|
+
this.#index++
|
|
286
|
+
this.#index %= this.#keys.length
|
|
428
287
|
return true
|
|
429
288
|
}
|
|
430
289
|
|
|
431
290
|
released () {
|
|
291
|
+
if (!this.getCurrentElement()) { return false }
|
|
432
292
|
this.timeStampReleased = Date.now()
|
|
433
293
|
this.timeHold = this.timeStampReleased - this.timeStampPressed
|
|
434
294
|
|
|
435
|
-
if (this.#style === ButtonStyle.NONE) { return }
|
|
436
|
-
|
|
437
295
|
if (this.timeHold < this.minPressed) {
|
|
438
296
|
// Update the State according to the not correct pressed state
|
|
439
297
|
console.log('Did not hold minimum time of ', this.minPressed, 'only', this.timeHold)
|
|
440
298
|
this.#index--
|
|
441
|
-
if (this.#index < 0) { this.#index = this.#
|
|
299
|
+
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
442
300
|
return false
|
|
443
301
|
}
|
|
444
302
|
|
|
@@ -449,7 +307,7 @@ export class TouchButton {
|
|
|
449
307
|
break
|
|
450
308
|
default:
|
|
451
309
|
this.#index--
|
|
452
|
-
if (this.#index < 0) { this.#index = this.#
|
|
310
|
+
if (this.#index < 0) { this.#index = this.#keys.length - 1 }
|
|
453
311
|
|
|
454
312
|
break
|
|
455
313
|
}
|
|
@@ -458,16 +316,58 @@ export class TouchButton {
|
|
|
458
316
|
}
|
|
459
317
|
|
|
460
318
|
async rotated (delta) {
|
|
319
|
+
if (!this.getCurrentElement()) { return false }
|
|
320
|
+
|
|
461
321
|
this.#value = calcDelta(this.#value, delta, this.#max)
|
|
462
322
|
return this.runCommand()
|
|
463
323
|
}
|
|
464
324
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
325
|
+
async touchmove (x, y) {
|
|
326
|
+
// if (!this.getCurrentElement()) { return false }
|
|
327
|
+
|
|
328
|
+
if (x > this.#x) {
|
|
329
|
+
this.#moveRight = true
|
|
330
|
+
this.#moveLeft = false
|
|
331
|
+
} else if (x < this.#x) {
|
|
332
|
+
this.#moveRight = false
|
|
333
|
+
this.#moveLeft = true
|
|
470
334
|
}
|
|
335
|
+
|
|
336
|
+
if (y > this.#y) {
|
|
337
|
+
this.#moveDown = true
|
|
338
|
+
this.#moveUp = false
|
|
339
|
+
} else if (y < this.#y) {
|
|
340
|
+
this.#moveDown = false
|
|
341
|
+
this.#moveUp = true
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.#x = x
|
|
345
|
+
this.#y = y
|
|
346
|
+
// console.log(`d: ${this.#moveDown} r: ${this.#moveRight} `)
|
|
471
347
|
return false
|
|
472
348
|
}
|
|
349
|
+
|
|
350
|
+
runCommand () {
|
|
351
|
+
const elem = this.getCurrentElement()
|
|
352
|
+
if (!elem || !elem.cmd) {
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
// Call an action
|
|
356
|
+
const params = {
|
|
357
|
+
id: this.id,
|
|
358
|
+
state: this.#keys[this.#index],
|
|
359
|
+
min: this.#min,
|
|
360
|
+
max: this.#max,
|
|
361
|
+
value: this.#value,
|
|
362
|
+
text: this.getCurrentText()
|
|
363
|
+
}
|
|
364
|
+
/* const keys = Object.keys(this.#data.config)
|
|
365
|
+
keys.forEach(key => {
|
|
366
|
+
params[key] = this.#data.config[key]
|
|
367
|
+
}) */
|
|
368
|
+
|
|
369
|
+
const cmdFormatted = format(elem.cmd, params)
|
|
370
|
+
|
|
371
|
+
return sh(cmdFormatted)
|
|
372
|
+
}
|
|
473
373
|
}
|
package/icons/bulb_filled.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/profile-1.json
CHANGED
|
@@ -7,120 +7,57 @@
|
|
|
7
7
|
"right": {},
|
|
8
8
|
"center": {
|
|
9
9
|
"0": {
|
|
10
|
-
"
|
|
11
|
-
"off":
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"on":
|
|
10
|
+
"states": {
|
|
11
|
+
"off": {
|
|
12
|
+
"color": "#aaaaaa",
|
|
13
|
+
"image": "icons/home.png",
|
|
14
|
+
"cmd": "echo \"{id} {state}\""
|
|
15
|
+
},
|
|
16
|
+
"on": {
|
|
17
|
+
"color": "#11ff11",
|
|
18
|
+
"image": "icons/home.png",
|
|
19
|
+
"cmd": "echo \"{id} {state}\""
|
|
20
|
+
}
|
|
17
21
|
},
|
|
18
22
|
"group": "kvm1"
|
|
19
23
|
},
|
|
20
24
|
"1": {
|
|
21
|
-
"
|
|
22
|
-
"off":
|
|
23
|
-
|
|
25
|
+
"states": {
|
|
26
|
+
"off": {
|
|
27
|
+
"color": "#aaaaaa",
|
|
28
|
+
"image": "icons/bulb.png",
|
|
29
|
+
"cmd": "echo \"{id} {state}\""
|
|
30
|
+
},
|
|
31
|
+
"on": {
|
|
32
|
+
"color": "#11ff11",
|
|
33
|
+
"image": "icons/bulb.png",
|
|
34
|
+
"cmd": "echo \"{id} {state}\""
|
|
35
|
+
}
|
|
24
36
|
},
|
|
25
37
|
"group": "kvm1"
|
|
26
|
-
},
|
|
27
|
-
"2": {
|
|
28
|
-
"type": "push",
|
|
29
|
-
"images": {
|
|
30
|
-
"off": "icons/bulp.png",
|
|
31
|
-
"on": "icons/bulp_filled.png"
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
"knob": {
|
|
36
|
-
"0": {
|
|
37
|
-
"images": {
|
|
38
|
-
"default": "icons/mountain.png"
|
|
39
|
-
},
|
|
40
|
-
"commands": {
|
|
41
|
-
"off": "echo \"knob {state}\""
|
|
42
|
-
}
|
|
43
38
|
}
|
|
44
|
-
}
|
|
39
|
+
},
|
|
40
|
+
"knob": {}
|
|
45
41
|
},
|
|
46
42
|
"knobs": {
|
|
47
43
|
"left": {},
|
|
48
44
|
"right": {}
|
|
49
45
|
},
|
|
50
46
|
"buttons": {
|
|
51
|
-
"knobTL": {
|
|
52
|
-
"commands": {
|
|
53
|
-
"off": "echo \"{id} {state} {value}\"",
|
|
54
|
-
"on": "echo \"{id} {state} {value}\""
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"knobTR": {
|
|
58
|
-
"commands": {
|
|
59
|
-
"off": "echo \"{id} {state} {value}\"",
|
|
60
|
-
"on": "echo \"{id} {state} {value}\""
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
"knobCL": {
|
|
64
|
-
"commands": {
|
|
65
|
-
"off": "echo \"{id} {state} {value}\"",
|
|
66
|
-
"on": "echo \"{id} {state} {value}\""
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
"0": {
|
|
70
|
-
"commands": {
|
|
71
|
-
"off": "echo \"{id} {state}\"",
|
|
72
|
-
"on": "echo \"{id} {state}\""
|
|
73
|
-
},
|
|
74
|
-
"rgb": {
|
|
75
|
-
"off": [
|
|
76
|
-
0,
|
|
77
|
-
0,
|
|
78
|
-
0
|
|
79
|
-
],
|
|
80
|
-
"on": [
|
|
81
|
-
0,
|
|
82
|
-
255,
|
|
83
|
-
0
|
|
84
|
-
],
|
|
85
|
-
"intermediate": [
|
|
86
|
-
0,
|
|
87
|
-
255,
|
|
88
|
-
255
|
|
89
|
-
]
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
47
|
"1": {
|
|
93
|
-
"
|
|
94
|
-
"off":
|
|
95
|
-
|
|
96
|
-
|
|
48
|
+
"states": {
|
|
49
|
+
"off": {
|
|
50
|
+
"color": "#aaaaaa",
|
|
51
|
+
"image": "icons/bulb.png",
|
|
52
|
+
"cmd": "echo \"{id} {state}\""
|
|
53
|
+
},
|
|
54
|
+
"on": {
|
|
55
|
+
"color": "#11ff11",
|
|
56
|
+
"image": "icons/bulb.png",
|
|
57
|
+
"cmd": "echo \"{id} {state}\""
|
|
58
|
+
}
|
|
97
59
|
},
|
|
98
|
-
"
|
|
99
|
-
"off": [
|
|
100
|
-
0,
|
|
101
|
-
0,
|
|
102
|
-
0
|
|
103
|
-
],
|
|
104
|
-
"on": [
|
|
105
|
-
0,
|
|
106
|
-
255,
|
|
107
|
-
0
|
|
108
|
-
],
|
|
109
|
-
"intermediate": [
|
|
110
|
-
0,
|
|
111
|
-
255,
|
|
112
|
-
255
|
|
113
|
-
]
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
"home": {
|
|
117
|
-
"rgb": {
|
|
118
|
-
"off": [
|
|
119
|
-
0,
|
|
120
|
-
0,
|
|
121
|
-
0
|
|
122
|
-
]
|
|
123
|
-
}
|
|
60
|
+
"group": "kvm1"
|
|
124
61
|
}
|
|
125
62
|
}
|
|
126
63
|
}
|
|
File without changes
|