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 { render } from "ink";
|
||||||
import { App } from "../components/App.tsx";
|
import { App } from "../components/App.tsx";
|
||||||
import { StatusApp } from "../components/StatusApp.tsx";
|
import { StatusApp } from "../components/StatusApp.tsx";
|
||||||
|
import { MediaDetector } from "../lib/processing/media-detector.ts";
|
||||||
|
|
||||||
export function setupCLI() {
|
export function setupCLI() {
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
@@ -101,7 +102,30 @@ export function setupCLI() {
|
|||||||
process.exit(1);
|
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
|
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 { Box, Text, useApp } from "ink";
|
||||||
import Spinner from "ink-spinner";
|
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 { useUpload } from "../hooks/useUpload.ts";
|
||||||
import type { UploadOptions } from "../cli/types.ts";
|
import type { UploadOptions } from "../cli/types.ts";
|
||||||
|
|
||||||
export interface AppProps {
|
export interface AppProps {
|
||||||
options: UploadOptions;
|
options: UploadOptions;
|
||||||
verbose: boolean;
|
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 { exit } = useApp();
|
||||||
const {
|
const {
|
||||||
status,
|
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 { Header } from './Header.tsx';
|
||||||
|
export { ConfirmAnimatedUpload } from './ConfirmAnimatedUpload.tsx';
|
||||||
export { UploadProgress } from './UploadProgress.tsx';
|
export { UploadProgress } from './UploadProgress.tsx';
|
||||||
export { Status } from './Status.tsx';
|
export { Status } from './Status.tsx';
|
||||||
export { ConnectionStatus, type ConnectionStep } from './ConnectionStatus.tsx';
|
export { ConnectionStatus, type ConnectionStep } from './ConnectionStatus.tsx';
|
||||||
|
|||||||
Reference in New Issue
Block a user