@saltcorn/workflow-editor 1.6.0-alpha.13 → 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/dist/workflow_bundle.js.map +1 -1
- package/package.json +1 -1
- package/src/WorkflowEditor.jsx +79 -17
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,21 +801,13 @@ const StepDrawer = ({
|
|
|
792
801
|
<button className="btn btn-secondary" onClick={onClose}>
|
|
793
802
|
Close
|
|
794
803
|
</button>
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
new Event("submit", { cancelable: true, bubbles: true })
|
|
803
|
-
)
|
|
804
|
-
}
|
|
805
|
-
disabled={submitting}
|
|
806
|
-
>
|
|
807
|
-
{submitting ? "Saving..." : "Save"}
|
|
808
|
-
</button>
|
|
809
|
-
)}
|
|
804
|
+
<button
|
|
805
|
+
className="btn btn-primary"
|
|
806
|
+
onClick={handleSaveClick}
|
|
807
|
+
disabled={submitting}
|
|
808
|
+
>
|
|
809
|
+
{submitting ? "Saving..." : "Save"}
|
|
810
|
+
</button>
|
|
810
811
|
</div>
|
|
811
812
|
</>
|
|
812
813
|
) : null}
|
|
@@ -848,6 +849,8 @@ const WorkflowEditor = ({ data }) => {
|
|
|
848
849
|
const [selectedEdgeId, setSelectedEdgeId] = useState(null);
|
|
849
850
|
const autoSaveTimerRef = useRef(null);
|
|
850
851
|
const autoSavingRef = useRef(false);
|
|
852
|
+
const lastAutosaveSnapshotRef = useRef("");
|
|
853
|
+
const pendingAutosaveSnapshotRef = useRef("");
|
|
851
854
|
const nodesRef = useRef([]);
|
|
852
855
|
const [headerHost, setHeaderHost] = useState(null);
|
|
853
856
|
const [shellHeight, setShellHeight] = useState(null);
|
|
@@ -985,6 +988,24 @@ const WorkflowEditor = ({ data }) => {
|
|
|
985
988
|
[viewportStorageKey]
|
|
986
989
|
);
|
|
987
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
|
+
|
|
988
1009
|
const onInit = useCallback((instance) => {
|
|
989
1010
|
rfInstanceRef.current = instance;
|
|
990
1011
|
}, []);
|
|
@@ -1491,6 +1512,8 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1491
1512
|
if (!modal) return undefined;
|
|
1492
1513
|
const formEl = modalRef.current?.querySelector("form");
|
|
1493
1514
|
if (!formEl) return undefined;
|
|
1515
|
+
lastAutosaveSnapshotRef.current = serializeForm(formEl);
|
|
1516
|
+
pendingAutosaveSnapshotRef.current = "";
|
|
1494
1517
|
// Re-run show-if logic so action-specific fields render correctly
|
|
1495
1518
|
setTimeout(() => {
|
|
1496
1519
|
if (typeof window !== "undefined") {
|
|
@@ -1502,21 +1525,44 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1502
1525
|
|
|
1503
1526
|
const actionSelect = formEl.querySelector('[name="wf_action_name"]');
|
|
1504
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
|
+
|
|
1505
1539
|
const handleActionChange = () => {
|
|
1506
1540
|
if (typeof window !== "undefined" && window.apply_showif)
|
|
1507
1541
|
window.apply_showif();
|
|
1542
|
+
if (!shouldAutoSaveForAction()) cancelPendingAutoSave();
|
|
1508
1543
|
};
|
|
1509
1544
|
if (actionSelect)
|
|
1510
1545
|
actionSelect.addEventListener("change", handleActionChange);
|
|
1511
1546
|
|
|
1512
1547
|
const scheduleAutoSave = allowAutoSave
|
|
1513
1548
|
? () => {
|
|
1549
|
+
if (!shouldAutoSaveForAction()) return;
|
|
1514
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;
|
|
1515
1555
|
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
1556
|
+
pendingAutosaveSnapshotRef.current = snapshot;
|
|
1516
1557
|
autoSaveTimerRef.current = setTimeout(async () => {
|
|
1558
|
+
pendingAutosaveSnapshotRef.current = "";
|
|
1559
|
+
const currentSnapshot = serializeForm(formEl);
|
|
1560
|
+
if (!currentSnapshot) return;
|
|
1561
|
+
if (currentSnapshot === lastAutosaveSnapshotRef.current) return;
|
|
1517
1562
|
autoSavingRef.current = true;
|
|
1518
1563
|
try {
|
|
1519
1564
|
await submitStepForm(formEl, { closeOnSuccess: false });
|
|
1565
|
+
lastAutosaveSnapshotRef.current = currentSnapshot;
|
|
1520
1566
|
} catch (err) {
|
|
1521
1567
|
setError(err.message);
|
|
1522
1568
|
} finally {
|
|
@@ -1529,8 +1575,23 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1529
1575
|
|
|
1530
1576
|
const submitHandler = async (e) => {
|
|
1531
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
|
+
|
|
1532
1591
|
try {
|
|
1592
|
+
cancelPendingAutoSave();
|
|
1533
1593
|
await submitStepForm(formEl);
|
|
1594
|
+
lastAutosaveSnapshotRef.current = currentSnapshot;
|
|
1534
1595
|
} catch (err) {
|
|
1535
1596
|
setError(err.message);
|
|
1536
1597
|
} finally {
|
|
@@ -1543,7 +1604,7 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1543
1604
|
formEl.addEventListener("change", scheduleAutoSave);
|
|
1544
1605
|
}
|
|
1545
1606
|
return () => {
|
|
1546
|
-
|
|
1607
|
+
cancelPendingAutoSave();
|
|
1547
1608
|
formEl.removeEventListener("submit", submitHandler);
|
|
1548
1609
|
if (scheduleAutoSave) {
|
|
1549
1610
|
formEl.removeEventListener("input", scheduleAutoSave);
|
|
@@ -1551,8 +1612,9 @@ const WorkflowEditor = ({ data }) => {
|
|
|
1551
1612
|
}
|
|
1552
1613
|
if (actionSelect)
|
|
1553
1614
|
actionSelect.removeEventListener("change", handleActionChange);
|
|
1615
|
+
lastAutosaveSnapshotRef.current = "";
|
|
1554
1616
|
};
|
|
1555
|
-
}, [modal, submitStepForm]);
|
|
1617
|
+
}, [modal, serializeForm, submitStepForm, clearDrawerState, setModal]);
|
|
1556
1618
|
|
|
1557
1619
|
useEffect(() => {
|
|
1558
1620
|
if (typeof window === "undefined") return undefined;
|