Skip to content

Commit 8d7269a

Browse files
ert78gbmondalaci
authored andcommitted
feat(device): Add 'Save to keyboard' button (#402)
* feat(device): Add 'Save to keyboard' button Created a 'Progress Button' that have 2 state in progress or not. Able to set different text for different state: - baseText for normal state - progressText for in progress state close: #377 * fix 'Save to keyboard' button visibility in web version * remove success notification when save to keyboard success * feat(notifier): Turn off auto hide of the notifier * feat(device): Show saved state of 'Save to keyboard button' * style: Format import in app.component.ts * feat(device): Auto hide 'Save to Keyboard' button * fix(device): Fix saving animation * fix(device): Fix saving animation * fix(device): Fix tslint
1 parent c135aed commit 8d7269a

File tree

16 files changed

+244
-69
lines changed

16 files changed

+244
-69
lines changed

packages/uhk-agent/src/services/device.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class DeviceService {
9292
const applyTransferData = this.getTransferData(applyBuffer);
9393
this.logService.debug('Fragment: ', JSON.stringify(applyTransferData));
9494
device.write(applyTransferData);
95-
95+
device.close();
9696
response.success = true;
9797
this.logService.info('transferring finished');
9898
}

packages/uhk-web/src/app/app.component.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@
1111
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
1212
</div>
1313
<notifier-container></notifier-container>
14+
<progress-button class="save-to-keyboard-button"
15+
*ngIf="(saveToKeyboardState$ | async).showButton"
16+
[@showSaveToKeyboardButton]
17+
[state]="saveToKeyboardState$ | async"
18+
(clicked)="clickedOnProgressButton($event)"></progress-button>

packages/uhk-web/src/app/app.component.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,9 @@ main-app {
3737
display: block;
3838
position: relative;
3939
}
40+
41+
.save-to-keyboard-button {
42+
position: fixed;
43+
bottom: 15px;
44+
right: 15px;
45+
}
Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,52 @@
11
import { Component, HostListener, ViewEncapsulation } from '@angular/core';
2+
import { animate, style, transition, trigger } from '@angular/animations';
23
import { Observable } from 'rxjs/Observable';
3-
import { Store } from '@ngrx/store';
4+
import { Action, Store } from '@ngrx/store';
5+
6+
import 'rxjs/add/operator/last';
47

58
import { DoNotUpdateAppAction, UpdateAppAction } from './store/actions/app-update.action';
69
import {
710
AppState,
811
getShowAppUpdateAvailable,
912
deviceConnected,
10-
runningInElectron
13+
runningInElectron,
14+
saveToKeyboardState
1115
} from './store';
1216
import { getUserConfiguration } from './store/reducers/user-configuration';
1317
import { UhkBuffer } from './config-serializer/uhk-buffer';
14-
import { SaveConfigurationAction } from './store/actions/device';
18+
import { ProgressButtonState } from './store/reducers/progress-button-state';
1519

1620
@Component({
1721
selector: 'main-app',
1822
templateUrl: './app.component.html',
1923
styleUrls: ['./app.component.scss'],
20-
encapsulation: ViewEncapsulation.None
24+
encapsulation: ViewEncapsulation.None,
25+
animations: [
26+
trigger(
27+
'showSaveToKeyboardButton', [
28+
transition(':enter', [
29+
style({transform: 'translateY(100%)'}),
30+
animate('400ms ease-in-out', style({transform: 'translateY(0)'}))
31+
]),
32+
transition(':leave', [
33+
style({transform: 'translateY(0)'}),
34+
animate('400ms ease-in-out', style({transform: 'translateY(100%)'}))
35+
])
36+
])
37+
]
2138
})
2239
export class MainAppComponent {
2340
showUpdateAvailable$: Observable<boolean>;
2441
deviceConnected$: Observable<boolean>;
2542
runningInElectron$: Observable<boolean>;
43+
saveToKeyboardState$: Observable<ProgressButtonState>;
2644

2745
constructor(private store: Store<AppState>) {
2846
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
2947
this.deviceConnected$ = store.select(deviceConnected);
3048
this.runningInElectron$ = store.select(runningInElectron);
49+
this.saveToKeyboardState$ = store.select(saveToKeyboardState);
3150
}
3251

3352
updateApp() {
@@ -38,12 +57,8 @@ export class MainAppComponent {
3857
this.store.dispatch(new DoNotUpdateAppAction());
3958
}
4059

41-
@HostListener('window:keydown.control.o', ['$event'])
42-
onCtrlO(event: KeyboardEvent): void {
43-
console.log('ctrl + o pressed');
44-
event.preventDefault();
45-
event.stopPropagation();
46-
this.sendUserConfiguration();
60+
clickedOnProgressButton(action: Action) {
61+
return this.store.dispatch(action);
4762
}
4863

4964
@HostListener('window:keydown.alt.j', ['$event'])
@@ -55,7 +70,7 @@ export class MainAppComponent {
5570
.first()
5671
.subscribe(userConfiguration => {
5772
const asString = JSON.stringify(userConfiguration.toJsonObject());
58-
const asBlob = new Blob([asString], { type: 'text/plain' });
73+
const asBlob = new Blob([asString], {type: 'text/plain'});
5974
saveAs(asBlob, 'UserConfiguration.json');
6075
});
6176
}
@@ -74,20 +89,4 @@ export class MainAppComponent {
7489
})
7590
.subscribe(blob => saveAs(blob, 'UserConfiguration.bin'));
7691
}
77-
78-
private sendUserConfiguration(): void {
79-
this.store
80-
.let(getUserConfiguration())
81-
.first()
82-
.map(userConfiguration => {
83-
const uhkBuffer = new UhkBuffer();
84-
userConfiguration.toBinary(uhkBuffer);
85-
return uhkBuffer.getBufferContent();
86-
})
87-
.subscribe(
88-
buffer => this.store.dispatch(new SaveConfigurationAction(buffer)),
89-
error => console.error('Error during uploading user configuration', error),
90-
() => console.log('User configuration has been successfully uploaded')
91-
);
92-
}
9392
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<button class="btn btn-primary"
2+
(click)="onClicked()"
3+
[disabled]="state.showProgress">
4+
<i class="fa fa-spin fa-spinner" *ngIf="state.showProgress"></i> {{state.text}}
5+
</button>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
button {
2+
min-width: 150px;
3+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
2+
import { Action } from '@ngrx/store';
3+
4+
import { ProgressButtonState, initProgressButtonState } from '../../store/reducers/progress-button-state';
5+
6+
@Component({
7+
selector: 'progress-button',
8+
templateUrl: './progress-button.component.html',
9+
styleUrls: ['./progress-button.component.scss'],
10+
changeDetection: ChangeDetectionStrategy.OnPush
11+
})
12+
export class ProgressButtonComponent {
13+
@Input() state: ProgressButtonState = initProgressButtonState;
14+
@Output() clicked: EventEmitter<Action> = new EventEmitter<Action>();
15+
16+
onClicked() {
17+
this.clicked.emit(this.state.action);
18+
}
19+
}

packages/uhk-web/src/app/models/angular-notifier-config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NotifierOptions } from 'angular-notifier';
22

33
export const angularNotifierConfig: NotifierOptions = {
4+
behaviour: {
5+
autoHide: false
6+
},
47
position: {
58

69
horizontal: {

packages/uhk-web/src/app/store/actions/device.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ export const ActionTypes = {
1010
CONNECTION_STATE_CHANGED: type(PREFIX + 'connection state changed'),
1111
PERMISSION_STATE_CHANGED: type(PREFIX + 'permission state changed'),
1212
SAVE_CONFIGURATION: type(PREFIX + 'save configuration'),
13-
SAVE_CONFIGURATION_REPLY: type(PREFIX + 'save configuration reply')
13+
SAVE_CONFIGURATION_REPLY: type(PREFIX + 'save configuration reply'),
14+
SAVING_CONFIGURATION: type(PREFIX + 'saving configuration'),
15+
SHOW_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'show save to keyboard button'),
16+
SAVE_TO_KEYBOARD_SUCCESS: type(PREFIX + 'save to keyboard success'),
17+
SAVE_TO_KEYBOARD_FAILED: type(PREFIX + 'save to keyboard failed'),
18+
HIDE_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'hide save to keyboard button')
1419
};
1520

1621
export class SetPrivilegeOnLinuxAction implements Action {
@@ -38,7 +43,7 @@ export class PermissionStateChangedAction implements Action {
3843
export class SaveConfigurationAction implements Action {
3944
type = ActionTypes.SAVE_CONFIGURATION;
4045

41-
constructor(public payload: Buffer) {}
46+
constructor() {}
4247
}
4348

4449
export class SaveConfigurationReplyAction implements Action {
@@ -47,6 +52,30 @@ export class SaveConfigurationReplyAction implements Action {
4752
constructor(public payload: IpcResponse) {}
4853
}
4954

55+
export class ShowSaveToKeyboardButtonAction implements Action {
56+
type = ActionTypes.SHOW_SAVE_TO_KEYBOARD_BUTTON;
57+
}
58+
59+
export class SaveToKeyboardSuccessAction implements Action {
60+
type = ActionTypes.SAVE_TO_KEYBOARD_SUCCESS;
61+
}
62+
63+
export class SaveToKeyboardSuccessFailed implements Action {
64+
type = ActionTypes.SAVE_TO_KEYBOARD_FAILED;
65+
}
66+
67+
export class HideSaveToKeyboardButton implements Action {
68+
type = ActionTypes.HIDE_SAVE_TO_KEYBOARD_BUTTON;
69+
}
70+
5071
export type Actions
5172
= SetPrivilegeOnLinuxAction
52-
| ConnectionStateChangedAction;
73+
| SetPrivilegeOnLinuxReplyAction
74+
| ConnectionStateChangedAction
75+
| PermissionStateChangedAction
76+
| ShowSaveToKeyboardButtonAction
77+
| SaveConfigurationAction
78+
| SaveConfigurationReplyAction
79+
| SaveToKeyboardSuccessAction
80+
| SaveToKeyboardSuccessFailed
81+
| HideSaveToKeyboardButton;

packages/uhk-web/src/app/store/effects/device.ts

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
import { Injectable } from '@angular/core';
22
import { Router } from '@angular/router';
3-
import { Action } from '@ngrx/store';
3+
import { Action, Store } from '@ngrx/store';
44
import { Actions, Effect, toPayload } from '@ngrx/effects';
55
import { Observable } from 'rxjs/Observable';
66

7+
import 'rxjs/add/observable/of';
8+
import 'rxjs/add/observable/empty';
9+
import 'rxjs/add/observable/timer';
710
import 'rxjs/add/operator/do';
811
import 'rxjs/add/operator/map';
912
import 'rxjs/add/operator/mergeMap';
13+
import 'rxjs/add/operator/withLatestFrom';
1014

1115
import { NotificationType, IpcResponse } from 'uhk-common';
12-
import { ActionTypes, ConnectionStateChangedAction, PermissionStateChangedAction } from '../actions/device';
16+
import {
17+
ActionTypes,
18+
ConnectionStateChangedAction, HideSaveToKeyboardButton,
19+
PermissionStateChangedAction,
20+
SaveToKeyboardSuccessAction,
21+
SaveToKeyboardSuccessFailed
22+
} from '../actions/device';
1323
import { DeviceRendererService } from '../../services/device-renderer.service';
1424
import { ShowNotificationAction } from '../actions/app';
25+
import { AppState } from '../index';
26+
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
27+
import { UhkBuffer } from '../../config-serializer/uhk-buffer';
1528

1629
@Injectable()
1730
export class DeviceEffects {
18-
@Effect({ dispatch: false })
31+
@Effect({dispatch: false})
1932
deviceConnectionStateChange$: Observable<Action> = this.actions$
2033
.ofType(ActionTypes.CONNECTION_STATE_CHANGED)
2134
.map(toPayload)
@@ -28,7 +41,7 @@ export class DeviceEffects {
2841
}
2942
});
3043

31-
@Effect({ dispatch: false })
44+
@Effect({dispatch: false})
3245
permissionStateChange$: Observable<Action> = this.actions$
3346
.ofType(ActionTypes.PERMISSION_STATE_CHANGED)
3447
.map(toPayload)
@@ -41,7 +54,7 @@ export class DeviceEffects {
4154
}
4255
});
4356

44-
@Effect({ dispatch: false })
57+
@Effect({dispatch: false})
4558
setPrivilegeOnLinux$: Observable<Action> = this.actions$
4659
.ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX)
4760
.do(() => {
@@ -67,35 +80,52 @@ export class DeviceEffects {
6780
];
6881
});
6982

70-
@Effect({ dispatch: false })
83+
@Effect({dispatch: false})
7184
saveConfiguration$: Observable<Action> = this.actions$
7285
.ofType(ActionTypes.SAVE_CONFIGURATION)
73-
.map(toPayload)
74-
.do((buffer: Buffer) => {
75-
this.deviceRendererService.saveUserConfiguration(buffer);
76-
});
86+
.withLatestFrom(this.store)
87+
.map(([action, state]) => state.userConfiguration)
88+
.do((userConfiguration: UserConfiguration) => {
89+
setTimeout(() => this.sendUserConfigToKeyboard(userConfiguration), 100);
90+
})
91+
.switchMap(() => Observable.empty());
7792

7893
@Effect()
7994
saveConfigurationReply$: Observable<Action> = this.actions$
8095
.ofType(ActionTypes.SAVE_CONFIGURATION_REPLY)
8196
.map(toPayload)
82-
.map((response: IpcResponse) => {
97+
.mergeMap((response: IpcResponse) => {
8398
if (response.success) {
84-
return new ShowNotificationAction({
85-
type: NotificationType.Success,
86-
message: 'Save configuration successful.'
87-
});
99+
return [
100+
new SaveToKeyboardSuccessAction()
101+
];
88102
}
89103

90-
return new ShowNotificationAction({
91-
type: NotificationType.Error,
92-
message: response.error.message
93-
});
104+
return [
105+
new ShowNotificationAction({
106+
type: NotificationType.Error,
107+
message: response.error.message
108+
}),
109+
new SaveToKeyboardSuccessFailed()
110+
];
94111
});
95112

113+
@Effect()
114+
autoHideSaveToKeyboardButton$: Observable<Action> = this.actions$
115+
.ofType(ActionTypes.SAVE_TO_KEYBOARD_SUCCESS)
116+
.switchMap(() => Observable.timer(1000)
117+
.switchMap(() => Observable.of(new HideSaveToKeyboardButton()))
118+
);
119+
96120
constructor(private actions$: Actions,
97121
private router: Router,
98-
private deviceRendererService: DeviceRendererService) {
122+
private deviceRendererService: DeviceRendererService,
123+
private store: Store<AppState>) {
99124
}
100125

126+
private sendUserConfigToKeyboard(userConfiguration: UserConfiguration): void {
127+
const uhkBuffer = new UhkBuffer();
128+
userConfiguration.toBinary(uhkBuffer);
129+
this.deviceRendererService.saveUserConfiguration(uhkBuffer.getBufferContent());
130+
}
101131
}

0 commit comments

Comments
 (0)