feat: code scanner widget default camera (#26083)
## Description - support for default mobile camera for code scanner widget #### PR fixes following issue(s) Fixes #25766 #### Media #### Type of change - New feature (non-breaking change which adds functionality) #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [x] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
727d30ad92
commit
0ed9fd6f30
|
|
@ -46,7 +46,7 @@ import {
|
|||
import type { ThemeProp } from "widgets/constants";
|
||||
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
||||
import { importSvg } from "design-system-old";
|
||||
import { getVideoConstraints } from "./utils";
|
||||
import { getVideoConstraints } from "../../utils";
|
||||
|
||||
const CameraOfflineIcon = importSvg(
|
||||
() => import("assets/icons/widget/camera/camera-offline.svg"),
|
||||
|
|
|
|||
|
|
@ -45,8 +45,3 @@ export enum DeviceTypes {
|
|||
CAMERA = "CAMERA",
|
||||
}
|
||||
export type DeviceType = keyof typeof DeviceTypes;
|
||||
|
||||
export enum DefaultMobileCameraTypes {
|
||||
FRONT = "user",
|
||||
BACK = "environment",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,12 @@ import { base64ToBlob, createBlobUrl } from "utils/AppsmithUtils";
|
|||
import type { DerivedPropertiesMap } from "utils/WidgetFactory";
|
||||
import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
|
||||
import BaseWidget from "widgets/BaseWidget";
|
||||
import { FileDataTypes } from "widgets/constants";
|
||||
import { DefaultMobileCameraTypes, FileDataTypes } from "widgets/constants";
|
||||
|
||||
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
|
||||
import CameraComponent from "../component";
|
||||
import type { CameraMode } from "../constants";
|
||||
import {
|
||||
CameraModeTypes,
|
||||
DefaultMobileCameraTypes,
|
||||
MediaCaptureStatusTypes,
|
||||
} from "../constants";
|
||||
import { CameraModeTypes, MediaCaptureStatusTypes } from "../constants";
|
||||
import type { AutocompletionDefinitions } from "widgets/constants";
|
||||
import {
|
||||
BACK_CAMERA_LABEL,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import { ScannerLayout } from "../constants";
|
|||
import type { ThemeProp } from "widgets/constants";
|
||||
import { usePageVisibility } from "react-page-visibility";
|
||||
import { importSvg } from "design-system-old";
|
||||
import { getVideoConstraints } from "widgets/utils";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
const CameraOfflineIcon = importSvg(
|
||||
() => import("assets/icons/widget/camera/camera-offline.svg"),
|
||||
|
|
@ -415,9 +417,13 @@ function CodeScannerComponent(props: CodeScannerComponentProps) {
|
|||
const [error, setError] = useState<string>("");
|
||||
const [isImageMirrored, setIsImageMirrored] = useState(false);
|
||||
const [videoConstraints, setVideoConstraints] =
|
||||
useState<MediaTrackConstraints>({
|
||||
facingMode: "environment",
|
||||
});
|
||||
useState<MediaTrackConstraints>(
|
||||
isMobile
|
||||
? {
|
||||
facingMode: { ideal: props.defaultCamera },
|
||||
}
|
||||
: {},
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if the tab is active.
|
||||
|
|
@ -458,10 +464,13 @@ function CodeScannerComponent(props: CodeScannerComponentProps) {
|
|||
const handleMediaDeviceChange = useCallback(
|
||||
(mediaDeviceInfo: MediaDeviceInfo) => {
|
||||
if (mediaDeviceInfo.kind === "videoinput") {
|
||||
setVideoConstraints({
|
||||
...videoConstraints,
|
||||
deviceId: mediaDeviceInfo.deviceId,
|
||||
});
|
||||
const constraints = getVideoConstraints(
|
||||
videoConstraints,
|
||||
isMobile,
|
||||
"",
|
||||
mediaDeviceInfo.deviceId,
|
||||
);
|
||||
setVideoConstraints(constraints);
|
||||
}
|
||||
},
|
||||
[],
|
||||
|
|
@ -629,6 +638,7 @@ export interface CodeScannerComponentProps extends ComponentProps {
|
|||
onCodeDetected: (value: string) => void;
|
||||
scannerLayout: ScannerLayout;
|
||||
shouldButtonFitContent: boolean;
|
||||
defaultCamera: string;
|
||||
}
|
||||
|
||||
export default CodeScannerComponent;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export interface CodeScannerWidgetProps extends WidgetProps {
|
|||
iconAlign?: Alignment;
|
||||
placement?: ButtonPlacement;
|
||||
scannerLayout: ScannerLayout;
|
||||
defaultCamera: string;
|
||||
}
|
||||
|
||||
export enum ScannerLayout {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class CodeScannerWidget extends BaseWidget<
|
|||
borderRadius={this.props.borderRadius}
|
||||
boxShadow={this.props.boxShadow}
|
||||
buttonColor={this.props.buttonColor || this.props.accentColor}
|
||||
defaultCamera={this.props.defaultCamera}
|
||||
iconAlign={this.props.iconAlign}
|
||||
iconName={this.props.iconName}
|
||||
isDisabled={this.props.isDisabled}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
||||
import { ValidationTypes } from "constants/WidgetValidation";
|
||||
import { isAutoLayout } from "utils/autoLayout/flexWidgetUtils";
|
||||
import { DefaultMobileCameraTypes } from "widgets/constants";
|
||||
import type { CodeScannerWidgetProps } from "widgets/CodeScannerWidget/constants";
|
||||
import { ScannerLayout } from "widgets/CodeScannerWidget/constants";
|
||||
import {
|
||||
BACK_CAMERA_LABEL,
|
||||
DEFAULT_CAMERA_LABEL,
|
||||
DEFAULT_CAMERA_LABEL_DESCRIPTION,
|
||||
FRONT_CAMERA_LABEL,
|
||||
createMessage,
|
||||
} from "@appsmith/constants/messages";
|
||||
export default [
|
||||
{
|
||||
sectionName: "Basic",
|
||||
|
|
@ -93,6 +101,35 @@ export default [
|
|||
props.scannerLayout === ScannerLayout.ALWAYS_ON,
|
||||
dependencies: ["scannerLayout"],
|
||||
},
|
||||
{
|
||||
propertyName: "defaultCamera",
|
||||
label: createMessage(DEFAULT_CAMERA_LABEL),
|
||||
helpText: createMessage(DEFAULT_CAMERA_LABEL_DESCRIPTION),
|
||||
controlType: "DROP_DOWN",
|
||||
defaultValue: DefaultMobileCameraTypes.BACK,
|
||||
options: [
|
||||
{
|
||||
label: createMessage(FRONT_CAMERA_LABEL),
|
||||
value: DefaultMobileCameraTypes.FRONT,
|
||||
},
|
||||
{
|
||||
label: createMessage(BACK_CAMERA_LABEL),
|
||||
value: DefaultMobileCameraTypes.BACK,
|
||||
},
|
||||
],
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
validation: {
|
||||
type: ValidationTypes.TEXT,
|
||||
params: {
|
||||
allowedValues: [
|
||||
DefaultMobileCameraTypes.FRONT,
|
||||
DefaultMobileCameraTypes.BACK,
|
||||
],
|
||||
default: DefaultMobileCameraTypes.BACK,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import type {
|
||||
PropertyPaneConfig,
|
||||
PropertyPaneControlConfig,
|
||||
} from "constants/PropertyControlConstants";
|
||||
import contentConfig from "./contentConfig";
|
||||
import { DefaultMobileCameraTypes } from "widgets/constants";
|
||||
|
||||
describe("codescanner property control", () => {
|
||||
const propertyConfigs = contentConfig
|
||||
.map((sectionConfig) => sectionConfig.children)
|
||||
.flat() as PropertyPaneControlConfig[];
|
||||
|
||||
const defaultCameraProperty = propertyConfigs.find(
|
||||
(propertyConfig) => propertyConfig.propertyName === "defaultCamera",
|
||||
) as PropertyPaneConfig;
|
||||
|
||||
it("validates the codescanner widget has a default camera property", () => {
|
||||
expect(defaultCameraProperty).not.toBeNull();
|
||||
});
|
||||
|
||||
it("validates the options for default mobile camera property", () => {
|
||||
expect((defaultCameraProperty as any).options).toHaveLength(2);
|
||||
expect((defaultCameraProperty as any).options[0].value).toEqual(
|
||||
DefaultMobileCameraTypes.FRONT,
|
||||
);
|
||||
expect((defaultCameraProperty as any).options[1].value).toEqual(
|
||||
DefaultMobileCameraTypes.BACK,
|
||||
);
|
||||
});
|
||||
|
||||
it("validates the default mobile camera value to be back camera", () => {
|
||||
expect((defaultCameraProperty as any).defaultValue).toEqual(
|
||||
DefaultMobileCameraTypes.BACK,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -377,3 +377,8 @@ export type ThemeProp = {
|
|||
};
|
||||
|
||||
export type SnipingModeProperty = Record<"data" | "run", string>;
|
||||
|
||||
export enum DefaultMobileCameraTypes {
|
||||
FRONT = "user",
|
||||
BACK = "environment",
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user