UNPKG

@ibm-adw/skill-toolkit

Version:

Developing your own skills with IBM Automation Digital Worker Skill Toolkit

1,177 lines (1,051 loc) 34.6 kB
import React, { useCallback, useState, useEffect, useImperativeHandle, useRef } from 'react'; import { widgets as widgets$1, useDirtyInput, templates as templates$1, FormCommon, fields as fields$1 } from '@adw/form-common'; import { Link, Icon, RadioButtonGroup, RadioButton, TextInput, TextArea, NumberInput, Button, SkeletonPlaceholder, Loading } from 'carbon-components-react'; import { iconLaunch } from 'carbon-icons'; import StateDecorator, { ConflictPolicy } from 'state-decorator'; import axios from 'axios'; /* 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 CarbonLinkField(props) { const { schema, disabled } = props; return React.createElement("div", null, React.createElement(Link, { className: "baiw--field-link", href: schema.url, disabled: disabled, target: "_blank" }, schema.title, React.createElement(Icon, { icon: iconLaunch, fill: "#0062ff", className: "baiw--field-link-icon", description: "", height: "16px", width: "16px" }))); } CarbonLinkField.defaultProps = { autofocus: false }; /* 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 fields = { LinkField: CarbonLinkField }; 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; } function CarbonCheckboxWidgetWrapper(props) { const { formContext, options, value } = props; // Checkbox's value are by default boolean // We add the possibility to have checkbox with string values // This provides the compatibility of checkbox with schema dependencies (oneOf) let checked = typeof value === 'undefined' || typeof value === 'boolean' ? value : true; let label = props.label; if (props.schema && props.schema.type === 'string' && props.schema.enum && props.schema.enum.length === 2) { if (value && typeof value === 'string') { checked = value === props.schema.enum[0]; if (props.schema.enumNames && props.schema.enumNames.length === 2) { label = props.schema.enumNames[props.schema.enum.indexOf(value)]; } else { label = value; } } else { checked = true; label = props.schema.enum[0]; } } // the onChange function called by Carbon's Checkbox has the value as first parameter const _onChange = checked => { if (options && options.triggerEvent) { formContext.setTriggerEvent(options.triggerEvent); } let value = checked; if (props.schema && props.schema.type === 'string' && props.schema.enum && props.schema.enum.length === 2) { value = checked ? props.schema.enum[0] : props.schema.enum[1]; } props.onChange(value); }; return React.createElement(widgets$1.CheckboxWidget, _extends({}, props, { onChange: _onChange, value: checked, label: label })); } function CarbonSelectWidgetWrapper(props) { const { options, formContext } = props; const _onChange = event => { props.onChange(event); if (options && options.triggerEvent) { formContext.setTriggerEvent(options.triggerEvent); } }; return React.createElement(widgets$1.SelectWidget, _extends({}, props, { onChange: _onChange })); } /* 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 CarbonRadioWidget(props) { //TODO Carbon had problem with : value, valueSelected, checked pros // check this is still valid const { options, value, required, disabled, readonly, autofocus, formContext, schema, onChange, id } = props; // Generating a unique field name to identify this set of radio buttons const name = Math.random().toString(); const { enumOptions, enumDisabled /*, inline*/ } = options; // checked={checked} has been moved above name={name}, As mentioned in #349; // this is a temporary fix for radio button rendering bug in React, facebook/react#7630. const _onGlobalChange = function _onGlobalChange(value) { if (options.triggerEvent) { formContext.setTriggerEvent(options.triggerEvent); } onChange(value === '' ? options.emptyValue : value); }; return React.createElement("div", { className: "bx--form-item" }, React.createElement(RadioButtonGroup // TODO: temporary fix for carbon issue with orientation vertical , { className: "baiw--vertical-radio-button", orientation: "vertical", legend: "Group Legend", name: id, valueSelected: value, onChange: _onGlobalChange }, enumOptions.map((option, i) => { const itemDisabled = enumDisabled && enumDisabled.indexOf(option.value) !== -1; return React.createElement(RadioButton, { name: name, required: required, value: option.value, disabled: disabled || itemDisabled || readonly, autoFocus: autofocus && i === 0 //onChange={() => onChange(option.value)} , labelText: option.label, labelPosition: "right", key: `id${i}` // TODO: temporary fix for carbon issue with orientation vertical , className: "baiw--vertical-radio-button-wrapper" }); })), schema.invalid ? React.createElement("div", { className: "baiw--vertical-radio-button-invalid-wrapper" }, React.createElement("div", { className: "baiw--vertical-radio-button-invalid bx--form-requirement" }, schema.invalidText)) : null); } CarbonRadioWidget.defaultProps = { autofocus: false }; function CarbonBaseInput(props) { const { options, formContext } = props; const _onChange = value => { if (options && options.triggerEvent) { formContext.setTriggerEvent(options.triggerEvent); } props.onChange(value); }; return React.createElement(widgets$1.BaseInput, _extends({}, props, { onChange: _onChange })); } function CarbonPasswordWidget(props) { const { id, value, defaultValue, readonly, disabled, autofocus, onBlur, onFocus, onChange, options, schema, formContext, registry, rawErrors } = props, inputProps = _objectWithoutProperties(props, ["id", "value", "defaultValue", "readonly", "disabled", "autofocus", "onBlur", "onFocus", "onChange", "options", "schema", "formContext", "registry", "rawErrors"]); const _onChange = useCallback(({ target: { value } }) => { if (options.triggerEvent) { formContext.setTriggerEvent(options.triggerEvent); } onChange(value === '' ? options.emptyValue : value); }, [options, formContext, onChange]); 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]); const [showPasswordLabel, setShowPasswordLabel] = useState(); const [hidePasswordLabel, setHidePasswordLabel] = useState(); useEffect(() => { if (formContext && formContext.showPasswordLabel) { setShowPasswordLabel(formContext.showPasswordLabel); } if (formContext && formContext.hidePasswordLabel) { setHidePasswordLabel(formContext.hidePasswordLabel); } }, [formContext]); return React.createElement(TextInput.PasswordInput, _extends({ id: id, disabled: disabled, invalid: invalid, invalidText: schema.invalid ? schema.invalidText : '', labelText: "", showPasswordLabel: showPasswordLabel, hidePasswordLabel: hidePasswordLabel, value: value == null ? '' : value, defaultValue: defaultValue, hideLabel: true, onChange: __onChange, onBlur: _onBlur }, inputProps)); } CarbonPasswordWidget.defaultProps = { required: true, disabled: false, readonly: false, autofocus: false }; /* 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. */ function CarbonTextAreaWidget(props) { const { id, options, formContext, schema, value, defaultValue, disabled, placeholder, onChange, onBlur } = props; const _onChange = useCallback(({ target: { value } }) => { if (options.triggerEvent) { formContext.setTriggerEvent(options.triggerEvent); } onChange(value === '' ? options.emptyValue : value); }, [onChange, options, formContext]); 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(TextArea, { hideLabel: true, id: id, onChange: __onChange, onBlur: _onBlur, labelText: "", invalid: invalid, invalidText: schema.invalid ? schema.invalidText : '', disabled: disabled, value: value == null ? '' : value, defaultValue: defaultValue, placeholder: placeholder }); } CarbonTextAreaWidget.defaultProps = { autofocus: false, options: {} }; function CarbonEmailWidget(props) { const { BaseInput } = props.registry.widgets; return React.createElement(BaseInput, _extends({ type: "email" }, props)); } // function CarbonUpDownWidget(props) { const { value, readonly, disabled, autofocus, onBlur, onFocus, options, schema, formContext, registry, rawErrors } = props, inputProps = _objectWithoutProperties(props, ["value", "readonly", "disabled", "autofocus", "onBlur", "onFocus", "options", "schema", "formContext", "registry", "rawErrors"]); const _onChange = function _onChange(_ref) { const value = _ref.imaginaryTarget.value; return props.onChange(value === '' ? options.emptyValue : value); }; return React.createElement(NumberInput, _extends({ className: "form-control bx--fieldset", readOnly: readonly, disabled: disabled, autoFocus: autofocus }, inputProps, { onChange: _onChange, label: "Number Input label", hideLabel: true //min={min} //max={max} , value: parseInt(value), step: 1, isMobile: false, invalidText: "Number is not valid", helperText: "Optional helper text." })); } CarbonUpDownWidget.defaultProps = { required: false, disabled: false, readonly: false, autofocus: false, value: 0, 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. */ const widgets = { BaseInput: CarbonBaseInput, CheckboxWidget: CarbonCheckboxWidgetWrapper, EmailWidget: CarbonEmailWidget, PasswordWidget: CarbonPasswordWidget, RadioWidget: CarbonRadioWidget, SelectWidget: CarbonSelectWidgetWrapper, TextareaWidget: CarbonTextAreaWidget, UpDownWidget: CarbonUpDownWidget }; function updateValidity(errors, schema) { const updatedSchema = _objectSpread2({}, schema); if (updatedSchema.error) { // server side validation error updatedSchema.invalid = true; updatedSchema.invalidText = updatedSchema.error; updatedSchema.error = ''; } else if (errors) { // client side validation error updatedSchema.invalid = true; updatedSchema.invalidText = errors[0]; } else { updatedSchema.invalid = false; updatedSchema.invalidText = ''; } return updatedSchema; } function CustomFieldTemplateWrapper(props) { const { children } = props; const displayLabel = props.displayLabel && typeof props.schema.title !== 'undefined'; return React.createElement(templates$1.FieldTemplate, _extends({ updateValidity: updateValidity }, props, { displayLabel: displayLabel }), children); // } /* 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: CustomFieldTemplateWrapper }; /* 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 LINK_TYPES = { DOCUMENTATION: 'documentation', SNIPPET: 'snippet', INPUT_SCHEMA: 'inputSchema', OUTPUT_SCHEMA: 'outputSchema' }; const SkillFormLinks = React.forwardRef((props, ref) => { const { serverUrl, labels, supportedLocales, saveConfig } = props; const [disabled, setDisabled] = useState(false); useImperativeHandle(ref, () => ({ setDisabled: value => { setDisabled(value); } }), []); // linkType: One of 'documentation', 'inputSchema', 'outputSchema' or 'snippet' const buildLink = useCallback(linkType => { if (linkType === LINK_TYPES.SNIPPET) { return `${serverUrl}/snippet`; } else if (linkType === LINK_TYPES.DOCUMENTATION) { return `${serverUrl}/documentation?language=${supportedLocales}`; } return `${serverUrl}/schema?type=${linkType === LINK_TYPES.INPUT_SCHEMA ? 'input' : 'output'}&pretty=true`; }, [serverUrl, supportedLocales]); const handleClick = useCallback(linkType => { return async () => { if (saveConfig) { // When saveConfig function is provided, try to save config before opening link if (await saveConfig()) { // Open link only when successfully saved config window.open(buildLink(linkType), '_blank', 'noopener,noreferrer'); } } else { window.open(buildLink(linkType), '_blank', 'noopener,noreferrer'); } }; }, [buildLink, saveConfig]); const openDocumentation = useCallback(() => { window.open(buildLink(LINK_TYPES.DOCUMENTATION), '_blank', 'noopener,noreferrer'); }, [buildLink]); return React.createElement("div", { className: "baiw--skills-form-links" }, React.createElement(React.Fragment, null, React.createElement("div", { className: 'bx--fieldset baiw--skills-form-links--fieldset' }, React.createElement(Button, { id: "skillDocumentationBtn", className: "baiw--skills-form-links--button", kind: "ghost", onClick: openDocumentation }, labels.buttons.skillDocumentation, React.createElement(Icon, { icon: iconLaunch, description: "" }))), React.createElement("div", { className: `bx--fieldset baiw--skills-form-links--fieldset ${disabled ? 'baiw--skills-form-links--fieldset-disabled' : ''}` }, React.createElement(Button, { id: "sampleInstructionsBtn", className: "baiw--skills-form-links--button", kind: "ghost", disabled: disabled, onClick: handleClick(LINK_TYPES.SNIPPET) }, labels.buttons.sampleInstructions, React.createElement(Icon, { icon: iconLaunch, description: "" }))), React.createElement("div", { className: `bx--fieldset baiw--skills-form-links--fieldset ${disabled ? 'baiw--skills-form-links--fieldset-disabled' : ''}` }, React.createElement(Button, { id: "schemaInputBtn", className: "baiw--skills-form-links--button", kind: "ghost", disabled: disabled, onClick: handleClick(LINK_TYPES.INPUT_SCHEMA) }, labels.buttons.schemaInput, React.createElement(Icon, { icon: iconLaunch, description: "" }))), React.createElement("div", { className: `bx--fieldset baiw--skills-form-links--fieldset ${disabled ? 'baiw--skills-form-links--fieldset-disabled' : ''}` }, React.createElement(Button, { id: "schemaOutputBtn", className: "baiw--skills-form-links--button", kind: "ghost", disabled: disabled, onClick: handleClick(LINK_TYPES.OUTPUT_SCHEMA) }, labels.buttons.schemaOutput, React.createElement(Icon, { icon: iconLaunch, description: "" }))))); }); /* 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 SkillFormButtons = React.forwardRef((props, ref) => { const { onClickCancel, onClickSubmit, buttonLabelCancel, buttonLabelSubmit, isFormSubmittable } = props; const [buttonDisabled, setButtonDisabled] = useState(!isFormSubmittable); useImperativeHandle(ref, () => ({ setDisabled: value => { setButtonDisabled(value); } }), []); return React.createElement("div", { className: "baiw--form_buttons" }, React.createElement(Button, { className: "baiw--form_button_left_column", type: "button", kind: "secondary", onClick: onClickCancel }, buttonLabelCancel), React.createElement(Button, { className: "baiw--form_button_right_column", type: "submit", kind: "primary", disabled: buttonDisabled, onClick: onClickSubmit }, buttonLabelSubmit)); }); /* 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. */ function SkillForm(props) { const { schema, uiSchema, formData, isLastStaticStep, isFormSubmittable, onSubmit, cancel, labels, serverUrl, saveConfig, loading, runStep, debounceRunStep, clearDebounceTimeout, isInitial, supportedLocales, transformErrors } = props; // 'triggerEvent' is created with useRef so all children components get always the same ref object const triggerEvent = useRef(null); // 'setTriggerEvent' is exposed in formContext so any child component can update its value const setTriggerEvent = useCallback(event => { triggerEvent.current = event; }, []); // context exposed in formContext const [context, setContext] = useState({}); useEffect(() => { setContext({ setTriggerEvent, optionalLabel: labels.skillForm.optional, addItemLabel: labels.skillForm.buttons.addItem, moveItemUpLabel: labels.skillForm.buttons.moveItemUp, moveItemDownLabel: labels.skillForm.buttons.moveItemDown, removeItemLabel: labels.skillForm.buttons.removeItem, showPasswordLabel: labels.skillForm.password.showPasswordLabel, hidePasswordLabel: labels.skillForm.password.hidePasswordLabel, unsupportedFieldLabel: labels.skillForm.unsupportedField, ignoreDirty: !isInitial }); }, [setTriggerEvent, labels.skillForm.optional, isInitial, labels.skillForm.buttons, labels.skillForm.password, labels.skillForm.unsupportedField]); const isInTimeOut = useRef(false); const setIsInTimeOut = useCallback(boolean => { isInTimeOut.current = boolean; }, []); let buttonLabel; let action; if (isLastStaticStep) { buttonLabel = labels.skillForm.buttons.submit; action = onSubmit; } else { buttonLabel = labels.skillForm.buttons.next; action = runStep; } // 'submitButtonRef' is exposed in skillFormButtons to update the submit button state const submitButtonRef = useRef(null); const setSubmitButtonRef = useCallback(button => { submitButtonRef.current = button; }, [submitButtonRef]); // 'skillLinksRef' is exposed in SkillFormLinks to update the links disable state const skillLinksRef = useRef(null); const setSkillLinksRef = useCallback(skillLinks => { skillLinksRef.current = skillLinks; }, [skillLinksRef]); // set the submit button and links 'disabled' state const setButtonLinkDisabledState = useCallback(formErrorList => { if (isInTimeOut.current || loading || !isFormSubmittable || Object.keys(formErrorList).length > 0) { submitButtonRef.current.setDisabled(true); skillLinksRef.current.setDisabled(true); } else { submitButtonRef.current.setDisabled(false); skillLinksRef.current.setDisabled(false); } }, [loading, isFormSubmittable]); const _onChange = useCallback(state => { if (triggerEvent.current !== null) { // At this point the form validation is already done // Don't call nextStaticStep if there are any errors from validation if (Object.keys(state.errors).length > 0) { setTriggerEvent(null); if (submitButtonRef.current && skillLinksRef.current) { setButtonLinkDisabledState(state.errors); } if (isInTimeOut.current) { clearDebounceTimeout(); } return; } if (debounceRunStep) { if (submitButtonRef.current && skillLinksRef.current) { setIsInTimeOut(true); setButtonLinkDisabledState(state.errors); } // Defer execution of step in case there are lots of request in a short amount of time debounceRunStep(state, triggerEvent.current, () => { setIsInTimeOut(false); }); setTriggerEvent(null); } } else if (submitButtonRef.current && skillLinksRef.current) { setButtonLinkDisabledState(state.errors); } }, [debounceRunStep, setTriggerEvent, setButtonLinkDisabledState, clearDebounceTimeout, setIsInTimeOut]); const _onSubmit = useCallback(action => { return state => { clearDebounceTimeout(); // Execute action (submit or nextStaticStep) action(state); }; }, [clearDebounceTimeout]); const handlerKeyDownEnter = useCallback(event => { if (event.key === 'Enter') { event.preventDefault(); } }, []); //Reference the form in order to externalize buttons //See https://react-jsonschema-form.readthedocs.io/en/latest/#submit-form-programmatically const formRef = useRef(null); const refCallback = useCallback(form => { if (form) { formRef.current = form; // Disable the "Enter" keydown by adding an event listener formRef.current.formElement.addEventListener('keydown', handlerKeyDownEnter); } }, [handlerKeyDownEnter]); // On unmount: remove the event listener useEffect(() => () => { if (formRef.current) { formRef.current.formElement.removeEventListener('keydown', handlerKeyDownEnter); } }, [handlerKeyDownEnter]); // when isFormSubmittable changes we update the submit button state useEffect(() => { if (formRef.current && submitButtonRef.current && skillLinksRef.current) { setButtonLinkDisabledState(formRef.current.state.errors); } }, [isFormSubmittable, loading, setButtonLinkDisabledState]); const _saveConfig = useCallback(async () => { try { await saveConfig(formRef.current.state); return true; } catch (error) { // This means that there was an error when trying to save the configuration. The errors are handled by SkillFormManager. return false; } }, [saveConfig]); const _transformErrors = useCallback(errors => { //translate errors if (transformErrors instanceof Function) { errors = transformErrors(errors); } // Update "disable" status of buttons based on errors if (submitButtonRef.current && skillLinksRef.current) { setButtonLinkDisabledState(errors); } return errors; }, [setButtonLinkDisabledState, transformErrors]); // If schema has a type : Form is not empty // Display the form // Avoid displaying buttons without form if (schema.type) { return React.createElement("div", { className: "baiw--skill-form" }, React.createElement("div", { className: "baiw--loading-skeleton" }, loading ? React.createElement(SkeletonPlaceholder, { className: "baiw--loading-skeleton-color" }) : ''), React.createElement("div", { className: "baiw--content" }, React.createElement("div", { className: "baiw--content-left_column" }, React.createElement(FormCommon, { schema: schema, uiSchema: uiSchema, formData: formData, formContext: context, onChange: _onChange, liveValidate: true, showErrorList: false, onSubmit: _onSubmit(action), ref: refCallback, customWidgets: widgets, customFields: fields, customTemplates: templates, transformErrors: _transformErrors }, React.createElement("span", null))), React.createElement("div", { className: "baiw--content-right_column" }, React.createElement("div", { id: 'right_column__info', className: 'bx--fieldset' }, React.createElement(fields$1.TitleField, { id: 'right_column__title', title: labels.skillForm.details.header }), React.createElement(fields$1.DescriptionField, { id: 'right_column__description', description: labels.skillForm.details.description })), React.createElement(SkillFormLinks, { ref: setSkillLinksRef, labels: labels.skillFormLinks, serverUrl: serverUrl, saveConfig: _saveConfig, supportedLocales: supportedLocales }))), React.createElement(SkillFormButtons, { ref: setSubmitButtonRef, onClickCancel: cancel, onClickSubmit: () => formRef.current.submit(), buttonLabelCancel: labels.skillForm.buttons.cancel, buttonLabelSubmit: buttonLabel, isFormSubmittable: isFormSubmittable })); } else { return React.createElement("div", { className: "baiw--skill-form" }, React.createElement("div", { className: "baiw--content" }, React.createElement("div", { className: "baiw--loading" }, React.createElement(Loading, { withOverlay: false })))); } } const FINAL_STEP = '__baiw_final'; let runStepSource; let runStepPromise; let debounceTimeout; const DEBOUNCE_DELAY = 500; const actions = (serverUrl, supportedLocales, { currentLocale, fallbackLocale }, formTitle) => { return { clearDebounceTimeout: state => { clearTimeout(debounceTimeout); if (runStepSource) { runStepSource.cancel(); } return state; }, debounceRunStep: { action: (state, [formState]) => { if (runStepPromise && runStepSource) { runStepSource.cancel(); } clearTimeout(debounceTimeout); return _objectSpread2({}, state, { formData: formState.formData }); }, onActionDone: (state, [{ formData }, step, callback], props, actions) => { debounceTimeout = setTimeout(() => { actions.runStep(formData, step); callback(); }, DEBOUNCE_DELAY); } }, runStep: { promise: ([formData, stepOverride], state) => { runStepSource = axios.CancelToken.source(); const step = stepOverride === undefined ? state.nextStaticStep : stepOverride; const options = { method: 'POST', headers: { 'Content-Language': supportedLocales }, url: serverUrl, data: formData, params: { event: step, currentLocale: currentLocale, fallbackLocale: fallbackLocale }, cancelToken: runStepSource ? runStepSource.token : null }; runStepPromise = axios(options); return runStepPromise; }, reducer: (state, { data }, [, stepOverride]) => { const step = stepOverride === undefined ? state.nextStaticStep : stepOverride; data.schema.title = formTitle; return { event: step, nextStaticStep: data.nextStaticStep, isLastStaticStep: !data.nextStaticStep, isFormSubmittable: data.isFormSubmittable === undefined ? true : data.isFormSubmittable, schema: data.schema, uiSchema: data.uiSchema || {}, formData: data.formData, isInitial: data.isInitial !== undefined ? data.isInitial : false }; }, errorReducer: (state, error, args, props) => { if (!axios.isCancel(error)) { // Call onError function if it was passed (only when the error is not due to the request being cancelled) props.onError(error); } return null; }, conflictPolicy: ConflictPolicy.KEEP_LAST }, saveConfig: { preReducer: (state, [{ formData }]) => { // Update state with latest formData return _objectSpread2({}, state, { formData }); }, promise: ([{ formData }]) => { const options = { method: 'POST', headers: { 'Content-Language': supportedLocales }, url: serverUrl, data: formData, params: { event: FINAL_STEP } }; return axios(options); }, reducer: () => { // Schema validation ok and data saved return null; }, errorReducer: (state, error, args, props) => { // Call onError function if it was passed props.onError(error); return null; }, rejectPromiseOnError: true }, cancel: () => { return null; } }; }; const initialState = { event: '__baiw_init', nextStaticStep: undefined, isLastStaticStep: true, isFormSubmittable: false, schema: {}, uiSchema: {}, formData: {}, isInitial: false }; const onMount = actions => { // On initialization, the step 0 is requested actions.runStep({}, initialState.event); }; const SkillFormManager = ({ serverUrl, supportedLocales, locales, onSubmit, onCancel, onError, labels, transformErrors }) => { const formActions = useRef(actions(`${serverUrl}/form-configuration`, supportedLocales, locales || {}, labels.skillForm.title)); const _onSubmit = useCallback(saveConfig => { return async formState => { // Run 'saveConfig' action try { await saveConfig(formState); // If passed in props, run onSubmit function (only when no errors) onSubmit && onSubmit(formState); } catch (err) {// Don't call user 'onSubmit' function when there are errors (errors are handled on the errorReducer of the 'saveConfig' action) } }; }, [onSubmit]); const _onCancel = useCallback(cancel => { return () => { // Run 'cancel' action cancel(); // If passed in props, run onCancel function onCancel && onCancel(); }; }, [onCancel]); const _onError = useCallback(error => { // Call onError function if it was passed onError && onError(error); }, [onError]); return React.createElement(StateDecorator, { actions: formActions.current, initialState: initialState, onMount: onMount, logEnabled: true, props: { onError: _onError }, name: "SkillFormManager" }, (state, actions, loading) => // TODO 'loading' could be used to show spinner when Saving or requesting next step React.createElement(SkillForm, _extends({}, state, { cancel: _onCancel(actions.cancel), onSubmit: _onSubmit(actions.saveConfig), debounceRunStep: actions.debounceRunStep, runStep: actions.runStep, clearDebounceTimeout: actions.clearDebounceTimeout, labels: labels, serverUrl: serverUrl, saveConfig: actions.saveConfig, loading: loading, supportedLocales: supportedLocales, transformErrors: transformErrors }))); }; export { SkillForm, SkillFormLinks, SkillFormManager, fields, templates, widgets }; //# sourceMappingURL=index.es.js.map