CI/CD
This commit is contained in:
114
ui/src/hooks/useThrottledParameterValue.js
Normal file
114
ui/src/hooks/useThrottledParameterValue.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
function valuesMatch(left, right) {
|
||||
return JSON.stringify(left) === JSON.stringify(right);
|
||||
}
|
||||
|
||||
export function useThrottledParameterValue(parameter, onParameterChange) {
|
||||
const [draftValue, setDraftValue] = useState(parameter.value);
|
||||
const [appliedValue, setAppliedValue] = useState(parameter.value);
|
||||
const pendingTimeoutRef = useRef(null);
|
||||
const latestDraftRef = useRef(parameter.value);
|
||||
const lastSentAtRef = useRef(0);
|
||||
const isInteractingRef = useRef(false);
|
||||
const isDirtyRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setDraftValue(parameter.value);
|
||||
setAppliedValue(parameter.value);
|
||||
latestDraftRef.current = parameter.value;
|
||||
lastSentAtRef.current = 0;
|
||||
isInteractingRef.current = false;
|
||||
isDirtyRef.current = false;
|
||||
}, [parameter.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setAppliedValue(parameter.value);
|
||||
latestDraftRef.current = draftValue;
|
||||
if (isDirtyRef.current && valuesMatch(parameter.value, latestDraftRef.current)) {
|
||||
isDirtyRef.current = false;
|
||||
}
|
||||
if (!isInteractingRef.current && !isDirtyRef.current) {
|
||||
setDraftValue(parameter.value);
|
||||
}
|
||||
}, [draftValue, parameter.value]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (pendingTimeoutRef.current) {
|
||||
clearTimeout(pendingTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const sendValue = (value) => {
|
||||
setDraftValue(value);
|
||||
latestDraftRef.current = value;
|
||||
isDirtyRef.current = true;
|
||||
lastSentAtRef.current = Date.now();
|
||||
const request = onParameterChange(parameter.id, value);
|
||||
if (request && typeof request.then === "function") {
|
||||
request
|
||||
.then((response) => {
|
||||
if (response?.ok) {
|
||||
setAppliedValue(value);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Keep showing the last confirmed value if the update failed.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleSendValue = (value, immediate = false) => {
|
||||
setDraftValue(value);
|
||||
latestDraftRef.current = value;
|
||||
isDirtyRef.current = true;
|
||||
|
||||
if (pendingTimeoutRef.current) {
|
||||
clearTimeout(pendingTimeoutRef.current);
|
||||
pendingTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const throttleMs = 90;
|
||||
const elapsed = now - lastSentAtRef.current;
|
||||
|
||||
if (immediate || elapsed >= throttleMs) {
|
||||
sendValue(value);
|
||||
return;
|
||||
}
|
||||
|
||||
pendingTimeoutRef.current = setTimeout(() => {
|
||||
pendingTimeoutRef.current = null;
|
||||
sendValue(latestDraftRef.current);
|
||||
}, throttleMs - elapsed);
|
||||
};
|
||||
|
||||
const flushScheduledValue = () => {
|
||||
if (pendingTimeoutRef.current) {
|
||||
clearTimeout(pendingTimeoutRef.current);
|
||||
pendingTimeoutRef.current = null;
|
||||
}
|
||||
sendValue(latestDraftRef.current);
|
||||
};
|
||||
|
||||
const beginInteraction = () => {
|
||||
isInteractingRef.current = true;
|
||||
};
|
||||
|
||||
const endInteraction = () => {
|
||||
isInteractingRef.current = false;
|
||||
flushScheduledValue();
|
||||
};
|
||||
|
||||
return {
|
||||
appliedValue,
|
||||
beginInteraction,
|
||||
draftValue,
|
||||
endInteraction,
|
||||
isPending: !valuesMatch(draftValue, appliedValue),
|
||||
scheduleSendValue,
|
||||
sendValue,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user