From bd7ebc5f7d3b2dda0c904cd041202a6a278243d0 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Fri, 27 Jun 2014 02:08:00 +0200 Subject: [PATCH] Fix : add backup service and make undo safer --- src/css/settings.css | 1 + src/js/app.js | 6 ++ .../settings/LocalStorageController.js | 69 ++++++++++++++----- src/js/drawingtools/Lighten.js | 8 +-- src/js/model/Layer.js | 6 ++ src/js/model/Piskel.js | 6 ++ src/js/service/BackupService.js | 59 ++++++++++++++++ src/js/service/BeforeUnloadService.js | 23 +++++++ src/js/service/HistoryService.js | 33 ++++++--- src/js/service/LocalStorageService.js | 2 - src/js/service/SavedStatusService.js | 11 +-- src/piskel-script-list.js | 2 + src/templates/settings/localstorage.html | 13 ++++ 13 files changed, 196 insertions(+), 43 deletions(-) create mode 100644 src/js/service/BackupService.js create mode 100644 src/js/service/BeforeUnloadService.js diff --git a/src/css/settings.css b/src/css/settings.css index 726d3403..d29686f4 100644 --- a/src/css/settings.css +++ b/src/css/settings.css @@ -121,6 +121,7 @@ } .settings-title { + color : gold; margin-top: 20px; margin-bottom: 10px; text-transform: uppercase; diff --git a/src/js/app.js b/src/js/app.js index 6d87087e..c9ab4bb1 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -99,6 +99,12 @@ this.savedStatusService = new pskl.service.SavedStatusService(this.piskelController); this.savedStatusService.init(); + this.backupService = new pskl.service.BackupService(this.piskelController); + this.backupService.init(); + + this.beforeUnloadService = new pskl.service.BeforeUnloadService(this.piskelController); + this.beforeUnloadService.init(); + if (this.isAppEngineVersion) { this.storageService = new pskl.service.AppEngineStorageService(this.piskelController); diff --git a/src/js/controller/settings/LocalStorageController.js b/src/js/controller/settings/LocalStorageController.js index 9e6fc80f..fcb4c49b 100644 --- a/src/js/controller/settings/LocalStorageController.js +++ b/src/js/controller/settings/LocalStorageController.js @@ -8,9 +8,13 @@ */ ns.LocalStorageController.prototype.init = function() { this.localStorageItemTemplate_ = pskl.utils.Template.get("local-storage-item-template"); + this.previousSessionTemplate_ = pskl.utils.Template.get("previous-session-info-template"); + this.service_ = pskl.app.localStorageService; this.piskelsList = $('.local-piskels-list'); + this.prevSessionContainer = $('.previous-session'); + this.fillRestoreSession_(); this.fillLocalPiskelsList_(); this.piskelsList.click(this.onPiskelsListClick_.bind(this)); @@ -27,24 +31,58 @@ } else if (action === 'delete') { if (window.confirm('This will permanently DELETE this piskel from your computer. Continue ?')) { this.service_.remove(name); - $.publish(Events.CLOSE_SETTINGS_DRAWER); + this.fillLocalPiskelsList_(); } } }; + ns.LocalStorageController.prototype.fillRestoreSession_ = function () { + var previousInfo = pskl.app.backupService.getPreviousPiskelInfo(); + if (previousInfo) { + var info = { + name : previousInfo.name, + date : this.formatDate_(previousInfo.date, "{{H}}:{{m}} - {{Y}}/{{M}}/{{D}}") + }; + + this.prevSessionContainer.html(pskl.utils.Template.replace(this.previousSessionTemplate_, info)); + $(".restore-session-button").click(this.onRestorePreviousSessionClick_.bind(this)); + } else { + this.prevSessionContainer.html("No piskel backup was found on this browser."); + } + }; + + ns.LocalStorageController.prototype.onRestorePreviousSessionClick_ = function () { + if (window.confirm('This will erase your current workspace. Continue ?')) { + pskl.app.backupService.load(); + $.publish(Events.CLOSE_SETTINGS_DRAWER); + } + }; + + var pad = function (num) { + if (num < 10) { + return "0" + num; + } else { + return "" + num; + } + }; + + ns.LocalStorageController.prototype.formatDate_ = function (date, format) { + date = new Date(date); + var formattedDate = pskl.utils.Template.replace(format, { + Y : date.getFullYear(), + M : pad(date.getMonth() + 1), + D : pad(date.getDate()), + H : pad(date.getHours()), + m : pad(date.getMinutes()) + }); + + return formattedDate; + }; + ns.LocalStorageController.prototype.fillLocalPiskelsList_ = function () { var html = ""; var keys = this.service_.getKeys(); - var pad = function (num) { - if (num < 10) { - return "0" + num; - } else { - return "" + num; - } - }; - - keys.sort(function (k1, k2) { if (k1.date < k2.date) {return 1;} if (k1.date > k2.date) {return -1;} @@ -52,15 +90,8 @@ }); keys.forEach((function (key) { - var date = new Date(key.date); - var formattedDate = pskl.utils.Template.replace("{{Y}}/{{M}}/{{D}} {{H}}:{{m}}", { - Y : date.getFullYear(), - M : pad(date.getMonth() + 1), - D : pad(date.getDate()), - H : pad(date.getHours()), - m : pad(date.getMinutes()) - }); - html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {name : key.name, date : formattedDate}); + var date = this.formatDate_(key.date, "{{Y}}/{{M}}/{{D}} {{H}}:{{m}}"); + html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {name : key.name, date : date}); }).bind(this)); var tableBody_ = this.piskelsList.get(0).tBodies[0]; diff --git a/src/js/drawingtools/Lighten.js b/src/js/drawingtools/Lighten.js index 0ca36646..78a3a7ed 100644 --- a/src/js/drawingtools/Lighten.js +++ b/src/js/drawingtools/Lighten.js @@ -46,12 +46,12 @@ } else { color = window.tinycolor.lighten(pixelColor, step); } + if (color) { + usedPixels[key] = true; + this.superclass.applyToolAt.call(this, col, row, color.toRgbString(), frame, overlay, event); + } } - if (color) { - usedPixels[key] = true; - this.superclass.applyToolAt.call(this, col, row, color.toRgbString(), frame, overlay, event); - } }; ns.Lighten.prototype.releaseToolAt = function(col, row, color, frame, overlay, event) { diff --git a/src/js/model/Layer.js b/src/js/model/Layer.js index d55b602e..13d5977e 100644 --- a/src/js/model/Layer.js +++ b/src/js/model/Layer.js @@ -96,4 +96,10 @@ ns.Layer.prototype.length = function () { return this.frames.length; }; + + ns.Layer.prototype.getHash = function () { + return this.frames.map(function (frame) { + return frame.getHash(); + }).join('-'); + }; })(); \ No newline at end of file diff --git a/src/js/model/Piskel.js b/src/js/model/Piskel.js index 974e8ece..ab28ec14 100644 --- a/src/js/model/Piskel.js +++ b/src/js/model/Piskel.js @@ -109,4 +109,10 @@ var appEngineEditorHeader = $('.piskel-name').html(this.descriptor.name); }; + ns.Piskel.prototype.getHash = function () { + return this.layers.map(function (layer) { + return layer.getHash(); + }).join('-'); + }; + })(); \ No newline at end of file diff --git a/src/js/service/BackupService.js b/src/js/service/BackupService.js new file mode 100644 index 00000000..8b884fbc --- /dev/null +++ b/src/js/service/BackupService.js @@ -0,0 +1,59 @@ +(function () { + var ns = $.namespace('pskl.service'); + var BACKUP_INTERVAL = 1000 * 30; + + ns.BackupService = function (piskelController) { + this.piskelController = piskelController; + this.lastHash = null; + }; + + ns.BackupService.prototype.init = function () { + var previousPiskel = window.localStorage.getItem('bkp.next.piskel'); + var previousInfo = window.localStorage.getItem('bkp.next.info'); + if (previousPiskel && previousInfo) { + window.localStorage.setItem('bkp.prev.piskel', previousPiskel); + window.localStorage.setItem('bkp.prev.info', previousInfo); + } + window.setInterval(this.backup.bind(this), BACKUP_INTERVAL); + }; + + ns.BackupService.prototype.backup = function () { + var piskel = this.piskelController.getPiskel(); + var descriptor = piskel.getDescriptor(); + var hash = piskel.getHash(); + var info = { + name : descriptor.name, + description : descriptor.info, + date : Date.now(), + hash : hash + }; + + // Do not save an unchanged piskel + if (hash !== this.lastHash) { + this.lastHash = hash; + window.localStorage.setItem('bkp.next.piskel', this.piskelController.serialize()); + window.localStorage.setItem('bkp.next.info', JSON.stringify(info)); + } + }; + + ns.BackupService.prototype.getPreviousPiskelInfo = function () { + var previousInfo = window.localStorage.getItem('bkp.prev.info'); + if (previousInfo) { + return JSON.parse(previousInfo); + } + }; + + + ns.BackupService.prototype.load = function() { + + var previousPiskel = window.localStorage.getItem('bkp.prev.piskel'); + var previousInfo = window.localStorage.getItem('bkp.prev.info'); + previousPiskel = JSON.parse(previousPiskel); + previousInfo = JSON.parse(previousInfo); + + pskl.utils.serialization.Deserializer.deserialize(previousPiskel, function (piskel) { + piskel.setDescriptor(new pskl.model.piskel.Descriptor(previousInfo.name, previousInfo.description, true)); + pskl.app.piskelController.setPiskel(piskel); + }); + }; +})(); \ No newline at end of file diff --git a/src/js/service/BeforeUnloadService.js b/src/js/service/BeforeUnloadService.js new file mode 100644 index 00000000..a6d4a63a --- /dev/null +++ b/src/js/service/BeforeUnloadService.js @@ -0,0 +1,23 @@ +(function () { + var ns = $.namespace('pskl.service'); + + ns.BeforeUnloadService = function (piskelController) { + this.piskelController = piskelController; + }; + + + ns.BeforeUnloadService.prototype.init = function () { + window.addEventListener("beforeunload", this.onBeforeUnload.bind(this)); + }; + + ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) { + pskl.app.backupService.backup(); + if (pskl.app.savedStatusService.isDirty()) { + var confirmationMessage = "Your Piskel seems to have unsaved changes"; + + (evt || window.event).returnValue = confirmationMessage; + return confirmationMessage; + } + }; + +})(); \ No newline at end of file diff --git a/src/js/service/HistoryService.js b/src/js/service/HistoryService.js index 18e7c944..512f1c8b 100644 --- a/src/js/service/HistoryService.js +++ b/src/js/service/HistoryService.js @@ -63,24 +63,39 @@ }; ns.HistoryService.prototype.getPreviousSnapshotIndex_ = function (index) { + var counter = 0; while (this.stateQueue[index] && !this.stateQueue[index].piskel) { index = index - 1; + if(++counter > 2*SNAPSHOT_PERIOD) { + break; + } } return index; }; ns.HistoryService.prototype.loadState = function (index) { - if (this.isLoadStateAllowed_(index)) { - this.lastLoadState = Date.now(); + try { + if (this.isLoadStateAllowed_(index)) { + this.lastLoadState = Date.now(); - var snapshotIndex = this.getPreviousSnapshotIndex_(index); - if (snapshotIndex < 0) { - throw 'Could not find previous SNAPSHOT saved in history stateQueue'; + var snapshotIndex = this.getPreviousSnapshotIndex_(index); + if (snapshotIndex < 0) { + throw 'Could not find previous SNAPSHOT saved in history stateQueue'; + } + var serializedPiskel = this.getSnapshotFromState_(snapshotIndex); + var onPiskelLoadedCb = this.onPiskelLoaded_.bind(this, index, snapshotIndex); + pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, onPiskelLoadedCb); + } + } catch (e) { + window.console.error("[CRITICAL ERROR] : Unable to load a history state."); + window.console.error("Can you open an issue on http://github.com/juliandescottes/piskel or contact @piskelapp on twitter ? Thanks !"); + window.console.error("Thanks !"); + if (typeof e === "string") { + window.console.error(e); + } else { + window.console.error(e.message); + window.console.error(e.stack); } - - var serializedPiskel = this.getSnapshotFromState_(snapshotIndex); - var onPiskelLoadedCb = this.onPiskelLoaded_.bind(this, index, snapshotIndex); - pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, onPiskelLoadedCb); } }; diff --git a/src/js/service/LocalStorageService.js b/src/js/service/LocalStorageService.js index 62a174a9..a0861b71 100644 --- a/src/js/service/LocalStorageService.js +++ b/src/js/service/LocalStorageService.js @@ -11,8 +11,6 @@ ns.LocalStorageService.prototype.init = function() {}; -// localStorage.setItem('piskel_bkp', pskl.app.piskelController.serialize()) - ns.LocalStorageService.prototype.save = function(name, description, piskel) { this.removeFromKeys_(name); this.addToKeys_(name, description, Date.now()); diff --git a/src/js/service/SavedStatusService.js b/src/js/service/SavedStatusService.js index 04ab950b..dba4fbf3 100644 --- a/src/js/service/SavedStatusService.js +++ b/src/js/service/SavedStatusService.js @@ -10,8 +10,6 @@ $.subscribe(Events.PISKEL_RESET, this.onPiskelReset.bind(this)); $.subscribe(Events.PISKEL_SAVED, this.onPiskelSaved.bind(this)); - - window.addEventListener("beforeunload", this.onBeforeUnload.bind(this)); }; ns.SavedStatusService.prototype.onPiskelReset = function () { @@ -52,13 +50,8 @@ } }; - ns.SavedStatusService.prototype.onBeforeUnload = function (evt) { + ns.SavedStatusService.prototype.isDirty = function (evt) { var piskel = this.piskelController.getPiskel(); - if (piskel.isDirty_) { - var confirmationMessage = "Your Piskel seems to have unsaved changes"; - - (evt || window.event).returnValue = confirmationMessage; - return confirmationMessage; - } + return piskel.isDirty_; }; })(); \ No newline at end of file diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index cbdc0061..12a8e832 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -104,6 +104,8 @@ "js/service/LocalStorageService.js", "js/service/GithubStorageService.js", "js/service/AppEngineStorageService.js", + "js/service/BackupService.js", + "js/service/BeforeUnloadService.js", "js/service/HistoryService.js", "js/service/SavedStatusService.js", "js/service/keyboard/ShortcutService.js", diff --git a/src/templates/settings/localstorage.html b/src/templates/settings/localstorage.html index 524cb15e..774c6084 100644 --- a/src/templates/settings/localstorage.html +++ b/src/templates/settings/localstorage.html @@ -1,4 +1,9 @@
+
+ Restore previous session +
+
+
Browse Local Piskels
@@ -15,4 +20,12 @@ x +
\ No newline at end of file