Sindbad~EG File Manager
class Parameter {
constructor(name, value, increment, index) {
this.name = name;
this.value = value;
this.increment = increment;
this.index = index;
}
}
let parameters; // defined globally so it will be accessible
function nextPowerOf10(t) {
const value = 2 * t;
const nextPower = Math.ceil(Math.log10(value));
return Math.pow(10, nextPower);
}
// adjustParameter puts up a popup with lower left corner at x,y in window coordinates
// That popup allows the user to adjust one parameter's value and increment.
// index is the index of that parameter in the parameters array.
// You have to load translateEnterPages.js before this file, to enable translations
function allowOnlyDigits(event) {
event.target.value = event.target.value.replace(/\D/g, ''); // Remove any non-digit characters
}
function allowOnlyNumbers(event) {
// Allow control keys like Backspace, Delete, Arrow keys
if (event.ctrlKey || event.metaKey || ["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key)) {
return true;
}
// Allow only digits and a single decimal point
if (!/^[0-9.]$/.test(event.key)) {
event.preventDefault();
return false; // Explicitly return false to stop the event
}
// Prevent multiple decimal points
if (event.key === "." && event.target.value.includes(".")) {
event.preventDefault();
return false; // Explicitly return false to stop the event
}
return true; // Allow valid input
}
function blockPlusMinusKeys(event) {
if (event.key === "+" || event.key === "-" || event.key === '=') {
event.preventDefault();
event.stopPropagation(); // Ensure it doesn't reach other listeners
}
}
function preventParameterChange(event) {
if (event.key === "+" || event.key === "-") {
event.preventDefault();
event.stopPropagation();
}
}
function adjustParameter(x, y, index, container) {
// Create the parameters array. This function isn't called until
// the DOM has loaded, and there must be parameters, so jsonParameterData will exist.
console.log("in adjustParameter with ", x, y);
if (!parameters) {
parameters = jsonParameterData.parameters || [];
console.log("oops! Shouldn't happen")
}
// Find the parameter with the matching index field
const parameter = parameters.find(param => param.index === index);
// Check if a matching parameter was found
if (!parameter) {
console.error(`No parameter found with index: ${index}`);
return;
}
let pvalue = parameter.value; // old value
let pincrement = parameter.increment; // old increment
let pname = parameter.name;
let integerNames = ["n","N","m","M","k","K"];
let ptype = integerNames.includes(pname) ? 0 : 1; // 0 means integer, 1 means real
// Disable the global event listener for + and -
window.addEventListener("keydown", blockPlusMinusKeys, true);
// Create a "Done" button to close the dialog
const doneButton = document.createElement('button');
doneButton.textContent = translations[languageName].done;
doneButton.style.display = 'block';
doneButton.style.margin = '0 auto';
doneButton.id = "donebutton"; // needed for translation
function handleEnterKey(event) {
if (event.key === "Enter") {
event.preventDefault(); // Prevents any accidental form submission or new lines
doneButton.click(); // Simulates clicking the Done button
}
}
// Add event listener to detect Enter key press
window.addEventListener("keydown", handleEnterKey);
// Add event listener to re-enable key handlers when the popup is closed
doneButton.addEventListener('click', () => {
popup.remove();
window.removeEventListener("keydown", blockPlusMinusKeys, true); // Re-enable keys after closing
window.removeEventListener("keydown", handleEnterKey); // Clean up event listener
});
// Create a container for the sliders and the Done button
const popup = document.createElement('div');
popup.className = 'popup-container'; // Assign the CSS class
// Position the popup using the x and y coordinates
popup.style.position = 'absolute';
popup.style.left = `${x}px`;
popup.style.top = `${y}px`;
popup.style.zIndex = '5000'; // Ensure it appears above other elements
popup.style.display = 'flex';
popup.style.flexDirection = 'column';
const valueLabel = document.createElement('label');
valueLabel.innerHTML = `<em>${parameter.name}</em> = `;
valueLabel.style.marginBottom = '5px';
valueLabel.style.display = 'block';
valueLabel.style.width = 'auto';
valueLabel.style.whiteSpace = 'nowrap'; // ✅ Forces it to stay on one line
valueLabel.style.paddingLeft = '63px';
valueLabel.style.boxSizing = 'border-box'; // to count the padding as part of the width
valueLabel.style.textAlign = 'right';
// Create the input field for the parameter value
const valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.id = 'valueInput';
if(ptype == 0){
valueInput.pattern="\d*";
valueInput.inputmode="numeric";
valueInput.addEventListener("input", allowOnlyDigits);
valueInput.value = parameter.value;
// <input type="text" id="digitInput" pattern="\d*" inputmode="numeric">
}
else{
//<input type="text" id="numberInput" onkeydown="return allowOnlyNumbers(event)">
valueInput.addEventListener('keydown', allowOnlyNumbers);
valueInput.value = parameter.value.toString(); // Preserve all decimals exactly
}
valueInput.style.marginBottom = '5px';
valueInput.style.display = 'block';
valueInput.style.boxSizing = 'border-box';
valueInput.style.border = '1px solid #ccc';
valueInput.style.fontSize = 'inherit'; // Match the surrounding text
valueInput.style.flex = '1'; // Input field takes up the remaining space
valueInput.style.minWidth = '50px';
valueInput.style.textAlign = 'left';
valueInput.style.flexGrow = '1'; // Allows input to expand while label stays fixed
// Event listener to update parameter when user types a valid number
valueInput.addEventListener('change', function () {
if (ptype == 0) {
parameter.value = valueInput.value;
}
else{
const parsedValue = parseFloat(valueInput.value.replace(/,/g, '')); // Remove formatting commas
if (!isNaN(parsedValue)) {
parameter.value = parsedValue;
console.log("Parameter updated:", parameter.value);
} else {
alert("Please enter a valid number.");
valueInput.value = parameter.value; // Reset to last valid value
}
}
});
const valueSlider = document.createElement('input');
valueSlider.type = 'range';
// Calculate t as the absolute value of the current parameter value
const t = Math.abs(parameter.value);
const T = nextPowerOf10(t);
// Modify the value slider range (modifying the min, max, and value)
if(ptype == 0){
valueSlider.min = 1;
if(T < 200)
valueSlider.step = 1;
else
valueSlider.step = parseInt(T/100); // so it's at least 1
}
else{
valueSlider.min = -T; // Set minimum to -t (as a string for the input element)
valueSlider.step = T * 0.02;
}
valueSlider.max = T; // Set maximum to t (as a string for the input element)
valueSlider.value = parameter.value;
console.log("setting slider.value to ", parameter.value);
valueSlider.style.width = '100%';
valueSlider.style.marginBottom = '10px';
valueSlider.id = 'parameterSlider';
valueSlider.addEventListener('input', function () {
if(ptype == 0){
parameter.value = valueSlider.value;
valueInput.value = valueSlider.value;
}
else{
parameter.value = parseFloat(valueSlider.value);
valueInput.value = parameter.value.toFixed(2); // Update input field
}
console.log("Parameter updated from slider:", parameter.value);
});
// Create the label and slider for the increment
const incrementLabel = document.createElement('label');
const S = nextPowerOf10(parameter.increment);
const changes = translations[languageName].changes;
const by = translations[languageName].by;
incrementLabel.innerHTML = `+/- ${changes} <em>${parameter.name}</em> ${by} `;
incrementLabel.style.marginBottom = '5px';
incrementLabel.style.display = 'block';
incrementLabel.style.width = '100%';
const incrementSlider = document.createElement('input');
incrementSlider.type = 'range';
incrementSlider.max = S;
if(ptype == 0){
incrementSlider.min = 1;
incrementSlider.step = 1;
}
else{
incrementSlider.min = 0;
incrementSlider.step = S * 0.01;
}
incrementSlider.value = parameter.increment;
incrementSlider.style.width = '100%';
incrementSlider.style.marginBottom = '10px';
incrementSlider.id = 'incrementSlider'
// Create an input field for the increment
const incrementInput = document.createElement('input');
incrementInput.id = 'incrementInput';
incrementInput.type = 'text';
if(ptype == 0){
incrementInput.pattern="\d*";
incrementInput.inputmode="numeric";
incrementInput.value = parameter.increment;
// <input type="text" id="digitInput" pattern="\d*" inputmode="numeric">
incrementInput.addEventListener("input", allowOnlyDigits);
}
else{
incrementInput.addEventListener('keydown', allowOnlyNumbers);
incrementInput.value = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 10,
}).format(parameter.increment);
}
incrementInput.style.marginBottom = '5px';
incrementInput.style.display = 'block';
incrementInput.style.boxSizing = 'border-box';
incrementInput.style.border = '1px solid #ccc';
incrementInput.style.fontSize = 'inherit'; // Match the surrounding text
incrementInput.style.flex = '1'; // Input field takes up the remaining space
incrementInput.style.minWidth = '50px';
incrementInput.style.textAlign = 'left';
incrementInput.style.flexGrow = '1'; // Allows input to expand while label stays fixed
// Event listener to update increment when user types a valid number
incrementInput.addEventListener('change', function () {
if(ptype == 0){
incrementInput.value = 1; // without making a fuss
console.log("got here");
if (incrementInput.value === "0") {
incrementInput.value = "1"; // Clear the input field. But this only works when the focus changes, not immediately
}
parameter.increment = parseInt(incrementInput.value);
}
else{
const parsedValue = parseFloat(incrementInput.value.replace(/,/g, '')); // Remove formatting commas
if (isNaN(parsedValue) || parsedValue <= 0) {
incrementInput.value = parameter.increment; // Reset to last valid value
} else {
parameter.increment = parsedValue;
console.log("Increment updated:", parameter.increment);
}
}
});
incrementInput.addEventListener('input', function () {
if (ptype === 0) { // Integer parameters (n, m, k)
if (this.value === "0") {
this.value = "1"; // Force it to 1
} else if (/^0\d+/.test(this.value)) {
this.value = this.value.replace(/^0+/, ''); // Remove leading zeros
}
} else { // Real-valued parameters
if (this.value === "0") {
this.value = ""; // Prevent standalone zero
}
// Ensure leading zeros are only removed for whole numbers, not decimals
else if (/^0\d+/.test(this.value) && !this.value.startsWith("0.")) {
this.value = this.value.replace(/^0+/, '');
}
}
});
// Create a container for the value label and input field
const valueContainer = document.createElement('div');
valueContainer.style.display = 'flex';
valueContainer.style.alignItems = 'center'; // Align text and input
valueContainer.style.gap = '5px'; // Small spacing between them
valueContainer.style.width = '100%';
valueContainer.style.justifyContent = 'center'; // Center the pair
valueContainer.style.margin = '0 auto'; // Ensures centering in the parent
// Append the label and input to this container
valueContainer.appendChild(valueLabel);
valueContainer.appendChild(valueInput);
// Create a container for the increment label and input field
const incrementContainer = document.createElement('div');
incrementContainer.style.display = 'flex';
incrementContainer.style.alignItems = 'center'; // Align text and input
incrementContainer.style.gap = '5px'; // Small spacing between them
incrementContainer.style.width = '100%';
incrementContainer.style.justifyContent = 'center'; // Center the pair
incrementContainer.style.margin = '0 auto'; // Ensures centering in the parent
// Append the label and input to this container
incrementContainer.appendChild(incrementLabel);
incrementContainer.appendChild(incrementInput);
// Append the containers to the popup
popup.style.alignItems = 'center'; // Ensures everything inside is centered
popup.appendChild(valueContainer);
popup.appendChild(valueSlider);
popup.appendChild(incrementContainer);
popup.appendChild(incrementSlider);
popup.appendChild(doneButton);
// Append the popup
container.appendChild(popup);
AddEventListeners();
// Update the parameter value when the user adjusts the valueSlider
valueSlider.addEventListener('input', () => {
if(ptype == 0)
parameter.value = valueSlider.value;
else{
parameter.value = parseFloat(valueSlider.value);
valueInput.value = parameter.value.toFixed(2); // Update the input field's value
}
});
// Update the parameter increment when the user adjusts the incrementSlider
incrementSlider.addEventListener('input', () => {
parameter.increment = parseFloat(incrementSlider.value);
console.log("parameter is ", parameter);
incrementInput.value = parameter.increment;
console.log(`Parameter ${index} increment updated to: ${parameter.increment}`);
});
// Remove the popup when the "Done" button is clicked
// Remove the popup and update parameters when the "Done" button is clicked
doneButton.addEventListener('click', () => {
popup.remove();
// Check if parameters[index] has had its value and/or increment adjusted
console.log("New increment: ", parameters[index].increment);
if(parameters[index].value == pvalue && parameters[index].increment != pincrement)
{ // then the screen doesn't change, so before there were parameter sliders, I could just send an Ajax message.
// But now, if the increment changes, the Engine will need to send back the new increment and a bunch of
// new invisible graphs for the slider to show. The old code is in record4.txt dated 3.10.25
updateParameters();
}
if(parameters[index].value != pvalue)
{ // then the screen must change, so we have to submit a hidden form with the
// new values of the parameters and their increments
updateParameters();
}
// and if nothing has changed, do nothing.
});
}
function updateParameters()
// this is called when adjustParameter terminates and a parameter value has changed.
// Submit a hidden form to PHP_SELF, which (when processed) will send an "update Parameters" message to the Engine.
{
// Assuming `parameters` is a global array of `Parameter` objects
let parametersString = parameters.map(param => `${param.name}#${param.value}#${param.increment}#${param.index}`).join(';');
// example, "a#1.23#0.1#0;b#4.56#0.2#1"
console.log("in updateParameters with ", parametersString);
// Create a hidden form element
const form = document.createElement('form');
form.method = 'POST';
form.action = window.location.href; // PHP_SELF equivalent
// Create a hidden input field for the JSON data
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.id = 'parameters';
hiddenField.name = 'parametersField';
hiddenField.value = parametersString;
// Append the hidden field to the form
form.appendChild(hiddenField);
// Append the form to the body (it must be in the DOM to be submitted)
document.body.appendChild(form);
// Submit the form
form.submit();
}
function AddEventListeners() {
const slider = document.getElementById('parameterSlider');
const incrementSlider = document.getElementById('incrementSlider');
const incrementInputField = document.getElementById('incrementInput');
const inputField = document.getElementById('valueInput');
const doneButton = document.getElementById('donebutton'); // New Done button
console.log("slider is ", slider);
// Function to update increment from slider
function updateIncrementFromSlider() {
incrementValue = parseFloat(incrementSlider.value);
incrementInputField.value = ''; // Clear input field when using the slider
// console.log("Increment updated from slider:", parameterValue);
}
// Function to update parameter from input field
function updateParameterFromInput() {
const inputValue = inputField.value.trim();
const parsedValue = parseFloat(inputValue);
if (!isNaN(parsedValue)) {
parameterValue = parsedValue;
slider.value = parameterValue; // Sync slider
// console.log("Parameter updated from input:", parameterValue);
} else {
alert("Please enter a valid number."); // can never happen as only numbers can be input
}
}
// Function to update increment from input field
function updateIncrementFromInput() {
const inputValue = incrementInputField.value.trim();
const parsedValue = parseFloat(inputValue);
if (!isNaN(parsedValue)) {
incrementValue = parsedValue;
incrementSlider.value = incrementValue; // Sync slider
// console.log("Increment updated from input:", incrementValue);
} else {
alert("Please enter a valid number.");
}
}
// Event listener for the increment slider
incrementSlider.addEventListener('input', updateIncrementFromSlider);
// Event listeners for the "Done" button
doneButton.addEventListener('click', function () {
updateParameterFromInput();
updateIncrementFromInput();
});
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists