template.html.in 17 KB

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