template.html.in 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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. var Module = {
  213. preRun: [],
  214. postRun: [],
  215. print: function () {
  216. var e = document.getElementById("output");
  217. if (e)
  218. e.innerHTML = "";
  219. return function (t) {
  220. if (arguments.length > 1)
  221. t = Array.prototype.slice.call(arguments).join(" ");
  222. t = Array.prototype.slice.call(arguments).join(" ");
  223. console.log(t);
  224. if (e) {
  225. e.innerHTML += t + "<br/>";
  226. e.scrollTop = e.scrollHeight;
  227. }
  228. }
  229. }(),
  230. printErr: function () {
  231. var e = document.getElementById("output");
  232. if (e)
  233. e.innerHTML = "";
  234. return function (t) {
  235. if (arguments.length > 1)
  236. t = Array.prototype.slice.call(arguments).join(" ");
  237. console.error(t);
  238. if (e) {
  239. e.innerHTML += "<span style='color:red;'>" + t + "</span><br/>";
  240. e.scrollTop = e.scrollHeight;
  241. }
  242. }
  243. }(),
  244. canvas: function () {
  245. var c = document.getElementById("canvas");
  246. c.addEventListener("webglcontextlost", (function (e) {
  247. alert("WebGL context lost. You will need to reload the page.");
  248. e.preventDefault();
  249. }), !1);
  250. // Fixed a bug where an iframe containing SuperTUx (e. g. Newgrounds)
  251. // would lose focus if the user clicks outside of the iframe and cannot
  252. // get focus back when user clicks inside the iframe, thus preventing
  253. // any keyboard input from being received by SuperTux.
  254. // If you want to try: Disable the three lines below, compile, upload on
  255. // Newgrounds, wait for the game to load, click outside the game, click
  256. // back inside the game, then try to press buttons (e. g. arrows), it'll
  257. // behave as if focus was outside the iframe (e. g. pressing down will scroll
  258. // the page instead of navigating the menus/worldmap/ducking/whatever)
  259. c.addEventListener("click", function(e) {
  260. c.focus();
  261. });
  262. return c;
  263. }(),
  264. setStatus: function (e) {
  265. overlayElement.style.display = e ? "flex" : "none";
  266. if (Module.setStatus.last || (Module.setStatus.last = { time: Date.now(), text: "" }), e !== Module.setStatus.last.text)
  267. {
  268. var t = e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/),
  269. n = Date.now();
  270. if (!(t && n - Module.setStatus.last.time < 30))
  271. {
  272. Module.setStatus.last.time = n;
  273. Module.setStatus.last.text = e;
  274. if (t)
  275. {
  276. e = t[1];
  277. progressElement.value = 100 * parseInt(t[2]);
  278. progressElement.max = 100 * parseInt(t[4]);
  279. progressElement.hidden = false;
  280. var update = Date.now() / 1000
  281. progressDescElement.innerText = e.match(/[Dd]ownload/)
  282. ? getExp(parseInt(t[2])) + "B / " + getExp(parseInt(t[4])) + "B (" + getExp((parseInt(t[2]) - lastStep) / (update - lastUpdate)) + "B/s)"
  283. : t[2] + " of " + t[4] + " assets loaded";
  284. lastUpdate = Date.now() / 1000;
  285. lastStep = parseInt(t[2]);
  286. spinnerElement.hidden = false;
  287. }
  288. else
  289. {
  290. progressElement.value = null;
  291. progressElement.max = null;
  292. progressElement.hidden = true;
  293. progressDescElement.innerText = "";
  294. if (!e)
  295. spinnerElement.style.display = "none";
  296. }
  297. statusElement.innerHTML = e;
  298. }
  299. }
  300. },
  301. totalDependencies: 0,
  302. monitorRunDependencies: function (e) {
  303. this.totalDependencies = Math.max(this.totalDependencies, e);
  304. Module.setStatus(e ? "Preparing... (" + (this.totalDependencies - e) + "/" + this.totalDependencies + ")" : "All downloads complete.")
  305. }
  306. };
  307. Module.setStatus("Downloading...");
  308. window.onerror = function (message, source, line, col, error) {
  309. if (typeof error == "number")
  310. message = Module.ccall('getExceptionMessage', 'string', ['number'], [error]);
  311. Module.setStatus("Oops!<br><br>An error occured and SuperTux crashed.<br><br><pre>" + message + "</pre>");
  312. spinnerElement.style.display = "none";
  313. Module.setStatus = function (e) {
  314. if (e)
  315. Module.printErr("[post-exception status] " + e)
  316. }
  317. }
  318. var autofit = true;
  319. var setResolution = null;
  320. function tryResize() {
  321. if (!autofit || !Module)
  322. return;
  323. if (!setResolution)
  324. setResolution = Module.cwrap('set_resolution', 'void', ['number']);
  325. try {
  326. setResolution(window.innerWidth, window.innerHeight);
  327. } catch(err) {}
  328. }
  329. window.supertux_setAutofit = function(newAutofit) {
  330. autofit = newAutofit;
  331. document.body.style.overflow = (autofit || ("@CMAKE_BUILD_TYPE@" == "Release")) ? "hidden" : "initial";
  332. tryResize();
  333. }
  334. // FIXME: Hardcoded
  335. const root = "/home/web_user/.local/share/supertux2/";
  336. window.supertux_loadFiles = function() {
  337. try {
  338. // Loading the config file from localStorage is needed, even though the
  339. // rest of the files are stored in IndexedDB, managed by Emscripten.
  340. // Check the supertux_saveFiles function below for details.
  341. for (var key of Object.keys(localStorage)) {
  342. if (key !== "supertux2_config" /*&& !key.match("^profile[0-9]+/")*/)
  343. continue;
  344. keyfilename = key.replace(/^supertux2_/, "");
  345. if (keyfilename.indexOf("/") !== -1) {
  346. try {
  347. FS.mkdir(root + keyfilename.substr(0, keyfilename.indexOf("/")));
  348. } catch {
  349. // Folder probably already exists
  350. }
  351. }
  352. FS.writeFile(root + keyfilename, localStorage.getItem(key));
  353. }
  354. } catch(e){}
  355. }
  356. window.supertux_saveFiles = function() {
  357. try { FS.syncfs((err) => { console.log(err); }); } catch(err) { console.log(err); }
  358. function save(file) {
  359. try {
  360. localStorage.setItem("supertux2_" + file, FS.readFile(root + file, { encoding: "utf8" }));
  361. return true;
  362. } catch(e) {
  363. console.error(e);
  364. console.error("ERROR: Couldn't save file '" + file + "'");
  365. return false;
  366. }
  367. }
  368. // IndexedDB can't save fast enough to be called when the window is closed,
  369. // so save the config file in localStorage just in case it wasn't flushed
  370. // to disk
  371. save("config");
  372. }
  373. window.supertux_download = function(path) {
  374. try {
  375. var downloadBlob = function(data, fileName, mimeType) {
  376. var blob, url;
  377. blob = new Blob([data], {
  378. type: mimeType
  379. });
  380. url = window.URL.createObjectURL(blob);
  381. var a;
  382. a = document.createElement('a');
  383. a.href = url;
  384. a.download = fileName;
  385. document.body.appendChild(a);
  386. a.style = 'display: none';
  387. a.click();
  388. a.remove()
  389. setTimeout(function() {
  390. return window.URL.revokeObjectURL(url);
  391. }, 1000);
  392. };
  393. var stat = FS.stat(path);
  394. var size = stat.size;
  395. var file = FS.open(path, 'r');
  396. var buf = new Uint8Array(size);
  397. FS.read(file, buf, 0, size, 0);
  398. FS.close(file);
  399. downloadBlob(buf, path.substr(path.lastIndexOf('/') + 1), 'application/octet-stream');
  400. return true;
  401. } catch(e) {
  402. console.error(e);
  403. return false;
  404. }
  405. }
  406. const file_upload = document.getElementById("file_upload");
  407. var base_path = '/';
  408. window.supertux_upload = function(base) {
  409. base_path = base;
  410. // Remove one (or multiple) leading slashes
  411. while (base_path.startsWith("/"))
  412. base_path = base_path.substr(1);
  413. // Add ending slash if missing
  414. if (!base_path.endsWith("/"))
  415. base_path += "/";
  416. // Remove duplicate slashes
  417. base_path = base_path.replaceAll(/\/+/g, "/");
  418. file_upload.click();
  419. }
  420. file_upload.addEventListener('change', () => {
  421. for (var file of file_upload.files) {
  422. var fr = new FileReader();
  423. fr.filename = file.name;
  424. fr.onload = function(e) {
  425. var data = new Uint8Array(e.target.result);
  426. // Create all dirs if necessary
  427. var index = 0;
  428. while (base_path.indexOf("/", index) != -1) {
  429. index = base_path.indexOf("/", index);
  430. var subfolder = base_path.substr(0, index);
  431. if (!FS.analyzePath(root + subfolder).exists)
  432. FS.mkdir(root + subfolder);
  433. index += 1;
  434. }
  435. var em_file = FS.open(root + base_path + e.target.filename, "w");
  436. FS.write(em_file, data, 0, data.length, 0);
  437. FS.close(em_file);
  438. };
  439. fr.readAsArrayBuffer(file);
  440. }
  441. });
  442. var saving = false;
  443. window.supertux2_syncfs = function() {
  444. if (saving) return;
  445. saving = true;
  446. try {
  447. FS.syncfs((err) => {
  448. if (err)
  449. console.log(err);
  450. saving = false;
  451. });
  452. } catch (err) {}
  453. }
  454. var onDownloadProgress, onDownloadFinished, onDownloadError, onDownloadAborted;
  455. window.supertux_xhr_download = function(id, url, file) {
  456. console.log('id: ' + id);
  457. console.log('url: ' + url);
  458. console.log('file: ' + file);
  459. if (!onDownloadProgress)
  460. onDownloadProgress = Module.cwrap('onDownloadProgress', 'void', ['number', 'number', 'number']);
  461. if (!onDownloadFinished)
  462. onDownloadFinished = Module.cwrap('onDownloadFinished', 'void', ['number']);
  463. if (!onDownloadError)
  464. onDownloadError = Module.cwrap('onDownloadError', 'void', ['number']);
  465. if (!onDownloadAborted)
  466. onDownloadAborted = Module.cwrap('onDownloadAborted', 'void', ['number']);
  467. var xhr = new XMLHttpRequest();
  468. xhr.onreadystatechange = function() {
  469. if (xhr.readyState == 4) {
  470. var data = new Uint8Array(xhr.response);
  471. var stream = FS.open(file, 'w+');
  472. FS.write(stream, data, 0, data.length, 0);
  473. FS.close(stream);
  474. onDownloadFinished(id);
  475. }
  476. };
  477. xhr.responseType = "arraybuffer";
  478. xhr.open("GET", url);
  479. xhr.send();
  480. xhr.addEventListener('progress', (e) => { onDownloadProgress(id, e.loaded, e.total); });
  481. xhr.addEventListener('error', (e) => { onDownloadError(id); });
  482. xhr.addEventListener('abort', (e) => { onDownloadAborted(id); })
  483. }
  484. window.addEventListener('resize', (e) => {
  485. tryResize();
  486. });
  487. window.addEventListener('unload', (e) => {
  488. Module.ccall("save_config", "void", [], []);
  489. supertux_saveFiles();
  490. });
  491. window.addEventListener('keydown', (e) => {
  492. if (e.key == "F11" || (e.key == "Enter" && e.altKey)) {
  493. try {
  494. if (document.fullscreenElement) {
  495. document.exitFullscreen();
  496. } else {
  497. Module.canvas.requestFullscreen();
  498. }
  499. } catch(e) {}
  500. }
  501. });
  502. </script>
  503. <script async src="supertux2.js"></script>
  504. </body>
  505. </html>