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 @@
+
\ No newline at end of file