UNPKG

synapse-react-client

Version:

[![Build Status](https://travis-ci.com/Sage-Bionetworks/Synapse-React-Client.svg?branch=main)](https://travis-ci.com/Sage-Bionetworks/Synapse-React-Client) [![npm version](https://badge.fury.io/js/synapse-react-client.svg)](https://badge.fury.io/js/synaps

207 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdditionalPropertiesSchemaField = exports.getWidgetFromPropertyType = exports.transformDataFromPropertyType = exports.guessPropertyType = exports.AdditionalPropertyWidget = exports.PropertyType = void 0; var tslib_1 = require("tslib"); var rjsf_core_1 = require("@sage-bionetworks/rjsf-core"); var react_1 = (0, tslib_1.__importStar)(require("react")); var react_bootstrap_1 = require("react-bootstrap"); var useListState_1 = require("../../../utils/hooks/useListState"); var CustomArrayFieldTemplate_1 = require("./CustomArrayFieldTemplate"); // Matches ####-##-##T##:##:##.###Z, e.g. 1970-01-01T12:00:000Z var ISO_TIMESTAMP_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/; // Types that correspond to the different input fields that the annotation editor supports var PropertyType; (function (PropertyType) { PropertyType["STRING"] = "String"; PropertyType["INTEGER"] = "Integer"; PropertyType["FLOAT"] = "Float"; PropertyType["BOOLEAN"] = "Boolean"; PropertyType["DATETIME"] = "Datetime"; })(PropertyType = exports.PropertyType || (exports.PropertyType = {})); // Selection of react-jsonschema-form Widget types that we can use for the supported property fields var AdditionalPropertyWidget; (function (AdditionalPropertyWidget) { AdditionalPropertyWidget["TextWidget"] = "TextWidget"; AdditionalPropertyWidget["DateTimeWidget"] = "DateTimeWidget"; AdditionalPropertyWidget["CheckboxWidget"] = "CheckboxWidget"; })(AdditionalPropertyWidget = exports.AdditionalPropertyWidget || (exports.AdditionalPropertyWidget = {})); function guessPropertyType(list) { if (list.every(function (item) { return typeof item === 'number' || item === 'NaN'; })) { if (list.every(function (item) { return Number.isInteger(item); })) { return PropertyType.INTEGER; } else { return PropertyType.FLOAT; } } else if (list.every(function (item) { return typeof item === 'boolean'; })) { return PropertyType.BOOLEAN; } else if (list.every(function (item) { return typeof item === 'string'; }) && list.every(function (item) { return !!ISO_TIMESTAMP_REGEX.exec(item); })) { return PropertyType.DATETIME; } // otherwise, default type is 'string' return PropertyType.STRING; } exports.guessPropertyType = guessPropertyType; function transformDataFromPropertyType(list, propertyType) { switch (propertyType) { case PropertyType.INTEGER: return list.map(function (item) { return Number.isNaN(Number(item)) ? undefined : Math.floor(Number(item)); }); case PropertyType.FLOAT: return list.map(function (item) { var asFloat = parseFloat(item); if (Number.isNaN(asFloat)) { return 'NaN'; } else if (Number.isInteger(asFloat)) { return asFloat.toFixed(1); } else { return asFloat; } }); case PropertyType.DATETIME: return list.map(function (item) { if (typeof item === 'string' && ISO_TIMESTAMP_REGEX.exec(item)) { return item; } else { return undefined; } }); case PropertyType.BOOLEAN: return list.map(function (item) { return !!item; }); case PropertyType.STRING: default: return list.map(function (item) { return String(item); }); } } exports.transformDataFromPropertyType = transformDataFromPropertyType; function getWidgetFromPropertyType(propertyType) { switch (propertyType) { case PropertyType.DATETIME: return AdditionalPropertyWidget.DateTimeWidget; case PropertyType.BOOLEAN: return AdditionalPropertyWidget.CheckboxWidget; case PropertyType.STRING: case PropertyType.INTEGER: case PropertyType.FLOAT: default: return AdditionalPropertyWidget.TextWidget; } } exports.getWidgetFromPropertyType = getWidgetFromPropertyType; /** * react-jsonschema-form SchemaField override for "additionalProperties" only. * Modifies the data to provide full compatibility with Synapse annotations features. * * This component provides these enhancements to the SchemaField: * - Supports selecting a type, and changing the input widget appropriately * - Identifying the type on mount * - Treat all field values as arrays * - When the last array value is removed, remove the entire key from the form. * @param props * @returns */ function AdditionalPropertiesSchemaField(props) { /** * Custom annotations in Synapse are always arrays. This function converts initial data to be an array type. * If the initial data is an array, return the data itself. * If the intitial data is a string, returns an array of substrings separated by commas. * Otherwise, wrap the data in an array. */ function convertToArray(value) { if (Array.isArray(value)) { return value; } else if (typeof value === 'string') { return value.split(',').map(function (s) { return s.trim(); }); // split a string of comma-separated values, then trim whitespace } else { return [value]; } } var id = props.id, formData = props.formData, onChange = props.onChange, registry = props.registry, schema = props.schema, name = props.name, onDropPropertyClick = props.onDropPropertyClick, uiSchema = props.uiSchema; var _a = (0, useListState_1.useListState)(convertToArray(formData)), list = _a.list, handleListChange = _a.handleListChange, handleListRemove = _a.handleListRemove, appendToList = _a.appendToList, setList = _a.setList; // The type determines which widget we show. var _b = (0, react_1.useState)(guessPropertyType(list)), propertyType = _b[0], setPropertyType = _b[1]; var _c = (0, react_1.useState)(AdditionalPropertyWidget.TextWidget), widget = _c[0], setWidget = _c[1]; (0, react_1.useEffect)(function () { // The item may not be an array when we get it, and we need to convert it right away because the order of items is not stable, and seems to depend on if the item is an array or not. // Otherwise, the order of the properties will change when the user modifies the data. We may be able to fix this by modifying react-jsonschema-form to stabilize the item order. // TODO: This doesn't work without a delay. setTimeout(function () { onChange(list); }, 100); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); (0, react_1.useEffect)(function () { // When the selected type changes, switch to the appropriate widget for accepting input setWidget(getWidgetFromPropertyType(propertyType)); // Coerce the data to match the new type setList(transformDataFromPropertyType(list, propertyType)); // Don't add other properties to dependency array because we don't want to automatically coerce input // i.e. Only coerce data when the type changes, which should only be on mount or when the user explicitly chooses a new type. // eslint-disable-next-line react-hooks/exhaustive-deps }, [propertyType]); (0, react_1.useEffect)(function () { onChange(list); }, [onChange, list]); var Widget = rjsf_core_1.utils.getWidget(schema, AdditionalPropertyWidget[widget], registry.widgets); var items = list.map(function (item, index) { var _a, _b, _c; return { children: (react_1.default.createElement(Widget, { id: name + "-" + index, "aria-label": name + "-" + index, schema: schema, value: item, onChange: function (value) { handleListChange(index)(value); }, uiSchema: uiSchema, required: props.required, disabled: props.disabled, readonly: props.readonly, autofocus: props.autofocus, placeholder: (_a = props.placeholder) !== null && _a !== void 0 ? _a : '', options: {}, formContext: props.formContext, onFocus: props.onFocus, onBlur: function (id, value) { setList(transformDataFromPropertyType(list, propertyType)); props.onBlur(id, value); }, label: (_b = props.title) !== null && _b !== void 0 ? _b : '', multiple: true, rawErrors: [], // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - The Widget needs the registry prop even though it's not in the type signature registry: registry })), onDropIndexClick: function () { if (list.length === 1) { // If this is the only item, then remove the property from the field return onDropPropertyClick(name); } else { // Otherwise, remove the item from the list return handleListRemove(index); } }, className: (_c = props.className) !== null && _c !== void 0 ? _c : '', disabled: props.disabled, hasMoveDown: false, hasMoveUp: false, hasRemove: false, hasToolbar: false, index: index, onAddIndexClick: function () { return function () { // no-op }; }, onReorderClick: function () { return function () { //no-op }; }, readonly: props.readonly, key: "" + index, }; }); return (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement(react_bootstrap_1.FormGroup, { className: "col-xs-3" }, react_1.default.createElement(react_bootstrap_1.FormLabel, { id: id + "-type" }, "Type"), react_1.default.createElement(react_bootstrap_1.FormControl, { as: "select", disabled: props.disabled, readOnly: props.readonly, value: propertyType, required: true, id: id + "-type", onChange: function (e) { setPropertyType(e.target.value); } }, Object.keys(PropertyType).map(function (type) { return (react_1.default.createElement("option", { key: type, value: PropertyType[type] }, PropertyType[type])); }))), react_1.default.createElement(CustomArrayFieldTemplate_1.CustomArrayFieldTemplate, { className: "col-xs-6", onAddClick: function () { return appendToList(null); }, canAdd: true, title: name, schema: schema, items: items, registry: registry, DescriptionField: function () { return null; }, TitleField: function () { return null; }, disabled: props.disabled, idSchema: props.idSchema, readonly: props.readonly, required: props.required, uiSchema: props.uiSchema, formContext: props.formContext, formData: props.formData }))); } exports.AdditionalPropertiesSchemaField = AdditionalPropertiesSchemaField; //# sourceMappingURL=AdditionalPropertiesSchemaField.js.map