feat: prompt for confirmation before uploading animated media from #2

Adds a confirmation step when users attempt to upload GIFs or videos. These are back to experimental and contain risk of bricking the device.
This commit is contained in:
2026-06-08 23:11:09 +07:00
parent ec3e2d217f
commit e64d2e6f1b
4 changed files with 121 additions and 4 deletions
+25 -1
View File
@@ -6,6 +6,7 @@ import type { UploadOptions, StatusOptions } from "./types.ts";
import { render } from "ink";
import { App } from "../components/App.tsx";
import { StatusApp } from "../components/StatusApp.tsx";
import { MediaDetector } from "../lib/processing/media-detector.ts";
export function setupCLI() {
const program = new Command();
@@ -101,7 +102,30 @@ export function setupCLI() {
process.exit(1);
}
render(<App options={options} verbose={verbose} />);
let confirmMediaType: "gif" | "video" | null = null;
if (options.image && !options.test) {
const filesToCheck = options.isBulk
? (options.images ?? [])
: [options.image];
for (const file of filesToCheck) {
const info = await MediaDetector.detectFromFile(file).catch(
() => null,
);
if (info?.type === "gif" || info?.type === "video") {
confirmMediaType = info.type;
break;
}
}
}
render(
<App
options={options}
verbose={verbose}
confirmMediaType={confirmMediaType}
/>,
);
});
program
+32 -3
View File
@@ -1,16 +1,45 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { Box, Text, useApp } from "ink";
import Spinner from "ink-spinner";
import { Header, UploadProgress, ConnectionStatus } from "./index.ts";
import {
Header,
UploadProgress,
ConnectionStatus,
ConfirmAnimatedUpload,
} from "./index.ts";
import { useUpload } from "../hooks/useUpload.ts";
import type { UploadOptions } from "../cli/types.ts";
export interface AppProps {
options: UploadOptions;
verbose: boolean;
/** Set when the upload contains a GIF/video and requires user confirmation */
confirmMediaType?: "gif" | "video" | null;
}
export const App: React.FC<AppProps> = ({ options, verbose }) => {
export const App: React.FC<AppProps> = ({
options,
verbose,
confirmMediaType,
}) => {
const [confirmed, setConfirmed] = useState(!confirmMediaType);
if (!confirmed && confirmMediaType) {
return (
<ConfirmAnimatedUpload
mediaType={confirmMediaType}
onConfirm={() => setConfirmed(true)}
/>
);
}
return <UploadFlow options={options} verbose={verbose} />;
};
const UploadFlow: React.FC<{ options: UploadOptions; verbose: boolean }> = ({
options,
verbose,
}) => {
const { exit } = useApp();
const {
status,
+63
View File
@@ -0,0 +1,63 @@
import React, { useState } from "react";
import { Box, Text, useApp, useInput } from "ink";
import { Header } from "./Header.tsx";
export interface ConfirmAnimatedUploadProps {
mediaType: "gif" | "video";
onConfirm: () => void;
}
const CONFIRM_PHRASE = "YES";
export const ConfirmAnimatedUpload: React.FC<ConfirmAnimatedUploadProps> = ({
mediaType,
onConfirm,
}) => {
const { exit } = useApp();
const [input, setInput] = useState("");
useInput((char, key) => {
if (key.escape || (key.ctrl && char === "c")) {
exit();
process.exit(1);
}
if (key.return) {
if (input === CONFIRM_PHRASE) {
onConfirm();
}
return;
}
if (key.backspace || key.delete) {
setInput((prev) => prev.slice(0, -1));
return;
}
if (char) {
setInput((prev) => prev + char);
}
});
return (
<Box flexDirection="column" padding={1}>
<Header />
<Box flexDirection="column" borderStyle="round" borderColor="red" paddingX={1}>
<Text color="red" bold>
WARNING: Uploading {mediaType} to your BeamBox device is EXPERIMENTAL.
</Text>
<Text color="red">
There are known cases where this can permanently brick your device.
</Text>
<Text color="red">Proceed only if you understand and accept this risk.</Text>
</Box>
<Box marginTop={1}>
<Text>
Type "{CONFIRM_PHRASE}" to continue (Esc to cancel): {input}
</Text>
</Box>
</Box>
);
};
+1
View File
@@ -1,4 +1,5 @@
export { Header } from './Header.tsx';
export { ConfirmAnimatedUpload } from './ConfirmAnimatedUpload.tsx';
export { UploadProgress } from './UploadProgress.tsx';
export { Status } from './Status.tsx';
export { ConnectionStatus, type ConnectionStep } from './ConnectionStatus.tsx';