UNPKG

@ibm-adw/skill-toolkit

Version:

Developing your own skills with IBM Automation Digital Worker Skill Toolkit

963 lines (847 loc) 28 kB
import React, { useState, useEffect, useCallback } from 'react'; import Form from 'react-jsonschema-form'; import { FormLabel, Checkbox, Dropdown, MultiSelect, TextInput, Button } from 'carbon-components-react'; import SchemaField from 'react-jsonschema-form/lib/components/fields/SchemaField'; import { getSchemaType, asNumber, guessType } from 'react-jsonschema-form/lib/utils'; import ArrowDown from '@carbon/icons-react/lib/arrow--down/16'; import ArrowUp from '@carbon/icons-react/lib/arrow--up/16'; import Close from '@carbon/icons-react/lib/close/16'; import Add from '@carbon/icons-react/lib/add/16'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CarbonTitleField(props) { const { id, title, required, formContext } = props; let legend = title; if (id !== 'root__title') { // Only add optionalLabel when it's not the root title legend += !required && formContext && formContext.optionalLabel ? ` (${formContext.optionalLabel})` : ''; } return React.createElement("div", { id: id, className: "baiw--form-title-field" }, legend); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CarbonDescriptionField(props) { const { id, description } = props; return React.createElement("div", { id: id, className: "baiw--form-description-field" }, description); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2020. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CustomSchemaField(props) { const { idSchema, required, registry } = props; let { schema } = props; const { fields, formContext } = registry; let unsupportedField, invalidRef = false; const COMPONENT_TYPES = { array: 'ArrayField', boolean: 'BooleanField', integer: 'NumberField', number: 'NumberField', object: 'ObjectField', string: 'StringField', null: 'NullField' }; // when the schema contains a ref : if (schema.$ref) { try { const definitionName = schema.$ref.split('/')[2]; if (!registry.definitions[definitionName]) { invalidRef = true; } else { schema = registry.definitions[definitionName]; if (props.schema.title) { schema.title = props.schema.title; } } } catch { invalidRef = true; } } // unsupported field should be tested when the ref is valid and when schema is not empty if (!invalidRef && Object.getOwnPropertyNames(schema).length > 0) { const componentName = COMPONENT_TYPES[getSchemaType(schema)]; if (!(componentName in fields)) { unsupportedField = true; } } if (unsupportedField) { const label = schema.title || idSchema.$id; return React.createElement("div", { className: "bx--fieldset" }, React.createElement(FormLabel, null, label, !required && formContext && formContext.optionalLabel ? ` (${formContext.optionalLabel})` : null), React.createElement("p", { className: "baiw--unsupported-field" }, formContext ? formContext.unsupportedFieldLabel : '')); } return React.createElement(SchemaField, props); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019,2020. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ const fields = { TitleField: CarbonTitleField, DescriptionField: CarbonDescriptionField, SchemaField: CustomSchemaField }; /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CarbonCheckboxWidget(props) { const { id, value, disabled, readonly, label, options } = props; // Because an unchecked checkbox will cause html5 validation to fail, only add // the "required" attribute if the field value must be "true", due to the // "const" or "enum" keywords const required = undefined; // the onChange function called by Carbon's Checkbox has the value as first parameter const _onChange = value => { props.onChange(value === '' ? options.emptyValue : value); }; return React.createElement(Checkbox, { checked: typeof value === 'undefined' ? false : value, required: required, labelText: label, disabled: disabled || readonly, hideLabel: false, onChange: _onChange, id: id }); } CarbonCheckboxWidget.defaultProps = { autofocus: false, value: false, options: { 'emptyValue': undefined } }; const useDirtyInput = (onChange, onBlur, value, defaultValue, resetDirty) => { const [state, setState] = useState({ inputDirty: !!(value || defaultValue), initiallyDirty: !!(value || defaultValue) }); useEffect(() => { setState(s => _objectSpread2({}, s, { initiallyDirty: !!(value || defaultValue) })); }, [value, defaultValue]); useEffect(() => { setState(s => _objectSpread2({}, s, { inputDirty: s.initiallyDirty })); }, [resetDirty]); const _onChange = useCallback(event => { setState(s => _objectSpread2({}, s, { inputDirty: true })); onChange && onChange(event); }, [onChange]); const _onBlur = useCallback(event => { setState(s => _objectSpread2({}, s, { inputDirty: true })); onBlur && onBlur(event); }, [onBlur]); return [state.inputDirty, _onChange, _onBlur]; }; /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ const nums = new Set(['number', 'integer']); function processValue(schema, value, required) { // "enum" is a reserved word, so only "type" and "items" can be destructured const { type, items } = schema; if (value === '') { return undefined; } else if (type === 'array' && items && nums.has(items.type)) { return required && value.length === 0 ? undefined : value.map(asNumber); } else if (type === 'array' && items) { return required && value.length === 0 ? undefined : value; } else if (type === 'boolean') { return value; } else if (type === 'number') { return asNumber(value); } // If type is undefined, but an enum is present, try and infer the type from // the enum values if (schema.enum) { if (schema.enum.every(x => guessType(x) === 'number')) { return asNumber(value); } else if (schema.enum.every(x => guessType(x) === 'boolean')) { return value === 'true'; } } return value; } function getValue(event, multiple) { if (multiple) { return event.selectedItems.map(o => o.value); } else { return event.selectedItem.value; } } function CarbonDropdownWidget(props) { const { schema, id, options, formContext, multiple, onChange, value, onBlur, required // onFocus, // placeholder, } = props; // INFO: carbon does not support disabled options in Dropdown // const { enumOptions, enumDisabled } = options; const { enumOptions } = options; const _onChange = event => { const newValue = getValue(event, multiple); onChange(processValue(schema, newValue, required)); }; const [inputDirty, __onChange, _onBlur] = useDirtyInput(_onChange, onBlur, value); const [invalid, setInvalid] = useState(false); useEffect(() => { if (formContext && formContext.ignoreDirty) { setInvalid(schema.invalid); } else { setInvalid(inputDirty ? schema.invalid : false); } }, [formContext, inputDirty, schema.invalid]); const stateReducer = useCallback((state, changes) => { if (changes.type === '__autocomplete_blur_button__' || changes.type === '__autocomplete_mouseup__') { _onBlur(); } return changes; }, [_onBlur]); const items = enumOptions.map(function ({ value, label }, i) { return { id: i, text: label, value: value }; }); if (!multiple) { const selectedItem = items.find(item => item.value === value); return React.createElement(Dropdown, { id: id //label is the text displayed as default , label: schema.label ? schema.label : '' // titleText is the label in most other component , invalid: invalid, invalidText: schema.invalid ? schema.invalidText : '', items: items, itemToString: item => item ? item.text : '', onChange: __onChange, downshiftProps: { stateReducer: stateReducer }, selectedItem: schema.label ? undefined : selectedItem }); } else { const selectedItems = value ? items.filter(item => { return JSON.stringify(value).includes(JSON.stringify(item.value)); }) : undefined; return React.createElement(MultiSelect, { id: id, useTitleInItem: false, label: schema.label ? schema.label : '', invalid: invalid, invalidText: schema.invalid ? schema.invalidText : '', onChange: __onChange, downshiftProps: { stateReducer: stateReducer }, items: items, itemToString: item => item ? item.text : '', initialSelectedItems: selectedItems }); } } CarbonDropdownWidget.defaultProps = { autofocus: false }; function CarbonBaseInput(props) { // Note: since React 15.2.0 we can't forward unknown element attributes, so we // exclude the "options" and "schema" ones here. if (!props.id) { throw new Error(`no id for props ${JSON.stringify(props)}`); } const { id, value, defaultValue, readonly, disabled, autofocus, onBlur, onChange, onFocus, options, schema, formContext, registry, rawErrors } = props, inputProps = _objectWithoutProperties(props, ["id", "value", "defaultValue", "readonly", "disabled", "autofocus", "onBlur", "onChange", "onFocus", "options", "schema", "formContext", "registry", "rawErrors"]); // If options.inputType is set use that as the input type if (options.inputType) { inputProps.type = options.inputType; } else if (!inputProps.type) { // If the schema is of type number or integer, set the input type to number if (schema.type === 'number') { inputProps.type = 'number'; // Setting step to 'any' fixes a bug in Safari where decimals are not // allowed in number inputs inputProps.step = 'any'; } else if (schema.type === 'integer') { inputProps.type = 'number'; // Since this is integer, you always want to step up or down in multiples // of 1 inputProps.step = '1'; } else { inputProps.type = 'text'; } } // If multipleOf is defined, use this as the step value. This mainly improves // the experience for keyboard users (who can use the up/down KB arrows). if (schema.multipleOf) { inputProps.step = schema.multipleOf; } const _onChange = useCallback(({ target: { value } }) => { onChange(value === '' ? options.emptyValue : value); }, [onChange, options.emptyValue]); const [inputDirty, __onChange, _onBlur] = useDirtyInput(_onChange, onBlur, value, defaultValue); const [invalid, setInvalid] = useState(false); useEffect(() => { if (formContext && formContext.ignoreDirty) { setInvalid(schema.invalid); } else { setInvalid(inputDirty ? schema.invalid : false); } }, [formContext, inputDirty, schema.invalid]); return React.createElement(TextInput, _extends({ id: id, readOnly: readonly, disabled: disabled, hideLabel: true, invalid: invalid, invalidText: schema.invalid ? schema.invalidText : '', value: value == null ? '' : value, defaultValue: defaultValue, onBlur: _onBlur, onChange: __onChange, labelText: inputProps.label ? inputProps.label : '' }, inputProps)); } CarbonBaseInput.defaultProps = { required: false, disabled: false, readonly: false, autofocus: false, options: {} }; /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CarbonTextWidget(props) { const { BaseInput } = props.registry.widgets; return React.createElement(BaseInput, props); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ const widgets = { BaseInput: CarbonBaseInput, CheckboxWidget: CarbonCheckboxWidget, SelectWidget: CarbonDropdownWidget, TextWidget: CarbonTextWidget }; function defaultUpdateValidity(errors, schema) { const updatedSchema = _objectSpread2({}, schema); if (errors) { // client side validation error updatedSchema.invalid = true; updatedSchema.invalidText = errors[0]; } else { updatedSchema.invalid = false; updatedSchema.invalidText = ''; } return updatedSchema; } const fieldTemplateObject = (id, label, required, description, formContext, help, _children, fieldSchema) => { let fieldsetClassName = 'bx--fieldset'; const isObjectWithNoPropNorAnyThing = !Object.keys(fieldSchema).includes('properties') && !Object.keys(fieldSchema).includes('allOf') && !Object.keys(fieldSchema).includes('oneOf') && !Object.keys(fieldSchema).includes('anyOf'); if (isObjectWithNoPropNorAnyThing) { fieldsetClassName = `${fieldsetClassName} baiw--fieldset-no-prop`; return React.createElement("div", { className: fieldsetClassName }, label && React.createElement(FormLabel, null, label, !required && formContext && formContext.optionalLabel ? ` (${formContext.optionalLabel})` : null), description && React.createElement("div", { className: "bx--form__helper-text", htmlFor: id }, description), formContext && formContext.getErrorTypeMessage && React.createElement("div", { className: "bx--form-requirement baiw--form-requirement-no-prop", htmlFor: id }, formContext.getErrorTypeMessage(fieldSchema.type, formContext.messageObjectNoProp)), help); } else { fieldsetClassName = fieldSchema.title ? fieldsetClassName : `${fieldsetClassName} baiw--fieldset-no-title`; // By default react json schema set displayLabel to false for object field. // TitleField and DescriptionField are already in _children. return React.createElement("div", { className: fieldsetClassName }, _children, help); } }; const fieldTemplateArray = (id, label, required, description, formContext, help, _children) => { // By default react json schema set displayLabel to false for array field. Here we force it to true by not taking it into account return React.createElement("div", { className: 'bx--fieldset baiw--form-array-field' }, React.createElement("div", { className: 'baiw--form-array-field-header' }, label && React.createElement(FormLabel, null, label, !required && formContext && formContext.optionalLabel ? ` (${formContext.optionalLabel})` : null), description && React.createElement("div", { className: "bx--form__helper-text", htmlFor: id }, description)), _children, help); }; function CustomFieldTemplate(props) { const { id, label, rawErrors, help, hidden, required, schema, description, children, displayLabel, formContext } = props; // updateValidity function may be overridden by props const updatedValidity = props.updateValidity || defaultUpdateValidity; const [fieldSchema, setFieldSchema] = useState(schema); useEffect(() => { setFieldSchema(schema); }, [schema]); useEffect(() => { if (!hidden) { setFieldSchema(schema => updatedValidity(rawErrors, schema)); } }, [hidden, rawErrors, schema, updatedValidity]); if (hidden) { return null; } const _children = React.Children.map(children, child => { if (child) { return React.cloneElement(child, { schema: fieldSchema }); } else { return child; } }); if (fieldSchema.type === 'object') { return fieldTemplateObject(id, label, required, description, formContext, help, _children, fieldSchema); } if (fieldSchema.type === 'array') { return fieldTemplateArray(id, label, required, description, formContext, help, _children); } return React.createElement("div", { className: 'bx--fieldset' }, displayLabel && label && React.createElement(FormLabel, null, label, !required && formContext && formContext.optionalLabel ? ` (${formContext.optionalLabel})` : null), description && React.createElement("div", { className: "bx--form__helper-text", htmlFor: id }, description), _children, help); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CustomObjectFieldTemplate(props) { const { TitleField, DescriptionField } = props; const mountTitleField = () => { if (props.uiSchema['ui:title'] || props.schema.title) { return React.createElement(TitleField, { id: `${props.idSchema.$id}__title`, title: props.schema.title || props.uiSchema['ui:title'], required: props.required, formContext: props.formContext }); } }; const mountDescriptionField = () => { if (props.uiSchema['ui:description'] || props.schema.description) { return React.createElement(DescriptionField, { id: `${props.idSchema.$id}__description`, description: props.uiSchema['ui:description'] || props.schema.description, formContext: props.formContext }); } }; const titleField = mountTitleField(); const descriptionField = mountDescriptionField(); const className = props.idSchema.$id === 'root' ? 'baiw--form-common-root' : ''; return React.createElement("div", { className: className }, (titleField || descriptionField) && React.createElement("div", { id: `${props.idSchema.$id}__info`, className: 'bx--fieldset baiw--title-container' }, titleField, descriptionField), props.properties.map(prop => prop.content)); } // /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CustomArrayItem({ key, children, disabled, readonly, index, onReorderClick, onDropIndexClick, hasToolbar, hasMoveDown, hasMoveUp, hasRemove, formContext }) { return React.createElement("div", { key: key, className: "baiw--form-array-item" }, children, hasToolbar && React.createElement("div", { className: "baiw--form-array-item__toolbox" }, (hasMoveUp || hasMoveDown) && React.createElement(Button, { renderIcon: ArrowUp, "aria-label": formContext ? formContext.moveItemUpLabel : '', iconDescription: formContext ? formContext.moveItemUpLabel : '', className: "baiw--form-array-item__move-up-button", kind: "secondary", disabled: disabled || readonly || !hasMoveUp, onClick: onReorderClick(index, index - 1) }), (hasMoveUp || hasMoveDown) && React.createElement(Button, { renderIcon: ArrowDown, "aria-label": formContext ? formContext.moveItemDownLabel : '', iconDescription: formContext ? formContext.moveItemDownLabel : '', className: "baiw--form-array-item__move-down-button", kind: "secondary", disabled: disabled || readonly || !hasMoveDown, onClick: onReorderClick(index, index + 1) }), hasRemove && React.createElement(Button, { renderIcon: Close, "aria-label": formContext ? formContext.removeItemLabel : '', iconDescription: formContext ? formContext.removeItemLabel : '', className: "baiw--form-array-item__remove-button", kind: "ghost", disabled: disabled || readonly, onClick: onDropIndexClick(index) }))); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ function CustomArrayFieldTemplate({ disabled, readonly, idSchema, formContext, items, canAdd, onAddClick, schema }) { // title and description are rendered by the CustomFieldTemplate return React.createElement(React.Fragment, null, React.createElement("div", { className: "row baiw--form-array-field__item_list", key: `array-item-list-${idSchema.$id}` }, items && items.map((item, index) => { item.key = `array-item-${idSchema.$id}-${index}`; item.formContext = formContext; return CustomArrayItem(item); })), canAdd && React.createElement("div", null, React.createElement(Button, { className: "array-item-add", kind: "ghost", renderIcon: Add, iconDescription: formContext ? formContext.addItemLabel : '', onClick: onAddClick, disabled: disabled || readonly }, formContext ? formContext.addItemLabel : '')), schema.invalid && React.createElement("div", { className: "bx--form-requirement" }, schema.invalidText)); } /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ const templates = { FieldTemplate: CustomFieldTemplate, ObjectFieldTemplate: CustomObjectFieldTemplate, ArrayFieldTemplate: CustomArrayFieldTemplate }; /* Licensed Materials - Property of IBM 5737-I23 Copyright IBM Corp. 2019. All Rights Reserved. U.S. Government Users Restricted Rights: Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ const addDefaultType = someObject => { // not adding a type when the object has a $ref if (!someObject.$ref && !someObject.type) { someObject.type = 'string'; } }; const fixIncompleteSchema = schema => { if (schema.type === 'array') { if (!schema.items) { schema.items = {}; addDefaultType(schema.items); } else { if (Array.prototype.isPrototypeOf(schema.items)) { if (schema.additionalItems) { addDefaultType(schema.additionalItems); } schema.items.forEach(fixIncompleteSchema); } else if (typeof schema.items === 'object') { addDefaultType(schema.items); fixIncompleteSchema(schema.items); } } } else if (schema.type === 'object' && schema.properties) { Object.values(schema.properties).map(fixIncompleteSchema); } if (schema.definitions) { // fixing incomplete definitions Object.values(schema.definitions).map(fixIncompleteSchema); } }; const FormCommon = React.forwardRef((props, ref) => { const { schema, uiSchema, formData, onSubmit, onChange, children, liveValidate, showErrorList, formContext, customWidgets, customFields, customTemplates, transformErrors } = props; // widgets, fields and templates can be passed as props. Merging them with the default FormCommon ones const mergedWidgets = _objectSpread2({}, widgets, {}, customWidgets); const mergedFields = _objectSpread2({}, fields, {}, customFields); const mergedTemplates = _objectSpread2({}, templates, {}, customTemplates); const [localSchema, setLocalSchema] = useState({}); const [localFormData, setLocalFormData] = useState(formData); useEffect(() => { // Ignoring $id/id as this causes issues in schema validation const filteredSchema = _objectWithoutProperties(schema, ["$id", "id"]); // some schemas may be incomplete (missing items definitions for array types, etc.) fixIncompleteSchema(filteredSchema); setLocalSchema(filteredSchema); }, [schema]); useEffect(() => { setLocalFormData(formData); }, [formData]); const _onChange = useCallback(state => { setLocalFormData(state.formData); onChange && onChange(state); }, [onChange]); return React.createElement(Form, { schema: localSchema, uiSchema: uiSchema, formData: localFormData, widgets: mergedWidgets, fields: mergedFields, FieldTemplate: mergedTemplates.FieldTemplate, ObjectFieldTemplate: mergedTemplates.ObjectFieldTemplate, ArrayFieldTemplate: mergedTemplates.ArrayFieldTemplate, onChange: _onChange, liveValidate: liveValidate, showErrorList: showErrorList, onSubmit: onSubmit, ref: ref, formContext: formContext, transformErrors: transformErrors }, children); }); export { FormCommon, fields, templates, useDirtyInput, widgets }; //# sourceMappingURL=index.es.js.map