@saltcorn/workflow-editor 1.6.0-alpha.14 → 1.6.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/workflow_bundle.js +1 -1
- package/package.json +1 -1
- package/src/WorkflowEditor.jsx +74 -11
package/package.json
CHANGED
package/src/WorkflowEditor.jsx
CHANGED
|
@@ -732,6 +732,15 @@ const StepDrawer = ({
|
|
|
732
732
|
if (e?.nativeEvent?.stopImmediatePropagation)
|
|
733
733
|
e.nativeEvent.stopImmediatePropagation();
|
|
734
734
|
}, []);
|
|
735
|
+
const handleSaveClick = useCallback(() => {
|
|
736
|
+
const form = innerRef?.current?.querySelector("form");
|
|
737
|
+
if (!form) return;
|
|
738
|
+
if (typeof form.requestSubmit === "function") form.requestSubmit();
|
|
739
|
+
else
|
|
740
|
+
form.dispatchEvent(
|
|
741
|
+
new Event("submit", { cancelable: true, bubbles: true })
|
|
742
|
+
);
|
|
743
|
+
}, [innerRef]);
|
|
735
744
|
return (
|
|
736
745
|
<div
|
|
737
746
|
ref={outerRef}
|
|
@@ -792,16 +801,9 @@ const StepDrawer = ({
|
|
|
792
801
|
<button className="btn btn-secondary" onClick={onClose}>
|
|
793
802
|
Close
|
|
794
803
|
</button>
|
|
795
|
-
|
|
796
804
|
<button
|
|
797
805
|
className="btn btn-primary"
|
|
798
|
-
onClick={
|
|
799
|
-
innerRef.current
|
|
800
|
-
?.querySelector("form")
|
|
801
|
-
?.dispatchEvent(
|
|
802
|
-
new Event("submit", { cancelable: true, bubbles: true })
|
|
803
|
-
)
|
|
804
|
-
}
|
|
806
|
+
onClick={handleSaveClick}
|
|
805
807
|
disabled={submitting}
|
|
806
808
|
>
|
|
807
809
|
{submitting ? "Saving..." : "Save"}
|
|
@@ -847,6 +849,8 @@ const WorkflowEditor = ({ data }) => {
|
|
|
847
849
|
const [selectedEdgeId, setSelectedEdgeId] = useState(null);
|
|
848
850
|
const autoSaveTimerRef = useRef(null);
|
|
849
851
|
const autoSavingRef = useRef(false);
|
|
852
|
+
const lastAutosaveSnapshotRef = useRef("");
|
|
853
|
+
const pendingAutosaveSnapshotRef = useRef("");
|
|
850
854
|
const nodesRef = useRef([]);
|
|
851
855
|
const [headerHost, setHeaderHost] = useState(null);
|
|
852
856
|
const [shellHeight, setShellHeight] = useState(null);
|
|
@@ -984,6 +988,24 @@ const WorkflowEditor = ({ data }) => {
|
|
|
984
988
|
[viewportStorageKey]
|
|
985
989
|
);
|
|
986
990
|
|
|
991
|
+
const serializeForm = useCallback((formEl) => {
|
|
992
|
+
if (!formEl) return "";
|
|
993
|
+
const fd = new FormData(formEl);
|
|
994
|
+
const entries = [];
|
|
995
|
+
for (const [key, value] of fd.entries()) {
|
|
996
|
+
if (value instanceof File) {
|
|
997
|
+
entries.push([key, `file:${value.name}:${value.size}`]);
|
|
998
|
+
} else {
|
|
999
|
+
entries.push([key, `${value}`]);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
entries.sort((a, b) => {
|
|
1003
|
+
if (a[0] === b[0]) return a[1].localeCompare(b[1]);
|
|
1004
|
+
return a[0].localeCompare(b[0]);
|
|
1005
|
+
});
|
|
1006
|
+
return JSON.stringify(entries);
|
|
1007
|
+
}, []);
|
|
1008
|
+
|
|
987
1009
|
const onInit = useCallback((instance) => {
|
|
988
1010
|
rfInstanceRef.current = instance;
|
|
989
1011
|
}, []);
|
|
@@ -1490,6 +1512,8 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1490
1512
|
if (!modal) return undefined;
|
|
1491
1513
|
const formEl = modalRef.current?.querySelector("form");
|
|
1492
1514
|
if (!formEl) return undefined;
|
|
1515
|
+
lastAutosaveSnapshotRef.current = serializeForm(formEl);
|
|
1516
|
+
pendingAutosaveSnapshotRef.current = "";
|
|
1493
1517
|
// Re-run show-if logic so action-specific fields render correctly
|
|
1494
1518
|
setTimeout(() => {
|
|
1495
1519
|
if (typeof window !== "undefined") {
|
|
@@ -1501,22 +1525,45 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1501
1525
|
|
|
1502
1526
|
const actionSelect = formEl.querySelector('[name="wf_action_name"]');
|
|
1503
1527
|
const allowAutoSave = !!modal?.stepId; // avoid auto-save for brand new steps to prevent insert errors
|
|
1528
|
+
|
|
1529
|
+
const shouldAutoSaveForAction = () => !!allowAutoSave;
|
|
1530
|
+
|
|
1531
|
+
const cancelPendingAutoSave = () => {
|
|
1532
|
+
if (autoSaveTimerRef.current) {
|
|
1533
|
+
clearTimeout(autoSaveTimerRef.current);
|
|
1534
|
+
autoSaveTimerRef.current = null;
|
|
1535
|
+
}
|
|
1536
|
+
pendingAutosaveSnapshotRef.current = "";
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1504
1539
|
const handleActionChange = () => {
|
|
1505
1540
|
if (typeof window !== "undefined" && window.apply_showif)
|
|
1506
1541
|
window.apply_showif();
|
|
1542
|
+
if (!shouldAutoSaveForAction()) cancelPendingAutoSave();
|
|
1507
1543
|
};
|
|
1508
1544
|
if (actionSelect)
|
|
1509
1545
|
actionSelect.addEventListener("change", handleActionChange);
|
|
1510
1546
|
|
|
1511
1547
|
const scheduleAutoSave = allowAutoSave
|
|
1512
1548
|
? () => {
|
|
1549
|
+
if (!shouldAutoSaveForAction()) return;
|
|
1513
1550
|
if (autoSavingRef.current) return;
|
|
1551
|
+
const snapshot = serializeForm(formEl);
|
|
1552
|
+
if (!snapshot) return;
|
|
1553
|
+
if (snapshot === lastAutosaveSnapshotRef.current) return;
|
|
1554
|
+
if (snapshot === pendingAutosaveSnapshotRef.current) return;
|
|
1514
1555
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1556
|
+
pendingAutosaveSnapshotRef.current = snapshot;
|
|
1515
1557
|
autoSaveTimerRef.current = setTimeout(async () => {
|
|
1558
|
+
pendingAutosaveSnapshotRef.current = "";
|
|
1559
|
+
const currentSnapshot = serializeForm(formEl);
|
|
1560
|
+
if (!currentSnapshot) return;
|
|
1561
|
+
if (currentSnapshot === lastAutosaveSnapshotRef.current) return;
|
|
1516
1562
|
autoSavingRef.current = true;
|
|
1517
1563
|
try {
|
|
1518
1564
|
await submitStepForm(formEl, { closeOnSuccess: false });
|
|
1519
|
-
|
|
1565
|
+
lastAutosaveSnapshotRef.current = currentSnapshot;
|
|
1566
|
+
} catch (err) {
|
|
1520
1567
|
setError(err.message);
|
|
1521
1568
|
} finally {
|
|
1522
1569
|
autoSavingRef.current = false;
|
|
@@ -1528,8 +1575,23 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1528
1575
|
|
|
1529
1576
|
const submitHandler = async (e) => {
|
|
1530
1577
|
e.preventDefault();
|
|
1578
|
+
const currentSnapshot = serializeForm(formEl);
|
|
1579
|
+
const alreadySaved =
|
|
1580
|
+
shouldAutoSaveForAction() &&
|
|
1581
|
+
!!lastAutosaveSnapshotRef.current &&
|
|
1582
|
+
currentSnapshot === lastAutosaveSnapshotRef.current;
|
|
1583
|
+
|
|
1584
|
+
if (alreadySaved) {
|
|
1585
|
+
setModal(null);
|
|
1586
|
+
clearDrawerState();
|
|
1587
|
+
setSavingModal(false);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1531
1591
|
try {
|
|
1592
|
+
cancelPendingAutoSave();
|
|
1532
1593
|
await submitStepForm(formEl);
|
|
1594
|
+
lastAutosaveSnapshotRef.current = currentSnapshot;
|
|
1533
1595
|
} catch (err) {
|
|
1534
1596
|
setError(err.message);
|
|
1535
1597
|
} finally {
|
|
@@ -1542,7 +1604,7 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1542
1604
|
formEl.addEventListener("change", scheduleAutoSave);
|
|
1543
1605
|
}
|
|
1544
1606
|
return () => {
|
|
1545
|
-
|
|
1607
|
+
cancelPendingAutoSave();
|
|
1546
1608
|
formEl.removeEventListener("submit", submitHandler);
|
|
1547
1609
|
if (scheduleAutoSave) {
|
|
1548
1610
|
formEl.removeEventListener("input", scheduleAutoSave);
|
|
@@ -1550,8 +1612,9 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1550
1612
|
}
|
|
1551
1613
|
if (actionSelect)
|
|
1552
1614
|
actionSelect.removeEventListener("change", handleActionChange);
|
|
1615
|
+
lastAutosaveSnapshotRef.current = "";
|
|
1553
1616
|
};
|
|
1554
|
-
}, [modal, submitStepForm]);
|
|
1617
|
+
}, [modal, serializeForm, submitStepForm, clearDrawerState, setModal]);
|
|
1555
1618
|
|
|
1556
1619
|
useEffect(() => {
|
|
1557
1620
|
if (typeof window === "undefined") return undefined;
|