Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38184e7968 | ||
|
|
f6092ea195 | ||
|
|
ac7d66e338 | ||
|
|
b82a1da92a | ||
|
|
b8859f7b64 | ||
|
|
a04fa67446 | ||
|
|
ac89aff018 | ||
|
|
e7cf8dc966 | ||
|
|
d0102f5bdb | ||
|
|
eb0daadf98 | ||
|
|
49d6ca173d | ||
|
|
a3eb6a6b7e | ||
|
|
144ed57b20 | ||
|
|
6086ddabf0 | ||
|
|
84f378a276 | ||
|
|
648e8d5f2c | ||
|
|
15df8d7129 | ||
|
|
cfc0af9655 | ||
|
|
f02e3181a6 | ||
|
|
3d59bcf97e | ||
|
|
5e4fc983fb | ||
|
|
32d9635b34 | ||
|
|
3978011d2e | ||
|
|
cd1952a7df | ||
|
|
4251477451 | ||
|
|
873f1de1ef | ||
|
|
150f993e5f | ||
|
|
06e76e5e0f | ||
|
|
a208a264c7 | ||
|
|
114014fa13 | ||
|
|
94cfd9d2e9 | ||
|
|
0aa9c73b4b | ||
|
|
5234f85dbe | ||
|
|
bd8a2f704f | ||
|
|
439886d69f | ||
|
|
b2a37795e3 | ||
|
|
440db56080 | ||
|
|
337e6e6bb6 | ||
|
|
a1aeda3d35 | ||
|
|
c6a83f8c9b | ||
|
|
0f24427628 | ||
|
|
f52dc36a6a | ||
|
|
63a936968d | ||
|
|
cabfde7963 | ||
|
|
79628c2351 | ||
|
|
762fa6f8bf | ||
|
|
a258c097a9 | ||
|
|
41faa98fcd | ||
|
|
c4d7318686 | ||
|
|
9ef11eaa34 | ||
|
|
f34cb2df56 | ||
|
|
83b9f0d1e9 | ||
|
|
7d81cf0c6a | ||
|
|
82b76a9455 | ||
|
|
4ae577f936 | ||
|
|
81a83994ab | ||
|
|
1d3a3c7f5f | ||
|
|
8bb645125d | ||
|
|
9471b31a5d | ||
|
|
ffa52757c9 | ||
|
|
ee53a0df9b | ||
|
|
8e20c85e07 | ||
|
|
65ea786358 | ||
|
|
1035837b3b | ||
|
|
18fc2e6b3f | ||
|
|
fc728697d7 | ||
|
|
cdf3caee9e | ||
|
|
0a4d3a002e | ||
|
|
d11c532ea4 | ||
|
|
1ff51697b1 | ||
|
|
ab8ae31324 | ||
|
|
daa0e723b1 | ||
|
|
609aba856a | ||
|
|
a6678bd537 | ||
|
|
6c4f580fc2 | ||
|
|
ea41661c65 | ||
|
|
c553c7b63b | ||
|
|
e5988aa800 | ||
|
|
ae319c607f | ||
|
|
5d23ad1c9e | ||
|
|
55eef50da7 | ||
|
|
653465f0e0 | ||
|
|
2cf8044987 | ||
|
|
3c056a7255 | ||
|
|
091796d13c | ||
|
|
eb97dd844f | ||
|
|
17693ec8fe | ||
|
|
7c7ce8f50f | ||
|
|
e294727ac5 | ||
|
|
f29d64c803 | ||
|
|
0385b0ce29 | ||
|
|
b526274cd7 | ||
|
|
88c16af4a9 | ||
|
|
05ac9a6832 | ||
|
|
04aa5236c2 | ||
|
|
ec98e4e1c6 | ||
|
|
bb9ece494c | ||
|
|
217e6776ac | ||
|
|
2286218980 | ||
|
|
3d9c83f9f4 | ||
|
|
14ed163238 | ||
|
|
c815de0718 | ||
|
|
6a46556d9e | ||
|
|
f8f820529f |
77
CHANGELOG.md
77
CHANGELOG.md
@@ -6,11 +6,88 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
|
|||||||
|
|
||||||
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||||
|
|
||||||
|
## [1.2.6] - 2018-07-26
|
||||||
|
|
||||||
|
Firmware: 8.**4.0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.4.0)] | Device Protocol: 4.**4.0** | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Replace the Linux blhost binary with a statically compiled version that doesn't use special instructions and shouldn't segfault.
|
||||||
|
- Keep the current layer when changing keymaps.
|
||||||
|
- Fix the sleep key of Mac keymaps.
|
||||||
|
- Add help page.
|
||||||
|
- Add "save to keyboard" and "remap key" shortcuts.
|
||||||
|
- Build only AppImages for Linux.
|
||||||
|
- Replace ng2-select2 widgets with ngx-select-ex that always shows up in the correct position.
|
||||||
|
- Improve the phrasing of the firmware update error message.
|
||||||
|
- Tweak unsupported Windows firmware update notification.
|
||||||
|
- Hide the Settings menu until auto update is implemented.
|
||||||
|
- Don't scroll when the macro tab of the key action popover gets selected.
|
||||||
|
- Add keyboard shortcut for enabling the USB stack test mode of the firmware. `DEVICEPROTOCOL:MINOR`
|
||||||
|
- Tone down the color of the separator line.
|
||||||
|
|
||||||
|
## [1.2.5] - 2018-06-26
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- When remapping a switch keymap action on all keymaps, don't set it on its own keymap.
|
||||||
|
- Make the key action popover always contain the action of the current key, even after cancelled.
|
||||||
|
- Include the firmware version to be updated to the firmware update log.
|
||||||
|
- Update the Agent icon of the side menu and the about page.
|
||||||
|
- When remapping a key, only flash the affected key instead of all keys.
|
||||||
|
- Fade in/out the keyboard separator line only when splitting the keyboard.
|
||||||
|
- Only show the unsupported OS message of the firmware page on relevant Windows versions.
|
||||||
|
- Close and reopen USB device when an error occurs.
|
||||||
|
- Temporarily remove the export keymap feature because it's useless until import is implemented.
|
||||||
|
|
||||||
|
## [1.2.4] - 2018-06-21
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Replace Linux x86-64 blhost with a statically linked version which should make firmware updates work on every Linux distro.
|
||||||
|
|
||||||
|
## [1.2.3] - 2018-06-19
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Add checkboxes for remapping keys on all layers and/or all keymaps.
|
||||||
|
- Add separator line between the keyboard halves.
|
||||||
|
- Add double tap icon for switch layer actions.
|
||||||
|
- Improve the looks and content of the tooltips of the key action popover.
|
||||||
|
- Make the left keyboard half less likely to timeout during firmware update.
|
||||||
|
- Terminate the firmware update process if blhost segfaults.
|
||||||
|
- Replace the Linux x86-64 version of the blhost binary which should not make it segfault anymore.
|
||||||
|
- Make the firmware update log shorter by listing one device per line and not repeating the list of available USB devices.
|
||||||
|
- Make the firmware update help text shorter.
|
||||||
|
|
||||||
|
## [1.2.2] - 2018-05-27
|
||||||
|
|
||||||
|
Firmware: 8.2.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.**1** | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Offer recovery for bricked right keyboard halfs.
|
||||||
|
- Detect when the hardware configuration of a device is invalid and display a notification. `DEVICEPROTOCOL:PATCH`
|
||||||
|
- Check if the keyboard is in factory reset mode and if so, display a relevant instruction.
|
||||||
|
- Only allow ASCII characters in type text macro actions.
|
||||||
|
- Allow uploading the same file multiple times in a row.
|
||||||
|
- Only send auto update notification when the user initiates the update.
|
||||||
|
- Update the firmware versions on the firmware update page right after firmware updates.
|
||||||
|
- Add a lot of useful instructions to the firmware page to help users update the firmware.
|
||||||
|
- Add the operating system and initial device list to the firmware update log.
|
||||||
|
- Add copy to clipboard button to the top right corner of the firmware update terminal widget.
|
||||||
|
|
||||||
|
## [1.2.1] - 2018-05-12
|
||||||
|
|
||||||
|
Firmware: 8.2.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.2)] | Device Protocol: 4.3.0 | User Config: 4.0.**1** | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Match for the new USB usage page and usage number. This is critical for UHKs flashed with firmware >=8.2.2 to be recognized by Agent on OSX.
|
||||||
|
- Make the config serializer handle long media macro actions. `USERCONFIG:PATCH`
|
||||||
|
- Add note on the macro page explaining that the macro engine of the firmware is not ready yet.
|
||||||
|
- Add an example to the scancode tooltip to better explain users how to invoke non-US characters.
|
||||||
|
|
||||||
## [1.2.0] - 2018-04-20
|
## [1.2.0] - 2018-04-20
|
||||||
|
|
||||||
Firmware: 8.**2.0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.0)] | Device Protocol: 4.**3.0** | User Config: 4.0.0 | Hardware Config: 1.0.0
|
Firmware: 8.**2.0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.0)] | Device Protocol: 4.**3.0** | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
- Tweak the default mouse speed. This was necessary because the last firmware version adjusted speed multipliers. The mouse speed can be reset via the "Reset speeds to default" button of the "Mouse speed" page.
|
- Tweak the default mouse speed. This was necessary because the last firmware version adjusted speed multipliers. The mouse speed can be reset via the "Reset speeds to default" button of the "Mouse speed" page.
|
||||||
|
- Make the newly added switch-keymap.js script utilize the new UsbCommandId_SwitchKeymap, allowing for programmatic keymap switching. `DEVICEPROTOCOL:MINOR`
|
||||||
|
|
||||||
## [1.1.5] - 2018-04-10
|
## [1.1.5] - 2018-04-10
|
||||||
|
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -5,17 +5,8 @@
|
|||||||
|
|
||||||
Agent is the configuration application of the [Ultimate Hacking Keyboard](https://ultimatehackingkeyboard.com/).
|
Agent is the configuration application of the [Ultimate Hacking Keyboard](https://ultimatehackingkeyboard.com/).
|
||||||
|
|
||||||
[Give it a whirl!](http://ultimatehackingkeyboard.github.io/agent/)
|
* Try out the [web build of Agent](http://ultimatehackingkeyboard.github.io/agent/) in your browser. This is meant to be used for demonstration purposes.
|
||||||
|
* Download the [desktop build of Agent](https://github.com/UltimateHackingKeyboard/agent/releases) from our releases page. Use this if you have an actual UHK at hand, or else you won't get past the opening screen!
|
||||||
## Two builds to rule them all
|
|
||||||
|
|
||||||
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 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
|
## Building the electron application
|
||||||
|
|
||||||
|
|||||||
1256
package-lock.json
generated
1256
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@@ -3,10 +3,10 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"author": "Ultimate Gadget Laboratories",
|
"author": "Ultimate Gadget Laboratories",
|
||||||
"main": "electron/dist/electron-main.js",
|
"main": "electron/dist/electron-main.js",
|
||||||
"version": "1.2.0",
|
"version": "1.2.6",
|
||||||
"firmwareVersion": "8.2.0",
|
"firmwareVersion": "8.4.0",
|
||||||
"deviceProtocolVersion": "4.3.0",
|
"deviceProtocolVersion": "4.4.0",
|
||||||
"userConfigVersion": "4.0.0",
|
"userConfigVersion": "4.0.1",
|
||||||
"hardwareConfigVersion": "1.0.0",
|
"hardwareConfigVersion": "1.0.0",
|
||||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -23,34 +23,38 @@
|
|||||||
"@types/electron-settings": "3.0.0",
|
"@types/electron-settings": "3.0.0",
|
||||||
"@types/fs-extra": "5.0.1",
|
"@types/fs-extra": "5.0.1",
|
||||||
"@types/jasmine": "2.6.0",
|
"@types/jasmine": "2.6.0",
|
||||||
"@types/jsonfile": "4.0.1",
|
|
||||||
"@types/jquery": "3.3.1",
|
"@types/jquery": "3.3.1",
|
||||||
|
"@types/jsonfile": "4.0.1",
|
||||||
|
"@types/lodash-es": "4.17.0",
|
||||||
"@types/node": "8.0.53",
|
"@types/node": "8.0.53",
|
||||||
"@types/node-hid": "0.5.2",
|
"@types/node-hid": "0.5.2",
|
||||||
"@types/request": "2.0.8",
|
"@types/request": "2.0.8",
|
||||||
"@types/usb": "1.1.3",
|
"@types/usb": "1.1.3",
|
||||||
"autoprefixer": "6.5.3",
|
"autoprefixer": "6.5.3",
|
||||||
"buffer": "5.0.6",
|
"buffer": "5.0.6",
|
||||||
"copyfiles": "^2.0.0",
|
"check-node-version": "^3.2.0",
|
||||||
"copy-webpack-plugin": "4.0.1",
|
"copy-webpack-plugin": "4.0.1",
|
||||||
|
"copyfiles": "^2.0.0",
|
||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"cross-env": "5.0.5",
|
"cross-env": "5.0.5",
|
||||||
"decompress": "4.2.0",
|
"decompress": "4.2.0",
|
||||||
"decompress-tarbz2": "^4.1.1",
|
"decompress-tarbz2": "4.1.1",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron": "1.8.4",
|
"electron": "1.8.4",
|
||||||
"electron-builder": "20.8.1",
|
"electron-builder": "20.15.0",
|
||||||
"electron-debug": "1.5.0",
|
"electron-debug": "1.5.0",
|
||||||
"electron-devtools-installer": "2.2.3",
|
"electron-devtools-installer": "2.2.3",
|
||||||
"electron-log": "2.2.14",
|
"electron-log": "2.2.16",
|
||||||
"electron-rebuild": "1.7.3",
|
"electron-rebuild": "1.8.1",
|
||||||
"electron-settings": "3.1.4",
|
"electron-settings": "3.1.4",
|
||||||
"electron-updater": "2.21.4",
|
"electron-updater": "2.21.4",
|
||||||
"exports-loader": "0.6.3",
|
"exports-loader": "0.6.3",
|
||||||
"file-loader": "0.10.0",
|
"file-loader": "0.10.0",
|
||||||
"fs-extra": "5.0.0",
|
"fs-extra": "5.0.0",
|
||||||
|
"gh-pages": "1.1.0",
|
||||||
"jsonfile": "4.0.0",
|
"jsonfile": "4.0.0",
|
||||||
"lerna": "2.9.0",
|
"lerna": "2.9.0",
|
||||||
|
"lodash-es": "4.17.4",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"node-hid": "0.5.7",
|
"node-hid": "0.5.7",
|
||||||
"npm-run-all": "4.0.2",
|
"npm-run-all": "4.0.2",
|
||||||
@@ -84,6 +88,7 @@
|
|||||||
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
|
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
|
||||||
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
|
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
|
||||||
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
|
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
|
||||||
|
"prebuild": "check-node-version --package",
|
||||||
"build": "run-s build:common build:usb build:web build:electron",
|
"build": "run-s build:common build:usb build:web build:electron",
|
||||||
"build:web": "lerna exec --scope uhk-web npm run build",
|
"build:web": "lerna exec --scope uhk-web npm run build",
|
||||||
"build:electron": "cross-env AOT_BUILD=true run-s -sn build:electron:renderer build:electron:main",
|
"build:electron": "cross-env AOT_BUILD=true run-s -sn build:electron:renderer build:electron:main",
|
||||||
@@ -99,7 +104,9 @@
|
|||||||
"pack": "node ./scripts/release.js",
|
"pack": "node ./scripts/release.js",
|
||||||
"sprites": "node ./scripts/generate-svg-sprites",
|
"sprites": "node ./scripts/generate-svg-sprites",
|
||||||
"release": "node ./scripts/release.js",
|
"release": "node ./scripts/release.js",
|
||||||
"clean": "lerna exec rimraf ./node_modules ./dist"
|
"clean": "lerna exec rimraf ./node_modules ./dist && rimraf ./node_modules ./dist",
|
||||||
|
"predeploy-gh-pages": "run-s build:web",
|
||||||
|
"deploy-gh-pages": "gh-pages -d packages/uhk-web/dist"
|
||||||
},
|
},
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import * as isDev from 'electron-is-dev';
|
|||||||
|
|
||||||
const optionDefinitions = [
|
const optionDefinitions = [
|
||||||
{name: 'addons', type: Boolean},
|
{name: 'addons', type: Boolean},
|
||||||
{name: 'spe', type: Boolean} // simulate privilege escalation error
|
{name: 'spe', type: Boolean}, // simulate privilege escalation error
|
||||||
|
// show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
|
||||||
|
{name: 'layer-double-tap', type: Boolean}
|
||||||
];
|
];
|
||||||
|
|
||||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||||
@@ -104,7 +106,7 @@ function createWindow() {
|
|||||||
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
||||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
|
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);
|
||||||
appUpdateService = new AppUpdateService(logger, win, app);
|
appUpdateService = new AppUpdateService(logger, win, app);
|
||||||
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
||||||
sudoService = new SudoService(logger, options);
|
sudoService = new SudoService(logger, options);
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ import { SynchrounousResult } from 'tmp';
|
|||||||
export interface TmpFirmware {
|
export interface TmpFirmware {
|
||||||
rightFirmwarePath: string;
|
rightFirmwarePath: string;
|
||||||
leftFirmwarePath: string;
|
leftFirmwarePath: string;
|
||||||
|
packageJsonPath: string;
|
||||||
tmpDirectory: SynchrounousResult;
|
tmpDirectory: SynchrounousResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { IpcEvents, LogService } from 'uhk-common';
|
|||||||
import { MainServiceBase } from './main-service-base';
|
import { MainServiceBase } from './main-service-base';
|
||||||
|
|
||||||
export class AppUpdateService extends MainServiceBase {
|
export class AppUpdateService extends MainServiceBase {
|
||||||
|
|
||||||
|
private sendAutoUpdateNotification = false;
|
||||||
|
|
||||||
constructor(protected logService: LogService,
|
constructor(protected logService: LogService,
|
||||||
protected win: Electron.BrowserWindow,
|
protected win: Electron.BrowserWindow,
|
||||||
private app: Electron.App) {
|
private app: Electron.App) {
|
||||||
@@ -24,16 +27,21 @@ export class AppUpdateService extends MainServiceBase {
|
|||||||
|
|
||||||
private initListeners() {
|
private initListeners() {
|
||||||
autoUpdater.on('checking-for-update', () => {
|
autoUpdater.on('checking-for-update', () => {
|
||||||
|
this.logService.debug('[AppUpdateService] checking for update');
|
||||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
|
this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-available', async (ev: any, info: UpdateInfo) => {
|
autoUpdater.on('update-available', async (ev: any, info: UpdateInfo) => {
|
||||||
|
this.logService.debug('[AppUpdateService] update available. Downloading started');
|
||||||
await autoUpdater.downloadUpdate();
|
await autoUpdater.downloadUpdate();
|
||||||
this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
|
this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-not-available', (ev: any, info: UpdateInfo) => {
|
autoUpdater.on('update-not-available', (ev: any, info: UpdateInfo) => {
|
||||||
this.sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
|
if (this.sendAutoUpdateNotification) {
|
||||||
|
this.logService.debug('[AppUpdateService] update not available');
|
||||||
|
this.sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('error', (ev: any, err: string) => {
|
autoUpdater.on('error', (ev: any, err: string) => {
|
||||||
@@ -51,6 +59,7 @@ export class AppUpdateService extends MainServiceBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-downloaded', (ev: any, info: UpdateInfo) => {
|
autoUpdater.on('update-downloaded', (ev: any, info: UpdateInfo) => {
|
||||||
|
this.logService.debug('[AppUpdateService] update downloaded');
|
||||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
|
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,12 +70,15 @@ export class AppUpdateService extends MainServiceBase {
|
|||||||
|
|
||||||
ipcMain.on(IpcEvents.app.appStarted, () => {
|
ipcMain.on(IpcEvents.app.appStarted, () => {
|
||||||
if (this.checkForUpdateAtStartup()) {
|
if (this.checkForUpdateAtStartup()) {
|
||||||
|
this.sendAutoUpdateNotification = false;
|
||||||
|
this.logService.debug('[AppUpdateService] app started. Automatically check for update.');
|
||||||
this.checkForUpdate();
|
this.checkForUpdate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => {
|
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => {
|
||||||
this.logService.debug('[AppUpdateService] checkForUpdate request from renderer process');
|
this.logService.debug('[AppUpdateService] checkForUpdate request from renderer process');
|
||||||
|
this.sendAutoUpdateNotification = true;
|
||||||
this.checkForUpdate();
|
this.checkForUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -75,14 +87,22 @@ export class AppUpdateService extends MainServiceBase {
|
|||||||
if (isDev) {
|
if (isDev) {
|
||||||
const msg = '[AppUpdateService] Application update is not working in dev mode.';
|
const msg = '[AppUpdateService] Application update is not working in dev mode.';
|
||||||
this.logService.info(msg);
|
this.logService.info(msg);
|
||||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
|
||||||
|
if (this.sendAutoUpdateNotification) {
|
||||||
|
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isFirstRun()) {
|
if (this.isFirstRun()) {
|
||||||
const msg = '[AppUpdateService] Application update is skipping at first run.';
|
const msg = '[AppUpdateService] Application update is skipping at first run.';
|
||||||
this.logService.info(msg);
|
this.logService.info(msg);
|
||||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
|
||||||
|
if (this.sendAutoUpdateNotification) {
|
||||||
|
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BrowserWindow, ipcMain, shell } from 'electron';
|
import { ipcMain, shell } from 'electron';
|
||||||
import { UhkHidDevice } from 'uhk-usb';
|
import { UhkHidDevice } from 'uhk-usb';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
||||||
import { MainServiceBase } from './main-service-base';
|
import { MainServiceBase } from './main-service-base';
|
||||||
@@ -22,13 +23,17 @@ export class AppService extends MainServiceBase {
|
|||||||
|
|
||||||
private async handleAppStartInfo(event: Electron.Event) {
|
private async handleAppStartInfo(event: Electron.Event) {
|
||||||
this.logService.info('[AppService] getAppStartInfo');
|
this.logService.info('[AppService] getAppStartInfo');
|
||||||
|
const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState();
|
||||||
const response: AppStartInfo = {
|
const response: AppStartInfo = {
|
||||||
commandLineArgs: {
|
commandLineArgs: {
|
||||||
addons: this.options.addons || false
|
addons: this.options.addons || false,
|
||||||
|
layerDoubleTap: this.options['layer-double-tap'] || false
|
||||||
},
|
},
|
||||||
deviceConnected: this.uhkHidDeviceService.deviceConnected(),
|
deviceConnected: deviceConnectionState.connected,
|
||||||
hasPermission: this.uhkHidDeviceService.hasPermission()
|
hasPermission: deviceConnectionState.hasPermission,
|
||||||
|
bootloaderActive: deviceConnectionState.bootloaderActive,
|
||||||
|
platform: process.platform as string,
|
||||||
|
osVersion: os.release()
|
||||||
};
|
};
|
||||||
this.logService.info('[AppService] getAppStartInfo response:', response);
|
this.logService.info('[AppService] getAppStartInfo response:', response);
|
||||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ipcMain } from 'electron';
|
|||||||
import {
|
import {
|
||||||
ConfigurationReply,
|
ConfigurationReply,
|
||||||
DeviceConnectionState,
|
DeviceConnectionState,
|
||||||
|
FirmwareUpgradeIpcResponse,
|
||||||
getHardwareConfigFromDeviceResponse,
|
getHardwareConfigFromDeviceResponse,
|
||||||
HardwareModules,
|
HardwareModules,
|
||||||
IpcEvents,
|
IpcEvents,
|
||||||
@@ -10,10 +11,11 @@ import {
|
|||||||
mapObjectToUserConfigBinaryBuffer,
|
mapObjectToUserConfigBinaryBuffer,
|
||||||
SaveUserConfigurationData
|
SaveUserConfigurationData
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { emptyDir } from 'fs-extra';
|
import { emptyDir } from 'fs-extra';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
import 'rxjs/add/observable/interval';
|
import 'rxjs/add/observable/interval';
|
||||||
import 'rxjs/add/operator/startWith';
|
import 'rxjs/add/operator/startWith';
|
||||||
@@ -21,10 +23,14 @@ import 'rxjs/add/operator/map';
|
|||||||
import 'rxjs/add/operator/do';
|
import 'rxjs/add/operator/do';
|
||||||
import 'rxjs/add/operator/distinctUntilChanged';
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
|
|
||||||
import { saveTmpFirmware } from '../util/save-extract-firmware';
|
|
||||||
import { TmpFirmware } from '../models/tmp-firmware';
|
import { TmpFirmware } from '../models/tmp-firmware';
|
||||||
import { QueueManager } from './queue-manager';
|
import { QueueManager } from './queue-manager';
|
||||||
import { backupUserConfiguration, getBackupUserConfigurationContent } from '../util/backup-user-confoguration';
|
import {
|
||||||
|
backupUserConfiguration,
|
||||||
|
getBackupUserConfigurationContent,
|
||||||
|
getPackageJsonFromPathAsync,
|
||||||
|
saveTmpFirmware
|
||||||
|
} from '../util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IpcMain pair of the UHK Communication
|
* IpcMain pair of the UHK Communication
|
||||||
@@ -39,7 +45,8 @@ export class DeviceService {
|
|||||||
constructor(private logService: LogService,
|
constructor(private logService: LogService,
|
||||||
private win: Electron.BrowserWindow,
|
private win: Electron.BrowserWindow,
|
||||||
private device: UhkHidDevice,
|
private device: UhkHidDevice,
|
||||||
private operations: UhkOperations) {
|
private operations: UhkOperations,
|
||||||
|
private rootDir: string) {
|
||||||
this.pollUhkDevice();
|
this.pollUhkDevice();
|
||||||
|
|
||||||
ipcMain.on(IpcEvents.device.saveUserConfiguration, (...args: any[]) => {
|
ipcMain.on(IpcEvents.device.saveUserConfiguration, (...args: any[]) => {
|
||||||
@@ -71,6 +78,24 @@ export class DeviceService {
|
|||||||
|
|
||||||
ipcMain.on(IpcEvents.device.startConnectionPoller, this.pollUhkDevice.bind(this));
|
ipcMain.on(IpcEvents.device.startConnectionPoller, this.pollUhkDevice.bind(this));
|
||||||
|
|
||||||
|
ipcMain.on(IpcEvents.device.recoveryDevice, (...args: any[]) => {
|
||||||
|
this.queueManager.add({
|
||||||
|
method: this.recoveryDevice,
|
||||||
|
bind: this,
|
||||||
|
params: args,
|
||||||
|
asynchronous: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on(IpcEvents.device.enableUsbStackTest, (...args: any[]) => {
|
||||||
|
this.queueManager.add({
|
||||||
|
method: this.enableUsbStackTest,
|
||||||
|
bind: this,
|
||||||
|
params: args,
|
||||||
|
asynchronous: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
logService.debug('[DeviceService] init success');
|
logService.debug('[DeviceService] init success');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,10 +109,7 @@ export class DeviceService {
|
|||||||
try {
|
try {
|
||||||
await this.device.waitUntilKeyboardBusy();
|
await this.device.waitUntilKeyboardBusy();
|
||||||
const result = await this.operations.loadConfigurations();
|
const result = await this.operations.loadConfigurations();
|
||||||
const modules: HardwareModules = {
|
const modules: HardwareModules = await this.getHardwareModules(false);
|
||||||
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
|
|
||||||
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
|
|
||||||
};
|
|
||||||
|
|
||||||
const hardwareConfig = getHardwareConfigFromDeviceResponse(result.hardwareConfiguration);
|
const hardwareConfig = getHardwareConfigFromDeviceResponse(result.hardwareConfiguration);
|
||||||
const uniqueId = hardwareConfig.uniqueId;
|
const uniqueId = hardwareConfig.uniqueId;
|
||||||
@@ -110,33 +132,67 @@ export class DeviceService {
|
|||||||
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getHardwareModules(catchError: boolean): Promise<HardwareModules> {
|
||||||
|
try {
|
||||||
|
await this.device.waitUntilKeyboardBusy();
|
||||||
|
|
||||||
|
return {
|
||||||
|
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
|
||||||
|
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (!catchError) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logService.error('[DeviceService] Read hardware modules information failed', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
this.stopPollTimer();
|
this.stopPollTimer();
|
||||||
this.logService.info('[DeviceService] Device connection checker stopped.');
|
this.logService.info('[DeviceService] Device connection checker stopped.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> {
|
public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> {
|
||||||
const response = new IpcResponse();
|
const response = new FirmwareUpgradeIpcResponse();
|
||||||
|
|
||||||
let firmwarePathData: TmpFirmware;
|
let firmwarePathData: TmpFirmware;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const hardwareModules = await this.getHardwareModules(false);
|
||||||
|
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
|
||||||
|
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
|
||||||
|
|
||||||
|
this.device.resetDeviceCache();
|
||||||
this.stopPollTimer();
|
this.stopPollTimer();
|
||||||
|
|
||||||
if (args && args.length > 0) {
|
if (args && args.length > 0) {
|
||||||
firmwarePathData = await saveTmpFirmware(args[0]);
|
firmwarePathData = await saveTmpFirmware(args[0]);
|
||||||
|
|
||||||
|
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
|
||||||
|
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||||
|
|
||||||
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
||||||
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json');
|
||||||
|
const packageJson = await getPackageJsonFromPathAsync(packageJsonPath);
|
||||||
|
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||||
|
|
||||||
await this.operations.updateRightFirmware();
|
await this.operations.updateRightFirmware();
|
||||||
await this.operations.updateLeftModule();
|
await this.operations.updateLeftModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
response.success = true;
|
response.success = true;
|
||||||
|
response.modules = await this.getHardwareModules(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const err = {message: error.message, stack: error.stack};
|
const err = {message: error.message, stack: error.stack};
|
||||||
this.logService.error('[DeviceService] updateFirmware error', err);
|
this.logService.error('[DeviceService] updateFirmware error', err);
|
||||||
|
|
||||||
|
response.modules = await this.getHardwareModules(true);
|
||||||
response.error = err;
|
response.error = err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,9 +201,42 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await snooze(500);
|
await snooze(500);
|
||||||
|
|
||||||
|
this.pollUhkDevice();
|
||||||
|
|
||||||
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async recoveryDevice(event: Electron.Event): Promise<void> {
|
||||||
|
const response = new FirmwareUpgradeIpcResponse();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.stopPollTimer();
|
||||||
|
|
||||||
|
await this.operations.updateRightFirmware();
|
||||||
|
|
||||||
|
await snooze(500);
|
||||||
|
|
||||||
|
this.pollUhkDevice();
|
||||||
|
|
||||||
|
response.modules = await this.getHardwareModules(false);
|
||||||
|
response.success = true;
|
||||||
|
} catch (error) {
|
||||||
|
const err = {message: error.message, stack: error.stack};
|
||||||
|
this.logService.error('[DeviceService] updateFirmware error', err);
|
||||||
|
|
||||||
|
response.modules = await this.getHardwareModules(true);
|
||||||
|
response.error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
await snooze(500);
|
||||||
|
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async enableUsbStackTest(event: Electron.Event) {
|
||||||
|
await this.device.enableUsbStackTest();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID API not support device attached and detached event.
|
* HID API not support device attached and detached event.
|
||||||
* This method check the keyboard is attached to the computer or not.
|
* This method check the keyboard is attached to the computer or not.
|
||||||
@@ -161,16 +250,11 @@ export class DeviceService {
|
|||||||
|
|
||||||
this.pollTimer$ = Observable.interval(1000)
|
this.pollTimer$ = Observable.interval(1000)
|
||||||
.startWith(0)
|
.startWith(0)
|
||||||
.map(() => this.device.deviceConnected())
|
.map(() => this.device.getDeviceConnectionState())
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged<DeviceConnectionState>(deviceConnectionStateComparer)
|
||||||
.do((connected: boolean) => {
|
.do((state: DeviceConnectionState) => {
|
||||||
const response: DeviceConnectionState = {
|
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
||||||
connected,
|
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
||||||
hasPermission: this.device.hasPermission()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, response);
|
|
||||||
this.logService.info('[DeviceService] Device connection state changed to:', response);
|
|
||||||
})
|
})
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
export const getPackageJsonFromPathAsync = async (filePath: string): Promise<any> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(filePath, {encoding: 'utf-8'}, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
3
packages/uhk-agent/src/util/index.ts
Normal file
3
packages/uhk-agent/src/util/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './backup-user-confoguration';
|
||||||
|
export * from './get-package-json-from-path-async';
|
||||||
|
export * from './save-extract-firmware';
|
||||||
@@ -16,8 +16,8 @@ export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
|
|||||||
return {
|
return {
|
||||||
tmpDirectory,
|
tmpDirectory,
|
||||||
rightFirmwarePath: path.join(tmpDirectory.name, 'devices/uhk60-right/firmware.hex'),
|
rightFirmwarePath: path.join(tmpDirectory.name, 'devices/uhk60-right/firmware.hex'),
|
||||||
leftFirmwarePath: path.join(tmpDirectory.name, 'modules/uhk60-left.bin')
|
leftFirmwarePath: path.join(tmpDirectory.name, 'modules/uhk60-left.bin'),
|
||||||
|
packageJsonPath: path.join(tmpDirectory.name, 'package.json')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,16 +41,20 @@ export class HardwareConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fromBinary(buffer: UhkBuffer): HardwareConfiguration {
|
fromBinary(buffer: UhkBuffer): HardwareConfiguration {
|
||||||
this.signature = buffer.readString();
|
try {
|
||||||
this.majorVersion = buffer.readUInt8();
|
this.signature = buffer.readString();
|
||||||
this.minorVersion = buffer.readUInt8();
|
this.majorVersion = buffer.readUInt8();
|
||||||
this.patchVersion = buffer.readUInt8();
|
this.minorVersion = buffer.readUInt8();
|
||||||
this.brandId = buffer.readUInt8();
|
this.patchVersion = buffer.readUInt8();
|
||||||
this.deviceId = buffer.readUInt8();
|
this.brandId = buffer.readUInt8();
|
||||||
this.uniqueId = buffer.readUInt32();
|
this.deviceId = buffer.readUInt8();
|
||||||
this.isVendorModeOn = buffer.readBoolean();
|
this.uniqueId = buffer.readUInt32();
|
||||||
this.isIso = buffer.readBoolean();
|
this.isVendorModeOn = buffer.readBoolean();
|
||||||
return this;
|
this.isIso = buffer.readBoolean();
|
||||||
|
return this;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Please power cycle your keyboard (Invalid hardware configuration: Index out of bounds)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJsonObject(): any {
|
toJsonObject(): any {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { binaryDefaultHelper, jsonDefaultHelper } from '../../../../test/serializer-test-helper';
|
import { binaryDefaultHelper, jsonDefaultHelper } from '../../../../test/serializer-test-helper';
|
||||||
import { SwitchLayerAction } from './switch-layer-action';
|
import { SwitchLayerAction, SwitchLayerMode } from './switch-layer-action';
|
||||||
|
import { keyActionType } from './key-action';
|
||||||
|
|
||||||
// TODO: Add null, undefined, empty object, empty buffer test cases
|
// TODO: Add null, undefined, empty object, empty buffer test cases
|
||||||
describe('switch-layer-action', () => {
|
describe('switch-layer-action', () => {
|
||||||
const action = new SwitchLayerAction(<SwitchLayerAction>{layer: 0, isLayerToggleable: false});
|
const action = new SwitchLayerAction(<SwitchLayerAction>{layer: 0, switchLayerMode: SwitchLayerMode.hold});
|
||||||
|
|
||||||
it('should be instantiate', () => {
|
it('should be instantiate', () => {
|
||||||
expect(new SwitchLayerAction()).toBeTruthy();
|
expect(new SwitchLayerAction()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('should return <SwitchLayerAction layer="0" toggle="false">', () => {
|
it('should return <SwitchLayerAction layer="0" switchLayerMode="hold">', () => {
|
||||||
expect(action.toString()).toEqual('<SwitchLayerAction layer="0" toggle="false">');
|
expect(action.toString()).toEqual('<SwitchLayerAction layer="0" switchLayerMode="hold">');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,4 +31,20 @@ describe('switch-layer-action', () => {
|
|||||||
binaryDefaultHelper(action);
|
binaryDefaultHelper(action);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('backward compatibility of the "toggle" property ', () => {
|
||||||
|
it('should map toggle=false to SwitchLayerMode.holdAndDoubleTapToggle', () => {
|
||||||
|
const oldAction = new SwitchLayerAction();
|
||||||
|
oldAction.fromJsonObject({keyActionType: keyActionType.SwitchLayerAction, layer: 0, toggle: false});
|
||||||
|
|
||||||
|
expect(oldAction.switchLayerMode).toEqual(SwitchLayerMode.holdAndDoubleTapToggle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map toggle=true to SwitchLayerMode.toggle', () => {
|
||||||
|
const oldAction = new SwitchLayerAction();
|
||||||
|
oldAction.fromJsonObject({keyActionType: keyActionType.SwitchLayerAction, layer: 0, toggle: true});
|
||||||
|
|
||||||
|
expect(oldAction.switchLayerMode).toEqual(SwitchLayerMode.toggle);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,9 +8,48 @@ export enum LayerName {
|
|||||||
mouse
|
mouse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SwitchLayerMode {
|
||||||
|
holdAndDoubleTapToggle = 'holdAndDoubleTapToggle',
|
||||||
|
toggle = 'toggle',
|
||||||
|
hold = 'hold'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapSwitchLayerModeToNumber = (switchLayerMode: SwitchLayerMode): number => {
|
||||||
|
switch (switchLayerMode) {
|
||||||
|
case SwitchLayerMode.holdAndDoubleTapToggle:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case SwitchLayerMode.toggle:
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case SwitchLayerMode.hold:
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Can not map ${switchLayerMode} to number`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapNumberToSwitchLayerMode = (value: number): SwitchLayerMode => {
|
||||||
|
switch (value) {
|
||||||
|
case 0:
|
||||||
|
return SwitchLayerMode.holdAndDoubleTapToggle;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return SwitchLayerMode.toggle;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return SwitchLayerMode.hold;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Can not map "${value}" to SwitchLayerMode`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export class SwitchLayerAction extends KeyAction {
|
export class SwitchLayerAction extends KeyAction {
|
||||||
|
|
||||||
isLayerToggleable: boolean;
|
@assertEnum(SwitchLayerMode)
|
||||||
|
switchLayerMode: SwitchLayerMode;
|
||||||
|
|
||||||
@assertEnum(LayerName)
|
@assertEnum(LayerName)
|
||||||
layer: LayerName;
|
layer: LayerName;
|
||||||
@@ -20,21 +59,29 @@ export class SwitchLayerAction extends KeyAction {
|
|||||||
if (!other) {
|
if (!other) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isLayerToggleable = other.isLayerToggleable;
|
this.switchLayerMode = other.switchLayerMode;
|
||||||
this.layer = other.layer;
|
this.layer = other.layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromJsonObject(jsonObject: any): SwitchLayerAction {
|
fromJsonObject(jsonObject: any): SwitchLayerAction {
|
||||||
this.assertKeyActionType(jsonObject);
|
this.assertKeyActionType(jsonObject);
|
||||||
this.layer = LayerName[<string>jsonObject.layer];
|
this.layer = LayerName[<string>jsonObject.layer];
|
||||||
this.isLayerToggleable = jsonObject.toggle;
|
|
||||||
|
// Backward compatibility when "switchLayerMode" was a boolean type as "toggle"
|
||||||
|
if (typeof jsonObject.toggle === 'boolean') {
|
||||||
|
this.switchLayerMode = jsonObject.toggle ? SwitchLayerMode.toggle : SwitchLayerMode.holdAndDoubleTapToggle;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.switchLayerMode = jsonObject.switchLayerMode;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromBinary(buffer: UhkBuffer): SwitchLayerAction {
|
fromBinary(buffer: UhkBuffer): SwitchLayerAction {
|
||||||
this.readAndAssertKeyActionId(buffer);
|
this.readAndAssertKeyActionId(buffer);
|
||||||
this.layer = buffer.readUInt8();
|
this.layer = buffer.readUInt8();
|
||||||
this.isLayerToggleable = buffer.readBoolean();
|
this.switchLayerMode = mapNumberToSwitchLayerMode(buffer.readUInt8());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,18 +89,18 @@ export class SwitchLayerAction extends KeyAction {
|
|||||||
return {
|
return {
|
||||||
keyActionType: keyActionType.SwitchLayerAction,
|
keyActionType: keyActionType.SwitchLayerAction,
|
||||||
layer: LayerName[this.layer],
|
layer: LayerName[this.layer],
|
||||||
toggle: this.isLayerToggleable
|
switchLayerMode: this.switchLayerMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toBinary(buffer: UhkBuffer) {
|
toBinary(buffer: UhkBuffer) {
|
||||||
buffer.writeUInt8(KeyActionId.SwitchLayerAction);
|
buffer.writeUInt8(KeyActionId.SwitchLayerAction);
|
||||||
buffer.writeUInt8(this.layer);
|
buffer.writeUInt8(this.layer);
|
||||||
buffer.writeBoolean(this.isLayerToggleable);
|
buffer.writeUInt8(mapSwitchLayerModeToNumber(this.switchLayerMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `<SwitchLayerAction layer="${this.layer}" toggle="${this.isLayerToggleable}">`;
|
return `<SwitchLayerAction layer="${this.layer}" switchLayerMode="${this.switchLayerMode}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export class Keymap {
|
|||||||
if (currentLayerId - 1 === baseKeyAction.layer) {
|
if (currentLayerId - 1 === baseKeyAction.layer) {
|
||||||
if (currentKeyAction instanceof SwitchLayerAction) {
|
if (currentKeyAction instanceof SwitchLayerAction) {
|
||||||
if (currentKeyAction.layer === baseKeyAction.layer &&
|
if (currentKeyAction.layer === baseKeyAction.layer &&
|
||||||
currentKeyAction.isLayerToggleable === baseKeyAction.isLayerToggleable) {
|
currentKeyAction.switchLayerMode === baseKeyAction.switchLayerMode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { assertEnum, assertUInt8 } from '../../assert';
|
import { assertEnum, assertUInt8, assertUInt16 } from '../../assert';
|
||||||
import { UhkBuffer } from '../../uhk-buffer';
|
import { UhkBuffer } from '../../uhk-buffer';
|
||||||
import { KeyModifiers } from '../key-modifiers';
|
import { KeyModifiers } from '../key-modifiers';
|
||||||
import { MacroAction, MacroActionId, MacroKeySubAction, macroActionType } from './macro-action';
|
import { MacroAction, MacroActionId, MacroKeySubAction, macroActionType } from './macro-action';
|
||||||
@@ -20,12 +20,24 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
@assertEnum(KeystrokeType)
|
@assertEnum(KeystrokeType)
|
||||||
type: KeystrokeType;
|
type: KeystrokeType;
|
||||||
|
|
||||||
@assertUInt8
|
|
||||||
scancode: number;
|
|
||||||
|
|
||||||
@assertUInt8
|
@assertUInt8
|
||||||
modifierMask: number;
|
modifierMask: number;
|
||||||
|
|
||||||
|
@assertUInt16
|
||||||
|
private _scancode: number;
|
||||||
|
|
||||||
|
set scancode(scancode: number) {
|
||||||
|
this._scancode = scancode;
|
||||||
|
if (this.type !== KeystrokeType.shortMedia && this.type !== KeystrokeType.longMedia) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.type = scancode < 256 ? KeystrokeType.shortMedia : KeystrokeType.longMedia;
|
||||||
|
}
|
||||||
|
|
||||||
|
get scancode() {
|
||||||
|
return this._scancode;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(other?: KeyMacroAction) {
|
constructor(other?: KeyMacroAction) {
|
||||||
super();
|
super();
|
||||||
if (!other) {
|
if (!other) {
|
||||||
@@ -33,7 +45,7 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
}
|
}
|
||||||
this.action = other.action;
|
this.action = other.action;
|
||||||
this.type = other.type;
|
this.type = other.type;
|
||||||
this.scancode = other.scancode;
|
this._scancode = other._scancode;
|
||||||
this.modifierMask = other.modifierMask;
|
this.modifierMask = other.modifierMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +57,7 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
} else {
|
} else {
|
||||||
this.type = KeystrokeType[jsObject.type];
|
this.type = KeystrokeType[jsObject.type];
|
||||||
}
|
}
|
||||||
this.scancode = jsObject.scancode;
|
this._scancode = jsObject.scancode;
|
||||||
this.modifierMask = jsObject.modifierMask;
|
this.modifierMask = jsObject.modifierMask;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -58,7 +70,7 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
this.type = keyMacroType & 0b11;
|
this.type = keyMacroType & 0b11;
|
||||||
keyMacroType >>= 2;
|
keyMacroType >>= 2;
|
||||||
if (keyMacroType & 0b10) {
|
if (keyMacroType & 0b10) {
|
||||||
this.scancode = buffer.readUInt8();
|
this._scancode = this.type === KeystrokeType.longMedia ? buffer.readUInt16() : buffer.readUInt8();
|
||||||
}
|
}
|
||||||
if (keyMacroType & 0b01) {
|
if (keyMacroType & 0b01) {
|
||||||
this.modifierMask = buffer.readUInt8();
|
this.modifierMask = buffer.readUInt8();
|
||||||
@@ -78,7 +90,7 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
} else {
|
} else {
|
||||||
jsObject.type = KeystrokeType[this.type];
|
jsObject.type = KeystrokeType[this.type];
|
||||||
}
|
}
|
||||||
jsObject.scancode = this.scancode;
|
jsObject.scancode = this._scancode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasModifiers()) {
|
if (this.hasModifiers()) {
|
||||||
@@ -98,7 +110,11 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
|
|
||||||
buffer.writeUInt8(keyMacroType);
|
buffer.writeUInt8(keyMacroType);
|
||||||
if (this.hasScancode()) {
|
if (this.hasScancode()) {
|
||||||
buffer.writeUInt8(this.scancode);
|
if (this.type === KeystrokeType.longMedia) {
|
||||||
|
buffer.writeUInt16(this.scancode);
|
||||||
|
} else {
|
||||||
|
buffer.writeUInt8(this.scancode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.hasModifiers()) {
|
if (this.hasModifiers()) {
|
||||||
buffer.writeUInt8(this.modifierMask);
|
buffer.writeUInt8(this.modifierMask);
|
||||||
@@ -106,7 +122,7 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `<KeyMacroAction action="${this.action}" scancode="${this.scancode}" modifierMask="${this.modifierMask}">`;
|
return `<KeyMacroAction action="${this.action}" scancode="${this._scancode}" modifierMask="${this.modifierMask}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
isModifierActive(modifier: KeyModifiers): boolean {
|
isModifierActive(modifier: KeyModifiers): boolean {
|
||||||
@@ -114,7 +130,7 @@ export class KeyMacroAction extends MacroAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasScancode(): boolean {
|
hasScancode(): boolean {
|
||||||
return !!this.scancode;
|
return !!this._scancode;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasModifiers(): boolean {
|
hasModifiers(): boolean {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -121,7 +121,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -151,7 +151,7 @@ describe('keymap', () => {
|
|||||||
|
|
||||||
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" toggle="false">');
|
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" switchLayerMode="holdAndDoubleTapToggle">');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should normalize SwitchLayerAction if non base layer action is other SwitchLayerAction', () => {
|
it('should normalize SwitchLayerAction if non base layer action is other SwitchLayerAction', () => {
|
||||||
@@ -262,7 +262,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -274,7 +274,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -304,6 +304,6 @@ describe('keymap', () => {
|
|||||||
|
|
||||||
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" toggle="false"> will be override with <SwitchLayerAction layer="0" toggle="false">');
|
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" switchLayerMode="holdAndDoubleTapToggle"> will be override with <SwitchLayerAction layer="0" switchLayerMode="holdAndDoubleTapToggle">');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,4 +4,7 @@ export interface AppStartInfo {
|
|||||||
commandLineArgs: CommandLineArgs;
|
commandLineArgs: CommandLineArgs;
|
||||||
deviceConnected: boolean;
|
deviceConnected: boolean;
|
||||||
hasPermission: boolean;
|
hasPermission: boolean;
|
||||||
|
bootloaderActive: boolean;
|
||||||
|
platform: string;
|
||||||
|
osVersion: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ export interface CommandLineArgs {
|
|||||||
* simulate privilege escalation error
|
* simulate privilege escalation error
|
||||||
*/
|
*/
|
||||||
spe?: boolean;
|
spe?: boolean;
|
||||||
|
/**
|
||||||
|
* show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
|
||||||
|
* if it false the checkbox invisible and the value of the checkbox = true
|
||||||
|
*/
|
||||||
|
layerDoubleTap?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface DeviceConnectionState {
|
export interface DeviceConnectionState {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
hasPermission: boolean;
|
hasPermission: boolean;
|
||||||
|
bootloaderActive: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
import { HardwareModules } from './hardware-modules';
|
||||||
|
|
||||||
export class IpcResponse {
|
export class IpcResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: { message: string };
|
error?: { message: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FirmwareUpgradeIpcResponse extends IpcResponse {
|
||||||
|
modules?: HardwareModules;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export namespace Constants {
|
export namespace Constants {
|
||||||
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
||||||
|
export const FIRMWARE_GITHUB_ISSUE_URL = 'https://github.com/UltimateHackingKeyboard/agent/issues/new';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ export const getHardwareConfigFromDeviceResponse = (json: string): HardwareConfi
|
|||||||
const hardwareConfig = new HardwareConfiguration();
|
const hardwareConfig = new HardwareConfiguration();
|
||||||
hardwareConfig.fromBinary(UhkBuffer.fromArray(data));
|
hardwareConfig.fromBinary(UhkBuffer.fromArray(data));
|
||||||
|
|
||||||
if (hardwareConfig.uniqueId > 0) {
|
if (hardwareConfig.signature === 'FTY') {
|
||||||
return hardwareConfig;
|
throw Error('The device is in factory reset mode. Power-cycle the device to use it with Agent!');
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
if (hardwareConfig.signature !== 'UHK') {
|
||||||
|
throw Error('Please power cycle your keyboard (Invalid hardware configuration: Invalid signature)');
|
||||||
|
}
|
||||||
|
|
||||||
|
return hardwareConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserConfigFromDeviceResponse = (json: string): UserConfiguration => {
|
export const getUserConfigFromDeviceResponse = (json: string): UserConfiguration => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export { IpcEvents } from './ipcEvents';
|
|||||||
export * from './log';
|
export * from './log';
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
export * from './helpers';
|
export * from './helpers';
|
||||||
|
export * from './is-equal-array';
|
||||||
|
|
||||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||||
export function camelCaseToSentence(camelCasedText: string): string {
|
export function camelCaseToSentence(camelCasedText: string): string {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export class Device {
|
|||||||
public static readonly updateFirmware = 'device-update-firmware';
|
public static readonly updateFirmware = 'device-update-firmware';
|
||||||
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
|
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
|
||||||
public static readonly startConnectionPoller = 'device-start-connection-poller';
|
public static readonly startConnectionPoller = 'device-start-connection-poller';
|
||||||
|
public static readonly recoveryDevice = 'device-recovery';
|
||||||
|
public static readonly enableUsbStackTest = 'enable-usb-stack-test';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IpcEvents {
|
export class IpcEvents {
|
||||||
|
|||||||
15
packages/uhk-common/src/util/is-equal-array.ts
Normal file
15
packages/uhk-common/src/util/is-equal-array.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
export const isEqualArray = (arr1: Array<any>, arr2: Array<any>): boolean => {
|
||||||
|
if (arr1.length !== arr2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const a of arr1) {
|
||||||
|
if (!arr2.some(b => isEqual(a, b))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export namespace Constants {
|
export namespace Constants {
|
||||||
export const VENDOR_ID = 0x1D50;
|
export const VENDOR_ID = 0x1D50;
|
||||||
export const PRODUCT_ID = 0x6122;
|
export const PRODUCT_ID = 0x6122;
|
||||||
|
export const BOOTLOADER_ID = 0x6120;
|
||||||
export const MAX_PAYLOAD_SIZE = 64;
|
export const MAX_PAYLOAD_SIZE = 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +23,12 @@ export enum UsbCommand {
|
|||||||
GetDebugBuffer = 0x0b,
|
GetDebugBuffer = 0x0b,
|
||||||
GetAdcValue = 0x0c,
|
GetAdcValue = 0x0c,
|
||||||
SetLedPwmBrightness = 0x0d,
|
SetLedPwmBrightness = 0x0d,
|
||||||
GetModuleProperty = 0x0e
|
GetModuleProperty = 0x0e,
|
||||||
|
GetSlaveI2cErrors = 0x0f,
|
||||||
|
SetI2cBaudRate = 0x10,
|
||||||
|
SwitchKeymap = 0x11,
|
||||||
|
GetVariable = 0x12,
|
||||||
|
SetVariable = 0x13
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EepromOperation {
|
export enum EepromOperation {
|
||||||
@@ -85,3 +91,10 @@ export enum KbootCommands {
|
|||||||
export enum ModulePropertyId {
|
export enum ModulePropertyId {
|
||||||
protocolVersions = 0
|
protocolVersions = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum UsbVariables {
|
||||||
|
testSwitches = 0,
|
||||||
|
testUsbStack = 1,
|
||||||
|
debounceTimePress = 2,
|
||||||
|
debounceTimeRelease = 3
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class UhkBlhost {
|
|||||||
|
|
||||||
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
||||||
|
|
||||||
if (code !== null && code !== 0) {
|
if (code !== 0) {
|
||||||
return reject(new Error(`blhost error code:${code}`));
|
return reject(new Error(`blhost error code:${code}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Device, devices, HID } from 'node-hid';
|
import { Device, devices, HID } from 'node-hid';
|
||||||
import { CommandLineArgs, LogService } from 'uhk-common';
|
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService } from 'uhk-common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConfigBufferId,
|
ConfigBufferId,
|
||||||
@@ -10,9 +10,10 @@ import {
|
|||||||
KbootCommands,
|
KbootCommands,
|
||||||
ModuleSlotToI2cAddress,
|
ModuleSlotToI2cAddress,
|
||||||
ModuleSlotToId,
|
ModuleSlotToId,
|
||||||
UsbCommand
|
UsbCommand,
|
||||||
|
UsbVariables
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { bufferToString, getTransferData, retry, snooze } from './util';
|
import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util';
|
||||||
|
|
||||||
export const BOOTLOADER_TIMEOUT_MS = 5000;
|
export const BOOTLOADER_TIMEOUT_MS = 5000;
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ export class UhkHidDevice {
|
|||||||
* Internal variable that represent the USB UHK device
|
* Internal variable that represent the USB UHK device
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
private _prevDevices = [];
|
||||||
private _device: HID;
|
private _device: HID;
|
||||||
private _hasPermission = false;
|
private _hasPermission = false;
|
||||||
|
|
||||||
@@ -48,12 +50,16 @@ export class UhkHidDevice {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.deviceConnected()) {
|
const dev = devices().find((x: Device) => isUhkDevice(x) || x.productId === Constants.BOOTLOADER_ID);
|
||||||
|
|
||||||
|
if (!dev) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._hasPermission = this.getDevice() !== null;
|
const device = new HID(dev.path);
|
||||||
this.close();
|
device.close();
|
||||||
|
|
||||||
|
this._hasPermission = true;
|
||||||
|
|
||||||
return this._hasPermission;
|
return this._hasPermission;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -67,15 +73,24 @@ export class UhkHidDevice {
|
|||||||
* Return with true is an UHK Device is connected to the computer.
|
* Return with true is an UHK Device is connected to the computer.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
public deviceConnected(): boolean {
|
public getDeviceConnectionState(): DeviceConnectionState {
|
||||||
const connected = devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
|
const devs = devices();
|
||||||
dev.productId === Constants.PRODUCT_ID);
|
const result: DeviceConnectionState = {
|
||||||
|
bootloaderActive: false,
|
||||||
|
connected: false,
|
||||||
|
hasPermission: this.hasPermission()
|
||||||
|
};
|
||||||
|
|
||||||
if (!connected) {
|
for (const dev of devs) {
|
||||||
this._hasPermission = false;
|
if (isUhkDevice(dev)) {
|
||||||
|
result.connected = true;
|
||||||
|
} else if (dev.vendorId === Constants.VENDOR_ID &&
|
||||||
|
dev.productId === Constants.BOOTLOADER_ID) {
|
||||||
|
result.bootloaderActive = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return connected;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,6 +110,7 @@ export class UhkHidDevice {
|
|||||||
device.read((err: any, receivedData: Array<number>) => {
|
device.read((err: any, receivedData: Array<number>) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.logService.error('[UhkHidDevice] Transfer error: ', err);
|
this.logService.error('[UhkHidDevice] Transfer error: ', err);
|
||||||
|
this.close();
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
const logString = bufferToString(receivedData);
|
const logString = bufferToString(receivedData);
|
||||||
@@ -118,6 +134,11 @@ export class UhkHidDevice {
|
|||||||
await this.waitUntilKeyboardBusy();
|
await this.waitUntilKeyboardBusy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async enableUsbStackTest(): Promise<void> {
|
||||||
|
await this.write(new Buffer([UsbCommand.SetVariable, UsbVariables.testUsbStack, 1]));
|
||||||
|
await this.waitUntilKeyboardBusy();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the communication chanel with UHK Device
|
* Close the communication chanel with UHK Device
|
||||||
*/
|
*/
|
||||||
@@ -142,6 +163,10 @@ export class UhkHidDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resetDeviceCache(): void {
|
||||||
|
this._prevDevices = [];
|
||||||
|
}
|
||||||
|
|
||||||
async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
|
async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
|
||||||
const reenumMode = EnumerationModes[enumerationMode].toString();
|
const reenumMode = EnumerationModes[enumerationMode].toString();
|
||||||
this.logService.debug(`[UhkHidDevice] Start reenumeration, mode: ${reenumMode}`);
|
this.logService.debug(`[UhkHidDevice] Start reenumeration, mode: ${reenumMode}`);
|
||||||
@@ -197,7 +222,7 @@ export class UhkHidDevice {
|
|||||||
|
|
||||||
async sendKbootCommandToModule(module: ModuleSlotToI2cAddress, command: KbootCommands, maxTry = 1): Promise<any> {
|
async sendKbootCommandToModule(module: ModuleSlotToI2cAddress, command: KbootCommands, maxTry = 1): Promise<any> {
|
||||||
let transfer;
|
let transfer;
|
||||||
const moduleName = kbootKommandName(module);
|
const moduleName = kbootCommandName(module);
|
||||||
this.logService.debug(`[UhkHidDevice] USB[T]: Send KbootCommand ${moduleName} ${KbootCommands[command].toString()}`);
|
this.logService.debug(`[UhkHidDevice] USB[T]: Send KbootCommand ${moduleName} ${KbootCommands[command].toString()}`);
|
||||||
if (command === KbootCommands.idle) {
|
if (command === KbootCommands.idle) {
|
||||||
transfer = new Buffer([UsbCommand.SendKbootCommandToModule, command]);
|
transfer = new Buffer([UsbCommand.SendKbootCommandToModule, command]);
|
||||||
@@ -233,19 +258,24 @@ export class UhkHidDevice {
|
|||||||
private connectToDevice(): HID {
|
private connectToDevice(): HID {
|
||||||
try {
|
try {
|
||||||
const devs = devices();
|
const devs = devices();
|
||||||
this.logService.debug('[UhkHidDevice] Available devices:', devs);
|
if (!isEqualArray(this._prevDevices, devs)) {
|
||||||
|
this.logService.debug('[UhkHidDevice] Available devices:');
|
||||||
|
for (const logDevice of devs) {
|
||||||
|
this.logService.debug(JSON.stringify(logDevice));
|
||||||
|
}
|
||||||
|
this._prevDevices = devs;
|
||||||
|
} else {
|
||||||
|
this.logService.debug('[UhkHidDevice] Available devices unchanged');
|
||||||
|
}
|
||||||
|
|
||||||
const dev = devs.find((x: Device) =>
|
const dev = devs.find(isUhkDevice);
|
||||||
x.vendorId === Constants.VENDOR_ID &&
|
|
||||||
x.productId === Constants.PRODUCT_ID &&
|
|
||||||
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
|
|
||||||
|
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
this.logService.debug('[UhkHidDevice] UHK Device not found:');
|
this.logService.debug('[UhkHidDevice] UHK Device not found:');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const device = new HID(dev.path);
|
const device = new HID(dev.path);
|
||||||
this.logService.debug('[UhkHidDevice] Used device:', dev);
|
this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev));
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -256,7 +286,7 @@ export class UhkHidDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function kbootKommandName(module: ModuleSlotToI2cAddress): string {
|
function kbootCommandName(module: ModuleSlotToI2cAddress): string {
|
||||||
switch (module) {
|
switch (module) {
|
||||||
case ModuleSlotToI2cAddress.leftHalf:
|
case ModuleSlotToI2cAddress.leftHalf:
|
||||||
return 'leftHalf';
|
return 'leftHalf';
|
||||||
@@ -267,7 +297,7 @@ function kbootKommandName(module: ModuleSlotToI2cAddress): string {
|
|||||||
case ModuleSlotToI2cAddress.rightAddon:
|
case ModuleSlotToI2cAddress.rightAddon:
|
||||||
return 'rightAddon';
|
return 'rightAddon';
|
||||||
|
|
||||||
default :
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
import { UhkBlhost } from './uhk-blhost';
|
import { UhkBlhost } from './uhk-blhost';
|
||||||
import { UhkHidDevice } from './uhk-hid-device';
|
import { UhkHidDevice } from './uhk-hid-device';
|
||||||
import { snooze } from './util';
|
import { snooze } from './util';
|
||||||
@@ -29,6 +30,7 @@ export class UhkOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) {
|
public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) {
|
||||||
|
this.logService.debug(`[UhkOperations] Operating system: ${os.type()} ${os.release()} ${os.arch()}`);
|
||||||
this.logService.debug('[UhkOperations] Start flashing right firmware');
|
this.logService.debug('[UhkOperations] Start flashing right firmware');
|
||||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`];
|
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`];
|
||||||
|
|
||||||
@@ -57,8 +59,9 @@ export class UhkOperations {
|
|||||||
|
|
||||||
const leftModuleBricked = await this.waitForKbootIdle();
|
const leftModuleBricked = await this.waitForKbootIdle();
|
||||||
if (!leftModuleBricked) {
|
if (!leftModuleBricked) {
|
||||||
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
const msg = '[UhkOperations] Couldn\'t connect to the left keyboard half.';
|
||||||
return;
|
this.logService.error(msg);
|
||||||
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { Device } from 'node-hid';
|
||||||
|
import { DeviceConnectionState, LogService } from 'uhk-common';
|
||||||
|
|
||||||
import { Constants, UsbCommand } from './constants';
|
import { Constants, UsbCommand } from './constants';
|
||||||
import { LogService } from 'uhk-common';
|
|
||||||
|
|
||||||
export const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
|
export const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
@@ -75,7 +77,6 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
|||||||
try {
|
try {
|
||||||
// logService.debug(`[retry] try to run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
// logService.debug(`[retry] try to run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||||
await command();
|
await command();
|
||||||
await snooze(100);
|
|
||||||
// logService.debug(`[retry] success FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
// logService.debug(`[retry] success FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -91,7 +92,23 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
|||||||
if (logService) {
|
if (logService) {
|
||||||
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||||
}
|
}
|
||||||
|
await snooze(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const deviceConnectionStateComparer = (a: DeviceConnectionState, b: DeviceConnectionState): boolean => {
|
||||||
|
return a.hasPermission === b.hasPermission
|
||||||
|
&& a.connected === b.connected
|
||||||
|
&& a.bootloaderActive === b.bootloaderActive;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isUhkDevice = (dev: Device): boolean => {
|
||||||
|
return dev.vendorId === Constants.VENDOR_ID &&
|
||||||
|
dev.productId === Constants.PRODUCT_ID &&
|
||||||
|
// hidapi can not read the interface number on Mac, so check the usage page and usage
|
||||||
|
((dev.usagePage === 128 && dev.usage === 129) || // Old firmware
|
||||||
|
(dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware
|
||||||
|
dev.interface === 0);
|
||||||
|
};
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||||
"../node_modules/select2/dist/js/select2.full.js",
|
|
||||||
"../node_modules/nouislider/distribute/nouislider.js"
|
"../node_modules/nouislider/distribute/nouislider.js"
|
||||||
],
|
],
|
||||||
"environmentSource": "environments/environment.ts",
|
"environmentSource": "environments/environment.ts",
|
||||||
|
|||||||
69
packages/uhk-web/package-lock.json
generated
69
packages/uhk-web/package-lock.json
generated
@@ -1154,6 +1154,14 @@
|
|||||||
"tslib": "1.9.0"
|
"tslib": "1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ert78gb/ngx-select-ex": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ert78gb/ngx-select-ex/-/ngx-select-ex-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-m3DyGB1VZrxsItgc/NjBt5ZfW1DuQrxLz82ekw/ur79DZHG89EYohKWbx68lonfu8wM+AT4IHUDVqF1gFhyK0g==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@ngrx/effects": {
|
"@ngrx/effects": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-4.0.5.tgz",
|
||||||
@@ -1260,19 +1268,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.9.tgz",
|
||||||
"integrity": "sha512-AmYGadmTv+Xh6re2CH5ruyvV3znvtJbhxyT00JQAGFP2U+xgqhf+C2xfjdP/GgK5d9YmSif/UYs2ssMl4gW6fw=="
|
"integrity": "sha512-AmYGadmTv+Xh6re2CH5ruyvV3znvtJbhxyT00JQAGFP2U+xgqhf+C2xfjdP/GgK5d9YmSif/UYs2ssMl4gW6fw=="
|
||||||
},
|
},
|
||||||
"@types/lodash": {
|
|
||||||
"version": "4.14.106",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.106.tgz",
|
|
||||||
"integrity": "sha512-tOSvCVrvSqFZ4A/qrqqm6p37GZoawsZtoR0SJhlF7EonNZUgrn8FfT+RNQ11h+NUpMt6QVe36033f3qEKBwfWA=="
|
|
||||||
},
|
|
||||||
"@types/lodash-es": {
|
|
||||||
"version": "4.17.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.0.tgz",
|
|
||||||
"integrity": "sha512-h8lkWQSgT4qjs9PcIhcL2nWubZeXRVzjZxYlRFmcX9BW1PIk5qRc0djtRWZqtM+GDDFhwBt0ztRu72D/YxIcEw==",
|
|
||||||
"requires": {
|
|
||||||
"@types/lodash": "4.14.106"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "9.6.2",
|
"version": "9.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.2.tgz",
|
||||||
@@ -1283,14 +1278,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
|
"resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
|
||||||
"integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU="
|
"integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU="
|
||||||
},
|
},
|
||||||
"@types/select2": {
|
|
||||||
"version": "4.0.44",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/select2/-/select2-4.0.44.tgz",
|
|
||||||
"integrity": "sha512-aunlkCCVG3uQZns+uAvxmYlWwvv8DuVLS+rKN9Az4ENylcIvwNHDfg7oJPeGlSYSZ9vacHQ91HoRGWnhZo7jHQ==",
|
|
||||||
"requires": {
|
|
||||||
"@types/jquery": "3.2.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/selenium-webdriver": {
|
"@types/selenium-webdriver": {
|
||||||
"version": "2.53.43",
|
"version": "2.53.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz",
|
"resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz",
|
||||||
@@ -1390,11 +1377,6 @@
|
|||||||
"repeat-string": "1.6.1"
|
"repeat-string": "1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"almond": {
|
|
||||||
"version": "0.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz",
|
|
||||||
"integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA="
|
|
||||||
},
|
|
||||||
"amdefine": {
|
"amdefine": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||||
@@ -5771,11 +5753,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
|
||||||
"integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c="
|
"integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c="
|
||||||
},
|
},
|
||||||
"jquery-mousewheel": {
|
|
||||||
"version": "3.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz",
|
|
||||||
"integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU="
|
|
||||||
},
|
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.4.3",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz",
|
||||||
@@ -6075,11 +6052,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
|
||||||
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
|
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
|
||||||
},
|
},
|
||||||
"lodash-es": {
|
|
||||||
"version": "4.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz",
|
|
||||||
"integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc="
|
|
||||||
},
|
|
||||||
"lodash.assign": {
|
"lodash.assign": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||||
@@ -6527,22 +6499,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.7.7.tgz",
|
||||||
"integrity": "sha1-uEH0sxPIycinY8gPOlnVqkw6cMg="
|
"integrity": "sha1-uEH0sxPIycinY8gPOlnVqkw6cMg="
|
||||||
},
|
},
|
||||||
"ng2-select2": {
|
|
||||||
"version": "1.0.0-beta.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/ng2-select2/-/ng2-select2-1.0.0-beta.10.tgz",
|
|
||||||
"integrity": "sha1-kIsLip+M0Gc287yhax41ofaWoUU=",
|
|
||||||
"requires": {
|
|
||||||
"@types/jquery": "2.0.49",
|
|
||||||
"@types/select2": "4.0.44"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/jquery": {
|
|
||||||
"version": "2.0.49",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-2.0.49.tgz",
|
|
||||||
"integrity": "sha512-/9xLnYmohN/vD2gDnLS4cym8TUmrJu7DvZa/LELKzZjdPsvWVJiedsdu2SXNtb/DA7FGimqL2g0IoyhbNKLl8g=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ngrx-store-freeze": {
|
"ngrx-store-freeze": {
|
||||||
"version": "0.1.9",
|
"version": "0.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.1.9.tgz",
|
||||||
@@ -8108,15 +8064,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
|
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
|
||||||
},
|
},
|
||||||
"select2": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/select2/-/select2-4.0.3.tgz",
|
|
||||||
"integrity": "sha1-IHcz/pHqy5yxoT8SRjQB9HJEng8=",
|
|
||||||
"requires": {
|
|
||||||
"almond": "0.3.3",
|
|
||||||
"jquery-mousewheel": "3.1.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"selenium-webdriver": {
|
"selenium-webdriver": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz",
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"@types/jasmine": "2.5.53",
|
"@types/jasmine": "2.5.53",
|
||||||
"@types/jasminewd2": "2.0.2",
|
"@types/jasminewd2": "2.0.2",
|
||||||
"@types/jquery": "3.2.9",
|
"@types/jquery": "3.2.9",
|
||||||
"@types/lodash-es": "4.17.0",
|
|
||||||
"@types/usb": "1.1.3",
|
"@types/usb": "1.1.3",
|
||||||
"angular-confirmation-popover": "3.2.0",
|
"angular-confirmation-popover": "3.2.0",
|
||||||
"angular-notifier": "2.0.0",
|
"angular-notifier": "2.0.0",
|
||||||
@@ -60,18 +59,16 @@
|
|||||||
"karma-coverage-istanbul-reporter": "1.2.1",
|
"karma-coverage-istanbul-reporter": "1.2.1",
|
||||||
"karma-jasmine": "1.1.0",
|
"karma-jasmine": "1.1.0",
|
||||||
"karma-jasmine-html-reporter": "0.2.2",
|
"karma-jasmine-html-reporter": "0.2.2",
|
||||||
"lodash-es": "4.17.4",
|
|
||||||
"ng2-dragula": "1.5.0",
|
"ng2-dragula": "1.5.0",
|
||||||
"ng2-nouislider": "^1.7.7",
|
"ng2-nouislider": "^1.7.7",
|
||||||
"ng2-select2": "1.0.0-beta.10",
|
|
||||||
"ngx-clipboard": "10.0.0",
|
"ngx-clipboard": "10.0.0",
|
||||||
|
"@ert78gb/ngx-select-ex": "3.7.0",
|
||||||
"ngrx-store-freeze": "0.1.9",
|
"ngrx-store-freeze": "0.1.9",
|
||||||
"nouislider": "^11.1.0",
|
"nouislider": "^11.1.0",
|
||||||
"postcss-url": "^7.1.2",
|
"postcss-url": "^7.1.2",
|
||||||
"protractor": "5.1.2",
|
"protractor": "5.1.2",
|
||||||
"reselect": "3.0.1",
|
"reselect": "3.0.1",
|
||||||
"rxjs": "5.5.8",
|
"rxjs": "5.5.8",
|
||||||
"select2": "4.0.3",
|
|
||||||
"typescript": "2.6.2",
|
"typescript": "2.6.2",
|
||||||
"uhk-common": "1.0.0",
|
"uhk-common": "1.0.0",
|
||||||
"xml-loader": "1.2.1",
|
"xml-loader": "1.2.1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<notifier-container></notifier-container>
|
<notifier-container></notifier-container>
|
||||||
<progress-button class="save-to-keyboard-button"
|
<progress-button class="save-to-keyboard-button"
|
||||||
*ngIf="(saveToKeyboardState$ | async).showButton"
|
*ngIf="saveToKeyboardState.showButton"
|
||||||
[@showSaveToKeyboardButton]
|
[@showSaveToKeyboardButton]
|
||||||
[state]="saveToKeyboardState$ | async"
|
[state]="saveToKeyboardState"
|
||||||
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
import { Component, HostListener, ViewEncapsulation, OnDestroy } from '@angular/core';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Action, Store } from '@ngrx/store';
|
import { Action, Store } from '@ngrx/store';
|
||||||
|
|
||||||
import 'rxjs/add/operator/last';
|
import 'rxjs/add/operator/last';
|
||||||
|
|
||||||
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
||||||
|
import { EnableUsbStackTestAction } from './store/actions/device';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
getShowAppUpdateAvailable,
|
getShowAppUpdateAvailable,
|
||||||
@@ -34,17 +36,45 @@ import { ProgressButtonState } from './store/reducers/progress-button-state';
|
|||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class MainAppComponent {
|
export class MainAppComponent implements OnDestroy {
|
||||||
showUpdateAvailable$: Observable<boolean>;
|
showUpdateAvailable$: Observable<boolean>;
|
||||||
deviceConfigurationLoaded$: Observable<boolean>;
|
deviceConfigurationLoaded$: Observable<boolean>;
|
||||||
runningInElectron$: Observable<boolean>;
|
runningInElectron$: Observable<boolean>;
|
||||||
saveToKeyboardState$: Observable<ProgressButtonState>;
|
saveToKeyboardState: ProgressButtonState;
|
||||||
|
|
||||||
|
private saveToKeyboardStateSubscription: Subscription;
|
||||||
|
|
||||||
constructor(private store: Store<AppState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
||||||
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
||||||
this.runningInElectron$ = store.select(runningInElectron);
|
this.runningInElectron$ = store.select(runningInElectron);
|
||||||
this.saveToKeyboardState$ = store.select(saveToKeyboardState);
|
this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState)
|
||||||
|
.subscribe(data => this.saveToKeyboardState = data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.saveToKeyboardStateSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.saveToKeyboardState.showButton &&
|
||||||
|
event.ctrlKey &&
|
||||||
|
event.key === 's' &&
|
||||||
|
!event.defaultPrevented) {
|
||||||
|
this.clickedOnProgressButton(this.saveToKeyboardState.action);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey &&
|
||||||
|
event.ctrlKey &&
|
||||||
|
event.altKey &&
|
||||||
|
event.metaKey &&
|
||||||
|
event.key === '|' &&
|
||||||
|
!event.defaultPrevented) {
|
||||||
|
this.enableUsbStackTest();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateApp() {
|
updateApp() {
|
||||||
@@ -58,4 +88,8 @@ export class MainAppComponent {
|
|||||||
clickedOnProgressButton(action: Action) {
|
clickedOnProgressButton(action: Action) {
|
||||||
return this.store.dispatch(action);
|
return this.store.dispatch(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableUsbStackTest() {
|
||||||
|
this.store.dispatch(new EnableUsbStackTestAction());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ import { deviceRoutes } from './components/device';
|
|||||||
import { addOnRoutes } from './components/add-on';
|
import { addOnRoutes } from './components/add-on';
|
||||||
import { keymapRoutes } from './components/keymap';
|
import { keymapRoutes } from './components/keymap';
|
||||||
import { macroRoutes } from './components/macro';
|
import { macroRoutes } from './components/macro';
|
||||||
import { PrivilegeCheckerComponent } from './components/privilege-checker/privilege-checker.component';
|
import { PrivilegeCheckerComponent } from './components/privilege-checker';
|
||||||
import { MissingDeviceComponent } from './components/missing-device/missing-device.component';
|
import { MissingDeviceComponent } from './components/missing-device';
|
||||||
import { UhkDeviceDisconnectedGuard } from './services/uhk-device-disconnected.guard';
|
import { UhkDeviceDisconnectedGuard } from './services/uhk-device-disconnected.guard';
|
||||||
import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
|
import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
|
||||||
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
|
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
|
||||||
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
|
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
|
||||||
import { MainPage } from './pages/main-page/main.page';
|
import { MainPage } from './pages/main-page/main.page';
|
||||||
import { agentRoutes } from './components/agent/agent.routes';
|
import { agentRoutes } from './components/agent';
|
||||||
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
|
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
|
||||||
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
|
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
|
||||||
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||||
|
import { RecoveryModeComponent } from './components/device';
|
||||||
|
import { UhkDeviceBootloaderNotActiveGuard } from './services/uhk-device-bootloader-not-active.guard';
|
||||||
|
|
||||||
const appRoutes: Routes = [
|
const appRoutes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -33,6 +35,11 @@ const appRoutes: Routes = [
|
|||||||
component: LoadingDevicePageComponent,
|
component: LoadingDevicePageComponent,
|
||||||
canActivate: [UhkDeviceLoadedGuard]
|
canActivate: [UhkDeviceLoadedGuard]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'recovery-device',
|
||||||
|
component: RecoveryModeComponent,
|
||||||
|
canActivate: [UhkDeviceBootloaderNotActiveGuard]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: MainPage,
|
component: MainPage,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<span>About</span>
|
<span>About</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="agent-version">Agent version: <span class="text-bold">{{version}}</span></div>
|
<div class="agent-version">Agent version: <span class="text-bold">{{ version }}</span></div>
|
||||||
<div><a class="link-github" (click)="openAgentGitHubPage($event)">Agent on GitHub</a></div>
|
<div><a class="link-github" [href]="agentGithubUrl" externalUrl>Agent on GitHub</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { Constants } from 'uhk-common';
|
import { Constants } from 'uhk-common';
|
||||||
|
|
||||||
import { AppState } from '../../../store';
|
|
||||||
import { getVersions } from '../../../util';
|
import { getVersions } from '../../../util';
|
||||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'about-page',
|
selector: 'about-page',
|
||||||
@@ -16,12 +13,5 @@ import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
|||||||
})
|
})
|
||||||
export class AboutComponent {
|
export class AboutComponent {
|
||||||
version: string = getVersions().version;
|
version: string = getVersions().version;
|
||||||
|
agentGithubUrl = Constants.AGENT_GITHUB_URL;
|
||||||
constructor(private store: Store<AppState>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
openAgentGitHubPage(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { AboutComponent } from './about/about.component';
|
import { AboutComponent } from './about/about.component';
|
||||||
|
import { HelpPageComponent } from './help-page/help-page.component';
|
||||||
|
|
||||||
export const agentRoutes: Routes = [
|
export const agentRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent
|
component: SettingsComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'help',
|
||||||
|
component: HelpPageComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
component: AboutComponent
|
component: AboutComponent
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="row">
|
||||||
|
<h1 class="col-xs-12 pane-title">
|
||||||
|
<i class="fa fa-question-circle"></i>
|
||||||
|
<span class="macro__name pane-title__name">Help</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
Frequently asked questions
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://ultimatehackingkeyboard.com/blog/2018/06/23/how-can-i-type-accented-characters-with-my-uhk" externalUrl>How can I type accented characters with my UHK?</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
Keyboard shortcuts
|
||||||
|
<ul>
|
||||||
|
<li><kbd>CTRL</kbd> + <kbd>Enter</kbd> = Remap key</li>
|
||||||
|
<li><kbd>CTRL</kbd> + <kbd>S</kbd> = Save to keyboard</li>
|
||||||
|
<li>Right click on a key = Capture key</li>
|
||||||
|
<li>Hold Shift while clicking on a key = Remap on all keymaps</li>
|
||||||
|
<li>Hold Alt while clicking on a key = Remap on all layers</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'help-page',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
templateUrl: './help-page.component.html',
|
||||||
|
styleUrls: ['./help-page.component.scss'],
|
||||||
|
host: {
|
||||||
|
'class': 'container-fluid'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class HelpPageComponent {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<input #inputControl
|
||||||
|
cancelable
|
||||||
|
[class]="css"
|
||||||
|
type="text"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[(ngModel)]="model"
|
||||||
|
(blur)="blur()"
|
||||||
|
(focus)="focus()"
|
||||||
|
(keyup.enter)="keyEnter($event)"
|
||||||
|
(keyup)="calculateTextWidth($event.target.value)">
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
forwardRef, HostListener,
|
||||||
|
Input,
|
||||||
|
Renderer2,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
import * as util from '../../util';
|
||||||
|
|
||||||
|
const noop = (_: any) => {
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'auto-grow-input',
|
||||||
|
templateUrl: './auto-grow-input.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => AutoGrowInputComponent),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AutoGrowInputComponent implements ControlValueAccessor {
|
||||||
|
@Input() maxParentWidthPercent = 1;
|
||||||
|
@Input() css: string;
|
||||||
|
|
||||||
|
@ViewChild('inputControl') inputControl: ElementRef;
|
||||||
|
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
get model(): string {
|
||||||
|
return this._model;
|
||||||
|
}
|
||||||
|
|
||||||
|
set model(value: string) {
|
||||||
|
if (this._model === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._model = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _model: string;
|
||||||
|
private _originalModel: string;
|
||||||
|
private _onChanged = noop;
|
||||||
|
private _onTouched = noop;
|
||||||
|
|
||||||
|
constructor(private _cdRef: ChangeDetectorRef,
|
||||||
|
private _renderer: Renderer2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: any): void {
|
||||||
|
this._onChanged = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any): void {
|
||||||
|
this._onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState(isDisabled: boolean): void {
|
||||||
|
if (this.disabled === isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disabled = isDisabled;
|
||||||
|
this._cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize')
|
||||||
|
windowResize(): void {
|
||||||
|
this.calculateTextWidth(this._model);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(obj: any): void {
|
||||||
|
console.log('write', new Date());
|
||||||
|
if (this.model === obj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._model = obj;
|
||||||
|
this._originalModel = obj;
|
||||||
|
this.calculateTextWidth(this._model);
|
||||||
|
this._cdRef.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
focus(): void {
|
||||||
|
this._onTouched(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
blur(): void {
|
||||||
|
if (!util.isValidName(this._model) || this._model.trim() === this._originalModel) {
|
||||||
|
this._model = this._originalModel;
|
||||||
|
this.calculateTextWidth(this._model);
|
||||||
|
this._cdRef.markForCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._originalModel = this._model;
|
||||||
|
this._onChanged(this._model);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyEnter(event): void {
|
||||||
|
event.target.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateTextWidth(text: string): void {
|
||||||
|
const htmlInput = this.inputControl.nativeElement as HTMLInputElement;
|
||||||
|
const maxWidth = htmlInput.parentElement.parentElement.offsetWidth * this.maxParentWidthPercent;
|
||||||
|
const textWidth = util.getContentWidth(window.getComputedStyle(htmlInput), text);
|
||||||
|
this._renderer.setStyle(htmlInput, 'width', Math.min(maxWidth, textWidth) + 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './auto-grow-input.component';
|
||||||
@@ -9,16 +9,14 @@
|
|||||||
|
|
||||||
<ul class="list-unstyled btn-list">
|
<ul class="list-unstyled btn-list">
|
||||||
<li>
|
<li>
|
||||||
<button class="btn btn-default"
|
<button class="btn btn-primary"
|
||||||
(click)="exportUserConfiguration($event)">Export device configuration
|
(click)="exportUserConfiguration($event)">Export device configuration
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label class="btn btn-default btn-file">
|
<file-upload (fileChanged)="changeFile($event)"
|
||||||
Import device configuration
|
label="Import device configuration">
|
||||||
<input type="file"
|
</file-upload>
|
||||||
(change)="changeFile($event)">
|
|
||||||
</label>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button class="btn btn-danger"
|
<button class="btn btn-danger"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SaveUserConfigInBinaryFileAction,
|
SaveUserConfigInBinaryFileAction,
|
||||||
SaveUserConfigInJsonFileAction
|
SaveUserConfigInJsonFileAction
|
||||||
} from '../../../store/actions/user-config';
|
} from '../../../store/actions/user-config';
|
||||||
|
import { UploadFileData } from '../../../models/upload-file-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'device-settings',
|
selector: 'device-settings',
|
||||||
@@ -42,16 +43,7 @@ export class DeviceConfigurationComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFile(event): void {
|
changeFile(data: UploadFileData): void {
|
||||||
const files = event.srcElement.files;
|
this.store.dispatch(new LoadUserConfigurationFromFileAction(data));
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { DeviceFirmwareComponent } from './firmware/device-firmware.component';
|
|||||||
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
|
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
|
||||||
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
|
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
|
||||||
import { RestoreConfigurationComponent } from './restore-configuration/restore-configuration.component';
|
import { RestoreConfigurationComponent } from './restore-configuration/restore-configuration.component';
|
||||||
|
import { RecoveryModeComponent } from './recovery-mode/recovery-mode.component';
|
||||||
|
|
||||||
export const deviceRoutes: Routes = [
|
export const deviceRoutes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -34,6 +35,10 @@ export const deviceRoutes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'restore-user-configuration',
|
path: 'restore-user-configuration',
|
||||||
component: RestoreConfigurationComponent
|
component: RestoreConfigurationComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'recovery-mode',
|
||||||
|
component: RecoveryModeComponent
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,42 +12,40 @@
|
|||||||
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p *ngIf="runningOnNotSupportedWindows$ | async">Firmware update doesn't work on Windows 7, Windows Vista,
|
||||||
<i>
|
and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
|
||||||
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>
|
<p *ngIf="firmwareUpgradeAllowed$ | async">
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||||
(click)="onUpdateFirmware()">
|
(click)="onUpdateFirmware()">
|
||||||
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
||||||
</button>
|
</button>
|
||||||
<label class="btn btn-primary btn-file"
|
<file-upload [disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||||
[class.disabled]="flashFirmwareButtonDisbabled$ | async">
|
(fileChanged)="changeFile($event)"
|
||||||
Choose firmware file and flash it
|
accept=".tar.bz2"
|
||||||
<input id="firmware-file-select"
|
label="Choose firmware file and flash it"></file-upload>
|
||||||
type="file"
|
|
||||||
accept=".tar.bz2"
|
|
||||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
|
||||||
(change)="changeFile($event)">
|
|
||||||
</label>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div *ngIf="firmwareUpgradeFailed$ | async"
|
||||||
|
class="alert alert-danger"
|
||||||
|
role="alert">
|
||||||
|
<p>Firmware update failed. Disconnect every USB device from your computer (including USB hubs, KVM switches, USB dongles, and everything else), then connect only your UHK and retry.</p>
|
||||||
|
|
||||||
|
<p>If you've tried the above and the update still keeps failing, please <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">create a GitHub issue</a>, and attach the update log.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="firmwareUpgradeSuccess$ | async"
|
||||||
|
class="alert alert-success"
|
||||||
|
role="alert">
|
||||||
|
<p>Firmware update succeeded.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-grow" #scrollMe>
|
<div class="flex-grow" *ngIf="firmwareUpgradeAllowed$ | async">
|
||||||
<xterm [logs]="xtermLog$ | async"></xterm>
|
<xterm [logs]="xtermLog$ | async"></xterm>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="flex-footer">
|
||||||
<button type="button"
|
|
||||||
class="btn btn-primary ok-button"
|
|
||||||
[disabled]="firmwareOkButtonDisabled$ | async"
|
|
||||||
(click)="onOkButtonClick()">OK
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,24 +6,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-container {
|
.link-github {
|
||||||
height: 100%;
|
cursor: pointer;
|
||||||
max-height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-grow {
|
|
||||||
background-color: black;
|
|
||||||
overflow: auto;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ok-button {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { HardwareModules, VersionInformation } from 'uhk-common';
|
import { Constants, HardwareModules, VersionInformation } from 'uhk-common';
|
||||||
|
|
||||||
|
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
firmwareOkButtonDisabled,
|
firmwareUpgradeAllowed,
|
||||||
|
firmwareUpgradeFailed,
|
||||||
|
firmwareUpgradeSuccess,
|
||||||
flashFirmwareButtonDisbabled,
|
flashFirmwareButtonDisbabled,
|
||||||
getAgentVersionInfo,
|
getAgentVersionInfo,
|
||||||
getHardwareModules,
|
getHardwareModules,
|
||||||
|
runningOnNotSupportedWindows,
|
||||||
xtermLog
|
xtermLog
|
||||||
} from '../../../store';
|
} from '../../../store';
|
||||||
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
import { UpdateFirmwareAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
||||||
import { XtermLog } from '../../../models/xterm-log';
|
import { XtermLog } from '../../../models/xterm-log';
|
||||||
|
import { UploadFileData } from '../../../models/upload-file-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'device-firmware',
|
selector: 'device-firmware',
|
||||||
@@ -26,33 +31,28 @@ import { XtermLog } from '../../../models/xterm-log';
|
|||||||
export class DeviceFirmwareComponent implements OnDestroy {
|
export class DeviceFirmwareComponent implements OnDestroy {
|
||||||
flashFirmwareButtonDisbabled$: Observable<boolean>;
|
flashFirmwareButtonDisbabled$: Observable<boolean>;
|
||||||
xtermLog$: Observable<Array<XtermLog>>;
|
xtermLog$: Observable<Array<XtermLog>>;
|
||||||
xtermLogSubscription: Subscription;
|
|
||||||
getAgentVersionInfo$: Observable<VersionInformation>;
|
getAgentVersionInfo$: Observable<VersionInformation>;
|
||||||
firmwareOkButtonDisabled$: Observable<boolean>;
|
|
||||||
hardwareModulesSubscription: Subscription;
|
hardwareModulesSubscription: Subscription;
|
||||||
hardwareModules: HardwareModules;
|
hardwareModules: HardwareModules;
|
||||||
|
runningOnNotSupportedWindows$: Observable<boolean>;
|
||||||
@ViewChild('scrollMe') divElement: ElementRef;
|
firmwareUpgradeAllowed$: Observable<boolean>;
|
||||||
|
firmwareUpgradeFailed$: Observable<boolean>;
|
||||||
|
firmwareUpgradeSuccess$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(private store: Store<AppState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
|
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
|
||||||
this.xtermLog$ = store.select(xtermLog);
|
this.xtermLog$ = store.select(xtermLog);
|
||||||
this.xtermLogSubscription = this.xtermLog$.subscribe(() => {
|
|
||||||
if (this.divElement && this.divElement.nativeElement) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.divElement.nativeElement.scrollTop = this.divElement.nativeElement.scrollHeight;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
|
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
|
||||||
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
|
|
||||||
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
||||||
this.hardwareModules = data;
|
this.hardwareModules = data;
|
||||||
});
|
});
|
||||||
|
this.runningOnNotSupportedWindows$ = store.select(runningOnNotSupportedWindows);
|
||||||
|
this.firmwareUpgradeAllowed$ = store.select(firmwareUpgradeAllowed);
|
||||||
|
this.firmwareUpgradeFailed$ = store.select(firmwareUpgradeFailed);
|
||||||
|
this.firmwareUpgradeSuccess$ = store.select(firmwareUpgradeSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.xtermLogSubscription.unsubscribe();
|
|
||||||
this.hardwareModulesSubscription.unsubscribe();
|
this.hardwareModulesSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,22 +60,12 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
|||||||
this.store.dispatch(new UpdateFirmwareAction());
|
this.store.dispatch(new UpdateFirmwareAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
onOkButtonClick(): void {
|
changeFile(data: UploadFileData): void {
|
||||||
this.store.dispatch(new UpdateFirmwareOkButtonAction());
|
this.store.dispatch(new UpdateFirmwareWithAction(data.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
changeFile(event): void {
|
openFirmwareGitHubIssuePage(event): void {
|
||||||
const files = event.srcElement.files;
|
event.preventDefault();
|
||||||
|
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.FIRMWARE_GITHUB_ISSUE_URL));
|
||||||
if (files.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileReader = new FileReader();
|
|
||||||
fileReader.onloadend = function () {
|
|
||||||
const arrayBuffer = new Uint8Array(fileReader.result);
|
|
||||||
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(arrayBuffer)));
|
|
||||||
}.bind(this);
|
|
||||||
fileReader.readAsArrayBuffer(files[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export * from './firmware/device-firmware.component';
|
|||||||
export * from './mouse-speed/mouse-speed.component';
|
export * from './mouse-speed/mouse-speed.component';
|
||||||
export * from './led-brightness/led-brightness.component';
|
export * from './led-brightness/led-brightness.component';
|
||||||
export * from './restore-configuration/restore-configuration.component';
|
export * from './restore-configuration/restore-configuration.component';
|
||||||
|
export * from './recovery-mode/recovery-mode.component';
|
||||||
export * from './device.routes';
|
export * from './device.routes';
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="full-height">
|
||||||
|
<div class="flex-container">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
<i class="fa fa-wrench"></i>
|
||||||
|
<span>Fix device</span>
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
Your device seems to be broken. No worries, Agent can fix it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
type="button"
|
||||||
|
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||||
|
(click)="onRecoveryDevice()">Fix device
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<xterm [logs]="xtermLog$ | async"></xterm>
|
||||||
|
</div>
|
||||||
|
<div class="flex-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
:host {
|
||||||
|
overflow-y: auto;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
import { XtermLog } from '../../../models/xterm-log';
|
||||||
|
import { AppState, flashFirmwareButtonDisbabled, xtermLog } from '../../../store';
|
||||||
|
import { RecoveryDeviceAction } from '../../../store/actions/device';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'device-recovery-mode',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
templateUrl: './recovery-mode.component.html',
|
||||||
|
styleUrls: ['./recovery-mode.component.scss'],
|
||||||
|
host: {
|
||||||
|
'class': 'container-fluid'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class RecoveryModeComponent implements OnInit {
|
||||||
|
flashFirmwareButtonDisbabled$: Observable<boolean>;
|
||||||
|
|
||||||
|
xtermLog$: Observable<Array<XtermLog>>;
|
||||||
|
|
||||||
|
constructor(private store: Store<AppState>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.flashFirmwareButtonDisbabled$ = this.store.select(flashFirmwareButtonDisbabled);
|
||||||
|
this.xtermLog$ = this.store.select(xtermLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRecoveryDevice(): void {
|
||||||
|
this.store.dispatch(new RecoveryDeviceAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<label class="btn btn-primary btn-file"
|
||||||
|
[class.disabled]="disabled">
|
||||||
|
{{ label }}
|
||||||
|
<input #inputControl
|
||||||
|
type="file"
|
||||||
|
[accept]="accept"
|
||||||
|
[disabled]="disabled"
|
||||||
|
(change)="changeFile($event)">
|
||||||
|
</label>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { UploadFileData } from '../../models/upload-file-data';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'file-upload',
|
||||||
|
templateUrl: './file-upload.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class FileUploadComponent {
|
||||||
|
@Input() label = 'Select file';
|
||||||
|
@Input() disabled: boolean;
|
||||||
|
@Input() accept: string;
|
||||||
|
|
||||||
|
@Output() fileChanged = new EventEmitter<UploadFileData>();
|
||||||
|
|
||||||
|
changeFile(event): void {
|
||||||
|
const files = event.srcElement.files;
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onloadend = function () {
|
||||||
|
const arrayBuffer = new Uint8Array(fileReader.result);
|
||||||
|
const target = event.target || event.srcElement || event.currentTarget;
|
||||||
|
target.value = null;
|
||||||
|
this.fileChanged.emit({
|
||||||
|
filename: event.srcElement.value,
|
||||||
|
data: Array.from(arrayBuffer)
|
||||||
|
});
|
||||||
|
}.bind(this);
|
||||||
|
fileReader.readAsArrayBuffer(files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/uhk-web/src/app/components/file-upload/index.ts
Normal file
1
packages/uhk-web/src/app/components/file-upload/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './file-upload.component';
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
[description]="description"
|
[description]="description"
|
||||||
[showDescription]="true"
|
[showDescription]="true"
|
||||||
|
oncontextmenu="return false;"
|
||||||
(keyClick)="keyClick.emit($event)"
|
(keyClick)="keyClick.emit($event)"
|
||||||
(keyHover)="keyHover.emit($event)"
|
(keyHover)="keyHover.emit($event)"
|
||||||
(capture)="capture.emit($event)"
|
(capture)="capture.emit($event)"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 809 B After Width: | Height: | Size: 853 B |
@@ -3,6 +3,11 @@ import { animate, keyframes, state, style, transition, trigger } from '@angular/
|
|||||||
import { Layer } from 'uhk-common';
|
import { Layer } from 'uhk-common';
|
||||||
|
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import {
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyHoverEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
type AnimationKeyboard =
|
type AnimationKeyboard =
|
||||||
'init' |
|
'init' |
|
||||||
@@ -82,9 +87,9 @@ export class KeyboardSliderComponent implements OnChanges {
|
|||||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||||
@Output() descriptionChanged = new EventEmitter<string>();
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
layerAnimationState: AnimationKeyboard[];
|
layerAnimationState: AnimationKeyboard[];
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||||
[halvesSplit]="keyboardSplit"
|
[halvesSplit]="keyboardSplit"
|
||||||
[keyboardLayout]="keyboardLayout$ | async"
|
[keyboardLayout]="keyboardLayout$ | async"
|
||||||
|
[allowLayerDoubleTap]="allowLayerDoubleTap$ | async"
|
||||||
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, HostListener, ViewChild } from '@angular/core';
|
import { Component, HostListener } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Keymap } from 'uhk-common';
|
import { Keymap } from 'uhk-common';
|
||||||
@@ -14,9 +14,8 @@ import 'rxjs/add/operator/combineLatest';
|
|||||||
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
import { AppState, getKeyboardLayout } from '../../../store';
|
import { allowLayerDoubleTap, AppState, getKeyboardLayout } from '../../../store';
|
||||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
import { KeymapActions } from '../../../store/actions';
|
import { KeymapActions } from '../../../store/actions';
|
||||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||||
@@ -31,13 +30,12 @@ import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription
|
|||||||
})
|
})
|
||||||
export class KeymapEditComponent {
|
export class KeymapEditComponent {
|
||||||
|
|
||||||
@ViewChild(SvgKeyboardWrapComponent) wrap: SvgKeyboardWrapComponent;
|
|
||||||
|
|
||||||
keyboardSplit: boolean;
|
keyboardSplit: boolean;
|
||||||
|
|
||||||
deletable$: Observable<boolean>;
|
deletable$: Observable<boolean>;
|
||||||
keymap$: Observable<Keymap>;
|
keymap$: Observable<Keymap>;
|
||||||
keyboardLayout$: Observable<KeyboardLayout>;
|
keyboardLayout$: Observable<KeyboardLayout>;
|
||||||
|
allowLayerDoubleTap$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
route: ActivatedRoute) {
|
route: ActivatedRoute) {
|
||||||
@@ -52,6 +50,7 @@ export class KeymapEditComponent {
|
|||||||
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
||||||
|
|
||||||
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
||||||
|
this.allowLayerDoubleTap$ = store.select(allowLayerDoubleTap);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadKeymap() {
|
downloadKeymap() {
|
||||||
|
|||||||
@@ -37,12 +37,12 @@
|
|||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
(click)="duplicateKeymap()"
|
(click)="duplicateKeymap()"
|
||||||
></i>
|
></i>
|
||||||
<i class="fa fa-download keymap__download pull-right"
|
<!--i class="fa fa-download keymap__download pull-right"
|
||||||
title="Download keymap"
|
title="Download keymap"
|
||||||
[html]="true"
|
[html]="true"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
(click)="onDownloadIconClick()"></i>
|
(click)="onDownloadIconClick()"></i-->
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</uhk-header>
|
</uhk-header>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export class MacroKeyTabComponent extends MacroBaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getKeyMacroAction(): KeyMacroAction {
|
getKeyMacroAction(): KeyMacroAction {
|
||||||
const keyMacroAction = Object.assign(new KeyMacroAction(), this.keypressTab.toKeyAction());
|
const keyMacroAction = new KeyMacroAction(this.keypressTab.toKeyAction() as any);
|
||||||
keyMacroAction.action = this.getActionType(this.activeTab);
|
keyMacroAction.action = this.getActionType(this.activeTab);
|
||||||
return keyMacroAction;
|
return keyMacroAction;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4>Type text</h4>
|
<h4>Type text</h4>
|
||||||
<textarea #macroTextInput name="macro-text" (change)="onTextChange()"
|
<textarea #macroTextInput
|
||||||
(keyup)="validate()" class="macro__text-input">{{ macroAction?.text }}</textarea>
|
name="macro-text"
|
||||||
|
(keydown)="onKeydown($event)"
|
||||||
|
(change)="onTextChange()"
|
||||||
|
(keyup)="validate()"
|
||||||
|
(paste)="onPaste($event)"
|
||||||
|
class="macro__text-input">{{ macroAction?.text }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { TextMacroAction } from 'uhk-common';
|
|||||||
|
|
||||||
import { MacroBaseComponent } from '../macro-base.component';
|
import { MacroBaseComponent } from '../macro-base.component';
|
||||||
|
|
||||||
|
const NON_ASCII_REGEXP = /[^\x00-\x7F]/g;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'macro-text-tab',
|
selector: 'macro-text-tab',
|
||||||
templateUrl: './macro-text.component.html',
|
templateUrl: './macro-text.component.html',
|
||||||
@@ -36,6 +38,41 @@ export class MacroTextTabComponent extends MacroBaseComponent implements OnInit,
|
|||||||
this.macroAction.text = this.input.nativeElement.value;
|
this.macroAction.text = this.input.nativeElement.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not allow non ascii character
|
||||||
|
* @param $event
|
||||||
|
*/
|
||||||
|
onKeydown($event: KeyboardEvent): void {
|
||||||
|
if (new RegExp(NON_ASCII_REGEXP).test($event.key)) {
|
||||||
|
$event.preventDefault();
|
||||||
|
$event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove non ascii character from clipboard data
|
||||||
|
* @param $event
|
||||||
|
*/
|
||||||
|
onPaste($event: ClipboardEvent): void {
|
||||||
|
$event.preventDefault();
|
||||||
|
|
||||||
|
const textarea: HTMLTextAreaElement = this.input.nativeElement;
|
||||||
|
const data = $event.clipboardData.getData('text/plain');
|
||||||
|
const text = data && data.replace(NON_ASCII_REGEXP, '') || '';
|
||||||
|
if (text.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = textarea.value || '';
|
||||||
|
const prefix = value.substr(0, textarea.selectionStart);
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const suffix = value.substr(textarea.selectionEnd);
|
||||||
|
textarea.value = prefix + text + suffix;
|
||||||
|
const correction = end === 0 ? 0 : 1;
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = end + text.length - correction;
|
||||||
|
this.macroAction.text = textarea.value;
|
||||||
|
}
|
||||||
|
|
||||||
isMacroValid = () => !!this.input.nativeElement.value;
|
isMacroValid = () => !!this.input.nativeElement.value;
|
||||||
|
|
||||||
private init = () => {
|
private init = () => {
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
|
<icon *ngIf="deletable" name="trash" (click)="deleteAction()"></icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group-item macro-action-editor__container"
|
<div class="list-group-item macro-action-editor__container"
|
||||||
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'">
|
[@toggler]="((editable && editing) || newItem) ? 'active' : 'inactive'"
|
||||||
<macro-action-editor
|
[style.overflow]="overflow">
|
||||||
[macroAction]="macroAction"
|
<macro-action-editor
|
||||||
(cancel)="cancelEdit()"
|
*ngIf="editable || newItem"
|
||||||
(save)="saveEditedAction($event)">
|
[macroAction]="macroAction"
|
||||||
</macro-action-editor>
|
(cancel)="cancelEdit()"
|
||||||
</div>
|
(save)="saveEditedAction($event)">
|
||||||
|
</macro-action-editor>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import '../../../../styles/variables';
|
@import '../../../../styles/variables';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
&.macro-item:first-of-type {
|
&.macro-item:first-of-type {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
iconName: string;
|
iconName: string;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
newItem: boolean = false;
|
newItem: boolean = false;
|
||||||
|
overflow = 'hidden';
|
||||||
|
|
||||||
constructor(private mapper: MapperService) { }
|
constructor(private mapper: MapperService) { }
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
if (!this.macroAction) {
|
if (!this.macroAction) {
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
this.newItem = true;
|
this.newItem = true;
|
||||||
|
this.overflow = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +67,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
saveEditedAction(editedAction: MacroAction): void {
|
saveEditedAction(editedAction: MacroAction): void {
|
||||||
this.macroAction = editedAction;
|
this.macroAction = editedAction;
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
|
this.overflow = 'hidden';
|
||||||
this.updateView();
|
this.updateView();
|
||||||
this.save.emit(editedAction);
|
this.save.emit(editedAction);
|
||||||
}
|
}
|
||||||
@@ -77,10 +80,12 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
this.edit.emit();
|
this.edit.emit();
|
||||||
|
this.setOverflow('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelEdit(): void {
|
cancelEdit(): void {
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
|
this.overflow = 'hidden';
|
||||||
this.cancel.emit();
|
this.cancel.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,4 +207,12 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
this.title += selectedButtonLabels.join(', ');
|
this.title += selectedButtonLabels.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setOverflow(value: string): void {
|
||||||
|
// tslint:disable: align
|
||||||
|
setTimeout(() => {
|
||||||
|
this.overflow = value;
|
||||||
|
}, 600);
|
||||||
|
// tslint:enable: align
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<div class="row list-container">
|
<div class="row list-container">
|
||||||
<div class="col-xs-10 col-xs-offset-1 list-group">
|
<div class="col-xs-10 col-xs-offset-1 list-group">
|
||||||
|
<p><i>Please note that macro playback is not implemented yet. You can create macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
|
||||||
<div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions">
|
<div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions">
|
||||||
<macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index"
|
<macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index"
|
||||||
[macroAction]="macroAction"
|
[macroAction]="macroAction"
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="defaultKeyAction"
|
||||||
[currentLayer]="currentLayer"
|
[currentLayer]="currentLayer"
|
||||||
|
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
||||||
(validAction)="keyActionValid=$event"
|
(validAction)="keyActionValid=$event"
|
||||||
></layer-tab>
|
></layer-tab>
|
||||||
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
||||||
@@ -75,8 +76,42 @@
|
|||||||
></none-tab>
|
></none-tab>
|
||||||
</div>
|
</div>
|
||||||
<div class="popover-action">
|
<div class="popover-action">
|
||||||
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel </button>
|
<form class="form-inline d-inline-block popover-action-form">
|
||||||
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button" (click)="onRemapKey()"> Remap Key </button>
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="remapOnAllKeymap"
|
||||||
|
[(ngModel)]="remapOnAllKeymap"> Remap on all keymaps
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="remapOnAllLayer"
|
||||||
|
[(ngModel)]="remapOnAllLayer"> Remap on all layers
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-block">
|
||||||
|
<icon name="question-circle"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
html="true"
|
||||||
|
maxWidth="525"
|
||||||
|
title="<ul class='no-indent text-left'>
|
||||||
|
<li><strong>Default behavior</strong>: Remap the key on the the current layer of the current keymap.</li>
|
||||||
|
<li><strong>Remap on all keymaps</strong>: Remap key on the current layer of all keymaps.</li>
|
||||||
|
<li><strong>Remap on all layers</strong>: Remap key on all layers of the current keymap.</li>
|
||||||
|
<li><strong>Remap on all keymaps + Remap on all layers</strong>: Remap key on all layers of all keymaps.</li>
|
||||||
|
</ul>"
|
||||||
|
data-placement="bottom"></icon>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="d-inline-block pull-right">
|
||||||
|
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel</button>
|
||||||
|
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button"
|
||||||
|
(click)="onRemapKey()"> Remap key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="popover-overlay" [class.display]="visible" (click)="onOverlay()"></div>
|
<div class="popover-overlay" [class.display]="visible" (click)="onOverlay()"></div>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@
|
|||||||
background-color: #f7f7f7;
|
background-color: #f7f7f7;
|
||||||
border-top: 1px solid #ebebeb;
|
border-top: 1px solid #ebebeb;
|
||||||
border-radius: 0 0 5px 5px;
|
border-radius: 0 0 5px 5px;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-title {
|
.popover-title {
|
||||||
@@ -117,19 +116,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-item {
|
.popover-action-form {
|
||||||
position: relative;
|
margin-top: 4px;
|
||||||
font-size: 1.5rem;
|
|
||||||
|
|
||||||
&.keymap-name--wrapper {
|
label {
|
||||||
padding-left: 50px;
|
margin-right: 5px;
|
||||||
}
|
|
||||||
|
|
||||||
.layout-segment-code {
|
|
||||||
height: 2rem;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ import {
|
|||||||
SwitchLayerAction
|
SwitchLayerAction
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from './tab/tab';
|
import { Tab } from './tab';
|
||||||
|
|
||||||
import { AppState } from '../../store';
|
import { AppState } from '../../store';
|
||||||
import { getKeymaps } from '../../store/reducers/user-configuration';
|
import { getKeymaps } from '../../store/reducers/user-configuration';
|
||||||
|
import { KeyActionRemap } from '../../models/key-action-remap';
|
||||||
|
|
||||||
enum TabName {
|
enum TabName {
|
||||||
Keypress,
|
Keypress,
|
||||||
@@ -59,8 +60,8 @@ enum TabName {
|
|||||||
})),
|
})),
|
||||||
transition('opened => closed', [
|
transition('opened => closed', [
|
||||||
animate('200ms ease-out', keyframes([
|
animate('200ms ease-out', keyframes([
|
||||||
style({ transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0 }),
|
style({transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0}),
|
||||||
style({ transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1 })
|
style({transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1})
|
||||||
]))
|
]))
|
||||||
]),
|
]),
|
||||||
transition('closed => opened', [
|
transition('closed => opened', [
|
||||||
@@ -68,8 +69,8 @@ enum TabName {
|
|||||||
visibility: 'visible'
|
visibility: 'visible'
|
||||||
}),
|
}),
|
||||||
animate('200ms ease-out', keyframes([
|
animate('200ms ease-out', keyframes([
|
||||||
style({ transform: 'translateY(30px)', opacity: 0, offset: 0 }),
|
style({transform: 'translateY(30px)', opacity: 0, offset: 0}),
|
||||||
style({ transform: 'translateY(0)', opacity: 1, offset: 1 })
|
style({transform: 'translateY(0)', opacity: 1, offset: 1})
|
||||||
]))
|
]))
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
@@ -82,9 +83,12 @@ export class PopoverComponent implements OnChanges {
|
|||||||
@Input() keyPosition: any;
|
@Input() keyPosition: any;
|
||||||
@Input() wrapPosition: any;
|
@Input() wrapPosition: any;
|
||||||
@Input() visible: boolean;
|
@Input() visible: boolean;
|
||||||
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
|
@Input() remapOnAllKeymap: boolean;
|
||||||
|
@Input() remapOnAllLayer: boolean;
|
||||||
|
|
||||||
@Output() cancel = new EventEmitter<any>();
|
@Output() cancel = new EventEmitter<any>();
|
||||||
@Output() remap = new EventEmitter<KeyAction>();
|
@Output() remap = new EventEmitter<KeyActionRemap>();
|
||||||
|
|
||||||
@ViewChild('tab') selectedTab: Tab;
|
@ViewChild('tab') selectedTab: Tab;
|
||||||
@ViewChild('popover') popoverHost: ElementRef;
|
@ViewChild('popover') popoverHost: ElementRef;
|
||||||
@@ -155,8 +159,11 @@ export class PopoverComponent implements OnChanges {
|
|||||||
onRemapKey(): void {
|
onRemapKey(): void {
|
||||||
if (this.keyActionValid) {
|
if (this.keyActionValid) {
|
||||||
try {
|
try {
|
||||||
const keyAction = this.selectedTab.toKeyAction();
|
this.remap.emit({
|
||||||
this.remap.emit(keyAction);
|
remapOnAllKeymap: this.remapOnAllKeymap,
|
||||||
|
remapOnAllLayer: this.remapOnAllLayer,
|
||||||
|
action: this.selectedTab.toKeyAction()
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: show error dialog
|
// TODO: show error dialog
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -169,6 +176,14 @@ export class PopoverComponent implements OnChanges {
|
|||||||
this.cancel.emit();
|
this.cancel.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown.control.enter', ['$event'])
|
||||||
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.visible) {
|
||||||
|
this.onRemapKey();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectTab(tab: TabName): void {
|
selectTab(tab: TabName): void {
|
||||||
this.activeTab = tab;
|
this.activeTab = tab;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,23 @@
|
|||||||
<ng-template [ngIf]="keymapOptions.length > 0">
|
<ng-template [ngIf]="keymapOptions.length > 0">
|
||||||
<div>
|
<div>
|
||||||
<b>Switch to keymap:</b>
|
<b>Switch to keymap:</b>
|
||||||
<select2
|
<ngx-select [items]="keymapOptions"
|
||||||
[data]="keymapOptions"
|
[ngModel]="selectedKeymap?.abbreviation || -1"
|
||||||
[value]="selectedKeymap?.abbreviation || -1"
|
[autoActiveOnMouseEnter]="false"
|
||||||
(valueChanged)="onChange($event)"
|
size="small"
|
||||||
[width]="'100%'"
|
optionValueField="id"
|
||||||
></select2>
|
optionTextField="text"
|
||||||
|
(select)="onChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</ngx-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
|
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select2 {
|
ngx-select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
|
||||||
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keymap-tab',
|
selector: 'keymap-tab',
|
||||||
@@ -14,7 +14,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
@Input() keymaps: Keymap[];
|
@Input() keymaps: Keymap[];
|
||||||
|
|
||||||
keymapOptions: Array<Select2OptionData>;
|
keymapOptions: Array<SelectOptionData>;
|
||||||
selectedKeymap: Keymap;
|
selectedKeymap: Keymap;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -25,7 +25,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes.keymaps) {
|
if (changes.keymaps) {
|
||||||
this.keymapOptions = this.keymaps
|
this.keymapOptions = this.keymaps
|
||||||
.map((keymap: Keymap): Select2OptionData => {
|
.map((keymap: Keymap): SelectOptionData => {
|
||||||
return {
|
return {
|
||||||
id: keymap.abbreviation,
|
id: keymap.abbreviation,
|
||||||
text: keymap.name
|
text: keymap.name
|
||||||
@@ -40,12 +40,11 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
this.validAction.emit(true);
|
this.validAction.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change to the correct type when the wrapper has added it.
|
onChange(event: string) {
|
||||||
onChange(event: any) {
|
if (event === '-1') {
|
||||||
if (event.value === '-1') {
|
|
||||||
this.selectedKeymap = undefined;
|
this.selectedKeymap = undefined;
|
||||||
} else {
|
} else {
|
||||||
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event.value);
|
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
<div class="scancode-options">
|
<div class="scancode-options">
|
||||||
<b class="setting-label">Scancode:</b>
|
<b class="setting-label">Scancode:</b>
|
||||||
<select2
|
<div class="scancode-container">
|
||||||
[data]="scanCodeGroups"
|
<ngx-select [items]="scanCodeGroups"
|
||||||
[value]="selectedScancodeOption.id"
|
[ngModel]="selectedScancodeOption?.id"
|
||||||
(valueChanged)="onScancodeChange($event)"
|
[autoActiveOnMouseEnter]="false"
|
||||||
[width]="200"
|
size="small"
|
||||||
[options]="options"
|
optionValueField="id"
|
||||||
></select2>
|
optionTextField="text"
|
||||||
|
optGroupLabelField="text"
|
||||||
|
optGroupOptionsField="children"
|
||||||
|
(select)="onScancodeChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '0'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ngx-select>
|
||||||
|
</div>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout."
|
html="true"
|
||||||
|
maxWidth="330"
|
||||||
|
title="<p>Looking for a non-US character? Just pick the character of the desired key according to the US layout.</p>
|
||||||
|
<p>Let's say you're a German user and want to map the Ö character. You can see that on US keyboards this is the semicolon key, so choose semicolon in this dropdown.</p>"
|
||||||
data-placement="bottom"></icon>
|
data-placement="bottom"></icon>
|
||||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,15 +56,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
||||||
<b class="setting-label">Secondary role:</b>
|
<b class="setting-label">Secondary role:</b>
|
||||||
<select2 #secondaryRoleSelect
|
<div class="secondary-role-groups-container">
|
||||||
[data]="secondaryRoleGroups"
|
<ngx-select [items]="secondaryRoleGroups"
|
||||||
[value]="selectedSecondaryRoleIndex.toString()"
|
[ngModel]="selectedSecondaryRoleIndex.toString()"
|
||||||
(valueChanged)="onSecondaryRoleChange($event)"
|
[autoActiveOnMouseEnter]="false"
|
||||||
[width]="140"
|
size="small"
|
||||||
></select2>
|
optionValueField="id"
|
||||||
|
optionTextField="text"
|
||||||
|
optGroupLabelField="text"
|
||||||
|
optGroupOptionsField="children"
|
||||||
|
(select)="onSecondaryRoleChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ngx-select>
|
||||||
|
</div>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="The secondary role activates when another key gets pressed while holding this key."
|
html="true"
|
||||||
|
maxWidth="620"
|
||||||
|
title="<p class='text-left'>The secondary role activates when another key gets pressed while holding this key.</p>
|
||||||
|
<p class='text-left'>Let's say that the scancode is Escape and the secondary role is Mouse. Then:</p>
|
||||||
|
<ul class='text-left'>
|
||||||
|
<li>Tap this key to trigger Escape. <i>(Primary role)</i></li>
|
||||||
|
<li>Hold this key and press another key to activate the relevant key of the Mouse layer. <i>(Secondary role)</i></li>
|
||||||
|
</ul>
|
||||||
|
<p class='text-left pt-3'>The secondary role can be any layer or modifier.</p>"
|
||||||
data-placement="bottom"></icon>
|
data-placement="bottom"></icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -79,4 +79,14 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scancode-container {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-role-groups-container {
|
||||||
|
display: inline-block;
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||||
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
|
|
||||||
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
import { MapperService } from '../../../../services/mapper.service';
|
import { MapperService } from '../../../../services/mapper.service';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keypress-tab',
|
selector: 'keypress-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './keypress-tab.component.html',
|
templateUrl: './keypress-tab.component.html',
|
||||||
styleUrls: ['./keypress-tab.component.scss']
|
styleUrls: ['./keypress-tab.component.scss']
|
||||||
})
|
})
|
||||||
@@ -20,11 +21,10 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
leftModifierSelects: boolean[];
|
leftModifierSelects: boolean[];
|
||||||
rightModifierSelects: boolean[];
|
rightModifierSelects: boolean[];
|
||||||
|
|
||||||
scanCodeGroups: Array<Select2OptionData>;
|
scanCodeGroups: Array<SelectOptionData>;
|
||||||
secondaryRoleGroups: Array<Select2OptionData>;
|
secondaryRoleGroups: Array<SelectOptionData>;
|
||||||
options: Select2Options;
|
|
||||||
|
|
||||||
selectedScancodeOption: Select2OptionData;
|
selectedScancodeOption: SelectOptionData;
|
||||||
selectedSecondaryRoleIndex: number;
|
selectedSecondaryRoleIndex: number;
|
||||||
|
|
||||||
constructor(private mapper: MapperService) {
|
constructor(private mapper: MapperService) {
|
||||||
@@ -41,18 +41,6 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
||||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||||
this.selectedSecondaryRoleIndex = -1;
|
this.selectedSecondaryRoleIndex = -1;
|
||||||
this.options = {
|
|
||||||
templateResult: this.scanCodeTemplateResult,
|
|
||||||
matcher: (term: string, text: string, data: Select2OptionData) => {
|
|
||||||
let found = text.toUpperCase().indexOf(term.toUpperCase()) > -1;
|
|
||||||
|
|
||||||
if (!found && data.additional && data.additional.explanation) {
|
|
||||||
found = data.additional.explanation.toUpperCase().indexOf(term.toUpperCase()) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
@@ -115,7 +103,7 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
const scTypePair = this.toScancodeTypePair(this.selectedScancodeOption);
|
const scTypePair = this.toScancodeTypePair(this.selectedScancodeOption);
|
||||||
keystrokeAction.scancode = scTypePair[0];
|
keystrokeAction.scancode = scTypePair[0];
|
||||||
if (scTypePair[1] === 'media') {
|
if (scTypePair[1] === 'media') {
|
||||||
keystrokeAction.type = KeystrokeType.shortMedia;
|
keystrokeAction.type = keystrokeAction.scancode > 255 ? KeystrokeType.longMedia : KeystrokeType.shortMedia;
|
||||||
} else {
|
} else {
|
||||||
keystrokeAction.type = KeystrokeType[scTypePair[1]];
|
keystrokeAction.type = KeystrokeType[scTypePair[1]];
|
||||||
}
|
}
|
||||||
@@ -134,25 +122,6 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scanCodeTemplateResult: Select2TemplateFunction = (state: Select2OptionData): JQuery | string => {
|
|
||||||
if (!state.id) {
|
|
||||||
return state.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.additional && state.additional.explanation) {
|
|
||||||
return jQuery(
|
|
||||||
'<span class="select2-item">'
|
|
||||||
+ '<span>' + state.text + '</span>'
|
|
||||||
+ '<span class="scancode--searchterm"> '
|
|
||||||
+ state.additional.explanation
|
|
||||||
+ '</span>' +
|
|
||||||
'</span>'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return jQuery('<span class="select2-item">' + state.text + '</span>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleModifier(right: boolean, index: number) {
|
toggleModifier(right: boolean, index: number) {
|
||||||
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
|
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
|
||||||
modifierSelects[index] = !modifierSelects[index];
|
modifierSelects[index] = !modifierSelects[index];
|
||||||
@@ -160,24 +129,20 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
this.validAction.emit(this.keyActionValid());
|
this.validAction.emit(this.keyActionValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
onSecondaryRoleChange(event: { value: string }) {
|
onSecondaryRoleChange(id: string) {
|
||||||
this.selectedSecondaryRoleIndex = +event.value;
|
this.selectedSecondaryRoleIndex = +id;
|
||||||
}
|
}
|
||||||
|
|
||||||
onScancodeChange(event: { value: string }) {
|
onScancodeChange(id: string) {
|
||||||
const id: string = event.value;
|
|
||||||
|
|
||||||
// ng2-select2 should provide the selectedOption in an upcoming release
|
|
||||||
// TODO: change this when it has become available
|
|
||||||
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
||||||
|
|
||||||
this.validAction.emit(this.keyActionValid());
|
this.validAction.emit(this.keyActionValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionBy(predicate: (option: Select2OptionData) => boolean): Select2OptionData {
|
private findScancodeOptionBy(predicate: (option: SelectOptionData) => boolean): SelectOptionData {
|
||||||
let selectedOption: Select2OptionData;
|
let selectedOption: SelectOptionData;
|
||||||
|
|
||||||
const scanCodeGroups: Select2OptionData[] = [...this.scanCodeGroups];
|
const scanCodeGroups: SelectOptionData[] = [...this.scanCodeGroups];
|
||||||
while (scanCodeGroups.length > 0) {
|
while (scanCodeGroups.length > 0) {
|
||||||
const scanCodeGroup = scanCodeGroups.shift();
|
const scanCodeGroup = scanCodeGroups.shift();
|
||||||
if (predicate(scanCodeGroup)) {
|
if (predicate(scanCodeGroup)) {
|
||||||
@@ -192,14 +157,14 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
return selectedOption;
|
return selectedOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionById(id: string): Select2OptionData {
|
private findScancodeOptionById(id: string): SelectOptionData {
|
||||||
return this.findScancodeOptionBy(option => option.id === id);
|
return this.findScancodeOptionBy(option => option.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): Select2OptionData {
|
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): SelectOptionData {
|
||||||
const typeToFind: string =
|
const typeToFind: string =
|
||||||
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
||||||
return this.findScancodeOptionBy((option: Select2OptionData) => {
|
return this.findScancodeOptionBy((option: SelectOptionData) => {
|
||||||
const additional = option.additional;
|
const additional = option.additional;
|
||||||
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
||||||
return true;
|
return true;
|
||||||
@@ -211,7 +176,11 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private toScancodeTypePair(option: Select2OptionData): [number, string] {
|
private toScancodeTypePair(option: SelectOptionData): [number, string] {
|
||||||
|
if (!option) {
|
||||||
|
return [0, 'basic'];
|
||||||
|
}
|
||||||
|
|
||||||
let scanCode: number;
|
let scanCode: number;
|
||||||
let type: string;
|
let type: string;
|
||||||
if (option.additional) {
|
if (option.additional) {
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
<ng-template [ngIf]="!isNotBase">
|
<ng-template [ngIf]="!isNotBase">
|
||||||
<select (change)="toggleChanged($event.target.value)">
|
<div>
|
||||||
<option *ngFor="let item of toggleData" [value]="item.id" [selected]="toggle === item.id">
|
<div>
|
||||||
{{ item.text }}
|
<select (change)="toggleChanged($event.target.value)">
|
||||||
</option>
|
<option *ngFor="let item of toggleData" [value]="item.id" [selected]="toggle === item.id">
|
||||||
</select>
|
{{ item.text }}
|
||||||
<span>the</span>
|
</option>
|
||||||
<select (change)="layerChanged($event.target.value)">
|
</select>
|
||||||
<option *ngFor="let item of layerData" [value]="item.id" [selected]="layer === item.id">
|
<span>the</span>
|
||||||
{{ item.text }}
|
<select (change)="layerChanged($event.target.value)">
|
||||||
</option>
|
<option *ngFor="let item of layerData" [value]="item.id" [selected]="layer === item.id">
|
||||||
</select>
|
{{ item.text }}
|
||||||
<span [ngSwitch]="toggle">
|
</option>
|
||||||
<ng-template [ngSwitchCase]="true">layer by pressing this key.</ng-template>
|
</select>
|
||||||
<ng-template ngSwitchDefault>layer by holding this key.</ng-template>
|
<span [ngSwitch]="toggle">
|
||||||
</span>
|
<ng-template [ngSwitchCase]="'toggle'">layer by tapping this key.</ng-template>
|
||||||
|
<ng-template ngSwitchDefault>layer by holding this key.</ng-template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="toggle === 'active' && allowLayerDoubleTap">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
[(ngModel)]="lockLayerWhenDoubleTapping"> Lock layer when double tapping this key.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="isNotBase">
|
<ng-template [ngIf]="isNotBase">
|
||||||
<span> Layer switching is only possible from the base layer. </span>
|
<span> Layer switching is only possible from the base layer. </span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { KeyAction, LayerName, SwitchLayerAction } from 'uhk-common';
|
import { KeyAction, LayerName, SwitchLayerAction, SwitchLayerMode } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
|
export type toggleType = 'active' | 'toggle';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'layer-tab',
|
selector: 'layer-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './layer-tab.component.html',
|
templateUrl: './layer-tab.component.html',
|
||||||
styleUrls: ['./layer-tab.component.scss']
|
styleUrls: ['./layer-tab.component.scss']
|
||||||
})
|
})
|
||||||
export class LayerTabComponent extends Tab implements OnChanges {
|
export class LayerTabComponent extends Tab implements OnChanges {
|
||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
@Input() currentLayer: number;
|
@Input() currentLayer: number;
|
||||||
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
|
|
||||||
@HostBinding('class.no-base') isNotBase: boolean;
|
@HostBinding('class.no-base') isNotBase: boolean;
|
||||||
|
|
||||||
toggleData: { id: boolean, text: string }[] = [
|
toggleData: { id: toggleType, text: string }[] = [
|
||||||
{
|
{
|
||||||
id: false,
|
id: 'active',
|
||||||
text: 'Activate'
|
text: 'Activate'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: true,
|
id: 'toggle',
|
||||||
text: 'Toggle'
|
text: 'Toggle'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -40,12 +44,13 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
toggle: boolean;
|
toggle: toggleType;
|
||||||
layer: LayerName;
|
layer: LayerName;
|
||||||
|
lockLayerWhenDoubleTapping: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.toggle = false;
|
this.toggle = 'active';
|
||||||
this.layer = LayerName.mod;
|
this.layer = LayerName.mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,14 +76,39 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
|
const switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
|
||||||
this.toggle = switchLayerAction.isLayerToggleable;
|
switch (switchLayerAction.switchLayerMode) {
|
||||||
|
case SwitchLayerMode.holdAndDoubleTapToggle: {
|
||||||
|
this.toggle = 'active';
|
||||||
|
this.lockLayerWhenDoubleTapping = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SwitchLayerMode.hold: {
|
||||||
|
this.toggle = 'active';
|
||||||
|
this.lockLayerWhenDoubleTapping = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
this.toggle = 'toggle';
|
||||||
|
this.lockLayerWhenDoubleTapping = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.layer = switchLayerAction.layer;
|
this.layer = switchLayerAction.layer;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
toKeyAction(): SwitchLayerAction {
|
toKeyAction(): SwitchLayerAction {
|
||||||
const keyAction = new SwitchLayerAction();
|
const keyAction = new SwitchLayerAction();
|
||||||
keyAction.isLayerToggleable = this.toggle;
|
if (this.toggle === 'toggle') {
|
||||||
|
keyAction.switchLayerMode = SwitchLayerMode.toggle;
|
||||||
|
} else if (!this.allowLayerDoubleTap || this.lockLayerWhenDoubleTapping) {
|
||||||
|
keyAction.switchLayerMode = SwitchLayerMode.holdAndDoubleTapToggle;
|
||||||
|
} else {
|
||||||
|
keyAction.switchLayerMode = SwitchLayerMode.hold;
|
||||||
|
}
|
||||||
|
|
||||||
keyAction.layer = this.layer;
|
keyAction.layer = this.layer;
|
||||||
if (!this.keyActionValid()) {
|
if (!this.keyActionValid()) {
|
||||||
throw new Error('KeyAction is invalid!');
|
throw new Error('KeyAction is invalid!');
|
||||||
@@ -86,8 +116,8 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
return keyAction;
|
return keyAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleChanged(value: string) {
|
toggleChanged(value: toggleType) {
|
||||||
this.toggle = value === 'true';
|
this.toggle = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
layerChanged(value: number) {
|
layerChanged(value: number) {
|
||||||
|
|||||||
@@ -2,10 +2,26 @@
|
|||||||
<span> No macros are available to choose from. Create a macro first! </span>
|
<span> No macros are available to choose from. Create a macro first! </span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="macroOptions.length > 0">
|
<ng-template [ngIf]="macroOptions.length > 0">
|
||||||
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they don't have any effect.</i></p>
|
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
|
||||||
<div class="macro-selector">
|
<div class="macro-selector">
|
||||||
<b> Play macro: </b>
|
<b> Play macro: </b>
|
||||||
<select2 [data]="macroOptions" [value]="macroOptions[selectedMacroIndex].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>
|
<ngx-select [items]="macroOptions"
|
||||||
|
[ngModel]="macroOptions[selectedMacroIndex]?.id"
|
||||||
|
[autoActiveOnMouseEnter]="false"
|
||||||
|
size="small"
|
||||||
|
optionValueField="id"
|
||||||
|
optionTextField="text"
|
||||||
|
(select)="onChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</ngx-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="macro-action-container">
|
<div class="macro-action-container">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
@@ -14,4 +30,4 @@
|
|||||||
</macro-item>
|
</macro-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select2 {
|
ngx-select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
|
||||||
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
import { AppState } from '../../../../store/index';
|
import { AppState } from '../../../../store';
|
||||||
import { getMacros } from '../../../../store/reducers/user-configuration';
|
import { getMacros } from '../../../../store/reducers/user-configuration';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'macro-tab',
|
selector: 'macro-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './macro-tab.component.html',
|
templateUrl: './macro-tab.component.html',
|
||||||
styleUrls: ['./macro-tab.component.scss']
|
styleUrls: ['./macro-tab.component.scss']
|
||||||
})
|
})
|
||||||
@@ -18,7 +19,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
|
|
||||||
macros: Macro[];
|
macros: Macro[];
|
||||||
macroOptions: Array<Select2OptionData>;
|
macroOptions: Array<SelectOptionData>;
|
||||||
selectedMacroIndex: number;
|
selectedMacroIndex: number;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.macroOptions = this.macros.map(function (macro: Macro, index: number): Select2OptionData {
|
this.macroOptions = this.macros.map(function (macro: Macro, index: number): SelectOptionData {
|
||||||
return {
|
return {
|
||||||
id: index.toString(),
|
id: index.toString(),
|
||||||
text: macro.name
|
text: macro.name
|
||||||
@@ -44,9 +45,8 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
this.validAction.emit(true);
|
this.validAction.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change to the correct type when the wrapper has added it.
|
onChange(id: string) {
|
||||||
onChange(event: any) {
|
this.selectedMacroIndex = +id;
|
||||||
this.selectedMacroIndex = +event.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyActionValid(): boolean {
|
keyActionValid(): boolean {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||||
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
|
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mouse-tab',
|
selector: 'mouse-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './mouse-tab.component.html',
|
templateUrl: './mouse-tab.component.html',
|
||||||
styleUrls: ['./mouse-tab.component.scss']
|
styleUrls: ['./mouse-tab.component.scss']
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'none-tab',
|
selector: 'none-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './none-tab.component.html',
|
templateUrl: './none-tab.component.html',
|
||||||
styleUrls: ['./none-tab.component.scss']
|
styleUrls: ['./none-tab.component.scss']
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
<li class="sidebar__level-0--item">
|
<li class="sidebar__level-0--item">
|
||||||
<div class="sidebar__level-0">
|
<div class="sidebar__level-0">
|
||||||
<i class="uhk-icon uhk-icon-0401-usb-stick rotate-right"></i>
|
<i class="uhk-icon uhk-icon-0401-usb-stick rotate-right"></i>
|
||||||
<input #deviceName cancelable
|
<auto-grow-input [ngModel]="state.deviceName"
|
||||||
class="pane-title__name"
|
[maxParentWidthPercent]="0.65"
|
||||||
type="text"
|
[css]="'side-menu-pane-title__name'"
|
||||||
[readonly]="state.restoreUserConfiguration"
|
[disabled]="state.restoreUserConfiguration || state.updatingFirmware"
|
||||||
(change)="editDeviceName($event.target.value)"
|
(ngModelChange)="editDeviceName($event)"></auto-grow-input>
|
||||||
(keyup.enter)="deviceName.blur()"
|
|
||||||
(keyup)="calculateHeaderTextWidth($event.target.value)">
|
|
||||||
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'device')"></i>
|
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'device')"></i>
|
||||||
</div>
|
</div>
|
||||||
<ul [@toggler]="animation['device']">
|
<ul [@toggler]="animation['device']">
|
||||||
@@ -138,11 +136,17 @@
|
|||||||
(click)="toggleHide($event, 'agent')"></i>
|
(click)="toggleHide($event, 'agent')"></i>
|
||||||
</div>
|
</div>
|
||||||
<ul [@toggler]="animation['agent']">
|
<ul [@toggler]="animation['agent']">
|
||||||
<li class="sidebar__level-2--item">
|
<!--li class="sidebar__level-2--item">
|
||||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||||
<a [routerLink]="['/settings']"
|
<a [routerLink]="['/settings']"
|
||||||
[class.disabled]="state.updatingFirmware">Settings</a>
|
[class.disabled]="state.updatingFirmware">Settings</a>
|
||||||
</div>
|
</div>
|
||||||
|
</li-->
|
||||||
|
<li class="sidebar__level-2--item">
|
||||||
|
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||||
|
<a [routerLink]="['/help']"
|
||||||
|
[class.disabled]="state.updatingFirmware">Help</a>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar__level-2--item">
|
<li class="sidebar__level-2--item">
|
||||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||||
|
|||||||
@@ -162,22 +162,3 @@ ul {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane-title {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
|
|
||||||
&__name {
|
|
||||||
border: none;
|
|
||||||
border-bottom: 2px dotted #999;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
|
|
||||||
border-color: transparent;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
AfterContentInit,
|
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
@@ -19,7 +18,6 @@ import 'rxjs/add/operator/let';
|
|||||||
|
|
||||||
import { AppState, getSideMenuPageState } from '../../store';
|
import { AppState, getSideMenuPageState } from '../../store';
|
||||||
import { MacroActions } from '../../store/actions';
|
import { MacroActions } from '../../store/actions';
|
||||||
import * as util from '../../util';
|
|
||||||
import { RenameUserConfigurationAction } from '../../store/actions/user-config';
|
import { RenameUserConfigurationAction } from '../../store/actions/user-config';
|
||||||
import { SideMenuPageState } from '../../models/side-menu-page-state';
|
import { SideMenuPageState } from '../../models/side-menu-page-state';
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ import { SideMenuPageState } from '../../models/side-menu-page-state';
|
|||||||
styleUrls: ['./side-menu.component.scss'],
|
styleUrls: ['./side-menu.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class SideMenuComponent implements AfterContentInit, OnInit, OnDestroy {
|
export class SideMenuComponent implements OnInit, OnDestroy {
|
||||||
state: SideMenuPageState;
|
state: SideMenuPageState;
|
||||||
animation: { [key: string]: 'active' | 'inactive' };
|
animation: { [key: string]: 'active' | 'inactive' };
|
||||||
@ViewChild('deviceName') deviceName: ElementRef;
|
@ViewChild('deviceName') deviceName: ElementRef;
|
||||||
@@ -62,15 +60,10 @@ export class SideMenuComponent implements AfterContentInit, OnInit, OnDestroy {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.stateSubscription = this.store.select(getSideMenuPageState).subscribe(data => {
|
this.stateSubscription = this.store.select(getSideMenuPageState).subscribe(data => {
|
||||||
this.state = data;
|
this.state = data;
|
||||||
this.setDeviceName();
|
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
|
||||||
this.setDeviceName();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.stateSubscription) {
|
if (this.stateSubscription) {
|
||||||
this.stateSubscription.unsubscribe();
|
this.stateSubscription.unsubscribe();
|
||||||
@@ -106,24 +99,6 @@ export class SideMenuComponent implements AfterContentInit, OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editDeviceName(name: string): void {
|
editDeviceName(name: string): void {
|
||||||
if (!util.isValidName(name) || name.trim() === this.state.deviceName) {
|
|
||||||
this.setDeviceName();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.store.dispatch(new RenameUserConfigurationAction(name));
|
this.store.dispatch(new RenameUserConfigurationAction(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateHeaderTextWidth(text): void {
|
|
||||||
const htmlInput = this.deviceName.nativeElement as HTMLInputElement;
|
|
||||||
const maxWidth = htmlInput.parentElement.offsetWidth * 0.66;
|
|
||||||
const textWidth = util.getContentWidth(window.getComputedStyle(htmlInput), text);
|
|
||||||
this.renderer.setStyle(htmlInput, 'width', Math.min(maxWidth, textWidth) + 'px');
|
|
||||||
}
|
|
||||||
|
|
||||||
private setDeviceName(): void {
|
|
||||||
if (this.deviceName) {
|
|
||||||
this.renderer.setProperty(this.deviceName.nativeElement, 'value', this.state.deviceName);
|
|
||||||
this.calculateHeaderTextWidth(this.deviceName.nativeElement.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg:g svg-module *ngFor="let module of modules; let i = index"
|
[attr.viewBox]="viewBox"
|
||||||
|
height="100%"
|
||||||
|
width="100%">
|
||||||
|
<svg:g svg-module
|
||||||
|
*ngFor="let module of modules; let i = index"
|
||||||
[coverages]="module.coverages"
|
[coverages]="module.coverages"
|
||||||
[keyboardKeys]="module.keyboardKeys"
|
[keyboardKeys]="module.keyboardKeys"
|
||||||
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
||||||
@@ -9,10 +13,13 @@
|
|||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[@split]="moduleAnimationStates[i]"
|
[@split]="moduleAnimationStates[i]"
|
||||||
[selected]="selectedKey?.moduleId === i"
|
[selected]="selectedKey?.moduleId === i"
|
||||||
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
(keyClick)="onKeyClick(i, $event)"
|
||||||
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
||||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
(capture)="onCapture(i, $event)" />
|
||||||
/>
|
|
||||||
|
<svg:path [@fadeSeparator]="separatorAnimation"
|
||||||
|
[attr.d]="separator.d"
|
||||||
|
[attr.style]="separatorStyle" />
|
||||||
</svg>
|
</svg>
|
||||||
<editable-text *ngIf="showDescription"
|
<editable-text *ngIf="showDescription"
|
||||||
[ngModel]="description"
|
[ngModel]="description"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,10 +1,18 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { animate, state, trigger, style, transition } from '@angular/animations';
|
import { animate, state, trigger, style, transition } from '@angular/animations';
|
||||||
|
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||||
import { Module } from 'uhk-common';
|
import { Module } from 'uhk-common';
|
||||||
|
|
||||||
import { SvgModule } from '../module';
|
import { SvgModule } from '../module';
|
||||||
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import { SvgSeparator } from '../separator';
|
||||||
|
import {
|
||||||
|
SvgKeyHoverEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgModuleKeyClickEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'svg-keyboard',
|
selector: 'svg-keyboard',
|
||||||
@@ -20,6 +28,16 @@ import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
|||||||
transform: 'translate(3%, 15%) rotate(-4deg) scale(0.92, 0.92)'
|
transform: 'translate(3%, 15%) rotate(-4deg) scale(0.92, 0.92)'
|
||||||
})),
|
})),
|
||||||
transition('* <=> *', animate(500))
|
transition('* <=> *', animate(500))
|
||||||
|
]),
|
||||||
|
trigger('fadeSeparator', [
|
||||||
|
state('visible', style({
|
||||||
|
opacity: 1
|
||||||
|
})),
|
||||||
|
state('invisible', style({
|
||||||
|
opacity: 0
|
||||||
|
})),
|
||||||
|
transition('visible => invisible', animate(500)),
|
||||||
|
transition('invisible => visible', animate(1500))
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -33,16 +51,20 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Input() showDescription = false;
|
@Input() showDescription = false;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||||
@Output() descriptionChanged = new EventEmitter<string>();
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
modules: SvgModule[];
|
modules: SvgModule[];
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
moduleAnimationStates: string[];
|
moduleAnimationStates: string[];
|
||||||
|
separator: SvgSeparator;
|
||||||
|
separatorStyle: SafeStyle;
|
||||||
|
separatorAnimation = 'visible';
|
||||||
|
|
||||||
constructor(private svgModuleProvider: SvgModuleProviderService) {
|
constructor(private svgModuleProvider: SvgModuleProviderService,
|
||||||
|
private sanitizer: DomSanitizer) {
|
||||||
this.modules = [];
|
this.modules = [];
|
||||||
this.viewBox = '-520 582 1100 470';
|
this.viewBox = '-520 582 1100 470';
|
||||||
this.halvesSplit = false;
|
this.halvesSplit = false;
|
||||||
@@ -63,19 +85,17 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void {
|
onKeyClick(moduleId: number, event: SvgModuleKeyClickEvent): void {
|
||||||
this.keyClick.emit({
|
this.keyClick.emit({
|
||||||
moduleId,
|
...event,
|
||||||
keyId,
|
moduleId
|
||||||
keyTarget
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCapture(moduleId: number, keyId: number, captured: { code: number, left: boolean[], right: boolean[] }): void {
|
onCapture(moduleId: number, event: SvgKeyboardCaptureEvent): void {
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
moduleId,
|
...event,
|
||||||
keyId,
|
moduleId
|
||||||
captured
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,12 +111,16 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
private updateModuleAnimationStates() {
|
private updateModuleAnimationStates() {
|
||||||
if (this.halvesSplit) {
|
if (this.halvesSplit) {
|
||||||
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
||||||
|
this.separatorAnimation = 'invisible';
|
||||||
} else {
|
} else {
|
||||||
this.moduleAnimationStates = [];
|
this.moduleAnimationStates = [];
|
||||||
|
this.separatorAnimation = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setModules() {
|
private setModules() {
|
||||||
this.modules = this.svgModuleProvider.getSvgModules(this.keyboardLayout);
|
this.modules = this.svgModuleProvider.getSvgModules(this.keyboardLayout);
|
||||||
|
this.separator = this.svgModuleProvider.getSvgSeparator();
|
||||||
|
this.separatorStyle = this.sanitizer.bypassSecurityTrustStyle(this.separator.style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ import {
|
|||||||
MouseAction,
|
MouseAction,
|
||||||
PlayMacroAction,
|
PlayMacroAction,
|
||||||
SwitchKeymapAction,
|
SwitchKeymapAction,
|
||||||
SwitchLayerAction
|
SwitchLayerAction,
|
||||||
|
SwitchLayerMode
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { CaptureService } from '../../../../services/capture.service';
|
import { CaptureService } from '../../../../services/capture.service';
|
||||||
import { MapperService } from '../../../../services/mapper.service';
|
import { MapperService } from '../../../../services/mapper.service';
|
||||||
|
|
||||||
import { AppState } from '../../../../store/index';
|
import { AppState } from '../../../../store';
|
||||||
import { getMacros } from '../../../../store/reducers/user-configuration';
|
import { getMacros } from '../../../../store/reducers/user-configuration';
|
||||||
|
import { SvgKeyCaptureEvent, SvgKeyClickEvent } from '../../../../models/svg-key-events';
|
||||||
|
|
||||||
enum LabelTypes {
|
enum LabelTypes {
|
||||||
KeystrokeKey,
|
KeystrokeKey,
|
||||||
@@ -81,8 +83,8 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input() capturingEnabled: boolean;
|
@Input() capturingEnabled: boolean;
|
||||||
@Input() active: boolean;
|
@Input() active: boolean;
|
||||||
|
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyClickEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyCaptureEvent>();
|
||||||
|
|
||||||
enumLabelTypes = LabelTypes;
|
enumLabelTypes = LabelTypes;
|
||||||
|
|
||||||
@@ -95,6 +97,10 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
macros: Macro[];
|
macros: Macro[];
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private scanCodePressed: boolean;
|
private scanCodePressed: boolean;
|
||||||
|
private pressedShiftLocation = -1;
|
||||||
|
private pressedAltLocation = -1;
|
||||||
|
private altPressed = false;
|
||||||
|
private shiftPressed = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private mapper: MapperService,
|
private mapper: MapperService,
|
||||||
@@ -114,12 +120,16 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@HostListener('click')
|
@HostListener('click')
|
||||||
onClick() {
|
onClick() {
|
||||||
this.reset();
|
this.reset();
|
||||||
this.keyClick.emit(this.element.nativeElement);
|
this.keyClick.emit({
|
||||||
|
keyTarget: this.element.nativeElement,
|
||||||
|
shiftPressed: this.pressedShiftLocation > -1,
|
||||||
|
altPressed: this.pressedAltLocation > -1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('mousedown', ['$event'])
|
@HostListener('mousedown', ['$event'])
|
||||||
onMouseDown(e: MouseEvent) {
|
onMouseDown(e: MouseEvent) {
|
||||||
if ((e.which === 2 || e.button === 1) && this.capturingEnabled) {
|
if ((e.which === 2 || e.button === 2) && this.capturingEnabled) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.renderer.invokeElementMethod(this.element.nativeElement, 'focus');
|
this.renderer.invokeElementMethod(this.element.nativeElement, 'focus');
|
||||||
|
|
||||||
@@ -128,13 +138,29 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.recording = true;
|
this.recording = true;
|
||||||
this.recordAnimation = 'active';
|
this.recordAnimation = 'active';
|
||||||
|
|
||||||
|
if (this.pressedShiftLocation > -1) {
|
||||||
|
this.shiftPressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pressedAltLocation > -1) {
|
||||||
|
this.altPressed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keyup', ['$event'])
|
@HostListener('document:keyup', ['$event'])
|
||||||
onKeyUp(e: KeyboardEvent) {
|
onKeyUp(e: KeyboardEvent) {
|
||||||
if (this.scanCodePressed) {
|
if (e.keyCode === 18 && this.pressedAltLocation > -1) {
|
||||||
|
this.pressedAltLocation = -1;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
else if (e.keyCode === 16 && this.pressedShiftLocation > -1) {
|
||||||
|
this.pressedShiftLocation = -1;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
else if (this.scanCodePressed) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.scanCodePressed = false;
|
this.scanCodePressed = false;
|
||||||
} else if (this.recording) {
|
} else if (this.recording) {
|
||||||
@@ -143,7 +169,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keydown', ['$event'])
|
@HostListener('document:keydown', ['$event'])
|
||||||
onKeyDown(e: KeyboardEvent) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
const code: number = e.keyCode;
|
const code: number = e.keyCode;
|
||||||
|
|
||||||
@@ -151,11 +177,29 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.captureService.hasMap(code)) {
|
if (this.captureService.hasMap(code)) {
|
||||||
|
// If the Alt or Shift key not released after start the capturing
|
||||||
|
// then add them as a modifier
|
||||||
|
if (this.pressedShiftLocation > -1) {
|
||||||
|
this.captureService.setModifier((this.pressedShiftLocation === 1), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pressedAltLocation > -1) {
|
||||||
|
this.captureService.setModifier((this.pressedAltLocation === 1), 18);
|
||||||
|
}
|
||||||
|
|
||||||
this.saveScanCode(this.captureService.getMap(code));
|
this.saveScanCode(this.captureService.getMap(code));
|
||||||
this.scanCodePressed = true;
|
this.scanCodePressed = true;
|
||||||
} else {
|
} else {
|
||||||
this.captureService.setModifier((e.location === 1), code);
|
this.captureService.setModifier((e.location === 1), code);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (e.keyCode === 16) {
|
||||||
|
this.pressedShiftLocation = e.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 18) {
|
||||||
|
this.pressedAltLocation = e.location;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,22 +241,25 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.recording = false;
|
this.recording = false;
|
||||||
this.changeAnimation = 'inactive';
|
this.changeAnimation = 'inactive';
|
||||||
this.captureService.initModifiers();
|
this.captureService.initModifiers();
|
||||||
|
this.shiftPressed = false;
|
||||||
|
this.altPressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveScanCode(code = 0) {
|
private saveScanCode(code = 0) {
|
||||||
this.recording = false;
|
|
||||||
this.changeAnimation = 'inactive';
|
|
||||||
|
|
||||||
const left: boolean[] = this.captureService.getModifiers(true);
|
const left: boolean[] = this.captureService.getModifiers(true);
|
||||||
const right: boolean[] = this.captureService.getModifiers(false);
|
const right: boolean[] = this.captureService.getModifiers(false);
|
||||||
|
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
code,
|
captured: {
|
||||||
left,
|
code,
|
||||||
right
|
left,
|
||||||
|
right
|
||||||
|
},
|
||||||
|
shiftPressed: this.shiftPressed,
|
||||||
|
altPressed: this.altPressed
|
||||||
});
|
});
|
||||||
|
|
||||||
this.captureService.initModifiers();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setLabels(): void {
|
private setLabels(): void {
|
||||||
@@ -288,12 +335,18 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyAction.isLayerToggleable) {
|
if (keyAction.switchLayerMode === SwitchLayerMode.toggle) {
|
||||||
this.labelType = LabelTypes.TextIcon;
|
this.labelType = LabelTypes.TextIcon;
|
||||||
this.labelSource = {
|
this.labelSource = {
|
||||||
text: newLabelSource,
|
text: newLabelSource,
|
||||||
icon: this.mapper.getIcon('toggle')
|
icon: this.mapper.getIcon('toggle')
|
||||||
};
|
};
|
||||||
|
} else if (keyAction.switchLayerMode === SwitchLayerMode.holdAndDoubleTapToggle) {
|
||||||
|
this.labelType = LabelTypes.TextIcon;
|
||||||
|
this.labelSource = {
|
||||||
|
text: newLabelSource,
|
||||||
|
icon: this.mapper.getIcon('double-tap')
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
this.labelType = LabelTypes.OneLineText;
|
this.labelType = LabelTypes.OneLineText;
|
||||||
this.labelSource = newLabelSource;
|
this.labelSource = newLabelSource;
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from
|
|||||||
import { KeyAction } from 'uhk-common';
|
import { KeyAction } from 'uhk-common';
|
||||||
|
|
||||||
import { SvgKeyboardKey } from '../keys';
|
import { SvgKeyboardKey } from '../keys';
|
||||||
|
import {
|
||||||
|
SvgKeyCaptureEvent,
|
||||||
|
SvgKeyClickEvent,
|
||||||
|
SvgModuleCaptureEvent,
|
||||||
|
SvgModuleKeyClickEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'g[svg-module]',
|
selector: 'g[svg-module]',
|
||||||
@@ -17,18 +23,18 @@ export class SvgModuleComponent {
|
|||||||
@Input() selected: boolean;
|
@Input() selected: boolean;
|
||||||
@Input() keybindAnimationEnabled: boolean;
|
@Input() keybindAnimationEnabled: boolean;
|
||||||
@Input() capturingEnabled: boolean;
|
@Input() capturingEnabled: boolean;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgModuleKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgModuleCaptureEvent>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.keyboardKeys = [];
|
this.keyboardKeys = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyClick(index: number, keyTarget: HTMLElement): void {
|
onKeyClick(keyId: number, event: SvgKeyClickEvent): void {
|
||||||
this.keyClick.emit({
|
this.keyClick.emit({
|
||||||
index,
|
...event,
|
||||||
keyTarget
|
keyId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +46,10 @@ export class SvgModuleComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCapture(index: number, captured: {code: number, left: boolean[], right: boolean[]}) {
|
onCapture(keyId: number, event: SvgKeyCaptureEvent) {
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
index,
|
...event,
|
||||||
captured
|
keyId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { SvgSeparator } from './svg-separator.model';
|
||||||
|
|
||||||
|
export const convertXmlToSvgSeparator = (obj: { path: any[], $: Object }): SvgSeparator => {
|
||||||
|
return obj.path[0].$;
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './svg-separator.model';
|
||||||
|
export * from './convert-xml-to-svg-separator';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface SvgSeparator {
|
||||||
|
style: string;
|
||||||
|
d: string;
|
||||||
|
}
|
||||||
@@ -8,13 +8,25 @@
|
|||||||
[halvesSplit]="halvesSplit"
|
[halvesSplit]="halvesSplit"
|
||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
[description]="keymap.description"
|
[description]="keymap.description"
|
||||||
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
|
(keyClick)="onKeyClick($event)"
|
||||||
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
|
(keyHover)="onKeyHover($event)"
|
||||||
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
|
(capture)="onCapture($event)"
|
||||||
(descriptionChanged)="onDescriptionChanged($event)"
|
(descriptionChanged)="onDescriptionChanged($event)"
|
||||||
></keyboard-slider>
|
></keyboard-slider>
|
||||||
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
|
|
||||||
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
|
<popover tabindex="0"
|
||||||
|
[visible]="popoverShown"
|
||||||
|
[keyPosition]="keyPosition"
|
||||||
|
[wrapPosition]="wrapPosition"
|
||||||
|
[defaultKeyAction]="popoverInitKeyAction"
|
||||||
|
[currentKeymap]="keymap"
|
||||||
|
[currentLayer]="currentLayer"
|
||||||
|
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
||||||
|
[remapOnAllKeymap]="remapOnAllKeymap"
|
||||||
|
[remapOnAllLayer]="remapOnAllLayer"
|
||||||
|
(cancel)="hidePopover()"
|
||||||
|
(remap)="onRemap($event)"></popover>
|
||||||
|
|
||||||
<div class="tooltip bottom"
|
<div class="tooltip bottom"
|
||||||
[class.in]="tooltipData.show"
|
[class.in]="tooltipData.show"
|
||||||
[style.top.px]="tooltipData.posTop"
|
[style.top.px]="tooltipData.posTop"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user