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:
Sangeeth Sivan 2023-08-10 12:18:51 +05:30 committed by GitHub
parent 727d30ad92
commit 0ed9fd6f30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 19 deletions

View File

@ -46,7 +46,7 @@ import {
import type { ThemeProp } from "widgets/constants"; import type { ThemeProp } from "widgets/constants";
import { isAirgapped } from "@appsmith/utils/airgapHelpers"; import { isAirgapped } from "@appsmith/utils/airgapHelpers";
import { importSvg } from "design-system-old"; import { importSvg } from "design-system-old";
import { getVideoConstraints } from "./utils"; import { getVideoConstraints } from "../../utils";
const CameraOfflineIcon = importSvg( const CameraOfflineIcon = importSvg(
() => import("assets/icons/widget/camera/camera-offline.svg"), () => import("assets/icons/widget/camera/camera-offline.svg"),

View File

@ -45,8 +45,3 @@ export enum DeviceTypes {
CAMERA = "CAMERA", CAMERA = "CAMERA",
} }
export type DeviceType = keyof typeof DeviceTypes; export type DeviceType = keyof typeof DeviceTypes;
export enum DefaultMobileCameraTypes {
FRONT = "user",
BACK = "environment",
}

View File

@ -7,16 +7,12 @@ import { base64ToBlob, createBlobUrl } from "utils/AppsmithUtils";
import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import type { DerivedPropertiesMap } from "utils/WidgetFactory";
import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
import BaseWidget 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 type { SetterConfig, Stylesheet } from "entities/AppTheming";
import CameraComponent from "../component"; import CameraComponent from "../component";
import type { CameraMode } from "../constants"; import type { CameraMode } from "../constants";
import { import { CameraModeTypes, MediaCaptureStatusTypes } from "../constants";
CameraModeTypes,
DefaultMobileCameraTypes,
MediaCaptureStatusTypes,
} from "../constants";
import type { AutocompletionDefinitions } from "widgets/constants"; import type { AutocompletionDefinitions } from "widgets/constants";
import { import {
BACK_CAMERA_LABEL, BACK_CAMERA_LABEL,

View File

@ -28,6 +28,8 @@ import { ScannerLayout } from "../constants";
import type { ThemeProp } from "widgets/constants"; import type { ThemeProp } from "widgets/constants";
import { usePageVisibility } from "react-page-visibility"; import { usePageVisibility } from "react-page-visibility";
import { importSvg } from "design-system-old"; import { importSvg } from "design-system-old";
import { getVideoConstraints } from "widgets/utils";
import { isMobile } from "react-device-detect";
const CameraOfflineIcon = importSvg( const CameraOfflineIcon = importSvg(
() => import("assets/icons/widget/camera/camera-offline.svg"), () => import("assets/icons/widget/camera/camera-offline.svg"),
@ -415,9 +417,13 @@ function CodeScannerComponent(props: CodeScannerComponentProps) {
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const [isImageMirrored, setIsImageMirrored] = useState(false); const [isImageMirrored, setIsImageMirrored] = useState(false);
const [videoConstraints, setVideoConstraints] = const [videoConstraints, setVideoConstraints] =
useState<MediaTrackConstraints>({ useState<MediaTrackConstraints>(
facingMode: "environment", isMobile
}); ? {
facingMode: { ideal: props.defaultCamera },
}
: {},
);
/** /**
* Check if the tab is active. * Check if the tab is active.
@ -458,10 +464,13 @@ function CodeScannerComponent(props: CodeScannerComponentProps) {
const handleMediaDeviceChange = useCallback( const handleMediaDeviceChange = useCallback(
(mediaDeviceInfo: MediaDeviceInfo) => { (mediaDeviceInfo: MediaDeviceInfo) => {
if (mediaDeviceInfo.kind === "videoinput") { if (mediaDeviceInfo.kind === "videoinput") {
setVideoConstraints({ const constraints = getVideoConstraints(
...videoConstraints, videoConstraints,
deviceId: mediaDeviceInfo.deviceId, isMobile,
}); "",
mediaDeviceInfo.deviceId,
);
setVideoConstraints(constraints);
} }
}, },
[], [],
@ -629,6 +638,7 @@ export interface CodeScannerComponentProps extends ComponentProps {
onCodeDetected: (value: string) => void; onCodeDetected: (value: string) => void;
scannerLayout: ScannerLayout; scannerLayout: ScannerLayout;
shouldButtonFitContent: boolean; shouldButtonFitContent: boolean;
defaultCamera: string;
} }
export default CodeScannerComponent; export default CodeScannerComponent;

View File

@ -15,6 +15,7 @@ export interface CodeScannerWidgetProps extends WidgetProps {
iconAlign?: Alignment; iconAlign?: Alignment;
placement?: ButtonPlacement; placement?: ButtonPlacement;
scannerLayout: ScannerLayout; scannerLayout: ScannerLayout;
defaultCamera: string;
} }
export enum ScannerLayout { export enum ScannerLayout {

View File

@ -77,6 +77,7 @@ class CodeScannerWidget extends BaseWidget<
borderRadius={this.props.borderRadius} borderRadius={this.props.borderRadius}
boxShadow={this.props.boxShadow} boxShadow={this.props.boxShadow}
buttonColor={this.props.buttonColor || this.props.accentColor} buttonColor={this.props.buttonColor || this.props.accentColor}
defaultCamera={this.props.defaultCamera}
iconAlign={this.props.iconAlign} iconAlign={this.props.iconAlign}
iconName={this.props.iconName} iconName={this.props.iconName}
isDisabled={this.props.isDisabled} isDisabled={this.props.isDisabled}

View File

@ -1,8 +1,16 @@
import type { PropertyPaneConfig } from "constants/PropertyControlConstants"; import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
import { ValidationTypes } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation";
import { isAutoLayout } from "utils/autoLayout/flexWidgetUtils"; import { isAutoLayout } from "utils/autoLayout/flexWidgetUtils";
import { DefaultMobileCameraTypes } from "widgets/constants";
import type { CodeScannerWidgetProps } from "widgets/CodeScannerWidget/constants"; import type { CodeScannerWidgetProps } from "widgets/CodeScannerWidget/constants";
import { ScannerLayout } 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 [ export default [
{ {
sectionName: "Basic", sectionName: "Basic",
@ -93,6 +101,35 @@ export default [
props.scannerLayout === ScannerLayout.ALWAYS_ON, props.scannerLayout === ScannerLayout.ALWAYS_ON,
dependencies: ["scannerLayout"], 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,
},
},
},
], ],
}, },

View File

@ -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,
);
});
});

View File

@ -377,3 +377,8 @@ export type ThemeProp = {
}; };
export type SnipingModeProperty = Record<"data" | "run", string>; export type SnipingModeProperty = Record<"data" | "run", string>;
export enum DefaultMobileCameraTypes {
FRONT = "user",
BACK = "environment",
}