Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/pluggableWidgets/signature-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

## [2.4.0] - 2026-4-10

### Added

- Added directImage save mode that uploads the signature directly to a System.Image object

## [2.3.0] - 2025-7-7

- Updated react-native-webview from version v13.12.5 to latest
Expand Down
3 changes: 2 additions & 1 deletion packages/pluggableWidgets/signature-native/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "signature-native",
"widgetName": "Signature",
"version": "2.3.0",
"version": "2.4.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
Expand All @@ -21,6 +21,7 @@
"dependencies": {
"@mendix/piw-native-utils-internal": "*",
"@mendix/piw-utils-internal": "*",
"react-native-blob-util": "0.21.2",
"react-native-signature-canvas": "3.4.0",
"react-native-webview": "13.13.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { StructurePreviewProps, topBar } from "@mendix/piw-utils-internal";
import { hidePropertiesIn, Properties } from "@mendix/pluggable-widgets-tools";

import { SignaturePreviewProps } from "../typings/SignatureProps";

export const getPreview = (_: SignaturePreviewProps, isDarkMode: boolean): StructurePreviewProps =>
topBar("Signature", [], isDarkMode);

export function getProperties(values: SignaturePreviewProps, defaultProperties: Properties): Properties {
if (values.saveMode === "attribute") {
hidePropertiesIn(defaultProperties, values, ["imageObject"]);
}

if (values.saveMode === "directImage") {
hidePropertiesIn(defaultProperties, values, ["imageAttribute"]);
}

return defaultProperties;
}
67 changes: 64 additions & 3 deletions packages/pluggableWidgets/signature-native/src/Signature.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mergeNativeStyles, extractStyles } from "@mendix/pluggable-widgets-tools";
import { executeAction } from "@mendix/piw-utils-internal";
import { ReactElement, useCallback, useRef } from "react";
import RNBlobUtil from "react-native-blob-util";
import { View, Text } from "react-native";
import SignatureScreen, { SignatureViewRef } from "react-native-signature-canvas";
import { Touchable } from "./components/Touchable";
Expand All @@ -10,6 +11,47 @@ import { SignatureStyle, defaultSignatureStyle, webStyles } from "./ui/Styles";

export type Props = SignatureProps<SignatureStyle>;

declare const mx: {
data: {
saveDocument(
guid: string,
fileName: string,
params: object,
blob: Blob,
callback: () => void,
error: (error: Error) => void
): void;
};
};

function getCleanBase64(signature: string): string {
return signature.includes(",") ? signature.split(",")[1].replace(/\s/g, "") : signature.replace(/\s/g, "");
}

async function uploadSignature(signature: string, guid: string): Promise<void> {
const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/temp_signature_${Date.now()}.png`;

try {
await RNBlobUtil.fs.writeFile(tempPath, getCleanBase64(signature), "base64");

const response = await fetch(`file://${tempPath}`);
const blob = await response.blob();

return await new Promise((resolve, reject) => {
mx.data.saveDocument(
guid,
"camera image",
{},
blob,
() => resolve(),
(error: Error) => reject(error)
);
});
} finally {
RNBlobUtil.fs.unlink(tempPath).catch(() => undefined);
}
}

export function Signature(props: Props): ReactElement {
const ref = useRef<SignatureViewRef>(null);
const styles = mergeNativeStyles(defaultSignatureStyle, props.style);
Expand All @@ -28,11 +70,30 @@ export function Signature(props: Props): ReactElement {
const buttonCaptionSave = props.buttonCaptionSave?.value ?? "Save";

const handleSignature = useCallback(
(base64signature: string): void => {
props.imageAttribute.setValue(base64signature);
async (base64signature: string): Promise<void> => {
if (props.saveMode === "directImage") {
const targetGuid = props.imageObject?.value?.id;

if (!targetGuid) {
console.error(
"Signature direct image mode requires the widget to be placed inside a System.Image data container."
);
return;
}

try {
await uploadSignature(base64signature, targetGuid);
executeAction(props.onSave);
} catch (error) {
console.error("Failed to upload signature image:", error);
}
return;
}

props.imageAttribute?.setValue(base64signature);
executeAction(props.onSave);
},
[props.imageAttribute, props.onSave]
[props.imageAttribute, props.imageObject, props.onSave, props.saveMode]
);

return (
Expand Down
29 changes: 19 additions & 10 deletions packages/pluggableWidgets/signature-native/src/Signature.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<widget id="com.mendix.widget.native.signature.Signature" supportedPlatform="Native" needsEntityContext="true"
offlineCapable="true" pluginWidget="true" xmlns="http://www.mendix.com/widget/1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../../../../node_modules/mendix/custom_widget.xsd">
<?xml version="1.0" encoding="utf-8" ?>
<widget id="com.mendix.widget.native.signature.Signature" supportedPlatform="Native" needsEntityContext="true" offlineCapable="true" pluginWidget="true" xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../../../../node_modules/mendix/custom_widget.xsd">
<name>Signature</name>
<description>Display signature.</description>
<icon>
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
</icon>
<properties>
<propertyGroup caption="General">
<propertyGroup caption="General">
<property key="imageAttribute" type="attribute">
<property key="saveMode" type="enumeration" defaultValue="attribute">
<caption>Save mode</caption>
<description>Choose how the signature is saved. "Store as Base64 string" saves the signature as a base64 value to a String attribute — use the Base64DecodeToImage nanoflow action to convert and commit it to an image object. "Direct image upload" skips the base64 step entirely: the signature is uploaded directly to the linked System.Image object, so you only need to commit the object in your nanoflow.</description>
<enumerationValues>
<enumerationValue key="attribute">Store as Base64 string</enumerationValue>
<enumerationValue key="directImage">Direct image</enumerationValue>
</enumerationValues>
</property>
<property key="imageAttribute" type="attribute" required="false">
<caption>Attribute</caption>
<description/>
<description>The String attribute that receives the signature as a base64 value when save mode is Store as Base64 string.</description>
<attributeTypes>
<attributeType name="String"/>
<attributeType name="String" />
</attributeTypes>
</property>
<property key="imageObject" type="contextObject" required="false">
<caption>Target image</caption>
<description>The context System.Image object to which the signature will be uploaded when save mode is Direct image.</description>
</property>
</propertyGroup>
<propertyGroup caption="Buttons">
<property key="buttonCaptionClear" type="textTemplate" required="false">
Expand Down Expand Up @@ -49,7 +58,7 @@
</property>
</propertyGroup>
<propertyGroup caption="Common">
<systemProperty key="Name"/>
<systemProperty key="Name" />
</propertyGroup>
</properties>
</widget>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import { fireEvent, render } from "@testing-library/react-native";
import { Signature, Props } from "../Signature";
import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal";

jest.mock("react-native-blob-util", () => ({
__esModule: true,
default: {
fs: {
dirs: { CacheDir: "/tmp" },
writeFile: jest.fn().mockResolvedValue(undefined),
unlink: jest.fn().mockResolvedValue(undefined)
},
fetch: jest.fn()
}
}));

jest.mock("react-native", () => {
const RN = jest.requireActual("react-native");
RN.NativeModules.RNCWebView = { isFileUploadSupported: jest.fn(() => true) };
Expand Down Expand Up @@ -45,7 +57,8 @@ const defaultProps: Props = {
style: [],
imageAttribute: new EditableValueBuilder<string>().withValue("").build(),
buttonCaptionClear: dynamicValue<string>("Clear"),
buttonCaptionSave: dynamicValue<string>("Save")
buttonCaptionSave: dynamicValue<string>("Save"),
saveMode: "attribute"
};

describe("Signature Android", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ import { fireEvent, render } from "@testing-library/react-native";
import { Signature, Props } from "../Signature";
import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal";

jest.mock("react-native-blob-util", () => ({
__esModule: true,
default: {
fs: {
dirs: { CacheDir: "/tmp" },
writeFile: jest.fn().mockResolvedValue(undefined),
unlink: jest.fn().mockResolvedValue(undefined)
},
fetch: jest.fn()
}
}));

jest.mock("react-native", () => {
const RN = jest.requireActual("react-native");
RN.NativeModules.RNCWebView = { isFileUploadSupported: jest.fn(() => true) };
Expand All @@ -21,7 +33,8 @@ const defaultProps: Props = {
style: [],
imageAttribute: new EditableValueBuilder<string>().withValue("").build(),
buttonCaptionClear: dynamicValue<string>("Clear"),
buttonCaptionSave: dynamicValue<string>("Save")
buttonCaptionSave: dynamicValue<string>("Save"),
saveMode: "attribute"
};

jest.mock("react-native-webview", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="Signature" version="2.3.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="Signature" version="2.4.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="Signature.xml" />
</widgetFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
* @author Mendix Widgets Framework Team
*/
import { CSSProperties } from "react";
import { ActionValue, DynamicValue, EditableValue } from "mendix";
import { ActionValue, DynamicValue, EditableValue, ObjectItem } from "mendix";

export type SaveModeEnum = "attribute" | "directImage";

export interface SignatureProps<Style> {
name: string;
style: Style[];
imageAttribute: EditableValue<string>;
saveMode: SaveModeEnum;
imageAttribute?: EditableValue<string>;
imageObject?: DynamicValue<ObjectItem>;
buttonCaptionClear?: DynamicValue<string>;
buttonCaptionSave?: DynamicValue<string>;
onClear?: ActionValue;
Expand All @@ -29,7 +33,9 @@ export interface SignaturePreviewProps {
readOnly: boolean;
renderMode: "design" | "xray" | "structure";
translate: (text: string) => string;
saveMode: SaveModeEnum;
imageAttribute: string;
imageObject: {} | { type: string } | null;
buttonCaptionClear: string;
buttonCaptionSave: string;
onClear: {} | null;
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading