71 Commits

Author SHA1 Message Date
László Monda
3e621a2818 Bump version to 1.2.11 and update package.json 2018-10-03 05:43:41 +02:00
Róbert Kiss
247ec4c1b2 feat: add backspace and caps lock icons (#803)
Summary:
- svg sprite generate with xmlns:xlink="http://www.w3.org/1999/xlink" namespace
- uhk-icon-agent-icon css class renamed to uhk-icon-pure-agent-icon because
  it collided with agent icon sprites
- added backspace and caps lock icons
2018-10-01 02:06:23 +02:00
Róbert Kiss
edcff069fd fix: write agent version into the log when upgrade firmware (#802) 2018-09-30 22:48:42 +02:00
László Monda
8afdeac306 Fix right and middle mouse click macro actions which were exchanged. Fixes #794. 2018-09-27 00:35:13 +02:00
László Monda
425f861451 Add issue template regarding Karabiner Elements. 2018-09-26 00:05:14 +02:00
László Monda
5a843ed02c Include Agent version to the firmware update log. 2018-09-25 15:48:27 +02:00
László Monda
f3bd83af03 Bump version to 1.2.10, update changelog and package.json 2018-09-24 04:21:24 +02:00
László Monda
0b3fca63b7 Add History Back and History Forward scancodes. Resolves #776. 2018-09-24 03:56:29 +02:00
László Monda
cbd4460df0 Map Caps Lock without Ctrl on default keymaps. 2018-09-24 03:03:05 +02:00
László Monda
b941bd9a75 Set the decelerated scroll speed of the default configuration to 10. 2018-09-24 02:32:25 +02:00
László Monda
439b84affc Save decelerated mouse scroll speed properly instead of using the accelerated scroll speed by accident. Fixes https://github.com/UltimateHackingKeyboard/firmware/issues/178 2018-09-24 02:21:00 +02:00
László Monda
66d5302e6f Add play/pause icon. Improves #591. 2018-09-21 01:43:15 +02:00
László Monda
9e2e2b9c5c Add more exact instructions to the permission setup screen. Fixes #781. 2018-09-21 00:56:50 +02:00
Róbert Kiss
aa243ac7b0 feat: allow Layer Switcher secondary roles only on base layer (#790) 2018-09-21 00:20:46 +02:00
László Monda
eb421e0681 Revert "Remove unused mustache file."
This reverts commit 63aae8f578.
2018-09-20 02:31:06 +02:00
László Monda
63aae8f578 Remove unused mustache file. 2018-09-20 02:08:58 +02:00
László Monda
e577454a31 Make all SVGs non-executable. 2018-09-20 02:05:51 +02:00
László Monda
e802bb0052 Remove unused SVGs. 2018-09-20 02:05:15 +02:00
László Monda
b98e5df20a Add backspace and caps lock icons. Improves #83. 2018-09-20 01:42:05 +02:00
Róbert Kiss
7332105edb feat: remap on all keymap warning (#789) 2018-09-19 23:02:32 +02:00
László Monda
6a4feaf18d Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-09-16 21:30:58 +02:00
László Monda
ee637d7958 Rename Scroll Lock to ScrLk and Num Lock to NumLk on keys. Improves #83. 2018-09-16 21:28:54 +02:00
Róbert Kiss
8d161ce8ff chore: upgrade Node.js => 8.12.0 (#783)
it's fix appveyor build
2018-09-16 21:17:40 +02:00
László Monda
8010bd8195 In the scancode select2 display "Print Screen SysRq" and add SysRq above PrtScn when rendering the key. Fixes #784. 2018-09-16 21:09:54 +02:00
Jan Christoph Ebersbach
059f1d5505 Fix swapped direction title for mouse movements (#778) 2018-09-16 01:16:11 +02:00
László Monda
123cab5724 Bump Agent version to 1.2.9 2018-09-13 20:42:07 +02:00
Róbert Kiss
c16365a0e5 fix: Alt+Tab triggers "Remap on all layers" (#773) 2018-09-11 22:29:30 +02:00
Róbert Kiss
a21d278c0c fix: modifier button layout (#772) 2018-09-11 21:56:30 +02:00
Róbert Kiss
0466916be1 fix: key modifier reordering (#771) 2018-09-11 00:29:22 +02:00
László Monda
9a845d8f6a Fix eeprom.js 2018-09-10 19:29:58 +02:00
Róbert Kiss
9ae1673499 feat: secondary role visualisation (#767)
* feat: secondary role visualisation

* fix: recalculate the text position of the secondary role if changes

* fix: recalculate the text position of SvgKeystrokeKeyComponent

* fix: recalculate the text position when changes anything

* fix: two line text key position calculation

* fix: fix space positioning

* fix: visualize second character of complex key

* style: remove extra line
2018-09-08 12:09:16 +02:00
László Monda
2d5a5e7aef Exchange Alt and Super modifiers in the key action popover. 2018-09-03 03:58:03 +02:00
Róbert Kiss
3e4d439852 feat: display OS-specific modifiers (#764)
* chore: git ignore "out-tsc/" folder in uhk-web package

* feat: add OperationSystem calculation to the app reducer

* feat: create os specific key modifier

* feat: Os specific texts

* revert: KeyModifierValues and getKeyModifiers selector

* refactor: remove unnecessary return

* refactor: rename OperationSystem => OperatingSystem
2018-09-03 00:21:55 +02:00
László Monda
aba0b09109 Bump Agent to version 1.2.8 and update changelog. 2018-08-26 23:42:31 +02:00
László Monda
af608ee17d Downgrade to firmware 8.2.5 2018-08-26 23:35:54 +02:00
Róbert Kiss
df817e86d6 fix: use OnPush change detection on the keymap edit and add pages (#760)
* fix: use OnPush change detection on the keymap edit and add pages

* fix: font-size
2018-08-26 22:08:29 +02:00
Róbert Kiss
a7d3b62512 chore: upgrade many dependencies and restructure tsconfigs (#757)
* chore: upgrade many dependencies and restructure tsconfigs

Summary:
- upgrade many dependencies
- remove dev dependencies from test-serializer and uhk-common
- created root tsconfig.json and test-serializer and uhk-common
  tsconfigs extends it
- fixed e2e test

* chore: upgrade more dependencies
2018-08-22 00:34:16 +02:00
Róbert Kiss
b41f14192a chore: upgrade stylelint => 9.5.0 (#756)
it is fix "Unknown CSS word" error in IntelliJ
2018-08-21 20:43:18 +02:00
Róbert Kiss
475ec71983 fix: uncheck 'Remap on all keyboard' and 'Remap on all layer' checkbox by default (#754)
* fix: uncheck 'Remap on all keyboard' and 'Remap on all layer' checkbox by default

* fix: popover checkbox checked state
2018-08-21 20:41:54 +02:00
László Monda
b5cff2fa93 Fix the padding of the secondary role tooltip. 2018-08-14 04:10:30 +02:00
László Monda
80e8c014ec Bind left and right Shift on the Mouse layer of all keymaps in the default configuration. 2018-08-08 17:00:32 +02:00
László Monda
67d42f666c Simplify the output of get-debug-info.js 2018-08-04 02:29:39 +02:00
László Monda
fa32f95438 Add note to the LED brightness page saying that current UHK versions are not backlit. Also include spacing-bootstrap-3 2018-07-30 00:05:04 +02:00
László Monda
06878dd56a Remove the redundant scrollbar from the LED brightness page. 2018-07-29 00:28:34 +02:00
Róbert Kiss
374f6a3e6e chore: downgrade electron builder => 20.14.7 (#741)
* chore: downgrade electron builder => 20.14.7

* chore: downgrade electron builder => 20.8.1
2018-07-26 23:30:20 +02:00
László Monda
0b9c804a3d Bump Agent version to 1.2.7 and update changelog. 2018-07-26 22:20:28 +02:00
László Monda
365a459d61 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-07-26 22:16:33 +02:00
László Monda
cec891a2c0 Change the shortcut which enables the USB stack test code, so that it can be triggered with the default Mac US keymap. 2018-07-26 22:15:21 +02:00
Róbert Kiss
8eb8aa3032 chore: upgrade electron builder => 20.26.0 (#740) 2018-07-26 22:06:32 +02:00
László Monda
38184e7968 Bump Agent version to 1.2.6 and firmware version to 8.4.0. Update changelog. 2018-07-26 06:04:47 +02:00
Róbert Kiss
f6092ea195 fix: popover components use OnPush change detection (#727)
* fix: popover components use OnPush change detection

* fix: select2 selection bug

* chore: upgrade @ert78gb/ngx-select-ex => 3.7.0
2018-07-26 05:37:58 +02:00
László Monda
ac7d66e338 Add keyboard shortcut for enabling the USB stack test mode of the firmware. Resolves #735. 2018-07-26 05:34:48 +02:00
László Monda
b82a1da92a Remove usbVariables in favor of variableNameToId. 2018-07-25 11:44:57 +02:00
László Monda
b8859f7b64 Make {get,set}-variable.js expect variable name and use async/await. 2018-07-25 02:08:36 +02:00
László Monda
a04fa67446 Comment out the Settings menu until auto update is implemented. 2018-07-24 16:40:34 +02:00
Eric Tang
ac89aff018 Add scripts for getting/setting firmware variables (#734) 2018-07-22 16:07:21 +02:00
Róbert Kiss
e7cf8dc966 fix: no scroll when macro tab selected on popover (#731) 2018-07-17 22:08:45 +02:00
Róbert Kiss
d0102f5bdb feat: add help page (#728)
* feat: add help page

* feat: add help page content
2018-07-16 23:05:41 +02:00
László Monda
eb0daadf98 Overwrite the blhost binary with a statically compiled version that doesn't use special instructions.
See https://github.com/UltimateHackingKeyboard/agent/issues/681#issuecomment-403057795
2018-07-14 14:43:11 +02:00
László Monda
49d6ca173d Remove the blhost-old x86-64 Linux blhost binary. 2018-07-14 14:41:53 +02:00
László Monda
a3eb6a6b7e Fix typo. 2018-07-11 14:48:28 +02:00
László Monda
144ed57b20 Fix epic typo. 2018-07-08 14:44:41 +02:00
Róbert Kiss
6086ddabf0 build: build only AppImage for Linux (#719)
upgrade:
 - electron-builder => 20.15.0
 - electron-log => 2.2.16
 - electron-rebuild => 1.8.1
2018-07-08 14:34:38 +02:00
Róbert Kiss
84f378a276 feat: add save to keyboard and remap shortcut keys (#712)
* feat: add save to keyboard and remap shortcut keys

* feat: Alt and Shift keys set the remapOnAllKeymap and remapOnAllLayer

* fix: control + enter trigger remap keymap
2018-07-08 14:31:48 +02:00
Róbert Kiss
648e8d5f2c feat: keep current selected layer when changing keymap (#714) 2018-07-05 23:58:44 +02:00
Róbert Kiss
15df8d7129 WIP feat: replace ng2-select2 => ngx-select-ex (#706)
* feat: replace ng2-select2 => ngx-select-ex

* feat: style the ngrx-select

* feat: replace secondary role select2

* feat: replace Select2OptionData => SelectOptionData

* feat: replace select2 => ngx-select in macro-tab component

* feat: replace select2 => ngx-select in keymap-tab component

* feat: fix styles

* chore: remove select2 from dependencies

* fix: macro editor overflow

* fix: set the same font size for the toggle button

* fix: overflow

* chore: use @ert78gb/ngx-select-ex version of ngx-select-ex
2018-07-02 23:44:39 +02:00
Sylvain Benner
cfc0af9655 Fix Sleep key for macOS users in Mac keymaps and Fn layers (#707)
Reference: https://support.apple.com/en-us/HT201236
2018-06-29 12:19:59 +02:00
László Monda
f02e3181a6 Improve the phrasing of the firmware update error message. 2018-06-28 18:49:01 +02:00
Róbert Kiss
3d59bcf97e feat: Tweak unsupported Windows firmware update notification (#705)
* feat: Tweak unsupported Windows firmware update notification

* feat: Display firmware update status

* feat: throw error when left half not connected under firmware upgrade
2018-06-28 18:41:16 +02:00
László Monda
5e4fc983fb Rename the "Remap Key" button to "Remay key". 2018-06-27 21:28:16 +02:00
László Monda
32d9635b34 Tone down the color of the separator line. 2018-06-26 06:05:03 +02:00
152 changed files with 18288 additions and 14910 deletions

2
.nvmrc
View File

@@ -1 +1 @@
8.11.2
8.12.0

View File

@@ -6,12 +6,79 @@ 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 popver always contain the action of the current key, even after cancelled.
- Make the key action popover always contain the action of the current key, even after cancelled.
- Include the firmware version to be updated to the firmware update log.
- 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.

1
ISSUE_TEMPLATE Normal file
View 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.

13768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
"private": true,
"author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js",
"version": "1.2.5",
"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,14 +15,14 @@
},
"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",
@@ -38,37 +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"
@@ -88,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",

View File

@@ -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",

View File

@@ -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"

View File

@@ -1,11 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"typeRoots": [
"./node_modules/@types"
]
"experimentalDecorators": true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,8 @@ import {
IpcResponse,
LogService,
mapObjectToUserConfigBinaryBuffer,
SaveUserConfigurationData
SaveUserConfigurationData,
UpdateFirmwareData
} from 'uhk-common';
import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { Observable } from 'rxjs/Observable';
@@ -87,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');
}
@@ -148,10 +158,12 @@ 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);
@@ -159,8 +171,8 @@ export class DeviceService {
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);
@@ -224,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.

View File

@@ -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');
@@ -21,10 +21,9 @@ export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
};
}
function writeDataToFile(data: string, filePath: string): Promise<void> {
function writeDataToFile(data: Array<number>, filePath: string): Promise<void> {
return new Promise((resolve, reject) => {
const array: Array<number> = JSON.parse(data);
const buffer = new Buffer(array);
const buffer = new Buffer(data);
fs.writeFile(filePath, buffer, err => {
if (err) {

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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
}
}
]
},

View File

@@ -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;
}
}
}
}
}
}
}

View File

@@ -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';

View File

@@ -0,0 +1,6 @@
import { VersionInformation } from './version-information';
export interface UpdateFirmwareData {
versionInformation: VersionInformation;
firmware?: Array<number>;
}

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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": {
@@ -179,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": {
@@ -187,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": {
@@ -202,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": {
@@ -223,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": {
@@ -236,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": {
@@ -263,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": {
@@ -272,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": {
@@ -283,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": {
@@ -322,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": {
@@ -332,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": {
@@ -342,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": {
@@ -350,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": {
@@ -363,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": {
@@ -374,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"
}
}
}
@@ -385,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": {
@@ -396,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": {
@@ -414,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": {

View File

@@ -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
}

View File

@@ -10,7 +10,8 @@ import {
KbootCommands,
ModuleSlotToI2cAddress,
ModuleSlotToId,
UsbCommand
UsbCommand,
UsbVariables
} from './constants';
import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util';
@@ -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
*/

View File

@@ -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);

View File

@@ -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
View File

@@ -0,0 +1 @@
out-tsc/

View File

@@ -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');
});
});

View File

@@ -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');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -36,8 +36,8 @@
"@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/usb": "1.1.3",
"angular-confirmation-popover": "3.2.0",
@@ -50,26 +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",
"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",
@@ -79,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"
}
}

View File

@@ -12,7 +12,7 @@ exports.config = {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
baseUrl: 'http://localhost:8080/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,

View File

@@ -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>

View File

@@ -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());
}
}

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
:host {
width: 100%;
height: 100%;
display: block;
}

View File

@@ -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 {
}

View File

@@ -12,13 +12,10 @@
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
</p>
<p *ngIf="showUnsupportedOsToFirmwareUpgrade$ | async">Firmware update doesn't work on Windows 7, Windows Vista, and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
<p *ngIf="runningOnNotSupportedWindows$ | async">Firmware update doesn't work on Windows 7, Windows Vista,
and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
<p>If the update process fails, disconnect every USB device from your computer including USB hubs, KVM switches, and every USB device. Then connect only your UHK and retry.</p>
<p>If you tried the above and the update still keeps failing, please <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">create a GitHub issue</a>, and attach the update log.</p>
<p>
<p *ngIf="firmwareUpgradeAllowed$ | async">
<button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmware()">
@@ -29,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 *ngIf="firmwareUpgradeSuccess$ | async"
class="alert alert-success"
role="alert">
<p>Firmware update succeeded.</p>
</div>
</div>
<div class="flex-grow">
<div class="flex-grow" *ngIf="firmwareUpgradeAllowed$ | async">
<xterm [logs]="xtermLog$ | async"></xterm>
</div>
<div class="flex-footer">

View File

@@ -7,10 +7,13 @@ import { Constants, HardwareModules, VersionInformation } from 'uhk-common';
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
import {
AppState,
firmwareUpgradeAllowed,
firmwareUpgradeFailed,
firmwareUpgradeSuccess,
flashFirmwareButtonDisbabled,
getAgentVersionInfo,
getHardwareModules,
showUnsupportedOsToFirmwareUpgrade,
runningOnNotSupportedWindows,
xtermLog
} from '../../../store';
import { UpdateFirmwareAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
@@ -31,7 +34,10 @@ export class DeviceFirmwareComponent implements OnDestroy {
getAgentVersionInfo$: Observable<VersionInformation>;
hardwareModulesSubscription: Subscription;
hardwareModules: HardwareModules;
showUnsupportedOsToFirmwareUpgrade$: Observable<boolean>;
runningOnNotSupportedWindows$: Observable<boolean>;
firmwareUpgradeAllowed$: Observable<boolean>;
firmwareUpgradeFailed$: Observable<boolean>;
firmwareUpgradeSuccess$: Observable<boolean>;
constructor(private store: Store<AppState>) {
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
@@ -40,7 +46,10 @@ export class DeviceFirmwareComponent implements OnDestroy {
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
this.hardwareModules = data;
});
this.showUnsupportedOsToFirmwareUpgrade$ = store.select(showUnsupportedOsToFirmwareUpgrade);
this.runningOnNotSupportedWindows$ = store.select(runningOnNotSupportedWindows);
this.firmwareUpgradeAllowed$ = store.select(firmwareUpgradeAllowed);
this.firmwareUpgradeFailed$ = store.select(firmwareUpgradeFailed);
this.firmwareUpgradeSuccess$ = store.select(firmwareUpgradeSuccess);
}
ngOnDestroy(): void {

View File

@@ -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"

View File

@@ -1,5 +1,4 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
width: 100%;

View File

@@ -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

View File

@@ -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[];

View File

@@ -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'
}

View File

@@ -1,4 +1,4 @@
import { Component, HostListener } 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';
@@ -24,6 +24,7 @@ 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'
}

View File

@@ -43,4 +43,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@@ -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>

View File

@@ -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;

View File

@@ -8,10 +8,12 @@
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
</div>
<div class="list-group-item macro-action-editor__container"
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'">
<macro-action-editor
[macroAction]="macroAction"
(cancel)="cancelEdit()"
(save)="saveEditedAction($event)">
</macro-action-editor>
</div>
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'"
[style.overflow]="overflow">
<macro-action-editor
*ngIf="editable || newItem"
[macroAction]="macroAction"
(cancel)="cancelEdit()"
(save)="saveEditedAction($event)">
</macro-action-editor>
</div>

View File

@@ -1,7 +1,7 @@
@import '../../../../styles/variables';
:host {
overflow: hidden;
overflow: visible;
display: block;
&.macro-item:first-of-type {

View File

@@ -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
}
}

View File

@@ -47,10 +47,14 @@
</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"
@@ -81,14 +85,16 @@
<label>
<input type="checkbox"
name="remapOnAllKeymap"
[(ngModel)]="remapOnAllKeymap"> Remap on all keymaps
[(ngModel)]="remapInfo.remapOnAllKeymap"> Remap on all keymaps
</label>
</div>
<div class="checkbox">
<label>
<label [ngClass]="{ disabled: disableRemapOnAllLayer }">
<input type="checkbox"
name="remapOnAllLayer"
[(ngModel)]="remapOnAllLayer"> Remap on all layers
[(ngModel)]="remapInfo.remapOnAllLayer"
[disabled]="disableRemapOnAllLayer"
(ngModelChange)="remapInfoChange()"> Remap on all layers
</label>
</div>
<div class="d-inline-block">
@@ -109,7 +115,7 @@
<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
(click)="onRemapKey()"> Remap key
</button>
</div>
</div>

View File

@@ -99,6 +99,10 @@
padding: 10px 24px;
}
.pr-10 {
padding-right: 10px;
}
.popover-overlay {
position: fixed;
width: 100%;
@@ -116,27 +120,15 @@
}
}
.select2-item {
position: relative;
font-size: 1.5rem;
&.keymap-name--wrapper {
padding-left: 50px;
}
.layout-segment-code {
height: 2rem;
position: absolute;
left: 0;
top: 50%;
margin-top: -1rem;
}
}
.popover-action-form {
margin-top: 4px;
label {
margin-right: 5px;
&.disabled {
cursor: not-allowed;
color: #959595;
}
}
}

View File

@@ -1,4 +1,6 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
@@ -23,6 +25,7 @@ import {
KeystrokeAction,
MouseAction,
PlayMacroAction,
SecondaryRoleAction,
SwitchKeymapAction,
SwitchLayerAction
} from 'uhk-common';
@@ -32,6 +35,7 @@ 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,
@@ -46,6 +50,7 @@ enum TabName {
selector: 'popover',
templateUrl: './popover.component.html',
styleUrls: ['./popover.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('popover', [
state('closed', style({
@@ -84,6 +89,7 @@ export class PopoverComponent implements OnChanges {
@Input() wrapPosition: any;
@Input() visible: boolean;
@Input() allowLayerDoubleTap: boolean;
@Input() remapInfo: RemapInfo;
@Output() cancel = new EventEmitter<any>();
@Output() remap = new EventEmitter<KeyActionRemap>();
@@ -100,13 +106,13 @@ export class PopoverComponent implements OnChanges {
topPosition: number = 0;
leftPosition: number = 0;
animationState: string;
remapOnAllKeymap: boolean;
remapOnAllLayer: boolean;
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$)
@@ -122,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;
@@ -143,8 +151,6 @@ export class PopoverComponent implements OnChanges {
if (change['visible']) {
if (change['visible'].currentValue) {
this.animationState = 'opened';
this.remapOnAllKeymap = false;
this.remapOnAllLayer = false;
} else {
this.animationState = 'closed';
}
@@ -163,8 +169,8 @@ export class PopoverComponent implements OnChanges {
if (this.keyActionValid) {
try {
this.remap.emit({
remapOnAllKeymap: this.remapOnAllKeymap,
remapOnAllLayer: this.remapOnAllLayer,
remapOnAllKeymap: this.remapInfo.remapOnAllKeymap,
remapOnAllLayer: this.remapInfo.remapOnAllLayer,
action: this.selectedTab.toKeyAction()
});
} catch (e) {
@@ -179,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;

View File

@@ -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">

View File

@@ -16,7 +16,7 @@
margin-right: 7px;
}
select2 {
ngx-select {
flex: 1;
}
}

View File

@@ -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);
}
}

View File

@@ -1,12 +1,27 @@
<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"
html="true"
@@ -21,32 +36,48 @@
<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"
html="true"
@@ -57,10 +88,15 @@
<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 pt-3'>The secondary role can be any layer or modifier.</p>"
<p class='text-left'>The secondary role can be any layer or modifier.</p>"
data-placement="bottom"></icon>
</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.

View File

@@ -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;
}
}

View File

@@ -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() {
ngOnChanges(changes: SimpleChanges) {
if (changes.showLayerSwitcherInSecondaryRoles) {
this.fillSecondaryRoles();
}
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;
}
if (state.additional && state.additional.explanation) {
return jQuery(
'<span class="select2-item">'
+ '<span>' + state.text + '</span>'
+ '<span class="scancode--searchterm"> '
+ state.additional.explanation
+ '</span>' +
'</span>'
);
} else {
return jQuery('<span class="select2-item">' + state.text + '</span>');
}
toggleModifier(modifier: KeyModifierModel): void {
modifier.checked = !modifier.checked;
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(id: string) {
this.selectedSecondaryRoleIndex = +id;
this.keyActionChanged();
}
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));
}
}

View File

@@ -1,4 +1,4 @@
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
import { KeyAction, LayerName, SwitchLayerAction, SwitchLayerMode } from 'uhk-common';
import { Tab } from '../tab';
@@ -7,6 +7,7 @@ export type toggleType = 'active' | 'toggle';
@Component({
selector: 'layer-tab',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './layer-tab.component.html',
styleUrls: ['./layer-tab.component.scss']
})

View File

@@ -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">
@@ -14,4 +30,4 @@
</macro-item>
</div>
</div>
</ng-template>
</ng-template>

View File

@@ -16,7 +16,7 @@
margin-right: 7px;
}
select2 {
ngx-select {
flex: 1;
}
}

View File

@@ -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 {

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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 {}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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

View File

@@ -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']">

View File

@@ -1,5 +1,5 @@
<label *ngIf="label">
<span>{{label}}</span>
<span [innerHtml]="label"></span>
<icon name="question-circle"
data-toggle="tooltip"
[title]="tooltip"

View File

@@ -0,0 +1 @@
export const SECONDARY_ROLE_BOTTOM_MARGIN = 1;

View File

@@ -13,9 +13,9 @@
[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"

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -7,6 +7,12 @@ 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',
@@ -45,9 +51,9 @@ 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[];
@@ -79,19 +85,17 @@ export class SvgKeyboardComponent implements OnInit {
}
}
onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void {
onKeyClick(moduleId: number, event: SvgModuleKeyClickEvent): void {
this.keyClick.emit({
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
});
}

View File

@@ -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';

View File

@@ -10,4 +10,11 @@
[attr.text-anchor]="'middle'"
[attr.font-size]="11">
<tspan [attr.x]="spanX">{{ text }}</tspan>
</svg:text>
</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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -26,6 +26,9 @@ import { MapperService } from '../../../../services/mapper.service';
import { AppState } from '../../../../store';
import { getMacros } from '../../../../store/reducers/user-configuration';
import { SvgKeyCaptureEvent, SvgKeyClickEvent } from '../../../../models/svg-key-events';
import { OperatingSystem } from '../../../../models/operating-system';
import { KeyModifierModel } from '../../../../models/key-modifier-model';
enum LabelTypes {
KeystrokeKey,
@@ -82,8 +85,8 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
@Input() capturingEnabled: boolean;
@Input() active: boolean;
@Output() keyClick = new EventEmitter();
@Output() capture = new EventEmitter();
@Output() keyClick = new EventEmitter<SvgKeyClickEvent>();
@Output() capture = new EventEmitter<SvgKeyCaptureEvent>();
enumLabelTypes = LabelTypes;
@@ -93,9 +96,14 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
labelType: LabelTypes;
labelSource: any;
secondaryText: string;
macros: Macro[];
private subscription: Subscription;
private scanCodePressed: boolean;
private pressedShiftLocation = -1;
private pressedAltLocation = -1;
private altPressed = false;
private shiftPressed = false;
constructor(
private mapper: MapperService,
@@ -112,15 +120,21 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
this.scanCodePressed = false;
}
@HostListener('click')
onClick() {
@HostListener('click', ['$event'])
onClick(e: MouseEvent) {
this.reset();
this.keyClick.emit(this.element.nativeElement);
this.keyClick.emit({
keyTarget: this.element.nativeElement,
shiftPressed: e.shiftKey,
altPressed: e.altKey
});
this.pressedShiftLocation = -1;
this.pressedAltLocation = -1;
}
@HostListener('mousedown', ['$event'])
onMouseDown(e: MouseEvent) {
if ((e.which === 2 || e.button === 1) && this.capturingEnabled) {
if ((e.which === 2 || e.button === 2) && this.capturingEnabled) {
e.preventDefault();
this.renderer.invokeElementMethod(this.element.nativeElement, 'focus');
@@ -129,13 +143,23 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
} else {
this.recording = true;
this.recordAnimation = 'active';
this.shiftPressed = e.shiftKey;
this.altPressed = e.altKey;
}
}
}
@HostListener('keyup', ['$event'])
@HostListener('document:keyup', ['$event'])
onKeyUp(e: KeyboardEvent) {
if (this.scanCodePressed) {
if (e.keyCode === 18 && this.pressedAltLocation > -1) {
this.pressedAltLocation = -1;
e.preventDefault();
}
else if (e.keyCode === 16 && this.pressedShiftLocation > -1) {
this.pressedShiftLocation = -1;
e.preventDefault();
}
else if (this.scanCodePressed) {
e.preventDefault();
this.scanCodePressed = false;
} else if (this.recording) {
@@ -144,7 +168,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
}
}
@HostListener('keydown', ['$event'])
@HostListener('document:keydown', ['$event'])
onKeyDown(e: KeyboardEvent) {
const code: number = e.keyCode;
@@ -152,11 +176,29 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
e.preventDefault();
if (this.captureService.hasMap(code)) {
// If the Alt or Shift key not released after start the capturing
// then add them as a modifier
if (this.pressedShiftLocation > -1) {
this.captureService.setModifier((this.pressedShiftLocation === 1), 16);
}
if (this.pressedAltLocation > -1) {
this.captureService.setModifier((this.pressedAltLocation === 1), 18);
}
this.saveScanCode(this.captureService.getMap(code));
this.scanCodePressed = true;
} else {
this.captureService.setModifier((e.location === 1), code);
}
} else {
if (e.keyCode === 16) {
this.pressedShiftLocation = e.location;
}
if (e.keyCode === 18) {
this.pressedAltLocation = e.location;
}
}
}
@@ -198,36 +240,40 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
this.recording = false;
this.changeAnimation = 'inactive';
this.captureService.initModifiers();
this.shiftPressed = false;
this.altPressed = false;
}
private saveScanCode(code = 0) {
this.recording = false;
this.changeAnimation = 'inactive';
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,
left,
right
captured: {
code,
left,
right
},
shiftPressed: this.shiftPressed,
altPressed: this.altPressed
});
this.captureService.initModifiers();
this.reset();
}
private setLabels(): void {
this.labelType = LabelTypes.OneLineText;
this.labelSource = undefined;
this.secondaryText = undefined;
if (!this.keyAction) {
this.labelSource = undefined;
this.labelType = LabelTypes.OneLineText;
return;
}
this.labelType = LabelTypes.OneLineText;
if (this.keyAction instanceof KeystrokeAction) {
const keyAction: KeystrokeAction = this.keyAction as KeystrokeAction;
let newLabelSource: string[];
this.secondaryText = this.mapper.getSecondaryRoleText(keyAction.secondaryRoleAction);
if (!keyAction.hasActiveModifier() && keyAction.hasScancode()) {
const scancode: number = keyAction.scancode;
@@ -245,29 +291,32 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
}
}
} else if (keyAction.hasOnlyOneActiveModifier() && !keyAction.hasScancode()) {
newLabelSource = [];
switch (keyAction.modifierMask) {
case KeyModifiers.leftCtrl:
case KeyModifiers.rightCtrl:
newLabelSource.push('Ctrl');
this.labelSource = ['Ctrl'];
break;
case KeyModifiers.leftShift:
case KeyModifiers.rightShift:
newLabelSource.push('Shift');
this.labelSource = ['Shift'];
break;
case KeyModifiers.leftAlt:
case KeyModifiers.rightAlt:
newLabelSource.push('Alt');
this.labelSource = [this.mapper.getOsSpecificText('Alt')];
break;
case KeyModifiers.leftGui:
case KeyModifiers.rightGui:
newLabelSource.push('Super');
if (this.mapper.getOperatingSystem() === OperatingSystem.Windows) {
this.labelSource = this.mapper.getIcon('command');
this.labelType = LabelTypes.SingleIcon;
} else {
this.labelSource = [this.mapper.getOsSpecificText('Super')];
}
break;
default:
newLabelSource.push('Undefined');
this.labelSource = ['Undefined'];
break;
}
this.labelSource = newLabelSource;
} else {
this.labelType = LabelTypes.KeystrokeKey;
this.labelSource = this.keyAction;

View File

@@ -13,7 +13,8 @@
<svg:g svg-two-line-text-key *ngSwitchCase="'two-line'"
[height]="height"
[width]="width"
[texts]="labelSource">
[texts]="labelSource"
[secondaryText]="subComponentSecondaryRoleText">
</svg:g>
</svg>
<svg [attr.viewBox]="viewBox" [attr.width]="modifierContainer.width" [attr.height]="modifierContainer.height" [attr.x]="modifierContainer.x"
@@ -28,10 +29,19 @@
</svg>
<svg viewBox="0 0 100 100" [attr.width]="option.width" [attr.height]="option.height" [attr.x]="option.x" [attr.y]="option.y"
preserveAspectRatio="none" [class.disabled]="option.disabled">
<svg:use [attr.xlink:href]="modifierIconNames.option" />
<svg:use *ngIf="modifierIconNames.option" [attr.xlink:href]="modifierIconNames.option" />
<svg:text *ngIf="!modifierIconNames.option" [attr.text-anchor]="'middle'" [attr.x]="50" [attr.y]="50">A</svg:text>
</svg>
<svg viewBox="0 0 100 100" [attr.width]="command.width" [attr.height]="command.height" [attr.x]="command.x" [attr.y]="command.y"
preserveAspectRatio="none" [class.disabled]="command.disabled">
<svg:use [attr.xlink:href]="modifierIconNames.command" />
<svg:use *ngIf="modifierIconNames.command" [attr.xlink:href]="modifierIconNames.command" />
<svg:text *ngIf="!modifierIconNames.command" [attr.text-anchor]="'middle'" [attr.x]="50" [attr.y]="50">S</svg:text>
</svg>
</svg>
<svg:g svg-secondary-role
*ngIf="thisSecondaryRoleText"
[height]="20"
[width]="secondaryTextWidth"
[y]="secondaryTextY"
[text]="thisSecondaryRoleText">
</svg:g>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,7 +1,9 @@
import { Component, Input, OnChanges, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { KeyModifiers, KeystrokeAction } from 'uhk-common';
import { MapperService } from '../../../../services/mapper.service';
import { isRectangleAsSecondaryRoleKey } from '../util';
import { SECONDARY_ROLE_BOTTOM_MARGIN } from '../../constants';
class SvgAttributes {
width: number;
@@ -25,10 +27,11 @@ class SvgAttributes {
styleUrls: ['./svg-keystroke-key.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
export class SvgKeystrokeKeyComponent implements OnChanges {
@Input() height: number;
@Input() width: number;
@Input() keystrokeAction: KeystrokeAction;
@Input() secondaryText: string;
viewBox: string;
textContainer: SvgAttributes;
@@ -46,6 +49,11 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
option?: string,
command?: string
};
secondaryTextY: number;
secondaryTextWidth: number;
secondaryHeight: number;
thisSecondaryRoleText: string;
subComponentSecondaryRoleText: string;
constructor(private mapper: MapperService) {
this.modifierIconNames = {};
@@ -57,15 +65,75 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
this.command = new SvgAttributes();
}
ngOnInit() {
ngOnChanges() {
this.calculatePositions();
}
private calculatePositions(): void {
let textYModifier = 0;
let secondaryYModifier = 0;
this.thisSecondaryRoleText = this.secondaryText;
this.subComponentSecondaryRoleText = null;
const bottomSideMode: boolean = this.width < this.height * 1.8;
const isRectangleAsSecondaryRole = isRectangleAsSecondaryRoleKey(this.width, this.height);
if (this.secondaryText && isRectangleAsSecondaryRole) {
textYModifier = this.height / 5;
secondaryYModifier = 5;
}
if (this.keystrokeAction.hasScancode()) {
const scancode: number = this.keystrokeAction.scancode;
this.labelSource = this.mapper.scanCodeToSvgImagePath(scancode, this.keystrokeAction.type);
if (this.labelSource) {
this.labelType = 'icon';
} else {
let newLabelSource: string[];
newLabelSource = this.mapper.scanCodeToText(scancode, this.keystrokeAction.type);
if (newLabelSource) {
if (this.secondaryText && newLabelSource.length === 2) {
if (isRectangleAsSecondaryRole || bottomSideMode) {
this.labelSource = newLabelSource[0];
this.labelType = 'one-line';
} else {
this.labelSource = newLabelSource;
this.labelType = 'two-line';
this.thisSecondaryRoleText = null;
this.subComponentSecondaryRoleText = this.secondaryText;
}
}
else {
if (newLabelSource.length === 1) {
this.labelSource = newLabelSource[0];
this.labelType = 'one-line';
} else {
this.labelSource = newLabelSource;
this.labelType = 'two-line';
}
}
}
}
} else {
this.labelType = 'empty';
}
this.shift.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftShift | KeyModifiers.rightShift);
this.control.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftCtrl | KeyModifiers.rightCtrl);
this.option.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftAlt | KeyModifiers.rightAlt);
this.command.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftGui | KeyModifiers.rightGui);
this.secondaryHeight = this.secondaryText ? this.height / 4 : 0;
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
this.viewBox = [0, 0, this.width, this.height].join(' ');
this.modifierIconNames.shift = this.mapper.getIcon('shift');
this.modifierIconNames.option = this.mapper.getIcon('option');
this.modifierIconNames.command = this.mapper.getIcon('command');
const bottomSideMode: boolean = this.width < this.height * 1.8;
this.textContainer.y = 0;
const heightWidthRatio = this.height / this.width;
this.secondaryTextWidth = this.width;
if (bottomSideMode) {
const maxIconWidth = this.width / 4;
@@ -75,7 +143,7 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
const iconHeight = iconScalingFactor * maxIconHeight;
this.modifierContainer.width = this.width;
this.modifierContainer.height = this.height / 5;
this.modifierContainer.y = this.height - this.modifierContainer.height;
this.modifierContainer.y = this.height - this.modifierContainer.height - this.secondaryHeight;
this.shift.width = iconWidth;
this.shift.height = iconHeight;
this.shift.x = (maxIconWidth - iconWidth) / 2;
@@ -92,7 +160,7 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
this.command.height = iconHeight;
this.command.x = this.option.x + maxIconWidth;
this.command.y = this.shift.y;
this.textContainer.y = -this.modifierContainer.height / 2;
this.textContainer.y = -this.modifierContainer.height / 2 - this.secondaryHeight / 2;
} else {
this.modifierContainer.width = this.width / 4;
this.modifierContainer.height = this.height;
@@ -120,40 +188,11 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
this.command.x = this.option.x + this.width / 2;
this.command.y = this.option.y;
this.textContainer.x = -this.modifierContainer.width / 2;
this.secondaryTextWidth = this.width - this.modifierContainer.width;
}
this.textContainer.y -= textYModifier;
this.textContainer.width = this.width;
this.textContainer.height = this.height;
}
ngOnChanges() {
if (this.keystrokeAction.hasScancode()) {
const scancode: number = this.keystrokeAction.scancode;
this.labelSource = this.mapper.scanCodeToSvgImagePath(scancode, this.keystrokeAction.type);
if (this.labelSource) {
this.labelType = 'icon';
} else {
let newLabelSource: string[];
newLabelSource = this.mapper.scanCodeToText(scancode, this.keystrokeAction.type);
if (newLabelSource) {
if (newLabelSource.length === 1) {
this.labelSource = newLabelSource[0];
this.labelType = 'one-line';
} else {
this.labelSource = newLabelSource;
this.labelType = 'two-line';
}
}
}
} else {
this.labelType = 'empty';
}
this.shift.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftShift | KeyModifiers.rightShift);
this.control.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftCtrl | KeyModifiers.rightCtrl);
this.option.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftAlt | KeyModifiers.rightAlt);
this.command.disabled = !this.keystrokeAction.isActive(KeyModifiers.leftGui | KeyModifiers.rightGui);
}
}

View File

@@ -1,6 +1,13 @@
<svg:text
<svg:text
[attr.x]="0"
[attr.y]="textY"
[attr.text-anchor]="'middle'">
<tspan [attr.x]="spanX" dy="0">{{ text }}</tspan>
</svg:text>
<tspan [attr.x]="spanX" dy="0">{{ 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: 154 B

After

Width:  |  Height:  |  Size: 316 B

View File

@@ -1,22 +1,43 @@
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-one-line-text-key]',
templateUrl: './svg-one-line-text-key.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SvgOneLineTextKeyComponent implements OnInit {
export class SvgOneLineTextKeyComponent implements OnChanges {
@Input() height: number;
@Input() width: number;
@Input() text: string;
@Input() secondaryText: string;
textY: number;
spanX: number;
secondaryTextY: number;
secondaryHeight: number;
constructor() { }
ngOnInit() {
this.textY = this.height / 2;
ngOnChanges(changes: SimpleChanges): void {
this.calculatePositions();
}
calculatePositions() {
let textYModifier = 0;
let secondaryYModifier = 0;
if (this.secondaryText && isRectangleAsSecondaryRoleKey(this.width, this.height)) {
textYModifier = this.height / 5;
secondaryYModifier = 5;
}
this.textY = this.height / 2 - textYModifier;
this.spanX = this.width / 2;
this.secondaryHeight = this.height / 4;
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
}
}

View File

@@ -0,0 +1 @@
export * from './svg-secondary-role.component';

View File

@@ -0,0 +1,15 @@
<svg [attr.viewBox]="viewBox" [attr.width]="width" [attr.height]="height" [attr.y]="y">
<g id="secondaryContent" [attr.transform]="transform">
<svg viewBox="0 0 14 14" width="12" height="12" x="2" [attr.y]="textY / 3.5">
<ellipse stroke="#fff" rx="6.5" ry="6.5" cy="7" cx="7" stroke-width="1" fill-opacity="0"/>
<text text-anchor="start" font-family="Helvetica" font-size="12" y="7.8" x="4" stroke-width="0">2
</text>
</svg>
<text [attr.y]="textY"
[attr.x]="textIndent"
font-size="12"
text-anchor="start">
{{ text }}
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 667 B

View File

@@ -0,0 +1,52 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnChanges,
OnInit,
SimpleChanges,
ViewChild
} from '@angular/core';
import { getContentWidth } from '../../../../util';
const SECONDARY_STYLE: CSSStyleDeclaration = {
font: '12px Helvetica'
} as any;
@Component({
selector: 'g[svg-secondary-role]',
templateUrl: './svg-secondary-role.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SvgSecondaryRoleComponent implements OnInit, OnChanges {
@Input() height: number;
@Input() width: number;
@Input() y: number;
@Input() text: string;
@ViewChild('secondary') svgElement: ElementRef;
viewBox: string;
textY: number;
transform: string;
textIndent = 16;
ngOnInit(): void {
this.viewBox = [0, 0, this.width, this.height].join(' ');
this.textY = this.height / 2 - 2;
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.text) {
this.calculateTextPosition();
}
}
private calculateTextPosition(): void {
const textWidth = getContentWidth(SECONDARY_STYLE, this.text) + this.textIndent;
const translateValue = Math.max(0, (this.width - textWidth) / 2);
this.transform = `translate(${ translateValue },0)`;
}
}

View File

@@ -4,3 +4,10 @@
[attr.x]="svgWidth"
[attr.y]="svgHeight">
</svg:use>
<svg:g svg-secondary-role
*ngIf="secondaryText"
[height]="20"
[width]="width"
[y]="secondaryTextY"
[text]="secondaryText">
</svg:g>

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 340 B

View File

@@ -1,22 +1,42 @@
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-single-icon-key]',
templateUrl: './svg-single-icon-key.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SvgSingleIconKeyComponent implements OnInit {
export class SvgSingleIconKeyComponent implements OnChanges {
@Input() width: number;
@Input() height: number;
@Input() icon: string;
@Input() secondaryText: string;
svgHeight: number;
svgWidth: number;
secondaryTextY: number;
secondaryHeight: number;
constructor() { }
ngOnInit() {
ngOnChanges(changes: SimpleChanges): void {
this.calculatePositions();
}
calculatePositions(): void {
let textYModifier = 0;
let secondaryYModifier = 0;
if (this.secondaryText && isRectangleAsSecondaryRoleKey(this.width, this.height)) {
textYModifier = this.height / 5;
secondaryYModifier = 5;
}
this.svgWidth = this.width / 3;
this.svgHeight = this.height / 3;
this.secondaryHeight = this.height / 4;
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
}
}

View File

@@ -9,4 +9,11 @@
[attr.height]="useHeight"
[attr.x]="useX"
[attr.y]="useY">
</svg:use>
</svg:use>
<svg:g svg-secondary-role
*ngIf="secondaryText"
[height]="20"
[width]="width"
[y]="secondaryTextY"
[text]="secondaryText">
</svg:g>

Before

Width:  |  Height:  |  Size: 309 B

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -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-text-icon-key]',
templateUrl: './svg-text-icon-key.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SvgTextIconKeyComponent implements OnInit {
export class SvgTextIconKeyComponent implements OnChanges {
@Input() width: number;
@Input() height: number;
@Input() text: string;
@Input() icon: string;
@Input() secondaryText: string;
useWidth: number;
useHeight: number;
@@ -18,16 +22,33 @@ export class SvgTextIconKeyComponent implements OnInit {
textY: number;
textAnchor: string;
spanX: number;
secondaryTextY: number;
secondaryHeight: number;
constructor() { }
ngOnInit() {
ngOnChanges(changes: SimpleChanges): void {
this.calculatePositions();
}
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) ? this.width * 0.6 : this.width / 3;
this.useY = (this.width > 2 * this.height) ? this.height / 3 : this.height / 2;
this.textY = (this.width > 2 * this.height) ? this.height / 2 : this.height / 3;
this.textY = ((this.width > 2 * this.height) ? this.height / 2 : this.height / 3) - textYModifier;
this.textAnchor = (this.width > 2 * this.height) ? 'end' : 'middle';
this.spanX = (this.width > 2 * this.height) ? 0.6 * this.width : this.width / 2;
this.secondaryHeight = this.height / 4;
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
}
}

View File

@@ -8,4 +8,11 @@
[attr.y]="spanYs[index]"
dy="0"
>{{ text }}</tspan>
</svg:text>
</svg:text>
<svg:g svg-secondary-role
*ngIf="secondaryText"
[height]="20"
[width]="width"
[y]="secondaryTextY"
[text]="secondaryText">
</svg:g>

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 474 B

View File

@@ -1,28 +1,51 @@
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Component, Input, ChangeDetectionStrategy, SimpleChanges, OnChanges } from '@angular/core';
import { SECONDARY_ROLE_BOTTOM_MARGIN } from '../../constants';
@Component({
selector: 'g[svg-two-line-text-key]',
templateUrl: './svg-two-line-text-key.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SvgTwoLineTextKeyComponent implements OnInit {
export class SvgTwoLineTextKeyComponent implements OnChanges {
@Input() height: number;
@Input() width: number;
@Input() texts: string[];
@Input() secondaryText: string;
textY: number;
spanX: number;
spanYs: number[];
secondaryTextY: number;
secondaryHeight: number;
constructor() {
this.spanYs = [];
}
ngOnInit() {
this.textY = this.height / 2;
this.spanX = this.width / 2;
for (let i = 0; i < this.texts.length; ++i) {
this.spanYs.push((0.75 - i * 0.5) * this.height);
ngOnChanges(changes: SimpleChanges): void {
this.calculatePositions();
}
calculatePositions(): void {
let textYModifier = 0;
let secondaryYModifier = 0;
this.secondaryHeight = 0;
let textContainerHeight = this.height;
if (this.secondaryText) {
textYModifier = this.height / 5;
secondaryYModifier = 0;
this.secondaryHeight = this.height / 4;
textContainerHeight -= this.secondaryHeight;
}
this.textY = textContainerHeight / 2;
this.spanX = this.width / 2;
this.spanYs = [];
for (let i = 0; i < this.texts.length; ++i) {
this.spanYs.push((0.75 - i * 0.5) * textContainerHeight);
}
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
}
}

View File

@@ -0,0 +1,3 @@
export const isRectangleAsSecondaryRoleKey = (width: number, height: number): boolean => {
return width > height * 2.4;
};

View File

@@ -2,6 +2,12 @@ import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from
import { KeyAction } from 'uhk-common';
import { SvgKeyboardKey } from '../keys';
import {
SvgKeyCaptureEvent,
SvgKeyClickEvent,
SvgModuleCaptureEvent,
SvgModuleKeyClickEvent
} from '../../../models/svg-key-events';
@Component({
selector: 'g[svg-module]',
@@ -17,18 +23,18 @@ export class SvgModuleComponent {
@Input() selected: boolean;
@Input() keybindAnimationEnabled: boolean;
@Input() capturingEnabled: boolean;
@Output() keyClick = new EventEmitter();
@Output() keyClick = new EventEmitter<SvgModuleKeyClickEvent>();
@Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter();
@Output() capture = new EventEmitter<SvgModuleCaptureEvent>();
constructor() {
this.keyboardKeys = [];
}
onKeyClick(index: number, keyTarget: HTMLElement): void {
onKeyClick(keyId: number, event: SvgKeyClickEvent): void {
this.keyClick.emit({
index,
keyTarget
...event,
keyId
});
}
@@ -40,10 +46,10 @@ export class SvgModuleComponent {
});
}
onCapture(index: number, captured: {code: number, left: boolean[], right: boolean[]}) {
onCapture(keyId: number, event: SvgKeyCaptureEvent) {
this.capture.emit({
index,
captured
...event,
keyId
});
}
}

View File

@@ -8,9 +8,9 @@
[halvesSplit]="halvesSplit"
[keyboardLayout]="keyboardLayout"
[description]="keymap.description"
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
(keyClick)="onKeyClick($event)"
(keyHover)="onKeyHover($event)"
(capture)="onCapture($event)"
(descriptionChanged)="onDescriptionChanged($event)"
></keyboard-slider>
@@ -22,6 +22,7 @@
[currentKeymap]="keymap"
[currentLayer]="currentLayer"
[allowLayerDoubleTap]="allowLayerDoubleTap"
[remapInfo]="remapInfo"
(cancel)="hidePopover()"
(remap)="onRemap($event)"></popover>

View File

@@ -43,6 +43,13 @@ import { PopoverComponent } from '../../popover';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
import { KeyActionRemap } from '../../../models/key-action-remap';
import {
SvgKeyboardCaptureEvent,
SvgKeyboardKeyClickEvent,
SvgKeyHoverEvent
} from '../../../models/svg-key-events';
import { RemapInfo } from '../../../models/remap-info';
import { mapLeftRigthModifierToKeyActionModifier } from '../../../util';
interface NameValuePair {
name: string;
@@ -65,7 +72,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
@Output() descriptionChanged = new EventEmitter<ChangeKeymapDescription>();
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
@ViewChild(PopoverComponent, {read: ElementRef}) popover: ElementRef;
popoverShown: boolean;
keyEditConfig: { moduleId: number, keyId: number };
@@ -82,6 +89,11 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
layers: Layer[];
keyPosition: ClientRect;
wrapPosition: ClientRect;
remapInfo: RemapInfo = {
remapOnAllKeymap: false,
remapOnAllLayer: false
};
private wrapHost: HTMLElement;
private keyElement: HTMLElement;
@@ -131,7 +143,6 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
this.layers = this.keymap.layers;
if (keymapChanges.isFirstChange() ||
keymapChanges.previousValue.abbreviation !== keymapChanges.currentValue.abbreviation) {
this.currentLayer = 0;
this.keybindAnimationEnabled = keymapChanges.isFirstChange();
} else {
this.keybindAnimationEnabled = true;
@@ -140,51 +151,50 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
}
onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void {
onKeyClick(event: SvgKeyboardKeyClickEvent): void {
if (!this.popoverShown && this.popoverEnabled) {
this.keyEditConfig = {
moduleId,
keyId
moduleId: event.moduleId,
keyId: event.keyId
};
this.selectedKey = {layerId: this.currentLayer, moduleId: event.moduleId, keyId: event.keyId};
const keyActionToEdit: KeyAction = this.layers[this.currentLayer].modules[event.moduleId].keyActions[event.keyId];
this.keyElement = event.keyTarget;
this.remapInfo = {
remapOnAllKeymap: event.shiftPressed,
remapOnAllLayer: event.altPressed
};
this.selectedKey = { layerId: this.currentLayer, moduleId, keyId };
const keyActionToEdit: KeyAction = this.layers[this.currentLayer].modules[moduleId].keyActions[keyId];
this.keyElement = keyTarget;
this.showPopover(keyActionToEdit);
}
}
onKeyHover(moduleId: number, event: MouseEvent, over: boolean, keyId: number): void {
onKeyHover(event: SvgKeyHoverEvent): void {
if (this.tooltipEnabled) {
const keyActionToEdit: KeyAction = this.layers[this.currentLayer].modules[moduleId].keyActions[keyId];
const keyActionToEdit: KeyAction = this.layers[this.currentLayer].modules[event.moduleId].keyActions[event.keyId];
if (over) {
this.showTooltip(keyActionToEdit, event);
if (event.over) {
this.showTooltip(keyActionToEdit, event.event);
} else {
this.hideTooltip();
}
}
}
onCapture(moduleId: number, keyId: number, captured: { code: number, left: boolean[], right: boolean[] }): void {
onCapture(event: SvgKeyboardCaptureEvent): void {
const keystrokeAction: KeystrokeAction = new KeystrokeAction();
const modifiers = captured.left.concat(captured.right).map(x => x ? 1 : 0);
keystrokeAction.scancode = captured.code;
keystrokeAction.modifierMask = 0;
for (let i = 0; i < modifiers.length; ++i) {
keystrokeAction.modifierMask |= modifiers[i] << this.mapper.modifierMapper(i);
}
keystrokeAction.scancode = event.captured.code;
keystrokeAction.modifierMask = mapLeftRigthModifierToKeyActionModifier(event.captured.left, event.captured.right);
this.store.dispatch(
KeymapActions.saveKey(
this.keymap,
this.currentLayer,
moduleId,
keyId,
event.moduleId,
event.keyId,
{
remapOnAllKeymap: false,
remapOnAllLayer: false,
remapOnAllKeymap: event.shiftPressed,
remapOnAllLayer: event.altPressed,
action: keystrokeAction
})
);

View File

@@ -0,0 +1,25 @@
import { Directive, ElementRef, HostListener } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '../../store';
import { OpenUrlInNewWindowAction } from '../../store/actions/app';
@Directive({
selector: 'a[externalUrl]'
})
export class ExternalUrlDirective {
constructor(private el: ElementRef,
private store: Store<AppState>) {
}
@HostListener('click', ['$event'])
onClick($event: MouseEvent): void {
$event.preventDefault();
$event.stopPropagation();
const anchor = this.el.nativeElement as HTMLAnchorElement;
if (anchor.href) {
this.store.dispatch(new OpenUrlInNewWindowAction(anchor.href));
}
}
}

View File

@@ -0,0 +1 @@
export * from './external-url.directive';

Some files were not shown because too many files have changed in this diff Show More