mirror of
https://github.com/YuzuZensai/beamboxctl.git
synced 2026-07-21 20:42:19 +00:00
✨ 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:
+25
-1
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
Reference in New Issue
Block a user