synapse-react-client
Version:
[](https://badge.fury.io/js/synapse-react-client) [](https://github.com/prettier/prettie
621 lines (620 loc) • 23.5 kB
JavaScript
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