33 Commits

Author SHA1 Message Date
László Monda
38184e7968 Bump Agent version to 1.2.6 and firmware version to 8.4.0. Update changelog. 2018-07-26 06:04:47 +02:00
Róbert Kiss
f6092ea195 fix: popover components use OnPush change detection (#727)
* fix: popover components use OnPush change detection

* fix: select2 selection bug

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

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

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

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

* feat: style the ngrx-select

* feat: replace secondary role select2

* feat: replace Select2OptionData => SelectOptionData

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

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

* feat: fix styles

* chore: remove select2 from dependencies

* fix: macro editor overflow

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

* fix: overflow

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

* feat: Display firmware update status

* feat: throw error when left half not connected under firmware upgrade
2018-06-28 18:41:16 +02:00
László Monda
5e4fc983fb Rename the "Remap Key" button to "Remay key". 2018-06-27 21:28:16 +02:00
László Monda
32d9635b34 Tone down the color of the separator line. 2018-06-26 06:05:03 +02:00
László Monda
3978011d2e Bump Agent version to 1.2.5 and update changelog. 2018-06-26 03:13:49 +02:00
László Monda
cd1952a7df Restore blhost as blhost.old from 0f24427628 2018-06-26 02:21:33 +02:00
László Monda
4251477451 Comment out the export keymap icon because there isn't a way to import keymap yet, so it's useless. 2018-06-26 01:53:04 +02:00
Róbert Kiss
873f1de1ef fix: cancelling the key action popover holds its state (#699) 2018-06-25 00:27:14 +02:00
Róbert Kiss
150f993e5f feat: change side menu Agent icon (#698) 2018-06-25 00:05:01 +02:00
Róbert Kiss
06e76e5e0f fix: only flash the remapped key (#697) 2018-06-24 23:39:32 +02:00
Róbert Kiss
a208a264c7 fix: only animate keyboard separator when splitting (#696) 2018-06-24 23:06:33 +02:00
Róbert Kiss
114014fa13 feat: Show not supported OS on firmware page when relevant (#695) 2018-06-24 22:16:00 +02:00
Róbert Kiss
94cfd9d2e9 fix: SwitchKeymapAction not allow to refer to itself (#694) 2018-06-24 20:32:32 +02:00
Róbert Kiss
0aa9c73b4b feat: log firmware version before upgrading firmware (#693) 2018-06-24 19:56:11 +02:00
Róbert Kiss
5234f85dbe fix: close device when any error occurred in the communication (#692) 2018-06-24 18:59:13 +02:00
84 changed files with 1550 additions and 725 deletions

View File

@@ -6,6 +6,38 @@ 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 ## [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 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

1067
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
"private": true, "private": true,
"author": "Ultimate Gadget Laboratories", "author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js", "main": "electron/dist/electron-main.js",
"version": "1.2.4", "version": "1.2.6",
"firmwareVersion": "8.2.5", "firmwareVersion": "8.4.0",
"deviceProtocolVersion": "4.3.1", "deviceProtocolVersion": "4.4.0",
"userConfigVersion": "4.0.1", "userConfigVersion": "4.0.1",
"hardwareConfigVersion": "1.0.0", "hardwareConfigVersion": "1.0.0",
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.", "description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
@@ -38,14 +38,14 @@
"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",

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import { 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';
@@ -30,7 +31,9 @@ export class AppService extends MainServiceBase {
}, },
deviceConnected: deviceConnectionState.connected, deviceConnected: deviceConnectionState.connected,
hasPermission: deviceConnectionState.hasPermission, hasPermission: deviceConnectionState.hasPermission,
bootloaderActive: deviceConnectionState.bootloaderActive 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);

View File

@@ -15,6 +15,7 @@ import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } fr
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';
@@ -22,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
@@ -40,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[]) => {
@@ -81,6 +87,15 @@ export class DeviceService {
}); });
}); });
ipcMain.on(IpcEvents.device.enableUsbStackTest, (...args: any[]) => {
this.queueManager.add({
method: this.enableUsbStackTest,
bind: this,
params: args,
asynchronous: true
});
});
logService.debug('[DeviceService] init success'); logService.debug('[DeviceService] init success');
} }
@@ -146,15 +161,27 @@ export class DeviceService {
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.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();
} }
@@ -206,6 +233,10 @@ export class DeviceService {
event.sender.send(IpcEvents.device.updateFirmwareReply, response); event.sender.send(IpcEvents.device.updateFirmwareReply, response);
} }
public async enableUsbStackTest(event: Electron.Event) {
await this.device.enableUsbStackTest();
}
/** /**
* HID API not support device attached and detached event. * HID API not support device attached and detached event.
* This method check the keyboard is attached to the computer or not. * This method check the keyboard is attached to the computer or not.

View File

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

View File

@@ -0,0 +1,3 @@
export * from './backup-user-confoguration';
export * from './get-package-json-from-path-async';
export * from './save-extract-firmware';

View File

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

View File

@@ -5,4 +5,6 @@ export interface AppStartInfo {
deviceConnected: boolean; deviceConnected: boolean;
hasPermission: boolean; hasPermission: boolean;
bootloaderActive: boolean; bootloaderActive: boolean;
platform: string;
osVersion: string;
} }

View File

@@ -30,6 +30,7 @@ export class Device {
public static readonly updateFirmwareReply = 'device-update-firmware-reply'; public static readonly updateFirmwareReply = 'device-update-firmware-reply';
public static readonly startConnectionPoller = 'device-start-connection-poller'; public static readonly startConnectionPoller = 'device-start-connection-poller';
public static readonly recoveryDevice = 'device-recovery'; public static readonly recoveryDevice = 'device-recovery';
public static readonly enableUsbStackTest = 'enable-usb-stack-test';
} }
export class IpcEvents { export class IpcEvents {

View File

@@ -23,7 +23,12 @@ export enum UsbCommand {
GetDebugBuffer = 0x0b, GetDebugBuffer = 0x0b,
GetAdcValue = 0x0c, GetAdcValue = 0x0c,
SetLedPwmBrightness = 0x0d, SetLedPwmBrightness = 0x0d,
GetModuleProperty = 0x0e GetModuleProperty = 0x0e,
GetSlaveI2cErrors = 0x0f,
SetI2cBaudRate = 0x10,
SwitchKeymap = 0x11,
GetVariable = 0x12,
SetVariable = 0x13
} }
export enum EepromOperation { export enum EepromOperation {
@@ -86,3 +91,10 @@ export enum KbootCommands {
export enum ModulePropertyId { export enum ModulePropertyId {
protocolVersions = 0 protocolVersions = 0
} }
export enum UsbVariables {
testSwitches = 0,
testUsbStack = 1,
debounceTimePress = 2,
debounceTimeRelease = 3
}

View File

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

View File

@@ -59,8 +59,9 @@ export class UhkOperations {
const leftModuleBricked = await this.waitForKbootIdle(); const leftModuleBricked = await this.waitForKbootIdle();
if (!leftModuleBricked) { if (!leftModuleBricked) {
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.'); const msg = '[UhkOperations] Couldn\'t connect to the left keyboard half.';
return; this.logService.error(msg);
throw new Error(msg);
} }
await this.device.reenumerate(EnumerationModes.Buspal); await this.device.reenumerate(EnumerationModes.Buspal);

View File

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

View File

@@ -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",
@@ -1270,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",
@@ -1377,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",
@@ -5758,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",
@@ -6509,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",
@@ -8090,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",

View File

@@ -61,15 +61,14 @@
"karma-jasmine-html-reporter": "0.2.2", "karma-jasmine-html-reporter": "0.2.2",
"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",

View File

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

View File

@@ -1,11 +1,13 @@
import { Component, ViewEncapsulation } from '@angular/core'; import { Component, HostListener, ViewEncapsulation, OnDestroy } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations'; import { 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());
}
} }

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="fa fa-question-circle"></i>
<span class="macro__name pane-title__name">Help</span>
</h1>
</div>
<div class="row">
<div class="col-xs-12">
Frequently asked questions
<ul>
<li><a href="https://ultimatehackingkeyboard.com/blog/2018/06/23/how-can-i-type-accented-characters-with-my-uhk" externalUrl>How can I type accented characters with my UHK?</a></li>
</ul>
</div>
</div>
<div class="row">
<div class="col-xs-12">
Keyboard shortcuts
<ul>
<li><kbd>CTRL</kbd> + <kbd>Enter</kbd> = Remap key</li>
<li><kbd>CTRL</kbd> + <kbd>S</kbd> = Save to keyboard</li>
<li>Right click on a key = Capture key</li>
<li>Hold Shift while clicking on a key = Remap on all keymaps</li>
<li>Hold Alt while clicking on a key = Remap on all layers</li>
</ul>
</div>
</div>

View File

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

View File

@@ -0,0 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'help-page',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './help-page.component.html',
styleUrls: ['./help-page.component.scss'],
host: {
'class': 'container-fluid'
}
})
export class HelpPageComponent {
}

View File

@@ -12,13 +12,10 @@
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half. Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
</p> </p>
<p>Please note that firmware update doesn't work on Windows 7, Windows Vista, and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p> <p *ngIf="runningOnNotSupportedWindows$ | async">Firmware update doesn't work on Windows 7, Windows Vista,
and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
<p>If the update process fails, disconnect every USB device from your computer including USB hubs, KVM switches, and every USB device. Then connect only your UHK and retry.</p> <p *ngIf="firmwareUpgradeAllowed$ | async">
<p>If you tried the above and the update still keeps failing, please <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">create a GitHub issue</a>, and attach the update log.</p>
<p>
<button class="btn btn-primary" <button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async" [disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmware()"> (click)="onUpdateFirmware()">
@@ -29,9 +26,23 @@
accept=".tar.bz2" accept=".tar.bz2"
label="Choose firmware file and flash it"></file-upload> label="Choose firmware file and flash it"></file-upload>
</p> </p>
<div *ngIf="firmwareUpgradeFailed$ | async"
class="alert alert-danger"
role="alert">
<p>Firmware update failed. Disconnect every USB device from your computer (including USB hubs, KVM switches, USB dongles, and everything else), then connect only your UHK and retry.</p>
<p>If you've tried the above and the update still keeps failing, please <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">create a GitHub issue</a>, and attach the update log.</p>
</div>
<div *ngIf="firmwareUpgradeSuccess$ | async"
class="alert alert-success"
role="alert">
<p>Firmware update succeeded.</p>
</div>
</div> </div>
<div class="flex-grow"> <div class="flex-grow" *ngIf="firmwareUpgradeAllowed$ | async">
<xterm [logs]="xtermLog$ | async"></xterm> <xterm [logs]="xtermLog$ | async"></xterm>
</div> </div>
<div class="flex-footer"> <div class="flex-footer">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -109,7 +109,7 @@
<div class="d-inline-block pull-right"> <div class="d-inline-block pull-right">
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel</button> <button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel</button>
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button" <button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button"
(click)="onRemapKey()"> Remap Key (click)="onRemapKey()"> Remap key
</button> </button>
</div> </div>
</div> </div>

View File

@@ -116,23 +116,6 @@
} }
} }
.select2-item {
position: relative;
font-size: 1.5rem;
&.keymap-name--wrapper {
padding-left: 50px;
}
.layout-segment-code {
height: 2rem;
position: absolute;
left: 0;
top: 50%;
margin-top: -1rem;
}
}
.popover-action-form { .popover-action-form {
margin-top: 4px; margin-top: 4px;

View File

@@ -84,6 +84,8 @@ export class PopoverComponent implements OnChanges {
@Input() wrapPosition: any; @Input() wrapPosition: any;
@Input() visible: boolean; @Input() visible: boolean;
@Input() allowLayerDoubleTap: boolean; @Input() allowLayerDoubleTap: boolean;
@Input() remapOnAllKeymap: boolean;
@Input() remapOnAllLayer: boolean;
@Output() cancel = new EventEmitter<any>(); @Output() cancel = new EventEmitter<any>();
@Output() remap = new EventEmitter<KeyActionRemap>(); @Output() remap = new EventEmitter<KeyActionRemap>();
@@ -101,9 +103,6 @@ export class PopoverComponent implements OnChanges {
leftPosition: number = 0; leftPosition: number = 0;
animationState: string; animationState: string;
remapOnAllKeymap: boolean;
remapOnAllLayer: boolean;
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined); private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
constructor(store: Store<AppState>) { constructor(store: Store<AppState>) {
@@ -143,8 +142,6 @@ export class PopoverComponent implements OnChanges {
if (change['visible']) { if (change['visible']) {
if (change['visible'].currentValue) { if (change['visible'].currentValue) {
this.animationState = 'opened'; this.animationState = 'opened';
this.remapOnAllKeymap = false;
this.remapOnAllLayer = false;
} else { } else {
this.animationState = 'closed'; this.animationState = 'closed';
} }
@@ -179,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;
} }

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,27 @@
<div class="scancode-options"> <div class="scancode-options">
<b class="setting-label">Scancode:</b> <b class="setting-label">Scancode:</b>
<select2 <div class="scancode-container">
[data]="scanCodeGroups" <ngx-select [items]="scanCodeGroups"
[value]="selectedScancodeOption.id" [ngModel]="selectedScancodeOption?.id"
(valueChanged)="onScancodeChange($event)" [autoActiveOnMouseEnter]="false"
[width]="200" size="small"
[options]="options" optionValueField="id"
></select2> optionTextField="text"
optGroupLabelField="text"
optGroupOptionsField="children"
(select)="onScancodeChange($event)">
<ng-template ngx-select-option let-option>
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '0'}">
<span>{{ option.text }}</span>
<span class="scancode--searchterm">
{{ option.data.additional?.explanation}}
</span>
</span>
</ng-template>
</ngx-select>
</div>
<icon name="question-circle" <icon name="question-circle"
data-toggle="tooltip" data-toggle="tooltip"
html="true" html="true"
@@ -41,12 +56,28 @@
</div> </div>
<div class="long-press-container" *ngIf="secondaryRoleEnabled"> <div class="long-press-container" *ngIf="secondaryRoleEnabled">
<b class="setting-label">Secondary role:</b> <b class="setting-label">Secondary role:</b>
<select2 #secondaryRoleSelect <div class="secondary-role-groups-container">
[data]="secondaryRoleGroups" <ngx-select [items]="secondaryRoleGroups"
[value]="selectedSecondaryRoleIndex.toString()" [ngModel]="selectedSecondaryRoleIndex.toString()"
(valueChanged)="onSecondaryRoleChange($event)" [autoActiveOnMouseEnter]="false"
[width]="140" size="small"
></select2> optionValueField="id"
optionTextField="text"
optGroupLabelField="text"
optGroupOptionsField="children"
(select)="onSecondaryRoleChange($event)">
<ng-template ngx-select-option let-option>
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
<span>{{ option.text }}</span>
<span class="scancode--searchterm">
{{ option.data.additional?.explanation}}
</span>
</span>
</ng-template>
</ngx-select>
</div>
<icon name="question-circle" <icon name="question-circle"
data-toggle="tooltip" data-toggle="tooltip"
html="true" html="true"

View File

@@ -79,4 +79,14 @@
display: block; display: block;
} }
} }
.scancode-container {
display: inline-block;
width: 200px;
}
.secondary-role-groups-container {
display: inline-block;
width: 140px;
}
} }

View File

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

View File

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

View File

@@ -5,7 +5,23 @@
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p> <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>

View File

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

View File

@@ -1,16 +1,17 @@
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store'; import { 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 {

View File

@@ -1,10 +1,11 @@
import { Component, Input, OnChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common'; import { 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']
}) })

View File

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

View File

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

View File

@@ -13,11 +13,11 @@
[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 [ngClass]="{'separator-visible': !halvesSplit, 'separator-hide': halvesSplit}" <svg:path [@fadeSeparator]="separatorAnimation"
[attr.d]="separator.d" [attr.d]="separator.d"
[attr.style]="separatorStyle" /> [attr.style]="separatorStyle" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -8,33 +8,3 @@ editable-text {
padding-right: 2em; padding-right: 2em;
display: block; display: block;
} }
.separator-visible {
animation: visible-fade-in 1.5s;
opacity: 1;
}
@keyframes visible-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.separator-hide {
animation: visible-fade-out 1.5s;
opacity: 0;
}
@keyframes visible-fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@@ -7,6 +7,12 @@ import { SvgModule } from '../module';
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service'; import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum'; import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { SvgSeparator } from '../separator'; import { SvgSeparator } from '../separator';
import {
SvgKeyHoverEvent,
SvgKeyboardKeyClickEvent,
SvgKeyboardCaptureEvent,
SvgModuleKeyClickEvent
} from '../../../models/svg-key-events';
@Component({ @Component({
selector: 'svg-keyboard', selector: 'svg-keyboard',
@@ -22,6 +28,16 @@ import { SvgSeparator } from '../separator';
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))
]) ])
] ]
}) })
@@ -35,9 +51,9 @@ export class SvgKeyboardComponent implements OnInit {
@Input() keyboardLayout = KeyboardLayout.ANSI; @Input() keyboardLayout = KeyboardLayout.ANSI;
@Input() description: string; @Input() description: string;
@Input() showDescription = false; @Input() showDescription = false;
@Output() keyClick = new EventEmitter(); @Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
@Output() keyHover = new EventEmitter(); @Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
@Output() capture = new EventEmitter(); @Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
@Output() descriptionChanged = new EventEmitter<string>(); @Output() descriptionChanged = new EventEmitter<string>();
modules: SvgModule[]; modules: SvgModule[];
@@ -45,6 +61,7 @@ export class SvgKeyboardComponent implements OnInit {
moduleAnimationStates: string[]; moduleAnimationStates: string[];
separator: SvgSeparator; separator: SvgSeparator;
separatorStyle: SafeStyle; separatorStyle: SafeStyle;
separatorAnimation = 'visible';
constructor(private svgModuleProvider: SvgModuleProviderService, constructor(private svgModuleProvider: SvgModuleProviderService,
private sanitizer: DomSanitizer) { private sanitizer: DomSanitizer) {
@@ -68,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
}); });
} }
@@ -96,8 +111,10 @@ 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';
} }
} }

View File

@@ -26,6 +26,7 @@ import { MapperService } from '../../../../services/mapper.service';
import { AppState } from '../../../../store'; 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,
@@ -82,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;
@@ -96,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,
@@ -115,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');
@@ -129,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) {
@@ -144,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;
@@ -152,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;
}
} }
} }
@@ -198,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 {

View File

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

View File

@@ -8,9 +8,9 @@
[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>
@@ -22,6 +22,8 @@
[currentKeymap]="keymap" [currentKeymap]="keymap"
[currentLayer]="currentLayer" [currentLayer]="currentLayer"
[allowLayerDoubleTap]="allowLayerDoubleTap" [allowLayerDoubleTap]="allowLayerDoubleTap"
[remapOnAllKeymap]="remapOnAllKeymap"
[remapOnAllLayer]="remapOnAllLayer"
(cancel)="hidePopover()" (cancel)="hidePopover()"
(remap)="onRemap($event)"></popover> (remap)="onRemap($event)"></popover>

View File

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

View File

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

View File

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

View File

@@ -1,2 +1,3 @@
export * from './cancelable'; export * from './cancelable';
export * from './tooltip'; export * from './tooltip';
export * from './external-url';

View File

@@ -1,3 +1,4 @@
///<reference path="../../../../node_modules/@types/jquery/index.d.ts"/>
import { AfterContentInit, Directive, ElementRef, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core'; import { AfterContentInit, Directive, ElementRef, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';

View File

@@ -0,0 +1,7 @@
export interface SelectOptionData {
id: string;
text: string;
disabled?: boolean;
children?: Array<SelectOptionData>;
additional?: any;
}

View File

@@ -0,0 +1,42 @@
export interface SvgKeyClickEvent {
keyTarget: HTMLElement;
shiftPressed?: boolean;
altPressed?: boolean;
}
export interface SvgModuleKeyClickEvent extends SvgKeyClickEvent {
keyId: number;
}
export interface SvgKeyboardKeyClickEvent extends SvgModuleKeyClickEvent {
moduleId: number;
}
export interface KeyCaptureData {
code: number;
left: boolean[];
right: boolean[];
}
export interface SvgKeyCaptureEvent {
captured: KeyCaptureData;
shiftPressed?: boolean;
altPressed?: boolean;
}
export interface SvgModuleCaptureEvent extends SvgKeyCaptureEvent {
keyId: number;
}
export interface SvgKeyboardCaptureEvent extends SvgModuleCaptureEvent {
moduleId: number;
}
export interface SvgKeyHoverEvent {
keyId: number;
event: MouseEvent;
over: boolean;
moduleId: number;
shiftPressed?: boolean;
altPressed?: boolean;
}

View File

@@ -50,6 +50,10 @@ export class DeviceRendererService {
this.ipcRenderer.send(IpcEvents.device.recoveryDevice); this.ipcRenderer.send(IpcEvents.device.recoveryDevice);
} }
enableUsbStackTest(): void {
this.ipcRenderer.send(IpcEvents.device.enableUsbStackTest);
}
private registerEvents(): void { private registerEvents(): void {
this.ipcRenderer.on(IpcEvents.device.deviceConnectionStateChanged, (event: string, arg: DeviceConnectionState) => { this.ipcRenderer.on(IpcEvents.device.deviceConnectionStateChanged, (event: string, arg: DeviceConnectionState) => {
this.dispachStoreAction(new ConnectionStateChangedAction(arg)); this.dispachStoreAction(new ConnectionStateChangedAction(arg));

View File

@@ -689,8 +689,9 @@
null, null,
{ {
"keyActionType": "keystroke", "keyActionType": "keystroke",
"type": "system", "type": "media",
"scancode": 130 "scancode": 184,
"modifierMask": 12
}, },
null, null,
{ {
@@ -2653,8 +2654,9 @@
null, null,
{ {
"keyActionType": "keystroke", "keyActionType": "keystroke",
"type": "system", "type": "media",
"scancode": 130 "scancode": 184,
"modifierMask": 12
}, },
null, null,
{ {
@@ -4611,8 +4613,9 @@
null, null,
{ {
"keyActionType": "keystroke", "keyActionType": "keystroke",
"type": "system", "type": "media",
"scancode": 130 "scancode": 184,
"modifierMask": 12
}, },
null, null,
{ {

View File

@@ -6,7 +6,7 @@ import { NotifierModule } from 'angular-notifier';
import { ConfirmationPopoverModule } from 'angular-confirmation-popover'; import { ConfirmationPopoverModule } from 'angular-confirmation-popover';
import { DragulaModule } from 'ng2-dragula/ng2-dragula'; import { DragulaModule } from 'ng2-dragula/ng2-dragula';
import { Select2Module } from 'ng2-select2/ng2-select2'; import { NgxSelectModule } from '@ert78gb/ngx-select-ex';
import { NouisliderModule } from 'ng2-nouislider'; import { NouisliderModule } from 'ng2-nouislider';
import { ClipboardModule } from 'ngx-clipboard'; import { ClipboardModule } from 'ngx-clipboard';
@@ -68,7 +68,7 @@ import { SvgModuleComponent } from './components/svg/module';
import { SvgKeyboardWrapComponent } from './components/svg/wrap'; import { SvgKeyboardWrapComponent } from './components/svg/wrap';
import { appRoutingProviders, routing } from './app.routes'; import { appRoutingProviders, routing } from './app.routes';
import { CancelableDirective, TooltipDirective } from './directives'; import { CancelableDirective, ExternalUrlDirective, TooltipDirective } from './directives';
import { SafeStylePipe } from './pipes'; import { SafeStylePipe } from './pipes';
import { CaptureService } from './services/capture.service'; import { CaptureService } from './services/capture.service';
@@ -109,6 +109,7 @@ import { Autofocus } from './directives/autofocus/autofocus.directive';
import { UhkDeviceBootloaderNotActiveGuard } from './services/uhk-device-bootloader-not-active.guard'; import { UhkDeviceBootloaderNotActiveGuard } from './services/uhk-device-bootloader-not-active.guard';
import { FileUploadComponent } from './components/file-upload'; import { FileUploadComponent } from './components/file-upload';
import { AutoGrowInputComponent } from './components/auto-grow-input'; import { AutoGrowInputComponent } from './components/auto-grow-input';
import { HelpPageComponent } from './components/agent/help-page/help-page.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -183,7 +184,9 @@ import { AutoGrowInputComponent } from './components/auto-grow-input';
RestoreConfigurationComponent, RestoreConfigurationComponent,
RecoveryModeComponent, RecoveryModeComponent,
FileUploadComponent, FileUploadComponent,
AutoGrowInputComponent AutoGrowInputComponent,
HelpPageComponent,
ExternalUrlDirective
], ],
imports: [ imports: [
CommonModule, CommonModule,
@@ -191,7 +194,7 @@ import { AutoGrowInputComponent } from './components/auto-grow-input';
FormsModule, FormsModule,
DragulaModule, DragulaModule,
routing, routing,
Select2Module, NgxSelectModule,
NouisliderModule, NouisliderModule,
NotifierModule.withConfig(angularNotifierConfig), NotifierModule.withConfig(angularNotifierConfig),
ConfirmationPopoverModule.forRoot({ ConfirmationPopoverModule.forRoot({

View File

@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type } from 'uhk-common'; import { AppStartInfo, HardwareConfiguration, Notification, type } from 'uhk-common';
import { ElectronLogEntry } from '../../models/xterm-log'; import { ElectronLogEntry } from '../../models/xterm-log';
const PREFIX = '[app] '; const PREFIX = '[app] ';
@@ -10,7 +10,7 @@ export const ActionTypes = {
APP_BOOTSRAPPED: type(PREFIX + 'bootstrapped'), APP_BOOTSRAPPED: type(PREFIX + 'bootstrapped'),
APP_STARTED: type(PREFIX + 'started'), APP_STARTED: type(PREFIX + 'started'),
APP_SHOW_NOTIFICATION: type(PREFIX + 'show notification'), APP_SHOW_NOTIFICATION: type(PREFIX + 'show notification'),
APPLY_COMMAND_LINE_ARGS: type(PREFIX + 'apply command line args'), APPLY_APP_START_INFO: type(PREFIX + 'apply command line args'),
APP_PROCESS_START_INFO: type(PREFIX + 'process start info'), APP_PROCESS_START_INFO: type(PREFIX + 'process start info'),
UNDO_LAST: type(PREFIX + 'undo last action'), UNDO_LAST: type(PREFIX + 'undo last action'),
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'), UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
@@ -38,10 +38,10 @@ export class ShowNotificationAction implements Action {
} }
} }
export class ApplyCommandLineArgsAction implements Action { export class ApplyAppStartInfoAction implements Action {
type = ActionTypes.APPLY_COMMAND_LINE_ARGS; type = ActionTypes.APPLY_APP_START_INFO;
constructor(public payload: CommandLineArgs) { constructor(public payload: AppStartInfo) {
} }
} }
@@ -107,7 +107,7 @@ export type Actions
= AppStartedAction = AppStartedAction
| AppBootsrappedAction | AppBootsrappedAction
| ShowNotificationAction | ShowNotificationAction
| ApplyCommandLineArgsAction | ApplyAppStartInfoAction
| ProcessAppStartInfoAction | ProcessAppStartInfoAction
| UndoLastAction | UndoLastAction
| UndoLastSuccessAction | UndoLastSuccessAction

View File

@@ -28,7 +28,8 @@ export const ActionTypes = {
HAS_BACKUP_USER_CONFIGURATION: type(PREFIX + 'Store backup user configuration'), HAS_BACKUP_USER_CONFIGURATION: type(PREFIX + 'Store backup user configuration'),
RESTORE_CONFIGURATION_FROM_BACKUP: type(PREFIX + 'Restore configuration from backup'), RESTORE_CONFIGURATION_FROM_BACKUP: type(PREFIX + 'Restore configuration from backup'),
RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS: type(PREFIX + 'Restore configuration from backup success'), RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS: type(PREFIX + 'Restore configuration from backup success'),
RECOVERY_DEVICE: type(PREFIX + 'Recovery device') RECOVERY_DEVICE: type(PREFIX + 'Recovery device'),
ENABLE_USB_STACK_TEST: type(PREFIX + 'USB stack test')
}; };
export class SetPrivilegeOnLinuxAction implements Action { export class SetPrivilegeOnLinuxAction implements Action {
@@ -144,6 +145,10 @@ export class RecoveryDeviceAction implements Action {
type = ActionTypes.RECOVERY_DEVICE; type = ActionTypes.RECOVERY_DEVICE;
} }
export class EnableUsbStackTestAction implements Action {
type = ActionTypes.ENABLE_USB_STACK_TEST;
}
export type Actions export type Actions
= SetPrivilegeOnLinuxAction = SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction | SetPrivilegeOnLinuxReplyAction
@@ -166,4 +171,5 @@ export type Actions
| HasBackupUserConfigurationAction | HasBackupUserConfigurationAction
| RestoreUserConfigurationFromBackupSuccessAction | RestoreUserConfigurationFromBackupSuccessAction
| RecoveryDeviceAction | RecoveryDeviceAction
| EnableUsbStackTestAction
; ;

View File

@@ -13,7 +13,7 @@ import 'rxjs/add/operator/catch';
import { AppStartInfo, LogService, Notification, NotificationType } from 'uhk-common'; import { AppStartInfo, LogService, Notification, NotificationType } from 'uhk-common';
import { import {
ActionTypes, ActionTypes,
ApplyCommandLineArgsAction, ApplyAppStartInfoAction,
AppStartedAction, AppStartedAction,
DismissUndoNotificationAction, DismissUndoNotificationAction,
OpenUrlInNewWindowAction, OpenUrlInNewWindowAction,
@@ -65,7 +65,7 @@ export class ApplicationEffects {
.mergeMap((appInfo: AppStartInfo) => { .mergeMap((appInfo: AppStartInfo) => {
this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo); this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo);
return [ return [
new ApplyCommandLineArgsAction(appInfo.commandLineArgs), new ApplyAppStartInfoAction(appInfo),
new ConnectionStateChangedAction({ new ConnectionStateChangedAction({
connected: appInfo.deviceConnected, connected: appInfo.deviceConnected,
hasPermission: appInfo.hasPermission, hasPermission: appInfo.hasPermission,

View File

@@ -23,6 +23,7 @@ import {
import { import {
ActionTypes, ActionTypes,
ConnectionStateChangedAction, ConnectionStateChangedAction,
EnableUsbStackTestAction,
HideSaveToKeyboardButton, HideSaveToKeyboardButton,
RecoveryDeviceAction, RecoveryDeviceAction,
ResetUserConfigurationAction, ResetUserConfigurationAction,
@@ -230,6 +231,10 @@ export class DeviceEffects {
.ofType<RecoveryDeviceAction>(ActionTypes.RECOVERY_DEVICE) .ofType<RecoveryDeviceAction>(ActionTypes.RECOVERY_DEVICE)
.do(() => this.deviceRendererService.recoveryDevice()); .do(() => this.deviceRendererService.recoveryDevice());
@Effect({dispatch: false}) enableUsbStackTest$ = this.actions$
.ofType<EnableUsbStackTestAction>(ActionTypes.ENABLE_USB_STACK_TEST)
.do(() => this.deviceRendererService.enableUsbStackTest());
constructor(private actions$: Actions, constructor(private actions$: Actions,
private router: Router, private router: Router,
private deviceRendererService: DeviceRendererService, private deviceRendererService: DeviceRendererService,

View File

@@ -52,6 +52,8 @@ export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLay
export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded); export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded);
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo); export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
export const getPrivilegePageState = createSelector(appState, fromApp.getPrivilagePageState); export const getPrivilegePageState = createSelector(appState, fromApp.getPrivilagePageState);
export const runningOnNotSupportedWindows = createSelector(appState, fromApp.runningOnNotSupportedWindows);
export const firmwareUpgradeAllowed = createSelector(runningOnNotSupportedWindows, notSupportedOs => !notSupportedOs);
export const appUpdateState = (state: AppState) => state.appUpdate; export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable); export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
@@ -81,6 +83,8 @@ export const getHardwareModules = createSelector(deviceState, fromDevice.getHard
export const getBackupUserConfigurationState = createSelector(deviceState, fromDevice.getBackupUserConfigurationState); export const getBackupUserConfigurationState = createSelector(deviceState, fromDevice.getBackupUserConfigurationState);
export const getRestoreUserConfiguration = createSelector(deviceState, fromDevice.getHasBackupUserConfiguration); export const getRestoreUserConfiguration = createSelector(deviceState, fromDevice.getHasBackupUserConfiguration);
export const bootloaderActive = createSelector(deviceState, fromDevice.bootloaderActive); export const bootloaderActive = createSelector(deviceState, fromDevice.bootloaderActive);
export const firmwareUpgradeFailed = createSelector(deviceState, fromDevice.firmwareUpgradeFailed);
export const firmwareUpgradeSuccess = createSelector(deviceState, fromDevice.firmwareUpgradeSuccess);
export const getSideMenuPageState = createSelector( export const getSideMenuPageState = createSelector(
showAddonMenu, showAddonMenu,

View File

@@ -1,6 +1,7 @@
import { ROUTER_NAVIGATION } from '@ngrx/router-store'; import { ROUTER_NAVIGATION } from '@ngrx/router-store';
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { import {
AppStartInfo,
CommandLineArgs, CommandLineArgs,
HardwareConfiguration, HardwareConfiguration,
Notification, Notification,
@@ -29,6 +30,8 @@ export interface State {
agentVersionInfo?: VersionInformation; agentVersionInfo?: VersionInformation;
privilegeWhatWillThisDoClicked: boolean; privilegeWhatWillThisDoClicked: boolean;
permissionError?: any; permissionError?: any;
platform?: string;
osVersion?: string;
} }
export const initialState: State = { export const initialState: State = {
@@ -50,10 +53,14 @@ export function reducer(state = initialState, action: Action & { payload: any })
}; };
} }
case ActionTypes.APPLY_COMMAND_LINE_ARGS: { case ActionTypes.APPLY_APP_START_INFO: {
const payload = action.payload as AppStartInfo;
return { return {
...state, ...state,
commandLineArgs: action.payload commandLineArgs: payload.commandLineArgs,
platform: payload.platform,
osVersion: payload.osVersion
}; };
} }
@@ -172,3 +179,15 @@ export const getPrivilagePageState = (state: State): PrivilagePageSate => {
showWhatWillThisDoContent: state.privilegeWhatWillThisDoClicked || permissionSetupFailed showWhatWillThisDoContent: state.privilegeWhatWillThisDoClicked || permissionSetupFailed
}; };
}; };
export const runningOnNotSupportedWindows = (state: State): boolean => {
if (!state.osVersion || state.platform !== 'win32') {
return false;
}
const version = state.osVersion.split('.');
const osMajor = +version[0];
const osMinor = +version[1];
return osMajor < 6 || (osMajor === 6 && osMinor < 2);
};

View File

@@ -23,6 +23,8 @@ export interface State {
savingToKeyboard: boolean; savingToKeyboard: boolean;
updatingFirmware: boolean; updatingFirmware: boolean;
firmwareUpdateFinished: boolean; firmwareUpdateFinished: boolean;
firmwareUpdateFailed?: boolean;
firmwareUpdateSuccess?: boolean;
modules: HardwareModules; modules: HardwareModules;
log: Array<XtermLog>; log: Array<XtermLog>;
restoringUserConfiguration: boolean; restoringUserConfiguration: boolean;
@@ -128,6 +130,8 @@ export function reducer(state = initialState, action: Action): State {
...state, ...state,
updatingFirmware: true, updatingFirmware: true,
firmwareUpdateFinished: false, firmwareUpdateFinished: false,
firmwareUpdateFailed: false,
firmwareUpdateSuccess: false,
log: [{message: 'Start flashing firmware', cssClass: XtermCssClass.standard}] log: [{message: 'Start flashing firmware', cssClass: XtermCssClass.standard}]
}; };
@@ -136,6 +140,7 @@ export function reducer(state = initialState, action: Action): State {
...state, ...state,
updatingFirmware: false, updatingFirmware: false,
firmwareUpdateFinished: true, firmwareUpdateFinished: true,
firmwareUpdateSuccess: true,
modules: (action as UpdateFirmwareSuccessAction).payload modules: (action as UpdateFirmwareSuccessAction).payload
}; };
@@ -150,6 +155,7 @@ export function reducer(state = initialState, action: Action): State {
...state, ...state,
updatingFirmware: false, updatingFirmware: false,
firmwareUpdateFinished: true, firmwareUpdateFinished: true,
firmwareUpdateFailed: true,
modules: data.modules, modules: data.modules,
log: [...state.log, logEntry] log: [...state.log, logEntry]
}; };
@@ -228,3 +234,5 @@ export const getBackupUserConfigurationState = (state: State): RestoreConfigurat
}; };
}; };
export const bootloaderActive = (state: State) => state.bootloaderActive; export const bootloaderActive = (state: State) => state.bootloaderActive;
export const firmwareUpgradeFailed = (state: State) => state.firmwareUpdateFailed;
export const firmwareUpgradeSuccess = (state: State) => state.firmwareUpdateSuccess;

View File

@@ -13,6 +13,7 @@ import {
Module, Module,
NoneAction, NoneAction,
PlayMacroAction, PlayMacroAction,
SwitchKeymapAction,
SwitchLayerAction, SwitchLayerAction,
UserConfiguration UserConfiguration
} from 'uhk-common'; } from 'uhk-common';
@@ -143,14 +144,17 @@ export function reducer(state = initialState, action: Action & { payload?: any }
const newKeyAction = keyActionRemap.action; const newKeyAction = keyActionRemap.action;
const newKeymap: Keymap = action.payload.keymap; const newKeymap: Keymap = action.payload.keymap;
const isSwitchLayerAction = newKeyAction instanceof SwitchLayerAction; const isSwitchLayerAction = newKeyAction instanceof SwitchLayerAction;
const isSwitchKeymapAction = newKeyAction instanceof SwitchKeymapAction;
changedUserConfiguration.keymaps = state.keymaps.map(keymap => { changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
if (keyActionRemap.remapOnAllKeymap || keymap.abbreviation === newKeymap.abbreviation) { // SwitchKeymapAction not allow to refer to itself
keymap = new Keymap(keymap); if (isSwitchKeymapAction && keymap.abbreviation === newKeyAction.keymapAbbreviation) {
return keymap;
}
if (keyActionRemap.remapOnAllKeymap || keymap.abbreviation === newKeymap.abbreviation) {
keymap.layers = keymap.layers.map((layer, index) => { keymap.layers = keymap.layers.map((layer, index) => {
if (keyActionRemap.remapOnAllLayer || index === layerIndex || isSwitchLayerAction) { if (keyActionRemap.remapOnAllLayer || index === layerIndex || isSwitchLayerAction) {
layer = new Layer(layer);
const clonedAction = KeyActionHelper.createKeyAction(newKeyAction); const clonedAction = KeyActionHelper.createKeyAction(newKeyAction);
// If the key action is a SwitchLayerAction then set the same SwitchLayerAction // If the key action is a SwitchLayerAction then set the same SwitchLayerAction

View File

@@ -2,6 +2,6 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="210mm" height="297mm"> <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="210mm" height="297mm">
<path <path
id="separator" id="separator"
style="fill:none;stroke:#f00;stroke-width:3.6496063;stroke-linecap:round" style="fill:none;stroke:#c00;stroke-width:3.6496063;stroke-linecap:round"
d="M 16.455118,651.55037 16.455118,737.88305 C 16.455118,737.88305 16.419979,743.14568 11.278346,743.14568 L -10.998425,743.14568 C -10.998425,743.14568 -16.174,743.39316 -16.174,748.40667 L -16.174,804.39801 C -16.174,807.0217 -14.110808,809.66218 -10.998425,809.66218 L -4.719685,809.66218 C -4.719685,809.66218 0.315,809.66109 0.315,814.92517 L 0.315,870.91651 C 0.315,870.91651 0.31884203,876.17868 5.3503937,876.17868 L 28.187008,876.17868 C 28.187008,876.17868 33.311,876.17121 33.311,881.44014 L 33.311,937.43147 C 33.311,937.43147 33.306776,942.69568 28.187008,942.69568 L 4.719685,942.69568 C 4.719685,942.69568 -0.01,942.67983 -0.01,947.95864 L -0.01,1050.5905" /> d="M 16.455118,651.55037 16.455118,737.88305 C 16.455118,737.88305 16.419979,743.14568 11.278346,743.14568 L -10.998425,743.14568 C -10.998425,743.14568 -16.174,743.39316 -16.174,748.40667 L -16.174,804.39801 C -16.174,807.0217 -14.110808,809.66218 -10.998425,809.66218 L -4.719685,809.66218 C -4.719685,809.66218 0.315,809.66109 0.315,814.92517 L 0.315,870.91651 C 0.315,870.91651 0.31884203,876.17868 5.3503937,876.17868 L 28.187008,876.17868 C 28.187008,876.17868 33.311,876.17121 33.311,881.44014 L 33.311,937.43147 C 33.311,937.43147 33.306776,942.69568 28.187008,942.69568 L 4.719685,942.69568 C 4.719685,942.69568 -0.01,942.67983 -0.01,947.95864 L -0.01,1050.5905" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 928 B

After

Width:  |  Height:  |  Size: 928 B

View File

@@ -5,6 +5,7 @@
@import './styles/tooltip'; @import './styles/tooltip';
@import './styles/uhk-icons/uhk-icon'; @import './styles/uhk-icons/uhk-icon';
@import './styles/side-menu'; @import './styles/side-menu';
@import './styles/ngx-select-ex';
html, body { html, body {
width: 100%; width: 100%;
@@ -25,10 +26,6 @@ html, body {
transform: rotate(90deg); // svg not aligned properly transform: rotate(90deg); // svg not aligned properly
} }
.select2-container--default .select2-selection--single .select2-selection__rendered {
line-height: 26px;
}
.main-content { .main-content {
height: 100%; height: 100%;
} }
@@ -39,34 +36,10 @@ html, body {
min-height: 100%; min-height: 100%;
} }
.select2 {
&-container {
z-index: 1100;
.scancode--searchterm {
text-align: right;
color: #b7b7b7;
}
}
&-item {
display: flex;
justify-content: space-between;
}
&-results {
text-align: left;
}
}
.nav-pills > li > a { .nav-pills > li > a {
cursor: pointer; cursor: pointer;
} }
.select2-container--default .select2-dropdown--below .select2-results > .select2-results__options {
max-height: 300px;
}
ul.btn-list { ul.btn-list {
& li { & li {
margin-top: 0.5em; margin-top: 0.5em;
@@ -187,3 +160,23 @@ pre {
.d-inline-block { .d-inline-block {
display: inline-block; display: inline-block;
} }
kbd {
padding: 0.1em 0.6em;
border: 1px solid #ccc;
font-size: 11px;
font-family: Arial, Helvetica, sans-serif;
background-color: #f7f7f7;
color: #333;
-moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
-webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
display: inline-block;
margin: 0 0.1em;
text-shadow: 0 1px 0 #fff;
line-height: 1.4;
white-space: nowrap;
}

View File

@@ -0,0 +1,40 @@
ngx-select,
.ngx-select {
.ngx-select__toggle {
font-size: 14px;
}
.dropdown-header {
font-weight: bold;
font-size: 1em;
color: black;
padding-left: 6px;
}
.dropdown-divider {
display: none;
}
a {
&.ngx-select__item {
padding-right: 6px;
padding-left: 6px;
}
&.ngx-select__item_active {
background-color: #dddddd;
color: black;
}
}
.indent-dropdown-item {
padding-left: 8px;
display: flex;
justify-content: space-between;
.scancode--searchterm {
text-align: right;
color: #b7b7b7;
}
}
}

View File

@@ -8,6 +8,5 @@
} }
.uhk-icon-agent-icon { .uhk-icon-agent-icon {
@extend %svg-common; background: url('assets/images/agent-icon.png') no-repeat;
background-position: 100% 0;
} }

View File

@@ -416,7 +416,6 @@ module.exports = {
"filename": "scripts.bundle.js", "filename": "scripts.bundle.js",
"scripts": [ "scripts": [
path.join(process.cwd(), "node_modules/bootstrap/dist/js/bootstrap.js"), path.join(process.cwd(), "node_modules/bootstrap/dist/js/bootstrap.js"),
path.join(process.cwd(), "node_modules/select2/dist/js/select2.full.js"),
// path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.js") // path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.js")
], ],
"basePath": path.resolve(process.cwd()) "basePath": path.resolve(process.cwd())

16
packages/usb/get-variable.js Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env node
const uhk = require('./uhk');
(async function() {
const device = uhk.getUhkDevice();
const variableName = process.argv[2];
const variableId = uhk.variableNameToId[variableName];
if (variableId === undefined) {
console.log(`The specified variable does not exist. Specify one of ${Object.keys(uhk.variableNameToId).join(', ')}`);
process.exit(1);
}
const receivedBuffer = await uhk.writeDevice(device, [uhk.usbCommands.getVariable, variableId]);
console.log(receivedBuffer[1]);
})();

15
packages/usb/set-variable.js Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env node
const uhk = require('./uhk');
(async function() {
const device = uhk.getUhkDevice();
const variableName = process.argv[2];
const variableId = uhk.variableNameToId[variableName];
if (variableId === undefined) {
console.log(`The specified variable does not exist. Specify one of ${Object.keys(uhk.variableNameToId).join(', ')}`);
process.exit(1);
}
await uhk.writeDevice(device, [uhk.usbCommands.setVariable, variableId, +process.argv[3]]);
})();

View File

@@ -450,6 +450,8 @@ uhk = exports = module.exports = moduleExports = {
getSlaveI2cErrors : 0x0f, getSlaveI2cErrors : 0x0f,
setI2cBaudRate : 0x10, setI2cBaudRate : 0x10,
switchKeymap : 0x11, switchKeymap : 0x11,
getVariable : 0x12,
setVariable : 0x13,
}, },
enumerationModes: { enumerationModes: {
bootloader: 0, bootloader: 0,
@@ -469,6 +471,12 @@ uhk = exports = module.exports = moduleExports = {
normalKeyboard: 0x6122, normalKeyboard: 0x6122,
compatibleKeyboard: 0x6123, compatibleKeyboard: 0x6123,
}, },
variableNameToId: {
testSwitches: 0,
testUsbStack: 1,
debounceTimePress: 2,
debounceTimeRelease: 3,
},
vendorId: 0x1D50, vendorId: 0x1D50,
devicePropertyIds: { devicePropertyIds: {
deviceProtocolVersion: 0, deviceProtocolVersion: 0,

View File

@@ -73,7 +73,7 @@ if (process.platform === 'darwin') {
target = Platform.WINDOWS.createTarget('nsis', builder.Arch.ia32, builder.Arch.x64); target = Platform.WINDOWS.createTarget('nsis', builder.Arch.ia32, builder.Arch.x64);
artifactName += '-${arch}.${ext}'; artifactName += '-${arch}.${ext}';
} else if (process.platform === 'linux') { } else if (process.platform === 'linux') {
target = Platform.LINUX.createTarget(); target = Platform.LINUX.createTarget('AppImage');
artifactName += '-${arch}.${ext}'; artifactName += '-${arch}.${ext}';
extraResources.push('rules/setup-rules.sh'); extraResources.push('rules/setup-rules.sh');
extraResources.push('rules/50-uhk60.rules'); extraResources.push('rules/50-uhk60.rules');