Fixed min/max snapping to be based off min if set not forced to zero
Issue: If Step was set to 4, you could not set min to 1 to get a range like: [1 , 5 , 9 , 13 , 17 ...] Fix was to set step to move off of min instead of zero as a fixed base. Small bug but in interest of time was left for now. Min can not be incremented downwards by the down arrow, must be manually typed. (can move up and Max can move up or down)
This commit is contained in:
+286
-214
@@ -57,23 +57,7 @@ class MultiFloatNodeAID {
|
||||
node.properties.floatPrecision = [];
|
||||
}
|
||||
this.adjustArrays();
|
||||
|
||||
// On load, snap any existing min/max to the step
|
||||
for (let i = 0; i < this.node.properties.activeCount; i++) {
|
||||
const stepVal = this.node.properties.floatSteps[i] || 0.1;
|
||||
|
||||
if (typeof this.node.properties.floatMin[i] === "number") {
|
||||
const oldMin = this.node.properties.floatMin[i];
|
||||
const newMin = stepVal * Math.round(oldMin / stepVal);
|
||||
this.node.properties.floatMin[i] = newMin;
|
||||
}
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
const newMax = stepVal * Math.round(oldMax / stepVal);
|
||||
this.node.properties.floatMax[i] = newMax;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (typeof node.properties.bottomPadding !== "number") {
|
||||
node.properties.bottomPadding = 0;
|
||||
}
|
||||
@@ -660,20 +644,24 @@ class MultiFloatNodeAID {
|
||||
|
||||
openGearPopup(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
// Before opening the popup, snap min/max to step again
|
||||
|
||||
// We'll leave Min exactly as the user typed it, and only snap Max from that Min.
|
||||
for (let i = 0; i < this.node.properties.activeCount; i++) {
|
||||
const stepVal = this.node.properties.floatSteps[i] || 0.1;
|
||||
if (typeof this.node.properties.floatMin[i] === "number") {
|
||||
const snappedMin = stepVal * Math.round(this.node.properties.floatMin[i] / stepVal);
|
||||
this.node.properties.floatMin[i] = snappedMin;
|
||||
}
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
const snappedMax = stepVal * Math.round(this.node.properties.floatMax[i] / stepVal);
|
||||
this.node.properties.floatMax[i] = snappedMax;
|
||||
const hasMin = (typeof this.node.properties.floatMin[i] === "number");
|
||||
const hasMax = (typeof this.node.properties.floatMax[i] === "number");
|
||||
|
||||
// If both min and max are defined, we snap max from min as the offset:
|
||||
// newMax = min + step * round( (oldMax - min) / step )
|
||||
if (hasMin && hasMax) {
|
||||
const oldMin = this.node.properties.floatMin[i];
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
const offsetMax = oldMin + (stepVal * Math.round((oldMax - oldMin) / stepVal));
|
||||
this.node.properties.floatMax[i] = offsetMax;
|
||||
}
|
||||
// Otherwise, do nothing to the user's min or max if one is missing
|
||||
}
|
||||
|
||||
|
||||
const nodeElem = this.node.htmlElement;
|
||||
if (!nodeElem) {
|
||||
this.createGearPopupFallback(e.clientX, e.clientY);
|
||||
@@ -685,14 +673,13 @@ class MultiFloatNodeAID {
|
||||
const popup = new SettingsPopup(this.node);
|
||||
popup.open(popupX, popupY);
|
||||
this.populateGearPopup(popup);
|
||||
}
|
||||
}
|
||||
|
||||
createGearPopupFallback(x, y) {
|
||||
const popup = new SettingsPopup(this.node);
|
||||
popup.open(x, y);
|
||||
this.populateGearPopup(popup);
|
||||
}
|
||||
|
||||
|
||||
populateGearPopup(popup) {
|
||||
const content = popup.getContentContainer();
|
||||
@@ -762,197 +749,282 @@ class MultiFloatNodeAID {
|
||||
const stepsList = document.createElement("div");
|
||||
stepsContainer.appendChild(stepsList);
|
||||
|
||||
const updateStepInputs = () => {
|
||||
stepsList.innerHTML = '';
|
||||
for (let i = 0; i < this.node.properties.activeCount; i++) {
|
||||
const rowDiv = document.createElement("div");
|
||||
rowDiv.style.display = "flex";
|
||||
rowDiv.style.justifyContent = "center";
|
||||
rowDiv.style.alignItems = "center";
|
||||
rowDiv.style.marginBottom = "5px";
|
||||
rowDiv.style.paddingBottom = "2px";
|
||||
rowDiv.style.borderBottom = "2px solid #262626";
|
||||
|
||||
const floatLabel = document.createElement("span");
|
||||
floatLabel.textContent = `f${i + 1}`;
|
||||
floatLabel.style.color = "#fff";
|
||||
floatLabel.style.fontSize = "14px";
|
||||
floatLabel.style.marginRight = "5px";
|
||||
rowDiv.appendChild(floatLabel);
|
||||
|
||||
const precInput = document.createElement("input");
|
||||
precInput.type = "number";
|
||||
precInput.min = "1";
|
||||
precInput.max = "5";
|
||||
precInput.value = this.node.properties.floatPrecision[i] || 1;
|
||||
precInput.placeholder = "Prec";
|
||||
precInput.style.width = "40px";
|
||||
precInput.style.fontSize = "12px";
|
||||
precInput.style.background = "#333";
|
||||
precInput.style.color = "#fff";
|
||||
precInput.style.border = "2px solid #4b3e72";
|
||||
precInput.style.borderRadius = "6px";
|
||||
precInput.style.textAlign = "center";
|
||||
precInput.style.marginRight = "5px";
|
||||
rowDiv.appendChild(precInput);
|
||||
|
||||
const stepInput = document.createElement("input");
|
||||
stepInput.type = "number";
|
||||
stepInput.value = this.node.properties.floatSteps[i] || 0.1;
|
||||
stepInput.style.width = "60px";
|
||||
stepInput.style.fontSize = "12px";
|
||||
stepInput.style.background = "#333";
|
||||
stepInput.style.color = "#fff";
|
||||
stepInput.style.border = "2px solid #4b3e72";
|
||||
stepInput.style.borderRadius = "6px";
|
||||
stepInput.style.textAlign = "center";
|
||||
stepInput.style.marginRight = "5px";
|
||||
rowDiv.appendChild(stepInput);
|
||||
|
||||
const minInput = document.createElement("input");
|
||||
minInput.type = "number";
|
||||
minInput.placeholder = "Min";
|
||||
minInput.style.width = "70px";
|
||||
minInput.style.fontSize = "12px";
|
||||
minInput.style.background = "#333";
|
||||
minInput.style.color = "#fff";
|
||||
minInput.style.border = "2px solid #4b3e72";
|
||||
minInput.style.borderRadius = "6px";
|
||||
minInput.style.textAlign = "center";
|
||||
minInput.style.marginRight = "5px";
|
||||
if (typeof this.node.properties.floatMin[i] === "number") {
|
||||
minInput.value = this.node.properties.floatMin[i];
|
||||
const updateStepInputs = () => {
|
||||
stepsList.innerHTML = '';
|
||||
for (let i = 0; i < this.node.properties.activeCount; i++) {
|
||||
const rowDiv = document.createElement("div");
|
||||
rowDiv.style.display = "flex";
|
||||
rowDiv.style.justifyContent = "center";
|
||||
rowDiv.style.alignItems = "center";
|
||||
rowDiv.style.marginBottom = "5px";
|
||||
rowDiv.style.paddingBottom = "2px";
|
||||
rowDiv.style.borderBottom = "2px solid #262626";
|
||||
|
||||
// Label
|
||||
const floatLabel = document.createElement("span");
|
||||
floatLabel.textContent = `f${i + 1}`;
|
||||
floatLabel.style.color = "#fff";
|
||||
floatLabel.style.fontSize = "14px";
|
||||
floatLabel.style.marginRight = "5px";
|
||||
rowDiv.appendChild(floatLabel);
|
||||
|
||||
// Precision
|
||||
const precInput = document.createElement("input");
|
||||
precInput.type = "number";
|
||||
precInput.min = "1";
|
||||
precInput.max = "5";
|
||||
precInput.value = this.node.properties.floatPrecision[i] || 1;
|
||||
precInput.placeholder = "Prec";
|
||||
precInput.style.width = "40px";
|
||||
precInput.style.fontSize = "12px";
|
||||
precInput.style.background = "#333";
|
||||
precInput.style.color = "#fff";
|
||||
precInput.style.border = "2px solid #4b3e72";
|
||||
precInput.style.borderRadius = "6px";
|
||||
precInput.style.textAlign = "center";
|
||||
precInput.style.marginRight = "5px";
|
||||
rowDiv.appendChild(precInput);
|
||||
|
||||
// Step
|
||||
const stepInput = document.createElement("input");
|
||||
stepInput.type = "number";
|
||||
stepInput.value = this.node.properties.floatSteps[i] || 0.1;
|
||||
stepInput.style.width = "60px";
|
||||
stepInput.style.fontSize = "12px";
|
||||
stepInput.style.background = "#333";
|
||||
stepInput.style.color = "#fff";
|
||||
stepInput.style.border = "2px solid #4b3e72";
|
||||
stepInput.style.borderRadius = "6px";
|
||||
stepInput.style.textAlign = "center";
|
||||
stepInput.style.marginRight = "5px";
|
||||
rowDiv.appendChild(stepInput);
|
||||
|
||||
// Min
|
||||
const minInput = document.createElement("input");
|
||||
minInput.type = "number";
|
||||
minInput.placeholder = "Min";
|
||||
minInput.style.width = "70px";
|
||||
minInput.style.fontSize = "12px";
|
||||
minInput.style.background = "#333";
|
||||
minInput.style.color = "#fff";
|
||||
minInput.style.border = "2px solid #4b3e72";
|
||||
minInput.style.borderRadius = "6px";
|
||||
minInput.style.textAlign = "center";
|
||||
minInput.style.marginRight = "5px";
|
||||
if (typeof this.node.properties.floatMin[i] === "number") {
|
||||
minInput.value = this.node.properties.floatMin[i];
|
||||
} else {
|
||||
minInput.value = "";
|
||||
}
|
||||
rowDiv.appendChild(minInput);
|
||||
|
||||
// Max
|
||||
const maxInput = document.createElement("input");
|
||||
maxInput.type = "number";
|
||||
maxInput.placeholder = "Max";
|
||||
maxInput.style.width = "70px";
|
||||
maxInput.style.fontSize = "12px";
|
||||
maxInput.style.background = "#333";
|
||||
maxInput.style.color = "#fff";
|
||||
maxInput.style.border = "2px solid #4b3e72";
|
||||
maxInput.style.borderRadius = "6px";
|
||||
maxInput.style.textAlign = "center";
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
maxInput.value = this.node.properties.floatMax[i];
|
||||
} else {
|
||||
maxInput.value = "";
|
||||
}
|
||||
rowDiv.appendChild(maxInput);
|
||||
|
||||
stepsList.appendChild(rowDiv);
|
||||
|
||||
// Helper to set .step on min/max, and also set minInput.min
|
||||
// so that the browser uses that as the stepping base.
|
||||
const syncMinMaxStep = () => {
|
||||
let currentPrec = this.node.properties.floatPrecision[i] || 1;
|
||||
let currentStep = parseFloat(
|
||||
(this.node.properties.floatSteps[i] || 0.1).toFixed(currentPrec)
|
||||
);
|
||||
|
||||
// 1) set step for min/max
|
||||
minInput.step = currentStep.toString();
|
||||
maxInput.step = currentStep.toString();
|
||||
|
||||
// 2) set min attribute on minInput if user typed a numeric min
|
||||
const userMin = parseFloat(minInput.value);
|
||||
if (!isNaN(userMin)) {
|
||||
minInput.min = userMin.toString();
|
||||
} else {
|
||||
minInput.removeAttribute("min");
|
||||
}
|
||||
};
|
||||
|
||||
// On creation, call it once
|
||||
syncMinMaxStep();
|
||||
|
||||
// ================= EVENT HANDLERS =================
|
||||
|
||||
// Precision changed
|
||||
precInput.addEventListener("change", () => {
|
||||
let newPrec = parseInt(precInput.value, 10);
|
||||
if (isNaN(newPrec) || newPrec < 1) newPrec = 1;
|
||||
if (newPrec > 5) newPrec = 5;
|
||||
precInput.value = newPrec;
|
||||
this.node.properties.floatPrecision[i] = newPrec;
|
||||
|
||||
// Snap step to new precision
|
||||
let oldStep = parseFloat(stepInput.value);
|
||||
if (isNaN(oldStep) || oldStep <= 0) oldStep = 0.1;
|
||||
let stepVal = parseFloat(oldStep.toFixed(newPrec));
|
||||
if (stepVal <= 0) stepVal = 0.1;
|
||||
stepInput.value = stepVal;
|
||||
this.node.properties.floatSteps[i] = stepVal;
|
||||
|
||||
// Resync step + min attribute
|
||||
syncMinMaxStep();
|
||||
|
||||
// Snap max from min if both exist
|
||||
if (typeof this.node.properties.floatMin[i] === "number"
|
||||
&& typeof this.node.properties.floatMax[i] === "number") {
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
const offsetMin = this.node.properties.floatMin[i];
|
||||
let newMax = offsetMin + (stepVal * Math.round((oldMax - offsetMin) / stepVal));
|
||||
newMax = parseFloat(newMax.toFixed(newPrec));
|
||||
if (newMax !== oldMax) {
|
||||
this.showEphemeralPopup(
|
||||
`Max snapped to: ${newMax} (step from min=${offsetMin})`,
|
||||
precInput.getBoundingClientRect().left,
|
||||
precInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
this.node.properties.floatMax[i] = newMax;
|
||||
maxInput.value = newMax.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Also snap the current value
|
||||
let oldVal = this.node.properties.values[i];
|
||||
let newVal = stepVal * Math.round(oldVal / stepVal);
|
||||
newVal = parseFloat(newVal.toFixed(newPrec));
|
||||
newVal = this.clampValue(i, newVal);
|
||||
this.node.properties.values[i] = newVal;
|
||||
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
|
||||
// Step changed
|
||||
stepInput.addEventListener("change", () => {
|
||||
const newStepRaw = parseFloat(stepInput.value);
|
||||
if (isNaN(newStepRaw) || newStepRaw <= 0) {
|
||||
stepInput.value = this.node.properties.floatSteps[i] || 0.1;
|
||||
return;
|
||||
}
|
||||
const decimals = this.node.properties.floatPrecision[i] || 1;
|
||||
const stepVal = parseFloat(newStepRaw.toFixed(decimals));
|
||||
stepInput.value = stepVal;
|
||||
this.node.properties.floatSteps[i] = stepVal;
|
||||
|
||||
// Resync step + min attribute
|
||||
syncMinMaxStep();
|
||||
|
||||
// If we have both min and max, snap the max from min
|
||||
if (typeof this.node.properties.floatMin[i] === "number"
|
||||
&& typeof this.node.properties.floatMax[i] === "number") {
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
const offsetMin = this.node.properties.floatMin[i];
|
||||
let newMax = offsetMin + (stepVal * Math.round((oldMax - offsetMin) / stepVal));
|
||||
newMax = parseFloat(newMax.toFixed(decimals));
|
||||
if (newMax !== oldMax) {
|
||||
this.showEphemeralPopup(
|
||||
`Max snapped to: ${newMax} (step from min=${offsetMin})`,
|
||||
stepInput.getBoundingClientRect().left,
|
||||
stepInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
this.node.properties.floatMax[i] = newMax;
|
||||
maxInput.value = newMax.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Snap the current value
|
||||
let oldVal = this.node.properties.values[i];
|
||||
let newVal = stepVal * Math.round(oldVal / stepVal);
|
||||
newVal = parseFloat(newVal.toFixed(decimals));
|
||||
newVal = this.clampValue(i, newVal);
|
||||
this.node.properties.values[i] = newVal;
|
||||
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
|
||||
// Min changed
|
||||
minInput.addEventListener("change", () => {
|
||||
const val = parseFloat(minInput.value);
|
||||
const decimals = this.node.properties.floatPrecision[i] || 1;
|
||||
const stepVal = this.node.properties.floatSteps[i] || 0.1;
|
||||
|
||||
if (!isNaN(val)) {
|
||||
const newMin = parseFloat(val.toFixed(decimals));
|
||||
this.node.properties.floatMin[i] = newMin;
|
||||
minInput.value = newMin.toString();
|
||||
|
||||
// Also set minInput.min so the arrow stepping starts at newMin
|
||||
minInput.min = newMin.toString();
|
||||
|
||||
// If max is defined, snap it from the new min
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
let newMax = newMin + (stepVal * Math.round((oldMax - newMin) / stepVal));
|
||||
newMax = parseFloat(newMax.toFixed(decimals));
|
||||
if (newMax !== oldMax) {
|
||||
this.showEphemeralPopup(
|
||||
`Max snapped to: ${newMax} (step from min=${newMin})`,
|
||||
minInput.getBoundingClientRect().left,
|
||||
minInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
this.node.properties.floatMax[i] = newMax;
|
||||
maxInput.value = newMax.toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Clear min
|
||||
this.node.properties.floatMin[i] = undefined;
|
||||
minInput.removeAttribute("min");
|
||||
minInput.value = "";
|
||||
}
|
||||
rowDiv.appendChild(minInput);
|
||||
|
||||
const maxInput = document.createElement("input");
|
||||
maxInput.type = "number";
|
||||
maxInput.placeholder = "Max";
|
||||
maxInput.style.width = "70px";
|
||||
maxInput.style.fontSize = "12px";
|
||||
maxInput.style.background = "#333";
|
||||
maxInput.style.color = "#fff";
|
||||
maxInput.style.border = "2px solid #4b3e72";
|
||||
maxInput.style.borderRadius = "6px";
|
||||
maxInput.style.textAlign = "center";
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
maxInput.value = this.node.properties.floatMax[i];
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
|
||||
// Max changed
|
||||
maxInput.addEventListener("change", () => {
|
||||
const val = parseFloat(maxInput.value);
|
||||
const decimals = this.node.properties.floatPrecision[i] || 1;
|
||||
const stepVal = this.node.properties.floatSteps[i] || 0.1;
|
||||
const offsetMin = (typeof this.node.properties.floatMin[i] === "number")
|
||||
? this.node.properties.floatMin[i]
|
||||
: 0;
|
||||
|
||||
if (!isNaN(val)) {
|
||||
let newMax = offsetMin + (stepVal * Math.round((val - offsetMin) / stepVal));
|
||||
newMax = parseFloat(newMax.toFixed(decimals));
|
||||
if (newMax !== val) {
|
||||
this.showEphemeralPopup(
|
||||
`Max snapped to: ${newMax} (step from min=${offsetMin})`,
|
||||
maxInput.getBoundingClientRect().left,
|
||||
maxInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
}
|
||||
this.node.properties.floatMax[i] = newMax;
|
||||
maxInput.value = newMax.toString();
|
||||
} else {
|
||||
// Clear max
|
||||
this.node.properties.floatMax[i] = undefined;
|
||||
maxInput.value = "";
|
||||
}
|
||||
rowDiv.appendChild(maxInput);
|
||||
|
||||
stepsList.appendChild(rowDiv);
|
||||
|
||||
precInput.addEventListener("change", () => {
|
||||
let newPrec = parseInt(precInput.value, 10);
|
||||
if (isNaN(newPrec) || newPrec < 1) newPrec = 1;
|
||||
if (newPrec > 5) newPrec = 5;
|
||||
precInput.value = newPrec;
|
||||
this.node.properties.floatPrecision[i] = newPrec;
|
||||
|
||||
let oldStep = parseFloat(stepInput.value);
|
||||
if (isNaN(oldStep) || oldStep <= 0) oldStep = 0.1;
|
||||
let stepVal = parseFloat(oldStep.toFixed(newPrec));
|
||||
if (stepVal <= 0) stepVal = 0.1;
|
||||
stepInput.value = stepVal;
|
||||
this.node.properties.floatSteps[i] = stepVal;
|
||||
|
||||
if (typeof this.node.properties.floatMin[i] === "number") {
|
||||
const oldMin = this.node.properties.floatMin[i];
|
||||
let snappedMin = stepVal * Math.round(oldMin / stepVal);
|
||||
snappedMin = parseFloat(snappedMin.toFixed(newPrec));
|
||||
this.node.properties.floatMin[i] = snappedMin;
|
||||
minInput.value = snappedMin.toString();
|
||||
}
|
||||
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
let snappedMax = stepVal * Math.round(oldMax / stepVal);
|
||||
snappedMax = parseFloat(snappedMax.toFixed(newPrec));
|
||||
this.node.properties.floatMax[i] = snappedMax;
|
||||
maxInput.value = snappedMax.toString();
|
||||
}
|
||||
|
||||
let oldVal = this.node.properties.values[i];
|
||||
let newVal = stepVal * Math.round(oldVal / stepVal);
|
||||
newVal = parseFloat(newVal.toFixed(newPrec));
|
||||
newVal = this.clampValue(i, newVal);
|
||||
this.node.properties.values[i] = newVal;
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
|
||||
stepInput.addEventListener("change", () => {
|
||||
const newStep = parseFloat(stepInput.value);
|
||||
if (isNaN(newStep) || newStep <= 0) {
|
||||
stepInput.value = this.node.properties.floatSteps[i] || 0.1;
|
||||
return;
|
||||
}
|
||||
const decimals = this.node.properties.floatPrecision[i] || 1;
|
||||
const stepVal = parseFloat(newStep.toFixed(decimals));
|
||||
stepInput.value = stepVal;
|
||||
this.node.properties.floatSteps[i] = stepVal;
|
||||
|
||||
if (typeof this.node.properties.floatMin[i] === "number") {
|
||||
const oldMin = this.node.properties.floatMin[i];
|
||||
let steppedMin = stepVal * Math.round(oldMin / stepVal);
|
||||
steppedMin = parseFloat(steppedMin.toFixed(decimals));
|
||||
this.node.properties.floatMin[i] = steppedMin;
|
||||
minInput.value = steppedMin.toString();
|
||||
}
|
||||
|
||||
if (typeof this.node.properties.floatMax[i] === "number") {
|
||||
const oldMax = this.node.properties.floatMax[i];
|
||||
let steppedMax = stepVal * Math.round(oldMax / stepVal);
|
||||
steppedMax = parseFloat(steppedMax.toFixed(decimals));
|
||||
this.node.properties.floatMax[i] = steppedMax;
|
||||
maxInput.value = steppedMax.toString();
|
||||
}
|
||||
|
||||
let oldVal = this.node.properties.values[i];
|
||||
let newVal = stepVal * Math.round(oldVal / stepVal);
|
||||
newVal = parseFloat(newVal.toFixed(decimals));
|
||||
newVal = this.clampValue(i, newVal);
|
||||
this.node.properties.values[i] = newVal;
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
|
||||
minInput.addEventListener("change", () => {
|
||||
const val = parseFloat(minInput.value);
|
||||
if (!isNaN(val)) {
|
||||
const decimals = this.node.properties.floatPrecision[i] || 1;
|
||||
const stepVal = this.node.properties.floatSteps[i] || 0.1;
|
||||
let steppedVal = stepVal * Math.round(val / stepVal);
|
||||
steppedVal = parseFloat(steppedVal.toFixed(decimals));
|
||||
this.node.properties.floatMin[i] = steppedVal;
|
||||
minInput.value = steppedVal.toString();
|
||||
} else {
|
||||
this.node.properties.floatMin[i] = undefined;
|
||||
minInput.value = "";
|
||||
}
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
|
||||
maxInput.addEventListener("change", () => {
|
||||
const val = parseFloat(maxInput.value);
|
||||
if (!isNaN(val)) {
|
||||
const decimals = this.node.properties.floatPrecision[i] || 1;
|
||||
const stepVal = this.node.properties.floatSteps[i] || 0.1;
|
||||
let steppedVal = stepVal * Math.round(val / stepVal);
|
||||
steppedVal = parseFloat(steppedVal.toFixed(decimals));
|
||||
this.node.properties.floatMax[i] = steppedVal;
|
||||
maxInput.value = steppedVal.toString();
|
||||
} else {
|
||||
this.node.properties.floatMax[i] = undefined;
|
||||
maxInput.value = "";
|
||||
}
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
updateStepInputs();
|
||||
this.node.setDirtyCanvas(true, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
updateStepInputs();
|
||||
|
||||
|
||||
|
||||
// Create a title for the color section
|
||||
const colorSectionTitle = document.createElement("div");
|
||||
|
||||
+88
-60
@@ -52,21 +52,6 @@ class MultiIntNode {
|
||||
node.properties.intMax = [];
|
||||
}
|
||||
this.adjustArrays();
|
||||
|
||||
for (let i = 0; i < this.node.properties.activeCount; i++) {
|
||||
const stepVal = this.node.properties.intSteps[i] || 1;
|
||||
|
||||
if (typeof this.node.properties.intMin[i] === "number") {
|
||||
const oldMin = this.node.properties.intMin[i];
|
||||
const newMin = stepVal * Math.round(oldMin / stepVal);
|
||||
this.node.properties.intMin[i] = newMin;
|
||||
}
|
||||
if (typeof this.node.properties.intMax[i] === "number") {
|
||||
const oldMax = this.node.properties.intMax[i];
|
||||
const newMax = stepVal * Math.round(oldMax / stepVal);
|
||||
this.node.properties.intMax[i] = newMax;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof node.properties.bottomPadding !== "number") {
|
||||
node.properties.bottomPadding = 0;
|
||||
@@ -625,21 +610,24 @@ drawGroupLabelRow(ctx, labelObj, margin, startY, rowBgWidth) {
|
||||
|
||||
openGearPopup(e) {
|
||||
e.stopPropagation();
|
||||
// Before actually showing the popup, force min/max to resnap to step.
|
||||
|
||||
for (let i = 0; i < this.node.properties.activeCount; i++) {
|
||||
const stepVal = this.node.properties.intSteps[i] || 1;
|
||||
if (typeof this.node.properties.intMin[i] === "number") {
|
||||
const oldMin = this.node.properties.intMin[i];
|
||||
const snappedMin = stepVal * Math.round(oldMin / stepVal);
|
||||
this.node.properties.intMin[i] = snappedMin;
|
||||
}
|
||||
if (typeof this.node.properties.intMax[i] === "number") {
|
||||
// If min is set, we respect the user's exact min.
|
||||
// If max is set, we snap it from min (if min is a number).
|
||||
if (typeof this.node.properties.intMin[i] === "number"
|
||||
&& typeof this.node.properties.intMax[i] === "number") {
|
||||
const minVal = this.node.properties.intMin[i];
|
||||
const oldMax = this.node.properties.intMax[i];
|
||||
const snappedMax = stepVal * Math.round(oldMax / stepVal);
|
||||
const snappedMax = minVal + (stepVal * Math.round((oldMax - minVal) / stepVal));
|
||||
this.node.properties.intMax[i] = snappedMax;
|
||||
}
|
||||
// If min is set but we do NOT forcibly snap it, we do nothing to the user’s intMin[i].
|
||||
// If max is undefined or min is undefined, skip it altogether.
|
||||
}
|
||||
let nodeElem = this.node.htmlElement;
|
||||
|
||||
// Now open the gear popup UI
|
||||
const nodeElem = this.node.htmlElement;
|
||||
if (!nodeElem) {
|
||||
this.createGearPopupFallback(e.clientX, e.clientY);
|
||||
return;
|
||||
@@ -651,7 +639,7 @@ drawGroupLabelRow(ctx, labelObj, margin, startY, rowBgWidth) {
|
||||
popup.open(popupX, popupY);
|
||||
this.populateGearPopup(popup);
|
||||
}
|
||||
|
||||
|
||||
// CHANGED populateGearPopup
|
||||
populateGearPopup(popup) {
|
||||
const content = popup.getContentContainer();
|
||||
@@ -722,7 +710,8 @@ populateGearPopup(popup) {
|
||||
content.appendChild(stepsContainer);
|
||||
|
||||
const stepsList = document.createElement("div");
|
||||
stepsContainer.appendChild(stepsList);
|
||||
stepsContainer.appendChild(stepsList);
|
||||
|
||||
|
||||
const updateStepInputs = () => {
|
||||
stepsList.innerHTML = '';
|
||||
@@ -742,6 +731,7 @@ populateGearPopup(popup) {
|
||||
intLabel.style.marginRight = "5px";
|
||||
rowDiv.appendChild(intLabel);
|
||||
|
||||
// STEP
|
||||
const stepInput = document.createElement("input");
|
||||
stepInput.type = "number";
|
||||
stepInput.value = this.node.properties.intSteps[i] || 1;
|
||||
@@ -756,6 +746,7 @@ populateGearPopup(popup) {
|
||||
stepInput.style.marginRight = "5px";
|
||||
rowDiv.appendChild(stepInput);
|
||||
|
||||
// MIN
|
||||
const minInput = document.createElement("input");
|
||||
minInput.type = "number";
|
||||
minInput.placeholder = "Min";
|
||||
@@ -774,6 +765,7 @@ populateGearPopup(popup) {
|
||||
}
|
||||
rowDiv.appendChild(minInput);
|
||||
|
||||
// MAX
|
||||
const maxInput = document.createElement("input");
|
||||
maxInput.type = "number";
|
||||
maxInput.placeholder = "Max";
|
||||
@@ -793,6 +785,32 @@ populateGearPopup(popup) {
|
||||
|
||||
stepsList.appendChild(rowDiv);
|
||||
|
||||
// Helper to set the .step attribute and also set the "min" attribute
|
||||
// so that the stepping base is userMin, not 0
|
||||
const syncMinMaxStep = () => {
|
||||
const currentStep = this.node.properties.intSteps[i] || 1;
|
||||
// Set .step for minInput & maxInput
|
||||
minInput.step = currentStep.toString();
|
||||
maxInput.step = currentStep.toString();
|
||||
|
||||
// If the user typed a numeric min, set that as the input's .min attribute
|
||||
// so that the stepping base is from min, not 0
|
||||
const userMin = parseInt(minInput.value, 10);
|
||||
if (!isNaN(userMin)) {
|
||||
// This ensures the arrow increments from userMin
|
||||
minInput.min = userMin.toString();
|
||||
} else {
|
||||
// If min is cleared or invalid, remove the "min" attribute
|
||||
minInput.removeAttribute("min");
|
||||
}
|
||||
};
|
||||
|
||||
// On creation, call syncMinMaxStep once
|
||||
syncMinMaxStep();
|
||||
|
||||
// ================ EVENT HANDLERS ================
|
||||
|
||||
// STEP CHANGED
|
||||
stepInput.addEventListener("change", () => {
|
||||
const newStep = parseInt(stepInput.value, 10);
|
||||
if (isNaN(newStep) || newStep < 1) {
|
||||
@@ -801,70 +819,80 @@ populateGearPopup(popup) {
|
||||
}
|
||||
this.node.properties.intSteps[i] = newStep;
|
||||
|
||||
if (typeof this.node.properties.intMin[i] === "number") {
|
||||
const oldMin = this.node.properties.intMin[i];
|
||||
const steppedMin = newStep * Math.round(oldMin / newStep);
|
||||
if (steppedMin !== oldMin) {
|
||||
this.showEphemeralPopup(
|
||||
`Min not aligned with step. Using stepped value: ${steppedMin}`,
|
||||
stepInput.getBoundingClientRect().left,
|
||||
stepInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
}
|
||||
this.node.properties.intMin[i] = steppedMin;
|
||||
minInput.value = steppedMin.toString();
|
||||
}
|
||||
// Sync .step attribute on min/max and update stepping base
|
||||
syncMinMaxStep();
|
||||
|
||||
if (typeof this.node.properties.intMax[i] === "number") {
|
||||
// We do NOT snap the Min itself; we leave the user's min alone.
|
||||
// But if there is a max, we snap it from min as the base offset.
|
||||
const curMin = this.node.properties.intMin[i];
|
||||
if (typeof curMin === "number" && typeof this.node.properties.intMax[i] === "number") {
|
||||
const oldMax = this.node.properties.intMax[i];
|
||||
const steppedMax = newStep * Math.round(oldMax / newStep);
|
||||
if (steppedMax !== oldMax) {
|
||||
const snappedMax = curMin + (newStep * Math.round((oldMax - curMin) / newStep));
|
||||
if (snappedMax !== oldMax) {
|
||||
this.showEphemeralPopup(
|
||||
`Max not aligned with step. Using stepped value: ${steppedMax}`,
|
||||
`Max snapped to: ${snappedMax} (step from min=${curMin})`,
|
||||
stepInput.getBoundingClientRect().left,
|
||||
stepInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
this.node.properties.intMax[i] = snappedMax;
|
||||
maxInput.value = snappedMax.toString();
|
||||
}
|
||||
this.node.properties.intMax[i] = steppedMax;
|
||||
maxInput.value = steppedMax.toString();
|
||||
}
|
||||
});
|
||||
|
||||
// MIN CHANGED
|
||||
minInput.addEventListener("change", () => {
|
||||
const val = parseInt(minInput.value, 10);
|
||||
if (!isNaN(val)) {
|
||||
const stepVal = this.node.properties.intSteps[i] || 1;
|
||||
const steppedVal = stepVal * Math.round(val / stepVal);
|
||||
if (steppedVal !== val) {
|
||||
this.showEphemeralPopup(
|
||||
`Min not aligned with step. Using stepped value: ${steppedVal}`,
|
||||
minInput.getBoundingClientRect().left,
|
||||
minInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
// Accept user’s min as typed
|
||||
this.node.properties.intMin[i] = val;
|
||||
// Update the .min attribute so the stepping base changes
|
||||
minInput.min = val.toString();
|
||||
|
||||
// Then if there's a max, snap that from the new min
|
||||
if (typeof this.node.properties.intMax[i] === "number") {
|
||||
const oldMax = this.node.properties.intMax[i];
|
||||
const stepVal = this.node.properties.intSteps[i] || 1;
|
||||
const snappedMax = val + (stepVal * Math.round((oldMax - val) / stepVal));
|
||||
if (snappedMax !== oldMax) {
|
||||
this.showEphemeralPopup(
|
||||
`Max snapped to: ${snappedMax} (step from min=${val})`,
|
||||
minInput.getBoundingClientRect().left,
|
||||
minInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
this.node.properties.intMax[i] = snappedMax;
|
||||
maxInput.value = snappedMax.toString();
|
||||
}
|
||||
}
|
||||
this.node.properties.intMin[i] = steppedVal;
|
||||
minInput.value = steppedVal.toString();
|
||||
} else {
|
||||
// if user clears it, we set intMin[i] = undefined
|
||||
this.node.properties.intMin[i] = undefined;
|
||||
// remove stepping base
|
||||
minInput.removeAttribute("min");
|
||||
minInput.value = "";
|
||||
}
|
||||
});
|
||||
|
||||
// MAX CHANGED
|
||||
maxInput.addEventListener("change", () => {
|
||||
const val = parseInt(maxInput.value, 10);
|
||||
if (!isNaN(val)) {
|
||||
const stepVal = this.node.properties.intSteps[i] || 1;
|
||||
const steppedVal = stepVal * Math.round(val / stepVal);
|
||||
if (steppedVal !== val) {
|
||||
const curMin = (typeof this.node.properties.intMin[i] === "number")
|
||||
? this.node.properties.intMin[i]
|
||||
: 0; // If min is not set, treat 0 as base
|
||||
const snappedMax = curMin + (stepVal * Math.round((val - curMin) / stepVal));
|
||||
if (snappedMax !== val) {
|
||||
this.showEphemeralPopup(
|
||||
`Max not aligned with step. Using stepped value: ${steppedVal}`,
|
||||
`Max snapped to: ${snappedMax} (step from min=${curMin})`,
|
||||
maxInput.getBoundingClientRect().left,
|
||||
maxInput.getBoundingClientRect().top - 25
|
||||
);
|
||||
}
|
||||
this.node.properties.intMax[i] = steppedVal;
|
||||
maxInput.value = steppedVal.toString();
|
||||
this.node.properties.intMax[i] = snappedMax;
|
||||
maxInput.value = snappedMax.toString();
|
||||
} else {
|
||||
// Clear
|
||||
this.node.properties.intMax[i] = undefined;
|
||||
maxInput.value = "";
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "The-AI-Doctors-Clinical-Tools"
|
||||
description = "Nodes: Multi Int and Multi Text; allows for the creation of multiple int, floats and string storage and output from a single node."
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user