diff --git a/packages/uhk-agent/src/services/device.service.ts b/packages/uhk-agent/src/services/device.service.ts index 33217fe5..f1a43422 100644 --- a/packages/uhk-agent/src/services/device.service.ts +++ b/packages/uhk-agent/src/services/device.service.ts @@ -92,7 +92,7 @@ export class DeviceService { const applyTransferData = this.getTransferData(applyBuffer); this.logService.debug('Fragment: ', JSON.stringify(applyTransferData)); device.write(applyTransferData); - + device.close(); response.success = true; this.logService.info('transferring finished'); } diff --git a/packages/uhk-web/src/app/app.component.html b/packages/uhk-web/src/app/app.component.html index 7bc22327..965f6c80 100644 --- a/packages/uhk-web/src/app/app.component.html +++ b/packages/uhk-web/src/app/app.component.html @@ -11,3 +11,8 @@ Fork me on GitHub + diff --git a/packages/uhk-web/src/app/app.component.scss b/packages/uhk-web/src/app/app.component.scss index 230b8b00..8715ffe3 100644 --- a/packages/uhk-web/src/app/app.component.scss +++ b/packages/uhk-web/src/app/app.component.scss @@ -37,3 +37,9 @@ main-app { display: block; position: relative; } + +.save-to-keyboard-button { + position: fixed; + bottom: 15px; + right: 15px; +} diff --git a/packages/uhk-web/src/app/app.component.ts b/packages/uhk-web/src/app/app.component.ts index 32bddab7..04fdeeff 100644 --- a/packages/uhk-web/src/app/app.component.ts +++ b/packages/uhk-web/src/app/app.component.ts @@ -1,33 +1,52 @@ import { Component, HostListener, ViewEncapsulation } from '@angular/core'; +import { animate, style, transition, trigger } from '@angular/animations'; import { Observable } from 'rxjs/Observable'; -import { Store } from '@ngrx/store'; +import { Action, Store } from '@ngrx/store'; + +import 'rxjs/add/operator/last'; import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action'; import { AppState, getShowAppUpdateAvailable, deviceConnected, - runningInElectron + runningInElectron, + saveToKeyboardState } from './store'; import { getUserConfiguration } from './store/reducers/user-configuration'; import { UhkBuffer } from './config-serializer/uhk-buffer'; -import { SaveConfigurationAction } from './store/actions/device'; +import { ProgressButtonState } from './store/reducers/progress-button-state'; @Component({ selector: 'main-app', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + animations: [ + trigger( + 'showSaveToKeyboardButton', [ + transition(':enter', [ + style({transform: 'translateY(100%)'}), + animate('400ms ease-in-out', style({transform: 'translateY(0)'})) + ]), + transition(':leave', [ + style({transform: 'translateY(0)'}), + animate('400ms ease-in-out', style({transform: 'translateY(100%)'})) + ]) + ]) + ] }) export class MainAppComponent { showUpdateAvailable$: Observable; deviceConnected$: Observable; runningInElectron$: Observable; + saveToKeyboardState$: Observable; constructor(private store: Store) { this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable); this.deviceConnected$ = store.select(deviceConnected); this.runningInElectron$ = store.select(runningInElectron); + this.saveToKeyboardState$ = store.select(saveToKeyboardState); } updateApp() { @@ -38,12 +57,8 @@ export class MainAppComponent { this.store.dispatch(new DoNotUpdateAppAction()); } - @HostListener('window:keydown.control.o', ['$event']) - onCtrlO(event: KeyboardEvent): void { - console.log('ctrl + o pressed'); - event.preventDefault(); - event.stopPropagation(); - this.sendUserConfiguration(); + clickedOnProgressButton(action: Action) { + return this.store.dispatch(action); } @HostListener('window:keydown.alt.j', ['$event']) @@ -55,7 +70,7 @@ export class MainAppComponent { .first() .subscribe(userConfiguration => { const asString = JSON.stringify(userConfiguration.toJsonObject()); - const asBlob = new Blob([asString], { type: 'text/plain' }); + const asBlob = new Blob([asString], {type: 'text/plain'}); saveAs(asBlob, 'UserConfiguration.json'); }); } @@ -74,20 +89,4 @@ export class MainAppComponent { }) .subscribe(blob => saveAs(blob, 'UserConfiguration.bin')); } - - private sendUserConfiguration(): void { - this.store - .let(getUserConfiguration()) - .first() - .map(userConfiguration => { - const uhkBuffer = new UhkBuffer(); - userConfiguration.toBinary(uhkBuffer); - return uhkBuffer.getBufferContent(); - }) - .subscribe( - buffer => this.store.dispatch(new SaveConfigurationAction(buffer)), - error => console.error('Error during uploading user configuration', error), - () => console.log('User configuration has been successfully uploaded') - ); - } } diff --git a/packages/uhk-web/src/app/components/progress-button/progress-button.component.html b/packages/uhk-web/src/app/components/progress-button/progress-button.component.html new file mode 100644 index 00000000..24b5ac9a --- /dev/null +++ b/packages/uhk-web/src/app/components/progress-button/progress-button.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/uhk-web/src/app/components/progress-button/progress-button.component.scss b/packages/uhk-web/src/app/components/progress-button/progress-button.component.scss new file mode 100644 index 00000000..d45efbef --- /dev/null +++ b/packages/uhk-web/src/app/components/progress-button/progress-button.component.scss @@ -0,0 +1,3 @@ +button { + min-width: 150px; +} diff --git a/packages/uhk-web/src/app/components/progress-button/progress-button.component.ts b/packages/uhk-web/src/app/components/progress-button/progress-button.component.ts new file mode 100644 index 00000000..d637c624 --- /dev/null +++ b/packages/uhk-web/src/app/components/progress-button/progress-button.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { Action } from '@ngrx/store'; + +import { ProgressButtonState, initProgressButtonState } from '../../store/reducers/progress-button-state'; + +@Component({ + selector: 'progress-button', + templateUrl: './progress-button.component.html', + styleUrls: ['./progress-button.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProgressButtonComponent { + @Input() state: ProgressButtonState = initProgressButtonState; + @Output() clicked: EventEmitter = new EventEmitter(); + + onClicked() { + this.clicked.emit(this.state.action); + } +} diff --git a/packages/uhk-web/src/app/models/angular-notifier-config.ts b/packages/uhk-web/src/app/models/angular-notifier-config.ts index e89a7019..4df803b2 100644 --- a/packages/uhk-web/src/app/models/angular-notifier-config.ts +++ b/packages/uhk-web/src/app/models/angular-notifier-config.ts @@ -1,6 +1,9 @@ import { NotifierOptions } from 'angular-notifier'; export const angularNotifierConfig: NotifierOptions = { + behaviour: { + autoHide: false + }, position: { horizontal: { diff --git a/packages/uhk-web/src/app/store/actions/device.ts b/packages/uhk-web/src/app/store/actions/device.ts index 9a0c3925..2beed482 100644 --- a/packages/uhk-web/src/app/store/actions/device.ts +++ b/packages/uhk-web/src/app/store/actions/device.ts @@ -10,7 +10,12 @@ export const ActionTypes = { CONNECTION_STATE_CHANGED: type(PREFIX + 'connection state changed'), PERMISSION_STATE_CHANGED: type(PREFIX + 'permission state changed'), SAVE_CONFIGURATION: type(PREFIX + 'save configuration'), - SAVE_CONFIGURATION_REPLY: type(PREFIX + 'save configuration reply') + SAVE_CONFIGURATION_REPLY: type(PREFIX + 'save configuration reply'), + SAVING_CONFIGURATION: type(PREFIX + 'saving configuration'), + SHOW_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'show save to keyboard button'), + SAVE_TO_KEYBOARD_SUCCESS: type(PREFIX + 'save to keyboard success'), + SAVE_TO_KEYBOARD_FAILED: type(PREFIX + 'save to keyboard failed'), + HIDE_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'hide save to keyboard button') }; export class SetPrivilegeOnLinuxAction implements Action { @@ -38,7 +43,7 @@ export class PermissionStateChangedAction implements Action { export class SaveConfigurationAction implements Action { type = ActionTypes.SAVE_CONFIGURATION; - constructor(public payload: Buffer) {} + constructor() {} } export class SaveConfigurationReplyAction implements Action { @@ -47,6 +52,30 @@ export class SaveConfigurationReplyAction implements Action { constructor(public payload: IpcResponse) {} } +export class ShowSaveToKeyboardButtonAction implements Action { + type = ActionTypes.SHOW_SAVE_TO_KEYBOARD_BUTTON; +} + +export class SaveToKeyboardSuccessAction implements Action { + type = ActionTypes.SAVE_TO_KEYBOARD_SUCCESS; +} + +export class SaveToKeyboardSuccessFailed implements Action { + type = ActionTypes.SAVE_TO_KEYBOARD_FAILED; +} + +export class HideSaveToKeyboardButton implements Action { + type = ActionTypes.HIDE_SAVE_TO_KEYBOARD_BUTTON; +} + export type Actions = SetPrivilegeOnLinuxAction - | ConnectionStateChangedAction; + | SetPrivilegeOnLinuxReplyAction + | ConnectionStateChangedAction + | PermissionStateChangedAction + | ShowSaveToKeyboardButtonAction + | SaveConfigurationAction + | SaveConfigurationReplyAction + | SaveToKeyboardSuccessAction + | SaveToKeyboardSuccessFailed + | HideSaveToKeyboardButton; diff --git a/packages/uhk-web/src/app/store/effects/device.ts b/packages/uhk-web/src/app/store/effects/device.ts index 39509403..bdbbf20f 100644 --- a/packages/uhk-web/src/app/store/effects/device.ts +++ b/packages/uhk-web/src/app/store/effects/device.ts @@ -1,21 +1,34 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Action } from '@ngrx/store'; +import { Action, Store } from '@ngrx/store'; import { Actions, Effect, toPayload } from '@ngrx/effects'; import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; +import 'rxjs/add/observable/empty'; +import 'rxjs/add/observable/timer'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mergeMap'; +import 'rxjs/add/operator/withLatestFrom'; import { NotificationType, IpcResponse } from 'uhk-common'; -import { ActionTypes, ConnectionStateChangedAction, PermissionStateChangedAction } from '../actions/device'; +import { + ActionTypes, + ConnectionStateChangedAction, HideSaveToKeyboardButton, + PermissionStateChangedAction, + SaveToKeyboardSuccessAction, + SaveToKeyboardSuccessFailed +} from '../actions/device'; import { DeviceRendererService } from '../../services/device-renderer.service'; import { ShowNotificationAction } from '../actions/app'; +import { AppState } from '../index'; +import { UserConfiguration } from '../../config-serializer/config-items/user-configuration'; +import { UhkBuffer } from '../../config-serializer/uhk-buffer'; @Injectable() export class DeviceEffects { - @Effect({ dispatch: false }) + @Effect({dispatch: false}) deviceConnectionStateChange$: Observable = this.actions$ .ofType(ActionTypes.CONNECTION_STATE_CHANGED) .map(toPayload) @@ -28,7 +41,7 @@ export class DeviceEffects { } }); - @Effect({ dispatch: false }) + @Effect({dispatch: false}) permissionStateChange$: Observable = this.actions$ .ofType(ActionTypes.PERMISSION_STATE_CHANGED) .map(toPayload) @@ -41,7 +54,7 @@ export class DeviceEffects { } }); - @Effect({ dispatch: false }) + @Effect({dispatch: false}) setPrivilegeOnLinux$: Observable = this.actions$ .ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX) .do(() => { @@ -67,35 +80,52 @@ export class DeviceEffects { ]; }); - @Effect({ dispatch: false }) + @Effect({dispatch: false}) saveConfiguration$: Observable = this.actions$ .ofType(ActionTypes.SAVE_CONFIGURATION) - .map(toPayload) - .do((buffer: Buffer) => { - this.deviceRendererService.saveUserConfiguration(buffer); - }); + .withLatestFrom(this.store) + .map(([action, state]) => state.userConfiguration) + .do((userConfiguration: UserConfiguration) => { + setTimeout(() => this.sendUserConfigToKeyboard(userConfiguration), 100); + }) + .switchMap(() => Observable.empty()); @Effect() saveConfigurationReply$: Observable = this.actions$ .ofType(ActionTypes.SAVE_CONFIGURATION_REPLY) .map(toPayload) - .map((response: IpcResponse) => { + .mergeMap((response: IpcResponse) => { if (response.success) { - return new ShowNotificationAction({ - type: NotificationType.Success, - message: 'Save configuration successful.' - }); + return [ + new SaveToKeyboardSuccessAction() + ]; } - return new ShowNotificationAction({ - type: NotificationType.Error, - message: response.error.message - }); + return [ + new ShowNotificationAction({ + type: NotificationType.Error, + message: response.error.message + }), + new SaveToKeyboardSuccessFailed() + ]; }); + @Effect() + autoHideSaveToKeyboardButton$: Observable = this.actions$ + .ofType(ActionTypes.SAVE_TO_KEYBOARD_SUCCESS) + .switchMap(() => Observable.timer(1000) + .switchMap(() => Observable.of(new HideSaveToKeyboardButton())) + ); + constructor(private actions$: Actions, private router: Router, - private deviceRendererService: DeviceRendererService) { + private deviceRendererService: DeviceRendererService, + private store: Store) { } + private sendUserConfigToKeyboard(userConfiguration: UserConfiguration): void { + const uhkBuffer = new UhkBuffer(); + userConfiguration.toBinary(uhkBuffer); + this.deviceRendererService.saveUserConfiguration(uhkBuffer.getBufferContent()); + } } diff --git a/packages/uhk-web/src/app/store/effects/user-config.ts b/packages/uhk-web/src/app/store/effects/user-config.ts index 1e7fc203..8f29392a 100644 --- a/packages/uhk-web/src/app/store/effects/user-config.ts +++ b/packages/uhk-web/src/app/store/effects/user-config.ts @@ -28,6 +28,7 @@ import { KeymapActions } from '../actions/keymap'; import { MacroActions } from '../actions/macro'; import { UndoUserConfigData } from '../../models/undo-user-config-data'; import { ShowNotificationAction, DismissUndoNotificationAction } from '../actions/app'; +import { ShowSaveToKeyboardButtonAction } from '../actions/device'; @Injectable() export class UserConfigEffects { @@ -64,11 +65,16 @@ export class UserConfigEffects { payload, type: KeymapActions.UNDO_LAST_ACTION } - }) + }), + new ShowSaveToKeyboardButtonAction() ]; } - return [new SaveUserConfigSuccessAction(config), new DismissUndoNotificationAction()]; + return [ + new SaveUserConfigSuccessAction(config), + new DismissUndoNotificationAction(), + new ShowSaveToKeyboardButtonAction() + ]; }); @Effect() undoUserConfig$: Observable = this.actions$ diff --git a/packages/uhk-web/src/app/store/index.ts b/packages/uhk-web/src/app/store/index.ts index 8411b272..cbcc77a4 100644 --- a/packages/uhk-web/src/app/store/index.ts +++ b/packages/uhk-web/src/app/store/index.ts @@ -12,6 +12,7 @@ import * as fromAppUpdate from './reducers/app-update.reducer'; import * as autoUpdateSettings from './reducers/auto-update-settings'; import * as fromApp from './reducers/app.reducer'; import * as fromDevice from './reducers/device'; +import { initProgressButtonState } from './reducers/progress-button-state'; export const reducers = { userConfiguration: userConfigurationReducer, @@ -41,7 +42,7 @@ export function reducer(state: any, action: any) { // if (isDev) { // return developmentReducer(state, action); // } else { - return productionReducer(state, action); + return productionReducer(state, action); // } } @@ -69,3 +70,7 @@ export const devicePermission = createSelector(deviceState, fromDevice.hasDevice export const hasDevicePermission = createSelector(runningInElectron, devicePermission, (electron, permission) => { return !electron ? true : permission; }); +export const saveToKeyboardStateSelector = createSelector(deviceState, fromDevice.getSaveToKeyboardState); +export const saveToKeyboardState = createSelector(runningInElectron, saveToKeyboardStateSelector, (electron, state) => { + return electron ? state : initProgressButtonState; +}); diff --git a/packages/uhk-web/src/app/store/reducers/device.ts b/packages/uhk-web/src/app/store/reducers/device.ts index 67a4d36d..adf8bdc5 100644 --- a/packages/uhk-web/src/app/store/reducers/device.ts +++ b/packages/uhk-web/src/app/store/reducers/device.ts @@ -1,15 +1,18 @@ import { Action } from '@ngrx/store'; -import { ActionTypes } from '../actions/device'; +import { ActionTypes, HideSaveToKeyboardButton, SaveConfigurationAction } from '../actions/device'; +import { initProgressButtonState, ProgressButtonState } from './progress-button-state'; export interface State { connected: boolean; hasPermission: boolean; + saveToKeyboard: ProgressButtonState; } const initialState: State = { connected: true, - hasPermission: true + hasPermission: true, + saveToKeyboard: initProgressButtonState }; export function reducer(state = initialState, action: Action) { @@ -26,6 +29,63 @@ export function reducer(state = initialState, action: Action) { hasPermission: action.payload }; + case ActionTypes.SAVING_CONFIGURATION: { + return { + ...state, + savingToKeyboard: true + }; + } + + case ActionTypes.SHOW_SAVE_TO_KEYBOARD_BUTTON: { + return { + ...state, + saveToKeyboard: { + showButton: true, + text: 'Save to keyboard', + action: new SaveConfigurationAction() + } + }; + } + + case ActionTypes.SAVE_CONFIGURATION: { + return { + ...state, + saveToKeyboard: { + showButton: true, + text: 'Saving', + showProgress: true + } + }; + } + + case ActionTypes.SAVE_TO_KEYBOARD_SUCCESS: { + return { + ...state, + saveToKeyboard: { + showButton: true, + text: 'Saved!', + action: null + } + }; + } + + case ActionTypes.SAVE_TO_KEYBOARD_FAILED: { + return { + ...state, + saveToKeyboard: { + showButton: true, + text: 'Save to keyboard', + action: new SaveConfigurationAction() + } + }; + } + + case ActionTypes.HIDE_SAVE_TO_KEYBOARD_BUTTON: { + return { + ...state, + saveToKeyboard: initProgressButtonState + }; + } default: return state; } @@ -33,3 +93,4 @@ export function reducer(state = initialState, action: Action) { export const isDeviceConnected = (state: State) => state.connected; export const hasDevicePermission = (state: State) => state.hasPermission; +export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard; diff --git a/packages/uhk-web/src/app/store/reducers/index.ts b/packages/uhk-web/src/app/store/reducers/index.ts deleted file mode 100644 index 05378611..00000000 --- a/packages/uhk-web/src/app/store/reducers/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { routerReducer } from '@ngrx/router-store'; - -import userConfigurationReducer from './user-configuration'; -import presetReducer from './preset'; -import { reducer as autoUpdateReducer } from './auto-update-settings'; -import { reducer as appReducer } from './app.reducer'; -import * as fromAppUpdate from './app-update.reducer'; -import * as fromDevice from './device'; - -export { userConfigurationReducer, presetReducer, autoUpdateReducer, appReducer }; - -// All reducers that are used in application diff --git a/packages/uhk-web/src/app/store/reducers/progress-button-state.ts b/packages/uhk-web/src/app/store/reducers/progress-button-state.ts new file mode 100644 index 00000000..2c6a44ba --- /dev/null +++ b/packages/uhk-web/src/app/store/reducers/progress-button-state.ts @@ -0,0 +1,14 @@ +import { Action } from '@ngrx/store'; + +export interface ProgressButtonState { + showButton: boolean; + text: string; + showProgress: boolean; + action?: Action; +} + +export const initProgressButtonState = { + showButton: false, + text: null, + showProgress: false +}; diff --git a/packages/uhk-web/src/app/web.module.ts b/packages/uhk-web/src/app/web.module.ts index a38c9816..dfc12731 100644 --- a/packages/uhk-web/src/app/web.module.ts +++ b/packages/uhk-web/src/app/web.module.ts @@ -103,6 +103,7 @@ import { MainPage } from './pages/main-page/main.page'; import { DeviceEffects } from './store/effects/device'; import { DeviceRendererService } from './services/device-renderer.service'; import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard'; +import { ProgressButtonComponent } from './components/progress-button/progress-button.component'; @NgModule({ declarations: [ @@ -163,7 +164,8 @@ import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.gua UhkMessageComponent, MissingDeviceComponent, PrivilegeCheckerComponent, - MainPage + MainPage, + ProgressButtonComponent ], imports: [ BrowserModule,