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!ia0vp^4j|0I0wfs{c7_5;mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5SkAA0v+6_s~>D#hy&Y19OhSkdeE|bz($x+s)3#SRI ze)@j2bIl8Th7$|(u6U%La9$n#W)F|<2Tti5)r<ds2`(*WN9r{rWf9U&-EW+AjLIl{0>a;8p9WH318!cYU~7lj#_t%U?MsclS~K zJ6(sjiLH4r7B6-Ev|9hPs8%7HMe!S^IBCjV-SA^O6I1`287I{GH|vU5Jo)eNV+pJD zjswwOdY%aOUs&C6ESe)iBjSN>yUo8>)3?3Z_2~eo%*qF1_JJO3qL0M2ejEhi!^{jS z>Xxkgf8@7*=lW>Ob@c%w!=&J=Vf{t*GcAvaaiq`IT9I`W7-^~{t`Q|Ei6yC4$wjF^ ziowXh$UxV?RM*ff#K6MJz`)ALOdCiV7|gxC0#!q9eoAIqC2kFGx&$hLHb{bO2+mI{ pDNig)WhgH%*UQYyE>2D?NY%?PN}v7CMhd8i!PC{xWt~$(69CFB2wDID 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();