Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7d625573 | ||
|
|
52a57c0e87 | ||
|
|
a52b34fc3f | ||
|
|
1a14ac020e | ||
|
|
330f7e72be | ||
|
|
1ed6669ced | ||
|
|
108d60a497 | ||
|
|
1a9bd7de83 | ||
|
|
10cd06c70b | ||
|
|
84b6c33c54 | ||
|
|
18808eae9c | ||
|
|
ca74b0a76b | ||
|
|
b45d60efb2 | ||
|
|
a4e3696078 | ||
|
|
2b963993d2 | ||
|
|
e333022043 | ||
|
|
2e2a59ccb8 | ||
|
|
6f073ad718 | ||
|
|
166834e46c | ||
|
|
0ff2364b9e | ||
|
|
2cbfc6a11e | ||
|
|
404ccc7b2b | ||
|
|
42e413ab65 | ||
|
|
d57ef66038 | ||
|
|
843b4cbf68 | ||
|
|
7e4b7c5c8b | ||
|
|
2ff65537a0 | ||
|
|
6e2b1fb18d | ||
|
|
3e621a2818 | ||
|
|
247ec4c1b2 | ||
|
|
edcff069fd | ||
|
|
8afdeac306 | ||
|
|
425f861451 | ||
|
|
5a843ed02c | ||
|
|
f3bd83af03 | ||
|
|
0b3fca63b7 | ||
|
|
cbd4460df0 | ||
|
|
b941bd9a75 | ||
|
|
439b84affc | ||
|
|
66d5302e6f | ||
|
|
9e2e2b9c5c | ||
|
|
aa243ac7b0 | ||
|
|
eb421e0681 | ||
|
|
63aae8f578 | ||
|
|
e577454a31 | ||
|
|
e802bb0052 | ||
|
|
b98e5df20a | ||
|
|
7332105edb | ||
|
|
6a4feaf18d | ||
|
|
ee637d7958 | ||
|
|
8d161ce8ff | ||
|
|
8010bd8195 | ||
|
|
059f1d5505 | ||
|
|
123cab5724 | ||
|
|
c16365a0e5 | ||
|
|
a21d278c0c | ||
|
|
0466916be1 | ||
|
|
9a845d8f6a | ||
|
|
9ae1673499 | ||
|
|
2d5a5e7aef | ||
|
|
3e4d439852 | ||
|
|
aba0b09109 | ||
|
|
af608ee17d | ||
|
|
df817e86d6 | ||
|
|
a7d3b62512 | ||
|
|
b41f14192a | ||
|
|
475ec71983 | ||
|
|
b5cff2fa93 | ||
|
|
80e8c014ec | ||
|
|
67d42f666c | ||
|
|
fa32f95438 | ||
|
|
06878dd56a | ||
|
|
374f6a3e6e | ||
|
|
0b9c804a3d | ||
|
|
365a459d61 | ||
|
|
cec891a2c0 | ||
|
|
8eb8aa3032 | ||
|
|
38184e7968 | ||
|
|
f6092ea195 | ||
|
|
ac7d66e338 | ||
|
|
b82a1da92a | ||
|
|
b8859f7b64 | ||
|
|
a04fa67446 | ||
|
|
ac89aff018 | ||
|
|
e7cf8dc966 | ||
|
|
d0102f5bdb | ||
|
|
eb0daadf98 | ||
|
|
49d6ca173d | ||
|
|
a3eb6a6b7e | ||
|
|
144ed57b20 | ||
|
|
6086ddabf0 | ||
|
|
84f378a276 | ||
|
|
648e8d5f2c | ||
|
|
15df8d7129 | ||
|
|
cfc0af9655 | ||
|
|
f02e3181a6 | ||
|
|
3d59bcf97e | ||
|
|
5e4fc983fb | ||
|
|
32d9635b34 |
89
CHANGELOG.md
@@ -6,12 +6,99 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
|
|||||||
|
|
||||||
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||||
|
|
||||||
|
## [1.2.12] - 2018-11-14
|
||||||
|
|
||||||
|
Firmware: 8.**5.3** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.5.3)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- When the firmware of the right keyboard half is larger or equal than 8.4.3 then display the "Lock layer when double tapping this key" checkbox and remove "... macro playback is not implemented yet..." notices.
|
||||||
|
- Upgrade to node-hid 0.7.3 which utilizes the hidraw USB driver on Linux instead of libusb.
|
||||||
|
- Update udev rules for the new hidraw based node-hid.
|
||||||
|
- Improve the "Cannot find your UHK" and the privilege escalation screens to show more relevant messages when transitioning from the libusb based node-hid to the hidraw based node-hid.
|
||||||
|
- Fix the rendering of macro actions, so that their text doesn't overlap.
|
||||||
|
- Add "International {1,2,3}" and "Language {1,2}" keypress actions.
|
||||||
|
- Add icon for the Play/Pause keypress action.
|
||||||
|
- Remove the Stop/Eject keypress action.
|
||||||
|
- Make the "Type text" macro action accept clipboard data on Mac.
|
||||||
|
- Display "You can't change this mapping because on the base layer a layer switcher key targets this key." in the key action popover whenever it applies.
|
||||||
|
- Fix UI bug which could be triggered by tapping Tab in the keymap abbreviation input.
|
||||||
|
- Don't trigger Agent shortcuts when capturing keypresses.
|
||||||
|
- Log USB device list before checking permissions.
|
||||||
|
- Show OS-specific modifiers in the title bar of macro actions.
|
||||||
|
- Only show the device list on Linux when the list actually changes.
|
||||||
|
|
||||||
|
## [1.2.11] - 2018-10-03
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Add backspace and caps lock icons which avoids the overlap of their old texts.
|
||||||
|
- Fix right and middle mouse click macro actions which were exchanged.
|
||||||
|
- Include Agent version to the firmware update log.
|
||||||
|
|
||||||
|
## [1.2.10] - 2018-09-24
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Add History Back and History Forward scancodes.
|
||||||
|
- Save the actual decelerated scroll speed instead of using the accelerated scroll speed by accident.
|
||||||
|
- Allow layer switcher secondary roles only on the base layer.
|
||||||
|
- When remapping modifiers, display a warning suggesting to remap them on all layers.
|
||||||
|
- Display more exact instructions on the permission setup screen.
|
||||||
|
- Set the decelerated scroll speed of the default configuration from 20 to 10.
|
||||||
|
- Map Caps Lock without Ctrl on default keymaps.
|
||||||
|
- Rename "Scroll Lock" to "ScrLk" and "Num Lock" to "NumLk" on keys to avoid text overlap.
|
||||||
|
- In the scancode select2, display "Print Screen SysRq" and add SysRq above PrtScn when rendering the key.
|
||||||
|
- Fix left and right direction titles for mouse movement macro actions.
|
||||||
|
|
||||||
|
## [1.2.9] - 2018-09-13
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Display OS-specific modifiers.
|
||||||
|
- Display secondary roles.
|
||||||
|
- Don't trigger "Remap on all layers" after leaving Agent with Alt+Tab.
|
||||||
|
|
||||||
|
## [1.2.8] - 2018-08-26
|
||||||
|
|
||||||
|
Firmware: 8.**2.5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Uncheck the "Remap on all keymaps" and "Remap on all layers" checkboxes of the key action popover by default.
|
||||||
|
- Bind left and right Shift on the Mouse layer of all keymaps in the default configuration.
|
||||||
|
- Make ng2-select2 widgets faster.
|
||||||
|
- Add note to the LED brightness page saying that current UHK versions are not backlit.
|
||||||
|
- Fix the padding of the secondary role tooltip.
|
||||||
|
- Remove the redundant scrollbar from the LED brightness page.
|
||||||
|
|
||||||
|
## [1.2.7] - 2018-07-26
|
||||||
|
|
||||||
|
Firmware: 8.4.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.4.0)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Fix Agent startup exception on Linux by upgrading Electron builder.
|
||||||
|
- Change the shortcut which enables the USB stack test code, so that it can be triggered with the default Mac US keymap.
|
||||||
|
|
||||||
|
## [1.2.6] - 2018-07-26
|
||||||
|
|
||||||
|
Firmware: 8.**4.0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.4.0)] | Device Protocol: 4.**4.0** | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Replace the Linux blhost binary with a statically compiled version that doesn't use special instructions and shouldn't segfault.
|
||||||
|
- Keep the current layer when changing keymaps.
|
||||||
|
- Fix the sleep key of Mac keymaps.
|
||||||
|
- Add help page.
|
||||||
|
- Add "save to keyboard" and "remap key" shortcuts.
|
||||||
|
- Build only AppImages for Linux.
|
||||||
|
- Replace ng2-select2 widgets with ngx-select-ex that always shows up in the correct position.
|
||||||
|
- Improve the phrasing of the firmware update error message.
|
||||||
|
- Tweak unsupported Windows firmware update notification.
|
||||||
|
- Hide the Settings menu until auto update is implemented.
|
||||||
|
- Don't scroll when the macro tab of the key action popover gets selected.
|
||||||
|
- Add keyboard shortcut for enabling the USB stack test mode of the firmware. `DEVICEPROTOCOL:MINOR`
|
||||||
|
- Tone down the color of the separator line.
|
||||||
|
|
||||||
## [1.2.5] - 2018-06-26
|
## [1.2.5] - 2018-06-26
|
||||||
|
|
||||||
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
- When remapping a switch keymap action on all keymaps, don't set it on its own keymap.
|
- When remapping a switch keymap action on all keymaps, don't set it on its own keymap.
|
||||||
- Make the key action popver always contain the action of the current key, even after cancelled.
|
- Make the key action popover always contain the action of the current key, even after cancelled.
|
||||||
- Include the firmware version to be updated to the firmware update log.
|
- Include the firmware version to be updated to the firmware update log.
|
||||||
- Update the Agent icon of the side menu and the about page.
|
- Update the Agent icon of the side menu and the about page.
|
||||||
- When remapping a key, only flash the affected key instead of all keys.
|
- When remapping a key, only flash the affected key instead of all keys.
|
||||||
|
|||||||
8
ISSUE_TEMPLATE
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Before submitting a new issue, make sure to do the following:
|
||||||
|
|
||||||
|
1. If you're using Karabiner Elements on your Mac, close it!
|
||||||
|
2. Install the latest Agent:
|
||||||
|
https://github.com/UltimateHackingKeyboard/agent/releases/latest
|
||||||
|
3. Use Agent to update to the latest firmware:
|
||||||
|
https://github.com/UltimateHackingKeyboard/firmware/releases/latest
|
||||||
|
4. Try to reproduce the issue, and only report it if it still persists.
|
||||||
15518
package-lock.json
generated
46
package.json
@@ -3,9 +3,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"author": "Ultimate Gadget Laboratories",
|
"author": "Ultimate Gadget Laboratories",
|
||||||
"main": "electron/dist/electron-main.js",
|
"main": "electron/dist/electron-main.js",
|
||||||
"version": "1.2.5",
|
"version": "1.2.12",
|
||||||
"firmwareVersion": "8.2.5",
|
"firmwareVersion": "8.5.3",
|
||||||
"deviceProtocolVersion": "4.3.1",
|
"deviceProtocolVersion": "4.4.0",
|
||||||
"userConfigVersion": "4.0.1",
|
"userConfigVersion": "4.0.1",
|
||||||
"hardwareConfigVersion": "1.0.0",
|
"hardwareConfigVersion": "1.0.0",
|
||||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||||
@@ -15,20 +15,21 @@
|
|||||||
},
|
},
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.9.1 <9.0.0",
|
"node": ">=8.12.0 <9.0.0",
|
||||||
"npm": ">=5.6.0 <6.0.0"
|
"npm": ">=6.4.0 <7.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/electron-devtools-installer": "2.0.2",
|
"@types/electron-devtools-installer": "2.0.2",
|
||||||
"@types/electron-settings": "3.0.0",
|
"@types/electron-settings": "3.0.0",
|
||||||
"@types/fs-extra": "5.0.1",
|
"@types/fs-extra": "5.0.1",
|
||||||
"@types/jasmine": "2.6.0",
|
"@types/jasmine": "2.8.8",
|
||||||
"@types/jquery": "3.3.1",
|
"@types/jquery": "3.3.1",
|
||||||
"@types/jsonfile": "4.0.1",
|
"@types/jsonfile": "4.0.1",
|
||||||
"@types/lodash-es": "4.17.0",
|
"@types/lodash-es": "4.17.0",
|
||||||
"@types/node": "8.0.53",
|
"@types/node": "8.0.53",
|
||||||
"@types/node-hid": "0.5.2",
|
"@types/node-hid": "0.7.0",
|
||||||
"@types/request": "2.0.8",
|
"@types/request": "2.0.8",
|
||||||
|
"@types/semver": "5.5.0",
|
||||||
"@types/usb": "1.1.3",
|
"@types/usb": "1.1.3",
|
||||||
"autoprefixer": "6.5.3",
|
"autoprefixer": "6.5.3",
|
||||||
"buffer": "5.0.6",
|
"buffer": "5.0.6",
|
||||||
@@ -38,37 +39,41 @@
|
|||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"cross-env": "5.0.5",
|
"cross-env": "5.0.5",
|
||||||
"decompress": "4.2.0",
|
"decompress": "4.2.0",
|
||||||
"decompress-tarbz2": "^4.1.1",
|
"decompress-tarbz2": "4.1.1",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron": "1.8.4",
|
"electron": "1.8.7",
|
||||||
"electron-builder": "20.8.1",
|
"electron-builder": "20.34.0",
|
||||||
"electron-debug": "1.5.0",
|
"electron-debug": "1.5.0",
|
||||||
"electron-devtools-installer": "2.2.3",
|
"electron-devtools-installer": "2.2.3",
|
||||||
"electron-log": "2.2.14",
|
"electron-log": "2.2.16",
|
||||||
"electron-rebuild": "1.7.3",
|
"electron-rebuild": "1.8.2",
|
||||||
"electron-settings": "3.1.4",
|
"electron-settings": "3.1.4",
|
||||||
"electron-updater": "2.21.4",
|
"electron-updater": "2.21.4",
|
||||||
"exports-loader": "0.6.3",
|
"exports-loader": "0.6.3",
|
||||||
"file-loader": "0.10.0",
|
"file-loader": "0.10.0",
|
||||||
"fs-extra": "5.0.0",
|
"fs-extra": "5.0.0",
|
||||||
"gh-pages": "1.1.0",
|
"gh-pages": "1.1.0",
|
||||||
|
"jasmine": "2.8.0",
|
||||||
|
"jasmine-core": "2.8.0",
|
||||||
|
"jasmine-node": "2.0.1",
|
||||||
|
"jasmine-ts": "0.2.1",
|
||||||
"jsonfile": "4.0.0",
|
"jsonfile": "4.0.0",
|
||||||
"lerna": "2.9.0",
|
"lerna": "3.2.0",
|
||||||
"lodash-es": "4.17.4",
|
"lodash-es": "4.17.4",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"node-hid": "0.5.7",
|
"node-hid": "0.7.3",
|
||||||
"npm-run-all": "4.0.2",
|
"npm-run-all": "4.0.2",
|
||||||
"pre-commit": "1.2.2",
|
"pre-commit": "1.2.2",
|
||||||
"request": "2.83.0",
|
"request": "2.88.0",
|
||||||
"rimraf": "2.6.1",
|
"rimraf": "2.6.1",
|
||||||
"standard-version": "4.2.0",
|
"standard-version": "4.4.0",
|
||||||
"stylelint": "7.13.0",
|
"stylelint": "9.6.0",
|
||||||
"svg-sprite": "1.3.7",
|
"svg-sprite": "1.5.0",
|
||||||
"ts-loader": "2.3.1",
|
"ts-loader": "2.3.1",
|
||||||
"ts-node": "3.0.4",
|
"ts-node": "7.0.1",
|
||||||
"tslint": "5.9.1",
|
"tslint": "5.9.1",
|
||||||
"typescript": "2.6.2",
|
"typescript": "2.6.2",
|
||||||
"webpack": "3.10.0"
|
"webpack": "3.12.0"
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
"pre-commit": [
|
||||||
"precommit-msg"
|
"precommit-msg"
|
||||||
@@ -88,6 +93,7 @@
|
|||||||
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
|
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
|
||||||
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
|
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
|
||||||
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
|
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
|
||||||
|
"e2e": "lerna run e2e --scope uhk-web",
|
||||||
"prebuild": "check-node-version --package",
|
"prebuild": "check-node-version --package",
|
||||||
"build": "run-s build:common build:usb build:web build:electron",
|
"build": "run-s build:common build:usb build:web build:electron",
|
||||||
"build:web": "lerna exec --scope uhk-web npm run build",
|
"build:web": "lerna exec --scope uhk-web npm run build",
|
||||||
|
|||||||
146
packages/test-serializer/package-lock.json
generated
@@ -201,11 +201,11 @@
|
|||||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||||
},
|
},
|
||||||
"gaze": {
|
"gaze": {
|
||||||
"version": "0.5.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
|
||||||
"integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=",
|
"integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"globule": "0.1.0"
|
"globule": "1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"get-caller-file": {
|
"get-caller-file": {
|
||||||
@@ -219,41 +219,37 @@
|
|||||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||||
},
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "3.1.21",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||||
"integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=",
|
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "1.2.3",
|
"fs.realpath": "1.0.0",
|
||||||
"inherits": "1.0.2",
|
"inflight": "1.0.6",
|
||||||
"minimatch": "0.2.14"
|
"inherits": "2.0.3",
|
||||||
},
|
"minimatch": "3.0.4",
|
||||||
"dependencies": {
|
"once": "1.4.0",
|
||||||
"inherits": {
|
"path-is-absolute": "1.0.1"
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"globule": {
|
"globule": {
|
||||||
"version": "0.1.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
|
||||||
"integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=",
|
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "3.1.21",
|
"glob": "7.1.2",
|
||||||
"lodash": "1.0.2",
|
"lodash": "4.17.10",
|
||||||
"minimatch": "0.2.14"
|
"minimatch": "3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graceful-fs": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz",
|
|
||||||
"integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q="
|
|
||||||
},
|
|
||||||
"growl": {
|
"growl": {
|
||||||
"version": "1.7.0",
|
"version": "1.10.5",
|
||||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
|
||||||
"integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto="
|
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA=="
|
||||||
|
},
|
||||||
|
"grunt-exec": {
|
||||||
|
"version": "0.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/grunt-exec/-/grunt-exec-0.4.7.tgz",
|
||||||
|
"integrity": "sha1-QAUf+k6wyWV+BTuV6I1ENSocLCU="
|
||||||
},
|
},
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -362,31 +358,34 @@
|
|||||||
"integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4="
|
"integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4="
|
||||||
},
|
},
|
||||||
"jasmine-growl-reporter": {
|
"jasmine-growl-reporter": {
|
||||||
"version": "0.2.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-1.0.1.tgz",
|
||||||
"integrity": "sha1-1fCje5L2qD/VxkgrgJSVyQqLVf4=",
|
"integrity": "sha512-dh7VjP3l0OLxL9+sw5vK6RrdH4gdHCNkTnUd9orViHDPr7Fe8LsXY+IObWauS2hX5khMFtjKRZCfTcDHKAjm/A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"growl": "1.7.0"
|
"growl": "1.10.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jasmine-node": {
|
"jasmine-node": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-2.0.1.tgz",
|
||||||
"integrity": "sha1-gXUacjJfVJdJCxQYGlUIfxsDcf8=",
|
"integrity": "sha512-1S5Z4Mof5yxwqLIApzyo2pV5WN2kRpTSgICvEo3+rJmKve9P94kolzC9eS0u5cyiT+gxBY2mwOQdxLbkhwKzoA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"coffee-script": "1.7.1",
|
"coffee-script": "1.7.1",
|
||||||
"gaze": "0.5.2",
|
"gaze": "1.1.3",
|
||||||
"jasmine-growl-reporter": "0.2.1",
|
"grunt-exec": "0.4.7",
|
||||||
"minimist": "0.0.8",
|
"jasmine-growl-reporter": "1.0.1",
|
||||||
|
"jasmine-reporters": "git://github.com/larrymyers/jasmine-reporters.git#2c7242dc11c15c2f156169bc704798568b8cb50d",
|
||||||
|
"minimist": "0.0.10",
|
||||||
"mkdirp": "0.3.5",
|
"mkdirp": "0.3.5",
|
||||||
"underscore": "1.6.0",
|
"underscore": "1.6.0",
|
||||||
"walkdir": "0.0.12"
|
"walkdir": "0.0.12",
|
||||||
|
"xml2js": "0.4.19"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
||||||
},
|
},
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
@@ -395,6 +394,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jasmine-reporters": {
|
||||||
|
"version": "git://github.com/larrymyers/jasmine-reporters.git#2c7242dc11c15c2f156169bc704798568b8cb50d",
|
||||||
|
"requires": {
|
||||||
|
"mkdirp": "0.3.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "0.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
|
||||||
|
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"jasmine-ts": {
|
"jasmine-ts": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.2.1.tgz",
|
||||||
@@ -442,14 +454,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "1.0.2",
|
"version": "4.17.10",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||||
"integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE="
|
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
|
||||||
},
|
|
||||||
"lru-cache": {
|
|
||||||
"version": "2.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
|
|
||||||
"integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
|
|
||||||
},
|
},
|
||||||
"make-error": {
|
"make-error": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@@ -470,12 +477,11 @@
|
|||||||
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg="
|
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg="
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "0.2.14",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
"integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=",
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lru-cache": "2.7.3",
|
"brace-expansion": "1.1.8"
|
||||||
"sigmund": "1.0.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
@@ -633,6 +639,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
||||||
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
|
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
|
||||||
},
|
},
|
||||||
|
"sax": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||||
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||||
@@ -656,11 +667,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
|
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
|
||||||
},
|
},
|
||||||
"sigmund": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
|
|
||||||
},
|
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||||
@@ -854,6 +860,20 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
},
|
},
|
||||||
|
"xml2js": {
|
||||||
|
"version": "0.4.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||||
|
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||||
|
"requires": {
|
||||||
|
"sax": "1.2.4",
|
||||||
|
"xmlbuilder": "9.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmlbuilder": {
|
||||||
|
"version": "9.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||||
|
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
||||||
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
||||||
|
|||||||
@@ -14,16 +14,9 @@
|
|||||||
"npm": ">=5.1.0 <6.0.0"
|
"npm": ">=5.1.0 <6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"uhk-common": "1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jasmine": "2.6.0",
|
|
||||||
"@types/node": "8.0.30",
|
|
||||||
"jasmine": "2.8.0",
|
|
||||||
"jasmine-core": "2.8.0",
|
|
||||||
"jasmine-node": "2.0.0",
|
|
||||||
"jasmine-ts": "0.2.1",
|
|
||||||
"ts-node": "3.3.0",
|
|
||||||
"uhk-common": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jasmine-ts --config=jasmine.json"
|
"test": "jasmine-ts --config=jasmine.json"
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true
|
||||||
"typeRoots": [
|
|
||||||
"./node_modules/@types"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
347
packages/uhk-agent/package-lock.json
generated
@@ -17,7 +17,7 @@
|
|||||||
"command-line-args": "4.0.7",
|
"command-line-args": "4.0.7",
|
||||||
"decompress": "4.2.0",
|
"decompress": "4.2.0",
|
||||||
"decompress-bzip2": "4.0.0",
|
"decompress-bzip2": "4.0.0",
|
||||||
"node-hid": "0.5.7",
|
"node-hid": "0.7.3",
|
||||||
"sudo-prompt": "7.0.0",
|
"sudo-prompt": "7.0.0",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"uhk-common": "^1.0.0",
|
"uhk-common": "^1.0.0",
|
||||||
@@ -31,10 +31,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron ./dist/electron-main.js",
|
"start": "electron ./dist/electron-main.js",
|
||||||
"electron:spe": "electron ./dist/electron-main.js --spe",
|
"electron:spe": "electron ./dist/electron-main.js --spe",
|
||||||
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
|
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-to-tmp-folder",
|
||||||
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
||||||
"install:build-deps": "cd ./dist && npm i",
|
"install:build-deps": "cd ./dist && npm i",
|
||||||
"download-firmware": "node ../../scripts/download-firmware.js",
|
"download-firmware": "node ../../scripts/download-firmware.js",
|
||||||
"copy-blhost": "node ../../scripts/copy-blhost.js"
|
"copy-to-tmp-folder": "node ../../scripts/copy-to-tmp-folder.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,11 @@ import { AppService } from './services/app.service';
|
|||||||
import { SudoService } from './services/sudo.service';
|
import { SudoService } from './services/sudo.service';
|
||||||
import { UhkBlhost } from '../../uhk-usb/src';
|
import { UhkBlhost } from '../../uhk-usb/src';
|
||||||
import * as isDev from 'electron-is-dev';
|
import * as isDev from 'electron-is-dev';
|
||||||
|
import { setMenu } from './electron-menu';
|
||||||
|
|
||||||
const optionDefinitions = [
|
const optionDefinitions = [
|
||||||
{name: 'addons', type: Boolean},
|
{name: 'addons', type: Boolean},
|
||||||
{name: 'spe', type: Boolean}, // simulate privilege escalation error
|
{name: 'spe', type: Boolean} // simulate privilege escalation error
|
||||||
// show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
|
|
||||||
{name: 'layer-double-tap', type: Boolean}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||||
@@ -101,9 +100,9 @@ function createWindow() {
|
|||||||
},
|
},
|
||||||
icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png')
|
icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png')
|
||||||
});
|
});
|
||||||
win.setMenuBarVisibility(false);
|
setMenu(win);
|
||||||
win.maximize();
|
win.maximize();
|
||||||
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir);
|
||||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);
|
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);
|
||||||
|
|||||||
37
packages/uhk-agent/src/electron-menu.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { app, BrowserWindow, Menu, systemPreferences } from 'electron';
|
||||||
|
import * as isDev from 'electron-is-dev';
|
||||||
|
|
||||||
|
export const setMenu = (win: BrowserWindow): void => {
|
||||||
|
if (process.platform !== 'darwin' || isDev) {
|
||||||
|
win.setMenuBarVisibility(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = [
|
||||||
|
{
|
||||||
|
label: app.getName(),
|
||||||
|
submenu: [
|
||||||
|
{role: 'quit'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{role: 'cut'},
|
||||||
|
{role: 'copy'},
|
||||||
|
{role: 'paste'},
|
||||||
|
{role: 'delete'},
|
||||||
|
{role: 'selectall'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// hide "Start Dictation" submenu item in Edit menu
|
||||||
|
systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true as any);
|
||||||
|
// hide "Emoji & Symbols" submenu item in Edit menu
|
||||||
|
systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', false as any);
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate(template);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
};
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
"npm": ">=5.1.0 <6.0.0"
|
"npm": ">=5.1.0 <6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-hid": "0.5.7"
|
"node-hid": "0.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,12 @@ export class AppService extends MainServiceBase {
|
|||||||
|
|
||||||
private async handleAppStartInfo(event: Electron.Event) {
|
private async handleAppStartInfo(event: Electron.Event) {
|
||||||
this.logService.info('[AppService] getAppStartInfo');
|
this.logService.info('[AppService] getAppStartInfo');
|
||||||
const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState();
|
const deviceConnectionState = await this.uhkHidDeviceService.getDeviceConnectionStateAsync();
|
||||||
const response: AppStartInfo = {
|
const response: AppStartInfo = {
|
||||||
|
deviceConnectionState,
|
||||||
commandLineArgs: {
|
commandLineArgs: {
|
||||||
addons: this.options.addons || false,
|
addons: this.options.addons || false
|
||||||
layerDoubleTap: this.options['layer-double-tap'] || false
|
|
||||||
},
|
},
|
||||||
deviceConnected: deviceConnectionState.connected,
|
|
||||||
hasPermission: deviceConnectionState.hasPermission,
|
|
||||||
bootloaderActive: deviceConnectionState.bootloaderActive,
|
|
||||||
platform: process.platform as string,
|
platform: process.platform as string,
|
||||||
osVersion: os.release()
|
osVersion: os.release()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
import {
|
import {
|
||||||
ConfigurationReply,
|
ConfigurationReply,
|
||||||
DeviceConnectionState,
|
DeviceConnectionState,
|
||||||
@@ -9,17 +10,19 @@ import {
|
|||||||
IpcResponse,
|
IpcResponse,
|
||||||
LogService,
|
LogService,
|
||||||
mapObjectToUserConfigBinaryBuffer,
|
mapObjectToUserConfigBinaryBuffer,
|
||||||
SaveUserConfigurationData
|
SaveUserConfigurationData,
|
||||||
|
UpdateFirmwareData
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { emptyDir } from 'fs-extra';
|
import { emptyDir } from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import 'rxjs/add/observable/interval';
|
import 'rxjs/add/observable/interval';
|
||||||
|
import 'rxjs/add/observable/fromPromise';
|
||||||
|
import 'rxjs/add/operator/switchMap';
|
||||||
import 'rxjs/add/operator/startWith';
|
import 'rxjs/add/operator/startWith';
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import 'rxjs/add/operator/do';
|
import 'rxjs/add/operator/do';
|
||||||
import 'rxjs/add/operator/distinctUntilChanged';
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
|
|
||||||
@@ -87,6 +90,15 @@ export class DeviceService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on(IpcEvents.device.enableUsbStackTest, (...args: any[]) => {
|
||||||
|
this.queueManager.add({
|
||||||
|
method: this.enableUsbStackTest,
|
||||||
|
bind: this,
|
||||||
|
params: args,
|
||||||
|
asynchronous: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
logService.debug('[DeviceService] init success');
|
logService.debug('[DeviceService] init success');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,10 +160,12 @@ export class DeviceService {
|
|||||||
|
|
||||||
public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> {
|
public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> {
|
||||||
const response = new FirmwareUpgradeIpcResponse();
|
const response = new FirmwareUpgradeIpcResponse();
|
||||||
|
const data: UpdateFirmwareData = JSON.parse(args[0]);
|
||||||
|
|
||||||
let firmwarePathData: TmpFirmware;
|
let firmwarePathData: TmpFirmware;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.logService.debug('Agent version:', data.versionInformation.version);
|
||||||
const hardwareModules = await this.getHardwareModules(false);
|
const hardwareModules = await this.getHardwareModules(false);
|
||||||
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
|
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
|
||||||
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
|
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
|
||||||
@@ -159,8 +173,8 @@ export class DeviceService {
|
|||||||
this.device.resetDeviceCache();
|
this.device.resetDeviceCache();
|
||||||
this.stopPollTimer();
|
this.stopPollTimer();
|
||||||
|
|
||||||
if (args && args.length > 0) {
|
if (data.firmware) {
|
||||||
firmwarePathData = await saveTmpFirmware(args[0]);
|
firmwarePathData = await saveTmpFirmware(data.firmware);
|
||||||
|
|
||||||
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
|
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
|
||||||
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||||
@@ -224,6 +238,10 @@ export class DeviceService {
|
|||||||
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async enableUsbStackTest(event: Electron.Event) {
|
||||||
|
await this.device.enableUsbStackTest();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID API not support device attached and detached event.
|
* HID API not support device attached and detached event.
|
||||||
* This method check the keyboard is attached to the computer or not.
|
* This method check the keyboard is attached to the computer or not.
|
||||||
@@ -237,8 +255,8 @@ export class DeviceService {
|
|||||||
|
|
||||||
this.pollTimer$ = Observable.interval(1000)
|
this.pollTimer$ = Observable.interval(1000)
|
||||||
.startWith(0)
|
.startWith(0)
|
||||||
.map(() => this.device.getDeviceConnectionState())
|
.switchMap(() => Observable.fromPromise(this.device.getDeviceConnectionStateAsync()))
|
||||||
.distinctUntilChanged<DeviceConnectionState>(deviceConnectionStateComparer)
|
.distinctUntilChanged<DeviceConnectionState>(isEqual)
|
||||||
.do((state: DeviceConnectionState) => {
|
.do((state: DeviceConnectionState) => {
|
||||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
||||||
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as decompressTarbz from 'decompress-tarbz2';
|
|||||||
|
|
||||||
import { TmpFirmware } from '../models/tmp-firmware';
|
import { TmpFirmware } from '../models/tmp-firmware';
|
||||||
|
|
||||||
export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
|
export async function saveTmpFirmware(data: Array<number>): Promise<TmpFirmware> {
|
||||||
const tmpDirectory = dirSync();
|
const tmpDirectory = dirSync();
|
||||||
const zipFilePath = path.join(tmpDirectory.name, 'firmware.bz2');
|
const zipFilePath = path.join(tmpDirectory.name, 'firmware.bz2');
|
||||||
|
|
||||||
@@ -21,10 +21,9 @@ export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeDataToFile(data: string, filePath: string): Promise<void> {
|
function writeDataToFile(data: Array<number>, filePath: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const array: Array<number> = JSON.parse(data);
|
const buffer = new Buffer(data);
|
||||||
const buffer = new Buffer(array);
|
|
||||||
|
|
||||||
fs.writeFile(filePath, buffer, err => {
|
fs.writeFile(filePath, buffer, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -19,16 +19,6 @@
|
|||||||
"coverage": "nyc jasmine-ts --config=jasmine.json"
|
"coverage": "nyc jasmine-ts --config=jasmine.json"
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"devDependencies": {
|
|
||||||
"@types/jasmine": "2.6.0",
|
|
||||||
"@types/node": "8.0.30",
|
|
||||||
"jasmine": "2.8.0",
|
|
||||||
"jasmine-core": "2.8.0",
|
|
||||||
"jasmine-node": "2.0.0",
|
|
||||||
"jasmine-ts": "0.2.1",
|
|
||||||
"nyc": "11.2.1",
|
|
||||||
"ts-node": "3.3.0"
|
|
||||||
},
|
|
||||||
"nyc": {
|
"nyc": {
|
||||||
"extension": [
|
"extension": [
|
||||||
".ts"
|
".ts"
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { UhkBuffer } from '../../uhk-buffer';
|
|||||||
import { MacroAction, MacroActionId, MacroMouseSubAction, macroActionType } from './macro-action';
|
import { MacroAction, MacroActionId, MacroMouseSubAction, macroActionType } from './macro-action';
|
||||||
|
|
||||||
export enum MouseButtons {
|
export enum MouseButtons {
|
||||||
Left = 1 << 0,
|
Left = 0,
|
||||||
Middle = 1 << 1,
|
Right = 1,
|
||||||
Right = 1 << 2
|
Middle = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JsObjectMouseButtonMacroAction {
|
export interface JsObjectMouseButtonMacroAction {
|
||||||
|
|||||||
@@ -259,7 +259,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "70",
|
"id": "70",
|
||||||
"text": "Print Screen"
|
"text": "Print Screen SysRq"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "72",
|
"id": "72",
|
||||||
@@ -454,14 +454,6 @@
|
|||||||
"scancode": 182
|
"scancode": 182
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "132",
|
|
||||||
"text": "Stop/Eject",
|
|
||||||
"additional": {
|
|
||||||
"type": "media",
|
|
||||||
"scancode": 204
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "133",
|
"id": "133",
|
||||||
"text": "Play/Pause",
|
"text": "Play/Pause",
|
||||||
@@ -503,11 +495,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "138",
|
"id": "145",
|
||||||
"text": "WWW",
|
"text": "History Back",
|
||||||
"additional": {
|
"additional": {
|
||||||
"type": "media",
|
"type": "media",
|
||||||
"scancode": 138
|
"scancode": 548
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "146",
|
||||||
|
"text": "History Forward",
|
||||||
|
"additional": {
|
||||||
|
"type": "media",
|
||||||
|
"scancode": 549
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -515,14 +515,6 @@
|
|||||||
{
|
{
|
||||||
"text": "Launch application",
|
"text": "Launch application",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
|
||||||
"id": "142",
|
|
||||||
"text": "Launch Web Browser",
|
|
||||||
"additional": {
|
|
||||||
"type": "media",
|
|
||||||
"scancode": 406
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "143",
|
"id": "143",
|
||||||
"text": "Launch Email Client",
|
"text": "Launch Email Client",
|
||||||
@@ -687,5 +679,50 @@
|
|||||||
"text": "."
|
"text": "."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "International",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "235",
|
||||||
|
"text": "International 1",
|
||||||
|
"additional": {
|
||||||
|
"type": "basic",
|
||||||
|
"scancode": 135
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "236",
|
||||||
|
"text": "International 2",
|
||||||
|
"additional": {
|
||||||
|
"type": "basic",
|
||||||
|
"scancode": 136
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "237",
|
||||||
|
"text": "International 3",
|
||||||
|
"additional": {
|
||||||
|
"type": "basic",
|
||||||
|
"scancode": 137
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "244",
|
||||||
|
"text": "Language 1",
|
||||||
|
"additional": {
|
||||||
|
"type": "basic",
|
||||||
|
"scancode": 144
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "245",
|
||||||
|
"text": "Language 2",
|
||||||
|
"additional": {
|
||||||
|
"type": "basic",
|
||||||
|
"scancode": 145
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { assertUInt8, assertUInt16 } from '../assert';
|
import { assertUInt16, assertUInt8 } from '../assert';
|
||||||
import { UhkBuffer } from '../uhk-buffer';
|
import { UhkBuffer } from '../uhk-buffer';
|
||||||
import { Keymap } from './keymap';
|
import { Keymap } from './keymap';
|
||||||
import { Macro } from './macro';
|
import { Macro } from './macro';
|
||||||
import { ModuleConfiguration } from './module-configuration';
|
import { ModuleConfiguration } from './module-configuration';
|
||||||
import { ConfigSerializer } from '../config-serializer';
|
import { ConfigSerializer } from '../config-serializer';
|
||||||
|
import { KeystrokeAction, NoneAction } from './key-action';
|
||||||
|
import { SecondaryRoleAction } from './secondary-role-action';
|
||||||
|
import { isScancodeExists } from './scancode-checker';
|
||||||
|
|
||||||
export class UserConfiguration {
|
export class UserConfiguration {
|
||||||
|
|
||||||
@@ -90,7 +93,7 @@ export class UserConfiguration {
|
|||||||
this.mouseMoveAcceleratedSpeed = jsonObject.mouseMoveAcceleratedSpeed;
|
this.mouseMoveAcceleratedSpeed = jsonObject.mouseMoveAcceleratedSpeed;
|
||||||
this.mouseScrollInitialSpeed = jsonObject.mouseScrollInitialSpeed;
|
this.mouseScrollInitialSpeed = jsonObject.mouseScrollInitialSpeed;
|
||||||
this.mouseScrollAcceleration = jsonObject.mouseScrollAcceleration;
|
this.mouseScrollAcceleration = jsonObject.mouseScrollAcceleration;
|
||||||
this.mouseScrollDeceleratedSpeed = jsonObject.mouseScrollAcceleration;
|
this.mouseScrollDeceleratedSpeed = jsonObject.mouseScrollDeceleratedSpeed;
|
||||||
this.mouseScrollBaseSpeed = jsonObject.mouseScrollBaseSpeed;
|
this.mouseScrollBaseSpeed = jsonObject.mouseScrollBaseSpeed;
|
||||||
this.mouseScrollAcceleratedSpeed = jsonObject.mouseScrollAcceleratedSpeed;
|
this.mouseScrollAcceleratedSpeed = jsonObject.mouseScrollAcceleratedSpeed;
|
||||||
this.moduleConfigurations = jsonObject.moduleConfigurations.map((moduleConfiguration: any) => {
|
this.moduleConfigurations = jsonObject.moduleConfigurations.map((moduleConfiguration: any) => {
|
||||||
@@ -102,7 +105,9 @@ export class UserConfiguration {
|
|||||||
return macro;
|
return macro;
|
||||||
});
|
});
|
||||||
this.keymaps = jsonObject.keymaps.map((keymap: any) => new Keymap().fromJsonObject(keymap, this.macros));
|
this.keymaps = jsonObject.keymaps.map((keymap: any) => new Keymap().fromJsonObject(keymap, this.macros));
|
||||||
|
this.clean();
|
||||||
this.recalculateConfigurationLength();
|
this.recalculateConfigurationLength();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +143,8 @@ export class UserConfiguration {
|
|||||||
this.keymaps = buffer.readArray<Keymap>(uhkBuffer => new Keymap().fromBinary(uhkBuffer, this.macros));
|
this.keymaps = buffer.readArray<Keymap>(uhkBuffer => new Keymap().fromBinary(uhkBuffer, this.macros));
|
||||||
ConfigSerializer.resolveSwitchKeymapActions(this.keymaps);
|
ConfigSerializer.resolveSwitchKeymapActions(this.keymaps);
|
||||||
|
|
||||||
|
this.clean();
|
||||||
|
|
||||||
if (this.userConfigurationLength === 0) {
|
if (this.userConfigurationLength === 0) {
|
||||||
this.recalculateConfigurationLength();
|
this.recalculateConfigurationLength();
|
||||||
}
|
}
|
||||||
@@ -222,4 +229,34 @@ export class UserConfiguration {
|
|||||||
this.deviceName = 'My UHK';
|
this.deviceName = 'My UHK';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove not allowed settings/bugs
|
||||||
|
* 1. Layer Switcher secondary roles allowed only on base layers
|
||||||
|
*/
|
||||||
|
private clean(): void {
|
||||||
|
for (const keymap of this.keymaps) {
|
||||||
|
for (let layerId = 1; layerId < keymap.layers.length; layerId++) {
|
||||||
|
const layer = keymap.layers[layerId];
|
||||||
|
|
||||||
|
for (const module of layer.modules) {
|
||||||
|
for (let keyActionId = 0; keyActionId < module.keyActions.length; keyActionId++) {
|
||||||
|
const keyAction = module.keyActions[keyActionId];
|
||||||
|
if (!keyAction || !(keyAction instanceof KeystrokeAction)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyAction.secondaryRoleAction === SecondaryRoleAction.fn ||
|
||||||
|
keyAction.secondaryRoleAction === SecondaryRoleAction.mod ||
|
||||||
|
keyAction.secondaryRoleAction === SecondaryRoleAction.mouse) {
|
||||||
|
(keyAction as any)._secondaryRoleAction = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyAction.hasScancode() && !isScancodeExists(keyAction.scancode)) {
|
||||||
|
module.keyActions[keyActionId] = new NoneAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { CommandLineArgs } from './command-line-args';
|
import { CommandLineArgs } from './command-line-args';
|
||||||
|
import { DeviceConnectionState } from './device-connection-state';
|
||||||
|
|
||||||
export interface AppStartInfo {
|
export interface AppStartInfo {
|
||||||
commandLineArgs: CommandLineArgs;
|
commandLineArgs: CommandLineArgs;
|
||||||
deviceConnected: boolean;
|
deviceConnectionState: DeviceConnectionState;
|
||||||
hasPermission: boolean;
|
|
||||||
bootloaderActive: boolean;
|
|
||||||
platform: string;
|
platform: string;
|
||||||
osVersion: string;
|
osVersion: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,4 @@ export interface CommandLineArgs {
|
|||||||
* simulate privilege escalation error
|
* simulate privilege escalation error
|
||||||
*/
|
*/
|
||||||
spe?: boolean;
|
spe?: boolean;
|
||||||
/**
|
|
||||||
* show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
|
|
||||||
* if it false the checkbox invisible and the value of the checkbox = true
|
|
||||||
*/
|
|
||||||
layerDoubleTap?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { UdevRulesInfo } from './udev-rules-info';
|
||||||
|
|
||||||
export interface DeviceConnectionState {
|
export interface DeviceConnectionState {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
hasPermission: boolean;
|
hasPermission: boolean;
|
||||||
bootloaderActive: boolean;
|
bootloaderActive: boolean;
|
||||||
|
zeroInterfaceAvailable: boolean;
|
||||||
|
udevRulesInfo: UdevRulesInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ export * from './device-connection-state';
|
|||||||
export * from './hardware-modules';
|
export * from './hardware-modules';
|
||||||
export * from './hardware-module-info';
|
export * from './hardware-module-info';
|
||||||
export * from './save-user-configuration-data';
|
export * from './save-user-configuration-data';
|
||||||
|
export * from './udev-rules-info';
|
||||||
|
export * from './update-firmware-data';
|
||||||
|
|||||||
16
packages/uhk-common/src/models/udev-rules-info.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* What is the state of the udev rules.
|
||||||
|
* Only on Linux need extra udev rules.
|
||||||
|
*/
|
||||||
|
export enum UdevRulesInfo {
|
||||||
|
Unkonwn,
|
||||||
|
Ok,
|
||||||
|
/**
|
||||||
|
* Udev rules not exists need to setup on Linux
|
||||||
|
*/
|
||||||
|
NeedToSetup,
|
||||||
|
/**
|
||||||
|
* Udev rules exist but different than expected on Linux
|
||||||
|
*/
|
||||||
|
Different
|
||||||
|
}
|
||||||
6
packages/uhk-common/src/models/update-firmware-data.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { VersionInformation } from './version-information';
|
||||||
|
|
||||||
|
export interface UpdateFirmwareData {
|
||||||
|
versionInformation: VersionInformation;
|
||||||
|
firmware?: Array<number>;
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ export class Device {
|
|||||||
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
|
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
|
||||||
public static readonly startConnectionPoller = 'device-start-connection-poller';
|
public static readonly startConnectionPoller = 'device-start-connection-poller';
|
||||||
public static readonly recoveryDevice = 'device-recovery';
|
public static readonly recoveryDevice = 'device-recovery';
|
||||||
|
public static readonly enableUsbStackTest = 'enable-usb-stack-test';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IpcEvents {
|
export class IpcEvents {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
@@ -9,9 +10,6 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"typeRoots": [
|
|
||||||
"node_modules/@types"
|
|
||||||
],
|
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2015.iterable",
|
"es2015.iterable",
|
||||||
"dom",
|
"dom",
|
||||||
|
|||||||
289
packages/uhk-usb/package-lock.json
generated
@@ -1,11 +1,14 @@
|
|||||||
{
|
{
|
||||||
"requires": true,
|
"name": "uhk-usb",
|
||||||
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "8.0.28",
|
"version": "8.0.28",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz",
|
||||||
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ=="
|
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
@@ -18,12 +21,12 @@
|
|||||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
||||||
},
|
},
|
||||||
"are-we-there-yet": {
|
"are-we-there-yet": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||||
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
|
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"delegates": "1.0.0",
|
"delegates": "^1.0.0",
|
||||||
"readable-stream": "2.3.6"
|
"readable-stream": "^2.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bindings": {
|
"bindings": {
|
||||||
@@ -33,17 +36,36 @@
|
|||||||
},
|
},
|
||||||
"bl": {
|
"bl": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||||
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
|
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"readable-stream": "2.3.6",
|
"readable-stream": "^2.3.5",
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "^5.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"buffer-alloc": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
|
||||||
|
"requires": {
|
||||||
|
"buffer-alloc-unsafe": "^1.1.0",
|
||||||
|
"buffer-fill": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer-alloc-unsafe": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
|
||||||
|
},
|
||||||
|
"buffer-fill": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
|
||||||
|
},
|
||||||
"chownr": {
|
"chownr": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
|
||||||
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE="
|
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@@ -65,13 +87,13 @@
|
|||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
||||||
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
|
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"mimic-response": "1.0.0"
|
"mimic-response": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deep-extend": {
|
"deep-extend": {
|
||||||
"version": "0.4.2",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8="
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
||||||
},
|
},
|
||||||
"delegates": {
|
"delegates": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -88,27 +110,32 @@
|
|||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||||
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"once": "1.4.0"
|
"once": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"expand-template": {
|
"expand-template": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
|
||||||
"integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ=="
|
"integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
|
||||||
|
},
|
||||||
|
"fs-constants": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||||
},
|
},
|
||||||
"gauge": {
|
"gauge": {
|
||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"aproba": "1.2.0",
|
"aproba": "^1.0.3",
|
||||||
"console-control-strings": "1.1.0",
|
"console-control-strings": "^1.0.0",
|
||||||
"has-unicode": "2.0.1",
|
"has-unicode": "^2.0.0",
|
||||||
"object-assign": "4.1.1",
|
"object-assign": "^4.1.0",
|
||||||
"signal-exit": "3.0.2",
|
"signal-exit": "^3.0.0",
|
||||||
"string-width": "1.0.2",
|
"string-width": "^1.0.1",
|
||||||
"strip-ansi": "3.0.1",
|
"strip-ansi": "^3.0.1",
|
||||||
"wide-align": "1.1.2"
|
"wide-align": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"github-from-package": {
|
"github-from-package": {
|
||||||
@@ -136,7 +163,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "1.0.1"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"isarray": {
|
"isarray": {
|
||||||
@@ -145,18 +172,18 @@
|
|||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
"mimic-response": {
|
"mimic-response": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
||||||
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
|
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||||
},
|
},
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
@@ -164,32 +191,32 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.10.0",
|
"version": "2.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
|
||||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
|
"integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA=="
|
||||||
},
|
},
|
||||||
"node-abi": {
|
"node-abi": {
|
||||||
"version": "2.3.0",
|
"version": "2.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz",
|
||||||
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==",
|
"integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"semver": "5.5.0"
|
"semver": "^5.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-hid": {
|
"node-hid": {
|
||||||
"version": "0.5.7",
|
"version": "0.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.7.3.tgz",
|
||||||
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==",
|
"integrity": "sha512-LOCqWqcOlng+Kn1Qj/54zrPVfCagg1O7RlSgMmugykBcoYvUud6BswTrJM2aXuBac+bCCm3lA2srRG8YfmyXZQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "1.3.0",
|
"bindings": "^1.3.0",
|
||||||
"nan": "2.10.0",
|
"nan": "^2.10.0",
|
||||||
"prebuild-install": "2.5.1"
|
"prebuild-install": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noop-logger": {
|
"noop-logger": {
|
||||||
@@ -202,10 +229,10 @@
|
|||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"are-we-there-yet": "1.1.4",
|
"are-we-there-yet": "~1.1.2",
|
||||||
"console-control-strings": "1.1.0",
|
"console-control-strings": "~1.1.0",
|
||||||
"gauge": "2.7.4",
|
"gauge": "~2.7.3",
|
||||||
"set-blocking": "2.0.0"
|
"set-blocking": "~2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
@@ -223,7 +250,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1.0.2"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"os-homedir": {
|
"os-homedir": {
|
||||||
@@ -232,25 +259,25 @@
|
|||||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
||||||
},
|
},
|
||||||
"prebuild-install": {
|
"prebuild-install": {
|
||||||
"version": "2.5.1",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
|
||||||
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==",
|
"integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"detect-libc": "1.0.3",
|
"detect-libc": "^1.0.3",
|
||||||
"expand-template": "1.1.0",
|
"expand-template": "^1.0.2",
|
||||||
"github-from-package": "0.0.0",
|
"github-from-package": "0.0.0",
|
||||||
"minimist": "1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"node-abi": "2.3.0",
|
"node-abi": "^2.2.0",
|
||||||
"noop-logger": "0.1.1",
|
"noop-logger": "^0.1.1",
|
||||||
"npmlog": "4.1.2",
|
"npmlog": "^4.0.1",
|
||||||
"os-homedir": "1.0.2",
|
"os-homedir": "^1.0.1",
|
||||||
"pump": "2.0.1",
|
"pump": "^2.0.1",
|
||||||
"rc": "1.2.6",
|
"rc": "^1.1.6",
|
||||||
"simple-get": "2.7.0",
|
"simple-get": "^2.7.0",
|
||||||
"tar-fs": "1.16.0",
|
"tar-fs": "^1.13.0",
|
||||||
"tunnel-agent": "0.6.0",
|
"tunnel-agent": "^0.6.0",
|
||||||
"which-pm-runs": "1.0.0"
|
"which-pm-runs": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
@@ -263,44 +290,44 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "1.4.1",
|
"end-of-stream": "^1.1.0",
|
||||||
"once": "1.4.0"
|
"once": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rc": {
|
"rc": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=",
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"deep-extend": "0.4.2",
|
"deep-extend": "^0.6.0",
|
||||||
"ini": "1.3.5",
|
"ini": "~1.3.0",
|
||||||
"minimist": "1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"strip-json-comments": "2.0.1"
|
"strip-json-comments": "~2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "1.0.2",
|
"core-util-is": "~1.0.0",
|
||||||
"inherits": "2.0.3",
|
"inherits": "~2.0.3",
|
||||||
"isarray": "1.0.0",
|
"isarray": "~1.0.0",
|
||||||
"process-nextick-args": "2.0.0",
|
"process-nextick-args": "~2.0.0",
|
||||||
"safe-buffer": "5.1.1",
|
"safe-buffer": "~5.1.1",
|
||||||
"string_decoder": "1.1.1",
|
"string_decoder": "~1.1.1",
|
||||||
"util-deprecate": "1.0.2"
|
"util-deprecate": "~1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
|
||||||
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
|
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
|
||||||
},
|
},
|
||||||
"set-blocking": {
|
"set-blocking": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -318,13 +345,13 @@
|
|||||||
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
|
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
|
||||||
},
|
},
|
||||||
"simple-get": {
|
"simple-get": {
|
||||||
"version": "2.7.0",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
|
||||||
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==",
|
"integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"decompress-response": "3.3.0",
|
"decompress-response": "^3.3.0",
|
||||||
"once": "1.4.0",
|
"once": "^1.3.1",
|
||||||
"simple-concat": "1.0.0"
|
"simple-concat": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
@@ -332,9 +359,9 @@
|
|||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "1.1.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
"strip-ansi": "3.0.1"
|
"strip-ansi": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
@@ -342,15 +369,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "2.1.1"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"strip-json-comments": {
|
"strip-json-comments": {
|
||||||
@@ -359,14 +386,14 @@
|
|||||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||||
},
|
},
|
||||||
"tar-fs": {
|
"tar-fs": {
|
||||||
"version": "1.16.0",
|
"version": "1.16.3",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
|
||||||
"integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==",
|
"integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chownr": "1.0.1",
|
"chownr": "^1.0.1",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"pump": "1.0.3",
|
"pump": "^1.0.0",
|
||||||
"tar-stream": "1.5.5"
|
"tar-stream": "^1.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pump": {
|
"pump": {
|
||||||
@@ -374,29 +401,37 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
|
||||||
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
|
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"end-of-stream": "1.4.1",
|
"end-of-stream": "^1.1.0",
|
||||||
"once": "1.4.0"
|
"once": "^1.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tar-stream": {
|
"tar-stream": {
|
||||||
"version": "1.5.5",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
|
||||||
"integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
|
"integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bl": "1.2.2",
|
"bl": "^1.0.0",
|
||||||
"end-of-stream": "1.4.1",
|
"buffer-alloc": "^1.2.0",
|
||||||
"readable-stream": "2.3.6",
|
"end-of-stream": "^1.0.0",
|
||||||
"xtend": "4.0.1"
|
"fs-constants": "^1.0.0",
|
||||||
|
"readable-stream": "^2.3.0",
|
||||||
|
"to-buffer": "^1.1.1",
|
||||||
|
"xtend": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"to-buffer": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
|
||||||
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "5.1.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
@@ -410,11 +445,11 @@
|
|||||||
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
|
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
|
||||||
},
|
},
|
||||||
"wide-align": {
|
"wide-align": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||||
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
|
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"string-width": "1.0.2"
|
"string-width": "^1.0.2 || 2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"@types/node": "8.0.28"
|
"@types/node": "8.0.28"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-hid": "0.5.7",
|
"node-hid": "0.7.3",
|
||||||
"uhk-common": "1.0.0"
|
"uhk-common": "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ export enum UsbCommand {
|
|||||||
GetDebugBuffer = 0x0b,
|
GetDebugBuffer = 0x0b,
|
||||||
GetAdcValue = 0x0c,
|
GetAdcValue = 0x0c,
|
||||||
SetLedPwmBrightness = 0x0d,
|
SetLedPwmBrightness = 0x0d,
|
||||||
GetModuleProperty = 0x0e
|
GetModuleProperty = 0x0e,
|
||||||
|
GetSlaveI2cErrors = 0x0f,
|
||||||
|
SetI2cBaudRate = 0x10,
|
||||||
|
SwitchKeymap = 0x11,
|
||||||
|
GetVariable = 0x12,
|
||||||
|
SetVariable = 0x13
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EepromOperation {
|
export enum EepromOperation {
|
||||||
@@ -86,3 +91,10 @@ export enum KbootCommands {
|
|||||||
export enum ModulePropertyId {
|
export enum ModulePropertyId {
|
||||||
protocolVersions = 0
|
protocolVersions = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum UsbVariables {
|
||||||
|
testSwitches = 0,
|
||||||
|
testUsbStack = 1,
|
||||||
|
debounceTimePress = 2,
|
||||||
|
debounceTimeRelease = 3
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { Device, devices, HID } from 'node-hid';
|
import { Device, devices, HID } from 'node-hid';
|
||||||
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService } from 'uhk-common';
|
import { pathExists } from 'fs-extra';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { platform } from 'os';
|
||||||
|
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService, UdevRulesInfo } from 'uhk-common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConfigBufferId,
|
ConfigBufferId,
|
||||||
@@ -10,9 +13,10 @@ import {
|
|||||||
KbootCommands,
|
KbootCommands,
|
||||||
ModuleSlotToI2cAddress,
|
ModuleSlotToI2cAddress,
|
||||||
ModuleSlotToId,
|
ModuleSlotToId,
|
||||||
UsbCommand
|
UsbCommand,
|
||||||
|
UsbVariables
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util';
|
import { bufferToString, getFileContentAsync, getTransferData, isUhkDevice, isUhkZeroInterface, retry, snooze } from './util';
|
||||||
|
|
||||||
export const BOOTLOADER_TIMEOUT_MS = 5000;
|
export const BOOTLOADER_TIMEOUT_MS = 5000;
|
||||||
|
|
||||||
@@ -27,9 +31,11 @@ export class UhkHidDevice {
|
|||||||
private _prevDevices = [];
|
private _prevDevices = [];
|
||||||
private _device: HID;
|
private _device: HID;
|
||||||
private _hasPermission = false;
|
private _hasPermission = false;
|
||||||
|
private _udevRulesInfo = UdevRulesInfo.Unkonwn;
|
||||||
|
|
||||||
constructor(private logService: LogService,
|
constructor(private logService: LogService,
|
||||||
private options: CommandLineArgs) {
|
private options: CommandLineArgs,
|
||||||
|
private rootDir: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +55,11 @@ export class UhkHidDevice {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dev = devices().find((x: Device) => isUhkDevice(x) || x.productId === Constants.BOOTLOADER_ID);
|
this.logService.debug('[UhkHidDevice] Devices before check permission:');
|
||||||
|
const devs = devices();
|
||||||
|
this.logDevices(devs);
|
||||||
|
|
||||||
|
const dev = devs.find((x: Device) => isUhkZeroInterface(x) || x.productId === Constants.BOOTLOADER_ID);
|
||||||
|
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
return true;
|
return true;
|
||||||
@@ -69,20 +79,26 @@ export class UhkHidDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return with true is an UHK Device is connected to the computer.
|
* Return with the USB device communication sate.
|
||||||
* @returns {boolean}
|
* @returns {DeviceConnectionState}
|
||||||
*/
|
*/
|
||||||
public getDeviceConnectionState(): DeviceConnectionState {
|
public async getDeviceConnectionStateAsync(): Promise<DeviceConnectionState> {
|
||||||
const devs = devices();
|
const devs = devices();
|
||||||
const result: DeviceConnectionState = {
|
const result: DeviceConnectionState = {
|
||||||
bootloaderActive: false,
|
bootloaderActive: false,
|
||||||
connected: false,
|
connected: false,
|
||||||
hasPermission: this.hasPermission()
|
zeroInterfaceAvailable: false,
|
||||||
|
hasPermission: this.hasPermission(),
|
||||||
|
udevRulesInfo: await this.getUdevInfoAsync()
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const dev of devs) {
|
for (const dev of devs) {
|
||||||
if (isUhkDevice(dev)) {
|
if (isUhkDevice(dev)) {
|
||||||
result.connected = true;
|
result.connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUhkZeroInterface(dev)) {
|
||||||
|
result.zeroInterfaceAvailable = true;
|
||||||
} else if (dev.vendorId === Constants.VENDOR_ID &&
|
} else if (dev.vendorId === Constants.VENDOR_ID &&
|
||||||
dev.productId === Constants.BOOTLOADER_ID) {
|
dev.productId === Constants.BOOTLOADER_ID) {
|
||||||
result.bootloaderActive = true;
|
result.bootloaderActive = true;
|
||||||
@@ -133,6 +149,11 @@ export class UhkHidDevice {
|
|||||||
await this.waitUntilKeyboardBusy();
|
await this.waitUntilKeyboardBusy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async enableUsbStackTest(): Promise<void> {
|
||||||
|
await this.write(new Buffer([UsbCommand.SetVariable, UsbVariables.testUsbStack, 1]));
|
||||||
|
await this.waitUntilKeyboardBusy();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the communication chanel with UHK Device
|
* Close the communication chanel with UHK Device
|
||||||
*/
|
*/
|
||||||
@@ -252,17 +273,25 @@ export class UhkHidDevice {
|
|||||||
private connectToDevice(): HID {
|
private connectToDevice(): HID {
|
||||||
try {
|
try {
|
||||||
const devs = devices();
|
const devs = devices();
|
||||||
if (!isEqualArray(this._prevDevices, devs)) {
|
let compareDevices = devs as any;
|
||||||
|
|
||||||
|
if (platform() === 'linux') {
|
||||||
|
compareDevices = devs.map(x => ({
|
||||||
|
productId: x.productId,
|
||||||
|
vendorId: x.vendorId,
|
||||||
|
interface: x.interface
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEqualArray(this._prevDevices, compareDevices)) {
|
||||||
this.logService.debug('[UhkHidDevice] Available devices:');
|
this.logService.debug('[UhkHidDevice] Available devices:');
|
||||||
for (const logDevice of devs) {
|
this.logDevices(devs);
|
||||||
this.logService.debug(JSON.stringify(logDevice));
|
this._prevDevices = compareDevices;
|
||||||
}
|
|
||||||
this._prevDevices = devs;
|
|
||||||
} else {
|
} else {
|
||||||
this.logService.debug('[UhkHidDevice] Available devices unchanged');
|
this.logService.debug('[UhkHidDevice] Available devices unchanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
const dev = devs.find(isUhkDevice);
|
const dev = devs.find(isUhkZeroInterface);
|
||||||
|
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
this.logService.debug('[UhkHidDevice] UHK Device not found:');
|
this.logService.debug('[UhkHidDevice] UHK Device not found:');
|
||||||
@@ -271,13 +300,43 @@ export class UhkHidDevice {
|
|||||||
const device = new HID(dev.path);
|
const device = new HID(dev.path);
|
||||||
this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev));
|
this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev));
|
||||||
return device;
|
return device;
|
||||||
}
|
} catch (err) {
|
||||||
catch (err) {
|
|
||||||
this.logService.error('[UhkHidDevice] Can not create device:', err);
|
this.logService.error('[UhkHidDevice] Can not create device:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private logDevices(devs: Array<Device>): void {
|
||||||
|
for (const logDevice of devs) {
|
||||||
|
this.logService.debug(JSON.stringify(logDevice));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getUdevInfoAsync(): Promise<UdevRulesInfo> {
|
||||||
|
if (this._udevRulesInfo === UdevRulesInfo.Ok) {
|
||||||
|
return UdevRulesInfo.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32' || process.platform === 'darwin') {
|
||||||
|
this._udevRulesInfo = UdevRulesInfo.Ok;
|
||||||
|
return UdevRulesInfo.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await pathExists('/etc/udev/rules.d/50-uhk60.rules'))) {
|
||||||
|
return UdevRulesInfo.NeedToSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedUdevSettings = await getFileContentAsync(path.join(this.rootDir, 'rules/50-uhk60.rules'));
|
||||||
|
const currentUdevSettings = await getFileContentAsync('/etc/udev/rules.d/50-uhk60.rules');
|
||||||
|
|
||||||
|
if (isEqualArray(expectedUdevSettings, currentUdevSettings)) {
|
||||||
|
this._udevRulesInfo = UdevRulesInfo.Ok;
|
||||||
|
return UdevRulesInfo.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UdevRulesInfo.Different;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function kbootCommandName(module: ModuleSlotToI2cAddress): string {
|
function kbootCommandName(module: ModuleSlotToI2cAddress): string {
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ export class UhkOperations {
|
|||||||
|
|
||||||
const leftModuleBricked = await this.waitForKbootIdle();
|
const leftModuleBricked = await this.waitForKbootIdle();
|
||||||
if (!leftModuleBricked) {
|
if (!leftModuleBricked) {
|
||||||
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
const msg = '[UhkOperations] Couldn\'t connect to the left keyboard half.';
|
||||||
return;
|
this.logService.error(msg);
|
||||||
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Device } from 'node-hid';
|
import { Device } from 'node-hid';
|
||||||
import { DeviceConnectionState, LogService } from 'uhk-common';
|
import { readFile } from 'fs-extra';
|
||||||
|
import { EOL } from 'os';
|
||||||
|
import { LogService } from 'uhk-common';
|
||||||
|
|
||||||
import { Constants, UsbCommand } from './constants';
|
import { Constants, UsbCommand } from './constants';
|
||||||
|
|
||||||
@@ -98,13 +100,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deviceConnectionStateComparer = (a: DeviceConnectionState, b: DeviceConnectionState): boolean => {
|
export const isUhkZeroInterface = (dev: Device): boolean => {
|
||||||
return a.hasPermission === b.hasPermission
|
|
||||||
&& a.connected === b.connected
|
|
||||||
&& a.bootloaderActive === b.bootloaderActive;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isUhkDevice = (dev: Device): boolean => {
|
|
||||||
return dev.vendorId === Constants.VENDOR_ID &&
|
return dev.vendorId === Constants.VENDOR_ID &&
|
||||||
dev.productId === Constants.PRODUCT_ID &&
|
dev.productId === Constants.PRODUCT_ID &&
|
||||||
// hidapi can not read the interface number on Mac, so check the usage page and usage
|
// hidapi can not read the interface number on Mac, so check the usage page and usage
|
||||||
@@ -112,3 +108,17 @@ export const isUhkDevice = (dev: Device): boolean => {
|
|||||||
(dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware
|
(dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware
|
||||||
dev.interface === 0);
|
dev.interface === 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isUhkDevice = (dev: Device): boolean => {
|
||||||
|
return dev.vendorId === Constants.VENDOR_ID &&
|
||||||
|
(dev.productId === Constants.PRODUCT_ID || dev.productId === Constants.BOOTLOADER_ID);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileContentAsync = async (filePath: string): Promise<Array<string>> => {
|
||||||
|
const fileContent = await readFile(filePath, {encoding: 'utf-8'});
|
||||||
|
|
||||||
|
return fileContent
|
||||||
|
.split(EOL)
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => !x.startsWith('#') && x.length > 0);
|
||||||
|
};
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||||
"../node_modules/select2/dist/js/select2.full.js",
|
|
||||||
"../node_modules/nouislider/distribute/nouislider.js"
|
"../node_modules/nouislider/distribute/nouislider.js"
|
||||||
],
|
],
|
||||||
"environmentSource": "environments/environment.ts",
|
"environmentSource": "environments/environment.ts",
|
||||||
|
|||||||
1
packages/uhk-web/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
out-tsc/
|
||||||
@@ -7,8 +7,8 @@ describe('web App', () => {
|
|||||||
page = new WebPage();
|
page = new WebPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display welcome message', () => {
|
it('should display default device name', () => {
|
||||||
page.navigateTo();
|
page.navigateTo();
|
||||||
expect(page.getParagraphText()).toEqual('Welcome to app!');
|
expect(page.getDeviceName()).toEqual('My UHK');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ export class WebPage {
|
|||||||
return browser.get('/');
|
return browser.get('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
getParagraphText() {
|
getDeviceName() {
|
||||||
return element(by.css('app-root h1')).getText();
|
return element(by.css('body > main-app > side-menu > ul > li:nth-child(1) > div > auto-grow-input > input'))
|
||||||
|
.getAttribute('value');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10983
packages/uhk-web/package-lock.json
generated
@@ -36,8 +36,8 @@
|
|||||||
"@types/electron-devtools-installer": "2.0.2",
|
"@types/electron-devtools-installer": "2.0.2",
|
||||||
"@types/electron-settings": "3.0.0",
|
"@types/electron-settings": "3.0.0",
|
||||||
"@types/file-saver": "0.0.1",
|
"@types/file-saver": "0.0.1",
|
||||||
"@types/jasmine": "2.5.53",
|
"@types/jasmine": "2.8.8",
|
||||||
"@types/jasminewd2": "2.0.2",
|
"@types/jasminewd2": "2.0.3",
|
||||||
"@types/jquery": "3.2.9",
|
"@types/jquery": "3.2.9",
|
||||||
"@types/usb": "1.1.3",
|
"@types/usb": "1.1.3",
|
||||||
"angular-confirmation-popover": "3.2.0",
|
"angular-confirmation-popover": "3.2.0",
|
||||||
@@ -50,27 +50,27 @@
|
|||||||
"dragula": "3.7.2",
|
"dragula": "3.7.2",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"html-webpack-plugin": "^2.29.0",
|
"html-webpack-plugin": "^2.29.0",
|
||||||
"jasmine-core": "2.6.2",
|
"jasmine-core": "3.2.1",
|
||||||
"jasmine-spec-reporter": "4.1.0",
|
"jasmine-spec-reporter": "4.2.1",
|
||||||
"jquery": "3.2.1",
|
"jquery": "3.2.1",
|
||||||
"jsonfile": "3.0.1",
|
"jsonfile": "3.0.1",
|
||||||
"karma": "1.7.0",
|
"karma": "3.0.0",
|
||||||
"karma-chrome-launcher": "2.1.1",
|
"karma-chrome-launcher": "2.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "1.2.1",
|
"karma-coverage-istanbul-reporter": "2.0.1",
|
||||||
"karma-jasmine": "1.1.0",
|
"karma-jasmine": "1.1.2",
|
||||||
"karma-jasmine-html-reporter": "0.2.2",
|
"karma-jasmine-html-reporter": "1.3.1",
|
||||||
"ng2-dragula": "1.5.0",
|
"ng2-dragula": "1.5.0",
|
||||||
"ng2-nouislider": "^1.7.7",
|
"ng2-nouislider": "^1.7.7",
|
||||||
"ng2-select2": "1.0.0-beta.10",
|
|
||||||
"ngx-clipboard": "10.0.0",
|
"ngx-clipboard": "10.0.0",
|
||||||
|
"@ert78gb/ngx-select-ex": "3.7.2",
|
||||||
"ngrx-store-freeze": "0.1.9",
|
"ngrx-store-freeze": "0.1.9",
|
||||||
"nouislider": "^11.1.0",
|
"nouislider": "^11.1.0",
|
||||||
"postcss-url": "^7.1.2",
|
"postcss-url": "^7.1.2",
|
||||||
"protractor": "5.1.2",
|
"protractor": "5.4.0",
|
||||||
"reselect": "3.0.1",
|
"reselect": "3.0.1",
|
||||||
"rxjs": "5.5.8",
|
"rxjs": "5.5.8",
|
||||||
"select2": "4.0.3",
|
|
||||||
"typescript": "2.6.2",
|
"typescript": "2.6.2",
|
||||||
|
"semver": "5.6.0",
|
||||||
"uhk-common": "1.0.0",
|
"uhk-common": "1.0.0",
|
||||||
"xml-loader": "1.2.1",
|
"xml-loader": "1.2.1",
|
||||||
"zone.js": "0.8.26",
|
"zone.js": "0.8.26",
|
||||||
@@ -79,6 +79,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classlist.js": "1.1.20150312",
|
"classlist.js": "1.1.20150312",
|
||||||
"file-saver": "1.3.3"
|
"file-saver": "1.3.3",
|
||||||
|
"spacing-bootstrap-3": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ exports.config = {
|
|||||||
'browserName': 'chrome'
|
'browserName': 'chrome'
|
||||||
},
|
},
|
||||||
directConnect: true,
|
directConnect: true,
|
||||||
baseUrl: 'http://localhost:4200/',
|
baseUrl: 'http://localhost:8080/',
|
||||||
framework: 'jasmine',
|
framework: 'jasmine',
|
||||||
jasmineNodeOpts: {
|
jasmineNodeOpts: {
|
||||||
showColors: true,
|
showColors: true,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<notifier-container></notifier-container>
|
<notifier-container></notifier-container>
|
||||||
<progress-button class="save-to-keyboard-button"
|
<progress-button class="save-to-keyboard-button"
|
||||||
*ngIf="(saveToKeyboardState$ | async).showButton"
|
*ngIf="saveToKeyboardState.showButton"
|
||||||
[@showSaveToKeyboardButton]
|
[@showSaveToKeyboardButton]
|
||||||
[state]="saveToKeyboardState$ | async"
|
[state]="saveToKeyboardState"
|
||||||
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
import { Component, HostListener, ViewEncapsulation, OnDestroy } from '@angular/core';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Action, Store } from '@ngrx/store';
|
import { Action, Store } from '@ngrx/store';
|
||||||
|
|
||||||
import 'rxjs/add/operator/last';
|
import 'rxjs/add/operator/last';
|
||||||
|
|
||||||
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
||||||
|
import { EnableUsbStackTestAction } from './store/actions/device';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
getShowAppUpdateAvailable,
|
getShowAppUpdateAvailable,
|
||||||
deviceConfigurationLoaded,
|
deviceConfigurationLoaded,
|
||||||
runningInElectron,
|
runningInElectron,
|
||||||
saveToKeyboardState
|
saveToKeyboardState,
|
||||||
|
keypressCapturing
|
||||||
} from './store';
|
} from './store';
|
||||||
import { ProgressButtonState } from './store/reducers/progress-button-state';
|
import { ProgressButtonState } from './store/reducers/progress-button-state';
|
||||||
|
|
||||||
@@ -34,17 +37,50 @@ import { ProgressButtonState } from './store/reducers/progress-button-state';
|
|||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class MainAppComponent {
|
export class MainAppComponent implements OnDestroy {
|
||||||
showUpdateAvailable$: Observable<boolean>;
|
showUpdateAvailable$: Observable<boolean>;
|
||||||
deviceConfigurationLoaded$: Observable<boolean>;
|
deviceConfigurationLoaded$: Observable<boolean>;
|
||||||
runningInElectron$: Observable<boolean>;
|
runningInElectron$: Observable<boolean>;
|
||||||
saveToKeyboardState$: Observable<ProgressButtonState>;
|
saveToKeyboardState: ProgressButtonState;
|
||||||
|
|
||||||
|
private keypressCapturing: boolean;
|
||||||
|
private saveToKeyboardStateSubscription: Subscription;
|
||||||
|
private keypressCapturingSubscription: Subscription;
|
||||||
|
|
||||||
constructor(private store: Store<AppState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
||||||
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
||||||
this.runningInElectron$ = store.select(runningInElectron);
|
this.runningInElectron$ = store.select(runningInElectron);
|
||||||
this.saveToKeyboardState$ = store.select(saveToKeyboardState);
|
this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState)
|
||||||
|
.subscribe(data => this.saveToKeyboardState = data);
|
||||||
|
this.keypressCapturingSubscription = store.select(keypressCapturing)
|
||||||
|
.subscribe(data => this.keypressCapturing = data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.saveToKeyboardStateSubscription.unsubscribe();
|
||||||
|
this.keypressCapturingSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.saveToKeyboardState.showButton &&
|
||||||
|
event.ctrlKey &&
|
||||||
|
event.key === 's' &&
|
||||||
|
!event.defaultPrevented &&
|
||||||
|
!this.keypressCapturing) {
|
||||||
|
this.clickedOnProgressButton(this.saveToKeyboardState.action);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey &&
|
||||||
|
event.ctrlKey &&
|
||||||
|
event.metaKey &&
|
||||||
|
event.key === '|' &&
|
||||||
|
!event.defaultPrevented) {
|
||||||
|
this.enableUsbStackTest();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateApp() {
|
updateApp() {
|
||||||
@@ -58,4 +94,8 @@ export class MainAppComponent {
|
|||||||
clickedOnProgressButton(action: Action) {
|
clickedOnProgressButton(action: Action) {
|
||||||
return this.store.dispatch(action);
|
return this.store.dispatch(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableUsbStackTest() {
|
||||||
|
this.store.dispatch(new EnableUsbStackTestAction());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="col-xs-12 pane-title">
|
<h1 class="col-xs-12 pane-title">
|
||||||
<i class="uhk-icon uhk-icon-agent-icon"></i>
|
<i class="uhk-icon uhk-icon-pure-agent-icon"></i>
|
||||||
<span>About</span>
|
<span>About</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="agent-version">Agent version: <span class="text-bold">{{version}}</span></div>
|
<div class="agent-version">Agent version: <span class="text-bold">{{ version }}</span></div>
|
||||||
<div><a class="link-github" (click)="openAgentGitHubPage($event)">Agent on GitHub</a></div>
|
<div><a class="link-github" [href]="agentGithubUrl" externalUrl>Agent on GitHub</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { Constants } from 'uhk-common';
|
import { Constants } from 'uhk-common';
|
||||||
|
|
||||||
import { AppState } from '../../../store';
|
|
||||||
import { getVersions } from '../../../util';
|
import { getVersions } from '../../../util';
|
||||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'about-page',
|
selector: 'about-page',
|
||||||
@@ -16,12 +13,5 @@ import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
|||||||
})
|
})
|
||||||
export class AboutComponent {
|
export class AboutComponent {
|
||||||
version: string = getVersions().version;
|
version: string = getVersions().version;
|
||||||
|
agentGithubUrl = Constants.AGENT_GITHUB_URL;
|
||||||
constructor(private store: Store<AppState>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
openAgentGitHubPage(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { AboutComponent } from './about/about.component';
|
import { AboutComponent } from './about/about.component';
|
||||||
|
import { HelpPageComponent } from './help-page/help-page.component';
|
||||||
|
|
||||||
export const agentRoutes: Routes = [
|
export const agentRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent
|
component: SettingsComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'help',
|
||||||
|
component: HelpPageComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
component: AboutComponent
|
component: AboutComponent
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="row">
|
||||||
|
<h1 class="col-xs-12 pane-title">
|
||||||
|
<i class="fa fa-question-circle"></i>
|
||||||
|
<span class="macro__name pane-title__name">Help</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
Frequently asked questions
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://ultimatehackingkeyboard.com/blog/2018/06/23/how-can-i-type-accented-characters-with-my-uhk" externalUrl>How can I type accented characters with my UHK?</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
Keyboard shortcuts
|
||||||
|
<ul>
|
||||||
|
<li><kbd>CTRL</kbd> + <kbd>Enter</kbd> = Remap key</li>
|
||||||
|
<li><kbd>CTRL</kbd> + <kbd>S</kbd> = Save to keyboard</li>
|
||||||
|
<li>Right click on a key = Capture key</li>
|
||||||
|
<li>Hold Shift while clicking on a key = Remap on all keymaps</li>
|
||||||
|
<li>Hold Alt while clicking on a key = Remap on all layers</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'help-page',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
templateUrl: './help-page.component.html',
|
||||||
|
styleUrls: ['./help-page.component.scss'],
|
||||||
|
host: {
|
||||||
|
'class': 'container-fluid'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class HelpPageComponent {
|
||||||
|
}
|
||||||
@@ -12,13 +12,10 @@
|
|||||||
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p *ngIf="showUnsupportedOsToFirmwareUpgrade$ | async">Firmware update doesn't work on Windows 7, Windows Vista, and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
|
<p *ngIf="runningOnNotSupportedWindows$ | async">Firmware update doesn't work on Windows 7, Windows Vista,
|
||||||
|
and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
|
||||||
|
|
||||||
<p>If the update process fails, disconnect every USB device from your computer including USB hubs, KVM switches, and every USB device. Then connect only your UHK and retry.</p>
|
<p *ngIf="firmwareUpgradeAllowed$ | async">
|
||||||
|
|
||||||
<p>If you tried the above and the update still keeps failing, please <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">create a GitHub issue</a>, and attach the update log.</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||||
(click)="onUpdateFirmware()">
|
(click)="onUpdateFirmware()">
|
||||||
@@ -29,9 +26,23 @@
|
|||||||
accept=".tar.bz2"
|
accept=".tar.bz2"
|
||||||
label="Choose firmware file and flash it"></file-upload>
|
label="Choose firmware file and flash it"></file-upload>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div *ngIf="firmwareUpgradeFailed$ | async"
|
||||||
|
class="alert alert-danger"
|
||||||
|
role="alert">
|
||||||
|
<p>Firmware update failed. Disconnect every USB device from your computer (including USB hubs, KVM switches, USB dongles, and everything else), then connect only your UHK and retry.</p>
|
||||||
|
|
||||||
|
<p>If you've tried the above and the update still keeps failing, please <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">create a GitHub issue</a>, and attach the update log.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="firmwareUpgradeSuccess$ | async"
|
||||||
|
class="alert alert-success"
|
||||||
|
role="alert">
|
||||||
|
<p>Firmware update succeeded.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-grow">
|
<div class="flex-grow" *ngIf="firmwareUpgradeAllowed$ | async">
|
||||||
<xterm [logs]="xtermLog$ | async"></xterm>
|
<xterm [logs]="xtermLog$ | async"></xterm>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-footer">
|
<div class="flex-footer">
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ import { Constants, HardwareModules, VersionInformation } from 'uhk-common';
|
|||||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
|
firmwareUpgradeAllowed,
|
||||||
|
firmwareUpgradeFailed,
|
||||||
|
firmwareUpgradeSuccess,
|
||||||
flashFirmwareButtonDisbabled,
|
flashFirmwareButtonDisbabled,
|
||||||
getAgentVersionInfo,
|
getAgentVersionInfo,
|
||||||
getHardwareModules,
|
getHardwareModules,
|
||||||
showUnsupportedOsToFirmwareUpgrade,
|
runningOnNotSupportedWindows,
|
||||||
xtermLog
|
xtermLog
|
||||||
} from '../../../store';
|
} from '../../../store';
|
||||||
import { UpdateFirmwareAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
import { UpdateFirmwareAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
||||||
@@ -31,7 +34,10 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
|||||||
getAgentVersionInfo$: Observable<VersionInformation>;
|
getAgentVersionInfo$: Observable<VersionInformation>;
|
||||||
hardwareModulesSubscription: Subscription;
|
hardwareModulesSubscription: Subscription;
|
||||||
hardwareModules: HardwareModules;
|
hardwareModules: HardwareModules;
|
||||||
showUnsupportedOsToFirmwareUpgrade$: Observable<boolean>;
|
runningOnNotSupportedWindows$: Observable<boolean>;
|
||||||
|
firmwareUpgradeAllowed$: Observable<boolean>;
|
||||||
|
firmwareUpgradeFailed$: Observable<boolean>;
|
||||||
|
firmwareUpgradeSuccess$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(private store: Store<AppState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
|
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
|
||||||
@@ -40,7 +46,10 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
|||||||
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
||||||
this.hardwareModules = data;
|
this.hardwareModules = data;
|
||||||
});
|
});
|
||||||
this.showUnsupportedOsToFirmwareUpgrade$ = store.select(showUnsupportedOsToFirmwareUpgrade);
|
this.runningOnNotSupportedWindows$ = store.select(runningOnNotSupportedWindows);
|
||||||
|
this.firmwareUpgradeAllowed$ = store.select(firmwareUpgradeAllowed);
|
||||||
|
this.firmwareUpgradeFailed$ = store.select(firmwareUpgradeFailed);
|
||||||
|
this.firmwareUpgradeSuccess$ = store.select(firmwareUpgradeSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<div class="row led-setting">
|
<div class="row led-setting">
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<slider-wrapper
|
<slider-wrapper
|
||||||
label="Key backlight brightness"
|
label="Key backlight brightness <span class='text-muted pl-1'>Please note that current UHK versions are not backlit.</span>"
|
||||||
[min]="0"
|
[min]="0"
|
||||||
[max]="255"
|
[max]="255"
|
||||||
[step]="1"
|
[step]="1"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
:host {
|
:host {
|
||||||
overflow-y: auto;
|
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
[description]="description"
|
[description]="description"
|
||||||
[showDescription]="true"
|
[showDescription]="true"
|
||||||
|
oncontextmenu="return false;"
|
||||||
(keyClick)="keyClick.emit($event)"
|
(keyClick)="keyClick.emit($event)"
|
||||||
(keyHover)="keyHover.emit($event)"
|
(keyHover)="keyHover.emit($event)"
|
||||||
(capture)="capture.emit($event)"
|
(capture)="capture.emit($event)"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 809 B After Width: | Height: | Size: 853 B |
@@ -3,6 +3,11 @@ import { animate, keyframes, state, style, transition, trigger } from '@angular/
|
|||||||
import { Layer } from 'uhk-common';
|
import { Layer } from 'uhk-common';
|
||||||
|
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import {
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyHoverEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
type AnimationKeyboard =
|
type AnimationKeyboard =
|
||||||
'init' |
|
'init' |
|
||||||
@@ -82,9 +87,9 @@ export class KeyboardSliderComponent implements OnChanges {
|
|||||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||||
@Output() descriptionChanged = new EventEmitter<string>();
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
layerAnimationState: AnimationKeyboard[];
|
layerAnimationState: AnimationKeyboard[];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Keymap } from 'uhk-common';
|
import { Keymap } from 'uhk-common';
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ import { KeymapActions } from '../../../store/actions';
|
|||||||
selector: 'keymap-add',
|
selector: 'keymap-add',
|
||||||
templateUrl: './keymap-add.component.html',
|
templateUrl: './keymap-add.component.html',
|
||||||
styleUrls: ['./keymap-add.component.scss'],
|
styleUrls: ['./keymap-add.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
host: {
|
host: {
|
||||||
'class': 'container-fluid'
|
'class': 'container-fluid'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,3 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, HostListener } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Keymap } from 'uhk-common';
|
import { Keymap } from 'uhk-common';
|
||||||
@@ -14,7 +14,7 @@ import 'rxjs/add/operator/combineLatest';
|
|||||||
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
import { allowLayerDoubleTap, AppState, getKeyboardLayout } from '../../../store';
|
import { layerDoubleTapSupported, AppState, getKeyboardLayout } from '../../../store';
|
||||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
import { KeymapActions } from '../../../store/actions';
|
import { KeymapActions } from '../../../store/actions';
|
||||||
@@ -24,6 +24,7 @@ import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription
|
|||||||
selector: 'keymap-edit',
|
selector: 'keymap-edit',
|
||||||
templateUrl: './keymap-edit.component.html',
|
templateUrl: './keymap-edit.component.html',
|
||||||
styleUrls: ['./keymap-edit.component.scss'],
|
styleUrls: ['./keymap-edit.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
host: {
|
host: {
|
||||||
'class': 'container-fluid'
|
'class': 'container-fluid'
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ export class KeymapEditComponent {
|
|||||||
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
||||||
|
|
||||||
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
||||||
this.allowLayerDoubleTap$ = store.select(allowLayerDoubleTap);
|
this.allowLayerDoubleTap$ = store.select(layerDoubleTapSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadKeymap() {
|
downloadKeymap() {
|
||||||
|
|||||||
@@ -43,4 +43,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,10 +93,17 @@
|
|||||||
<h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4>
|
<h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4>
|
||||||
<h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4>
|
<h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button *ngFor="let buttonLabel of buttonLabels; let buttonIndex = index"
|
<button class="btn btn-default"
|
||||||
class="btn btn-default"
|
[class.btn-primary]="hasButton(MouseButtons.Left)"
|
||||||
[class.btn-primary]="hasButton(buttonIndex)"
|
(click)="setMouseClick(MouseButtons.Left)">Left
|
||||||
(click)="setMouseClick(buttonIndex)">{{buttonLabel}}
|
</button>
|
||||||
|
<button class="btn btn-default"
|
||||||
|
[class.btn-primary]="hasButton(MouseButtons.Middle)"
|
||||||
|
(click)="setMouseClick(MouseButtons.Middle)">Middle
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default"
|
||||||
|
[class.btn-primary]="hasButton(MouseButtons.Right)"
|
||||||
|
(click)="setMouseClick(MouseButtons.Right)">Right
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
MacroMouseSubAction,
|
||||||
|
MouseButtons,
|
||||||
MouseButtonMacroAction,
|
MouseButtonMacroAction,
|
||||||
MoveMouseMacroAction,
|
MoveMouseMacroAction,
|
||||||
ScrollMouseMacroAction,
|
ScrollMouseMacroAction
|
||||||
MacroMouseSubAction
|
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
import { Tab } from '../../../../popover/tab';
|
import { Tab } from '../../../../popover/tab';
|
||||||
import { MacroBaseComponent } from '../macro-base.component';
|
import { MacroBaseComponent } from '../macro-base.component';
|
||||||
@@ -33,6 +34,7 @@ export class MacroMouseTabComponent extends MacroBaseComponent implements OnInit
|
|||||||
@ViewChild('tab') selectedTab: Tab;
|
@ViewChild('tab') selectedTab: Tab;
|
||||||
|
|
||||||
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
|
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
|
||||||
|
MouseButtons = MouseButtons;
|
||||||
TabName = TabName;
|
TabName = TabName;
|
||||||
/* tslint:enable:variable-name */
|
/* tslint:enable:variable-name */
|
||||||
activeTab: TabName;
|
activeTab: TabName;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
Input,
|
Input,
|
||||||
Renderer,
|
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { TextMacroAction } from 'uhk-common';
|
import { TextMacroAction } from 'uhk-common';
|
||||||
@@ -23,14 +22,14 @@ export class MacroTextTabComponent extends MacroBaseComponent implements OnInit,
|
|||||||
@Input() macroAction: TextMacroAction;
|
@Input() macroAction: TextMacroAction;
|
||||||
@ViewChild('macroTextInput') input: ElementRef;
|
@ViewChild('macroTextInput') input: ElementRef;
|
||||||
|
|
||||||
constructor(private renderer: Renderer) { super(); }
|
constructor() { super(); }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.renderer.invokeElementMethod(this.input.nativeElement, 'focus');
|
this.input.nativeElement.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTextChange() {
|
onTextChange() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
></macro-header>
|
></macro-header>
|
||||||
<macro-list
|
<macro-list
|
||||||
[macro]="macro"
|
[macro]="macro"
|
||||||
|
[macroPlaybackSupported]="macroPlaybackSupported$ | async"
|
||||||
(add)="addAction($event.macroId, $event.action)"
|
(add)="addAction($event.macroId, $event.action)"
|
||||||
(edit)="editAction($event.macroId, $event.index, $event.action)"
|
(edit)="editAction($event.macroId, $event.index, $event.action)"
|
||||||
(delete)="deleteAction($event.macroId, $event.index, $event.action)"
|
(delete)="deleteAction($event.macroId, $event.index, $event.action)"
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Macro, MacroAction } from 'uhk-common';
|
import { Macro, MacroAction } from 'uhk-common';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import 'rxjs/add/operator/pluck';
|
import 'rxjs/add/operator/pluck';
|
||||||
|
|
||||||
import { MacroActions } from '../../../store/actions';
|
import { MacroActions } from '../../../store/actions';
|
||||||
import { AppState } from '../../../store';
|
import { AppState, macroPlaybackSupported } from '../../../store';
|
||||||
import { getMacro } from '../../../store/reducers/user-configuration';
|
import { getMacro } from '../../../store/reducers/user-configuration';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -22,6 +23,7 @@ export class MacroEditComponent implements OnDestroy {
|
|||||||
macro: Macro;
|
macro: Macro;
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
macroId: number;
|
macroId: number;
|
||||||
|
macroPlaybackSupported$: Observable<boolean>;
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
||||||
@@ -37,6 +39,7 @@ export class MacroEditComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.isNew = this.route.snapshot.params['empty'] === 'new';
|
this.isNew = this.route.snapshot.params['empty'] === 'new';
|
||||||
|
this.macroPlaybackSupported$ = this.store.select(macroPlaybackSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
|
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group-item macro-action-editor__container"
|
<div class="list-group-item macro-action-editor__container"
|
||||||
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'">
|
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'"
|
||||||
<macro-action-editor
|
[style.overflow]="overflow">
|
||||||
[macroAction]="macroAction"
|
<macro-action-editor
|
||||||
(cancel)="cancelEdit()"
|
*ngIf="editable || newItem"
|
||||||
(save)="saveEditedAction($event)">
|
[macroAction]="macroAction"
|
||||||
</macro-action-editor>
|
(cancel)="cancelEdit()"
|
||||||
</div>
|
(save)="saveEditedAction($event)">
|
||||||
|
</macro-action-editor>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import '../../../../styles/variables';
|
@import '../../../../styles/variables';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
&.macro-item:first-of-type {
|
&.macro-item:first-of-type {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
KeyMacroAction,
|
KeyMacroAction,
|
||||||
KeyModifiers,
|
KeyModifiers,
|
||||||
MacroAction,
|
MacroAction,
|
||||||
|
MouseButtons,
|
||||||
MouseButtonMacroAction,
|
MouseButtonMacroAction,
|
||||||
MoveMouseMacroAction,
|
MoveMouseMacroAction,
|
||||||
ScrollMouseMacroAction,
|
ScrollMouseMacroAction,
|
||||||
@@ -45,6 +46,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
iconName: string;
|
iconName: string;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
newItem: boolean = false;
|
newItem: boolean = false;
|
||||||
|
overflow = 'hidden';
|
||||||
|
|
||||||
constructor(private mapper: MapperService) { }
|
constructor(private mapper: MapperService) { }
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
if (!this.macroAction) {
|
if (!this.macroAction) {
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
this.newItem = true;
|
this.newItem = true;
|
||||||
|
this.overflow = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +68,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
saveEditedAction(editedAction: MacroAction): void {
|
saveEditedAction(editedAction: MacroAction): void {
|
||||||
this.macroAction = editedAction;
|
this.macroAction = editedAction;
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
|
this.overflow = 'hidden';
|
||||||
this.updateView();
|
this.updateView();
|
||||||
this.save.emit(editedAction);
|
this.save.emit(editedAction);
|
||||||
}
|
}
|
||||||
@@ -77,10 +81,12 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
this.edit.emit();
|
this.edit.emit();
|
||||||
|
this.setOverflow('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelEdit(): void {
|
cancelEdit(): void {
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
|
this.overflow = 'hidden';
|
||||||
this.cancel.emit();
|
this.cancel.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +155,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
// Tap/press/release modifiers
|
// Tap/press/release modifiers
|
||||||
for (let i = KeyModifiers.leftCtrl; i <= KeyModifiers.rightGui; i <<= 1) {
|
for (let i = KeyModifiers.leftCtrl; i <= KeyModifiers.rightGui; i <<= 1) {
|
||||||
if (action.isModifierActive(i)) {
|
if (action.isModifierActive(i)) {
|
||||||
this.title += ' ' + KeyModifiers[i];
|
this.title += ' ' + this.mapper.getOsSpecificModifierTextByValue(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +177,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
let needAnd: boolean;
|
let needAnd: boolean;
|
||||||
if (Math.abs(typedAction.x) !== 0) {
|
if (Math.abs(typedAction.x) !== 0) {
|
||||||
this.title += ` by ${Math.abs(typedAction.x)}px ${typedAction.x > 0 ? 'leftward' : 'rightward'}`;
|
this.title += ` by ${Math.abs(typedAction.x)}px ${typedAction.x > 0 ? 'rightward' : 'leftward'}`;
|
||||||
needAnd = true;
|
needAnd = true;
|
||||||
}
|
}
|
||||||
if (Math.abs(typedAction.y) !== 0) {
|
if (Math.abs(typedAction.y) !== 0) {
|
||||||
@@ -192,14 +198,21 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
this.title = 'Release mouse button: ';
|
this.title = 'Release mouse button: ';
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonLabels: string[] = ['Left', 'Middle', 'Right'];
|
|
||||||
const selectedButtons: boolean[] = action.getMouseButtons();
|
const selectedButtons: boolean[] = action.getMouseButtons();
|
||||||
const selectedButtonLabels: string[] = [];
|
const selectedButtonLabels: string[] = [];
|
||||||
selectedButtons.forEach((isSelected, idx) => {
|
selectedButtons.forEach((isSelected, idx) => {
|
||||||
if (isSelected && buttonLabels[idx]) {
|
if (isSelected && MouseButtons[idx]) {
|
||||||
selectedButtonLabels.push(buttonLabels[idx]);
|
selectedButtonLabels.push(MouseButtons[idx]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.title += selectedButtonLabels.join(', ');
|
this.title += selectedButtonLabels.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setOverflow(value: string): void {
|
||||||
|
// tslint:disable: align
|
||||||
|
setTimeout(() => {
|
||||||
|
this.overflow = value;
|
||||||
|
}, 600);
|
||||||
|
// tslint:enable: align
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="row list-container">
|
<div class="row list-container">
|
||||||
<div class="col-xs-10 col-xs-offset-1 list-group">
|
<div class="col-xs-10 col-xs-offset-1 list-group">
|
||||||
<p><i>Please note that macro playback is not implemented yet. You can create macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
|
<p *ngIf="!macroPlaybackSupported"><i>Please note that macro playback is not implemented yet. You can create macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
|
||||||
<div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions">
|
<div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions">
|
||||||
<macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index"
|
<macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index"
|
||||||
[macroAction]="macroAction"
|
[macroAction]="macroAction"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { MacroItemComponent } from '../item';
|
|||||||
})
|
})
|
||||||
export class MacroListComponent {
|
export class MacroListComponent {
|
||||||
@Input() macro: Macro;
|
@Input() macro: Macro;
|
||||||
|
@Input() macroPlaybackSupported: boolean;
|
||||||
@ViewChildren(forwardRef(() => MacroItemComponent)) macroItems: QueryList<MacroItemComponent>;
|
@ViewChildren(forwardRef(() => MacroItemComponent)) macroItems: QueryList<MacroItemComponent>;
|
||||||
|
|
||||||
@Output() add = new EventEmitter();
|
@Output() add = new EventEmitter();
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<uhk-message header="Cannot find your UHK" subtitle="Please plug it in!"></uhk-message>
|
<uhk-message [header]="state.header" [subtitle]="state.subtitle"></uhk-message>
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
|
||||||
import 'rxjs/add/operator/distinctUntilChanged';
|
import { AppState, getMissingDeviceState } from '../../store';
|
||||||
import 'rxjs/add/operator/ignoreElements';
|
import { MissingDeviceState } from '../../models/missing-device-state';
|
||||||
import 'rxjs/add/operator/takeWhile';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'missing-device',
|
selector: 'missing-device',
|
||||||
templateUrl: './missing-device.component.html'
|
templateUrl: './missing-device.component.html'
|
||||||
})
|
})
|
||||||
export class MissingDeviceComponent {
|
export class MissingDeviceComponent implements OnDestroy {
|
||||||
|
|
||||||
constructor() {}
|
state: MissingDeviceState;
|
||||||
|
|
||||||
|
private stateSubscription: Subscription;
|
||||||
|
|
||||||
|
constructor(private store: Store<AppState>) {
|
||||||
|
this.stateSubscription = this.store
|
||||||
|
.select(getMissingDeviceState)
|
||||||
|
.subscribe(state => this.state = state);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.stateSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,71 +8,50 @@
|
|||||||
<div class="arrowCustom"></div>
|
<div class="arrowCustom"></div>
|
||||||
<div class="popover-title menu-tabs">
|
<div class="popover-title menu-tabs">
|
||||||
<ul class="nav nav-tabs popover-menu">
|
<ul class="nav nav-tabs popover-menu">
|
||||||
<li #keypress [class.active]="activeTab === tabName.Keypress" (click)="selectTab(tabName.Keypress)">
|
<li *ngFor="let tab of tabHeaders; trackBy:trackTabHeader"
|
||||||
|
[class.active]="activeTab === tab.tabName"
|
||||||
|
[class.disabled]="tab.disabled"
|
||||||
|
(click)="selectTab(tab)">
|
||||||
<a class="menu-tabs--item">
|
<a class="menu-tabs--item">
|
||||||
<i class="fa fa-keyboard-o"></i>
|
<i class="fa"
|
||||||
<span>Keypress</span>
|
[ngClass]="tab.icon"></i>
|
||||||
</a>
|
<span>{{ tab.text }}</span>
|
||||||
</li>
|
|
||||||
<li #layer [class.active]="activeTab === tabName.Layer" (click)="selectTab(tabName.Layer)">
|
|
||||||
<a class="menu-tabs--item">
|
|
||||||
<i class="fa fa-clone"></i>
|
|
||||||
<span>Layer</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li #mouse [class.active]="activeTab === tabName.Mouse" (click)="selectTab(tabName.Mouse)">
|
|
||||||
<a class="menu-tabs--item">
|
|
||||||
<i class="fa fa-mouse-pointer"></i>
|
|
||||||
<span>Mouse</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li #macro [class.active]="activeTab === tabName.Macro" (click)="selectTab(tabName.Macro)">
|
|
||||||
<a class="menu-tabs--item">
|
|
||||||
<i class="fa fa-play"></i>
|
|
||||||
<span>Macro</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li #keymap [class.active]="activeTab === tabName.Keymap" (click)="selectTab(tabName.Keymap)">
|
|
||||||
<a class="menu-tabs--item">
|
|
||||||
<i class="fa fa-keyboard-o"></i>
|
|
||||||
<span>Keymap</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li #none [class.active]="activeTab === tabName.None" (click)="selectTab(tabName.None)">
|
|
||||||
<a class="menu-tabs--item">
|
|
||||||
<i class="fa fa-ban"></i>
|
|
||||||
<span>None</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div [ngSwitch]="activeTab">
|
<div [ngSwitch]="activeTab">
|
||||||
<keypress-tab #tab *ngSwitchCase="tabName.Keypress" class="popover-content"
|
<keypress-tab #tab *ngSwitchCase="tabName.Keypress" class="popover-content pr-10"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="shadowKeyAction"
|
||||||
[secondaryRoleEnabled]="true"
|
[secondaryRoleEnabled]="true"
|
||||||
(validAction)="keyActionValid=$event"
|
[allowRemapOnAllKeymapWarning]="true"
|
||||||
|
[remapInfo]="remapInfo"
|
||||||
|
[showLayerSwitcherInSecondaryRoles]="currentLayer === 0"
|
||||||
|
(validAction)="setKeyActionValidState($event)"
|
||||||
|
(keyActionChange)="keystrokeActionChange($event)"
|
||||||
></keypress-tab>
|
></keypress-tab>
|
||||||
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="defaultKeyAction"
|
||||||
[currentLayer]="currentLayer"
|
[currentLayer]="currentLayer"
|
||||||
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
||||||
(validAction)="keyActionValid=$event"
|
(validAction)="setKeyActionValidState($event)"
|
||||||
></layer-tab>
|
></layer-tab>
|
||||||
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="defaultKeyAction"
|
||||||
(validAction)="keyActionValid=$event"
|
(validAction)="setKeyActionValidState($event)"
|
||||||
></mouse-tab>
|
></mouse-tab>
|
||||||
<macro-tab #tab *ngSwitchCase="tabName.Macro" class="popover-content"
|
<macro-tab #tab *ngSwitchCase="tabName.Macro" class="popover-content"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="defaultKeyAction"
|
||||||
(validAction)="keyActionValid=$event"
|
[macroPlaybackSupported]="macroPlaybackSupported$ | async"
|
||||||
|
(validAction)="setKeyActionValidState($event)"
|
||||||
></macro-tab>
|
></macro-tab>
|
||||||
<keymap-tab #tab *ngSwitchCase="tabName.Keymap" class="popover-content"
|
<keymap-tab #tab *ngSwitchCase="tabName.Keymap" class="popover-content"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="defaultKeyAction"
|
||||||
[keymaps]="keymaps$ | async"
|
[keymaps]="keymaps$ | async"
|
||||||
(validAction)="keyActionValid=$event"
|
(validAction)="setKeyActionValidState($event)"
|
||||||
></keymap-tab>
|
></keymap-tab>
|
||||||
<none-tab #tab *ngSwitchCase="tabName.None" class="popover-content"
|
<none-tab #tab *ngSwitchCase="tabName.None" class="popover-content"
|
||||||
(validAction)="keyActionValid=$event"
|
(validAction)="setKeyActionValidState($event)"
|
||||||
></none-tab>
|
></none-tab>
|
||||||
</div>
|
</div>
|
||||||
<div class="popover-action">
|
<div class="popover-action">
|
||||||
@@ -81,14 +60,16 @@
|
|||||||
<label>
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="remapOnAllKeymap"
|
name="remapOnAllKeymap"
|
||||||
[(ngModel)]="remapOnAllKeymap"> Remap on all keymaps
|
[(ngModel)]="remapInfo.remapOnAllKeymap"> Remap on all keymaps
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label [ngClass]="{ disabled: disableRemapOnAllLayer }">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="remapOnAllLayer"
|
name="remapOnAllLayer"
|
||||||
[(ngModel)]="remapOnAllLayer"> Remap on all layers
|
[(ngModel)]="remapInfo.remapOnAllLayer"
|
||||||
|
[disabled]="disableRemapOnAllLayer"
|
||||||
|
(ngModelChange)="remapInfoChange()"> Remap on all layers
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-inline-block">
|
<div class="d-inline-block">
|
||||||
@@ -109,7 +90,7 @@
|
|||||||
<div class="d-inline-block pull-right">
|
<div class="d-inline-block pull-right">
|
||||||
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel</button>
|
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel</button>
|
||||||
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button"
|
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button"
|
||||||
(click)="onRemapKey()"> Remap Key
|
(click)="onRemapKey()"> Remap key
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,6 +28,11 @@
|
|||||||
|
|
||||||
.nav-tabs > li {
|
.nav-tabs > li {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrowCustom {
|
.arrowCustom {
|
||||||
@@ -85,7 +90,6 @@
|
|||||||
.menu-tabs--item {
|
.menu-tabs--item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
i {
|
i {
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
@@ -99,6 +103,10 @@
|
|||||||
padding: 10px 24px;
|
padding: 10px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pr-10 {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.popover-overlay {
|
.popover-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -116,27 +124,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-item {
|
|
||||||
position: relative;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
|
|
||||||
&.keymap-name--wrapper {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-segment-code {
|
|
||||||
height: 2rem;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-action-form {
|
.popover-action-form {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #959595;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@@ -23,15 +25,17 @@ import {
|
|||||||
KeystrokeAction,
|
KeystrokeAction,
|
||||||
MouseAction,
|
MouseAction,
|
||||||
PlayMacroAction,
|
PlayMacroAction,
|
||||||
|
SecondaryRoleAction,
|
||||||
SwitchKeymapAction,
|
SwitchKeymapAction,
|
||||||
SwitchLayerAction
|
SwitchLayerAction
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from './tab';
|
import { Tab } from './tab';
|
||||||
|
|
||||||
import { AppState } from '../../store';
|
import { AppState, macroPlaybackSupported } from '../../store';
|
||||||
import { getKeymaps } from '../../store/reducers/user-configuration';
|
import { getKeymaps } from '../../store/reducers/user-configuration';
|
||||||
import { KeyActionRemap } from '../../models/key-action-remap';
|
import { KeyActionRemap } from '../../models/key-action-remap';
|
||||||
|
import { RemapInfo } from '../../models/remap-info';
|
||||||
|
|
||||||
enum TabName {
|
enum TabName {
|
||||||
Keypress,
|
Keypress,
|
||||||
@@ -42,10 +46,18 @@ enum TabName {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TabHeader {
|
||||||
|
text: string;
|
||||||
|
icon: string;
|
||||||
|
tabName: TabName;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'popover',
|
selector: 'popover',
|
||||||
templateUrl: './popover.component.html',
|
templateUrl: './popover.component.html',
|
||||||
styleUrls: ['./popover.component.scss'],
|
styleUrls: ['./popover.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
animations: [
|
animations: [
|
||||||
trigger('popover', [
|
trigger('popover', [
|
||||||
state('closed', style({
|
state('closed', style({
|
||||||
@@ -84,6 +96,7 @@ export class PopoverComponent implements OnChanges {
|
|||||||
@Input() wrapPosition: any;
|
@Input() wrapPosition: any;
|
||||||
@Input() visible: boolean;
|
@Input() visible: boolean;
|
||||||
@Input() allowLayerDoubleTap: boolean;
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
|
@Input() remapInfo: RemapInfo;
|
||||||
|
|
||||||
@Output() cancel = new EventEmitter<any>();
|
@Output() cancel = new EventEmitter<any>();
|
||||||
@Output() remap = new EventEmitter<KeyActionRemap>();
|
@Output() remap = new EventEmitter<KeyActionRemap>();
|
||||||
@@ -100,19 +113,53 @@ export class PopoverComponent implements OnChanges {
|
|||||||
topPosition: number = 0;
|
topPosition: number = 0;
|
||||||
leftPosition: number = 0;
|
leftPosition: number = 0;
|
||||||
animationState: string;
|
animationState: string;
|
||||||
|
shadowKeyAction: KeyAction;
|
||||||
remapOnAllKeymap: boolean;
|
disableRemapOnAllLayer = false;
|
||||||
remapOnAllLayer: boolean;
|
tabHeaders: TabHeader[] = [
|
||||||
|
{
|
||||||
|
tabName: TabName.Keypress,
|
||||||
|
icon: 'fa-keyboard-o',
|
||||||
|
text: 'Keypress'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabName: TabName.Layer,
|
||||||
|
icon: 'fa-clone',
|
||||||
|
text: 'Layer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabName: TabName.Mouse,
|
||||||
|
icon: 'fa-mouse-pointer',
|
||||||
|
text: 'Mouse'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabName: TabName.Macro,
|
||||||
|
icon: 'fa-play',
|
||||||
|
text: 'Macro'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabName: TabName.Keymap,
|
||||||
|
icon: 'fa-keyboard-o',
|
||||||
|
text: 'Keymap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabName: TabName.None,
|
||||||
|
icon: 'fa-ban',
|
||||||
|
text: 'None'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
macroPlaybackSupported$: Observable<boolean>;
|
||||||
|
|
||||||
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
|
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
|
||||||
|
|
||||||
constructor(store: Store<AppState>) {
|
constructor(private store: Store<AppState>,
|
||||||
|
private cdRef: ChangeDetectorRef) {
|
||||||
this.animationState = 'closed';
|
this.animationState = 'closed';
|
||||||
this.keymaps$ = store.let(getKeymaps())
|
this.keymaps$ = store.let(getKeymaps())
|
||||||
.combineLatest(this.currentKeymap$)
|
.combineLatest(this.currentKeymap$)
|
||||||
.map(([keymaps, currentKeymap]: [Keymap[], Keymap]) =>
|
.map(([keymaps, currentKeymap]: [Keymap[], Keymap]) =>
|
||||||
keymaps.filter((keymap: Keymap) => currentKeymap.abbreviation !== keymap.abbreviation)
|
keymaps.filter((keymap: Keymap) => currentKeymap.abbreviation !== keymap.abbreviation)
|
||||||
);
|
);
|
||||||
|
this.macroPlaybackSupported$ = store.select(macroPlaybackSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(change: SimpleChanges) {
|
ngOnChanges(change: SimpleChanges) {
|
||||||
@@ -121,30 +168,36 @@ export class PopoverComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (change['defaultKeyAction']) {
|
if (change['defaultKeyAction']) {
|
||||||
let tab: TabName;
|
let tab: TabHeader;
|
||||||
|
this.disableRemapOnAllLayer = false;
|
||||||
|
|
||||||
if (this.defaultKeyAction instanceof KeystrokeAction) {
|
if (this.defaultKeyAction instanceof KeystrokeAction) {
|
||||||
tab = TabName.Keypress;
|
this.keystrokeActionChange(this.defaultKeyAction);
|
||||||
|
tab = this.tabHeaders[0];
|
||||||
} else if (this.defaultKeyAction instanceof SwitchLayerAction) {
|
} else if (this.defaultKeyAction instanceof SwitchLayerAction) {
|
||||||
tab = TabName.Layer;
|
tab = this.tabHeaders[1];
|
||||||
} else if (this.defaultKeyAction instanceof MouseAction) {
|
} else if (this.defaultKeyAction instanceof MouseAction) {
|
||||||
tab = TabName.Mouse;
|
tab = this.tabHeaders[2];
|
||||||
} else if (this.defaultKeyAction instanceof PlayMacroAction) {
|
} else if (this.defaultKeyAction instanceof PlayMacroAction) {
|
||||||
tab = TabName.Macro;
|
tab = this.tabHeaders[3];
|
||||||
} else if (this.defaultKeyAction instanceof SwitchKeymapAction) {
|
} else if (this.defaultKeyAction instanceof SwitchKeymapAction) {
|
||||||
tab = TabName.Keymap;
|
tab = this.tabHeaders[4];
|
||||||
} else {
|
} else {
|
||||||
tab = TabName.None;
|
tab = this.tabHeaders[5];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const tabHeader of this.tabHeaders) {
|
||||||
|
const allowOnlyLayerTab = tab.tabName === TabName.Layer && this.currentLayer !== 0;
|
||||||
|
|
||||||
|
tabHeader.disabled = allowOnlyLayerTab && tabHeader.tabName !== TabName.Layer;
|
||||||
|
console.log(tabHeader);
|
||||||
|
}
|
||||||
this.selectTab(tab);
|
this.selectTab(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change['visible']) {
|
if (change['visible']) {
|
||||||
if (change['visible'].currentValue) {
|
if (change['visible'].currentValue) {
|
||||||
this.animationState = 'opened';
|
this.animationState = 'opened';
|
||||||
this.remapOnAllKeymap = false;
|
|
||||||
this.remapOnAllLayer = false;
|
|
||||||
} else {
|
} else {
|
||||||
this.animationState = 'closed';
|
this.animationState = 'closed';
|
||||||
}
|
}
|
||||||
@@ -163,8 +216,8 @@ export class PopoverComponent implements OnChanges {
|
|||||||
if (this.keyActionValid) {
|
if (this.keyActionValid) {
|
||||||
try {
|
try {
|
||||||
this.remap.emit({
|
this.remap.emit({
|
||||||
remapOnAllKeymap: this.remapOnAllKeymap,
|
remapOnAllKeymap: this.remapInfo.remapOnAllKeymap,
|
||||||
remapOnAllLayer: this.remapOnAllLayer,
|
remapOnAllLayer: this.remapInfo.remapOnAllLayer,
|
||||||
action: this.selectedTab.toKeyAction()
|
action: this.selectedTab.toKeyAction()
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -179,14 +232,62 @@ export class PopoverComponent implements OnChanges {
|
|||||||
this.cancel.emit();
|
this.cancel.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectTab(tab: TabName): void {
|
@HostListener('document:keydown.control.enter', ['$event'])
|
||||||
this.activeTab = tab;
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.visible) {
|
||||||
|
this.onRemapKey();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectTab(tab: TabHeader): void {
|
||||||
|
if (tab.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeTab = tab.tabName;
|
||||||
|
if (tab.tabName === TabName.Keypress) {
|
||||||
|
this.keystrokeActionChange(this.defaultKeyAction as KeystrokeAction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOverlay() {
|
onOverlay() {
|
||||||
this.cancel.emit(undefined);
|
this.cancel.emit(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remapInfoChange(): void {
|
||||||
|
this.selectedTab.remapInfoChanged(this.remapInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
keystrokeActionChange(keystrokeAction: KeystrokeAction): void {
|
||||||
|
this.shadowKeyAction = keystrokeAction;
|
||||||
|
const disableRemapOnAllLayer =
|
||||||
|
keystrokeAction &&
|
||||||
|
this.currentLayer === 0 &&
|
||||||
|
(keystrokeAction.secondaryRoleAction === SecondaryRoleAction.fn ||
|
||||||
|
keystrokeAction.secondaryRoleAction === SecondaryRoleAction.mod ||
|
||||||
|
keystrokeAction.secondaryRoleAction === SecondaryRoleAction.mouse);
|
||||||
|
|
||||||
|
if (this.disableRemapOnAllLayer !== disableRemapOnAllLayer) {
|
||||||
|
this.disableRemapOnAllLayer = disableRemapOnAllLayer;
|
||||||
|
|
||||||
|
if (disableRemapOnAllLayer) {
|
||||||
|
this.remapInfo.remapOnAllLayer = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeyActionValidState($event: boolean): void {
|
||||||
|
this.keyActionValid = $event;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
trackTabHeader(index: number, tabItem: TabHeader): string {
|
||||||
|
return tabItem.tabName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private calculatePosition() {
|
private calculatePosition() {
|
||||||
const offsetLeft: number = this.wrapPosition.left + 265; // 265 is a width of the side menu with a margin
|
const offsetLeft: number = this.wrapPosition.left + 265; // 265 is a width of the side menu with a margin
|
||||||
const popover: HTMLElement = this.popoverHost.nativeElement;
|
const popover: HTMLElement = this.popoverHost.nativeElement;
|
||||||
|
|||||||
@@ -4,12 +4,23 @@
|
|||||||
<ng-template [ngIf]="keymapOptions.length > 0">
|
<ng-template [ngIf]="keymapOptions.length > 0">
|
||||||
<div>
|
<div>
|
||||||
<b>Switch to keymap:</b>
|
<b>Switch to keymap:</b>
|
||||||
<select2
|
<ngx-select [items]="keymapOptions"
|
||||||
[data]="keymapOptions"
|
[ngModel]="selectedKeymap?.abbreviation || -1"
|
||||||
[value]="selectedKeymap?.abbreviation || -1"
|
[autoActiveOnMouseEnter]="false"
|
||||||
(valueChanged)="onChange($event)"
|
size="small"
|
||||||
[width]="'100%'"
|
optionValueField="id"
|
||||||
></select2>
|
optionTextField="text"
|
||||||
|
(select)="onChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</ngx-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
|
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select2 {
|
ngx-select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
|
||||||
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keymap-tab',
|
selector: 'keymap-tab',
|
||||||
@@ -14,7 +14,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
@Input() keymaps: Keymap[];
|
@Input() keymaps: Keymap[];
|
||||||
|
|
||||||
keymapOptions: Array<Select2OptionData>;
|
keymapOptions: Array<SelectOptionData>;
|
||||||
selectedKeymap: Keymap;
|
selectedKeymap: Keymap;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -25,7 +25,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes.keymaps) {
|
if (changes.keymaps) {
|
||||||
this.keymapOptions = this.keymaps
|
this.keymapOptions = this.keymaps
|
||||||
.map((keymap: Keymap): Select2OptionData => {
|
.map((keymap: Keymap): SelectOptionData => {
|
||||||
return {
|
return {
|
||||||
id: keymap.abbreviation,
|
id: keymap.abbreviation,
|
||||||
text: keymap.name
|
text: keymap.name
|
||||||
@@ -40,12 +40,11 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
this.validAction.emit(true);
|
this.validAction.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change to the correct type when the wrapper has added it.
|
onChange(event: string) {
|
||||||
onChange(event: any) {
|
if (event === '-1') {
|
||||||
if (event.value === '-1') {
|
|
||||||
this.selectedKeymap = undefined;
|
this.selectedKeymap = undefined;
|
||||||
} else {
|
} else {
|
||||||
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event.value);
|
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
<div class="scancode-options">
|
<div class="scancode-options">
|
||||||
<b class="setting-label">Scancode:</b>
|
<b class="setting-label">Scancode:</b>
|
||||||
<select2
|
<div class="scancode-container">
|
||||||
[data]="scanCodeGroups"
|
<ngx-select [items]="scanCodeGroups"
|
||||||
[value]="selectedScancodeOption.id"
|
[ngModel]="selectedScancodeOption?.id"
|
||||||
(valueChanged)="onScancodeChange($event)"
|
[autoActiveOnMouseEnter]="false"
|
||||||
[width]="200"
|
size="small"
|
||||||
[options]="options"
|
optionValueField="id"
|
||||||
></select2>
|
optionTextField="text"
|
||||||
|
optGroupLabelField="text"
|
||||||
|
optGroupOptionsField="children"
|
||||||
|
(select)="onScancodeChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '0'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ngx-select>
|
||||||
|
</div>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
html="true"
|
html="true"
|
||||||
@@ -21,32 +36,48 @@
|
|||||||
<div class="btn-toolbar modifiers">
|
<div class="btn-toolbar modifiers">
|
||||||
<div class="btn-group btn-group-sm modifiers__left">
|
<div class="btn-group btn-group-sm modifiers__left">
|
||||||
<button type="button" class="btn btn-default"
|
<button type="button" class="btn btn-default"
|
||||||
*ngFor="let modifier of leftModifiers; let index = index"
|
*ngFor="let modifier of leftModifiers; trackBy:modifiersTrackBy"
|
||||||
[class.btn-primary]="leftModifierSelects[index]"
|
[class.btn-primary]="modifier.checked"
|
||||||
(click)="toggleModifier(false, index)"
|
(click)="toggleModifier(modifier)"
|
||||||
>
|
>
|
||||||
{{modifier}}
|
{{ modifier.text }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group btn-group-sm modifiers__right">
|
<div class="btn-group btn-group-sm modifiers__right">
|
||||||
<button type="button" class="btn btn-default"
|
<button type="button" class="btn btn-default"
|
||||||
*ngFor="let modifier of rightModifiers; let index = index"
|
*ngFor="let modifier of rightModifiers; trackBy:modifiersTrackBy"
|
||||||
[class.btn-primary]="rightModifierSelects[index]"
|
[class.btn-primary]="modifier.checked"
|
||||||
(click)="toggleModifier(true, index)"
|
(click)="toggleModifier(modifier)"
|
||||||
>
|
>
|
||||||
{{modifier}}
|
{{ modifier.text }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
||||||
<b class="setting-label">Secondary role:</b>
|
<b class="setting-label">Secondary role:</b>
|
||||||
<select2 #secondaryRoleSelect
|
<div class="secondary-role-groups-container">
|
||||||
[data]="secondaryRoleGroups"
|
<ngx-select [items]="secondaryRoleGroups"
|
||||||
[value]="selectedSecondaryRoleIndex.toString()"
|
[ngModel]="selectedSecondaryRoleIndex.toString()"
|
||||||
(valueChanged)="onSecondaryRoleChange($event)"
|
[autoActiveOnMouseEnter]="false"
|
||||||
[width]="140"
|
size="small"
|
||||||
></select2>
|
optionValueField="id"
|
||||||
|
optionTextField="text"
|
||||||
|
optGroupLabelField="text"
|
||||||
|
optGroupOptionsField="children"
|
||||||
|
(select)="onSecondaryRoleChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ngx-select>
|
||||||
|
</div>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
html="true"
|
html="true"
|
||||||
@@ -57,10 +88,15 @@
|
|||||||
<li>Tap this key to trigger Escape. <i>(Primary role)</i></li>
|
<li>Tap this key to trigger Escape. <i>(Primary role)</i></li>
|
||||||
<li>Hold this key and press another key to activate the relevant key of the Mouse layer. <i>(Secondary role)</i></li>
|
<li>Hold this key and press another key to activate the relevant key of the Mouse layer. <i>(Secondary role)</i></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class='text-left pt-3'>The secondary role can be any layer or modifier.</p>"
|
<p class='text-left'>The secondary role can be any layer or modifier.</p>"
|
||||||
data-placement="bottom"></icon>
|
data-placement="bottom"></icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="warningVisible" class="alert alert-warning remap-warning" role="alert">
|
||||||
|
You're about to remap a modifier key only on this layer. You probably want to remap it on all layers. If so, check
|
||||||
|
the <strong>Remap on all layers</strong> checkbox below.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="disabled-state--text">
|
<div class="disabled-state--text">
|
||||||
<i class="fa fa-info-circle"></i>
|
<i class="fa fa-info-circle"></i>
|
||||||
When a key is configured as layer switcher key, you can't assign other functions to it.
|
When a key is configured as layer switcher key, you can't assign other functions to it.
|
||||||
|
|||||||
@@ -79,4 +79,21 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scancode-container {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-role-groups-container {
|
||||||
|
display: inline-block;
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remap-warning {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,78 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import {
|
||||||
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
Output,
|
||||||
|
SimpleChanges
|
||||||
|
} from '@angular/core';
|
||||||
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
import { MapperService } from '../../../../services/mapper.service';
|
import { MapperService } from '../../../../services/mapper.service';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
import { KeyModifierModel } from '../../../../models/key-modifier-model';
|
||||||
|
import { mapLeftRigthModifierToKeyActionModifier } from '../../../../util';
|
||||||
|
import { RemapInfo } from '../../../../models/remap-info';
|
||||||
|
|
||||||
|
export const secondaryRoleFilter = (showLayerSwitchers: boolean) => {
|
||||||
|
return (data): boolean => {
|
||||||
|
if (showLayerSwitchers) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.text !== 'Layer switcher';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keypress-tab',
|
selector: 'keypress-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './keypress-tab.component.html',
|
templateUrl: './keypress-tab.component.html',
|
||||||
styleUrls: ['./keypress-tab.component.scss']
|
styleUrls: ['./keypress-tab.component.scss']
|
||||||
})
|
})
|
||||||
export class KeypressTabComponent extends Tab implements OnChanges {
|
export class KeypressTabComponent extends Tab implements OnChanges {
|
||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
@Input() secondaryRoleEnabled: boolean;
|
@Input() secondaryRoleEnabled: boolean;
|
||||||
|
@Input() allowRemapOnAllKeymapWarning: boolean;
|
||||||
|
@Input() remapInfo: RemapInfo;
|
||||||
|
@Input() showLayerSwitcherInSecondaryRoles: boolean;
|
||||||
|
|
||||||
leftModifiers: string[];
|
@Output() keyActionChange = new EventEmitter<KeystrokeAction>();
|
||||||
rightModifiers: string[];
|
|
||||||
|
|
||||||
leftModifierSelects: boolean[];
|
leftModifiers: KeyModifierModel[];
|
||||||
rightModifierSelects: boolean[];
|
rightModifiers: KeyModifierModel[];
|
||||||
|
|
||||||
scanCodeGroups: Array<Select2OptionData>;
|
scanCodeGroups: Array<SelectOptionData>;
|
||||||
secondaryRoleGroups: Array<Select2OptionData>;
|
secondaryRoleGroups: Array<SelectOptionData> = [];
|
||||||
options: Select2Options;
|
|
||||||
|
|
||||||
selectedScancodeOption: Select2OptionData;
|
selectedScancodeOption: SelectOptionData;
|
||||||
selectedSecondaryRoleIndex: number;
|
selectedSecondaryRoleIndex: number;
|
||||||
|
warningVisible: boolean;
|
||||||
|
|
||||||
constructor(private mapper: MapperService) {
|
constructor(private mapper: MapperService,
|
||||||
|
private cdRef: ChangeDetectorRef) {
|
||||||
super();
|
super();
|
||||||
this.leftModifiers = ['LShift', 'LCtrl', 'LSuper', 'LAlt'];
|
this.leftModifiers = mapper.getLeftKeyModifiers();
|
||||||
this.rightModifiers = ['RShift', 'RCtrl', 'RSuper', 'RAlt'];
|
this.rightModifiers = mapper.getRightKeyModifiers();
|
||||||
|
|
||||||
this.scanCodeGroups = [{
|
this.scanCodeGroups = [{
|
||||||
id: '0',
|
id: '0',
|
||||||
text: 'None'
|
text: 'None'
|
||||||
}];
|
}];
|
||||||
this.scanCodeGroups = this.scanCodeGroups.concat(SCANCODES);
|
this.scanCodeGroups = this.scanCodeGroups.concat(SCANCODES);
|
||||||
this.secondaryRoleGroups = SECONDARY_ROLES;
|
|
||||||
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
|
|
||||||
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
|
||||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||||
this.selectedSecondaryRoleIndex = -1;
|
this.selectedSecondaryRoleIndex = -1;
|
||||||
this.options = {
|
|
||||||
templateResult: this.scanCodeTemplateResult,
|
|
||||||
matcher: (term: string, text: string, data: Select2OptionData) => {
|
|
||||||
let found = text.toUpperCase().indexOf(term.toUpperCase()) > -1;
|
|
||||||
|
|
||||||
if (!found && data.additional && data.additional.explanation) {
|
|
||||||
found = data.additional.explanation.toUpperCase().indexOf(term.toUpperCase()) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes.showLayerSwitcherInSecondaryRoles) {
|
||||||
|
this.fillSecondaryRoles();
|
||||||
|
}
|
||||||
this.fromKeyAction(this.defaultKeyAction);
|
this.fromKeyAction(this.defaultKeyAction);
|
||||||
this.validAction.emit(this.keyActionValid());
|
this.keyActionChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyActionValid(keystrokeAction?: KeystrokeAction): boolean {
|
keyActionValid(keystrokeAction?: KeystrokeAction): boolean {
|
||||||
@@ -68,16 +83,16 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
return (keystrokeAction) ? (keystrokeAction.scancode > 0 || keystrokeAction.modifierMask > 0) : false;
|
return (keystrokeAction) ? (keystrokeAction.scancode > 0 || keystrokeAction.modifierMask > 0) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeysCapture(event: { code: number, left: boolean[], right: boolean[] }) {
|
onKeysCapture(event: { code: number, left: KeyModifierModel[], right: KeyModifierModel[] }) {
|
||||||
if (event.code) {
|
if (event.code) {
|
||||||
this.selectedScancodeOption = this.findScancodeOptionByScancode(event.code, KeystrokeType.basic);
|
this.selectedScancodeOption = this.findScancodeOptionByScancode(event.code, KeystrokeType.basic);
|
||||||
} else {
|
} else {
|
||||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.leftModifierSelects = event.left;
|
this.leftModifiers = event.left;
|
||||||
this.rightModifierSelects = event.right;
|
this.rightModifiers = event.right;
|
||||||
this.validAction.emit(this.keyActionValid());
|
this.keyActionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
fromKeyAction(keyAction: KeyAction): boolean {
|
fromKeyAction(keyAction: KeyAction): boolean {
|
||||||
@@ -88,16 +103,12 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
// Restore selectedScancodeOption
|
// Restore selectedScancodeOption
|
||||||
this.selectedScancodeOption = this.findScancodeOptionByScancode(keystrokeAction.scancode || 0, keystrokeAction.type);
|
this.selectedScancodeOption = this.findScancodeOptionByScancode(keystrokeAction.scancode || 0, keystrokeAction.type);
|
||||||
|
|
||||||
const leftModifiersLength: number = this.leftModifiers.length;
|
for (const modifier of this.leftModifiers) {
|
||||||
|
modifier.checked = (keystrokeAction.modifierMask & modifier.value) > 0;
|
||||||
// Restore modifiers
|
|
||||||
for (let i = 0; i < leftModifiersLength; ++i) {
|
|
||||||
this.leftModifierSelects[this.mapper.modifierMapper(i)] = ((keystrokeAction.modifierMask >> i) & 1) === 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = leftModifiersLength; i < leftModifiersLength + this.rightModifierSelects.length; ++i) {
|
for (const modifier of this.rightModifiers) {
|
||||||
const index: number = this.mapper.modifierMapper(i) - leftModifiersLength;
|
modifier.checked = (keystrokeAction.modifierMask & modifier.value) > 0;
|
||||||
this.rightModifierSelects[index] = ((keystrokeAction.modifierMask >> i) & 1) === 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore secondaryRoleAction
|
// Restore secondaryRoleAction
|
||||||
@@ -119,11 +130,7 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
} else {
|
} else {
|
||||||
keystrokeAction.type = KeystrokeType[scTypePair[1]];
|
keystrokeAction.type = KeystrokeType[scTypePair[1]];
|
||||||
}
|
}
|
||||||
keystrokeAction.modifierMask = 0;
|
keystrokeAction.modifierMask = mapLeftRigthModifierToKeyActionModifier(this.leftModifiers, this.rightModifiers);
|
||||||
const modifiers = this.leftModifierSelects.concat(this.rightModifierSelects).map(x => x ? 1 : 0);
|
|
||||||
for (let i = 0; i < modifiers.length; ++i) {
|
|
||||||
keystrokeAction.modifierMask |= modifiers[i] << this.mapper.modifierMapper(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
keystrokeAction.secondaryRoleAction = this.selectedSecondaryRoleIndex === -1
|
keystrokeAction.secondaryRoleAction = this.selectedSecondaryRoleIndex === -1
|
||||||
? undefined
|
? undefined
|
||||||
@@ -134,50 +141,37 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scanCodeTemplateResult: Select2TemplateFunction = (state: Select2OptionData): JQuery | string => {
|
toggleModifier(modifier: KeyModifierModel): void {
|
||||||
if (!state.id) {
|
modifier.checked = !modifier.checked;
|
||||||
return state.text;
|
this.keyActionChanged();
|
||||||
}
|
|
||||||
|
|
||||||
if (state.additional && state.additional.explanation) {
|
|
||||||
return jQuery(
|
|
||||||
'<span class="select2-item">'
|
|
||||||
+ '<span>' + state.text + '</span>'
|
|
||||||
+ '<span class="scancode--searchterm"> '
|
|
||||||
+ state.additional.explanation
|
|
||||||
+ '</span>' +
|
|
||||||
'</span>'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return jQuery('<span class="select2-item">' + state.text + '</span>');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModifier(right: boolean, index: number) {
|
onSecondaryRoleChange(id: string) {
|
||||||
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
|
this.selectedSecondaryRoleIndex = +id;
|
||||||
modifierSelects[index] = !modifierSelects[index];
|
this.keyActionChanged();
|
||||||
|
|
||||||
this.validAction.emit(this.keyActionValid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSecondaryRoleChange(event: { value: string }) {
|
onScancodeChange(id: string) {
|
||||||
this.selectedSecondaryRoleIndex = +event.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
onScancodeChange(event: { value: string }) {
|
|
||||||
const id: string = event.value;
|
|
||||||
|
|
||||||
// ng2-select2 should provide the selectedOption in an upcoming release
|
|
||||||
// TODO: change this when it has become available
|
|
||||||
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
||||||
|
|
||||||
this.validAction.emit(this.keyActionValid());
|
this.keyActionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionBy(predicate: (option: Select2OptionData) => boolean): Select2OptionData {
|
modifiersTrackBy(index: number, modifier: KeyModifierModel): string {
|
||||||
let selectedOption: Select2OptionData;
|
return `${modifier.value}${modifier.checked}`;
|
||||||
|
}
|
||||||
|
|
||||||
const scanCodeGroups: Select2OptionData[] = [...this.scanCodeGroups];
|
remapInfoChanged(remapInfo: RemapInfo): void {
|
||||||
|
this.remapInfo = remapInfo;
|
||||||
|
const keystrokeAction = this.toKeyAction();
|
||||||
|
this.calculateRemapOnAllLayerWarningVisibility(keystrokeAction);
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private findScancodeOptionBy(predicate: (option: SelectOptionData) => boolean): SelectOptionData {
|
||||||
|
let selectedOption: SelectOptionData;
|
||||||
|
|
||||||
|
const scanCodeGroups: SelectOptionData[] = [...this.scanCodeGroups];
|
||||||
while (scanCodeGroups.length > 0) {
|
while (scanCodeGroups.length > 0) {
|
||||||
const scanCodeGroup = scanCodeGroups.shift();
|
const scanCodeGroup = scanCodeGroups.shift();
|
||||||
if (predicate(scanCodeGroup)) {
|
if (predicate(scanCodeGroup)) {
|
||||||
@@ -192,14 +186,14 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
return selectedOption;
|
return selectedOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionById(id: string): Select2OptionData {
|
private findScancodeOptionById(id: string): SelectOptionData {
|
||||||
return this.findScancodeOptionBy(option => option.id === id);
|
return this.findScancodeOptionBy(option => option.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): Select2OptionData {
|
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): SelectOptionData {
|
||||||
const typeToFind: string =
|
const typeToFind: string =
|
||||||
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
||||||
return this.findScancodeOptionBy((option: Select2OptionData) => {
|
return this.findScancodeOptionBy((option: SelectOptionData) => {
|
||||||
const additional = option.additional;
|
const additional = option.additional;
|
||||||
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
||||||
return true;
|
return true;
|
||||||
@@ -211,7 +205,11 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private toScancodeTypePair(option: Select2OptionData): [number, string] {
|
private toScancodeTypePair(option: SelectOptionData): [number, string] {
|
||||||
|
if (!option) {
|
||||||
|
return [0, 'basic'];
|
||||||
|
}
|
||||||
|
|
||||||
let scanCode: number;
|
let scanCode: number;
|
||||||
let type: string;
|
let type: string;
|
||||||
if (option.additional) {
|
if (option.additional) {
|
||||||
@@ -227,4 +225,27 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
return [scanCode, type];
|
return [scanCode, type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private keyActionChanged(dispatch = true): void {
|
||||||
|
const keystrokeAction = this.toKeyAction();
|
||||||
|
this.validAction.emit(this.keyActionValid(keystrokeAction));
|
||||||
|
this.calculateRemapOnAllLayerWarningVisibility(keystrokeAction);
|
||||||
|
|
||||||
|
if (dispatch) {
|
||||||
|
this.keyActionChange.emit(keystrokeAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateRemapOnAllLayerWarningVisibility(keystrokeAction: KeystrokeAction): void {
|
||||||
|
this.warningVisible = this.allowRemapOnAllKeymapWarning &&
|
||||||
|
this.remapInfo &&
|
||||||
|
!this.remapInfo.remapOnAllLayer &&
|
||||||
|
keystrokeAction &&
|
||||||
|
!keystrokeAction.hasScancode() &&
|
||||||
|
keystrokeAction.hasOnlyOneActiveModifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
private fillSecondaryRoles(): void {
|
||||||
|
this.secondaryRoleGroups = SECONDARY_ROLES
|
||||||
|
.filter(secondaryRoleFilter(this.showLayerSwitcherInSecondaryRoles));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { KeyAction, LayerName, SwitchLayerAction, SwitchLayerMode } from 'uhk-common';
|
import { KeyAction, LayerName, SwitchLayerAction, SwitchLayerMode } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
@@ -7,6 +7,7 @@ export type toggleType = 'active' | 'toggle';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'layer-tab',
|
selector: 'layer-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './layer-tab.component.html',
|
templateUrl: './layer-tab.component.html',
|
||||||
styleUrls: ['./layer-tab.component.scss']
|
styleUrls: ['./layer-tab.component.scss']
|
||||||
})
|
})
|
||||||
@@ -62,7 +63,7 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
this.isNotBase = this.currentLayer > 0;
|
this.isNotBase = this.currentLayer > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validAction.emit(true);
|
this.validAction.emit(!this.isNotBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyActionValid(): boolean {
|
keyActionValid(): boolean {
|
||||||
|
|||||||
@@ -2,10 +2,26 @@
|
|||||||
<span> No macros are available to choose from. Create a macro first! </span>
|
<span> No macros are available to choose from. Create a macro first! </span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="macroOptions.length > 0">
|
<ng-template [ngIf]="macroOptions.length > 0">
|
||||||
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
|
<p *ngIf="!macroPlaybackSupported"><i>Please note that macro playback is not implemented yet. You can bind macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
|
||||||
<div class="macro-selector">
|
<div class="macro-selector">
|
||||||
<b> Play macro: </b>
|
<b> Play macro: </b>
|
||||||
<select2 [data]="macroOptions" [value]="macroOptions[selectedMacroIndex].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>
|
<ngx-select [items]="macroOptions"
|
||||||
|
[ngModel]="macroOptions[selectedMacroIndex]?.id"
|
||||||
|
[autoActiveOnMouseEnter]="false"
|
||||||
|
size="small"
|
||||||
|
optionValueField="id"
|
||||||
|
optionTextField="text"
|
||||||
|
(select)="onChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</ngx-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="macro-action-container">
|
<div class="macro-action-container">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
@@ -14,4 +30,4 @@
|
|||||||
</macro-item>
|
</macro-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select2 {
|
ngx-select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
|
||||||
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
import { AppState } from '../../../../store/index';
|
import { AppState } from '../../../../store';
|
||||||
import { getMacros } from '../../../../store/reducers/user-configuration';
|
import { getMacros } from '../../../../store/reducers/user-configuration';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'macro-tab',
|
selector: 'macro-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './macro-tab.component.html',
|
templateUrl: './macro-tab.component.html',
|
||||||
styleUrls: ['./macro-tab.component.scss']
|
styleUrls: ['./macro-tab.component.scss']
|
||||||
})
|
})
|
||||||
export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestroy {
|
export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
|
@Input() macroPlaybackSupported: boolean;
|
||||||
|
|
||||||
macros: Macro[];
|
macros: Macro[];
|
||||||
macroOptions: Array<Select2OptionData>;
|
macroOptions: Array<SelectOptionData>;
|
||||||
selectedMacroIndex: number;
|
selectedMacroIndex: number;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.macroOptions = this.macros.map(function (macro: Macro, index: number): Select2OptionData {
|
this.macroOptions = this.macros.map(function (macro: Macro, index: number): SelectOptionData {
|
||||||
return {
|
return {
|
||||||
id: index.toString(),
|
id: index.toString(),
|
||||||
text: macro.name
|
text: macro.name
|
||||||
@@ -44,9 +46,8 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
this.validAction.emit(true);
|
this.validAction.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change to the correct type when the wrapper has added it.
|
onChange(id: string) {
|
||||||
onChange(event: any) {
|
this.selectedMacroIndex = +id;
|
||||||
this.selectedMacroIndex = +event.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyActionValid(): boolean {
|
keyActionValid(): boolean {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||||
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
|
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mouse-tab',
|
selector: 'mouse-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './mouse-tab.component.html',
|
templateUrl: './mouse-tab.component.html',
|
||||||
styleUrls: ['./mouse-tab.component.scss']
|
styleUrls: ['./mouse-tab.component.scss']
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'none-tab',
|
selector: 'none-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './none-tab.component.html',
|
templateUrl: './none-tab.component.html',
|
||||||
styleUrls: ['./none-tab.component.scss']
|
styleUrls: ['./none-tab.component.scss']
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { EventEmitter, Output } from '@angular/core';
|
import { EventEmitter, Output } from '@angular/core';
|
||||||
import { KeyAction } from 'uhk-common';
|
import { KeyAction } from 'uhk-common';
|
||||||
|
|
||||||
|
import { RemapInfo } from '../../../models/remap-info';
|
||||||
|
|
||||||
export abstract class Tab {
|
export abstract class Tab {
|
||||||
@Output() validAction = new EventEmitter<boolean>();
|
@Output() validAction = new EventEmitter<boolean>();
|
||||||
|
|
||||||
abstract keyActionValid(): boolean;
|
abstract keyActionValid(): boolean;
|
||||||
abstract fromKeyAction(keyAction: KeyAction): boolean;
|
abstract fromKeyAction(keyAction: KeyAction): boolean;
|
||||||
abstract toKeyAction(): KeyAction;
|
abstract toKeyAction(): KeyAction;
|
||||||
|
remapInfoChanged(remapInfo: RemapInfo): void {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { Component, EventEmitter, HostListener, Output, Input } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output } from '@angular/core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { CaptureService } from '../../../../services/capture.service';
|
import { CaptureService } from '../../../../services/capture.service';
|
||||||
|
import { KeyModifierModel } from '../../../../models/key-modifier-model';
|
||||||
|
import { AppState } from '../../../../store';
|
||||||
|
import { StartKeypressCapturingAction, StopKeypressCapturingAction } from '../../../../store/actions/app';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'capture-keystroke-button',
|
selector: 'capture-keystroke-button',
|
||||||
templateUrl: './capture-keystroke-button.component.html',
|
templateUrl: './capture-keystroke-button.component.html',
|
||||||
styleUrls: ['./capture-keystroke-button.component.scss']
|
styleUrls: ['./capture-keystroke-button.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class CaptureKeystrokeButtonComponent {
|
export class CaptureKeystrokeButtonComponent {
|
||||||
@Input() isLink = false;
|
@Input() isLink = false;
|
||||||
@@ -15,7 +21,8 @@ export class CaptureKeystrokeButtonComponent {
|
|||||||
private first: boolean; // enable usage of Enter to start capturing
|
private first: boolean; // enable usage of Enter to start capturing
|
||||||
private scanCodePressed: boolean;
|
private scanCodePressed: boolean;
|
||||||
|
|
||||||
constructor(private captureService: CaptureService) {
|
constructor(private captureService: CaptureService,
|
||||||
|
private store: Store<AppState>) {
|
||||||
this.record = false;
|
this.record = false;
|
||||||
this.captureService.initModifiers();
|
this.captureService.initModifiers();
|
||||||
this.captureService.populateMapping();
|
this.captureService.populateMapping();
|
||||||
@@ -26,9 +33,11 @@ export class CaptureKeystrokeButtonComponent {
|
|||||||
onKeyUp(e: KeyboardEvent) {
|
onKeyUp(e: KeyboardEvent) {
|
||||||
if (this.scanCodePressed) {
|
if (this.scanCodePressed) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
this.scanCodePressed = false;
|
this.scanCodePressed = false;
|
||||||
} else if (this.record && !this.first) {
|
} else if (this.record && !this.first) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
this.saveScanCode();
|
this.saveScanCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +61,7 @@ export class CaptureKeystrokeButtonComponent {
|
|||||||
} else if (code === enter) {
|
} else if (code === enter) {
|
||||||
this.record = true;
|
this.record = true;
|
||||||
this.first = true;
|
this.first = true;
|
||||||
|
this.store.dispatch(new StartKeypressCapturingAction());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,12 +73,13 @@ export class CaptureKeystrokeButtonComponent {
|
|||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
this.record = true;
|
this.record = true;
|
||||||
|
this.store.dispatch(new StartKeypressCapturingAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveScanCode(code?: number) {
|
private saveScanCode(code?: number) {
|
||||||
this.record = false;
|
this.record = false;
|
||||||
const left: boolean[] = this.captureService.getModifiers(true);
|
const left: KeyModifierModel[] = this.captureService.getModifiers(true);
|
||||||
const right: boolean[] = this.captureService.getModifiers(false);
|
const right: KeyModifierModel[] = this.captureService.getModifiers(false);
|
||||||
|
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
code,
|
code,
|
||||||
@@ -82,5 +93,6 @@ export class CaptureKeystrokeButtonComponent {
|
|||||||
private reset() {
|
private reset() {
|
||||||
this.first = false;
|
this.first = false;
|
||||||
this.captureService.initModifiers();
|
this.captureService.initModifiers();
|
||||||
|
this.store.dispatch(new StopKeypressCapturingAction());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'icon',
|
selector: 'icon',
|
||||||
templateUrl: './icon.component.html',
|
templateUrl: './icon.component.html',
|
||||||
styleUrls: ['./icon.component.scss']
|
styleUrls: ['./icon.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class IconComponent implements OnInit {
|
export class IconComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
<uhk-message header="Cannot talk to your UHK"
|
<uhk-message header="Cannot talk to your UHK"
|
||||||
subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
|
subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
|
||||||
|
|
||||||
<button class="btn btn-default btn-lg btn-primary"
|
<div *ngIf="state.updateUdevRules">
|
||||||
(click)="setUpPermissions()"> Set up permissions
|
You seem to have an old udev rule file installed. New Agent versions require and updated udev rule file to find your UHK.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-default btn-lg btn-primary mt-10"
|
||||||
|
(click)="setUpPermissions()">
|
||||||
|
<span *ngIf="!state.updateUdevRules">Set up permissions</span>
|
||||||
|
<span *ngIf="state.updateUdevRules">Update udev rule file</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
@@ -21,18 +27,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div *ngIf="state.showWhatWillThisDoContent">
|
<div *ngIf="state.showWhatWillThisDoContent">
|
||||||
Agent uses the following script to set up permissions. You can run it manually as root, then
|
If you want to set up permissions manually:
|
||||||
<a class="link-inline"
|
<udev-rules></udev-rules>
|
||||||
(click)="retry()">retry</a>.
|
|
||||||
<div class="copy-container">
|
|
||||||
<span class="fa fa-2x fa-copy"
|
|
||||||
ngxClipboard
|
|
||||||
[cbContent]="command"
|
|
||||||
title="Copy to clipboard"
|
|
||||||
data-toggle="tooltip"
|
|
||||||
data-placement="top"></span>
|
|
||||||
<pre><code>{{ command }}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,15 +18,6 @@ export class PrivilegeCheckerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
state: PrivilagePageSate;
|
state: PrivilagePageSate;
|
||||||
|
|
||||||
command = `cat <<EOF >/etc/udev/rules.d/50-uhk60.rules
|
|
||||||
# Ultimate Hacking Keyboard rules
|
|
||||||
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
|
|
||||||
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666"
|
|
||||||
EOF
|
|
||||||
udevadm trigger
|
|
||||||
udevadm settle`;
|
|
||||||
|
|
||||||
private stateSubscription: Subscription;
|
private stateSubscription: Subscription;
|
||||||
|
|
||||||
constructor(private store: Store<AppState>,
|
constructor(private store: Store<AppState>,
|
||||||
|
|||||||
@@ -131,16 +131,22 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="sidebar__level-0--item" [routerLinkActive]="['active']">
|
<li class="sidebar__level-0--item" [routerLinkActive]="['active']">
|
||||||
<div class="sidebar__level-0">
|
<div class="sidebar__level-0">
|
||||||
<i class="uhk-icon uhk-icon-agent-icon"></i> Agent
|
<i class="uhk-icon uhk-icon-pure-agent-icon"></i> Agent
|
||||||
<i class="fa fa-chevron-up pull-right"
|
<i class="fa fa-chevron-up pull-right"
|
||||||
(click)="toggleHide($event, 'agent')"></i>
|
(click)="toggleHide($event, 'agent')"></i>
|
||||||
</div>
|
</div>
|
||||||
<ul [@toggler]="animation['agent']">
|
<ul [@toggler]="animation['agent']">
|
||||||
<li class="sidebar__level-2--item">
|
<!--li class="sidebar__level-2--item">
|
||||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||||
<a [routerLink]="['/settings']"
|
<a [routerLink]="['/settings']"
|
||||||
[class.disabled]="state.updatingFirmware">Settings</a>
|
[class.disabled]="state.updatingFirmware">Settings</a>
|
||||||
</div>
|
</div>
|
||||||
|
</li-->
|
||||||
|
<li class="sidebar__level-2--item">
|
||||||
|
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||||
|
<a [routerLink]="['/help']"
|
||||||
|
[class.disabled]="state.updatingFirmware">Help</a>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar__level-2--item">
|
<li class="sidebar__level-2--item">
|
||||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<label *ngIf="label">
|
<label *ngIf="label">
|
||||||
<span>{{label}}</span>
|
<span [innerHtml]="label"></span>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
[title]="tooltip"
|
[title]="tooltip"
|
||||||
|
|||||||
1
packages/uhk-web/src/app/components/svg/constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const SECONDARY_ROLE_BOTTOM_MARGIN = 1;
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[@split]="moduleAnimationStates[i]"
|
[@split]="moduleAnimationStates[i]"
|
||||||
[selected]="selectedKey?.moduleId === i"
|
[selected]="selectedKey?.moduleId === i"
|
||||||
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
(keyClick)="onKeyClick(i, $event)"
|
||||||
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
||||||
(capture)="onCapture(i, $event.index, $event.captured)" />
|
(capture)="onCapture(i, $event)" />
|
||||||
|
|
||||||
<svg:path [@fadeSeparator]="separatorAnimation"
|
<svg:path [@fadeSeparator]="separatorAnimation"
|
||||||
[attr.d]="separator.d"
|
[attr.d]="separator.d"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -7,6 +7,12 @@ import { SvgModule } from '../module';
|
|||||||
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
import { SvgSeparator } from '../separator';
|
import { SvgSeparator } from '../separator';
|
||||||
|
import {
|
||||||
|
SvgKeyHoverEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgModuleKeyClickEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'svg-keyboard',
|
selector: 'svg-keyboard',
|
||||||
@@ -45,9 +51,9 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Input() showDescription = false;
|
@Input() showDescription = false;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||||
@Output() descriptionChanged = new EventEmitter<string>();
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
modules: SvgModule[];
|
modules: SvgModule[];
|
||||||
@@ -79,19 +85,17 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void {
|
onKeyClick(moduleId: number, event: SvgModuleKeyClickEvent): void {
|
||||||
this.keyClick.emit({
|
this.keyClick.emit({
|
||||||
moduleId,
|
...event,
|
||||||
keyId,
|
moduleId
|
||||||
keyTarget
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCapture(moduleId: number, keyId: number, captured: { code: number, left: boolean[], right: boolean[] }): void {
|
onCapture(moduleId: number, event: SvgKeyboardCaptureEvent): void {
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
moduleId,
|
...event,
|
||||||
keyId,
|
moduleId
|
||||||
captured
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ export { SvgSingleIconKeyComponent } from './svg-single-icon-key';
|
|||||||
export { SvgSwitchKeymapKeyComponent } from './svg-switch-keymap-key';
|
export { SvgSwitchKeymapKeyComponent } from './svg-switch-keymap-key';
|
||||||
export { SvgTextIconKeyComponent } from './svg-text-icon-key';
|
export { SvgTextIconKeyComponent } from './svg-text-icon-key';
|
||||||
export { SvgTwoLineTextKeyComponent } from './svg-two-line-text-key';
|
export { SvgTwoLineTextKeyComponent } from './svg-two-line-text-key';
|
||||||
|
export { SvgSecondaryRoleComponent } from './svg-secondary-role';
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
[attr.x]="0"
|
[attr.x]="0"
|
||||||
[attr.y]="textY"
|
[attr.y]="textY"
|
||||||
[attr.text-anchor]="'middle'"
|
[attr.text-anchor]="'middle'"
|
||||||
[attr.font-size]="11">
|
[attr.font-size]="fontSize">
|
||||||
<tspan [attr.x]="spanX">{{ text }}</tspan>
|
<tspan [attr.x]="spanX" [attr.dy]="text1Y">{{ text1 }}</tspan>
|
||||||
</svg:text>
|
<tspan [attr.x]="spanX" [attr.dy]="text2Y">{{ text2 }}</tspan>
|
||||||
|
</svg:text>
|
||||||
|
<svg:g svg-secondary-role
|
||||||
|
*ngIf="secondaryText"
|
||||||
|
[height]="20"
|
||||||
|
[width]="width"
|
||||||
|
[y]="secondaryTextY"
|
||||||
|
[text]="secondaryText">
|
||||||
|
</svg:g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 596 B |
@@ -1,15 +1,20 @@
|
|||||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, Input, ChangeDetectionStrategy, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
|
import { isRectangleAsSecondaryRoleKey } from '../util';
|
||||||
|
import { SECONDARY_ROLE_BOTTOM_MARGIN } from '../../constants';
|
||||||
|
import { getContentWidth } from '../../../../util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'g[svg-icon-text-key]',
|
selector: 'g[svg-icon-text-key]',
|
||||||
templateUrl: './svg-icon-text-key.component.html',
|
templateUrl: './svg-icon-text-key.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class SvgIconTextKeyComponent implements OnInit {
|
export class SvgIconTextKeyComponent implements OnChanges {
|
||||||
@Input() width: number;
|
@Input() width: number;
|
||||||
@Input() height: number;
|
@Input() height: number;
|
||||||
@Input() icon: string;
|
@Input() icon: string;
|
||||||
@Input() text: string;
|
@Input() text: string;
|
||||||
|
@Input() secondaryText: string;
|
||||||
|
|
||||||
useWidth: number;
|
useWidth: number;
|
||||||
useHeight: number;
|
useHeight: number;
|
||||||
@@ -17,16 +22,137 @@ export class SvgIconTextKeyComponent implements OnInit {
|
|||||||
useY: number;
|
useY: number;
|
||||||
textY: number;
|
textY: number;
|
||||||
spanX: number;
|
spanX: number;
|
||||||
|
textWidth: number;
|
||||||
|
secondaryTextY: number;
|
||||||
|
secondaryHeight: number;
|
||||||
|
fontSize: number;
|
||||||
|
text1: string;
|
||||||
|
text1Y: number;
|
||||||
|
text2: string;
|
||||||
|
text2Y: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.calculatePositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculatePositions(): void {
|
||||||
|
let secondaryYModifier = 0;
|
||||||
|
|
||||||
|
if (this.secondaryText && isRectangleAsSecondaryRoleKey(this.width, this.height)) {
|
||||||
|
secondaryYModifier = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRectangle = this.width > this.height * 1.8;
|
||||||
|
|
||||||
this.useWidth = this.width / 3;
|
this.useWidth = this.width / 3;
|
||||||
this.useHeight = this.height / 3;
|
this.useHeight = this.height / 3;
|
||||||
this.useX = (this.width > 2 * this.height) ? 0 : this.width / 3;
|
|
||||||
this.useY = (this.width > 2 * this.height) ? this.height / 3 : this.height / 10;
|
if (isRectangle) {
|
||||||
this.textY = (this.width > 2 * this.height) ? this.height / 2 : this.height * 0.6;
|
this.textWidth = this.width * 0.65;
|
||||||
this.spanX = (this.width > 2 * this.height) ? this.width * 0.6 : this.width / 2;
|
this.useX = 0;
|
||||||
|
this.useY = this.height / 3;
|
||||||
|
this.spanX = this.width * 0.6;
|
||||||
|
} else {
|
||||||
|
this.textWidth = this.width * 0.95;
|
||||||
|
this.useX = this.width / 3;
|
||||||
|
this.useY = this.height / 10;
|
||||||
|
this.spanX = this.width / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.secondaryText) {
|
||||||
|
this.secondaryHeight = this.height / 4;
|
||||||
|
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
|
||||||
|
} else {
|
||||||
|
this.secondaryHeight = 0;
|
||||||
|
this.secondaryTextY = 0;
|
||||||
|
}
|
||||||
|
this.fontSize = 19;
|
||||||
|
this.text1 = '';
|
||||||
|
this.text2 = '';
|
||||||
|
while (this.fontSize > 10 && !this.isFullTextVisible()) {
|
||||||
|
this.calculateTexts(isRectangle);
|
||||||
|
this.fontSize--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateTexts(isRectangle: boolean): void {
|
||||||
|
if (!this.text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.text1 = this.getText(0);
|
||||||
|
|
||||||
|
this.text2 = this.getText(this.text1.length);
|
||||||
|
|
||||||
|
const lineHeight = this.fontSize;
|
||||||
|
const lines = this.text2 ? 1 : 0;
|
||||||
|
|
||||||
|
if (isRectangle) {
|
||||||
|
const textboxHeight = this.height - this.secondaryHeight;
|
||||||
|
this.textY = textboxHeight / 2 - 0.5 * lines * lineHeight;
|
||||||
|
} else {
|
||||||
|
const textboxHeight = this.height - this.secondaryHeight + this.useHeight;
|
||||||
|
this.textY = textboxHeight / 2 - 0.5 * lines * lineHeight;
|
||||||
|
}
|
||||||
|
this.text1Y = 0;
|
||||||
|
this.text2Y = this.text1Y + 1.2 * lines * lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getText(startPosition: number): string {
|
||||||
|
const style: CSSStyleDeclaration = {
|
||||||
|
font: `${this.fontSize}px Helvetica`
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
let lastSpacePosition = 0;
|
||||||
|
|
||||||
|
for (let i = startPosition; i < this.text.length; i++) {
|
||||||
|
const char = this.text[i];
|
||||||
|
|
||||||
|
// skip space if result start with space
|
||||||
|
if (char === ' ' && result === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newText = result += char;
|
||||||
|
const textWidth = getContentWidth(style, newText);
|
||||||
|
|
||||||
|
if (char === ' ') {
|
||||||
|
lastSpacePosition = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textWidth > this.textWidth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = newText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSpacePosition > 0 && lastSpacePosition < result.length) {
|
||||||
|
result = result.substr(0, lastSpacePosition);
|
||||||
|
} else if (this.fontSize === 11) {
|
||||||
|
const cleanResult = result.replace(/ /g, '');
|
||||||
|
const cleanText = this.text.substr(startPosition).replace(/ /g, '');
|
||||||
|
if (cleanResult.length < cleanText.length) {
|
||||||
|
result = result.substring(0, result.length - 3) + '...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isFullTextVisible(): boolean {
|
||||||
|
const visibleText = (this.text1 + this.text2).replace(/ /g, '');
|
||||||
|
|
||||||
|
if (this.text2.endsWith('...')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textLength = this.text.replace(/ /g, '').length;
|
||||||
|
|
||||||
|
return visibleText.length === textLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,34 +25,40 @@
|
|||||||
<svg:g svg-keystroke-key *ngSwitchCase="enumLabelTypes.KeystrokeKey"
|
<svg:g svg-keystroke-key *ngSwitchCase="enumLabelTypes.KeystrokeKey"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
[width]="width"
|
[width]="width"
|
||||||
[keystrokeAction]="labelSource">
|
[keystrokeAction]="labelSource"
|
||||||
|
[secondaryText]="secondaryText">
|
||||||
</svg:g>
|
</svg:g>
|
||||||
<svg:g svg-one-line-text-key *ngSwitchCase="enumLabelTypes.OneLineText"
|
<svg:g svg-one-line-text-key *ngSwitchCase="enumLabelTypes.OneLineText"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
[width]="width"
|
[width]="width"
|
||||||
[text]="labelSource">
|
[text]="labelSource"
|
||||||
|
[secondaryText]="secondaryText">
|
||||||
</svg:g>
|
</svg:g>
|
||||||
<svg:g svg-two-line-text-key *ngSwitchCase="enumLabelTypes.TwoLineText"
|
<svg:g svg-two-line-text-key *ngSwitchCase="enumLabelTypes.TwoLineText"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
[width]="width"
|
[width]="width"
|
||||||
[texts]="labelSource">
|
[texts]="labelSource"
|
||||||
|
[secondaryText]="secondaryText">
|
||||||
</svg:g>
|
</svg:g>
|
||||||
<svg:g svg-text-icon-key *ngSwitchCase="enumLabelTypes.TextIcon"
|
<svg:g svg-text-icon-key *ngSwitchCase="enumLabelTypes.TextIcon"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
[width]="width"
|
[width]="width"
|
||||||
[text]="labelSource.text"
|
[text]="labelSource.text"
|
||||||
[icon]="labelSource.icon">
|
[icon]="labelSource.icon"
|
||||||
|
[secondaryText]="secondaryText">
|
||||||
</svg:g>
|
</svg:g>
|
||||||
<svg:g svg-icon-text-key *ngSwitchCase="enumLabelTypes.IconText"
|
<svg:g svg-icon-text-key *ngSwitchCase="enumLabelTypes.IconText"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
[width]="width"
|
[width]="width"
|
||||||
[icon]="labelSource.icon"
|
[icon]="labelSource.icon"
|
||||||
[text]="labelSource.text">
|
[text]="labelSource.text"
|
||||||
|
[secondaryText]="secondaryText">
|
||||||
</svg:g>
|
</svg:g>
|
||||||
<svg:g svg-single-icon-key *ngSwitchCase="enumLabelTypes.SingleIcon"
|
<svg:g svg-single-icon-key *ngSwitchCase="enumLabelTypes.SingleIcon"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
[width]="width"
|
[width]="width"
|
||||||
[icon]="labelSource">
|
[icon]="labelSource"
|
||||||
|
[secondaryText]="secondaryText">
|
||||||
</svg:g>
|
</svg:g>
|
||||||
<svg:g svg-switch-keymap-key *ngSwitchCase="enumLabelTypes.SwitchKeymap"
|
<svg:g svg-switch-keymap-key *ngSwitchCase="enumLabelTypes.SwitchKeymap"
|
||||||
[height]="height"
|
[height]="height"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.7 KiB |