Sindbad~EG File Manager
<?php
// Set HTTP header to prevent caching
header('Content-Type: text/html; chars=UTF-8');
header("Cache-Control: no-cache, must-revalidate");
// Start or resume the session
session_start();
$sessionId = session_id(); // guaranteed not to contain a pipe character
ini_set('display_errors', 1);
error_reporting(E_ALL);
if ($_SERVER['SERVER_NAME'] == 'localhost') {
$nextpagegraph = "https://localhost:8443/GraphDoc.php";
$serverAddress = 'localhost';
$username = $password = null;
} else {
$nextpagegraph = "https://mathxpert.org/GraphDoc.php";
$serverAddress = 'mathxpert.org';
$username = 'beeson';
$password = 'Turing2024';
}
$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
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();
}
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["language"]))
$language = $_POST["language"];
require("SendMessage.php");
$clientSocket = createClientSocket($serverAddress, $serverPort, $timeout);
// Handle any Ajax requests before emitting any HTML
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['suppliedArgText'])) {
$inputText = $_POST['suppliedArgText'];
$needsArgCondition = $_POST['needsArgCondition'];
// Process the AJAX request
$param = $needsArgCondition . "+" . $inputText;
$response = sendMessage($clientSocket, "checkArg", $param);
} else if (isset($_POST['windowWidth']) && isset($_POST['windowHeight'])) {
$windowWidth = $_POST['windowWidth'];
$windowHeight = $_POST['windowHeight'];
$reasonstart = $_POST['reasonstart'];
$param = $windowWidth . "+" . $windowHeight . "+" . $reasonstart;
$response = sendMessage($clientSocket, "symbolWindowResized", $param);
} else {
$response = "nothing";
}
if ($response === false) {
$errcode = socket_last_error($socket);
$message = socket_strerror($errcode);
echo "Socket_read error: $message<br>";
} else if ($response != "nothing") {
echo($response); // for debugging
exit; // Stop further processing, so the rest of the HTML isn't sent
}
}
// Normal page loading code continues below
?>
<!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 Symbolic Calculator</title>
<style>
/* Set a global font-family rule for all text elements. Only Times New Roman actually works well. */
text {
font-family: 'Times New Roman';
}
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';
}
.hidden {
visibility: hidden;
position: absolute;
}
#svgContainer {
position: relative;
user-select: none; /* Prevent text and element selection */
flex-grow:1;
overflow-x: auto; /* Enables horizontal scrolling */
overflow-y: auto; /* Enables vertical scrolling */
}
#selectionRectangle {
position: absolute;
border: 1px solid red;
pointer-events: none; /* Ignore this element during mouse events */
}
body {
margin: 0;
padding: 0;
height: 100vh; /* Fills entire viewport */
overflow: hidden; /* Prevents body from scrolling */
display: flex;
flex-direction: column; /* Ensures divs stack vertically */
background-color: CornSilk;
user-select: none;
}
.button-container {
display: flex;
gap: 3px; /* the gap before and after the form containing seven buttons, not the gap between the buttons */
width: 100%;
z-index: 10;
}
.button-container button {
height: 36px; /* Adjust the height as needed */
}
.button-container form {
display: flex;
gap: 3px; /* This is the gap between the buttons */
}
.button-container form:first-of-type {
margin-right: -4px; /* Reduce the 6px gap to 2px between the two forms */
}
.selectmenuitem{
pointer-events:all;
}
.selectMenuItemWrapper {
display: inline-block;
cursor: pointer;
position: relative;
pointer-events:hover;
}
.selectMenuItemWrapper:hover {
background-color: rgb(200,200,255);
}
.hovering {
background-color: rgb(200, 200, 255); /* same color as for hover, but this works on iPhone and iPad */
}
.highlighted {
background-color: rgb(200,255,200);
}
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1000; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
background-color: lightblue
}
.modal-content {
background-color: lightblue;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 60%; /* Could be more/less, depending on screen size */
text-align: left;
}
.progress {
margin: 10px 0;
text-align: center;
}
.modal-close {
float: right;
font-size: 24px;
font-weight: bold;
cursor: pointer;
}
#hintModal,#assumptionsModal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0);
justify-content: center;
align-items: center;
}
#modalContent {
background: rgb(200,200,255);
padding: 20px;
border-radius: 5px;
width: 50%;
height: 30%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
#hintModalContent,#assumptionsModalContent {
background: rgb(200,200,255);
padding: 20px;
border-radius: 5px;
width: 50%;
height: 30%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
#selectionMenu {
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
width: 100px;
z-index: 1;
}
.selectHint {
position: absolute;
top: 50px;
left: 150px;
padding: 5px 10px;
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
color: #333;
opacity:1;
transition: opacity 0.5s ease;
}
</style>
<script>
let cheerAudio = new Audio('cheer.mp3');
function playCheerSound() {
// Create the popup
const popup = document.createElement("div");
popup.style.position = "absolute";
popup.style.top = "50px";
popup.style.left = "50px";
popup.style.padding = "20px";
popup.style.border = "1px solid black";
popup.style.borderRadius = "8px";
popup.style.backgroundColor = "rgb(220,220,255)"; // White background for the popup
popup.style.color = "black";
popup.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.2)";
popup.style.zIndex = "1000";
popup.style.fontFamily = "Arial, sans-serif";
popup.style.minWidth = "300px";
popup.style.textAlign = "center";
// Add the close button (X)
const closeButton = document.createElement("span");
closeButton.textContent = "×";
closeButton.style.position = "absolute";
closeButton.style.top = "10px";
closeButton.style.right = "10px";
closeButton.style.cursor = "pointer";
closeButton.style.fontSize = "20px";
closeButton.style.color = "black";
// Close button click handler
closeButton.addEventListener("click", function (e) {
e.stopPropagation(); // Prevent triggering other event listeners
document.body.removeChild(popup); // Remove the popup
});
// the first message
const message = document.createElement("p");
message.id = "cheerMessage"; // Assign an ID for translation
message.textContent = "You have earned applause. Click Play to hear it.";
message.style.marginBottom = "20px";
message.style.fontSize = "16px";
// the Play button
const playButton = document.createElement("button");
playButton.textContent = "Play";
playButton.id = "playButton";
playButton.style.padding = "10px 20px";
playButton.style.backgroundColor = "rgb(0, 0, 128)"; // Blue background for the button
playButton.style.color = "white";
playButton.style.border = "none";
playButton.style.borderRadius = "5px";
playButton.style.cursor = "pointer";
playButton.style.margin = "0 auto";
// Play button click handler
playButton.addEventListener("click", function (event) {
console.log("clicked on ",event.target);
if(event.target !== playButton)
return;
cheerAudio.play().catch(error => {
console.error("Error playing sound:", error);
});
document.body.removeChild(popup); // Remove the popup after playing
});
// the second message
const message2 = document.createElement("p");
message2.id = "cheerMessage2"; // Assign an ID for translation
message2.textContent = "You took more than half the steps without assistance.";
message2.style.marginBottom = "20px";
message2.style.fontSize = "16px";
// Append elements to the popup
popup.appendChild(closeButton);
popup.appendChild(message);
popup.appendChild(playButton);
popup.appendChild(message2);
// Add the popup to the document
document.body.appendChild(popup);
}
</script>
</head>
<body>
<script>
// Following is for iPhone and iPad, where there is no "hover",
// so we simulate hovering this way.
document.querySelectorAll('.selectMenuItemWrapper').forEach(item => {
item.addEventListener('touchstart', () => {
item.classList.add('hovering'); // Simulate the hover effect
});
item.addEventListener('touchend', () => {
item.classList.remove('hovering');
});
});
</script>
<script src="DocumentID.js"></script>
<script src="ToolbarWidth.js"></script>
<script src="TranslateEnterPages.js"></script>
<?php
ob_start();
include 'toolbar.php';
$toolbarContent = ob_get_clean();
?>
<div id="tooltip-container" class="tooltip"></div>
<div id="hiddenToolbar" class="hidden">
<?php echo $toolbarContent; ?>
</div>
<div style="display:none;">
<?php include 'ButtonDefinitions.svg'; ?>
</div>
<style>
.svg-button {
border: none;
background: none;
padding: 0 !important;
background-color: transparent; /* This is ignored even with !important */
cursor: pointer;
flex: 1 1 32px;
}
.video-icon {
background-color: white; /* Adjust to a contrasting color */
font-size: 1.2em; /* Slightly larger for better visibility */
}
.video-button {
background-color: rgb(0,0,128); /* dark blue background */
color: white; /* White text color */
border: none; /* No border */
padding: 5px 10px; /* Padding around the button */
cursor: pointer; /* Pointer cursor on hover */
border-radius: 4px; /* Rounded corners */
font-size: 14px; /* Text size */
display: inline-flex; /* Inline-flex to align icon and text */
align-items: center; /* Align icon and text vertically */
gap: 5px; /* Space between the icon and the text */
text-align: center; /* Center text alignment */
}
.video-button:hover {
background-color: rgb(0, 0, 180); /* Slightly lighter blue on hover */
}
/* Tooltips */
.tooltip {
position: absolute; /* so it will serve as origin for svg text it contains */
background-color: lightblue;
color: black;
padding: 5px;
margin-top: 3px;
border-radius: 3px;
white-space: nowrap;
z-index: 1000;
display: none; /* Hide by default */
font-size: 12px;
font-family: Arial, sans-serif;
}
.tooltip::before {
content: '';
position: absolute;
top: -8px; /* Adjust based on the size of the triangle */
left: 0px; /* Adjust based on desired position */
border-width: 0 5px 10px 0px; /* Create a triangle pointing up */
border-style: solid;
border-color: transparent transparent lightblue transparent; /* Only the bottom part is visible */
}
.hidden {
visibility: hidden;
position: absolute;
}
</style>
<!-- promptModal is used by any dialog. The prompt for the dialog should be dynamically inserted.
Until needed this div has display: none -->
<div id="promptModal" style="display: none; position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5);">
<div id="modalContent" style="background: white; margin: 15% auto; padding: 20px; width: 50%;">
<!-- SVG will be inserted here -->
<button id="submitResponse">Submit</button>
</div>
</div>
<style>
/* General styles for the select box */
#languageSelector,#typesize {
font-size: 12px;
padding: 5px;
border-radius: 5px;
background-color: white;
border: 1px solid #ccc;
background-repeat: no-repeat;
background-position: left center;
}
</style>
<!-- arrowhead definition needed for the undo button, it doesn't get included automatically -->
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrowhead" markerWidth="6" markerHeight="6" refX="1" refY="3" orient="auto">
<polygon points="0 0, 6 3, 0 6" fill="white" />
</marker>
</defs>
<defs>
<marker id="arrowhead2" markerWidth="4" markerHeight="4" refX="2" refY="2" orient="auto">
<polygon points="0 0, 4 2, 0 4" fill="white" />
</marker>
</defs>
</svg>
<div class="button-container" style="display: flex; flex-direction:row; align-items: top;">
<!-- Wrapper for select elements to stack them vertically -->
<div style="display: flex; flex-direction: column; margin-right: 0px;">
<select id="typesize">
<option value="1" class="selector-item" id="changeTypeSize">Type Size</option>
<option value="2" class="selector-item" id="normalSize">Normal</option>
<option value="3" class="selector-item" id="largerSize">Larger</option>
<option value="4" class="selector-item" id="smallerSize">Smaller</option>
</select>
<select id="languageSelector" onchange="setSelectedLanguage(this.value)">
<option value="english">🇬🇧 English</option>
<option value="german">🇩🇪 Deutsch</option>
<option value="french">🇫🇷 Français</option>
<option value="spanish">🇪🇸 Español</option>
<option value="italian">🇮🇹 Italiano</option>
<option value="dutch">🇳🇱 Nederlands</option>
<option value="chinese">🇨🇳 䏿–‡</option>
</select>
</div>
<!-- Other elements remain in the horizontal flow -->
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" >
<button type="button" name="assumptions" id="assumptionsButton" class="svg-button" onclick="showAssumptions()">
<svg width="66" height="36"><use href="#assumptionsButtonRef"></use></svg>
</button>
<button type="submit" name="autoStep" value="autostep" id="autoStepButton" class="svg-button"> <svg width="66" height="36"><use href="#autoStepButtonRef"></use></svg></button>
<button type="submit" name="autoFinish" value="autofinish" id="autoFinishButton" class="svg-button"> <svg width="66" height="36"><use href="#autoFinishButtonRef"></use></svg></button>
<button type="submit" name="showStep" value="showstep" id="showStepButton" class="svg-button"> <svg width="66" height="36"><use href="#showStepButtonRef"></use></svg></button>
<button type="submit" name="undo" value="undo" id="undoButton" class="svg-button"> <svg width="66" height="36"><use href="#undoButtonRef"></use></svg></button>
<button type="submit" name="hint" value="hint" id="hintButton" class="svg-button"> <svg width="66" height="36"><use href="#hintButtonRef"></use></svg></button>
<button type="submit" name="finished" value="finished" id="finishedButton" class="svg-button"> <svg width="66" height="36"><use href="#finishedButtonRef"></use></svg></button>
</form>
<form method="post" action="<?php echo htmlspecialchars($nextpagegraph); ?>" target="_blank" id="graphForm">
<button type="submit" name="graphButton" value="graphButton" id="graphButton" class="svg-button">
<svg width="66" height="36"><use href="#graphButtonRef"></use></svg>
</button>
<!-- Shortcut Button (Link Styled as a Button) -->
<a href="https://www.mathxpert.org" id="shortcutButton" class="svg-button" target="_blank">
<img src="./images/MathXpertLink.png" width="66" height="36" alt="MathXpert">
</a>
<input type="hidden" name="widthField2" id="widthField2" value="0">
<input type="hidden" name="heightField2" id="heightField2" value="0">
<input type="hidden" name="toolbarwidthField" id="toolbarwidthField" value="0">
<input type="hidden" name="language" id="language" value='<?php echo($language); ?>' >
<input type="hidden" name="PHPSESSID" id="PHPSESSID" value='<?php echo session_id(); ?>' >
</form>
</div>
<script>
// Function to update width and height inputs and submit form
function updateDimensionsAndSubmit() {
// Get width and height of the window in CSS pixels
var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
const toolbarwidth = getToolbarWidth();
// Set values to the hidden inputs
hiddenToolbar.value = toolbarwidth;
document.getElementById('widthField2').value = width-toolbarwidth;
document.getElementById('heightField2').value = height;
document.getElementById('toolbarwidthField').value = toolbarwidth;
// Submit the form
document.getElementById('graphForm').submit();
}
// Attach the function to the submit event
document.getElementById('graphForm').addEventListener('submit', function(event) {
// Prevent the form from submitting immediately
event.preventDefault();
// Update dimensions and submit the form
updateDimensionsAndSubmit();
});
// Function to set the height of the svgContainer to the scrollHeight of the window,
// and also the height and width of the element of class "highlight" that contains the selected rectangle
function setSvgContainerHeight() {
const svgContainer = document.getElementById('svgContainer');
// Get the scroll height of the svgContainer (which is at least the total height of its contents)
const containerScrollHeight = svgContainer.scrollHeight-5;
const containerScrollWidth = svgContainer.scrollWidth;
const highlightSvg = document.querySelector('svg.highlight');
if (highlightSvg) {
// Set the height using both attribute and style
highlightSvg.setAttribute("height", containerScrollHeight);
highlightSvg.style.height = containerScrollHeight + "px"; // Adding 'px' to specify the unit
highlightSvg.setAttribute("width", containerScrollWidth);
highlightSvg.style.width = containerScrollWidth + "px"; // Adding 'px' to specify the unit
}
// console.log("scrolling", svgContainer.style.height, svgContainer.scrollTop, containerScrollHeight);
// Scroll the container to the bottom to ensure new content is visible
svgContainer.scrollTop = containerScrollHeight;
// console.log("scrolling2", svgContainer.style.height, svgContainer.scrollTop, containerScrollHeight);
}
// Set the height on page load
window.addEventListener('load', setSvgContainerHeight);
// Update the height if the window is resized
window.addEventListener('resize', setSvgContainerHeight);
</script>
<script src="displayProgress.js"></script>
<script>
// This function is not actually used, since resizing is handled locally and the Engine
// is just notified of the change by an Ajax message. But if this form is used, the
// Engine can handle resizing, which will be needed if in the future line breaks are implemented.
// It does, however, inevitably create flicker
function submitResizeForm(width, height) {
// Create a form element
const form = document.createElement('form');
form.method = 'POST';
form.action = '<?php echo $_SERVER["PHP_SELF"]; ?>';
// Create hidden input elements for width and height
const widthInput = document.createElement('input');
widthInput.type = 'hidden';
widthInput.name = 'windowWidth';
widthInput.value = width;
const heightInput = document.createElement('input');
heightInput.type = 'hidden';
heightInput.name = 'windowHeight';
heightInput.value = height;
// Append inputs to the form
form.appendChild(widthInput);
form.appendChild(heightInput);
// Append form to the body
document.body.appendChild(form);
// Submit the form
form.submit();
}
window.addEventListener('resize', function() {
const width = window.innerWidth;
const height = window.innerHeight;
let reasonstart = 0.7 * width; // keep this in sync with the original setting
// Select all elements with the class 'reason'
const reasons = document.querySelectorAll('.reason');
// Update the .left attribute of each reason element
reasons.forEach(reason => {
reason.style.left = reasonstart + 'px';
});
// send Ajax message notifying the Engine
var xhr = new XMLHttpRequest();
xhr.open("POST", "<?php echo ($_SERVER['PHP_SELF']); ?>", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
const data = `windowWidth=${encodeURIComponent(width)}&windowHeight=${encodeURIComponent(height)}&reasonstart=${encodeURIComponent(reasonstart)}`;
console.log('Data being sent:', data);
xhr.send(data);
});
</script>
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST")
{ if (isset($_POST['autoProblemField']))
{
$problemnumber = $_POST["autoProblemField"];
$topicnumber = $_POST["autoTopicField"];
$sender = "autoTest";
$language = $_POST["language"];
$width = $_POST["width"];
$_SESSION['languagenumber'] = $language;
}
if (isset($_POST['problemnumberField']))
{
$problemnumber = $_POST["problemnumberField"];
$topicnumber = $_POST["topicField"];
$sender = "GetProblem";
$language = $_POST["language"];
$width = $_POST["widthField"];
$height = $_POST["heightField"];
$_SESSION['languagenumber'] = $language;
$windowWidth = $_SESSION['width'] = intval($_POST["widthField"]); // width of the browser window
$toolbarwidth = $_SESSION['toolbarwidth'] = intval($_POST["toolbarwidthField"]);
}
if (isset($_POST["widthField3"])) // from EnterLinearEquations.php or other EnterSomething.php files
{
$sender = "startSolving";
$width = $_SESSION['width'] = intval($_POST["widthField3"]);
$toolbarwidth = $_SESSION['toolbarwidth'] = intval($_POST["toolbarwidthField"]);
$height = $_SESSION['height'] = intval($_POST["heightField3"]);
$language = $_POST["language"];
$topic = $_POST["topicField"];
}
if (isset($_POST['autoStep']))
{
$sender = "autoStep";
}
if (isset($_POST['showStep']))
{
$sender = "showStep";
}
if (isset($_POST['autoFinish']))
{
$sender = "autoFinish";
}
if (isset($_POST['undo']))
{
$sender = "undo";
}
if (isset($_POST['hint']))
{
$sender = "hint";
}
if (isset($_POST['assumptions']))
{
$sender = "assumptions";
}
if (isset($_POST['finished']))
{
$sender = "finished";
}
if (isset($_POST['language2']))
{
$sender = "setLanguage";
$language = $_POST['language2'];
}
if (isset($_POST['commandID']))
{
$commandID = $_POST['commandID'];
$_SESSION['commandID'] = $commandID;
// so we can find it when we are ready to exec it
$sender = "selectMenuChoice";
}
if (isset($_POST['selectedRectangleField']))
{
$sender = "selectedRectangleSymbol";
$param = $_POST['selectedRectangleField'];
}
if (isset($_POST['suppliedArgText']))
{
$sender = "suppliedArgText";
$param = $_POST['suppliedArgText'];
}
if (isset($_POST['arg'])) // this is the arg text that has parsed and passed check_arg
{
$sender = "execOpWithArg";
$param = $_SESSION['commandID'] . "+" . $_POST['arg'];
$_SESSION['show_selectTermHint'] = false;
}
$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));
// setting the receive timeout to zero effectively says don't time out, wait as long as it takes for an answer.
$result = socket_connect($socket, $serverAddress, $serverPort);
if ($result)
{ if($sender=="GetProblem")
{
$reasonstart = intval(0.7 * $width); // x-coordinate where we want the reasons to start, in papyrus coordinates
$response = sendMessage($clientSocket,"initSymbolDocFromLibrary",
$topicnumber . "+" . $problemnumber . "+" . $reasonstart . "+" . $width. "+" . "$language");
if ($response === false)
{
$errcode = socket_last_error($socket);
$message = socket_strerror($errcode);
echo "Socket_read error: $message<br>";
}
else
{
// The response consists of some Javascript, followed by <svg> elements,
// one for the problem, and one for the reason, in this case "the problem"
// but then also svg for the tooltips
// Find the position of the first occurrence of "</script>"
$position = stripos($response, '</script>');
// Check if "</script>" was found
if ($position !== false) {
// Include the length of "</script>" to split after it
$position += strlen('</script>');
// Split the response into two parts
$scriptPart = substr($response, 0, $position);
$svgPart = substr($response, $position);
} else {
// If "</script>" is not found, keep the response as is--but it will be found
$svgPart = $response;
$scriptPart = '';
}
echo($scriptPart);
?>
<div id="svgContainer">
<div id="selectTermHint" class="selectHint">
To take a step yourself, select the expression you want to change.
<button id="showMeButton"
class="video-button" style="margin-left: 10px;">
<span class="video-icon">🎥</span>
Show me how
</button>
</div>
<?php
echo($svgPart);
?>
</div>
<?php
}
socket_close($socket); // because server has already closed it
}
else if($sender=="autoTest")
{ $breakwidth = $width; // max length of pieces of broken lines
$reasonstart = intval(0.7 * $width); // x-coordinate where we want the reasons to start, in papyrus coordinates 2
$param = $topicnumber . "+" . $problemnumber . "+" . $reasonstart . "+" . $breakwidth. "+" . "$language";
$response = sendMessage($clientSocket,"initAndAutoFinish",$param);
if ($response === false)
{
$errcode = socket_last_error($socket);
$message = socket_strerror($errcode);
echo "Socket_read error: $message<br>";
}
else
{
?>
<div id="svgContainer">
<?php
echo($response);
?>
</div>
<?php
}
socket_close($socket); // because server has already closed it
}
else if($sender == "autoStep" or $sender == "undo" or $sender == "autoFinish" or $sender == "selectMenuChoice" or $sender == "execOpWithArg" or
$sender == "showStep" or $sender == "finished" or $sender == "hint" or $sender == "setLanguage" or $sender == "selectedRectangleSymbol" or
$sender == "symbolWindowResized" or $sender == "startSolving"
)
{
if($sender == "setLanguage")
{
$param = $language;
$_SESSION['languagenumber'] = $language;
}
else if($sender == "selectMenuChoice")
{
$param = $commandID;
}
else if($sender == "startSolving")
{ $param = "$width+$height+$topic";
}
else if(! isset($param))
$param = "dummy";
$response = sendMessage($clientSocket,$sender,$param);
if ($response === false)
{
$errcode = socket_last_error($socket);
$message = socket_strerror($errcode);
echo "Socket_read error: $message<br>";
}
else
{
// The response consists of many <svg> elements, to redraw the whole computation
// it may also include other things to be echoed, such as "<script> highlightIndex = 3; </script>"
?>
<div id="svgContainer">
<div id="selectTermHint" class="selectHint">
To take a step yourself, select the expression you want to change.
<button id="showMeButton"
class="video-button" style="margin-left: 10px;">
<span class="video-icon">🎥</span>
Show me how
</button>
</div>
<?php
echo($response);
?>
</div>
<?php
}
socket_close($socket); // because server has already closed it
}
}
else
{
echo "Failed to connect to the C program: " . socket_strerror(socket_last_error()) . "<br>";
}
}
}
?>
<div id="hintModal">
<div id="hintModalContent">
<button onclick="hideHintModal()">Close</button>
</div>
</div>
<div id="assumptionsModal">
<div id="assumptionsModalContent">
<button onclick="hideAssumptionsModal()">Close</button>
</div>
</div>
<script src="crashPopup.js"></script>
<script>
window.addEventListener('beforeunload', function (e) {
// Send the beacon request to notify the server about session end
navigator.sendBeacon('<?php echo $_SERVER["PHP_SELF"]; ?>?action=endSession');
});
</script>
<script>
<?php
if($sender == "hint")
{ // embed JavaScript to show the hint modal on page load
echo 'document.addEventListener("DOMContentLoaded", function() {
// Show the hint modal
showHintModal();
});';
}
if($sender == "assumptions")
{ // embed JavaScript to show the hint modal on page load
echo 'document.addEventListener("DOMContentLoaded", function() {
// Show the assumptions modal
showAssumptions();
});';
}
if($sender == "selectedRectangleSymbol" || $sender == "showStep")
{
echo 'document.addEventListener("DOMContentLoaded", function() {
console.log("about to display selection menu");
showSelectionMenu(200,100);
});';
}
?>
document.getElementById('languageSelector').addEventListener('change', function() {
var language = this.value;
setLanguage(language);
});
function Graph(){
// submit a hidden form, so that PHP can process it and send the graphButton message
// the form needs no input fields.
var myform = document.createElement('form');
// Set form attributes
myform.setAttribute('method', 'post');
myform.setAttribute('action', <?php echo json_encode($nextpagegraph); ?>);
myform.style.display = 'none';
// Append the form to the body
document.body.appendChild(myform);
// Submit the form
myform.submit();
}
function setSelectedLanguage(){
// Assume 'languageSelector' is the id of the language select dropdown
var languageValue = document.getElementById('languageSelector').value;
var myform = document.createElement('form');
// Set form attributes
myform.setAttribute('method', 'post');
myform.setAttribute('action', <?php echo json_encode($_SERVER['PHP_SELF']); ?>); // Use json_encode to properly handle strings
myform.style.display = 'none';
// Create a hidden input to carry the language value
var languageInput = document.createElement('input');
languageInput.setAttribute('type', 'hidden');
languageInput.setAttribute('name', 'language2');
languageInput.setAttribute('value', languageValue);
// Append the hidden input to the form
myform.appendChild(languageInput);
// Append the form to the body
document.body.appendChild(myform);
// Submit the form
myform.submit();
}
function selectedRect(param)
// called on pointerup, with param set to be the parameter for the selectedRectangleSymbol message
{
var myform = document.createElement('form');
// Set form attributes
myform.setAttribute('method', 'post');
myform.setAttribute('action', <?php echo json_encode($_SERVER['PHP_SELF']); ?>); // Use json_encode to properly handle strings
myform.style.display = 'none';
// Create a hidden input to carry the selected rectangle
var selectedRectangleInput = document.createElement('input');
selectedRectangleInput.setAttribute('type', 'hidden');
selectedRectangleInput.setAttribute('name', 'selectedRectangleField');
selectedRectangleInput.setAttribute('value', param);
// Append the hidden input to the form
myform.appendChild(selectedRectangleInput);
// Append the form to the body
document.body.appendChild(myform);
// Submit the form
myform.submit();
}
function showAssumptions() {
// Create a popup window
var assumptionsModal = document.getElementById('assumptionsModal');
assumptionsModal.style.display = 'flex';
// Create a div with relative positioning
var div = document.createElement('div');
div.style.position = 'relative';
// Append the div to the modal content
var modalContent = document.getElementById('assumptionsModalContent');
modalContent.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 to show the hint modal
function showHintModal() {
// Get the hint modal
var hintModal = document.getElementById('hintModal');
hintModal.style.display = 'flex';
console.log("called showHintModal");
// Create a div with relative positioning
var div = document.createElement('div');
div.style.position = 'relative';
// Append the div to the modal content
var modalContent = document.getElementById('hintModalContent');
modalContent.appendChild(div);
// Display all SVG elements with class "hint" (There will only be one)
var hints = document.querySelectorAll('.hint');
hints.forEach(function (hint) {
// Clone the hint
var clonedHint = hint.cloneNode(true);
// perhaps it's a very long hint, in which case we need to wrap it
let maxwidth = 800;
if (clonedHint.style.width > maxwidth)
{ wrapSVGTextWithTspans(clonedHint,maxwidth );
}
// Set the display property to block (or any other appropriate value)
clonedHint.style.display = 'block';
// Append the cloned hint to the div
div.appendChild(clonedHint);
});
}
// Function to hide the hint modal
function hideHintModal() {
var hintModal = document.getElementById('hintModal');
hintModal.style.display = 'none';
}
// Function to hide the assumptions modal
function hideAssumptionsModal() {
var hintModal = document.getElementById('assumptionsModal');
assumptionsModal.style.display = 'none';
}
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
let selectedX = selectionRectangle.style.left = 0;
let selectedY = selectionRectangle.style.top = 0;
let selectedWidth = selectionRectangle.style.width;
let selectedHeight = selectionRectangle.style.height;
let windowwidth = document.body.clientWidth;
let windowheight = window.innerHeight; // technically this is the "viewport height"
let startX, startY, isSelecting = false;
let activeGraphBounds = null;
let whichline = 0;
// the following variables are set in pointerdown and used in pointerup
let lineX = 0;
let lineY = 0;
let lineWidth = 0;
let lineHeight = 0;
// let activeline = 0; sent by the Engine now
function currentlinenumber() {
const lines = document.querySelectorAll('.line'); // Get all elements with class "line"
let maxId = -1; // Start with -1, so if no lines are found, the result is -1
lines.forEach(line => {
const id = parseInt(line.getAttribute('id').replace('line', ''), 10);
if (id > maxId) {
maxId = id;
}
});
return Math.abs(maxId);
}
document.addEventListener('pointerdown', function(e) { // pointerdown, not mousedown, so it will work on iPhone and iPad
if (e.shiftkey == false && selectionMade)
selectionMade = false;
if (!svgContainer.contains(selectionRectangle))
svgContainer.appendChild(selectionRectangle);
let lines = document.querySelectorAll('.line');
let rect = svgContainer.getBoundingClientRect();
// if there is a selectionMenu already on-screen, and this mousedown is outside it, remove it
const menuContainer = document.getElementById('selectionMenu');
if (menuContainer) {
let menurect = menuContainer.getBoundingClientRect();
// Check if the click is outside of menurect
if (e.clientX < menurect.left || e.clientX > menurect.right ||
e.clientY < menurect.top || e.clientY > menurect.bottom) {
// Click is outside, so remove the menuContainer
document.body.removeChild(menuContainer);
document.querySelectorAll('.selectmenuitem').forEach(item => {
item.parentNode.removeChild(item); // Remove all the menu items too
});
// Remove the rectangles of class "selectedRectangle", which
// color the background of the selected term(s)
document.querySelectorAll('.selectedRectangle').forEach(rect => {
rect.parentNode.removeChild(rect); // Remove each rectangle from its parent node
// And we also remove the svg elements that contain the selected rectangles.
document.querySelectorAll('.highlight').forEach(svg => {
svg.parentNode.removeChild(svg);
});
// This doesn't remove the selectedRectangle from the document in the Engine.
// It will be removed anyway at the next step, so even though there is an Ajax
// messsage to remove it, that's not necessary.
// The selectionRectangle div now appears as a 2px by 2px dot.
// The following code removes it
const selectedRectangle = document.getElementById('selectionRectangle');
selectedRectangle.parentNode.removeChild(selectionRectangle);
});
}
}
let x = e.clientX - rect.left + svgContainer.scrollLeft;
let y = e.clientY - rect.top + svgContainer.scrollTop; // Now these coordinates are relative to svgContainer
// activeline = currentlinenumber(); // should be active line number,
// Now activeline is defined in Javascript sent by the Engine
let lineid = "line" + activeline;
var line = document.getElementById(lineid);
lineX = parseInt(line.style.left,10); // these coordinates are relative to svgContainer
lineY = parseInt(line.style.top,10);
lineWidth = line.width.baseVal.value;
lineHeight = line.height.baseVal.value;
if (x >= lineX && x <= lineX + lineWidth && y >= lineY && y <= lineY + lineHeight)
{
isSelecting = true;
startX = x;
startY = y;
activeGraphBounds = { left: lineX, top: lineY, right: lineX + lineWidth, bottom: lineY + lineHeight };
updateSelectionRectangle(x, y, 0, 0);
}
});
document.addEventListener('pointermove', function(e) {
e.stopPropagation();
e.preventDefault();
if (!isSelecting || !activeGraphBounds) return;
// prevent the browser from getting the pointermoves
// but this doesn't seem to work
let rect = svgContainer.getBoundingClientRect();
let x = e.clientX - rect.left + svgContainer.scrollLeft;
let y = e.clientY - rect.top + svgContainer.scrollTop;
// Constrain the selection 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 width = Math.abs(x - startX);
let height = Math.abs(y - startY);
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;
updateSelectionRectangle(newX, newY, width, height);
});
document.addEventListener('pointerup', function(e) {
if (!isSelecting) return;
isSelecting = false;
activeGraphBounds = null; // Reset the active graph bounds
// The final selected rectangle is available here
// selectedX = parseInt(selectionRectangle.style.left,10);
selectedX = parseInt(selectionRectangle.style.left,10);
selectedY = parseInt(selectionRectangle.style.top,10);
selectedWidth = parseInt(selectionRectangle.style.width,10);
selectedHeight = parseInt(selectionRectangle.style.height,10);
if (!(selectedWidth > 0 && selectedHeight > 0))
return;
selectionMade = true;
// Next create the parameter for the selectedRectangleSymbol method, which is supposed to be
// [left,top,width,height,lineleft,linetop,linewidth,lineheight,windowwidth,windowheight].
// but they all have to be scaled down by scaleFactor
param = "[" + [
Math.floor(selectedX / scaleFactor),
Math.floor(selectedY / scaleFactor),
Math.floor(selectedWidth / scaleFactor),
Math.floor(selectedHeight / scaleFactor),
Math.floor(lineX / scaleFactor),
Math.floor(lineY / scaleFactor),
lineWidth / scaleFactor, // a double
lineHeight / scaleFactor, // a double
windowwidth, // this hasn't been scaled
windowheight // this hasn't been scaled
] + "]";
console.log(param);
selectedRect(param);
});
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';
}
function showSelectionMenu(x, y)
// This shows the selection menu with upper left corner at x,y,
// provided there are any elements of class "selectmenuitem".
// If global variable highlightIndex is nonnegative, highlight the item with that index
{
// make the popup that displays "Select the term you want to change" disappear, so it doesn't lie behind the menu
selectTermHint = document.getElementById('selectTermHint');
selectTermHint.style.display = "none";
if(typeof highlightIndex == 'undefined')
highlightIndex = -1; // Maybe it's already been defined in the response to the showStep message
// Find all selectmenuitem SVG elements
const menuItems = document.querySelectorAll('.selectmenuitem');
// Ensure that each item has a viewBox // but they all do so this is superfluous
menuItems.forEach((item, index) => {
console.log(item);
if (!item.hasAttribute('viewBox')) {
let width = parseInt(item.getAttribute('width'));
let height = parseInt(item.getAttribute('height'));
item.setAttribute('viewBox', `0 0 ${width} ${height}`);
console.log(`Updated: ID ${item.id}, viewBox=${item.getAttribute('viewBox')}`);
}
});
// Check if menuItems is empty
if (menuItems.length === 0) {
console.log('No menu items found.');
return; // Exit the function early
}
// Create or find the menu container
let menuContainer = document.getElementById('selectionMenu');
if (!menuContainer) {
menuContainer = document.createElement('div');
menuContainer.id = 'selectionMenu';
document.body.appendChild(menuContainer);
}
// Clear previous menu items
menuContainer.innerHTML = '';
// Set position
menuContainer.style.position = 'absolute';
menuContainer.style.left = x + 'px';
menuContainer.style.top = y + 'px';
menuContainer.style.display = 'block';
menuContainer.style.backgroundColor = 'white';
menuContainer.style.border = '1px solid black';
menuContainer.style.padding = '0px';
let menuwidth = 20; // not 20px
let nitems = 0;
let accumulatedHeight = 0; // Keep track of the total height of menu items added so far
const menuItemSpacing = 3; // Spacing between menu items
// calculate maximum width of the menuItems
menuItems.forEach((item, index) => {
const itemWidth = parseInt(item.getAttribute('width'));
if (itemWidth > menuwidth) {
menuwidth = itemWidth;
}
});
// Append items to the container and add click event listeners
menuItems.forEach((item, index) => {
const wrapper = document.createElement('div');
wrapper.className = 'selectMenuItemWrapper';
const menuItem = item.cloneNode(true); // Clone the SVG element
menuItem.style.display = "block"; // Make SVG visible
const itemWidth = parseInt(menuItem.getAttribute('width'));
const itemHeight = parseInt(menuItem.getAttribute('height'));
// Set wrapper styles
wrapper.style.width = `${menuwidth}px`;
wrapper.style.height = `${itemHeight}px`;
wrapper.style.position = 'absolute';
wrapper.style.left = '0px'; // align items to the left
wrapper.style.top = `${accumulatedHeight}px`; // Position items vertically
// Highlight only if highlightIndex is non-negative and matches the current index
if (index === highlightIndex && highlightIndex >= 0) {
wrapper.classList.add('highlighted');
}
// Add the current item's height plus spacing to the total height
accumulatedHeight += itemHeight + menuItemSpacing;
wrapper.appendChild(menuItem); // Wrap the SVG with the div
wrapper.addEventListener('click', function() {
const commandID = parseInt(menuItem.getAttribute('id')); // Use menuItem to get the commandID
console.log(commandID);
selectMenuChoice(commandID);
});
menuContainer.appendChild(wrapper);
});
// Adjust the menu container's width to fit all items
menuContainer.style.width = `${menuwidth}px`;
// Set the menu container's total height to fit all items including spacing
accumulatedHeight -= menuItemSpacing;
menuContainer.style.height = `${accumulatedHeight}px`;
}
function selectMenuChoice(commandID) {
console.log('Selected command ID:', commandID);
// Create and submit a hidden form to carry commandID as $_POST data
const form = document.createElement('form');
form.method = 'POST';
form.action = '<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>'; // Dynamically set to this script
form.style.display = 'none';
// Create a hidden field for commandID
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = 'commandID';
hiddenField.value = commandID;
// Append the hidden field to the form
form.appendChild(hiddenField);
// Append the form to the body and submit it
document.body.appendChild(form);
form.submit();
}
// Declare c at a higher scope
let conditionC;
document.addEventListener('DOMContentLoaded', function() {
showCrashPopup(); // if a crash has occurred, notify the user.
const svgPrompt = document.querySelector('.needsargprompt');
if (svgPrompt) {
displayPrompt(svgPrompt);
}
});
function displayPrompt(svgElement) {
const modal = document.getElementById('promptModal');
const modalContent = document.getElementById('modalContent');
// Clear previous content
modalContent.innerHTML = '';
// Extract condition from svgElement's ID
const idStr = svgElement.id;
const match = idStr.match(/\d+$/); // Matches number at the end of the string
conditionC = match ? parseInt(match[0], 10) : null; // Save the parsed number globally
// Clone the SVG element, modify its style properties, and append it
const clonedSvg = svgElement.cloneNode(true);
clonedSvg.style.display = 'block'; // Ensure it is visible within the modal
clonedSvg.style.position = 'relative'; // Adjust positioning to be relative within the modal
modalContent.appendChild(clonedSvg);
// Create and append the input field
const inputField = document.createElement('input');
inputField.id = 'responseInput';
inputField.type = 'text';
inputField.placeholder = 'Enter your response here';
modalContent.appendChild(inputField);
// Create a container for the buttons
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'space-evenly';
buttonContainer.style.marginTop = '10px'; // Space above the buttons
// Create and append the OK (submit) button
const okButton = document.createElement('button');
okButton.textContent = 'OK';
okButton.addEventListener('click', submitResponse);
buttonContainer.appendChild(okButton);
// Create and append the Cancel button
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.addEventListener('click', function() {
document.getElementById('promptModal').style.display = 'none';
// and get rid of the rectangle(s) highlighting the selected term (or terms)
document.querySelectorAll('.selectedRectangle').forEach(rect => {
rect.parentNode.removeChild(rect); // Remove each rectangle from its parent node
// And we also remove the svg elements that contain the selected rectangles.
document.querySelectorAll('.highlight').forEach(svg => {
svg.parentNode.removeChild(svg);
});
// This doesn't remove the selectedRectangle from the document in the Engine.
// It will be removed anyway at the next step, so even though there is an Ajax
// messsage to remove it, that's not necessary.
// The selectionRectangle div now appears as a 2px by 2px dot.
// The following code removes it
const selectedRectangle = document.getElementById('selectionRectangle');
selectedRectangle.parentNode.removeChild(selectionRectangle);
});
});
buttonContainer.appendChild(cancelButton);
// Append the button container to the modal content
modalContent.appendChild(buttonContainer);
// Create and append the response area
const responseArea = document.createElement('div');
responseArea.id = "responseArea";
modalContent.appendChild(responseArea);
// Show the modal
modal.style.display = 'block';
// Ensure the modal and its contents are accessible
modal.style.zIndex = '1000'; // Make sure modal is above other content
modal.style.pointerEvents = 'auto'; // Ensure it can be interacted with
// Focus on input after showing the modal
inputField.focus();
}
function submitResponse() {
const inputText = document.getElementById('responseInput').value;
console.log('Entered text:', inputText);
console.log('conditionC = ',conditionC);
if (inputText.trim().length == 0)
{ alert("You must enter a formula.") // This will have to be localized.
// document.querySelector('.needsargprompt'); is the prompt, as SVG; Let's just repeat that in the alert.
return;
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
const data = `suppliedArgText=${encodeURIComponent(inputText)}&needsArgCondition=${encodeURIComponent(conditionC)}`;
console.log('Data being sent:', data);
xhr.send(data);
// Get the server response
xhr.onload = function() {
if (xhr.status == 200) {
console.log("Response received:", xhr.responseText);
// Did it parse? Did it satisfy check_arg?
let responseText = xhr.responseText;
if(responseText == "0+0") {
// no message: it parsed and passed check_arg
checkArgMessage = "OK";
// Create a hidden form to submit the argument
const form = document.createElement('form');
form.method = 'POST';
form.action = '<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>'; // Adjust the action as needed
form.style.display = 'none';
// Create a hidden input to carry the argument value
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'arg';
hiddenInput.value = inputText;
// Append the hidden input to the form
form.appendChild(hiddenInput);
document.body.appendChild(form);
form.submit();
console.log("Form submitted with arg:", inputText);
document.getElementById('promptModal').style.display = 'none'; // close the modal
// There is no real need to close the modal as the whole file is finished when we submit the form,
// but if I don't close it here, there is a brief flashing view of "0+0", which is thus prevented.
}
// Update the modal with the response from the server
const responseArea = document.getElementById('responseArea');
// responseText is an <svg> element, possibly with display:none.
// We need to put it into responseArea after setting display:block
responseArea.innerHTML = responseText;
svgElement = responseArea.querySelector('svg');
if(svgElement) {
svgElement.style.display = 'block'; // Ensure SVG is visible
console.log("SVG element found and made visible");
}
responseArea.style.display = 'block'; // Make sure it's visible
responseArea.style.position = "relative";
if(svgElement.classList.contains("feedback")) {
// conditionC is intbyparts1, so change it to intbyparts2 now
conditionC = conditionC + 1;
console.log("New conditionC is ", conditionC);
}
} else {
console.log("Error in Ajax request");
}
};
// Optionally, one might clear the input
// document.getElementById('responseInput').value = '';
// or close the modal by
// document.getElementById('promptModal').style.display = 'none';
}
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
if(languageNumber == undefined)
var languageNumber = 0; // ENGLISH
// if it is already defined, then languageName has been set in code supplied by the Engine with sendDocument
setLanguage(languageName); // translate button text etc. Engine is told what the language is by setSelectedLanguage. That's done when it changes, not here.
// set the language selector to the current language
document.getElementById('languageSelector').value = languageName;
console.log("setting language to ",languageName);
if(document.getElementById("progress0")) // steps of factorization or integration by parts
displayProgress();
if(hide_selectTermHint){
let theHint = document.getElementById("selectTermHint");
// theHint.style.opacity = 0; // this still allows a flickering "ghost" to appear
theHint.style = "display:none";
}
if (typeof showGraphButton !== 'undefined' && showGraphButton === 0) {
const graphButton = document.getElementById('graphButton');
// Set display to none to hide the button entirely
graphButton.style.display = 'none';
}
});
</script>
<script>
// script to play ShowMe video
document.addEventListener("DOMContentLoaded", function () {
// Check for either button
const button = document.getElementById("showMeButton");
// If no such button exists, do nothing
if (!button)
return;
// Add the event listener to the button
button.addEventListener("click", function () {
console.log("Trying to play video");
// Create the popup container
const videoPopup = document.createElement("div");
videoPopup.id = "videoPopup";
videoPopup.style.position = "fixed";
videoPopup.style.top = "50%";
videoPopup.style.left = "50%";
videoPopup.style.transform = "translate(-50%, -50%)";
videoPopup.style.backgroundColor = "rgba(0, 0, 0, 0.8)";
videoPopup.style.padding = "20px";
videoPopup.style.borderRadius = "8px";
videoPopup.style.zIndex = "1000";
videoPopup.style.display = "flex";
videoPopup.style.flexDirection = "column";
videoPopup.style.alignItems = "center";
// Create the video element
const videoElement = document.createElement("video");
videoElement.src = "ShowMe.mp4";
videoElement.controls = true;
videoElement.autoplay = true;
videoElement.onloadeddata = function () { // inserted 3.12.25
if (videoElement.requestFullscreen) {
videoElement.requestFullscreen();
} else if (videoElement.webkitRequestFullscreen) { // Safari
videoElement.webkitRequestFullscreen();
} else if (videoElement.mozRequestFullScreen) { // Firefox
videoElement.mozRequestFullScreen();
} else if (videoElement.msRequestFullscreen) { // Edge/IE
videoElement.msRequestFullscreen();
}
};
videoElement.addEventListener("fullscreenchange", function () {
if (!document.fullscreenElement) {
document.body.removeChild(videoPopup);
}
});
videoElement.style.width = "100%";
videoElement.style.maxWidth = "600px";
videoElement.style.borderRadius = "8px";
// Create the close button
const closeButton = document.createElement("button");
closeButton.textContent = "X";
closeButton.style.position = "absolute";
closeButton.style.top = "10px";
closeButton.style.right = "10px";
closeButton.style.backgroundColor = "red";
closeButton.style.color = "white";
closeButton.style.border = "none";
closeButton.style.borderRadius = "50%";
closeButton.style.cursor = "pointer";
closeButton.style.fontSize = "16px";
closeButton.style.width = "30px";
closeButton.style.height = "30px";
closeButton.addEventListener("click", function () {
document.body.removeChild(videoPopup);
});
// Append elements to the popup
videoPopup.appendChild(videoElement);
videoPopup.appendChild(closeButton);
// Append the popup to the body
document.body.appendChild(videoPopup);
});
});
</script>
<script>
// Script to move the reasons to the right of long lines
document.addEventListener('DOMContentLoaded', () => {
// Iterate over all elements with ids starting with "line"
const lineElements = Array.from(document.querySelectorAll('[id^="line"]'));
lineElements.forEach(lineElement => {
const lineId = lineElement.id; // E.g., "line13"
const lineNumber = lineId.replace('line', ''); // Extract the number, e.g., "13"
const reasonId = `reason${lineNumber}`; // Form the corresponding reason id, e.g., "reason13"
const reasonElement = document.getElementById(reasonId);
if (reasonElement) {
const lineWidth = parseFloat(lineElement.getAttribute('width')) || 0; // Get line width as a number
const currentLeft = parseFloat(reasonElement.style.left) || 0; // Parse existing left position of reason
// Update left position if necessary
if (currentLeft <= lineWidth + 16) {
reasonElement.style.left = `${lineWidth + 16}px`;
}
}
});
});
</script>
<script src="TypeSize.js"></script>
<script src="wrapSVGtext.js"></script>
<script src="Tooltips.js"></script> <!-- has to come last, or tooltips don't work on .selectionmenuitem -->
<script>
document.getElementById('svgContainer').addEventListener('pointermove', function(event) {
const container = this;
const rect = container.getBoundingClientRect();
const margin = 20; // How close to the edge before scrolling starts
const scrollSpeed = 10; // Adjust scrolling speed
let scrolled = false; // Track if scrolling happens
// Scroll right if the pointer is near the right edge
if (event.clientX > rect.right - margin) {
container.scrollLeft += scrollSpeed;
scrolled = true;
}
// Scroll left if the pointer is near the left edge
else if (event.clientX < rect.left + margin) {
container.scrollLeft -= scrollSpeed;
scrolled = true;
}
// If scrolling occurred, adjust the selection rectangle's position
if (scrolled) {
const selectionRectangle = document.getElementById('selectionRectangle');
if (selectionRectangle) {
selectionRectangle.style.left = parseInt(selectionRectangle.style.left) + container.scrollLeft + 'px';
}
}
});
</script>
</body>
</html>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists