commit 69cfec68bd5202a1fbc349976938704bf1dbb80c Author: artur root Date: Fri Jan 15 14:28:17 2016 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4e3d79d16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.settings/ +.project + +private/ +libs/ +node_modules/ diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..f2bf0afb2 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,137 @@ +module.exports = function(grunt) { + + var appPath = 'bundle', + buildPath = 'dist', + configPath = 'config/require-config.js'; + + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-sass'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-requirejs'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-mocha'); + + grunt.initConfig({ + appDir: appPath, + builtDir: buildPath, + pkg: grunt.file.readJSON("package.json"), + requirejs:{ + compile:{ + options: { + mainConfigFile: '<%= appDir %>/' + configPath, + appDir: '<%= appDir %>', + dir: '<%= builtDir %>', + baseUrl: './', + name: 'main', + removeCombined: true, + findNestedDependencies: true, + keepBuildDir: true, + inlineText: true, + optimize: 'none' + //paths: { "jquery": "empty:" }, //try to exclude + } + } + }, + + jshint: { + all: [ + 'Gruntfile.js', + '<%= appDir %>/**/*.js', + ] + }, + + uglify: { + options: { + banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */' + }, + build:{ + files: { + '<%= builtDir %>/grapes.min.js': ['<%= builtDir %>/main.js'] + } + } + }, + + sass: { + dist: { + files: [{ + expand: true, + cwd: 'styles/scss', + src: ['**/*.scss'], + dest: 'styles/css', + ext: '.css' + }] + } + }, + + mocha: { + test: { + src: ['test/index.html'], + options: { log: true, }, + }, + }, + + connect: { + /* + app: { + options: { + port: 8001, + open: { + target: 'http://localhost:8001', + //appName: 'Firefox' // 'Google Chrome' + } + } + }, + */ + test: { + options: { + open: { + target: 'http://localhost:8000/test', + } + } + } + }, + + watch: { + script: { + files: [ '<%= appDir %>/**/*.js' ], + tasks: ['jshint'] + }, + css: { + files: '**/*.scss', + tasks: ['sass'] + }, + test: { + files: [ 'test/specs/**/*.js' ], + tasks: ['mocha'], + options: { livereload: true }, //default port 35729 + } + } + + }); + + /** + * Need to copy require configs cause r.js will try to load them from the path indicated inside + * main.js file. This is the only way I have found to do it and only for the pleasure of using separate config + * requirejs file. + * */ + grunt.registerTask('before-requirejs', function() { + //if(grunt.file.exists(buildPath)) + //grunt.file.delete(buildPath); + grunt.file.mkdir(buildPath); + grunt.file.copy(appPath + '/' + configPath, buildPath + '/' + appPath + '/' + configPath); + }); + + grunt.registerTask('after-requirejs', function() { + //grunt.file.copy(buildPath + '/main.js', buildPath + '/main.min.js'); + }); + + grunt.registerTask('dev', ['connect', 'watch']); + + grunt.registerTask('test', ['mocha']); + + grunt.registerTask('deploy', ['jshint', 'before-requirejs', 'requirejs', 'after-requirejs', 'uglify']); + + grunt.registerTask('default', ['dev']); + +}; \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..58b6e3575 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,27 @@ +# Grapes.js + +Copyright (c) Artur Arseniev +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +- Neither the name "Grapes" nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..39af52c07 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# README # + +This README would normally document whatever steps are necessary to get your application up and running. + +### What is this repository for? ### + +* Quick summary +* Version +* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) + +### How do I get set up? ### + +* Summary of set up +* Configuration +* Dependencies +* Database configuration +* How to run tests +* Deployment instructions + +### Contribution guidelines ### + +* Writing tests +* Code review +* Other guidelines + +### Who do I talk to? ### + +* Repo owner or admin +* Other community or team contact \ No newline at end of file diff --git a/bundle/asset_manager/config/config.js b/bundle/asset_manager/config/config.js new file mode 100644 index 000000000..3271eac9e --- /dev/null +++ b/bundle/asset_manager/config/config.js @@ -0,0 +1,52 @@ +define(function () { + return { + + // Style prefix + stylePrefix : 'am-', + + // Default assets + assets : [], + + // Indicates which storage to use. Available: local | remote + storageType : 'local', + + // The name that will be used to identify assets inside storage. + // If empty will be used: prefix + 'assets' + storageName : 'assets', + + // Where store remote assets + urlStore : 'http://localhost/assets/store', + + // Where fetch remote assets + urlLoad : 'http://localhost/assets/load', + + // Custom parameters to pass with set request + paramsStore : {}, + + // Custom parameters to pass with get request + paramsLoad : {}, + + // Callback before request + beforeSend : function(jqXHR,settings){}, + + // Callback after request + onComplete : function(jqXHR,status){}, + + // Url where uploads will be send + urlUpload : 'http://localhost/assets/upload', + + // Text on upload input + uploadText : 'Drop files here or click to upload', + + // Disable upload input + disableUpload : false, + + // Store assets data where the new one is added or deleted + storeOnChange : true, + + // It could be useful avoid to send other requests, for saving assets, + // after each upload because the uploader script has already done it + storeAfterUpload : false, + + }; +}); \ No newline at end of file diff --git a/bundle/asset_manager/main.js b/bundle/asset_manager/main.js new file mode 100644 index 000000000..d37449ec1 --- /dev/null +++ b/bundle/asset_manager/main.js @@ -0,0 +1,70 @@ +define(function(require) { + /** + * @class AssetManager + * @param {Object} Configurations + * + * @return {Object} + * */ + var AssetManager = function(config) + { + var c = config || {}, + defaults = require('./config/config'), + Assets = require('./model/Assets'), + AssetsView = require('./view/AssetsView'), + FileUpload = require('./view/FileUploader'); + + for (var name in defaults) { + if (!(name in c)) + c[name] = defaults[name]; + } + + this.assets = new Assets(c.assets); + var obj = { + collection : this.assets, + config : c, + }; + + this.am = new AssetsView(obj); + this.fu = new FileUpload(obj); + }; + + AssetManager.prototype = { + + /** + * Get collection of assets + * + * @return {Object} + * */ + getAssets : function(){ + return this.assets; + }, + + /** + * Set new target + * @param {Object} m Model + * + * @return void + * */ + setTarget : function(m){ + this.am.collection.target = m; + }, + + /** + * Set callback after asset was selected + * @param {Object} f Callback function + * + * @return void + * */ + onSelect : function(f){ + this.am.collection.onSelect = f; + }, + + render : function(){ + if(!this.rendered) + this.rendered = this.am.render().$el.add(this.fu.render().$el); + return this.rendered; + }, + }; + + return AssetManager; +}); \ No newline at end of file diff --git a/bundle/asset_manager/model/Asset.js b/bundle/asset_manager/model/Asset.js new file mode 100644 index 000000000..810e4dad6 --- /dev/null +++ b/bundle/asset_manager/model/Asset.js @@ -0,0 +1,36 @@ +define(['backbone'], + function (Backbone) { + /** + * @class Asset + * */ + return Backbone.Model.extend({ + + defaults: { + type: 'none', //Type of the asset + src: '', //Location + }, + + initialize: function(options) { + this.options = options || {}; + }, + + /** + * Get filename of the asset + * + * @return {String} + * */ + getFilename: function(){ + return this.get('src').split('/').pop(); + }, + + /** + * Get extension of the asset + * + * @return {String} + * */ + getExtension: function(){ + return this.getFilename().split('.').pop(); + }, + + }); +}); diff --git a/bundle/asset_manager/model/AssetImage.js b/bundle/asset_manager/model/AssetImage.js new file mode 100644 index 000000000..b43ade7df --- /dev/null +++ b/bundle/asset_manager/model/AssetImage.js @@ -0,0 +1,18 @@ +define(['backbone', './Asset'], + function (Backbone, Asset) { + /** + * @class AssetImage + * */ + return Asset.extend({ + + defaults: _.extend({},Asset.prototype.defaults, + { + type: 'image', + unitDim: 'px', + height: 0, + width: 0, + } + ), + + }); +}); diff --git a/bundle/asset_manager/model/Assets.js b/bundle/asset_manager/model/Assets.js new file mode 100644 index 000000000..a78092357 --- /dev/null +++ b/bundle/asset_manager/model/Assets.js @@ -0,0 +1,11 @@ +define(['backbone','./Asset'], + function (Backbone, Asset) { + /** + * @class Assets + * */ + return Backbone.Collection.extend({ + + model: Asset, + + }); +}); diff --git a/bundle/asset_manager/template/assetImage.html b/bundle/asset_manager/template/assetImage.html new file mode 100644 index 000000000..afcb72b44 --- /dev/null +++ b/bundle/asset_manager/template/assetImage.html @@ -0,0 +1,7 @@ +
+
+
<%= name %>
+
<%= dim %>
+
+
+
\ No newline at end of file diff --git a/bundle/asset_manager/template/fileUploader.html b/bundle/asset_manager/template/fileUploader.html new file mode 100644 index 000000000..8f8a07751 --- /dev/null +++ b/bundle/asset_manager/template/fileUploader.html @@ -0,0 +1,5 @@ +
+
<%= title %>
+ multiple/> +
+
\ No newline at end of file diff --git a/bundle/asset_manager/view/AssetImageView.js b/bundle/asset_manager/view/AssetImageView.js new file mode 100644 index 000000000..a6f1c4e5d --- /dev/null +++ b/bundle/asset_manager/view/AssetImageView.js @@ -0,0 +1,89 @@ +define(['./AssetView','text!./../template/assetImage.html'], + function (AssetView, assetTemplate) { + /** + * @class AssetImageView + * */ + return AssetView.extend({ + + events:{ + 'click' : 'selected', + 'dblclick' : 'chosen', + }, + + template: _.template(assetTemplate), + + initialize: function(o) { + AssetView.prototype.initialize.apply(this, arguments); + this.className += ' ' + this.pfx + 'asset-image'; + this.events['click #' + this.pfx + 'close'] = 'removeItem'; + }, + + /** + * Trigger when asset is been selected + * + * @return void + * */ + selected: function(){ + this.model.collection.trigger('deselectAll'); + this.$el.addClass(this.pfx + 'highlight'); + + this.updateTarget(this.model.get('src')); + }, + + /** + * Trigger when asset is been chosen (double clicked) + * + * @return void + * */ + chosen: function(){ + this.updateTarget(this.model.get('src')); + var f = this.model.collection.onSelect; + if(f && typeof f == 'function'){ + f(this.model); + } + }, + + /** + * Update target if exists + * @param {String} v Value + * + * @return void + * */ + updateTarget: function(v){ + var target = this.model.collection.target; + if(target && target.set){ + var attr = _.clone( target.get('attributes') ); + attr['class'] = []; + target.set('attributes', attr ); + target.set('src', v ); + } + }, + + /** + * Remove asset from collection + * + * @return void + * */ + removeItem: function(e){ + e.stopPropagation(); + this.model.collection.remove(this.model); + }, + + render : function(){ + var name = this.model.get('name'), + dim = this.model.get('width') && this.model.get('height') ? + this.model.get('width')+' x '+this.model.get('height') : ''; + name = name ? name : this.model.get('src').split("/").pop(); + name = name && name.length > 30 ? name.substring(0, 30)+'...' : name; + dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : ''; + this.$el.html( this.template({ + name: name, + src: this.model.get('src'), + dim: dim, + pfx: this.pfx + })); + this.$el.attr('class', this.className); + return this; + }, + }); +}); diff --git a/bundle/asset_manager/view/AssetView.js b/bundle/asset_manager/view/AssetView.js new file mode 100644 index 000000000..1658a0d31 --- /dev/null +++ b/bundle/asset_manager/view/AssetView.js @@ -0,0 +1,17 @@ +define(['backbone'], + function (Backbone) { + /** + * @class AssetView + * */ + return Backbone.View.extend({ + + initialize: function(o) { + this.options = o; + this.config = o.config || {}; + this.pfx = this.config.stylePrefix; + this.className = this.pfx + 'asset'; + this.listenTo( this.model, 'destroy remove', this.remove ); + }, + + }); +}); diff --git a/bundle/asset_manager/view/AssetsView.js b/bundle/asset_manager/view/AssetsView.js new file mode 100644 index 000000000..342816300 --- /dev/null +++ b/bundle/asset_manager/view/AssetsView.js @@ -0,0 +1,122 @@ +define(['backbone', './AssetView', './AssetImageView', './FileUploader'], + function (Backbone, AssetView, AssetImageView, FileUploader) { + /** + * @class AssetsView + * */ + return Backbone.View.extend({ + + initialize: function(o) { + this.options = o; + this.config = o.config; + this.pfx = this.config.stylePrefix; + this.listenTo( this.collection, 'add', this.addToAsset ); + this.listenTo( this.collection, 'deselectAll', this.deselectAll ); + this.className = this.pfx + 'assets'; + + // Check if storage is required and if Storage Manager is available + if(this.config.stm && this.config.storageType !== ''){ + var type = this.config.storageType; + this.provider = this.config.stm.getProvider(type); + this.storeName = this.config.storageName ? this.config.storageName : this.className; + if(this.provider){ + // Create new instance of provider + this.storagePrv = this.provider.clone().set(this.config); + this.collection.reset(); + this.collection.add(this.load()); + if(this.config.storeOnChange){ + var ev = 'remove' + (this.config.storeAfterUpload ? ' add' : ''); + this.listenTo(this.collection, ev, this.store); + } + } + } + }, + + /** + * Store collection + * + * @return void + * */ + store: function(){ + if(this.storagePrv) + this.storagePrv.store(this.storeName, JSON.stringify(this.collection.toJSON()) ); + }, + + /** + * Load collection + * + * @return {Object} + * */ + load: function(){ + var result = null; + if(this.storagePrv) + result = this.storagePrv.load(this.storeName); + if(typeof result !== 'object'){ + try{ + result = JSON.parse(result); + }catch(err){ + console.warn(err); + } + } + return result; + }, + + /** + * Add asset to collection + * */ + addToAsset: function(model){ + this.addAsset(model); + }, + + /** + * Add new asset to collection + * @param Object Model + * @param Object Fragment collection + * + * @return Object Object created + * */ + addAsset: function(model, fragmentEl){ + var fragment = fragmentEl || null; + var viewObject = AssetView; + + if(model.get('type').indexOf("image") > -1) + viewObject = AssetImageView; + + var view = new viewObject({ + model : model, + config : this.config, + }); + var rendered = view.render().el; + + if(fragment){ + fragment.appendChild( rendered ); + }else{ + this.$el.prepend(rendered); + } + + return rendered; + }, + + /** + * Deselect all assets + * + * @return void + * */ + deselectAll: function(){ + this.$el.find('.' + this.pfx + 'highlight').removeClass(this.pfx + 'highlight'); + }, + + render: function() { + var fragment = document.createDocumentFragment(); + this.$el.empty(); + + this.collection.each(function(model){ + this.addAsset(model, fragment); + },this); + + this.$el.append(fragment); + this.$el.attr('class', this.className); + + return this; + } + }); +}); diff --git a/bundle/asset_manager/view/FileUploader.js b/bundle/asset_manager/view/FileUploader.js new file mode 100644 index 000000000..667a009b3 --- /dev/null +++ b/bundle/asset_manager/view/FileUploader.js @@ -0,0 +1,102 @@ +define(['backbone', 'text!./../template/fileUploader.html'], + function (Backbone, fileUploaderTemplate) { + /** + * @class FileUploader + * */ + + return Backbone.View.extend({ + + template: _.template(fileUploaderTemplate), + + events: {}, + + initialize: function(o) { + this.options = o || {}; + this.config = o.config || {}; + this.pfx = this.config.stylePrefix; + this.target = this.collection || {}; + this.uploadId = this.pfx + 'uploadFile'; + this.disabled = this.config.disableUpload; + this.events['change #' + this.uploadId] = 'uploadFile'; + }, + + /** + * Upload files + * @param {Object} e Event + * + * @return void + * */ + uploadFile : function(e){ + var files = e.dataTransfer ? e.dataTransfer.files : e.target.files, + formData = new FormData(); + for (var i = 0; i < files.length; i++) { + formData.append('files[]', files[i]); + } + var target = this.target; + $.ajax({ + url : this.config.urlUpload, //this.config.urlUpload + type : 'POST', + data : formData, + beforeSend : this.config.beforeSend, + complete : this.config.onComplete, + xhrFields : { + onprogress: function (e) { + if (e.lengthComputable) { + /*var result = e.loaded / e.total * 100 + '%';*/ + } + }, + onload: function (e) { + //progress.value = 100; + } + }, + cache: false, contentType: false, processData: false + }).done(function(data){ + target.add(data.data); + }).always(function(){ + //turnOff loading + }); + }, + + /** + * Make input file droppable + * + * @return void + * */ + initDrop: function(){ + var that = this; + if(!this.uploadForm){ + this.uploadForm = this.$el.find('form').get(0); + if( 'draggable' in this.uploadForm ){ + var uploadFile = this.uploadFile; + this.uploadForm.ondragover = function(){ + this.className = that.pfx + 'hover'; + return false; + }; + this.uploadForm.ondragleave = function(){ + this.className = ''; + return false; + }; + this.uploadForm.ondrop = function(e){ + this.className = ''; + e.preventDefault(); + that.uploadFile(e); + return; + }; + } + } + }, + + render : function(){ + this.$el.html( this.template({ + title : this.config.uploadText, + uploadId : this.uploadId, + disabled : this.disabled, + pfx : this.pfx + }) ); + this.initDrop(); + this.$el.attr('class', this.pfx + 'file-uploader'); + return this; + }, + + }); +}); diff --git a/bundle/canvas/config/config.js b/bundle/canvas/config/config.js new file mode 100644 index 000000000..449f904e7 --- /dev/null +++ b/bundle/canvas/config/config.js @@ -0,0 +1,10 @@ +define(function () { + return { + + stylePrefix : 'cv-', + + // Coming soon + rulers : false, + + }; +}); \ No newline at end of file diff --git a/bundle/canvas/main.js b/bundle/canvas/main.js new file mode 100644 index 000000000..318a33bb5 --- /dev/null +++ b/bundle/canvas/main.js @@ -0,0 +1,60 @@ +define(function(require) { + /** + * @class Canvas + * @param {Object} Configurations + * + * @return {Object} + * */ + var Canvas = function(config) + { + var c = config || {}, + defaults = require('./config/config'), + Canvas = require('./model/Canvas'), + CanvasView = require('./view/CanvasView'); + + for (var name in defaults) { + if (!(name in c)) + c[name] = defaults[name]; + } + + this.canvas = new Canvas(config); + var obj = { + model : this.canvas, + config : c, + }; + + this.CanvasView = new CanvasView(obj); + }; + + Canvas.prototype = { + /** + * Add wrapper + * @param {Object} wrp Wrapper + * + * */ + setWrapper : function(wrp) + { + this.canvas.set('wrapper', wrp); + }, + + /** + * Get wrapper + * + * @return {Object} + * */ + getWrapper : function() + { + return this.canvas.get('wrapper').getComponent(); + }, + + /** + * Render canvas + * */ + render : function() + { + return this.CanvasView.render().$el; + }, + }; + + return Canvas; +}); \ No newline at end of file diff --git a/bundle/canvas/model/Canvas.js b/bundle/canvas/model/Canvas.js new file mode 100644 index 000000000..c096c8744 --- /dev/null +++ b/bundle/canvas/model/Canvas.js @@ -0,0 +1,14 @@ +define(['backbone'], + function(Backbone){ + /** + * @class Canvas + * */ + return Backbone.Model.extend({ + + defaults :{ + wrapper : '', + rulers : false, + }, + + }); + }); diff --git a/bundle/canvas/view/CanvasView.js b/bundle/canvas/view/CanvasView.js new file mode 100644 index 000000000..101274bb4 --- /dev/null +++ b/bundle/canvas/view/CanvasView.js @@ -0,0 +1,25 @@ +define(['backbone'], +function(Backbone) { + /** + * @class CanvasView + * */ + return Backbone.View.extend({ + + id: 'canvas', + + initialize: function(o) { + this.config = o.config; + this.className = this.config.stylePrefix + 'canvas'; + }, + + render: function() { + this.wrapper = this.model.get('wrapper'); + if(this.wrapper && typeof this.wrapper.render == 'function'){ + this.$el.append( this.wrapper.render() ); + } + this.$el.attr('class', this.className); + return this; + }, + + }); +}); \ No newline at end of file diff --git a/bundle/code_manager/config/config.js b/bundle/code_manager/config/config.js new file mode 100644 index 000000000..9af9e059a --- /dev/null +++ b/bundle/code_manager/config/config.js @@ -0,0 +1,6 @@ +define(function () { + return { + // Style prefix + stylePrefix : 'cm-', + }; +}); \ No newline at end of file diff --git a/bundle/code_manager/main.js b/bundle/code_manager/main.js new file mode 100644 index 000000000..e88de7755 --- /dev/null +++ b/bundle/code_manager/main.js @@ -0,0 +1,254 @@ +define(function(require) { + /** + * @class CodeManager + * @param {Object} Configurations + * + * @return {Object} + * */ + function CodeManager(config) + { + var c = config || {}, + defaults = require('./config/config'), + gInterface = require('./model/GeneratorInterface'), + gHtml = require('./model/HtmlGenerator'), + gCss = require('./model/CssGenerator'), + gJson = require('./model/JsonGenerator'), + eInterface = require('./model/EditorInterface'), + eCM = require('./model/CodeMirrorEditor'), + editorView = require('./view/EditorView'); + + for (var name in defaults) { + if (!(name in c)) + c[name] = defaults[name]; + } + + this.gi = new gInterface(); + this.generators = {}; + this.defaultGenerators = {}; + this.currentGenerator = null; + + this.ei = new eInterface(); + this.editors = {}; + this.defaultEditors = {}; + this.currentEditor = null; + + var geHtml = new gHtml(), + geCss = new gCss(), + geJson = new gJson(), + edCM = new eCM(); + + this.defaultGenerators[geHtml.getId()] = geHtml; + this.defaultGenerators[geCss.getId()] = geCss; + this.defaultGenerators[geJson.getId()] = geJson; + + this.defaultEditors[edCM.getId()] = edCM; + + this.EditorView = editorView; + this.config = c; + } + + CodeManager.prototype = { + + /** + * Add new code generator + * @param {GeneratorInterface} generator + * + * @return this + * */ + addGenerator : function(generator) + { + // Check interface implementation + for (var method in this.gi) + if(!generator[method]){ + console.warn("addGenerator: method '"+ method +"' was not found"); + return; + } + + var id = generator.getId(); + this.generators[id] = generator; + + if(!this.currentGenerator) + this.currentGenerator = id; + + return this; + }, + + /** + * Returns generator + * @param {String}|{Integer} id Generator ID + * + * @return {GeneratorInterface}|null + * */ + getGenerator : function(id) + { + if(id && this.generators[id]) + generator = this.generators[id]; + + return generator ? generator : null; + }, + + /** + * Returns generators + * + * @return {Array} + * */ + getGenerators : function() + { + return this.generators; + }, + + /** + * Get current generator + * + * @return {GeneratorInterface} + * */ + getCurrentGenerator : function() + { + if(!this.currentGenerator) + this.loadDefaultGenerators(); + return this.getGenerator(this.currentGenerator); + }, + + /** + * Set current generator + * @param {Integer} id Generator ID + * + * @return this + * */ + setCurrentGenerator : function(id) + { + this.currentGenerator = id; + return this; + }, + + /** + * Load default generators + * + * @return this + * */ + loadDefaultGenerators : function() + { + for (var id in this.defaultGenerators) { + this.addGenerator(this.defaultGenerators[id]); + } + + return this; + }, + + /** + * Add new editor + * @param {EditorInterface} editor + * + * @return this + * */ + addEditor : function(editor) + { + // Check interface implementation + for (var method in this.ei) + if(!editor[method]){ + console.warn("addEditor: method '"+ method +"' was not found"); + return; + } + + var id = editor.getId(); + this.editors[id] = editor; + + if(!this.currentEditor) + this.currentEditor = id; + + return this; + }, + + /** + * Returns editor + * @param {String}|{Integer} id Editor ID + * + * @return {EditorInterface}|null + * */ + getEditor : function(id) + { + if(id && this.editors[id]) + editor = this.editors[id]; + + return editor ? editor : null; + }, + + /** + * Returns editors + * + * @return {Array} + * */ + getEditors : function() + { + return this.editors; + }, + + /** + * Get current editor + * + * @return {EditorInterface} + * */ + getCurrentEditor : function() + { + if(!this.currentEditor) + this.loadDefaultEditors(); + return this.getEditor(this.currentEditor); + }, + + /** + * Set current editor + * @param {Integer} id Editor ID + * + * @return this + * */ + setCurrentEditor : function(id) + { + this.currentEditor = id; + return this; + }, + + /** + * Load default editors + * + * @return this + * */ + loadDefaultEditors : function() + { + for (var id in this.defaultEditors) { + this.addEditor(this.defaultEditors[id]); + } + + return this; + }, + + /** + * Get code by name + * @param {Backbone.Model} model Model + * @param {String}|{Integer} v Id of code generator + * + * @return {String}|null + * */ + getCode : function(model, v) + { + var id = v || this.currentGenerator, + generator = this.generators[id]; + return generator ? generator.build(model) : null; + }, + + /** + * Update editor content + * @param {EditorInteface} editor Editor + * @param {String} code Code value + * + * @return void + * */ + updateEditor : function(editor, code) + { + editor.setContent(code); + }, + + + }; + + return CodeManager; +}); \ No newline at end of file diff --git a/bundle/code_manager/model/CodeMirrorEditor.js b/bundle/code_manager/model/CodeMirrorEditor.js new file mode 100644 index 000000000..47705b408 --- /dev/null +++ b/bundle/code_manager/model/CodeMirrorEditor.js @@ -0,0 +1,57 @@ +define(['backbone', + 'text!../../../libs/codemirror/lib/codemirror.css', + '../../../libs/codemirror/lib/codemirror', + '../../../libs/codemirror/mode/htmlmixed/htmlmixed', + '../../../libs/codemirror/mode/css/css', + '../../../libs/codemirror/lib/util/formatting' + ], + function(Backbone, CodeMirrorStyle, CodeMirror, htmlMode, cssMode, formatting ) { + /** + * @class CodeViewer + * */ + return Backbone.Model.extend({ + + defaults: { + input : '', + label : '', + codeName : '', + theme : '', + readOnly : true, + lineNumbers : true, + }, + + /** @inheritdoc */ + getId : function() + { + return 'CodeMirror'; + }, + + /** @inheritdoc */ + init: function(el) + { + this.editor = CodeMirror.fromTextArea(el, { + dragDrop : false, + lineNumbers : this.get('lineNumbers'), + readOnly : this.get('readOnly'), + mode : this.get('codeName'), + theme : this.get('theme'), + }); + + return this; + }, + + /** @inheritdoc */ + setContent : function(v) + { + if(!this.editor) + return; + this.editor.setValue(v); + if(this.editor.autoFormatRange){ + CodeMirror.commands.selectAll(this.editor); + this.editor.autoFormatRange(this.editor.getCursor(true), this.editor.getCursor(false) ); + CodeMirror.commands.goDocStart(this.editor); + } + }, + + }); + }); \ No newline at end of file diff --git a/bundle/code_manager/model/CssGenerator.js b/bundle/code_manager/model/CssGenerator.js new file mode 100644 index 000000000..60162cffb --- /dev/null +++ b/bundle/code_manager/model/CssGenerator.js @@ -0,0 +1,44 @@ +define(['backbone'], + function (Backbone) { + /** + * @class CssGenerator + * */ + return Backbone.Model.extend({ + + /** @inheritdoc */ + getId : function() + { + return 'css'; + }, + + /** @inheritdoc */ + build: function(model) + { + + var coll = model.get('components') || model, + code = ''; + + coll.each(function(m){ + var css = m.get('style'), + cln = m.get('components'); // Children + + if(css && Object.keys(css).length !== 0){ + code += '#' + m.cid + '{'; + + for(var prop in css) + if(css.hasOwnProperty(prop)) + code += prop + ': ' + css[prop] + ';'; + + code += '}'; + } + + if(cln.length) + code += this.build(cln); + + }, this); + + return code; + }, + + }); +}); diff --git a/bundle/code_manager/model/EditorInterface.js b/bundle/code_manager/model/EditorInterface.js new file mode 100644 index 000000000..a47a6b3e2 --- /dev/null +++ b/bundle/code_manager/model/EditorInterface.js @@ -0,0 +1,31 @@ +define(function() { + /** + * @class EditorInterface + * */ + function EditorInterface() {} + + EditorInterface.prototype = { + + /** + * Get id + * + * @return {String}|{Integer} + * */ + getId : function(){}, + + /** + * Set content + * @param {String} str + * + * */ + setContent : function(str){}, + + /** + * Init editor + * @param {Object} el DOM element + * */ + init : function(el){}, + }; + + return EditorInterface; +}); \ No newline at end of file diff --git a/bundle/code_manager/model/GeneratorInterface.js b/bundle/code_manager/model/GeneratorInterface.js new file mode 100644 index 000000000..91cee0ebe --- /dev/null +++ b/bundle/code_manager/model/GeneratorInterface.js @@ -0,0 +1,26 @@ +define(function() { + /** + * @class GeneratorInterface + * */ + function GeneratorInterface() {} + + GeneratorInterface.prototype = { + + /** + * Get id + * + * @return {String}|{Integer} + * */ + getId : function(){}, + + /** + * Generate code from model + * @param {Backbone.Model} model + * + * @return {String} + * */ + build : function(model){}, + }; + + return GeneratorInterface; +}); \ No newline at end of file diff --git a/bundle/code_manager/model/HtmlGenerator.js b/bundle/code_manager/model/HtmlGenerator.js new file mode 100644 index 000000000..ca08caf1d --- /dev/null +++ b/bundle/code_manager/model/HtmlGenerator.js @@ -0,0 +1,39 @@ +define(['backbone'], + function (Backbone) { + /** + * @class HtmlGenerator + * */ + return Backbone.Model.extend({ + + /** @inheritdoc */ + getId : function(){ + return 'html'; + }, + + /** @inheritdoc */ + build: function(model){ + var coll = model.get('components') || model, + code = ''; + + coll.each(function(m){ + var tag = m.get('tagName'), // Tag name + attr = '', // Attributes string + cln = m.get('components'); // Children + + _.each(m.get('attributes'),function(value, prop){ + attr += value && prop!='style' ? ' ' + prop + '="' + value + '" ' : ''; + }); + + code += '<'+tag+' id="'+m.cid+'"' + attr + '>' + m.get('content'); + + if(cln.length) + code += this.build(cln); + + code += ''; + }, this); + + return code; + }, + + }); +}); diff --git a/bundle/code_manager/model/JsonGenerator.js b/bundle/code_manager/model/JsonGenerator.js new file mode 100644 index 000000000..d8920f5f7 --- /dev/null +++ b/bundle/code_manager/model/JsonGenerator.js @@ -0,0 +1,40 @@ +define(['backbone'], + function (Backbone) { + /** + * @class JsonGenerator + * */ + return Backbone.Model.extend({ + + /** @inheritdoc */ + getId : function() + { + return 'json'; + }, + + /** @inheritdoc */ + build: function(model) + { + var json = model.toJSON(); + + // Avoid jshint 'loopfunc' error + _.each(json,function(v, attr){ + var obj = json[attr]; + if(obj instanceof Backbone.Model){ + json[attr] = this.build(obj); + }else if(obj instanceof Backbone.Collection){ + var coll = obj; + json[attr] = []; + if(coll.length){ + coll.each(function (el, index) { + json[attr][index] = this.build(el); + }, this); + } + } + + }, this); + + return json; + }, + + }); +}); diff --git a/bundle/code_manager/template/editor.html b/bundle/code_manager/template/editor.html new file mode 100644 index 000000000..f8148afbd --- /dev/null +++ b/bundle/code_manager/template/editor.html @@ -0,0 +1,4 @@ +
+
<%= label %>
+
+
diff --git a/bundle/code_manager/view/EditorView.js b/bundle/code_manager/view/EditorView.js new file mode 100644 index 000000000..5c010d015 --- /dev/null +++ b/bundle/code_manager/view/EditorView.js @@ -0,0 +1,25 @@ +define(['backbone', 'text!./../template/editor.html'], + function (Backbone, vTemplate) { + /** + * @class EditorView + * */ + return Backbone.View.extend({ + + template: _.template(vTemplate), + + initialize: function(o){ + this.config = o.config || {}; + this.pfx = this.config.stylePrefix; + }, + + render : function(){ + var obj = this.model.toJSON(); + obj.pfx = this.pfx; + this.$el.html( this.template(obj) ); + this.$el.attr('class', this.pfx + 'editor-c'); + this.$el.find('#'+this.pfx+'code').html(this.model.get('input')); + return this; + }, + + }); +}); diff --git a/bundle/commands/config/config.js b/bundle/commands/config/config.js new file mode 100644 index 000000000..d82eb6611 --- /dev/null +++ b/bundle/commands/config/config.js @@ -0,0 +1,25 @@ +define(function () { + return { + + ESCAPE_KEY : 27, + + stylePrefix : 'com-', + + defaults : [], + + // Editor model + em : null, + + // If true center new first-level components + firstCentered : true, + + // If true the new component will created with 'height', else 'min-height' + newFixedH : false, + + // Minimum height (in px) of new component + minComponentH : 50, + + // Minimum width (in px) of component on creation + minComponentW : 50, + }; +}); \ No newline at end of file diff --git a/bundle/commands/main.js b/bundle/commands/main.js new file mode 100644 index 000000000..099d786a9 --- /dev/null +++ b/bundle/commands/main.js @@ -0,0 +1,90 @@ +define(function(require) { + /** + * @class Commands + * @param {Object} Configurations + * + * @return {Object} + * */ + function Commands(config) + { + var c = config || {}, + defaults = require('./config/config'), + AbsCommands = require('./view/CommandAbstract'); + + for (var name in defaults) { + if (!(name in c)) + c[name] = defaults[name]; + } + + this.commands = {}; + this.config = c; + this.Abstract = AbsCommands; + + this.defaultCommands = {}; + this.defaultCommands['select-comp'] = require('./view/SelectComponent'); + this.defaultCommands['create-comp'] = require('./view/CreateComponent'); + this.defaultCommands['delete-comp'] = require('./view/DeleteComponent'); + this.defaultCommands['resize-comp'] = require('./view/ResizeComponent'); + this.defaultCommands['image-comp'] = require('./view/ImageComponent'); + this.defaultCommands['move-comp'] = require('./view/MoveComponent'); + this.defaultCommands['text-comp'] = require('./view/TextComponent'); + this.defaultCommands['insert-var'] = require('./view/InsertCustom'); + this.defaultCommands['export-template'] = require('./view/ExportTemplate'); + this.defaultCommands['sw-visibility'] = require('./view/SwitchVisibility'); + this.defaultCommands['open-layers'] = require('./view/OpenLayers'); + this.defaultCommands['open-sm'] = require('./view/OpenStyleManager'); + + this.config.model = this.config.em.get('Canvas'); + } + + Commands.prototype = { + + /** + * Add new command + * @param {String} id + * @param {Object} obj + * + * @return this + * */ + add : function(id, obj) + { + delete obj.initialize; + this.commands[id] = this.Abstract.extend(obj); + return this; + }, + + /** + * Get command + * @param {String} id + * + * @return Command + * */ + get : function(id) + { + var el = this.commands[id]; + + if(typeof el == 'function'){ + el = new el(this.config); + this.commands[id] = el; + } + + return el; + }, + + /** + * Load default commands + * + * @return this + * */ + loadDefaultCommands : function() + { + for (var id in this.defaultCommands) { + this.add(id, this.defaultCommands[id]); + } + + return this; + }, + }; + + return Commands; +}); \ No newline at end of file diff --git a/bundle/commands/model/Command.js b/bundle/commands/model/Command.js new file mode 100644 index 000000000..34b6765c4 --- /dev/null +++ b/bundle/commands/model/Command.js @@ -0,0 +1,13 @@ +define([ 'backbone'], + function (Backbone) { + /** + * @class Command + * */ + return Backbone.Model.extend({ + + defaults :{ + id : '', + } + + }); +}); diff --git a/bundle/commands/model/Commands.js b/bundle/commands/model/Commands.js new file mode 100644 index 000000000..e9dc389ed --- /dev/null +++ b/bundle/commands/model/Commands.js @@ -0,0 +1,11 @@ +define([ 'backbone','./Command'], + function (Backbone, Command) { + /** + * @class Commands + * */ + return Backbone.Collection.extend({ + + model: Command, + + }); +}); diff --git a/bundle/commands/view/CommandAbstract.js b/bundle/commands/view/CommandAbstract.js new file mode 100644 index 000000000..bb914a9bd --- /dev/null +++ b/bundle/commands/view/CommandAbstract.js @@ -0,0 +1,51 @@ +define(['backbone'], + function(Backbone) { + /** + * @class CommandAbstract + * */ + return Backbone.View.extend({ + + /** + * Initialize method that can't be removed + * @param {Object} o Options + * */ + initialize: function(o) { + this.editorModel = this.em = o.em || {}; + this.canvasId = o.canvasId || ''; + this.wrapperId = o.wrapperId || ''; + this.pfx = o.stylePrefix; + this.hoverClass = this.pfx + 'hover'; + this.badgeClass = this.pfx + 'badge'; + this.plhClass = this.pfx + 'placeholder'; + this.setElement('#' + this.canvasId); + this.$canvas = this.$el; + this.$wrapper = $('#' + this.wrapperId); + this.init(o); + }, + + /** + * Callback triggered after initialize + * @param {Object} o Options + * */ + init: function(o){}, + + /** + * Method that run command + * @param {Object} em Editor model + * @param {Object} sender Button sender + * */ + run: function(em, sender) { + console.warn("No run method found"); + }, + + /** + * Method that stop command + * @param {Object} em Editor model + * @param {Object} sender Button sender + * */ + stop: function(em, sender) { + console.warn("No stop method found"); + } + + }); + }); \ No newline at end of file diff --git a/bundle/commands/view/CreateComponent.js b/bundle/commands/view/CreateComponent.js new file mode 100644 index 000000000..a6ecc696b --- /dev/null +++ b/bundle/commands/view/CreateComponent.js @@ -0,0 +1,269 @@ +define(['backbone','./SelectPosition'], + function(Backbone, SelectPosition) { + /** + * @class CreateComponent + * */ + return _.extend({},SelectPosition,{ + + newElement : null, + + tempComponent: { style:{} }, + + init: function(opt) { + SelectPosition.init.apply(this, arguments); + _.bindAll(this,'startDraw','draw','endDraw','rollback'); + this.config = opt; + this.heightType = this.config.newFixedH ? 'height' : 'min-height'; + }, + + /** + * Returns creation placeholder + * + * @return {Object} + * */ + getCreationPlaceholder: function() + { + return this.newElem; + }, + + /** + * Removes creation placeholder + * + * @return void + * */ + removeCreationPlaceholder: function() + { + this.newElem.remove(); + }, + + /** + * Start with enabling to select position and listening to start drawning + * @return void + * */ + enable: function() + { + SelectPosition.enable.apply(this, arguments); + this.$el.css('cursor','crosshair'); + this.enableToDraw(); + }, + + /** + * Enable user to draw components + * + * @return void + * */ + enableToDraw: function() + { + this.$el.on('mousedown', this.startDraw); + this.$el.disableSelection(); //Disable text selection + }, + + /** + * Start drawing component + * @param {Object} e Event + * + * @return void + * */ + startDraw : function(e) + { + e.preventDefault(); + this.stopSelectPosition(); //Interrupt selecting position + this.tempComponent = { style: {} }; //Reset the helper + this.isDragged = false; + this.beforeDraw(this.tempComponent); + this.getPositionPlaceholder().addClass('change-placeholder'); //Change color of the position placeholder + this.newElemOrig = { top : e.pageY, left: e.pageX }; + this.newElem = $('
', {class: "tempComp"}).css(this.newElemOrig); //Create helper element with initial position + this.newElem.data('helper',1); + $('body').append(this.newElem); //Show helper component + this.parentElem=this.newElem.parent(); //For percent count + this.targetC = this.outsideElem; + $(document).mousemove(this.draw); + $(document).mouseup(this.endDraw); + $(document).keypress(this.rollback); + }, + + /** + * While drawing the component + * @param {Object} e Event + * + * @return void + * */ + draw: function(e) + { + this.isDragged = true; + this.updateComponentSize(e); + }, + + /** + * End drawing component + * @param {Object} e Event + * + * @return void + * */ + endDraw : function(e) + { + $(document).off('mouseup', this.endDraw); + $(document).off('mousemove', this.draw); + $(document).off('keypress',this.rollback); + var model = {}; + if(this.isDragged){ //Only if the mouse was moved + this.updateComponentSize(e); + this.setRequirements(this.tempComponent); + model = this.create(null,this.tempComponent,this.posIndex,this.posMethod); + } + if(this.getPositionPlaceholder()) + this.getPositionPlaceholder().removeClass('change-placeholder'); //Turn back the original color of the placeholder + this.startSelectPosition(); //Return with selecting new position + this.removeCreationPlaceholder(); //Remove the element used for size indication + this.afterDraw(model); + }, + + /** + * Create component + * @param {Object} target DOM of the target element which to push new component + * @param {Object} component New component to push + * @param {Integer} posIndex Index inside the collection, 0 if no children inside + * @param {String} method Before or after of the children + * + * @return {Object} Created model + * */ + create: function(target, component, posIndex, method) + { + var index = posIndex || 0; + if(this.posTargetCollection && this.posTargetModel.get('droppable')){ + //Check config parameters for center in wrapper + if(this.config.firstCentered && (this.el == this.posTargetEl.get(0)) ){ + component.style.margin = '0 auto'; + } + if(this.nearToFloat()) //Set not in flow if the nearest is too + component.style.float = 'left'; + this.beforeCreation(component); + var model = this.posTargetCollection.add(component, { at: index, silent:false }); + this.afterCreation(model); + return model; + }else + console.warn("Invalid target position"); + }, + + /** + * Check and set basic requirements for the component + * @param {Object} component New component to be created + * @return {Object} Component updated + * */ + setRequirements: function(component) + { + var c = this.config; + if(component.style.width.replace(/\D/g,'') < c.minComponentW) //Check min width + component.style.width = c.minComponentW +'px'; + if(component.style[this.heightType].replace(/\D/g,'') < c.minComponentH) //Check min height + component.style[this.heightType] = c.minComponentH +'px'; + if(c.newFixedH) //Set overflow in case of fixed height + component.style.overflow = 'auto'; + if(!this.absoluteMode){ + delete component.style.left; + delete component.style.top; + }else + component.style.position = 'absolute'; + return component; + }, + + /** + * Update new component size while drawing + * @param {Object} e Event + * + * @return void + * */ + updateComponentSize : function (e) + { + var newLeft = e.pageX; + var newTop = e.pageY; + var startLeft = this.newElemOrig.left; + var startTop = this.newElemOrig.top; + var newWidth = newLeft - startLeft;//$(this.newElem).offset().left + var newHeight = newTop - startTop;//$(this.newElem).offset().top + if (newLeft < this.newElemOrig.left) { + startLeft = newLeft; + newWidth = this.newElemOrig.left - newLeft; + } + if (newTop < this.newElemOrig.top) { + startTop = newTop; + newHeight = this.newElemOrig.top - newTop; + } + newWidth = this.absoluteMode ? (newWidth/this.parentElem.width()*100+"%") : newWidth+'px'; + this.newElem[0].style.left = startLeft+'px'; + this.newElem[0].style.top = startTop+'px'; + this.newElem[0].style.width = newWidth; + this.newElem[0].style['min-height'] = newHeight+'px'; + this.tempComponent.style.width = newWidth; + this.tempComponent.style[this.heightType] = newHeight+"px"; + this.tempComponent.style.left = startLeft + "px"; + this.tempComponent.style.top = startTop + "px"; + }, + + /** + * Used to bring the previous situation before event started + * @param {Object} e Event + * @param {Boolean} forse Indicates if rollback in anycase + * + * @return void + * */ + rollback: function(e, force) + { + var key = e.which || e.keyCode; + if(key == this.config.ESCAPE_KEY || force){ + this.isDragged = false; + this.endDraw(); + } + return; + }, + + /** + * This event is triggered at the beginning of a draw operation + * @param {Object} component Object component before creation + * + * @return void + * */ + beforeDraw: function(component){ + component.editable = false;//set this component editable + }, + + /** + * This event is triggered at the end of a draw operation + * @param {Object} model Component model created + * + * @return void + * */ + afterDraw: function(model){}, + + /** + * This event is triggered just before a create operation + * @param {Object} component Object component before creation + * + * @return void + * */ + beforeCreation: function(component){}, + + /** + * This event is triggered at the end of a create operation + * @param {Object} model Component model created + * + * @return void + * */ + afterCreation: function(model){}, + + /** Run method + * */ + run: function(){ + this.enable(); + }, + + /** Stop method + * */ + stop: function(){ + this.removePositionPlaceholder(); //Removes placeholder from eventSelectPosition + this.$el.css('cursor',''); //Changes back aspect of the cursor + this.$el.unbind(); //Removes all attached events + } + }); + }); \ No newline at end of file diff --git a/bundle/commands/view/DeleteComponent.js b/bundle/commands/view/DeleteComponent.js new file mode 100644 index 000000000..703787cd2 --- /dev/null +++ b/bundle/commands/view/DeleteComponent.js @@ -0,0 +1,59 @@ +define(['backbone', './SelectComponent'], + function(Backbone, SelectComponent) { + /** + * @class DeleteComponent + * */ + return _.extend({},SelectComponent,{ + + init: function(o){ + this.hoverClass = this.pfx + 'hover-delete'; + this.badgeClass = this.pfx + 'badge-red'; + }, + + enable: function(){ + + if(!this.$el.length) + this.$el = $('#' + this.canvasId); + + var that = this; + this.$el.find('*').mouseover(function (e){ + e.stopPropagation(); + if($(this).data('model').get('removable')){ //Show badge if possible + $(this).addClass(that.hoverClass); + that.attachBadge(this); + } + }).mouseout(function (e){ //hover out + e.stopPropagation(); + $(this).removeClass(that.hoverClass); + if(that.badge) //Hide badge if possible + that.badge.css({ left: -1000, top:-1000 }); + }).click(function(e){ + that.onSelect(e,this); //Callback on select + }); + }, + + /** + * Say what to do after the component was selected + * @param Event + * @param Object Selected element + * */ + onSelect: function(e, el){ + e.stopPropagation(); + var $selected = $(el); + if(!$selected.data('model').get('removable')) //Do nothing in case can't remove + return; + $selected.data('model').destroy(); + this.removeBadge(); + this.clean(); + }, + + /** + * Updates badge label + * @param Object Model + * @return void + * */ + updateBadgeLabel: function (model){ + this.badge.html( 'Remove '+model.getName() ); + }, + }); + }); \ No newline at end of file diff --git a/bundle/commands/view/ExportTemplate.js b/bundle/commands/view/ExportTemplate.js new file mode 100644 index 000000000..6f4494736 --- /dev/null +++ b/bundle/commands/view/ExportTemplate.js @@ -0,0 +1,73 @@ +define(function() { + /** + * @class ExportTemplate + * */ + return { + + run: function(em, sender){ + this.sender = sender; + this.components = em.get('Canvas').getWrapper().get('components'); + this.modal = em.get('Modal') || null; + this.cm = em.get('CodeManager') || null; + this.enable(); + }, + + /** + * Build editor + * @param {String} codeName + * @param {String} theme + * @param {String} label + * + * @return {Object} Editor + * */ + buildEditor: function(codeName, theme, label) + { + if(!this.codeMirror) + this.codeMirror = this.cm.getEditor('CodeMirror'); + + var $input = $('