refactore: create feature modules (#387)
* add @angular/cli to the project * increase nodejs version -> 8.2.1 * add lerna * merge web and shared module * move electron module into packages as uhk-agent Electron agent functionality is not working * delete symlinker * convert private properties to public of component if used in html * revert uhk-message.component * fix component path * fix the correct name of the uhk-message.component.scss * building web and electron module * delete uhk-renderer package * handle device connect disconnect state * add privilege detection * fix set privilege functionality * turn back download keymap functionality * add bootstrap, select2 js and fix null pointer exception * turn back upload data to keyboard * fix send keymap * fix test-serializer * add missing package.json * merging * fix appveyor build * fix linting * turn back electron storage service * commit the missing electron-datastorage-repository * update node to 8.3.0 in .nvmrc and log node version in appveyor build * set exact version number in appveyor build * vertical align privilege and missing device components * set back node version to 8 in appveyor * move node-usb dependency from usb dir to root maybe it is fix the appveyor build * revert usb to root * fix electron builder script * fix electron builder script * turn off electron devtools * remove CTRL+U functionality * fix CTRL+o * fix lint error * turnoff store freeze * start process when got `Error: EPERM: operation not permitted` error * move files from root usb dir -> packages/usb
This commit is contained in:
committed by
László Monda
parent
97770f67c0
commit
0f558e4132
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
*-debug.log
|
||||
.vscode
|
||||
dist
|
||||
.idea
|
||||
|
||||
@@ -20,11 +20,13 @@ install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- set CI=true
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
- npm install
|
||||
- node -v
|
||||
- npm -v
|
||||
- appveyor-retry npm install
|
||||
|
||||
test_script:
|
||||
- npm run build
|
||||
- npm run build:test
|
||||
- appveyor-retry npm run build
|
||||
- appveyor-retry npm run build:test
|
||||
- npm run lint
|
||||
- npm run test
|
||||
- npm run release
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
### Before build:
|
||||
Add symbolic link from shared/src to electron/src/shared with **npm run symlink -- -i**
|
||||
1
electron/src/.gitignore
vendored
1
electron/src/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
shared
|
||||
@@ -1,7 +0,0 @@
|
||||
<app-update-available *ngIf="showUpdateAvailable$ | async"
|
||||
(updateApp)="updateApp()"
|
||||
(doNotUpdateApp)="doNotUpdateApp()">
|
||||
</app-update-available>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<notifier-container></notifier-container>
|
||||
@@ -1,6 +0,0 @@
|
||||
app {
|
||||
display: block;
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
import { AppState, getShowAppUpdateAvailable } from '../store';
|
||||
import { DoNotUpdateAppAction, UpdateAppAction } from '../shared/store/actions/app-update.action';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent {
|
||||
showUpdateAvailable$: Observable<boolean>;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
||||
}
|
||||
|
||||
updateApp() {
|
||||
this.store.dispatch(new UpdateAppAction());
|
||||
}
|
||||
|
||||
doNotUpdateApp() {
|
||||
this.store.dispatch(new DoNotUpdateAppAction());
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { ModuleWithProviders } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { MissingDeviceComponent } from './../components/missing-device/missing-device.component';
|
||||
import { PrivilegeCheckerComponent } from './../components/privilege-checker';
|
||||
import { MainAppComponent } from './../main-app/main-app.component';
|
||||
import { mainAppRoutes } from './../main-app/main-app.routes';
|
||||
|
||||
import { UhkDeviceConnectedGuard } from './../services/uhk-device-connected.guard';
|
||||
import { UhkDeviceDisconnectedGuard } from './../services/uhk-device-disconnected.guard';
|
||||
import { UhkDeviceInitializedGuard } from './../services/uhk-device-initialized.guard';
|
||||
import { UhkDeviceUninitializedGuard } from './../services/uhk-device-uninitialized.guard';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: 'detection',
|
||||
component: MissingDeviceComponent,
|
||||
canActivate: [UhkDeviceDisconnectedGuard]
|
||||
},
|
||||
{
|
||||
path: 'privilege',
|
||||
component: PrivilegeCheckerComponent,
|
||||
canActivate: [UhkDeviceConnectedGuard, UhkDeviceUninitializedGuard]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MainAppComponent,
|
||||
canActivate: [UhkDeviceInitializedGuard],
|
||||
children: mainAppRoutes
|
||||
}
|
||||
];
|
||||
|
||||
export const appRoutingProviders: any[] = [];
|
||||
|
||||
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { useHash: true });
|
||||
@@ -1 +0,0 @@
|
||||
export * from './app.component';
|
||||
@@ -1 +0,0 @@
|
||||
export { KeymapEditComponent } from './keymap-edit.component';
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Component, HostListener, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import '@ngrx/core/add/operator/select';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/first';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/withLatestFrom';
|
||||
|
||||
import { Keymap } from '../../../shared/config-serializer/config-items/keymap';
|
||||
import { UhkBuffer } from '../../../shared/config-serializer/uhk-buffer';
|
||||
import { AppState } from '../../../shared/store';
|
||||
import { SvgKeyboardWrapComponent } from '../../../shared/components/svg/wrap';
|
||||
import { KeymapEditComponent as SharedKeymapEditComponent } from '../../../shared/components/keymap/edit';
|
||||
|
||||
import { UhkDeviceService } from '../../../services/uhk-device.service';
|
||||
import { getUserConfiguration } from '../../../shared/store/reducers/user-configuration';
|
||||
|
||||
@Component({
|
||||
selector: 'keymap-edit',
|
||||
templateUrl: '../../../shared/components/keymap/edit/keymap-edit.component.html',
|
||||
styleUrls: ['../../../shared/components/keymap/edit/keymap-edit.component.scss'],
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class KeymapEditComponent extends SharedKeymapEditComponent {
|
||||
|
||||
@ViewChild(SvgKeyboardWrapComponent) wrap: SvgKeyboardWrapComponent;
|
||||
|
||||
constructor(
|
||||
store: Store<AppState>,
|
||||
route: ActivatedRoute,
|
||||
private uhkDevice: UhkDeviceService
|
||||
) {
|
||||
super(store, route);
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.u', ['$event'])
|
||||
onCtrlU(event: KeyboardEvent): void {
|
||||
console.log('ctrl + u pressed');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.sendLayer();
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.i', ['$event'])
|
||||
onCtrlI(event: KeyboardEvent): void {
|
||||
console.log('ctrl + i pressed');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.sendKeymap();
|
||||
}
|
||||
|
||||
private sendLayer(): void {
|
||||
const currentLayer: number = this.wrap.getSelectedLayer();
|
||||
this.keymap$
|
||||
.first()
|
||||
.map(keymap => keymap.layers[currentLayer])
|
||||
.withLatestFrom(this.store.let(getUserConfiguration()))
|
||||
.map(([layer, userConfig]) => {
|
||||
const uhkBuffer = new UhkBuffer();
|
||||
layer.toBinary(uhkBuffer, userConfig);
|
||||
return uhkBuffer.getBufferContent();
|
||||
})
|
||||
.switchMap((buffer: Buffer) => this.uhkDevice.sendConfig(buffer))
|
||||
.do(response => console.log('Sending layer finished', response))
|
||||
.switchMap(() => this.uhkDevice.applyConfig())
|
||||
.subscribe(
|
||||
response => console.log('Applying layer finished', response),
|
||||
error => console.error('Error during uploading layer', error),
|
||||
() => console.log('Layer has been sucessfully uploaded')
|
||||
);
|
||||
}
|
||||
|
||||
private sendKeymap(): void {
|
||||
this.keymap$
|
||||
.first()
|
||||
.withLatestFrom(this.store.let(getUserConfiguration()))
|
||||
.map(([keymap, userConfig]) => {
|
||||
const uhkBuffer = new UhkBuffer();
|
||||
keymap.toBinary(uhkBuffer, userConfig);
|
||||
return uhkBuffer.getBufferContent();
|
||||
})
|
||||
.switchMap((buffer: Buffer) => this.uhkDevice.sendConfig(buffer))
|
||||
.do(response => console.log('Sending keymap finished', response))
|
||||
.switchMap(() => this.uhkDevice.applyConfig())
|
||||
.subscribe(
|
||||
response => console.log('Applying keymap finished', response),
|
||||
error => console.error('Error during uploading keymap', error),
|
||||
() => console.log('Keymap has been sucessfully uploaded')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './keymap.routes';
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/ignoreElements';
|
||||
import 'rxjs/add/operator/takeWhile';
|
||||
|
||||
import { UhkDeviceService } from '../../services/uhk-device.service';
|
||||
|
||||
@Component({
|
||||
selector: 'missing-device',
|
||||
templateUrl: 'missing-device.component.html'
|
||||
})
|
||||
export class MissingDeviceComponent {
|
||||
|
||||
constructor(uhkDevice: UhkDeviceService, router: Router) {
|
||||
uhkDevice.isConnected()
|
||||
.distinctUntilChanged()
|
||||
.takeWhile(connected => !connected)
|
||||
.ignoreElements()
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
router.navigate(['/privilege']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/// <reference path="../../custom_types/sudo-prompt.d.ts"/>
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/ignoreElements';
|
||||
import 'rxjs/add/operator/takeWhile';
|
||||
|
||||
import { remote } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
|
||||
import { UhkDeviceService } from '../../services/uhk-device.service';
|
||||
import { LogService } from '../../shared/services/logger.service';
|
||||
|
||||
@Component({
|
||||
selector: 'privilege-checker',
|
||||
templateUrl: 'privilege-checker.component.html',
|
||||
styleUrls: ['privilege-checker.component.scss']
|
||||
})
|
||||
export class PrivilegeCheckerComponent {
|
||||
|
||||
private rootDir: string;
|
||||
|
||||
constructor(private router: Router,
|
||||
private uhkDevice: UhkDeviceService,
|
||||
private logService: LogService) {
|
||||
if (isDev) {
|
||||
this.rootDir = path.resolve(path.join(remote.process.cwd(), remote.process.argv[1]), '..');
|
||||
} else {
|
||||
this.rootDir = path.dirname(remote.app.getAppPath());
|
||||
}
|
||||
this.logService.info('App root dir: ', this.rootDir);
|
||||
|
||||
uhkDevice.isConnected()
|
||||
.distinctUntilChanged()
|
||||
.takeWhile(connected => connected)
|
||||
.ignoreElements()
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
router.navigate(['/detection']);
|
||||
}
|
||||
});
|
||||
uhkDevice.isInitialized()
|
||||
.distinctUntilChanged()
|
||||
.takeWhile(initialized => !initialized)
|
||||
.ignoreElements()
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
router.navigate(['/']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setUpPermissions(): void {
|
||||
let permissionSetter: Observable<void>;
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
permissionSetter = this.setUpPermissionsOnLinux();
|
||||
break;
|
||||
// HID API shouldn't need privilege escalation on Windows
|
||||
// TODO: If all HID API test success then delete this branch and setUpPermissionsOnWin() method
|
||||
// case 'win32':
|
||||
// permissionSetter = this.setUpPermissionsOnWin();
|
||||
// break;
|
||||
default:
|
||||
permissionSetter = Observable.throw('Permissions couldn\'t be set. Invalid platform: ' + process.platform);
|
||||
break;
|
||||
}
|
||||
permissionSetter.subscribe({
|
||||
error: e => {
|
||||
console.log(e);
|
||||
},
|
||||
complete: () => {
|
||||
this.logService.info('Permissions has been successfully set');
|
||||
this.uhkDevice.initialize();
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setUpPermissionsOnLinux(): Observable<void> {
|
||||
const subject = new ReplaySubject<void>();
|
||||
const scriptPath = path.resolve(this.rootDir, 'rules/setup-rules.sh');
|
||||
const options = {
|
||||
name: 'Setting UHK access rules'
|
||||
};
|
||||
const command = `sh ${scriptPath}`;
|
||||
console.log(command);
|
||||
sudo.exec(command, options, (error: any) => {
|
||||
if (error) {
|
||||
subject.error(error);
|
||||
} else {
|
||||
subject.complete();
|
||||
}
|
||||
});
|
||||
|
||||
return subject.asObservable();
|
||||
}
|
||||
|
||||
// TODO: If all HID API test success then delete this method.
|
||||
// and remove zadic-${process.arch}.exe files from windows installer resources
|
||||
// private setUpPermissionsOnWin(): Observable<void> {
|
||||
// const subject = new ReplaySubject<void>();
|
||||
//
|
||||
// // source code: https://github.com/pbatard/libwdi
|
||||
// const scriptPath = path.resolve(this.rootDir, `rules/zadic-${process.arch}.exe`);
|
||||
// const options = {
|
||||
// name: 'Setting UHK access rules'
|
||||
// };
|
||||
// const params = [
|
||||
// `--vid $\{Constants.VENDOR_ID}`,
|
||||
// `--pid $\{Constants.PRODUCT_ID}`,
|
||||
// '--iface 0', // interface ID
|
||||
// '--usealldevices', // if the device has installed USB driver than overwrite it
|
||||
// '--noprompt' // return at the end of the installation and not waiting for any user command
|
||||
// ];
|
||||
// const paramsString = params.join(' ');
|
||||
// const command = `"${scriptPath}" ${paramsString}`;
|
||||
//
|
||||
// sudo.exec(command, options, (error: any) => {
|
||||
// if (error) {
|
||||
// subject.error(error);
|
||||
// } else {
|
||||
// subject.complete();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return subject.asObservable();
|
||||
// }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'uhk-message',
|
||||
templateUrl: 'uhk-message.component.html',
|
||||
styleUrls: ['uhk-message.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UhkMessageComponent {
|
||||
@Input() title: string;
|
||||
@Input() subtitle: string;
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/// <reference path="./custom_types/electron-is-dev.d.ts"/>
|
||||
/// <reference path="./custom_types/command-line-args.d.ts"/>
|
||||
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import * as log from 'electron-log';
|
||||
import * as path from 'path';
|
||||
import { ProgressInfo } from 'electron-builder-http/out/ProgressCallbackTransform';
|
||||
import { VersionInfo } from 'electron-builder-http/out/updateInfo';
|
||||
import * as settings from 'electron-settings';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
import * as commandLineArgs from 'command-line-args';
|
||||
|
||||
import { IpcEvents } from './shared/util';
|
||||
import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
import { CommandLineArgs } from './shared/models/command-line-args';
|
||||
|
||||
const optionDefinitions = [
|
||||
{ name: 'addons', type: Boolean, defaultOption: false }
|
||||
];
|
||||
|
||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||
|
||||
// import './dev-extension';
|
||||
require('electron-debug')({ showDevTools: false, enabled: true });
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let win: Electron.BrowserWindow;
|
||||
|
||||
log.transports.file.level = 'debug';
|
||||
autoUpdater.logger = log;
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
title: 'UHK Agent',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
},
|
||||
icon: 'images/agent-icon.png'
|
||||
});
|
||||
win.setMenuBarVisibility(false);
|
||||
win.maximize();
|
||||
|
||||
const indexPath = path.resolve(__dirname, './index.html');
|
||||
// and load the index.html of the app.
|
||||
|
||||
win.loadURL(`file://${indexPath}`);
|
||||
|
||||
win.on('page-title-updated', event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Emitted when the window is closed.
|
||||
win.on('closed', () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
});
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
});
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
saveFirtsRun();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here
|
||||
|
||||
// =========================================================================
|
||||
// Auto update events
|
||||
// =========================================================================
|
||||
function checkForUpdate() {
|
||||
if (isDev) {
|
||||
const msg = 'Application update is not working in dev mode.';
|
||||
log.info(msg);
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFirstRun()) {
|
||||
const msg = 'Application update is skipping at first run.';
|
||||
log.info(msg);
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdater.allowPrerelease = allowPreRelease();
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (ev: any, info: VersionInfo) => {
|
||||
autoUpdater.downloadUpdate();
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (ev: any, info: VersionInfo) => {
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (ev: any, err: string) => {
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, err.substr(0, 100));
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj: ProgressInfo) => {
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloadProgress, progressObj);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (ev: any, info: VersionInfo) => {
|
||||
sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.autoUpdater.updateAndRestart, () => autoUpdater.quitAndInstall(true));
|
||||
|
||||
ipcMain.on(IpcEvents.app.appStarted, () => {
|
||||
if (checkForUpdateAtStartup()) {
|
||||
checkForUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => checkForUpdate());
|
||||
|
||||
ipcMain.on(IpcEvents.app.getCommandLineArgs, (event: any) => event.sender.send(IpcEvents.app.getCommandLineArgsReply, options));
|
||||
|
||||
function isFirstRun() {
|
||||
if (!settings.has('firstRunVersion')) {
|
||||
return true;
|
||||
}
|
||||
const firstRunVersion = settings.get('firstRunVersion');
|
||||
log.info(`firstRunVersion: ${firstRunVersion}`);
|
||||
log.info(`package.version: ${app.getVersion()}`);
|
||||
|
||||
return firstRunVersion !== app.getVersion();
|
||||
}
|
||||
|
||||
function saveFirtsRun() {
|
||||
settings.set('firstRunVersion', app.getVersion());
|
||||
}
|
||||
|
||||
function sendIpcToWindow(message: string, arg?: any) {
|
||||
log.info('sendIpcToWindow:', message, arg);
|
||||
if (!win || win.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
win.webContents.send(message, arg);
|
||||
}
|
||||
|
||||
function allowPreRelease() {
|
||||
const autoUpdateSettings = getAutoUpdateSettings();
|
||||
|
||||
return autoUpdateSettings && autoUpdateSettings.usePreReleaseUpdate;
|
||||
}
|
||||
|
||||
function checkForUpdateAtStartup() {
|
||||
const autoUpdateSettings = getAutoUpdateSettings();
|
||||
|
||||
return autoUpdateSettings && autoUpdateSettings.checkForUpdateOnStartUp;
|
||||
}
|
||||
|
||||
function getAutoUpdateSettings() {
|
||||
const storageService = new ElectronDataStorageRepositoryService();
|
||||
return storageService.getAutoUpdateSettings();
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ultimate Hacking Keyboard Configurator</title>
|
||||
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="shortcut icon" href="images/favicon.ico">
|
||||
<script src="polyfills.uhk.js"></script>
|
||||
<script src="vendor.uhk.js"></script>
|
||||
<!--<script src="vendor/usb/usb.js"></script>-->
|
||||
<script>
|
||||
usb = require('usb');
|
||||
nodeHid = require('node-hid');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Google Tag Manager -->
|
||||
<noscript>
|
||||
<iframe src="//www.googletagmanager.com/ns.html?id=GTM-PQLCXB" height="0" width="0" style="display:none;visibility:hidden"></iframe>
|
||||
</noscript>
|
||||
<script>
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-PQLCXB');
|
||||
</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
|
||||
<app></app>
|
||||
|
||||
<script src="app.uhk.js"></script> <!-- This should be moved to the head -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './main-app.component';
|
||||
export * from './main-app.routes';
|
||||
@@ -1,6 +0,0 @@
|
||||
<notification></notification>
|
||||
<side-menu></side-menu>
|
||||
<div id="main-content" class="split split-horizontal main-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<ngrx-store-log-monitor toggleCommand="alt-t"></ngrx-store-log-monitor>
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { addOnRoutes } from '../shared/components/add-on';
|
||||
import { keymapRoutes } from '../components/keymap';
|
||||
import { macroRoutes } from '../shared/components/macro';
|
||||
import { settingsRoutes } from '../shared/components/settings';
|
||||
|
||||
export const mainAppRoutes: Routes = [
|
||||
...keymapRoutes,
|
||||
...macroRoutes,
|
||||
...addOnRoutes,
|
||||
...settingsRoutes
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import { IpcEvents } from '../shared/util';
|
||||
import { AppState } from '../store';
|
||||
import { CommandLineArgs } from '../shared/models/command-line-args';
|
||||
import { ProcessCommandLineArgsAction } from '../../../shared/src/store/actions/app.action';
|
||||
|
||||
@Injectable()
|
||||
export class AppRendererService {
|
||||
constructor(private store: Store<AppState>,
|
||||
private zone: NgZone) {
|
||||
this.registerEvents();
|
||||
}
|
||||
|
||||
getCommandLineArgs() {
|
||||
ipcRenderer.send(IpcEvents.app.getCommandLineArgs);
|
||||
}
|
||||
|
||||
private registerEvents() {
|
||||
ipcRenderer.on(IpcEvents.app.getCommandLineArgsReply, (event: string, arg: CommandLineArgs) => {
|
||||
this.dispachStoreAction(new ProcessCommandLineArgsAction(arg));
|
||||
});
|
||||
}
|
||||
|
||||
private dispachStoreAction(action: Action) {
|
||||
this.zone.run(() => this.store.dispatch(action));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkDeviceInitializedGuard implements CanActivate {
|
||||
|
||||
constructor(private uhkDevice: UhkDeviceService, private router: Router) { }
|
||||
|
||||
canActivate(): Observable<boolean> {
|
||||
return this.uhkDevice.isInitialized()
|
||||
.do(initialized => {
|
||||
if (!initialized) {
|
||||
this.router.navigate(['/detection']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkDeviceUninitializedGuard implements CanActivate {
|
||||
|
||||
constructor(private uhkDevice: UhkDeviceService, private router: Router) { }
|
||||
|
||||
canActivate(): Observable<boolean> {
|
||||
return this.uhkDevice.isInitialized()
|
||||
.do(initialized => {
|
||||
if (initialized) {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
})
|
||||
.map(initialized => !initialized);
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import { Inject, Injectable, NgZone, OnDestroy } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/combineLatest';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/first';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import { Device, findByIds, InEndpoint, Interface, on, OutEndpoint } from 'usb';
|
||||
|
||||
import { LogService } from '../shared/services/logger.service';
|
||||
import { Constants } from '../shared/util';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkLibUsbApiService extends UhkDeviceService implements OnDestroy {
|
||||
private device: Device;
|
||||
|
||||
static isUhkDevice(device: Device) {
|
||||
return device.deviceDescriptor.idVendor === Constants.VENDOR_ID &&
|
||||
device.deviceDescriptor.idProduct === Constants.PRODUCT_ID;
|
||||
}
|
||||
|
||||
constructor(zone: NgZone, protected logService: LogService) {
|
||||
super(logService);
|
||||
|
||||
this.initialize();
|
||||
|
||||
// The change detection doesn't work properly if the callbacks are called outside Angular Zone
|
||||
on('attach', (device: Device) => zone.run(() => this.onDeviceAttach(device)));
|
||||
on('detach', (device: Device) => zone.run(() => this.onDeviceDetach(device)));
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.initialized$.getValue()) {
|
||||
return;
|
||||
}
|
||||
this.device = findByIds(Constants.VENDOR_ID, Constants.PRODUCT_ID);
|
||||
this.connected$.next(!!this.device);
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.device.open();
|
||||
this.deviceOpened$.next(true);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const usbInterface: Interface = this.device.interface(0);
|
||||
// https://github.com/tessel/node-usb/issues/147
|
||||
// The function 'isKernelDriverActive' is not available on Windows and not even needed.
|
||||
if (usbInterface.isKernelDriverActive()) {
|
||||
usbInterface.detachKernelDriver();
|
||||
}
|
||||
|
||||
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
const inEndPoint: InEndpoint = <InEndpoint>usbInterface.endpoints[0];
|
||||
this.logService.info('Try to read');
|
||||
inEndPoint.transfer(Constants.MAX_PAYLOAD_SIZE, (error: string, receivedBuffer: Buffer) => {
|
||||
if (error) {
|
||||
this.logService.error('reading error', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
this.logService.info('read data', receivedBuffer);
|
||||
subscriber.next(receivedBuffer);
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const outEndPoint: OutEndpoint = <OutEndpoint>usbInterface.endpoints[1];
|
||||
const outSending = this.messageOut$.concatMap(senderPackage => {
|
||||
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
|
||||
this.logService.info('transfering', senderPackage.buffer);
|
||||
outEndPoint.transfer(senderPackage.buffer, error => {
|
||||
if (error) {
|
||||
this.logService.error('transfering errored', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
this.logService.info('transfering finished');
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
})).concat(this.messageIn$)
|
||||
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
|
||||
.catch((error: string) => {
|
||||
senderPackage.observer.error(error);
|
||||
return Observable.empty<void>();
|
||||
});
|
||||
}).publish();
|
||||
this.outSubscription = outSending.connect();
|
||||
|
||||
this.initialized$.next(true);
|
||||
}
|
||||
|
||||
hasPermissions(): Observable<boolean> {
|
||||
return this.isConnected()
|
||||
.combineLatest(this.deviceOpened$)
|
||||
.map((latest: boolean[]) => {
|
||||
const connected = latest[0];
|
||||
const opened = latest[1];
|
||||
if (!connected) {
|
||||
return false;
|
||||
} else if (opened) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
this.device.open();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
this.device.close();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
onDeviceAttach(device: Device) {
|
||||
if (!UhkLibUsbApiService.isUhkDevice(device)) {
|
||||
return;
|
||||
}
|
||||
// Ugly hack: device is not openable (on Windows) right after the attach
|
||||
Observable.timer(100)
|
||||
.first()
|
||||
.subscribe(() => this.initialize());
|
||||
}
|
||||
|
||||
onDeviceDetach(device: Device) {
|
||||
if (!UhkLibUsbApiService.isUhkDevice(device)) {
|
||||
return;
|
||||
}
|
||||
this.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { Effect, Actions } from '@ngrx/effects';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import * as app from '../../shared/store/actions/app.action';
|
||||
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
|
||||
import { AppRendererService } from '../../services/app-renderer.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationEffect {
|
||||
@Effect()
|
||||
appStart$: Observable<Action> = this.actions$
|
||||
.ofType(app.ActionTypes.APP_BOOTSRAPPED)
|
||||
.startWith(new app.AppStartedAction())
|
||||
.do(() => {
|
||||
this.appUpdateRendererService.sendAppStarted();
|
||||
this.appRendererService.getCommandLineArgs();
|
||||
});
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private appUpdateRendererService: AppUpdateRendererService,
|
||||
private appRendererService: AppRendererService) { }
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { AppUpdateEffect } from './app-update.effect';
|
||||
export { ApplicationEffect } from './app.effect';
|
||||
@@ -1,34 +0,0 @@
|
||||
/// <reference path="../custom_types/electron-is-dev.d.ts"/>
|
||||
import { createSelector } from 'reselect';
|
||||
import { compose } from '@ngrx/core/compose';
|
||||
import { storeFreeze } from 'ngrx-store-freeze';
|
||||
import { ActionReducer, combineReducers } from '@ngrx/store';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
|
||||
import { AppState as CommonState } from '../shared/store';
|
||||
import * as fromAppUpdate from './reducers/app-update.reducer';
|
||||
import { reducer as CommonReducer } from '../shared/store/reducers';
|
||||
|
||||
export interface AppState extends CommonState {
|
||||
appUpdate: fromAppUpdate.State;
|
||||
}
|
||||
|
||||
const reducers = {
|
||||
...CommonReducer,
|
||||
appUpdate: fromAppUpdate.reducer
|
||||
};
|
||||
|
||||
const developmentReducer: ActionReducer<AppState> = compose(storeFreeze, combineReducers)(reducers);
|
||||
const productionReducer: ActionReducer<AppState> = combineReducers(reducers);
|
||||
|
||||
export function reducer(state: any, action: any) {
|
||||
if (isDev) {
|
||||
return developmentReducer(state, action);
|
||||
} else {
|
||||
return productionReducer(state, action);
|
||||
}
|
||||
}
|
||||
|
||||
export const appUpdateState = (state: AppState) => state.appUpdate;
|
||||
|
||||
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [
|
||||
"electron-main.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"exclude": [
|
||||
"electron-main.ts",
|
||||
"webpack.config.*"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import './shared/vendor.ts';
|
||||
|
||||
import 'sudo-prompt';
|
||||
@@ -1,30 +0,0 @@
|
||||
//var webpack = require("webpack");
|
||||
var path = require('path');
|
||||
|
||||
var rootDir = path.resolve(__dirname, '../');
|
||||
|
||||
module.exports = {
|
||||
entry: [ path.resolve(rootDir, 'src/electron-main.ts')],
|
||||
output: {
|
||||
path: rootDir + "/dist",
|
||||
filename: "electron-main.js"
|
||||
},
|
||||
target: 'electron-main',
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
extensions: ['.webpack.js', '.web.js', '.ts', '.js'],
|
||||
modules: ["node_modules"]
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.ts$/, loader: 'ts-loader?' + JSON.stringify({ configFileName: 'tsconfig-electron-main.json' }), exclude: /node_modules/ },
|
||||
]},
|
||||
plugins: [
|
||||
// new webpack.optimize.UglifyJsPlugin({ minimize: true })
|
||||
],
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
const webpack = require("webpack");
|
||||
const SvgStore = require('webpack-svgstore-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
const CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
|
||||
const ContextReplacementPlugin = require("webpack/lib/ContextReplacementPlugin");
|
||||
|
||||
const webpackHelper = require('../../scripts/webpack-helper');
|
||||
|
||||
const rootDir = path.resolve(__dirname, '../');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
polyfills: path.resolve(rootDir, 'src/shared/polyfills.ts'),
|
||||
vendor: path.resolve(rootDir, 'src/vendor.ts'),
|
||||
app: path.resolve(rootDir, 'src/main.ts')
|
||||
},
|
||||
output: {
|
||||
path: rootDir + "/dist",
|
||||
filename: "[name].uhk.js"
|
||||
},
|
||||
target: 'electron-renderer',
|
||||
externals: {
|
||||
usb: 'usb',
|
||||
'node-hid': 'nodeHid'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
extensions: ['.webpack.js', '.web.js', '.ts', '.js'],
|
||||
modules: ["node_modules"],
|
||||
alias: {
|
||||
jquery: 'jquery/dist/jquery.min.js',
|
||||
select2: 'select2/dist/js/select2.full.min.js',
|
||||
'file-saver': 'filesaver.js/FileSaver.min.js'
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.ts$/, use: ['ts-loader', 'angular2-template-loader'], exclude: /node_modules/ },
|
||||
{ test: /\.html$/, loader: 'html-loader?attrs=false' },
|
||||
{
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['raw-loader', 'sass-loader']
|
||||
},
|
||||
{ test: /jquery/, loader: 'expose-loader?$!expose-loader?jQuery' },
|
||||
{ test: require.resolve("usb"), loader: "expose-loader?usb" },
|
||||
{ test: require.resolve("node-hid"), loader: "expose-loader?node-hid" }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
// new webpack.optimize.UglifyJsPlugin({ minimize: true })
|
||||
new SvgStore({
|
||||
svgoOptions: {
|
||||
plugins: [
|
||||
{ removeTitle: true }
|
||||
]
|
||||
}
|
||||
}),
|
||||
new CopyWebpackPlugin(
|
||||
[
|
||||
{
|
||||
from: path.join(__dirname, 'index.html'), flatten: true
|
||||
},
|
||||
{
|
||||
from: 'node_modules/font-awesome/css/font-awesome.min.css',
|
||||
to: 'vendor/font-awesome/css/font-awesome.min.css'
|
||||
},
|
||||
{
|
||||
from: 'node_modules/font-awesome/fonts',
|
||||
to: 'vendor/font-awesome/fonts'
|
||||
},
|
||||
{
|
||||
from: 'node_modules/bootstrap/dist/',
|
||||
to: 'vendor/bootstrap'
|
||||
},
|
||||
{
|
||||
from: 'images',
|
||||
to: 'images'
|
||||
},
|
||||
{
|
||||
from: 'rules',
|
||||
to: 'rules'
|
||||
},
|
||||
{
|
||||
from: 'node_modules/usb',
|
||||
to: 'vendor/usb'
|
||||
},
|
||||
{
|
||||
from: 'electron/src/package.json',
|
||||
to: 'package.json'
|
||||
}
|
||||
]
|
||||
),
|
||||
new webpack.ProvidePlugin({
|
||||
$: "jquery",
|
||||
jQuery: "jquery"
|
||||
}),
|
||||
new CommonsChunkPlugin({
|
||||
name: ['app', 'vendor', 'polyfills']
|
||||
}),
|
||||
new ContextReplacementPlugin(
|
||||
// The (\\|\/) piece accounts for path separators in *nix and Windows
|
||||
/angular(\\|\/)core(\\|\/)@angular/,
|
||||
webpackHelper.root(__dirname, './src') // location of your src
|
||||
)
|
||||
]
|
||||
|
||||
};
|
||||
7
lerna.json
Normal file
7
lerna.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"lerna": "2.0.0",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "1.0.0"
|
||||
}
|
||||
11156
package-lock.json
generated
11156
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
158
package.json
158
package.json
@@ -14,108 +14,100 @@
|
||||
"npm": ">=5.1.0 <6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ngrx/store-devtools": "3.2.4",
|
||||
"@ngrx/store-log-monitor": "3.0.2",
|
||||
"@types/bootstrap": "^3.3.35",
|
||||
"@types/core-js": "0.9.42",
|
||||
"@angular/cli": "^1.3.0-rc.5",
|
||||
"@angular/compiler-cli": "^4.3.3",
|
||||
"@angular/language-service": "^4.3.3",
|
||||
"@types/electron-devtools-installer": "^2.0.2",
|
||||
"@types/electron-settings": "^3.0.0",
|
||||
"@types/file-saver": "0.0.1",
|
||||
"@types/jquery": "^3.2.11",
|
||||
"@types/node": "^8.0.20",
|
||||
"@types/file-saver": "~0.0.1",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/jquery": "^3.2.9",
|
||||
"@types/node": "~6.0.60",
|
||||
"@types/node-hid": "^0.5.2",
|
||||
"@types/usb": "^1.1.3",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"angular-notifier": "^2.0.0",
|
||||
"autoprefixer": "^6.5.3",
|
||||
"buffer": "^5.0.6",
|
||||
"codelyzer": "~3.0.1",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"core-js": "^2.4.1",
|
||||
"css-loader": "^0.28.1",
|
||||
"cssnano": "^3.10.0",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "1.7.5",
|
||||
"electron-builder": "^19.21.0",
|
||||
"electron-debug": "^1.4.0",
|
||||
"electron-builder": "19.6.1",
|
||||
"electron-debug": "^1.3.0",
|
||||
"electron-devtools-installer": "^2.2.0",
|
||||
"electron-rebuild": "^1.6.0",
|
||||
"expose-loader": "^0.7.3",
|
||||
"html-loader": "^0.5.1",
|
||||
"jsonfile": "^3.0.1",
|
||||
"node-sass": "^4.5.2",
|
||||
"npm-run-all": "4.0.2",
|
||||
"path": "^0.12.7",
|
||||
"raw-loader": "^0.5.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"sass-loader": "^6.0.6",
|
||||
"standard-version": "^4.2.0",
|
||||
"stylelint": "^8.0.0",
|
||||
"ts-loader": "^2.3.2",
|
||||
"tslint": "^5.6.0",
|
||||
"webpack": "^3.5.4",
|
||||
"webpack-dev-server": "^2.7.1",
|
||||
"webpack-svgstore-plugin": "4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^4.3.4",
|
||||
"@angular/common": "^4.3.4",
|
||||
"@angular/compiler": "^4.3.4",
|
||||
"@angular/core": "^4.3.4",
|
||||
"@angular/forms": "^4.3.4",
|
||||
"@angular/platform-browser": "^4.3.4",
|
||||
"@angular/platform-browser-dynamic": "^4.3.4",
|
||||
"@angular/router": "^4.3.4",
|
||||
"@ngrx/core": "^1.2.0",
|
||||
"@ngrx/effects": "^2.0.4",
|
||||
"@ngrx/router-store": "^1.2.6",
|
||||
"@ngrx/store": "^2.2.3",
|
||||
"angular-notifier": "^2.0.0",
|
||||
"bootstrap": "^3.3.7",
|
||||
"browser-stdout": "^1.3.0",
|
||||
"buffer": "^5.0.7",
|
||||
"command-line-args": "^4.0.7",
|
||||
"core-js": "^2.5.0",
|
||||
"dragula": "^3.7.2",
|
||||
"electron-is-dev": "^0.3.0",
|
||||
"electron-log": "^2.2.7",
|
||||
"electron-rebuild": "^1.5.11",
|
||||
"electron-settings": "^3.1.1",
|
||||
"electron-updater": "^2.8.7",
|
||||
"filesaver.js": "^0.2.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "^3.2.1",
|
||||
"json-loader": "^0.5.7",
|
||||
"ng2-dragula": "^1.5.0",
|
||||
"ng2-select2": "^1.0.0-beta.10",
|
||||
"ngrx-store-freeze": "^0.1.9",
|
||||
"node-hid": "^0.5.4",
|
||||
"exports-loader": "^0.6.3",
|
||||
"file-loader": "^0.10.0",
|
||||
"istanbul-instrumenter-loader": "^2.0.0",
|
||||
"jasmine-core": "~2.6.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"jsonfile": "3.0.1",
|
||||
"karma": "~1.7.0",
|
||||
"karma-chrome-launcher": "~2.1.1",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"lerna": "^2.0.0",
|
||||
"less-loader": "^4.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"postcss-loader": "^1.3.3",
|
||||
"postcss-url": "^5.1.2",
|
||||
"protractor": "~5.1.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"reselect": "^3.0.1",
|
||||
"rxjs": "^5.4.3",
|
||||
"select2": "^4.0.3",
|
||||
"sudo-prompt": "^7.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"rxjs": "^5.4.2",
|
||||
"sass-loader": "^6.0.3",
|
||||
"script-loader": "^0.7.0",
|
||||
"source-map-loader": "^0.2.0",
|
||||
"standard-version": "^4.2.0",
|
||||
"style-loader": "^0.13.1",
|
||||
"stylelint": "^7.13.0",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"svg-sprite": "^1.3.7",
|
||||
"ts-loader": "^2.3.1",
|
||||
"ts-node": "~3.0.4",
|
||||
"tslint": "~5.5.0",
|
||||
"typescript": "^2.4.2",
|
||||
"usb": "git+https://github.com/tessel/node-usb.git#1.3.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^2.4.1",
|
||||
"webpack-dev-server": "~2.4.5",
|
||||
"webpack-svgstore-plugin": "^4.0.1",
|
||||
"xml-loader": "^1.2.1",
|
||||
"zone.js": "^0.8.16"
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "run-p \"symlink -- -i\" ",
|
||||
"test": "cd ./test-serializer && node ./test-serializer.js",
|
||||
"postinstall": "lerna bootstrap",
|
||||
"test": "lerna exec --scope test-serializer npm test",
|
||||
"lint": "run-s -scn lint:ts lint:style",
|
||||
"lint:ts": "run-p -sn lint:ts:electron-main lint:ts:electron-renderer lint:ts:web lint:ts:test-serializer",
|
||||
"lint:ts:electron-main": "tslint --type-check --project ./electron/src/tsconfig-electron-main.json",
|
||||
"lint:ts:electron-renderer": "tslint --type-check --project ./electron/src/tsconfig.json",
|
||||
"lint:ts:web": "tslint --type-check --project ./web/src/tsconfig.json",
|
||||
"lint:ts:test-serializer": "tslint --type-check --project ./test-serializer/tsconfig.json",
|
||||
"lint:style": "stylelint \"electron/**/*.scss\" \"web/**/*.scss\" \"shared/**/*.scss\" --syntax scss",
|
||||
"build": "run-p build:web build:electron",
|
||||
"build:web": "webpack --config \"web/src/webpack.config.js\"",
|
||||
"build:electron": "run-s -sn build:electron:main build:electron:app install:build-deps",
|
||||
"build:electron:main": "webpack --config \"electron/src/webpack.config.electron-main.js\"",
|
||||
"build:electron:app": "webpack --config \"electron/src/webpack.config.js\"",
|
||||
"build:usb": "electron-rebuild -w usb,node-hid -p -m electron/dist",
|
||||
"build:test": "webpack --config \"test-serializer/webpack.config.js\"",
|
||||
"server:web": "webpack-dev-server --config \"web/src/webpack.config.js\" --content-base \"./web/dist\"",
|
||||
"server:electron": "webpack --config \"electron/src/webpack.config.js\" --watch",
|
||||
"electron": "electron electron/dist/electron-main.js",
|
||||
"symlink": "node ./tools/symlinker",
|
||||
"lint:ts:electron-main": "tslint --type-check --project ./packages/uhk-agent/tsconfig.json",
|
||||
"lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json",
|
||||
"lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json",
|
||||
"lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json",
|
||||
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
|
||||
"build": "run-s build:web build:electron",
|
||||
"build:web": "lerna exec --scope uhk-web npm run build",
|
||||
"build:electron": "run-s -sn build:electron:renderer build:electron:main",
|
||||
"build:electron:main": "lerna exec --scope uhk-agent npm run build",
|
||||
"build:electron:renderer": "lerna exec --scope uhk-web npm run build:renderer",
|
||||
"build:test": "lerna exec --scope test-serializer npm run build",
|
||||
"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",
|
||||
"standard-version": "standard-version",
|
||||
"pack": "node ./scripts/release.js",
|
||||
"install:build-deps": "cd electron/dist && npm i && cd .. && npm run build:usb",
|
||||
"sprites": "node ./scripts/generate-svg-sprites",
|
||||
"release": "node ./scripts/release.js",
|
||||
"clean": "rimraf ./node_modules ./electron/dist"
|
||||
"clean": "lerna exec rimraf ./node_modules ./dist && rimraf ./node_modules ./dist package-lock.json"
|
||||
}
|
||||
}
|
||||
|
||||
25
packages/test-serializer/package.json
Normal file
25
packages/test-serializer/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "test-serializer",
|
||||
"main": "test-serializer.js",
|
||||
"version": "0.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"engines": {
|
||||
"node": ">=8.1.0 <9.0.0",
|
||||
"npm": ">=5.1.0 <6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"uhk-web": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"test": "node ./test-serializer.js"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { UserConfiguration } from '../shared/src/config-serializer/config-items/user-configuration';
|
||||
import { UhkBuffer } from '../shared/src/config-serializer/uhk-buffer';
|
||||
import { UserConfiguration } from '../uhk-web/src/app/config-serializer/config-items/user-configuration';
|
||||
import { UhkBuffer } from '../uhk-web/src/app/config-serializer/uhk-buffer';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const userConfig = JSON.parse(fs.readFileSync('../shared/src/config-serializer/user-config.json'));
|
||||
const userConfig = JSON.parse(fs.readFileSync('../uhk-web/src/app/config-serializer/user-config.json'));
|
||||
|
||||
const config1Js = userConfig;
|
||||
const config1Ts: UserConfiguration = new UserConfiguration().fromJsonObject(config1Js);
|
||||
@@ -8,8 +8,7 @@
|
||||
"../node_modules/@types"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
"core-js"
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
2604
packages/uhk-agent/package-lock.json
generated
Normal file
2604
packages/uhk-agent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
packages/uhk-agent/package.json
Normal file
36
packages/uhk-agent/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "uhk-agent",
|
||||
"main": "electron-main.js",
|
||||
"version": "0.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"engines": {
|
||||
"node": ">=8.1.0 <9.0.0",
|
||||
"npm": ">=5.1.0 <6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"command-line-args": "4.0.6",
|
||||
"electron": "1.7.5",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"electron-log": "2.2.6",
|
||||
"electron-rebuild": "1.6.0",
|
||||
"electron-settings": "3.0.14",
|
||||
"electron-updater": "2.2.0",
|
||||
"node-hid": "0.5.4",
|
||||
"sudo-prompt": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"uhk-common": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./dist/electron-main.js",
|
||||
"build": "webpack && npm run install:build-deps && npm run build:usb",
|
||||
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
||||
"install:build-deps": "cd ./dist && npm i"
|
||||
}
|
||||
}
|
||||
117
packages/uhk-agent/src/electron-main.ts
Normal file
117
packages/uhk-agent/src/electron-main.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/// <reference path="./custom_types/electron-is-dev.d.ts"/>
|
||||
/// <reference path="./custom_types/command-line-args.d.ts"/>
|
||||
|
||||
import './polyfills';
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import * as commandLineArgs from 'command-line-args';
|
||||
|
||||
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
import { CommandLineArgs } from 'uhk-common';
|
||||
import { DeviceService } from './services/device.service';
|
||||
import { logger } from './services/logger.service';
|
||||
import { AppUpdateService } from './services/app-update.service';
|
||||
import { AppService } from './services/app.service';
|
||||
import { SudoService } from './services/sudo.service';
|
||||
|
||||
const optionDefinitions = [
|
||||
{ name: 'addons', type: Boolean, defaultOption: false }
|
||||
];
|
||||
|
||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||
|
||||
// import './dev-extension';
|
||||
// require('electron-debug')({ showDevTools: true, enabled: true });
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let win: Electron.BrowserWindow;
|
||||
autoUpdater.logger = logger;
|
||||
|
||||
let deviceService: DeviceService;
|
||||
let appUpdateService: AppUpdateService;
|
||||
let appService: AppService;
|
||||
let sudoService: SudoService;
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
title: 'UHK Agent',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
},
|
||||
icon: 'assets/images/agent-icon.png'
|
||||
});
|
||||
win.setMenuBarVisibility(false);
|
||||
win.maximize();
|
||||
deviceService = new DeviceService(logger, win);
|
||||
appUpdateService = new AppUpdateService(logger, win, app);
|
||||
appService = new AppService(logger, win, deviceService, options);
|
||||
sudoService = new SudoService(logger);
|
||||
// and load the index.html of the app.
|
||||
|
||||
win.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'renderer/index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
|
||||
win.on('page-title-updated', (event: any) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Emitted when the window is closed.
|
||||
win.on('closed', () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
deviceService = null;
|
||||
appUpdateService = null;
|
||||
appService = null;
|
||||
sudoService = null;
|
||||
});
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
});
|
||||
|
||||
win.webContents.on('crashed', (event: any) => {
|
||||
logger.error(event);
|
||||
});
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
if (appUpdateService) {
|
||||
appUpdateService.saveFirtsRun();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here
|
||||
6
packages/uhk-agent/src/manifest.json
Normal file
6
packages/uhk-agent/src/manifest.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "UHK-Agent",
|
||||
"version": "1.0",
|
||||
"devtools_page": "foo.html",
|
||||
"default_locale": "en"
|
||||
}
|
||||
@@ -14,8 +14,6 @@
|
||||
"npm": ">=5.1.0 <6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-hid": "0.5.4",
|
||||
"usb": "git+https://github.com/aktary/node-usb.git"
|
||||
"node-hid": "0.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/** Evergreen browsers require these. **/
|
||||
import 'core-js/es6/reflect';
|
||||
import 'core-js/es7/array';
|
||||
import 'core-js/es7/object';
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js/dist/zone';
|
||||
110
packages/uhk-agent/src/services/app-update.service.ts
Normal file
110
packages/uhk-agent/src/services/app-update.service.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { ProgressInfo } from 'electron-builder-http/out/ProgressCallbackTransform';
|
||||
import { VersionInfo } from 'electron-builder-http/out/publishOptions';
|
||||
import * as settings from 'electron-settings';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
|
||||
import { IpcEvents, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
|
||||
export class AppUpdateService extends MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow,
|
||||
private app: Electron.App) {
|
||||
super(logService, win);
|
||||
|
||||
this.initListeners();
|
||||
logService.info('AppUpdateService init success');
|
||||
}
|
||||
|
||||
saveFirtsRun() {
|
||||
settings.set('firstRunVersion', this.app.getVersion());
|
||||
}
|
||||
|
||||
private initListeners() {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (ev: any, info: VersionInfo) => {
|
||||
autoUpdater.downloadUpdate();
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (ev: any, info: VersionInfo) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (ev: any, err: string) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, err.substr(0, 100));
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj: ProgressInfo) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloadProgress, progressObj);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (ev: any, info: VersionInfo) => {
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.autoUpdater.updateAndRestart, () => autoUpdater.quitAndInstall(true));
|
||||
|
||||
ipcMain.on(IpcEvents.app.appStarted, () => {
|
||||
if (this.checkForUpdateAtStartup()) {
|
||||
this.checkForUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => this.checkForUpdate());
|
||||
}
|
||||
|
||||
private checkForUpdate() {
|
||||
if (isDev) {
|
||||
const msg = 'Application update is not working in dev mode.';
|
||||
this.logService.info(msg);
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isFirstRun()) {
|
||||
const msg = 'Application update is skipping at first run.';
|
||||
this.logService.info(msg);
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdater.allowPrerelease = this.allowPreRelease();
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
private isFirstRun() {
|
||||
if (!settings.has('firstRunVersion')) {
|
||||
return true;
|
||||
}
|
||||
const firstRunVersion = settings.get('firstRunVersion');
|
||||
this.logService.info(`firstRunVersion: ${firstRunVersion}`);
|
||||
this.logService.info(`package.version: ${this.app.getVersion()}`);
|
||||
|
||||
return firstRunVersion !== this.app.getVersion();
|
||||
}
|
||||
|
||||
private allowPreRelease() {
|
||||
const autoUpdateSettings = this.getAutoUpdateSettings();
|
||||
|
||||
return autoUpdateSettings && autoUpdateSettings.usePreReleaseUpdate;
|
||||
}
|
||||
|
||||
private checkForUpdateAtStartup() {
|
||||
const autoUpdateSettings = this.getAutoUpdateSettings();
|
||||
|
||||
return autoUpdateSettings && autoUpdateSettings.checkForUpdateOnStartUp;
|
||||
}
|
||||
|
||||
private getAutoUpdateSettings() {
|
||||
// const storageService = new ElectronDataStorageRepositoryService();
|
||||
// return storageService.getAutoUpdateSettings();
|
||||
return { checkForUpdateOnStartUp: false, usePreReleaseUpdate: false };
|
||||
}
|
||||
|
||||
}
|
||||
28
packages/uhk-agent/src/services/app.service.ts
Normal file
28
packages/uhk-agent/src/services/app.service.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
|
||||
import { CommandLineArgs, IpcEvents, AppStartInfo, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
import { DeviceService } from './device.service';
|
||||
|
||||
export class AppService extends MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow,
|
||||
private deviceService: DeviceService,
|
||||
private options: CommandLineArgs) {
|
||||
super(logService, win);
|
||||
|
||||
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
|
||||
logService.info('AppService init success');
|
||||
}
|
||||
|
||||
private handleAppStartInfo(event: Electron.Event) {
|
||||
this.logService.info('getStartInfo');
|
||||
const response: AppStartInfo = {
|
||||
commandLineArgs: this.options,
|
||||
deviceConnected: this.deviceService.isConnected,
|
||||
hasPermission: this.deviceService.hasPermission()
|
||||
};
|
||||
this.logService.info('getStartInfo response:', response);
|
||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||
}
|
||||
}
|
||||
167
packages/uhk-agent/src/services/device.service.ts
Normal file
167
packages/uhk-agent/src/services/device.service.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { ipcMain, BrowserWindow } from 'electron';
|
||||
|
||||
import { Constants, IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
|
||||
enum Command {
|
||||
UploadConfig = 8,
|
||||
ApplyConfig = 9
|
||||
}
|
||||
|
||||
export class DeviceService {
|
||||
private static convertBufferToIntArray(buffer: Buffer): number[] {
|
||||
return Array.prototype.slice.call(buffer, 0);
|
||||
}
|
||||
|
||||
private pollTimer$: Subscription;
|
||||
private connected: boolean = false;
|
||||
|
||||
constructor(private logService: LogService,
|
||||
private win: Electron.BrowserWindow) {
|
||||
this.pollUhkDevice();
|
||||
ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this));
|
||||
logService.info('DeviceService init success');
|
||||
}
|
||||
|
||||
public get isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
public hasPermission(): boolean {
|
||||
try {
|
||||
const devs = devices();
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.logService.error('[DeviceService] hasPermission', err);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* HID API not support device attached and detached event.
|
||||
* This method check the keyboard is attached to the computer or not.
|
||||
* Every second check the HID device list.
|
||||
*/
|
||||
private pollUhkDevice(): void {
|
||||
this.pollTimer$ = Observable.interval(1000)
|
||||
.startWith(0)
|
||||
.map(() => {
|
||||
return devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
|
||||
dev.productId === Constants.PRODUCT_ID);
|
||||
})
|
||||
.distinctUntilChanged()
|
||||
.do((connected: boolean) => {
|
||||
this.connected = connected;
|
||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, connected);
|
||||
this.logService.info(`Device connection state changed to: ${connected}`);
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private saveUserConfiguration(event: Electron.Event, json: string): void {
|
||||
const response = new IpcResponse();
|
||||
|
||||
try {
|
||||
const buffer: Buffer = new Buffer(JSON.parse(json).data);
|
||||
const fragments = this.getTransferBuffers(buffer);
|
||||
const device = this.getDevice();
|
||||
device.read((err, data) => {
|
||||
if (err) {
|
||||
this.logService.error('Send data to device err:', err);
|
||||
}
|
||||
this.logService.debug('send data to device response:', data.toString());
|
||||
});
|
||||
|
||||
for (const fragment of fragments) {
|
||||
const transferData = this.getTransferData(fragment);
|
||||
this.logService.debug('Fragment: ', JSON.stringify(transferData));
|
||||
device.write(transferData);
|
||||
}
|
||||
|
||||
const applyBuffer = new Buffer([Command.ApplyConfig]);
|
||||
const applyTransferData = this.getTransferData(applyBuffer);
|
||||
this.logService.debug('Fragment: ', JSON.stringify(applyTransferData));
|
||||
device.write(applyTransferData);
|
||||
|
||||
response.success = true;
|
||||
this.logService.info('transferring finished');
|
||||
}
|
||||
catch (error) {
|
||||
this.logService.error('transferring error', error);
|
||||
response.error = { message: error.message };
|
||||
}
|
||||
|
||||
event.sender.send(IpcEvents.device.saveUserConfigurationReply, response);
|
||||
}
|
||||
|
||||
private getTransferData(buffer: Buffer): number[] {
|
||||
const data = DeviceService.convertBufferToIntArray(buffer);
|
||||
// if data start with 0 need to add additional leading zero because HID API remove it.
|
||||
// https://github.com/node-hid/node-hid/issues/187
|
||||
if (data.length > 0 && data[0] === 0) {
|
||||
data.unshift(0);
|
||||
}
|
||||
|
||||
// From HID API documentation:
|
||||
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
||||
// The first byte of data[] must contain the Report ID.
|
||||
// For devices which only support a single report, this must be set to 0x0.
|
||||
data.unshift(0);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private getTransferBuffers(configBuffer: Buffer): Buffer[] {
|
||||
const fragments: Buffer[] = [];
|
||||
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
|
||||
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
|
||||
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
|
||||
? MAX_SENDING_PAYLOAD_SIZE
|
||||
: configBuffer.length - offset;
|
||||
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
|
||||
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 0 interface of the keyboard.
|
||||
* @returns {HID}
|
||||
*/
|
||||
private getDevice(): HID {
|
||||
try {
|
||||
const devs = devices();
|
||||
this.logService.silly('Available devices:', devs);
|
||||
|
||||
const dev = devs.find((x: Device) =>
|
||||
x.vendorId === Constants.VENDOR_ID &&
|
||||
x.productId === Constants.PRODUCT_ID &&
|
||||
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
|
||||
|
||||
if (!dev) {
|
||||
this.logService.info('[DeviceService] UHK Device not found:');
|
||||
return null;
|
||||
}
|
||||
const device = new HID(dev.path);
|
||||
this.logService.info('Used device:', dev);
|
||||
return device;
|
||||
}
|
||||
catch (err) {
|
||||
this.logService.error('Can not create device:', err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
4
packages/uhk-agent/src/services/logger.service.ts
Normal file
4
packages/uhk-agent/src/services/logger.service.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import * as log from 'electron-log';
|
||||
log.transports.file.level = 'debug';
|
||||
|
||||
export const logger = log;
|
||||
16
packages/uhk-agent/src/services/main-service-base.ts
Normal file
16
packages/uhk-agent/src/services/main-service-base.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
export class MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow) {}
|
||||
|
||||
protected sendIpcToWindow(message: string, arg?: any) {
|
||||
this.logService.info('sendIpcToWindow:', message, arg);
|
||||
if (!this.win || this.win.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.win.webContents.send(message, arg);
|
||||
}
|
||||
|
||||
}
|
||||
57
packages/uhk-agent/src/services/sudo.service.ts
Normal file
57
packages/uhk-agent/src/services/sudo.service.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ipcMain, app } from 'electron';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
import * as path from 'path';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
|
||||
import { IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
|
||||
export class SudoService {
|
||||
private rootDir: string;
|
||||
|
||||
constructor(private logService: LogService) {
|
||||
if (isDev) {
|
||||
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../..');
|
||||
} else {
|
||||
this.rootDir = path.dirname(app.getAppPath());
|
||||
}
|
||||
this.logService.info('[SudoService] App root dir: ', this.rootDir);
|
||||
ipcMain.on(IpcEvents.device.setPrivilegeOnLinux, this.setPrivilege.bind(this));
|
||||
}
|
||||
|
||||
private setPrivilege(event: Electron.Event) {
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
this.setPrivilegeOnLinux(event);
|
||||
break;
|
||||
default:
|
||||
const response: IpcResponse = {
|
||||
success: false,
|
||||
error: { message: 'Permissions couldn\'t be set. Invalid platform: ' + process.platform }
|
||||
};
|
||||
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private setPrivilegeOnLinux(event: Electron.Event) {
|
||||
const scriptPath = path.join(this.rootDir, 'rules/setup-rules.sh');
|
||||
const options = {
|
||||
name: 'Setting UHK access rules'
|
||||
};
|
||||
const command = `sh ${scriptPath}`;
|
||||
console.log(command);
|
||||
sudo.exec(command, options, (error: any) => {
|
||||
const response = new IpcResponse();
|
||||
|
||||
if (error) {
|
||||
response.success = false;
|
||||
response.error = error;
|
||||
} else {
|
||||
response.success = true;
|
||||
}
|
||||
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
});
|
||||
}
|
||||
}
|
||||
1
packages/uhk-agent/src/vendor.ts
Normal file
1
packages/uhk-agent/src/vendor.ts
Normal file
@@ -0,0 +1 @@
|
||||
import 'sudo-prompt';
|
||||
21
packages/uhk-agent/tsconfig.json
Normal file
21
packages/uhk-agent/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es2016",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2015.iterable",
|
||||
"es2016",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
]
|
||||
}
|
||||
}
|
||||
45
packages/uhk-agent/webpack.config.js
Normal file
45
packages/uhk-agent/webpack.config.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const path = require('path');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const rootDir = __dirname;
|
||||
|
||||
module.exports = {
|
||||
entry: [path.resolve(rootDir, 'src/electron-main.ts')],
|
||||
output: {
|
||||
path: rootDir + "/dist",
|
||||
filename: "electron-main.js"
|
||||
},
|
||||
target: 'electron-main',
|
||||
externals: {
|
||||
"node-hid": "require('node-hid')"
|
||||
},
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
extensions: ['.webpack.js', '.web.js', '.ts', '.js'],
|
||||
modules: ["node_modules"]
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.tsx?$/, loader: 'ts-loader' , exclude: /node_modules/},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin(
|
||||
[
|
||||
{
|
||||
from: 'src/manifest.json',
|
||||
to: 'manifest.json'
|
||||
},
|
||||
{
|
||||
from: 'src/package.json',
|
||||
to: 'package.json'
|
||||
}
|
||||
]
|
||||
)
|
||||
],
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false
|
||||
}
|
||||
|
||||
};
|
||||
3
packages/uhk-common/index.ts
Normal file
3
packages/uhk-common/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './src/util';
|
||||
export * from './src/models';
|
||||
export * from './src/services';
|
||||
29
packages/uhk-common/package-lock.json
generated
Normal file
29
packages/uhk-common/package-lock.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@angular/core": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-4.3.3.tgz",
|
||||
"integrity": "sha1-jmp2kUZh20B/otiN0kQcTAFv9iU=",
|
||||
"requires": {
|
||||
"tslib": "1.7.1"
|
||||
}
|
||||
},
|
||||
"@ngrx/core": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngrx/core/-/core-1.2.0.tgz",
|
||||
"integrity": "sha1-iCtGq6+i4ObYh8txobLC+j5tDcY="
|
||||
},
|
||||
"@ngrx/store": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-2.2.3.tgz",
|
||||
"integrity": "sha1-570RSfHEQgjxzEdENT8PmKDx9Xs="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz",
|
||||
"integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw="
|
||||
}
|
||||
}
|
||||
}
|
||||
17
packages/uhk-common/package.json
Normal file
17
packages/uhk-common/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "uhk-common",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "Common Library contains the common code for uhk-agent (electron-main) and web (electron-renderer) modules",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@angular/core": "^4.3.3",
|
||||
"@ngrx/core": "1.2.0",
|
||||
"@ngrx/store": "^2.2.3"
|
||||
}
|
||||
}
|
||||
7
packages/uhk-common/src/models/app-start-info.ts
Normal file
7
packages/uhk-common/src/models/app-start-info.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { CommandLineArgs } from './command-line-args';
|
||||
|
||||
export interface AppStartInfo {
|
||||
commandLineArgs: CommandLineArgs;
|
||||
deviceConnected: boolean;
|
||||
hasPermission: boolean;
|
||||
}
|
||||
4
packages/uhk-common/src/models/index.ts
Normal file
4
packages/uhk-common/src/models/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './command-line-args';
|
||||
export * from './notification';
|
||||
export * from './ipc-response';
|
||||
export * from './app-start-info';
|
||||
4
packages/uhk-common/src/models/ipc-response.ts
Normal file
4
packages/uhk-common/src/models/ipc-response.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class IpcResponse {
|
||||
success: boolean;
|
||||
error?: { message: string };
|
||||
}
|
||||
1
packages/uhk-common/src/services/index.ts
Normal file
1
packages/uhk-common/src/services/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './logger.service';
|
||||
@@ -6,6 +6,14 @@ export class LogService {
|
||||
console.error(args);
|
||||
}
|
||||
|
||||
debug(...args: any[]): void {
|
||||
console.debug(args);
|
||||
}
|
||||
|
||||
silly(...args: any[]): void {
|
||||
console.debug(args);
|
||||
}
|
||||
|
||||
info(...args: any[]): void {
|
||||
console.info(args);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Constants } from './constants';
|
||||
|
||||
export { Constants };
|
||||
export { Constants } from './constants';
|
||||
export { IpcEvents } from './ipcEvents';
|
||||
|
||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||
export function camelCaseToSentence(camelCasedText: string): string {
|
||||
@@ -35,8 +34,6 @@ export function type<T>(label: T | ''): T {
|
||||
return <T>label;
|
||||
}
|
||||
|
||||
export { IpcEvents } from './ipcEvents';
|
||||
|
||||
export function runInElectron() {
|
||||
return window && (<any>window).process && (<any>window).process.type;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
class App {
|
||||
public static readonly appStarted = 'app-started';
|
||||
public static readonly getCommandLineArgs = 'app-get-command-line-args';
|
||||
public static readonly getCommandLineArgsReply = 'app-get-command-line-args-reply';
|
||||
public static readonly getAppStartInfo = 'app-get-start-info';
|
||||
public static readonly getAppStartInfoReply = 'app-get-start-info-reply';
|
||||
}
|
||||
|
||||
class AutoUpdate {
|
||||
@@ -16,7 +16,16 @@ class AutoUpdate {
|
||||
public static readonly checkForUpdateNotAvailable = 'check-for-update-not-available';
|
||||
}
|
||||
|
||||
class Device {
|
||||
public static readonly setPrivilegeOnLinux = 'set-privilege-on-linux';
|
||||
public static readonly setPrivilegeOnLinuxReply = 'set-privilege-on-linux-reply';
|
||||
public static readonly deviceConnectionStateChanged = 'device-connection-state-changed';
|
||||
public static readonly saveUserConfiguration = 'device-save-user-configuration';
|
||||
public static readonly saveUserConfigurationReply = 'device-save-user-configuration-reply';
|
||||
}
|
||||
|
||||
export class IpcEvents {
|
||||
public static readonly app = App;
|
||||
public static readonly autoUpdater = AutoUpdate;
|
||||
public static readonly device = Device;
|
||||
}
|
||||
20
packages/uhk-common/tsconfig.json
Normal file
20
packages/uhk-common/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2015.iterable",
|
||||
"dom",
|
||||
"es2016"
|
||||
]
|
||||
}
|
||||
}
|
||||
69
packages/uhk-web/.angular-cli.json
Normal file
69
packages/uhk-web/.angular-cli.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "uhk"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"name": "web",
|
||||
"root": "src",
|
||||
"outDir": "./dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main-web.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||
"../node_modules/select2/dist/js/select2.full.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "scss",
|
||||
"component": {
|
||||
},
|
||||
"serve": {
|
||||
"port": 8080
|
||||
}
|
||||
}
|
||||
}
|
||||
28
packages/uhk-web/README.md
Normal file
28
packages/uhk-web/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Web
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.4.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||
14
packages/uhk-web/e2e/app.e2e-spec.ts
Normal file
14
packages/uhk-web/e2e/app.e2e-spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { WebPage } from './app.po';
|
||||
|
||||
describe('web App', () => {
|
||||
let page: WebPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new WebPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('Welcome to app!');
|
||||
});
|
||||
});
|
||||
11
packages/uhk-web/e2e/app.po.ts
Normal file
11
packages/uhk-web/e2e/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class WebPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
||||
14
packages/uhk-web/e2e/tsconfig.e2e.json
Normal file
14
packages/uhk-web/e2e/tsconfig.e2e.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"baseUrl": "./",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
packages/uhk-web/index.ts
Normal file
3
packages/uhk-web/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './src/app/web.module';
|
||||
export * from './src/app/app.routes';
|
||||
export * from './src/app/app.component';
|
||||
33
packages/uhk-web/karma.conf.js
Normal file
33
packages/uhk-web/karma.conf.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular/cli/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
angularCli: {
|
||||
environment: 'dev'
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
10654
packages/uhk-web/package-lock.json
generated
Normal file
10654
packages/uhk-web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
103
packages/uhk-web/package.json
Normal file
103
packages/uhk-web/package.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"name": "uhk-web",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"build:renderer": "webpack --config webpack.config.js",
|
||||
"server:renderer": "webpack --config webpack.config.js --watch"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@angular/animations": "^4.3.3",
|
||||
"@angular/cli": "^1.3.0-rc.5",
|
||||
"@angular/common": "^4.3.3",
|
||||
"@angular/compiler": "^4.3.3",
|
||||
"@angular/compiler-cli": "^4.3.3",
|
||||
"@angular/core": "^4.3.3",
|
||||
"@angular/forms": "^4.3.3",
|
||||
"@angular/http": "^4.3.3",
|
||||
"@angular/language-service": "^4.3.3",
|
||||
"@angular/platform-browser": "^4.3.3",
|
||||
"@angular/platform-browser-dynamic": "^4.3.3",
|
||||
"@angular/router": "^4.3.3",
|
||||
"@ngrx/core": "1.2.0",
|
||||
"@ngrx/effects": "^2.0.4",
|
||||
"@ngrx/router-store": "^1.2.6",
|
||||
"@ngrx/store": "^2.2.3",
|
||||
"@ngrx/store-devtools": "3.2.4",
|
||||
"@ngrx/store-log-monitor": "3.0.2",
|
||||
"@types/electron-devtools-installer": "^2.0.2",
|
||||
"@types/electron-settings": "^3.0.0",
|
||||
"@types/file-saver": "0.0.1",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/jquery": "^3.2.9",
|
||||
"@types/node": "~8.0.19",
|
||||
"@types/node-hid": "^0.5.2",
|
||||
"@types/usb": "^1.1.3",
|
||||
"angular-notifier": "^2.0.0",
|
||||
"autoprefixer": "^6.5.3",
|
||||
"bootstrap": "^3.3.7",
|
||||
"buffer": "^5.0.6",
|
||||
"circular-dependency-plugin": "^3.0.0",
|
||||
"codelyzer": "~3.0.1",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"core-js": "^2.4.1",
|
||||
"css-loader": "^0.28.1",
|
||||
"cssnano": "^3.10.0",
|
||||
"dragula": "^3.7.2",
|
||||
"exports-loader": "^0.6.3",
|
||||
"file-loader": "^0.10.0",
|
||||
"filesaver.js": "^0.2.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"html-webpack-plugin": "^2.29.0",
|
||||
"istanbul-instrumenter-loader": "^2.0.0",
|
||||
"jasmine-core": "~2.6.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"jquery": "3.2.1",
|
||||
"jsonfile": "3.0.1",
|
||||
"karma": "~1.7.0",
|
||||
"karma-chrome-launcher": "~2.1.1",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"less-loader": "^4.0.5",
|
||||
"ng2-dragula": "1.5.0",
|
||||
"ng2-select2": "1.0.0-beta.10",
|
||||
"ngrx-store-freeze": "^0.1.9",
|
||||
"node-hid": "0.5.4",
|
||||
"postcss-loader": "^1.3.3",
|
||||
"postcss-url": "^5.1.2",
|
||||
"protractor": "~5.1.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"reselect": "^3.0.1",
|
||||
"sass-loader": "^6.0.3",
|
||||
"script-loader": "^0.7.0",
|
||||
"select2": "^4.0.3",
|
||||
"source-map-loader": "^0.2.0",
|
||||
"style-loader": "^0.13.1",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"sudo-prompt": "^7.1.1",
|
||||
"ts-loader": "^2.3.1",
|
||||
"ts-node": "~3.0.4",
|
||||
"uhk-common": "1.0.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"usb": "git+https://github.com/aktary/node-usb.git",
|
||||
"webpack": "~3.4.1",
|
||||
"webpack-dev-server": "~2.5.1",
|
||||
"webpack-svgstore-plugin": "^4.0.1",
|
||||
"xml-loader": "^1.2.1",
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"classlist.js": "^1.1.20150312",
|
||||
"file-saver": "^1.3.3"
|
||||
}
|
||||
}
|
||||
28
packages/uhk-web/protractor.conf.js
Normal file
28
packages/uhk-web/protractor.conf.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e/tsconfig.e2e.json'
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
17
packages/uhk-web/sprite-config.json
Normal file
17
packages/uhk-web/sprite-config.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"dest": "src/assets/",
|
||||
"shape": {
|
||||
"id": {
|
||||
"generator": "function(name, file){return name}"
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"defs": {
|
||||
"inline": true,
|
||||
"dest": "./",
|
||||
"sprite": "compiled_sprite.svg",
|
||||
"prefix": "icon-%s",
|
||||
"bust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
13
packages/uhk-web/src/app/app.component.html
Normal file
13
packages/uhk-web/src/app/app.component.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<app-update-available *ngIf="showUpdateAvailable$ | async"
|
||||
(updateApp)="updateApp()"
|
||||
(doNotUpdateApp)="doNotUpdateApp()">
|
||||
</app-update-available>
|
||||
|
||||
<side-menu *ngIf="deviceConnected$ | async"></side-menu>
|
||||
<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>
|
||||
@@ -34,4 +34,39 @@
|
||||
main-app {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.select2 {
|
||||
&-container {
|
||||
z-index: 1100;
|
||||
|
||||
.scancode--searchterm {
|
||||
text-align: right;
|
||||
color: #b7b7b7;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-results {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-pills > li > a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-dropdown--below .select2-results > .select2-results__options {
|
||||
max-height: 300px;
|
||||
}
|
||||
@@ -1,41 +1,41 @@
|
||||
import { Component, ViewEncapsulation, HostListener } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Component, HostListener, ViewEncapsulation } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/first';
|
||||
import 'rxjs/add/operator/ignoreElements';
|
||||
import 'rxjs/add/operator/let';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/takeWhile';
|
||||
|
||||
import { AppState } from '../shared/store';
|
||||
import { getUserConfiguration } from '../shared/store/reducers/user-configuration';
|
||||
|
||||
import { UhkBuffer } from '../shared/config-serializer/uhk-buffer';
|
||||
|
||||
import { UhkDeviceService } from '../services/uhk-device.service';
|
||||
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
|
||||
import {
|
||||
AppState,
|
||||
getShowAppUpdateAvailable,
|
||||
deviceConnected,
|
||||
runningInElectron
|
||||
} from './store';
|
||||
import { getUserConfiguration } from './store/reducers/user-configuration';
|
||||
import { UhkBuffer } from './config-serializer/uhk-buffer';
|
||||
import { SaveConfigurationAction } from './store/actions/device';
|
||||
|
||||
@Component({
|
||||
selector: 'main-app',
|
||||
templateUrl: './main-app.component.html',
|
||||
styleUrls: ['../shared/main-app/main-app.component.scss'],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class MainAppComponent {
|
||||
showUpdateAvailable$: Observable<boolean>;
|
||||
deviceConnected$: Observable<boolean>;
|
||||
runningInElectron$: Observable<boolean>;
|
||||
|
||||
constructor(private uhkDevice: UhkDeviceService, private store: Store<AppState>, router: Router) {
|
||||
uhkDevice.isInitialized()
|
||||
.distinctUntilChanged()
|
||||
.takeWhile(initialized => initialized)
|
||||
.ignoreElements()
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
router.navigate(['/detection']);
|
||||
constructor(private store: Store<AppState>) {
|
||||
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
|
||||
this.deviceConnected$ = store.select(deviceConnected);
|
||||
this.runningInElectron$ = store.select(runningInElectron);
|
||||
}
|
||||
});
|
||||
|
||||
updateApp() {
|
||||
this.store.dispatch(new UpdateAppAction());
|
||||
}
|
||||
|
||||
doNotUpdateApp() {
|
||||
this.store.dispatch(new DoNotUpdateAppAction());
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.o', ['$event'])
|
||||
@@ -78,19 +78,16 @@ export class MainAppComponent {
|
||||
private sendUserConfiguration(): void {
|
||||
this.store
|
||||
.let(getUserConfiguration())
|
||||
.first()
|
||||
.map(userConfiguration => {
|
||||
const uhkBuffer = new UhkBuffer();
|
||||
userConfiguration.toBinary(uhkBuffer);
|
||||
return uhkBuffer.getBufferContent();
|
||||
})
|
||||
.switchMap((buffer: Buffer) => this.uhkDevice.sendConfig(buffer))
|
||||
.do(response => console.log('Sending user configuration finished', response))
|
||||
.switchMap(() => this.uhkDevice.applyConfig())
|
||||
.subscribe(
|
||||
response => console.log('Applying user configuration finished', response),
|
||||
buffer => this.store.dispatch(new SaveConfigurationAction(buffer)),
|
||||
error => console.error('Error during uploading user configuration', error),
|
||||
() => console.log('User configuration has been sucessfully uploaded')
|
||||
() => console.log('User configuration has been successfully uploaded')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
42
packages/uhk-web/src/app/app.routes.ts
Normal file
42
packages/uhk-web/src/app/app.routes.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ModuleWithProviders } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { addOnRoutes } from './components/add-on';
|
||||
import { keymapRoutes } from './components/keymap';
|
||||
import { macroRoutes } from './components/macro';
|
||||
import { PrivilegeCheckerComponent } from './components/privilege-checker/privilege-checker.component';
|
||||
import { MissingDeviceComponent } from './components/missing-device/missing-device.component';
|
||||
import { UhkDeviceDisconnectedGuard } from './services/uhk-device-disconnected.guard';
|
||||
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';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: 'detection',
|
||||
component: MissingDeviceComponent,
|
||||
canActivate: [UhkDeviceConnectedGuard, UhkDeviceUninitializedGuard]
|
||||
},
|
||||
{
|
||||
path: 'privilege',
|
||||
component: PrivilegeCheckerComponent,
|
||||
canActivate: [UhkDeviceInitializedGuard]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MainPage,
|
||||
canActivate: [UhkDeviceDisconnectedGuard],
|
||||
children: [
|
||||
...keymapRoutes,
|
||||
...macroRoutes,
|
||||
...addOnRoutes,
|
||||
...settingsRoutes
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const appRoutingProviders: any[] = [];
|
||||
|
||||
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { useHash: true });
|
||||
@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/pluck';
|
||||
|
||||
@Component({
|
||||
selector: 'add-on',
|
||||
@@ -17,6 +18,6 @@ export class AddOnComponent {
|
||||
constructor(route: ActivatedRoute) {
|
||||
this.name$ = route
|
||||
.params
|
||||
.select<string>('name');
|
||||
.pluck<{}, string>('name');
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,5 @@
|
||||
<span *ngIf="checkingForUpdate"
|
||||
class="fa fa-spinner fa-spin"></span>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
{{message}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,7 +10,7 @@ import { State } from '../../store/reducers/auto-update-settings';
|
||||
export class AutoUpdateSettings {
|
||||
|
||||
@Input() version: string;
|
||||
@Input() settings: State;
|
||||
@Input() settings: State | undefined;
|
||||
@Input() checkingForUpdate: boolean;
|
||||
|
||||
@Output() toggleCheckForUpdateOnStartUp = new EventEmitter<boolean>();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user