template.html.in 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <!DOCTYPE html>
  2. <html lang="en-us">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  6. <title>SuperTux @SUPERTUX_VERSION_STRING@</title>
  7. <style>
  8. body {
  9. font-family: arial;
  10. margin: 0;
  11. padding: none;
  12. background-color: black;
  13. }
  14. .emscripten {
  15. padding-right: 0;
  16. margin-left: auto;
  17. margin-right: auto;
  18. display: block
  19. }
  20. canvas.emscripten {
  21. border: 0 none;
  22. background-color: #0000;
  23. }
  24. .spinner {
  25. height: 30px;
  26. width: 30px;
  27. margin: 0;
  28. margin-top: 20px;
  29. margin-left: 20px;
  30. display: inline-block;
  31. vertical-align: top;
  32. -webkit-animation: rotation .8s linear infinite;
  33. -moz-animation: rotation .8s linear infinite;
  34. -o-animation: rotation .8s linear infinite;
  35. animation: rotation .8s linear infinite;
  36. border-left: 5px solid #ebebeb;
  37. border-right: 5px solid #ebebeb;
  38. border-bottom: 5px solid #ebebeb;
  39. border-top: 5px solid #78787800;
  40. border-radius: 100%;
  41. }
  42. @-webkit-keyframes rotation {
  43. from {
  44. -webkit-transform: rotate(0)
  45. }
  46. to {
  47. -webkit-transform: rotate(360deg)
  48. }
  49. }
  50. @-moz-keyframes rotation {
  51. from {
  52. -moz-transform: rotate(0)
  53. }
  54. to {
  55. -moz-transform: rotate(360deg)
  56. }
  57. }
  58. @-o-keyframes rotation {
  59. from {
  60. -o-transform: rotate(0)
  61. }
  62. to {
  63. -o-transform: rotate(360deg)
  64. }
  65. }
  66. @keyframes rotation {
  67. from {
  68. transform: rotate(0)
  69. }
  70. to {
  71. transform: rotate(360deg)
  72. }
  73. }
  74. #status {
  75. display: inline-block;
  76. vertical-align: top;
  77. margin-top: 30px;
  78. margin-left: 20px;
  79. margin-bottom: 30px;
  80. margin-right: 20px;
  81. font-weight: 700;
  82. color: #d7d7d7;
  83. }
  84. #progress {
  85. height: 6px;
  86. width: 200px;
  87. border: none;
  88. border-radius: 3px;
  89. background: #787878;
  90. margin-left: 20px;
  91. }
  92. progress::-moz-progress-bar {
  93. border-radius: 3px;
  94. background: white;
  95. }
  96. #output {
  97. width: 100%;
  98. height: 200px;
  99. margin: 0 auto;
  100. margin-top: 10px;
  101. border: 0;
  102. padding-left: 0;
  103. padding-right: 0;
  104. display: block;
  105. background-color: #0000;
  106. color: #fff;
  107. font-family: 'Lucida Console', Monaco, monospace;
  108. outline: 0;
  109. overflow: scroll;
  110. }
  111. #overlay {
  112. position: fixed;
  113. left: 0;
  114. top: 0;
  115. height: 100%;
  116. width: 100%;
  117. background-color: #000d;
  118. z-index: 10;
  119. background-image: linear-gradient(to bottom, #000a, #000a), url('supertux2.png'), url('supertux2_bkg.png');
  120. background-position: 0 0, 50% calc(50% - 100px);
  121. background-repeat: repeat, no-repeat;
  122. animation: movebkg 10s linear infinite;
  123. }
  124. .center_stuff {
  125. display: flex;
  126. align-items: center;
  127. justify-content: center;
  128. }
  129. .bottom_stuff {
  130. text-align: center;
  131. color: #fff7;
  132. position: absolute;
  133. bottom: 0;
  134. width: 100%;
  135. padding: 15px;
  136. }
  137. .bottom_stuff a {
  138. color: #8cf;
  139. font-weight: bold;
  140. }
  141. .bottom_stuff a.light {
  142. color: #8cf7;
  143. font-weight: normal;
  144. }
  145. @keyframes movebkg {
  146. from {
  147. background-position: 0 0, 50% calc(50% - 100px);
  148. }
  149. to {
  150. background-position: -256px 0, 50% calc(50% - 100px);
  151. }
  152. }
  153. </style>
  154. </head>
  155. <body>
  156. <div id="overlay" class="center_stuff">
  157. <center>
  158. <div class="spinner" id="spinner"></div>
  159. <div class="emscripten" id="status">Downloading...</div>
  160. <br/>
  161. <div class="emscripten"><progress hidden id="progress" max="100" value="0"></progress></div>
  162. </center>
  163. <div class="bottom_stuff">
  164. <p>If the game is unplayably slow, try downloading the desktop version for your platform at <a href="https://www.supertux.org/download.html" target="_blank">supertux.org</a></p>
  165. <p id="data_warning"></p>
  166. <p id="progress_desc"></p>
  167. </div>
  168. </div>
  169. <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex="0"></canvas>
  170. <p id="output"></p>
  171. <input type="file" id="file_upload" multiple="multiple" style="display:none">
  172. <script>
  173. if ("@CMAKE_BUILD_TYPE@" == "Release") {
  174. document.getElementById("output").style.display = "none";
  175. }
  176. var data_persistent = false;
  177. if (navigator.storage && navigator.storage.persist) {
  178. navigator.storage.persist().then((persists) => {
  179. if (!persists) {
  180. //alert("Your browser denied persistent storage. That means your data could be cleared next time you open the game.\n\nIf you just received a prompt asking you if you want data to persist, you may ignore this message.\n\nChrome and Chromium-based browsers (Edge, Opera, Brave...) will not ask the user; instead, the browser will choose by itself based on if it considers the site important. It detemines which sites are important based on certain data, such as how often you visit the site, what you do, etc. If you want to choose whether or not to save your progress, you may use Firefox instead.\n\nYou can read more at https://web.dev/persistent-storage/#how-is-permission-granted\n\nIf you want to choose to save your data, please use Firefox.");
  181. document.getElementById("data_warning").innerHTML = 'Your browser denied persistent data. That means the browser can choose to delete your progress anytime. Chrome, Edge and Opera are known to <a class="light" href="https://web.dev/persistent-storage/#how-is-permission-granted">silently deny persistent data</a>; please try with Firefox if necessary.';
  182. } else {
  183. data_persistent = true;
  184. }
  185. });
  186. } else {
  187. document.getElementById("data_warning").innerHTML = 'Your browser does not support persistent data. That means the browser can choose to delete your progress anytime.';
  188. }
  189. window.supertux2_ispersistent = function() {
  190. return data_persistent ? 1 : 0;
  191. }
  192. var statusElement = document.getElementById("status"),
  193. progressElement = document.getElementById("progress"),
  194. progressDescElement = document.getElementById("progress_desc"),
  195. overlayElement = document.getElementById("overlay"),
  196. spinnerElement = document.getElementById("spinner");
  197. var lastUpdate = Date.now() / 1000;
  198. var lastStep = 0;
  199. function getExp(num) {
  200. if (num > 1e12) {
  201. return (num / 1e12).toFixed(3) + "T";
  202. } else if (num > 1e9) {
  203. return (num / 1e9).toFixed(3) + "G";
  204. } else if (num > 1e6) {
  205. return (num / 1e6).toFixed(3) + "M";
  206. } else if (num > 1e3) {
  207. return (num / 1e3).toFixed(3) + "K";
  208. } else {
  209. return num.toFixed();
  210. }
  211. }
  212. // Functions to be loaded with cwrap
  213. var setResolution = null;
  214. var onDownloadProgress = null;
  215. var onDownloadFinished = null;
  216. var onDownloadError = null;
  217. var onDownloadAborted = null;
  218. var Module = {
  219. preRun: [],
  220. postRun: [],
  221. print: function () {
  222. var e = document.getElementById("output");
  223. if (e)
  224. e.innerHTML = "";
  225. return function (t) {
  226. if (arguments.length > 1)
  227. t = Array.prototype.slice.call(arguments).join(" ");
  228. t = Array.prototype.slice.call(arguments).join(" ");
  229. console.log(t);
  230. if (e) {
  231. e.innerHTML += t + "<br/>";
  232. e.scrollTop = e.scrollHeight;
  233. }
  234. }
  235. }(),
  236. printErr: function () {
  237. var e = document.getElementById("output");
  238. if (e)
  239. e.innerHTML = "";
  240. return function (t) {
  241. if (arguments.length > 1)
  242. t = Array.prototype.slice.call(arguments).join(" ");
  243. console.error(t);
  244. if (e) {
  245. e.innerHTML += "<span style='color:red;'>" + t + "</span><br/>";
  246. e.scrollTop = e.scrollHeight;
  247. }
  248. }
  249. }(),
  250. canvas: function () {
  251. var c = document.getElementById("canvas");
  252. c.addEventListener("webglcontextlost", (function (e) {
  253. alert("WebGL context lost. You will need to reload the page.");
  254. e.preventDefault();
  255. }), !1);
  256. // Fixed a bug where an iframe containing SuperTUx (e. g. Newgrounds)
  257. // would lose focus if the user clicks outside of the iframe and cannot
  258. // get focus back when user clicks inside the iframe, thus preventing
  259. // any keyboard input from being received by SuperTux.
  260. // If you want to try: Disable the three lines below, compile, upload on
  261. // Newgrounds, wait for the game to load, click outside the game, click
  262. // back inside the game, then try to press buttons (e. g. arrows), it'll
  263. // behave as if focus was outside the iframe (e. g. pressing down will scroll
  264. // the page instead of navigating the menus/worldmap/ducking/whatever)
  265. c.addEventListener("click", function(e) {
  266. c.focus();
  267. });
  268. return c;
  269. }(),
  270. setStatus: function (e) {
  271. overlayElement.style.display = e ? "flex" : "none";
  272. if (Module.setStatus.last || (Module.setStatus.last = { time: Date.now(), text: "" }), e !== Module.setStatus.last.text)
  273. {
  274. var t = e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/),
  275. n = Date.now();
  276. if (!(t && n - Module.setStatus.last.time < 30))
  277. {
  278. Module.setStatus.last.time = n;
  279. Module.setStatus.last.text = e;
  280. if (t)
  281. {
  282. e = t[1];
  283. progressElement.value = 100 * parseInt(t[2]);
  284. progressElement.max = 100 * parseInt(t[4]);
  285. progressElement.hidden = false;
  286. var update = Date.now() / 1000
  287. progressDescElement.innerText = e.match(/[Dd]ownload/)
  288. ? getExp(parseInt(t[2])) + "B / " + getExp(parseInt(t[4])) + "B (" + getExp((parseInt(t[2]) - lastStep) / (update - lastUpdate)) + "B/s)"
  289. : t[2] + " of " + t[4] + " assets loaded";
  290. lastUpdate = Date.now() / 1000;
  291. lastStep = parseInt(t[2]);
  292. spinnerElement.hidden = false;
  293. }
  294. else
  295. {
  296. progressElement.value = null;
  297. progressElement.max = null;
  298. progressElement.hidden = true;
  299. progressDescElement.innerText = "";
  300. if (!e)
  301. spinnerElement.style.display = "none";
  302. }
  303. statusElement.innerHTML = e;
  304. }
  305. }
  306. },
  307. totalDependencies: 0,
  308. monitorRunDependencies: function (e) {
  309. this.totalDependencies = Math.max(this.totalDependencies, e);
  310. Module.setStatus(e ? "Preparing... (" + (this.totalDependencies - e) + "/" + this.totalDependencies + ")" : "All downloads complete.")
  311. },
  312. onRuntimeInitialized: function() {
  313. setResolution = Module.cwrap('set_resolution', 'void', ['number']);
  314. onDownloadProgress = Module.cwrap('onDownloadProgress', 'void', ['number', 'number', 'number']);
  315. onDownloadFinished = Module.cwrap('onDownloadFinished', 'void', ['number']);
  316. onDownloadError = Module.cwrap('onDownloadError', 'void', ['number']);
  317. onDownloadAborted = Module.cwrap('onDownloadAborted', 'void', ['number']);
  318. },
  319. };
  320. Module.setStatus("Downloading...");
  321. window.onerror = function (message, source, line, col, error) {
  322. if (typeof error == "number")
  323. message = Module.ccall('getExceptionMessage', 'string', ['number'], [error]);
  324. Module.setStatus("Oops!<br><br>An error occured and SuperTux crashed.<br><br><pre>" + message + "</pre>");
  325. spinnerElement.style.display = "none";
  326. Module.setStatus = function (e) {
  327. if (e)
  328. Module.printErr("[post-exception status] " + e)
  329. }
  330. }
  331. var autofit = true;
  332. function tryResize() {
  333. if (!autofit || !Module)
  334. return;
  335. if (!setResolution)
  336. return;
  337. try {
  338. setResolution(window.innerWidth, window.innerHeight);
  339. } catch(err) {}
  340. }
  341. window.supertux_setAutofit = function(newAutofit) {
  342. autofit = newAutofit;
  343. document.body.style.overflow = (autofit || ("@CMAKE_BUILD_TYPE@" == "Release")) ? "hidden" : "initial";
  344. tryResize();
  345. }
  346. // FIXME: Hardcoded
  347. const root = "/home/web_user/.local/share/supertux2/";
  348. window.supertux_loadFiles = function() {
  349. try {
  350. // Loading the config file from localStorage is needed, even though the
  351. // rest of the files are stored in IndexedDB, managed by Emscripten.
  352. // Check the supertux_saveFiles function below for details.
  353. for (var key of Object.keys(localStorage)) {
  354. if (key !== "supertux2_config" /*&& !key.match("^profile[0-9]+/")*/)
  355. continue;
  356. keyfilename = key.replace(/^supertux2_/, "");
  357. if (keyfilename.indexOf("/") !== -1) {
  358. try {
  359. FS.mkdir(root + keyfilename.substr(0, keyfilename.indexOf("/")));
  360. } catch {
  361. // Folder probably already exists
  362. }
  363. }
  364. FS.writeFile(root + keyfilename, localStorage.getItem(key));
  365. }
  366. } catch(e){}
  367. }
  368. window.supertux_saveFiles = function() {
  369. try { FS.syncfs((err) => { console.log(err); }); } catch(err) { console.log(err); }
  370. function save(file) {
  371. try {
  372. localStorage.setItem("supertux2_" + file, FS.readFile(root + file, { encoding: "utf8" }));
  373. return true;
  374. } catch(e) {
  375. console.error(e);
  376. console.error("ERROR: Couldn't save file '" + file + "'");
  377. return false;
  378. }
  379. }
  380. // IndexedDB can't save fast enough to be called when the window is closed,
  381. // so save the config file in localStorage just in case it wasn't flushed
  382. // to disk
  383. save("config");
  384. }
  385. window.supertux_download = function(path) {
  386. try {
  387. var downloadBlob = function(data, fileName, mimeType) {
  388. var blob, url;
  389. blob = new Blob([data], {
  390. type: mimeType
  391. });
  392. url = window.URL.createObjectURL(blob);
  393. var a;
  394. a = document.createElement('a');
  395. a.href = url;
  396. a.download = fileName;
  397. document.body.appendChild(a);
  398. a.style = 'display: none';
  399. a.click();
  400. a.remove()
  401. setTimeout(function() {
  402. return window.URL.revokeObjectURL(url);
  403. }, 1000);
  404. };
  405. var stat = FS.stat(path);
  406. var size = stat.size;
  407. var file = FS.open(path, 'r');
  408. var buf = new Uint8Array(size);
  409. FS.read(file, buf, 0, size, 0);
  410. FS.close(file);
  411. downloadBlob(buf, path.substr(path.lastIndexOf('/') + 1), 'application/octet-stream');
  412. return true;
  413. } catch(e) {
  414. console.error(e);
  415. return false;
  416. }
  417. }
  418. const file_upload = document.getElementById("file_upload");
  419. var base_path = '/';
  420. window.supertux_upload = function(base) {
  421. base_path = base;
  422. // Remove one (or multiple) leading slashes
  423. while (base_path.startsWith("/"))
  424. base_path = base_path.substr(1);
  425. // Add ending slash if missing
  426. if (!base_path.endsWith("/"))
  427. base_path += "/";
  428. // Remove duplicate slashes
  429. base_path = base_path.replaceAll(/\/+/g, "/");
  430. file_upload.click();
  431. }
  432. file_upload.addEventListener('change', () => {
  433. for (var file of file_upload.files) {
  434. var fr = new FileReader();
  435. fr.filename = file.name;
  436. fr.onload = function(e) {
  437. var data = new Uint8Array(e.target.result);
  438. // Create all dirs if necessary
  439. var index = 0;
  440. while (base_path.indexOf("/", index) != -1) {
  441. index = base_path.indexOf("/", index);
  442. var subfolder = base_path.substr(0, index);
  443. if (!FS.analyzePath(root + subfolder).exists)
  444. FS.mkdir(root + subfolder);
  445. index += 1;
  446. }
  447. var em_file = FS.open(root + base_path + e.target.filename, "w");
  448. FS.write(em_file, data, 0, data.length, 0);
  449. FS.close(em_file);
  450. };
  451. fr.readAsArrayBuffer(file);
  452. }
  453. });
  454. var saving = false;
  455. window.supertux2_syncfs = function() {
  456. if (saving) return;
  457. saving = true;
  458. try {
  459. FS.syncfs((err) => {
  460. if (err)
  461. console.log(err);
  462. saving = false;
  463. });
  464. } catch (err) {}
  465. }
  466. var onDownloadProgress, onDownloadFinished, onDownloadError, onDownloadAborted;
  467. window.supertux_xhr_download = function(id, url, file) {
  468. console.log('id: ' + id);
  469. console.log('url: ' + url);
  470. console.log('file: ' + file);
  471. if (!onDownloadProgress || !onDownloadFinished || !onDownloadError || !onDownloadAborted)
  472. return;
  473. var xhr = new XMLHttpRequest();
  474. xhr.onreadystatechange = function() {
  475. if (xhr.readyState == 4) {
  476. var data = new Uint8Array(xhr.response);
  477. var stream = FS.open(file, 'w+');
  478. FS.write(stream, data, 0, data.length, 0);
  479. FS.close(stream);
  480. onDownloadFinished(id);
  481. }
  482. };
  483. xhr.responseType = "arraybuffer";
  484. xhr.open("GET", url);
  485. xhr.send();
  486. xhr.addEventListener('progress', (e) => { onDownloadProgress(id, e.loaded, e.total); });
  487. xhr.addEventListener('error', (e) => { onDownloadError(id); });
  488. xhr.addEventListener('abort', (e) => { onDownloadAborted(id); })
  489. }
  490. window.addEventListener('resize', (e) => {
  491. tryResize();
  492. });
  493. window.addEventListener('unload', (e) => {
  494. try {
  495. Module.ccall("save_config", "void", [], []);
  496. } catch(e) {}
  497. supertux_saveFiles();
  498. });
  499. window.addEventListener('keydown', (e) => {
  500. if (e.key == "F11" || (e.key == "Enter" && e.altKey)) {
  501. try {
  502. if (document.fullscreenElement) {
  503. document.exitFullscreen();
  504. } else {
  505. Module.canvas.requestFullscreen();
  506. }
  507. } catch(e) {}
  508. }
  509. });
  510. </script>
  511. <script async src="supertux2.js"></script>
  512. </body>
  513. </html>