133 Commits

Author SHA1 Message Date
László Monda
6e1f0ded9e Bump Agent version to 1.1.3 and update changelog. 2018-04-06 16:26:21 +02:00
László Monda
d58386ef4b Reference firmware 8.1.5 2018-04-04 15:51:04 +02:00
László Monda
179c982bfb Make the Fn+Backspace shortcut of the QWERTY for PC keymap switch to the TES keymap for testing purposes. 2018-04-03 23:25:30 +02:00
László Monda
a7d07dbf4c Make factory-update.js allow the layout to be set. 2018-04-03 21:04:47 +02:00
László Monda
0ca922d24a Sleep 1s between sending reset and idle kboot commands. Hopefully this will always make the left half resume after firmware updates. 2018-04-03 00:49:51 +02:00
László Monda
a6f1aa15a5 Supply the correct configBufferId values to launchEepromTransfer() and get rid of the obsoleted eepromTransfer mapping. 2018-04-02 23:54:48 +02:00
László Monda
fc2d025cc4 Don't display the buffer related USB transfers of writeConfig() because they generate too much noise. 2018-04-02 23:32:13 +02:00
László Monda
8b5ae106bd Display kboot command names instead of numberic ids. 2018-04-02 23:20:07 +02:00
László Monda
44639bbf53 Make apply-config.js and get-module-state.js use read() instead of readSync() 2018-04-02 22:56:28 +02:00
László Monda
9fcce9234a Make getDeviceState await. Dump transfer descriptions before the actual transfers when the debug mode is on. 2018-04-02 21:35:47 +02:00
László Monda
b26fecfc7a Make factory-update.js switch keymap. 2018-04-02 18:28:12 +02:00
László Monda
1b15911783 Extract code to uhk.writeUca() 2018-04-02 17:31:27 +02:00
László Monda
148dd8d361 Rewrite writeHca() using the JavaScript USB API instead of TypeScript because the latter couldn't reopen the USB device. 2018-04-02 17:25:35 +02:00
László Monda
0d9ac50999 Move writeHca() to uhk.js 2018-04-02 14:47:48 +02:00
László Monda
e19e4bc5a4 Move the gist of write-hca.js into writeHca() 2018-04-02 14:43:11 +02:00
László Monda
bdd79a5a9a Clean up writeConfig() a bit. 2018-04-02 14:32:45 +02:00
László Monda
fb4e05fdc4 Dump USB reads and writes via writeDevice() 2018-04-02 00:30:03 +02:00
László Monda
01fcf9053a Strip down factory-update.js to its essentials. 2018-04-02 00:11:35 +02:00
László Monda
533c2f13d2 Copy update-firmwares.js as factory-update.js 2018-04-01 23:58:13 +02:00
László Monda
b9c32b46a9 Extract uhk.applyConfig() and uhk.launchEepromTransfer() 2018-04-01 23:55:40 +02:00
László Monda
f9b7260be6 Extract uhk.writeUserConfig() 2018-04-01 01:23:50 +02:00
László Monda
847694d590 Rewrite write-config.js using async/await. 2018-04-01 01:13:17 +02:00
László Monda
58178a5c7b Extract uhk.updateFirmwares() 2018-03-31 23:50:02 +02:00
László Monda
9b93b4dac5 Rename update-all-firmwares.js to update-firmwares.js 2018-03-31 23:24:34 +02:00
Róbert Kiss
478dac0621 build: extract electron dependencies to the root package.json (#593) 2018-03-31 22:11:41 +02:00
László Monda
f196fcdaa2 Make switch-keymap.js accept a keymap abbreviation. 2018-03-30 19:17:42 +02:00
László Monda
0b420ff516 Extract uhk.switchKeymap() 2018-03-30 18:59:50 +02:00
László Monda
7656af76e4 Add switch-keymap.js 2018-03-30 12:48:28 +02:00
László Monda
beed546ae4 Remove switch keymap actions that point to the TES keymap. 2018-03-29 19:09:00 +02:00
László Monda
bf94370f2f Add the "Export device configuration" button instead of the export link. Make the button export JSON by default and BIN when pressed with Shift. 2018-03-29 18:44:09 +02:00
László Monda
2476049681 Out of play, play/pause, stop, and pause only leave play/pause in the default configuration. 2018-03-29 18:12:08 +02:00
László Monda
f8d8b6d213 Remove Launch Browser, Launch Calculator, and Eject Disk key actions from the default configurations because they don't work reliably across OSes and very rarely used. 2018-03-29 14:51:06 +02:00
László Monda
05bbce1d50 Remove redundant stop media playback actions. 2018-03-29 13:27:13 +02:00
László Monda
32494fa228 Remove relative switch keymap actions. 2018-03-29 13:12:27 +02:00
László Monda
e0ce38988e Remove shut down key actions, only keep sleep key actions, and bind them to the \ key. 2018-03-29 12:54:51 +02:00
László Monda
5c660c549d Update the test keymap. This one should be the final version. 2018-03-28 20:56:56 +02:00
László Monda
510b914e26 Change terminology from download / upload to export / import for greater clarity. 2018-03-28 18:27:27 +02:00
László Monda
cf64fc0c08 Make the tooltip text regarding non-US characters easier to understand. 2018-03-25 22:24:33 +02:00
Róbert Kiss
b25bc9d81d feat: show firmware version of the device/modules on firmware page (#589) 2018-03-23 07:10:02 +01:00
Róbert Kiss
2f00a5eaf4 feat: enhance device firmware page (#588)
* feat: enhance device firmware page

* remove confirmation dialog from firmware upgrade buttons
2018-03-15 12:20:35 +01:00
László Monda
e8fe0f8d3e Fix menu scancode. (#586)
* Fix menu scancode.

* Change the old menu key scancode 118 to 101.

* validate scancodes
2018-03-11 22:56:12 +01:00
László Monda
e84dbf2c15 Bump Agent version to 1.1.2. Bump firmware version to 8.1.4. Update changelog. 2018-03-09 19:02:46 +01:00
Róbert Kiss
990ff8e980 ci: mac code sign (#585)
* ci: register certificate into the mac keychain

* try to not use yarn electron-builder installer on mac

* use -P

* debug electron-osx-sign

* use cscLink

* use xcode xcode9.3beta

* increase package.json version

* revert version to 1.1.1

* delete unused files

* format release.js file

* format release.js file

* format release.js file
2018-03-09 18:31:48 +01:00
Róbert Kiss
1ca8e67e52 try to use xcode 9.2 (#584) 2018-03-08 18:09:17 +01:00
Róbert Kiss
23cb583bf7 chore: upgrade lerna => 2.9.0 (#583) 2018-03-06 00:34:06 +01:00
Róbert Kiss
d5cc735b85 build: Windows code sign (#581)
* build: Add windows certificate

* temporary bump version

* change CSC_LINK settings

* rdp connection

* remove appveyor RDP block
2018-03-01 17:29:55 +01:00
László Monda
1981311136 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-02-28 00:31:48 +01:00
László Monda
bbb5d4a35b Add a notice regarding the lack of robustness of the firmware update process. 2018-02-28 00:31:06 +01:00
serge-rosov
58ef40fb02 year correction (#582) 2018-02-27 18:11:46 +01:00
Róbert Kiss
b8f35df155 build: switch off mac codesign 2018-02-24 23:33:22 +01:00
Róbert Kiss
c3e712851c build: Sign mac installer 2018-02-24 23:12:01 +01:00
László Monda
2eaa1e0634 Update package-lock.json 2018-02-19 17:11:16 +01:00
László Monda
6ee21bcd7a Fix get-debug-info.js 2018-02-18 03:44:17 +01:00
Róbert Kiss
10ae68ad4b fix(keymap): Fix undefined keymap serialization (#573) 2018-02-17 18:07:33 +01:00
László Monda
02044ae1d0 Bump version to 1.1.1 and update changelog. 2018-02-13 03:54:28 +01:00
László Monda
3f99d47bba Use firmware 8.1.2 2018-02-13 03:40:52 +01:00
László Monda
9beadb4aac Add keymap descriptions. 2018-02-13 03:24:06 +01:00
László Monda
d9fb7a4b42 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-02-13 03:05:18 +01:00
László Monda
83912ec21f Assign "switch to test keymap" action on all keymaps. 2018-02-13 03:04:36 +01:00
Róbert Kiss
6c7232a5ba feat(device): Make Agent able to unbrick bricked modules (#577) 2018-02-13 02:11:27 +01:00
László Monda
65fc8b5efb Tweak the scancode related help text. 2018-02-12 15:36:39 +01:00
Róbert Kiss
7a64191955 fix(config): Save reset user configuration in web module (#574) 2018-02-11 20:44:08 +01:00
Róbert Kiss
1a413c824e Fix 560 delete bind play macro action when macro delete (#576)
* fix(config): delete KeyAction binding of deleted macro

* refactor: use sorter import

* fix(macro): read the macro id from route params

* fix(keyAction): use NoneAction in keyAction mapping
2018-02-11 20:12:12 +01:00
Róbert Kiss
e545c9d67b feat(popover): Add Non-US help tooltip (#578) 2018-02-11 19:47:41 +01:00
László Monda
8650fef7ae Make reenumerate() more reliable. 2018-01-31 04:26:49 +01:00
László Monda
5c618869a2 Use updateDeviceFirmware(), reenumerate(), and updateModuleFirmware() in update-all-firmwares.js instead of forking processes. 2018-01-31 03:40:46 +01:00
László Monda
1b8d6949e0 Extract updateModuleFirmware() to uhk.js 2018-01-31 03:03:34 +01:00
László Monda
aabc0a8746 Use waitForKbootIdle() in update-module-firmware.js 2018-01-31 02:28:29 +01:00
László Monda
9589398834 Move waitForKbootIdle() to uhk.js 2018-01-31 02:25:37 +01:00
László Monda
933a715ea5 Extract waitForKbootIdle() 2018-01-31 02:22:41 +01:00
László Monda
df14e2d569 Extract uhk.jumpToModuleBootloader() and use it. 2018-01-31 01:17:28 +01:00
László Monda
4f8a0247d3 Call uhk.sendKbootCommandToModule() instead of forking send-kboot-command-to-module.js 2018-01-31 00:57:53 +01:00
László Monda
85ec5f6b6a Make update-module-firmware.js use reenumerate() and sendKbootCommandToModule() instead of forking more processes. 2018-01-31 00:17:27 +01:00
László Monda
739b830f47 Use uhk.writeDevice() in jump-to-module-bootloader.js 2018-01-31 00:09:16 +01:00
László Monda
482cff3d3b Extract sendKbootCommandToModule() from send-kboot-command-to-module.js to uhk.js 2018-01-30 23:44:25 +01:00
László Monda
9284ae5032 Use reenumerate() instead of forking reenumerate.js 2018-01-30 23:32:37 +01:00
László Monda
cac6fdc190 Move updateDeviceFirmware() from update-device-firmware.js to uhk.js 2018-01-30 21:48:42 +01:00
László Monda
ca9bf60a1b Extract updateDeviceFirmware() 2018-01-30 21:46:39 +01:00
László Monda
bb7edb8e4d Remove shared.js 2018-01-30 21:39:05 +01:00
László Monda
0d9c976eb8 Move execRetry() from shared.js to uhk.js 2018-01-30 21:37:27 +01:00
László Monda
288d4f75b6 Move getBlhostCmd() from shared.js to uhk.js 2018-01-30 21:35:35 +01:00
László Monda
73e07eae2d Move checkFirmwareImage() from shared.js to uhk.js 2018-01-30 21:28:00 +01:00
László Monda
8e620caac5 Use reenumerate() in update-device-firmware.js instead of forking reenumerate.js 2018-01-30 20:39:48 +01:00
László Monda
0d4e1acf76 Extract reenumerate() from reenumerate.js to uhk.js 2018-01-30 19:34:29 +01:00
László Monda
88c42d58b1 Extract reenumerate() as an async function in reenumerate.js 2018-01-30 18:36:16 +01:00
László Monda
5099e904fc Use uhk.writeDevice() in set-i2c-baud-rate.js 2018-01-30 17:30:12 +01:00
László Monda
5476f7c3a5 Extract I2C0_F. 2018-01-30 17:25:29 +01:00
László Monda
e0bb0bcca3 Add the async uhk.writeDevice() and use it in get-i2c-baud-rate.js 2018-01-30 17:21:50 +01:00
László Monda
124c3ec29b Simplify reenumerate.js mainly by using uint32ToArray() 2018-01-30 06:09:09 +01:00
László Monda
2310320b8a Replace pushUint32() with uint32ToArray() and don't use it where not needed. 2018-01-30 05:30:32 +01:00
László Monda
67346b4cda Simplify getTransferData() 2018-01-30 05:10:29 +01:00
Róbert Kiss
662ca0152f fix(keymap): Don't show keymap description in the key action popover (#572) 2018-01-29 23:40:41 +01:00
Róbert Kiss
6358528438 ci: use node 8.9.4 (#571)
Node 8.9.4 contain a few security update and use npm 5.6.0 so not need to
install npm in the build process. The build time will a few sec sorter
2018-01-29 23:20:07 +01:00
József Farkas
02f1053d46 feat(popover): sort keymaps and macros alphabetically (#534)
* feat(popover): sort keymaps and macros alphabetically

Closes #512

* small performance refactoring
2018-01-29 23:15:21 +01:00
Róbert Kiss
a44a7dc5f8 chore: use lodash-es version (#569)
lodash-es support tree shaking and the bundle size will near 2MB less
2018-01-29 23:02:17 +01:00
Róbert Kiss
02d57fdabf feat(keymap): add description to keymap (#559)
* feat(keymap): add description to keymaps

* add new feature request

* preserve new lines
2018-01-29 22:54:29 +01:00
Róbert Kiss
38f6688930 chore: upgrade electron => 1.7.11 (#568) 2018-01-29 21:26:32 +01:00
Róbert Kiss
6ca12d0ccd build: set Apple appId => com.ultimategadgetlabs.agent (#566) 2018-01-23 19:19:15 +01:00
László Monda
acd17ac657 Fix the background color of the toplevel device node. Fixes #552 2018-01-22 04:41:43 +01:00
László Monda
5393501f68 Update CHANGELOG.md 2018-01-16 19:43:10 +01:00
László Monda
99e020d66f Bump Agent version to 1.1.0 and update package.json and the changelog accordingly. 2018-01-15 19:02:29 +01:00
László Monda
2c74ce8d3e Reference firmware 8.1.0 and adopt the newly introduced "v" git tag prefix for firmware tags. 2018-01-15 12:04:11 +01:00
László Monda
3cd2d208b9 Make get-i2c-health.js output uptime and baud rate. 2018-01-15 01:37:52 +01:00
László Monda
d0cd30f915 Add get-i2c-health.js 2018-01-15 01:21:56 +01:00
László Monda
010a23aaeb Extract slaveI2cErrorBufferToString() 2018-01-15 01:07:23 +01:00
László Monda
c723fe2651 Simplify get-slave-i2c-errors.js by using padEnd() 2018-01-15 00:55:38 +01:00
László Monda
95caa58624 Utilize uhk.getUint* in get-slave-i2c-errors.js 2018-01-15 00:37:31 +01:00
László Monda
9089f088b6 Clean up get-left-firmware-version.js a bit. 2018-01-15 00:31:24 +01:00
László Monda
1aeb4e8326 Make get-left-firmware-version.js display module protocol version, too. 2018-01-15 00:07:30 +01:00
László Monda
96b9226adb Fix script to display the correct firmware version. 2018-01-15 00:02:57 +01:00
László Monda
7c065f4368 Remove --buspal speed specification because it gets disrespected by the firmware anyways. 2018-01-14 22:13:31 +01:00
Róbert Kiss
a8108b9abf revert: Revert auto write user configuration (#558) 2018-01-14 19:32:41 +01:00
László Monda
c7baa00720 Add get-uptime.js 2018-01-14 18:33:14 +01:00
László Monda
5cdf2282f8 Add {get,set}-i2c-baud-rate.js 2018-01-14 18:15:50 +01:00
László Monda
89221faf60 Add set-i2c-baud-rate.js 2018-01-14 00:45:51 +01:00
László Monda
3b70c84c61 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-01-13 19:33:28 +01:00
László Monda
5b1f4cb584 On the mouse speed section of the key action popover remove the bottom sentence and slightly rephrase the top sentence. 2018-01-13 19:30:08 +01:00
Mikko Lakomaa
3ee6c680a1 Agent menu (#540)
* Add generate version module script

* Remove Fork me on GitHub banner

* Add app-version.ts

* Revert "Add app-version.ts"

This reverts commit fe1a37e631.

* Add app-version.ts

* Add agent icon class

* Move settings component under agent folder

* Add AboutComponent

* Add agent routes

* Add index.ts for agent folder

* Fix agent folder imports in shared module

* Add agent menu to side menu, with Settings and About pages under it

* Fix agent icon alignment in side menu

* Simplify About page

* Make Agent menu 0 level in side menu

* Remove bottom Settings menu

* Fix Agent menu closing if My UHK is closed in side menu

* Fix version text alignment in auto update settings

* Remove github fork ribbon styles

* use package.json instead of app-version.ts

* fix OpenUrlInNewWindow naming

* fix lint request

* fix: firmware download url calculation
2018-01-13 17:10:21 +01:00
László Monda
fdcf64d5c6 Only display minutes in the I2C error logger script. 2018-01-13 00:06:18 +01:00
László Monda
6c327ee414 Add I2C logger script. 2018-01-11 02:57:49 +01:00
László Monda
b6bdd1486c Make update-module-firmware.js more robust and able to recover bricked modules (including the left half) by utilizing the newly added wait-for-kboot-idle.js 2018-01-10 03:15:19 +01:00
László Monda
bd5be98d99 Restore wdi-simple.exe and 50-uhk60-rules.cmd just in case. 2018-01-08 06:13:37 +01:00
László Monda
802e6a4649 Update README.md 2018-01-08 06:03:58 +01:00
László Monda
ae11c01725 Tidy up else clauses based on our coding standards. 2018-01-08 05:41:36 +01:00
Róbert Kiss
f0139c55ee feat(user-config): Upload user configuration from json/bin file (#545)
* feat(user-config): Upload user configuration from json/bin file

* fix error message

* remove file extension filter

* apply user config after loaded from file

* add file filter

* remove file filter
2018-01-08 05:29:31 +01:00
László Monda
b3f2e3451e Update README.md 2018-01-07 21:30:03 +01:00
Róbert Kiss
906beaac0e Update README.md (#548) 2018-01-07 21:27:07 +01:00
Andrew Kraut
46f855d1db Update readme (#547)
* Fix typos

* Update build instructions
2018-01-07 19:56:24 +01:00
László Monda
5341d953ff Fix statusCodesToStrings map. 2018-01-07 05:21:08 +01:00
László Monda
bd9a2a0eeb Make get-slave-i2c-errors.js display slave names and I2C error names. 2018-01-06 21:29:14 +01:00
László Monda
4c10954721 Add script which reads I2C errors. 2018-01-05 03:26:26 +01:00
Róbert Kiss
bbce1e0e0f fix(user-config): Validate device, keymap, and macro names (#543)
* fix(user-config): Validate device, keymap, and macro names

* fix device name renaming
2018-01-03 21:06:08 +01:00
László Monda
13f064229f Add keyAssigmentOrder array to uhk60-right/device.json 2018-01-02 04:05:26 +01:00
143 changed files with 9719 additions and 9888 deletions

2
.nvmrc
View File

@@ -1 +1 @@
8.9.1
8.9.4

View File

@@ -11,7 +11,7 @@ matrix:
fast_finish: true
include:
- os: osx
osx_image: xcode8.3
osx_image: xcode9.3beta
- os: linux
env: CC=clang CXX=clang++ npm_config_clang=1
compiler: clang
@@ -45,7 +45,6 @@ addons:
install:
- nvm install
- npm i -g npm@5.6.0
- npm install
before_script:

View File

@@ -4,15 +4,62 @@ All notable changes to this project will be documented in this file.
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
## [1.1.3] - 2018-04-06
Firmware: 8.1.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Show the firmware versions of the left and right keyboard halves on the firmware page.
- Fix menu scancode.
- Make the tooltip text regarding non-US characters easier to understand.
- On the Device Configuration page change terminology from download/upload to export/import for greater clarity.
## [1.1.2] - 2018-03-09
Firmware: 8.1.**4** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.4)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Fix the configuration serializer so that the correct key actions get serialized, and the save button always appears when needed.
- Add instructions to the firmware page to aid users.
- Fix code signing on OSX.
- Sign Agent on Windows.
## [1.1.1] - 2018-02-13
Firmware: 8.1.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.2)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Sign Agent on OSX resulting in easier installation.
- Add per-keymap description field.
- Sort keymaps and macros alphabetically within the key action popover.
- Add tooltip regarding non-US scancodes.
- When deleting a macro, also delete the relevant play macro actions.
- Make the reset configuration button persist the reset configuration in Agent-web.
- Make Agent able to unbrick bricked modules.
- Assign "switch to test keymap" action on all keymaps in the default configuration.
- Add keymap descriptions in the default configuration.
## [1.1.0] - 2018-01-15
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Only accept device, keymap, and macro names upon editing if their trimmed length is non-zero.
- Add diagnostics USB scripts, most notably /packages/usb/{get-i2c-health,set-i2c-baud-rate}.js, some utilizing new device protocol commands and properties. `DEVICEPROTOCOL:MINOR`
- Implement the Device -> Upload device configuration feature.
- Make update-module-firmware.js more robust and able to recover bricked modules (including the left half) by utilizing the newly added wait-for-kboot-idle.js. `DEVICEPROTOCOL:MINOR`
- Add the Agent -> About page containing the version number of Agent.
- On the mouse speed section of the key action popover, remove the now incorrect bottom sentence and slightly rephrase the top sentence.
- Remove --buspal speed specification argument because it gets disrespected by the firmware anyways.
- Fix get-left-firmware-version.js to display the correct firmware version.
## [1.0.4] - 2017-12-30
Firmware: [8.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/8.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: 8.0.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Add mouse speed settings.
## [1.0.3] - 2017-12-28
Firmware: [8.0.**0**](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/8.0.**0**) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: 8.0.**0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Add LED brightness settings.
- Some key actions, for example Left Arrow were displayed as text with modifiers and as icon without modifires. Now, they're always displayed as icons.
@@ -23,18 +70,18 @@ Firmware: [8.0.**0**](https://github.com/UltimateHackingKeyboard/firmware/releas
## [1.0.2] - 2017-12-25
Firmware: [**8.0.1**](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/8.0.1) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: **8.0.1**[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.1)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Fix firmware upgrade on Linux.
## [1.0.1] - 2017-12-22
Firmware: [7.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: 7.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Fix Linux privilege escalation when udev rules aren't set up.
## [1.0.0] - 2017-12-14
Firmware: [**7**.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: **7**.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- First release

View File

@@ -13,23 +13,29 @@ It's worth mentioning that Agent has two builds.
The **electron build** is the desktop application which is meant to be used if you have an actual UHK at hand. It starts with an opening screen which detects your UHK. You cannot get past this screen without connecting a UHK via USB.
The **web build** is meant to be used for demonstation purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
The **web build** is meant to be used for demonstration purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browsers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
The two builds share code as much as possible.
## Building the electron application
First up, make sure that node >=8.1.x and npm >=5.1.x are installed on your system. Next up:
### Step 1: Build Dependencies
You'll need Node.js LTS. Use your OS package manager to install it. [Check the NodeJS site for more info.](https://nodejs.org/en/download/package-manager/ "Installing Node.js via package manager") Mac OS users can simply `brew install node` to get both. Should you need multiple Node.js versions on the same computer, use Node Version Manager for [Mac/Linux](https://github.com/creationix/nvm) or for [Windows](https://github.com/coreybutler/nvm-windows)
You'll also need `libusb`.
On debian-based linux distros, `apt-get install libusb-dev libudev-dev g++` is sufficient.
On Mac OS, use `brew install libusb libusb-compat`.
For everyone else, use the appropriate package manager for your OS.
### Step 2: Build Environment
```
# Execute the following line on Linux. Use relevant package manager and package names on non-Debian based distros.
apt-get install libusb-dev libudev-dev g++
git clone git@github.com:UltimateHackingKeyboard/agent.git
cd agent
npm install
npm run build:electron
npm run electron
npm install # to install Node dependencies
npm run build:electron # to build the agent
npm run electron # to run the newly built agent
```
At this point, Agent should be running on your machine.

View File

@@ -1,8 +1,11 @@
os: unstable
clone_folder: c:\projects\uhk-agent
environment:
GH_TOKEN:
secure: 3IebpEKmC39codi1wT6dXx8mql4/mCL1JzZ7lir7GQ5MWRnCxlED2OXbiKHHigDV
CSC_LINK: c:\projects\uhk-agent\scripts\certs\windows-cert.p12
matrix:
- nodejs_version: "8"
@@ -18,7 +21,6 @@ shallow_clone: true
install:
- ps: Install-Product node $env:nodejs_version
- npm i -g npm@5.6.0
- choco install chromium
- set CI=true
- set PATH=%APPDATA%\npm;%PATH%

2834
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,11 @@
"private": true,
"author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js",
"version": "1.0.4",
"version": "1.1.3",
"firmwareVersion": "8.1.5",
"deviceProtocolVersion": "4.2.0",
"userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0",
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
"repository": {
"type": "git",
@@ -26,24 +30,26 @@
"@types/usb": "1.1.3",
"autoprefixer": "6.5.3",
"buffer": "5.0.6",
"copyfiles": "^2.0.0",
"copy-webpack-plugin": "4.0.1",
"core-js": "2.4.1",
"cross-env": "5.0.5",
"decompress": "4.2.0",
"decompress-tarbz2": "^4.1.1",
"devtron": "1.4.0",
"electron": "1.7.5",
"electron-builder": "19.45.5",
"electron-debug": "1.4.0",
"electron-devtools-installer": "2.2.0",
"electron-log": "2.2.9",
"electron-rebuild": "1.6.0",
"electron-settings": "3.1.2",
"electron": "1.8.4",
"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-settings": "3.1.4",
"electron-updater": "2.21.4",
"exports-loader": "0.6.3",
"file-loader": "0.10.0",
"fs-extra": "4.0.2",
"jsonfile": "4.0.0",
"lerna": "2.0.0",
"lerna": "2.9.0",
"mkdirp": "0.5.1",
"npm-run-all": "4.0.2",
"pre-commit": "1.2.2",
@@ -86,7 +92,6 @@
"server:web": "lerna exec --scope uhk-web npm start",
"server:electron": "lerna exec --scope uhk-web npm run server:renderer",
"electron": "lerna exec --scope uhk-agent npm start",
"electron:auto-write-config": "lerna exec --scope uhk-agent npm run auto-write-config",
"standard-version": "standard-version",
"pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites",

File diff suppressed because it is too large Load Diff

View File

@@ -17,12 +17,6 @@
"command-line-args": "4.0.7",
"decompress": "4.2.0",
"decompress-bzip2": "4.0.0",
"electron": "1.7.9",
"electron-is-dev": "0.1.2",
"electron-log": "2.2.9",
"electron-rebuild": "1.6.0",
"electron-settings": "3.1.2",
"electron-updater": "2.15.0",
"node-hid": "0.5.4",
"sudo-prompt": "7.0.0",
"tmp": "0.0.33",
@@ -36,7 +30,6 @@
},
"scripts": {
"start": "electron ./dist/electron-main.js",
"auto-write-config": "electron ./dist/electron-main.js --auto-write-config",
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
"install:build-deps": "cd ./dist && npm i",

View File

@@ -21,8 +21,7 @@ import * as isDev from 'electron-is-dev';
import { CommandLineInputs } from './models/command-line-inputs';
const optionDefinitions = [
{name: 'addons', type: Boolean},
{name: 'auto-write-config', type: Boolean}
{name: 'addons', type: Boolean}
];
const options: CommandLineInputs = commandLineArgs(optionDefinitions);

View File

@@ -1,4 +1,3 @@
export interface CommandLineInputs {
addons?: boolean;
'auto-write-config'?: boolean;
}

View File

@@ -15,9 +15,5 @@
},
"dependencies": {
"node-hid": "0.5.7"
},
"firmwareVersion": "8.0.0",
"deviceProtocolVersion": "4.0.0",
"userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0"
}
}

View File

@@ -1,7 +1,5 @@
import { BrowserWindow, ipcMain } from 'electron';
import { BrowserWindow, ipcMain, shell } from 'electron';
import { UhkHidDevice } from 'uhk-usb';
import { readFile } from 'fs';
import { join } from 'path';
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
import { MainServiceBase } from './main-service-base';
@@ -18,53 +16,30 @@ export class AppService extends MainServiceBase {
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
ipcMain.on(IpcEvents.app.exit, this.exit.bind(this));
ipcMain.on(IpcEvents.app.openUrl, this.openUrl.bind(this));
logService.info('[AppService] init success');
}
private async handleAppStartInfo(event: Electron.Event) {
this.logService.info('[AppService] getAppStartInfo');
const packageJson = await this.getPackageJson();
const response: AppStartInfo = {
commandLineArgs: {
addons: this.options.addons || false,
autoWriteConfig: this.options['auto-write-config'] || false
addons: this.options.addons || false
},
deviceConnected: this.uhkHidDeviceService.deviceConnected(),
hasPermission: this.uhkHidDeviceService.hasPermission(),
agentVersionInfo: {
version: packageJson.version,
firmwareVersion: packageJson.firmwareVersion,
deviceProtocolVersion: packageJson.deviceProtocolVersion,
moduleProtocolVersion: packageJson.moduleProtocolVersion,
userConfigVersion: packageJson.userConfigVersion,
hardwareConfigVersion: packageJson.hardwareConfigVersion
}
hasPermission: this.uhkHidDeviceService.hasPermission()
};
this.logService.info('[AppService] getAppStartInfo response:', response);
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
}
/**
* Read the package.json that delivered with the bundle. Do not use require('package.json')
* because the deploy process change the package.json after the build
* @returns {Promise<any>}
*/
private async getPackageJson(): Promise<any> {
return new Promise((resolve, reject) => {
readFile(join(__dirname, 'package.json'), {encoding: 'utf-8'}, (err, data) => {
if (err) {
return reject(err);
}
resolve(JSON.parse(data));
});
});
}
private exit() {
this.logService.info('[AppService] exit');
this.win.close();
}
private openUrl(event: Electron.Event, urls: Array<string>) {
shell.openExternal(urls[0]);
}
}

View File

@@ -1,5 +1,5 @@
import { ipcMain } from 'electron';
import { ConfigurationReply, DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { ConfigurationReply, DeviceConnectionState, HardwareModules, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
@@ -73,10 +73,15 @@ export class DeviceService {
try {
await this.device.waitUntilKeyboardBusy();
const result = await this.operations.loadConfigurations();
const modules: HardwareModules = {
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
};
response = {
success: true,
...result
...result,
modules
};
} catch (error) {
response = {

View File

@@ -10,7 +10,10 @@
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
},
"scripts": {
"build": "tsc",
"build": "run-s tsc copy:*",
"tsc": "tsc",
"copy:scancodes": "copyfiles ./src/config-serializer/config-items/scancodes.json dist",
"copy:secondary-roles": "copyfiles ./src/config-serializer/config-items/secondaryRole.json dist",
"test": "jasmine-ts --config=jasmine.json",
"coverage": "nyc jasmine-ts --config=jasmine.json"
},

View File

@@ -9,3 +9,6 @@ export * from './macro';
export * from './module';
export * from './module-configuration';
export * from './user-configuration';
export const SCANCODES = require('./scancodes.json');
export const SECONDARY_ROLES = require('./secondaryRole.json');

View File

@@ -7,6 +7,8 @@ import { SwitchLayerAction } from './switch-layer-action';
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
import { MouseAction } from './mouse-action';
import { PlayMacroAction } from './play-macro-action';
import { NoneAction } from './none-action';
import { isScancodeExists } from '../scancode-checker';
export class Helper {
@@ -25,7 +27,12 @@ export class Helper {
buffer.backtrack();
if (keyActionFirstByte >= KeyActionId.KeystrokeAction && keyActionFirstByte < KeyActionId.LastKeystrokeAction) {
return new KeystrokeAction().fromBinary(buffer);
const keystrokeAction = new KeystrokeAction().fromBinary(buffer);
if (isValidKeystrokeAction(keystrokeAction)) {
return keystrokeAction;
}
return new NoneAction();
}
switch (keyActionFirstByte) {
@@ -67,8 +74,14 @@ export class Helper {
}
switch (keyAction.keyActionType) {
case keyActionType.KeystrokeAction:
return new KeystrokeAction().fromJsonObject(keyAction);
case keyActionType.KeystrokeAction: {
const keystrokeAction = new KeystrokeAction().fromJsonObject(keyAction);
if (isValidKeystrokeAction(keystrokeAction)) {
return keystrokeAction;
}
return new NoneAction();
}
case keyActionType.SwitchLayerAction:
return new SwitchLayerAction().fromJsonObject(keyAction);
case keyActionType.SwitchKeymapAction:
@@ -77,8 +90,16 @@ export class Helper {
return new MouseAction().fromJsonObject(keyAction);
case keyActionType.PlayMacroAction:
return new PlayMacroAction().fromJsonObject(keyAction, macros);
case keyActionType.NoneAction:
return new NoneAction();
default:
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
}
}
}
function isValidKeystrokeAction(keystrokeAction: KeystrokeAction): boolean {
return keystrokeAction.hasSecondaryRoleAction() ||
keystrokeAction.hasActiveModifier() ||
keystrokeAction.hasScancode() && isScancodeExists(keystrokeAction.scancode);
}

View File

@@ -54,15 +54,12 @@ export class Module {
const noneAction = new NoneAction();
const keyActions: KeyAction[] = this.keyActions.map(keyAction => {
buffer.writeArray(this.keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
if (keyAction) {
return keyAction;
keyAction.toBinary(uhkBuffer, userConfiguration);
} else {
noneAction.toBinary(uhkBuffer);
}
return noneAction;
});
buffer.writeArray(keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
keyAction.toBinary(uhkBuffer, userConfiguration);
});
}

View File

@@ -0,0 +1,26 @@
import { SCANCODES } from './';
let scancodeMap: Map<number, any>;
export function isScancodeExists(scancode: number): boolean {
if (!scancodeMap) {
fillScancodeMap();
}
return scancodeMap.has(scancode);
}
function fillScancodeMap(): void {
scancodeMap = new Map<number, any>();
for (const scanGroup of SCANCODES) {
for (const child of scanGroup.children) {
if (child.additional && child.additional.scancode) {
scancodeMap.set(child.additional.scancode, child);
}
else {
scancodeMap.set(Number.parseInt(child.id), child);
}
}
}
}

View File

@@ -242,7 +242,7 @@
"text": "Delete"
},
{
"id": "118",
"id": "101",
"text": "Menu"
},
{

View File

@@ -1,12 +1,7 @@
import { CommandLineArgs } from './command-line-args';
import { VersionInformation } from './version-information';
export interface AppStartInfo {
commandLineArgs: CommandLineArgs;
deviceConnected: boolean;
hasPermission: boolean;
/**
* This property contains the version information of the deployed agent components
*/
agentVersionInfo: VersionInformation;
}

View File

@@ -1,4 +1,3 @@
export interface CommandLineArgs {
addons: boolean;
autoWriteConfig: boolean;
}

View File

@@ -1,6 +1,9 @@
import { HardwareModules } from './hardware-modules';
export interface ConfigurationReply {
success: boolean;
userConfiguration?: string;
hardwareConfiguration?: string;
modules?: HardwareModules;
error?: string;
}

View File

@@ -0,0 +1,4 @@
export interface HardwareModuleInfo {
firmwareVersion?: string;
moduleProtocolVersion?: string;
}

View File

@@ -0,0 +1,6 @@
import { HardwareModuleInfo } from './hardware-module-info';
export interface HardwareModules {
leftModuleInfo?: HardwareModuleInfo;
rightModuleInfo?: HardwareModuleInfo;
}

View File

@@ -5,3 +5,5 @@ export * from './app-start-info';
export * from './configuration-reply';
export * from './version-information';
export * from './device-connection-state';
export * from './hardware-modules';
export * from './hardware-module-info';

View File

@@ -1,5 +1,3 @@
export namespace Constants {
export const VENDOR_ID = 0x1D50;
export const PRODUCT_ID = 0x6122;
export const MAX_PAYLOAD_SIZE = 64;
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
}

View File

@@ -1,5 +1,6 @@
export { IpcEvents } from './ipcEvents';
export * from './log';
export * from './constants';
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
export function camelCaseToSentence(camelCasedText: string): string {

View File

@@ -3,6 +3,7 @@ class App {
public static readonly getAppStartInfo = 'app-get-start-info';
public static readonly getAppStartInfoReply = 'app-get-start-info-reply';
public static readonly exit = 'app-exit';
public static readonly openUrl = 'open-url';
}
class AutoUpdate {

View File

@@ -22,7 +22,7 @@ export enum UsbCommand {
GetDebugBuffer = 0x0b,
GetAdcValue = 0x0c,
SetLedPwmBrightness = 0x0d,
GetModuleProperties = 0x0e
GetModuleProperty = 0x0e
}
export enum EepromOperation {
@@ -39,7 +39,8 @@ export enum ConfigBufferId {
export enum DevicePropertyIds {
DeviceProtocolVersion = 0,
ProtocolVersions = 1,
ConfigSizes = 2
ConfigSizes = 2,
CurrentKbootCommand = 3
}
export enum EnumerationModes {
@@ -80,3 +81,7 @@ export enum KbootCommands {
ping = 1,
reset = 2
}
export enum ModulePropertyId {
protocolVersions = 0
}

View File

@@ -1,12 +1,24 @@
import { LogService } from 'uhk-common';
import { EnumerationModes, EnumerationNameToProductId, KbootCommands, ModuleSlotToI2cAddress, ModuleSlotToId } from './constants';
import { HardwareModuleInfo, LogService, UhkBuffer } from 'uhk-common';
import {
EnumerationModes,
EnumerationNameToProductId,
KbootCommands,
ModulePropertyId,
ModuleSlotToI2cAddress,
ModuleSlotToId
} from './constants';
import * as path from 'path';
import * as fs from 'fs';
import { UhkBlhost } from './uhk-blhost';
import { UhkHidDevice } from './uhk-hid-device';
import { snooze } from './util';
import { convertBufferToIntArray, getTransferBuffers, DevicePropertyIds, UsbCommand, ConfigBufferId
} from '../index';
import {
convertBufferToIntArray,
getTransferBuffers,
DevicePropertyIds,
UsbCommand,
ConfigBufferId
} from '../index';
import { LoadConfigurationsResult } from './models/load-configurations-result';
export class UhkOperations {
@@ -33,7 +45,7 @@ export class UhkOperations {
this.logService.debug('[UhkOperations] Start flashing left module firmware');
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`];
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf},100k`];
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf}`];
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
this.device.close();
@@ -42,6 +54,13 @@ export class UhkOperations {
await snooze(1000);
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
this.device.close();
const leftModuleBricked = await this.waitForKbootIdle();
if (!leftModuleBricked) {
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
return;
}
await this.device.reenumerate(EnumerationModes.Buspal);
this.device.close();
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
@@ -161,6 +180,70 @@ export class UhkOperations {
}
}
public async waitForKbootIdle(): Promise<boolean> {
const timeoutTime = new Date(new Date().getTime() + 30000);
while (new Date() < timeoutTime) {
const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, DevicePropertyIds.CurrentKbootCommand]));
this.device.close();
if (buffer[1] === 0) {
return true;
}
// tslint:disable-next-line: max-line-length
this.logService.info('[DeviceOperation] Cannot ping the bootloader. Please reconnect the left keyboard half. It probably needs several tries, so keep reconnecting until you see this message.');
await snooze(1000);
}
return false;
}
public async getLeftModuleVersionInfo(): Promise<HardwareModuleInfo> {
try {
this.logService.debug('[DeviceOperation] USB[T]: Read left module version information');
const command = new Buffer([
UsbCommand.GetModuleProperty,
ModuleSlotToId.leftHalf,
ModulePropertyId.protocolVersions
]);
const buffer = await this.device.write(command);
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
// skip the first 2 byte
uhkBuffer.readUInt16();
return {
moduleProtocolVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
};
}
catch (error) {
this.logService.error('[DeviceOperation] Could not read left module version information', error);
}
return {
moduleProtocolVersion: '',
firmwareVersion: ''
};
}
public async getRightModuleVersionInfo(): Promise<HardwareModuleInfo> {
this.logService.debug('[DeviceOperation] USB[T]: Read right module version information');
const command = new Buffer([UsbCommand.GetProperty, DevicePropertyIds.ProtocolVersions]);
const buffer = await this.device.write(command);
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
// skip the first byte
uhkBuffer.readUInt8();
return {
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
};
}
/**
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
* @param {string} json - UserConfiguration in JSON format

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,7 @@
"@types/jasmine": "2.5.53",
"@types/jasminewd2": "2.0.2",
"@types/jquery": "3.2.9",
"@types/lodash-es": "4.17.0",
"@types/node-hid": "0.5.2",
"@types/usb": "1.1.3",
"angular-confirmation-popover": "3.2.0",
@@ -67,7 +68,7 @@
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "0.2.2",
"less-loader": "4.0.5",
"lodash": "4.17.4",
"lodash-es": "4.17.4",
"ng2-dragula": "1.5.0",
"ng2-nouislider": "^1.7.6",
"ng2-select2": "1.0.0-beta.10",

View File

@@ -7,9 +7,6 @@
<div id="main-content" class="main-content">
<router-outlet></router-outlet>
</div>
<div class="github-fork-ribbon" *ngIf="!(runningInElectron$ | async)">
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
</div>
<notifier-container></notifier-container>
<progress-button class="save-to-keyboard-button"
*ngIf="(saveToKeyboardState$ | async).showButton"

View File

@@ -1,36 +1,3 @@
/* GitHub ribbon */
.github-fork-ribbon {
background-color: #a00;
overflow: hidden;
white-space: nowrap;
position: fixed;
right: -50px;
bottom: 40px;
z-index: 2000;
/* stylelint-disable indentation */
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-box-shadow: 0 0 10px #888;
-moz-box-shadow: 0 0 10px #888;
box-shadow: 0 0 10px #888;
/* stylelint-enable indentation */
a {
border: 1px solid #faa;
color: #fff;
display: block;
font: bold 81.25% 'Helvetica Neue', Helvetica, Arial, sans-serif;
margin: 1px 0;
padding: 10px 50px;
text-align: center;
text-decoration: none;
text-shadow: 0 0 5px #444;
}
}
main-app {
min-height: 100%;
height: 100%;

View File

@@ -12,7 +12,7 @@ import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
import { MainPage } from './pages/main-page/main.page';
import { settingsRoutes } from './components/settings/settings.routes';
import { agentRoutes } from './components/agent/agent.routes';
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
@@ -42,7 +42,7 @@ const appRoutes: Routes = [
...keymapRoutes,
...macroRoutes,
...addOnRoutes,
...settingsRoutes
...agentRoutes
]
}
];

View File

@@ -0,0 +1,10 @@
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="uhk-icon uhk-icon-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>
</div>

View File

@@ -0,0 +1,20 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
width: 100%;
}
.agent {
&-version {
margin-bottom: 1rem;
span {
font-weight: bold;
}
}
}
.link-github {
cursor: pointer;
}

View File

@@ -0,0 +1,27 @@
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',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss'],
host: {
'class': 'container-fluid'
}
})
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));
}
}

View File

@@ -0,0 +1,15 @@
import { Routes } from '@angular/router';
import { SettingsComponent } from './settings/settings.component';
import { AboutComponent } from './about/about.component';
export const agentRoutes: Routes = [
{
path: 'settings',
component: SettingsComponent
},
{
path: 'about',
component: AboutComponent
}
];

View File

@@ -0,0 +1,3 @@
export * from './agent.routes';
export * from './about/about.component';
export * from './settings/settings.component';

View File

@@ -2,13 +2,14 @@ import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../store';
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../../store';
import {
CheckForUpdateNowAction,
ToggleCheckForUpdateOnStartupAction,
TogglePreReleaseFlagAction
} from '../../store/actions/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
} from '../../../store/actions/auto-update-settings';
import { AutoUpdateSettings } from '../../../models/auto-update-settings';
import { getVersions } from '../../../util';
@Component({
selector: 'settings',
@@ -19,8 +20,7 @@ import { AutoUpdateSettings } from '../../models/auto-update-settings';
}
})
export class SettingsComponent {
// TODO: From where do we get the version number? The electron gives back in main process, but the web...
version = '1.0.0';
version: string = getVersions().version;
autoUpdateSettings$: Observable<AutoUpdateSettings>;
checkingForUpdate$: Observable<boolean>;

View File

@@ -19,7 +19,7 @@
<div class="form-group">
<label class="col-sm-2 control-label">Version:</label>
<div class="col-sm-10">
<p class="form-control-static">{{version}}</p>
<p>{{version}}</p>
</div>
</div>

View File

@@ -9,14 +9,16 @@
<ul class="list-unstyled btn-list">
<li>
Download device configuration in
<span role="button" class="btn-link" (click)="saveConfigurationInJSONFormat()">JSON</span> or
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format.
<button class="btn btn-default"
(click)="exportUserConfiguration($event)">Export device configuration
</button>
</li>
<li>
<button class="btn btn-default"
>Upload device configuration
</button>
<label class="btn btn-default btn-file">
Import device configuration
<input type="file"
(change)="changeFile($event)">
</label>
</li>
<li>
<button class="btn btn-danger"

View File

@@ -3,7 +3,11 @@ import { Store } from '@ngrx/store';
import { AppState } from '../../../store';
import { ResetUserConfigurationAction } from '../../../store/actions/device';
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from '../../../store/actions/user-config';
import {
LoadUserConfigurationFromFileAction,
SaveUserConfigInBinaryFileAction,
SaveUserConfigInJsonFileAction
} from '../../../store/actions/user-config';
@Component({
selector: 'device-settings',
@@ -29,4 +33,25 @@ export class DeviceConfigurationComponent {
saveConfigurationInBINFormat() {
this.store.dispatch(new SaveUserConfigInBinaryFileAction());
}
exportUserConfiguration(event: MouseEvent) {
if (event.shiftKey) {
this.saveConfigurationInBINFormat();
} else {
this.saveConfigurationInJSONFormat();
}
}
changeFile(event): void {
const files = event.srcElement.files;
const fileReader = new FileReader();
fileReader.onloadend = function () {
const arrayBuffer = new Uint8Array(fileReader.result);
this.store.dispatch(new LoadUserConfigurationFromFileAction({
filename: event.srcElement.value,
data: Array.from(arrayBuffer)
}));
}.bind(this);
fileReader.readAsArrayBuffer(files[0]);
}
}

View File

@@ -6,23 +6,36 @@
<i class="fa fa-sliders"></i>
<span>Firmware</span>
</h1>
<p>
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
<button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmware()">Flash firmware
</button>
Firmware {{ hardwareModules.leftModuleInfo.firmwareVersion }} is running on the left keyboard half.<br>
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
</p>
<p>
<i>
Please note that the firmware update process may sometimes fail. If if fails then
simply retry until it succeeds. If the left half becomes unresponsive after a failed
update then retry and follow the instructions displayed during the update to fix it.
We'll make the firmware update process more robust.
</i>
</p>
<p>
Flash firmware file <input id="firmware-file-select"
type="file"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(change)="changeFile($event)">
<button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmwareWithFile()">Flash firmware
(click)="onUpdateFirmware()">
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
</button>
<label class="btn btn-primary btn-file"
[class.disabled]="flashFirmwareButtonDisbabled$ | async">
Choose firmware file and flash it
<input id="firmware-file-select"
type="file"
accept=".tar.bz2"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(change)="changeFile($event)">
</label>
</p>
</div>

View File

@@ -2,9 +2,16 @@ import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { VersionInformation } from 'uhk-common';
import { HardwareModules, VersionInformation } from 'uhk-common';
import { AppState, firmwareOkButtonDisabled, flashFirmwareButtonDisbabled, getAgentVersionInfo, xtermLog } from '../../../store';
import {
AppState,
firmwareOkButtonDisabled,
flashFirmwareButtonDisbabled,
getAgentVersionInfo,
getHardwareModules,
xtermLog
} from '../../../store';
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
import { XtermLog } from '../../../models/xterm-log';
@@ -22,8 +29,9 @@ export class DeviceFirmwareComponent implements OnDestroy {
xtermLogSubscription: Subscription;
getAgentVersionInfo$: Observable<VersionInformation>;
firmwareOkButtonDisabled$: Observable<boolean>;
hardwareModulesSubscription: Subscription;
hardwareModules: HardwareModules;
arrayBuffer: Uint8Array;
@ViewChild('scrollMe') divElement: ElementRef;
constructor(private store: Store<AppState>) {
@@ -38,24 +46,20 @@ export class DeviceFirmwareComponent implements OnDestroy {
});
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
this.hardwareModules = data;
});
}
ngOnDestroy(): void {
this.xtermLogSubscription.unsubscribe();
this.hardwareModulesSubscription.unsubscribe();
}
onUpdateFirmware(): void {
this.store.dispatch(new UpdateFirmwareAction());
}
onUpdateFirmwareWithFile(): void {
if (!this.arrayBuffer) {
return;
}
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
}
onOkButtonClick(): void {
this.store.dispatch(new UpdateFirmwareOkButtonAction());
}
@@ -64,14 +68,13 @@ export class DeviceFirmwareComponent implements OnDestroy {
const files = event.srcElement.files;
if (files.length === 0) {
this.arrayBuffer = null;
return;
}
const fileReader = new FileReader();
fileReader.onloadend = function () {
this.arrayBuffer = new Uint8Array(fileReader.result);
const arrayBuffer = new Uint8Array(fileReader.result);
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(arrayBuffer)));
}.bind(this);
fileReader.readAsArrayBuffer(files[0]);
}

View File

@@ -0,0 +1,31 @@
<div class="text-center">
<span *ngIf="showPlaceholder"
class="placeholder">
<span (click)="editText()">{{ placeholder }}</span>
</span>
<span *ngIf="showText"
class="editable">
<span (click)="editText()"
[innerHtml]="displayText"></span>
</span>
</div>
<div *ngIf="editing">
<textarea class="text-editor"
[(ngModel)]="text"
autofocus
(keydown.control.enter)="keydownEnter()"
(keydown.alt.enter)="keydownEnter()"></textarea>
<div class="pull-right buttons">
<button class="btn btn-danger"
(click)="cancelEditText()">
Cancel
</button>
<button class="btn btn-primary"
(click)="saveText()"
[disabled]="isSaveDisabled">
Update description
</button>
</div>
</div>

View File

@@ -0,0 +1,26 @@
:host {
margin-top: 0.5em;
span.placeholder {
color: gray;
display: inline-block;
.glyphicon {
color: black;
}
}
span.editable,
span.placeholder {
cursor: pointer;
}
textarea.text-editor {
display: block;
width: 100%;
}
.buttons {
margin-top: 0.5em;
}
}

View File

@@ -0,0 +1,82 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'editable-text',
templateUrl: './editable-text.component.html',
styleUrls: ['./editable-text.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditableTextComponent), multi: true}
]
})
export class EditableTextComponent implements ControlValueAccessor {
@Input() placeholder = 'No editable content';
text: string;
originalText: string;
editing = false;
get isSaveDisabled(): boolean {
return !this.text || this.text.trim().length === 0;
}
get displayText(): string {
return this.text && this.text.replace(/\n/g, '<br>');
}
constructor(private cdr: ChangeDetectorRef) {
}
writeValue(obj: any): void {
if (this.text === obj) {
return;
}
this.text = obj;
this.cdr.markForCheck();
}
registerOnChange(fn: any): void {
this.textChange = fn;
}
registerOnTouched(fn: any): void {
}
saveText(): void {
this.originalText = null;
this.editing = false;
this.textChange(this.text);
}
editText(): void {
this.originalText = this.text;
this.editing = true;
}
cancelEditText(): void {
this.text = this.originalText;
this.editing = false;
}
keydownEnter(): void {
if (this.isSaveDisabled) {
return;
}
this.saveText();
}
get showPlaceholder(): boolean {
return !this.editing && !this.text;
}
get showText(): boolean {
return !this.editing && !!this.text;
}
private textChange: any = () => {
}
}

View File

@@ -7,8 +7,11 @@
[selectedKey]="selectedKey"
[selected]="selectedKey?.layerId === index"
[keyboardLayout]="keyboardLayout"
[description]="description"
[showDescription]="true"
(keyClick)="keyClick.emit($event)"
(keyHover)="keyHover.emit($event)"
(capture)="capture.emit($event)"
(descriptionChanged)="descriptionChanged.emit($event)"
>
</svg-keyboard>

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 809 B

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
import { Layer } from 'uhk-common';
@@ -81,11 +81,14 @@ export class KeyboardSliderComponent implements OnChanges {
@Input() halvesSplit: boolean;
@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() descriptionChanged = new EventEmitter<string>();
layerAnimationState: AnimationKeyboard[];
ngOnChanges(changes: SimpleChanges) {
if (changes['layers']) {
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');

View File

@@ -4,7 +4,8 @@
(downloadClick)="downloadKeymap()"></keymap-header>
<svg-keyboard-wrap [keymap]="keymap$ | async"
[halvesSplit]="keyboardSplit"
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap>
[keyboardLayout]="keyboardLayout$ | async"
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
</ng-template>
<div *ngIf="!(keymap$ | async)" class="not-found">

View File

@@ -18,6 +18,8 @@ import { AppState, getKeyboardLayout } from '../../../store';
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { KeymapActions } from '../../../store/actions';
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
@Component({
selector: 'keymap-edit',
@@ -64,7 +66,7 @@ export class KeymapEditComponent {
const keymap = latest[0];
const exportableJSON = latest[1];
const fileName = keymap.name + '_keymap.json';
saveAs(new Blob([exportableJSON], { type: 'application/json' }), fileName);
saveAs(new Blob([exportableJSON], {type: 'application/json'}), fileName);
});
}
@@ -73,6 +75,10 @@ export class KeymapEditComponent {
this.keyboardSplit = !this.keyboardSplit;
}
descriptionChanged(event: ChangeKeymapDescription): void {
this.store.dispatch(new KeymapActions.EditDescriptionAction(event));
}
private toExportableJSON(keymap: Keymap): Observable<any> {
return this.store
.let(getUserConfiguration())

View File

@@ -74,7 +74,7 @@ export class KeymapHeaderComponent implements OnChanges {
}
editKeymapName(name: string) {
if (name.length === 0) {
if (!util.isValidName(name)) {
this.setName();
return;
}

View File

@@ -13,5 +13,5 @@
</ng-template>
<div *ngIf="!macro" class="not-found">
There is no macro with id {{ route.params.select('id') | async }}.
There is no macro with id {{ macroId }}.
</div>

View File

@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/pluck';
import { MacroActions } from '../../../store/actions';
import { AppState } from '../../../store/index';
import { AppState } from '../../../store';
import { getMacro } from '../../../store/reducers/user-configuration';
@Component({
@@ -21,13 +21,17 @@ import { getMacro } from '../../../store/reducers/user-configuration';
export class MacroEditComponent implements OnDestroy {
macro: Macro;
isNew: boolean;
macroId: number;
private subscription: Subscription;
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
this.subscription = route
.params
.pluck<{}, string>('id')
.switchMap((id: string) => store.let(getMacro(+id)))
.switchMap((id: string) => {
this.macroId = +id;
return store.let(getMacro(this.macroId));
})
.subscribe((macro: Macro) => {
this.macro = macro;
});

View File

@@ -59,7 +59,7 @@ export class MacroHeaderComponent implements AfterViewInit, OnChanges {
}
editMacroName(name: string) {
if (name.length === 0) {
if (!util.isValidName(name)) {
this.setName();
return;
}

View File

@@ -7,6 +7,10 @@
[width]="200"
[options]="options"
></select2>
<icon name="question-circle"
data-toggle="tooltip"
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout."
data-placement="bottom"></icon>
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
</div>
<div class="modifier-options">

View File

@@ -10,6 +10,10 @@
position: relative;
top: 2px;
}
icon {
display: inline-block;
};
}
.modifier-options {

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnChanges } from '@angular/core';
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
import { KeyAction, KeystrokeAction, KeystrokeType } from 'uhk-common';
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
import { Tab } from '../tab';
import { MapperService } from '../../../../services/mapper.service';
@@ -35,8 +35,8 @@ export class KeypressTabComponent extends Tab implements OnChanges {
id: '0',
text: 'None'
}];
this.scanCodeGroups = this.scanCodeGroups.concat(require('./scancodes.json'));
this.secondaryRoleGroups = require('./secondaryRole.json');
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];

View File

@@ -83,7 +83,7 @@
</div>
<div *ngSwitchCase="3" class="mouse__config mouse__config--speed text-center">
<div class="help-text--mouse-speed text-left">
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate the speed of the action.</p>
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate its speed.</p>
</div>
<div class="btn-group btn-group-lg" role="group">
<button class="btn btn-default"
@@ -101,9 +101,6 @@
<span>Accelerate</span>
</button>
</div>
<div class="help-text--mouse-speed last-help text-left">
<p>You can set the multiplier in the <a [routerLink]="['/settings']" title="Settings">settings</a>.</p>
</div>
</div>
<div *ngSwitchDefault>
</div>

View File

@@ -47,16 +47,6 @@
}
}
.help-text--mouse-speed {
margin-bottom: 2rem;
font-size: 0.9em;
color: #666;
p {
margin: 0;
}
}
.details {
.btn-placeholder {
visibility: hidden;
@@ -78,8 +68,3 @@
}
}
}
.help-text--mouse-speed.last-help {
margin-bottom: 0;
margin-top: 2rem;
}

View File

@@ -1,2 +0,0 @@
export * from './settings.component';
export * from './settings.routes';

View File

@@ -1,10 +0,0 @@
import { Routes } from '@angular/router';
import { SettingsComponent } from './settings.component';
export const settingsRoutes: Routes = [
{
path: 'settings',
component: SettingsComponent
}
];

View File

@@ -117,14 +117,27 @@
</li>
</ul>
</li>
</ul>
</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="fa fa-chevron-up pull-right"
(click)="toggleHide($event, 'agent')"></i>
</div>
<ul [@toggler]="animation['agent']">
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/settings']"
[class.disabled]="updatingFirmware$ | async">Settings</a>
</div>
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/about']"
[class.disabled]="updatingFirmware$ | async">About</a>
</div>
</li>
</ul>
</li>
</ul>
<ul class="menu--bottom" *ngIf="runInElectron$ | async">
<li class="sidebar__level-1--item" [routerLinkActive]="['active']">
<a class="sidebar__level-1" [routerLink]="['/settings']">
<i class="fa fa-gear"></i> Settings
</a>
</li>
</ul>

View File

@@ -9,6 +9,10 @@
a {
color: #333;
&.disabled {
opacity: 0.65;
}
}
// General list styles for the sidebar-menu.
@@ -64,6 +68,10 @@ ul {
display: none;
cursor: pointer;
}
.uhk-icon-agent-icon {
margin-left: -3px;
}
}
&__level-2 {
@@ -108,6 +116,10 @@ ul {
&:focus {
text-decoration: none;
}
&.disabled {
opacity: 0.65;
}
}
}
}
@@ -160,12 +172,12 @@ ul {
padding: 0;
margin: 0 0.25rem;
text-overflow: ellipsis;
background-color: inherit;
background-color: transparent;
&:focus {
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
border-color: transparent;
background-color: inherit;
background-color: transparent;
}
}
}

View File

@@ -56,17 +56,8 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
addon: 'active'
};
this.keymaps$ = store.let(getKeymaps())
.map(keymaps => keymaps.slice()) // Creating a new array reference, because the sort is working in place
.do((keymaps: Keymap[]) => {
keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
});
this.macros$ = store.let(getMacros())
.map(macros => macros.slice()) // Creating a new array reference, because the sort is working in place
.do((macros: Macro[]) => {
macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
});
this.keymaps$ = store.let(getKeymaps());
this.macros$ = store.let(getMacros());
this.showAddonMenu$ = this.store.select(showAddonMenu);
this.runInElectron$ = this.store.select(runningInElectron);
@@ -118,7 +109,11 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
this.store.dispatch(MacroActions.addMacro());
}
editDeviceName(name): void {
editDeviceName(name: string): void {
if (!util.isValidName(name) || name.trim() === this.deviceNameValue) {
this.setDeviceName();
return;
}
this.store.dispatch(new RenameUserConfigurationAction(name));
}

View File

@@ -1,16 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
<svg:g svg-module *ngFor="let module of modules; let i = index"
[coverages]="module.coverages"
[keyboardKeys]="module.keyboardKeys"
[keybindAnimationEnabled]="keybindAnimationEnabled"
[capturingEnabled]="capturingEnabled"
[attr.transform]="module.attributes.transform"
[keyActions]="moduleConfig[i].keyActions"
[selectedKey]="selectedKey"
[@split]="moduleAnimationStates[i]"
[selected]="selectedKey?.moduleId === i"
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
(capture)="onCapture(i, $event.index, $event.captured)"
[coverages]="module.coverages"
[keyboardKeys]="module.keyboardKeys"
[keybindAnimationEnabled]="keybindAnimationEnabled"
[capturingEnabled]="capturingEnabled"
[attr.transform]="module.attributes.transform"
[keyActions]="moduleConfig[i].keyActions"
[selectedKey]="selectedKey"
[@split]="moduleAnimationStates[i]"
[selected]="selectedKey?.moduleId === i"
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
(capture)="onCapture(i, $event.index, $event.captured)"
/>
</svg>
<editable-text *ngIf="showDescription"
[ngModel]="description"
(ngModelChange)="descriptionChanged.emit($event)"
placeholder="No description provided for this keymap."></editable-text>

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,5 +1,10 @@
:host {
display: flex;
display: block;
width: 100%;
position: relative;
}
editable-text {
padding-left: 2em;
padding-right: 2em;
display: block;
}

View File

@@ -31,9 +31,12 @@ export class SvgKeyboardComponent implements OnInit {
@Input() selected: boolean;
@Input() halvesSplit: boolean;
@Input() keyboardLayout = KeyboardLayout.ANSI;
@Input() description: string;
@Input() showDescription = false;
@Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter();
@Output() descriptionChanged = new EventEmitter<string>();
modules: SvgModule[];
viewBox: string;

View File

@@ -7,9 +7,11 @@
[selectedKey]="selectedKey"
[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)"
(descriptionChanged)="onDescriptionChanged($event)"
></keyboard-slider>
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>

View File

@@ -2,14 +2,16 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
Renderer,
EventEmitter,
HostBinding,
HostListener,
Input,
OnChanges,
OnInit,
ViewChild,
SimpleChanges
Output,
Renderer,
SimpleChanges,
ViewChild
} from '@angular/core';
import { Observable } from 'rxjs/Observable';
@@ -25,10 +27,10 @@ import {
KeystrokeAction,
Layer,
LayerName,
SecondaryRoleAction,
MouseAction,
MouseActionParam,
PlayMacroAction,
SecondaryRoleAction,
SwitchKeymapAction,
SwitchLayerAction
} from 'uhk-common';
@@ -38,6 +40,7 @@ import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions';
import { PopoverComponent } from '../../popover';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
interface NameValuePair {
name: string;
@@ -56,6 +59,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
@Input() tooltipEnabled: boolean = false;
@Input() halvesSplit: boolean;
@Input() keyboardLayout: KeyboardLayout.ANSI;
@Output() descriptionChanged = new EventEmitter<ChangeKeymapDescription>();
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
@@ -237,6 +241,13 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
return this.currentLayer;
}
onDescriptionChanged(description: string): void {
this.descriptionChanged.emit({
description,
abbr: this.keymap.abbreviation
});
}
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
if (keyAction instanceof KeystrokeAction) {
const keystrokeAction: KeystrokeAction = keyAction;

View File

@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Notification } from 'uhk-common';
import { AppState, getUndoableNotification } from '../../store/index';
import { AppState, getUndoableNotification } from '../../store';
import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app';
@Component({

View File

@@ -0,0 +1,16 @@
import { AfterViewInit, Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[autofocus]'
})
export class Autofocus implements AfterViewInit {
constructor(private el: ElementRef) {
}
ngOnInit() {
}
ngAfterViewInit() {
this.el.nativeElement.focus();
}
}

View File

@@ -0,0 +1,4 @@
export interface ChangeKeymapDescription {
abbr: string;
description: string;
}

View File

@@ -0,0 +1,4 @@
export interface UploadFileData {
filename: string;
data: Array<number>;
}

View File

@@ -26,6 +26,11 @@ export class AppRendererService {
this.ipcRenderer.send(IpcEvents.app.exit);
}
openUrl(url: string): void {
this.logService.info(`[AppRendererService] open url: ${url}`);
this.ipcRenderer.send(IpcEvents.app.openUrl, url);
}
private registerEvents() {
this.ipcRenderer.on(IpcEvents.app.getAppStartInfoReply, (event: string, arg: AppStartInfo) => {
this.dispachStoreAction(new ProcessAppStartInfoAction(arg));

View File

@@ -94,7 +94,7 @@ export class CaptureService {
this.mapping.set(88, 27); // X
this.mapping.set(89, 28); // Y
this.mapping.set(90, 29); // Z
this.mapping.set(93, 118); // Menu
this.mapping.set(93, 101); // Menu
this.mapping.set(96, 98); // Num pad 0
this.mapping.set(97, 89); // Num pad 1
this.mapping.set(98, 90); // Num pad 2

View File

@@ -190,6 +190,7 @@ export class MapperService {
this.basicScanCodeTextMap.set(98, ['Insert', '0']);
this.basicScanCodeTextMap.set(99, ['Del', '.']);
this.basicScanCodeTextMap.set(100, ['ISO key', '|']);
this.basicScanCodeTextMap.set(101, ['Menu']);
this.basicScanCodeTextMap.set(104, ['F13']);
this.basicScanCodeTextMap.set(105, ['F14']);
this.basicScanCodeTextMap.set(106, ['F15']);
@@ -202,7 +203,6 @@ export class MapperService {
this.basicScanCodeTextMap.set(113, ['F22']);
this.basicScanCodeTextMap.set(114, ['F23']);
this.basicScanCodeTextMap.set(115, ['F24']);
this.basicScanCodeTextMap.set(118, ['Menu']);
this.basicScanCodeTextMap.set(176, ['00']);
this.basicScanCodeTextMap.set(177, ['000']);
@@ -236,7 +236,7 @@ export class MapperService {
this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left');
this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down');
this.basicScancodeIcons.set(82, 'icon-kbd__mod--arrow-up');
this.basicScancodeIcons.set(118, 'icon-kbd__mod--menu');
this.basicScancodeIcons.set(101, 'icon-kbd__mod--menu');
this.mediaScancodeIcons = new Map<number, string>();
this.mediaScancodeIcons.set(138, 'icon-kbd__fn--browser');

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ import {
} from './components/popover/tab';
import { CaptureKeystrokeButtonComponent } from './components/popover/widgets/capture-keystroke';
import { IconComponent } from './components/popover/widgets/icon';
import { SettingsComponent } from './components/settings';
import { AboutComponent, SettingsComponent } from './components/agent';
import { SideMenuComponent } from './components/side-menu';
import { SvgKeyboardComponent } from './components/svg/keyboard';
import {
@@ -101,6 +101,8 @@ import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
import { XtermComponent } from './components/xterm/xterm.component';
import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapper.component';
import { EditableTextComponent } from './components/editable-text/editable-text.component';
import { Autofocus } from './directives/autofocus/autofocus.directive';
@NgModule({
declarations: [
@@ -152,6 +154,7 @@ import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapp
MacroTextTabComponent,
MacroNotFoundComponent,
AddOnComponent,
AboutComponent,
SettingsComponent,
KeyboardSliderComponent,
CancelableDirective,
@@ -168,7 +171,9 @@ import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapp
ProgressButtonComponent,
LoadingDevicePageComponent,
XtermComponent,
SliderWrapperComponent
SliderWrapperComponent,
EditableTextComponent,
Autofocus
],
imports: [
CommonModule,

View File

@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type, VersionInformation } from 'uhk-common';
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type } from 'uhk-common';
import { ElectronLogEntry } from '../../models/xterm-log';
const PREFIX = '[app] ';
@@ -16,8 +16,8 @@ export const ActionTypes = {
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
UPDATE_AGENT_VERSION_INFORMATION: type(PREFIX + 'update agent version information'),
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received')
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'),
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window')
};
export class AppBootsrappedAction implements Action {
@@ -66,18 +66,18 @@ export class LoadHardwareConfigurationSuccessAction implements Action {
constructor(public payload: HardwareConfiguration) {}
}
export class UpdateAgentVersionInformationAction implements Action {
type = ActionTypes.UPDATE_AGENT_VERSION_INFORMATION;
constructor(public payload: VersionInformation) {}
}
export class ElectronMainLogReceivedAction implements Action {
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
constructor(public payload: ElectronLogEntry) {}
}
export class OpenUrlInNewWindowAction implements Action {
type = ActionTypes.OPEN_URL_IN_NEW_WINDOW;
constructor(public payload: string) {}
}
export type Actions
= AppStartedAction
| AppBootsrappedAction
@@ -88,6 +88,6 @@ export type Actions
| UndoLastSuccessAction
| DismissUndoNotificationAction
| LoadHardwareConfigurationSuccessAction
| UpdateAgentVersionInformationAction
| ElectronMainLogReceivedAction
| OpenUrlInNewWindowAction
;

View File

@@ -1,5 +1,6 @@
import { Action } from '@ngrx/store';
import { DeviceConnectionState, IpcResponse, type } from 'uhk-common';
import { HardwareModules } from '../../../../../uhk-common/src/models';
const PREFIX = '[device] ';
@@ -22,7 +23,8 @@ export const ActionTypes = {
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
UPDATE_FIRMWARE_SUCCESS: type(PREFIX + 'update firmware success'),
UPDATE_FIRMWARE_FAILED: type(PREFIX + 'update firmware failed'),
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click')
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click'),
MODULES_INFO_LOADED: type(PREFIX + 'module info loaded')
};
export class SetPrivilegeOnLinuxAction implements Action {
@@ -114,6 +116,13 @@ export class ResetMouseSpeedSettingsAction implements Action {
type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS;
}
export class HardwareModulesLoadedAction implements Action {
type = ActionTypes.MODULES_INFO_LOADED;
constructor(public payload: HardwareModules) {
}
}
export type Actions
= SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction
@@ -132,4 +141,5 @@ export type Actions
| UpdateFirmwareSuccessAction
| UpdateFirmwareFailedAction
| UpdateFirmwareOkButtonAction
| HardwareModulesLoadedAction
;

View File

@@ -1,6 +1,7 @@
import { Action } from '@ngrx/store';
import { KeyAction, Keymap, Macro } from 'uhk-common';
import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
export type KeymapAction =
KeymapActions.AddKeymapAction |
@@ -11,7 +12,8 @@ export type KeymapAction =
KeymapActions.SetDefaultAction |
KeymapActions.RemoveKeymapAction |
KeymapActions.SaveKeyAction |
KeymapActions.CheckMacroAction;
KeymapActions.CheckMacroAction |
KeymapActions.EditDescriptionAction;
export namespace KeymapActions {
export const ADD = '[Keymap] Add keymap';
@@ -98,6 +100,16 @@ export namespace KeymapActions {
payload: UndoUserConfigData
};
export const EDIT_DESCRIPTION = '[Keymap] Edit description';
export class EditDescriptionAction {
type = EDIT_DESCRIPTION;
constructor(public payload: ChangeKeymapDescription) {
}
}
export function loadKeymaps(): Action {
return {
type: KeymapActions.LOAD_KEYMAPS

View File

@@ -1,6 +1,7 @@
import { Action } from '@ngrx/store';
import { type, UserConfiguration, ConfigurationReply } from 'uhk-common';
import { UserConfigurationValue } from '../../models/user-configuration-value';
import { UploadFileData } from '../../models/upload-file-data';
const PREFIX = '[user-config] ';
@@ -15,7 +16,9 @@ export const ActionTypes = {
SAVE_USER_CONFIG_IN_BIN_FILE: type(PREFIX + 'Save User Config in binary file'),
LOAD_RESET_USER_CONFIGURATION: type(PREFIX + 'Load reset user configuration'),
RENAME_USER_CONFIGURATION: type(PREFIX + 'Rename user configuration'),
SET_USER_CONFIGURATION_VALUE: type(PREFIX + 'Set user configuration value')
SET_USER_CONFIGURATION_VALUE: type(PREFIX + 'Set user configuration value'),
LOAD_USER_CONFIGURATION_FROM_FILE: type(PREFIX + 'Load user configuration from file'),
APPLY_USER_CONFIGURATION_FROM_FILE: type(PREFIX + 'Apply user configuration from file')
};
export class LoadUserConfigAction implements Action {
@@ -76,6 +79,20 @@ export class SetUserConfigurationValueAction implements Action {
}
}
export class LoadUserConfigurationFromFileAction implements Action {
type = ActionTypes.LOAD_USER_CONFIGURATION_FROM_FILE;
constructor(public payload: UploadFileData) {
}
}
export class ApplyUserConfigurationFromFileAction implements Action {
type = ActionTypes.APPLY_USER_CONFIGURATION_FROM_FILE;
constructor(public payload: UserConfiguration) {
}
}
export type Actions
= LoadUserConfigAction
| LoadUserConfigSuccessAction
@@ -87,4 +104,6 @@ export type Actions
| LoadResetUserConfigurationAction
| RenameUserConfigurationAction
| SetUserConfigurationValueAction
| LoadUserConfigurationFromFileAction
| ApplyUserConfigurationFromFileAction
;

View File

@@ -16,19 +16,15 @@ import {
ApplyCommandLineArgsAction,
AppStartedAction,
DismissUndoNotificationAction,
OpenUrlInNewWindowAction,
ProcessAppStartInfoAction,
ShowNotificationAction,
UndoLastAction,
UpdateAgentVersionInformationAction
UndoLastAction
} from '../actions/app';
import { AppRendererService } from '../../services/app-renderer.service';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
import {
ActionTypes as DeviceActions,
ConnectionStateChangedAction,
SaveToKeyboardSuccessAction
} from '../actions/device';
import { AppState, autoWriteUserConfiguration } from '../index';
import { ConnectionStateChangedAction } from '../actions/device';
import { AppState, runningInElectron } from '../index';
@Injectable()
export class ApplicationEffects {
@@ -66,8 +62,7 @@ export class ApplicationEffects {
new ConnectionStateChangedAction({
connected: appInfo.deviceConnected,
hasPermission: appInfo.hasPermission
}),
new UpdateAgentVersionInformationAction(appInfo.agentVersionInfo)
})
];
});
@@ -76,12 +71,16 @@ export class ApplicationEffects {
.map(action => action.payload)
.mergeMap((action: Action) => [action, new DismissUndoNotificationAction()]);
@Effect({dispatch: false}) saveToKeyboardSuccess$ = this.actions$
.ofType<SaveToKeyboardSuccessAction>(DeviceActions.SAVE_TO_KEYBOARD_SUCCESS)
.withLatestFrom(this.store.select(autoWriteUserConfiguration))
.do(([action, autoWriteUserConfig]) => {
if (autoWriteUserConfig) {
this.appRendererService.exit();
@Effect({dispatch: false}) openUrlInNewWindow$ = this.actions$
.ofType<OpenUrlInNewWindowAction>(ActionTypes.OPEN_URL_IN_NEW_WINDOW)
.withLatestFrom(this.store.select(runningInElectron))
.do(([action, inElectron]) => {
const url = action.payload;
if (inElectron) {
this.appRendererService.openUrl(url);
} else {
window.open(url, '_blank');
}
});

View File

@@ -35,10 +35,12 @@ import { ShowNotificationAction } from '../actions/app';
import { AppState } from '../index';
import {
ActionTypes as UserConfigActions,
ApplyUserConfigurationFromFileAction,
LoadConfigFromDeviceAction,
LoadResetUserConfigurationAction
} from '../actions/user-config';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
@Injectable()
export class DeviceEffects {
@@ -162,8 +164,16 @@ export class DeviceEffects {
});
@Effect() saveResetUserConfigurationToDevice$ = this.actions$
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION)
.switchMap(() => Observable.of(new SaveConfigurationAction()));
.ofType<ApplyUserConfigurationFromFileAction
| LoadResetUserConfigurationAction>(
UserConfigActions.LOAD_RESET_USER_CONFIGURATION,
UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
.map(action => action.payload)
.switchMap((config: UserConfiguration) => {
this.dataStorageRepository.saveConfig(config);
return Observable.of(new SaveConfigurationAction());
});
@Effect({dispatch: false}) updateFirmware$ = this.actions$
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
@@ -193,6 +203,7 @@ export class DeviceEffects {
private router: Router,
private deviceRendererService: DeviceRendererService,
private store: Store<AppState>,
private dataStorageRepository: DataStorageRepositoryService,
private defaultUserConfigurationService: DefaultUserConfigurationService) {
}

View File

@@ -7,14 +7,17 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/observable/of';
import { Keymap } from 'uhk-common';
import { findNewItem } from '../../util';
import { KeymapActions } from '../actions';
import { AppState } from '../index';
import { getKeymaps } from '../reducers/user-configuration';
@Injectable()
export class KeymapEffects {
@@ -32,10 +35,10 @@ export class KeymapEffects {
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
.ofType(KeymapActions.ADD, KeymapActions.DUPLICATE)
.withLatestFrom(this.store)
.map(latest => latest[1].userConfiguration.keymaps)
.do(keymaps => {
this.router.navigate(['/keymap', keymaps[keymaps.length - 1].abbreviation]);
.withLatestFrom(this.store.let(getKeymaps()).pairwise(), (action, latest) => latest)
.do(([prevKeymaps, newKeymaps]) => {
const newKeymap = findNewItem(prevKeymaps, newKeymaps);
this.router.navigate(['/keymap', newKeymap.abbreviation]);
});
@Effect({ dispatch: false }) remove$: any = this.actions$

View File

@@ -2,14 +2,18 @@ import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Actions, Effect } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { Store, Action } from '@ngrx/store';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/withLatestFrom';
import { Macro } from 'uhk-common';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
import { getMacros } from '../reducers/user-configuration';
import { findNewItem } from '../../util';
@Injectable()
export class MacroEffects {
@@ -27,23 +31,17 @@ export class MacroEffects {
}
});
@Effect({ dispatch: false }) add$: any = this.actions$
.ofType(MacroActions.ADD)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1])
.do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id, 'new']);
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
.ofType(MacroActions.ADD, MacroActions.DUPLICATE)
.withLatestFrom(this.store.let(getMacros()).pairwise(), (action, latest) => ([action, latest[0], latest[1]]))
.do(([action, prevMacros, newMacros]: [Action, Macro[], Macro[]]) => {
const newMacro = findNewItem(prevMacros, newMacros);
const commands = ['/macro', newMacro.id];
if (action.type === MacroActions.ADD) {
commands.push('new');
}
this.router.navigate(commands);
});
@Effect({ dispatch: false }) duplicate: any = this.actions$
.ofType(MacroActions.DUPLICATE)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1])
.do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id]);
});
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {}
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }
}

View File

@@ -15,26 +15,38 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import {
ConfigurationReply, HardwareConfiguration, LogService, NotificationType, UhkBuffer,
ConfigurationReply,
HardwareConfiguration,
LogService,
NotificationType,
UhkBuffer,
UserConfiguration
} from 'uhk-common';
import {
ActionTypes, LoadConfigFromDeviceReplyAction, LoadUserConfigSuccessAction, RenameUserConfigurationAction,
ActionTypes,
ApplyUserConfigurationFromFileAction,
LoadConfigFromDeviceReplyAction,
LoadUserConfigSuccessAction,
LoadUserConfigurationFromFileAction,
RenameUserConfigurationAction,
SaveUserConfigSuccessAction
} from '../actions/user-config';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { AppState, autoWriteUserConfiguration, getPrevUserConfiguration, getUserConfiguration } from '../index';
import { AppState, getPrevUserConfiguration, getUserConfiguration } from '../index';
import { KeymapAction, KeymapActions, MacroAction, MacroActions } from '../actions';
import {
DismissUndoNotificationAction, LoadHardwareConfigurationSuccessAction, ShowNotificationAction,
DismissUndoNotificationAction,
LoadHardwareConfigurationSuccessAction,
ShowNotificationAction,
UndoLastAction
} from '../actions/app';
import { SaveConfigurationAction, ShowSaveToKeyboardButtonAction } from '../actions/device';
import { HardwareModulesLoadedAction, ShowSaveToKeyboardButtonAction } from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { UploadFileData } from '../../models/upload-file-data';
@Injectable()
export class UserConfigEffects {
@@ -69,7 +81,7 @@ export class UserConfigEffects {
@Effect() saveUserConfig$: Observable<Action> = (this.actions$
.ofType(
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY,
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY, KeymapActions.EDIT_DESCRIPTION,
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
@@ -161,6 +173,8 @@ export class UserConfigEffects {
}));
}
result.push(new HardwareModulesLoadedAction(data.modules));
this.router.navigate(['/']);
return result;
@@ -185,16 +199,34 @@ export class UserConfigEffects {
saveAs(blob, 'UserConfiguration.bin');
});
@Effect() loadUserConfigurationSuccess$ = this.actions$
.ofType(ActionTypes.LOAD_USER_CONFIG_SUCCESS)
.withLatestFrom(this.store.select(autoWriteUserConfiguration))
.switchMap(([action, autoWriteUserConfig]) => {
this.logService.debug('[UserConfigEffect] LOAD_USER_CONFIG_SUCCESS', {autoWriteUserConfig});
if (autoWriteUserConfig) {
return Observable.of(new SaveConfigurationAction());
}
else {
return Observable.empty();
@Effect() loadUserConfigurationFromFile$ = this.actions$
.ofType<LoadUserConfigurationFromFileAction>(ActionTypes.LOAD_USER_CONFIGURATION_FROM_FILE)
.map(action => action.payload)
.map((info: UploadFileData) => {
try {
const userConfig = new UserConfiguration();
if (info.filename.endsWith('.bin')) {
userConfig.fromBinary(UhkBuffer.fromArray(info.data));
} else {
const buffer = new Buffer(info.data);
const json = buffer.toString();
userConfig.fromJsonObject(JSON.parse(json));
}
if (userConfig.userConfigMajorVersion) {
return new ApplyUserConfigurationFromFileAction(userConfig);
}
return new ShowNotificationAction({
type: NotificationType.Error,
message: 'Invalid configuration specified.'
});
} catch (err) {
return new ShowNotificationAction({
type: NotificationType.Error,
message: 'Invalid configuration specified.'
});
}
});

View File

@@ -39,11 +39,11 @@ export const metaReducers: MetaReducer<AppState>[] = environment.production
: [storeFreeze];
export const getUserConfiguration = (state: AppState) => state.userConfiguration;
export const getDeviceName = (state: AppState) => state.userConfiguration.deviceName;
export const getDeviceName = createSelector(getUserConfiguration, fromUserConfig.getDeviceName);
export const appState = (state: AppState) => state.app;
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
export const autoWriteUserConfiguration = createSelector(appState, fromApp.autoWriteUserConfiguration);
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
@@ -77,3 +77,4 @@ export const xtermLog = createSelector(deviceState, fromDevice.xtermLog);
export const firmwareOkButtonDisabled = createSelector(deviceState, fromDevice.firmwareOkButtonDisabled);
// tslint:disable-next-line: max-line-length
export const flashFirmwareButtonDisbabled = createSelector(runningInElectron, deviceState, (electron, state: fromDevice.State) => !electron || state.updatingFirmware);
export const getHardwareModules = createSelector(deviceState, fromDevice.getHardwareModules);

View File

@@ -7,11 +7,11 @@ import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { ActionTypes as DeviceActionTypes } from '../actions/device';
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
import { getVersions } from '../../util';
export interface State {
started: boolean;
showAddonMenu: boolean;
autoWriteUserConfiguration: boolean;
undoableNotification?: Notification;
navigationCountAfterNotification: number;
prevUserConfig?: UserConfiguration;
@@ -24,10 +24,10 @@ export interface State {
export const initialState: State = {
started: false,
showAddonMenu: false,
autoWriteUserConfiguration: false,
navigationCountAfterNotification: 0,
runningInElectron: runInElectron(),
configLoading: true
configLoading: true,
agentVersionInfo: getVersions()
};
export function reducer(state = initialState, action: Action & { payload: any }) {
@@ -42,8 +42,7 @@ export function reducer(state = initialState, action: Action & { payload: any })
case ActionTypes.APPLY_COMMAND_LINE_ARGS: {
return {
...state,
showAddonMenu: action.payload.addons,
autoWriteUserConfiguration: action.payload.autoWriteConfig
showAddonMenu: action.payload.addons
};
}
@@ -116,18 +115,12 @@ export function reducer(state = initialState, action: Action & { payload: any })
};
}
case ActionTypes.UPDATE_AGENT_VERSION_INFORMATION:
return {
...state,
agentVersionInfo: action.payload
};
default:
return state;
}
}
export const showAddonMenu = (state: State) => state.showAddonMenu;
export const autoWriteUserConfiguration = (state: State) => state.autoWriteUserConfiguration;
export const getUndoableNotification = (state: State) => state.undoableNotification;
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
export const runningInElectron = (state: State) => state.runningInElectron;

View File

@@ -1,8 +1,10 @@
import { Action } from '@ngrx/store';
import { HardwareModules } from 'uhk-common';
import {
ActionTypes,
ConnectionStateChangedAction,
HardwareModulesLoadedAction,
SaveConfigurationAction,
UpdateFirmwareFailedAction
} from '../actions/device';
@@ -16,6 +18,7 @@ export interface State {
saveToKeyboard: ProgressButtonState;
updatingFirmware: boolean;
firmwareUpdateFinished: boolean;
modules: HardwareModules;
log: Array<XtermLog>;
}
@@ -25,6 +28,15 @@ export const initialState: State = {
saveToKeyboard: initProgressButtonState,
updatingFirmware: false,
firmwareUpdateFinished: false,
modules: {
leftModuleInfo: {
firmwareVersion: '',
moduleProtocolVersion: ''
},
rightModuleInfo: {
firmwareVersion: ''
}
},
log: [{message: '', cssClass: XtermCssClass.standard}]
};
@@ -148,6 +160,13 @@ export function reducer(state = initialState, action: Action) {
log: [...state.log, logEntry]
};
}
case ActionTypes.MODULES_INFO_LOADED:
return {
...state,
modules: (action as HardwareModulesLoadedAction).payload
};
default:
return state;
}
@@ -159,3 +178,4 @@ export const hasDevicePermission = (state: State) => state.hasPermission;
export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard;
export const xtermLog = (state: State) => state.log;
export const firmwareOkButtonDisabled = (state: State) => !state.firmwareUpdateFinished;
export const getHardwareModules = (state: State) => state.modules;

View File

@@ -7,7 +7,7 @@ export const initialState: Keymap[] = [];
export function reducer(state = initialState, action: KeymapAction): Keymap[] {
switch (action.type) {
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
return action.payload;
return (action as KeymapActions.LoadKeymapSuccessAction).payload ;
}
default:

View File

@@ -4,10 +4,22 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { KeyAction, Keymap, KeyActionHelper, Layer, Macro, Module, SwitchLayerAction, UserConfiguration } from 'uhk-common';
import {
KeyAction,
KeyActionHelper,
Keymap,
Layer,
Macro,
Module,
NoneAction,
PlayMacroAction,
SwitchLayerAction,
UserConfiguration
} from 'uhk-common';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
import { ActionTypes } from '../actions/user-config';
import { isValidName } from '../../util';
export const initialState: UserConfiguration = new UserConfiguration();
@@ -15,9 +27,15 @@ export function reducer(state = initialState, action: Action & { payload?: any }
const changedUserConfiguration: UserConfiguration = Object.assign(new UserConfiguration(), state);
switch (action.type) {
case ActionTypes.APPLY_USER_CONFIGURATION_FROM_FILE:
case ActionTypes.LOAD_RESET_USER_CONFIGURATION:
case ActionTypes.LOAD_USER_CONFIG_SUCCESS: {
return Object.assign(changedUserConfiguration, action.payload);
Object.assign(changedUserConfiguration, action.payload);
changedUserConfiguration.keymaps = [...changedUserConfiguration.keymaps];
changedUserConfiguration.keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
changedUserConfiguration.macros = [...changedUserConfiguration.macros];
changedUserConfiguration.macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
return changedUserConfiguration;
}
case KeymapActions.ADD:
@@ -27,25 +45,36 @@ export function reducer(state = initialState, action: Action & { payload?: any }
newKeymap.name = generateName(state.keymaps, newKeymap.name);
newKeymap.isDefault = (state.keymaps.length === 0);
changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap);
changedUserConfiguration.keymaps = insertItemInNameOrder(state.keymaps, newKeymap);
break;
}
case KeymapActions.EDIT_NAME: {
const name: string = action.payload.name;
if (!isValidName(action.payload.name)) {
break;
}
const name: string = action.payload.name.trim();
let keymapToRename: Keymap = null;
const duplicate = state.keymaps.some((keymap: Keymap) => {
if (keymap.abbreviation === action.payload.abbr) {
keymapToRename = keymap;
}
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
});
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
keymap = Object.assign(new Keymap(), keymap);
if (duplicate) {
break;
}
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
keymap.name = name;
}
return keymap;
});
const newKeymap = Object.assign(new Keymap(), keymapToRename, { name });
changedUserConfiguration.keymaps = insertItemInNameOrder(
state.keymaps,
newKeymap,
keymap => keymap.abbreviation !== newKeymap.abbreviation
);
break;
}
case KeymapActions.EDIT_ABBR: {
@@ -157,7 +186,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
newMacro.isPrivate = true;
newMacro.macroActions = [];
changedUserConfiguration.macros = state.macros.concat(newMacro);
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
break;
}
case MacroActions.DUPLICATE: {
@@ -165,30 +194,61 @@ export function reducer(state = initialState, action: Action & { payload?: any }
newMacro.name = generateName(state.macros, newMacro.name);
newMacro.id = generateMacroId(state.macros);
changedUserConfiguration.macros = state.macros.concat(newMacro);
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
break;
}
case MacroActions.EDIT_NAME: {
const name: string = action.payload.name;
if (!isValidName(action.payload.name)) {
break;
}
const name: string = action.payload.name.trim();
let macroToRename: Macro = null;
const duplicate = state.macros.some((macro: Macro) => {
if (macro.id === action.payload.id) {
macroToRename = macro;
}
return macro.id !== action.payload.id && macro.name === name;
});
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
macro = Object.assign(new Macro(), macro);
if (!duplicate && macro.id === action.payload.id) {
macro.name = name;
}
return macro;
});
if (duplicate) {
break;
}
const newMacro = Object.assign(new Macro(), macroToRename, { name });
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro, macro => macro.id !== newMacro.id);
break;
}
case MacroActions.REMOVE:
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== action.payload);
const macroId = action.payload;
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== macroId);
for (let k = 0; k < changedUserConfiguration.keymaps.length; k++) {
const keymap = changedUserConfiguration.keymaps[k];
let hasChanges = false;
for (const layer of keymap.layers) {
for (const module of layer.modules) {
for (let ka = 0; ka < module.keyActions.length; ka++) {
const keyAction = module.keyActions[ka];
if (keyAction instanceof PlayMacroAction && keyAction.macroId === macroId) {
hasChanges = true;
module.keyActions[ka] = new NoneAction();
}
}
}
}
if (hasChanges) {
changedUserConfiguration.keymaps[k] = new Keymap(keymap);
}
}
break;
case MacroActions.ADD_ACTION:
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
if (macro.id === action.payload.id) {
@@ -242,7 +302,9 @@ export function reducer(state = initialState, action: Action & { payload?: any }
break;
case ActionTypes.RENAME_USER_CONFIGURATION: {
changedUserConfiguration.deviceName = action.payload;
if (isValidName(action.payload)) {
changedUserConfiguration.deviceName = action.payload.trim();
}
break;
}
@@ -251,6 +313,18 @@ export function reducer(state = initialState, action: Action & { payload?: any }
break;
}
case KeymapActions.EDIT_DESCRIPTION: {
const data = (action as KeymapActions.EditDescriptionAction).payload;
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
if (keymap.abbreviation === data.abbr) {
keymap.description = data.description;
}
return keymap;
});
break;
}
default:
break;
}
@@ -348,6 +422,27 @@ function generateMacroId(macros: Macro[]) {
return newId + 1;
}
function insertItemInNameOrder<T extends { name: string }>(
items: T[], newItem: T, keepItem: (item: T) => boolean = () => true
): T[] {
const newItems: T[] = [];
let added = false;
for (const item of items) {
if (!added && item.name.localeCompare(newItem.name) > 0) {
newItems.push(newItem);
added = true;
}
if (keepItem(item)) {
newItems.push(item);
}
}
if (!added) {
newItems.push(newItem);
}
return newItems;
}
function checkExistence(layers: Layer[], property: string, value: any): Layer[] {
const keyActionsToClear: {
layerIdx: number,
@@ -399,3 +494,5 @@ function setKeyActionToLayer(newLayer: Layer, moduleIndex: number, keyIndex: num
newModule.keyActions = newModule.keyActions.slice();
newModule.keyActions[keyIndex] = newKeyAction;
}
export const getDeviceName = (state: UserConfiguration) => state.deviceName;

View File

@@ -0,0 +1,9 @@
export function findNewItem<T>(oldItems: T[], newItems: T[]): T {
for (let i = 0; i < oldItems.length; ++i) {
if (oldItems[i] !== newItems[i]) {
return newItems[i];
}
}
return newItems[newItems.length - 1];
}

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