Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38184e7968 | ||
|
|
f6092ea195 | ||
|
|
ac7d66e338 | ||
|
|
b82a1da92a | ||
|
|
b8859f7b64 | ||
|
|
a04fa67446 | ||
|
|
ac89aff018 | ||
|
|
e7cf8dc966 | ||
|
|
d0102f5bdb | ||
|
|
eb0daadf98 | ||
|
|
49d6ca173d | ||
|
|
a3eb6a6b7e | ||
|
|
144ed57b20 | ||
|
|
6086ddabf0 | ||
|
|
84f378a276 | ||
|
|
648e8d5f2c | ||
|
|
15df8d7129 | ||
|
|
cfc0af9655 | ||
|
|
f02e3181a6 | ||
|
|
3d59bcf97e | ||
|
|
5e4fc983fb | ||
|
|
32d9635b34 | ||
|
|
3978011d2e | ||
|
|
cd1952a7df | ||
|
|
4251477451 | ||
|
|
873f1de1ef | ||
|
|
150f993e5f | ||
|
|
06e76e5e0f | ||
|
|
a208a264c7 | ||
|
|
114014fa13 | ||
|
|
94cfd9d2e9 | ||
|
|
0aa9c73b4b | ||
|
|
5234f85dbe | ||
|
|
bd8a2f704f | ||
|
|
439886d69f | ||
|
|
b2a37795e3 | ||
|
|
440db56080 | ||
|
|
337e6e6bb6 | ||
|
|
a1aeda3d35 | ||
|
|
c6a83f8c9b | ||
|
|
0f24427628 | ||
|
|
f52dc36a6a | ||
|
|
63a936968d | ||
|
|
cabfde7963 | ||
|
|
79628c2351 | ||
|
|
762fa6f8bf | ||
|
|
a258c097a9 | ||
|
|
41faa98fcd | ||
|
|
c4d7318686 | ||
|
|
9ef11eaa34 | ||
|
|
f34cb2df56 | ||
|
|
83b9f0d1e9 | ||
|
|
7d81cf0c6a | ||
|
|
82b76a9455 | ||
|
|
4ae577f936 | ||
|
|
81a83994ab | ||
|
|
1d3a3c7f5f |
56
CHANGELOG.md
@@ -6,12 +6,64 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
|
|||||||
|
|
||||||
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||||
|
|
||||||
|
## [1.2.6] - 2018-07-26
|
||||||
|
|
||||||
|
Firmware: 8.**4.0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.4.0)] | Device Protocol: 4.**4.0** | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Replace the Linux blhost binary with a statically compiled version that doesn't use special instructions and shouldn't segfault.
|
||||||
|
- Keep the current layer when changing keymaps.
|
||||||
|
- Fix the sleep key of Mac keymaps.
|
||||||
|
- Add help page.
|
||||||
|
- Add "save to keyboard" and "remap key" shortcuts.
|
||||||
|
- Build only AppImages for Linux.
|
||||||
|
- Replace ng2-select2 widgets with ngx-select-ex that always shows up in the correct position.
|
||||||
|
- Improve the phrasing of the firmware update error message.
|
||||||
|
- Tweak unsupported Windows firmware update notification.
|
||||||
|
- Hide the Settings menu until auto update is implemented.
|
||||||
|
- Don't scroll when the macro tab of the key action popover gets selected.
|
||||||
|
- Add keyboard shortcut for enabling the USB stack test mode of the firmware. `DEVICEPROTOCOL:MINOR`
|
||||||
|
- Tone down the color of the separator line.
|
||||||
|
|
||||||
|
## [1.2.5] - 2018-06-26
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- When remapping a switch keymap action on all keymaps, don't set it on its own keymap.
|
||||||
|
- Make the key action popover always contain the action of the current key, even after cancelled.
|
||||||
|
- Include the firmware version to be updated to the firmware update log.
|
||||||
|
- Update the Agent icon of the side menu and the about page.
|
||||||
|
- When remapping a key, only flash the affected key instead of all keys.
|
||||||
|
- Fade in/out the keyboard separator line only when splitting the keyboard.
|
||||||
|
- Only show the unsupported OS message of the firmware page on relevant Windows versions.
|
||||||
|
- Close and reopen USB device when an error occurs.
|
||||||
|
- Temporarily remove the export keymap feature because it's useless until import is implemented.
|
||||||
|
|
||||||
|
## [1.2.4] - 2018-06-21
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Replace Linux x86-64 blhost with a statically linked version which should make firmware updates work on every Linux distro.
|
||||||
|
|
||||||
|
## [1.2.3] - 2018-06-19
|
||||||
|
|
||||||
|
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.1 | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
|
- Add checkboxes for remapping keys on all layers and/or all keymaps.
|
||||||
|
- Add separator line between the keyboard halves.
|
||||||
|
- Add double tap icon for switch layer actions.
|
||||||
|
- Improve the looks and content of the tooltips of the key action popover.
|
||||||
|
- Make the left keyboard half less likely to timeout during firmware update.
|
||||||
|
- Terminate the firmware update process if blhost segfaults.
|
||||||
|
- Replace the Linux x86-64 version of the blhost binary which should not make it segfault anymore.
|
||||||
|
- Make the firmware update log shorter by listing one device per line and not repeating the list of available USB devices.
|
||||||
|
- Make the firmware update help text shorter.
|
||||||
|
|
||||||
## [1.2.2] - 2018-05-27
|
## [1.2.2] - 2018-05-27
|
||||||
|
|
||||||
Firmware: 8.2.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.**1** | User Config: 4.0.**1** | Hardware Config: 1.0.0
|
Firmware: 8.2.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.**1** | User Config: 4.0.1 | Hardware Config: 1.0.0
|
||||||
|
|
||||||
- Offer recovery for bricked right keyboard halfs.
|
- Offer recovery for bricked right keyboard halfs.
|
||||||
- Detect when the hardware configuration of a device is invalid and display a notification.
|
- Detect when the hardware configuration of a device is invalid and display a notification. `DEVICEPROTOCOL:PATCH`
|
||||||
- Check if the keyboard is in factory reset mode and if so, display a relevant instruction.
|
- Check if the keyboard is in factory reset mode and if so, display a relevant instruction.
|
||||||
- Only allow ASCII characters in type text macro actions.
|
- Only allow ASCII characters in type text macro actions.
|
||||||
- Allow uploading the same file multiple times in a row.
|
- Allow uploading the same file multiple times in a row.
|
||||||
|
|||||||
1088
package-lock.json
generated
16
package.json
@@ -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.2",
|
"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.",
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"@types/jasmine": "2.6.0",
|
"@types/jasmine": "2.6.0",
|
||||||
"@types/jquery": "3.3.1",
|
"@types/jquery": "3.3.1",
|
||||||
"@types/jsonfile": "4.0.1",
|
"@types/jsonfile": "4.0.1",
|
||||||
|
"@types/lodash-es": "4.17.0",
|
||||||
"@types/node": "8.0.53",
|
"@types/node": "8.0.53",
|
||||||
"@types/node-hid": "0.5.2",
|
"@types/node-hid": "0.5.2",
|
||||||
"@types/request": "2.0.8",
|
"@types/request": "2.0.8",
|
||||||
@@ -37,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",
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"gh-pages": "1.1.0",
|
"gh-pages": "1.1.0",
|
||||||
"jsonfile": "4.0.0",
|
"jsonfile": "4.0.0",
|
||||||
"lerna": "2.9.0",
|
"lerna": "2.9.0",
|
||||||
|
"lodash-es": "4.17.4",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"node-hid": "0.5.7",
|
"node-hid": "0.5.7",
|
||||||
"npm-run-all": "4.0.2",
|
"npm-run-all": "4.0.2",
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import * as isDev from 'electron-is-dev';
|
|||||||
|
|
||||||
const optionDefinitions = [
|
const optionDefinitions = [
|
||||||
{name: 'addons', type: Boolean},
|
{name: 'addons', type: Boolean},
|
||||||
{name: 'spe', type: Boolean} // simulate privilege escalation error
|
{name: 'spe', type: Boolean}, // simulate privilege escalation error
|
||||||
|
// show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
|
||||||
|
{name: 'layer-double-tap', type: Boolean}
|
||||||
];
|
];
|
||||||
|
|
||||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||||
@@ -104,7 +106,7 @@ function createWindow() {
|
|||||||
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
||||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
|
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);
|
||||||
appUpdateService = new AppUpdateService(logger, win, app);
|
appUpdateService = new AppUpdateService(logger, win, app);
|
||||||
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
||||||
sudoService = new SudoService(logger, options);
|
sudoService = new SudoService(logger, options);
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ import { SynchrounousResult } from 'tmp';
|
|||||||
export interface TmpFirmware {
|
export interface TmpFirmware {
|
||||||
rightFirmwarePath: string;
|
rightFirmwarePath: string;
|
||||||
leftFirmwarePath: string;
|
leftFirmwarePath: string;
|
||||||
|
packageJsonPath: string;
|
||||||
tmpDirectory: SynchrounousResult;
|
tmpDirectory: SynchrounousResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BrowserWindow, ipcMain, shell } from 'electron';
|
import { ipcMain, shell } from 'electron';
|
||||||
import { UhkHidDevice } from 'uhk-usb';
|
import { UhkHidDevice } from 'uhk-usb';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
||||||
import { MainServiceBase } from './main-service-base';
|
import { MainServiceBase } from './main-service-base';
|
||||||
@@ -25,11 +26,14 @@ export class AppService extends MainServiceBase {
|
|||||||
const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState();
|
const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState();
|
||||||
const response: AppStartInfo = {
|
const response: AppStartInfo = {
|
||||||
commandLineArgs: {
|
commandLineArgs: {
|
||||||
addons: this.options.addons || false
|
addons: this.options.addons || false,
|
||||||
|
layerDoubleTap: this.options['layer-double-tap'] || false
|
||||||
},
|
},
|
||||||
deviceConnected: 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);
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
export const getPackageJsonFromPathAsync = async (filePath: string): Promise<any> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(filePath, {encoding: 'utf-8'}, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
3
packages/uhk-agent/src/util/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './backup-user-confoguration';
|
||||||
|
export * from './get-package-json-from-path-async';
|
||||||
|
export * from './save-extract-firmware';
|
||||||
@@ -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')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { binaryDefaultHelper, jsonDefaultHelper } from '../../../../test/serializer-test-helper';
|
import { binaryDefaultHelper, jsonDefaultHelper } from '../../../../test/serializer-test-helper';
|
||||||
import { SwitchLayerAction } from './switch-layer-action';
|
import { SwitchLayerAction, SwitchLayerMode } from './switch-layer-action';
|
||||||
|
import { keyActionType } from './key-action';
|
||||||
|
|
||||||
// TODO: Add null, undefined, empty object, empty buffer test cases
|
// TODO: Add null, undefined, empty object, empty buffer test cases
|
||||||
describe('switch-layer-action', () => {
|
describe('switch-layer-action', () => {
|
||||||
const action = new SwitchLayerAction(<SwitchLayerAction>{layer: 0, isLayerToggleable: false});
|
const action = new SwitchLayerAction(<SwitchLayerAction>{layer: 0, switchLayerMode: SwitchLayerMode.hold});
|
||||||
|
|
||||||
it('should be instantiate', () => {
|
it('should be instantiate', () => {
|
||||||
expect(new SwitchLayerAction()).toBeTruthy();
|
expect(new SwitchLayerAction()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('should return <SwitchLayerAction layer="0" toggle="false">', () => {
|
it('should return <SwitchLayerAction layer="0" switchLayerMode="hold">', () => {
|
||||||
expect(action.toString()).toEqual('<SwitchLayerAction layer="0" toggle="false">');
|
expect(action.toString()).toEqual('<SwitchLayerAction layer="0" switchLayerMode="hold">');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,4 +31,20 @@ describe('switch-layer-action', () => {
|
|||||||
binaryDefaultHelper(action);
|
binaryDefaultHelper(action);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('backward compatibility of the "toggle" property ', () => {
|
||||||
|
it('should map toggle=false to SwitchLayerMode.holdAndDoubleTapToggle', () => {
|
||||||
|
const oldAction = new SwitchLayerAction();
|
||||||
|
oldAction.fromJsonObject({keyActionType: keyActionType.SwitchLayerAction, layer: 0, toggle: false});
|
||||||
|
|
||||||
|
expect(oldAction.switchLayerMode).toEqual(SwitchLayerMode.holdAndDoubleTapToggle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map toggle=true to SwitchLayerMode.toggle', () => {
|
||||||
|
const oldAction = new SwitchLayerAction();
|
||||||
|
oldAction.fromJsonObject({keyActionType: keyActionType.SwitchLayerAction, layer: 0, toggle: true});
|
||||||
|
|
||||||
|
expect(oldAction.switchLayerMode).toEqual(SwitchLayerMode.toggle);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,9 +8,48 @@ export enum LayerName {
|
|||||||
mouse
|
mouse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SwitchLayerMode {
|
||||||
|
holdAndDoubleTapToggle = 'holdAndDoubleTapToggle',
|
||||||
|
toggle = 'toggle',
|
||||||
|
hold = 'hold'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapSwitchLayerModeToNumber = (switchLayerMode: SwitchLayerMode): number => {
|
||||||
|
switch (switchLayerMode) {
|
||||||
|
case SwitchLayerMode.holdAndDoubleTapToggle:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case SwitchLayerMode.toggle:
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case SwitchLayerMode.hold:
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Can not map ${switchLayerMode} to number`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapNumberToSwitchLayerMode = (value: number): SwitchLayerMode => {
|
||||||
|
switch (value) {
|
||||||
|
case 0:
|
||||||
|
return SwitchLayerMode.holdAndDoubleTapToggle;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return SwitchLayerMode.toggle;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return SwitchLayerMode.hold;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Can not map "${value}" to SwitchLayerMode`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export class SwitchLayerAction extends KeyAction {
|
export class SwitchLayerAction extends KeyAction {
|
||||||
|
|
||||||
isLayerToggleable: boolean;
|
@assertEnum(SwitchLayerMode)
|
||||||
|
switchLayerMode: SwitchLayerMode;
|
||||||
|
|
||||||
@assertEnum(LayerName)
|
@assertEnum(LayerName)
|
||||||
layer: LayerName;
|
layer: LayerName;
|
||||||
@@ -20,21 +59,29 @@ export class SwitchLayerAction extends KeyAction {
|
|||||||
if (!other) {
|
if (!other) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isLayerToggleable = other.isLayerToggleable;
|
this.switchLayerMode = other.switchLayerMode;
|
||||||
this.layer = other.layer;
|
this.layer = other.layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromJsonObject(jsonObject: any): SwitchLayerAction {
|
fromJsonObject(jsonObject: any): SwitchLayerAction {
|
||||||
this.assertKeyActionType(jsonObject);
|
this.assertKeyActionType(jsonObject);
|
||||||
this.layer = LayerName[<string>jsonObject.layer];
|
this.layer = LayerName[<string>jsonObject.layer];
|
||||||
this.isLayerToggleable = jsonObject.toggle;
|
|
||||||
|
// Backward compatibility when "switchLayerMode" was a boolean type as "toggle"
|
||||||
|
if (typeof jsonObject.toggle === 'boolean') {
|
||||||
|
this.switchLayerMode = jsonObject.toggle ? SwitchLayerMode.toggle : SwitchLayerMode.holdAndDoubleTapToggle;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.switchLayerMode = jsonObject.switchLayerMode;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromBinary(buffer: UhkBuffer): SwitchLayerAction {
|
fromBinary(buffer: UhkBuffer): SwitchLayerAction {
|
||||||
this.readAndAssertKeyActionId(buffer);
|
this.readAndAssertKeyActionId(buffer);
|
||||||
this.layer = buffer.readUInt8();
|
this.layer = buffer.readUInt8();
|
||||||
this.isLayerToggleable = buffer.readBoolean();
|
this.switchLayerMode = mapNumberToSwitchLayerMode(buffer.readUInt8());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,18 +89,18 @@ export class SwitchLayerAction extends KeyAction {
|
|||||||
return {
|
return {
|
||||||
keyActionType: keyActionType.SwitchLayerAction,
|
keyActionType: keyActionType.SwitchLayerAction,
|
||||||
layer: LayerName[this.layer],
|
layer: LayerName[this.layer],
|
||||||
toggle: this.isLayerToggleable
|
switchLayerMode: this.switchLayerMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toBinary(buffer: UhkBuffer) {
|
toBinary(buffer: UhkBuffer) {
|
||||||
buffer.writeUInt8(KeyActionId.SwitchLayerAction);
|
buffer.writeUInt8(KeyActionId.SwitchLayerAction);
|
||||||
buffer.writeUInt8(this.layer);
|
buffer.writeUInt8(this.layer);
|
||||||
buffer.writeBoolean(this.isLayerToggleable);
|
buffer.writeUInt8(mapSwitchLayerModeToNumber(this.switchLayerMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `<SwitchLayerAction layer="${this.layer}" toggle="${this.isLayerToggleable}">`;
|
return `<SwitchLayerAction layer="${this.layer}" switchLayerMode="${this.switchLayerMode}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export class Keymap {
|
|||||||
if (currentLayerId - 1 === baseKeyAction.layer) {
|
if (currentLayerId - 1 === baseKeyAction.layer) {
|
||||||
if (currentKeyAction instanceof SwitchLayerAction) {
|
if (currentKeyAction instanceof SwitchLayerAction) {
|
||||||
if (currentKeyAction.layer === baseKeyAction.layer &&
|
if (currentKeyAction.layer === baseKeyAction.layer &&
|
||||||
currentKeyAction.isLayerToggleable === baseKeyAction.isLayerToggleable) {
|
currentKeyAction.switchLayerMode === baseKeyAction.switchLayerMode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -121,7 +121,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -151,7 +151,7 @@ describe('keymap', () => {
|
|||||||
|
|
||||||
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" toggle="false">');
|
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is not switch layer. <KeystrokeAction type="basic" scancode="44"> will be override with <SwitchLayerAction layer="0" switchLayerMode="holdAndDoubleTapToggle">');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should normalize SwitchLayerAction if non base layer action is other SwitchLayerAction', () => {
|
it('should normalize SwitchLayerAction if non base layer action is other SwitchLayerAction', () => {
|
||||||
@@ -262,7 +262,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -274,7 +274,7 @@ describe('keymap', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 'holdAndDoubleTapToggle'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -304,6 +304,6 @@ describe('keymap', () => {
|
|||||||
|
|
||||||
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
expect(inputUserConfig.toJsonObject()).toEqual(expectedJsonConfig);
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" toggle="false"> will be override with <SwitchLayerAction layer="0" toggle="false">');
|
expect(console.warn).toHaveBeenCalledWith('QWERTY.layers[1]modules[0].keyActions[0] is different switch layer. <SwitchLayerAction layer="1" switchLayerMode="holdAndDoubleTapToggle"> will be override with <SwitchLayerAction layer="0" switchLayerMode="holdAndDoubleTapToggle">');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ export interface AppStartInfo {
|
|||||||
deviceConnected: boolean;
|
deviceConnected: boolean;
|
||||||
hasPermission: boolean;
|
hasPermission: boolean;
|
||||||
bootloaderActive: boolean;
|
bootloaderActive: boolean;
|
||||||
|
platform: string;
|
||||||
|
osVersion: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ export interface CommandLineArgs {
|
|||||||
* simulate privilege escalation error
|
* simulate privilege escalation error
|
||||||
*/
|
*/
|
||||||
spe?: boolean;
|
spe?: boolean;
|
||||||
|
/**
|
||||||
|
* show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
|
||||||
|
* if it false the checkbox invisible and the value of the checkbox = true
|
||||||
|
*/
|
||||||
|
layerDoubleTap?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export namespace Constants {
|
export namespace Constants {
|
||||||
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
||||||
export const FIRMWARE_GITHUB_ISSUE_URL = 'https://github.com/UltimateHackingKeyboard/agent/issues/567';
|
export const FIRMWARE_GITHUB_ISSUE_URL = 'https://github.com/UltimateHackingKeyboard/agent/issues/new';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export { IpcEvents } from './ipcEvents';
|
|||||||
export * from './log';
|
export * from './log';
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
export * from './helpers';
|
export * from './helpers';
|
||||||
|
export * from './is-equal-array';
|
||||||
|
|
||||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||||
export function camelCaseToSentence(camelCasedText: string): string {
|
export function camelCaseToSentence(camelCasedText: string): string {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
15
packages/uhk-common/src/util/is-equal-array.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
export const isEqualArray = (arr1: Array<any>, arr2: Array<any>): boolean => {
|
||||||
|
if (arr1.length !== arr2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const a of arr1) {
|
||||||
|
if (!arr2.some(b => isEqual(a, b))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
5
packages/uhk-usb/package-lock.json
generated
@@ -144,11 +144,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
"lodash-es": {
|
|
||||||
"version": "4.17.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz",
|
|
||||||
"integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg=="
|
|
||||||
},
|
|
||||||
"mimic-response": {
|
"mimic-response": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
"@types/node": "8.0.28"
|
"@types/node": "8.0.28"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash-es": "^4.17.10",
|
|
||||||
"node-hid": "0.5.7",
|
"node-hid": "0.5.7",
|
||||||
"uhk-common": "1.0.0"
|
"uhk-common": "1.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class UhkBlhost {
|
|||||||
|
|
||||||
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
||||||
|
|
||||||
if (code !== null && code !== 0) {
|
if (code !== 0) {
|
||||||
return reject(new Error(`blhost error code:${code}`));
|
return reject(new Error(`blhost error code:${code}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { isEqual } from 'lodash';
|
|
||||||
import { Device, devices, HID } from 'node-hid';
|
import { Device, devices, HID } from 'node-hid';
|
||||||
import { CommandLineArgs, DeviceConnectionState, LogService } from 'uhk-common';
|
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService } from 'uhk-common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConfigBufferId,
|
ConfigBufferId,
|
||||||
@@ -11,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';
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export class UhkHidDevice {
|
|||||||
* Internal variable that represent the USB UHK device
|
* Internal variable that represent the USB UHK device
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private _prevDevices = {};
|
private _prevDevices = [];
|
||||||
private _device: HID;
|
private _device: HID;
|
||||||
private _hasPermission = false;
|
private _hasPermission = false;
|
||||||
|
|
||||||
@@ -110,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);
|
||||||
@@ -133,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
|
||||||
*/
|
*/
|
||||||
@@ -158,7 +164,7 @@ export class UhkHidDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public resetDeviceCache(): void {
|
public resetDeviceCache(): void {
|
||||||
this._prevDevices = {};
|
this._prevDevices = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
|
async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
|
||||||
@@ -252,8 +258,11 @@ export class UhkHidDevice {
|
|||||||
private connectToDevice(): HID {
|
private connectToDevice(): HID {
|
||||||
try {
|
try {
|
||||||
const devs = devices();
|
const devs = devices();
|
||||||
if (!isEqual(this._prevDevices, devs)) {
|
if (!isEqualArray(this._prevDevices, devs)) {
|
||||||
this.logService.debug('[UhkHidDevice] Available devices:', devs);
|
this.logService.debug('[UhkHidDevice] Available devices:');
|
||||||
|
for (const logDevice of devs) {
|
||||||
|
this.logService.debug(JSON.stringify(logDevice));
|
||||||
|
}
|
||||||
this._prevDevices = devs;
|
this._prevDevices = devs;
|
||||||
} else {
|
} else {
|
||||||
this.logService.debug('[UhkHidDevice] Available devices unchanged');
|
this.logService.debug('[UhkHidDevice] Available devices unchanged');
|
||||||
@@ -266,7 +275,7 @@ export class UhkHidDevice {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const device = new HID(dev.path);
|
const device = new HID(dev.path);
|
||||||
this.logService.debug('[UhkHidDevice] Used device:', dev);
|
this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev));
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -288,7 +297,7 @@ function kbootCommandName(module: ModuleSlotToI2cAddress): string {
|
|||||||
case ModuleSlotToI2cAddress.rightAddon:
|
case ModuleSlotToI2cAddress.rightAddon:
|
||||||
return 'rightAddon';
|
return 'rightAddon';
|
||||||
|
|
||||||
default :
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
|||||||
try {
|
try {
|
||||||
// logService.debug(`[retry] try to run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
// logService.debug(`[retry] try to run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||||
await command();
|
await command();
|
||||||
await snooze(100);
|
|
||||||
// logService.debug(`[retry] success FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
// logService.debug(`[retry] success FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -93,6 +92,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
|||||||
if (logService) {
|
if (logService) {
|
||||||
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||||
}
|
}
|
||||||
|
await snooze(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||||
"../node_modules/select2/dist/js/select2.full.js",
|
|
||||||
"../node_modules/nouislider/distribute/nouislider.js"
|
"../node_modules/nouislider/distribute/nouislider.js"
|
||||||
],
|
],
|
||||||
"environmentSource": "environments/environment.ts",
|
"environmentSource": "environments/environment.ts",
|
||||||
|
|||||||
69
packages/uhk-web/package-lock.json
generated
@@ -1154,6 +1154,14 @@
|
|||||||
"tslib": "1.9.0"
|
"tslib": "1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ert78gb/ngx-select-ex": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ert78gb/ngx-select-ex/-/ngx-select-ex-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-m3DyGB1VZrxsItgc/NjBt5ZfW1DuQrxLz82ekw/ur79DZHG89EYohKWbx68lonfu8wM+AT4IHUDVqF1gFhyK0g==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@ngrx/effects": {
|
"@ngrx/effects": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-4.0.5.tgz",
|
||||||
@@ -1260,19 +1268,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.2.9.tgz",
|
||||||
"integrity": "sha512-AmYGadmTv+Xh6re2CH5ruyvV3znvtJbhxyT00JQAGFP2U+xgqhf+C2xfjdP/GgK5d9YmSif/UYs2ssMl4gW6fw=="
|
"integrity": "sha512-AmYGadmTv+Xh6re2CH5ruyvV3znvtJbhxyT00JQAGFP2U+xgqhf+C2xfjdP/GgK5d9YmSif/UYs2ssMl4gW6fw=="
|
||||||
},
|
},
|
||||||
"@types/lodash": {
|
|
||||||
"version": "4.14.106",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.106.tgz",
|
|
||||||
"integrity": "sha512-tOSvCVrvSqFZ4A/qrqqm6p37GZoawsZtoR0SJhlF7EonNZUgrn8FfT+RNQ11h+NUpMt6QVe36033f3qEKBwfWA=="
|
|
||||||
},
|
|
||||||
"@types/lodash-es": {
|
|
||||||
"version": "4.17.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.0.tgz",
|
|
||||||
"integrity": "sha512-h8lkWQSgT4qjs9PcIhcL2nWubZeXRVzjZxYlRFmcX9BW1PIk5qRc0djtRWZqtM+GDDFhwBt0ztRu72D/YxIcEw==",
|
|
||||||
"requires": {
|
|
||||||
"@types/lodash": "4.14.106"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "9.6.2",
|
"version": "9.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.2.tgz",
|
||||||
@@ -1283,14 +1278,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
|
"resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
|
||||||
"integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU="
|
"integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU="
|
||||||
},
|
},
|
||||||
"@types/select2": {
|
|
||||||
"version": "4.0.44",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/select2/-/select2-4.0.44.tgz",
|
|
||||||
"integrity": "sha512-aunlkCCVG3uQZns+uAvxmYlWwvv8DuVLS+rKN9Az4ENylcIvwNHDfg7oJPeGlSYSZ9vacHQ91HoRGWnhZo7jHQ==",
|
|
||||||
"requires": {
|
|
||||||
"@types/jquery": "3.2.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/selenium-webdriver": {
|
"@types/selenium-webdriver": {
|
||||||
"version": "2.53.43",
|
"version": "2.53.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz",
|
"resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz",
|
||||||
@@ -1390,11 +1377,6 @@
|
|||||||
"repeat-string": "1.6.1"
|
"repeat-string": "1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"almond": {
|
|
||||||
"version": "0.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz",
|
|
||||||
"integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA="
|
|
||||||
},
|
|
||||||
"amdefine": {
|
"amdefine": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||||
@@ -5771,11 +5753,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
|
||||||
"integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c="
|
"integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c="
|
||||||
},
|
},
|
||||||
"jquery-mousewheel": {
|
|
||||||
"version": "3.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz",
|
|
||||||
"integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU="
|
|
||||||
},
|
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.4.3",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz",
|
||||||
@@ -6075,11 +6052,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
|
||||||
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
|
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
|
||||||
},
|
},
|
||||||
"lodash-es": {
|
|
||||||
"version": "4.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz",
|
|
||||||
"integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc="
|
|
||||||
},
|
|
||||||
"lodash.assign": {
|
"lodash.assign": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||||
@@ -6527,22 +6499,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.7.7.tgz",
|
||||||
"integrity": "sha1-uEH0sxPIycinY8gPOlnVqkw6cMg="
|
"integrity": "sha1-uEH0sxPIycinY8gPOlnVqkw6cMg="
|
||||||
},
|
},
|
||||||
"ng2-select2": {
|
|
||||||
"version": "1.0.0-beta.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/ng2-select2/-/ng2-select2-1.0.0-beta.10.tgz",
|
|
||||||
"integrity": "sha1-kIsLip+M0Gc287yhax41ofaWoUU=",
|
|
||||||
"requires": {
|
|
||||||
"@types/jquery": "2.0.49",
|
|
||||||
"@types/select2": "4.0.44"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/jquery": {
|
|
||||||
"version": "2.0.49",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-2.0.49.tgz",
|
|
||||||
"integrity": "sha512-/9xLnYmohN/vD2gDnLS4cym8TUmrJu7DvZa/LELKzZjdPsvWVJiedsdu2SXNtb/DA7FGimqL2g0IoyhbNKLl8g=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ngrx-store-freeze": {
|
"ngrx-store-freeze": {
|
||||||
"version": "0.1.9",
|
"version": "0.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.1.9.tgz",
|
||||||
@@ -8108,15 +8064,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
|
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
|
||||||
},
|
},
|
||||||
"select2": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/select2/-/select2-4.0.3.tgz",
|
|
||||||
"integrity": "sha1-IHcz/pHqy5yxoT8SRjQB9HJEng8=",
|
|
||||||
"requires": {
|
|
||||||
"almond": "0.3.3",
|
|
||||||
"jquery-mousewheel": "3.1.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"selenium-webdriver": {
|
"selenium-webdriver": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz",
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
"@types/jasmine": "2.5.53",
|
"@types/jasmine": "2.5.53",
|
||||||
"@types/jasminewd2": "2.0.2",
|
"@types/jasminewd2": "2.0.2",
|
||||||
"@types/jquery": "3.2.9",
|
"@types/jquery": "3.2.9",
|
||||||
"@types/lodash-es": "4.17.0",
|
|
||||||
"@types/usb": "1.1.3",
|
"@types/usb": "1.1.3",
|
||||||
"angular-confirmation-popover": "3.2.0",
|
"angular-confirmation-popover": "3.2.0",
|
||||||
"angular-notifier": "2.0.0",
|
"angular-notifier": "2.0.0",
|
||||||
@@ -60,18 +59,16 @@
|
|||||||
"karma-coverage-istanbul-reporter": "1.2.1",
|
"karma-coverage-istanbul-reporter": "1.2.1",
|
||||||
"karma-jasmine": "1.1.0",
|
"karma-jasmine": "1.1.0",
|
||||||
"karma-jasmine-html-reporter": "0.2.2",
|
"karma-jasmine-html-reporter": "0.2.2",
|
||||||
"lodash-es": "4.17.4",
|
|
||||||
"ng2-dragula": "1.5.0",
|
"ng2-dragula": "1.5.0",
|
||||||
"ng2-nouislider": "^1.7.7",
|
"ng2-nouislider": "^1.7.7",
|
||||||
"ng2-select2": "1.0.0-beta.10",
|
|
||||||
"ngx-clipboard": "10.0.0",
|
"ngx-clipboard": "10.0.0",
|
||||||
|
"@ert78gb/ngx-select-ex": "3.7.0",
|
||||||
"ngrx-store-freeze": "0.1.9",
|
"ngrx-store-freeze": "0.1.9",
|
||||||
"nouislider": "^11.1.0",
|
"nouislider": "^11.1.0",
|
||||||
"postcss-url": "^7.1.2",
|
"postcss-url": "^7.1.2",
|
||||||
"protractor": "5.1.2",
|
"protractor": "5.1.2",
|
||||||
"reselect": "3.0.1",
|
"reselect": "3.0.1",
|
||||||
"rxjs": "5.5.8",
|
"rxjs": "5.5.8",
|
||||||
"select2": "4.0.3",
|
|
||||||
"typescript": "2.6.2",
|
"typescript": "2.6.2",
|
||||||
"uhk-common": "1.0.0",
|
"uhk-common": "1.0.0",
|
||||||
"xml-loader": "1.2.1",
|
"xml-loader": "1.2.1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<notifier-container></notifier-container>
|
<notifier-container></notifier-container>
|
||||||
<progress-button class="save-to-keyboard-button"
|
<progress-button class="save-to-keyboard-button"
|
||||||
*ngIf="(saveToKeyboardState$ | async).showButton"
|
*ngIf="saveToKeyboardState.showButton"
|
||||||
[@showSaveToKeyboardButton]
|
[@showSaveToKeyboardButton]
|
||||||
[state]="saveToKeyboardState$ | async"
|
[state]="saveToKeyboardState"
|
||||||
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
(clicked)="clickedOnProgressButton($event)"></progress-button>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
import { Component, HostListener, ViewEncapsulation, OnDestroy } from '@angular/core';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Action, Store } from '@ngrx/store';
|
import { Action, Store } from '@ngrx/store';
|
||||||
|
|
||||||
import 'rxjs/add/operator/last';
|
import 'rxjs/add/operator/last';
|
||||||
|
|
||||||
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
||||||
|
import { EnableUsbStackTestAction } from './store/actions/device';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
getShowAppUpdateAvailable,
|
getShowAppUpdateAvailable,
|
||||||
@@ -34,17 +36,45 @@ import { ProgressButtonState } from './store/reducers/progress-button-state';
|
|||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class MainAppComponent {
|
export class MainAppComponent implements OnDestroy {
|
||||||
showUpdateAvailable$: Observable<boolean>;
|
showUpdateAvailable$: Observable<boolean>;
|
||||||
deviceConfigurationLoaded$: Observable<boolean>;
|
deviceConfigurationLoaded$: Observable<boolean>;
|
||||||
runningInElectron$: Observable<boolean>;
|
runningInElectron$: Observable<boolean>;
|
||||||
saveToKeyboardState$: Observable<ProgressButtonState>;
|
saveToKeyboardState: ProgressButtonState;
|
||||||
|
|
||||||
|
private saveToKeyboardStateSubscription: Subscription;
|
||||||
|
|
||||||
constructor(private store: Store<AppState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
||||||
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
this.deviceConfigurationLoaded$ = store.select(deviceConfigurationLoaded);
|
||||||
this.runningInElectron$ = store.select(runningInElectron);
|
this.runningInElectron$ = store.select(runningInElectron);
|
||||||
this.saveToKeyboardState$ = store.select(saveToKeyboardState);
|
this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState)
|
||||||
|
.subscribe(data => this.saveToKeyboardState = data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.saveToKeyboardStateSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.saveToKeyboardState.showButton &&
|
||||||
|
event.ctrlKey &&
|
||||||
|
event.key === 's' &&
|
||||||
|
!event.defaultPrevented) {
|
||||||
|
this.clickedOnProgressButton(this.saveToKeyboardState.action);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey &&
|
||||||
|
event.ctrlKey &&
|
||||||
|
event.altKey &&
|
||||||
|
event.metaKey &&
|
||||||
|
event.key === '|' &&
|
||||||
|
!event.defaultPrevented) {
|
||||||
|
this.enableUsbStackTest();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateApp() {
|
updateApp() {
|
||||||
@@ -58,4 +88,8 @@ export class MainAppComponent {
|
|||||||
clickedOnProgressButton(action: Action) {
|
clickedOnProgressButton(action: Action) {
|
||||||
return this.store.dispatch(action);
|
return this.store.dispatch(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableUsbStackTest() {
|
||||||
|
this.store.dispatch(new EnableUsbStackTestAction());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<span>About</span>
|
<span>About</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="agent-version">Agent version: <span class="text-bold">{{version}}</span></div>
|
<div class="agent-version">Agent version: <span class="text-bold">{{ version }}</span></div>
|
||||||
<div><a class="link-github" (click)="openAgentGitHubPage($event)">Agent on GitHub</a></div>
|
<div><a class="link-github" [href]="agentGithubUrl" externalUrl>Agent on GitHub</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { Constants } from 'uhk-common';
|
import { Constants } from 'uhk-common';
|
||||||
|
|
||||||
import { AppState } from '../../../store';
|
|
||||||
import { getVersions } from '../../../util';
|
import { getVersions } from '../../../util';
|
||||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'about-page',
|
selector: 'about-page',
|
||||||
@@ -16,12 +13,5 @@ import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
|||||||
})
|
})
|
||||||
export class AboutComponent {
|
export class AboutComponent {
|
||||||
version: string = getVersions().version;
|
version: string = getVersions().version;
|
||||||
|
agentGithubUrl = Constants.AGENT_GITHUB_URL;
|
||||||
constructor(private store: Store<AppState>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
openAgentGitHubPage(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { AboutComponent } from './about/about.component';
|
import { AboutComponent } from './about/about.component';
|
||||||
|
import { HelpPageComponent } from './help-page/help-page.component';
|
||||||
|
|
||||||
export const agentRoutes: Routes = [
|
export const agentRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent
|
component: SettingsComponent
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'help',
|
||||||
|
component: HelpPageComponent
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
component: AboutComponent
|
component: AboutComponent
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="row">
|
||||||
|
<h1 class="col-xs-12 pane-title">
|
||||||
|
<i class="fa fa-question-circle"></i>
|
||||||
|
<span class="macro__name pane-title__name">Help</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
Frequently asked questions
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://ultimatehackingkeyboard.com/blog/2018/06/23/how-can-i-type-accented-characters-with-my-uhk" externalUrl>How can I type accented characters with my UHK?</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
Keyboard shortcuts
|
||||||
|
<ul>
|
||||||
|
<li><kbd>CTRL</kbd> + <kbd>Enter</kbd> = Remap key</li>
|
||||||
|
<li><kbd>CTRL</kbd> + <kbd>S</kbd> = Save to keyboard</li>
|
||||||
|
<li>Right click on a key = Capture key</li>
|
||||||
|
<li>Hold Shift while clicking on a key = Remap on all keymaps</li>
|
||||||
|
<li>Hold Alt while clicking on a key = Remap on all layers</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'help-page',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
templateUrl: './help-page.component.html',
|
||||||
|
styleUrls: ['./help-page.component.scss'],
|
||||||
|
host: {
|
||||||
|
'class': 'container-fluid'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class HelpPageComponent {
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ul class="list-unstyled btn-list">
|
<ul class="list-unstyled btn-list">
|
||||||
<li>
|
<li>
|
||||||
<button class="btn btn-default"
|
<button class="btn btn-primary"
|
||||||
(click)="exportUserConfiguration($event)">Export device configuration
|
(click)="exportUserConfiguration($event)">Export device configuration
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -12,19 +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>
|
||||||
|
|
||||||
If the update process fails, consider the following points:
|
<p *ngIf="runningOnNotSupportedWindows$ | async">Firmware update doesn't work on Windows 7, Windows Vista,
|
||||||
<ol>
|
and Windows XP. Use Windows 10, Windows 8, Linux, or OSX instead.</p>
|
||||||
<li>Windows 7, Windows Vista, and Windows XP are not supported. Use Linux, OSX, Windows 10, or Windows 8.</li>
|
|
||||||
<li>Connect your UHK directly to the host computer. Don't use USB hubs or KVM switches.</li>
|
|
||||||
<li>Run Agent directly on the host operating system. Don't use VirtualBox or VMware Workstation.</li>
|
|
||||||
<li>Give a try to every USB port of your computer.</li>
|
|
||||||
<li>Remove every other USB device from your computer.</li>
|
|
||||||
<li>If the left half becomes unresponsive after a failed update then retry and follow the instructions displayed during the update to fix it.</li>
|
|
||||||
<li>If the above fails, retry a couple of times.</li>
|
|
||||||
<li>If everything else fails, please add a new comment to <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">the GitHub issue</a>, and attach the update log.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>
|
<p *ngIf="firmwareUpgradeAllowed$ | async">
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||||
(click)="onUpdateFirmware()">
|
(click)="onUpdateFirmware()">
|
||||||
@@ -35,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">
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
[description]="description"
|
[description]="description"
|
||||||
[showDescription]="true"
|
[showDescription]="true"
|
||||||
|
oncontextmenu="return false;"
|
||||||
(keyClick)="keyClick.emit($event)"
|
(keyClick)="keyClick.emit($event)"
|
||||||
(keyHover)="keyHover.emit($event)"
|
(keyHover)="keyHover.emit($event)"
|
||||||
(capture)="capture.emit($event)"
|
(capture)="capture.emit($event)"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 809 B After Width: | Height: | Size: 853 B |
@@ -3,6 +3,11 @@ import { animate, keyframes, state, style, transition, trigger } from '@angular/
|
|||||||
import { Layer } from 'uhk-common';
|
import { Layer } from 'uhk-common';
|
||||||
|
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import {
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyHoverEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
type AnimationKeyboard =
|
type AnimationKeyboard =
|
||||||
'init' |
|
'init' |
|
||||||
@@ -82,9 +87,9 @@ export class KeyboardSliderComponent implements OnChanges {
|
|||||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||||
@Output() descriptionChanged = new EventEmitter<string>();
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
layerAnimationState: AnimationKeyboard[];
|
layerAnimationState: AnimationKeyboard[];
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||||
[halvesSplit]="keyboardSplit"
|
[halvesSplit]="keyboardSplit"
|
||||||
[keyboardLayout]="keyboardLayout$ | async"
|
[keyboardLayout]="keyboardLayout$ | async"
|
||||||
|
[allowLayerDoubleTap]="allowLayerDoubleTap$ | async"
|
||||||
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, HostListener, ViewChild } from '@angular/core';
|
import { Component, HostListener } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Keymap } from 'uhk-common';
|
import { Keymap } from 'uhk-common';
|
||||||
@@ -14,9 +14,8 @@ import 'rxjs/add/operator/combineLatest';
|
|||||||
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
import { AppState, getKeyboardLayout } from '../../../store';
|
import { allowLayerDoubleTap, AppState, getKeyboardLayout } from '../../../store';
|
||||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
import { KeymapActions } from '../../../store/actions';
|
import { KeymapActions } from '../../../store/actions';
|
||||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||||
@@ -31,13 +30,12 @@ import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription
|
|||||||
})
|
})
|
||||||
export class KeymapEditComponent {
|
export class KeymapEditComponent {
|
||||||
|
|
||||||
@ViewChild(SvgKeyboardWrapComponent) wrap: SvgKeyboardWrapComponent;
|
|
||||||
|
|
||||||
keyboardSplit: boolean;
|
keyboardSplit: boolean;
|
||||||
|
|
||||||
deletable$: Observable<boolean>;
|
deletable$: Observable<boolean>;
|
||||||
keymap$: Observable<Keymap>;
|
keymap$: Observable<Keymap>;
|
||||||
keyboardLayout$: Observable<KeyboardLayout>;
|
keyboardLayout$: Observable<KeyboardLayout>;
|
||||||
|
allowLayerDoubleTap$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
route: ActivatedRoute) {
|
route: ActivatedRoute) {
|
||||||
@@ -52,6 +50,7 @@ export class KeymapEditComponent {
|
|||||||
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
||||||
|
|
||||||
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
this.keyboardLayout$ = store.select(getKeyboardLayout);
|
||||||
|
this.allowLayerDoubleTap$ = store.select(allowLayerDoubleTap);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadKeymap() {
|
downloadKeymap() {
|
||||||
|
|||||||
@@ -37,12 +37,12 @@
|
|||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
(click)="duplicateKeymap()"
|
(click)="duplicateKeymap()"
|
||||||
></i>
|
></i>
|
||||||
<i class="fa fa-download keymap__download pull-right"
|
<!--i class="fa fa-download keymap__download pull-right"
|
||||||
title="Download keymap"
|
title="Download keymap"
|
||||||
[html]="true"
|
[html]="true"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
(click)="onDownloadIconClick()"></i>
|
(click)="onDownloadIconClick()"></i-->
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</uhk-header>
|
</uhk-header>
|
||||||
|
|||||||
@@ -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()"
|
||||||
|
(save)="saveEditedAction($event)">
|
||||||
|
</macro-action-editor>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@import '../../../../styles/variables';
|
@import '../../../../styles/variables';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
&.macro-item:first-of-type {
|
&.macro-item:first-of-type {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
iconName: string;
|
iconName: string;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
newItem: boolean = false;
|
newItem: boolean = false;
|
||||||
|
overflow = 'hidden';
|
||||||
|
|
||||||
constructor(private mapper: MapperService) { }
|
constructor(private mapper: MapperService) { }
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
if (!this.macroAction) {
|
if (!this.macroAction) {
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
this.newItem = true;
|
this.newItem = true;
|
||||||
|
this.overflow = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +67,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
saveEditedAction(editedAction: MacroAction): void {
|
saveEditedAction(editedAction: MacroAction): void {
|
||||||
this.macroAction = editedAction;
|
this.macroAction = editedAction;
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
|
this.overflow = 'hidden';
|
||||||
this.updateView();
|
this.updateView();
|
||||||
this.save.emit(editedAction);
|
this.save.emit(editedAction);
|
||||||
}
|
}
|
||||||
@@ -77,10 +80,12 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
this.editing = true;
|
this.editing = true;
|
||||||
this.edit.emit();
|
this.edit.emit();
|
||||||
|
this.setOverflow('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelEdit(): void {
|
cancelEdit(): void {
|
||||||
this.editing = false;
|
this.editing = false;
|
||||||
|
this.overflow = 'hidden';
|
||||||
this.cancel.emit();
|
this.cancel.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,4 +207,12 @@ export class MacroItemComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
this.title += selectedButtonLabels.join(', ');
|
this.title += selectedButtonLabels.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setOverflow(value: string): void {
|
||||||
|
// tslint:disable: align
|
||||||
|
setTimeout(() => {
|
||||||
|
this.overflow = value;
|
||||||
|
}, 600);
|
||||||
|
// tslint:enable: align
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
|
||||||
[defaultKeyAction]="defaultKeyAction"
|
[defaultKeyAction]="defaultKeyAction"
|
||||||
[currentLayer]="currentLayer"
|
[currentLayer]="currentLayer"
|
||||||
|
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
||||||
(validAction)="keyActionValid=$event"
|
(validAction)="keyActionValid=$event"
|
||||||
></layer-tab>
|
></layer-tab>
|
||||||
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
||||||
@@ -75,8 +76,42 @@
|
|||||||
></none-tab>
|
></none-tab>
|
||||||
</div>
|
</div>
|
||||||
<div class="popover-action">
|
<div class="popover-action">
|
||||||
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel </button>
|
<form class="form-inline d-inline-block popover-action-form">
|
||||||
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button" (click)="onRemapKey()"> Remap Key </button>
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="remapOnAllKeymap"
|
||||||
|
[(ngModel)]="remapOnAllKeymap"> Remap on all keymaps
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="remapOnAllLayer"
|
||||||
|
[(ngModel)]="remapOnAllLayer"> Remap on all layers
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-block">
|
||||||
|
<icon name="question-circle"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
html="true"
|
||||||
|
maxWidth="525"
|
||||||
|
title="<ul class='no-indent text-left'>
|
||||||
|
<li><strong>Default behavior</strong>: Remap the key on the the current layer of the current keymap.</li>
|
||||||
|
<li><strong>Remap on all keymaps</strong>: Remap key on the current layer of all keymaps.</li>
|
||||||
|
<li><strong>Remap on all layers</strong>: Remap key on all layers of the current keymap.</li>
|
||||||
|
<li><strong>Remap on all keymaps + Remap on all layers</strong>: Remap key on all layers of all keymaps.</li>
|
||||||
|
</ul>"
|
||||||
|
data-placement="bottom"></icon>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="d-inline-block pull-right">
|
||||||
|
<button class="btn btn-sm btn-default" type="button" (click)="onCancelClick()"> Cancel</button>
|
||||||
|
<button class="btn btn-sm btn-primary" [class.disabled]="!keyActionValid" type="button"
|
||||||
|
(click)="onRemapKey()"> Remap key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="popover-overlay" [class.display]="visible" (click)="onOverlay()"></div>
|
<div class="popover-overlay" [class.display]="visible" (click)="onOverlay()"></div>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@
|
|||||||
background-color: #f7f7f7;
|
background-color: #f7f7f7;
|
||||||
border-top: 1px solid #ebebeb;
|
border-top: 1px solid #ebebeb;
|
||||||
border-radius: 0 0 5px 5px;
|
border-radius: 0 0 5px 5px;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover-title {
|
.popover-title {
|
||||||
@@ -117,19 +116,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-item {
|
.popover-action-form {
|
||||||
position: relative;
|
margin-top: 4px;
|
||||||
font-size: 1.5rem;
|
|
||||||
|
|
||||||
&.keymap-name--wrapper {
|
label {
|
||||||
padding-left: 50px;
|
margin-right: 5px;
|
||||||
}
|
|
||||||
|
|
||||||
.layout-segment-code {
|
|
||||||
height: 2rem;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ import {
|
|||||||
SwitchLayerAction
|
SwitchLayerAction
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from './tab/tab';
|
import { Tab } from './tab';
|
||||||
|
|
||||||
import { AppState } from '../../store';
|
import { AppState } from '../../store';
|
||||||
import { getKeymaps } from '../../store/reducers/user-configuration';
|
import { getKeymaps } from '../../store/reducers/user-configuration';
|
||||||
|
import { KeyActionRemap } from '../../models/key-action-remap';
|
||||||
|
|
||||||
enum TabName {
|
enum TabName {
|
||||||
Keypress,
|
Keypress,
|
||||||
@@ -59,8 +60,8 @@ enum TabName {
|
|||||||
})),
|
})),
|
||||||
transition('opened => closed', [
|
transition('opened => closed', [
|
||||||
animate('200ms ease-out', keyframes([
|
animate('200ms ease-out', keyframes([
|
||||||
style({ transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0 }),
|
style({transform: 'translateY(0)', visibility: 'visible', opacity: 1, offset: 0}),
|
||||||
style({ transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1 })
|
style({transform: 'translateY(30px)', visibility: 'hidden', opacity: 0, offset: 1})
|
||||||
]))
|
]))
|
||||||
]),
|
]),
|
||||||
transition('closed => opened', [
|
transition('closed => opened', [
|
||||||
@@ -68,8 +69,8 @@ enum TabName {
|
|||||||
visibility: 'visible'
|
visibility: 'visible'
|
||||||
}),
|
}),
|
||||||
animate('200ms ease-out', keyframes([
|
animate('200ms ease-out', keyframes([
|
||||||
style({ transform: 'translateY(30px)', opacity: 0, offset: 0 }),
|
style({transform: 'translateY(30px)', opacity: 0, offset: 0}),
|
||||||
style({ transform: 'translateY(0)', opacity: 1, offset: 1 })
|
style({transform: 'translateY(0)', opacity: 1, offset: 1})
|
||||||
]))
|
]))
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
@@ -82,9 +83,12 @@ export class PopoverComponent implements OnChanges {
|
|||||||
@Input() keyPosition: any;
|
@Input() keyPosition: any;
|
||||||
@Input() wrapPosition: any;
|
@Input() wrapPosition: any;
|
||||||
@Input() visible: boolean;
|
@Input() visible: boolean;
|
||||||
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
|
@Input() remapOnAllKeymap: boolean;
|
||||||
|
@Input() remapOnAllLayer: boolean;
|
||||||
|
|
||||||
@Output() cancel = new EventEmitter<any>();
|
@Output() cancel = new EventEmitter<any>();
|
||||||
@Output() remap = new EventEmitter<KeyAction>();
|
@Output() remap = new EventEmitter<KeyActionRemap>();
|
||||||
|
|
||||||
@ViewChild('tab') selectedTab: Tab;
|
@ViewChild('tab') selectedTab: Tab;
|
||||||
@ViewChild('popover') popoverHost: ElementRef;
|
@ViewChild('popover') popoverHost: ElementRef;
|
||||||
@@ -155,8 +159,11 @@ export class PopoverComponent implements OnChanges {
|
|||||||
onRemapKey(): void {
|
onRemapKey(): void {
|
||||||
if (this.keyActionValid) {
|
if (this.keyActionValid) {
|
||||||
try {
|
try {
|
||||||
const keyAction = this.selectedTab.toKeyAction();
|
this.remap.emit({
|
||||||
this.remap.emit(keyAction);
|
remapOnAllKeymap: this.remapOnAllKeymap,
|
||||||
|
remapOnAllLayer: this.remapOnAllLayer,
|
||||||
|
action: this.selectedTab.toKeyAction()
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: show error dialog
|
// TODO: show error dialog
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -169,6 +176,14 @@ export class PopoverComponent implements OnChanges {
|
|||||||
this.cancel.emit();
|
this.cancel.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown.control.enter', ['$event'])
|
||||||
|
onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (this.visible) {
|
||||||
|
this.onRemapKey();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectTab(tab: TabName): void {
|
selectTab(tab: TabName): void {
|
||||||
this.activeTab = tab;
|
this.activeTab = tab;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,23 @@
|
|||||||
<ng-template [ngIf]="keymapOptions.length > 0">
|
<ng-template [ngIf]="keymapOptions.length > 0">
|
||||||
<div>
|
<div>
|
||||||
<b>Switch to keymap:</b>
|
<b>Switch to keymap:</b>
|
||||||
<select2
|
<ngx-select [items]="keymapOptions"
|
||||||
[data]="keymapOptions"
|
[ngModel]="selectedKeymap?.abbreviation || -1"
|
||||||
[value]="selectedKeymap?.abbreviation || -1"
|
[autoActiveOnMouseEnter]="false"
|
||||||
(valueChanged)="onChange($event)"
|
size="small"
|
||||||
[width]="'100%'"
|
optionValueField="id"
|
||||||
></select2>
|
optionTextField="text"
|
||||||
|
(select)="onChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</ngx-select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
|
<div class="empty" *ngIf="!selectedKeymap?.abbreviation">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select2 {
|
ngx-select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
|
||||||
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keymap-tab',
|
selector: 'keymap-tab',
|
||||||
@@ -14,7 +14,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
@Input() keymaps: Keymap[];
|
@Input() keymaps: Keymap[];
|
||||||
|
|
||||||
keymapOptions: Array<Select2OptionData>;
|
keymapOptions: Array<SelectOptionData>;
|
||||||
selectedKeymap: Keymap;
|
selectedKeymap: Keymap;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -25,7 +25,7 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes.keymaps) {
|
if (changes.keymaps) {
|
||||||
this.keymapOptions = this.keymaps
|
this.keymapOptions = this.keymaps
|
||||||
.map((keymap: Keymap): Select2OptionData => {
|
.map((keymap: Keymap): SelectOptionData => {
|
||||||
return {
|
return {
|
||||||
id: keymap.abbreviation,
|
id: keymap.abbreviation,
|
||||||
text: keymap.name
|
text: keymap.name
|
||||||
@@ -40,12 +40,11 @@ export class KeymapTabComponent extends Tab implements OnChanges {
|
|||||||
this.validAction.emit(true);
|
this.validAction.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change to the correct type when the wrapper has added it.
|
onChange(event: string) {
|
||||||
onChange(event: any) {
|
if (event === '-1') {
|
||||||
if (event.value === '-1') {
|
|
||||||
this.selectedKeymap = undefined;
|
this.selectedKeymap = undefined;
|
||||||
} else {
|
} else {
|
||||||
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event.value);
|
this.selectedKeymap = this.keymaps.find((keymap: Keymap) => keymap.abbreviation === event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
<div class="scancode-options">
|
<div class="scancode-options">
|
||||||
<b class="setting-label">Scancode:</b>
|
<b class="setting-label">Scancode:</b>
|
||||||
<select2
|
<div class="scancode-container">
|
||||||
[data]="scanCodeGroups"
|
<ngx-select [items]="scanCodeGroups"
|
||||||
[value]="selectedScancodeOption.id"
|
[ngModel]="selectedScancodeOption?.id"
|
||||||
(valueChanged)="onScancodeChange($event)"
|
[autoActiveOnMouseEnter]="false"
|
||||||
[width]="200"
|
size="small"
|
||||||
[options]="options"
|
optionValueField="id"
|
||||||
></select2>
|
optionTextField="text"
|
||||||
|
optGroupLabelField="text"
|
||||||
|
optGroupOptionsField="children"
|
||||||
|
(select)="onScancodeChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '0'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ngx-select>
|
||||||
|
</div>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout. For example, on US keyboards next to Tab there is the Q key, but it's й on Russian keyboards, so in this case choose Q instead of й in Agent."
|
html="true"
|
||||||
|
maxWidth="330"
|
||||||
|
title="<p>Looking for a non-US character? Just pick the character of the desired key according to the US layout.</p>
|
||||||
|
<p>Let's say you're a German user and want to map the Ö character. You can see that on US keyboards this is the semicolon key, so choose semicolon in this dropdown.</p>"
|
||||||
data-placement="bottom"></icon>
|
data-placement="bottom"></icon>
|
||||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,15 +56,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
<div class="long-press-container" *ngIf="secondaryRoleEnabled">
|
||||||
<b class="setting-label">Secondary role:</b>
|
<b class="setting-label">Secondary role:</b>
|
||||||
<select2 #secondaryRoleSelect
|
<div class="secondary-role-groups-container">
|
||||||
[data]="secondaryRoleGroups"
|
<ngx-select [items]="secondaryRoleGroups"
|
||||||
[value]="selectedSecondaryRoleIndex.toString()"
|
[ngModel]="selectedSecondaryRoleIndex.toString()"
|
||||||
(valueChanged)="onSecondaryRoleChange($event)"
|
[autoActiveOnMouseEnter]="false"
|
||||||
[width]="140"
|
size="small"
|
||||||
></select2>
|
optionValueField="id"
|
||||||
|
optionTextField="text"
|
||||||
|
optGroupLabelField="text"
|
||||||
|
optGroupOptionsField="children"
|
||||||
|
(select)="onSecondaryRoleChange($event)">
|
||||||
|
|
||||||
|
<ng-template ngx-select-option let-option>
|
||||||
|
<span [ngClass]="{'indent-dropdown-item':option.data.id !== '-1'}">
|
||||||
|
<span>{{ option.text }}</span>
|
||||||
|
<span class="scancode--searchterm">
|
||||||
|
{{ option.data.additional?.explanation}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ngx-select>
|
||||||
|
</div>
|
||||||
<icon name="question-circle"
|
<icon name="question-circle"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="The secondary role activates when another key gets pressed while holding this key."
|
html="true"
|
||||||
|
maxWidth="620"
|
||||||
|
title="<p class='text-left'>The secondary role activates when another key gets pressed while holding this key.</p>
|
||||||
|
<p class='text-left'>Let's say that the scancode is Escape and the secondary role is Mouse. Then:</p>
|
||||||
|
<ul class='text-left'>
|
||||||
|
<li>Tap this key to trigger Escape. <i>(Primary role)</i></li>
|
||||||
|
<li>Hold this key and press another key to activate the relevant key of the Mouse layer. <i>(Secondary role)</i></li>
|
||||||
|
</ul>
|
||||||
|
<p class='text-left pt-3'>The secondary role can be any layer or modifier.</p>"
|
||||||
data-placement="bottom"></icon>
|
data-placement="bottom"></icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -79,4 +79,14 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scancode-container {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-role-groups-container {
|
||||||
|
display: inline-block;
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||||
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
|
|
||||||
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
import { MapperService } from '../../../../services/mapper.service';
|
import { MapperService } from '../../../../services/mapper.service';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keypress-tab',
|
selector: 'keypress-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './keypress-tab.component.html',
|
templateUrl: './keypress-tab.component.html',
|
||||||
styleUrls: ['./keypress-tab.component.scss']
|
styleUrls: ['./keypress-tab.component.scss']
|
||||||
})
|
})
|
||||||
@@ -20,11 +21,10 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
leftModifierSelects: boolean[];
|
leftModifierSelects: boolean[];
|
||||||
rightModifierSelects: boolean[];
|
rightModifierSelects: boolean[];
|
||||||
|
|
||||||
scanCodeGroups: Array<Select2OptionData>;
|
scanCodeGroups: Array<SelectOptionData>;
|
||||||
secondaryRoleGroups: Array<Select2OptionData>;
|
secondaryRoleGroups: Array<SelectOptionData>;
|
||||||
options: Select2Options;
|
|
||||||
|
|
||||||
selectedScancodeOption: Select2OptionData;
|
selectedScancodeOption: SelectOptionData;
|
||||||
selectedSecondaryRoleIndex: number;
|
selectedSecondaryRoleIndex: number;
|
||||||
|
|
||||||
constructor(private mapper: MapperService) {
|
constructor(private mapper: MapperService) {
|
||||||
@@ -41,18 +41,6 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
||||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||||
this.selectedSecondaryRoleIndex = -1;
|
this.selectedSecondaryRoleIndex = -1;
|
||||||
this.options = {
|
|
||||||
templateResult: this.scanCodeTemplateResult,
|
|
||||||
matcher: (term: string, text: string, data: Select2OptionData) => {
|
|
||||||
let found = text.toUpperCase().indexOf(term.toUpperCase()) > -1;
|
|
||||||
|
|
||||||
if (!found && data.additional && data.additional.explanation) {
|
|
||||||
found = data.additional.explanation.toUpperCase().indexOf(term.toUpperCase()) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
@@ -134,25 +122,6 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scanCodeTemplateResult: Select2TemplateFunction = (state: Select2OptionData): JQuery | string => {
|
|
||||||
if (!state.id) {
|
|
||||||
return state.text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.additional && state.additional.explanation) {
|
|
||||||
return jQuery(
|
|
||||||
'<span class="select2-item">'
|
|
||||||
+ '<span>' + state.text + '</span>'
|
|
||||||
+ '<span class="scancode--searchterm"> '
|
|
||||||
+ state.additional.explanation
|
|
||||||
+ '</span>' +
|
|
||||||
'</span>'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return jQuery('<span class="select2-item">' + state.text + '</span>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleModifier(right: boolean, index: number) {
|
toggleModifier(right: boolean, index: number) {
|
||||||
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
|
const modifierSelects: boolean[] = right ? this.rightModifierSelects : this.leftModifierSelects;
|
||||||
modifierSelects[index] = !modifierSelects[index];
|
modifierSelects[index] = !modifierSelects[index];
|
||||||
@@ -160,24 +129,20 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
this.validAction.emit(this.keyActionValid());
|
this.validAction.emit(this.keyActionValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
onSecondaryRoleChange(event: { value: string }) {
|
onSecondaryRoleChange(id: string) {
|
||||||
this.selectedSecondaryRoleIndex = +event.value;
|
this.selectedSecondaryRoleIndex = +id;
|
||||||
}
|
}
|
||||||
|
|
||||||
onScancodeChange(event: { value: string }) {
|
onScancodeChange(id: string) {
|
||||||
const id: string = event.value;
|
|
||||||
|
|
||||||
// ng2-select2 should provide the selectedOption in an upcoming release
|
|
||||||
// TODO: change this when it has become available
|
|
||||||
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
this.selectedScancodeOption = this.findScancodeOptionById(id);
|
||||||
|
|
||||||
this.validAction.emit(this.keyActionValid());
|
this.validAction.emit(this.keyActionValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionBy(predicate: (option: Select2OptionData) => boolean): Select2OptionData {
|
private findScancodeOptionBy(predicate: (option: SelectOptionData) => boolean): SelectOptionData {
|
||||||
let selectedOption: Select2OptionData;
|
let selectedOption: SelectOptionData;
|
||||||
|
|
||||||
const scanCodeGroups: Select2OptionData[] = [...this.scanCodeGroups];
|
const scanCodeGroups: SelectOptionData[] = [...this.scanCodeGroups];
|
||||||
while (scanCodeGroups.length > 0) {
|
while (scanCodeGroups.length > 0) {
|
||||||
const scanCodeGroup = scanCodeGroups.shift();
|
const scanCodeGroup = scanCodeGroups.shift();
|
||||||
if (predicate(scanCodeGroup)) {
|
if (predicate(scanCodeGroup)) {
|
||||||
@@ -192,14 +157,14 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
return selectedOption;
|
return selectedOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionById(id: string): Select2OptionData {
|
private findScancodeOptionById(id: string): SelectOptionData {
|
||||||
return this.findScancodeOptionBy(option => option.id === id);
|
return this.findScancodeOptionBy(option => option.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): Select2OptionData {
|
private findScancodeOptionByScancode(scancode: number, type: KeystrokeType): SelectOptionData {
|
||||||
const typeToFind: string =
|
const typeToFind: string =
|
||||||
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
(type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) ? 'media' : KeystrokeType[type];
|
||||||
return this.findScancodeOptionBy((option: Select2OptionData) => {
|
return this.findScancodeOptionBy((option: SelectOptionData) => {
|
||||||
const additional = option.additional;
|
const additional = option.additional;
|
||||||
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
if (additional && additional.scancode === scancode && additional.type === typeToFind) {
|
||||||
return true;
|
return true;
|
||||||
@@ -211,7 +176,11 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private toScancodeTypePair(option: Select2OptionData): [number, string] {
|
private toScancodeTypePair(option: SelectOptionData): [number, string] {
|
||||||
|
if (!option) {
|
||||||
|
return [0, 'basic'];
|
||||||
|
}
|
||||||
|
|
||||||
let scanCode: number;
|
let scanCode: number;
|
||||||
let type: string;
|
let type: string;
|
||||||
if (option.additional) {
|
if (option.additional) {
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
<ng-template [ngIf]="!isNotBase">
|
<ng-template [ngIf]="!isNotBase">
|
||||||
<select (change)="toggleChanged($event.target.value)">
|
<div>
|
||||||
<option *ngFor="let item of toggleData" [value]="item.id" [selected]="toggle === item.id">
|
<div>
|
||||||
{{ item.text }}
|
<select (change)="toggleChanged($event.target.value)">
|
||||||
</option>
|
<option *ngFor="let item of toggleData" [value]="item.id" [selected]="toggle === item.id">
|
||||||
</select>
|
{{ item.text }}
|
||||||
<span>the</span>
|
</option>
|
||||||
<select (change)="layerChanged($event.target.value)">
|
</select>
|
||||||
<option *ngFor="let item of layerData" [value]="item.id" [selected]="layer === item.id">
|
<span>the</span>
|
||||||
{{ item.text }}
|
<select (change)="layerChanged($event.target.value)">
|
||||||
</option>
|
<option *ngFor="let item of layerData" [value]="item.id" [selected]="layer === item.id">
|
||||||
</select>
|
{{ item.text }}
|
||||||
<span [ngSwitch]="toggle">
|
</option>
|
||||||
<ng-template [ngSwitchCase]="true">layer by tapping this key.</ng-template>
|
</select>
|
||||||
<ng-template ngSwitchDefault>layer by holding this key.</ng-template>
|
<span [ngSwitch]="toggle">
|
||||||
</span>
|
<ng-template [ngSwitchCase]="'toggle'">layer by tapping this key.</ng-template>
|
||||||
|
<ng-template ngSwitchDefault>layer by holding this key.</ng-template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="toggle === 'active' && allowLayerDoubleTap">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
[(ngModel)]="lockLayerWhenDoubleTapping"> Lock layer when double tapping this key.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="isNotBase">
|
<ng-template [ngIf]="isNotBase">
|
||||||
<span> Layer switching is only possible from the base layer. </span>
|
<span> Layer switching is only possible from the base layer. </span>
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { KeyAction, LayerName, SwitchLayerAction } from 'uhk-common';
|
import { KeyAction, LayerName, SwitchLayerAction, SwitchLayerMode } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
|
export type toggleType = 'active' | 'toggle';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'layer-tab',
|
selector: 'layer-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './layer-tab.component.html',
|
templateUrl: './layer-tab.component.html',
|
||||||
styleUrls: ['./layer-tab.component.scss']
|
styleUrls: ['./layer-tab.component.scss']
|
||||||
})
|
})
|
||||||
export class LayerTabComponent extends Tab implements OnChanges {
|
export class LayerTabComponent extends Tab implements OnChanges {
|
||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
@Input() currentLayer: number;
|
@Input() currentLayer: number;
|
||||||
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
|
|
||||||
@HostBinding('class.no-base') isNotBase: boolean;
|
@HostBinding('class.no-base') isNotBase: boolean;
|
||||||
|
|
||||||
toggleData: { id: boolean, text: string }[] = [
|
toggleData: { id: toggleType, text: string }[] = [
|
||||||
{
|
{
|
||||||
id: false,
|
id: 'active',
|
||||||
text: 'Activate'
|
text: 'Activate'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: true,
|
id: 'toggle',
|
||||||
text: 'Toggle'
|
text: 'Toggle'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -40,12 +44,13 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
toggle: boolean;
|
toggle: toggleType;
|
||||||
layer: LayerName;
|
layer: LayerName;
|
||||||
|
lockLayerWhenDoubleTapping: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.toggle = false;
|
this.toggle = 'active';
|
||||||
this.layer = LayerName.mod;
|
this.layer = LayerName.mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,14 +76,39 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
|
const switchLayerAction: SwitchLayerAction = <SwitchLayerAction>keyAction;
|
||||||
this.toggle = switchLayerAction.isLayerToggleable;
|
switch (switchLayerAction.switchLayerMode) {
|
||||||
|
case SwitchLayerMode.holdAndDoubleTapToggle: {
|
||||||
|
this.toggle = 'active';
|
||||||
|
this.lockLayerWhenDoubleTapping = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SwitchLayerMode.hold: {
|
||||||
|
this.toggle = 'active';
|
||||||
|
this.lockLayerWhenDoubleTapping = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
this.toggle = 'toggle';
|
||||||
|
this.lockLayerWhenDoubleTapping = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.layer = switchLayerAction.layer;
|
this.layer = switchLayerAction.layer;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
toKeyAction(): SwitchLayerAction {
|
toKeyAction(): SwitchLayerAction {
|
||||||
const keyAction = new SwitchLayerAction();
|
const keyAction = new SwitchLayerAction();
|
||||||
keyAction.isLayerToggleable = this.toggle;
|
if (this.toggle === 'toggle') {
|
||||||
|
keyAction.switchLayerMode = SwitchLayerMode.toggle;
|
||||||
|
} else if (!this.allowLayerDoubleTap || this.lockLayerWhenDoubleTapping) {
|
||||||
|
keyAction.switchLayerMode = SwitchLayerMode.holdAndDoubleTapToggle;
|
||||||
|
} else {
|
||||||
|
keyAction.switchLayerMode = SwitchLayerMode.hold;
|
||||||
|
}
|
||||||
|
|
||||||
keyAction.layer = this.layer;
|
keyAction.layer = this.layer;
|
||||||
if (!this.keyActionValid()) {
|
if (!this.keyActionValid()) {
|
||||||
throw new Error('KeyAction is invalid!');
|
throw new Error('KeyAction is invalid!');
|
||||||
@@ -86,8 +116,8 @@ export class LayerTabComponent extends Tab implements OnChanges {
|
|||||||
return keyAction;
|
return keyAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleChanged(value: string) {
|
toggleChanged(value: toggleType) {
|
||||||
this.toggle = value === 'true';
|
this.toggle = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
layerChanged(value: number) {
|
layerChanged(value: number) {
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
select2 {
|
ngx-select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Select2OptionData } from 'ng2-select2/ng2-select2';
|
|
||||||
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
import { AppState } from '../../../../store/index';
|
import { AppState } from '../../../../store';
|
||||||
import { getMacros } from '../../../../store/reducers/user-configuration';
|
import { getMacros } from '../../../../store/reducers/user-configuration';
|
||||||
|
import { SelectOptionData } from '../../../../models/select-option-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'macro-tab',
|
selector: 'macro-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './macro-tab.component.html',
|
templateUrl: './macro-tab.component.html',
|
||||||
styleUrls: ['./macro-tab.component.scss']
|
styleUrls: ['./macro-tab.component.scss']
|
||||||
})
|
})
|
||||||
@@ -18,7 +19,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
@Input() defaultKeyAction: KeyAction;
|
@Input() defaultKeyAction: KeyAction;
|
||||||
|
|
||||||
macros: Macro[];
|
macros: Macro[];
|
||||||
macroOptions: Array<Select2OptionData>;
|
macroOptions: Array<SelectOptionData>;
|
||||||
selectedMacroIndex: number;
|
selectedMacroIndex: number;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.macroOptions = this.macros.map(function (macro: Macro, index: number): Select2OptionData {
|
this.macroOptions = this.macros.map(function (macro: Macro, index: number): SelectOptionData {
|
||||||
return {
|
return {
|
||||||
id: index.toString(),
|
id: index.toString(),
|
||||||
text: macro.name
|
text: macro.name
|
||||||
@@ -44,9 +45,8 @@ export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestr
|
|||||||
this.validAction.emit(true);
|
this.validAction.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change to the correct type when the wrapper has added it.
|
onChange(id: string) {
|
||||||
onChange(event: any) {
|
this.selectedMacroIndex = +id;
|
||||||
this.selectedMacroIndex = +event.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyActionValid(): boolean {
|
keyActionValid(): boolean {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, Input, OnChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||||
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
|
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mouse-tab',
|
selector: 'mouse-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './mouse-tab.component.html',
|
templateUrl: './mouse-tab.component.html',
|
||||||
styleUrls: ['./mouse-tab.component.scss']
|
styleUrls: ['./mouse-tab.component.scss']
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { Tab } from '../tab';
|
import { Tab } from '../tab';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'none-tab',
|
selector: 'none-tab',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: './none-tab.component.html',
|
templateUrl: './none-tab.component.html',
|
||||||
styleUrls: ['./none-tab.component.scss']
|
styleUrls: ['./none-tab.component.scss']
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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']">
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
<svg:g svg-module *ngFor="let module of modules; let i = index"
|
[attr.viewBox]="viewBox"
|
||||||
|
height="100%"
|
||||||
|
width="100%">
|
||||||
|
<svg:g svg-module
|
||||||
|
*ngFor="let module of modules; let i = index"
|
||||||
[coverages]="module.coverages"
|
[coverages]="module.coverages"
|
||||||
[keyboardKeys]="module.keyboardKeys"
|
[keyboardKeys]="module.keyboardKeys"
|
||||||
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
||||||
@@ -9,10 +13,13 @@
|
|||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[@split]="moduleAnimationStates[i]"
|
[@split]="moduleAnimationStates[i]"
|
||||||
[selected]="selectedKey?.moduleId === i"
|
[selected]="selectedKey?.moduleId === i"
|
||||||
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
(keyClick)="onKeyClick(i, $event)"
|
||||||
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
||||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
(capture)="onCapture(i, $event)" />
|
||||||
/>
|
|
||||||
|
<svg:path [@fadeSeparator]="separatorAnimation"
|
||||||
|
[attr.d]="separator.d"
|
||||||
|
[attr.style]="separatorStyle" />
|
||||||
</svg>
|
</svg>
|
||||||
<editable-text *ngIf="showDescription"
|
<editable-text *ngIf="showDescription"
|
||||||
[ngModel]="description"
|
[ngModel]="description"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,10 +1,18 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { animate, state, trigger, style, transition } from '@angular/animations';
|
import { animate, state, trigger, style, transition } from '@angular/animations';
|
||||||
|
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||||
import { Module } from 'uhk-common';
|
import { Module } from 'uhk-common';
|
||||||
|
|
||||||
import { SvgModule } from '../module';
|
import { SvgModule } from '../module';
|
||||||
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import { SvgSeparator } from '../separator';
|
||||||
|
import {
|
||||||
|
SvgKeyHoverEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgModuleKeyClickEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'svg-keyboard',
|
selector: 'svg-keyboard',
|
||||||
@@ -20,6 +28,16 @@ import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
|||||||
transform: 'translate(3%, 15%) rotate(-4deg) scale(0.92, 0.92)'
|
transform: 'translate(3%, 15%) rotate(-4deg) scale(0.92, 0.92)'
|
||||||
})),
|
})),
|
||||||
transition('* <=> *', animate(500))
|
transition('* <=> *', animate(500))
|
||||||
|
]),
|
||||||
|
trigger('fadeSeparator', [
|
||||||
|
state('visible', style({
|
||||||
|
opacity: 1
|
||||||
|
})),
|
||||||
|
state('invisible', style({
|
||||||
|
opacity: 0
|
||||||
|
})),
|
||||||
|
transition('visible => invisible', animate(500)),
|
||||||
|
transition('invisible => visible', animate(1500))
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -33,16 +51,20 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
@Input() description: string;
|
@Input() description: string;
|
||||||
@Input() showDescription = false;
|
@Input() showDescription = false;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyboardKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter<SvgKeyHoverEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyboardCaptureEvent>();
|
||||||
@Output() descriptionChanged = new EventEmitter<string>();
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
modules: SvgModule[];
|
modules: SvgModule[];
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
moduleAnimationStates: string[];
|
moduleAnimationStates: string[];
|
||||||
|
separator: SvgSeparator;
|
||||||
|
separatorStyle: SafeStyle;
|
||||||
|
separatorAnimation = 'visible';
|
||||||
|
|
||||||
constructor(private svgModuleProvider: SvgModuleProviderService) {
|
constructor(private svgModuleProvider: SvgModuleProviderService,
|
||||||
|
private sanitizer: DomSanitizer) {
|
||||||
this.modules = [];
|
this.modules = [];
|
||||||
this.viewBox = '-520 582 1100 470';
|
this.viewBox = '-520 582 1100 470';
|
||||||
this.halvesSplit = false;
|
this.halvesSplit = false;
|
||||||
@@ -63,19 +85,17 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void {
|
onKeyClick(moduleId: number, event: SvgModuleKeyClickEvent): void {
|
||||||
this.keyClick.emit({
|
this.keyClick.emit({
|
||||||
moduleId,
|
...event,
|
||||||
keyId,
|
moduleId
|
||||||
keyTarget
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCapture(moduleId: number, keyId: number, captured: { code: number, left: boolean[], right: boolean[] }): void {
|
onCapture(moduleId: number, event: SvgKeyboardCaptureEvent): void {
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
moduleId,
|
...event,
|
||||||
keyId,
|
moduleId
|
||||||
captured
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,12 +111,16 @@ export class SvgKeyboardComponent implements OnInit {
|
|||||||
private updateModuleAnimationStates() {
|
private updateModuleAnimationStates() {
|
||||||
if (this.halvesSplit) {
|
if (this.halvesSplit) {
|
||||||
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
this.moduleAnimationStates = ['rotateRight', 'rotateLeft'];
|
||||||
|
this.separatorAnimation = 'invisible';
|
||||||
} else {
|
} else {
|
||||||
this.moduleAnimationStates = [];
|
this.moduleAnimationStates = [];
|
||||||
|
this.separatorAnimation = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setModules() {
|
private setModules() {
|
||||||
this.modules = this.svgModuleProvider.getSvgModules(this.keyboardLayout);
|
this.modules = this.svgModuleProvider.getSvgModules(this.keyboardLayout);
|
||||||
|
this.separator = this.svgModuleProvider.getSvgSeparator();
|
||||||
|
this.separatorStyle = this.sanitizer.bypassSecurityTrustStyle(this.separator.style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ import {
|
|||||||
MouseAction,
|
MouseAction,
|
||||||
PlayMacroAction,
|
PlayMacroAction,
|
||||||
SwitchKeymapAction,
|
SwitchKeymapAction,
|
||||||
SwitchLayerAction
|
SwitchLayerAction,
|
||||||
|
SwitchLayerMode
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { CaptureService } from '../../../../services/capture.service';
|
import { CaptureService } from '../../../../services/capture.service';
|
||||||
import { MapperService } from '../../../../services/mapper.service';
|
import { MapperService } from '../../../../services/mapper.service';
|
||||||
|
|
||||||
import { AppState } from '../../../../store/index';
|
import { AppState } from '../../../../store';
|
||||||
import { getMacros } from '../../../../store/reducers/user-configuration';
|
import { getMacros } from '../../../../store/reducers/user-configuration';
|
||||||
|
import { SvgKeyCaptureEvent, SvgKeyClickEvent } from '../../../../models/svg-key-events';
|
||||||
|
|
||||||
enum LabelTypes {
|
enum LabelTypes {
|
||||||
KeystrokeKey,
|
KeystrokeKey,
|
||||||
@@ -81,8 +83,8 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input() capturingEnabled: boolean;
|
@Input() capturingEnabled: boolean;
|
||||||
@Input() active: boolean;
|
@Input() active: boolean;
|
||||||
|
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgKeyClickEvent>();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgKeyCaptureEvent>();
|
||||||
|
|
||||||
enumLabelTypes = LabelTypes;
|
enumLabelTypes = LabelTypes;
|
||||||
|
|
||||||
@@ -95,6 +97,10 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
macros: Macro[];
|
macros: Macro[];
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private scanCodePressed: boolean;
|
private scanCodePressed: boolean;
|
||||||
|
private pressedShiftLocation = -1;
|
||||||
|
private pressedAltLocation = -1;
|
||||||
|
private altPressed = false;
|
||||||
|
private shiftPressed = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private mapper: MapperService,
|
private mapper: MapperService,
|
||||||
@@ -114,12 +120,16 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@HostListener('click')
|
@HostListener('click')
|
||||||
onClick() {
|
onClick() {
|
||||||
this.reset();
|
this.reset();
|
||||||
this.keyClick.emit(this.element.nativeElement);
|
this.keyClick.emit({
|
||||||
|
keyTarget: this.element.nativeElement,
|
||||||
|
shiftPressed: this.pressedShiftLocation > -1,
|
||||||
|
altPressed: this.pressedAltLocation > -1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('mousedown', ['$event'])
|
@HostListener('mousedown', ['$event'])
|
||||||
onMouseDown(e: MouseEvent) {
|
onMouseDown(e: MouseEvent) {
|
||||||
if ((e.which === 2 || e.button === 1) && this.capturingEnabled) {
|
if ((e.which === 2 || e.button === 2) && this.capturingEnabled) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.renderer.invokeElementMethod(this.element.nativeElement, 'focus');
|
this.renderer.invokeElementMethod(this.element.nativeElement, 'focus');
|
||||||
|
|
||||||
@@ -128,13 +138,29 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.recording = true;
|
this.recording = true;
|
||||||
this.recordAnimation = 'active';
|
this.recordAnimation = 'active';
|
||||||
|
|
||||||
|
if (this.pressedShiftLocation > -1) {
|
||||||
|
this.shiftPressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pressedAltLocation > -1) {
|
||||||
|
this.altPressed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keyup', ['$event'])
|
@HostListener('document:keyup', ['$event'])
|
||||||
onKeyUp(e: KeyboardEvent) {
|
onKeyUp(e: KeyboardEvent) {
|
||||||
if (this.scanCodePressed) {
|
if (e.keyCode === 18 && this.pressedAltLocation > -1) {
|
||||||
|
this.pressedAltLocation = -1;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
else if (e.keyCode === 16 && this.pressedShiftLocation > -1) {
|
||||||
|
this.pressedShiftLocation = -1;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
else if (this.scanCodePressed) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.scanCodePressed = false;
|
this.scanCodePressed = false;
|
||||||
} else if (this.recording) {
|
} else if (this.recording) {
|
||||||
@@ -143,7 +169,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keydown', ['$event'])
|
@HostListener('document:keydown', ['$event'])
|
||||||
onKeyDown(e: KeyboardEvent) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
const code: number = e.keyCode;
|
const code: number = e.keyCode;
|
||||||
|
|
||||||
@@ -151,11 +177,29 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.captureService.hasMap(code)) {
|
if (this.captureService.hasMap(code)) {
|
||||||
|
// If the Alt or Shift key not released after start the capturing
|
||||||
|
// then add them as a modifier
|
||||||
|
if (this.pressedShiftLocation > -1) {
|
||||||
|
this.captureService.setModifier((this.pressedShiftLocation === 1), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pressedAltLocation > -1) {
|
||||||
|
this.captureService.setModifier((this.pressedAltLocation === 1), 18);
|
||||||
|
}
|
||||||
|
|
||||||
this.saveScanCode(this.captureService.getMap(code));
|
this.saveScanCode(this.captureService.getMap(code));
|
||||||
this.scanCodePressed = true;
|
this.scanCodePressed = true;
|
||||||
} else {
|
} else {
|
||||||
this.captureService.setModifier((e.location === 1), code);
|
this.captureService.setModifier((e.location === 1), code);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (e.keyCode === 16) {
|
||||||
|
this.pressedShiftLocation = e.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 18) {
|
||||||
|
this.pressedAltLocation = e.location;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,22 +241,25 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.recording = false;
|
this.recording = false;
|
||||||
this.changeAnimation = 'inactive';
|
this.changeAnimation = 'inactive';
|
||||||
this.captureService.initModifiers();
|
this.captureService.initModifiers();
|
||||||
|
this.shiftPressed = false;
|
||||||
|
this.altPressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveScanCode(code = 0) {
|
private saveScanCode(code = 0) {
|
||||||
this.recording = false;
|
|
||||||
this.changeAnimation = 'inactive';
|
|
||||||
|
|
||||||
const left: boolean[] = this.captureService.getModifiers(true);
|
const left: boolean[] = this.captureService.getModifiers(true);
|
||||||
const right: boolean[] = this.captureService.getModifiers(false);
|
const right: boolean[] = this.captureService.getModifiers(false);
|
||||||
|
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
code,
|
captured: {
|
||||||
left,
|
code,
|
||||||
right
|
left,
|
||||||
|
right
|
||||||
|
},
|
||||||
|
shiftPressed: this.shiftPressed,
|
||||||
|
altPressed: this.altPressed
|
||||||
});
|
});
|
||||||
|
|
||||||
this.captureService.initModifiers();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setLabels(): void {
|
private setLabels(): void {
|
||||||
@@ -288,12 +335,18 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyAction.isLayerToggleable) {
|
if (keyAction.switchLayerMode === SwitchLayerMode.toggle) {
|
||||||
this.labelType = LabelTypes.TextIcon;
|
this.labelType = LabelTypes.TextIcon;
|
||||||
this.labelSource = {
|
this.labelSource = {
|
||||||
text: newLabelSource,
|
text: newLabelSource,
|
||||||
icon: this.mapper.getIcon('toggle')
|
icon: this.mapper.getIcon('toggle')
|
||||||
};
|
};
|
||||||
|
} else if (keyAction.switchLayerMode === SwitchLayerMode.holdAndDoubleTapToggle) {
|
||||||
|
this.labelType = LabelTypes.TextIcon;
|
||||||
|
this.labelSource = {
|
||||||
|
text: newLabelSource,
|
||||||
|
icon: this.mapper.getIcon('double-tap')
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
this.labelType = LabelTypes.OneLineText;
|
this.labelType = LabelTypes.OneLineText;
|
||||||
this.labelSource = newLabelSource;
|
this.labelSource = newLabelSource;
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from
|
|||||||
import { KeyAction } from 'uhk-common';
|
import { KeyAction } from 'uhk-common';
|
||||||
|
|
||||||
import { SvgKeyboardKey } from '../keys';
|
import { SvgKeyboardKey } from '../keys';
|
||||||
|
import {
|
||||||
|
SvgKeyCaptureEvent,
|
||||||
|
SvgKeyClickEvent,
|
||||||
|
SvgModuleCaptureEvent,
|
||||||
|
SvgModuleKeyClickEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'g[svg-module]',
|
selector: 'g[svg-module]',
|
||||||
@@ -17,18 +23,18 @@ export class SvgModuleComponent {
|
|||||||
@Input() selected: boolean;
|
@Input() selected: boolean;
|
||||||
@Input() keybindAnimationEnabled: boolean;
|
@Input() keybindAnimationEnabled: boolean;
|
||||||
@Input() capturingEnabled: boolean;
|
@Input() capturingEnabled: boolean;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter<SvgModuleKeyClickEvent>();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter<SvgModuleCaptureEvent>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.keyboardKeys = [];
|
this.keyboardKeys = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyClick(index: number, keyTarget: HTMLElement): void {
|
onKeyClick(keyId: number, event: SvgKeyClickEvent): void {
|
||||||
this.keyClick.emit({
|
this.keyClick.emit({
|
||||||
index,
|
...event,
|
||||||
keyTarget
|
keyId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +46,10 @@ export class SvgModuleComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCapture(index: number, captured: {code: number, left: boolean[], right: boolean[]}) {
|
onCapture(keyId: number, event: SvgKeyCaptureEvent) {
|
||||||
this.capture.emit({
|
this.capture.emit({
|
||||||
index,
|
...event,
|
||||||
captured
|
keyId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { SvgSeparator } from './svg-separator.model';
|
||||||
|
|
||||||
|
export const convertXmlToSvgSeparator = (obj: { path: any[], $: Object }): SvgSeparator => {
|
||||||
|
return obj.path[0].$;
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './svg-separator.model';
|
||||||
|
export * from './convert-xml-to-svg-separator';
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface SvgSeparator {
|
||||||
|
style: string;
|
||||||
|
d: string;
|
||||||
|
}
|
||||||
@@ -8,13 +8,25 @@
|
|||||||
[halvesSplit]="halvesSplit"
|
[halvesSplit]="halvesSplit"
|
||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
[description]="keymap.description"
|
[description]="keymap.description"
|
||||||
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
|
(keyClick)="onKeyClick($event)"
|
||||||
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
|
(keyHover)="onKeyHover($event)"
|
||||||
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
|
(capture)="onCapture($event)"
|
||||||
(descriptionChanged)="onDescriptionChanged($event)"
|
(descriptionChanged)="onDescriptionChanged($event)"
|
||||||
></keyboard-slider>
|
></keyboard-slider>
|
||||||
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
|
|
||||||
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
|
<popover tabindex="0"
|
||||||
|
[visible]="popoverShown"
|
||||||
|
[keyPosition]="keyPosition"
|
||||||
|
[wrapPosition]="wrapPosition"
|
||||||
|
[defaultKeyAction]="popoverInitKeyAction"
|
||||||
|
[currentKeymap]="keymap"
|
||||||
|
[currentLayer]="currentLayer"
|
||||||
|
[allowLayerDoubleTap]="allowLayerDoubleTap"
|
||||||
|
[remapOnAllKeymap]="remapOnAllKeymap"
|
||||||
|
[remapOnAllLayer]="remapOnAllLayer"
|
||||||
|
(cancel)="hidePopover()"
|
||||||
|
(remap)="onRemap($event)"></popover>
|
||||||
|
|
||||||
<div class="tooltip bottom"
|
<div class="tooltip bottom"
|
||||||
[class.in]="tooltipData.show"
|
[class.in]="tooltipData.show"
|
||||||
[style.top.px]="tooltipData.posTop"
|
[style.top.px]="tooltipData.posTop"
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import {
|
|||||||
PlayMacroAction,
|
PlayMacroAction,
|
||||||
SecondaryRoleAction,
|
SecondaryRoleAction,
|
||||||
SwitchKeymapAction,
|
SwitchKeymapAction,
|
||||||
SwitchLayerAction
|
SwitchLayerAction,
|
||||||
|
SwitchLayerMode
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { MapperService } from '../../../services/mapper.service';
|
import { MapperService } from '../../../services/mapper.service';
|
||||||
@@ -41,6 +42,12 @@ import { KeymapActions } from '../../../store/actions';
|
|||||||
import { PopoverComponent } from '../../popover';
|
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 {
|
||||||
|
SvgKeyboardCaptureEvent,
|
||||||
|
SvgKeyboardKeyClickEvent,
|
||||||
|
SvgKeyHoverEvent
|
||||||
|
} from '../../../models/svg-key-events';
|
||||||
|
|
||||||
interface NameValuePair {
|
interface NameValuePair {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -59,9 +66,11 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
|||||||
@Input() tooltipEnabled: boolean = false;
|
@Input() tooltipEnabled: boolean = false;
|
||||||
@Input() halvesSplit: boolean;
|
@Input() halvesSplit: boolean;
|
||||||
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
||||||
|
@Input() allowLayerDoubleTap: boolean;
|
||||||
|
|
||||||
@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 };
|
||||||
@@ -78,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;
|
||||||
|
|
||||||
@@ -127,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;
|
||||||
@@ -136,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) {
|
||||||
@@ -176,13 +189,17 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
|||||||
KeymapActions.saveKey(
|
KeymapActions.saveKey(
|
||||||
this.keymap,
|
this.keymap,
|
||||||
this.currentLayer,
|
this.currentLayer,
|
||||||
moduleId,
|
event.moduleId,
|
||||||
keyId,
|
event.keyId,
|
||||||
keystrokeAction)
|
{
|
||||||
|
remapOnAllKeymap: event.shiftPressed,
|
||||||
|
remapOnAllLayer: event.altPressed,
|
||||||
|
action: keystrokeAction
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemap(keyAction: KeyAction): void {
|
onRemap(keyAction: KeyActionRemap): void {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
KeymapActions.saveKey(
|
KeymapActions.saveKey(
|
||||||
this.keymap,
|
this.keymap,
|
||||||
@@ -231,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 {
|
||||||
@@ -350,7 +368,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Toogle',
|
name: 'Toogle',
|
||||||
value: switchLayerAction.isLayerToggleable ? 'On' : 'Off'
|
value: switchLayerAction.switchLayerMode === SwitchLayerMode.toggle ? 'On' : 'Off'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
return Observable.of(content);
|
return Observable.of(content);
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './external-url.directive';
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './cancelable';
|
export * from './cancelable';
|
||||||
export * from './tooltip';
|
export * from './tooltip';
|
||||||
|
export * from './external-url';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
@@ -9,15 +10,10 @@ export class TooltipDirective implements AfterContentInit, OnChanges {
|
|||||||
@HostBinding('attr.data-placement') placement: string;
|
@HostBinding('attr.data-placement') placement: string;
|
||||||
@Input('title') title: string;
|
@Input('title') title: string;
|
||||||
@Input('html') html: boolean;
|
@Input('html') html: boolean;
|
||||||
|
@Input() maxWidth: number;
|
||||||
|
|
||||||
private customTooltipTemplate = `
|
constructor(private elementRef: ElementRef, private sanitizer: DomSanitizer) {
|
||||||
<div class="tooltip">
|
}
|
||||||
<div class="tooltip-arrow"></div>
|
|
||||||
<div class="tooltip-inner"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
constructor(private elementRef: ElementRef, private sanitizer: DomSanitizer) { }
|
|
||||||
|
|
||||||
ngAfterContentInit() {
|
ngAfterContentInit() {
|
||||||
this.init();
|
this.init();
|
||||||
@@ -33,7 +29,7 @@ export class TooltipDirective implements AfterContentInit, OnChanges {
|
|||||||
(<any>jQuery(this.elementRef.nativeElement)).tooltip({
|
(<any>jQuery(this.elementRef.nativeElement)).tooltip({
|
||||||
placement: this.placement,
|
placement: this.placement,
|
||||||
html: this.html,
|
html: this.html,
|
||||||
template: this.customTooltipTemplate,
|
template: this.getCustomTemplate(),
|
||||||
title: this.title
|
title: this.title
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -42,7 +38,7 @@ export class TooltipDirective implements AfterContentInit, OnChanges {
|
|||||||
(<any>jQuery(this.elementRef.nativeElement)).tooltip({
|
(<any>jQuery(this.elementRef.nativeElement)).tooltip({
|
||||||
placement: this.placement,
|
placement: this.placement,
|
||||||
html: this.html,
|
html: this.html,
|
||||||
template: this.customTooltipTemplate,
|
template: this.getCustomTemplate(),
|
||||||
title: this.title
|
title: this.title
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,4 +46,19 @@ export class TooltipDirective implements AfterContentInit, OnChanges {
|
|||||||
.attr('title', this.title))
|
.attr('title', this.title))
|
||||||
.tooltip('fixTitle');
|
.tooltip('fixTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCustomTemplate(): string {
|
||||||
|
let tooltipStyle = '';
|
||||||
|
let innerStyle = '';
|
||||||
|
|
||||||
|
if (this.maxWidth) {
|
||||||
|
tooltipStyle = `style="width: ${this.maxWidth}px;"`;
|
||||||
|
innerStyle = `style="max-width: ${this.maxWidth}px;"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<div class="tooltip" ${tooltipStyle}>
|
||||||
|
<div class="tooltip-arrow"></div>
|
||||||
|
<div class="tooltip-inner" ${innerStyle}></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
packages/uhk-web/src/app/models/key-action-remap.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { KeyAction } from 'uhk-common';
|
||||||
|
|
||||||
|
export interface KeyActionRemap {
|
||||||
|
remapOnAllKeymap: boolean;
|
||||||
|
remapOnAllLayer: boolean;
|
||||||
|
action: KeyAction;
|
||||||
|
}
|
||||||
7
packages/uhk-web/src/app/models/select-option-data.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface SelectOptionData {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
children?: Array<SelectOptionData>;
|
||||||
|
additional?: any;
|
||||||
|
}
|
||||||
42
packages/uhk-web/src/app/models/svg-key-events.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ export class MapperService {
|
|||||||
private initNameToFileNames(): void {
|
private initNameToFileNames(): void {
|
||||||
this.nameToFileName = new Map<string, string>();
|
this.nameToFileName = new Map<string, string>();
|
||||||
this.nameToFileName.set('toggle', 'icon-kbd__fn--toggle');
|
this.nameToFileName.set('toggle', 'icon-kbd__fn--toggle');
|
||||||
|
this.nameToFileName.set('double-tap', 'icon-kbd__fn--double-tap');
|
||||||
this.nameToFileName.set('switch-keymap', 'icon-kbd__mod--switch-keymap');
|
this.nameToFileName.set('switch-keymap', 'icon-kbd__mod--switch-keymap');
|
||||||
this.nameToFileName.set('macro', 'icon-icon__macro');
|
this.nameToFileName.set('macro', 'icon-icon__macro');
|
||||||
this.nameToFileName.set('shift', 'icon-kbd__default--modifier-shift');
|
this.nameToFileName.set('shift', 'icon-kbd__default--modifier-shift');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
|
|
||||||
import { SvgModule } from '../components/svg/module';
|
import { SvgModule } from '../components/svg/module';
|
||||||
import { KeyboardLayout } from '../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../keyboard/keyboard-layout.enum';
|
||||||
|
import { convertXmlToSvgSeparator, SvgSeparator } from '../components/svg/separator';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SvgModuleProviderService {
|
export class SvgModuleProviderService {
|
||||||
@@ -9,11 +10,20 @@ export class SvgModuleProviderService {
|
|||||||
private ansiLeft: SvgModule;
|
private ansiLeft: SvgModule;
|
||||||
private isoLeft: SvgModule;
|
private isoLeft: SvgModule;
|
||||||
private right: SvgModule;
|
private right: SvgModule;
|
||||||
|
private separator: SvgSeparator;
|
||||||
|
|
||||||
getSvgModules(layout = KeyboardLayout.ANSI): SvgModule[] {
|
getSvgModules(layout = KeyboardLayout.ANSI): SvgModule[] {
|
||||||
return [this.getRightModule(), this.getLeftModule(layout)];
|
return [this.getRightModule(), this.getLeftModule(layout)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSvgSeparator(): SvgSeparator {
|
||||||
|
if (!this.separator) {
|
||||||
|
this.separator = convertXmlToSvgSeparator(require('xml-loader!../../devices/uhk60-right/separator.xml').svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.separator;
|
||||||
|
}
|
||||||
|
|
||||||
private getLeftModule(layout = KeyboardLayout.ANSI): SvgModule {
|
private getLeftModule(layout = KeyboardLayout.ANSI): SvgModule {
|
||||||
if (layout === KeyboardLayout.ISO) {
|
if (layout === KeyboardLayout.ISO) {
|
||||||
if (!this.isoLeft) {
|
if (!this.isoLeft) {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { KeyAction, Keymap, Macro } from 'uhk-common';
|
import { Keymap, Macro } from 'uhk-common';
|
||||||
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
||||||
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
|
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
|
||||||
|
import { KeyActionRemap } from '../../models/key-action-remap';
|
||||||
|
|
||||||
export type KeymapAction =
|
export type KeymapAction =
|
||||||
KeymapActions.AddKeymapAction |
|
KeymapActions.AddKeymapAction |
|
||||||
@@ -60,7 +61,7 @@ export namespace KeymapActions {
|
|||||||
layer: number;
|
layer: number;
|
||||||
module: number;
|
module: number;
|
||||||
key: number;
|
key: number;
|
||||||
keyAction: KeyAction;
|
keyAction: KeyActionRemap;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,7 +173,11 @@ export namespace KeymapActions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveKey(keymap: Keymap, layer: number, module: number, key: number, keyAction: KeyAction): SaveKeyAction {
|
export function saveKey(keymap: Keymap,
|
||||||
|
layer: number,
|
||||||
|
module: number,
|
||||||
|
key: number,
|
||||||
|
keyAction: KeyActionRemap): SaveKeyAction {
|
||||||
return {
|
return {
|
||||||
type: KeymapActions.SAVE_KEY,
|
type: KeymapActions.SAVE_KEY,
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const getUserConfiguration = (state: AppState) => state.userConfiguration
|
|||||||
export const appState = (state: AppState) => state.app;
|
export const appState = (state: AppState) => state.app;
|
||||||
|
|
||||||
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
|
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
|
||||||
|
export const allowLayerDoubleTap = createSelector(appState, fromApp.allowLayerDoubleTap);
|
||||||
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
|
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
|
||||||
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
|
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
|
||||||
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
|
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
|
||||||
@@ -51,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);
|
||||||
@@ -80,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,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
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,
|
||||||
HardwareConfiguration,
|
HardwareConfiguration,
|
||||||
Notification,
|
Notification,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
@@ -18,7 +20,7 @@ import { PrivilagePageSate } from '../../models/privilage-page-sate';
|
|||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
started: boolean;
|
started: boolean;
|
||||||
showAddonMenu: boolean;
|
commandLineArgs: CommandLineArgs;
|
||||||
undoableNotification?: Notification;
|
undoableNotification?: Notification;
|
||||||
navigationCountAfterNotification: number;
|
navigationCountAfterNotification: number;
|
||||||
prevUserConfig?: UserConfiguration;
|
prevUserConfig?: UserConfiguration;
|
||||||
@@ -28,11 +30,13 @@ 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 = {
|
||||||
started: false,
|
started: false,
|
||||||
showAddonMenu: false,
|
commandLineArgs: {},
|
||||||
navigationCountAfterNotification: 0,
|
navigationCountAfterNotification: 0,
|
||||||
runningInElectron: runInElectron(),
|
runningInElectron: runInElectron(),
|
||||||
configLoading: true,
|
configLoading: true,
|
||||||
@@ -49,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,
|
||||||
showAddonMenu: action.payload.addons
|
commandLineArgs: payload.commandLineArgs,
|
||||||
|
platform: payload.platform,
|
||||||
|
osVersion: payload.osVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +156,8 @@ export function reducer(state = initialState, action: Action & { payload: any })
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showAddonMenu = (state: State) => state.showAddonMenu;
|
export const showAddonMenu = (state: State) => state.commandLineArgs.addons;
|
||||||
|
export const allowLayerDoubleTap = (state: State) => state.commandLineArgs.layerDoubleTap;
|
||||||
export const getUndoableNotification = (state: State) => state.undoableNotification;
|
export const getUndoableNotification = (state: State) => state.undoableNotification;
|
||||||
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
|
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
|
||||||
export const runningInElectron = (state: State) => state.runningInElectron;
|
export const runningInElectron = (state: State) => state.runningInElectron;
|
||||||
@@ -170,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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { reducer, initialState } from './user-configuration';
|
import { reducer, initialState } from './user-configuration';
|
||||||
import { KeystrokeAction, KeystrokeType, SwitchLayerAction, UserConfiguration, LayerName, Keymap } from 'uhk-common';
|
import {
|
||||||
|
KeystrokeAction,
|
||||||
|
KeystrokeType,
|
||||||
|
SwitchLayerAction,
|
||||||
|
UserConfiguration,
|
||||||
|
LayerName,
|
||||||
|
Keymap,
|
||||||
|
SwitchLayerMode
|
||||||
|
} from 'uhk-common';
|
||||||
|
|
||||||
import { getDefaultUserConfig } from '../../../../test/user-config-helper';
|
import { getDefaultUserConfig } from '../../../../test/user-config-helper';
|
||||||
import { KeymapActions } from '../actions';
|
import { KeymapActions } from '../actions';
|
||||||
@@ -22,7 +30,11 @@ describe('user-configuration reducer', () => {
|
|||||||
layer: 0,
|
layer: 0,
|
||||||
module: 0,
|
module: 0,
|
||||||
key: 0,
|
key: 0,
|
||||||
keyAction: keystrokeAction
|
keyAction: {
|
||||||
|
remapOnAllKeymap: false,
|
||||||
|
remapOnAllLayer: false,
|
||||||
|
action: keystrokeAction
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const result = reducer(state, saveKeyAction);
|
const result = reducer(state, saveKeyAction);
|
||||||
@@ -42,7 +54,11 @@ describe('user-configuration reducer', () => {
|
|||||||
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
||||||
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
||||||
const destinationLayerId = LayerName.mod;
|
const destinationLayerId = LayerName.mod;
|
||||||
const switchLayerAction = new SwitchLayerAction({isLayerToggleable: false, layer: destinationLayerId} as any);
|
const switchLayerAction = new SwitchLayerAction({
|
||||||
|
switchLayerMode: SwitchLayerMode.toggle,
|
||||||
|
layer: destinationLayerId
|
||||||
|
} as any);
|
||||||
|
|
||||||
const saveKeyAction: KeymapActions.SaveKeyAction = {
|
const saveKeyAction: KeymapActions.SaveKeyAction = {
|
||||||
type: KeymapActions.SAVE_KEY,
|
type: KeymapActions.SAVE_KEY,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -50,7 +66,12 @@ describe('user-configuration reducer', () => {
|
|||||||
layer: 0,
|
layer: 0,
|
||||||
module: 0,
|
module: 0,
|
||||||
key: 0,
|
key: 0,
|
||||||
keyAction: switchLayerAction
|
keyAction: {
|
||||||
|
remapOnAllKeymap: false,
|
||||||
|
remapOnAllLayer: false,
|
||||||
|
action: switchLayerAction
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const result = reducer(state, saveKeyAction);
|
const result = reducer(state, saveKeyAction);
|
||||||
@@ -81,7 +102,7 @@ describe('user-configuration reducer', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyActionType: 'keystroke',
|
keyActionType: 'keystroke',
|
||||||
@@ -89,9 +110,9 @@ describe('user-configuration reducer', () => {
|
|||||||
scancode: 37
|
scancode: 37
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'keyActionType': 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
'layer': 'mod',
|
layer: 'mod',
|
||||||
'toggle': false
|
switchLayerMode: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -128,7 +149,7 @@ describe('user-configuration reducer', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'mod',
|
layer: 'mod',
|
||||||
toggle: false
|
switchLayerMode: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyActionType: 'keystroke',
|
keyActionType: 'keystroke',
|
||||||
@@ -136,9 +157,9 @@ describe('user-configuration reducer', () => {
|
|||||||
scancode: 65
|
scancode: 65
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'keyActionType': 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
'layer': 'mod',
|
layer: 'mod',
|
||||||
'toggle': false
|
switchLayerMode: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -219,7 +240,11 @@ describe('user-configuration reducer', () => {
|
|||||||
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
const defaultUserConfig = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
||||||
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
const state = new UserConfiguration().fromJsonObject(getDefaultUserConfig());
|
||||||
const destinationLayerId = LayerName.fn;
|
const destinationLayerId = LayerName.fn;
|
||||||
const switchLayerAction = new SwitchLayerAction({isLayerToggleable: false, layer: destinationLayerId} as any);
|
const switchLayerAction = new SwitchLayerAction({
|
||||||
|
switchLayerMode: SwitchLayerMode.toggle,
|
||||||
|
layer: destinationLayerId
|
||||||
|
} as any);
|
||||||
|
|
||||||
const saveKeyAction: KeymapActions.SaveKeyAction = {
|
const saveKeyAction: KeymapActions.SaveKeyAction = {
|
||||||
type: KeymapActions.SAVE_KEY,
|
type: KeymapActions.SAVE_KEY,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -227,7 +252,12 @@ describe('user-configuration reducer', () => {
|
|||||||
layer: 0,
|
layer: 0,
|
||||||
module: 0,
|
module: 0,
|
||||||
key: 2,
|
key: 2,
|
||||||
keyAction: switchLayerAction
|
keyAction: {
|
||||||
|
remapOnAllKeymap: false,
|
||||||
|
remapOnAllLayer: false,
|
||||||
|
action: switchLayerAction
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const result = reducer(state, saveKeyAction);
|
const result = reducer(state, saveKeyAction);
|
||||||
@@ -266,9 +296,9 @@ describe('user-configuration reducer', () => {
|
|||||||
scancode: 37
|
scancode: 37
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'keyActionType': 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
'layer': 'fn',
|
layer: 'fn',
|
||||||
'toggle': false
|
switchLayerMode: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -345,7 +375,7 @@ describe('user-configuration reducer', () => {
|
|||||||
{
|
{
|
||||||
keyActionType: 'switchLayer',
|
keyActionType: 'switchLayer',
|
||||||
layer: 'fn',
|
layer: 'fn',
|
||||||
toggle: false
|
switchLayerMode: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Module,
|
Module,
|
||||||
NoneAction,
|
NoneAction,
|
||||||
PlayMacroAction,
|
PlayMacroAction,
|
||||||
|
SwitchKeymapAction,
|
||||||
SwitchLayerAction,
|
SwitchLayerAction,
|
||||||
UserConfiguration
|
UserConfiguration
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
@@ -68,7 +69,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newKeymap = Object.assign(new Keymap(), keymapToRename, { name });
|
const newKeymap = Object.assign(new Keymap(), keymapToRename, {name});
|
||||||
|
|
||||||
changedUserConfiguration.keymaps = insertItemInNameOrder(
|
changedUserConfiguration.keymaps = insertItemInNameOrder(
|
||||||
state.keymaps,
|
state.keymaps,
|
||||||
@@ -139,38 +140,49 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
const keyIndex: number = action.payload.key;
|
const keyIndex: number = action.payload.key;
|
||||||
const layerIndex: number = action.payload.layer;
|
const layerIndex: number = action.payload.layer;
|
||||||
const moduleIndex: number = action.payload.module;
|
const moduleIndex: number = action.payload.module;
|
||||||
const newKeyAction = KeyActionHelper.createKeyAction(action.payload.keyAction);
|
const keyActionRemap = action.payload.keyAction;
|
||||||
const newKeymap: Keymap = Object.assign(new Keymap(), action.payload.keymap);
|
const newKeyAction = keyActionRemap.action;
|
||||||
newKeymap.layers = newKeymap.layers.slice();
|
const newKeymap: Keymap = action.payload.keymap;
|
||||||
|
const isSwitchLayerAction = newKeyAction instanceof SwitchLayerAction;
|
||||||
newKeymap.layers = newKeymap.layers.map((layer, index) => {
|
const isSwitchKeymapAction = newKeyAction instanceof SwitchKeymapAction;
|
||||||
const newLayer = Object.assign(new Layer(), layer);
|
|
||||||
|
|
||||||
if (index === layerIndex) {
|
|
||||||
setKeyActionToLayer(newLayer, moduleIndex, keyIndex, newKeyAction);
|
|
||||||
}
|
|
||||||
// If the key action is a SwitchLayerAction then set the same SwitchLayerAction
|
|
||||||
// on the target layer
|
|
||||||
else if (newKeyAction instanceof SwitchLayerAction) {
|
|
||||||
if (index - 1 === newKeyAction.layer) {
|
|
||||||
const clonedAction = KeyActionHelper.createKeyAction(action.payload.keyAction);
|
|
||||||
setKeyActionToLayer(newLayer, moduleIndex, keyIndex, clonedAction);
|
|
||||||
} else {
|
|
||||||
setKeyActionToLayer(newLayer, moduleIndex, keyIndex, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newLayer;
|
|
||||||
});
|
|
||||||
|
|
||||||
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
|
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
|
||||||
if (keymap.abbreviation === newKeymap.abbreviation) {
|
// SwitchKeymapAction not allow to refer to itself
|
||||||
keymap = newKeymap;
|
if (isSwitchKeymapAction && keymap.abbreviation === newKeyAction.keymapAbbreviation) {
|
||||||
|
return keymap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyActionRemap.remapOnAllKeymap || keymap.abbreviation === newKeymap.abbreviation) {
|
||||||
|
keymap.layers = keymap.layers.map((layer, index) => {
|
||||||
|
if (keyActionRemap.remapOnAllLayer || index === layerIndex || isSwitchLayerAction) {
|
||||||
|
const clonedAction = KeyActionHelper.createKeyAction(newKeyAction);
|
||||||
|
|
||||||
|
// If the key action is a SwitchLayerAction then set the same SwitchLayerAction
|
||||||
|
// on the target layer and remove SwitchLayerAction from other layers
|
||||||
|
if (isSwitchLayerAction) {
|
||||||
|
if (index === 0 || index - 1 === (newKeyAction as SwitchLayerAction).layer) {
|
||||||
|
setKeyActionToLayer(layer, moduleIndex, keyIndex, clonedAction);
|
||||||
|
} else {
|
||||||
|
const actionOnLayer = layer.modules[moduleIndex].keyActions[keyIndex];
|
||||||
|
if (actionOnLayer && actionOnLayer instanceof SwitchLayerAction) {
|
||||||
|
setKeyActionToLayer(layer, moduleIndex, keyIndex, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setKeyActionToLayer(layer, moduleIndex, keyIndex, clonedAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return keymap;
|
return keymap;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case KeymapActions.CHECK_MACRO:
|
case KeymapActions.CHECK_MACRO:
|
||||||
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
|
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
|
||||||
keymap = Object.assign(new Keymap(), keymap);
|
keymap = Object.assign(new Keymap(), keymap);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 88 KiB |
41
packages/uhk-web/src/assets/images/agent-logo-with-text.svg
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
viewBox="-5 -5 49.822906 12.159602"
|
||||||
|
height="18.239403"
|
||||||
|
width="74.73436">
|
||||||
|
<path
|
||||||
|
id="path4"
|
||||||
|
style="fill:#000000"
|
||||||
|
d="M -3,-5 C -4.108,-5 -5,-4.108 -5,-3 L -5,3 C -5,4.108 -4.108,5 -3,5 L 3,5 C 4.108,5 5,4.108 5,3 L 5,-3 C 5,-4.108 4.108,-5 3,-5 L -3,-5 Z M -4.0833333,-2.5 C -4.0364583,-2.5078125 -4,-2.5 -4,-2.5 L -0.99999998,-2.5 C -0.74999998,-2.5 -0.49999998,-2.25 -0.49999998,-2.25 -0.49999998,-2.25 -0.24999998,-2 2.4999999e-8,-2 0.25000003,-2 0.50000003,-2.25 0.50000003,-2.25 0.50000003,-2.25 0.75000003,-2.5 1,-2.5 L 4,-2.5 C 4.5,-2.5 4.5,-2 4.5,-2 L 4.5,-0.99999998 C 4.5,0.50000003 3.5,0.50000003 3.5,0.50000003 L 1,0.50000003 C 0.75000003,0.50000003 0.50000003,-0.24999998 0.50000003,-0.24999998 0.50000003,-0.24999998 0.25000003,-0.99999998 2.4999999e-8,-0.99999998 -0.24999998,-0.99999998 -0.49999998,-0.24999998 -0.49999998,-0.24999998 -0.49999998,-0.24999998 -0.74999998,0.50000003 -0.99999998,0.50000003 L -3.5,0.50000003 C -3.5,0.50000003 -4.5,0.50000003 -4.5,-0.99999998 L -4.5,-2 C -4.5,-2.375 -4.2239583,-2.4765625 -4.0833333,-2.5 Z" />
|
||||||
|
<g
|
||||||
|
id="text3338"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:13.33333302px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
|
||||||
|
<path
|
||||||
|
id="path3343"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.83333302px;font-family:Zekton;-inkscape-font-specification:'Zekton Bold'"
|
||||||
|
d="M 13.8736,1.4179352 16.1811,1.4179352 14.740267,-2.297898 11.8911,4.9929351 10.3636,4.9929351 14.155267,-4.5403979 15.3361,-4.5403979 19.149433,4.9929351 17.6111,4.9929351 16.744433,2.8262685 13.310267,2.8262685 13.8736,1.4179352 Z" />
|
||||||
|
<path
|
||||||
|
id="path3345"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.83333302px;font-family:Zekton;-inkscape-font-specification:'Zekton Bold'"
|
||||||
|
d="M 24.13886,5.7512684 Q 24.78886,5.7512684 24.78886,5.1012684 L 24.78886,0.22626858 Q 24.78886,-0.4237314 24.13886,-0.4237314 L 21.972193,-0.4237314 Q 21.322193,-0.4237314 21.322193,0.22626858 L 21.322193,2.9346018 Q 21.322193,3.5846018 21.972193,3.5846018 L 24.355527,3.5846018 24.355527,4.9929351 21.972193,4.9929351 Q 19.91386,4.9929351 19.91386,2.9346018 L 19.91386,0.22626858 Q 19.91386,-1.8320647 21.972193,-1.8320647 L 24.13886,-1.8320647 Q 26.197193,-1.8320647 26.197193,0.22626858 L 26.197193,5.1012684 Q 26.197193,7.1596017 24.13886,7.1596017 L 21.53886,7.1596017 21.53886,5.7512684 24.13886,5.7512684 Z" />
|
||||||
|
<path
|
||||||
|
id="path3347"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.83333302px;font-family:Zekton;-inkscape-font-specification:'Zekton Bold'"
|
||||||
|
d="M 33.018808,3.5846018 33.018808,4.9929351 29.335475,4.9929351 Q 27.277141,4.9929351 27.277141,2.9346018 L 27.277141,0.22626858 Q 27.277141,-1.8320647 29.335475,-1.8320647 L 31.502141,-1.8320647 Q 33.560474,-1.8320647 33.560474,0.22626858 L 33.560474,0.24793524 Q 33.560474,2.2846019 31.502141,2.2846019 L 29.118808,2.2846019 29.118808,0.87626856 31.502141,0.87626856 Q 32.152141,0.87626856 32.152141,0.24793524 L 32.152141,0.22626858 Q 32.152141,-0.4237314 31.502141,-0.4237314 L 29.335475,-0.4237314 Q 28.685475,-0.4237314 28.685475,0.22626858 L 28.685475,2.9346018 Q 28.685475,3.5846018 29.335475,3.5846018 L 33.018808,3.5846018 Z" />
|
||||||
|
<path
|
||||||
|
id="path3349"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.83333302px;font-family:Zekton;-inkscape-font-specification:'Zekton Bold'"
|
||||||
|
d="M 40.817961,4.9929351 39.409627,4.9929351 39.409627,0.22626858 Q 39.409627,-0.4237314 38.759627,-0.4237314 L 35.942961,-0.4237314 35.942961,4.9929351 34.534628,4.9929351 34.534628,-1.8320647 38.759627,-1.8320647 Q 40.817961,-1.8320647 40.817961,0.22626858 L 40.817961,4.9929351 Z" />
|
||||||
|
<path
|
||||||
|
id="path3351"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.83333302px;font-family:Zekton;-inkscape-font-specification:'Zekton Bold'"
|
||||||
|
d="M 41.789576,-1.8320647 42.331242,-1.8320647 42.331242,-4.5403979 43.663742,-4.5403979 43.663742,-1.8320647 44.822909,-1.8320647 44.822909,-0.4237314 43.663742,-0.4237314 43.663742,4.9929351 42.331242,4.9929351 42.331242,-0.4237314 41.789576,-0.4237314 41.789576,-1.8320647 Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -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: 926 B After Width: | Height: | Size: 928 B |