Files
charade_experiment/experiment-scripts/index.html
2025-09-18 10:26:33 +02:00

578 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
/* width: 100%; */
border-collapse: collapse;
font-family: sans-serif;
font-size: 0.95rem;
background-color: #fff;
border: 1px solid #ccc;
max-height: 400px;
overflow-y: scroll;
margin: 10px;
}
th, td {
padding: 0.25em 1em;
border: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f4f4f4;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f1f1f1;
}
div.container table {
max-height: 100vh;
overflow-y: scroll;
}
body {
font-family: Arial, sans-serif;
margin: 20px;
padding: 0;
background-color: #f4f4f4;
}
.container {
max-width: 700px;
min-width: 400px;
margin: 10px;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
margin-bottom: 20px;
}
label {
display: block;
margin: 8px 0 4px;
font-weight: bold;
}
select, input[type="number"], input[type="text"] {
width: 100%;
padding: 8px;
margin-bottom: 16px;
border-radius: 4px;
border: 1px solid #ccc;
}
.row {
display: flex;
justify-content: space-between;
}
.row .input-group {
width: 32%;
}
.row .input-group input {
width: 100%;
}
.button-container {
text-align: center;
}
.button-container button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button-container button:hover {
background-color: #45a049;
}
.word {
border-radius: 4px;
padding: 3px 8px;
display: inline;
border: 1px solid gray;
}
.word > input[type="checkbox"] {
display: none;
}
.word:hover > input[type="checkbox"] {
display: inline;
}
.word-selected {
background-color:#4c6faf;
}
.word:hover {
border: 1px solid black;
background-color:#0c59e7;
color: white;
font-weight: bold;
}
.word-timer::after {
content: "s";
}
.word-done::before {
content: "❌ ";
}
.word-correct::before {
content: "✅ ";
}
.word:hover .word-correct::before {
content: "";
}
.word:hover .word-done::before {
content: "";
}
.current-word {
font-weight: bold;
}
.current-word::before {
content: "▶ ";
}
#word-list-interactive > div {
margin: 20px 10px;
}
.flex {
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<h1 style="text-align: center;">VR Charades</h1>
<div class="flex">
<!-- <div class="container">
<h2>Facial Expressions</h2>
<div style="max-height: 80vh; overflow-y: scroll;">
<table id="facialExpressions">
<tr>
<th>Index</th>
<th>Property</th>
<th>Value Slider</th>
<th>Value Number</th>
</tr>
</table>
</div>
</div>
<div class="container">
<h2>Modify Element</h2>
<label for="element-select">Select Element</label>
<select id="element-select">
</select>
<div class="row">
<div class="input-group">
<label for="translate-x">Translation X</label>
<input type="number" id="translate-x" placeholder="X">
</div>
<div class="input-group">
<label for="translate-y">Translation Y</label>
<input type="number" id="translate-y" placeholder="Y">
</div>
<div class="input-group">
<label for="translate-z">Translation Z</label>
<input type="number" id="translate-z" placeholder="Z">
</div>
</div>
<div class="row">
<div class="input-group">
<label for="rotate-x">Rotation X</label>
<input type="number" id="rotate-x" placeholder="X">
</div>
<div class="input-group">
<label for="rotate-y">Rotation Y</label>
<input type="number" id="rotate-y" placeholder="Y">
</div>
<div class="input-group">
<label for="rotate-z">Rotation Z</label>
<input type="number" id="rotate-z" placeholder="Z">
</div>
</div>
<div class="button-container">
<button type="button">Modify</button>
</div>
</div> -->
<div class="container">
<h2>Charades</h2>
<label for="ip-player1">IP Player 1</label>
<input type="text" id="ip-player1" placeholder="10.42.0.38">
<label for="ip-player2">IP Player 2</label>
<input type="text" id="ip-player2" placeholder="10.42.0.100">
<fieldset style="display: block; margin: 20px 0px;">
<legend>Current Player For Acting</legend>
<div style="margin: 0.4em;">
<input id="chosen-player-1" name="chosen-player" type="radio" value="player1" checked />
<label style="display: inline; font-weight: normal;" for="chosen-player-1">Player 1</label>
</div>
<div style="margin: 0.4em;">
<input id="chosen-player-2" name="chosen-player" type="radio" value="player2" />
<label style="display: inline; font-weight: normal;" for="chosen-player-2">Player 2</label>
</div>
</fieldset>
<div class="row" style="margin-bottom: 60px;">
<div class="input-group">
<label for="time-s">Time (s)</label>
<input type="number" id="time-s" placeholder="30">
</div>
</div>
<label for="last-word-status">Last Word Status</label>
<select id="last-word-status">
<option value="-1">None</option>
<option value="0">False</option>
<option value="1">Correct</option>
</select>
<div class="row">
<div class="input-group">
<label for="word">Word</label>
<input type="text" id="word" placeholder="Word...">
</div>
</div>
<div class="button-container">
<button type="button" id="button-word">Send</button>
</div>
</div>
<div class="container">
<textarea id="word-list" name="word-list" rows="40" cols="50" placeholder="charade words"></textarea>
<br>
<div class="button-container">
<button type="button" id="button-shuffle-words">Shuffle</button>
<button type="button" id="button-create-word-items">Modify</button>
</div>
</div>
<div class="container" style="overflow-x: scroll; max-height: 80vh;">
<div id="word-list-interactive">
<p>
Press <b>Modify</b> to generate and run the word list.
</p>
</div>
<div class="button-container">
<button type="button" id="button-save-to-file">Save as CSV</button>
<button type="button" id="button-stop">Stop</button>
</div>
</div>
</div>
<script>
let runningWordIndex = -1;
let runningWordList = [];
function createWordItems() {
const wordList = document.getElementById("word-list");
const text = wordList.value;
const wordListInteractive = document.getElementById("word-list-interactive");
wordListInteractive.innerHTML = "";
runningWordList = [];
runningWordIndex = -1;
let index = 0;
text.trim().split('\n').forEach((word) => {
index += 1;
const div = document.createElement("div");
div.classList.add("word");
div.style.display = "flex";
div.setAttribute("word", word.trim());
const div2 = document.createElement("div");
div2.style.width = "100%";
div2.style.display = "flex";
div2.style.justifyContent = "space-between";
const p = document.createElement("p");
const span = document.createElement("span");
span.innerText = `Word ${index}: `;
span.style.color = "gray";
const wordText = document.createTextNode(word.trim());
p.appendChild(span);
p.appendChild(wordText);
const input = document.createElement("input");
input.setAttribute("type", "checkbox");
input.addEventListener("input", () => {
if (input.checked) {
p.classList.add("word-correct");
} else {
p.classList.remove("word-correct");
}
});
const timerP = document.createElement("p");
timerP.classList.add("word-timer");
const timeSeconds = parseFloat(document.getElementById("time-s").value);
timerP.innerText = timeSeconds.toFixed(2);
timerP.setAttribute("remainingTime", timeSeconds.toFixed(2))
div2.appendChild(p);
div2.appendChild(timerP);
div.appendChild(input);
div.appendChild(div2);
wordListInteractive.appendChild(div);
runningWordList.push(div);
});
}
let frameId = undefined;
let last = undefined;
let newWord;
let lastWordStatus = -1;
function step(timestamp) {
if (last === undefined) {
last = timestamp;
runningWordIndex = 0;
newWord = true;
lastWordStatus = 1;
}
const elapsed = timestamp - last;
last = timestamp;
const runningWord = runningWordList[runningWordIndex];
const p = runningWord.querySelector("p");
const timer = runningWord.querySelector(".word-timer");
let remainingTime = parseFloat(timer.getAttribute("remainingTime")) - (elapsed / 1000);
if (newWord) {
newWord = false;
let word = runningWord.getAttribute("word");
sendNewWord(word, lastWordStatus, remainingTime);
}
if (p.classList.contains("word-correct") || remainingTime < 0) {
if (remainingTime < 0) {
remainingTime = 0;
}
p.classList.add("word-done");
runningWordIndex += 1;
lastWordStatus = p.classList.contains("word-correct") ? 1 : 0;
newWord = true;
}
timer.innerText = remainingTime.toFixed(2);
timer.setAttribute("remainingTime", remainingTime);
if (runningWordIndex < runningWordList.length) {
frameId = requestAnimationFrame(step);
} else {
frameId = undefined;
}
}
document.getElementById("button-save-to-file").addEventListener("click", () => {
let data = "word;correct;time left\n";
for (let i = 0; i < runningWordList.length; ++i) {
const p = runningWordList[i].querySelector("p");
const timer = runningWordList[i].querySelector(".word-timer");
const word = runningWordList[i].getAttribute("word");
const isCorrect = p.classList.contains("word-correct");
const timeLeft = parseFloat(timer.getAttribute("remainingTime"));
data += `${word};${isCorrect};${timeLeft}\n`;
}
window.open('data:text/csv;charset=utf-8,' + escape(data), '_blank');
});
document.getElementById("button-shuffle-words").addEventListener("click", async () => {
const wordList = document.getElementById("word-list");
const text = wordList.value.trim();
if (!text) {
alert("Please enter words to shuffle first.");
return;
}
// Don't allow shuffle if word list is already running
if (frameId !== undefined) {
alert("Cannot shuffle while word list is running. Please stop first.");
return;
}
const words = text.split('\n').map(word => word.trim()).filter(word => word);
try {
const response = await fetch("/shuffle", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ words: words }),
});
const result = await response.json();
if (result.status === "ok") {
wordList.value = result.shuffled_words.join('\n');
}
} catch (error) {
console.error("Error shuffling words:", error);
alert("Failed to shuffle words. Please try again.");
}
});
document.getElementById("button-create-word-items").addEventListener("click", () => {
createWordItems();
if (frameId) {
cancelAnimationFrame(frameId);
}
last = undefined;
newWord = undefined;
frameId = requestAnimationFrame(step);
});
document.getElementById('button-word').addEventListener('click', () => {
const chosenPlayer1 = document.getElementById("chosen-player-1").checked;
const chosenPlayer2 = document.getElementById("chosen-player-2").checked;
const ipPlayer1 = document.getElementById("ip-player1").value;
const ipPlayer2 = document.getElementById("ip-player2").value;
const target = chosenPlayer1 ? ipPlayer1 : ipPlayer2;
const targetOther = chosenPlayer1 ? ipPlayer2 : ipPlayer1;
const lastWordStatus = document.getElementById("last-word-status").value;
const timeSeconds = document.getElementById("time-s").value;
const word = document.getElementById("word").value;
const data = {
target: target,
lastWordStatus: parseInt(lastWordStatus),
timeSeconds: parseFloat(timeSeconds),
word: word,
};
fetch("/word", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data),
}).then(res => {
// console.log("Request complete! response:", res);
});
const data2 = {
target: targetOther,
lastWordStatus: parseInt(lastWordStatus),
timeSeconds: 0.0, // parseFloat(timeSeconds),
word: "", // word,
};
fetch("/word", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data),
}).then(res => {
// console.log("Request complete! response:", res);
});
});
function sendNewWord(word, lastWordStatus, timeSeconds) {
const chosenPlayer1 = document.getElementById("chosen-player-1").checked;
const chosenPlayer2 = document.getElementById("chosen-player-2").checked;
const ipPlayer1 = document.getElementById("ip-player1").value;
const ipPlayer2 = document.getElementById("ip-player2").value;
const target = chosenPlayer1 ? ipPlayer1 : ipPlayer2;
const targetOther = chosenPlayer1 ? ipPlayer2 : ipPlayer1;
console.log("target:", target);
console.log("targetOther:", targetOther);
const data = {
target: target,
lastWordStatus: lastWordStatus,
timeSeconds,
word: word,
};
fetch("/word", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data),
}).then(res => {
// console.log("Request complete! response:", res);
});
const dataOther = {
target: targetOther,
lastWordStatus: lastWordStatus,
timeSeconds: 0.0,
word: "", // word,
};
fetch("/word", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(dataOther),
}).then(res => {
// console.log("Request complete! response:", res);
});
}
</script>
</body>
</html>