From a2b1342f55a1eec3d578e22ae61e3c19ac1a6adc Mon Sep 17 00:00:00 2001 From: tobspr Date: Sat, 16 May 2020 10:05:19 +0200 Subject: [PATCH] Allow downloading savegames --- res/ui/icons/download.png | Bin 0 -> 712 bytes src/css/states/main_menu.scss | 21 ++++++++++++++++----- src/js/core/read_write_proxy.js | 10 ++++++++++ src/js/core/utils.js | 17 +++++++++++++++++ src/js/states/main_menu.js | 19 ++++++++++++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 res/ui/icons/download.png diff --git a/res/ui/icons/download.png b/res/ui/icons/download.png new file mode 100644 index 0000000000000000000000000000000000000000..bb28bf81fdf6e4b414d65b372994554495ccb000 GIT binary patch literal 712 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4i*Lm28M*4p$rTREa{HEjtmSN`?>!lvNA9* zC?tCX`7$t6sWC7#v@kIIVqjosc)`F>YQVtoDuIE)Y6b&?c)^@qfi?^b3`|Mh?k)@r zt9q4<7#J8h3p^r=85sDEfH31!Z9ZuR1_t&LPhVH|C#(V-3{qO1&5{fZjMF?_978hh zy`8z+@34VLTYq*(&_=^1mz|;!GbTo?n9tbR_&w>luaMdeNzLiovTn|p@M?zD%K$Eu z(pkw-)~5@n39EkkezbGV3wwqW3-hjcq@HkI9sOnxkM0Lf=^NFI3eo&GIEs^bZB@4) zU^aMjrh?JEnZ=_43%a(vL*;i;L}nnaA1p^Pf|9 z4PC#$Ec}Ib#n-m?%dTJkjw(FxOk|t?o9$JsuFU=pnR^q2`J`=% zws93#BnMdLe2^&n`+$2=kUH1iEvEhYH`rgv-fh}0`nZ)deuv;y>!>vW3#WH|xLK3w z7@^BwIVX4bQT;nzhqsBXc`p_(b^Wwj|Fo!9A)7_<8>TpE%3a;?V>=U5|C<>n)cQB; zidQ`O@9<*@tMrZo(O-I=2=-rC-Eb_LBSItMfo{9azgN?@z1j8Y0H@5#2V(Yt9&DnI z#I=4L6xaH3n3+LE-I8_xkNno}Tpx|Ou0CL7m=t_9tiPy!rsXj)j`Z1DE3%FRYPumN@iLm zZVhj`1S%OA7$iY91m~xflqVLYGL)B>>t*I;7bhncr0V4trO$q6BLzyKp00i_>zopr E0Lm>0S^xk5 literal 0 HcmV?d00001 diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index a67cef3a..458bda6f 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -85,21 +85,22 @@ .savegames { @include S(max-height, 92px); overflow-y: auto; - @include S(width, 200px); + @include S(width, 250px); pointer-events: all; @include S(padding-right, 5px); display: grid; grid-auto-flow: row; @include S(grid-gap, 5px); @include S(margin-top, 10px); + .savegame { background: #eee; @include BorderRadius(4px); @include S(padding, 5px); display: grid; - grid-template-columns: 1fr auto; + grid-template-columns: 1fr auto auto; grid-template-rows: auto auto; - @include S(grid-column-gap, 15px); + @include S(grid-column-gap, 5px); .internalId { grid-column: 1 / 2; @@ -114,8 +115,9 @@ @include PlainText; } - button.resumeGame { - grid-column: 2 / 3; + button.resumeGame, + button.downloadGame { + grid-column: 3 / 4; grid-row: 1 / 3; @include S(width, 30px); @include S(height, 30px); @@ -123,6 +125,15 @@ align-self: center; background: #44484a uiResource("icons/play.png") center center / 40% no-repeat; } + + button.downloadGame { + grid-column: 2 / 3; + background-image: uiResource("icons/download.png"); + @include S(width, 15px); + @include S(height, 15px); + align-self: end; + background-size: 60%; + } } } } diff --git a/src/js/core/read_write_proxy.js b/src/js/core/read_write_proxy.js index a735f9ad..b0f16704 100644 --- a/src/js/core/read_write_proxy.js +++ b/src/js/core/read_write_proxy.js @@ -79,6 +79,16 @@ export class ReadWriteProxy { return this.currentData; } + /** + * + * @param {object} obj + */ + static serializeObject(obj) { + const jsonString = JSON_stringify(compressObject(obj)); + const checksum = sha1(jsonString + salt); + return compressionPrefix + compressX64(checksum + jsonString); + } + /** * Writes the data asychronously, fails if verify() fails * @returns {Promise} diff --git a/src/js/core/utils.js b/src/js/core/utils.js index 08011a45..e6736ed1 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -859,3 +859,20 @@ export function formatSecondsToTimeAgo(secs) { return days + " days ago"; } } + +/** + * Generates a file download + * @param {string} filename + * @param {string} text + */ +export function generateFileDownload(filename, text) { + var element = document.createElement("a"); + element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text)); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + document.body.removeChild(element); +} diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 4cf3ad79..8dfc8c83 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -1,7 +1,8 @@ import { GameState } from "../core/game_state"; import { cachebust } from "../core/cachebust"; import { globalConfig } from "../core/config"; -import { makeDiv, formatSecondsToTimeAgo } from "../core/utils"; +import { makeDiv, formatSecondsToTimeAgo, generateFileDownload } from "../core/utils"; +import { ReadWriteProxy } from "../core/read_write_proxy"; export class MainMenuState extends GameState { constructor() { @@ -90,10 +91,15 @@ export class MainMenuState extends GameState { formatSecondsToTimeAgo((new Date().getTime() - games[i].lastUpdate) / 1000.0) ); + const downloadButton = document.createElement("button"); + downloadButton.classList.add("styledButton", "downloadGame"); + elem.appendChild(downloadButton); + const resumeBtn = document.createElement("button"); resumeBtn.classList.add("styledButton", "resumeGame"); elem.appendChild(resumeBtn); + this.trackClicks(downloadButton, () => this.downloadGame(games[i])); this.trackClicks(resumeBtn, () => this.resumeGame(games[i])); } } @@ -111,6 +117,17 @@ export class MainMenuState extends GameState { }); } + /** + * @param {object} game + */ + downloadGame(game) { + const savegame = this.app.savegameMgr.getSavegameById(game.internalId); + savegame.readAsync().then(() => { + const data = ReadWriteProxy.serializeObject(savegame.currentData); + generateFileDownload(savegame.filename, data); + }); + } + onPlayButtonClicked() { const savegame = this.app.savegameMgr.createNewSavegame();