Skip to content

Commit 4ce09f5

Browse files
mondalacicursoragentert78gb
authored
Display loading and save progress bars (#2973)
Report byte-accurate progress over IPC during keyboard config transfer, show a grayscale bar on the startup loading screen, and fill the Save to keyboard button while saving. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Kiss Róbert <ert78gb@gmail.com>
1 parent 57b273c commit 4ce09f5

19 files changed

Lines changed: 282 additions & 36 deletions

File tree

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,24 @@ export class DeviceService {
362362
try {
363363
await this.stopPollUhkDevice();
364364

365+
const sendProgress = (progress: number) => {
366+
event.sender.send(IpcEvents.device.loadConfigurationProgress, progress);
367+
};
368+
369+
sendProgress(0);
365370
await this.operations.waitUntilKeyboardBusy();
366-
const result = await this.operations.loadConfigurations();
371+
372+
const preTransferPercent = 3;
373+
const transferPercentRange = 0.82;
374+
sendProgress(preTransferPercent);
375+
376+
const result = await this.operations.loadConfigurations((percent) => {
377+
sendProgress(preTransferPercent + Math.round(percent * transferPercentRange));
378+
});
379+
380+
sendProgress(88);
367381
const modules: HardwareModules = await this.getHardwareModules(false);
382+
sendProgress(95);
368383

369384
const hardwareConfig = getHardwareConfigFromDeviceResponse(result.hardwareConfiguration);
370385
const uniqueId = hardwareConfig.uniqueId;
@@ -385,6 +400,7 @@ export class DeviceService {
385400
info: BackupUserConfigurationInfo.Unknown
386401
}
387402
};
403+
sendProgress(100);
388404
} catch (error) {
389405
response = {
390406
success: false,
@@ -1298,18 +1314,32 @@ export class DeviceService {
12981314

12991315
try {
13001316
await this.stopPollUhkDevice();
1317+
1318+
const sendProgress = (progress: number) => {
1319+
event.sender.send(IpcEvents.device.saveUserConfigurationProgress, progress);
1320+
};
1321+
1322+
sendProgress(0);
13011323
await backupUserConfiguration(data);
1324+
const preTransferPercent = 1;
1325+
const transferPercentRange = 0.94;
1326+
sendProgress(preTransferPercent);
13021327

13031328
this.logService.config('[DeviceService] User configuration will be saved', data.configuration);
13041329
const buffer = mapObjectToUserConfigBinaryBuffer(data.configuration);
1305-
await this.operations.saveUserConfiguration(buffer);
1330+
await this.operations.saveUserConfiguration(buffer, (percent) => {
1331+
sendProgress(preTransferPercent + Math.round(percent * transferPercentRange));
1332+
});
1333+
13061334
this._checkStatusBuffer = true;
13071335

13081336
if (data.saveInHistory) {
1337+
sendProgress(97);
13091338
await saveUserConfigHistoryAsync(buffer, data.deviceId, data.uniqueId);
13101339
await this.loadUserConfigFromHistory(event);
13111340
}
13121341

1342+
sendProgress(100);
13131343
response.success = true;
13141344
} catch (error) {
13151345
this.logService.error('[DeviceService] Transferring error', error);

packages/uhk-common/src/util/ipcEvents.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ export class Device {
4545
public static readonly setPrivilegeOnLinuxReply = 'set-privilege-on-linux-reply';
4646
public static readonly deviceConnectionStateChanged = 'device-connection-state-changed';
4747
public static readonly saveUserConfiguration = 'device-save-user-configuration';
48+
public static readonly saveUserConfigurationProgress = 'device-save-user-configuration-progress';
4849
public static readonly saveUserConfigurationReply = 'device-save-user-configuration-reply';
4950
public static readonly loadConfigurations = 'device-load-configuration';
51+
public static readonly loadConfigurationProgress = 'device-load-configuration-progress';
5052
public static readonly loadConfigurationReply = 'device-load-configuration-reply';
5153
public static readonly updateFirmware = 'device-update-firmware';
5254
public static readonly updateFirmwareJson = 'device-update-firmware-json';

packages/uhk-usb/src/uhk-operations.ts

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,35 @@ export class UhkOperations {
308308
* Return with the actual UserConfiguration from UHK Device
309309
* @returns {Promise<Buffer>}
310310
*/
311-
public async loadConfigurations(): Promise<LoadConfigurationsResult> {
311+
public async loadConfigurations(onProgress?: (percent: number) => void): Promise<LoadConfigurationsResult> {
312312
try {
313313
await this.waitUntilKeyboardBusy();
314-
const userConfiguration = await this.loadConfiguration(ConfigBufferId.validatedUserConfig);
315-
const hardwareConfiguration = await this.loadConfiguration(ConfigBufferId.hardwareConfig);
314+
onProgress?.(0);
315+
316+
const configSizes = await this.getConfigSizesFromKeyboard();
317+
let userConfigSize = configSizes.userConfig;
318+
const hardwareConfigSize = configSizes.hardwareConfig;
319+
320+
const reportTransferProgress = (userOffset: number, hardwareOffset: number) => {
321+
const totalBytes = Math.max(userConfigSize + hardwareConfigSize, 1);
322+
const transferredBytes = userOffset + hardwareOffset;
323+
324+
onProgress?.(Math.round(transferredBytes / totalBytes * 100));
325+
};
326+
327+
const userConfiguration = await this.loadConfiguration(
328+
ConfigBufferId.validatedUserConfig,
329+
(offset, configSize) => {
330+
userConfigSize = configSize;
331+
reportTransferProgress(offset, 0);
332+
}
333+
);
334+
const hardwareConfiguration = await this.loadConfiguration(
335+
ConfigBufferId.hardwareConfig,
336+
(offset) => {
337+
reportTransferProgress(userConfigSize, offset);
338+
}
339+
);
316340

317341
return {
318342
userConfiguration: JSON.stringify(convertBufferToIntArray(userConfiguration)),
@@ -327,7 +351,10 @@ export class UhkOperations {
327351
* Return with the actual user / hardware fonfiguration from UHK Device
328352
* @returns {Promise<Buffer>}
329353
*/
330-
public async loadConfiguration(configBufferId: ConfigBufferId): Promise<Buffer> {
354+
public async loadConfiguration(
355+
configBufferId: ConfigBufferId,
356+
onProgress?: (offset: number, configSize: number) => void
357+
): Promise<Buffer> {
331358
const configBufferIdToName = ['HardwareConfig', 'StagingUserConfig', 'ValidatedUserConfig'];
332359
const configName = configBufferIdToName[configBufferId];
333360

@@ -360,6 +387,8 @@ export class UhkOperations {
360387
configSize = originalConfigSize;
361388
}
362389
}
390+
391+
onProgress?.(offset, configSize);
363392
}
364393

365394
return configBuffer;
@@ -393,8 +422,13 @@ export class UhkOperations {
393422
};
394423
}
395424

396-
public async saveUserConfiguration(buffer: Buffer): Promise<void> {
425+
public async saveUserConfiguration(buffer: Buffer, onProgress?: (percent: number) => void): Promise<void> {
426+
const reportProgress = (percent: number) => {
427+
onProgress?.(percent);
428+
};
429+
397430
try {
431+
reportProgress(0);
398432
this.logService.usbOps('[DeviceOperation] USB[T]: Write user configuration to keyboard');
399433
let shouldRecalculateLength = false;
400434
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -423,11 +457,23 @@ export class UhkOperations {
423457

424458
const resultBuffer = new UhkBuffer(UHK_EEPROM_SIZE)
425459
userConfiguration.toBinary(resultBuffer)
426-
await this.sendConfigToKeyboard(resultBuffer.getBufferContent(), true);
460+
const configBuffer = resultBuffer.getBufferContent();
461+
462+
const preTransferPercent = 2;
463+
const transferPercentRange = 0.83;
464+
465+
reportProgress(preTransferPercent);
466+
await this.sendConfigToKeyboard(configBuffer, true, (percent) => {
467+
reportProgress(preTransferPercent + Math.round(percent * transferPercentRange));
468+
});
469+
reportProgress(86);
427470
await this.applyConfiguration();
471+
reportProgress(90);
428472
this.logService.usbOps('[DeviceOperation] USB[T]: Write user configuration to EEPROM');
429473
await this.writeConfigToEeprom(ConfigBufferId.validatedUserConfig);
474+
reportProgress(94);
430475
await this.waitUntilKeyboardBusy();
476+
reportProgress(96);
431477
} catch (error) {
432478
this.logService.error('[DeviceOperation] Transferring error', error);
433479
throw error;
@@ -916,14 +962,19 @@ export class UhkOperations {
916962
* @returns {Promise<void>}
917963
* @private
918964
*/
919-
private async sendConfigToKeyboard(buffer: Buffer, isUserConfiguration): Promise<void> {
965+
private async sendConfigToKeyboard(
966+
buffer: Buffer,
967+
isUserConfiguration,
968+
onProgress?: (percent: number) => void
969+
): Promise<void> {
920970
const command = isUserConfiguration
921971
? UsbCommand.WriteStagingUserConfig
922972
: UsbCommand.WriteHardwareConfig;
923973

924974
const fragments = getTransferBuffers(command, buffer);
925-
for (const fragment of fragments) {
926-
await this.device.write(fragment);
975+
for (let i = 0; i < fragments.length; i++) {
976+
await this.device.write(fragments[i]);
977+
onProgress?.(Math.round((i + 1) / fragments.length * 100));
927978
}
928979
}
929980

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
<button class="btn btn-primary"
1+
<button class="btn btn-primary progress-button"
22
(click)="onClicked()"
3+
[class.progress-button--active]="state.showProgress"
34
[disabled]="state.showProgress">
4-
<fa-icon *ngIf="state.showProgress"
5-
[icon]="faSpinner"
6-
animation="spin"></fa-icon>
7-
{{state.text}}
5+
<span class="progress-button__fill"
6+
role="progressbar"
7+
[attr.aria-valuenow]="state.progressPercent"
8+
aria-valuemin="0"
9+
aria-valuemax="100"
10+
[style.width.%]="state.showProgress ? state.progressPercent : 0">
11+
</span>
12+
<span class="progress-button__content">
13+
{{state.text}}
14+
</span>
815
</button>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
11
button {
22
min-width: 150px;
33
}
4+
5+
.progress-button {
6+
position: relative;
7+
overflow: hidden;
8+
}
9+
10+
.progress-button__fill {
11+
position: absolute;
12+
top: 0;
13+
left: 0;
14+
bottom: 0;
15+
background-color: var(--color-loading-progress-bar-fill);
16+
opacity: 0.35;
17+
transition: width 200ms ease-out;
18+
}
19+
20+
.progress-button__content {
21+
position: relative;
22+
z-index: 1;
23+
}

packages/uhk-web/src/app/components/progress-button/progress-button.component.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
22
import { Action } from '@ngrx/store';
3-
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
43

54
import { ProgressButtonState, initProgressButtonState } from '../../store/reducers/progress-button-state';
65

@@ -15,8 +14,6 @@ export class ProgressButtonComponent {
1514
@Input() state: ProgressButtonState = initProgressButtonState;
1615
@Output() clicked: EventEmitter<Action> = new EventEmitter<Action>();
1716

18-
faSpinner = faSpinner;
19-
2017
onClicked() {
2118
this.clicked.emit(this.state.action);
2219
}
Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
<div class="uhk-message-wrapper">
2-
<uhk-agent-icon *ngIf="showLogo"
3-
class="agent-logo"
4-
[ngClass]="{'spin-logo': rotateLogo}">
5-
</uhk-agent-icon>
6-
<div class="messages">
7-
<h1> {{ header }} </h1>
8-
<h2 *ngIf="!smallText"> {{ subtitle }} </h2>
9-
<div *ngIf="smallText"> {{ subtitle }} </div>
10-
<p *ngIf="description"> {{ description }} </p>
2+
<div class="uhk-message-content">
3+
<div class="uhk-message-main">
4+
<uhk-agent-icon *ngIf="showLogo"
5+
class="agent-logo"
6+
[ngClass]="{'spin-logo': rotateLogo}">
7+
</uhk-agent-icon>
8+
<div class="messages">
9+
<h1> {{ header }} </h1>
10+
<h2 *ngIf="!smallText"> {{ subtitle }} </h2>
11+
<div *ngIf="smallText"> {{ subtitle }} </div>
12+
<p *ngIf="description"> {{ description }} </p>
13+
</div>
14+
</div>
15+
<div *ngIf="showProgressBar" class="loading-progress-bar-wrapper">
16+
<progress class="loading-progress-bar"
17+
[value]="progressPercent"
18+
max="100">
19+
</progress>
20+
</div>
1121
</div>
1222
</div>

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,59 @@
55
justify-content: center;
66
}
77

8+
.uhk-message-content {
9+
display: flex;
10+
flex-direction: column;
11+
align-items: stretch;
12+
}
13+
14+
.uhk-message-main {
15+
display: flex;
16+
align-items: center;
17+
}
18+
819
.agent-logo {
920
margin: 1.25em;
1021
height: 8em;
1122
width: 8em;
1223
}
1324

25+
.loading-progress-bar-wrapper {
26+
width: 100%;
27+
padding: 0 1.25em;
28+
}
29+
30+
.loading-progress-bar {
31+
display: block;
32+
width: 100%;
33+
height: 1rem;
34+
appearance: none;
35+
border: none;
36+
border-radius: 0.375rem;
37+
overflow: hidden;
38+
background-color: var(--color-loading-progress-bar-bg);
39+
40+
&::-webkit-progress-inner-element {
41+
border: none;
42+
}
43+
44+
&::-webkit-progress-bar {
45+
background-color: var(--color-loading-progress-bar-bg);
46+
}
47+
48+
&::-webkit-progress-value {
49+
background-color: var(--color-loading-progress-bar-fill);
50+
border-radius: 0;
51+
transition: inline-size 200ms ease-out;
52+
}
53+
54+
&::-moz-progress-bar {
55+
background-color: var(--color-loading-progress-bar-fill);
56+
border-radius: 0;
57+
transition: inline-size 200ms ease-out;
58+
}
59+
}
60+
1461
.message {
1562
display: flex;
1663
flex-direction: column;

packages/uhk-web/src/app/components/uhk-message/uhk-message.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ export class UhkMessageComponent {
1313
@Input() subtitle: string;
1414
@Input() rotateLogo = false;
1515
@Input() showLogo = false;
16+
@Input() showProgressBar = false;
17+
@Input() progressPercent = 0;
1618
@Input() smallText = false;
1719
}
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { Component } from '@angular/core';
2+
import { Store } from '@ngrx/store';
3+
import { Observable } from 'rxjs';
4+
5+
import { AppState, getConfigurationLoadingProgress } from '../../store';
26

37
@Component({
48
selector: 'loading-device',
@@ -7,11 +11,15 @@ import { Component } from '@angular/core';
711
<uhk-message header="Loading keyboard configuration..."
812
subtitle="Hang tight!"
913
[showLogo]="true"
10-
[rotateLogo]="true"></uhk-message>
14+
[rotateLogo]="true"
15+
[showProgressBar]="true"
16+
[progressPercent]="progressPercent$ | async"></uhk-message>
1117
`,
1218
})
1319
export class LoadingDevicePageComponent {
20+
progressPercent$: Observable<number>;
1421

15-
constructor() {
22+
constructor(store: Store<AppState>) {
23+
this.progressPercent$ = store.select(getConfigurationLoadingProgress);
1624
}
1725
}

0 commit comments

Comments
 (0)