diff --git a/src/components/App.tsx b/src/components/App.tsx
index 0e7e660..1ebed77 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -44,7 +44,8 @@ const UploadFlow: React.FC<{ options: UploadOptions; verbose: boolean }> = ({
const {
status,
message,
- progress,
+ sendProgress,
+ confirmProgress,
currentFileIndex,
totalFiles,
uploadSteps,
@@ -96,7 +97,8 @@ const UploadFlow: React.FC<{ options: UploadOptions; verbose: boolean }> = ({
@@ -107,7 +109,8 @@ const UploadFlow: React.FC<{ options: UploadOptions; verbose: boolean }> = ({
Device is ready to use!
@@ -120,7 +123,8 @@ const UploadFlow: React.FC<{ options: UploadOptions; verbose: boolean }> = ({
Please check your device and try again.
diff --git a/src/components/UploadProgress.tsx b/src/components/UploadProgress.tsx
index af6328f..1cd7662 100644
--- a/src/components/UploadProgress.tsx
+++ b/src/components/UploadProgress.tsx
@@ -5,28 +5,45 @@ import Spinner from "ink-spinner";
interface UploadProgressProps {
status: "connecting" | "uploading" | "success" | "error";
message?: string;
- progress?: number;
+ sendProgress?: number;
+ confirmProgress?: number;
}
-const ProgressBar: React.FC<{ progress: number }> = ({ progress }) => {
- const percentage = Math.round(progress);
- const barLength = 30;
- const filledLength = Math.round((progress / 100) * barLength);
- const filled = "█".repeat(filledLength);
- const empty = "░".repeat(barLength - filledLength);
+const BAR_LENGTH = 30;
- const getBarColor = () => {
- if (percentage < 100) return "cyan";
- return "green";
- };
+const ShadowProgressBar: React.FC<{
+ sendProgress: number;
+ confirmProgress: number;
+}> = ({ sendProgress, confirmProgress }) => {
+ const sentFilled = Math.round((sendProgress / 100) * BAR_LENGTH);
+ const confirmFilled = Math.round((confirmProgress / 100) * BAR_LENGTH);
+ const pct = Math.round(sendProgress);
- const barContent = filled + empty;
+ const bar = Array.from({ length: BAR_LENGTH }, (_, i) => {
+ if (i < confirmFilled)
+ return (
+
+ █
+
+ );
+ if (i < sentFilled)
+ return (
+
+ █
+
+ );
+ return (
+
+ ░
+
+ );
+ });
return (
[
- {barContent}
- ] {percentage}%
+ {bar}
+ ] {pct}%
);
};
@@ -34,7 +51,8 @@ const ProgressBar: React.FC<{ progress: number }> = ({ progress }) => {
export const UploadProgress: React.FC = ({
status,
message,
- progress,
+ sendProgress = 0,
+ confirmProgress = 0,
}) => {
const getStatusColor = () => {
switch (status) {
@@ -49,18 +67,18 @@ export const UploadProgress: React.FC = ({
}
};
- const getStatusIcon = () => {
- switch (status) {
- case "success":
- return "✓";
- case "error":
- return "✗";
- default:
- return null;
- }
- };
+ const statusIcon =
+ status === "success" ? "✓" : status === "error" ? "✗" : null;
- const statusIcon = getStatusIcon();
+ const showBar = status === "uploading" || status === "success";
+ const effectiveSend = status === "success" ? 100 : sendProgress;
+ const effectiveConfirm = status === "success" ? 100 : confirmProgress;
+
+ const isConfirming =
+ status === "uploading" && sendProgress >= 100 && confirmProgress < 100;
+ const displayMessage = isConfirming
+ ? (message?.replace(/^Sending:/, "Confirming:") ?? "Confirming...")
+ : message || status.toUpperCase();
return (
@@ -73,12 +91,15 @@ export const UploadProgress: React.FC = ({
{statusIcon && {statusIcon}}
- {message || status.toUpperCase()}
+ {displayMessage}
- {progress !== undefined && status === "uploading" && (
-
+ {showBar && (
+
)}
);
diff --git a/src/hooks/useUpload.ts b/src/hooks/useUpload.ts
index 979dfb5..fb97d30 100644
--- a/src/hooks/useUpload.ts
+++ b/src/hooks/useUpload.ts
@@ -12,7 +12,8 @@ export function useUpload(options: UploadOptions, verbose: boolean) {
"connecting" | "uploading" | "success" | "error"
>("connecting");
const [message, setMessage] = useState("Initializing...");
- const [progress, setProgress] = useState(0);
+ const [sendProgress, setSendProgress] = useState(0);
+ const [confirmProgress, setConfirmProgress] = useState(0);
const [currentFileIndex, setCurrentFileIndex] = useState(0);
const [totalFiles, setTotalFiles] = useState(1);
@@ -174,14 +175,16 @@ export function useUpload(options: UploadOptions, verbose: boolean) {
},
{ id: "complete", label: "Finalizing", status: "pending" },
]);
- setProgress(0);
+ setSendProgress(0);
+ setConfirmProgress(0);
const success = await uploader.uploadImageFromFile(
imagePath,
targetSize,
animationSize,
- (prog: number, status?: string) => {
- setProgress(prog);
+ (send: number, confirm: number, status?: string) => {
+ setSendProgress(send);
+ setConfirmProgress(confirm);
if (status) {
setMessage(`${i + 1}/${imagesToUpload.length}: ${status}`);
}
@@ -215,8 +218,9 @@ export function useUpload(options: UploadOptions, verbose: boolean) {
success = await uploader.uploadCheckerboard(
targetSize,
8,
- (prog: number, status?: string) => {
- setProgress(prog);
+ (send: number, confirm: number, status?: string) => {
+ setSendProgress(send);
+ setConfirmProgress(confirm);
if (status) {
setMessage(status);
}
@@ -227,8 +231,9 @@ export function useUpload(options: UploadOptions, verbose: boolean) {
options.image,
targetSize,
animationSize,
- (prog: number, status?: string) => {
- setProgress(prog);
+ (send: number, confirm: number, status?: string) => {
+ setSendProgress(send);
+ setConfirmProgress(confirm);
if (status) {
setMessage(status);
}
@@ -271,7 +276,8 @@ export function useUpload(options: UploadOptions, verbose: boolean) {
return {
status,
message,
- progress,
+ sendProgress,
+ confirmProgress,
currentFileIndex,
totalFiles,
uploadSteps,
diff --git a/src/lib/ble/ble-client.ts b/src/lib/ble/ble-client.ts
index f37b3e3..79021c1 100644
--- a/src/lib/ble/ble-client.ts
+++ b/src/lib/ble/ble-client.ts
@@ -510,13 +510,13 @@ export class BleUploader {
* Send image data packets to device with batched acknowledgment waiting
* @param fullData Complete image data payload
* @param packetType Packet type for header
- * @param onProgress Progress callback with (progress, status)
+ * @param onProgress Progress callback with (sendProgress 0-100, confirmProgress 0-100, status)
* @returns True if successful
*/
public async sendData(
fullData: Buffer,
packetType: PacketType,
- onProgress?: (progress: number, status?: string) => void,
+ onProgress?: (sendProgress: number, confirmProgress: number, status?: string) => void,
): Promise {
if (!this.writeCharacteristic) {
throw new ConnectionError("BLE client not connected");
@@ -589,17 +589,9 @@ export class BleUploader {
// Report progress
if (onProgress) {
- // Sending contributes 50% of this packet's progress
- const sendProgress = ((i + 1) / totalChunks) * 50;
- // Acks contribute the other 50%
- const ackProgress =
- (this.notificationHandler.packetSuccessCount / totalChunks) * 50;
- const combinedProgress = sendProgress + ackProgress;
-
- onProgress(
- combinedProgress,
- `Sending: (${i + 1}/${totalChunks} packets)`,
- );
+ const sendProgress = ((i + 1) / totalChunks) * 100;
+ const confirmProgress = (this.notificationHandler.packetSuccessCount / totalChunks) * 100;
+ onProgress(sendProgress, confirmProgress, `Sending: (${i + 1}/${totalChunks} packets)`);
}
await this.sleep(this.chunkDelay * 1000);
@@ -623,16 +615,9 @@ export class BleUploader {
}
if (onProgress) {
- // Update progress as acks come in
- const sendProgress = ((i + 1) / totalChunks) * 50;
- const ackProgress =
- (this.notificationHandler.packetSuccessCount / totalChunks) * 50;
- const combinedProgress = sendProgress + ackProgress;
-
- onProgress(
- combinedProgress,
- `Sending: (${this.notificationHandler.packetSuccessCount}/${totalChunks} packets)`,
- );
+ const sendProgress = ((i + 1) / totalChunks) * 100;
+ const confirmProgress = (this.notificationHandler.packetSuccessCount / totalChunks) * 100;
+ onProgress(sendProgress, confirmProgress, `Sending: (${this.notificationHandler.packetSuccessCount}/${totalChunks} packets)`);
}
await this.sleep(100);
@@ -651,7 +636,7 @@ export class BleUploader {
* @returns True if all acks received without error
*/
public async waitForResponse(
- onProgress?: (progress: number, status?: string) => void,
+ onProgress?: (sendProgress: number, confirmProgress: number, status?: string) => void,
): Promise {
this.notificationHandler.waitingForAck = true;
@@ -679,18 +664,11 @@ export class BleUploader {
}
if (onProgress) {
- // All packets sent (50%), waiting for remaining acks (0-50%)
- const sendProgress = 50; // All packets already sent
- const ackProgress =
+ const confirmProgress =
(this.notificationHandler.packetSuccessCount /
this.notificationHandler.expectedAckCount) *
- 50;
- const combinedProgress = sendProgress + ackProgress;
-
- onProgress(
- combinedProgress,
- `Confirming: (${this.notificationHandler.packetSuccessCount}/${this.notificationHandler.expectedAckCount} packets)`,
- );
+ 100;
+ onProgress(100, confirmProgress, `Confirming: (${this.notificationHandler.packetSuccessCount}/${this.notificationHandler.expectedAckCount} packets)`);
}
await this.sleep(100); // Check every 100ms
@@ -704,7 +682,7 @@ export class BleUploader {
if (success) {
logger.info("All acknowledgments received");
if (onProgress) {
- onProgress(100, "Upload complete");
+ onProgress(100, 100, "Upload complete");
}
} else if (
this.notificationHandler.packetSuccessCount <
diff --git a/src/lib/core/beambox-uploader.ts b/src/lib/core/beambox-uploader.ts
index 75c7460..a94259d 100644
--- a/src/lib/core/beambox-uploader.ts
+++ b/src/lib/core/beambox-uploader.ts
@@ -24,7 +24,7 @@ export interface UploadOptions {
imageData?: Buffer;
targetSize?: [number, number];
animationSize?: [number, number];
- onProgress?: (progress: number, status?: string) => void;
+ onProgress?: (sendProgress: number, confirmProgress: number, status?: string) => void;
dumpDir?: string;
}
@@ -358,7 +358,7 @@ export class BeamBoxUploader {
private async uploadAnimation(
filePath: string,
targetSize: [number, number],
- onProgress?: (progress: number) => void,
+ onProgress?: (sendProgress: number, confirmProgress: number, status?: string) => void,
dumpDir?: string,
): Promise {
const animationSize: [number, number] = targetSize;
@@ -476,7 +476,7 @@ export class BeamBoxUploader {
imagePath: string,
targetSize?: [number, number],
animationSize?: [number, number],
- onProgress?: (progress: number, status?: string) => void,
+ onProgress?: (sendProgress: number, confirmProgress: number, status?: string) => void,
dumpDir?: string,
): Promise {
return await this.upload({
@@ -498,7 +498,7 @@ export class BeamBoxUploader {
public async uploadCheckerboard(
targetSize?: [number, number],
squares?: number,
- onProgress?: (progress: number, status?: string) => void,
+ onProgress?: (sendProgress: number, confirmProgress: number, status?: string) => void,
dumpDir?: string,
): Promise {
const effectiveSize = targetSize ?? this.imageConfig.defaultSize;