Spaces:
Runtime error
Runtime error
Update public/index.html
Browse files- public/index.html +62 -94
public/index.html
CHANGED
|
@@ -290,8 +290,9 @@
|
|
| 290 |
let characters = [];
|
| 291 |
let activeIndex = -1;
|
| 292 |
let sessionToken = null;
|
| 293 |
-
let originalRaw = '';
|
| 294 |
|
|
|
|
|
|
|
| 295 |
const STAT_MAP = {
|
| 296 |
civPower: 9, civDef: 10, civSpd: 11,
|
| 297 |
supPower: 13, supDef: 14, supSpd: 15,
|
|
@@ -328,21 +329,17 @@ fetch('/download-link').then(r => r.json()).then(data => {
|
|
| 328 |
}).catch(() => {});
|
| 329 |
|
| 330 |
// ββ AUTO-INIT ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 331 |
-
const LS_TOKEN_KEY
|
| 332 |
-
const LS_RAW_KEY = 'supercity_raw';
|
| 333 |
-
const LS_EDITED_KEY = 'supercity_edited';
|
| 334 |
|
| 335 |
(function initFromServer() {
|
| 336 |
const wrap = document.getElementById('progressWrap');
|
| 337 |
const bar = document.getElementById('progressBar');
|
| 338 |
const label = document.getElementById('progressLabel');
|
| 339 |
|
| 340 |
-
// Try to restore a previous session from
|
| 341 |
const savedToken = localStorage.getItem(LS_TOKEN_KEY);
|
| 342 |
-
const savedRaw = localStorage.getItem(LS_RAW_KEY);
|
| 343 |
|
| 344 |
-
if (savedToken
|
| 345 |
-
// Verify the session is still alive on the server
|
| 346 |
label.textContent = 'Resuming sessionβ¦';
|
| 347 |
wrap.style.display = 'block';
|
| 348 |
bar.style.width = '50%';
|
|
@@ -351,32 +348,22 @@ const LS_EDITED_KEY = 'supercity_edited';
|
|
| 351 |
.then(r => r.json())
|
| 352 |
.then(data => {
|
| 353 |
if (data.alive) {
|
| 354 |
-
bar.style.width = '100%';
|
| 355 |
sessionToken = savedToken;
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
wrap.style.display = 'none';
|
| 369 |
-
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 370 |
-
document.getElementById('fileInfoBar').innerHTML = `
|
| 371 |
-
<div class="file-info">β» <span class="file-info-name">Session restored</span>
|
| 372 |
-
Β· ${characters.length} characters loaded${savedEdited ? ' Β· <b style="color:var(--accent2)">edits recovered</b>' : ''}</div>`;
|
| 373 |
-
document.getElementById('editorLayout').style.display = 'grid';
|
| 374 |
-
setStep(savedEdited ? 3 : 2);
|
| 375 |
-
showToast(savedEdited ? 'β Session + edits restored β ready to export' : 'β Session restored');
|
| 376 |
} else {
|
| 377 |
localStorage.removeItem(LS_TOKEN_KEY);
|
| 378 |
-
|
| 379 |
-
loadFresh(wrap, bar, label);
|
| 380 |
}
|
| 381 |
})
|
| 382 |
.catch(() => loadFresh(wrap, bar, label));
|
|
@@ -392,12 +379,13 @@ const LS_EDITED_KEY = 'supercity_edited';
|
|
| 392 |
fetch('/init')
|
| 393 |
.then(r => { if (!r.ok) return r.json().then(e => { throw new Error(e.error); }); return r.json(); })
|
| 394 |
.then(data => {
|
| 395 |
-
bar.style.width = '100%';
|
| 396 |
sessionToken = data.token;
|
| 397 |
-
originalRaw = data.characters;
|
| 398 |
localStorage.setItem(LS_TOKEN_KEY, data.token);
|
| 399 |
-
|
| 400 |
-
|
|
|
|
|
|
|
|
|
|
| 401 |
wrap.style.display = 'none';
|
| 402 |
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 403 |
document.getElementById('fileInfoBar').innerHTML = `
|
|
@@ -414,41 +402,15 @@ const LS_EDITED_KEY = 'supercity_edited';
|
|
| 414 |
}
|
| 415 |
})();
|
| 416 |
|
| 417 |
-
// ββ
|
| 418 |
-
function
|
| 419 |
-
characters
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
}
|
| 426 |
-
function isNumeric(s) { return /^-?[\d.]+$/.test(s.trim()); }
|
| 427 |
-
|
| 428 |
-
const boundaries = [];
|
| 429 |
-
for (let i = 1; i < allFields.length - 1; i++) {
|
| 430 |
-
if (isNameToken(allFields[i]) && isNumeric(allFields[i + 1]) && isNameToken(allFields[i - 1])) {
|
| 431 |
-
boundaries.push(i);
|
| 432 |
-
}
|
| 433 |
-
}
|
| 434 |
-
|
| 435 |
-
for (let b = 0; b < boundaries.length; b++) {
|
| 436 |
-
const start = boundaries[b];
|
| 437 |
-
const end = b + 1 < boundaries.length ? boundaries[b + 1] - 1 : allFields.length;
|
| 438 |
-
const fields = allFields.slice(start, end);
|
| 439 |
-
characters.push({
|
| 440 |
-
index: b,
|
| 441 |
-
name: allFields[start - 1].trim(),
|
| 442 |
-
alias: fields[0].trim(),
|
| 443 |
-
fields: fields,
|
| 444 |
-
original: [...fields],
|
| 445 |
-
startIdx: start,
|
| 446 |
-
endIdx: end,
|
| 447 |
});
|
| 448 |
-
}
|
| 449 |
-
|
| 450 |
-
document.getElementById('charCount').textContent = characters.length;
|
| 451 |
-
renderList(characters);
|
| 452 |
}
|
| 453 |
|
| 454 |
// ββ LIST βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -524,45 +486,51 @@ function saveCharacter() {
|
|
| 524 |
if (activeIndex < 0) return;
|
| 525 |
const ch = characters[activeIndex];
|
| 526 |
let valid = true;
|
|
|
|
|
|
|
| 527 |
Object.keys(STAT_MAP).forEach(key => {
|
| 528 |
const v = parseInt(document.getElementById(key).value, 10);
|
| 529 |
if (isNaN(v) || v < 0 || v > 99999) { valid = false; document.getElementById(key).classList.add('invalid'); return; }
|
|
|
|
|
|
|
| 530 |
ch.fields[STAT_MAP[key]] = String(v);
|
| 531 |
updateBar(key, v);
|
| 532 |
});
|
|
|
|
| 533 |
if (!valid) { showToast('Fix invalid values first', true); return; }
|
| 534 |
-
document.getElementById('unsavedBadge').style.display = 'none';
|
| 535 |
-
showToast(`β ${ch.name} saved`);
|
| 536 |
-
setStep(3);
|
| 537 |
-
persistEdits();
|
| 538 |
-
}
|
| 539 |
|
| 540 |
-
|
| 541 |
-
/
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
})
|
| 546 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
}
|
| 548 |
|
| 549 |
function resetCharacter() {
|
| 550 |
if (activeIndex < 0) return;
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
}
|
| 555 |
|
| 556 |
// ββ EXPORT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 557 |
function exportAPK() {
|
| 558 |
-
if (!sessionToken) { showToast('No APK session β
|
| 559 |
-
|
| 560 |
-
// Rebuild raw text from all character fields
|
| 561 |
-
const allFields = originalRaw.split('|');
|
| 562 |
-
characters.forEach(ch => {
|
| 563 |
-
ch.fields.forEach((val, j) => { allFields[ch.startIdx + j] = val; });
|
| 564 |
-
});
|
| 565 |
-
const rebuiltChars = allFields.join('|');
|
| 566 |
|
| 567 |
const btn = document.getElementById('exportBtn');
|
| 568 |
btn.disabled = true;
|
|
@@ -572,7 +540,7 @@ function exportAPK() {
|
|
| 572 |
fetch('/patch', {
|
| 573 |
method: 'POST',
|
| 574 |
headers: { 'Content-Type': 'application/json' },
|
| 575 |
-
body: JSON.stringify({ token: sessionToken
|
| 576 |
})
|
| 577 |
.then(res => {
|
| 578 |
if (!res.ok) return res.json().then(e => { throw new Error(e.error); });
|
|
@@ -583,7 +551,7 @@ function exportAPK() {
|
|
| 583 |
btn.textContent = 'β¬ Export Patched APK';
|
| 584 |
showToast('β Patched β click the download link');
|
| 585 |
setStep(3);
|
| 586 |
-
//
|
| 587 |
const a = document.createElement('a');
|
| 588 |
a.href = data.url;
|
| 589 |
a.download = 'supercity_modded.apk';
|
|
|
|
| 290 |
let characters = [];
|
| 291 |
let activeIndex = -1;
|
| 292 |
let sessionToken = null;
|
|
|
|
| 293 |
|
| 294 |
+
// Field indices within each character's fields[] array (server-side record)
|
| 295 |
+
// fields[0]=name, fields[1]=alias, numeric stats follow
|
| 296 |
const STAT_MAP = {
|
| 297 |
civPower: 9, civDef: 10, civSpd: 11,
|
| 298 |
supPower: 13, supDef: 14, supSpd: 15,
|
|
|
|
| 329 |
}).catch(() => {});
|
| 330 |
|
| 331 |
// ββ AUTO-INIT ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 332 |
+
const LS_TOKEN_KEY = 'supercity_session_token';
|
|
|
|
|
|
|
| 333 |
|
| 334 |
(function initFromServer() {
|
| 335 |
const wrap = document.getElementById('progressWrap');
|
| 336 |
const bar = document.getElementById('progressBar');
|
| 337 |
const label = document.getElementById('progressLabel');
|
| 338 |
|
| 339 |
+
// Try to restore a previous session from the server
|
| 340 |
const savedToken = localStorage.getItem(LS_TOKEN_KEY);
|
|
|
|
| 341 |
|
| 342 |
+
if (savedToken) {
|
|
|
|
| 343 |
label.textContent = 'Resuming sessionβ¦';
|
| 344 |
wrap.style.display = 'block';
|
| 345 |
bar.style.width = '50%';
|
|
|
|
| 348 |
.then(r => r.json())
|
| 349 |
.then(data => {
|
| 350 |
if (data.alive) {
|
|
|
|
| 351 |
sessionToken = savedToken;
|
| 352 |
+
bar.style.width = '80%';
|
| 353 |
+
return loadCharacters(savedToken).then(() => {
|
| 354 |
+
bar.style.width = '100%';
|
| 355 |
+
wrap.style.display = 'none';
|
| 356 |
+
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 357 |
+
document.getElementById('fileInfoBar').innerHTML = `
|
| 358 |
+
<div class="file-info">β» <span class="file-info-name">Session restored</span>
|
| 359 |
+
Β· ${characters.length} characters loaded</div>`;
|
| 360 |
+
document.getElementById('editorLayout').style.display = 'grid';
|
| 361 |
+
setStep(2);
|
| 362 |
+
showToast('β Session restored');
|
| 363 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
} else {
|
| 365 |
localStorage.removeItem(LS_TOKEN_KEY);
|
| 366 |
+
return loadFresh(wrap, bar, label);
|
|
|
|
| 367 |
}
|
| 368 |
})
|
| 369 |
.catch(() => loadFresh(wrap, bar, label));
|
|
|
|
| 379 |
fetch('/init')
|
| 380 |
.then(r => { if (!r.ok) return r.json().then(e => { throw new Error(e.error); }); return r.json(); })
|
| 381 |
.then(data => {
|
|
|
|
| 382 |
sessionToken = data.token;
|
|
|
|
| 383 |
localStorage.setItem(LS_TOKEN_KEY, data.token);
|
| 384 |
+
bar.style.width = '70%';
|
| 385 |
+
return loadCharacters(data.token);
|
| 386 |
+
})
|
| 387 |
+
.then(() => {
|
| 388 |
+
bar.style.width = '100%';
|
| 389 |
wrap.style.display = 'none';
|
| 390 |
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 391 |
document.getElementById('fileInfoBar').innerHTML = `
|
|
|
|
| 402 |
}
|
| 403 |
})();
|
| 404 |
|
| 405 |
+
// ββ LOAD CHARACTERS FROM SERVER ββββββββββββββββββββββββββββββββββββ
|
| 406 |
+
function loadCharacters(token) {
|
| 407 |
+
return fetch('/characters/' + token)
|
| 408 |
+
.then(r => { if (!r.ok) return r.json().then(e => { throw new Error(e.error); }); return r.json(); })
|
| 409 |
+
.then(data => {
|
| 410 |
+
characters = data.characters;
|
| 411 |
+
document.getElementById('charCount').textContent = characters.length;
|
| 412 |
+
renderList(characters);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
}
|
| 415 |
|
| 416 |
// ββ LIST βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 486 |
if (activeIndex < 0) return;
|
| 487 |
const ch = characters[activeIndex];
|
| 488 |
let valid = true;
|
| 489 |
+
const edits = [];
|
| 490 |
+
|
| 491 |
Object.keys(STAT_MAP).forEach(key => {
|
| 492 |
const v = parseInt(document.getElementById(key).value, 10);
|
| 493 |
if (isNaN(v) || v < 0 || v > 99999) { valid = false; document.getElementById(key).classList.add('invalid'); return; }
|
| 494 |
+
edits.push({ characterIndex: ch.index, fieldIndex: STAT_MAP[key], value: String(v) });
|
| 495 |
+
// Update local cache too
|
| 496 |
ch.fields[STAT_MAP[key]] = String(v);
|
| 497 |
updateBar(key, v);
|
| 498 |
});
|
| 499 |
+
|
| 500 |
if (!valid) { showToast('Fix invalid values first', true); return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
|
| 502 |
+
// Persist edits server-side via /edit-bulk
|
| 503 |
+
fetch('/edit-bulk', {
|
| 504 |
+
method: 'POST',
|
| 505 |
+
headers: { 'Content-Type': 'application/json' },
|
| 506 |
+
body: JSON.stringify({ token: sessionToken, edits }),
|
| 507 |
+
})
|
| 508 |
+
.then(r => { if (!r.ok) return r.json().then(e => { throw new Error(e.error); }); return r.json(); })
|
| 509 |
+
.then(() => {
|
| 510 |
+
document.getElementById('unsavedBadge').style.display = 'none';
|
| 511 |
+
showToast(`β ${ch.name} saved`);
|
| 512 |
+
setStep(3);
|
| 513 |
+
})
|
| 514 |
+
.catch(err => showToast('Save failed: ' + err.message, true));
|
| 515 |
}
|
| 516 |
|
| 517 |
function resetCharacter() {
|
| 518 |
if (activeIndex < 0) return;
|
| 519 |
+
// Ask server to reset characters.txt to original APK state
|
| 520 |
+
fetch('/reset/' + sessionToken, { method: 'POST' })
|
| 521 |
+
.then(r => { if (!r.ok) return r.json().then(e => { throw new Error(e.error); }); return r.json(); })
|
| 522 |
+
.then(() => loadCharacters(sessionToken))
|
| 523 |
+
.then(() => {
|
| 524 |
+
// Re-select current character to refresh UI
|
| 525 |
+
if (activeIndex < characters.length) selectChar(activeIndex);
|
| 526 |
+
showToast('βΊ Reset all to original');
|
| 527 |
+
})
|
| 528 |
+
.catch(err => showToast('Reset failed: ' + err.message, true));
|
| 529 |
}
|
| 530 |
|
| 531 |
// ββ EXPORT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 532 |
function exportAPK() {
|
| 533 |
+
if (!sessionToken) { showToast('No APK session β reload page', true); return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
|
| 535 |
const btn = document.getElementById('exportBtn');
|
| 536 |
btn.disabled = true;
|
|
|
|
| 540 |
fetch('/patch', {
|
| 541 |
method: 'POST',
|
| 542 |
headers: { 'Content-Type': 'application/json' },
|
| 543 |
+
body: JSON.stringify({ token: sessionToken }),
|
| 544 |
})
|
| 545 |
.then(res => {
|
| 546 |
if (!res.ok) return res.json().then(e => { throw new Error(e.error); });
|
|
|
|
| 551 |
btn.textContent = 'β¬ Export Patched APK';
|
| 552 |
showToast('β Patched β click the download link');
|
| 553 |
setStep(3);
|
| 554 |
+
// Trigger browser download
|
| 555 |
const a = document.createElement('a');
|
| 556 |
a.href = data.url;
|
| 557 |
a.download = 'supercity_modded.apk';
|