Sindbad~EG File Manager

Current Path : /home/beeson/public_html/WebMathXpert/
Upload File :
Current File : //home/beeson/public_html/WebMathXpert/GraphDoc.php

<?php
header('Content-Type: text/html; charset=UTF-8');
// Set HTTP header to prevent caching
header("Cache-Control: no-cache, must-revalidate");

if ($_SERVER['SERVER_NAME'] == 'localhost') {
    $serverAddress = 'localhost';  
    $username = $password = null; 
	 set_time_limit(3600); // Allow the script to run for up to 3600 seconds for debugging
} else {
    $serverAddress = 'mathxpert.org'; 
    $username = 'beeson'; 
    $password = 'Turing2024';
}

ini_set('display_errors', 1);
ini_set('session.cookie_samesite', 'None');
ini_set('session.cookie_secure', '1');  // Required for SameSite=None
// Start or resume the session
// Ensure we're using the correct session ID before starting the session
if (isset($_POST["PHPSESSID"])) {
    session_id($_POST["PHPSESSID"]);  // Use the session ID from the form
}
session_start();
$sessionId = session_id();  // guaranteed not to contain a pipe character

error_reporting(E_ALL);

$serverPort = 12349; // Adjust the server port. Ending in 9 for the Engine;  in 7 for Polygon
$timeout = 3600; // Connection timeout in seconds. If the server does not respond by then, close the socket.
$startupDelay = 5; // Delay for server startup in seconds if the server is not already running
$imageBaseDir = "/Users/beeson/Dropbox/MathXpert/images/";

if (!(isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) {
    $redirect = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    header('HTTP/1.1 301 Moved Permanently');
    header('Location: ' . $redirect);
    exit();
}
 
require("SendMessage.php");
$clientSocket = createClientSocket($serverAddress, $serverPort, $timeout);

// Handle  Ajax requests before emitting  HTML 
if ($_SERVER['REQUEST_METHOD'] === 'POST')
{  $response = "nothing";
	if ( isset($_POST['graphTitleMovedParam'])) 
		{
			$param = $_POST['graphTitleMovedParam']; 
		    // Process the AJAX request
			//$response = sendMessage($clientSocket,"graphTitleMoved",$param); 
   	   // We do not care what the response is as long as it is not false
		}   
	if ( isset($_POST['askPointSlopeParam'])) 
		{ 
			$param = $_POST['askPointSlopeParam']; 
		    // Process the AJAX request
			$pointSlopeResponse = sendMessage($clientSocket,"askPointSlope",$param);
			echo($pointSlopeResponse);
			exit; // without this, the entire document GetProb.php produces with this $_POST 
					// would be sent to the listener waiting for the Ajax result. 
		}  
 	else if ($response != "nothing")
    {    // $response is the response to the Ajax request
   		// echo($response);  // this will display an error message for the developer if an incorrect parameter was sent.
   		 exit; // Stop further processing, so the rest of the HTML isn't sent
    }
   
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, maximum-scale=1.0, user-scalable=0">
    <title>MathXpert Grapher</title>
    <style>
		 body {
	      user-select: none; 
	      overflow: hidden; /* Disable scrollbars for the entire page */
		 }
		body, html {
		    margin: 0;
		    padding: 0; 
		    height: 100%;
		    border: none;
		}
        /* Set a global font-family rule for all text elements. Only Times New Roman actually works well. */
        text {
            font-family: 'Times New Roman', 'STIX Two Math', 'Cambria Math', 'Latin Modern Math', 'TeX Gyre Termes Math';
        }
	    svg text {
	          font-family: 'Times New Roman';
	    }
		 /* Ensure Arial for text inside elements with the class svg-button */
		 svg-button text, .svg-button svg text {
		 font-family: 'Arial';
		 }
		 
		 
		/* the following container with horizontal flow will hold the Toolbar and the svgContainer, 
		 so the svgContainer will come to the right of the Toolbar */
		.container {
		  display: flex; 
		  height: 100%;
		  align-items: flex-start;  /*  begin each column at the top  */
		  /* the first column contains the Toolbar, so this gets the Toolbar at the top.
		  position:relative; */
		  top: 0;
		  left: 0;
		}
		/* Define svgContainer div so it comes to the right of the Toolbar.
			We have to have either absolute or relative positioning here so that the SVG output of the Engine will be 
			interpreted relative to svgContainer.   If we use position:absolute,  then we must also specify left and top. 
			Using postion:relative allows flex layout in .container to place svgContainer to the 
			right of the Toolbar without hardcoding the width of the Toolbar.
		*/
			
		#svgContainer {
			position:relative; 
			overflow: hidden; /* Prevent scrollbars appearing when there's a wheel event  */
			z-index: 1;
			text-shadow: none;
		}
		
		.popup svg {
		    width: 100%;
		    height: auto;
		    word-wrap: break-word;
		    white-space: pre-wrap;
		}
			
		#titleRect {
			position: absolute;
			cursor: move;
			border: none;
			background-color: transparent;
			text-shadow: none;
		} 
	
		[id^="draggableTitle"] { /* applies to draggableTitle0,  draggableTitle1, etc. */
			position: absolute;
			cursor: move;
			border: 1px solid rgb(0,0,128);
			background-color: transparent;  // will be overwritten with inline style
			display: none;  // will be changed when used;
			text-shadow: none;
			padding: 5px;
			color: black;
			top: 10px;
			left: 10px; /* Initial position */
			z-index: 2; /* Ensure it's above other elements */ 
		 }
		 
 		#PointSlopeDiv { 
 			position: absolute;
 			cursor: move;
 			border: 5px;
 			background-color: transparent;
			border-color: transparent;
 			text-shadow: none;
 			padding: 5px;
 			color: black;
 			top: 50px;
 			left: 50px; /* Initial position */
			width:auto;
			height:auto;
 			z-index: 2; /* Ensure it's above other elements */
 		 }
		
		#crosshairs-container {
			position: absolute;
			top: 0;
			left: 0;
			display:none;
			background-color:transparent;
			pointer-events: none; /* Allow click events to pass through */
		}
		
		#selectionRectangle {
			position: absolute;
			border: 1px solid red;  
			pointer-events: none; /* Ignore this element during mouse events */
		}
		body {
			background-color:none;
		}

		/* Style for the modal */
		#hintModal {
			display: none;
			position: fixed;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background: rgba(0, 0, 0, 0);
			justify-content: center;
			align-items: center;
		}

		/* Style for the modal content */
		#modalContent {
			background: rgb(200,200,255);
			padding: 20px;
			border-radius: 5px;
			width: 50%;
			height: 50%;
			box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
		}
	 </style>
	 <style>
		.popup-container {
			position: absolute;
			background-color: lightblue;
			border: 1px solid black;
			padding: 10px;
			box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
			z-index: 5000; /* Ensure it appears above other elements */
			display: flex;
			flex-direction: column;
			align-items: center; /*  align labels and sliders */
			width: 180px; /* Set a fixed width to prevent resizing and flicker */ 
			pointer-events: all; /* Make sure the popup can capture events */
		}

		.popup-container label {
			margin-bottom: 5px;
			text-align:left;
			width: 100%;
			display: block;  
			background-color: lightblue;
		}

		.popup-container input[type="range"] {
			width: 100%;
			margin-bottom: 10px; 
			pointer-events: all;
		}

		.popup-container button {
			display: block;
			margin: 0 auto; 
			background-color:lightyellow !important;
		}
	</style>
	<style>
		#customContextMenu {
			font-family: Arial, sans-serif;
		}
	</style>
	<style>
	    .ODEhint {
	        position: absolute;
	        top: 10px;
	        left: 10px;
	        padding: 5px 10px;
	        background-color: rgba(255, 255, 255, 0.8);
	        border: 1px solid #ccc;
	        border-radius: 4px;
	        font-size: 14px;
	        color: #333;
	        pointer-events: none; /* Allow clicks to pass through */ 
	        opacity:0
	        /*transition: opacity 0.5s ease; */
	    }
	</style>
	<style>
	/* Default white knob (inactive slider) */
	input[type="range"]::-webkit-slider-thumb {
	    -webkit-appearance: none;
	    width: 20px;
	    height: 20px;
	    background: white;
	    border: 2px solid black;
	    border-radius: 50%;
	    cursor: pointer;
	}

	/* Lighter green knob for the active slider */
	input[type="range"].active-slider::-webkit-slider-thumb {
	    background: #90EE90; /* Light green (a softer contrast than dark green) */
	}

	/* Firefox */
	input[type="range"]::-moz-range-thumb {
	    width: 20px;
	    height: 20px;
	    background: white;
	    border: 2px solid black;
	    border-radius: 50%;
	    cursor: pointer;
	}

	input[type="range"].active-slider::-moz-range-thumb {
	    background: #90EE90; /* Light green */
	}
	</style>
</head>


<?php

if (!(isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || 
   $_SERVER['HTTPS'] == 1) ||  
   isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&   
   $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'))
{
   $redirect = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
   header('HTTP/1.1 301 Moved Permanently');
   header('Location: ' . $redirect);
   exit();
}
?>
<body>
<script src="DocumentID.js"></script> 
<script src="TranslateEnterPages.js"></script>
<script src="GraphPaperButton.js"></script> 
<script src="Keyboard.js"></script>
<script src="FetchMessage.js"></script>
<script src="AdjustParameter.js"></script>
<script src="ParameterSlider.js"></script>
<script src="ContextMenu.js"></script>
<?php


function Olddecode($response,$fillcolor){
    // To save bandwidth we've compressed some SVG commands, now expand them
	// This only saves bandwidth between the Engine and the PHP code, not between the web server and browser,
	// but otherwise the 1 megabyte limit on messages between the Engine and PHP is broken, i.e., the length of 
	// messages needed to draw these graphs is more than 6 digits.   It's easier 
	// just to do this decoding than alter the basic Engine message mechanisms. 
	
	$pattern = '/<q (-?\d+) (-?\d+)>/';
	return  preg_replace_callback(
		$pattern,
		function ($matches) use ($fillcolor) { // Use "use" to inherit $fillcolor
			$x = $matches[1] - 0.5;
			$y = $matches[2] - 0.5;
			return "<circle cx=\"{$x}\" cy=\"{$y}\" r=\"1\" style=\"fill:{$fillcolor};stroke:none;\" />\n";
		},
		$response
	); 
}

// following version of decode avoids regular expression matching,  which uses too much memory. 
// it echoes the result rather than returning it.
function decode($response, $fillcolor) {
    // Initialize the output buffer
    $output = '';
    
    // Define the tag we're looking for
    $tag = '<q ';
    $tagLength = strlen($tag);
    
    // Position in the string
    $pos = 0;
    $length = strlen($response);
    
    // Loop through the response and process each occurrence of the tag
    while (($start = strpos($response, $tag, $pos)) !== false) {
        // Output the part of the response before the tag
        echo (substr($response, $pos, $start - $pos));
        
        // Find the closing '>'
        $end = strpos($response, '>', $start);
        if ($end === false) {
            // If no closing '>', stop processing and output the rest
            echo(substr($response, $start));
            break;
        }
        
        // Extract the coordinates
        $coords = substr($response, $start + $tagLength, $end - $start - $tagLength);
        list($x, $y) = explode(' ', $coords);
        
        // Adjust the coordinates
        $x = $x - 0.5;
        $y = $y - 0.5;
        
        // Add the decoded circle element to the output
         echo ("<circle cx=\"{$x}\" cy=\"{$y}\" r=\"1\" style=\"fill:{$fillcolor};stroke:none;\" />\n");
        
        // Move the position past this tag
        $pos = $end + 1;
    }
    
    // Output the remaining part of the response (after the last tag)
    if ($pos < $length) {
        echo (substr($response, $pos));
    }
}

 
function decode2($response){
	$pattern2 = '/<Q (-?\d+) (-?\d+) (\d+) (\d+)\s*\/>/';
		return preg_replace_callback(
			$pattern2,
			function ($matches) {
				$x = $matches[1];
				$y = $matches[2];
				$w = $matches[3];
				$h = $matches[4];
				return "<rect x=\"{$x}\" y=\"{$y}\" width=\"{$w}\" height=\"{$h}\" />\n";
			},
			$response
		);
}
?>

<?php
	if ($_SERVER["REQUEST_METHOD"] === "POST") 
	{	if(isset($_SESSION['windowWidth']))  
		 // in case width and height don't get set below, let's make sure they are set.
			$width = $windowWidth = $_SESSION['windowWidth'] ; 
		if(isset($_SESSION['graphWidth'])) 
				$graphwidth = $_SESSION['graphWidth'];
		if(isset($_SESSION['height']))
			$height =  $_SESSION['height'];
		$fillcolor = "rgb(128,128,255)"; 
		if(isset($_SESSION['toolbarwidth']))
		   {  $toolbarwidth = $_SESSION['toolbarwidth'];    //  it's 66 at this point 
		   }		
		if (isset($_POST['problemnumberField'])) 
			//  this means the problemsource is SOURCE_MATHXPERT, as typed-in problems don't have numbers
			{
				$problemNumber = intval($_POST["problemnumberField"]);
				$topicNumber =  intval($_POST["topicField"]);
				$sender = "GetProblem";
				$language = $_POST["language"]; 
				$graphwidth = $_SESSION['graphWidth'] = intval($_POST["widthField"]);  // width of the browser window minus toolbar 
				$toolbarwidth = $_SESSION['toolbarwidth'] = intval($_POST["toolbarwidthField"] ); 
				$height = $_SESSION['height'] = intval($_POST["heightField"]); // height of the browser window
				$_SESSION['height'] = $height;
				$_SESSION['topicNumber'] = $topicNumber;  
				$_SESSION['problemNumber'] = $problemNumber; 
			}
		if (isset($_POST["widthField2"]))  // 'graphButton_x'  doesn't work!
			{
				$sender = "graphButton";
				$graphwidth =  $_SESSION['graphWidth'] = intval($_POST["widthField2"]); 
				$toolbarwidth = $_SESSION['toolbarwidth'] = intval($_POST["toolbarwidthField"]); 
				$height = $_SESSION['height'] = intval($_POST["heightField2"]);  
				$windowWidth = $graphwidth + $toolbarwidth;
				$width = $windowWidth;  
				// echo("width = $width and toolbarwidth = $toolbarwidth and graphwidth = $graphwidth");  
				$language = $_POST["language"];  
				$sessionID = $_POST["PHPSESSID"];
			} 
		if (isset($_POST["widthField3"]))  // from EnterOrdinaryGraph.php or other EnterSomething.php files
			{
				$sender = "drawAll";
				$graphwidth =  $_SESSION['graphWidth'] = intval($_POST["widthField3"]); 
				$toolbarwidth = $_SESSION['toolbarwidth'] = intval($_POST["toolbarwidthField"]); 
				$height = $_SESSION['height'] = intval($_POST["heightField3"]);  
				$windowWidth = $graphwidth + $toolbarwidth;
				$width = $windowWidth;  
					// echo("width = $width and toolbarwidth = $toolbarwidth and graphwidth = $graphwidth"); 
				$language = $_POST["language"];  
			}
		if (isset($_POST['language2']))
		{
			$sender = "setLanguage";
			$language =  $languageName = $_POST['language2'];
		} 
		if (isset($_POST['languageName']))
		{
			$sender = "setLanguage";
			$languageName = $_POST['languageName'];
		}
		if (isset($_POST['action'])) {
		    $sender = $_POST['action'];
		    // Now, $sender should contain something like "parameterplus0"
		} 
		if(isset($_POST['newActiveParameter']))
		{  
			$sender = "activeParameterChanged"; 
			$newActiveParameter = $_POST['newActiveParameter'];
		}
		$resetSliderColors = isset($_POST['newActiveParameter']);
		if (isset($_POST['horizontalzoomout']))
		{
			$sender = "horizontalzoomout";
		}
		if (isset($_POST['horizontalzoomin']))
		{
			$sender = "horizontalzoomin";
		}
		if (isset($_POST['verticalzoomout']))
		{
			$sender = "verticalzoomout";
		}
		if (isset($_POST['verticalzoomin']))
		{
			$sender = "verticalzoomin";
		}  
		if (isset($_POST['doublezoomout']))
		{
			$sender = "doublezoomout";
		} 
		if (isset($_POST['doublezoomin']))
		{
			$sender = "doublezoomin";
		}   
		if ( isset($_POST['zoomAtPointParam'])) 
		{
			$param = $_POST['zoomAtPointParam']; 
			$sender = "zoomAtPoint"; 
		} 
		if ( isset($_POST['graphMovedParam'])) 
		{
			$param = $_POST['graphMovedParam']; 
			$sender = "graphMoved"; 
			$_SESSION['mouseFunction'] = 0;   // mouse will do nothing by default
			// You must click something to make the mouse do something 
			// scrolling default made it difficult to resize the window without moving the graph
		} 
		if (isset($_POST['selectedRectangleField']) && !empty($_POST['selectedRectangleField'])) 
		{
			$selectedRectangleParameter = $_POST['selectedRectangleField'];  
			$_SESSION['mouseFunction'] = $_POST['mouseFunctionField'];  
			// so it can be restored after Draw causes sendGraphDocumnt
			$sender = "draw"; 
		} 
		if (isset($_POST['graphWindowResizedField'])) 
			{ $param = $_POST['graphWindowResizedField']; 
			  $sender = "graphWindowResized";   
			  $newgraphwidth = $_POST['graphwidth']; 
			  $_SESSION['graphWidth'] = $newgraphwidth;
			  $_SESSION['height'] = $_POST['newHeight'];
			} 
		if(isset($_POST['directionField'])) 
			{ 
				$sender =  "toggleDirectionField";  
			}   
		if(isset($_POST['circularAspect'])) 
			{ 
				$sender =  "circularAspect";  
			}  
		if(isset($_POST['graphPaperIndexFieldName'])) 
			{  
				$sender = "setGraphPaper";  
				$index = $_POST['graphPaperIndexFieldName'];  
				$param = $graphwidth . "+" . $height . "+" . $index;  
			} 
		if(isset($_POST['parametersField'])) 
			{ 
			$sender = "updateParameters";
			$param = $graphwidth . "+" . $height . "+" . $_POST['parametersField'];  
			// See AdjustParameter.js for the format of the POSTed data.
			} 
		if(isset($_POST['newStrokeWidth']))  
			{  
				// from the context menu of a graph 
				$thickness = $_POST['newStrokeWidth'];  // it will be 1 or 3  as a string 
				$param = $graphwidth . "+" . $height . "+" . $thickness; 
				$sender = "changeGraphLineWidth";
			}				
		$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		if ($socket === false) 
		{
			echo "Socket creation failed: " . socket_strerror(socket_last_error()) . "<br>";
		} 
		else 
		{   
			socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => $timeout, "usec" => 0));
			$result = socket_connect($socket, $serverAddress, $serverPort); 
			if(isset($_SESSION['graphWidth'])) 
				{  
					$graphwidth  = $_SESSION['graphWidth']; 
					$windowWidth = $graphwidth + $toolbarwidth;
				}
			if(isset($_SESSION['height']))
				$height =  $_SESSION['height'];   
		   	// echo("$windowWidth  $toolbarwidth  $graphwidth");   
			$fillcolor = "rgb(128,128,255)";
			if ($result) 
			{	
				if($sender=="GetProblem")
				{	$param = $topicNumber . "+" . $problemNumber . "+" . $language . "+" . $graphwidth . "+" . $height;  
					unset($_SESSION['mouseFunction']);
					$response = sendMessage($clientSocket, "initGraphDocFromLibrary", $param);
				}
				else if ($sender == "graphButton")
				{  // echo("sending graphButton message");
					$param =  $graphwidth  . "+" . $height;
					$response = sendMessage($clientSocket,"graphButton", $param); 
				}
				else if($sender == "setLanguage")
				{ 
					$param = $languageName;
				}
				else if( str_starts_with($sender, "parameterplus"))
				{   
					$index = $sender[13];   // the character after parameterplus
					$param = $graphwidth . "+" . $height. "+" . $index;
					$response = sendMessage($clientSocket,"incrementParameter", $param); 
					// this also resets pDocData->active_parameter  in the Engine.
				}
				else if( str_starts_with($sender, "parameterminus"))
				{		
					$index = $sender[14];   // the character after parameterminus
					$param = $graphwidth . "+" . $height. "+" . $index;
					$response = sendMessage($clientSocket,"decrementParameter", $param);
				}   
				else if ($sender == "activeParameterChanged") 
				{
					$param ="$newActiveParameter+$graphwidth+$height";
					$response = sendMessage($clientSocket,"activeParameterChanged",$param);	
				} 
				else if ($sender == "incrementActiveParameter") 
				{
					$param ="$graphwidth+$height";
					$response = sendMessage($clientSocket,"incrementActiveParameter",$param);	
				} 
				else if ($sender == "decrementActiveParameter") 
				{  
					$param ="$graphwidth+$height";
					$response = sendMessage($clientSocket,"decrementActiveParameter",$param);	
				}
				else if ($sender == "draw")
				{
					$param = $selectedRectangleParameter;
					$response = sendMessage($clientSocket,"selectedRectangleGraph", $param); 
				}  
				else if ($sender == "drawAll")
				{
					$param = "$graphwidth+$height";
					$response = sendMessage($clientSocket,"drawAll", $param); 
				}  
			   else if ($sender == "zoomAtPoint") 
				{
					$response = sendMessage($clientSocket,"zoomAtPoint", $param);
				} 
			   else if ($sender == "graphMoved") 
				{
					$response = sendMessage($clientSocket,"graphMoved", $param);
				} 
				else if ($sender == "graphWindowResized") 
				{
					$response = sendMessage($clientSocket,"graphWindowResized", $param);
				} 
				else if ($sender == "setGraphPaper") 
				{
					$response = sendMessage($clientSocket,"setGraphPaper", $param);
				}  
				else if ($sender == "updateParameters") 
				{
					$response = sendMessage($clientSocket,"updateParameters", $param);
				}  
				else if ($sender == "changeGraphLineWidth") 
				{
					$response = sendMessage($clientSocket,"changeGraphLineWidth", $param);
				} 
				else
				{
					if(!is_string($sender) || strlen($sender) == 0)
					   $response = false;
					else
					{
						$param =  $graphwidth  . "+" . $height;
						$response = sendMessage($clientSocket,$sender,$param);
				    }
				}
				if ($response === false || $response == null)
				{
					$errcode = socket_last_error($socket);
					$message = socket_strerror($errcode);
					echo "Socket_read error: $message<br>";
				} 
				else if (strpos($response, 'ERROR:') === 0) 
				{	// It's an error message
					$errorMessage = substr($response, strlen('ERROR:'));
					echo "<div class='error'>Error: {$errorMessage}</div>";
				}
				else  
				{	// The response consists of multiple <script> elements followed by <svg> elements
					$position = 0;
					while (($position = stripos($response, '</script>', $position)) !== false) {
					    $afterScript = substr($response, $position + strlen('</script>'), 5); // Check next 5 characters
					    if (stripos($afterScript, '<script') === false) {
					        // Found the first `</script>` that is NOT followed by another `<script>` within 5 chars
					        $position += strlen('</script>'); // Include `</script>` in the split
					        break;
					    }
					    $position += strlen('</script>'); // Move forward in the string
					}

					if ($position !== false) {
					    $scriptPart = substr($response, 0, $position);
					    $svgPart = substr($response, $position);
					} else {
					    // If "</script>" is not found, something went wrong, possibly an error message
					    echo($response);
					    $scriptPart = ''; 
					}

					if (!strstr($scriptPart, "languageName")) die();

					echo($scriptPart); 
              	?> 
						<div class="container" id="wholeWindow"> 
							<?php include 'toolbar.php'; ?>  
							<script>  
								setLanguage(languageName);
							</script>  
							<script src="Tooltips.js"></script>
							<script src="AdjustToolbar.js"></script>  
							<div id="svgContainer">  
								<div id="ODEhint" class="ODEhint">Click anywhere on the graph to start a new solution at that point.</div>  
								<?php
									if(isset($_SESSION['ODEhint']))
									 	echo("<script> show_ODEhint = false; </script>");  
									else 
										echo("<script> show_ODEhint = true; </script>"); 
								?>
								<div id="PointSlopeDiv" style="position: absolute; cursor: move; border: 5px; background-color: transparent; padding: 5px; left:50px; top:50px"></div>  
								<div id="crosshairs-container">
									<svg width="38" height="38">
										<!-- Vertical lines -->
										<line x1="19" y1="0" x2="19" y2="18" stroke="black" stroke-width="1"/>
										<line x1="19" y1="20" x2="19" y2="38" stroke="black" stroke-width="1"/>
										<!-- Horizontal lines -->
										<line x1="0" y1="19" x2="18" y2="19" stroke="black" stroke-width="1"/>
										<line x1="20" y1="19" x2="38" y2="19" stroke="black" stroke-width="1"/>
									</svg>
								</div> 							
								<div id="draggableTitle0" style="position: absolute; display:none; cursor: move; background-color: lightyellow; padding: 5px; left:10px; top:10px"></div> 
								<div id="draggableTitle1" style="position: absolute; display:none; cursor: move; background-color: lightyellow; padding: 5px; left:10px; top:10px"></div> 
								<div id="draggableTitle2" style="position: absolute; display:none; cursor: move; background-color: lightyellow; padding: 5px; left:10px; top:10px"></div> 
								<div id="draggableTitle3" style="position: absolute; display:none; cursor: move; background-color: lightyellow; padding: 5px; left:10px; top:10px"></div>  
								<div id="draggableTitle4" style="position: absolute; display:none; ursor: move; background-color: lightyellow; padding: 5px; left:10px; top:10px"></div> 
								<div id="draggableTitle5" style="position: absolute; display:none; cursor: move; background-color: lightyellow; padding: 5px; left:10px; top:10px"></div>  
								<svg id="graphSVG" width="<?php echo $graphwidth; ?>" height="<?php echo $height; ?>" top = "0px ?>"; xmlns="http://www.w3.org/2000/svg"> 
								<?php 
									if(strstr($svgPart,"<q") || strstr($svgPart,"<Q"))  
										{
											// on relation graphs, colored pixels are coded with <q  and small rectangles with <Q
											decode($svgPart,$fillcolor); 	
										} 
									else
										echo $svgPart;  
								?>
								</svg>
							</div>   <!--  This closes the svgContainer div -->
						  </div>    <!--  This closes the container div -->
					<?php
				}
				socket_close($socket);  // because server has already closed it
			}  // close if($result)
			else 
			{
				echo "Failed to connect to the C program: " . socket_strerror(socket_last_error()) . "<br>";
			}
		}
	}
?>  
 
<script src="wrapSVGtext.js"></script>
<script src="commentaryModal.js"></script>
<script src="crashPopup.js"></script>  
<script src="MoveSvgElement.js"></script>
<script>
updateGraphPaperDisplay();  // initialize the GraphPaperButton
</script>
<script>     
// The mouse can perform different functions.  Which one it is  
// used for is controlled by these variables, which are changed  
//  by buttons on the Graph Toolbar
 
	const mouseScrollGraph = 1;  
	const mouseSelectRectangle = 2; 
	const mouseSelectCenteredRectangle = 3; 
	const mousePointSlope = 4;
	const mouseDragTitle = 5; 
	let mouseFunction =  <?php if(isset($_SESSION['mouseFunction'])) echo($_SESSION['mouseFunction']); else echo("0"); ?>;  
	let pointSlopeOn = false;    
	// Code to drag the titles 
	// Make the draggableTitle elements draggable (always, this can't be turned on or off)
	dragElement(document.getElementById("draggableTitle0"));
	dragElement(document.getElementById("draggableTitle1"));   
	dragElement(document.getElementById("draggableTitle2"));
	dragElement(document.getElementById("draggableTitle3"));  
	dragElement(document.getElementById("draggableTitle4"));
	dragElement(document.getElementById("draggableTitle5")); 
	// also the PointSlope window 
	dragElement(document.getElementById("PointSlopeDiv"));
	 
	function dragElement(elmnt) {
		var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;  
		var oldmouseFunction = mouseFunction; 
		let dragContainer = elmnt.parentElement; 
		let maxWidth = parseFloat(getComputedStyle(dragContainer).width);
		let maxHeight = parseFloat(getComputedStyle(dragContainer).height);
		if (document.getElementById(elmnt.id)) {
			document.getElementById(elmnt.id).onpointerdown = dragMouseDown;
		} else {
			elmnt.onpointerdown = dragMouseDown;
		}

		function dragMouseDown(e) {
			e.preventDefault();  
			e.stopPropagation(); 
			pos3 = e.clientX;
			pos4 = e.clientY; 
			//console.log("Pointer down at:", pos3, pos4);
			document.onpointerup = closeDragElement;
			document.onpointermove = elementDrag;
			console.log("Pointer move listener attached."); 
			oldMouseFunction = mouseFunction;
			mouseFunction = mouseDragTitle; 
		}

		function elementDrag(e) {
			e.preventDefault();
			e.stopPropagation();

			// Calculate the position delta
			pos1 = pos3 - e.clientX;
			pos2 = pos4 - e.clientY;

			// New potential positions of the element
			let newLeft = elmnt.offsetLeft - pos1;
			let newTop = elmnt.offsetTop - pos2; 

			// Prevent dragging off the left 
			if (newLeft < 0) {
			  newLeft = 0;
			} 
			// Prevent dragging off the top 
			if (newTop < 0) {
			  newTop = 0;
			} 
			let newRight = newLeft + elmnt.offsetWidth; 
			let newBottom = newTop + elmnt.offsetHeight;
			// Prevent dragging off the right 
			if (newRight > maxWidth) {
			  newRight = maxWidth; 
			  newLeft = newRight - elmnt.offsetWidth;
			}
			// Prevent dragging off the bottom 
			if (newBottom > maxHeight) { 
			 newBottom = maxHeight;
			 newTop = maxHeight - elmnt.offsetHeight;
			}
			// Update element's position
			elmnt.style.left = newLeft + "px";
			elmnt.style.top = newTop + "px";

			// Update last mouse positions
			pos3 = e.clientX;
			pos4 = e.clientY; 
			console.log("dragging",newRight,maxWidth);
		}
 
		function getLastDigit(str) {
			const match = str.match(/\d$/); // Match a digit at the end of the string
			return match ? match[0] : null;  // Return the digit if found, otherwise null
		}

		function closeDragElement(e) {
			e.preventDefault();  
			e.stopPropagation();  
			if(elmnt.id.startsWith("draggableTitle")) 
				{  /* param is "[index,x1,y1,x2,y2]"  specifying which graph,
						and the  pixel coordinates of the title window, relative  
						to the rectangle that contains
						the graphs but not the graph toolbar. */    
						let x1 = parseFloat(elmnt.style.left); 
						let y1 = parseFloat(elmnt.style.top);
						let x2 = x1 + parseFloat(elmnt.style.width); 
						let y2 = y1 + parseFloat(elmnt.style.height); 
						index = getLastDigit(elmnt.id);
					param = `[${index},${x1},${y1},${x2},${y2}]`;
					sendMessageToEngine("graphTitleMoved", param); 
					// no need to await the result, so no need to label this function async 
				}
			document.onpointerup = null;
			document.onpointermove = null;
			// Delay the reassignment by 50 ms to prevent unwanted clicks
			setTimeout(() => {
				mouseFunction = oldmouseFunction;
			}, 50);
		}
	}
	
	// End of title-dragging code.   



// implement the Toolbar buttons that don't require that the Engine resend the document:	 
  
function turnOnMouseScroll(){   
	mouseFunction = mouseScrollGraph;
	console.log("mouseFunction is", mouseFunction);  
	if (svgContainer.contains(selectionRectangle)) 
		svgContainer.removeChild(selectionRectangle);
} 
 
function turnOnSelectRectangle(){   
	mouseFunction =  mouseSelectRectangle;
	console.log("mouseFunction is", mouseFunction);  
 } 
 
 function turnOnSelectCenteredRectangle(){   
 	mouseFunction = mouseSelectCenteredRectangle;
 	console.log("mouseFunction is", mouseFunction); 
 } 
 
function turnOnPointSlope(){    
	mouseFunction = mousePointSlope; 
	//  pointSlopeOn = true;  Not yet, only on mousedown
  	console.log("mouseFunction is", mouseFunction); 
  } 
 
 
function showAssumptions() {
	    // Create a popup window
	    var popupWindow = window.open('', 'Assumptions', 'width=600, height=400');

	    // Create a div with relative positioning
	    var div = popupWindow.document.createElement('div');
	    div.style.position = 'relative';

	    // Append the div to the popup window
	    popupWindow.document.body.appendChild(div);

	    // Display all SVG elements with class "assumption"
	    var assumptions = document.querySelectorAll('.assumption');
	    assumptions.forEach(function (assumption) {
	        // Clone the assumption
	        var clonedAssumption = assumption.cloneNode(true);

	        // Set the display property to block (or any other appropriate value)
	        clonedAssumption.style.display = 'block';

	        // Append the cloned assumption to the div
	        div.appendChild(clonedAssumption);
	    });
	}
	
	function showSingularities() {
	    // Create a popup window
	    var popupWindow = window.open('', 'Singularities', 'width=600, height=400');

	    // Create a div with relative positioning
	    var div = popupWindow.document.createElement('div');
	    div.style.position = 'relative';

	    // Append the div to the popup window
	    popupWindow.document.body.appendChild(div);

	    // Display all SVG elements with class "singularity"
	    var singularities = document.querySelectorAll('.singularity');
	    singularities.forEach(function (t) {
	        var clonedSingularity = t.cloneNode(true);

	        // Set the display property to block (or any other appropriate value)
	        clonedSingularity.style.display = 'block';

	        // Append the cloned assumption to the div
	        div.appendChild(clonedSingularity);
	    });
	}
	
	function showJumps() {
	    // Create a popup window
	    var popupWindow = window.open('', 'Singularities', 'width=600, height=400');

	    // Create a div with relative positioning
	    var div = popupWindow.document.createElement('div');
	    div.style.position = 'relative';

	    // Append the div to the popup window
	    popupWindow.document.body.appendChild(div);

	    // Display all SVG elements with class "jump"
	    var jumps = document.querySelectorAll('.jump');
	    jumps.forEach(function (t) {
	        var clonedJump = t.cloneNode(true);

	        // Set the display property to block (or any other appropriate value)
	        clonedJump.style.display = 'block';

	        // Append the cloned assumption to the div
	        div.appendChild(clonedJump);
	    });
	}
	

function svgToImageInput(svgElementId, inputElementId) {
	var svgElement = document.getElementById(svgElementId);
	var inputElement = document.getElementById(inputElementId);

	// Serialize SVG to string
	var serializer = new XMLSerializer();
	var svgString = serializer.serializeToString(svgElement);

	// Encode SVG string in Base64
	var svgBase64 = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgString)));

	// Set the encoded SVG as the source for the input image
	inputElement.setAttribute('src', svgBase64);
}

// Ensure this script runs after the DOM is fully loaded
document.addEventListener('DOMContentLoaded', function() {   
	// <svg> elements come from the Engine with top and left set, but SVG ignores those.
	// We must set them as CSS attributes.
	let graphs = document.querySelectorAll('.graph'); 
	for (let graph of graphs) {  
		let top = graph.getAttribute("top");
		let left = graph.getAttribute("left");
		if (top !== null) {
			graph.style.top = `${top}px`;
		}
		if (left !== null) {
			graph.style.left = `${left}px`;
		}
	} 
	// now for the titles
	let title0 = document.getElementById('title0'); 
	if (title0) {
		let left = title0.style.left; 
		let top = title0.style.top;   
		title0.style.display = "block";
		// now (left,top) is where we want draggableTitle0 to be 

		let draggableTitle0 = document.getElementById("draggableTitle0");
		if (draggableTitle0)  
			{
				draggableTitle0.style.left = left;
				draggableTitle0.style.top = top; 
				draggableTitle0.style.display = "block"; 
				moveSvgElementToContainer('title0', 'draggableTitle0');  
				// that changes title0.left and title0.top to 0 
				// moveSvgElementToContainer('titleRect0', 'draggableTitle0');  
				// titleRect0 is no longer sent by the Engine; interface controls title background
			}
	}

	// Now do the same for title1 through title5 if they exist
	for (let k = 1; k < 6; k++) { 
			// 6 is MAXGRAPHS  in the Engine
		let id = "title" + k;
		let title = document.getElementById(id);
		if (title === null) 
			{  //  console.log("breaking because title doesn't exist");
				break;  
				// This works with === but not with ==, which I think is a Javascript bug
			}
		let titleRectId = "titleRect" + k;
		let titleRect = document.getElementById(titleRectId);     
		let draggableTitleId = "draggableTitle" + k;
		let draggableTitle = document.getElementById(draggableTitleId);   
		let left = title.style.left; 
		let top = title.style.top; 
		draggableTitle.style.left = left;
		draggableTitle.style.top = top; 
		draggableTitle.style.display = "block";  // it was none before 
		title.style.display = "block";
		if (draggableTitle) {
			moveSvgElementToContainer(id, draggableTitleId);
			// moveSvgElementToContainer(titleRectId, draggableTitleId);   
			// obsolete, interface now controls title background
			}
	} 	
});


let selectionMade = false;  // set it to true on mouse_up when a rectangle is selected


document.addEventListener('DOMContentLoaded', function() { 
	showCrashPopup();  // if a crash has occurred, notify the user.
// Give the Draw button something to do when it's clicked: 

	document.getElementById('draw').addEventListener('click', function(event) {
		// Construct the selected rectangle string
		// The form will submit automatically as this button is of type image/input inside the form.  Therefore:
		event.preventDefault(); // Prevent the default form submission
		// console.log("Draw button was clicked");
		// console.log("Value of selectionMade = ");
		// console.log(selectionMade);
	    if (selectionMade) { // Check if a selection has been made
			let graphwidth = <?php echo($_SESSION['graphWidth']);?>;
			let windowheight = <?php echo($_SESSION['height']);?>;
			let toolbarwidth = <?php echo($toolbarwidth);?>; 
			let windowwidth = graphwidth+toolbarwidth; 
			let selectedRectangleValue = "[" + [whichgraph, parseInt(selectedX), parseInt(selectedY), parseInt(selectedWidth), parseInt(selectedHeight),parseInt(toolbarwidth), windowwidth, windowheight].join(',') + "]"; 
			// coordinate parameters are expected by the Engine to be integers either all in pixels or all without units.  Here, all are withouit units, as shown by console.log.  
			// console.log(selectedRectangleValue)
			// return;  // use this to see the console.log output
			document.getElementById('selectedRectangleField').value = selectedRectangleValue; 
			document.getElementById('mouseFunctionField').value = mouseFunction;
		   // Now submit it
		   selectionMade = false;  // we're done with it
		   document.getElementById('myForm').submit();
	   }  // end if
	  }  //  end of the function
    );   //  end of addEventListener 
	
//  Create and label buttons to increment and decrement parameters
	
	// Step 1: Identify all SVG elements of class "param" 
	let firstsvg = true; // while true, processing the first element of class param
	const svgs = document.querySelectorAll('svg.param');

	// Step 2 & 3: Process each SVG and create corresponding button groups
	svgs.forEach((svg, index) => { 
	   /*  // this code saved in case it's someday useful; 
		//     but not used here because can't color an image in CSS 
		// Convert SVG to Data URL
		const xml = new XMLSerializer().serializeToString(svg);
		const svg64 = btoa(unescape(encodeURIComponent(xml)));
		const svgDataUrl = 'data:image/svg+xml;base64,' + svg64;
		svg.style.display = "none";   // Hide the original SVG to prevent clutter.
		// svg.parentNode.removeChild(svg); 
		const inputImage = document.createElement('input');
		inputImage.type = 'image';
		inputImage.src = svgDataUrl;
		inputImage.alt = `Parameter ${index}`;
		inputImage.style.width = '24px';
		inputImage.style.height = '24px';    
		*/

		// Create button group container with specified styles
		const buttonGroup = document.createElement('div');
		buttonGroup.className = 'button-group';
		buttonGroup.style.display = 'flex';
		buttonGroup.style.flexDirection = 'row'; 
		buttonGroup.style.alignItems = 'center';   
		
		
		// Create the main button with SVG in it and specified styles
		const button = document.createElement('button');
		button.type = 'button'; // Use 'button' type for generic buttons
		button.alt = `Parameter ${index}`; 
		button.id = `parameterButton${index}`;
		button.style.width = '24px';
		button.style.height = '24px'; 
		button.style.backgroundColor = 'rgb(0,0,128)'; 
		button.style.color = 'white';   
		button.style.border = 'none'; 

		// button.style.position = 'absolute';   // see change 4749 in record4.txt 
		// Add a class so we can do something to all these buttons if we want
		button.classList.add('buttonsvg');  
      if (!parameters) {
           parameters = jsonParameterData.parameters || [];
       }
		button.addEventListener('pointerdown', (event) => {
			if (lastPointerId !== null) {
				svgContainer.releasePointerCapture(lastPointerId); 
				console.log("releasing lastPointerId");  
				mouseFunction = null; 
			} 
			lastPointerId = event.pointerId; // Store pointer ID for releasing later 
			// Get the click coordinates from the PointerEvent
			const clickX = event.clientX;
			const clickY = event.clientY; 
			adjustParameter(clickX, clickY-100,index,svgContainer);  // see AdjustParameter.js   
			// This just sets up event listeners to the sliders in the parameter dialog, nothing to do with the parameter sliders in  
			// the Graph Toolbar. 
			// When the Done button is pressed, it's handled in AdjustParameter.js 
		}); 
		
		// change the color of the svg
		svg.querySelectorAll('text').forEach(textElement => {
							textElement.style.fill = 'white';	// Set the text color to white  
				}); 
		svg.setAttribute('height', '24');  
	
		// Append the SVG to the button
		button.appendChild(svg);
		// Append the button to the desired parent element
		buttonGroup.appendChild(button); 
		// add an event listener so Tooltips works 
		button.addEventListener('mouseenter', showTooltip);
		button.addEventListener('mouseleave', hideTooltip);

		// Container for the + and - buttons, aligned vertically
		const buttonsContainer = document.createElement('div');
		buttonsContainer.style.display = 'flex';
		buttonsContainer.style.flexDirection = 'column'; 
		buttonsContainer.style.alignItems = 'center'; 

		// Create the "+" button with specified styles for background and text color
		const inputPlus = document.createElement('button');
		inputPlus.innerHTML = "+";
		inputPlus.style.cssText = 'height: 12px; width: 12px; background-color: rgb(0,0,128); color: white; border: none; cursor: pointer;'; 
		inputPlus.id = `parameterIncrementButton${index}`; 
		// add an event listener so Tooltips works 
		inputPlus.addEventListener('mouseenter', showTooltip);
		inputPlus.addEventListener('mouseleave', hideTooltip);
		
		// Create the "-" button with specified styles for background and text color
		const inputMinus = document.createElement('button');
		inputMinus.innerHTML = "-";
		inputMinus.style.cssText = 'height: 12px; width: 12px; background-color: rgb(0,0,128); color: white; border: none; cursor: pointer;'; 
		inputMinus.id = `parameterDecrementButton${index}`; 
		// add an event listener so Tooltips works 
		inputMinus.addEventListener('mouseenter', showTooltip);
		inputMinus.addEventListener('mouseleave', hideTooltip);

		// Event listeners for "+" and "-" buttons to submit the form
		inputPlus.addEventListener('click', function() {
			submitFormWithAction('parameterplus' + index);
		});
		inputMinus.addEventListener('click', function() {
			submitFormWithAction('parameterminus' + index);
		});
		
		// Append + and - buttons to their container
		buttonsContainer.appendChild(inputPlus);
		buttonsContainer.appendChild(inputMinus);

		// Append elements to the button group
		buttonGroup.appendChild(buttonsContainer); // Container with + and - buttons 
		 
		if(firstsvg &&  
			graphtypeNumber != 8 &&  // don't put the toggleEraseButton on POLYROOT or MC_INEQ or MC_SET 
			graphtypeNumber != 3 &&  //  these are values from mainchoi.h
			graphtypeNumber != 4     //  the Engine sends Javascript to define graphtypeNumber 
		)
					
			{ 	const toggleEraseButton = document.createElement('button'); 
				let redcircle = "🔴"; 
				let greencircle = "🟢"; 
				if (eraseOld)
					toggleEraseButton.innerHTML = greencircle; // thus this is the default, if it wasn't already set. 
				else
				 	toggleEraseButton.innerHTML = redcircle;  
				toggleEraseButton.style.cssText = 'height: 24px; width: 25px; background-color: rgb(0,0,128); color: white; border: none; cursor: pointer;'; 
				toggleEraseButton.id = "toggleErase"; 
				toggleEraseButton.addEventListener('click',function(){  
					submitFormWithAction('toggleErase'); 
				}); 
				// add an event listener so Tooltips works 
				toggleEraseButton.addEventListener('mouseenter', showTooltip);
				toggleEraseButton.addEventListener('mouseleave', hideTooltip);
			   buttonGroup.appendChild(toggleEraseButton); 
				firstsvg = false;  // only create one such button!  
			}  
		// Append the button group to the target container
		document.getElementById('toolbar').appendChild(buttonGroup);  
		let parameterName = parameters[index].name; 
		if(supportsSlider(graphtypeNumber,parameterName)){  // supportsSlider is in ParameterSlider.js 
			//  Make a slider to control this parameter.  See ParameterSlider.js 
			makeParameterSlider(index, parameters, document.getElementById("toolbar")); 
		}
	}); 
	//  Create and label hidden buttons to increment and decrement the parameter 
	const inputPlusActive = document.createElement('button');
	inputPlusActive.style.cssText = 'height: 0px; width: 0px; display:none'; 
	inputPlusActive.id = "incrementActiveParameterButton"; 
	inputPlusActive.addEventListener('click', function() {
		submitFormWithAction('incrementActiveParameter');
		});
	const inputMinusActive = document.createElement('button');
	inputMinusActive.style.cssText = 'height: 0px; width: 0px; display:none'; 
	inputMinusActive.id = "decrementActiveParameterButton";   
	inputMinusActive.addEventListener('click', function() {
		submitFormWithAction('decrementActiveParameter');
		});
	document.getElementById('toolbar').appendChild(inputPlusActive); 
	document.getElementById('toolbar').appendChild(inputMinusActive);
});

function submitFormWithAction(actionValue) {
    const form = document.getElementById('myForm');
    const actionInput = document.getElementById('formAction');
    actionInput.value = actionValue;
    form.submit();
}

let svgContainer = document.getElementById('svgContainer');
let selectionRectangle = document.createElement('div');
selectionRectangle.id = 'selectionRectangle';
selectionRectangle.style.position = 'absolute';
selectionRectangle.style.border = '1px solid blue';
selectionRectangle.style.pointerEvents = 'none'; // Ignore pointer events
//  svgContainer.appendChild(selectionRectangle);  
//  This is now done on pointerdown, when selection starts.  If it's done here,
//  it makes a visible black dot below the graph and creates scroll bars. 
let selectedX = 0; 
selectionRectangle.style.left = 0+"px";
let selectedY = 0;
selectionRectangle.style.top = 0+"px";
let selectedWidth = parseInt(selectionRectangle.style.width,10);
let selectedHeight = parseInt(selectionRectangle.style.height,10);
let startX = 0;
let startY = 0;
let initialX = 0;
let initialY = 0;
let isScrolling = false;
let isSelecting = false;
let activeGraphBounds = null; 
let whichgraph = 0; 

const svg0 = document.getElementById('graph0');

 
// the svgContainer will receive mouse events, and  
// will handle the selected rectangle and also the scrolling of graphs.
// The scrollable elements are the <g> groups that contain each 
// graph; but if two graphs (or more) are drawn on the same axes, they both (or all) 
// must scroll, which I could only arrange by having svgContainer process 
// the mouse messages
 
let firstGraphIndex = -1      

async function handleClick(event) {
	// Get the click position within svgContainer 
	// This is used when graphing ODEs with a direction field   
	console.log("In handleClick");   
	// Arrange that ODEhint should not be shown again 
	// we want to set $_SESSION['ODEhint'], but that has to be done in PHP, not here in Javascript 
	 fetch("ODEhint.php", { method: "POST" });  // ODEhint.php will set $_SESSION['ODEhint']
	
	if(mouseFunction > 0) 
		{	console.log("returning, because mouseFunction = ", mouseFunction); 
			return;  // we are dragging, or selecting a rectangle  
		} 
	const svgContainer = document.getElementById("svgContainer");
	const rect = svgContainer.getBoundingClientRect();

	// Calculate x and y coordinates relative to svgContainer
	const clickX = event.clientX - rect.left;
	const clickY = event.clientY - rect.top; 
	
	if (typeof sendMessageToEngine === 'undefined') {
		console.error("sendMessageToEngine is not defined!");
		return;
	 }

	// Send the message to the engine 
	try{
		const response = await sendMessageToEngine("askPointSlope", `[${clickX},${clickY}]`);  
		console.log(response); 
		// response has the form [x,y]  as plain text, not SVG  
		// so the following line extracts x and y
		const [x, y] = response.slice(1, -1).split(","); 
		// Now find the parameters that corresponds to x0 and y0,  adjust them, and  
		// arrange for an updateParameters message to be sent.
		// They are always the last two parameters  
		// Make sure the parameter data has been upacked:
		if (!parameters) {
			parameters = jsonParameterData.parameters || [];
			console.log("oops!  Shouldn't happen")
		} 
		if(parameters.length < 2){  
			console.log("oops!  less than 2 parameters in an ODE."); 
			return;
		}  
		console.log("got here");
		let nparameters = parameters.length; 
		parameters[nparameters-2].value = x; 
		parameters[nparameters-1].value = y; 
		updateParameters();   // defined in AdjustParameters.js.    
		//   It submits a form, which when processed sends an updateParameters message to the Engine.
	} 
	catch (error) {
		console.error('Error in handleClick:', error);
		return;
	}
}

if(graphtypeNumber == ODE || graphtypeNumber == ODE2){
	// graphtypeNunmber is defined in code emitted by the Engine by sendJavascript 
	mouseFunction = 0;  
	// if a rectangle is selected and Draw is pressed,  mousefunction will be saved in SESSION so use can continue selecting rectangles; 
	// but in ODE2 that's not good, as it disables starting a new solution by clicking.  So this should prevent it.   In ODE2,  if you want 
	// to select another rectangle, you'll have to click one of the select buttons first. 
	svgContainer.addEventListener('click', handleClick); 
	// this is used to let a click start a new solution line on the graph  
	const ODEhint = document.getElementById("ODEhint");  
	console.log("show_ODEhint = ", show_ODEhint);
	if(!show_ODEhint)
		{	ODEhint.style.opacity = 0; // Make it invisible		 
			console.log("making ODEhint invisible"); 
		} 
	else 
		{	ODEhint.style.opacity = 1; // Make it visible		 
			console.log("making ODEhint visible"); 
		} 
} 
let lastPointerId = null;  // Global variable to store the pointer ID  

svgContainer.addEventListener('pointerdown', function(e) { 
	if(mouseFunction == mousePointSlope || mouseFunction == mouseSelectRectangle || 
		mouseFunction == mouseSelectCenteredRectangle || mouseFunction == mouseScrollGraph 
	  ) 
		{	e.preventDefault();
			svgContainer.setPointerCapture(e.pointerId);   
			// if these lines aren't inside the above "if" then they disable the AdjustParameters sliders
			lastPointerId = e.pointerId; // Store pointer ID for releasing later 
			console.log("setting lastPointerId to ", e.pointerId);
		}
	selectionMade = false; 
	if (mouseFunction == mousePointSlope) 
		{  
			pointSlopeOn = true;  
			handleMouseMove(e); 
			return;
		}
	if (!svgContainer.contains(selectionRectangle)) 
		svgContainer.appendChild(selectionRectangle);
	let graphs = document.querySelectorAll('.graph');
	let rect = svgContainer.getBoundingClientRect();
	let x = e.clientX - rect.left;
	let y = e.clientY - rect.top;
	let k = 0;
	for (let graph of graphs) {
		// Use the manually defined bounds instead of getBoundingClientRect()
		let graphX = parseInt(graph.getAttribute("left"), 10);
		let graphY = parseInt(graph.getAttribute("top"), 10);
		let graphWidth = parseInt(graph.getAttribute("mywidth"), 10);
		let graphHeight = parseInt(graph.getAttribute("myheight"), 10); 
		// console.log("in mousedown: ", graphX, graphY, graphWidth, graphHeight,mouseFunction,x,y); 
		if (x >= graphX && x <= graphX + graphWidth && y >= graphY && y <= graphY + graphHeight)  
			{  startX = x;
				startY = y;  
				if(firstGraphIndex == -1) 
					firstGraphIndex = k;   // this will be used on mouseup to know which graph to scroll
				if(mouseFunction == mouseSelectRectangle || mouseFunction == mouseSelectCenteredRectangle)
					{	isSelecting = true; 
						// console.log("set isSelecting to true because svgContainer got mousedown");
						activeGraphBounds = { left: graphX, top: graphY, right: graphX + graphWidth, bottom: graphY + graphHeight };
						updateSelectionRectangle(x, y, 0, 0);
						break; 
					} 
				else if(mouseFunction == mouseScrollGraph) 
					{
						isScrolling = true; 
						const transform = `translate(0,0)`; 
						elmnt = graph.querySelector('g.scrollable');  // the scrollable <g> of the graph
						elmnt.setAttribute('transform', transform); 
						isSelecting = false;   // prevent selection rectangle
						// console.log("set isSelecting to false");
						// do not break out of the loop.
						// do this to all the graphs containing the mouse point, 
						// not just the first one.
					} 
			} 
		k=k+1;
	} 
});   
	 
let graphs = document.querySelectorAll('.graph'); 
function handleMouseMove(e){
	if ((mouseFunction == mouseSelectRectangle || mouseFunction == mouseSelectCenteredRectangle)  && ( !isSelecting || !activeGraphBounds) ) 
		return;  
	if(mouseFunction == mouseScrollGraph && !isScrolling) 
	   return;

	let rect = svgContainer.getBoundingClientRect();
	let x = e.clientX - rect.left;
	let y = e.clientY - rect.top;

	if (isSelecting && (mouseFunction == mouseSelectRectangle || mouseFunction == mouseSelectCenteredRectangle)) 
	{ 
		// Constrain the selection or the scroll within the manually defined bounds
		x = Math.max(activeGraphBounds.left, Math.min(x, activeGraphBounds.right));
		y = Math.max(activeGraphBounds.top, Math.min(y, activeGraphBounds.bottom));
		let deltax = x - startX; 
		let deltay = y - startY;
		let width = Math.abs(deltax);
		let height = Math.abs(deltay); 
		if(mouseFunction == mouseSelectCenteredRectangle)
			{ 
				width *= 2;
				height *= 2;
			}
		let newX = startX < x ? startX : x;
		let newY = startY < y ? startY : y;

	    // Adjust width and height to not exceed the active graph bounds
		if (newX + width > activeGraphBounds.right)
			 width = activeGraphBounds.right - newX;
		if (newY + height > activeGraphBounds.bottom) 
			height = activeGraphBounds.bottom - newY;
		if (mouseFunction != mouseScrollGraph)
			updateSelectionRectangle(newX, newY, width, height);  
	}
	else if(mouseFunction == mouseScrollGraph && isScrolling)  
		{
			// scroll all the graphs that contain (e.clientX, e.clientY) 
			for (let graph of graphs) {
				// Use the manually defined bounds instead of getBoundingClientRect()
				let graphX = parseInt(graph.getAttribute("left"), 10);
				let graphY = parseInt(graph.getAttribute("top"), 10);
				let graphWidth = parseInt(graph.getAttribute("mywidth"), 10);
				let graphHeight = parseInt(graph.getAttribute("myheight"), 10);
				console.log(graphX, graphY, graphWidth, graphHeight);
				if (x >= graphX && x <= graphX + graphWidth && y >= graphY && y <= graphY + graphHeight)  
					{
						var dx = e.clientX -rect.left - startX;
						var dy = e.clientY -rect.top - startY;
						const transform = `translate(${dx}, ${dy})`; 
						elmnt = graph.querySelector('g.scrollable');  // the scrollable <g> of the graph
						elmnt.setAttribute('transform', transform);
					} 
			} 
		} 
	else if(mouseFunction == mousePointSlope && pointSlopeOn) 
		{	// Initialize the parameters and data string

			var param = `[${x},${y}]`;
			var data = `askPointSlopeParam=${encodeURIComponent(param)}`;

			// Create the XMLHttpRequest object and set up the request
			var xhr = new XMLHttpRequest();
			xhr.open("POST", "<?php echo ($_SERVER['PHP_SELF']); ?>", true);
			xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

			// Set up an event listener to handle the response
			xhr.onreadystatechange = function() {
					if (xhr.readyState === 4)  
					{
						if (xhr.status === 200)  
						{ 
							pointSlopeResponse = xhr.responseText;
							console.log('Response received:', pointSlopeResponse);  
							// pointSlopeResponse consists of two parts: [px,py] followed by PointSlopeSVG 
							const marker = pointSlopeResponse.indexOf(']') + 1;  // start of the SVG 
							let coordinates = pointSlopeResponse.substring(0,marker); 
							pointSlopeSVG = pointSlopeResponse.substring(marker); 
							// Now get this SVG displayed in the PointSlopeElementDiv
							pointSlopeElement = document.getElementById("PointSlopeDiv"); 
							pointSlopeElement.style.display = "block";  
							pointSlopeElement.style.backgroundColor = "lightblue"; 
							pointSlopeElement.style.borderColor = "darkblue";
							pointSlopeElement.innerHTML = pointSlopeSVG;   
						   // Wait for the SVG to be inserted into the DOM and set width and height
						      requestAnimationFrame(() => {
						          const svgElement = pointSlopeElement.querySelector('svg');
						          if (svgElement) {
						              const bbox = svgElement.getBBox(); 
						              let newwidth = bbox.width + 10; 
						              let newheight = bbox.height;  // doesn't look good larger
						              pointSlopeElement.style.width = `${newwidth}px`;
						              pointSlopeElement.style.height = `${newheight}px`;
						          }
						      });
							// Now extract the coordinates px,py as numbers 
							console.log(coordinates);
							const [px,py] = coordinates.slice(1, -1).split(',').map(Number); 
							// Now move the crosshairs there		
							const container = document.getElementById('crosshairs-container');
							container.style.left = `${px - 19}px`;
							container.style.top = `${py - 19}px`;	
							container.style.display = "block";  // make it visible
							console.log(container);
						}	 
						else  
						{
								console.error('Error with request, status code:', xhr.status);
								console.error('Response received:', xhr.responseText);
						} 
					}
				}
			// Send the AJAX message notifying the Engine
			console.log('Data being sent:', data);
			xhr.send(data); 
	   } 
}  
	 
svgContainer.addEventListener('pointermove', handleMouseMove); 
	
svgContainer.addEventListener('pointerup', function(e) { 
	svgContainer.releasePointerCapture(e.pointerId);
	if(mouseFunction == mousePointSlope) 
		{  
			pointSlopeOn = false;
		}
	if(mouseFunction == mouseScrollGraph) 
		{  
			isScrolling = false;   
			// get the element to scroll
			elmnt = document.getElementById("scrollable"+firstGraphIndex);
		
			// How much did it scroll?  
			let rect = svgContainer.getBoundingClientRect();
			let x = e.clientX - rect.left;
			let y = e.clientY - rect.top;
			let deltax = x - startX; 
			let deltay = y - startY; 
			if(deltax == 0 && deltay == 0) 
				return;  // don't bother telling the Engine nothing happened
			// Next we will submit a form that, when processed, will 
			// send a graphMoved message to the Engine   
			
			// Form the parameter in the required format 
			let graphwidth = <?php echo($_SESSION['graphWidth']);?>;
			let height = <?php echo($_SESSION['height']);?>;
			var param = `[${firstGraphIndex},${deltax},${deltay},${graphwidth},${height}]`;  
			// submit a hidden form so param will become $_POST['graphMovedParam'] 
			let form = document.createElement('form');
			form.method = 'POST';
			form.action = "<?php echo ($_SERVER['PHP_SELF']); ?>"; 

			// Create a hidden input to hold the param value
			let input = document.createElement('input');
			input.type = 'hidden';
			input.name = 'graphMovedParam';
			input.value = param;

			// Append the input to the form
			form.appendChild(input);

			// Append the form to the body
			document.body.appendChild(form); 
			
			// Submit the form
			form.submit();  
		}
	if (!isSelecting)  
		return;  
		//  So now we have selected a rectangle (unless its width or height is zero)
	isSelecting = false;
	activeGraphBounds = null; // Reset the active graph bounds 

	// The final selected rectangle is available here
	selectedX = selectionRectangle.style.left;
	selectedY = selectionRectangle.style.top;
	selectedWidth = selectionRectangle.style.width;
	selectedHeight = selectionRectangle.style.height;
	if(parseInt(selectedWidth) == 0 || parseInt(selectedHeight) == 0) 
		return;  //  you can't select a rectangle with zero width or height  
	selectionMade = true; 
	// console.log("on mouse up, whichgraph and selected rectangle:");
	// console.log(whichgraph,selectedX,selectedY,selectedWidth,selectedHeight);  
	isScrolling = false; 
	//  When the Draw button is clicked, the selected rectangle 
	//  will be submitted in a form, and when that form is processed, 
	//  a selectedRectangleGraph message will be sent to the Engine 
	//  with the rectangle as parameter.

});

function updateSelectionRectangle(x, y, width, height) {
    selectionRectangle.style.left = x + 'px';
    selectionRectangle.style.top = y + 'px';
    selectionRectangle.style.width = width + 'px';
    selectionRectangle.style.height = height + 'px'; 
}     

<?php include "wheel.php" ?>   // for dragging on a touchpad with two fingers
<?php include "touch.php" ?>   // for dragging on iPad or iPhone with two fingers.

/*______________________________________________________________*/ 
/* script for resizing the window  */  
/* We don't want to contact the server until the resize operation 
is completed.  But that is not an event.  So instead, we just wait 
300 milliseconds after a resize event to contact the server
*/

let resizeTimer;  
 
function onResizeEnd() 
	{  console.log("in onResizeEnd");  
		const newWidth = window.innerWidth;
		const newHeight = parseInt(window.innerHeight);  
		toolbarwidth = <?php echo($toolbarwidth); ?>;  
		toolbarwidth = toolbarwidth;
		graphwidth = newWidth - toolbarwidth; 
		if(graphwidth <= 0) 
		   return;   // just ignore this event  
	
		graphwidth = parseInt(graphwidth);   // Engine expects integer
		let param = `[${graphwidth},${newHeight}]`; 
		
		let form = document.createElement('form');
		form.method = 'POST';
		form.action = "<?php echo ($_SERVER['PHP_SELF']); ?>"; 

		// Create a hidden input to hold the param value
		let input = document.createElement('input');
		input.type = 'hidden';
		input.name = 'graphWindowResizedField';
		input.value = param;

		// Append the input to the form
		form.appendChild(input); 
		 
		// we also need the new width and height 
		// to be stored in $_SESSION variables so they will 
		// be available to set the size of svgContainer after Engine 
		// returns an answer to the graphWindowResized message
		// To get them back to PHP they have to be attachd to this form
		 
		// Create hidden inputs to hold the new width and height
		let input1 = document.createElement('input'); 
		let input2 = document.createElement('input'); 
		input1.type = 'hidden';
		input1.name = 'graphwidth';
		input1.value = graphwidth;   
		form.appendChild(input1); 
		input2.type = 'hidden';
		input2.name = 'newHeight'; 
		input2.value = newHeight;	
		form.appendChild(input2); 

		// Append the form to the body
		document.body.appendChild(form); 
		
		// Submit the form
		form.submit();  
	}
window.addEventListener('resize', () => 
	{
		// Clear the previous timer
		clearTimeout(resizeTimer);

		// Set a new timer
		resizeTimer = setTimeout(() => 
		{
		  console.log('Resize ended');
		  onResizeEnd();
		}, 300); // Adjust the timeout value as needed
});
</script> 			
<script>
// code to control slider knob colors 
document.addEventListener("DOMContentLoaded", function () {
    console.log("DOM fully loaded. Active parameter:", activeParameter);

    <?php if ($resetSliderColors): ?>
        console.log("Form submitted: Resetting all sliders to white.");
        document.querySelectorAll("input[type=range]").forEach(slider => {
            slider.classList.remove("active-slider"); // Reset all to white
        });
    <?php else: ?>
        console.log("Normal page load: Setting active slider to green.");
        updateSliderColors(); // ✅ Ensures active slider is green on load
    <?php endif; ?>

    // ✅ Listen for ALL relevant events
    document.querySelectorAll("input[type=range]").forEach(slider => {
        
        slider.addEventListener("pointerup", updateSliderColors);   // Pointer already down
        slider.addEventListener("click", updateSliderColors);       // Edge cases
    });
});

function updateSliderColors() {
    document.querySelectorAll("input[type=range]").forEach(slider => {
        let sliderId = slider.id.match(/\d+$/);
        if (sliderId && parseInt(sliderId[0]) === activeParameter) {
            slider.classList.add("active-slider"); // ✅ Turn green immediately
        } else {
            slider.classList.remove("active-slider"); // ❌ Keep others white
        }
    });
}
</script>
</body>
</html>


 

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists