diff --git a/src/js/Events.js b/src/js/Events.js index 8b71a254..cbb967ee 100644 --- a/src/js/Events.js +++ b/src/js/Events.js @@ -41,6 +41,8 @@ var Events = { HISTORY_STATE_LOADED: 'HISTORY_STATE_LOADED', PISKEL_SAVED: 'PISKEL_SAVED', + BEFORE_SAVING_PISKEL: 'BEFORE_SAVING_PISKEL', + AFTER_SAVING_PISKEL: 'AFTER_SAVING_PISKEL', FRAME_SIZE_CHANGED : 'FRAME_SIZE_CHANGED', diff --git a/src/js/app.js b/src/js/app.js index b3c377cd..3150ec53 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -109,15 +109,21 @@ this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController(); this.canvasBackgroundController.init(); - this.galleryStorageService = new pskl.service.storage.GalleryStorageService(this.piskelController); - this.galleryStorageService.init(); - this.localStorageService = new pskl.service.storage.LocalStorageService(this.piskelController); this.localStorageService.init(); + this.fileDownloadStorageService = new pskl.service.storage.FileDownloadStorageService(this.piskelController); + this.fileDownloadStorageService.init(); + this.desktopStorageService = new pskl.service.storage.DesktopStorageService(this.piskelController); this.desktopStorageService.init(); + this.galleryStorageService = new pskl.service.storage.GalleryStorageService(this.piskelController); + this.galleryStorageService.init(); + + this.storageService = new pskl.service.storage.StorageService(this.piskelController); + this.storageService.init(); + this.imageUploadService = new pskl.service.ImageUploadService(); this.imageUploadService.init(); diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 89493858..974c70b7 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -11,52 +11,61 @@ * @public */ ns.SaveController.prototype.init = function () { - // timestamp used to generate unique name when saving as .piskel - this.timestamp = new Date(); + var saveForm = document.querySelector('.save-form'); + this.getPartials_().forEach(function (partial) { + pskl.utils.Template.insert(saveForm, 'beforeend', partial); + }); - this.insertPartials_(); - - // Only available in app-engine mode this.piskelName = document.querySelector('.piskel-name'); - this.saveOnlineStatus = document.querySelector('#save-online-status'); this.saveFileStatus = document.querySelector('#save-file-status'); this.descriptionInput = document.querySelector('#save-description'); this.nameInput = document.querySelector('#save-name'); - this.saveOnlineButton = document.querySelector('#save-online-button'); - this.isPublicCheckbox = document.querySelector('input[name=save-public-checkbox]'); + + this.saveFileButton = document.querySelector('#save-file-button'); + this.saveBrowserButton = document.querySelector('#save-browser-button'); var descriptor = this.piskelController.getPiskel().getDescriptor(); this.descriptionInput.value = descriptor.description; this.nameInput.value = descriptor.name; - if (descriptor.isPublic) { - this.isPublicCheckbox.setAttribute('checked', true); + + this.addEventListener(this.saveFileButton, 'click', this.saveFile_); + this.addEventListener(this.saveBrowserButton, 'click', this.saveBrowser_); + this.addEventListener('form[name=save-form]', 'submit', this.onSaveFormSubmit_); + + $.subscribe(Events.BEFORE_SAVING_PISKEL, this.disableSaveButtons_.bind(this)); + $.subscribe(Events.AFTER_SAVING_PISKEL, this.enableSaveButtons_.bind(this)); + + if (pskl.app.isLoggedIn()) { + this.authenticatedUserInit_(); } if (pskl.utils.Environment.detectNodeWebkit()) { - this.addEventListener('#save-as-button', 'click', this.saveAs_); - } - - this.addEventListener('#save-file-button', 'click', this.saveFile_); - this.addEventListener('#save-browser-button', 'click', this.saveLocal_); - this.addEventListener(this.saveOnlineButton, 'click', this.saveOnline_); - this.addEventListener('form[name=save-form]', 'submit', this.onSaveFormSubmit_); - - if (pskl.app.isLoggedIn()) { - pskl.utils.Template.insert(this.saveOnlineStatus, 'beforeend', 'save-online-status-partial'); - } else { - pskl.utils.Template.insert(this.saveOnlineStatus, 'beforeend', 'save-please-login-partial'); - var container = document.querySelector('.setting-save-section'); - container.classList.add('anonymous'); + this.desktopApplicationInit_(); } }; - ns.SaveController.prototype.insertPartials_ = function () { + ns.SaveController.prototype.authenticatedUserInit_ = function () { + var descriptor = this.piskelController.getPiskel().getDescriptor(); + this.isPublicCheckbox = document.querySelector('input[name=save-public-checkbox]'); + if (descriptor.isPublic) { + this.isPublicCheckbox.setAttribute('checked', true); + } + this.saveOnlineButton = document.querySelector('#save-online-button'); + this.addEventListener(this.saveOnlineButton, 'click', this.saveOnline_); + }; + + ns.SaveController.prototype.desktopApplicationInit_ = function () { + this.saveAsNewButton = document.querySelector('#save-as-button'); + this.addEventListener('#save-as-button', 'click', this.saveAs_); + }; + + ns.SaveController.prototype.getPartials_ = function () { var partials = []; if (pskl.utils.Environment.detectNodeWebkit()) { partials = [ 'save-file-nw-partial', 'save-localstorage-partial', - 'save-online-partial' + 'save-online-unavailable-partial' ]; } else if (pskl.app.isLoggedIn()) { partials = [ @@ -68,21 +77,11 @@ partials = [ 'save-file-partial', 'save-localstorage-partial', - 'save-online-partial' + 'save-online-unavailable-partial' ]; } - var container = document.querySelector('.save-form'); - partials.forEach(function (partial) { - pskl.utils.Template.insert(container, 'beforeend', partial); - }); - - }; - - ns.SaveController.prototype.getLocalFilename_ = function () { - var piskelName = this.getName(); - var timestamp = pskl.utils.DateUtils.format(this.timestamp, '{{Y}}{{M}}{{D}}-{{H}}{{m}}{{s}}'); - return piskelName + '-' + timestamp + '.piskel'; + return partials; }; ns.SaveController.prototype.onSaveFormSubmit_ = function (evt) { @@ -94,127 +93,105 @@ } else { this.saveLocal_(); } - }; ns.SaveController.prototype.saveOnline_ = function () { - var name = this.getName(); - - if (!name) { - name = window.prompt('Please specify a name', 'New piskel'); - } - - if (name) { - var description = this.getDescription(); - var isPublic = this.isPublic_(); - - var descriptor = new pskl.model.piskel.Descriptor(name, description, isPublic); - this.piskelController.getPiskel().setDescriptor(descriptor); - - this.beforeSaving_(); - - this.saveOnlineButton.setAttribute('disabled', true); - this.saveOnlineStatus.innerHTML = 'Saving ...'; - - pskl.app.storageService.store({ - success : this.onSaveSuccess_.bind(this), - error : this.onSaveError_.bind(this), - after : this.afterOnlineSaving_.bind(this) - }); - } + this.beforeSaving_(); + var piskel = this.piskelController.getPiskel(); + pskl.app.storageService.saveToGallery(piskel).then(this.onSaveSuccess_); }; - ns.SaveController.prototype.saveLocal_ = function () { - var localStorageService = pskl.app.localStorageService; - var isOk = true; - var name = this.getName(); - var description = this.getDescription(); - if (localStorageService.getPiskel(name)) { - isOk = window.confirm('There is already a piskel saved as ' + name + '. Override ?'); - } - - if (isOk) { - this.beforeSaving_(); - localStorageService.save(name, description, pskl.app.piskelController.serialize()); - window.setTimeout(function () { - this.onSaveSuccess_(); - this.afterSaving_(); - }.bind(this), 500); - } + ns.SaveController.prototype.saveBrowser_ = function () { + this.beforeSaving_(); + var piskel = this.piskelController.getPiskel(); + pskl.app.storageService.saveToLocalStorage(piskel).then(this.onSaveSuccess_); }; ns.SaveController.prototype.saveFile_ = function () { - // detect if this is running in NodeWebkit if (pskl.utils.Environment.detectNodeWebkit()) { - pskl.app.desktopStorageService.save(); + this.saveFileDesktop_(); } else { this.saveFileBrowser_(); } }; - ns.SaveController.prototype.saveAs_ = function () { - pskl.app.desktopStorageService.savePiskelAs(); - }; - ns.SaveController.prototype.saveFileBrowser_ = function () { this.beforeSaving_(); - pskl.utils.BlobUtils.stringToBlob(pskl.app.piskelController.serialize(), function(blob) { - pskl.utils.FileUtils.downloadAsFile(blob, this.getLocalFilename_()); - this.onSaveSuccess_(); - this.afterSaving_(); - }.bind(this), 'application/piskel+json'); + var piskel = this.piskelController.getPiskel(); + pskl.app.storageService.saveToFileBrowser(piskel).then(this.onSaveSuccess_); }; - ns.SaveController.prototype.getName = function () { + ns.SaveController.prototype.saveFileDesktop_ = function () { + this.beforeSaving_(); + var piskel = this.piskelController.getPiskel(); + pskl.app.storageService.saveToFileNodeWebkit(piskel).then(this.onSaveSuccess_); + }; + + ns.SaveController.prototype.saveAs_ = function () { + this.beforeSaving_(); + var piskel = this.piskelController.getPiskel(); + pskl.app.storageService.saveToFileNodeWebkit(piskel, true).then(this.onSaveSuccess_); + }; + + ns.SaveController.prototype.getDescriptor_ = function () { + var name = this.getName_(); + var description = this.getDescription_(); + var isPublic = this.isPublic_(); + return new pskl.model.piskel.Descriptor(name, description, isPublic); + }; + + ns.SaveController.prototype.getName_ = function () { return this.nameInput.value; }; - ns.SaveController.prototype.getDescription = function () { + ns.SaveController.prototype.getDescription_ = function () { return this.descriptionInput.value; }; ns.SaveController.prototype.isPublic_ = function () { - return !!this.isPublicCheckbox.checked; - }; - - ns.SaveController.prototype.beforeSaving_ = function () { - this.updatePiskelDescriptor_(); - - if (this.piskelName) { - this.piskelName.classList.add('piskel-name-saving'); + if (!this.isPublicCheckbox) { + return true; } - }; - ns.SaveController.prototype.updatePiskelDescriptor_ = function () { - var name = this.getName(); - var description = this.getDescription(); - var isPublic = this.isPublic_(); - - var descriptor = new pskl.model.piskel.Descriptor(name, description, isPublic); - this.piskelController.getPiskel().setDescriptor(descriptor); + return !!this.isPublicCheckbox.checked; }; ns.SaveController.prototype.onSaveSuccess_ = function () { $.publish(Events.CLOSE_SETTINGS_DRAWER); - $.publish(Events.SHOW_NOTIFICATION, [{'content': 'Successfully saved !'}]); - $.publish(Events.PISKEL_SAVED); }; - ns.SaveController.prototype.onSaveError_ = function (status) { - $.publish(Events.SHOW_NOTIFICATION, [{'content': 'Saving failed (' + status + ')'}]); + ns.SaveController.prototype.beforeSaving_ = function () { + this.piskelController.getPiskel().setDescriptor(this.getDescriptor_()); }; - ns.SaveController.prototype.afterOnlineSaving_ = function () { - this.saveOnlineButton.setAttribute('disabled', false); - this.saveOnlineStatus.innerHTML = ''; - this.afterSaving_(); + ns.SaveController.prototype.disableSaveButtons_ = function () { + this.setDisabled_(this.saveFileButton, true); + this.setDisabled_(this.saveBrowserButton, true); + this.setDisabled_(this.saveOnlineButton, true); + this.setDisabled_(this.saveAsNewButton, true); }; - ns.SaveController.prototype.afterSaving_ = function () { - if (this.piskelName) { - this.piskelName.classList.remove('piskel-name-saving'); + ns.SaveController.prototype.enableSaveButtons_ = function () { + this.setDisabled_(this.saveFileButton, false); + this.setDisabled_(this.saveBrowserButton, false); + this.setDisabled_(this.saveOnlineButton, false); + this.setDisabled_(this.saveAsNewButton, false); + }; + + /** + * Safely update the disabled attribute on a HTML element. + * Noop if the element is falsy + */ + ns.SaveController.prototype.setDisabled_ = function (element, isDisabled) { + if (!element) { + return; } - window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 5000); + if (isDisabled) { + element.setAttribute('disabled', 'disabled'); + } else { + element.removeAttribute('disabled'); + } }; + })(); diff --git a/src/js/service/storage/DesktopStorageService.js b/src/js/service/storage/DesktopStorageService.js index 2e0913d2..32ba0499 100644 --- a/src/js/service/storage/DesktopStorageService.js +++ b/src/js/service/storage/DesktopStorageService.js @@ -1,69 +1,58 @@ (function () { var ns = $.namespace('pskl.service.storage'); + var PISKEL_EXTENSION = '.piskel'; 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.init = function () {}; - 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); + ns.DesktopStorageService.prototype.save = function (piskel, saveAsNew) { + if (piskel.savePath && !saveAsNew) { + return this.saveAtPath_(piskel, piskel.savePath); } else { - this.savePiskelAs(); + var name = piskel.getDescriptor().name; + var filenamePromise = pskl.utils.FileUtilsDesktop.chooseFilenameDialog(name, PISKEL_EXTENSION); + return filenamePromise.then(this.saveAtPath_.bind(this, piskel)); } }; - 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.saveAtPath_ = function (piskel, savePath) { + if (!savePath) { + return Q.reject('Invalid file name'); + } + + var serialized = pskl.utils.Serializer.serializePiskel(piskel, false); + savePath = this.addExtensionIfNeeded_(savePath); + piskel.savePath = savePath; + piskel.setName(this.extractFilename_(savePath)); + return pskl.utils.FileUtilsDesktop.saveToFile(serialized, savePath); }; ns.DesktopStorageService.prototype.openPiskel = function () { - pskl.utils.FileUtilsDesktop.chooseFileDialog(function (filename) { - 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 = savePath; - pskl.app.piskelController.setPiskel(piskel); - pskl.app.previewController.setFPS(fps); - }); + return pskl.utils.FileUtilsDesktop.chooseFilenameDialog().then(this.load); + }; + + ns.DesktopStorageService.prototype.load = function (savePath) { + pskl.utils.FileUtilsDesktop.readFile(savePath).then(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 = savePath; + pskl.app.piskelController.setPiskel(piskel); + pskl.app.previewController.setFPS(fps); }); }); }; - ns.DesktopStorageService.prototype.savePiskelAs = function () { - var serialized = this.piskelController.serialize(); - var name = this.piskelController.getPiskel().getDescriptor().name; - // TODO: if there is already a file path, use it for the dialog's - // working directory and filename - pskl.utils.FileUtilsDesktop.saveAs(serialized, name, 'piskel', function (selectedSavePath) { - this.piskelController.setSavePath(selectedSavePath); - - var filename = this.extractFilename_(selectedSavePath); - if (filename) { - var descriptor = this.piskelController.getPiskel().getDescriptor(); - descriptor.name = filename; - this.piskelController.getPiskel().setDescriptor(descriptor); - } - - this.onSaveSuccess_(); - }.bind(this)); + ns.DesktopStorageService.prototype.addExtensionIfNeeded_ = function (filename) { + var hasExtension = filename.substr(-PISKEL_EXTENSION.length) === PISKEL_EXTENSION; + if (!hasExtension) { + return filename + PISKEL_EXTENSION; + } + return filename; }; ns.DesktopStorageService.prototype.extractFilename_ = function (savePath) { @@ -72,15 +61,4 @@ return matches[1]; } }; - - ns.DesktopStorageService.prototype.onSaveSuccess_ = function () { - var savePath = this.piskelController.getSavePath(); - $.publish(Events.CLOSE_SETTINGS_DRAWER); - $.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), 3000); - }; - })(); diff --git a/src/js/service/storage/FileDownloadStorageService.js b/src/js/service/storage/FileDownloadStorageService.js new file mode 100644 index 00000000..87814f1a --- /dev/null +++ b/src/js/service/storage/FileDownloadStorageService.js @@ -0,0 +1,27 @@ +(function () { + var ns = $.namespace('pskl.service.storage'); + + ns.FileDownloadStorageService = function () {}; + ns.FileDownloadStorageService.prototype.init = function () {}; + + ns.FileDownloadStorageService.prototype.save = function (piskel) { + var serialized = pskl.utils.Serializer.serializePiskel(piskel, false); + var deferred = Q.defer(); + + pskl.utils.BlobUtils.stringToBlob(serialized, function(blob) { + var piskelName = piskel.getDescriptor().name; + var timestamp = pskl.utils.DateUtils.format(new Date(), '{{Y}}{{M}}{{D}}-{{H}}{{m}}{{s}}'); + var fileName = piskelName + '-' + timestamp + '.piskel'; + + try { + pskl.utils.FileUtils.downloadAsFile(blob, fileName); + deferred.resolve(); + } catch (e) { + deferred.reject(e.message); + } + }.bind(this), 'application/piskel+json'); + + return deferred.promise; + }; + +})(); diff --git a/src/js/service/storage/GalleryStorageService.js b/src/js/service/storage/GalleryStorageService.js index bd38f5dd..3169d831 100644 --- a/src/js/service/storage/GalleryStorageService.js +++ b/src/js/service/storage/GalleryStorageService.js @@ -7,8 +7,9 @@ ns.GalleryStorageService.prototype.init = function () {}; - ns.GalleryStorageService.prototype.store = function (piskel, onSuccess, onError) { + ns.GalleryStorageService.prototype.save = function (piskel) { var descriptor = piskel.getDescriptor(); + var deferred = Q.defer(); var data = { framesheet : this.piskelController.serialize(), @@ -24,10 +25,30 @@ data.public = true; } - var errorCallback = function (response) { - onError(response.status); + var successCallback = function (response) { + deferred.resolve(); }; - pskl.utils.Xhr.post(Constants.APPENGINE_SAVE_URL, data, onSuccess, errorCallback); + var errorCallback = function (response) { + deferred.reject(this.getErrorMessage_(response)); + }; + + pskl.utils.Xhr.post(Constants.APPENGINE_SAVE_URL, data, successCallback, errorCallback.bind(this)); + + return deferred.promise; + }; + + ns.GalleryStorageService.prototype.getErrorMessage_ = function (response) { + var errorMessage = ''; + if (response.status === 401) { + errorMessage = 'Session expired, please log in again.'; + } else if (response.status === 403) { + errorMessage = 'Unauthorized action, this sprite belongs to another account.'; + } else if (response.status === 500) { + errorMessage = 'Unexpected server error, please contact us on Github (piskel) or Twitter (@piskelapp)'; + } else { + errorMessage = 'Unknown error'; + } + return errorMessage; }; })(); diff --git a/src/js/service/storage/LocalStorageService.js b/src/js/service/storage/LocalStorageService.js index 75e1e18b..29058314 100644 --- a/src/js/service/storage/LocalStorageService.js +++ b/src/js/service/storage/LocalStorageService.js @@ -10,10 +10,26 @@ ns.LocalStorageService.prototype.init = function() {}; - ns.LocalStorageService.prototype.save = function(name, description, piskel) { - this.removeFromKeys_(name); - this.addToKeys_(name, description, Date.now()); - window.localStorage.setItem('piskel.' + name, piskel); + ns.LocalStorageService.prototype.save = function(piskel) { + var name = piskel.getDescriptor().name; + var description = piskel.getDescriptor().description; + var serialized = pskl.utils.Serializer.serializePiskel(piskel, false); + + if (pskl.app.localStorageService.getPiskel(name)) { + var confirmOverwrite = window.confirm('There is already a piskel saved as ' + name + '. Overwrite ?'); + if (!confirmOverwrite) { + return Q.reject('Cancelled by user, "' + name + '" already exists'); + } + } + + try { + this.removeFromKeys_(name); + this.addToKeys_(name, description, Date.now()); + window.localStorage.setItem('piskel.' + name, serialized); + return Q.resolve(); + } catch (e) { + return Q.reject(e.message); + } }; ns.LocalStorageService.prototype.load = function(name) { diff --git a/src/js/service/storage/StorageService.js b/src/js/service/storage/StorageService.js new file mode 100644 index 00000000..7df84c91 --- /dev/null +++ b/src/js/service/storage/StorageService.js @@ -0,0 +1,82 @@ +(function () { + var ns = $.namespace('pskl.service.storage'); + + ns.StorageService = function (piskelController) { + this.piskelController = piskelController; + + this.onSaveSuccess_ = this.onSaveSuccess_.bind(this); + this.onSaveError_ = this.onSaveError_.bind(this); + }; + + ns.StorageService.prototype.init = function () { + pskl.app.shortcutService.addShortcut('ctrl+o', this.onOpenKey_.bind(this)); + pskl.app.shortcutService.addShortcut('ctrl+s', this.onSaveKey_.bind(this)); + pskl.app.shortcutService.addShortcut('ctrl+shift+s', this.onSaveAsKey_.bind(this)); + }; + + ns.StorageService.prototype.onOpenKey_ = function () { + if (pskl.utils.Environment.detectNodeWebkit()) { + pskl.app.desktopStorageService.openPiskel(); + } + // no other implementation for now + }; + + ns.StorageService.prototype.onSaveKey_ = function () { + var piskel = this.piskelController.getPiskel(); + if (pskl.app.isLoggedIn()) { + this.saveToGallery(this.piskelController.getPiskel()); + } else if (pskl.utils.Environment.detectNodeWebkit()) { + this.saveToFileNodeWebkit(this.piskelController.getPiskel()); + } else { + this.saveToLocalStorage(this.piskelController.getPiskel()); + } + }; + + ns.StorageService.prototype.onSaveAsKey_ = function () { + if (pskl.utils.Environment.detectNodeWebkit()) { + this.saveToFileNodeWebkit(this.piskelController.getPiskel(), true); + } + // no other implementation for now + }; + + ns.StorageService.prototype.saveToGallery = function (piskel) { + $.publish(Events.BEFORE_SAVING_PISKEL); + return pskl.app.galleryStorageService.save(piskel).then(this.onSaveSuccess_, this.onSaveError_); + }; + + ns.StorageService.prototype.saveToLocalStorage = function (piskel) { + $.publish(Events.BEFORE_SAVING_PISKEL); + return pskl.app.localStorageService.save(piskel).then(this.onSaveSuccess_, this.onSaveError_); + }; + + ns.StorageService.prototype.saveToFileBrowser = function (piskel) { + $.publish(Events.BEFORE_SAVING_PISKEL); + return pskl.app.fileDownloadStorageService.save(piskel).then(this.onSaveSuccess_, this.onSaveError_); + }; + + ns.StorageService.prototype.saveToFileNodeWebkit = function (piskel, saveAsNew) { + $.publish(Events.BEFORE_SAVING_PISKEL); + return pskl.app.desktopStorageService.save(piskel, saveAsNew).then(this.onSaveSuccess_, this.onSaveError_); + }; + + ns.StorageService.prototype.onSaveSuccess_ = function () { + $.publish(Events.SHOW_NOTIFICATION, [{'content': 'Successfully saved !'}]); + $.publish(Events.PISKEL_SAVED); + this.afterSaving_(); + }; + + ns.StorageService.prototype.onSaveError_ = function (errorMessage) { + var errorText = 'Saving failed'; + if (errorMessage) { + errorText += ' : ' + errorMessage; + } + $.publish(Events.SHOW_NOTIFICATION, [{'content': errorText}]); + this.afterSaving_(); + return Q.reject(errorMessage); + }; + + ns.StorageService.prototype.afterSaving_ = function () { + $.publish(Events.AFTER_SAVING_PISKEL); + window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 5000); + }; +})(); diff --git a/src/js/utils/FileUtilsDesktop.js b/src/js/utils/FileUtilsDesktop.js index d53ef89b..ac2dc26f 100644 --- a/src/js/utils/FileUtilsDesktop.js +++ b/src/js/utils/FileUtilsDesktop.js @@ -1,63 +1,39 @@ (function () { var ns = $.namespace('pskl.utils'); - var stopPropagation = function (e) { - e.stopPropagation(); + var getFileInputElement = function (nwsaveas, accept) { + var fileInputElement = document.createElement('INPUT'); + fileInputElement.setAttribute('type', 'file'); + fileInputElement.setAttribute('nwworkingdir', ''); + if (nwsaveas) { + fileInputElement.setAttribute('nwsaveas', nwsaveas); + } + if (accept) { + fileInputElement.setAttribute('accept', accept); + } + + return fileInputElement; }; - var CONFIRM_OVERRIDE = 'File already exists, do you want to override it ?'; - ns.FileUtilsDesktop = { + chooseFilenameDialog : function (nwsaveas, accept) { + var deferred = Q.defer(); + var fileInputElement = getFileInputElement(nwsaveas, accept); + var changeListener = function (evt) { + fileInputElement.removeEventListener('change', changeListener); + document.removeEventListener('click', changeListener); + deferred.resolve(fileInputElement.value); + }; - chooseFileDialog: function (callback) { - var tagString = ''; - var $chooser = $(tagString); - $chooser.change(function (e) { - var filename = $(this).val(); - callback(filename); - }); - $chooser.trigger('click'); - }, + fileInputElement.click(); - addExtensionIfNeeded : function (filename, extension) { - if (typeof extension == 'string') { - if (extension[0] !== '.') { - extension = '.' + extension; - } - var hasExtension = (filename.substring(filename.length - extension.length) === extension); - if (!hasExtension) { - filename += extension; - } - } - return filename; - }, + fileInputElement.addEventListener('change', changeListener); + // there is no way to detect a cancelled fileInput popup + // as a crappy workaround we add a click listener on the document + // on top the change event listener + document.addEventListener('click', changeListener); - /** - * - * @param content - * @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, 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 ) - defaultFilename = defaultFilename || 'New Piskel'; - defaultFilename = pskl.utils.FileUtilsDesktop.addExtensionIfNeeded(defaultFilename, extension); - var tagString = ''; - var $chooser = $(tagString); - $chooser.change(function (e) { - var filename = $(this).val(); - filename = pskl.utils.FileUtilsDesktop.addExtensionIfNeeded(filename, extension); - pskl.utils.FileUtilsDesktop.saveToFile(content, filename, function () { - callback(filename); - }); - }); - - $chooser.trigger('click'); + return deferred.promise; }, /** @@ -67,26 +43,32 @@ * @param {string} filename - fill path to the file * @callback callback */ - saveToFile : function(content, filename, callback) { + saveToFile : function(content, filename) { + var deferred = Q.defer(); var fs = window.require('fs'); fs.writeFile(filename, content, function (err) { if (err) { - //throw err; - console.log('FileUtilsDesktop::savetoFile() - error saving file:', filename, 'Error:', err); + deferred.reject('FileUtilsDesktop::savetoFile() - error saving file: ' + filename + ' Error: ' + err); + } else { + deferred.resolve(); } - callback(); }); + + return deferred.promise; }, - readFile : function(filename, callback) { + readFile : function(filename) { + var deferred = Q.defer(); 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) { - console.log('FileUtilsDesktop::readFile() - error reading file:', filename, 'Error:', err); + deferred.reject('FileUtilsDesktop::readFile() - error reading file: ' + filename + ' Error: ' + err); + } else { + deferred.resolve(data); } - callback(data); }); + return deferred.promise; } }; })(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 15a20a28..085e0548 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -136,6 +136,11 @@ "js/widgets/SizeInput.js", // Services + "js/service/storage/StorageService.js", + "js/service/storage/FileDownloadStorageService.js", + "js/service/storage/LocalStorageService.js", + "js/service/storage/GalleryStorageService.js", + "js/service/storage/DesktopStorageService.js", "js/service/BackupService.js", "js/service/BeforeUnloadService.js", "js/service/HistoryService.js", diff --git a/src/templates/settings/save.html b/src/templates/settings/save.html index 21f0eb23..6dde4ae8 100644 --- a/src/templates/settings/save.html +++ b/src/templates/settings/save.html @@ -25,7 +25,18 @@