UNPKG

synapse-react-client

Version:

[![npm version](https://badge.fury.io/js/synapse-react-client.svg)](https://badge.fury.io/js/synapse-react-client) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettie

621 lines (620 loc) 23.5 kB
import { jsxs as p, jsx as i, Fragment as v } from "react/jsx-runtime"; import R from "../utils/Blocker.js"; import A from "@rjsf/core"; import I from "@rjsf/validator-ajv8"; import { Engine as N } from "json-rules-engine"; import y from "lodash-es/cloneDeep"; import W from "lodash-es/find"; import k from "lodash-es/findIndex"; import L from "lodash-es/first"; import g from "lodash-es/get"; import U from "lodash-es/isEqual"; import M from "lodash-es/isUndefined"; import O from "lodash-es/keys"; import V from "lodash-es/pick"; import P from "lodash-es/remove"; import C from "lodash-es/set"; import w from "lodash-es/trimStart"; import { Component as z, createRef as D } from "react"; import B from "react-switch"; import { ConfirmationDialog as _ } from "../ConfirmationDialog/ConfirmationDialog.js"; import $ from "./DataDebug.js"; import H from "./Header.js"; import { NextStepLink as q, NavButtons as j } from "./NavButtons.js"; import G from "./StepsSideNav.js"; import J from "./SummaryTable.js"; import Y from "./SynapseFormCheckboxesWidget.js"; import X from "./SynapseFormCheckboxWidget.js"; import K from "./SynapseFormRadioWidget.js"; import { NavActionEnum as l, StatusEnum as b, StepStateEnum as f } from "./types.js"; import { WarningDialog as Q } from "./WarningDialog.js"; class At extends z { excludeWarningText = /* @__PURE__ */ p("div", { children: [ /* @__PURE__ */ i("p", { children: "This action will clear any entered data on this page and remove this form from your submission. You can include it again at anytime. Only this page will be affected." }), /* @__PURE__ */ i("p", { children: "Are you sure you want to skip this step and clear any entered data?" }) ] }); excludeWarningHeader = "Skip This Step?"; unsavedDataWarning = "You might have some unsaved data. Are you sure you want to leave?"; formRef; //ref to form for submission submitButtonRef; formDivRef; // ref to the div containing form (for scrolling on validation failure) navAction = l.NONE; uiSchema; nextStep; extraErrors = []; isNewForm = (t) => Object.keys(t).length == 1 && Object.keys(t)[0] === "metadata" || Object.keys(t).length == 0; getFirstStep = (t, e) => this.isNewForm(e) ? t[0] : t.find((s) => s.final === !0) || t[0]; constructor(t) { super(t), this.uiSchema = T( y(t.uiSchema), "ui:help" ); const e = t.navSchema.steps.map((a, r) => ({ ...a, inProgress: r === 0 })).sort((a, r) => a.order - r.order); this.formRef = D(), this.formDivRef = D(), this.submitButtonRef = D(); const s = this.getFirstStep(e, t.formData); this.state = { currentStep: s, steps: e, previousStepIds: [], formData: t.formData, doShowErrors: !1, doShowHelp: !0, hasUnsavedChanges: !1, isSubmitted: t.isSubmitted, isLoadingSaved: !this.isNewForm(this.props.formData) }; } onUnload = (t) => this.state.hasUnsavedChanges ? (t.preventDefault(), t.returnValue = this.unsavedDataWarning) : void 0; // Setup the `beforeunload` event listener setupBeforeUnloadListener = () => { window.addEventListener("beforeunload", this.onUnload); }; componentWillUnmount() { window.removeEventListener("beforeunload", this.onUnload); } componentDidUpdate(t) { const e = this.props.callbackStatus !== t.callbackStatus, s = this.props.callbackStatus === b.SAVE_SUCCESS || this.props.callbackStatus === b.SUBMIT_SUCCESS; e && s && (this.setState({ hasUnsavedChanges: !1 }), this.props.callbackStatus === b.SUBMIT_SUCCESS && (this.setState({ isSubmitted: !0 }), window.history.back())); } _setIncludedPropInFormDataNonWizard = (t, e) => { const s = {}, a = t.formData; return Object.keys(e).forEach((r) => { g(e[r], "properties.included") && C(s, `${r}.included`, !0); }), { ...a, ...s }; }; _setIncludedPropInFormDataWizard = (t) => { const e = t.currentStep.id, s = y(t.formData); return C(s, `${e}.included`, !0), s; }; componentDidMount() { this.setupBeforeUnloadListener(), this.isNewForm(this.state.formData) ? this.setState((e) => ({ formData: this.props.isWizardMode ? this._setIncludedPropInFormDataWizard(e) : this._setIncludedPropInFormDataNonWizard( e, this.props.schema ) })) : this.triggerAction(l.VALIDATE); } // get the schema slice for the current screen/step getSchema = ({ id: t, final: e }) => e ? this.props.schema : V(this.props.schema, [ "title", "type", `properties.${t}` ]); // find the next step getNextStepId = async (t, e, s) => { if (s) return s; if (!t.rules || t.rules.length === 0) return t.default; const a = new N(t.rules); try { const r = await a.run(e); return r.events.length > 0 ? r.events[0].params.next : t.default; } catch { return t.default; } }; // called when going next, previous or a given step moveStep = async (t, e, s, a = [...this.state.previousStepIds]) => { const r = this.state.currentStep; let h; this.formRef?.current && this.formRef.current.setState({ errorSchema: {} }); const d = this.props.isWizardMode && !e; d && a.push(r.id), s ? h = f.ERROR : (h = f.COMPLETED, !d && this.props.isWizardMode && (h = f.TODO)), e = await this.getNextStepId(r, t, e); const m = this.state.steps.map((o) => o.id === r.id ? { ...o, state: h, inProgress: !1 } : o.id === e ? { ...o, inProgress: !0 } : o); d && C(t, `${e}.included`, !0); const n = this.state.steps.find((o) => o.id === e); this.props.isWizardMode && n.final && Object.keys(t).forEach((o) => { t[o].included === void 0 && (t[o] = {}); }), this.saveStepState(a, m, n, t); }; //save the state of the current screen saveStepState = (t, e, s, a) => { this.setState({ previousStepIds: t, steps: e, currentStep: s, formData: a, hasValidated: !1, doShowErrors: !1 }); }; //--------- fns to support navigation --------------------// goPrevious = async (t, e) => { let s; const a = [...this.state.previousStepIds]; if (this.props.isWizardMode) s = a.pop(), this.isSubmitScreen() || C(t, `${this.state.currentStep.id}.included`, void 0); else { const r = k(this.state.steps, { id: this.state.currentStep.id }); r > 0 && (s = this.state.steps[r - 1].id); } if (!M(s)) return this.moveStep(t, s, e, a); }; triggerAction = async (t) => { if (t === l.SAVE) return this.props.onSave(this.state.formData); this.navAction = t, this.extraErrors = await this.runCustomValidation( this.state.formData, this.state.currentStep, this.state.steps ), this.submitButtonRef.current && this.submitButtonRef.current.click(); }; // triggered when we click on the step name in left nav (doesn't happen in wizard mode) triggerStepChange = (t) => { this.nextStep = t, this.triggerAction(l.GO_TO_STEP); }; onError = (t) => { if (this.setState({ doShowErrors: !0, hasValidated: !1 }), this.navAction === l.VALIDATE) { const e = this.setStepStatusForFailedValidation( t.props, this.state.steps, !!this.props.isWizardMode, this.state.formData, this.getSchema(this.state.currentStep).properties || this.getSchema(this.state.currentStep) ); this.setState({ steps: e }), this.formDivRef.current.scrollTo(0, 0), this.state.isLoadingSaved && (this.moveStep(this.state.formData, e[0].id, !0), this.setState({ isLoadingSaved: !1 })); } }; setStepStatusForFailedValidation = (t, e, s, a, r) => { const h = t.map( (n) => w(n.property, ".").split(".")[0] ), d = Object.keys(r); return e.map((n) => { if (h.indexOf(n.id) > -1) return { ...n, state: f.ERROR }; if (d.indexOf(n.id) > -1) { let o = f.COMPLETED; return s && !g(a[n.id], "included") && (o = n.state), { ...n, state: o }; } else return n; }); }; //we are constantly saving form data. Needed to overwrite on-error behavior handleOnChange({ formData: t }) { if (!this.isSubmitScreen() && !this.state.currentStep.excluded) { const e = !U(this.state.formData, t); this.setState({ formData: t, hasUnsavedChanges: e }); } } performAction(t, e) { const s = this.state.formData; switch (t) { case l.NEXT: return this.moveStep(s, void 0, e); case l.PREVIOUS: return this.goPrevious(s, e); case l.GO_TO_STEP: return this.nextStep ? this.moveStep(s, this.nextStep.id, e) : void 0; case l.SUBMIT: { this.props.onSubmit(s); return; } case l.VALIDATE: { const a = this.setStepStatusForFailedValidation( [], this.state.steps, !!this.props.isWizardMode, this.state.formData, this.getSchema(this.state.currentStep).properties || this.getSchema(this.state.currentStep) ), r = { ...this.state.currentStep, state: f.COMPLETED }; this.setState({ hasValidated: !0, currentStep: r, steps: a }), this.state.isLoadingSaved && (this.moveStep(this.state.formData, a[0].id, !1), this.setState({ isLoadingSaved: !1 })); return; } default: return; } } //we need to route things through submit - otherwise validation does not kick in // it triggers internal library validation and calls the performAction with the params for action onSubmit = () => { this.performAction( this.navAction, this.state.currentStep.state === f.ERROR ); }; isSubmitScreen = () => this.state.currentStep.final === !0 && !this.state.isLoadingSaved; showExcludeStateWarningDialog = (t, e = !1) => { this.setState({ modalContext: { action: this.toggleExcludeStep, arguments: [t, !0, e] } }); }; toggleExcludeStep = (t, e) => { this.setState((s, a) => { const r = s.steps.map((m) => m.id === t ? { ...m, excluded: e } : m), h = y(s.formData), d = y(s.currentStep); return d.id === t && (d.excluded = e), e ? h[t] = {} : C(h, `${t}.included`, !0), { steps: r, formData: h, modalContext: void 0, currentStep: d }; }); }; renderNotification = (t) => t === b.SAVE_SUCCESS ? /* @__PURE__ */ i("div", { className: "notification-area", children: " Successfully saved " }) : t === b.SUBMIT_SUCCESS ? /* @__PURE__ */ i("div", { className: "notification-area", children: " Successfully submitted " }) : t === b.PROGRESS ? /* @__PURE__ */ i("div", { className: "notification-area", children: " working on it ...." }) : /* @__PURE__ */ i(v, {}); // displays the text for screens that don't have any form data renderTextForStaticScreen = () => { if (!this.state.currentStep.copy) return /* @__PURE__ */ i(v, {}); const t = this.state.currentStep.copy; return /* @__PURE__ */ i( "div", { className: "static-screen", dangerouslySetInnerHTML: { __html: t } } ); }; //displays subheader for forms that can be excluded renderOptionalFormSubheader = (t = !1) => { if (t) return /* @__PURE__ */ i(v, {}); const e = this.state.currentStep; return e.excluded === !0 ? /* @__PURE__ */ p("div", { className: "step-exclude-directions", children: [ "This form is currently not included in the submission.", /* @__PURE__ */ i( "button", { className: "btn btn-link", onClick: () => this.toggleExcludeStep(e.id, !1), children: "Include" } ) ] }) : e.excluded === !1 ? /* @__PURE__ */ p("div", { className: "step-exclude-directions", children: [ 'This form is currently included in the submission. Enter some data if you have it, or click "Skip".', /* @__PURE__ */ i( "button", { className: "btn btn-link", onClick: () => this.showExcludeStateWarningDialog(this.state.currentStep.id), children: "Skip" } ) ] }) : /* @__PURE__ */ i(v, {}); }; renderHelpToggle = (t, e, s) => t.static || t.final ? /* @__PURE__ */ i(v, {}) : /* @__PURE__ */ i(v, { children: /* @__PURE__ */ p("label", { className: "pull-right toggle-help-label", children: [ /* @__PURE__ */ i("span", { children: "Hide help" }), /* @__PURE__ */ i( B, { checkedIcon: !1, uncheckedIcon: !1, height: 20, width: 45, className: "toggle-help", offColor: "#ccc", onChange: () => s(), checked: e } ), /* @__PURE__ */ i("span", { children: "Show help" }) ] }) }); runCustomValidation = async (t, e, s) => { const a = []; let r = e.validationRules || [], h = { [e.id]: t[e.id] }; if (e.final && (r = s.reduce((n, o) => o.validationRules && o.validationRules.length > 0 ? n.concat(o.validationRules) : n, []), h = y(t)), r.length === 0) return []; const d = []; r.forEach((n) => { const o = n.event.params.property; if (o.indexOf("[*]") === -1) d.push(n); else { const S = o.split("[*]")[0].substring(1), c = g(t, S); if (Array.isArray(c) && typeof c != "string") for (let E = 0; E < c.length; E++) { const x = JSON.parse( JSON.stringify(n).replace(/\[\*\]/g, `[${E}]`) ); d.push(x); } else d.push(n); } }); const m = new N(d, { allowUndefinedFacts: !0 }); try { (await m.run(h)).events.forEach((S) => { const c = { ...S.params, params: {}, stack: `${S.params.property} ${S.params.message}` }; a.push(c); }); } catch (n) { console.log(n); } return a; }; transformErrors = (t) => { if (this.extraErrors.forEach((s) => { t.find((a) => a.stack === s.stack) || t.push(s); }), this.navAction !== l.SUBMIT && this.navAction !== l.VALIDATE && (!this.props.isWizardMode || this.state.currentStep.final)) { const s = { ...this.state.currentStep }; return t.length > 0 ? s.state = f.ERROR : s.state = f.COMPLETED, this.setState({ currentStep: s }), []; } return t.filter((s) => s.name === "required").forEach((s) => { if (s.property) { const a = s.property.substring( 0, s.property.lastIndexOf(".") ); P(t, (r) => (r.property || "").indexOf(a) > -1 && (r.name === "enum" || r.name === "oneOf")); } }), t.map((s) => (s.message && (s.message = s.message.replace("property", "field")), s)); }; renderErrorListTemplate = (t) => { const { errors: e } = t, s = e.map((a, r) => Z( this.state.steps, a, this.uiSchema, r, this.props.schema )).sort((a, r) => a.order - r.order).map((a) => a.element); return /* @__PURE__ */ i("div", { className: "form-error-summary", children: /* @__PURE__ */ i("ul", { className: "error-detail", children: s }) }); }; render() { return /* @__PURE__ */ p("div", { className: "outter-wrap", children: [ /* @__PURE__ */ i( R, { shouldBlock: ({ currentLocation: t, nextLocation: e }) => this.state.hasUnsavedChanges && t.pathname !== e.pathname, children: (t) => /* @__PURE__ */ i( _, { open: t.state == "blocked", title: "Unsaved Changes", content: this.unsavedDataWarning, onConfirm: () => { t.proceed && t.proceed(); }, onCancel: () => { t.reset && t.reset(); } } ) } ), /* @__PURE__ */ i( H, { isSubmitted: this.state.isSubmitted, bodyText: this.state.currentStep.description, title: this.props.formTitle } ), /* @__PURE__ */ i("div", { children: /* @__PURE__ */ p("div", { className: "inner-wrap", children: [ /* @__PURE__ */ i( G, { stepList: this.state.steps, isWizardMode: this.props.isWizardMode, onStepChange: this.triggerStepChange } ), this.state.isLoadingSaved && /* @__PURE__ */ i("div", { className: "text-center", children: /* @__PURE__ */ i("span", { className: "spinner" }) }), /* @__PURE__ */ p("div", { className: "form-wrap", children: [ /* @__PURE__ */ i("div", { className: "form-title", children: this.state.currentStep.title }), this.renderNotification(this.props.callbackStatus), /* @__PURE__ */ p( "div", { className: `right-top-actions ${this.state.isSubmitted ? "hide" : ""}`, children: [ !this.state.currentStep.static && /* @__PURE__ */ i( "button", { type: "button", className: "btn btn-action save pull-right", onClick: () => { this.triggerAction(l.VALIDATE); }, children: "VALIDATE" } ), this.renderHelpToggle( this.state.currentStep, this.state.doShowHelp, () => this.setState({ doShowHelp: !this.state.doShowHelp }) ), this.isSubmitScreen() && /* @__PURE__ */ i( "button", { type: "button", className: "btn btn-action save pull-right", disabled: this.state.isSubmitted, onClick: () => { this.triggerAction(l.SUBMIT); }, children: "SUBMIT" } ) ] } ), this.renderOptionalFormSubheader(this.props.isWizardMode), /* @__PURE__ */ p( "div", { className: this.isSubmitScreen() || this.state.currentStep.static ? "hide-form-only" : "wrap", children: [ this.state.hasValidated && /* @__PURE__ */ i("div", { className: "notification-area", children: "Great! All required data on this form has been entered." }), /* @__PURE__ */ p( "div", { ref: this.formDivRef, className: `scroll-area ${this.state.currentStep.excluded ? "disabled" : " "} `, children: [ /* @__PURE__ */ i( A, { validator: I, noHtml5Validate: !0, className: this.state.doShowHelp ? "submissionInputForm" : "submissionInputForm no-help", liveValidate: !1, formData: this.state.formData, schema: this.getSchema(this.state.currentStep), uiSchema: this.uiSchema, onSubmit: this.onSubmit, onChange: (t) => this.handleOnChange(t), onError: (t) => this.onError({ props: t, form: this.formRef }), showErrorList: this.state.doShowErrors || this.props.isWizardMode ? "top" : !1, templates: { ErrorListTemplate: this.renderErrorListTemplate }, transformErrors: this.transformErrors, ref: this.formRef, disabled: this.state.currentStep.excluded || this.state.isSubmitted, widgets: { CheckboxWidget: X, CheckboxesWidget: Y, RadioWidget: K }, children: /* @__PURE__ */ i("div", { style: { display: "none" }, children: /* @__PURE__ */ i("button", { type: "submit", ref: this.submitButtonRef }) }) } ), this.renderTextForStaticScreen(), !this.props.isWizardMode && /* @__PURE__ */ i( q, { steps: this.state.steps, nextStepId: this.state.currentStep.default, onNavAction: (t) => this.triggerStepChange(t) } ) ] } ) ] } ), this.isSubmitScreen() && /* @__PURE__ */ i( J, { formData: this.state.formData, steps: this.state.steps, callbackFn: (t) => this.showExcludeStateWarningDialog(t, !0), uiSchema: this.props.uiSchema, schema: this.props.schema } ), /* @__PURE__ */ i( j, { currentStep: this.state.currentStep, steps: this.state.steps, previousStepIds: this.state.previousStepIds, isFormSubmitted: this.state.isSubmitted, onNavAction: (t) => { this.triggerAction(t); } } ) ] }) ] }) }), this.state.modalContext && /* @__PURE__ */ i( Q, { open: !0, title: this.excludeWarningHeader, content: this.excludeWarningText, className: `theme-${this.props.formClass}`, onConfirmCallbackArgs: this.state.modalContext.arguments, onCancel: () => this.setState({ modalContext: void 0 }), onConfirm: (t, e) => this.toggleExcludeStep(t, e) } ), /* @__PURE__ */ i($, { formData: this.state.formData, hidden: !0 }) ] }); } } function Z(u, t, e, s, a) { const r = w(t.property, "."), h = r.split("."), d = `${h.join(".properties.")}.title`, m = `${r}.ui:title`, n = d.replace(/\[.*?\]/, ".items"), o = m.replace(/\[.*?\]/, ".items"), S = d.match(/\[.*?\]/); let c = L(S); c && (c = c.substring(1, c.length - 1), c = isNaN(parseInt(c)) ? "" : ` [${parseInt(c) + 1}]`); const E = g(e, m) || g(a.properties, d) || g(e, o) || g(a.properties, n) || t.property, x = W(u, { id: h[0] }) || { title: h[0], order: 0 }, F = /* @__PURE__ */ i("li", { className: "", children: /* @__PURE__ */ p("span", { children: [ /* @__PURE__ */ p("strong", { children: [ x.title, c, ":" ] }), E, "  ", t.message ] }) }, s); return { order: x.order, element: F }; } function T(u, t) { return O(u).some((e) => { if (e === t) { const s = u[e]; return u[e] = /* @__PURE__ */ i("span", { dangerouslySetInnerHTML: { __html: s } }), u; } u[e] && typeof u[e] == "object" && T(u[e], t); }), u; } export { At as default }; //# sourceMappingURL=SynapseForm.js.map