diff --git a/Gruntfile.js b/Gruntfile.js index b948be15..2201f43e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -12,6 +12,7 @@ module.exports = function(grunt) { var piskelScripts = require('./piskel-script-list.js').scripts; + var piskelStyles = require('./piskel-style-list.js').styles; var getGhostConfig = function (delay) { return { filesSrc : ['tests/integration/casperjs/*_test.js'], @@ -68,12 +69,16 @@ module.exports = function(grunt) { local : getGhostConfig(50) }, concat : { - options : { - separator : ';' - }, - dist : { + js : { + options : { + separator : ';' + }, src : piskelScripts, dest : 'build/piskel-packaged.js' + }, + css : { + src : piskelStyles, + dest : 'build/piskel-style-packaged.css' } }, uglify : { @@ -171,8 +176,10 @@ module.exports = function(grunt) { // Compile JS code (eg verify JSDoc annotation and types, no actual minified code generated). grunt.registerTask('compile', ['closureCompiler:compile']); + grunt.registerTask('merge', ['concat:js', 'concat:css', 'uglify']); + // Validate & Build - grunt.registerTask('default', ['leadingIndent:jsFiles', 'leadingIndent:cssFiles', 'jshint', 'concat', 'compile', 'uglify']); + grunt.registerTask('default', ['leadingIndent:jsFiles', 'leadingIndent:cssFiles', 'jshint', 'concat:js', 'concat:css', 'compile', 'uglify']); // Start webserver grunt.registerTask('serve', ['connect:serve']); diff --git a/css/forms.css b/css/forms.css index 8cc1dd53..cb912f01 100644 --- a/css/forms.css +++ b/css/forms.css @@ -1,9 +1,16 @@ +.row { + display: block; +} + .textfield { background : black; border : 1px solid #888; border-radius : 2px; padding : 3px 10px; color : white; + + box-sizing:border-box; + -moz-box-sizing:border-box; } .textfield[disabled=disabled] { diff --git a/css/settings.css b/css/settings.css index 9d147179..171579c9 100644 --- a/css/settings.css +++ b/css/settings.css @@ -8,6 +8,7 @@ -webkit-transition: all 200ms ease-out; -moz-transition: all 200ms ease-out; -ms-transition: all 200ms ease-out; + -o-transition: all 200ms ease-out; transition: all 200ms ease-out; } @@ -51,6 +52,10 @@ text-shadow: 1px 1px #000; } +.settings-section .button { + margin: 0; +} + .settings-title { margin-top: 20px; margin-bottom: 10px; @@ -59,7 +64,9 @@ padding-bottom: 5px; } -.settings-item {} +.settings-form-section { + margin-bottom: 10px; +} .background-picker-wrapper { overflow: hidden; @@ -152,7 +159,7 @@ } .import-resize-field { - width: 30px; + width: 50px; margin-right: 8px; text-align: right; } @@ -179,7 +186,26 @@ text-shadow: none; } +.save-field { + width: 100%; +} + +#save-status { + margin-left: 10px; +} +.status { + height: 1.5rem; + + word-break : break-all; + vertical-align: middle; + font-weight: normal; + text-shadow: none; +} + [name=smooth-resize-checkbox] { margin : 0 8px; +} + +[name*=checkbox] { vertical-align: middle; } \ No newline at end of file diff --git a/css/tools.css b/css/tools.css index f0a69e10..fee6ca36 100644 --- a/css/tools.css +++ b/css/tools.css @@ -19,8 +19,10 @@ .tool-icon.selected { cursor: default; background-color: #444; - border: 1px gold solid; - margin: 0; + border: 3px solid gold; + box-sizing: border-box; + -moz-box-sizing: border-box; + background-position: 9px 9px; } .tool-icon:hover { @@ -64,7 +66,6 @@ .tool-icon.tool-move { background-image: url(../img/tools/hand.png); - background-position: 12px 12px; background-size: 24px 24px; } @@ -74,13 +75,16 @@ background-size: 24px 20px; } +.tool-icon.tool-rectangle.selected, .tool-icon.tool-rectangle-select.selected { + background-position: 9px 11px; +} + .tool-icon.tool-shape-select { background-image: url(../img/tools/magicwand.png); } .tool-icon.tool-colorpicker { background-image: url(../img/tools/eyedropper.png); - background-position: 12px 12px; background-size: 23px 23px; } diff --git a/index.html b/index.html index f7ff5b04..9550a6b8 100644 --- a/index.html +++ b/index.html @@ -7,20 +7,21 @@ - - - - - - - - - - - - +
+ Loading pixels ... +
@@ -53,6 +54,7 @@ +
diff --git a/js/app.js b/js/app.js index 6ddccc21..3f37fcc0 100644 --- a/js/app.js +++ b/js/app.js @@ -10,11 +10,19 @@ ns.app = { init : function () { + /** + * True when piskel is running in static mode (no back end needed). + * When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl + */ + this.isAppEngineVersion = !!pskl.appEngineToken_; + this.shortcutService = new pskl.service.keyboard.ShortcutService(); this.shortcutService.init(); var size = this.readSizeFromURL_(); - var piskel = new pskl.model.Piskel(size.width, size.height); + + var descriptor = new pskl.model.piskel.Descriptor('New Piskel', ''); + var piskel = new pskl.model.Piskel(size.width, size.height, descriptor); var layer = new pskl.model.Layer("Layer 1"); var frame = new pskl.model.Frame(size.width, size.height); @@ -64,31 +72,31 @@ this.imageUploadService = new pskl.service.ImageUploadService(); this.imageUploadService.init(); - this.cheatsheetService = new pskl.service.keyboard.CheatsheetService(); this.cheatsheetService.init(); + if (this.isAppEngineVersion) { + this.storageService = new pskl.service.AppEngineStorageService(this.piskelController); + } else { + this.storageService = new pskl.service.GithubStorageService(this.piskelController); + } + this.storageService.init(); + var drawingLoop = new pskl.rendering.DrawingLoop(); drawingLoop.addCallback(this.render, this); drawingLoop.start(); - this.initBootstrapTooltips_(); + this.initTooltips_(); - /** - * True when piskel is running in static mode (no back end needed). - * When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl - */ - this.isStaticVersion = !pskl.appEngineToken_; - - if (this.isStaticVersion) { - this.finishInitStatic_(); - } else { + if (this.isAppEngineVersion) { this.finishInitAppEngine_(); + } else { + this.finishInitGithub_(); } }, - finishInitStatic_ : function () { + finishInitGithub_ : function () { var framesheetId = this.readFramesheetIdFromURL_(); if (framesheetId) { $.publish(Events.SHOW_NOTIFICATION, [{ @@ -101,15 +109,16 @@ }, finishInitAppEngine_ : function () { - if (pskl.framesheetData_ && pskl.framesheetData_.content) { - pskl.utils.serialization.Deserializer.deserialize(pskl.framesheetData_.content, function (piskel) { + if (pskl.appEnginePiskelData_ && pskl.appEnginePiskelData_.piskel) { + pskl.utils.serialization.Deserializer.deserialize(pskl.appEnginePiskelData_.piskel, function (piskel) { + piskel.setDescriptor(pskl.appEnginePiskelData_.descriptor); pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(pskl.framesheetData_.fps); + pskl.app.animationController.setFPS(pskl.appEnginePiskelData_.fps); }); } }, - initBootstrapTooltips_ : function () { + initTooltips_ : function () { $('body').tooltip({ selector: '[rel=tooltip]' }); @@ -122,8 +131,8 @@ }, readSizeFromURL_ : function () { - var sizeParam = this.readUrlParameter_("size"), - size; + var sizeParam = this.readUrlParameter_("size"); + var size; // parameter expected as size=64x48 => size=widthxheight var parts = sizeParam.split("x"); if (parts && parts.length == 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { @@ -148,13 +157,12 @@ }, readUrlParameter_ : function (paramName) { - var searchString = window.location.search.substring(1), - i, val, params = searchString.split("&"); - - for (i = 0; i < params.length; i++) { - val = params[i].split("="); - if (val[0] == paramName) { - return window.unescape(val[1]); + var searchString = window.location.search.substring(1); + var params = searchString.split("&"); + for (var i = 0; i < params.length; i++) { + var param = params[i].split("="); + if (param[0] == paramName) { + return window.unescape(param[1]); } } return ""; @@ -181,65 +189,8 @@ xhr.send(); }, - storeSheet : function (event) { - if (this.isStaticVersion) { - this.storeSheetStatic_(); - } else { - this.storeSheetAppEngine_(); - } - - if(event) { - event.stopPropagation(); - event.preventDefault(); - } - return false; - }, - - storeSheetStatic_ : function () { - var xhr = new XMLHttpRequest(); - var formData = new FormData(); - formData.append('framesheet_content', this.piskelController.serialize()); - formData.append('fps_speed', $('#preview-fps').val()); - - xhr.open('POST', Constants.STATIC.URL.SAVE, true); - - xhr.onload = function(e) { - if (this.status == 200) { - var baseUrl = window.location.href.replace(window.location.search, ""); - window.location.href = baseUrl + "?frameId=" + this.responseText; - } else { - this.onerror(e); - } - }; - xhr.onerror = function(e) { - $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]); - }; - xhr.send(formData); - }, - - storeSheetAppEngine_ : function () { - var xhr = new XMLHttpRequest(); - var formData = new FormData(); - formData.append('framesheet_content', this.piskelController.serialize()); - formData.append('fps_speed', $('#preview-fps').val()); - formData.append('name', $('#piskel-name').val()); - formData.append('frames', this.piskelController.getFrameCount()); - formData.append('preview', this.getFirstFrameAsPng()); - formData.append('framesheet', this.getFramesheetAsPng()); - - xhr.open('POST', Constants.APPENGINE.URL.SAVE, true); - - xhr.onload = function(e) { - if (this.status == 200) { - $.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]); - } else { - this.onerror(e); - } - }; - xhr.onerror = function(e) { - $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]); - }; - xhr.send(formData); + store : function (callbacks) { + this.storageService.store(callbacks); }, getFirstFrameAsPng : function () { diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 71a7ce31..ee7ab623 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -63,7 +63,8 @@ ns.DrawingController.prototype.initMouseBehavior = function() { var body = $('body'); this.container.mousedown($.proxy(this.onMousedown_, this)); - this.container.mousemove($.proxy(this.onMousemove_, this)); + this.container.mouseenter($.proxy(this.onMouseenter_, this)); + this.container.mouseleave($.proxy(this.onMouseleave_, this)); if (pskl.utils.UserAgent.isChrome) { this.container.on('mousewheel', $.proxy(this.onMousewheel_, this)); @@ -82,7 +83,7 @@ window.clearInterval(this.resizeTimer); } this.resizeTimer = window.setTimeout($.proxy(this.afterWindowResize_, this), 200); - }, + }; ns.DrawingController.prototype.afterWindowResize_ = function () { var initialWidth = this.compositeRenderer.getDisplaySize().width; @@ -93,7 +94,7 @@ this.compositeRenderer.setZoom(newZoom); $.publish(Events.ZOOM_CHANGED); - }, + }; /** * @private @@ -102,7 +103,7 @@ if(settingsName == pskl.UserSettings.SHOW_GRID) { console.warn('DrawingController:onUserSettingsChange_ not implemented !'); } - }, + }; ns.DrawingController.prototype.onFrameSizeChanged_ = function () { this.compositeRenderer.setDisplaySize(this.getContainerWidth_(), this.getContainerHeight_()); @@ -111,6 +112,21 @@ $.publish(Events.ZOOM_CHANGED); }; + /** + * @private + */ + ns.DrawingController.prototype.onMouseenter_ = function (event) { + this.container.bind('mousemove', $.proxy(this.onMousemove_, this)); + }; + + /** + * @private + */ + ns.DrawingController.prototype.onMouseleave_ = function (event) { + this.container.unbind('mousemove'); + this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame); + }; + /** * @private */ @@ -124,6 +140,7 @@ } } else { this.isClicked = true; + this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame); this.currentToolBehavior.applyToolAt( coords.x, diff --git a/js/controller/NotificationController.js b/js/controller/NotificationController.js index 8b3ba25a..e6554d55 100644 --- a/js/controller/NotificationController.js +++ b/js/controller/NotificationController.js @@ -15,6 +15,8 @@ * @private */ ns.NotificationController.prototype.displayMessage_ = function (evt, messageInfo) { + this.removeMessage_(); + var message = document.createElement('div'); message.id = "user-message"; message.className = "user-message"; diff --git a/js/controller/PreviewFilmController.js b/js/controller/PreviewFilmController.js index 67f0f03f..8fdce874 100644 --- a/js/controller/PreviewFilmController.js +++ b/js/controller/PreviewFilmController.js @@ -180,7 +180,7 @@ } var tileCount = document.createElement("div"); tileCount.className = "tile-overlay tile-count"; - tileCount.innerHTML = tileNumber; + tileCount.innerHTML = tileNumber + 1; previewTileRoot.appendChild(tileCount); diff --git a/js/controller/settings/ImportController.js b/js/controller/settings/ImportController.js index 3c94eaa5..0186adfe 100644 --- a/js/controller/settings/ImportController.js +++ b/js/controller/settings/ImportController.js @@ -154,7 +154,9 @@ var frame = pskl.utils.FrameUtils.createFromImage(image); var layer = pskl.model.Layer.fromFrames('Layer 1', [frame]); - var piskel = pskl.model.Piskel.fromLayers([layer]); + + var descriptor = new pskl.model.piskel.Descriptor('Imported piskel', ''); + var piskel = pskl.model.Piskel.fromLayers([layer], descriptor); pskl.app.piskelController.setPiskel(piskel); pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); diff --git a/js/controller/settings/SaveController.js b/js/controller/settings/SaveController.js new file mode 100644 index 00000000..0b10eebe --- /dev/null +++ b/js/controller/settings/SaveController.js @@ -0,0 +1,75 @@ +(function () { + var ns = $.namespace('pskl.controller.settings'); + + ns.SaveController = function (piskelController) { + this.piskelController = piskelController; + }; + + /** + * @public + */ + ns.SaveController.prototype.init = function () { + this.saveForm = $('form[name=save-form]'); + this.nameInput = $('#save-name'); + this.descriptionInput = $('#save-description'); + this.isPublicCheckbox = $('input[name=save-public-checkbox]'); + this.saveButton = $('#save-button'); + this.status = $('#save-status'); + + var descriptor = this.piskelController.piskel.getDescriptor(); + this.nameInput.val(descriptor.name); + this.descriptionInput.val(descriptor.description); + + this.isPublicCheckbox.prop('checked', descriptor.isPublic); + + if (!pskl.app.isAppEngineVersion) { + this.nameInput.attr('disabled', 'disabled'); + this.descriptionInput.attr('disabled', 'disabled'); + this.isPublicCheckbox.attr('disabled', 'disabled'); + } + + this.saveForm.submit(this.onSaveFormSubmit_.bind(this)); + }; + + ns.SaveController.prototype.onSaveFormSubmit_ = function (evt) { + evt.preventDefault(); + evt.stopPropagation(); + + var name = this.nameInput.val(); + var description = this.descriptionInput.val(); + var isPublic = !!this.isPublicCheckbox.prop('checked'); + + var descriptor = new pskl.model.piskel.Descriptor(name, description, isPublic); + this.piskelController.piskel.setDescriptor(descriptor); + + this.beforeSaving_(); + pskl.app.store({ + success : this.onSaveSuccess_.bind(this), + error : this.onSaveError_.bind(this), + after : this.afterSaving_.bind(this) + }); + }; + + ns.SaveController.prototype.beforeSaving_ = function () { + this.saveButton.attr('disabled', true); + this.status.html('Saving ...'); + $('.piskel-name').get(0).classList.add('piskel-name-saving'); + }; + + ns.SaveController.prototype.onSaveSuccess_ = function () { + $.publish(Events.CLOSE_SETTINGS_DRAWER); + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]); + }; + + ns.SaveController.prototype.onSaveError_ = function (status) { + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+status+")"}]); + }; + + ns.SaveController.prototype.afterSaving_ = function () { + this.saveButton.attr('disabled', false); + this.status.html(''); + $('.piskel-name').get(0).classList.remove('piskel-name-saving'); + + window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 2000); + }; +})(); \ No newline at end of file diff --git a/js/controller/settings/SettingsController.js b/js/controller/settings/SettingsController.js index 708b3fa1..10db6890 100644 --- a/js/controller/settings/SettingsController.js +++ b/js/controller/settings/SettingsController.js @@ -13,6 +13,10 @@ 'import' : { template : 'templates/settings/import.html', controller : ns.ImportController + }, + 'save' : { + template : 'templates/settings/save.html', + controller : ns.SaveController } }; diff --git a/js/drawingtools/BaseTool.js b/js/drawingtools/BaseTool.js index b3e33e30..1d2dfd40 100644 --- a/js/drawingtools/BaseTool.js +++ b/js/drawingtools/BaseTool.js @@ -31,6 +31,15 @@ } }; + ns.BaseTool.prototype.hideHighlightedPixel = function(overlay) { + if (this.highlightedPixelRow !== null && this.highlightedPixelCol !== null) { + overlay.setPixel(this.highlightedPixelCol, this.highlightedPixelRow, Constants.TRANSPARENT_COLOR); + this.highlightedPixelRow = null; + this.highlightedPixelCol = null; + } + }; + + ns.BaseTool.prototype.releaseToolAt = function(col, row, color, frame, overlay, event) {}; /** diff --git a/js/drawingtools/selectiontools/BaseSelect.js b/js/drawingtools/selectiontools/BaseSelect.js index 19972394..f6475699 100644 --- a/js/drawingtools/selectiontools/BaseSelect.js +++ b/js/drawingtools/selectiontools/BaseSelect.js @@ -70,6 +70,9 @@ } }; + ns.BaseSelect.prototype.hideHighlightedPixel = function () { + // not implemented for selection tools + }; /** * If we mouseover the selection draw inside the overlay frame, show the 'move' cursor diff --git a/js/lib/iframeLoader.js b/js/lib/iframeLoader.js index 4820fb16..b2cb5adf 100644 --- a/js/lib/iframeLoader.js +++ b/js/lib/iframeLoader.js @@ -37,7 +37,11 @@ var storeFrame = function (iframe) { var script=document.createElement("script"); script.setAttribute("type", "text/html"); - script.setAttribute("id", iframe.getAttribute("src")); + if (window.pskl && window.pskl.appEngineToken_) { + script.setAttribute("id", iframe.getAttribute("src").replace('../','')); + } else { + script.setAttribute("id", iframe.getAttribute("src")); + } script.innerHTML = iframe.contentWindow.document.body.innerHTML; iframe.parentNode.removeChild(iframe); document.body.appendChild(script); diff --git a/js/model/Piskel.js b/js/model/Piskel.js index 77e7f261..974e8ece 100644 --- a/js/model/Piskel.js +++ b/js/model/Piskel.js @@ -5,9 +5,11 @@ * @constructor * @param {Number} width * @param {Number} height + * @param {String} name + * @param {String} description */ - ns.Piskel = function (width, height) { - if (width && height) { + ns.Piskel = function (width, height, descriptor) { + if (width && height && descriptor) { /** @type {Array} */ this.layers = []; @@ -16,6 +18,8 @@ /** @type {Number} */ this.height = height; + + this.descriptor = descriptor; } else { throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ","); } @@ -27,11 +31,11 @@ * @param {Array} layers * @return {pskl.model.Piskel} */ - ns.Piskel.fromLayers = function (layers) { + ns.Piskel.fromLayers = function (layers, descriptor) { var piskel = null; if (layers.length > 0 && layers[0].length() > 0) { var sampleFrame = layers[0].getFrameAt(0); - piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight()); + piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight(), descriptor); layers.forEach(piskel.addLayer.bind(piskel)); } else { throw 'Piskel.fromLayers expects array of non empty pskl.model.Layer as first argument'; @@ -96,4 +100,13 @@ this.layers.splice(index, 1); }; + ns.Piskel.prototype.getDescriptor = function () { + return this.descriptor; + }; + + ns.Piskel.prototype.setDescriptor = function (descriptor) { + this.descriptor = descriptor; + var appEngineEditorHeader = $('.piskel-name').html(this.descriptor.name); + }; + })(); \ No newline at end of file diff --git a/js/model/piskel/Descriptor.js b/js/model/piskel/Descriptor.js new file mode 100644 index 00000000..35510dfa --- /dev/null +++ b/js/model/piskel/Descriptor.js @@ -0,0 +1,9 @@ +(function () { + var ns = $.namespace('pskl.model.piskel'); + + ns.Descriptor = function (name, description, isPublic) { + this.name = name; + this.description = description; + this.isPublic = isPublic; + }; +})(); \ No newline at end of file diff --git a/js/service/AppEngineStorageService.js b/js/service/AppEngineStorageService.js new file mode 100644 index 00000000..47184b37 --- /dev/null +++ b/js/service/AppEngineStorageService.js @@ -0,0 +1,49 @@ +(function () { + var ns = $.namespace('pskl.service'); + + ns.AppEngineStorageService = function (piskelController) { + this.piskelController = piskelController; + }; + + ns.AppEngineStorageService.prototype.init = function () {}; + + ns.AppEngineStorageService.prototype.store = function (callbacks) { + var formData = this.prepareFormData_(); + + var xhr = new XMLHttpRequest(); + xhr.open('POST', Constants.APPENGINE.URL.SAVE, true); + + xhr.onload = function(e) { + if (this.status == 200) { + callbacks.success(); + callbacks.after(); + } else { + this.onerror(e); + } + }; + xhr.onerror = function(e) { + callbacks.error(this.status); + callbacks.after(); + }; + xhr.send(formData); + }; + + ns.AppEngineStorageService.prototype.prepareFormData_ = function () { + var piskel = this.piskelController.piskel; + var descriptor = piskel.getDescriptor(); + + var formData = new FormData(); + formData.append('framesheet', this.piskelController.serialize()); + formData.append('fps', this.piskelController.getFPS()); + formData.append('name', descriptor.name); + formData.append('description', descriptor.description); + if (descriptor.isPublic) { + formData.append('public', true); + } + formData.append('frames', this.piskelController.getFrameCount()); + formData.append('first_frame_as_png', pskl.app.getFirstFrameAsPng()); + formData.append('framesheet_as_png', pskl.app.getFramesheetAsPng()); + + return formData; + }; +})(); \ No newline at end of file diff --git a/js/service/GithubStorageService.js b/js/service/GithubStorageService.js new file mode 100644 index 00000000..87b34cda --- /dev/null +++ b/js/service/GithubStorageService.js @@ -0,0 +1,31 @@ +(function () { + var ns = $.namespace('pskl.service'); + + ns.GithubStorageService = function (piskelController) { + this.piskelController = piskelController; + }; + + ns.GithubStorageService.prototype.init = function () {}; + + ns.GithubStorageService.prototype.store = function (callbacks) { + var xhr = new XMLHttpRequest(); + var formData = new FormData(); + formData.append('framesheet_content', this.piskelController.serialize()); + formData.append('fps_speed', this.piskelController.getFPS()); + + xhr.open('POST', Constants.STATIC.URL.SAVE, true); + + xhr.onload = function(e) { + if (this.status == 200) { + var baseUrl = window.location.href.replace(window.location.search, ""); + window.location.href = baseUrl + "?frameId=" + this.responseText; + } else { + this.onerror(e); + } + }; + xhr.onerror = function(e) { + $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]); + }; + xhr.send(formData); + }; +})(); \ No newline at end of file diff --git a/js/service/HistoryService.js b/js/service/HistoryService.js index bfc6d03b..a9067ac5 100644 --- a/js/service/HistoryService.js +++ b/js/service/HistoryService.js @@ -2,11 +2,13 @@ var ns = $.namespace("pskl.service"); ns.HistoryService = function (piskelController) { this.piskelController = piskelController; + this.saveState__b = this.saveState.bind(this); }; ns.HistoryService.prototype.init = function () { - $.subscribe(Events.TOOL_RELEASED, this.saveState.bind(this)); + $.subscribe(Events.PISKEL_RESET, this.saveState__b); + $.subscribe(Events.TOOL_RELEASED, this.saveState__b); pskl.app.shortcutService.addShortcut('ctrl+Z', this.undo.bind(this)); pskl.app.shortcutService.addShortcut('ctrl+Y', this.redo.bind(this)); @@ -18,12 +20,16 @@ ns.HistoryService.prototype.undo = function () { this.piskelController.getCurrentFrame().loadPreviousState(); + $.unsubscribe(Events.PISKEL_RESET, this.saveState__b); $.publish(Events.PISKEL_RESET); + $.subscribe(Events.PISKEL_RESET, this.saveState__b); }; ns.HistoryService.prototype.redo = function () { this.piskelController.getCurrentFrame().loadNextState(); + $.unsubscribe(Events.PISKEL_RESET, this.saveState__b); $.publish(Events.PISKEL_RESET); + $.subscribe(Events.PISKEL_RESET, this.saveState__b); }; })(); \ No newline at end of file diff --git a/js/service/LocalStorageService.js b/js/service/LocalStorageService.js index 783211c3..c68cee38 100644 --- a/js/service/LocalStorageService.js +++ b/js/service/LocalStorageService.js @@ -45,6 +45,7 @@ */ ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() { var framesheet = JSON.parse(window.localStorage.snapShot); + pskl.utils.serialization.Deserializer.deserialize(framesheet, function (piskel) { pskl.app.piskelController.setPiskel(piskel); }); diff --git a/js/service/keyboard/ShortcutService.js b/js/service/keyboard/ShortcutService.js index 07419af7..648abc6c 100644 --- a/js/service/keyboard/ShortcutService.js +++ b/js/service/keyboard/ShortcutService.js @@ -54,28 +54,36 @@ * @private */ ns.ShortcutService.prototype.onKeyUp_ = function(evt) { - // jquery names FTW ... - var keycode = evt.which; - var charkey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode); + if (!this.isInInput_(evt)) { + // jquery names FTW ... + var keycode = evt.which; + var targetTagName = evt.target.nodeName.toUpperCase(); + var charkey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode); - var keyShortcuts = this.shortcuts_[charkey]; - if(keyShortcuts) { - var cb; - if (this.isCtrlKeyPressed_(evt)) { - cb = keyShortcuts.ctrl; - } else if (this.isShiftKeyPressed_(evt)) { - cb = keyShortcuts.shift; - } else { - cb = keyShortcuts.normal; - } + var keyShortcuts = this.shortcuts_[charkey]; + if(keyShortcuts) { + var cb; + if (this.isCtrlKeyPressed_(evt)) { + cb = keyShortcuts.ctrl; + } else if (this.isShiftKeyPressed_(evt)) { + cb = keyShortcuts.shift; + } else { + cb = keyShortcuts.normal; + } - if(cb) { - cb(charkey); - evt.preventDefault(); + if(cb) { + cb(charkey); + evt.preventDefault(); + } } } }; + ns.ShortcutService.prototype.isInInput_ = function (evt) { + var targetTagName = evt.target.nodeName.toUpperCase(); + return targetTagName === 'INPUT' || targetTagName === 'TEXTAREA'; + }; + ns.ShortcutService.prototype.isCtrlKeyPressed_ = function (evt) { return this.isMac_() ? evt.metaKey : evt.ctrlKey; }; diff --git a/js/utils/serialization/Deserializer.js b/js/utils/serialization/Deserializer.js index 118538c6..799b1f09 100644 --- a/js/utils/serialization/Deserializer.js +++ b/js/utils/serialization/Deserializer.js @@ -23,7 +23,9 @@ ns.Deserializer.prototype.deserialize = function () { var data = this.data_; var piskelData = data.piskel; - this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height); + + var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', ''); + this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor); this.layersToLoad_ = piskelData.layers.length; diff --git a/js/utils/serialization/backward/Deserializer_v0.js b/js/utils/serialization/backward/Deserializer_v0.js index c976acb8..78991b9a 100644 --- a/js/utils/serialization/backward/Deserializer_v0.js +++ b/js/utils/serialization/backward/Deserializer_v0.js @@ -11,8 +11,9 @@ var frames = pixelGrids.map(function (grid) { return pskl.model.Frame.fromPixelGrid(grid); }); + var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', ''); var layer = pskl.model.Layer.fromFrames('Layer 1', frames); - this.callback_(pskl.model.Piskel.fromLayers([layer])); + this.callback_(pskl.model.Piskel.fromLayers([layer], descriptor)); }; })(); \ No newline at end of file diff --git a/js/utils/serialization/backward/Deserializer_v1.js b/js/utils/serialization/backward/Deserializer_v1.js index 92b05741..0ad97777 100644 --- a/js/utils/serialization/backward/Deserializer_v1.js +++ b/js/utils/serialization/backward/Deserializer_v1.js @@ -8,7 +8,8 @@ ns.Deserializer_v1.prototype.deserialize = function () { var piskelData = this.data_.piskel; - var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); + var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', ''); + var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor); piskelData.layers.forEach(function (serializedLayer) { var layer = this.deserializeLayer(serializedLayer); diff --git a/piskel-boot.js b/piskel-boot.js index 204eb7a7..b9be8a96 100644 --- a/piskel-boot.js +++ b/piskel-boot.js @@ -1,28 +1,61 @@ (function () { + window.onPiskelReady = function () { + var loadingMask = document.getElementById('loading-mask'); + loadingMask.style.opacity = 0; + window.setTimeout(function () {loadingMask.parentNode.removeChild(loadingMask);}, 600) + pskl.app.init(); + // cleanup + delete window.exports; + delete window.loadDebugScripts; + delete window.done; + }; + + var prefixPath = function (path) { + if (window.pskl && window.pskl.appEngineToken_) { + return '../' + path; + } else { + return path; + } + }; + var loadScript = function (src, callback) { + src = prefixPath(src); var script = window.document.createElement('script'); script.setAttribute('src',src); script.setAttribute('onload',callback); window.document.body.appendChild(script); }; + var loadStyle = function (src) { + src = prefixPath(src); + var link = document.createElement('link'); + link.setAttribute('href', src); + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('type', 'text/css'); + document.head.appendChild(link); + }; + if (window.location.href.indexOf("debug") != -1) { window.exports = {}; var scriptIndex = 0; window.loadNextScript = function () { if (scriptIndex == window.exports.scripts.length) { - pskl.app.init(); - // cleanup - delete window.exports; - delete window.loadDebugScripts; - delete window.done; + window.onPiskelReady(); } else { loadScript(window.exports.scripts[scriptIndex], "loadNextScript()"); scriptIndex ++; } }; loadScript("piskel-script-list.js", "loadNextScript()"); + + window.loadStyles = function () { + var styles = window.exports.styles; + for (var i = 0 ; i < styles.length ; i++) { + loadStyle(styles[i]); + } + }; + loadScript("piskel-style-list.js", "loadStyles()"); } else { var script; if (window.location.href.indexOf("pack") != -1) { @@ -30,10 +63,12 @@ } else { script = "build/piskel-packaged-min.js"; } + loadStyle('build/piskel-style-packaged.css'); + var loaderInterval = window.setInterval(function () { if (document.querySelectorAll("[data-iframe-loader]").length === 0) { window.clearInterval(loaderInterval); - loadScript(script, "pskl.app.init()"); + loadScript(script, "onPiskelReady()"); } else { console.log("waiting for templates to load ...."); } diff --git a/piskel-script-list.js b/piskel-script-list.js index b4e54c12..4dbd1bfb 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -37,6 +37,7 @@ exports.scripts = [ // Models "js/model/Frame.js", "js/model/Layer.js", + "js/model/piskel/Descriptor.js", "js/model/Piskel.js", // Selection @@ -69,12 +70,15 @@ exports.scripts = [ // Settings sub-controllers "js/controller/settings/ApplicationSettingsController.js", "js/controller/settings/GifExportController.js", + "js/controller/settings/SaveController.js", "js/controller/settings/ImportController.js", // Settings controller "js/controller/settings/SettingsController.js", // Services "js/service/LocalStorageService.js", + "js/service/GithubStorageService.js", + "js/service/AppEngineStorageService.js", "js/service/HistoryService.js", "js/service/keyboard/ShortcutService.js", "js/service/keyboard/KeycodeTranslator.js", diff --git a/piskel-style-list.js b/piskel-style-list.js new file mode 100644 index 00000000..c1fb7df7 --- /dev/null +++ b/piskel-style-list.js @@ -0,0 +1,16 @@ +// This list is used both by the grunt build and index.html (in debug mode) + +exports.styles = [ + "css/reset.css", + "css/style.css", + "css/forms.css", + "css/settings.css", + "css/tools.css", + "css/cheatsheet.css", + "css/spectrum/spectrum.css", + "css/spectrum/spectrum-overrides.css", + "css/bootstrap/bootstrap.css", + "css/bootstrap/bootstrap-tooltip-custom.css", + "css/preview-film-section.css", + "css/minimap.css" +]; \ No newline at end of file diff --git a/templates/settings.html b/templates/settings.html index efb455eb..72c80ccd 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -1,4 +1,11 @@
+
+
+
- - -
-
-
-
diff --git a/templates/settings/save.html b/templates/settings/save.html new file mode 100644 index 00000000..a2ac1030 --- /dev/null +++ b/templates/settings/save.html @@ -0,0 +1,22 @@ +
+
Save
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
\ No newline at end of file