/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/widgets/CameraInput/WebcamComponent.tsx
184 строки
5 KB
Ken McGrady
Update copyright header to 2026 (#13491)
02 янв 2026, 16:21
Не верифицирован
02 янв 2026, 16:21
374540a
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { memo, ReactElement, useCallback, useEffect, useRef, useState, } from "react" import { Video } from "@emotion-icons/open-iconic" import Webcam from "react-webcam" import Icon from "~lib/components/shared/Icon" import { useEmotionTheme } from "~lib/hooks/useEmotionTheme" import themeColors from "~lib/theme/emotionBaseTheme/themeColors" import { CAMERA_PERMISSION_URL } from "~lib/urls" import { isMobile } from "~lib/util/isMobile" import { debounce } from "~lib/util/utils" import CameraInputButton from "./CameraInputButton" import { StyledBox, StyledCameraInput, StyledDescription, StyledLink, } from "./styled-components" import SwitchFacingModeButton, { FacingMode } from "./SwitchFacingModeButton" export interface Props { handleCapture: (capturedPhoto: string | null) => void width: number disabled: boolean clearPhotoInProgress: boolean setClearPhotoInProgress: (clearPhotoInProgress: boolean) => void facingMode: FacingMode setFacingMode: () => void // Allow for unit testing testOverride?: WebcamPermission } export enum WebcamPermission { PENDING = "pending", SUCCESS = "success", ERROR = "error", } interface AskForCameraPermissionProps { width: number } export const AskForCameraPermission = ({ width, }: AskForCameraPermissionProps): ReactElement => { return ( <StyledBox width={width}> <Icon size="threeXL" color={themeColors.gray60} content={Video} /> <StyledDescription> This app would like to use your camera. <StyledLink href={CAMERA_PERMISSION_URL} rel="noopener noreferrer" target="_blank" > Learn how to allow access. </StyledLink> </StyledDescription> </StyledBox> ) } const WebcamComponent = ({ handleCapture, width, disabled, clearPhotoInProgress, setClearPhotoInProgress, facingMode, setFacingMode, testOverride, }: Props): ReactElement => { const [webcamPermission, setWebcamPermissionState] = useState( testOverride || WebcamPermission.PENDING ) const videoRef = useRef<Webcam>(null) const [debouncedWidth, setDebouncedWidth] = useState(width) // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: Update to match React best practices const memoizedSetDebouncedCallback = useCallback( debounce(1000, setDebouncedWidth), [] ) useEffect(() => { memoizedSetDebouncedCallback(width) }, [width, memoizedSetDebouncedCallback]) function capture(): void { if (videoRef.current !== null) { const imageSrc = videoRef.current.getScreenshot() handleCapture(imageSrc) } } const theme = useEmotionTheme() return ( <StyledCameraInput data-testid="stCameraInputWebcamComponent"> {webcamPermission !== WebcamPermission.SUCCESS && !disabled && !clearPhotoInProgress ? ( <AskForCameraPermission width={debouncedWidth} /> ) : ( isMobile() && ( <SwitchFacingModeButton switchFacingMode={setFacingMode} /> ) )} <StyledBox data-testid="stCameraInputWebcamStyledBox" hidden={ webcamPermission !== WebcamPermission.SUCCESS && !disabled && !clearPhotoInProgress } width={debouncedWidth} > {!disabled && ( <Webcam audio={false} ref={videoRef} screenshotFormat="image/jpeg" screenshotQuality={1} width={debouncedWidth} // We keep Aspect ratio of container always equal 16 / 9. // The aspect ration of video stream may be different depending on a camera. height={(debouncedWidth * 9) / 16} style={{ borderRadius: `${theme.radii.default} ${theme.radii.default} 0 0`, }} onUserMediaError={() => { setWebcamPermissionState(WebcamPermission.ERROR) }} onUserMedia={() => { setWebcamPermissionState(WebcamPermission.SUCCESS) setClearPhotoInProgress(false) }} videoConstraints={{ width: { ideal: debouncedWidth }, facingMode, }} /> )} </StyledBox> <CameraInputButton onClick={capture} disabled={ webcamPermission !== WebcamPermission.SUCCESS || disabled || clearPhotoInProgress } > Take Photo </CameraInputButton> </StyledCameraInput> ) } export default memo(WebcamComponent)