mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
CameraCaptureModal: open with options
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import type { FileWithHandle } from 'browser-fs-access';
|
||||
|
||||
import type { CameraCaptureDialogOptions } from '~/common/components/camera/useCameraCaptureDialog';
|
||||
import type { CameraLiveStream } from '~/common/components/camera/useCameraCapture';
|
||||
import { addSnackbar } from '~/common/components/snackbar/useSnackbarsStore';
|
||||
import { useCameraCaptureDialog } from '~/common/components/camera/useCameraCaptureDialog';
|
||||
@@ -12,7 +13,7 @@ import { useWebAttachmentModal } from './useWebAttachmentModal';
|
||||
// Focused hooks that bridge `useAttachmentDrafts` return values to UI callback shapes.
|
||||
// Each hook wraps one attachment source. Consumers compose only what they need.
|
||||
|
||||
type _HandleCameraOpen = () => Promise<void>;
|
||||
type _HandleCameraOpen = (options?: CameraCaptureDialogOptions) => Promise<void>;
|
||||
type _HandleFiles = (files: FileWithHandle[], errorMessage: string | null) => void;
|
||||
type _HandlePasteIntercept = (event: React.ClipboardEvent) => void;
|
||||
type _HandleScreenCapture = (file: File) => void;
|
||||
@@ -24,15 +25,18 @@ type _HandleWebLinks = (links: { url: string }[]) => void;
|
||||
*/
|
||||
export function useAttachHandler_CameraOpen(
|
||||
attachAppendFile: AttachmentDraftsApi['attachAppendFile'],
|
||||
handleLiveStream?: (stream: CameraLiveStream) => void
|
||||
handleLiveStream?: (stream: CameraLiveStream) => void,
|
||||
): _HandleCameraOpen {
|
||||
|
||||
// external state
|
||||
const { openCameraCapture } = useCameraCaptureDialog(); // -> showPromisedOverlay
|
||||
|
||||
return React.useCallback<_HandleCameraOpen>(async () => {
|
||||
return React.useCallback(async (optionsOrEvent?: CameraCaptureDialogOptions | React.SyntheticEvent) => {
|
||||
|
||||
const result = await openCameraCapture({ allowMultiCapture: true, allowLiveFeed: !!handleLiveStream });
|
||||
// guard: onClick handlers pass the event as first arg
|
||||
const options = optionsOrEvent && 'nativeEvent' in optionsOrEvent ? undefined : optionsOrEvent;
|
||||
|
||||
const result = await openCameraCapture({ allowMultiCapture: true, allowLiveFeed: !!handleLiveStream, ...options });
|
||||
if (!result) return; // user dismissed the dialog without capturing anything
|
||||
|
||||
// append all captured images
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, Button, ButtonGroup, IconButton, Modal, ModalClose, ModalSlotsAndSlotProps, Option, Select, SelectSlotsAndSlotProps, Sheet, Tooltip, Typography, } from '@mui/joy';
|
||||
import { Box, Button, ButtonGroup, IconButton, Modal, ModalClose, ModalSlotsAndSlotProps, Option, Select, SelectSlotsAndSlotProps, Sheet, Tooltip, Typography } from '@mui/joy';
|
||||
import AddRoundedIcon from '@mui/icons-material/AddRounded';
|
||||
import CameraEnhanceIcon from '@mui/icons-material/CameraEnhance';
|
||||
import CameraFrontIcon from '@mui/icons-material/CameraFront';
|
||||
@@ -40,7 +40,7 @@ const _modalSlotProps: ModalSlotsAndSlotProps['slotProps'] = {
|
||||
|
||||
const _selectSlotProps: SelectSlotsAndSlotProps<false>['slotProps'] = {
|
||||
listbox: {
|
||||
size: 'md'
|
||||
size: 'md',
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -124,7 +124,14 @@ const _styles = {
|
||||
},
|
||||
},
|
||||
|
||||
recButton: {
|
||||
recButtonLarge: {
|
||||
pl: 3.25,
|
||||
pr: 4.5,
|
||||
py: 1.5,
|
||||
minWidth: { md: 200 },
|
||||
},
|
||||
|
||||
recButtonRight: {
|
||||
pl: 2,
|
||||
pr: 2.5,
|
||||
},
|
||||
@@ -135,6 +142,7 @@ const _styles = {
|
||||
export function CameraCaptureModal(props: {
|
||||
allowMultiCapture?: boolean;
|
||||
allowLiveFeed?: boolean;
|
||||
liveFeedOnly?: boolean;
|
||||
// allowOcr?: boolean;
|
||||
onDone: (result: CameraCaptureResult | null) => void;
|
||||
}) {
|
||||
@@ -160,7 +168,7 @@ export function CameraCaptureModal(props: {
|
||||
|
||||
|
||||
// derived state
|
||||
const { allowMultiCapture, allowLiveFeed, onDone } = props;
|
||||
const { allowMultiCapture, allowLiveFeed, liveFeedOnly, onDone } = props;
|
||||
|
||||
|
||||
// single exit point: gather results and close (stream cleanup happens via effect on unmount)
|
||||
@@ -306,6 +314,16 @@ export function CameraCaptureModal(props: {
|
||||
const cameraButtons = React.useMemo(() => {
|
||||
const btns: React.ReactNode[] = [];
|
||||
|
||||
// Live-feed-only mode: single prominent red Record button
|
||||
if (liveFeedOnly) {
|
||||
btns.push(
|
||||
<Button key='rec' size='lg' color='danger' disabled={cameraIdx === -1} onClick={handleStartLiveFeedClicked} endDecorator={<FiberManualRecordIcon />} sx={_styles.recButtonLarge}>
|
||||
Record
|
||||
</Button>,
|
||||
);
|
||||
return btns;
|
||||
}
|
||||
|
||||
// After first capture: [wide +] [Done (N)] - no confusing Capture
|
||||
if (capturedCount > 0) {
|
||||
btns.push(
|
||||
@@ -343,14 +361,14 @@ export function CameraCaptureModal(props: {
|
||||
color='danger'
|
||||
disabled={cameraIdx === -1}
|
||||
onClick={handleStartLiveFeedClicked}
|
||||
sx={_styles.recButton}
|
||||
sx={_styles.recButtonRight}
|
||||
>
|
||||
<FiberManualRecordIcon />
|
||||
</IconButton>
|
||||
</Tooltip>,
|
||||
);
|
||||
return btns;
|
||||
}, [allowLiveFeed, allowMultiCapture, cameraIdx, capturedCount, handleCloseModal, handleStartLiveFeedClicked, handleVideoAddClicked, handleVideoSnapClicked, isAddButtonDisabled]);
|
||||
}, [allowLiveFeed, allowMultiCapture, liveFeedOnly, cameraIdx, capturedCount, handleCloseModal, handleStartLiveFeedClicked, handleVideoAddClicked, handleVideoSnapClicked, isAddButtonDisabled]);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,16 @@ import type { CameraCaptureResult } from './useCameraCapture';
|
||||
import { CameraCaptureModal } from './CameraCaptureModal';
|
||||
|
||||
|
||||
export type CameraCaptureDialogOptions = {
|
||||
/** show [+] button to queue multiple captures */
|
||||
allowMultiCapture?: boolean;
|
||||
/** show small Record button alongside Capture */
|
||||
allowLiveFeed?: boolean;
|
||||
/** only show a prominent Record button (no Capture/Add) */
|
||||
liveFeedOnly?: boolean;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a function to open the camera overlay dialog.
|
||||
* Resolves with null if dismissed empty, or CameraCaptureResult (images + optional live stream).
|
||||
@@ -15,11 +25,12 @@ export function useCameraCaptureDialog() {
|
||||
// external state
|
||||
const { showPromisedOverlay } = useOverlayComponents();
|
||||
|
||||
const openCameraCapture = React.useCallback((options?: { allowMultiCapture?: boolean; allowLiveFeed?: boolean }): Promise<CameraCaptureResult | null> =>
|
||||
const openCameraCapture = React.useCallback((options?: CameraCaptureDialogOptions): Promise<CameraCaptureResult | null> =>
|
||||
showPromisedOverlay<CameraCaptureResult | null>('camera-capture', { rejectWithValue: null }, ({ onResolve }) => (
|
||||
<CameraCaptureModal
|
||||
allowMultiCapture={options?.allowMultiCapture}
|
||||
allowLiveFeed={options?.allowLiveFeed}
|
||||
liveFeedOnly={options?.liveFeedOnly}
|
||||
onDone={onResolve}
|
||||
/>
|
||||
)), [showPromisedOverlay]);
|
||||
|
||||
Reference in New Issue
Block a user