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 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"),
|
||||||
|
|
|
||||||
|
|
@ -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",
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 type SnipingModeProperty = Record<"data" | "run", string>;
|
||||||
|
|
||||||
|
export enum DefaultMobileCameraTypes {
|
||||||
|
FRONT = "user",
|
||||||
|
BACK = "environment",
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user