diff --git a/Gruntfile.js b/Gruntfile.js index 497085a8..f11c60fd 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -37,8 +37,7 @@ module.exports = function(grunt) { undef : true, latedef : true, browser : true, - jquery : true, - globals : {'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true} + globals : {'$':true, 'jQuery' : true, 'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true} }, files: [ 'Gruntfile.js', diff --git a/css/settings.css b/css/settings.css new file mode 100644 index 00000000..d872702a --- /dev/null +++ b/css/settings.css @@ -0,0 +1,118 @@ + +/** Righty sticky drawer expanded state. */ + +.right-sticky-section.sticky-section { + right: 0; + width: 47px; + + -webkit-transition: all 200ms ease-out; + -moz-transition: all 200ms ease-out; + -ms-transition: all 200ms ease-out; + transition: all 200ms ease-out; +} + +.right-sticky-section.expanded { + right: 280px; +} + +.right-sticky-section .tool-icon { + float: right; + margin-right: 0; +} + +.drawer-content { + overflow: hidden; + background-color: #444; + height: 550px; + max-height: 100%; + width: 280px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.right-sticky-section.expanded .tool-icon { + margin-right: 1px; +} + +.right-sticky-section .tool-icon.has-expanded-drawer { + position: relative; + background-color: #444; + margin-right: 0; + padding-right: 1px; +} + +.settings-section { + margin: 10px 20px; + font-size: 12px; + font-weight: bold; + color: #ccc; + text-shadow: 1px 1px #000; +} + +.settings-title { + margin-top: 20px; + margin-bottom: 10px; + text-transform: uppercase; + border-bottom: 1px #aaa solid; + padding-bottom: 5px; +} + +.settings-item {} + +.background-picker-wrapper { + overflow: hidden; + padding: 10px 5px 20px 5px; +} + +.background-picker { + cursor: pointer; + float: left; + height: 35px; + width: 35px; + background-color: transparent; + margin-right: 15px; + padding: 1px; + position: relative; +} + +.background-picker:after { + content: " "; + position: absolute; + top: -2px; + right: -2px; + bottom: -2px; + left: -2px; +} + +.background-picker:hover:after { + border: #eee 1px solid; +} + +.background-picker.selected:after { + border: gold 1px solid; +} + +/* Gif Export Setting panel*/ +.export-gif-upload-button { + margin-top : 10px; +} + +.export-gif-preview { + margin-top:20px; + max-width:240px; + position:relative; +} + +.preview-upload-ongoing:before{ + content: "Upload ongoing ..."; + position: absolute; + display: block; + height: 100%; + width: 100%; + text-align: center; + padding-top: 45%; + box-sizing:border-box; + -moz-box-sizing:border-box; + background: rgba(0,0,0,0.5); + color: white; +} \ No newline at end of file diff --git a/css/style.css b/css/style.css index 453b2751..50428da5 100644 --- a/css/style.css +++ b/css/style.css @@ -98,103 +98,6 @@ body { float: left; } -.right-sticky-section.sticky-section { - right: 0; - width: 47px; - - -webkit-transition: all 200ms ease-out; - -moz-transition: all 200ms ease-out; - -ms-transition: all 200ms ease-out; - transition: all 200ms ease-out; -} - -.right-sticky-section .tool-icon { - float: right; - margin-right: 0; -} - -.drawer { - -} - -.drawer-content { - overflow: hidden; - background-color: #444; - height: 550px; - max-height: 100%; - width: 280px; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} - -/** Righty sticky drawer expanded state. */ - -.right-sticky-section.expanded { - right: 280px; -} - -.right-sticky-section.expanded .tool-icon { - margin-right: 1px; -} - -.right-sticky-section .tool-icon.has-expanded-drawer { - position: relative; - background-color: #444; - margin-right: 0; - padding-right: 1px; -} - -.settings-section { - margin: 10px 20px; - font-size: 12px; - font-weight: bold; - color: #ccc; - text-shadow: 1px 1px #000; -} - -.settings-title { - margin-top: 20px; - margin-bottom: 10px; - text-transform: uppercase; - border-bottom: 1px #aaa solid; - padding-bottom: 5px; -} - -.settings-item {} - -.background-picker-wrapper { - overflow: hidden; - padding: 10px 5px 20px 5px; -} - -.background-picker { - cursor: pointer; - float: left; - height: 35px; - width: 35px; - background-color: transparent; - margin-right: 15px; - padding: 1px; - position: relative; -} - -.background-picker:after { - content: " "; - position: absolute; - top: -2px; - right: -2px; - bottom: -2px; - left: -2px; -} - -.background-picker:hover:after { - border: #eee 1px solid; -} - -.background-picker.selected:after { - border: gold 1px solid; -} - /** * Canvases layout */ diff --git a/index.html b/index.html index 9a178db9..3035a2cc 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@ + @@ -22,37 +23,34 @@
-
-
- -
-
- - - -
-
-
-
-
-
-
-
+ +
+
+ +
+
+ + +
+
+
+
+
diff --git a/js/controller/SettingsController.js b/js/controller/SettingsController.js index 353d201d..2a5b4c01 100644 --- a/js/controller/SettingsController.js +++ b/js/controller/SettingsController.js @@ -15,9 +15,10 @@ var SEL_SETTING_CLS = 'has-expanded-drawer'; var EXP_DRAWER_CLS = 'expanded'; - ns.SettingsController = function () { + ns.SettingsController = function (framesheet) { + this.framesheet = framesheet; this.drawerContainer = document.getElementById("drawer-container"); - this.settingsContainer = $('.right-sticky-section'); + this.settingsContainer = $('[data-pskl-controller=settings]'); this.expanded = false; this.currentSetting = null; }; @@ -28,7 +29,7 @@ ns.SettingsController.prototype.init = function() { // Expand drawer when clicking 'Settings' tab. $('[data-setting]').click(function(evt) { - var el = event.currentTarget; + var el = evt.originalEvent.currentTarget; var setting = el.dataset.setting; if (this.currentSetting != setting) { this.loadSetting(setting); @@ -36,17 +37,25 @@ this.closeDrawer(); } }.bind(this)); + + $('body').click(function (evt) { + var isInSettingsContainer = $.contains(this.settingsContainer.get(0), evt.target); + if (this.expanded && !isInSettingsContainer) { + this.closeDrawer(); + } + }.bind(this)); }; ns.SettingsController.prototype.loadSetting = function (setting) { - this.drawerContainer.innerHTML = this.getTemplate_(settings[setting].template); - (new settings[setting].controller()).init(); + this.drawerContainer.innerHTML = pskl.utils.Template.get(settings[setting].template); + (new settings[setting].controller(this.framesheet)).init(); this.settingsContainer.addClass(EXP_DRAWER_CLS); $('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS); $('[data-setting='+setting+']').addClass(SEL_SETTING_CLS); + this.expanded = true; this.currentSetting = setting; }; @@ -54,16 +63,8 @@ this.settingsContainer.removeClass(EXP_DRAWER_CLS); $('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS); + this.expanded = false; this.currentSetting = null; }; - - ns.SettingsController.prototype.getTemplate_ = function (templateId) { - var template = document.getElementById(templateId); - if (template) { - return template.innerHTML; - } else { - console.error("Could not find template for id :", templateId); - } - }; })(); \ No newline at end of file diff --git a/js/controller/settings/GifExportController.js b/js/controller/settings/GifExportController.js index c3c36d4d..ff59abf2 100644 --- a/js/controller/settings/GifExportController.js +++ b/js/controller/settings/GifExportController.js @@ -1,8 +1,107 @@ (function () { var ns = $.namespace("pskl.controller.settings"); - ns.GifExportController = function () { - + ns.GifExportController = function (framesheet) { + this.framesheet = framesheet; }; - ns.GifExportController.prototype.init = function () {}; + ns.GifExportController.prototype.init = function () { + this.initRadioElements_(); + + this.previewContainer = document.querySelectorAll(".export-gif-preview div")[0]; + this.uploadForm = $("[name=gif-export-upload-form]"); + + this.uploadForm.submit(this.upload.bind(this)); + }; + + ns.GifExportController.prototype.upload = function (evt) { + evt.originalEvent.preventDefault(); + var selectedDpi = this.getSelectedDpi_(), + fps = pskl.app.animationController.fps, + dpi = selectedDpi; + + this.renderAsImageDataAnimatedGIF(dpi, fps, function (imageData) { + this.updatePreview_(imageData); + this.previewContainer.classList.add("preview-upload-ongoing"); + pskl.app.imageUploadService.upload(imageData, function (imageUrl) { + this.updatePreview_(imageUrl); + this.previewContainer.classList.remove("preview-upload-ongoing"); + }.bind(this)); + }.bind(this)); + }; + + ns.GifExportController.prototype.updatePreview_ = function (src) { + this.previewContainer.innerHTML = ""; + }; + + ns.GifExportController.prototype.getSelectedDpi_ = function () { + var radiosColl = this.uploadForm.get(0).querySelectorAll("[name=gif-dpi]"), + radios = Array.prototype.slice.call(radiosColl,0); + var selectedRadios = radios.filter(function(radio) {return !!radio.checked;}); + + if (selectedRadios.length == 1) { + return selectedRadios[0].value; + } else { + throw "Unexpected error when retrieving selected dpi"; + } + }; + + ns.GifExportController.prototype.initRadioElements_ = function () { + var dpis = [ + [1], + [5], + [10,true] //default + ]; + + var radioTpl = $("#export-gif-radio-template").get(0); + for (var i = 0 ; i < dpis.length ; i++) { + var dpi = dpis[i]; + var radio = this.createRadioForDpi_(dpi, radioTpl.innerHTML); + radioTpl.parentNode.insertBefore(radio, radioTpl); + } + }; + + ns.GifExportController.prototype.createRadioForDpi_ = function (dpi, template) { + var label = dpi[0]*this.framesheet.getWidth() + "x" + dpi[0]*this.framesheet.getHeight(); + var value = dpi[0]; + var radioHTML = pskl.utils.Template.replace(template, {value : value, label : label}); + var radio = pskl.utils.Template.createFromHTML(radioHTML); + + if (dpi[1]) { + radio.getElementsByTagName("input")[0].setAttribute("checked", "checked"); + } + + return radio; + }; + + ns.GifExportController.prototype.blobToBase64_ = function(blob, cb) { + var reader = new FileReader(); + reader.onload = function() { + var dataUrl = reader.result; + cb(dataUrl); + }; + reader.readAsDataURL(blob); + }; + + ns.GifExportController.prototype.renderAsImageDataAnimatedGIF = function(dpi, fps, cb) { + var gif = new window.GIF({ + workers: 2, + quality: 10, + width: this.framesheet.getWidth()*dpi, + height: this.framesheet.getHeight()*dpi + }); + + for (var i = 0; i < this.framesheet.frames.length; i++) { + var frame = this.framesheet.frames[i]; + var renderer = new pskl.rendering.CanvasRenderer(frame, dpi); + gif.addFrame(renderer.render(), { + delay: 1000 / fps + }); + } + + gif.on('finished', function(blob) { + this.blobToBase64_(blob, cb); + }.bind(this)); + + gif.render(); + }; })(); \ No newline at end of file diff --git a/js/piskel.js b/js/piskel.js index 923e51fa..9f32ad99 100644 --- a/js/piskel.js +++ b/js/piskel.js @@ -34,7 +34,7 @@ this.previewsController = new pskl.controller.PreviewFilmController(frameSheet, $('#preview-list')); this.previewsController.init(); - this.settingsController = new pskl.controller.SettingsController(); + this.settingsController = new pskl.controller.SettingsController(frameSheet); this.settingsController.init(); this.selectionManager = new pskl.selection.SelectionManager(frameSheet); @@ -52,6 +52,9 @@ this.localStorageService = new pskl.service.LocalStorageService(frameSheet); this.localStorageService.init(); + this.imageUploadService = new pskl.service.ImageUploadService(); + this.imageUploadService.init(); + this.toolController = new pskl.controller.ToolController(); this.toolController.init(); @@ -211,34 +214,18 @@ return false; }, - uploadToScreenletstore : function (imageData) { - var xhr = new XMLHttpRequest(); - var formData = new FormData(); - formData.append('data', imageData); - xhr.open('POST', "http://screenletstore.appspot.com/__/upload", true); - var cloudURL; - var that = this; - xhr.onload = function (e) { - if (this.status == 200) { - cloudURL = "http://screenletstore.appspot.com/img/" + this.responseText; - that.openWindow(cloudURL); - } - }; - - xhr.send(formData); - }, - uploadAsAnimatedGIF : function () { var fps = pskl.app.animationController.fps; var renderer = new pskl.rendering.SpritesheetRenderer(frameSheet); - var cb = this.uploadToScreenletstore.bind(this); - renderer.renderAsImageDataAnimatedGIF(fps, cb); + renderer.renderAsImageDataAnimatedGIF(fps, function (imageData) { + this.imageUploadService.upload(imageData, this.openWindow); + }.bind(this)); }, uploadAsSpritesheetPNG : function () { var imageData = (new pskl.rendering.SpritesheetRenderer(frameSheet)).renderAsImageDataSpritesheetPNG(); - this.uploadToScreenletstore(imageData); + this.imageUploadService.upload(imageData, this.openWindow); }, openWindow : function (url) { diff --git a/js/rendering/SpritesheetRenderer.js b/js/rendering/SpritesheetRenderer.js index e751c4c2..0a966e42 100644 --- a/js/rendering/SpritesheetRenderer.js +++ b/js/rendering/SpritesheetRenderer.js @@ -15,40 +15,6 @@ return canvas.toDataURL("image/png"); }; - ns.SpritesheetRenderer.prototype.blobToBase64_ = function(blob, cb) { - var reader = new FileReader(); - reader.onload = function() { - var dataUrl = reader.result; - cb(dataUrl); - }; - reader.readAsDataURL(blob); - }; - - ns.SpritesheetRenderer.prototype.renderAsImageDataAnimatedGIF = function(fps, cb) { - var dpi = 10; - var gif = new window.GIF({ - workers: 2, - quality: 10, - width: 320, - height: 320 - }); - - for (var i = 0; i < this.framesheet.frames.length; i++) { - var frame = this.framesheet.frames[i]; - var renderer = new pskl.rendering.CanvasRenderer(frame, dpi); - gif.addFrame(renderer.render(), { - delay: 1000 / fps - }); - } - - gif.on('finished', function(blob) { - this.blobToBase64_(blob, cb); - }.bind(this)); - - gif.render(); - }; - - /** * TODO(juliandescottes): Mutualize with code already present in FrameRenderer */ diff --git a/js/service/ImageUploadService.js b/js/service/ImageUploadService.js new file mode 100644 index 00000000..30954a07 --- /dev/null +++ b/js/service/ImageUploadService.js @@ -0,0 +1,34 @@ +(function () { + var ns = $.namespace("pskl.service"); + ns.ImageUploadService = function () { + this.serviceUrl_ = "http://screenletstore.appspot.com/__/upload"; + }; + + ns.ImageUploadService.prototype.init = function () { + // service interface + }; + + /** + * Upload a base64 image data to distant service. If successful, will call provided callback with the image URL as first argument; + * @param {String} imageData base64 image data (such as the return value of canvas.toDataUrl()) + * @param {Function} cbSuccess success callback. 1st argument will be the uploaded image URL + * @param {Function} cbError error callback + */ + ns.ImageUploadService.prototype.upload = function (imageData, cbSuccess, cbError) { + var xhr = new XMLHttpRequest(); + var formData = new FormData(); + formData.append('data', imageData); + xhr.open('POST', this.serviceUrl_, true); + xhr.onload = function (e) { + if (this.status == 200) { + var imageUrl = "http://screenletstore.appspot.com/img/" + this.responseText; + cbSuccess(imageUrl); + } else { + cbError(); + } + }; + + xhr.send(formData); + }; + +})(); \ No newline at end of file diff --git a/js/utils/Template.js b/js/utils/Template.js new file mode 100644 index 00000000..3eda787d --- /dev/null +++ b/js/utils/Template.js @@ -0,0 +1,30 @@ +(function () { + var ns = $.namespace("pskl"); + + ns.utils.Template = { + get : function (templateId) { + var template = document.getElementById(templateId); + if (template) { + return template.innerHTML; + } else { + console.error("Could not find template for id :", templateId); + } + }, + + createFromHTML : function (html) { + var dummyEl = document.createElement("div"); + dummyEl.innerHTML = html; + return dummyEl.children[0]; + }, + + replace : function (template, dict) { + for (var key in dict) { + if (dict.hasOwnProperty(key)) { + var value = dict[key]; + template = template.replace(new RegExp('\\{\\{'+key+'\\}\\}', 'g'), value); + } + } + return template; + } + }; +})(); \ No newline at end of file diff --git a/piskel-boot.js b/piskel-boot.js index 2ac97da7..2c2149ce 100644 --- a/piskel-boot.js +++ b/piskel-boot.js @@ -9,6 +9,9 @@ if (window.location.href.indexOf("debug") != -1) { window.exports = {}; + //debug shortcuts + cl = function () {console.log.apply(console, arguments)}; + d = function () {debugger}; var scriptIndex = 0; window.loadNextScript = function () { if (scriptIndex == exports.scripts.length) { diff --git a/piskel-script-list.js b/piskel-script-list.js index f0daebef..fb53bda2 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -13,6 +13,7 @@ exports.scripts = [ // Libraries "js/utils/core.js", + "js/utils/Template.js", "js/utils/PixelUtils.js", "js/utils/CanvasUtils.js", "js/utils/UserSettings.js", @@ -49,6 +50,7 @@ exports.scripts = [ "js/service/LocalStorageService.js", "js/service/HistoryService.js", "js/service/KeyboardEventService.js", + "js/service/ImageUploadService.js", // Tools "js/drawingtools/BaseTool.js", diff --git a/templates/settings-export-gif.html b/templates/settings-export-gif.html index 41e2196c..12fff2d6 100644 --- a/templates/settings-export-gif.html +++ b/templates/settings-export-gif.html @@ -1 +1,16 @@ -LOLOLOLOL \ No newline at end of file +
+
+ Export to Animated GIF +
+
+ +
+ + +
+
+
+
\ No newline at end of file