Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02044ae1d0 | ||
|
|
3f99d47bba | ||
|
|
9beadb4aac | ||
|
|
d9fb7a4b42 | ||
|
|
83912ec21f | ||
|
|
6c7232a5ba | ||
|
|
65fc8b5efb | ||
|
|
7a64191955 | ||
|
|
1a413c824e | ||
|
|
e545c9d67b | ||
|
|
8650fef7ae | ||
|
|
5c618869a2 | ||
|
|
1b8d6949e0 | ||
|
|
aabc0a8746 | ||
|
|
9589398834 | ||
|
|
933a715ea5 | ||
|
|
df14e2d569 | ||
|
|
4f8a0247d3 | ||
|
|
85ec5f6b6a | ||
|
|
739b830f47 | ||
|
|
482cff3d3b | ||
|
|
9284ae5032 | ||
|
|
cac6fdc190 | ||
|
|
ca9bf60a1b | ||
|
|
bb7edb8e4d | ||
|
|
0d9c976eb8 | ||
|
|
288d4f75b6 | ||
|
|
73e07eae2d | ||
|
|
8e620caac5 | ||
|
|
0d4e1acf76 | ||
|
|
88c42d58b1 | ||
|
|
5099e904fc | ||
|
|
5476f7c3a5 | ||
|
|
e0bb0bcca3 | ||
|
|
124c3ec29b | ||
|
|
2310320b8a | ||
|
|
67346b4cda | ||
|
|
662ca0152f | ||
|
|
6358528438 | ||
|
|
02f1053d46 | ||
|
|
a44a7dc5f8 | ||
|
|
02d57fdabf | ||
|
|
38f6688930 | ||
|
|
6ca12d0ccd | ||
|
|
acd17ac657 | ||
|
|
5393501f68 | ||
|
|
99e020d66f | ||
|
|
2c74ce8d3e | ||
|
|
3cd2d208b9 | ||
|
|
d0cd30f915 | ||
|
|
010a23aaeb | ||
|
|
c723fe2651 | ||
|
|
95caa58624 | ||
|
|
9089f088b6 | ||
|
|
1aeb4e8326 | ||
|
|
96b9226adb | ||
|
|
7c065f4368 | ||
|
|
a8108b9abf | ||
|
|
c7baa00720 | ||
|
|
5cdf2282f8 | ||
|
|
89221faf60 | ||
|
|
3b70c84c61 | ||
|
|
5b1f4cb584 | ||
|
|
3ee6c680a1 | ||
|
|
fdcf64d5c6 | ||
|
|
6c327ee414 | ||
|
|
b6bdd1486c | ||
|
|
bd5be98d99 | ||
|
|
802e6a4649 | ||
|
|
ae11c01725 | ||
|
|
f0139c55ee | ||
|
|
b3f2e3451e | ||
|
|
906beaac0e | ||
|
|
46f855d1db | ||
|
|
5341d953ff | ||
|
|
bd9a2a0eeb | ||
|
|
4c10954721 | ||
|
|
bbce1e0e0f | ||
|
|
13f064229f | ||
|
|
d3295c5666 | ||
|
|
216793bbb8 | ||
|
|
558c8b0dbf | ||
|
|
e3c65f77df | ||
|
|
227f8f0d2c | ||
|
|
7e0bc39de1 | ||
|
|
c4d3648f73 | ||
|
|
547ab738c2 | ||
|
|
3de9e9aa84 | ||
|
|
01ac4c1e8b | ||
|
|
a0c8849f13 | ||
|
|
2a3dfcb0d0 | ||
|
|
2b3462c33f | ||
|
|
a0b838b2e9 | ||
|
|
90f56c350e | ||
|
|
5ceca41e0f |
@@ -45,7 +45,6 @@ addons:
|
||||
|
||||
install:
|
||||
- nvm install
|
||||
- npm i -g npm@5.6.0
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
|
||||
52
CHANGELOG.md
52
CHANGELOG.md
@@ -4,20 +4,66 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||
|
||||
## [1.1.1] - 2017-02-13
|
||||
|
||||
Firmware: 8.1.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.2)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Sign Agent on OSX resulting in easier installation.
|
||||
- Add per-keymap description field.
|
||||
- Sort keymaps and macros alphabetically within the key action popover.
|
||||
- Add tooltip regarding non-US scancodes.
|
||||
- When deleting a macro, also delete the relevant play macro actions.
|
||||
- Make the reset configuration button persist the reset configuration in Agent-web.
|
||||
- Make Agent able to unbrick bricked modules.
|
||||
- Assign "switch to test keymap" action on all keymaps in the default configuration.
|
||||
- Add keymap descriptions in the default configuration.
|
||||
|
||||
## [1.1.0] - 2017-01-15
|
||||
|
||||
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Only accept device, keymap, and macro names upon editing if their trimmed length is non-zero.
|
||||
- Add diagnostics USB scripts, most notably /packages/usb/{get-i2c-health,set-i2c-baud-rate}.js, some utilizing new device protocol commands and properties. `DEVICEPROTOCOL:MINOR`
|
||||
- Implement the Device -> Upload device configuration feature.
|
||||
- Make update-module-firmware.js more robust and able to recover bricked modules (including the left half) by utilizing the newly added wait-for-kboot-idle.js. `DEVICEPROTOCOL:MINOR`
|
||||
- Add the Agent -> About page containing the version number of Agent.
|
||||
- On the mouse speed section of the key action popover, remove the now incorrect bottom sentence and slightly rephrase the top sentence.
|
||||
- Remove --buspal speed specification argument because it gets disrespected by the firmware anyways.
|
||||
- Fix get-left-firmware-version.js to display the correct firmware version.
|
||||
|
||||
## [1.0.4] - 2017-12-30
|
||||
|
||||
Firmware: 8.0.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Add mouse speed settings.
|
||||
|
||||
## [1.0.3] - 2017-12-28
|
||||
|
||||
Firmware: 8.0.**0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Add LED brightness settings.
|
||||
- Some key actions, for example Left Arrow were displayed as text with modifiers and as icon without modifires. Now, they're always displayed as icons.
|
||||
- Clean up firmware update console messages a bit.
|
||||
- Remove the add keymap button because this feature is not only useless but confusing until it gets reimplemented.
|
||||
- Explicitly mention on the macro tab of the key action popover that macro playback is not implemented yet.
|
||||
- Downgrade to firmware 8.0.0 because the left I2C watchdog of firmware 8.0.1 is not proven yet.
|
||||
|
||||
## [1.0.2] - 2017-12-25
|
||||
|
||||
Firmware: [**8.0.1**](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/8.0.1) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
Firmware: **8.0.1**[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.1)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Fix firmware upgrade on Linux.
|
||||
|
||||
## [1.0.1] - 2017-12-22
|
||||
|
||||
Firmware: [7.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
Firmware: 7.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Fix Linux privilege escalation when udev rules aren't set up.
|
||||
|
||||
## [1.0.0] - 2017-12-14
|
||||
|
||||
Firmware: [**7**.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
Firmware: **7**.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- First release
|
||||
|
||||
22
README.md
22
README.md
@@ -13,23 +13,29 @@ It's worth mentioning that Agent has two builds.
|
||||
|
||||
The **electron build** is the desktop application which is meant to be used if you have an actual UHK at hand. It starts with an opening screen which detects your UHK. You cannot get past this screen without connecting a UHK via USB.
|
||||
|
||||
The **web build** is meant to be used for demonstation purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
|
||||
The **web build** is meant to be used for demonstration purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browsers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
|
||||
|
||||
The two builds share code as much as possible.
|
||||
|
||||
## Building the electron application
|
||||
|
||||
First up, make sure that node >=8.1.x and npm >=5.1.x are installed on your system. Next up:
|
||||
### Step 1: Build Dependencies
|
||||
|
||||
You'll need Node.js LTS. Use your OS package manager to install it. [Check the NodeJS site for more info.](https://nodejs.org/en/download/package-manager/ "Installing Node.js via package manager") Mac OS users can simply `brew install node` to get both. Should you need multiple Node.js versions on the same computer, use Node Version Manager for [Mac/Linux](https://github.com/creationix/nvm) or for [Windows](https://github.com/coreybutler/nvm-windows)
|
||||
|
||||
You'll also need `libusb`.
|
||||
On debian-based linux distros, `apt-get install libusb-dev libudev-dev g++` is sufficient.
|
||||
On Mac OS, use `brew install libusb libusb-compat`.
|
||||
For everyone else, use the appropriate package manager for your OS.
|
||||
|
||||
### Step 2: Build Environment
|
||||
|
||||
```
|
||||
# Execute the following line on Linux. Use relevant package manager and package names on non-Debian based distros.
|
||||
apt-get install libusb-dev libudev-dev g++
|
||||
|
||||
git clone git@github.com:UltimateHackingKeyboard/agent.git
|
||||
cd agent
|
||||
npm install
|
||||
npm run build:electron
|
||||
npm run electron
|
||||
npm install # to install Node dependencies
|
||||
npm run build:electron # to build the agent
|
||||
npm run electron # to run the newly built agent
|
||||
```
|
||||
|
||||
At this point, Agent should be running on your machine.
|
||||
|
||||
@@ -18,7 +18,6 @@ shallow_clone: true
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- npm i -g npm@5.6.0
|
||||
- choco install chromium
|
||||
- set CI=true
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
|
||||
394
package-lock.json
generated
394
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,11 @@
|
||||
"private": true,
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"main": "electron/dist/electron-main.js",
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.1",
|
||||
"firmwareVersion": "8.1.2",
|
||||
"deviceProtocolVersion": "4.2.0",
|
||||
"userConfigVersion": "4.0.0",
|
||||
"hardwareConfigVersion": "1.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -32,7 +36,7 @@
|
||||
"decompress": "4.2.0",
|
||||
"decompress-tarbz2": "^4.1.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "1.7.5",
|
||||
"electron": "1.7.11",
|
||||
"electron-builder": "19.45.5",
|
||||
"electron-debug": "1.4.0",
|
||||
"electron-devtools-installer": "2.2.0",
|
||||
@@ -86,7 +90,6 @@
|
||||
"server:web": "lerna exec --scope uhk-web npm start",
|
||||
"server:electron": "lerna exec --scope uhk-web npm run server:renderer",
|
||||
"electron": "lerna exec --scope uhk-agent npm start",
|
||||
"electron:auto-write-config": "lerna exec --scope uhk-agent npm run auto-write-config",
|
||||
"standard-version": "standard-version",
|
||||
"pack": "node ./scripts/release.js",
|
||||
"sprites": "node ./scripts/generate-svg-sprites",
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./dist/electron-main.js",
|
||||
"auto-write-config": "electron ./dist/electron-main.js --auto-write-config",
|
||||
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
|
||||
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
||||
"install:build-deps": "cd ./dist && npm i",
|
||||
|
||||
@@ -21,8 +21,7 @@ import * as isDev from 'electron-is-dev';
|
||||
import { CommandLineInputs } from './models/command-line-inputs';
|
||||
|
||||
const optionDefinitions = [
|
||||
{name: 'addons', type: Boolean},
|
||||
{name: 'auto-write-config', type: Boolean}
|
||||
{name: 'addons', type: Boolean}
|
||||
];
|
||||
|
||||
const options: CommandLineInputs = commandLineArgs(optionDefinitions);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export interface CommandLineInputs {
|
||||
addons?: boolean;
|
||||
'auto-write-config'?: boolean;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,5 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-hid": "0.5.7"
|
||||
},
|
||||
"firmwareVersion": "8.0.1",
|
||||
"deviceProtocolVersion": "4.0.0",
|
||||
"userConfigVersion": "4.0.0",
|
||||
"hardwareConfigVersion": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { BrowserWindow, ipcMain, shell } from 'electron';
|
||||
import { UhkHidDevice } from 'uhk-usb';
|
||||
import { readFile } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
@@ -18,53 +16,30 @@ export class AppService extends MainServiceBase {
|
||||
|
||||
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
|
||||
ipcMain.on(IpcEvents.app.exit, this.exit.bind(this));
|
||||
ipcMain.on(IpcEvents.app.openUrl, this.openUrl.bind(this));
|
||||
logService.info('[AppService] init success');
|
||||
}
|
||||
|
||||
private async handleAppStartInfo(event: Electron.Event) {
|
||||
this.logService.info('[AppService] getAppStartInfo');
|
||||
|
||||
const packageJson = await this.getPackageJson();
|
||||
|
||||
const response: AppStartInfo = {
|
||||
commandLineArgs: {
|
||||
addons: this.options.addons || false,
|
||||
autoWriteConfig: this.options['auto-write-config'] || false
|
||||
addons: this.options.addons || false
|
||||
},
|
||||
deviceConnected: this.uhkHidDeviceService.deviceConnected(),
|
||||
hasPermission: this.uhkHidDeviceService.hasPermission(),
|
||||
agentVersionInfo: {
|
||||
version: packageJson.version,
|
||||
firmwareVersion: packageJson.firmwareVersion,
|
||||
deviceProtocolVersion: packageJson.deviceProtocolVersion,
|
||||
moduleProtocolVersion: packageJson.moduleProtocolVersion,
|
||||
userConfigVersion: packageJson.userConfigVersion,
|
||||
hardwareConfigVersion: packageJson.hardwareConfigVersion
|
||||
}
|
||||
hasPermission: this.uhkHidDeviceService.hasPermission()
|
||||
};
|
||||
this.logService.info('[AppService] getAppStartInfo response:', response);
|
||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the package.json that delivered with the bundle. Do not use require('package.json')
|
||||
* because the deploy process change the package.json after the build
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
private async getPackageJson(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
readFile(join(__dirname, 'package.json'), {encoding: 'utf-8'}, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private exit() {
|
||||
this.logService.info('[AppService] exit');
|
||||
this.win.close();
|
||||
}
|
||||
|
||||
private openUrl(event: Electron.Event, urls: Array<string>) {
|
||||
shell.openExternal(urls[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SwitchLayerAction } from './switch-layer-action';
|
||||
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
|
||||
import { MouseAction } from './mouse-action';
|
||||
import { PlayMacroAction } from './play-macro-action';
|
||||
import { NoneAction } from './none-action';
|
||||
|
||||
export class Helper {
|
||||
|
||||
@@ -77,6 +78,8 @@ export class Helper {
|
||||
return new MouseAction().fromJsonObject(keyAction);
|
||||
case keyActionType.PlayMacroAction:
|
||||
return new PlayMacroAction().fromJsonObject(keyAction, macros);
|
||||
case keyActionType.NoneAction:
|
||||
return new NoneAction();
|
||||
default:
|
||||
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { CommandLineArgs } from './command-line-args';
|
||||
import { VersionInformation } from './version-information';
|
||||
|
||||
export interface AppStartInfo {
|
||||
commandLineArgs: CommandLineArgs;
|
||||
deviceConnected: boolean;
|
||||
hasPermission: boolean;
|
||||
/**
|
||||
* This property contains the version information of the deployed agent components
|
||||
*/
|
||||
agentVersionInfo: VersionInformation;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export interface CommandLineArgs {
|
||||
addons: boolean;
|
||||
autoWriteConfig: boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export namespace Constants {
|
||||
export const VENDOR_ID = 0x1D50;
|
||||
export const PRODUCT_ID = 0x6122;
|
||||
export const MAX_PAYLOAD_SIZE = 64;
|
||||
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { IpcEvents } from './ipcEvents';
|
||||
export * from './log';
|
||||
export * from './constants';
|
||||
|
||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||
export function camelCaseToSentence(camelCasedText: string): string {
|
||||
|
||||
@@ -3,6 +3,7 @@ class App {
|
||||
public static readonly getAppStartInfo = 'app-get-start-info';
|
||||
public static readonly getAppStartInfoReply = 'app-get-start-info-reply';
|
||||
public static readonly exit = 'app-exit';
|
||||
public static readonly openUrl = 'open-url';
|
||||
}
|
||||
|
||||
class AutoUpdate {
|
||||
|
||||
@@ -39,7 +39,8 @@ export enum ConfigBufferId {
|
||||
export enum DevicePropertyIds {
|
||||
DeviceProtocolVersion = 0,
|
||||
ProtocolVersions = 1,
|
||||
ConfigSizes = 2
|
||||
ConfigSizes = 2,
|
||||
CurrentKbootCommand = 3
|
||||
}
|
||||
|
||||
export enum EnumerationModes {
|
||||
|
||||
@@ -5,8 +5,13 @@ import * as fs from 'fs';
|
||||
import { UhkBlhost } from './uhk-blhost';
|
||||
import { UhkHidDevice } from './uhk-hid-device';
|
||||
import { snooze } from './util';
|
||||
import { convertBufferToIntArray, getTransferBuffers, DevicePropertyIds, UsbCommand, ConfigBufferId
|
||||
} from '../index';
|
||||
import {
|
||||
convertBufferToIntArray,
|
||||
getTransferBuffers,
|
||||
DevicePropertyIds,
|
||||
UsbCommand,
|
||||
ConfigBufferId
|
||||
} from '../index';
|
||||
import { LoadConfigurationsResult } from './models/load-configurations-result';
|
||||
|
||||
export class UhkOperations {
|
||||
@@ -26,14 +31,14 @@ export class UhkOperations {
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-erase-region', '0xc000', '475136']);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-image', `"${firmwarePath}"`]);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'reset']);
|
||||
this.logService.debug('[UhkOperations] End flashing right firmware');
|
||||
this.logService.debug('[UhkOperations] Right firmware successfully flashed');
|
||||
}
|
||||
|
||||
public async updateLeftModule(firmwarePath = this.getLeftModuleFirmwarePath()) {
|
||||
this.logService.debug('[UhkOperations] Start flashing left module firmware');
|
||||
|
||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`];
|
||||
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf},100k`];
|
||||
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf}`];
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
@@ -42,6 +47,13 @@ export class UhkOperations {
|
||||
await snooze(1000);
|
||||
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
||||
this.device.close();
|
||||
|
||||
const leftModuleBricked = await this.waitForKbootIdle();
|
||||
if (!leftModuleBricked) {
|
||||
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
||||
@@ -58,7 +70,8 @@ export class UhkOperations {
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.idle);
|
||||
this.device.close();
|
||||
|
||||
this.logService.debug('[UhkOperations] End flashing left module firmware');
|
||||
this.logService.debug('[UhkOperations] Left firmware successfully flashed');
|
||||
this.logService.debug('[UhkOperations] Both left and right firmwares successfully flashed');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,6 +173,26 @@ export class UhkOperations {
|
||||
}
|
||||
}
|
||||
|
||||
public async waitForKbootIdle(): Promise<boolean> {
|
||||
const timeoutTime = new Date(new Date().getTime() + 30000);
|
||||
|
||||
while (new Date() < timeoutTime) {
|
||||
const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, DevicePropertyIds.CurrentKbootCommand]));
|
||||
this.device.close();
|
||||
|
||||
if (buffer[1] === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-line-length
|
||||
this.logService.info('[DeviceOperation] Cannot ping the bootloader. Please reconnect the left keyboard half. It probably needs several tries, so keep reconnecting until you see this message.');
|
||||
|
||||
await snooze(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
|
||||
* @param {string} json - UserConfiguration in JSON format
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
||||
throw err;
|
||||
} else {
|
||||
if (logService) {
|
||||
logService.error(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"../node_modules/nouislider/distribute/nouislider.min.css",
|
||||
"styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||
"../node_modules/select2/dist/js/select2.full.js"
|
||||
"../node_modules/select2/dist/js/select2.full.js",
|
||||
"../node_modules/nouislider/distribute/nouislider.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
|
||||
2751
packages/uhk-web/package-lock.json
generated
2751
packages/uhk-web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,7 @@
|
||||
"@types/jasmine": "2.5.53",
|
||||
"@types/jasminewd2": "2.0.2",
|
||||
"@types/jquery": "3.2.9",
|
||||
"@types/lodash-es": "4.17.0",
|
||||
"@types/node-hid": "0.5.2",
|
||||
"@types/usb": "1.1.3",
|
||||
"angular-confirmation-popover": "3.2.0",
|
||||
@@ -67,11 +68,13 @@
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"less-loader": "4.0.5",
|
||||
"lodash": "4.17.4",
|
||||
"lodash-es": "4.17.4",
|
||||
"ng2-dragula": "1.5.0",
|
||||
"ng2-nouislider": "^1.7.6",
|
||||
"ng2-select2": "1.0.0-beta.10",
|
||||
"ngrx-store-freeze": "0.1.9",
|
||||
"node-hid": "0.5.4",
|
||||
"nouislider": "^10.1.0",
|
||||
"postcss-loader": "1.3.3",
|
||||
"postcss-url": "5.1.2",
|
||||
"protractor": "5.1.2",
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
<div id="main-content" class="main-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<div class="github-fork-ribbon" *ngIf="!(runningInElectron$ | async)">
|
||||
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
|
||||
</div>
|
||||
<notifier-container></notifier-container>
|
||||
<progress-button class="save-to-keyboard-button"
|
||||
*ngIf="(saveToKeyboardState$ | async).showButton"
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
/* GitHub ribbon */
|
||||
.github-fork-ribbon {
|
||||
background-color: #a00;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
position: fixed;
|
||||
right: -50px;
|
||||
bottom: 40px;
|
||||
z-index: 2000;
|
||||
/* stylelint-disable indentation */
|
||||
-webkit-transform: rotate(-45deg);
|
||||
-moz-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
-o-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
-webkit-box-shadow: 0 0 10px #888;
|
||||
-moz-box-shadow: 0 0 10px #888;
|
||||
box-shadow: 0 0 10px #888;
|
||||
/* stylelint-enable indentation */
|
||||
|
||||
a {
|
||||
border: 1px solid #faa;
|
||||
color: #fff;
|
||||
display: block;
|
||||
font: bold 81.25% 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
margin: 1px 0;
|
||||
padding: 10px 50px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 5px #444;
|
||||
}
|
||||
}
|
||||
|
||||
main-app {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
|
||||
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
|
||||
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
|
||||
import { MainPage } from './pages/main-page/main.page';
|
||||
import { settingsRoutes } from './components/settings/settings.routes';
|
||||
import { agentRoutes } from './components/agent/agent.routes';
|
||||
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
|
||||
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
|
||||
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||
@@ -42,7 +42,7 @@ const appRoutes: Routes = [
|
||||
...keymapRoutes,
|
||||
...macroRoutes,
|
||||
...addOnRoutes,
|
||||
...settingsRoutes
|
||||
...agentRoutes
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="row">
|
||||
<h1 class="col-xs-12 pane-title">
|
||||
<i class="uhk-icon uhk-icon-agent-icon"></i>
|
||||
<span>About</span>
|
||||
</h1>
|
||||
<div class="col-xs-12">
|
||||
<div class="agent-version">Agent version: <span class="text-bold">{{version}}</span></div>
|
||||
<div><a class="link-github" (click)="openAgentGitHubPage($event)">Agent on GitHub</a></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
:host {
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.agent {
|
||||
&-version {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link-github {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Constants } from 'uhk-common';
|
||||
|
||||
import { AppState } from '../../../store';
|
||||
import { getVersions } from '../../../util';
|
||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||
|
||||
@Component({
|
||||
selector: 'about-page',
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['./about.component.scss'],
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class AboutComponent {
|
||||
version: string = getVersions().version;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
}
|
||||
|
||||
openAgentGitHubPage(event) {
|
||||
event.preventDefault();
|
||||
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
|
||||
}
|
||||
}
|
||||
15
packages/uhk-web/src/app/components/agent/agent.routes.ts
Normal file
15
packages/uhk-web/src/app/components/agent/agent.routes.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { AboutComponent } from './about/about.component';
|
||||
|
||||
export const agentRoutes: Routes = [
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent
|
||||
}
|
||||
];
|
||||
3
packages/uhk-web/src/app/components/agent/index.ts
Normal file
3
packages/uhk-web/src/app/components/agent/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './agent.routes';
|
||||
export * from './about/about.component';
|
||||
export * from './settings/settings.component';
|
||||
@@ -2,13 +2,14 @@ import { Component } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../store';
|
||||
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../../store';
|
||||
import {
|
||||
CheckForUpdateNowAction,
|
||||
ToggleCheckForUpdateOnStartupAction,
|
||||
TogglePreReleaseFlagAction
|
||||
} from '../../store/actions/auto-update-settings';
|
||||
import { AutoUpdateSettings } from '../../models/auto-update-settings';
|
||||
} from '../../../store/actions/auto-update-settings';
|
||||
import { AutoUpdateSettings } from '../../../models/auto-update-settings';
|
||||
import { getVersions } from '../../../util';
|
||||
|
||||
@Component({
|
||||
selector: 'settings',
|
||||
@@ -19,8 +20,7 @@ import { AutoUpdateSettings } from '../../models/auto-update-settings';
|
||||
}
|
||||
})
|
||||
export class SettingsComponent {
|
||||
// TODO: From where do we get the version number? The electron gives back in main process, but the web...
|
||||
version = '1.0.0';
|
||||
version: string = getVersions().version;
|
||||
autoUpdateSettings$: Observable<AutoUpdateSettings>;
|
||||
checkingForUpdate$: Observable<boolean>;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Version:</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static">{{version}}</p>
|
||||
<p>{{version}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format.
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-default"
|
||||
>Upload device configuration
|
||||
</button>
|
||||
<label class="btn btn-default btn-file">
|
||||
Upload device configuration
|
||||
<input type="file"
|
||||
(change)="changeFile($event)">
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-danger"
|
||||
|
||||
@@ -3,7 +3,11 @@ import { Store } from '@ngrx/store';
|
||||
|
||||
import { AppState } from '../../../store';
|
||||
import { ResetUserConfigurationAction } from '../../../store/actions/device';
|
||||
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from '../../../store/actions/user-config';
|
||||
import {
|
||||
LoadUserConfigurationFromFileAction,
|
||||
SaveUserConfigInBinaryFileAction,
|
||||
SaveUserConfigInJsonFileAction
|
||||
} from '../../../store/actions/user-config';
|
||||
|
||||
@Component({
|
||||
selector: 'device-settings',
|
||||
@@ -29,4 +33,17 @@ export class DeviceConfigurationComponent {
|
||||
saveConfigurationInBINFormat() {
|
||||
this.store.dispatch(new SaveUserConfigInBinaryFileAction());
|
||||
}
|
||||
|
||||
changeFile(event): void {
|
||||
const files = event.srcElement.files;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = function () {
|
||||
const arrayBuffer = new Uint8Array(fileReader.result);
|
||||
this.store.dispatch(new LoadUserConfigurationFromFileAction({
|
||||
filename: event.srcElement.value,
|
||||
data: Array.from(arrayBuffer)
|
||||
}));
|
||||
}.bind(this);
|
||||
fileReader.readAsArrayBuffer(files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Routes } from '@angular/router';
|
||||
import { DeviceConfigurationComponent } from './configuration/device-configuration.component';
|
||||
import { DeviceFirmwareComponent } from './firmware/device-firmware.component';
|
||||
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
|
||||
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
|
||||
|
||||
export const deviceRoutes: Routes = [
|
||||
{
|
||||
@@ -21,6 +22,10 @@ export const deviceRoutes: Routes = [
|
||||
path: 'mouse-speed',
|
||||
component: MouseSpeedComponent
|
||||
},
|
||||
{
|
||||
path: 'led-brightness',
|
||||
component: LEDBrightnessComponent
|
||||
},
|
||||
{
|
||||
path: 'firmware',
|
||||
component: DeviceFirmwareComponent
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './configuration/device-configuration.component';
|
||||
export * from './firmware/device-firmware.component';
|
||||
export * from './mouse-speed/mouse-speed.component';
|
||||
export * from './led-brightness/led-brightness.component';
|
||||
export * from './device.routes';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<h1>
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>LED brightness</span>
|
||||
</h1>
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
label="LED display icon and layer texts brightness"
|
||||
[min]="0"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
[pips]="sliderPips"
|
||||
[(ngModel)]="iconsAndLayerTextsBrightness"
|
||||
(ngModelChange)="onSetPropertyValue('iconsAndLayerTextsBrightness', $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
label="LED display alphanumeric segments brightness"
|
||||
[min]="0"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
[pips]="sliderPips"
|
||||
[(ngModel)]="alphanumericSegmentsBrightness"
|
||||
(ngModelChange)="onSetPropertyValue('alphanumericSegmentsBrightness', $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
label="Key backlight brightness"
|
||||
[min]="0"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
[pips]="sliderPips"
|
||||
[(ngModel)]="keyBacklightBrightness"
|
||||
(ngModelChange)="onSetPropertyValue('keyBacklightBrightness', $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
:host {
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.led-setting {
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { AfterViewInit, Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState, getUserConfiguration } from '../../../store';
|
||||
import { SetUserConfigurationValueAction } from '../../../store/actions/user-config';
|
||||
import { SliderPips } from '../../slider-wrapper/slider-wrapper.component';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { UserConfiguration } from 'uhk-common';
|
||||
|
||||
@Component({
|
||||
selector: 'device-led-brightness',
|
||||
templateUrl: './led-brightness.component.html',
|
||||
styleUrls: ['./led-brightness.component.scss'],
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class LEDBrightnessComponent implements OnInit, OnDestroy {
|
||||
public iconsAndLayerTextsBrightness: number = 0;
|
||||
public alphanumericSegmentsBrightness: number = 0;
|
||||
public keyBacklightBrightness: number = 0;
|
||||
public sliderPips: SliderPips = {
|
||||
mode: 'positions',
|
||||
values: [0, 50, 100],
|
||||
density: 6,
|
||||
stepped: true
|
||||
};
|
||||
|
||||
private userConfig$: Store<UserConfiguration>;
|
||||
private userConfigSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.userConfig$ = this.store.select(getUserConfiguration);
|
||||
this.userConfigSubscription = this.userConfig$.subscribe(config => {
|
||||
this.iconsAndLayerTextsBrightness = config.iconsAndLayerTextsBrightness;
|
||||
this.alphanumericSegmentsBrightness = config.alphanumericSegmentsBrightness;
|
||||
this.keyBacklightBrightness = config.keyBacklightBrightness;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.userConfigSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onSetPropertyValue(propertyName: string, value: number): void {
|
||||
this.store.dispatch(new SetUserConfigurationValueAction({
|
||||
propertyName,
|
||||
value
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,41 @@
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>Mouse speed</span>
|
||||
</h1>
|
||||
<p>
|
||||
Coming soon ...
|
||||
</p>
|
||||
<h3>Mouse pointer speed</h3>
|
||||
<div class="row mouse-speed-setting" *ngFor="let prop of moveProps">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
[label]="prop.title"
|
||||
[tooltip]="prop.tooltip"
|
||||
[min]="moveSettings.min"
|
||||
[max]="moveSettings.max"
|
||||
[step]="moveSettings.step"
|
||||
[pips]="sliderPips"
|
||||
[valueUnit]="prop.valueUnit"
|
||||
[(ngModel)]="prop.value"
|
||||
(ngModelChange)="onSetPropertyValue(prop.prop, $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Mouse scroll speed</h3>
|
||||
<div class="row mouse-speed-setting" *ngFor="let prop of scrollProps">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
[label]="prop.title"
|
||||
[tooltip]="prop.tooltip"
|
||||
[min]="scrollSettings.min"
|
||||
[max]="scrollSettings.max"
|
||||
[step]="scrollSettings.step"
|
||||
[pips]="sliderPips"
|
||||
[valueUnit]="prop.valueUnit"
|
||||
[(ngModel)]="prop.value"
|
||||
(ngModelChange)="onSetPropertyValue(prop.prop, $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger mouse-speed-reset-button"
|
||||
mwlConfirmationPopover
|
||||
title="Are you sure?"
|
||||
placement="top"
|
||||
confirmText="Yes"
|
||||
cancelText="No"
|
||||
(confirm)="resetToDefault()">Reset speeds to default
|
||||
</button>
|
||||
@@ -4,4 +4,25 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
|
||||
icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.mouse-speed-reset-button {
|
||||
display: block;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.mouse-speed-setting {
|
||||
margin-bottom: 6rem;
|
||||
|
||||
+ h3 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState, getUserConfiguration } from '../../../store';
|
||||
import { SetUserConfigurationValueAction } from '../../../store/actions/user-config';
|
||||
import { DefaultUserConfigurationService } from '../../../services/default-user-configuration.service';
|
||||
import { SliderPips, SliderProps } from '../../slider-wrapper/slider-wrapper.component';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { UserConfiguration } from 'uhk-common';
|
||||
import { ResetMouseSpeedSettingsAction } from '../../../store/actions/device';
|
||||
|
||||
const MOUSE_MOVE_VALUE_MULTIPLIER = 25;
|
||||
|
||||
@Component({
|
||||
selector: 'device-mouse-speed',
|
||||
@@ -8,5 +18,131 @@ import { Component } from '@angular/core';
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class MouseSpeedComponent {
|
||||
export class MouseSpeedComponent implements OnInit, OnDestroy {
|
||||
public moveProps = [
|
||||
{
|
||||
prop: 'mouseMoveInitialSpeed',
|
||||
title: 'Initial speed',
|
||||
tooltip: 'When mouse movement begins, this is the starting speed.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveBaseSpeed',
|
||||
title: 'Base speed',
|
||||
tooltip: 'This speed is reached after the initial moving speed sufficiently ramps up.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveAcceleration',
|
||||
title: 'Acceleration',
|
||||
tooltip: 'The rate of acceleration from the initial movement speed to the base speed.',
|
||||
valueUnit: 'px/s²',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveDeceleratedSpeed',
|
||||
title: 'Decelerated speed',
|
||||
tooltip: 'This speed is used while moving with the <i>decelerate key</i> pressed.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveAcceleratedSpeed',
|
||||
title: 'Accelerated speed',
|
||||
tooltip: 'This speed is used while moving with the <i>accelerate key</i> pressed.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
}
|
||||
];
|
||||
|
||||
public scrollProps = [
|
||||
{
|
||||
prop: 'mouseScrollInitialSpeed',
|
||||
title: 'Initial speed',
|
||||
tooltip: 'When mouse scrolling begins, this is the starting speed.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollBaseSpeed',
|
||||
title: 'Base speed',
|
||||
tooltip: 'This speed is reached after the initial scrolling speed sufficiently ramps up.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollAcceleration',
|
||||
title: 'Acceleration',
|
||||
tooltip: 'The rate of acceleration from the initial scrolling speed to the base speed.',
|
||||
valueUnit: 'pulse/s²',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollDeceleratedSpeed',
|
||||
title: 'Decelerated speed',
|
||||
tooltip: 'This speed is used while scrolling with the <i>decelerate key</i> pressed.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollAcceleratedSpeed',
|
||||
title: 'Accelerated speed',
|
||||
tooltip: 'This speed is used while scrolling with the <i>accelerate key</i> pressed.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
}
|
||||
];
|
||||
|
||||
public sliderPips: SliderPips = {
|
||||
mode: 'positions',
|
||||
values: [0, 50, 100],
|
||||
density: 6,
|
||||
stepped: true
|
||||
};
|
||||
|
||||
public moveSettings: SliderProps = {
|
||||
min: MOUSE_MOVE_VALUE_MULTIPLIER,
|
||||
max: 6375,
|
||||
step: MOUSE_MOVE_VALUE_MULTIPLIER
|
||||
};
|
||||
|
||||
public scrollSettings: SliderProps = {
|
||||
min: 1,
|
||||
max: 255,
|
||||
step: 1
|
||||
};
|
||||
|
||||
private userConfig$: Store<UserConfiguration>;
|
||||
private userConfigSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>, private defaultUserConfigurationService: DefaultUserConfigurationService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userConfig$ = this.store.select(getUserConfiguration);
|
||||
this.userConfigSubscription = this.userConfig$.subscribe(config => {
|
||||
this.moveProps.forEach(moveProp => {
|
||||
moveProp.value = config[moveProp.prop] * MOUSE_MOVE_VALUE_MULTIPLIER || 0;
|
||||
});
|
||||
this.scrollProps.forEach(scrollProp => {
|
||||
scrollProp.value = config[scrollProp.prop] || 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.userConfigSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onSetPropertyValue(propertyName: string, value: number): void {
|
||||
this.store.dispatch(new SetUserConfigurationValueAction({
|
||||
propertyName,
|
||||
value: propertyName.indexOf('mouseMove') !== -1 ? value / MOUSE_MOVE_VALUE_MULTIPLIER : value
|
||||
}));
|
||||
}
|
||||
|
||||
resetToDefault() {
|
||||
this.store.dispatch(new ResetMouseSpeedSettingsAction());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="text-center">
|
||||
<span *ngIf="showPlaceholder"
|
||||
class="placeholder">
|
||||
<span (click)="editText()">{{ placeholder }}</span>
|
||||
</span>
|
||||
|
||||
<span *ngIf="showText"
|
||||
class="editable">
|
||||
<span (click)="editText()"
|
||||
[innerHtml]="displayText"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="editing">
|
||||
<textarea class="text-editor"
|
||||
[(ngModel)]="text"
|
||||
autofocus
|
||||
(keydown.control.enter)="keydownEnter()"
|
||||
(keydown.alt.enter)="keydownEnter()"></textarea>
|
||||
<div class="pull-right buttons">
|
||||
<button class="btn btn-danger"
|
||||
(click)="cancelEditText()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary"
|
||||
(click)="saveText()"
|
||||
[disabled]="isSaveDisabled">
|
||||
Update description
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
:host {
|
||||
margin-top: 0.5em;
|
||||
|
||||
span.placeholder {
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
|
||||
.glyphicon {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
span.editable,
|
||||
span.placeholder {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea.text-editor {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'editable-text',
|
||||
templateUrl: './editable-text.component.html',
|
||||
styleUrls: ['./editable-text.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditableTextComponent), multi: true}
|
||||
]
|
||||
})
|
||||
export class EditableTextComponent implements ControlValueAccessor {
|
||||
|
||||
@Input() placeholder = 'No editable content';
|
||||
text: string;
|
||||
originalText: string;
|
||||
editing = false;
|
||||
|
||||
get isSaveDisabled(): boolean {
|
||||
return !this.text || this.text.trim().length === 0;
|
||||
}
|
||||
|
||||
get displayText(): string {
|
||||
return this.text && this.text.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
if (this.text === obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = obj;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.textChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
saveText(): void {
|
||||
this.originalText = null;
|
||||
this.editing = false;
|
||||
this.textChange(this.text);
|
||||
}
|
||||
|
||||
editText(): void {
|
||||
this.originalText = this.text;
|
||||
this.editing = true;
|
||||
}
|
||||
|
||||
cancelEditText(): void {
|
||||
this.text = this.originalText;
|
||||
this.editing = false;
|
||||
}
|
||||
|
||||
keydownEnter(): void {
|
||||
if (this.isSaveDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saveText();
|
||||
}
|
||||
|
||||
get showPlaceholder(): boolean {
|
||||
return !this.editing && !this.text;
|
||||
}
|
||||
|
||||
get showText(): boolean {
|
||||
return !this.editing && !!this.text;
|
||||
}
|
||||
|
||||
private textChange: any = () => {
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,11 @@
|
||||
[selectedKey]="selectedKey"
|
||||
[selected]="selectedKey?.layerId === index"
|
||||
[keyboardLayout]="keyboardLayout"
|
||||
[description]="description"
|
||||
[showDescription]="true"
|
||||
(keyClick)="keyClick.emit($event)"
|
||||
(keyHover)="keyHover.emit($event)"
|
||||
(capture)="capture.emit($event)"
|
||||
(descriptionChanged)="descriptionChanged.emit($event)"
|
||||
>
|
||||
</svg-keyboard>
|
||||
|
||||
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 809 B |
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Layer } from 'uhk-common';
|
||||
|
||||
@@ -81,11 +81,14 @@ export class KeyboardSliderComponent implements OnChanges {
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
layerAnimationState: AnimationKeyboard[];
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['layers']) {
|
||||
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
(downloadClick)="downloadKeymap()"></keymap-header>
|
||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||
[halvesSplit]="keyboardSplit"
|
||||
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap>
|
||||
[keyboardLayout]="keyboardLayout$ | async"
|
||||
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!(keymap$ | async)" class="not-found">
|
||||
|
||||
@@ -18,6 +18,8 @@ import { AppState, getKeyboardLayout } from '../../../store';
|
||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { KeymapActions } from '../../../store/actions';
|
||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||
|
||||
@Component({
|
||||
selector: 'keymap-edit',
|
||||
@@ -64,7 +66,7 @@ export class KeymapEditComponent {
|
||||
const keymap = latest[0];
|
||||
const exportableJSON = latest[1];
|
||||
const fileName = keymap.name + '_keymap.json';
|
||||
saveAs(new Blob([exportableJSON], { type: 'application/json' }), fileName);
|
||||
saveAs(new Blob([exportableJSON], {type: 'application/json'}), fileName);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +75,10 @@ export class KeymapEditComponent {
|
||||
this.keyboardSplit = !this.keyboardSplit;
|
||||
}
|
||||
|
||||
descriptionChanged(event: ChangeKeymapDescription): void {
|
||||
this.store.dispatch(new KeymapActions.EditDescriptionAction(event));
|
||||
}
|
||||
|
||||
private toExportableJSON(keymap: Keymap): Observable<any> {
|
||||
return this.store
|
||||
.let(getUserConfiguration())
|
||||
|
||||
@@ -74,7 +74,7 @@ export class KeymapHeaderComponent implements OnChanges {
|
||||
}
|
||||
|
||||
editKeymapName(name: string) {
|
||||
if (name.length === 0) {
|
||||
if (!util.isValidName(name)) {
|
||||
this.setName();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!macro" class="not-found">
|
||||
There is no macro with id {{ route.params.select('id') | async }}.
|
||||
There is no macro with id {{ macroId }}.
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
import 'rxjs/add/operator/pluck';
|
||||
|
||||
import { MacroActions } from '../../../store/actions';
|
||||
import { AppState } from '../../../store/index';
|
||||
import { AppState } from '../../../store';
|
||||
import { getMacro } from '../../../store/reducers/user-configuration';
|
||||
|
||||
@Component({
|
||||
@@ -21,13 +21,17 @@ import { getMacro } from '../../../store/reducers/user-configuration';
|
||||
export class MacroEditComponent implements OnDestroy {
|
||||
macro: Macro;
|
||||
isNew: boolean;
|
||||
macroId: number;
|
||||
|
||||
private subscription: Subscription;
|
||||
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
||||
this.subscription = route
|
||||
.params
|
||||
.pluck<{}, string>('id')
|
||||
.switchMap((id: string) => store.let(getMacro(+id)))
|
||||
.switchMap((id: string) => {
|
||||
this.macroId = +id;
|
||||
return store.let(getMacro(this.macroId));
|
||||
})
|
||||
.subscribe((macro: Macro) => {
|
||||
this.macro = macro;
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ export class MacroHeaderComponent implements AfterViewInit, OnChanges {
|
||||
}
|
||||
|
||||
editMacroName(name: string) {
|
||||
if (name.length === 0) {
|
||||
if (!util.isValidName(name)) {
|
||||
this.setName();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
[width]="200"
|
||||
[options]="options"
|
||||
></select2>
|
||||
<icon name="question-circle"
|
||||
data-toggle="tooltip"
|
||||
title="Looking for a non-US character, but can't find it? Please note that USB keyboards send scancodes, not characters to your computer. Then your operating system translates the scancodes to characters according to your current OS keyboard layout. This means that you have to select the US-equivalent character of the desired key in Agent."
|
||||
data-placement="bottom"></icon>
|
||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||
</div>
|
||||
<div class="modifier-options">
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
icon {
|
||||
display: inline-block;
|
||||
};
|
||||
}
|
||||
|
||||
.modifier-options {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<span> No macros are available to choose from. Create a macro first! </span>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="macroOptions.length > 0">
|
||||
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they don't have any effect.</i></p>
|
||||
<div class="macro-selector">
|
||||
<b> Play macro: </b>
|
||||
<select2 [data]="macroOptions" [value]="macroOptions[selectedMacroIndex].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</div>
|
||||
<div *ngSwitchCase="3" class="mouse__config mouse__config--speed text-center">
|
||||
<div class="help-text--mouse-speed text-left">
|
||||
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate the speed of the action.</p>
|
||||
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate its speed.</p>
|
||||
</div>
|
||||
<div class="btn-group btn-group-lg" role="group">
|
||||
<button class="btn btn-default"
|
||||
@@ -101,9 +101,6 @@
|
||||
<span>Accelerate</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="help-text--mouse-speed last-help text-left">
|
||||
<p>You can set the multiplier in the <a [routerLink]="['/settings']" title="Settings">settings</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
</div>
|
||||
|
||||
@@ -47,16 +47,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.help-text--mouse-speed {
|
||||
margin-bottom: 2rem;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
.btn-placeholder {
|
||||
visibility: hidden;
|
||||
@@ -78,8 +68,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-text--mouse-speed.last-help {
|
||||
margin-bottom: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './settings.component';
|
||||
export * from './settings.routes';
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { SettingsComponent } from './settings.component';
|
||||
|
||||
export const settingsRoutes: Routes = [
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent
|
||||
}
|
||||
];
|
||||
@@ -23,6 +23,12 @@
|
||||
[class.disabled]="updatingFirmware$ | async">Mouse speed</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/device/led-brightness']"
|
||||
[class.disabled]="updatingFirmware$ | async">LED brightness</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/device/configuration']"
|
||||
@@ -40,11 +46,11 @@
|
||||
<li class="sidebar__level-1--item">
|
||||
<div class="sidebar__level-1">
|
||||
<i class="fa fa-keyboard-o"></i> Keymaps
|
||||
<a [routerLink]="['/keymap/add']"
|
||||
<!--a [routerLink]="['/keymap/add']"
|
||||
class="btn btn-default pull-right btn-sm"
|
||||
[class.disabled]="updatingFirmware$ | async">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</a-->
|
||||
<i class="fa fa-chevron-up pull-right"
|
||||
(click)="toggleHide($event, 'keymap')"></i>
|
||||
</div>
|
||||
@@ -111,14 +117,25 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li class="sidebar__level-0--item" [routerLinkActive]="['active']">
|
||||
<div class="sidebar__level-0">
|
||||
<i class="uhk-icon uhk-icon-agent-icon"></i> Agent
|
||||
<i class="fa fa-chevron-up pull-right"
|
||||
(click)="toggleHide($event, 'agent')"></i>
|
||||
</div>
|
||||
<ul [@toggler]="animation['agent']">
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/settings']">Settings</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/about']">About</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="menu--bottom" *ngIf="runInElectron$ | async">
|
||||
<li class="sidebar__level-1--item" [routerLinkActive]="['active']">
|
||||
<a class="sidebar__level-1" [routerLink]="['/settings']">
|
||||
<i class="fa fa-gear"></i> Settings
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -64,6 +64,10 @@ ul {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.uhk-icon-agent-icon {
|
||||
margin-left: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
&__level-2 {
|
||||
@@ -160,12 +164,12 @@ ul {
|
||||
padding: 0;
|
||||
margin: 0 0.25rem;
|
||||
text-overflow: ellipsis;
|
||||
background-color: inherit;
|
||||
background-color: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
|
||||
border-color: transparent;
|
||||
background-color: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,17 +56,8 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
|
||||
addon: 'active'
|
||||
};
|
||||
|
||||
this.keymaps$ = store.let(getKeymaps())
|
||||
.map(keymaps => keymaps.slice()) // Creating a new array reference, because the sort is working in place
|
||||
.do((keymaps: Keymap[]) => {
|
||||
keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
|
||||
});
|
||||
|
||||
this.macros$ = store.let(getMacros())
|
||||
.map(macros => macros.slice()) // Creating a new array reference, because the sort is working in place
|
||||
.do((macros: Macro[]) => {
|
||||
macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
|
||||
});
|
||||
this.keymaps$ = store.let(getKeymaps());
|
||||
this.macros$ = store.let(getMacros());
|
||||
|
||||
this.showAddonMenu$ = this.store.select(showAddonMenu);
|
||||
this.runInElectron$ = this.store.select(runningInElectron);
|
||||
@@ -118,7 +109,11 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
|
||||
this.store.dispatch(MacroActions.addMacro());
|
||||
}
|
||||
|
||||
editDeviceName(name): void {
|
||||
editDeviceName(name: string): void {
|
||||
if (!util.isValidName(name) || name.trim() === this.deviceNameValue) {
|
||||
this.setDeviceName();
|
||||
return;
|
||||
}
|
||||
this.store.dispatch(new RenameUserConfigurationAction(name));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<label *ngIf="label">
|
||||
<span>{{label}}</span>
|
||||
<icon name="question-circle"
|
||||
data-toggle="tooltip"
|
||||
[title]="tooltip"
|
||||
html="true"
|
||||
data-placement="bottom"
|
||||
*ngIf="tooltip"></icon>
|
||||
</label>
|
||||
<div class="slider-wrapper">
|
||||
<div class="slider-container">
|
||||
<nouislider
|
||||
[min]="min"
|
||||
[max]="max"
|
||||
[step]="step"
|
||||
[keyboard]="true"
|
||||
[tooltips]="true"
|
||||
[(ngModel)]="value"
|
||||
(ngModelChange)="onSliderChange($event)"></nouislider>
|
||||
</div>
|
||||
<div class="slider-value">
|
||||
<div class="value-indicator">{{value}} {{valueUnit}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
:host {
|
||||
label {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
|
||||
icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 1.6rem;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.value-indicator {
|
||||
margin: 1rem 1rem 1rem 3rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { AfterViewInit, Component, EventEmitter, forwardRef, Input, Output, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { NouisliderComponent } from 'ng2-nouislider/src/nouislider';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
export interface SliderPips {
|
||||
mode: string;
|
||||
values: number[];
|
||||
density: number;
|
||||
stepped?: boolean;
|
||||
}
|
||||
|
||||
export interface SliderProps {
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
pips?: SliderPips;
|
||||
valueUnit?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'slider-wrapper',
|
||||
templateUrl: './slider-wrapper.component.html',
|
||||
styleUrls: ['./slider-wrapper.component.scss'],
|
||||
providers: [
|
||||
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SliderWrapperComponent), multi: true }
|
||||
]
|
||||
})
|
||||
export class SliderWrapperComponent implements AfterViewInit, ControlValueAccessor, OnDestroy {
|
||||
@ViewChild(NouisliderComponent) slider: NouisliderComponent;
|
||||
@Input() label: string;
|
||||
@Input() tooltip: string;
|
||||
@Input() min: number;
|
||||
@Input() max: number;
|
||||
@Input() step: number;
|
||||
@Input() pips: SliderPips;
|
||||
@Input() valueUnit: string;
|
||||
@Output() onChange = new EventEmitter<number>();
|
||||
|
||||
public value: number;
|
||||
private changeObserver$: Observer<number>;
|
||||
private changeDebounceTime: number = 300;
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this.pips) {
|
||||
this.slider.slider.pips(this.pips);
|
||||
}
|
||||
|
||||
// Hide tooltips and show them when dragging slider handle
|
||||
this.slider.slider.target.querySelector('.noUi-tooltip').style.display = 'none';
|
||||
|
||||
this.slider.slider.on('start', function() {
|
||||
this.target.querySelector('.noUi-tooltip').style.display = 'block';
|
||||
});
|
||||
this.slider.slider.on('end', function() {
|
||||
this.target.querySelector('.noUi-tooltip').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.changeObserver$) {
|
||||
this.changeObserver$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: number): void {
|
||||
this.value = value || this.min;
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched() {}
|
||||
|
||||
onSliderChange(value: number): void {
|
||||
if (!this.changeObserver$) {
|
||||
Observable.create(observer => {
|
||||
this.changeObserver$ = observer;
|
||||
}).debounceTime(this.changeDebounceTime)
|
||||
.distinctUntilChanged()
|
||||
.subscribe(this.propagateChange);
|
||||
|
||||
return; // No change event on first change as the value is just being set
|
||||
}
|
||||
this.changeObserver$.next(value);
|
||||
}
|
||||
|
||||
private propagateChange: any = () => {};
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
|
||||
<svg:g svg-module *ngFor="let module of modules; let i = index"
|
||||
[coverages]="module.coverages"
|
||||
[keyboardKeys]="module.keyboardKeys"
|
||||
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
||||
[capturingEnabled]="capturingEnabled"
|
||||
[attr.transform]="module.attributes.transform"
|
||||
[keyActions]="moduleConfig[i].keyActions"
|
||||
[selectedKey]="selectedKey"
|
||||
[@split]="moduleAnimationStates[i]"
|
||||
[selected]="selectedKey?.moduleId === i"
|
||||
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
||||
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
||||
[coverages]="module.coverages"
|
||||
[keyboardKeys]="module.keyboardKeys"
|
||||
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
||||
[capturingEnabled]="capturingEnabled"
|
||||
[attr.transform]="module.attributes.transform"
|
||||
[keyActions]="moduleConfig[i].keyActions"
|
||||
[selectedKey]="selectedKey"
|
||||
[@split]="moduleAnimationStates[i]"
|
||||
[selected]="selectedKey?.moduleId === i"
|
||||
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
||||
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
||||
/>
|
||||
</svg>
|
||||
<editable-text *ngIf="showDescription"
|
||||
[ngModel]="description"
|
||||
(ngModelChange)="descriptionChanged.emit($event)"
|
||||
placeholder="No description provided for this keymap."></editable-text>
|
||||
|
||||
|
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +1,10 @@
|
||||
:host {
|
||||
display: flex;
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
editable-text {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,12 @@ export class SvgKeyboardComponent implements OnInit {
|
||||
@Input() selected: boolean;
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Input() showDescription = false;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
modules: SvgModule[];
|
||||
viewBox: string;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<svg [attr.viewBox]="viewBox" [attr.width]="textContainer.width" [attr.height]="textContainer.height"
|
||||
[attr.x]="textContainer.x" [attr.y]="textContainer.y" [ngSwitch]="labelType">
|
||||
<svg:g svg-single-icon-key *ngSwitchCase="'icon'"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
[icon]="labelSource">
|
||||
</svg:g>
|
||||
<svg:g svg-one-line-text-key *ngSwitchCase="'one-line'"
|
||||
[height]="height"
|
||||
[width]="width"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -127,21 +127,23 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
let newLabelSource: string[];
|
||||
if (this.keystrokeAction.hasScancode()) {
|
||||
const scancode: number = this.keystrokeAction.scancode;
|
||||
newLabelSource = this.mapper.scanCodeToText(scancode, this.keystrokeAction.type);
|
||||
if (newLabelSource) {
|
||||
if (newLabelSource.length === 1) {
|
||||
this.labelSource = newLabelSource[0];
|
||||
this.labelType = 'one-line';
|
||||
} else {
|
||||
this.labelSource = newLabelSource;
|
||||
this.labelType = 'two-line';
|
||||
}
|
||||
} else {
|
||||
this.labelSource = this.mapper.scanCodeToSvgImagePath(scancode, this.keystrokeAction.type);
|
||||
this.labelSource = this.mapper.scanCodeToSvgImagePath(scancode, this.keystrokeAction.type);
|
||||
if (this.labelSource) {
|
||||
this.labelType = 'icon';
|
||||
} else {
|
||||
let newLabelSource: string[];
|
||||
newLabelSource = this.mapper.scanCodeToText(scancode, this.keystrokeAction.type);
|
||||
if (newLabelSource) {
|
||||
if (newLabelSource.length === 1) {
|
||||
this.labelSource = newLabelSource[0];
|
||||
this.labelType = 'one-line';
|
||||
} else {
|
||||
this.labelSource = newLabelSource;
|
||||
this.labelType = 'two-line';
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.labelType = 'empty';
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
[selectedKey]="selectedKey"
|
||||
[halvesSplit]="halvesSplit"
|
||||
[keyboardLayout]="keyboardLayout"
|
||||
[description]="keymap.description"
|
||||
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
|
||||
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
|
||||
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
|
||||
(descriptionChanged)="onDescriptionChanged($event)"
|
||||
></keyboard-slider>
|
||||
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
|
||||
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
|
||||
|
||||
@@ -2,14 +2,16 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
Renderer,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
SimpleChanges
|
||||
Output,
|
||||
Renderer,
|
||||
SimpleChanges,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@@ -25,10 +27,10 @@ import {
|
||||
KeystrokeAction,
|
||||
Layer,
|
||||
LayerName,
|
||||
SecondaryRoleAction,
|
||||
MouseAction,
|
||||
MouseActionParam,
|
||||
PlayMacroAction,
|
||||
SecondaryRoleAction,
|
||||
SwitchKeymapAction,
|
||||
SwitchLayerAction
|
||||
} from 'uhk-common';
|
||||
@@ -38,6 +40,7 @@ import { AppState } from '../../../store';
|
||||
import { KeymapActions } from '../../../store/actions';
|
||||
import { PopoverComponent } from '../../popover';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||
|
||||
interface NameValuePair {
|
||||
name: string;
|
||||
@@ -56,6 +59,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
||||
@Input() tooltipEnabled: boolean = false;
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
||||
@Output() descriptionChanged = new EventEmitter<ChangeKeymapDescription>();
|
||||
|
||||
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
|
||||
|
||||
@@ -237,6 +241,13 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
||||
return this.currentLayer;
|
||||
}
|
||||
|
||||
onDescriptionChanged(description: string): void {
|
||||
this.descriptionChanged.emit({
|
||||
description,
|
||||
abbr: this.keymap.abbreviation
|
||||
});
|
||||
}
|
||||
|
||||
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
|
||||
if (keyAction instanceof KeystrokeAction) {
|
||||
const keystrokeAction: KeystrokeAction = keyAction;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Notification } from 'uhk-common';
|
||||
import { AppState, getUndoableNotification } from '../../store/index';
|
||||
import { AppState, getUndoableNotification } from '../../store';
|
||||
import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { AfterViewInit, Directive, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[autofocus]'
|
||||
})
|
||||
export class Autofocus implements AfterViewInit {
|
||||
constructor(private el: ElementRef) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.el.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ChangeKeymapDescription {
|
||||
abbr: string;
|
||||
description: string;
|
||||
}
|
||||
4
packages/uhk-web/src/app/models/upload-file-data.ts
Normal file
4
packages/uhk-web/src/app/models/upload-file-data.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface UploadFileData {
|
||||
filename: string;
|
||||
data: Array<number>;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface UserConfigurationValue {
|
||||
propertyName: string;
|
||||
value: number;
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Component } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'main-page',
|
||||
templateUrl: './main.page.html',
|
||||
styles: [':host{height:100%; display: inline-block; width: 100%}']
|
||||
styles: [':host{height:100%; width:100%}']
|
||||
})
|
||||
export class MainPage {
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export class AppRendererService {
|
||||
this.ipcRenderer.send(IpcEvents.app.exit);
|
||||
}
|
||||
|
||||
openUrl(url: string): void {
|
||||
this.logService.info(`[AppRendererService] open url: ${url}`);
|
||||
this.ipcRenderer.send(IpcEvents.app.openUrl, url);
|
||||
}
|
||||
|
||||
private registerEvents() {
|
||||
this.ipcRenderer.on(IpcEvents.app.getAppStartInfoReply, (event: string, arg: AppStartInfo) => {
|
||||
this.dispachStoreAction(new ProcessAppStartInfoAction(arg));
|
||||
|
||||
@@ -71,7 +71,11 @@ export class MapperService {
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return 'assets/compiled_sprite.svg#' + map.get(scanCode);
|
||||
const id = map.get(scanCode);
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
return `assets/compiled_sprite.svg#${id}`;
|
||||
}
|
||||
|
||||
public getIcon(iconName: string): string {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,13 +7,15 @@ import { ConfirmationPopoverModule } from 'angular-confirmation-popover';
|
||||
|
||||
import { DragulaModule } from 'ng2-dragula/ng2-dragula';
|
||||
import { Select2Module } from 'ng2-select2/ng2-select2';
|
||||
import { NouisliderModule } from 'ng2-nouislider';
|
||||
|
||||
import { AddOnComponent } from './components/add-on';
|
||||
import { KeyboardSliderComponent } from './components/keyboard/slider';
|
||||
import {
|
||||
DeviceConfigurationComponent,
|
||||
DeviceFirmwareComponent,
|
||||
MouseSpeedComponent
|
||||
MouseSpeedComponent,
|
||||
LEDBrightnessComponent
|
||||
} from './components/device';
|
||||
import { KeymapAddComponent, KeymapEditComponent, KeymapHeaderComponent } from './components/keymap';
|
||||
import { LayersComponent } from './components/layers';
|
||||
@@ -41,7 +43,7 @@ import {
|
||||
} from './components/popover/tab';
|
||||
import { CaptureKeystrokeButtonComponent } from './components/popover/widgets/capture-keystroke';
|
||||
import { IconComponent } from './components/popover/widgets/icon';
|
||||
import { SettingsComponent } from './components/settings';
|
||||
import { AboutComponent, SettingsComponent } from './components/agent';
|
||||
import { SideMenuComponent } from './components/side-menu';
|
||||
import { SvgKeyboardComponent } from './components/svg/keyboard';
|
||||
import {
|
||||
@@ -98,6 +100,9 @@ import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.
|
||||
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
|
||||
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||
import { XtermComponent } from './components/xterm/xterm.component';
|
||||
import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapper.component';
|
||||
import { EditableTextComponent } from './components/editable-text/editable-text.component';
|
||||
import { Autofocus } from './directives/autofocus/autofocus.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -105,6 +110,7 @@ import { XtermComponent } from './components/xterm/xterm.component';
|
||||
DeviceConfigurationComponent,
|
||||
DeviceFirmwareComponent,
|
||||
MouseSpeedComponent,
|
||||
LEDBrightnessComponent,
|
||||
KeymapEditComponent,
|
||||
KeymapHeaderComponent,
|
||||
NotificationComponent,
|
||||
@@ -148,6 +154,7 @@ import { XtermComponent } from './components/xterm/xterm.component';
|
||||
MacroTextTabComponent,
|
||||
MacroNotFoundComponent,
|
||||
AddOnComponent,
|
||||
AboutComponent,
|
||||
SettingsComponent,
|
||||
KeyboardSliderComponent,
|
||||
CancelableDirective,
|
||||
@@ -163,7 +170,10 @@ import { XtermComponent } from './components/xterm/xterm.component';
|
||||
MainPage,
|
||||
ProgressButtonComponent,
|
||||
LoadingDevicePageComponent,
|
||||
XtermComponent
|
||||
XtermComponent,
|
||||
SliderWrapperComponent,
|
||||
EditableTextComponent,
|
||||
Autofocus
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -172,6 +182,7 @@ import { XtermComponent } from './components/xterm/xterm.component';
|
||||
DragulaModule,
|
||||
routing,
|
||||
Select2Module,
|
||||
NouisliderModule,
|
||||
NotifierModule.withConfig(angularNotifierConfig),
|
||||
ConfirmationPopoverModule.forRoot({
|
||||
confirmButtonType: 'danger' // set defaults here
|
||||
@@ -187,7 +198,6 @@ import { XtermComponent } from './components/xterm/xterm.component';
|
||||
DataStorageRepositoryService,
|
||||
DefaultUserConfigurationService,
|
||||
LogService,
|
||||
DefaultUserConfigurationService,
|
||||
AppUpdateRendererService,
|
||||
AppRendererService,
|
||||
IpcCommonRenderer,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type, VersionInformation } from 'uhk-common';
|
||||
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type } from 'uhk-common';
|
||||
import { ElectronLogEntry } from '../../models/xterm-log';
|
||||
|
||||
const PREFIX = '[app] ';
|
||||
@@ -16,8 +16,8 @@ export const ActionTypes = {
|
||||
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
|
||||
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
|
||||
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
|
||||
UPDATE_AGENT_VERSION_INFORMATION: type(PREFIX + 'update agent version information'),
|
||||
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received')
|
||||
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'),
|
||||
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window')
|
||||
};
|
||||
|
||||
export class AppBootsrappedAction implements Action {
|
||||
@@ -66,18 +66,18 @@ export class LoadHardwareConfigurationSuccessAction implements Action {
|
||||
constructor(public payload: HardwareConfiguration) {}
|
||||
}
|
||||
|
||||
export class UpdateAgentVersionInformationAction implements Action {
|
||||
type = ActionTypes.UPDATE_AGENT_VERSION_INFORMATION;
|
||||
|
||||
constructor(public payload: VersionInformation) {}
|
||||
}
|
||||
|
||||
export class ElectronMainLogReceivedAction implements Action {
|
||||
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
|
||||
|
||||
constructor(public payload: ElectronLogEntry) {}
|
||||
}
|
||||
|
||||
export class OpenUrlInNewWindowAction implements Action {
|
||||
type = ActionTypes.OPEN_URL_IN_NEW_WINDOW;
|
||||
|
||||
constructor(public payload: string) {}
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= AppStartedAction
|
||||
| AppBootsrappedAction
|
||||
@@ -88,6 +88,6 @@ export type Actions
|
||||
| UndoLastSuccessAction
|
||||
| DismissUndoNotificationAction
|
||||
| LoadHardwareConfigurationSuccessAction
|
||||
| UpdateAgentVersionInformationAction
|
||||
| ElectronMainLogReceivedAction
|
||||
| OpenUrlInNewWindowAction
|
||||
;
|
||||
|
||||
@@ -16,6 +16,7 @@ export const ActionTypes = {
|
||||
SAVE_TO_KEYBOARD_FAILED: type(PREFIX + 'save to keyboard failed'),
|
||||
HIDE_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'hide save to keyboard button'),
|
||||
RESET_USER_CONFIGURATION: type(PREFIX + 'reset user configuration'),
|
||||
RESET_MOUSE_SPEED_SETTINGS: type(PREFIX + 'reset mouse speed settings'),
|
||||
UPDATE_FIRMWARE: type(PREFIX + 'update firmware'),
|
||||
UPDATE_FIRMWARE_WITH: type(PREFIX + 'update firmware with'),
|
||||
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
|
||||
@@ -109,6 +110,10 @@ export class UpdateFirmwareOkButtonAction implements Action {
|
||||
type = ActionTypes.UPDATE_FIRMWARE_OK_BUTTON;
|
||||
}
|
||||
|
||||
export class ResetMouseSpeedSettingsAction implements Action {
|
||||
type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS;
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= SetPrivilegeOnLinuxAction
|
||||
| SetPrivilegeOnLinuxReplyAction
|
||||
@@ -119,6 +124,7 @@ export type Actions
|
||||
| SaveToKeyboardSuccessAction
|
||||
| SaveToKeyboardSuccessFailed
|
||||
| HideSaveToKeyboardButton
|
||||
| ResetMouseSpeedSettingsAction
|
||||
| ResetUserConfigurationAction
|
||||
| UpdateFirmwareAction
|
||||
| UpdateFirmwareWithAction
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { KeyAction, Keymap, Macro } from 'uhk-common';
|
||||
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
||||
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
|
||||
|
||||
export type KeymapAction =
|
||||
KeymapActions.AddKeymapAction |
|
||||
@@ -11,7 +12,8 @@ export type KeymapAction =
|
||||
KeymapActions.SetDefaultAction |
|
||||
KeymapActions.RemoveKeymapAction |
|
||||
KeymapActions.SaveKeyAction |
|
||||
KeymapActions.CheckMacroAction;
|
||||
KeymapActions.CheckMacroAction |
|
||||
KeymapActions.EditDescriptionAction;
|
||||
|
||||
export namespace KeymapActions {
|
||||
export const ADD = '[Keymap] Add keymap';
|
||||
@@ -98,6 +100,16 @@ export namespace KeymapActions {
|
||||
payload: UndoUserConfigData
|
||||
};
|
||||
|
||||
export const EDIT_DESCRIPTION = '[Keymap] Edit description';
|
||||
|
||||
export class EditDescriptionAction {
|
||||
type = EDIT_DESCRIPTION;
|
||||
|
||||
constructor(public payload: ChangeKeymapDescription) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export function loadKeymaps(): Action {
|
||||
return {
|
||||
type: KeymapActions.LOAD_KEYMAPS
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type, UserConfiguration, ConfigurationReply } from 'uhk-common';
|
||||
import { UserConfigurationValue } from '../../models/user-configuration-value';
|
||||
import { UploadFileData } from '../../models/upload-file-data';
|
||||
|
||||
const PREFIX = '[user-config] ';
|
||||
|
||||
@@ -13,7 +15,10 @@ export const ActionTypes = {
|
||||
SAVE_USER_CONFIG_IN_JSON_FILE: type(PREFIX + 'Save User Config in JSON file'),
|
||||
SAVE_USER_CONFIG_IN_BIN_FILE: type(PREFIX + 'Save User Config in binary file'),
|
||||
LOAD_RESET_USER_CONFIGURATION: type(PREFIX + 'Load reset user configuration'),
|
||||
RENAME_USER_CONFIGURATION: type(PREFIX + 'Rename user configuration')
|
||||
RENAME_USER_CONFIGURATION: type(PREFIX + 'Rename user configuration'),
|
||||
SET_USER_CONFIGURATION_VALUE: type(PREFIX + 'Set user configuration value'),
|
||||
LOAD_USER_CONFIGURATION_FROM_FILE: type(PREFIX + 'Load user configuration from file'),
|
||||
APPLY_USER_CONFIGURATION_FROM_FILE: type(PREFIX + 'Apply user configuration from file')
|
||||
};
|
||||
|
||||
export class LoadUserConfigAction implements Action {
|
||||
@@ -67,6 +72,27 @@ export class RenameUserConfigurationAction implements Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class SetUserConfigurationValueAction implements Action {
|
||||
type = ActionTypes.SET_USER_CONFIGURATION_VALUE;
|
||||
|
||||
constructor(public payload: UserConfigurationValue) {
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadUserConfigurationFromFileAction implements Action {
|
||||
type = ActionTypes.LOAD_USER_CONFIGURATION_FROM_FILE;
|
||||
|
||||
constructor(public payload: UploadFileData) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplyUserConfigurationFromFileAction implements Action {
|
||||
type = ActionTypes.APPLY_USER_CONFIGURATION_FROM_FILE;
|
||||
|
||||
constructor(public payload: UserConfiguration) {
|
||||
}
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= LoadUserConfigAction
|
||||
| LoadUserConfigSuccessAction
|
||||
@@ -77,4 +103,7 @@ export type Actions
|
||||
| SaveUserConfigInBinaryFileAction
|
||||
| LoadResetUserConfigurationAction
|
||||
| RenameUserConfigurationAction
|
||||
| SetUserConfigurationValueAction
|
||||
| LoadUserConfigurationFromFileAction
|
||||
| ApplyUserConfigurationFromFileAction
|
||||
;
|
||||
|
||||
@@ -16,19 +16,15 @@ import {
|
||||
ApplyCommandLineArgsAction,
|
||||
AppStartedAction,
|
||||
DismissUndoNotificationAction,
|
||||
OpenUrlInNewWindowAction,
|
||||
ProcessAppStartInfoAction,
|
||||
ShowNotificationAction,
|
||||
UndoLastAction,
|
||||
UpdateAgentVersionInformationAction
|
||||
UndoLastAction
|
||||
} from '../actions/app';
|
||||
import { AppRendererService } from '../../services/app-renderer.service';
|
||||
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
|
||||
import {
|
||||
ActionTypes as DeviceActions,
|
||||
ConnectionStateChangedAction,
|
||||
SaveToKeyboardSuccessAction
|
||||
} from '../actions/device';
|
||||
import { AppState, autoWriteUserConfiguration } from '../index';
|
||||
import { ConnectionStateChangedAction } from '../actions/device';
|
||||
import { AppState, runningInElectron } from '../index';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationEffects {
|
||||
@@ -66,8 +62,7 @@ export class ApplicationEffects {
|
||||
new ConnectionStateChangedAction({
|
||||
connected: appInfo.deviceConnected,
|
||||
hasPermission: appInfo.hasPermission
|
||||
}),
|
||||
new UpdateAgentVersionInformationAction(appInfo.agentVersionInfo)
|
||||
})
|
||||
];
|
||||
});
|
||||
|
||||
@@ -76,12 +71,16 @@ export class ApplicationEffects {
|
||||
.map(action => action.payload)
|
||||
.mergeMap((action: Action) => [action, new DismissUndoNotificationAction()]);
|
||||
|
||||
@Effect({dispatch: false}) saveToKeyboardSuccess$ = this.actions$
|
||||
.ofType<SaveToKeyboardSuccessAction>(DeviceActions.SAVE_TO_KEYBOARD_SUCCESS)
|
||||
.withLatestFrom(this.store.select(autoWriteUserConfiguration))
|
||||
.do(([action, autoWriteUserConfig]) => {
|
||||
if (autoWriteUserConfig) {
|
||||
this.appRendererService.exit();
|
||||
@Effect({dispatch: false}) openUrlInNewWindow$ = this.actions$
|
||||
.ofType<OpenUrlInNewWindowAction>(ActionTypes.OPEN_URL_IN_NEW_WINDOW)
|
||||
.withLatestFrom(this.store.select(runningInElectron))
|
||||
.do(([action, inElectron]) => {
|
||||
const url = action.payload;
|
||||
|
||||
if (inElectron) {
|
||||
this.appRendererService.openUrl(url);
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -35,10 +35,12 @@ import { ShowNotificationAction } from '../actions/app';
|
||||
import { AppState } from '../index';
|
||||
import {
|
||||
ActionTypes as UserConfigActions,
|
||||
ApplyUserConfigurationFromFileAction,
|
||||
LoadConfigFromDeviceAction,
|
||||
LoadResetUserConfigurationAction
|
||||
} from '../actions/user-config';
|
||||
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
|
||||
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceEffects {
|
||||
@@ -130,6 +132,30 @@ export class DeviceEffects {
|
||||
.switchMap(() => Observable.of(new HideSaveToKeyboardButton()))
|
||||
);
|
||||
|
||||
@Effect()
|
||||
resetMouseSpeedSettings$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.RESET_MOUSE_SPEED_SETTINGS)
|
||||
.switchMap(() => {
|
||||
const config = this.defaultUserConfigurationService.getDefault();
|
||||
const mouseSpeedDefaultSettings = {};
|
||||
const mouseSpeedProps = [
|
||||
'mouseMoveInitialSpeed',
|
||||
'mouseMoveAcceleration',
|
||||
'mouseMoveDeceleratedSpeed',
|
||||
'mouseMoveBaseSpeed',
|
||||
'mouseMoveAcceleratedSpeed',
|
||||
'mouseScrollInitialSpeed',
|
||||
'mouseScrollAcceleration',
|
||||
'mouseScrollDeceleratedSpeed',
|
||||
'mouseScrollBaseSpeed',
|
||||
'mouseScrollAcceleratedSpeed'
|
||||
];
|
||||
mouseSpeedProps.forEach(prop => {
|
||||
mouseSpeedDefaultSettings[prop] = config[prop];
|
||||
});
|
||||
return Observable.of(new LoadResetUserConfigurationAction(<UserConfiguration>mouseSpeedDefaultSettings));
|
||||
});
|
||||
|
||||
@Effect() resetUserConfiguration$: Observable<Action> = this.actions$
|
||||
.ofType(ActionTypes.RESET_USER_CONFIGURATION)
|
||||
.switchMap(() => {
|
||||
@@ -138,8 +164,16 @@ export class DeviceEffects {
|
||||
});
|
||||
|
||||
@Effect() saveResetUserConfigurationToDevice$ = this.actions$
|
||||
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION)
|
||||
.switchMap(() => Observable.of(new SaveConfigurationAction()));
|
||||
.ofType<ApplyUserConfigurationFromFileAction
|
||||
| LoadResetUserConfigurationAction>(
|
||||
UserConfigActions.LOAD_RESET_USER_CONFIGURATION,
|
||||
UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
|
||||
.map(action => action.payload)
|
||||
.switchMap((config: UserConfiguration) => {
|
||||
this.dataStorageRepository.saveConfig(config);
|
||||
|
||||
return Observable.of(new SaveConfigurationAction());
|
||||
});
|
||||
|
||||
@Effect({dispatch: false}) updateFirmware$ = this.actions$
|
||||
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
|
||||
@@ -169,6 +203,7 @@ export class DeviceEffects {
|
||||
private router: Router,
|
||||
private deviceRendererService: DeviceRendererService,
|
||||
private store: Store<AppState>,
|
||||
private dataStorageRepository: DataStorageRepositoryService,
|
||||
private defaultUserConfigurationService: DefaultUserConfigurationService) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,17 @@ import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/pairwise';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/withLatestFrom';
|
||||
import 'rxjs/add/observable/of';
|
||||
|
||||
import { Keymap } from 'uhk-common';
|
||||
import { findNewItem } from '../../util';
|
||||
import { KeymapActions } from '../actions';
|
||||
import { AppState } from '../index';
|
||||
import { getKeymaps } from '../reducers/user-configuration';
|
||||
|
||||
@Injectable()
|
||||
export class KeymapEffects {
|
||||
@@ -32,10 +35,10 @@ export class KeymapEffects {
|
||||
|
||||
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
||||
.ofType(KeymapActions.ADD, KeymapActions.DUPLICATE)
|
||||
.withLatestFrom(this.store)
|
||||
.map(latest => latest[1].userConfiguration.keymaps)
|
||||
.do(keymaps => {
|
||||
this.router.navigate(['/keymap', keymaps[keymaps.length - 1].abbreviation]);
|
||||
.withLatestFrom(this.store.let(getKeymaps()).pairwise(), (action, latest) => latest)
|
||||
.do(([prevKeymaps, newKeymaps]) => {
|
||||
const newKeymap = findNewItem(prevKeymaps, newKeymaps);
|
||||
this.router.navigate(['/keymap', newKeymap.abbreviation]);
|
||||
});
|
||||
|
||||
@Effect({ dispatch: false }) remove$: any = this.actions$
|
||||
|
||||
@@ -2,14 +2,18 @@ import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Store, Action } from '@ngrx/store';
|
||||
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/pairwise';
|
||||
import 'rxjs/add/operator/withLatestFrom';
|
||||
|
||||
import { Macro } from 'uhk-common';
|
||||
import { KeymapActions, MacroActions } from '../actions';
|
||||
import { AppState } from '../index';
|
||||
import { getMacros } from '../reducers/user-configuration';
|
||||
import { findNewItem } from '../../util';
|
||||
|
||||
@Injectable()
|
||||
export class MacroEffects {
|
||||
@@ -27,23 +31,17 @@ export class MacroEffects {
|
||||
}
|
||||
});
|
||||
|
||||
@Effect({ dispatch: false }) add$: any = this.actions$
|
||||
.ofType(MacroActions.ADD)
|
||||
.withLatestFrom(this.store)
|
||||
.map(([action, state]) => state.userConfiguration.macros)
|
||||
.map(macros => macros[macros.length - 1])
|
||||
.do(lastMacro => {
|
||||
this.router.navigate(['/macro', lastMacro.id, 'new']);
|
||||
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
||||
.ofType(MacroActions.ADD, MacroActions.DUPLICATE)
|
||||
.withLatestFrom(this.store.let(getMacros()).pairwise(), (action, latest) => ([action, latest[0], latest[1]]))
|
||||
.do(([action, prevMacros, newMacros]: [Action, Macro[], Macro[]]) => {
|
||||
const newMacro = findNewItem(prevMacros, newMacros);
|
||||
const commands = ['/macro', newMacro.id];
|
||||
if (action.type === MacroActions.ADD) {
|
||||
commands.push('new');
|
||||
}
|
||||
this.router.navigate(commands);
|
||||
});
|
||||
|
||||
@Effect({ dispatch: false }) duplicate: any = this.actions$
|
||||
.ofType(MacroActions.DUPLICATE)
|
||||
.withLatestFrom(this.store)
|
||||
.map(([action, state]) => state.userConfiguration.macros)
|
||||
.map(macros => macros[macros.length - 1])
|
||||
.do(lastMacro => {
|
||||
this.router.navigate(['/macro', lastMacro.id]);
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {}
|
||||
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }
|
||||
}
|
||||
|
||||
@@ -15,26 +15,38 @@ import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/observable/empty';
|
||||
|
||||
import {
|
||||
ConfigurationReply, HardwareConfiguration, LogService, NotificationType, UhkBuffer,
|
||||
ConfigurationReply,
|
||||
HardwareConfiguration,
|
||||
LogService,
|
||||
NotificationType,
|
||||
UhkBuffer,
|
||||
UserConfiguration
|
||||
} from 'uhk-common';
|
||||
|
||||
import {
|
||||
ActionTypes, LoadConfigFromDeviceReplyAction, LoadUserConfigSuccessAction, RenameUserConfigurationAction,
|
||||
ActionTypes,
|
||||
ApplyUserConfigurationFromFileAction,
|
||||
LoadConfigFromDeviceReplyAction,
|
||||
LoadUserConfigSuccessAction,
|
||||
LoadUserConfigurationFromFileAction,
|
||||
RenameUserConfigurationAction,
|
||||
SaveUserConfigSuccessAction
|
||||
} from '../actions/user-config';
|
||||
|
||||
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
|
||||
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
|
||||
import { AppState, autoWriteUserConfiguration, getPrevUserConfiguration, getUserConfiguration } from '../index';
|
||||
import { AppState, getPrevUserConfiguration, getUserConfiguration } from '../index';
|
||||
import { KeymapAction, KeymapActions, MacroAction, MacroActions } from '../actions';
|
||||
import {
|
||||
DismissUndoNotificationAction, LoadHardwareConfigurationSuccessAction, ShowNotificationAction,
|
||||
DismissUndoNotificationAction,
|
||||
LoadHardwareConfigurationSuccessAction,
|
||||
ShowNotificationAction,
|
||||
UndoLastAction
|
||||
} from '../actions/app';
|
||||
import { SaveConfigurationAction, ShowSaveToKeyboardButtonAction } from '../actions/device';
|
||||
import { ShowSaveToKeyboardButtonAction } from '../actions/device';
|
||||
import { DeviceRendererService } from '../../services/device-renderer.service';
|
||||
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
||||
import { UploadFileData } from '../../models/upload-file-data';
|
||||
|
||||
@Injectable()
|
||||
export class UserConfigEffects {
|
||||
@@ -69,10 +81,10 @@ export class UserConfigEffects {
|
||||
@Effect() saveUserConfig$: Observable<Action> = (this.actions$
|
||||
.ofType(
|
||||
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
|
||||
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY,
|
||||
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY, KeymapActions.EDIT_DESCRIPTION,
|
||||
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
|
||||
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
|
||||
ActionTypes.RENAME_USER_CONFIGURATION) as
|
||||
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
|
||||
Observable<KeymapAction | MacroAction | RenameUserConfigurationAction>)
|
||||
.withLatestFrom(this.store.select(getUserConfiguration), this.store.select(getPrevUserConfiguration))
|
||||
.mergeMap(([action, config, prevUserConfiguration]) => {
|
||||
@@ -185,16 +197,34 @@ export class UserConfigEffects {
|
||||
saveAs(blob, 'UserConfiguration.bin');
|
||||
});
|
||||
|
||||
@Effect() loadUserConfigurationSuccess$ = this.actions$
|
||||
.ofType(ActionTypes.LOAD_USER_CONFIG_SUCCESS)
|
||||
.withLatestFrom(this.store.select(autoWriteUserConfiguration))
|
||||
.switchMap(([action, autoWriteUserConfig]) => {
|
||||
this.logService.debug('[UserConfigEffect] LOAD_USER_CONFIG_SUCCESS', {autoWriteUserConfig});
|
||||
if (autoWriteUserConfig) {
|
||||
return Observable.of(new SaveConfigurationAction());
|
||||
}
|
||||
else {
|
||||
return Observable.empty();
|
||||
@Effect() loadUserConfigurationFromFile$ = this.actions$
|
||||
.ofType<LoadUserConfigurationFromFileAction>(ActionTypes.LOAD_USER_CONFIGURATION_FROM_FILE)
|
||||
.map(action => action.payload)
|
||||
.map((info: UploadFileData) => {
|
||||
try {
|
||||
const userConfig = new UserConfiguration();
|
||||
|
||||
if (info.filename.endsWith('.bin')) {
|
||||
userConfig.fromBinary(UhkBuffer.fromArray(info.data));
|
||||
} else {
|
||||
const buffer = new Buffer(info.data);
|
||||
const json = buffer.toString();
|
||||
userConfig.fromJsonObject(JSON.parse(json));
|
||||
}
|
||||
|
||||
if (userConfig.userConfigMajorVersion) {
|
||||
return new ApplyUserConfigurationFromFileAction(userConfig);
|
||||
}
|
||||
|
||||
return new ShowNotificationAction({
|
||||
type: NotificationType.Error,
|
||||
message: 'Invalid configuration specified.'
|
||||
});
|
||||
} catch (err) {
|
||||
return new ShowNotificationAction({
|
||||
type: NotificationType.Error,
|
||||
message: 'Invalid configuration specified.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -39,11 +39,11 @@ export const metaReducers: MetaReducer<AppState>[] = environment.production
|
||||
: [storeFreeze];
|
||||
|
||||
export const getUserConfiguration = (state: AppState) => state.userConfiguration;
|
||||
export const getDeviceName = (state: AppState) => state.userConfiguration.deviceName;
|
||||
export const getDeviceName = createSelector(getUserConfiguration, fromUserConfig.getDeviceName);
|
||||
|
||||
export const appState = (state: AppState) => state.app;
|
||||
|
||||
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
|
||||
export const autoWriteUserConfiguration = createSelector(appState, fromApp.autoWriteUserConfiguration);
|
||||
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
|
||||
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
|
||||
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
|
||||
|
||||
@@ -7,11 +7,11 @@ import { ActionTypes, ShowNotificationAction } from '../actions/app';
|
||||
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
|
||||
import { ActionTypes as DeviceActionTypes } from '../actions/device';
|
||||
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
|
||||
import { getVersions } from '../../util';
|
||||
|
||||
export interface State {
|
||||
started: boolean;
|
||||
showAddonMenu: boolean;
|
||||
autoWriteUserConfiguration: boolean;
|
||||
undoableNotification?: Notification;
|
||||
navigationCountAfterNotification: number;
|
||||
prevUserConfig?: UserConfiguration;
|
||||
@@ -24,10 +24,10 @@ export interface State {
|
||||
export const initialState: State = {
|
||||
started: false,
|
||||
showAddonMenu: false,
|
||||
autoWriteUserConfiguration: false,
|
||||
navigationCountAfterNotification: 0,
|
||||
runningInElectron: runInElectron(),
|
||||
configLoading: true
|
||||
configLoading: true,
|
||||
agentVersionInfo: getVersions()
|
||||
};
|
||||
|
||||
export function reducer(state = initialState, action: Action & { payload: any }) {
|
||||
@@ -42,8 +42,7 @@ export function reducer(state = initialState, action: Action & { payload: any })
|
||||
case ActionTypes.APPLY_COMMAND_LINE_ARGS: {
|
||||
return {
|
||||
...state,
|
||||
showAddonMenu: action.payload.addons,
|
||||
autoWriteUserConfiguration: action.payload.autoWriteConfig
|
||||
showAddonMenu: action.payload.addons
|
||||
};
|
||||
}
|
||||
|
||||
@@ -116,18 +115,12 @@ export function reducer(state = initialState, action: Action & { payload: any })
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UPDATE_AGENT_VERSION_INFORMATION:
|
||||
return {
|
||||
...state,
|
||||
agentVersionInfo: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const showAddonMenu = (state: State) => state.showAddonMenu;
|
||||
export const autoWriteUserConfiguration = (state: State) => state.autoWriteUserConfiguration;
|
||||
export const getUndoableNotification = (state: State) => state.undoableNotification;
|
||||
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
|
||||
export const runningInElectron = (state: State) => state.runningInElectron;
|
||||
|
||||
@@ -7,7 +7,7 @@ export const initialState: Keymap[] = [];
|
||||
export function reducer(state = initialState, action: KeymapAction): Keymap[] {
|
||||
switch (action.type) {
|
||||
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
|
||||
return action.payload;
|
||||
return (action as KeymapActions.LoadKeymapSuccessAction).payload ;
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -4,10 +4,22 @@ import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { KeyAction, Keymap, KeyActionHelper, Layer, Macro, Module, SwitchLayerAction, UserConfiguration } from 'uhk-common';
|
||||
import {
|
||||
KeyAction,
|
||||
KeyActionHelper,
|
||||
Keymap,
|
||||
Layer,
|
||||
Macro,
|
||||
Module,
|
||||
NoneAction,
|
||||
PlayMacroAction,
|
||||
SwitchLayerAction,
|
||||
UserConfiguration
|
||||
} from 'uhk-common';
|
||||
import { KeymapActions, MacroActions } from '../actions';
|
||||
import { AppState } from '../index';
|
||||
import { ActionTypes } from '../actions/user-config';
|
||||
import { isValidName } from '../../util';
|
||||
|
||||
export const initialState: UserConfiguration = new UserConfiguration();
|
||||
|
||||
@@ -15,9 +27,15 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
const changedUserConfiguration: UserConfiguration = Object.assign(new UserConfiguration(), state);
|
||||
|
||||
switch (action.type) {
|
||||
case ActionTypes.APPLY_USER_CONFIGURATION_FROM_FILE:
|
||||
case ActionTypes.LOAD_RESET_USER_CONFIGURATION:
|
||||
case ActionTypes.LOAD_USER_CONFIG_SUCCESS: {
|
||||
return Object.assign(changedUserConfiguration, action.payload);
|
||||
Object.assign(changedUserConfiguration, action.payload);
|
||||
changedUserConfiguration.keymaps = [...changedUserConfiguration.keymaps];
|
||||
changedUserConfiguration.keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
|
||||
changedUserConfiguration.macros = [...changedUserConfiguration.macros];
|
||||
changedUserConfiguration.macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
|
||||
return changedUserConfiguration;
|
||||
}
|
||||
|
||||
case KeymapActions.ADD:
|
||||
@@ -27,25 +45,36 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
newKeymap.name = generateName(state.keymaps, newKeymap.name);
|
||||
newKeymap.isDefault = (state.keymaps.length === 0);
|
||||
|
||||
changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap);
|
||||
changedUserConfiguration.keymaps = insertItemInNameOrder(state.keymaps, newKeymap);
|
||||
break;
|
||||
}
|
||||
case KeymapActions.EDIT_NAME: {
|
||||
const name: string = action.payload.name;
|
||||
if (!isValidName(action.payload.name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const name: string = action.payload.name.trim();
|
||||
let keymapToRename: Keymap = null;
|
||||
|
||||
const duplicate = state.keymaps.some((keymap: Keymap) => {
|
||||
if (keymap.abbreviation === action.payload.abbr) {
|
||||
keymapToRename = keymap;
|
||||
}
|
||||
|
||||
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
|
||||
});
|
||||
|
||||
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
|
||||
keymap = Object.assign(new Keymap(), keymap);
|
||||
if (duplicate) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
|
||||
keymap.name = name;
|
||||
}
|
||||
return keymap;
|
||||
});
|
||||
const newKeymap = Object.assign(new Keymap(), keymapToRename, { name });
|
||||
|
||||
changedUserConfiguration.keymaps = insertItemInNameOrder(
|
||||
state.keymaps,
|
||||
newKeymap,
|
||||
keymap => keymap.abbreviation !== newKeymap.abbreviation
|
||||
);
|
||||
break;
|
||||
}
|
||||
case KeymapActions.EDIT_ABBR: {
|
||||
@@ -157,7 +186,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
newMacro.isPrivate = true;
|
||||
newMacro.macroActions = [];
|
||||
|
||||
changedUserConfiguration.macros = state.macros.concat(newMacro);
|
||||
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
|
||||
break;
|
||||
}
|
||||
case MacroActions.DUPLICATE: {
|
||||
@@ -165,30 +194,61 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
newMacro.name = generateName(state.macros, newMacro.name);
|
||||
newMacro.id = generateMacroId(state.macros);
|
||||
|
||||
changedUserConfiguration.macros = state.macros.concat(newMacro);
|
||||
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
|
||||
break;
|
||||
}
|
||||
case MacroActions.EDIT_NAME: {
|
||||
const name: string = action.payload.name;
|
||||
if (!isValidName(action.payload.name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const name: string = action.payload.name.trim();
|
||||
let macroToRename: Macro = null;
|
||||
|
||||
const duplicate = state.macros.some((macro: Macro) => {
|
||||
if (macro.id === action.payload.id) {
|
||||
macroToRename = macro;
|
||||
}
|
||||
|
||||
return macro.id !== action.payload.id && macro.name === name;
|
||||
});
|
||||
|
||||
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
||||
macro = Object.assign(new Macro(), macro);
|
||||
if (!duplicate && macro.id === action.payload.id) {
|
||||
macro.name = name;
|
||||
}
|
||||
|
||||
return macro;
|
||||
});
|
||||
if (duplicate) {
|
||||
break;
|
||||
}
|
||||
|
||||
const newMacro = Object.assign(new Macro(), macroToRename, { name });
|
||||
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro, macro => macro.id !== newMacro.id);
|
||||
break;
|
||||
}
|
||||
|
||||
case MacroActions.REMOVE:
|
||||
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== action.payload);
|
||||
const macroId = action.payload;
|
||||
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== macroId);
|
||||
|
||||
for (let k = 0; k < changedUserConfiguration.keymaps.length; k++) {
|
||||
const keymap = changedUserConfiguration.keymaps[k];
|
||||
let hasChanges = false;
|
||||
|
||||
for (const layer of keymap.layers) {
|
||||
for (const module of layer.modules) {
|
||||
for (let ka = 0; ka < module.keyActions.length; ka++) {
|
||||
const keyAction = module.keyActions[ka];
|
||||
|
||||
if (keyAction instanceof PlayMacroAction && keyAction.macroId === macroId) {
|
||||
hasChanges = true;
|
||||
module.keyActions[ka] = new NoneAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
changedUserConfiguration.keymaps[k] = new Keymap(keymap);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MacroActions.ADD_ACTION:
|
||||
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
||||
if (macro.id === action.payload.id) {
|
||||
@@ -242,7 +302,26 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
break;
|
||||
|
||||
case ActionTypes.RENAME_USER_CONFIGURATION: {
|
||||
changedUserConfiguration.deviceName = action.payload;
|
||||
if (isValidName(action.payload)) {
|
||||
changedUserConfiguration.deviceName = action.payload.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ActionTypes.SET_USER_CONFIGURATION_VALUE: {
|
||||
changedUserConfiguration[action.payload.propertyName] = action.payload.value;
|
||||
break;
|
||||
}
|
||||
|
||||
case KeymapActions.EDIT_DESCRIPTION: {
|
||||
const data = (action as KeymapActions.EditDescriptionAction).payload;
|
||||
|
||||
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
|
||||
if (keymap.abbreviation === data.abbr) {
|
||||
keymap.description = data.description;
|
||||
}
|
||||
return keymap;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -343,6 +422,27 @@ function generateMacroId(macros: Macro[]) {
|
||||
return newId + 1;
|
||||
}
|
||||
|
||||
function insertItemInNameOrder<T extends { name: string }>(
|
||||
items: T[], newItem: T, keepItem: (item: T) => boolean = () => true
|
||||
): T[] {
|
||||
const newItems: T[] = [];
|
||||
let added = false;
|
||||
for (const item of items) {
|
||||
if (!added && item.name.localeCompare(newItem.name) > 0) {
|
||||
newItems.push(newItem);
|
||||
added = true;
|
||||
}
|
||||
if (keepItem(item)) {
|
||||
newItems.push(item);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
newItems.push(newItem);
|
||||
}
|
||||
|
||||
return newItems;
|
||||
}
|
||||
|
||||
function checkExistence(layers: Layer[], property: string, value: any): Layer[] {
|
||||
const keyActionsToClear: {
|
||||
layerIdx: number,
|
||||
@@ -394,3 +494,5 @@ function setKeyActionToLayer(newLayer: Layer, moduleIndex: number, keyIndex: num
|
||||
newModule.keyActions = newModule.keyActions.slice();
|
||||
newModule.keyActions[keyIndex] = newKeyAction;
|
||||
}
|
||||
|
||||
export const getDeviceName = (state: UserConfiguration) => state.deviceName;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user