483 lines
15 KiB
HTML
483 lines
15 KiB
HTML
<html>
|
|
<style>
|
|
body {
|
|
background: #111;
|
|
color: #f2f2f2;
|
|
font-family: "Press Start 2P", monospace;
|
|
/* retro pixel font */
|
|
padding: 20px;
|
|
text-shadow: 0 0 8px #ff00ff;
|
|
}
|
|
|
|
/* Import pixel font */
|
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
|
|
|
|
h1 {
|
|
color: #00eaff;
|
|
margin-top: 25px;
|
|
margin-bottom: 10px;
|
|
/* font-size: 18px; */
|
|
text-shadow: 0 0 6px #00eaff;
|
|
}
|
|
|
|
/* Retro select elements */
|
|
select {
|
|
background: #222;
|
|
color: #00f6ff;
|
|
border: 2px solid #00eaff;
|
|
padding: 6px 8px;
|
|
font-size: 12px;
|
|
font-family: "Press Start 2P", monospace;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
text-shadow: 0 0 6px #00eaff;
|
|
margin-right: 6px;
|
|
}
|
|
|
|
select:hover {
|
|
box-shadow: 0 0 10px #00eaff;
|
|
}
|
|
|
|
/* Buttons */
|
|
button {
|
|
background: #330044;
|
|
color: #ff00ff;
|
|
border: 2px solid #ff00ff;
|
|
padding: 8px 12px;
|
|
margin-top: 10px;
|
|
font-family: "Press Start 2P", monospace;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
transition: 0.15s;
|
|
text-shadow: 0 0 8px #ff00ff;
|
|
}
|
|
|
|
button:hover {
|
|
background: #550066;
|
|
box-shadow: 0 0 12px #ff00ff, 0 0 20px #ff00ff inset;
|
|
}
|
|
|
|
/* Car block layout */
|
|
.car-block {
|
|
margin-top: 15px;
|
|
padding: 12px;
|
|
background: #1a001f;
|
|
border: 2px solid #660099;
|
|
border-radius: 6px;
|
|
box-shadow: 0 0 12px #660099;
|
|
animation-name: appear_animation;
|
|
animation-duration: 0.5s;
|
|
}
|
|
|
|
.car-block.remove-anim {
|
|
animation: disappear_animation 0.35s forwards;
|
|
}
|
|
|
|
.car-select,
|
|
.skin-select {
|
|
min-width: 140px;
|
|
}
|
|
|
|
/* Car list container */
|
|
#carList {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
/* Image previews */
|
|
.parent {
|
|
display: flex;
|
|
gap: 20px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
#preview,
|
|
#outline {
|
|
border: 2px solid #00eaff;
|
|
box-shadow: 0 0 12px #00eaff;
|
|
}
|
|
|
|
/* Glow for images on hover */
|
|
img:hover {
|
|
box-shadow: 0 0 16px #00eaff;
|
|
}
|
|
|
|
@keyframes appear_animation {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes disappear_animation {
|
|
from {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
|
|
to {
|
|
opacity: 0;
|
|
transform: scale(0.9);
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<body>
|
|
<h1>Choose a track</h1>
|
|
|
|
<select id="trackSelect">
|
|
{{tracks}}
|
|
</select>
|
|
<select id="configSelect">
|
|
{{configs}}
|
|
</select>
|
|
<button onclick="apply()">Change track</button>
|
|
|
|
<br><br>
|
|
|
|
<div class="parent">
|
|
<img id="preview" src="" width="400" style="border:1px solid #444">
|
|
<img id="outline" src="" width="400">
|
|
</div>
|
|
|
|
<h1>Choose gamemode</h1>
|
|
<p>Set to 0 to disable</p>
|
|
<input type="number" class="practice-minutes" placeholder="Practice minutes">
|
|
<input type="number" class="qualify-minutes" placeholder="Qualify minutes">
|
|
<input type="number" class="race-laps" placeholder="Race laps">
|
|
<button onclick="applyGamemodes()">Apply</button>
|
|
|
|
<h1>Choose cars</h1>
|
|
<button id="addCarBtn">Add Car</button>
|
|
<button id="applyCarsBtn">Change cars</button>
|
|
|
|
<div id="carList"></div>
|
|
|
|
<script>
|
|
const trackSelect = document.getElementById("trackSelect");
|
|
const configSelect = document.getElementById("configSelect");
|
|
const previewImg = document.getElementById("preview");
|
|
const outlineImg = document.getElementById("outline");
|
|
|
|
function updateCurrentTrack() {
|
|
fetch('/currenttrack')
|
|
.then(r => r.json())
|
|
.then(async data => {
|
|
trackSelect.value = data.track;
|
|
|
|
await updateTrack(true);
|
|
|
|
configSelect.value = data.config;
|
|
updateTrackImages();
|
|
});
|
|
}
|
|
|
|
function updateTrack(shouldUpdateConfig = true, preselectConfig = null) {
|
|
return fetch(`/track/${trackSelect.value}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
previewImg.src = data.image;
|
|
outlineImg.src = data.outline;
|
|
|
|
if (shouldUpdateConfig) {
|
|
configSelect.innerHTML = "";
|
|
data.configs.forEach(c => {
|
|
let opt = document.createElement("option");
|
|
opt.value = c;
|
|
opt.textContent = c;
|
|
configSelect.appendChild(opt);
|
|
});
|
|
}
|
|
|
|
if (preselectConfig) {
|
|
configSelect.value = preselectConfig;
|
|
}
|
|
|
|
updateTrackImages();
|
|
});
|
|
}
|
|
|
|
function updateTrackImages() {
|
|
previewImg.src = "/img/preview/" + trackSelect.value + "/" + configSelect.value;
|
|
outlineImg.src = "/img/outline/" + trackSelect.value + "/" + configSelect.value;
|
|
}
|
|
|
|
function apply() {
|
|
const URL = '/changetrack/' + trackSelect.value + "/" + configSelect.value;
|
|
fetch(URL, { method: "POST" })
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
console.log("server response:" + data)
|
|
if (data.status == "success") {
|
|
alert("Track changed successfully!");
|
|
} else {
|
|
alert("Failed to change track: " + data.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
trackSelect.addEventListener("change", updateTrack);
|
|
configSelect.addEventListener("change", updateTrackImages);
|
|
|
|
if (window.addEventListener) // W3C standard
|
|
{
|
|
window.addEventListener('load', updateCurrentTrack, false); // NB **not** 'onload'
|
|
}
|
|
</script>
|
|
<script>
|
|
function updateGameModes() {
|
|
fetch('/gamemodes')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
document.querySelector(".practice-minutes").value = data.gamemodes.practice_minutes;
|
|
document.querySelector(".qualify-minutes").value = data.gamemodes.qualify_minutes;
|
|
document.querySelector(".race-laps").value = data.gamemodes.race_laps;
|
|
});
|
|
}
|
|
|
|
function applyGamemodes() {
|
|
const practiceMinutes = document.querySelector(".practice-minutes").value || 0;
|
|
const qualifyMinutes = document.querySelector(".qualify-minutes").value || 0;
|
|
const raceLaps = document.querySelector(".race-laps").value || 0;
|
|
|
|
const payload = {
|
|
gamemodes: {
|
|
practice_minutes: parseInt(practiceMinutes),
|
|
qualify_minutes: parseInt(qualifyMinutes),
|
|
race_laps: parseInt(raceLaps)
|
|
}
|
|
};
|
|
|
|
fetch("/changegamemodes", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify(payload)
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
console.log("Server response:", data);
|
|
if (data.status == "success") {
|
|
alert("Gamemodes changed successfully!");
|
|
} else {
|
|
alert("Failed to change gamemodes: " + data.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (window.addEventListener) // W3C standard
|
|
{
|
|
window.addEventListener('load', updateGameModes, false); // NB **not** 'onload'
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
const addCarBtn = document.getElementById("addCarBtn");
|
|
const carList = document.getElementById("carList");
|
|
const applyCarsBtn = document.getElementById("applyCarsBtn");
|
|
|
|
applyCarsBtn.addEventListener("click", applyCars);
|
|
|
|
function applyCars() {
|
|
const blocks = document.querySelectorAll(".car-block");
|
|
|
|
let cars = [];
|
|
|
|
blocks.forEach(block => {
|
|
const carSelect = block.querySelector(".car-select");
|
|
const skinSelect = block.querySelector(".skin-select");
|
|
const amountInput = block.querySelector(".amount-input");
|
|
|
|
const carName = carSelect?.value || "";
|
|
const skinName = skinSelect?.value || "";
|
|
let amount = 1;
|
|
if (parseInt(amountInput.value) >= 1) {
|
|
|
|
console.log("setting amount to value:", amountInput.value);
|
|
amount = parseInt(amountInput.value);
|
|
}
|
|
console.log("Adding car:", carName, "skin:", skinName, "amount:", amount);
|
|
|
|
if (carName) {
|
|
for (let i = 0; i < amount; i++) {
|
|
cars.push({
|
|
car: carName,
|
|
skin: skinName
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const payload = { cars };
|
|
|
|
console.log("Sending cars:", payload);
|
|
|
|
fetch("/changecars", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify(payload)
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
console.log("Server response:", data);
|
|
if (data.status == "success") {
|
|
alert("Cars changed successfully!");
|
|
} else {
|
|
alert("Failed to change cars: " + data.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function addCar(preselectedCar = null, preselectedSkin = null, amount = 0) {
|
|
const wrapper = document.createElement("div");
|
|
wrapper.style.marginTop = "12px";
|
|
wrapper.className = "car-block";
|
|
|
|
const carSelect = document.createElement("select");
|
|
carSelect.className = "car-select";
|
|
|
|
const skinSelect = document.createElement("select");
|
|
skinSelect.className = "skin-select";
|
|
skinSelect.style.marginLeft = "10px";
|
|
|
|
const delBtn = document.createElement("button");
|
|
delBtn.textContent = "Remove";
|
|
delBtn.style.marginLeft = "10px";
|
|
delBtn.onclick = () => {
|
|
wrapper.classList.add("remove-anim");
|
|
setTimeout(() => wrapper.remove(), 350);
|
|
};
|
|
|
|
const copyBtn = document.createElement("button");
|
|
copyBtn.textContent = "Copy";
|
|
copyBtn.style.marginLeft = "10px";
|
|
copyBtn.onclick = () => {
|
|
addCar(carSelect.value, skinSelect.value);
|
|
};
|
|
|
|
const amountSelect = document.createElement("input");
|
|
amountSelect.style.marginLeft = "10px";
|
|
amountSelect.className = "amount-input";
|
|
amountSelect.type = "number";
|
|
amountSelect.min = "1";
|
|
if (amount > 0) {
|
|
amountSelect.value = amount;
|
|
}
|
|
amountSelect.placeholder = "Amount";
|
|
amountSelect.min = "10";
|
|
|
|
const img = document.createElement("img");
|
|
img.className = "skin-preview";
|
|
img.style.display = "block";
|
|
img.style.marginTop = "10px";
|
|
img.style.maxWidth = "400px";
|
|
|
|
wrapper.appendChild(carSelect);
|
|
wrapper.appendChild(skinSelect);
|
|
wrapper.appendChild(amountSelect);
|
|
wrapper.appendChild(copyBtn);
|
|
wrapper.appendChild(delBtn);
|
|
wrapper.appendChild(img);
|
|
carList.appendChild(wrapper);
|
|
|
|
const url = '/cars';
|
|
const res = await fetch(url);
|
|
const data = await res.json();
|
|
|
|
const cars = data.cars;
|
|
|
|
console.log(cars);
|
|
|
|
cars.forEach(car => {
|
|
let o = document.createElement("option");
|
|
o.value = car.name; // of car.id
|
|
o.textContent = car.name;
|
|
carSelect.appendChild(o);
|
|
});
|
|
|
|
function loadSkins(carName) {
|
|
const car = cars.find(c => c.name === carName);
|
|
skinSelect.innerHTML = "";
|
|
|
|
car.skins.forEach(cfg => {
|
|
let o = document.createElement("option");
|
|
o.value = cfg.name;
|
|
o.textContent = cfg.name;
|
|
skinSelect.appendChild(o);
|
|
});
|
|
|
|
// show first skin
|
|
try {
|
|
updateImage(car.skins[0].image);
|
|
} catch (e) {
|
|
updateImage("");
|
|
}
|
|
}
|
|
|
|
function updateImage(imgPath) {
|
|
img.src = imgPath;
|
|
}
|
|
|
|
carSelect.addEventListener("change", () => {
|
|
const selectedCar = carSelect.value;
|
|
loadSkins(selectedCar);
|
|
});
|
|
|
|
skinSelect.addEventListener("change", () => {
|
|
const selectedCar = carSelect.value;
|
|
const selectedSkin = skinSelect.value;
|
|
|
|
const car = cars.find(c => c.name === selectedCar);
|
|
const cfg = car.skins.find(x => x.name === selectedSkin);
|
|
|
|
try {
|
|
updateImage(cfg.image);
|
|
} catch (e) {
|
|
updateImage("");
|
|
}
|
|
});
|
|
|
|
if (preselectedCar) {
|
|
carSelect.value = preselectedCar;
|
|
loadSkins(preselectedCar);
|
|
if (preselectedSkin) {
|
|
skinSelect.value = preselectedSkin;
|
|
const car = cars.find(c => c.name === preselectedCar);
|
|
const cfg = car.skins.find(x => x.name === preselectedSkin);
|
|
updateImage(cfg ? cfg.image : "");
|
|
}
|
|
} else {
|
|
loadSkins(cars[0].name);
|
|
}
|
|
}
|
|
|
|
function getCurrentCars() {
|
|
const url = '/currentcars';
|
|
fetch(url)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
console.log("Current cars:", data);
|
|
data.cars.forEach(c => {
|
|
addCar(c.car, c.skin, c.amount);
|
|
});
|
|
});
|
|
}
|
|
|
|
addCarBtn.addEventListener("click", addCar);
|
|
|
|
if (window.addEventListener) // W3C standard
|
|
{
|
|
window.addEventListener('load', getCurrentCars, false); // NB **not** 'onload'
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |