From 1c66282b010859b68151b9c67a934d10db63cb8e Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Sat, 14 Mar 2015 00:11:24 +1300 Subject: [PATCH 01/11] Added pskl.utils.Environment for detecting if Piskel is running in Node-Webkit. --- src/js/utils/Environment.js | 25 +++++++++++++++++++++++++ src/piskel-script-list.js | 1 + 2 files changed, 26 insertions(+) create mode 100644 src/js/utils/Environment.js diff --git a/src/js/utils/Environment.js b/src/js/utils/Environment.js new file mode 100644 index 0000000..58b973a --- /dev/null +++ b/src/js/utils/Environment.js @@ -0,0 +1,25 @@ +/** + * detection method from: + * http://videlais.com/2014/08/23/lessons-learned-from-detecting-node-webkit/ + */ + +(function () { + + var ns = $.namespace('pskl.utils'); + + ns.Environment = { + detectNodeWebkit : function () { + var isNode = (typeof process !== "undefined" && typeof require !== "undefined"); + var isNodeWebkit = false; + if (isNode) { + try { + isNodeWebkit = (typeof require('nw.gui') !== "undefined"); + } catch (e) { + isNodeWebkit = false; + } + } + return isNodeWebkit; + } + }; + +})(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 1d50391..34353af 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -22,6 +22,7 @@ "js/utils/FileUtils.js", "js/utils/FrameTransform.js", "js/utils/FrameUtils.js", + "js/utils/Environment.js", "js/utils/LayerUtils.js", "js/utils/ImageResizer.js", "js/utils/PixelUtils.js", From fa6f2e5db69894b6efcbbfa15b24533877f28e9b Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Sun, 15 Mar 2015 01:40:00 +1300 Subject: [PATCH 02/11] Added new save functions for when running in Node-Webkit. --- src/js/controller/settings/SaveController.js | 49 +++++++++++++++++++- src/js/utils/FileUtils.js | 42 +++++++++++++++++ src/piskel-script-list.js | 2 +- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 0ac5b7f..840c6d2 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -25,8 +25,26 @@ this.isPublicCheckbox = $('input[name=save-public-checkbox]'); this.isPublicCheckbox.prop('checked', descriptor.isPublic); - this.saveFileButton = $('#save-file-button'); - this.saveFileButton.click(this.saveFile_.bind(this)); + + // prevent default behaviour of ctrl-s is there a way to do this + // with the pre-existing system? Should I use alt-s instead? + $(document).bind('keydown', 'ctrl+s', function(e) { + e.preventDefault(); + return false; + }); + + //Environment dependent configuration: + if (pskl.utils.Environment.detectNodeWebkit()) { + // running in Node-Webkit... + pskl.app.shortcutService.addShortcut('ctrl+s', this.saveFileDesktop_.bind(this)); + this.saveFileButton = $('#save-file-button'); + this.saveFileButton.click(this.saveFileDesktop_.bind(this)); + } else { + // running in browser... + pskl.app.shortcutService.addShortcut('ctrl+s', this.saveFileBrowser_.bind(this)); + this.saveFileButton = $('#save-file-button'); + this.saveFileButton.click(this.saveFileBrowser_.bind(this)); + } this.saveLocalButton = $('#save-browser-button'); this.saveLocalButton.click(this.saveLocal_.bind(this)); @@ -122,7 +140,20 @@ } }; + /** + * @deprecated - renamed "saveFileBrowser_" + * @private + */ ns.SaveController.prototype.saveFile_ = function () { + // detect if this is running in NodeWebkit + if (pskl.utils.Environment.detectNodeWebkit()) { + this.saveFileDesktop_(); + } else { + this.saveFileBrowser_(); + } + }; + + ns.SaveController.prototype.saveFileBrowser_ = function () { this.beforeSaving_(); pskl.utils.BlobUtils.stringToBlob(pskl.app.piskelController.serialize(), function(blob) { pskl.utils.FileUtils.downloadAsFile(blob, this.getLocalFilename_()); @@ -131,6 +162,20 @@ }.bind(this), "application/piskel+json"); }; + ns.SaveController.prototype.saveFileDesktop_ = function () { + this.beforeSaving_(); + var serialized = pskl.app.piskelController.serialize(); + // TODO: hold on to the full path to the file: + var fullSavePath = null; + // TODO: if we already have a filename (and we are sure this is the same document!!!) + // then save over the old file without opening a save dialog (using nodejs 'fs' api) + pskl.utils.FileUtils.desktopSaveAs(serialized, null, function (filename) { + this.onSaveSuccess_(); + this.afterSaving_(); + fullSavePath = filename; + }.bind(this)); + } + ns.SaveController.prototype.getName = function () { return this.nameInput.val(); }; diff --git a/src/js/utils/FileUtils.js b/src/js/utils/FileUtils.js index 4189e32..14230ce 100644 --- a/src/js/utils/FileUtils.js +++ b/src/js/utils/FileUtils.js @@ -37,6 +37,48 @@ downloadLink.removeEventListener('click', stopPropagation); document.body.removeChild(downloadLink); } + }, + + /** + * + * @param content + * @param defaultFileName + * @param callback + */ + desktopSaveAs: function (content, defaultFileName, callback) { + // NodeWebkit has no js api for opening the save dialog. + // Instead, it adds two new attributes to the anchor tag: nwdirectory and nwsaveas + // (see: https://github.com/nwjs/nw.js/wiki/File-dialogs ) + defaultFileName = defaultFileName || ""; + var tagString = ''; + var $chooser = $(tagString); + $chooser.change(function(e) { + var filename = $(this).val(); + pskl.utils.FileUtils.desktopSaveToFile(content, filename, function(){ + callback(filename); + }); + }); + $chooser.trigger('click'); + }, + + /** + * Save data directly to disk, without showing a save dialog + * Requires Node-Webkit environment for file system access + * @param content - data to be saved + * @param {string} filename - fill path to the file + * @callback callback + */ + desktopSaveToFile: function(content, filename, callback) { + var fs = require('fs'); + //var arrayBuffer = ns.FileUtils.toArrayBuffer(content); + fs.writeFile(filename, content, function(err){ + if (err) { + //throw err; + console.log('destopSavetoFile() - error saving file', filename, 'Error:', err); + } + callback(); + }); } + }; })(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 34353af..9e697cf 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -18,11 +18,11 @@ "js/utils/DateUtils.js", "js/utils/Dom.js", "js/utils/Event.js", + "js/utils/Environment.js", "js/utils/Math.js", "js/utils/FileUtils.js", "js/utils/FrameTransform.js", "js/utils/FrameUtils.js", - "js/utils/Environment.js", "js/utils/LayerUtils.js", "js/utils/ImageResizer.js", "js/utils/PixelUtils.js", From b168e8ca769b66678b807506620a393de1292186 Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Mon, 16 Mar 2015 23:13:36 +1300 Subject: [PATCH 03/11] Store user selected filePath on the Piskel instance stored in pskl.app.piskelController. Getter and Setter in PublicPiskelController for filePath. --- .../piskel/PublicPiskelController.js | 8 +++++++ src/js/controller/settings/SaveController.js | 24 ++++++++++++------- src/js/model/Piskel.js | 4 ++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/js/controller/piskel/PublicPiskelController.js b/src/js/controller/piskel/PublicPiskelController.js index e5da46b..4d6c4fb 100644 --- a/src/js/controller/piskel/PublicPiskelController.js +++ b/src/js/controller/piskel/PublicPiskelController.js @@ -136,6 +136,14 @@ return this.piskelController.piskel; }; + ns.PublicPiskelController.prototype.setSavePath = function (savePath) { + this.piskelController.piskel.savePath = savePath; + } + + ns.PublicPiskelController.prototype.getSavePath = function () { + return this.piskelController.piskel.savePath; + } + ns.PublicPiskelController.prototype.raiseSaveStateEvent_ = function (fn, args) { $.publish(Events.PISKEL_SAVE_STATE, { type : pskl.service.HistoryService.REPLAY_NO_SNAPSHOT, diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 840c6d2..1d5d2d4 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -165,15 +165,21 @@ ns.SaveController.prototype.saveFileDesktop_ = function () { this.beforeSaving_(); var serialized = pskl.app.piskelController.serialize(); - // TODO: hold on to the full path to the file: - var fullSavePath = null; - // TODO: if we already have a filename (and we are sure this is the same document!!!) - // then save over the old file without opening a save dialog (using nodejs 'fs' api) - pskl.utils.FileUtils.desktopSaveAs(serialized, null, function (filename) { - this.onSaveSuccess_(); - this.afterSaving_(); - fullSavePath = filename; - }.bind(this)); + var savePath = pskl.app.piskelController.getSavePath(); + // if we already have a filename, just save the file (using nodejs 'fs' api) + if (savePath !== null) { + pskl.utils.FileUtils.desktopSaveToFile(serialized, savePath, function () { + this.onSaveSuccess_(); + this.afterSaving_(); + }.bind(this)); + } else { + // "save as" = open a save dialog, and store the returned save path + pskl.utils.FileUtils.desktopSaveAs(serialized, null, function (selectedSavePath) { + this.onSaveSuccess_(); + this.afterSaving_(); + pskl.app.piskelController.setSavePath(selectedSavePath); + }.bind(this)); + } } ns.SaveController.prototype.getName = function () { diff --git a/src/js/model/Piskel.js b/src/js/model/Piskel.js index fcc1ebd..bbd0637 100644 --- a/src/js/model/Piskel.js +++ b/src/js/model/Piskel.js @@ -20,6 +20,10 @@ this.height = height; this.descriptor = descriptor; + + /** @type {String} */ + this.savePath = null; + } else { throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ","); } From 04a1633a90299349f050eb14cde6ad6867939f22 Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Wed, 18 Mar 2015 00:24:03 +1300 Subject: [PATCH 04/11] Moved desktop FileUtils into their own class. Split PiskelFileUtils::loadFromFile() so the decoding portion can be called separately by desktop load function. "savePath" is stored in piskel instance, and propagated to new instances in HistoryService. "savePath" is also stored on load so it is available for resave. --- .../controller/settings/ImportController.js | 27 ++++++- src/js/controller/settings/SaveController.js | 6 +- src/js/service/HistoryService.js | 2 + src/js/utils/FileUtils.js | 41 ----------- src/js/utils/FileUtilsDesktop.js | 71 +++++++++++++++++++ src/js/utils/PiskelFileUtils.js | 17 +++-- src/piskel-script-list.js | 1 + 7 files changed, 112 insertions(+), 53 deletions(-) create mode 100644 src/js/utils/FileUtilsDesktop.js diff --git a/src/js/controller/settings/ImportController.js b/src/js/controller/settings/ImportController.js index 3821a1e..d3aaae8 100644 --- a/src/js/controller/settings/ImportController.js +++ b/src/js/controller/settings/ImportController.js @@ -17,10 +17,15 @@ this.fileInputButton.click(this.onFileInputClick_.bind(this)); this.hiddenOpenPiskelInput = $('[name=open-piskel-input]'); - this.hiddenOpenPiskelInput.change(this.onOpenPiskelChange_.bind(this)); - this.openPiskelInputButton = $('.open-piskel-button'); - this.openPiskelInputButton.click(this.onOpenPiskelClick_.bind(this)); + + // different handlers, depending on the Environment + if (pskl.utils.Environment.detectNodeWebkit()) { + this.openPiskelInputButton.click(this.openPiskelDesktop_.bind(this)); + } else { + this.hiddenOpenPiskelInput.change(this.onOpenPiskelChange_.bind(this)); + this.openPiskelInputButton.click(this.onOpenPiskelClick_.bind(this)); + } this.prevSessionContainer = $('.previous-session'); this.previousSessionTemplate_ = pskl.utils.Template.get("previous-session-info-template"); @@ -45,6 +50,22 @@ } }; + ns.ImportController.prototype.openPiskelDesktop_ = function (evt) { + this.closeDrawer_(); + pskl.utils.FileUtilsDesktop.chooseFileDialog(function(filename){ + var chosenFilename = filename; + pskl.utils.FileUtilsDesktop.readFile(chosenFilename, function(content){ + pskl.utils.PiskelFileUtils.decodePiskelFile(content, function (piskel, descriptor, fps) { + piskel.setDescriptor(descriptor); + // store save path so we can resave without opening the save dialog + piskel.savePath = chosenFilename; + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(fps); + }); + }); + }); + }; + ns.ImportController.prototype.onOpenPiskelClick_ = function (evt) { this.hiddenOpenPiskelInput.click(); }; diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 1d5d2d4..d32b82d 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -167,14 +167,14 @@ var serialized = pskl.app.piskelController.serialize(); var savePath = pskl.app.piskelController.getSavePath(); // if we already have a filename, just save the file (using nodejs 'fs' api) - if (savePath !== null) { - pskl.utils.FileUtils.desktopSaveToFile(serialized, savePath, function () { + if (savePath) { + pskl.utils.FileUtilsDesktop.saveToFile(serialized, savePath, function () { this.onSaveSuccess_(); this.afterSaving_(); }.bind(this)); } else { // "save as" = open a save dialog, and store the returned save path - pskl.utils.FileUtils.desktopSaveAs(serialized, null, function (selectedSavePath) { + pskl.utils.FileUtilsDesktop.saveAs(serialized, null, function (selectedSavePath) { this.onSaveSuccess_(); this.afterSaving_(); pskl.app.piskelController.setSavePath(selectedSavePath); diff --git a/src/js/service/HistoryService.js b/src/js/service/HistoryService.js index 052056f..7528195 100644 --- a/src/js/service/HistoryService.js +++ b/src/js/service/HistoryService.js @@ -127,6 +127,8 @@ ns.HistoryService.prototype.onPiskelLoaded_ = function (index, snapshotIndex, piskel) { var originalSize = this.getPiskelSize_(); piskel.setDescriptor(this.piskelController.piskel.getDescriptor()); + // propagate save path to the new piskel instance + piskel.savePath = this.piskelController.piskel.savePath; this.piskelController.setPiskel(piskel); for (var i = snapshotIndex + 1 ; i <= index ; i++) { diff --git a/src/js/utils/FileUtils.js b/src/js/utils/FileUtils.js index 14230ce..4d8a02e 100644 --- a/src/js/utils/FileUtils.js +++ b/src/js/utils/FileUtils.js @@ -37,47 +37,6 @@ downloadLink.removeEventListener('click', stopPropagation); document.body.removeChild(downloadLink); } - }, - - /** - * - * @param content - * @param defaultFileName - * @param callback - */ - desktopSaveAs: function (content, defaultFileName, callback) { - // NodeWebkit has no js api for opening the save dialog. - // Instead, it adds two new attributes to the anchor tag: nwdirectory and nwsaveas - // (see: https://github.com/nwjs/nw.js/wiki/File-dialogs ) - defaultFileName = defaultFileName || ""; - var tagString = ''; - var $chooser = $(tagString); - $chooser.change(function(e) { - var filename = $(this).val(); - pskl.utils.FileUtils.desktopSaveToFile(content, filename, function(){ - callback(filename); - }); - }); - $chooser.trigger('click'); - }, - - /** - * Save data directly to disk, without showing a save dialog - * Requires Node-Webkit environment for file system access - * @param content - data to be saved - * @param {string} filename - fill path to the file - * @callback callback - */ - desktopSaveToFile: function(content, filename, callback) { - var fs = require('fs'); - //var arrayBuffer = ns.FileUtils.toArrayBuffer(content); - fs.writeFile(filename, content, function(err){ - if (err) { - //throw err; - console.log('destopSavetoFile() - error saving file', filename, 'Error:', err); - } - callback(); - }); } }; diff --git a/src/js/utils/FileUtilsDesktop.js b/src/js/utils/FileUtilsDesktop.js new file mode 100644 index 0000000..298ca37 --- /dev/null +++ b/src/js/utils/FileUtilsDesktop.js @@ -0,0 +1,71 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + var stopPropagation = function (e) { + e.stopPropagation(); + }; + + ns.FileUtilsDesktop = { + + chooseFileDialog: function (callback) { + var tagString = ''; + var $chooser = $(tagString); + $chooser.change(function(e) { + var filename = $(this).val(); + callback(filename); + }); + $chooser.trigger('click'); + }, + + /** + * + * @param content + * @param defaultFileName + * @param callback + */ + saveAs: function (content, defaultFileName, callback) { + // NodeWebkit has no js api for opening the save dialog. + // Instead, it adds two new attributes to the anchor tag: nwdirectory and nwsaveas + // (see: https://github.com/nwjs/nw.js/wiki/File-dialogs ) + defaultFileName = defaultFileName || ""; + var tagString = ''; + var $chooser = $(tagString); + $chooser.change(function(e) { + var filename = $(this).val(); + pskl.utils.FileUtilsDesktop.saveToFile(content, filename, function(){ + callback(filename); + }); + }); + $chooser.trigger('click'); + }, + + /** + * Save data directly to disk, without showing a save dialog + * Requires Node-Webkit environment for file system access + * @param content - data to be saved + * @param {string} filename - fill path to the file + * @callback callback + */ + saveToFile : function(content, filename, callback) { + var fs = require('fs'); + fs.writeFile(filename, content, function(err){ + if (err) { + //throw err; + console.log('FileUtilsDesktop::savetoFile() - error saving file:', filename, 'Error:', err); + } + callback(); + }); + }, + + readFile : function(filename, callback) { + var fs = require('fs'); + // NOTE: currently loading everything as utf8, which may not be desirable in future + fs.readFile(filename, 'utf8', function(err, data){ + if (err) { + console.log('FileUtilsDesktop::readFile() - error reading file:', filename, 'Error:', err); + } + callback(data); + }); + } + }; +})(); diff --git a/src/js/utils/PiskelFileUtils.js b/src/js/utils/PiskelFileUtils.js index 59d64ae..c236583 100644 --- a/src/js/utils/PiskelFileUtils.js +++ b/src/js/utils/PiskelFileUtils.js @@ -13,13 +13,18 @@ loadFromFile : function (file, onSuccess, onError) { pskl.utils.FileUtils.readFile(file, function (content) { var rawPiskel = pskl.utils.Base64.toText(content); - var serializedPiskel = JSON.parse(rawPiskel); - var fps = serializedPiskel.piskel.fps; - var descriptor = new pskl.model.piskel.Descriptor(serializedPiskel.piskel.name, serializedPiskel.piskel.description, true); - pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) { - onSuccess(piskel, descriptor, fps); - }); + ns.PiskelFileUtils.decodePiskelFile(rawPiskel, onSuccess, onError); + }); + }, + + decodePiskelFile : function (rawPiskel, onSuccess, onError) { + var serializedPiskel = JSON.parse(rawPiskel); + var fps = serializedPiskel.piskel.fps; + var descriptor = new pskl.model.piskel.Descriptor(serializedPiskel.piskel.name, serializedPiskel.piskel.description, true); + pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) { + onSuccess(piskel, descriptor, fps); }); } + }; })(); \ No newline at end of file diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 9e697cf..4fed59c 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -21,6 +21,7 @@ "js/utils/Environment.js", "js/utils/Math.js", "js/utils/FileUtils.js", + "js/utils/FileUtilsDesktop.js", "js/utils/FrameTransform.js", "js/utils/FrameUtils.js", "js/utils/LayerUtils.js", From 6a6f75b3ce5c83f037a1a6013fb6e794c94bb8f0 Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Thu, 19 Mar 2015 23:46:53 +1300 Subject: [PATCH 05/11] Moved desktop lO logic to new DesktopStorageService class. Bound keypresses for ctrl-o, ctrl-s, and ctrl-shift-s. Savepath is now also propagated on resize operation. SaveFile can optionally guarantee that a supplied file extension will be present on output file. --- src/js/app.js | 3 + .../controller/settings/ImportController.js | 13 +--- .../controller/settings/ResizeController.js | 3 +- src/js/controller/settings/SaveController.js | 30 +------- src/js/service/DesktopStorageService.js | 70 +++++++++++++++++++ src/js/utils/FileUtilsDesktop.js | 18 ++++- src/piskel-script-list.js | 1 + 7 files changed, 94 insertions(+), 44 deletions(-) create mode 100644 src/js/service/DesktopStorageService.js diff --git a/src/js/app.js b/src/js/app.js index 6fa651f..101fc4e 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -99,6 +99,9 @@ this.localStorageService = new pskl.service.LocalStorageService(this.piskelController); this.localStorageService.init(); + this.desktopStorageService = new pskl.service.DesktopStorageService(this.piskelController); + this.desktopStorageService.init(); + this.imageUploadService = new pskl.service.ImageUploadService(); this.imageUploadService.init(); diff --git a/src/js/controller/settings/ImportController.js b/src/js/controller/settings/ImportController.js index d3aaae8..0dc8f62 100644 --- a/src/js/controller/settings/ImportController.js +++ b/src/js/controller/settings/ImportController.js @@ -52,18 +52,7 @@ ns.ImportController.prototype.openPiskelDesktop_ = function (evt) { this.closeDrawer_(); - pskl.utils.FileUtilsDesktop.chooseFileDialog(function(filename){ - var chosenFilename = filename; - pskl.utils.FileUtilsDesktop.readFile(chosenFilename, function(content){ - pskl.utils.PiskelFileUtils.decodePiskelFile(content, function (piskel, descriptor, fps) { - piskel.setDescriptor(descriptor); - // store save path so we can resave without opening the save dialog - piskel.savePath = chosenFilename; - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(fps); - }); - }); - }); + pskl.app.desktopStorageService.openPiskel(); }; ns.ImportController.prototype.onOpenPiskelClick_ = function (evt) { diff --git a/src/js/controller/settings/ResizeController.js b/src/js/controller/settings/ResizeController.js index b10f244..1a09d4f 100644 --- a/src/js/controller/settings/ResizeController.js +++ b/src/js/controller/settings/ResizeController.js @@ -31,7 +31,8 @@ var resizedLayers = this.piskelController.getLayers().map(this.resizeLayer_.bind(this)); var piskel = pskl.model.Piskel.fromLayers(resizedLayers, this.piskelController.getPiskel().getDescriptor()); - + // propagate savepath to new Piskel + piskel.savePath = pskl.app.piskelController.getSavePath(); pskl.app.piskelController.setPiskel(piskel, true); $.publish(Events.CLOSE_SETTINGS_DRAWER); }; diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index d32b82d..2bbe59a 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -25,24 +25,14 @@ this.isPublicCheckbox = $('input[name=save-public-checkbox]'); this.isPublicCheckbox.prop('checked', descriptor.isPublic); - - // prevent default behaviour of ctrl-s is there a way to do this - // with the pre-existing system? Should I use alt-s instead? - $(document).bind('keydown', 'ctrl+s', function(e) { - e.preventDefault(); - return false; - }); + this.saveFileButton = $('#save-file-button'); //Environment dependent configuration: if (pskl.utils.Environment.detectNodeWebkit()) { // running in Node-Webkit... - pskl.app.shortcutService.addShortcut('ctrl+s', this.saveFileDesktop_.bind(this)); - this.saveFileButton = $('#save-file-button'); this.saveFileButton.click(this.saveFileDesktop_.bind(this)); } else { // running in browser... - pskl.app.shortcutService.addShortcut('ctrl+s', this.saveFileBrowser_.bind(this)); - this.saveFileButton = $('#save-file-button'); this.saveFileButton.click(this.saveFileBrowser_.bind(this)); } @@ -163,23 +153,7 @@ }; ns.SaveController.prototype.saveFileDesktop_ = function () { - this.beforeSaving_(); - var serialized = pskl.app.piskelController.serialize(); - var savePath = pskl.app.piskelController.getSavePath(); - // if we already have a filename, just save the file (using nodejs 'fs' api) - if (savePath) { - pskl.utils.FileUtilsDesktop.saveToFile(serialized, savePath, function () { - this.onSaveSuccess_(); - this.afterSaving_(); - }.bind(this)); - } else { - // "save as" = open a save dialog, and store the returned save path - pskl.utils.FileUtilsDesktop.saveAs(serialized, null, function (selectedSavePath) { - this.onSaveSuccess_(); - this.afterSaving_(); - pskl.app.piskelController.setSavePath(selectedSavePath); - }.bind(this)); - } + pskl.app.desktopStorageService.saveFile(); } ns.SaveController.prototype.getName = function () { diff --git a/src/js/service/DesktopStorageService.js b/src/js/service/DesktopStorageService.js new file mode 100644 index 0000000..c26a1e7 --- /dev/null +++ b/src/js/service/DesktopStorageService.js @@ -0,0 +1,70 @@ +(function () { + var ns = $.namespace('pskl.service'); + + ns.DesktopStorageService = function(piskelController) { + this.piskelController = piskelController || pskl.app.piskelController; + this.hideNotificationTimeoutID = 0; + }; + + ns.DesktopStorageService.prototype.init = function (){ + // activate keyboard shortcuts if this is the desktop version + if (pskl.utils.Environment.detectNodeWebkit()) { + pskl.app.shortcutService.addShortcut('ctrl+o', this.openPiskel.bind(this)); + pskl.app.shortcutService.addShortcut('ctrl+s', this.save.bind(this)); + pskl.app.shortcutService.addShortcut('ctrl+shift+s', this.savePiskelAs.bind(this)); + } + }; + + ns.DesktopStorageService.prototype.save = function () { + var savePath = this.piskelController.getSavePath(); + // if we already have a filename, just save the file (using nodejs 'fs' api) + if (savePath) { + this.savePiskel(savePath); + } else { + this.savePiskelAs(savePath); + } + }; + + ns.DesktopStorageService.prototype.savePiskel = function (savePath) { + var serialized = this.piskelController.serialize(); + pskl.utils.FileUtilsDesktop.saveToFile(serialized, savePath, function () { + this.onSaveSuccess_(); + }.bind(this)); + }; + + ns.DesktopStorageService.prototype.openPiskel = function () { + pskl.utils.FileUtilsDesktop.chooseFileDialog(function(filename){ + var chosenFilename = filename; + pskl.utils.FileUtilsDesktop.readFile(chosenFilename, function(content){ + pskl.utils.PiskelFileUtils.decodePiskelFile(content, function (piskel, descriptor, fps) { + piskel.setDescriptor(descriptor); + // store save path so we can re-save without opening the save dialog + piskel.savePath = chosenFilename; + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(fps); + }); + }); + }); + }; + + ns.DesktopStorageService.prototype.savePiskelAs = function (savePath) { + var serialized = this.piskelController.serialize(); + // TODO: if there is already a file path, use it for the dialog's + // working directory and filename + pskl.utils.FileUtilsDesktop.saveAs(serialized, null, 'piskel', function (selectedSavePath) { + this.onSaveSuccess_(); + this.piskelController.setSavePath(selectedSavePath); + }.bind(this)); + } + + ns.DesktopStorageService.prototype.onSaveSuccess_ = function () { + $.publish(Events.CLOSE_SETTINGS_DRAWER); + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]); + $.publish(Events.PISKEL_SAVED); + // clear the old time out, if any. + window.clearTimeout(this.hideNotificationTimeoutID); + this.hideNotificationTimeoutID = + window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000); + }; + +})(); diff --git a/src/js/utils/FileUtilsDesktop.js b/src/js/utils/FileUtilsDesktop.js index 298ca37..2f596e7 100644 --- a/src/js/utils/FileUtilsDesktop.js +++ b/src/js/utils/FileUtilsDesktop.js @@ -20,10 +20,13 @@ /** * * @param content - * @param defaultFileName + * @param defaultFileName - file name to pre-populate the dialog + * @param extension - if supplied, the selected extension will guaranteed to be on the filename - + * NOTE: there is a possible danger here... If the extension is added to a fileName, but there + * is already another file of the same name *with* the extension, it will get overwritten. * @param callback */ - saveAs: function (content, defaultFileName, callback) { + saveAs: function (content, defaultFileName, extension, callback) { // NodeWebkit has no js api for opening the save dialog. // Instead, it adds two new attributes to the anchor tag: nwdirectory and nwsaveas // (see: https://github.com/nwjs/nw.js/wiki/File-dialogs ) @@ -31,7 +34,16 @@ var tagString = ''; var $chooser = $(tagString); $chooser.change(function(e) { - var filename = $(this).val(); + var filename = $(this).val(); + if (typeof extension == 'string') { + if (extension[0] !== '.') { + extension = "." + extension; + } + var hasExtension = (filename.substring(filename.length - extension.length) === extension); + if (!hasExtension) { + filename += extension; + } + } pskl.utils.FileUtilsDesktop.saveToFile(content, filename, function(){ callback(filename); }); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 4fed59c..cb069e6 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -127,6 +127,7 @@ "js/service/LocalStorageService.js", "js/service/GithubStorageService.js", "js/service/AppEngineStorageService.js", + "js/service/DesktopStorageService.js", "js/service/BackupService.js", "js/service/BeforeUnloadService.js", "js/service/HistoryService.js", From c9b581f6dbc920636256fe69b7dff0975c43979b Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Sun, 22 Mar 2015 00:39:23 +1300 Subject: [PATCH 06/11] When running in Node-Webkit, hold onto the full savePath when opening.piskel files via the gui or drag and drop. --- src/js/service/DesktopStorageService.js | 6 +++--- src/js/service/HistoryService.js | 1 - src/js/utils/PiskelFileUtils.js | 13 ++++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/js/service/DesktopStorageService.js b/src/js/service/DesktopStorageService.js index c26a1e7..940b7bc 100644 --- a/src/js/service/DesktopStorageService.js +++ b/src/js/service/DesktopStorageService.js @@ -34,12 +34,12 @@ ns.DesktopStorageService.prototype.openPiskel = function () { pskl.utils.FileUtilsDesktop.chooseFileDialog(function(filename){ - var chosenFilename = filename; - pskl.utils.FileUtilsDesktop.readFile(chosenFilename, function(content){ + var savePath = filename; + pskl.utils.FileUtilsDesktop.readFile(savePath, function(content){ pskl.utils.PiskelFileUtils.decodePiskelFile(content, function (piskel, descriptor, fps) { piskel.setDescriptor(descriptor); // store save path so we can re-save without opening the save dialog - piskel.savePath = chosenFilename; + piskel.savePath = savePath; pskl.app.piskelController.setPiskel(piskel); pskl.app.animationController.setFPS(fps); }); diff --git a/src/js/service/HistoryService.js b/src/js/service/HistoryService.js index 7528195..94d11bd 100644 --- a/src/js/service/HistoryService.js +++ b/src/js/service/HistoryService.js @@ -8,7 +8,6 @@ this.stateQueue = []; this.currentIndex = -1; - this.lastLoadState = -1; this.saveNextAsSnapshot = false; diff --git a/src/js/utils/PiskelFileUtils.js b/src/js/utils/PiskelFileUtils.js index c236583..009f135 100644 --- a/src/js/utils/PiskelFileUtils.js +++ b/src/js/utils/PiskelFileUtils.js @@ -13,7 +13,18 @@ loadFromFile : function (file, onSuccess, onError) { pskl.utils.FileUtils.readFile(file, function (content) { var rawPiskel = pskl.utils.Base64.toText(content); - ns.PiskelFileUtils.decodePiskelFile(rawPiskel, onSuccess, onError); + ns.PiskelFileUtils.decodePiskelFile( + rawPiskel, + function (piskel, descriptor, fps) { + // if using Node-Webkit, store the savePath on load + // Note: the 'path' property is unique to Node-Webkit, and holds the full path + if (pskl.utils.Environment.detectNodeWebkit()) { + piskel.savePath = file.path; + } + onSuccess(piskel, descriptor, fps); + }, + onError + ); }); }, From 93deb1c2c57a5cc5addf41f2aa0c445841b3e8fb Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Tue, 24 Mar 2015 22:33:14 +1300 Subject: [PATCH 07/11] Just adding semicolons to make the linter happy --- src/js/controller/piskel/PublicPiskelController.js | 4 ++-- src/js/controller/settings/SaveController.js | 4 ++-- src/js/rendering/CompositeRenderer.js | 1 - src/js/service/DesktopStorageService.js | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/js/controller/piskel/PublicPiskelController.js b/src/js/controller/piskel/PublicPiskelController.js index 4d6c4fb..e31915f 100644 --- a/src/js/controller/piskel/PublicPiskelController.js +++ b/src/js/controller/piskel/PublicPiskelController.js @@ -138,11 +138,11 @@ ns.PublicPiskelController.prototype.setSavePath = function (savePath) { this.piskelController.piskel.savePath = savePath; - } + }; ns.PublicPiskelController.prototype.getSavePath = function () { return this.piskelController.piskel.savePath; - } + }; ns.PublicPiskelController.prototype.raiseSaveStateEvent_ = function (fn, args) { $.publish(Events.PISKEL_SAVE_STATE, { diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 2bbe59a..5b801f5 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -153,8 +153,8 @@ }; ns.SaveController.prototype.saveFileDesktop_ = function () { - pskl.app.desktopStorageService.saveFile(); - } + pskl.app.desktopStorageService.save(); + }; ns.SaveController.prototype.getName = function () { return this.nameInput.val(); diff --git a/src/js/rendering/CompositeRenderer.js b/src/js/rendering/CompositeRenderer.js index 453a763..ff8a2bd 100644 --- a/src/js/rendering/CompositeRenderer.js +++ b/src/js/rendering/CompositeRenderer.js @@ -48,7 +48,6 @@ return this.getSampleRenderer_().getOffset(); }; - ns.CompositeRenderer.prototype.setGridWidth = function (b) { this.renderers.forEach(function (renderer) { renderer.setGridWidth(b); diff --git a/src/js/service/DesktopStorageService.js b/src/js/service/DesktopStorageService.js index 940b7bc..211ebda 100644 --- a/src/js/service/DesktopStorageService.js +++ b/src/js/service/DesktopStorageService.js @@ -55,7 +55,7 @@ this.onSaveSuccess_(); this.piskelController.setSavePath(selectedSavePath); }.bind(this)); - } + }; ns.DesktopStorageService.prototype.onSaveSuccess_ = function () { $.publish(Events.CLOSE_SETTINGS_DRAWER); From eb4941417c54437971ef8da063b5764b9f38c432 Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Tue, 24 Mar 2015 22:58:42 +1300 Subject: [PATCH 08/11] suppressing build errors when referring to "require" and "process" --- src/js/utils/Environment.js | 3 ++- src/js/utils/FileUtilsDesktop.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/utils/Environment.js b/src/js/utils/Environment.js index 58b973a..861bfe8 100644 --- a/src/js/utils/Environment.js +++ b/src/js/utils/Environment.js @@ -9,9 +9,10 @@ ns.Environment = { detectNodeWebkit : function () { - var isNode = (typeof process !== "undefined" && typeof require !== "undefined"); + var isNode = (typeof window["process"] !== "undefined" && typeof window["require"] !== "undefined"); var isNodeWebkit = false; if (isNode) { + var require = window["require"]; try { isNodeWebkit = (typeof require('nw.gui') !== "undefined"); } catch (e) { diff --git a/src/js/utils/FileUtilsDesktop.js b/src/js/utils/FileUtilsDesktop.js index 2f596e7..dc08529 100644 --- a/src/js/utils/FileUtilsDesktop.js +++ b/src/js/utils/FileUtilsDesktop.js @@ -59,6 +59,7 @@ * @callback callback */ saveToFile : function(content, filename, callback) { + var require = window["require"]; // suppress build error var fs = require('fs'); fs.writeFile(filename, content, function(err){ if (err) { @@ -70,6 +71,7 @@ }, readFile : function(filename, callback) { + var require = window["require"]; // suppress build error var fs = require('fs'); // NOTE: currently loading everything as utf8, which may not be desirable in future fs.readFile(filename, 'utf8', function(err, data){ From 932974d7445f62d6717081b0ac8742135551313c Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Tue, 24 Mar 2015 23:09:44 +1300 Subject: [PATCH 09/11] Undid changes to suppress Errors, since grunt test did not like it that way either. (Bad style.) --- src/js/utils/Environment.js | 3 +-- src/js/utils/FileUtilsDesktop.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/js/utils/Environment.js b/src/js/utils/Environment.js index 861bfe8..58b973a 100644 --- a/src/js/utils/Environment.js +++ b/src/js/utils/Environment.js @@ -9,10 +9,9 @@ ns.Environment = { detectNodeWebkit : function () { - var isNode = (typeof window["process"] !== "undefined" && typeof window["require"] !== "undefined"); + var isNode = (typeof process !== "undefined" && typeof require !== "undefined"); var isNodeWebkit = false; if (isNode) { - var require = window["require"]; try { isNodeWebkit = (typeof require('nw.gui') !== "undefined"); } catch (e) { diff --git a/src/js/utils/FileUtilsDesktop.js b/src/js/utils/FileUtilsDesktop.js index dc08529..2f596e7 100644 --- a/src/js/utils/FileUtilsDesktop.js +++ b/src/js/utils/FileUtilsDesktop.js @@ -59,7 +59,6 @@ * @callback callback */ saveToFile : function(content, filename, callback) { - var require = window["require"]; // suppress build error var fs = require('fs'); fs.writeFile(filename, content, function(err){ if (err) { @@ -71,7 +70,6 @@ }, readFile : function(filename, callback) { - var require = window["require"]; // suppress build error var fs = require('fs'); // NOTE: currently loading everything as utf8, which may not be desirable in future fs.readFile(filename, 'utf8', function(err, data){ From 0ec3787fc4c8fbe94b170c4ad931e80329153d08 Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Wed, 25 Mar 2015 18:36:55 +1300 Subject: [PATCH 10/11] change require() to window.require() so compiler does not report errors. --- src/js/utils/Environment.js | 4 ++-- src/js/utils/FileUtilsDesktop.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/utils/Environment.js b/src/js/utils/Environment.js index 58b973a..a6b6cfb 100644 --- a/src/js/utils/Environment.js +++ b/src/js/utils/Environment.js @@ -9,11 +9,11 @@ ns.Environment = { detectNodeWebkit : function () { - var isNode = (typeof process !== "undefined" && typeof require !== "undefined"); + var isNode = (typeof window.process !== "undefined" && typeof window.require !== "undefined"); var isNodeWebkit = false; if (isNode) { try { - isNodeWebkit = (typeof require('nw.gui') !== "undefined"); + isNodeWebkit = (typeof window.require('nw.gui') !== "undefined"); } catch (e) { isNodeWebkit = false; } diff --git a/src/js/utils/FileUtilsDesktop.js b/src/js/utils/FileUtilsDesktop.js index 2f596e7..6a724e1 100644 --- a/src/js/utils/FileUtilsDesktop.js +++ b/src/js/utils/FileUtilsDesktop.js @@ -59,7 +59,7 @@ * @callback callback */ saveToFile : function(content, filename, callback) { - var fs = require('fs'); + var fs = window.require('fs'); fs.writeFile(filename, content, function(err){ if (err) { //throw err; @@ -70,7 +70,7 @@ }, readFile : function(filename, callback) { - var fs = require('fs'); + var fs = window.require('fs'); // NOTE: currently loading everything as utf8, which may not be desirable in future fs.readFile(filename, 'utf8', function(err, data){ if (err) { From f114676db7d9e5a175e00d055a5f216962cfd38d Mon Sep 17 00:00:00 2001 From: Lee Grey Date: Thu, 26 Mar 2015 20:32:27 +1300 Subject: [PATCH 11/11] Include save file path in "Successfully Saved" popup. Show full save path under the save button. Hide the "save to browser" portion of the save panel when running in desktop mode. --- src/css/settings.css | 9 ++++++++- src/js/controller/settings/SaveController.js | 17 ++++++++++++++--- src/js/service/DesktopStorageService.js | 5 +++-- src/templates/settings/save.html | 18 ++++++++++++------ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/css/settings.css b/src/css/settings.css index e62f72c..ad56334 100644 --- a/src/css/settings.css +++ b/src/css/settings.css @@ -208,4 +208,11 @@ font-weight: bold; color: white; font-style: normal; -} \ No newline at end of file +} + +.save-desktop-file-name { + word-wrap: break-word; + font-weight: bold; + color: white; + font-style: normal; +} diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 5b801f5..c66616b 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -31,6 +31,8 @@ if (pskl.utils.Environment.detectNodeWebkit()) { // running in Node-Webkit... this.saveFileButton.click(this.saveFileDesktop_.bind(this)); + // hide the "save in browser" part of the gui + $('#save-in-browser').css('display', 'none'); } else { // running in browser... this.saveFileButton.click(this.saveFileBrowser_.bind(this)); @@ -61,9 +63,18 @@ }; ns.SaveController.prototype.updateLocalStatusFilename_ = function () { - this.saveFileStatus.html(pskl.utils.Template.getAndReplace('save-file-status-template', { - name : this.getLocalFilename_() - })); + if (pskl.utils.Environment.detectNodeWebkit()) { + var fileName = this.piskelController.getSavePath(); + if (fileName !== null) { + this.saveFileStatus.html(pskl.utils.Template.getAndReplace('save-file-status-desktop-template', { + name : this.piskelController.getSavePath() + })); + } + } else { + this.saveFileStatus.html(pskl.utils.Template.getAndReplace('save-file-status-template', { + name : this.getLocalFilename_() + })); + } }; ns.SaveController.prototype.getLocalFilename_ = function () { diff --git a/src/js/service/DesktopStorageService.js b/src/js/service/DesktopStorageService.js index 211ebda..7aef36a 100644 --- a/src/js/service/DesktopStorageService.js +++ b/src/js/service/DesktopStorageService.js @@ -58,13 +58,14 @@ }; ns.DesktopStorageService.prototype.onSaveSuccess_ = function () { + var savePath = this.piskelController.getSavePath(); $.publish(Events.CLOSE_SETTINGS_DRAWER); - $.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]); + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved: " + savePath}]); $.publish(Events.PISKEL_SAVED); // clear the old time out, if any. window.clearTimeout(this.hideNotificationTimeoutID); this.hideNotificationTimeoutID = - window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000); + window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 3000); }; })(); diff --git a/src/templates/settings/save.html b/src/templates/settings/save.html index 65ac903..75f3e56 100644 --- a/src/templates/settings/save.html +++ b/src/templates/settings/save.html @@ -20,10 +20,12 @@
-
Save offline in Browser
-
- -
Your piskel will be saved locally and will only be accessible from this browser.
+
+
Save offline in Browser
+
+ +
Your piskel will be saved locally and will only be accessible from this browser.
+
Save offline as File
@@ -41,6 +43,10 @@ -
\ No newline at end of file + + +