Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3978011d2e | ||
|
|
cd1952a7df | ||
|
|
4251477451 | ||
|
|
873f1de1ef | ||
|
|
150f993e5f | ||
|
|
06e76e5e0f | ||
|
|
a208a264c7 | ||
|
|
114014fa13 | ||
|
|
94cfd9d2e9 | ||
|
|
0aa9c73b4b | ||
|
|
5234f85dbe | ||
|
|
bd8a2f704f | ||
|
|
439886d69f | ||
|
|
b2a37795e3 | ||
|
|
440db56080 | ||
|
|
337e6e6bb6 | ||
|
|
a1aeda3d35 | ||
|
|
c6a83f8c9b | ||
|
|
0f24427628 | ||
|
|
f52dc36a6a | ||
|
|
63a936968d | ||
|
|
cabfde7963 | ||
|
|
79628c2351 | ||
|
|
762fa6f8bf | ||
|
|
a258c097a9 | ||
|
|
41faa98fcd | ||
|
|
c4d7318686 | ||
|
|
9ef11eaa34 | ||
|
|
f34cb2df56 | ||
|
|
83b9f0d1e9 | ||
|
|
7d81cf0c6a | ||
|
|
82b76a9455 | ||
|
|
4ae577f936 | ||
|
|
81a83994ab | ||
|
|
1d3a3c7f5f |
105
CHANGELOG.md
@@ -6,12 +6,113 @@ 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).
|
||||
|
||||
## [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
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- 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.
|
||||
- Fade in/out the keyboard separator line only when splitting the keyboard.
|
||||
- Only show the unsupported OS message of the firmware page on relevant Windows versions.
|
||||
- Close and reopen USB device when an error occurs.
|
||||
- Temporarily remove the export keymap feature because it's useless until import is implemented.
|
||||
|
||||
## [1.2.4] - 2018-06-21
|
||||
|
||||
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
|
||||
|
||||
- Replace Linux x86-64 blhost with a statically linked version which should make firmware updates work on every Linux distro.
|
||||
|
||||
## [1.2.3] - 2018-06-19
|
||||
|
||||
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
|
||||
|
||||
- Add checkboxes for remapping keys on all layers and/or all keymaps.
|
||||
- Add separator line between the keyboard halves.
|
||||
- Add double tap icon for switch layer actions.
|
||||
- Improve the looks and content of the tooltips of the key action popover.
|
||||
- Make the left keyboard half less likely to timeout during firmware update.
|
||||
- Terminate the firmware update process if blhost segfaults.
|
||||
- Replace the Linux x86-64 version of the blhost binary which should not make it segfault anymore.
|
||||
- Make the firmware update log shorter by listing one device per line and not repeating the list of available USB devices.
|
||||
- Make the firmware update help text shorter.
|
||||
|
||||
## [1.2.2] - 2018-05-27
|
||||
|
||||
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
|
||||
|
||||
- Offer recovery for bricked right keyboard halfs.
|
||||
- Detect when the hardware configuration of a device is invalid and display a notification.
|
||||
- Detect when the hardware configuration of a device is invalid and display a notification. `DEVICEPROTOCOL:PATCH`
|
||||
- Check if the keyboard is in factory reset mode and if so, display a relevant instruction.
|
||||
- Only allow ASCII characters in type text macro actions.
|
||||
- Allow uploading the same file multiple times in a row.
|
||||
|
||||
1
ISSUE_TEMPLATE
Normal file
@@ -0,0 +1 @@
|
||||
If you're using Karabiner Elements on your Mac, then stop here! Make sure to close Karabiner Elements, then try to reproduce the issue again, even if you think that Karabiner Elements shouldn't be the cause. Karabiner Elements is the source of numerous problems, and we don't want to receive any more reports it causes.
|
||||
13761
package-lock.json
generated
37
package.json
@@ -3,9 +3,9 @@
|
||||
"private": true,
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"main": "electron/dist/electron-main.js",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.11",
|
||||
"firmwareVersion": "8.2.5",
|
||||
"deviceProtocolVersion": "4.3.1",
|
||||
"deviceProtocolVersion": "4.4.0",
|
||||
"userConfigVersion": "4.0.1",
|
||||
"hardwareConfigVersion": "1.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
@@ -15,16 +15,17 @@
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"engines": {
|
||||
"node": ">=8.9.1 <9.0.0",
|
||||
"npm": ">=5.6.0 <6.0.0"
|
||||
"node": ">=8.12.0 <9.0.0",
|
||||
"npm": ">=6.4.0 <7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/electron-devtools-installer": "2.0.2",
|
||||
"@types/electron-settings": "3.0.0",
|
||||
"@types/fs-extra": "5.0.1",
|
||||
"@types/jasmine": "2.6.0",
|
||||
"@types/jasmine": "2.8.8",
|
||||
"@types/jquery": "3.3.1",
|
||||
"@types/jsonfile": "4.0.1",
|
||||
"@types/lodash-es": "4.17.0",
|
||||
"@types/node": "8.0.53",
|
||||
"@types/node-hid": "0.5.2",
|
||||
"@types/request": "2.0.8",
|
||||
@@ -37,36 +38,41 @@
|
||||
"core-js": "2.4.1",
|
||||
"cross-env": "5.0.5",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-tarbz2": "^4.1.1",
|
||||
"decompress-tarbz2": "4.1.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "1.8.4",
|
||||
"electron": "1.8.7",
|
||||
"electron-builder": "20.8.1",
|
||||
"electron-debug": "1.5.0",
|
||||
"electron-devtools-installer": "2.2.3",
|
||||
"electron-log": "2.2.14",
|
||||
"electron-rebuild": "1.7.3",
|
||||
"electron-log": "2.2.16",
|
||||
"electron-rebuild": "1.8.2",
|
||||
"electron-settings": "3.1.4",
|
||||
"electron-updater": "2.21.4",
|
||||
"exports-loader": "0.6.3",
|
||||
"file-loader": "0.10.0",
|
||||
"fs-extra": "5.0.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",
|
||||
"lerna": "2.9.0",
|
||||
"lerna": "3.1.4",
|
||||
"lodash-es": "4.17.4",
|
||||
"mkdirp": "0.5.1",
|
||||
"node-hid": "0.5.7",
|
||||
"npm-run-all": "4.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
"request": "2.83.0",
|
||||
"request": "2.88.0",
|
||||
"rimraf": "2.6.1",
|
||||
"standard-version": "4.2.0",
|
||||
"stylelint": "7.13.0",
|
||||
"svg-sprite": "1.3.7",
|
||||
"stylelint": "9.5.0",
|
||||
"svg-sprite": "1.5.0",
|
||||
"ts-loader": "2.3.1",
|
||||
"ts-node": "3.0.4",
|
||||
"ts-node": "7.0.1",
|
||||
"tslint": "5.9.1",
|
||||
"typescript": "2.6.2",
|
||||
"webpack": "3.10.0"
|
||||
"webpack": "3.12.0"
|
||||
},
|
||||
"pre-commit": [
|
||||
"precommit-msg"
|
||||
@@ -86,6 +92,7 @@
|
||||
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/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",
|
||||
"e2e": "lerna run e2e --scope uhk-web",
|
||||
"prebuild": "check-node-version --package",
|
||||
"build": "run-s build:common build:usb build:web build:electron",
|
||||
"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="
|
||||
},
|
||||
"gaze": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
|
||||
"integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
|
||||
"integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
|
||||
"requires": {
|
||||
"globule": "0.1.0"
|
||||
"globule": "1.2.1"
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
@@ -219,41 +219,37 @@
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"glob": {
|
||||
"version": "3.1.21",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
|
||||
"integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "1.2.3",
|
||||
"inherits": "1.0.2",
|
||||
"minimatch": "0.2.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz",
|
||||
"integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js="
|
||||
}
|
||||
"fs.realpath": "1.0.0",
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
},
|
||||
"globule": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
|
||||
"integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
|
||||
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
|
||||
"requires": {
|
||||
"glob": "3.1.21",
|
||||
"lodash": "1.0.2",
|
||||
"minimatch": "0.2.14"
|
||||
"glob": "7.1.2",
|
||||
"lodash": "4.17.10",
|
||||
"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": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz",
|
||||
"integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto="
|
||||
"version": "1.10.5",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
@@ -362,31 +358,34 @@
|
||||
"integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4="
|
||||
},
|
||||
"jasmine-growl-reporter": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-0.2.1.tgz",
|
||||
"integrity": "sha1-1fCje5L2qD/VxkgrgJSVyQqLVf4=",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-growl-reporter/-/jasmine-growl-reporter-1.0.1.tgz",
|
||||
"integrity": "sha512-dh7VjP3l0OLxL9+sw5vK6RrdH4gdHCNkTnUd9orViHDPr7Fe8LsXY+IObWauS2hX5khMFtjKRZCfTcDHKAjm/A==",
|
||||
"requires": {
|
||||
"growl": "1.7.0"
|
||||
"growl": "1.10.5"
|
||||
}
|
||||
},
|
||||
"jasmine-node": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-2.0.0.tgz",
|
||||
"integrity": "sha1-gXUacjJfVJdJCxQYGlUIfxsDcf8=",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-node/-/jasmine-node-2.0.1.tgz",
|
||||
"integrity": "sha512-1S5Z4Mof5yxwqLIApzyo2pV5WN2kRpTSgICvEo3+rJmKve9P94kolzC9eS0u5cyiT+gxBY2mwOQdxLbkhwKzoA==",
|
||||
"requires": {
|
||||
"coffee-script": "1.7.1",
|
||||
"gaze": "0.5.2",
|
||||
"jasmine-growl-reporter": "0.2.1",
|
||||
"minimist": "0.0.8",
|
||||
"gaze": "1.1.3",
|
||||
"grunt-exec": "0.4.7",
|
||||
"jasmine-growl-reporter": "1.0.1",
|
||||
"jasmine-reporters": "git://github.com/larrymyers/jasmine-reporters.git#2c7242dc11c15c2f156169bc704798568b8cb50d",
|
||||
"minimist": "0.0.10",
|
||||
"mkdirp": "0.3.5",
|
||||
"underscore": "1.6.0",
|
||||
"walkdir": "0.0.12"
|
||||
"walkdir": "0.0.12",
|
||||
"xml2js": "0.4.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
||||
},
|
||||
"mkdirp": {
|
||||
"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": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.2.1.tgz",
|
||||
@@ -442,14 +454,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
|
||||
"integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
|
||||
"integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.0",
|
||||
@@ -470,12 +477,11 @@
|
||||
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
|
||||
"integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"lru-cache": "2.7.3",
|
||||
"sigmund": "1.0.1"
|
||||
"brace-expansion": "1.1.8"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
@@ -633,6 +639,11 @@
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
||||
"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": {
|
||||
"version": "5.4.1",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.0.2",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
||||
|
||||
@@ -14,16 +14,9 @@
|
||||
"npm": ">=5.1.0 <6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"uhk-common": "1.0.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",
|
||||
"ts-node": "3.3.0",
|
||||
"uhk-common": "1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jasmine-ts --config=jasmine.json"
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
||||
|
||||
255
packages/uhk-agent/package-lock.json
generated
@@ -21,7 +21,9 @@ import * as isDev from 'electron-is-dev';
|
||||
|
||||
const optionDefinitions = [
|
||||
{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);
|
||||
@@ -104,7 +106,7 @@ function createWindow() {
|
||||
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);
|
||||
appUpdateService = new AppUpdateService(logger, win, app);
|
||||
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
||||
sudoService = new SudoService(logger, options);
|
||||
|
||||
@@ -3,5 +3,6 @@ import { SynchrounousResult } from 'tmp';
|
||||
export interface TmpFirmware {
|
||||
rightFirmwarePath: string;
|
||||
leftFirmwarePath: string;
|
||||
packageJsonPath: string;
|
||||
tmpDirectory: SynchrounousResult;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BrowserWindow, ipcMain, shell } from 'electron';
|
||||
import { ipcMain, shell } from 'electron';
|
||||
import { UhkHidDevice } from 'uhk-usb';
|
||||
import * as os from 'os';
|
||||
|
||||
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
@@ -25,11 +26,14 @@ export class AppService extends MainServiceBase {
|
||||
const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState();
|
||||
const response: AppStartInfo = {
|
||||
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
|
||||
bootloaderActive: deviceConnectionState.bootloaderActive,
|
||||
platform: process.platform as string,
|
||||
osVersion: os.release()
|
||||
};
|
||||
this.logService.info('[AppService] getAppStartInfo response:', response);
|
||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||
|
||||
@@ -9,12 +9,14 @@ import {
|
||||
IpcResponse,
|
||||
LogService,
|
||||
mapObjectToUserConfigBinaryBuffer,
|
||||
SaveUserConfigurationData
|
||||
SaveUserConfigurationData,
|
||||
UpdateFirmwareData
|
||||
} from 'uhk-common';
|
||||
import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { emptyDir } from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
@@ -22,10 +24,14 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
import { saveTmpFirmware } from '../util/save-extract-firmware';
|
||||
import { TmpFirmware } from '../models/tmp-firmware';
|
||||
import { QueueManager } from './queue-manager';
|
||||
import { backupUserConfiguration, getBackupUserConfigurationContent } from '../util/backup-user-confoguration';
|
||||
import {
|
||||
backupUserConfiguration,
|
||||
getBackupUserConfigurationContent,
|
||||
getPackageJsonFromPathAsync,
|
||||
saveTmpFirmware
|
||||
} from '../util';
|
||||
|
||||
/**
|
||||
* IpcMain pair of the UHK Communication
|
||||
@@ -40,7 +46,8 @@ export class DeviceService {
|
||||
constructor(private logService: LogService,
|
||||
private win: Electron.BrowserWindow,
|
||||
private device: UhkHidDevice,
|
||||
private operations: UhkOperations) {
|
||||
private operations: UhkOperations,
|
||||
private rootDir: string) {
|
||||
this.pollUhkDevice();
|
||||
|
||||
ipcMain.on(IpcEvents.device.saveUserConfiguration, (...args: any[]) => {
|
||||
@@ -81,6 +88,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');
|
||||
}
|
||||
|
||||
@@ -142,19 +158,33 @@ export class DeviceService {
|
||||
|
||||
public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> {
|
||||
const response = new FirmwareUpgradeIpcResponse();
|
||||
const data: UpdateFirmwareData = JSON.parse(args[0]);
|
||||
|
||||
let firmwarePathData: TmpFirmware;
|
||||
|
||||
try {
|
||||
this.logService.debug('Agent version:', data.versionInformation.version);
|
||||
const hardwareModules = await this.getHardwareModules(false);
|
||||
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
|
||||
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
|
||||
|
||||
this.device.resetDeviceCache();
|
||||
this.stopPollTimer();
|
||||
|
||||
if (args && args.length > 0) {
|
||||
firmwarePathData = await saveTmpFirmware(args[0]);
|
||||
if (data.firmware) {
|
||||
firmwarePathData = await saveTmpFirmware(data.firmware);
|
||||
|
||||
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
|
||||
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||
|
||||
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
||||
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
||||
}
|
||||
else {
|
||||
const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json');
|
||||
const packageJson = await getPackageJsonFromPathAsync(packageJsonPath);
|
||||
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||
|
||||
await this.operations.updateRightFirmware();
|
||||
await this.operations.updateLeftModule();
|
||||
}
|
||||
@@ -206,6 +236,10 @@ export class DeviceService {
|
||||
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.
|
||||
* This method check the keyboard is attached to the computer or not.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const getPackageJsonFromPathAsync = async (filePath: string): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(filePath, {encoding: 'utf-8'}, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
};
|
||||
3
packages/uhk-agent/src/util/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './backup-user-confoguration';
|
||||
export * from './get-package-json-from-path-async';
|
||||
export * from './save-extract-firmware';
|
||||
@@ -6,7 +6,7 @@ import * as decompressTarbz from 'decompress-tarbz2';
|
||||
|
||||
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 zipFilePath = path.join(tmpDirectory.name, 'firmware.bz2');
|
||||
|
||||
@@ -16,15 +16,14 @@ export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
|
||||
return {
|
||||
tmpDirectory,
|
||||
rightFirmwarePath: path.join(tmpDirectory.name, 'devices/uhk60-right/firmware.hex'),
|
||||
leftFirmwarePath: path.join(tmpDirectory.name, 'modules/uhk60-left.bin')
|
||||
|
||||
leftFirmwarePath: path.join(tmpDirectory.name, 'modules/uhk60-left.bin'),
|
||||
packageJsonPath: path.join(tmpDirectory.name, 'package.json')
|
||||
};
|
||||
}
|
||||
|
||||
function writeDataToFile(data: string, filePath: string): Promise<void> {
|
||||
function writeDataToFile(data: Array<number>, filePath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const array: Array<number> = JSON.parse(data);
|
||||
const buffer = new Buffer(array);
|
||||
const buffer = new Buffer(data);
|
||||
|
||||
fs.writeFile(filePath, buffer, err => {
|
||||
if (err) {
|
||||
|
||||
@@ -19,16 +19,6 @@
|
||||
"coverage": "nyc jasmine-ts --config=jasmine.json"
|
||||
},
|
||||
"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": {
|
||||
"extension": [
|
||||
".ts"
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { binaryDefaultHelper, jsonDefaultHelper } from '../../../../test/serializer-test-helper';
|
||||
import { SwitchLayerAction } from './switch-layer-action';
|
||||
import { SwitchLayerAction, SwitchLayerMode } from './switch-layer-action';
|
||||
import { keyActionType } from './key-action';
|
||||
|
||||
// TODO: Add null, undefined, empty object, empty buffer test cases
|
||||
describe('switch-layer-action', () => {
|
||||
const action = new SwitchLayerAction(<SwitchLayerAction>{layer: 0, isLayerToggleable: false});
|
||||
const action = new SwitchLayerAction(<SwitchLayerAction>{layer: 0, switchLayerMode: SwitchLayerMode.hold});
|
||||
|
||||
it('should be instantiate', () => {
|
||||
expect(new SwitchLayerAction()).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('should return <SwitchLayerAction layer="0" toggle="false">', () => {
|
||||
expect(action.toString()).toEqual('<SwitchLayerAction layer="0" toggle="false">');
|
||||
it('should return <SwitchLayerAction layer="0" switchLayerMode="hold">', () => {
|
||||
expect(action.toString()).toEqual('<SwitchLayerAction layer="0" switchLayerMode="hold">');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,4 +31,20 @@ describe('switch-layer-action', () => {
|
||||
binaryDefaultHelper(action);
|
||||
});
|
||||
});
|
||||
|
||||
describe('backward compatibility of the "toggle" property ', () => {
|
||||
it('should map toggle=false to SwitchLayerMode.holdAndDoubleTapToggle', () => {
|
||||
const oldAction = new SwitchLayerAction();
|
||||
oldAction.fromJsonObject({keyActionType: keyActionType.SwitchLayerAction, layer: 0, toggle: false});
|
||||
|
||||
expect(oldAction.switchLayerMode).toEqual(SwitchLayerMode.holdAndDoubleTapToggle);
|
||||
});
|
||||
|
||||
it('should map toggle=true to SwitchLayerMode.toggle', () => {
|
||||
const oldAction = new SwitchLayerAction();
|
||||
oldAction.fromJsonObject({keyActionType: keyActionType.SwitchLayerAction, layer: 0, toggle: true});
|
||||
|
||||
expect(oldAction.switchLayerMode).toEqual(SwitchLayerMode.toggle);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,9 +8,48 @@ export enum LayerName {
|
||||
mouse
|
||||
}
|
||||
|
||||
export enum SwitchLayerMode {
|
||||
holdAndDoubleTapToggle = 'holdAndDoubleTapToggle',
|
||||
toggle = 'toggle',
|
||||
hold = 'hold'
|
||||
}
|
||||
|
||||
export const mapSwitchLayerModeToNumber = (switchLayerMode: SwitchLayerMode): number => {
|
||||
switch (switchLayerMode) {
|
||||
case SwitchLayerMode.holdAndDoubleTapToggle:
|
||||
return 0;
|
||||
|
||||
case SwitchLayerMode.toggle:
|
||||
return 1;
|
||||
|
||||
case SwitchLayerMode.hold:
|
||||
return 2;
|
||||
|
||||
default:
|
||||
throw new Error(`Can not map ${switchLayerMode} to number`);
|
||||
}
|
||||
};
|
||||
|
||||
export const mapNumberToSwitchLayerMode = (value: number): SwitchLayerMode => {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return SwitchLayerMode.holdAndDoubleTapToggle;
|
||||
|
||||
case 1:
|
||||
return SwitchLayerMode.toggle;
|
||||
|
||||
case 2:
|
||||
return SwitchLayerMode.hold;
|
||||
|
||||
default:
|
||||
throw new Error(`Can not map "${value}" to SwitchLayerMode`);
|
||||
}
|
||||
};
|
||||
|
||||
export class SwitchLayerAction extends KeyAction {
|
||||
|
||||
isLayerToggleable: boolean;
|
||||
@assertEnum(SwitchLayerMode)
|
||||
switchLayerMode: SwitchLayerMode;
|
||||
|
||||
@assertEnum(LayerName)
|
||||
layer: LayerName;
|
||||
@@ -20,21 +59,29 @@ export class SwitchLayerAction extends KeyAction {
|
||||
if (!other) {
|
||||
return;
|
||||
}
|
||||
this.isLayerToggleable = other.isLayerToggleable;
|
||||
this.switchLayerMode = other.switchLayerMode;
|
||||
this.layer = other.layer;
|
||||
}
|
||||
|
||||
fromJsonObject(jsonObject: any): SwitchLayerAction {
|
||||
this.assertKeyActionType(jsonObject);
|
||||
this.layer = LayerName[<string>jsonObject.layer];
|
||||
this.isLayerToggleable = jsonObject.toggle;
|
||||
|
||||
// Backward compatibility when "switchLayerMode" was a boolean type as "toggle"
|
||||
if (typeof jsonObject.toggle === 'boolean') {
|
||||
this.switchLayerMode = jsonObject.toggle ? SwitchLayerMode.toggle : SwitchLayerMode.holdAndDoubleTapToggle;
|
||||
}
|
||||
else {
|
||||
this.switchLayerMode = jsonObject.switchLayerMode;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fromBinary(buffer: UhkBuffer): SwitchLayerAction {
|
||||
this.readAndAssertKeyActionId(buffer);
|
||||
this.layer = buffer.readUInt8();
|
||||
this.isLayerToggleable = buffer.readBoolean();
|
||||
this.switchLayerMode = mapNumberToSwitchLayerMode(buffer.readUInt8());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -42,18 +89,18 @@ export class SwitchLayerAction extends KeyAction {
|
||||
return {
|
||||
keyActionType: keyActionType.SwitchLayerAction,
|
||||
layer: LayerName[this.layer],
|
||||
toggle: this.isLayerToggleable
|
||||
switchLayerMode: this.switchLayerMode
|
||||
};
|
||||
}
|
||||
|
||||
toBinary(buffer: UhkBuffer) {
|
||||
buffer.writeUInt8(KeyActionId.SwitchLayerAction);
|
||||
buffer.writeUInt8(this.layer);
|
||||
buffer.writeBoolean(this.isLayerToggleable);
|
||||
buffer.writeUInt8(mapSwitchLayerModeToNumber(this.switchLayerMode));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `<SwitchLayerAction layer="${this.layer}" toggle="${this.isLayerToggleable}">`;
|
||||
return `<SwitchLayerAction layer="${this.layer}" switchLayerMode="${this.switchLayerMode}">`;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
|
||||
@@ -127,7 +127,7 @@ export class Keymap {
|
||||
if (currentLayerId - 1 === baseKeyAction.layer) {
|
||||
if (currentKeyAction instanceof SwitchLayerAction) {
|
||||
if (currentKeyAction.layer === baseKeyAction.layer &&
|
||||
currentKeyAction.isLayerToggleable === baseKeyAction.isLayerToggleable) {
|
||||
currentKeyAction.switchLayerMode === baseKeyAction.switchLayerMode) {
|
||||
continue;
|
||||
}
|
||||
// tslint:disable-next-line: max-line-length
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('keymap', () => {
|
||||
{
|
||||
keyActionType: 'switchLayer',
|
||||
layer: 'mod',
|
||||
toggle: false
|
||||
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -121,7 +121,7 @@ describe('keymap', () => {
|
||||
{
|
||||
keyActionType: 'switchLayer',
|
||||
layer: 'mod',
|
||||
toggle: false
|
||||
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -151,7 +151,7 @@ describe('keymap', () => {
|
||||
|
||||
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
||||
// tslint:disable-next-line: max-line-length
|
||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" toggle="false">');
|
||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" switchLayerMode="holdAndDoubleTapToggle">');
|
||||
});
|
||||
|
||||
it('should normalize SwitchLayerAction if non base layer action is other SwitchLayerAction', () => {
|
||||
@@ -262,7 +262,7 @@ describe('keymap', () => {
|
||||
{
|
||||
keyActionType: 'switchLayer',
|
||||
layer: 'mod',
|
||||
toggle: false
|
||||
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -274,7 +274,7 @@ describe('keymap', () => {
|
||||
{
|
||||
keyActionType: 'switchLayer',
|
||||
layer: 'mod',
|
||||
toggle: false
|
||||
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -304,6 +304,6 @@ describe('keymap', () => {
|
||||
|
||||
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
||||
// tslint:disable-next-line: max-line-length
|
||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" toggle="false"> will be override with <SwitchLayerAction layer="0" toggle="false">');
|
||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" switchLayerMode="holdAndDoubleTapToggle"> will be override with <SwitchLayerAction layer="0" switchLayerMode="holdAndDoubleTapToggle">');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,9 +3,9 @@ import { UhkBuffer } from '../../uhk-buffer';
|
||||
import { MacroAction, MacroActionId, MacroMouseSubAction, macroActionType } from './macro-action';
|
||||
|
||||
export enum MouseButtons {
|
||||
Left = 1 << 0,
|
||||
Middle = 1 << 1,
|
||||
Right = 1 << 2
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
Middle = 2
|
||||
}
|
||||
|
||||
export interface JsObjectMouseButtonMacroAction {
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
{
|
||||
"id": "70",
|
||||
"text": "Print Screen"
|
||||
"text": "Print Screen SysRq"
|
||||
},
|
||||
{
|
||||
"id": "72",
|
||||
@@ -509,6 +509,22 @@
|
||||
"type": "media",
|
||||
"scancode": 138
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "145",
|
||||
"text": "History Back",
|
||||
"additional": {
|
||||
"type": "media",
|
||||
"scancode": 548
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "146",
|
||||
"text": "History Forward",
|
||||
"additional": {
|
||||
"type": "media",
|
||||
"scancode": 549
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { assertUInt8, assertUInt16 } from '../assert';
|
||||
import { assertUInt16, assertUInt8 } from '../assert';
|
||||
import { UhkBuffer } from '../uhk-buffer';
|
||||
import { Keymap } from './keymap';
|
||||
import { Macro } from './macro';
|
||||
import { ModuleConfiguration } from './module-configuration';
|
||||
import { ConfigSerializer } from '../config-serializer';
|
||||
import { KeystrokeAction } from './key-action';
|
||||
import { SecondaryRoleAction } from './secondary-role-action';
|
||||
|
||||
export class UserConfiguration {
|
||||
|
||||
@@ -90,7 +92,7 @@ export class UserConfiguration {
|
||||
this.mouseMoveAcceleratedSpeed = jsonObject.mouseMoveAcceleratedSpeed;
|
||||
this.mouseScrollInitialSpeed = jsonObject.mouseScrollInitialSpeed;
|
||||
this.mouseScrollAcceleration = jsonObject.mouseScrollAcceleration;
|
||||
this.mouseScrollDeceleratedSpeed = jsonObject.mouseScrollAcceleration;
|
||||
this.mouseScrollDeceleratedSpeed = jsonObject.mouseScrollDeceleratedSpeed;
|
||||
this.mouseScrollBaseSpeed = jsonObject.mouseScrollBaseSpeed;
|
||||
this.mouseScrollAcceleratedSpeed = jsonObject.mouseScrollAcceleratedSpeed;
|
||||
this.moduleConfigurations = jsonObject.moduleConfigurations.map((moduleConfiguration: any) => {
|
||||
@@ -102,7 +104,9 @@ export class UserConfiguration {
|
||||
return macro;
|
||||
});
|
||||
this.keymaps = jsonObject.keymaps.map((keymap: any) => new Keymap().fromJsonObject(keymap, this.macros));
|
||||
this.clean();
|
||||
this.recalculateConfigurationLength();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -138,6 +142,8 @@ export class UserConfiguration {
|
||||
this.keymaps = buffer.readArray<Keymap>(uhkBuffer => new Keymap().fromBinary(uhkBuffer, this.macros));
|
||||
ConfigSerializer.resolveSwitchKeymapActions(this.keymaps);
|
||||
|
||||
this.clean();
|
||||
|
||||
if (this.userConfigurationLength === 0) {
|
||||
this.recalculateConfigurationLength();
|
||||
}
|
||||
@@ -222,4 +228,30 @@ export class UserConfiguration {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,6 @@ export interface AppStartInfo {
|
||||
deviceConnected: boolean;
|
||||
hasPermission: boolean;
|
||||
bootloaderActive: boolean;
|
||||
platform: string;
|
||||
osVersion: string;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ export interface CommandLineArgs {
|
||||
* simulate privilege escalation error
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from './device-connection-state';
|
||||
export * from './hardware-modules';
|
||||
export * from './hardware-module-info';
|
||||
export * from './save-user-configuration-data';
|
||||
export * from './update-firmware-data';
|
||||
|
||||
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>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export namespace Constants {
|
||||
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
||||
export const FIRMWARE_GITHUB_ISSUE_URL = 'https://github.com/UltimateHackingKeyboard/agent/issues/567';
|
||||
export const FIRMWARE_GITHUB_ISSUE_URL = 'https://github.com/UltimateHackingKeyboard/agent/issues/new';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export { IpcEvents } from './ipcEvents';
|
||||
export * from './log';
|
||||
export * from './constants';
|
||||
export * from './helpers';
|
||||
export * from './is-equal-array';
|
||||
|
||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||
export function camelCaseToSentence(camelCasedText: string): string {
|
||||
|
||||
@@ -30,6 +30,7 @@ export class Device {
|
||||
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
|
||||
public static readonly startConnectionPoller = 'device-start-connection-poller';
|
||||
public static readonly recoveryDevice = 'device-recovery';
|
||||
public static readonly enableUsbStackTest = 'enable-usb-stack-test';
|
||||
}
|
||||
|
||||
export class IpcEvents {
|
||||
|
||||
15
packages/uhk-common/src/util/is-equal-array.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
export const isEqualArray = (arr1: Array<any>, arr2: Array<any>): boolean => {
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const a of arr1) {
|
||||
if (!arr2.some(b => isEqual(a, b))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
@@ -9,9 +10,6 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2015.iterable",
|
||||
"dom",
|
||||
|
||||
154
packages/uhk-usb/package-lock.json
generated
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"requires": true,
|
||||
"name": "uhk-usb",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "8.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz",
|
||||
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ=="
|
||||
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
@@ -22,8 +25,8 @@
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
|
||||
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
|
||||
"requires": {
|
||||
"delegates": "1.0.0",
|
||||
"readable-stream": "2.3.6"
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"bindings": {
|
||||
@@ -36,8 +39,8 @@
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
|
||||
"requires": {
|
||||
"readable-stream": "2.3.6",
|
||||
"safe-buffer": "5.1.1"
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
@@ -65,7 +68,7 @@
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
||||
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
|
||||
"requires": {
|
||||
"mimic-response": "1.0.0"
|
||||
"mimic-response": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
@@ -88,7 +91,7 @@
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
||||
"requires": {
|
||||
"once": "1.4.0"
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"expand-template": {
|
||||
@@ -101,14 +104,14 @@
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||
"requires": {
|
||||
"aproba": "1.2.0",
|
||||
"console-control-strings": "1.1.0",
|
||||
"has-unicode": "2.0.1",
|
||||
"object-assign": "4.1.1",
|
||||
"signal-exit": "3.0.2",
|
||||
"string-width": "1.0.2",
|
||||
"strip-ansi": "3.0.1",
|
||||
"wide-align": "1.1.2"
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"github-from-package": {
|
||||
@@ -136,7 +139,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"requires": {
|
||||
"number-is-nan": "1.0.1"
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
@@ -144,11 +147,6 @@
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz",
|
||||
"integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg=="
|
||||
},
|
||||
"mimic-response": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
|
||||
@@ -184,7 +182,7 @@
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz",
|
||||
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==",
|
||||
"requires": {
|
||||
"semver": "5.5.0"
|
||||
"semver": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"node-hid": {
|
||||
@@ -192,9 +190,9 @@
|
||||
"resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.5.7.tgz",
|
||||
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==",
|
||||
"requires": {
|
||||
"bindings": "1.3.0",
|
||||
"nan": "2.10.0",
|
||||
"prebuild-install": "2.5.1"
|
||||
"bindings": "^1.3.0",
|
||||
"nan": "^2.6.2",
|
||||
"prebuild-install": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"noop-logger": {
|
||||
@@ -207,10 +205,10 @@
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||
"requires": {
|
||||
"are-we-there-yet": "1.1.4",
|
||||
"console-control-strings": "1.1.0",
|
||||
"gauge": "2.7.4",
|
||||
"set-blocking": "2.0.0"
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
"gauge": "~2.7.3",
|
||||
"set-blocking": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
@@ -228,7 +226,7 @@
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
@@ -241,21 +239,21 @@
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz",
|
||||
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==",
|
||||
"requires": {
|
||||
"detect-libc": "1.0.3",
|
||||
"expand-template": "1.1.0",
|
||||
"detect-libc": "^1.0.3",
|
||||
"expand-template": "^1.0.2",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "1.2.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"node-abi": "2.3.0",
|
||||
"noop-logger": "0.1.1",
|
||||
"npmlog": "4.1.2",
|
||||
"os-homedir": "1.0.2",
|
||||
"pump": "2.0.1",
|
||||
"rc": "1.2.6",
|
||||
"simple-get": "2.7.0",
|
||||
"tar-fs": "1.16.0",
|
||||
"tunnel-agent": "0.6.0",
|
||||
"which-pm-runs": "1.0.0"
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-abi": "^2.2.0",
|
||||
"noop-logger": "^0.1.1",
|
||||
"npmlog": "^4.0.1",
|
||||
"os-homedir": "^1.0.1",
|
||||
"pump": "^2.0.1",
|
||||
"rc": "^1.1.6",
|
||||
"simple-get": "^2.7.0",
|
||||
"tar-fs": "^1.13.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"which-pm-runs": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
@@ -268,8 +266,8 @@
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.1",
|
||||
"once": "1.4.0"
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
@@ -277,10 +275,10 @@
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz",
|
||||
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=",
|
||||
"requires": {
|
||||
"deep-extend": "0.4.2",
|
||||
"ini": "1.3.5",
|
||||
"minimist": "1.2.0",
|
||||
"strip-json-comments": "2.0.1"
|
||||
"deep-extend": "~0.4.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
@@ -288,13 +286,13 @@
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.1.1",
|
||||
"util-deprecate": "1.0.2"
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
@@ -327,9 +325,9 @@
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz",
|
||||
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==",
|
||||
"requires": {
|
||||
"decompress-response": "3.3.0",
|
||||
"once": "1.4.0",
|
||||
"simple-concat": "1.0.0"
|
||||
"decompress-response": "^3.3.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
@@ -337,9 +335,9 @@
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"requires": {
|
||||
"code-point-at": "1.1.0",
|
||||
"is-fullwidth-code-point": "1.0.0",
|
||||
"strip-ansi": "3.0.1"
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
@@ -347,7 +345,7 @@
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
@@ -355,7 +353,7 @@
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "2.1.1"
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
@@ -368,10 +366,10 @@
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz",
|
||||
"integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==",
|
||||
"requires": {
|
||||
"chownr": "1.0.1",
|
||||
"mkdirp": "0.5.1",
|
||||
"pump": "1.0.3",
|
||||
"tar-stream": "1.5.5"
|
||||
"chownr": "^1.0.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"pump": "^1.0.0",
|
||||
"tar-stream": "^1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"pump": {
|
||||
@@ -379,8 +377,8 @@
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
|
||||
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.1",
|
||||
"once": "1.4.0"
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,10 +388,10 @@
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
|
||||
"integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
|
||||
"requires": {
|
||||
"bl": "1.2.2",
|
||||
"end-of-stream": "1.4.1",
|
||||
"readable-stream": "2.3.6",
|
||||
"xtend": "4.0.1"
|
||||
"bl": "^1.0.0",
|
||||
"end-of-stream": "^1.0.0",
|
||||
"readable-stream": "^2.0.0",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
@@ -401,7 +399,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
@@ -419,7 +417,7 @@
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
|
||||
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
|
||||
"requires": {
|
||||
"string-width": "1.0.2"
|
||||
"string-width": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"@types/node": "8.0.28"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash-es": "^4.17.10",
|
||||
"node-hid": "0.5.7",
|
||||
"uhk-common": "1.0.0"
|
||||
}
|
||||
|
||||
@@ -23,7 +23,12 @@ export enum UsbCommand {
|
||||
GetDebugBuffer = 0x0b,
|
||||
GetAdcValue = 0x0c,
|
||||
SetLedPwmBrightness = 0x0d,
|
||||
GetModuleProperty = 0x0e
|
||||
GetModuleProperty = 0x0e,
|
||||
GetSlaveI2cErrors = 0x0f,
|
||||
SetI2cBaudRate = 0x10,
|
||||
SwitchKeymap = 0x11,
|
||||
GetVariable = 0x12,
|
||||
SetVariable = 0x13
|
||||
}
|
||||
|
||||
export enum EepromOperation {
|
||||
@@ -86,3 +91,10 @@ export enum KbootCommands {
|
||||
export enum ModulePropertyId {
|
||||
protocolVersions = 0
|
||||
}
|
||||
|
||||
export enum UsbVariables {
|
||||
testSwitches = 0,
|
||||
testUsbStack = 1,
|
||||
debounceTimePress = 2,
|
||||
debounceTimeRelease = 3
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class UhkBlhost {
|
||||
|
||||
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
||||
|
||||
if (code !== null && code !== 0) {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`blhost error code:${code}`));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
import { CommandLineArgs, DeviceConnectionState, LogService } from 'uhk-common';
|
||||
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService } from 'uhk-common';
|
||||
|
||||
import {
|
||||
ConfigBufferId,
|
||||
@@ -11,7 +10,8 @@ import {
|
||||
KbootCommands,
|
||||
ModuleSlotToI2cAddress,
|
||||
ModuleSlotToId,
|
||||
UsbCommand
|
||||
UsbCommand,
|
||||
UsbVariables
|
||||
} from './constants';
|
||||
import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util';
|
||||
|
||||
@@ -25,7 +25,7 @@ export class UhkHidDevice {
|
||||
* Internal variable that represent the USB UHK device
|
||||
* @private
|
||||
*/
|
||||
private _prevDevices = {};
|
||||
private _prevDevices = [];
|
||||
private _device: HID;
|
||||
private _hasPermission = false;
|
||||
|
||||
@@ -110,6 +110,7 @@ export class UhkHidDevice {
|
||||
device.read((err: any, receivedData: Array<number>) => {
|
||||
if (err) {
|
||||
this.logService.error('[UhkHidDevice] Transfer error: ', err);
|
||||
this.close();
|
||||
return reject(err);
|
||||
}
|
||||
const logString = bufferToString(receivedData);
|
||||
@@ -133,6 +134,11 @@ export class UhkHidDevice {
|
||||
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
|
||||
*/
|
||||
@@ -158,7 +164,7 @@ export class UhkHidDevice {
|
||||
}
|
||||
|
||||
public resetDeviceCache(): void {
|
||||
this._prevDevices = {};
|
||||
this._prevDevices = [];
|
||||
}
|
||||
|
||||
async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
|
||||
@@ -252,8 +258,11 @@ export class UhkHidDevice {
|
||||
private connectToDevice(): HID {
|
||||
try {
|
||||
const devs = devices();
|
||||
if (!isEqual(this._prevDevices, devs)) {
|
||||
this.logService.debug('[UhkHidDevice] Available devices:', devs);
|
||||
if (!isEqualArray(this._prevDevices, devs)) {
|
||||
this.logService.debug('[UhkHidDevice] Available devices:');
|
||||
for (const logDevice of devs) {
|
||||
this.logService.debug(JSON.stringify(logDevice));
|
||||
}
|
||||
this._prevDevices = devs;
|
||||
} else {
|
||||
this.logService.debug('[UhkHidDevice] Available devices unchanged');
|
||||
@@ -266,7 +275,7 @@ export class UhkHidDevice {
|
||||
return null;
|
||||
}
|
||||
const device = new HID(dev.path);
|
||||
this.logService.debug('[UhkHidDevice] Used device:', dev);
|
||||
this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev));
|
||||
return device;
|
||||
}
|
||||
catch (err) {
|
||||
@@ -288,7 +297,7 @@ function kbootCommandName(module: ModuleSlotToI2cAddress): string {
|
||||
case ModuleSlotToI2cAddress.rightAddon:
|
||||
return 'rightAddon';
|
||||
|
||||
default :
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,9 @@ export class UhkOperations {
|
||||
|
||||
const leftModuleBricked = await this.waitForKbootIdle();
|
||||
if (!leftModuleBricked) {
|
||||
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
||||
return;
|
||||
const msg = '[UhkOperations] Couldn\'t connect to the left keyboard half.';
|
||||
this.logService.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||
|
||||
@@ -77,7 +77,6 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
||||
try {
|
||||
// logService.debug(`[retry] try to run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
await command();
|
||||
await snooze(100);
|
||||
// logService.debug(`[retry] success FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
return;
|
||||
} catch (err) {
|
||||
@@ -93,6 +92,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
||||
if (logService) {
|
||||
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
}
|
||||
await snooze(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||
"../node_modules/select2/dist/js/select2.full.js",
|
||||
"../node_modules/nouislider/distribute/nouislider.js"
|
||||
],
|
||||
"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();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
it('should display default device name', () => {
|
||||
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('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
getDeviceName() {
|
||||
return element(by.css('body > main-app > side-menu > ul > li:nth-child(1) > div > auto-grow-input > input'))
|
||||
.getAttribute('value');
|
||||
}
|
||||
}
|
||||
|
||||
11007
packages/uhk-web/package-lock.json
generated
@@ -36,10 +36,9 @@
|
||||
"@types/electron-devtools-installer": "2.0.2",
|
||||
"@types/electron-settings": "3.0.0",
|
||||
"@types/file-saver": "0.0.1",
|
||||
"@types/jasmine": "2.5.53",
|
||||
"@types/jasminewd2": "2.0.2",
|
||||
"@types/jasmine": "2.8.8",
|
||||
"@types/jasminewd2": "2.0.3",
|
||||
"@types/jquery": "3.2.9",
|
||||
"@types/lodash-es": "4.17.0",
|
||||
"@types/usb": "1.1.3",
|
||||
"angular-confirmation-popover": "3.2.0",
|
||||
"angular-notifier": "2.0.0",
|
||||
@@ -51,27 +50,25 @@
|
||||
"dragula": "3.7.2",
|
||||
"font-awesome": "4.7.0",
|
||||
"html-webpack-plugin": "^2.29.0",
|
||||
"jasmine-core": "2.6.2",
|
||||
"jasmine-spec-reporter": "4.1.0",
|
||||
"jasmine-core": "3.2.1",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"jquery": "3.2.1",
|
||||
"jsonfile": "3.0.1",
|
||||
"karma": "1.7.0",
|
||||
"karma-chrome-launcher": "2.1.1",
|
||||
"karma-coverage-istanbul-reporter": "1.2.1",
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"lodash-es": "4.17.4",
|
||||
"karma": "3.0.0",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "2.0.1",
|
||||
"karma-jasmine": "1.1.2",
|
||||
"karma-jasmine-html-reporter": "1.3.1",
|
||||
"ng2-dragula": "1.5.0",
|
||||
"ng2-nouislider": "^1.7.7",
|
||||
"ng2-select2": "1.0.0-beta.10",
|
||||
"ngx-clipboard": "10.0.0",
|
||||
"@ert78gb/ngx-select-ex": "3.7.2",
|
||||
"ngrx-store-freeze": "0.1.9",
|
||||
"nouislider": "^11.1.0",
|
||||
"postcss-url": "^7.1.2",
|
||||
"protractor": "5.1.2",
|
||||
"protractor": "5.4.0",
|
||||
"reselect": "3.0.1",
|
||||
"rxjs": "5.5.8",
|
||||
"select2": "4.0.3",
|
||||
"typescript": "2.6.2",
|
||||
"uhk-common": "1.0.0",
|
||||
"xml-loader": "1.2.1",
|
||||
@@ -81,6 +78,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"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'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
baseUrl: 'http://localhost:8080/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<notifier-container></notifier-container>
|
||||
<progress-button class="save-to-keyboard-button"
|
||||
*ngIf="(saveToKeyboardState$ | async).showButton"
|
||||
*ngIf="saveToKeyboardState.showButton"
|
||||
[@showSaveToKeyboardButton]
|
||||
[state]="saveToKeyboardState$ | async"
|
||||
[state]="saveToKeyboardState"
|
||||
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, HostListener, ViewEncapsulation, OnDestroy } from '@angular/core';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
|
||||
import 'rxjs/add/operator/last';
|
||||
|
||||
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
||||
import { EnableUsbStackTestAction } from './store/actions/device';
|
||||
import {
|
||||
AppState,
|
||||
getShowAppUpdateAvailable,
|
||||
@@ -34,17 +36,44 @@ import { ProgressButtonState } from './store/reducers/progress-button-state';
|
||||
])
|
||||
]
|
||||
})
|
||||
export class MainAppComponent {
|
||||
export class MainAppComponent implements OnDestroy {
|
||||
showUpdateAvailable$: Observable<boolean>;
|
||||
deviceConfigurationLoaded$: Observable<boolean>;
|
||||
runningInElectron$: Observable<boolean>;
|
||||
saveToKeyboardState$: Observable<ProgressButtonState>;
|
||||
saveToKeyboardState: ProgressButtonState;
|
||||
|
||||
private saveToKeyboardStateSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
||||
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
||||
this.runningInElectron$ = store.select(runningInElectron);
|
||||
this.saveToKeyboardState$ = store.select(saveToKeyboardState);
|
||||
this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState)
|
||||
.subscribe(data => this.saveToKeyboardState = data);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.saveToKeyboardStateSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
onKeyDown(event: KeyboardEvent) {
|
||||
if (this.saveToKeyboardState.showButton &&
|
||||
event.ctrlKey &&
|
||||
event.key === 's' &&
|
||||
!event.defaultPrevented) {
|
||||
this.clickedOnProgressButton(this.saveToKeyboardState.action);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.shiftKey &&
|
||||
event.ctrlKey &&
|
||||
event.metaKey &&
|
||||
event.key === '|' &&
|
||||
!event.defaultPrevented) {
|
||||
this.enableUsbStackTest();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
updateApp() {
|
||||
@@ -58,4 +87,8 @@ export class MainAppComponent {
|
||||
clickedOnProgressButton(action: Action) {
|
||||
return this.store.dispatch(action);
|
||||
}
|
||||
|
||||
enableUsbStackTest() {
|
||||
this.store.dispatch(new EnableUsbStackTestAction());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="row">
|
||||
<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>
|
||||
</h1>
|
||||
<div class="col-xs-12">
|
||||
<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 class="agent-version">Agent version: <span class="text-bold">{{ version }}</span></div>
|
||||
<div><a class="link-github" [href]="agentGithubUrl" externalUrl>Agent on GitHub</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Constants } from 'uhk-common';
|
||||
|
||||
import { AppState } from '../../../store';
|
||||
import { getVersions } from '../../../util';
|
||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||
|
||||
@Component({
|
||||
selector: 'about-page',
|
||||
@@ -16,12 +13,5 @@ import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||
})
|
||||
export class AboutComponent {
|
||||
version: string = getVersions().version;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
}
|
||||
|
||||
openAgentGitHubPage(event) {
|
||||
event.preventDefault();
|
||||
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
|
||||
}
|
||||
agentGithubUrl = Constants.AGENT_GITHUB_URL;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@ import { Routes } from '@angular/router';
|
||||
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { AboutComponent } from './about/about.component';
|
||||
import { HelpPageComponent } from './help-page/help-page.component';
|
||||
|
||||
export const agentRoutes: Routes = [
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent
|
||||
},
|
||||
{
|
||||
path: 'help',
|
||||
component: HelpPageComponent
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
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 {
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<ul class="list-unstyled btn-list">
|
||||
<li>
|
||||
<button class="btn btn-default"
|
||||
<button class="btn btn-primary"
|
||||
(click)="exportUserConfiguration($event)">Export device configuration
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -12,19 +12,10 @@
|
||||
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
||||
</p>
|
||||
|
||||
If the update process fails, consider the following points:
|
||||
<ol>
|
||||
<li>Windows 7, Windows Vista, and Windows XP are not supported. Use Linux, OSX, Windows 10, or Windows 8.</li>
|
||||
<li>Connect your UHK directly to the host computer. Don't use USB hubs or KVM switches.</li>
|
||||
<li>Run Agent directly on the host operating system. Don't use VirtualBox or VMware Workstation.</li>
|
||||
<li>Give a try to every USB port of your computer.</li>
|
||||
<li>Remove every other USB device from your computer.</li>
|
||||
<li>If the left half becomes unresponsive after a failed update then retry and follow the instructions displayed during the update to fix it.</li>
|
||||
<li>If the above fails, retry a couple of times.</li>
|
||||
<li>If everything else fails, please add a new comment to <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">the GitHub issue</a>, and attach the update log.</li>
|
||||
</ol>
|
||||
<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>
|
||||
<p *ngIf="firmwareUpgradeAllowed$ | async">
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmware()">
|
||||
@@ -35,9 +26,23 @@
|
||||
accept=".tar.bz2"
|
||||
label="Choose firmware file and flash it"></file-upload>
|
||||
</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 class="flex-grow">
|
||||
<div *ngIf="firmwareUpgradeSuccess$ | async"
|
||||
class="alert alert-success"
|
||||
role="alert">
|
||||
<p>Firmware update succeeded.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow" *ngIf="firmwareUpgradeAllowed$ | async">
|
||||
<xterm [logs]="xtermLog$ | async"></xterm>
|
||||
</div>
|
||||
<div class="flex-footer">
|
||||
|
||||
@@ -2,15 +2,18 @@ import { Component, OnDestroy } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { HardwareModules, VersionInformation } from 'uhk-common';
|
||||
import { Constants } from 'uhk-common';
|
||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||
import { Constants, HardwareModules, VersionInformation } from 'uhk-common';
|
||||
|
||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||
import {
|
||||
AppState,
|
||||
firmwareUpgradeAllowed,
|
||||
firmwareUpgradeFailed,
|
||||
firmwareUpgradeSuccess,
|
||||
flashFirmwareButtonDisbabled,
|
||||
getAgentVersionInfo,
|
||||
getHardwareModules,
|
||||
runningOnNotSupportedWindows,
|
||||
xtermLog
|
||||
} from '../../../store';
|
||||
import { UpdateFirmwareAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
||||
@@ -31,6 +34,10 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
getAgentVersionInfo$: Observable<VersionInformation>;
|
||||
hardwareModulesSubscription: Subscription;
|
||||
hardwareModules: HardwareModules;
|
||||
runningOnNotSupportedWindows$: Observable<boolean>;
|
||||
firmwareUpgradeAllowed$: Observable<boolean>;
|
||||
firmwareUpgradeFailed$: Observable<boolean>;
|
||||
firmwareUpgradeSuccess$: Observable<boolean>;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
|
||||
@@ -39,6 +46,10 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
||||
this.hardwareModules = data;
|
||||
});
|
||||
this.runningOnNotSupportedWindows$ = store.select(runningOnNotSupportedWindows);
|
||||
this.firmwareUpgradeAllowed$ = store.select(firmwareUpgradeAllowed);
|
||||
this.firmwareUpgradeFailed$ = store.select(firmwareUpgradeFailed);
|
||||
this.firmwareUpgradeSuccess$ = store.select(firmwareUpgradeSuccess);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<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"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
:host {
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[keyboardLayout]="keyboardLayout"
|
||||
[description]="description"
|
||||
[showDescription]="true"
|
||||
oncontextmenu="return false;"
|
||||
(keyClick)="keyClick.emit($event)"
|
||||
(keyHover)="keyHover.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 { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import {
|
||||
SvgKeyboardCaptureEvent,
|
||||
SvgKeyboardKeyClickEvent,
|
||||
SvgKeyHoverEvent
|
||||
} from '../../../models/svg-key-events';
|
||||
|
||||
type AnimationKeyboard =
|
||||
'init' |
|
||||
@@ -82,9 +87,9 @@ export class KeyboardSliderComponent implements OnChanges {
|
||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
layerAnimationState: AnimationKeyboard[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Keymap } from 'uhk-common';
|
||||
|
||||
@@ -14,6 +14,7 @@ import { KeymapActions } from '../../../store/actions';
|
||||
selector: 'keymap-add',
|
||||
templateUrl: './keymap-add.component.html',
|
||||
styleUrls: ['./keymap-add.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||
[halvesSplit]="keyboardSplit"
|
||||
[keyboardLayout]="keyboardLayout$ | async"
|
||||
[allowLayerDoubleTap]="allowLayerDoubleTap$ | async"
|
||||
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, HostListener, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Keymap } from 'uhk-common';
|
||||
@@ -14,9 +14,8 @@ import 'rxjs/add/operator/combineLatest';
|
||||
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
import { AppState, getKeyboardLayout } from '../../../store';
|
||||
import { allowLayerDoubleTap, AppState, getKeyboardLayout } from '../../../store';
|
||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { KeymapActions } from '../../../store/actions';
|
||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||
@@ -25,19 +24,19 @@ import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription
|
||||
selector: 'keymap-edit',
|
||||
templateUrl: './keymap-edit.component.html',
|
||||
styleUrls: ['./keymap-edit.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class KeymapEditComponent {
|
||||
|
||||
@ViewChild(SvgKeyboardWrapComponent) wrap: SvgKeyboardWrapComponent;
|
||||
|
||||
keyboardSplit: boolean;
|
||||
|
||||
deletable$: Observable<boolean>;
|
||||
keymap$: Observable<Keymap>;
|
||||
keyboardLayout$: Observable<KeyboardLayout>;
|
||||
allowLayerDoubleTap$: Observable<boolean>;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
route: ActivatedRoute) {
|
||||
@@ -52,6 +51,7 @@ export class KeymapEditComponent {
|
||||
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
||||
|
||||
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
||||
this.allowLayerDoubleTap$ = store.select(allowLayerDoubleTap);
|
||||
}
|
||||
|
||||
downloadKeymap() {
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
data-placement="bottom"
|
||||
(click)="duplicateKeymap()"
|
||||
></i>
|
||||
<i class="fa fa-download keymap__download pull-right"
|
||||
<!--i class="fa fa-download keymap__download pull-right"
|
||||
title="Download keymap"
|
||||
[html]="true"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
(click)="onDownloadIconClick()"></i>
|
||||
(click)="onDownloadIconClick()"></i-->
|
||||
</h1>
|
||||
</div>
|
||||
</uhk-header>
|
||||
|
||||
@@ -93,10 +93,17 @@
|
||||
<h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4>
|
||||
<h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4>
|
||||
<div class="btn-group">
|
||||
<button *ngFor="let buttonLabel of buttonLabels; let buttonIndex = index"
|
||||
class="btn btn-default"
|
||||
[class.btn-primary]="hasButton(buttonIndex)"
|
||||
(click)="setMouseClick(buttonIndex)">{{buttonLabel}}
|
||||
<button class="btn btn-default"
|
||||
[class.btn-primary]="hasButton(MouseButtons.Left)"
|
||||
(click)="setMouseClick(MouseButtons.Left)">Left
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
|
||||
import {
|
||||
MacroMouseSubAction,
|
||||
MouseButtons,
|
||||
MouseButtonMacroAction,
|
||||
MoveMouseMacroAction,
|
||||
ScrollMouseMacroAction,
|
||||
MacroMouseSubAction
|
||||
ScrollMouseMacroAction
|
||||
} from 'uhk-common';
|
||||
import { Tab } from '../../../../popover/tab';
|
||||
import { MacroBaseComponent } from '../macro-base.component';
|
||||
@@ -33,6 +34,7 @@ export class MacroMouseTabComponent extends MacroBaseComponent implements OnInit
|
||||
@ViewChild('tab') selectedTab: Tab;
|
||||
|
||||
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
|
||||
MouseButtons = MouseButtons;
|
||||
TabName = TabName;
|
||||
/* tslint:enable:variable-name */
|
||||
activeTab: TabName;
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
|
||||
</div>
|
||||
<div class="list-group-item macro-action-editor__container"
|
||||
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'">
|
||||
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'"
|
||||
[style.overflow]="overflow">
|
||||
<macro-action-editor
|
||||
*ngIf="editable || newItem"
|
||||
[macroAction]="macroAction"
|
||||
(cancel)="cancelEdit()"
|
||||
(save)="saveEditedAction($event)">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import '../../../../styles/variables';
|
||||
|
||||
:host {
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
display: block;
|
||||
|
||||
&.macro-item:first-of-type {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
KeyMacroAction,
|
||||
KeyModifiers,
|
||||
MacroAction,
|
||||
MouseButtons,
|
||||
MouseButtonMacroAction,
|
||||
MoveMouseMacroAction,
|
||||
ScrollMouseMacroAction,
|
||||
@@ -45,6 +46,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
||||
iconName: string;
|
||||
editing: boolean;
|
||||
newItem: boolean = false;
|
||||
overflow = 'hidden';
|
||||
|
||||
constructor(private mapper: MapperService) { }
|
||||
|
||||
@@ -53,6 +55,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
||||
if (!this.macroAction) {
|
||||
this.editing = true;
|
||||
this.newItem = true;
|
||||
this.overflow = 'visible';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +68,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
||||
saveEditedAction(editedAction: MacroAction): void {
|
||||
this.macroAction = editedAction;
|
||||
this.editing = false;
|
||||
this.overflow = 'hidden';
|
||||
this.updateView();
|
||||
this.save.emit(editedAction);
|
||||
}
|
||||
@@ -77,10 +81,12 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
||||
|
||||
this.editing = true;
|
||||
this.edit.emit();
|
||||
this.setOverflow('visible');
|
||||
}
|
||||
|
||||
cancelEdit(): void {
|
||||
this.editing = false;
|
||||
this.overflow = 'hidden';
|
||||
this.cancel.emit();
|
||||
}
|
||||
|
||||
@@ -171,7 +177,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
||||
|
||||
let needAnd: boolean;
|
||||
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;
|
||||
}
|
||||
if (Math.abs(typedAction.y) !== 0) {
|
||||
@@ -192,14 +198,21 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
||||
this.title = 'Release mouse button: ';
|
||||
}
|
||||
|
||||
const buttonLabels: string[] = ['Left', 'Middle', 'Right'];
|
||||
const selectedButtons: boolean[] = action.getMouseButtons();
|
||||
const selectedButtonLabels: string[] = [];
|
||||
selectedButtons.forEach((isSelected, idx) => {
|
||||
if (isSelected && buttonLabels[idx]) {
|
||||
selectedButtonLabels.push(buttonLabels[idx]);
|
||||
if (isSelected && MouseButtons[idx]) {
|
||||
selectedButtonLabels.push(MouseButtons[idx]);
|
||||
}
|
||||
});
|
||||
this.title += selectedButtonLabels.join(', ');
|
||||
}
|
||||
|
||||
private setOverflow(value: string): void {
|
||||
// tslint:disable: align
|
||||
setTimeout(() => {
|
||||
this.overflow = value;
|
||||
}, 600);
|
||||
// tslint:enable: align
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +47,19 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div [ngSwitch]="activeTab">
|
||||
<keypress-tab #tab *ngSwitchCase="tabName.Keypress" class="popover-content"
|
||||
[defaultKeyAction]="defaultKeyAction"
|
||||
<keypress-tab #tab *ngSwitchCase="tabName.Keypress" class="popover-content pr-10"
|
||||
[defaultKeyAction]="shadowKeyAction"
|
||||
[secondaryRoleEnabled]="true"
|
||||
[allowRemapOnAllKeymapWarning]="true"
|
||||
[remapInfo]="remapInfo"
|
||||
[showLayerSwitcherInSecondaryRoles]="currentLayer === 0"
|
||||
(validAction)="keyActionValid=$event"
|
||||
(keyActionChange)="keystrokeActionChange($event)"
|
||||
></keypress-tab>
|
||||
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
||||
[defaultKeyAction]="defaultKeyAction"
|
||||
[currentLayer]="currentLayer"
|
||||
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
||||
(validAction)="keyActionValid=$event"
|
||||
></layer-tab>
|
||||
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
||||
@@ -75,8 +80,44 @@
|
||||
></none-tab>
|
||||
</div>
|
||||
<div class="popover-action">
|
||||
<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" (click)="onRemapKey()"> Remap Key </button>
|
||||
<form class="form-inline d-inline-block popover-action-form">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
name="remapOnAllKeymap"
|
||||
[(ngModel)]="remapInfo.remapOnAllKeymap"> Remap on all keymaps
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label [ngClass]="{ disabled: disableRemapOnAllLayer }">
|
||||
<input type="checkbox"
|
||||
name="remapOnAllLayer"
|
||||
[(ngModel)]="remapInfo.remapOnAllLayer"
|
||||
[disabled]="disableRemapOnAllLayer"
|
||||
(ngModelChange)="remapInfoChange()"> Remap on all layers
|
||||
</label>
|
||||
</div>
|
||||
<div class="d-inline-block">
|
||||
<icon name="question-circle"
|
||||
data-toggle="tooltip"
|
||||
html="true"
|
||||
maxWidth="525"
|
||||
title="<ul class='no-indent text-left'>
|
||||
<li><strong>Default behavior</strong>: Remap the key on the the current layer of the current keymap.</li>
|
||||
<li><strong>Remap on all keymaps</strong>: Remap key on the current layer of all keymaps.</li>
|
||||
<li><strong>Remap on all layers</strong>: Remap key on all layers of the current keymap.</li>
|
||||
<li><strong>Remap on all keymaps + Remap on all layers</strong>: Remap key on all layers of all keymaps.</li>
|
||||
</ul>"
|
||||
data-placement="bottom"></icon>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<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-primary" [class.disabled]="!keyActionValid" type="button"
|
||||
(click)="onRemapKey()"> Remap key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover-overlay" [class.display]="visible" (click)="onOverlay()"></div>
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
background-color: #f7f7f7;
|
||||
border-top: 1px solid #ebebeb;
|
||||
border-radius: 0 0 5px 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.popover-title {
|
||||
@@ -100,6 +99,10 @@
|
||||
padding: 10px 24px;
|
||||
}
|
||||
|
||||
.pr-10 {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.popover-overlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
@@ -117,19 +120,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.select2-item {
|
||||
position: relative;
|
||||
font-size: 1.5rem;
|
||||
.popover-action-form {
|
||||
margin-top: 4px;
|
||||
|
||||
&.keymap-name--wrapper {
|
||||
padding-left: 50px;
|
||||
label {
|
||||
margin-right: 5px;
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #959595;
|
||||
}
|
||||
|
||||
.layout-segment-code {
|
||||
height: 2rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
margin-top: -1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
@@ -23,14 +25,17 @@ import {
|
||||
KeystrokeAction,
|
||||
MouseAction,
|
||||
PlayMacroAction,
|
||||
SecondaryRoleAction,
|
||||
SwitchKeymapAction,
|
||||
SwitchLayerAction
|
||||
} from 'uhk-common';
|
||||
|
||||
import { Tab } from './tab/tab';
|
||||
import { Tab } from './tab';
|
||||
|
||||
import { AppState } from '../../store';
|
||||
import { getKeymaps } from '../../store/reducers/user-configuration';
|
||||
import { KeyActionRemap } from '../../models/key-action-remap';
|
||||
import { RemapInfo } from '../../models/remap-info';
|
||||
|
||||
enum TabName {
|
||||
Keypress,
|
||||
@@ -45,6 +50,7 @@ enum TabName {
|
||||
selector: 'popover',
|
||||
templateUrl: './popover.component.html',
|
||||
styleUrls: ['./popover.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [
|
||||
trigger('popover', [
|
||||
state('closed', style({
|
||||
@@ -59,8 +65,8 @@ enum TabName {
|
||||
})),
|
||||
transition('opened => closed', [
|
||||
animate('200ms ease-out', keyframes([
|
||||
style({ transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0 }),
|
||||
style({ transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1 })
|
||||
style({transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0}),
|
||||
style({transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1})
|
||||
]))
|
||||
]),
|
||||
transition('closed => opened', [
|
||||
@@ -68,8 +74,8 @@ enum TabName {
|
||||
visibility: 'visible'
|
||||
}),
|
||||
animate('200ms ease-out', keyframes([
|
||||
style({ transform: 'translateY(30px)', opacity: 0, offset: 0 }),
|
||||
style({ transform: 'translateY(0)', opacity: 1, offset: 1 })
|
||||
style({transform: 'translateY(30px)', opacity: 0, offset: 0}),
|
||||
style({transform: 'translateY(0)', opacity: 1, offset: 1})
|
||||
]))
|
||||
])
|
||||
])
|
||||
@@ -82,9 +88,11 @@ export class PopoverComponent implements OnChanges {
|
||||
@Input() keyPosition: any;
|
||||
@Input() wrapPosition: any;
|
||||
@Input() visible: boolean;
|
||||
@Input() allowLayerDoubleTap: boolean;
|
||||
@Input() remapInfo: RemapInfo;
|
||||
|
||||
@Output() cancel = new EventEmitter<any>();
|
||||
@Output() remap = new EventEmitter<KeyAction>();
|
||||
@Output() remap = new EventEmitter<KeyActionRemap>();
|
||||
|
||||
@ViewChild('tab') selectedTab: Tab;
|
||||
@ViewChild('popover') popoverHost: ElementRef;
|
||||
@@ -98,10 +106,13 @@ export class PopoverComponent implements OnChanges {
|
||||
topPosition: number = 0;
|
||||
leftPosition: number = 0;
|
||||
animationState: string;
|
||||
shadowKeyAction: KeyAction;
|
||||
disableRemapOnAllLayer = false;
|
||||
|
||||
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
|
||||
|
||||
constructor(store: Store<AppState>) {
|
||||
constructor(private store: Store<AppState>,
|
||||
private cdRef: ChangeDetectorRef) {
|
||||
this.animationState = 'closed';
|
||||
this.keymaps$ = store.let(getKeymaps())
|
||||
.combineLatest(this.currentKeymap$)
|
||||
@@ -117,8 +128,10 @@ export class PopoverComponent implements OnChanges {
|
||||
|
||||
if (change['defaultKeyAction']) {
|
||||
let tab: TabName;
|
||||
this.disableRemapOnAllLayer = false;
|
||||
|
||||
if (this.defaultKeyAction instanceof KeystrokeAction) {
|
||||
this.keystrokeActionChange(this.defaultKeyAction);
|
||||
tab = TabName.Keypress;
|
||||
} else if (this.defaultKeyAction instanceof SwitchLayerAction) {
|
||||
tab = TabName.Layer;
|
||||
@@ -155,8 +168,11 @@ export class PopoverComponent implements OnChanges {
|
||||
onRemapKey(): void {
|
||||
if (this.keyActionValid) {
|
||||
try {
|
||||
const keyAction = this.selectedTab.toKeyAction();
|
||||
this.remap.emit(keyAction);
|
||||
this.remap.emit({
|
||||
remapOnAllKeymap: this.remapInfo.remapOnAllKeymap,
|
||||
remapOnAllLayer: this.remapInfo.remapOnAllLayer,
|
||||
action: this.selectedTab.toKeyAction()
|
||||
});
|
||||
} catch (e) {
|
||||
// TODO: show error dialog
|
||||
console.error(e);
|
||||
@@ -169,14 +185,49 @@ export class PopoverComponent implements OnChanges {
|
||||
this.cancel.emit();
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.control.enter', ['$event'])
|
||||
onKeyDown(event: KeyboardEvent) {
|
||||
if (this.visible) {
|
||||
this.onRemapKey();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
selectTab(tab: TabName): void {
|
||||
this.activeTab = tab;
|
||||
if (tab === TabName.Keypress) {
|
||||
this.keystrokeActionChange(this.defaultKeyAction as KeystrokeAction);
|
||||
}
|
||||
}
|
||||
|
||||
onOverlay() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private calculatePosition() {
|
||||
const offsetLeft: number = this.wrapPosition.left + 265; // 265 is a width of the side menu with a margin
|
||||
const popover: HTMLElement = this.popoverHost.nativeElement;
|
||||
|
||||
@@ -4,12 +4,23 @@
|
||||
<ng-template [ngIf]="keymapOptions.length > 0">
|
||||
<div>
|
||||
<b>Switch to keymap:</b>
|
||||
<select2
|
||||
[data]="keymapOptions"
|
||||
[value]="selectedKeymap?.abbreviation || -1"
|
||||
(valueChanged)="onChange($event)"
|
||||
[width]="'100%'"
|
||||
></select2>
|
||||
<ngx-select [items]="keymapOptions"
|
||||
[ngModel]="selectedKeymap?.abbreviation || -1"
|
||||
[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="empty" *ngIf="!selectedKeymap?.abbreviation">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
select2 {
|
||||
ngx-select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
||||
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
||||
|
||||
import { Tab } from '../tab';
|
||||
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||
|
||||
@Component({
|
||||
selector: 'keymap-tab',
|
||||
@@ -14,7 +14,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
||||
@Input() defaultKeyAction: KeyAction;
|
||||
@Input() keymaps: Keymap[];
|
||||
|
||||
keymapOptions: Array<Select2OptionData>;
|
||||
keymapOptions: Array<SelectOptionData>;
|
||||
selectedKeymap: Keymap;
|
||||
|
||||
constructor() {
|
||||
@@ -25,7 +25,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.keymaps) {
|
||||
this.keymapOptions = this.keymaps
|
||||
.map((keymap: Keymap): Select2OptionData => {
|
||||
.map((keymap: Keymap): SelectOptionData => {
|
||||
return {
|
||||
id: keymap.abbreviation,
|
||||
text: keymap.name
|
||||
@@ -40,12 +40,11 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
||||
this.validAction.emit(true);
|
||||
}
|
||||
|
||||
// TODO: change to the correct type when the wrapper has added it.
|
||||
onChange(event: any) {
|
||||
if (event.value === '-1') {
|
||||
onChange(event: string) {
|
||||
if (event === '-1') {
|
||||
this.selectedKeymap = undefined;
|
||||
} else {
|
||||
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event.value);
|
||||
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
<div class="scancode-options">
|
||||
<b class="setting-label">Scancode:</b>
|
||||
<select2
|
||||
[data]="scanCodeGroups"
|
||||
[value]="selectedScancodeOption.id"
|
||||
(valueChanged)="onScancodeChange($event)"
|
||||
[width]="200"
|
||||
[options]="options"
|
||||
></select2>
|
||||
<div class="scancode-container">
|
||||
<ngx-select [items]="scanCodeGroups"
|
||||
[ngModel]="selectedScancodeOption?.id"
|
||||
[autoActiveOnMouseEnter]="false"
|
||||
size="small"
|
||||
optionValueField="id"
|
||||
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"
|
||||
data-toggle="tooltip"
|
||||
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout. For example, on US keyboards next to Tab there is the Q key, but it's й on Russian keyboards, so in this case choose Q instead of й in Agent."
|
||||
html="true"
|
||||
maxWidth="330"
|
||||
title="<p>Looking for a non-US character? Just pick the character of the desired key according to the US layout.</p>
|
||||
<p>Let's say you're a German user and want to map the Ö character. You can see that on US keyboards this is the semicolon key, so choose semicolon in this dropdown.</p>"
|
||||
data-placement="bottom"></icon>
|
||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||
</div>
|
||||
@@ -18,38 +36,67 @@
|
||||
<div class="btn-toolbar modifiers">
|
||||
<div class="btn-group btn-group-sm modifiers__left">
|
||||
<button type="button" class="btn btn-default"
|
||||
*ngFor="let modifier of leftModifiers; let index = index"
|
||||
[class.btn-primary]="leftModifierSelects[index]"
|
||||
(click)="toggleModifier(false, index)"
|
||||
*ngFor="let modifier of leftModifiers; trackBy:modifiersTrackBy"
|
||||
[class.btn-primary]="modifier.checked"
|
||||
(click)="toggleModifier(modifier)"
|
||||
>
|
||||
{{modifier}}
|
||||
{{ modifier.text }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm modifiers__right">
|
||||
<button type="button" class="btn btn-default"
|
||||
*ngFor="let modifier of rightModifiers; let index = index"
|
||||
[class.btn-primary]="rightModifierSelects[index]"
|
||||
(click)="toggleModifier(true, index)"
|
||||
*ngFor="let modifier of rightModifiers; trackBy:modifiersTrackBy"
|
||||
[class.btn-primary]="modifier.checked"
|
||||
(click)="toggleModifier(modifier)"
|
||||
>
|
||||
{{modifier}}
|
||||
{{ modifier.text }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
||||
<b class="setting-label">Secondary role:</b>
|
||||
<select2 #secondaryRoleSelect
|
||||
[data]="secondaryRoleGroups"
|
||||
[value]="selectedSecondaryRoleIndex.toString()"
|
||||
(valueChanged)="onSecondaryRoleChange($event)"
|
||||
[width]="140"
|
||||
></select2>
|
||||
<div class="secondary-role-groups-container">
|
||||
<ngx-select [items]="secondaryRoleGroups"
|
||||
[ngModel]="selectedSecondaryRoleIndex.toString()"
|
||||
[autoActiveOnMouseEnter]="false"
|
||||
size="small"
|
||||
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"
|
||||
data-toggle="tooltip"
|
||||
title="The secondary role activates when another key gets pressed while holding this key."
|
||||
html="true"
|
||||
maxWidth="620"
|
||||
title="<p class='text-left'>The secondary role activates when another key gets pressed while holding this key.</p>
|
||||
<p class='text-left'>Let's say that the scancode is Escape and the secondary role is Mouse. Then:</p>
|
||||
<ul class='text-left'>
|
||||
<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>
|
||||
</ul>
|
||||
<p class='text-left'>The secondary role can be any layer or modifier.</p>"
|
||||
data-placement="bottom"></icon>
|
||||
</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">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
When a key is configured as layer switcher key, you can't assign other functions to it.
|
||||
|
||||
@@ -79,4 +79,21 @@
|
||||
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 { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
SimpleChanges
|
||||
} from '@angular/core';
|
||||
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
||||
|
||||
import { Tab } from '../tab';
|
||||
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({
|
||||
selector: 'keypress-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './keypress-tab.component.html',
|
||||
styleUrls: ['./keypress-tab.component.scss']
|
||||
})
|
||||
export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
@Input() defaultKeyAction: KeyAction;
|
||||
@Input() secondaryRoleEnabled: boolean;
|
||||
@Input() allowRemapOnAllKeymapWarning: boolean;
|
||||
@Input() remapInfo: RemapInfo;
|
||||
@Input() showLayerSwitcherInSecondaryRoles: boolean;
|
||||
|
||||
leftModifiers: string[];
|
||||
rightModifiers: string[];
|
||||
@Output() keyActionChange = new EventEmitter<KeystrokeAction>();
|
||||
|
||||
leftModifierSelects: boolean[];
|
||||
rightModifierSelects: boolean[];
|
||||
leftModifiers: KeyModifierModel[];
|
||||
rightModifiers: KeyModifierModel[];
|
||||
|
||||
scanCodeGroups: Array<Select2OptionData>;
|
||||
secondaryRoleGroups: Array<Select2OptionData>;
|
||||
options: Select2Options;
|
||||
scanCodeGroups: Array<SelectOptionData>;
|
||||
secondaryRoleGroups: Array<SelectOptionData> = [];
|
||||
|
||||
selectedScancodeOption: Select2OptionData;
|
||||
selectedScancodeOption: SelectOptionData;
|
||||
selectedSecondaryRoleIndex: number;
|
||||
warningVisible: boolean;
|
||||
|
||||
constructor(private mapper: MapperService) {
|
||||
constructor(private mapper: MapperService,
|
||||
private cdRef: ChangeDetectorRef) {
|
||||
super();
|
||||
this.leftModifiers = ['LShift', 'LCtrl', 'LSuper', 'LAlt'];
|
||||
this.rightModifiers = ['RShift', 'RCtrl', 'RSuper', 'RAlt'];
|
||||
this.leftModifiers = mapper.getLeftKeyModifiers();
|
||||
this.rightModifiers = mapper.getRightKeyModifiers();
|
||||
|
||||
this.scanCodeGroups = [{
|
||||
id: '0',
|
||||
text: 'None'
|
||||
}];
|
||||
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.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(changes: SimpleChanges) {
|
||||
if (changes.showLayerSwitcherInSecondaryRoles) {
|
||||
this.fillSecondaryRoles();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.fromKeyAction(this.defaultKeyAction);
|
||||
this.validAction.emit(this.keyActionValid());
|
||||
this.keyActionChanged(false);
|
||||
}
|
||||
|
||||
keyActionValid(keystrokeAction?: KeystrokeAction): boolean {
|
||||
@@ -68,16 +83,16 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
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) {
|
||||
this.selectedScancodeOption = this.findScancodeOptionByScancode(event.code, KeystrokeType.basic);
|
||||
} else {
|
||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||
}
|
||||
|
||||
this.leftModifierSelects = event.left;
|
||||
this.rightModifierSelects = event.right;
|
||||
this.validAction.emit(this.keyActionValid());
|
||||
this.leftModifiers = event.left;
|
||||
this.rightModifiers = event.right;
|
||||
this.keyActionChanged();
|
||||
}
|
||||
|
||||
fromKeyAction(keyAction: KeyAction): boolean {
|
||||
@@ -88,16 +103,12 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
// Restore selectedScancodeOption
|
||||
this.selectedScancodeOption = this.findScancodeOptionByScancode(keystrokeAction.scancode || 0, keystrokeAction.type);
|
||||
|
||||
const leftModifiersLength: number = this.leftModifiers.length;
|
||||
|
||||
// Restore modifiers
|
||||
for (let i = 0; i < leftModifiersLength; ++i) {
|
||||
this.leftModifierSelects[this.mapper.modifierMapper(i)] = ((keystrokeAction.modifierMask >> i) & 1) === 1;
|
||||
for (const modifier of this.leftModifiers) {
|
||||
modifier.checked = (keystrokeAction.modifierMask & modifier.value) > 0;
|
||||
}
|
||||
|
||||
for (let i = leftModifiersLength; i < leftModifiersLength + this.rightModifierSelects.length; ++i) {
|
||||
const index: number = this.mapper.modifierMapper(i) - leftModifiersLength;
|
||||
this.rightModifierSelects[index] = ((keystrokeAction.modifierMask >> i) & 1) === 1;
|
||||
for (const modifier of this.rightModifiers) {
|
||||
modifier.checked = (keystrokeAction.modifierMask & modifier.value) > 0;
|
||||
}
|
||||
|
||||
// Restore secondaryRoleAction
|
||||
@@ -119,11 +130,7 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
} else {
|
||||
keystrokeAction.type = KeystrokeType[scTypePair[1]];
|
||||
}
|
||||
keystrokeAction.modifierMask = 0;
|
||||
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.modifierMask = mapLeftRigthModifierToKeyActionModifier(this.leftModifiers, this.rightModifiers);
|
||||
|
||||
keystrokeAction.secondaryRoleAction = this.selectedSecondaryRoleIndex === -1
|
||||
? undefined
|
||||
@@ -134,50 +141,37 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
scanCodeTemplateResult: Select2TemplateFunction = (state: Select2OptionData): JQuery | string => {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
toggleModifier(modifier: KeyModifierModel): void {
|
||||
modifier.checked = !modifier.checked;
|
||||
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>');
|
||||
}
|
||||
onSecondaryRoleChange(id: string) {
|
||||
this.selectedSecondaryRoleIndex = +id;
|
||||
this.keyActionChanged();
|
||||
}
|
||||
|
||||
toggleModifier(right: boolean, index: number) {
|
||||
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
|
||||
modifierSelects[index] = !modifierSelects[index];
|
||||
|
||||
this.validAction.emit(this.keyActionValid());
|
||||
}
|
||||
|
||||
onSecondaryRoleChange(event: { value: 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
|
||||
onScancodeChange(id: string) {
|
||||
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
||||
|
||||
this.validAction.emit(this.keyActionValid());
|
||||
this.keyActionChanged();
|
||||
}
|
||||
|
||||
private findScancodeOptionBy(predicate: (option: Select2OptionData) => boolean): Select2OptionData {
|
||||
let selectedOption: Select2OptionData;
|
||||
modifiersTrackBy(index: number, modifier: KeyModifierModel): string {
|
||||
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) {
|
||||
const scanCodeGroup = scanCodeGroups.shift();
|
||||
if (predicate(scanCodeGroup)) {
|
||||
@@ -192,14 +186,14 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
return selectedOption;
|
||||
}
|
||||
|
||||
private findScancodeOptionById(id: string): Select2OptionData {
|
||||
private findScancodeOptionById(id: string): SelectOptionData {
|
||||
return this.findScancodeOptionBy(option => option.id === id);
|
||||
}
|
||||
|
||||
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): Select2OptionData {
|
||||
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): SelectOptionData {
|
||||
const typeToFind: string =
|
||||
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
||||
return this.findScancodeOptionBy((option: Select2OptionData) => {
|
||||
return this.findScancodeOptionBy((option: SelectOptionData) => {
|
||||
const additional = option.additional;
|
||||
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
||||
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 type: string;
|
||||
if (option.additional) {
|
||||
@@ -227,4 +225,27 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
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,6 @@
|
||||
<ng-template [ngIf]="!isNotBase">
|
||||
<div>
|
||||
<div>
|
||||
<select (change)="toggleChanged($event.target.value)">
|
||||
<option *ngFor="let item of toggleData" [value]="item.id" [selected]="toggle === item.id">
|
||||
{{ item.text }}
|
||||
@@ -11,9 +13,19 @@
|
||||
</option>
|
||||
</select>
|
||||
<span [ngSwitch]="toggle">
|
||||
<ng-template [ngSwitchCase]="true">layer by tapping this key.</ng-template>
|
||||
<ng-template [ngSwitchCase]="'toggle'">layer by tapping this key.</ng-template>
|
||||
<ng-template ngSwitchDefault>layer by holding this key.</ng-template>
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="toggle === 'active' && allowLayerDoubleTap">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
[(ngModel)]="lockLayerWhenDoubleTapping"> Lock layer when double tapping this key.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="isNotBase">
|
||||
<span> Layer switching is only possible from the base layer. </span>
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { KeyAction, LayerName, SwitchLayerAction } from 'uhk-common';
|
||||
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { KeyAction, LayerName, SwitchLayerAction, SwitchLayerMode } from 'uhk-common';
|
||||
|
||||
import { Tab } from '../tab';
|
||||
|
||||
export type toggleType = 'active' | 'toggle';
|
||||
|
||||
@Component({
|
||||
selector: 'layer-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './layer-tab.component.html',
|
||||
styleUrls: ['./layer-tab.component.scss']
|
||||
})
|
||||
export class LayerTabComponent extends Tab implements OnChanges {
|
||||
@Input() defaultKeyAction: KeyAction;
|
||||
@Input() currentLayer: number;
|
||||
@Input() allowLayerDoubleTap: boolean;
|
||||
|
||||
@HostBinding('class.no-base') isNotBase: boolean;
|
||||
|
||||
toggleData: { id: boolean, text: string }[] = [
|
||||
toggleData: { id: toggleType, text: string }[] = [
|
||||
{
|
||||
id: false,
|
||||
id: 'active',
|
||||
text: 'Activate'
|
||||
},
|
||||
{
|
||||
id: true,
|
||||
id: 'toggle',
|
||||
text: 'Toggle'
|
||||
}
|
||||
];
|
||||
@@ -40,12 +44,13 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
||||
}
|
||||
];
|
||||
|
||||
toggle: boolean;
|
||||
toggle: toggleType;
|
||||
layer: LayerName;
|
||||
lockLayerWhenDoubleTapping: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.toggle = false;
|
||||
this.toggle = 'active';
|
||||
this.layer = LayerName.mod;
|
||||
}
|
||||
|
||||
@@ -71,14 +76,39 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
||||
}
|
||||
|
||||
const switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
|
||||
this.toggle = switchLayerAction.isLayerToggleable;
|
||||
switch (switchLayerAction.switchLayerMode) {
|
||||
case SwitchLayerMode.holdAndDoubleTapToggle: {
|
||||
this.toggle = 'active';
|
||||
this.lockLayerWhenDoubleTapping = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SwitchLayerMode.hold: {
|
||||
this.toggle = 'active';
|
||||
this.lockLayerWhenDoubleTapping = false;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
this.toggle = 'toggle';
|
||||
this.lockLayerWhenDoubleTapping = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.layer = switchLayerAction.layer;
|
||||
return true;
|
||||
}
|
||||
|
||||
toKeyAction(): SwitchLayerAction {
|
||||
const keyAction = new SwitchLayerAction();
|
||||
keyAction.isLayerToggleable = this.toggle;
|
||||
if (this.toggle === 'toggle') {
|
||||
keyAction.switchLayerMode = SwitchLayerMode.toggle;
|
||||
} else if (!this.allowLayerDoubleTap || this.lockLayerWhenDoubleTapping) {
|
||||
keyAction.switchLayerMode = SwitchLayerMode.holdAndDoubleTapToggle;
|
||||
} else {
|
||||
keyAction.switchLayerMode = SwitchLayerMode.hold;
|
||||
}
|
||||
|
||||
keyAction.layer = this.layer;
|
||||
if (!this.keyActionValid()) {
|
||||
throw new Error('KeyAction is invalid!');
|
||||
@@ -86,8 +116,8 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
||||
return keyAction;
|
||||
}
|
||||
|
||||
toggleChanged(value: string) {
|
||||
this.toggle = value === 'true';
|
||||
toggleChanged(value: toggleType) {
|
||||
this.toggle = value;
|
||||
}
|
||||
|
||||
layerChanged(value: number) {
|
||||
|
||||
@@ -5,7 +5,23 @@
|
||||
<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>
|
||||
<div class="macro-selector">
|
||||
<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 class="macro-action-container">
|
||||
<div class="list-group">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
select2 {
|
||||
ngx-select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
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 { Subscription } from 'rxjs/Subscription';
|
||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
||||
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
||||
|
||||
import { Tab } from '../tab';
|
||||
|
||||
import { AppState } from '../../../../store/index';
|
||||
import { AppState } from '../../../../store';
|
||||
import { getMacros } from '../../../../store/reducers/user-configuration';
|
||||
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||
|
||||
@Component({
|
||||
selector: 'macro-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './macro-tab.component.html',
|
||||
styleUrls: ['./macro-tab.component.scss']
|
||||
})
|
||||
@@ -18,7 +19,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
||||
@Input() defaultKeyAction: KeyAction;
|
||||
|
||||
macros: Macro[];
|
||||
macroOptions: Array<Select2OptionData>;
|
||||
macroOptions: Array<SelectOptionData>;
|
||||
selectedMacroIndex: number;
|
||||
private subscription: Subscription;
|
||||
|
||||
@@ -31,7 +32,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.macroOptions = this.macros.map(function (macro: Macro, index: number): Select2OptionData {
|
||||
this.macroOptions = this.macros.map(function (macro: Macro, index: number): SelectOptionData {
|
||||
return {
|
||||
id: index.toString(),
|
||||
text: macro.name
|
||||
@@ -44,9 +45,8 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
||||
this.validAction.emit(true);
|
||||
}
|
||||
|
||||
// TODO: change to the correct type when the wrapper has added it.
|
||||
onChange(event: any) {
|
||||
this.selectedMacroIndex = +event.value;
|
||||
onChange(id: string) {
|
||||
this.selectedMacroIndex = +id;
|
||||
}
|
||||
|
||||
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 { Tab } from '../tab';
|
||||
|
||||
@Component({
|
||||
selector: 'mouse-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './mouse-tab.component.html',
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'none-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './none-tab.component.html',
|
||||
styleUrls: ['./none-tab.component.scss']
|
||||
})
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { EventEmitter, Output } from '@angular/core';
|
||||
import { KeyAction } from 'uhk-common';
|
||||
|
||||
import { RemapInfo } from '../../../models/remap-info';
|
||||
|
||||
export abstract class Tab {
|
||||
@Output() validAction = new EventEmitter<boolean>();
|
||||
|
||||
abstract keyActionValid(): boolean;
|
||||
abstract fromKeyAction(keyAction: KeyAction): boolean;
|
||||
abstract toKeyAction(): KeyAction;
|
||||
remapInfoChanged(remapInfo: RemapInfo): void {}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Component, EventEmitter, HostListener, Output, Input } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output } from '@angular/core';
|
||||
import { CaptureService } from '../../../../services/capture.service';
|
||||
import { KeyModifierModel } from '../../../../models/key-modifier-model';
|
||||
|
||||
@Component({
|
||||
selector: 'capture-keystroke-button',
|
||||
templateUrl: './capture-keystroke-button.component.html',
|
||||
styleUrls: ['./capture-keystroke-button.component.scss']
|
||||
styleUrls: ['./capture-keystroke-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CaptureKeystrokeButtonComponent {
|
||||
@Input() isLink = false;
|
||||
@@ -67,8 +69,8 @@ export class CaptureKeystrokeButtonComponent {
|
||||
|
||||
private saveScanCode(code?: number) {
|
||||
this.record = false;
|
||||
const left: boolean[] = this.captureService.getModifiers(true);
|
||||
const right: boolean[] = this.captureService.getModifiers(false);
|
||||
const left: KeyModifierModel[] = this.captureService.getModifiers(true);
|
||||
const right: KeyModifierModel[] = this.captureService.getModifiers(false);
|
||||
|
||||
this.capture.emit({
|
||||
code,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'icon',
|
||||
templateUrl: './icon.component.html',
|
||||
styleUrls: ['./icon.component.scss']
|
||||
styleUrls: ['./icon.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class IconComponent implements OnInit {
|
||||
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
</p>
|
||||
|
||||
<div *ngIf="state.showWhatWillThisDoContent">
|
||||
Agent uses the following script to set up permissions. You can run it manually as root, then
|
||||
<a class="link-inline"
|
||||
(click)="retry()">retry</a>.
|
||||
If you want to set up permissions manually:
|
||||
<ol>
|
||||
<li>Open a terminal.</li>
|
||||
<li>Run <code>su</code> to become root.</li>
|
||||
<li>Paste the following script, and <a class="link-inline" (click)="retry()">retry</a>.</li>
|
||||
</ol>
|
||||
|
||||
<div class="copy-container">
|
||||
<span class="fa fa-2x fa-copy"
|
||||
ngxClipboard
|
||||
|
||||
@@ -131,16 +131,22 @@
|
||||
</li>
|
||||
<li class="sidebar__level-0--item" [routerLinkActive]="['active']">
|
||||
<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"
|
||||
(click)="toggleHide($event, 'agent')"></i>
|
||||
</div>
|
||||
<ul [@toggler]="animation['agent']">
|
||||
<li class="sidebar__level-2--item">
|
||||
<!--li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/settings']"
|
||||
[class.disabled]="state.updatingFirmware">Settings</a>
|
||||
</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 class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<label *ngIf="label">
|
||||
<span>{{label}}</span>
|
||||
<span [innerHtml]="label"></span>
|
||||
<icon name="question-circle"
|
||||
data-toggle="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;
|
||||
@@ -1,5 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
|
||||
<svg:g svg-module *ngFor="let module of modules; let i = index"
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
[attr.viewBox]="viewBox"
|
||||
height="100%"
|
||||
width="100%">
|
||||
<svg:g svg-module
|
||||
*ngFor="let module of modules; let i = index"
|
||||
[coverages]="module.coverages"
|
||||
[keyboardKeys]="module.keyboardKeys"
|
||||
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
||||
@@ -9,10 +13,13 @@
|
||||
[selectedKey]="selectedKey"
|
||||
[@split]="moduleAnimationStates[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)"
|
||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
||||
/>
|
||||
(capture)="onCapture(i, $event)" />
|
||||
|
||||
<svg:path [@fadeSeparator]="separatorAnimation"
|
||||
[attr.d]="separator.d"
|
||||
[attr.style]="separatorStyle" />
|
||||
</svg>
|
||||
<editable-text *ngIf="showDescription"
|
||||
[ngModel]="description"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,10 +1,18 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { animate, state, trigger, style, transition } from '@angular/animations';
|
||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||
import { Module } from 'uhk-common';
|
||||
|
||||
import { SvgModule } from '../module';
|
||||
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { SvgSeparator } from '../separator';
|
||||
import {
|
||||
SvgKeyHoverEvent,
|
||||
SvgKeyboardKeyClickEvent,
|
||||
SvgKeyboardCaptureEvent,
|
||||
SvgModuleKeyClickEvent
|
||||
} from '../../../models/svg-key-events';
|
||||
|
||||
@Component({
|
||||
selector: 'svg-keyboard',
|
||||
@@ -20,6 +28,16 @@ import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
transform: 'translate(3%, 15%) rotate(-4deg) scale(0.92, 0.92)'
|
||||
})),
|
||||
transition('* <=> *', animate(500))
|
||||
]),
|
||||
trigger('fadeSeparator', [
|
||||
state('visible', style({
|
||||
opacity: 1
|
||||
})),
|
||||
state('invisible', style({
|
||||
opacity: 0
|
||||
})),
|
||||
transition('visible => invisible', animate(500)),
|
||||
transition('invisible => visible', animate(1500))
|
||||
])
|
||||
]
|
||||
})
|
||||
@@ -33,16 +51,20 @@ export class SvgKeyboardComponent implements OnInit {
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Input() showDescription = false;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
modules: SvgModule[];
|
||||
viewBox: string;
|
||||
moduleAnimationStates: string[];
|
||||
separator: SvgSeparator;
|
||||
separatorStyle: SafeStyle;
|
||||
separatorAnimation = 'visible';
|
||||
|
||||
constructor(private svgModuleProvider: SvgModuleProviderService) {
|
||||
constructor(private svgModuleProvider: SvgModuleProviderService,
|
||||
private sanitizer: DomSanitizer) {
|
||||
this.modules = [];
|
||||
this.viewBox = '-520 582 1100 470';
|
||||
this.halvesSplit = false;
|
||||
@@ -63,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({
|
||||
moduleId,
|
||||
keyId,
|
||||
keyTarget
|
||||
...event,
|
||||
moduleId
|
||||
});
|
||||
}
|
||||
|
||||
onCapture(moduleId: number, keyId: number, captured: { code: number, left: boolean[], right: boolean[] }): void {
|
||||
onCapture(moduleId: number, event: SvgKeyboardCaptureEvent): void {
|
||||
this.capture.emit({
|
||||
moduleId,
|
||||
keyId,
|
||||
captured
|
||||
...event,
|
||||
moduleId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,12 +111,16 @@ export class SvgKeyboardComponent implements OnInit {
|
||||
private updateModuleAnimationStates() {
|
||||
if (this.halvesSplit) {
|
||||
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
||||
this.separatorAnimation = 'invisible';
|
||||
} else {
|
||||
this.moduleAnimationStates = [];
|
||||
this.separatorAnimation = 'visible';
|
||||
}
|
||||
}
|
||||
|
||||
private setModules() {
|
||||
this.modules = this.svgModuleProvider.getSvgModules(this.keyboardLayout);
|
||||
this.separator = this.svgModuleProvider.getSvgSeparator();
|
||||
this.separatorStyle = this.sanitizer.bypassSecurityTrustStyle(this.separator.style);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,4 @@ export { SvgSingleIconKeyComponent } from './svg-single-icon-key';
|
||||
export { SvgSwitchKeymapKeyComponent } from './svg-switch-keymap-key';
|
||||
export { SvgTextIconKeyComponent } from './svg-text-icon-key';
|
||||
export { SvgTwoLineTextKeyComponent } from './svg-two-line-text-key';
|
||||
export { SvgSecondaryRoleComponent } from './svg-secondary-role';
|
||||
|
||||
@@ -11,3 +11,10 @@
|
||||
[attr.font-size]="11">
|
||||
<tspan [attr.x]="spanX">{{ text }}</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: 499 B |
@@ -1,15 +1,19 @@
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'g[svg-icon-text-key]',
|
||||
templateUrl: './svg-icon-text-key.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SvgIconTextKeyComponent implements OnInit {
|
||||
export class SvgIconTextKeyComponent implements OnChanges {
|
||||
@Input() width: number;
|
||||
@Input() height: number;
|
||||
@Input() icon: string;
|
||||
@Input() text: string;
|
||||
@Input() secondaryText: string;
|
||||
|
||||
useWidth: number;
|
||||
useHeight: number;
|
||||
@@ -17,16 +21,33 @@ export class SvgIconTextKeyComponent implements OnInit {
|
||||
useY: number;
|
||||
textY: number;
|
||||
spanX: number;
|
||||
secondaryTextY: number;
|
||||
secondaryHeight: number;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.calculatePositions();
|
||||
}
|
||||
|
||||
private calculatePositions(): void {
|
||||
let textYModifier = 0;
|
||||
let secondaryYModifier = 0;
|
||||
|
||||
if (this.secondaryText && isRectangleAsSecondaryRoleKey(this.width, this.height)) {
|
||||
textYModifier = this.height / 5;
|
||||
secondaryYModifier = 5;
|
||||
}
|
||||
|
||||
this.useWidth = this.width / 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;
|
||||
this.textY = (this.width > 2 * this.height) ? this.height / 2 : this.height * 0.6;
|
||||
this.spanX = (this.width > 2 * this.height) ? this.width * 0.6 : this.width / 2;
|
||||
|
||||
this.secondaryHeight = this.height / 4;
|
||||
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,34 +25,40 @@
|
||||
<svg:g svg-keystroke-key *ngSwitchCase="enumLabelTypes.KeystrokeKey"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[keystrokeAction]="labelSource">
|
||||
[keystrokeAction]="labelSource"
|
||||
[secondaryText]="secondaryText">
|
||||
</svg:g>
|
||||
<svg:g svg-one-line-text-key *ngSwitchCase="enumLabelTypes.OneLineText"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[text]="labelSource">
|
||||
[text]="labelSource"
|
||||
[secondaryText]="secondaryText">
|
||||
</svg:g>
|
||||
<svg:g svg-two-line-text-key *ngSwitchCase="enumLabelTypes.TwoLineText"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[texts]="labelSource">
|
||||
[texts]="labelSource"
|
||||
[secondaryText]="secondaryText">
|
||||
</svg:g>
|
||||
<svg:g svg-text-icon-key *ngSwitchCase="enumLabelTypes.TextIcon"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[text]="labelSource.text"
|
||||
[icon]="labelSource.icon">
|
||||
[icon]="labelSource.icon"
|
||||
[secondaryText]="secondaryText">
|
||||
</svg:g>
|
||||
<svg:g svg-icon-text-key *ngSwitchCase="enumLabelTypes.IconText"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[icon]="labelSource.icon"
|
||||
[text]="labelSource.text">
|
||||
[text]="labelSource.text"
|
||||
[secondaryText]="secondaryText">
|
||||
</svg:g>
|
||||
<svg:g svg-single-icon-key *ngSwitchCase="enumLabelTypes.SingleIcon"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[icon]="labelSource">
|
||||
[icon]="labelSource"
|
||||
[secondaryText]="secondaryText">
|
||||
</svg:g>
|
||||
<svg:g svg-switch-keymap-key *ngSwitchCase="enumLabelTypes.SwitchKeymap"
|
||||
[height]="height"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.7 KiB |