diff --git a/.gitignore b/.gitignore index 3fec0a67..1171d6d8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,10 +28,14 @@ node_modules editor/vendors # All installed packages -packages +packages/* +!packages/.gitkeep # Build output build # Tmp directory .tmp + +# Packages for testing +test/packages \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 53b798f8..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,235 +0,0 @@ -var path = require("path"); -var Q = require("q"); -var _ = require("lodash"); -var fs = require("fs"); -var wrench = require("wrench"); -var pkg = require("./package.json"); - -module.exports = function (grunt) { - // Path to the client src - var srcPath = path.resolve(__dirname, "editor"); - - // Path to the output folder - var buildPath = path.resolve(__dirname, "build"); - - // Path to the packages storing folder - var packagesPath = grunt.option("packages") - if (packagesPath) { - packagesPath = path.resolve(process.cwd(), packagesPath); - } else { - packagesPath = path.resolve(__dirname, "packages"); - } - - // Load grunt modules - grunt.loadNpmTasks('grunt-hr-builder'); - grunt.loadNpmTasks("grunt-bower-install-simple"); - grunt.loadNpmTasks('grunt-exec'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-copy'); - - // Init GRUNT configuraton - grunt.initConfig({ - "pkg": pkg, - "bower-install-simple": { - options: { - color: true, - production: false, - directory: "editor/vendors" - } - }, - "hr": { - "app": { - "source": path.resolve(__dirname, "node_modules/happyrhino"), - - // Base directory for the application - "base": srcPath, - - // Application name - "name": "Codebox", - - // Mode debug - "debug": true, - - // Main entry point for application - "main": "main", - "index": grunt.file.read(path.resolve(srcPath, "index.html")), - - // Build output directory - "build": buildPath, - - // Static files map - "static": { - "fonts": path.resolve(srcPath, "resources/fonts"), - "images": path.resolve(srcPath, "resources/images"), - "fonts/octicons": path.resolve(srcPath, "vendors/octicons/octicons") - }, - - // Vendors - "paths": { - "sockjs": path.resolve(srcPath, "vendors/bower-sockjs-client/sockjs"), - "moment": path.resolve(srcPath, "vendors/moment/moment") - }, - - // Stylesheet entry point - "style": path.resolve(srcPath, "resources/stylesheets/main.less") - } - }, - "exec": { - "clear_packages_build": { - command: 'rm -f packages/**/pkg-build.js', - stdout: false, - stderr: false - }, - 'publish': { - command: "npm publish", - cwd: '.tmp/', - stdout: true, - stderr: true - } - }, - "copy": { - // Copy most files over - tmp: { - expand: true, - dot: false, - cwd: './', - dest: '.tmp/', - src: [ - // Most files except the ones below - "./**", - - // Ignore gitignore - "!.gitignore", - - // Ignore dev related things - "!./tmp/**", - "!./.git/**", - - // Only take "./client/build" - "!./editor/**", - "./editor/build/**", - - // Ignore some build time only modules - "./node_modules/.bin/**", - "!./node_modules/grunt/**", - "!./node_modules/grunt-*/**", - "!./node_modules/happyrhino/**", - - // Exclude test directories from node modules - "!./node_modules/**/test/**", - ], - - // Preserve permissions - options: { - mode: true - } - } - }, - "clean": { - tmp: ['.tmp/'] - } - }); - - grunt.registerTask('link', 'Link a folder containing packages at packages folder', function() { - var origin = grunt.option('origin'); - var prefix = grunt.option('prefix') || "package-"; - - if (!origin) { - grunt.log.error('Need --origin'); - return false; - } - - origin = path.resolve(process.cwd(), origin); - var filenames = fs.readdirSync(origin); - - _.each(filenames, function(filename) { - if (filename.slice(0, prefix.length) != prefix) return; - - var from = path.resolve(origin, filename); - var to = path.resolve(__dirname, "packages", filename.slice(prefix.length)); - - grunt.log.writeln('Link', filename, 'to', to); - - var stat = null; - - try { stat = fs.lstatSync(to); } catch (e) {}; - - if (stat && stat.isSymbolicLink()) { - fs.unlinkSync(to); - } else if (fs.existsSync(to)) { - wrench.rmdirSyncRecursive(to); - } - - fs.symlinkSync(from, to, 'dir'); - }); - }); - - grunt.registerTask('resetPkg', 'Reset a package state', function() { - var topkg = grunt.option('pkg'); - if (!topkg) { - grunt.log.error('Need --pkg'); - return false; - } - - var reset = grunt.option('reset') || "node,bower,build"; - reset = _.compact(reset.split(",")); - - var pkgPath = path.resolve(packagesPath, topkg); - - var doReset = { - 'node': [ "node_modules/" ], - 'bower': [ "bower_components/" ], - 'build': [ "pkg-build.js" ] - }; - - grunt.log.writeln("Reseting: "+reset.join(", ")); - _.chain(reset) - .map(function(r) { - return doReset[r]; - }) - .flatten() - .each(function(r) { - var toRemove = path.resolve(pkgPath, r); - - grunt.log.writeln("Removing ", toRemove); - try { - if (r[r.length-1] !== '/') { - fs.unlink(toRemove); - } else { - wrench.rmdirSyncRecursive(toRemove); - } - } catch (e) { - grunt.log.warn(e.message || e); - } - }); - }); - - grunt.registerTask("prepare", 'Prepare client build', [ - 'bower-install-simple' - ]); - - grunt.registerTask('build', 'Build client code', [ - 'hr:app' - ]); - - grunt.registerTask('resetPkgs', 'Reset packages build', [ - 'exec:clear_packages_build' - ]); - - grunt.registerTask('tmp', 'Build tmp directory to publish', [ - 'build', - 'clean:tmp', - 'copy:tmp' - ]); - - grunt.registerTask("publish", 'Publish new version', [ - 'tmp', - 'exec:publish', - 'clean:tmp' - ]); - - grunt.registerTask('default', [ - 'prepare', - 'build' - ]); -}; diff --git a/README.md b/README.md index 31ca68c6..52f8fab9 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ [![Build Status](https://travis-ci.org/CodeboxIDE/codebox.png?branch=master)](https://travis-ci.org/CodeboxIDE/codebox) [![NPM version](https://badge.fury.io/js/codebox.svg)](http://badge.fury.io/js/codebox) -#### :warning: Instructions are for the not yet published version 1.0.0 - Codebox is a complete and modular Cloud IDE. It can run on any unix-like machine (Linux, Mac OS X). It is an open source component of [codebox.io](https://www.codebox.io) (Cloud IDE as a Service). The IDE can run on your desktop (Linux or Mac), on your server or the cloud. You can use the [codebox.io](https://www.codebox.io) service to host and manage IDE instances. @@ -36,7 +34,7 @@ $ npm install -g codebox And start the IDE from the command line: ``` -$ codebox --root=./myworkspace --open +$ codebox run ./myworkspace --open ``` Use this command to run and open Codebox IDE. By default, Codebox uses GIT to identify you, you can use the option ```--email=john.doe@gmail.com``` to define the email you want to use during GIT operations. @@ -53,21 +51,6 @@ Others comand line options are available and can be list with: ```codebox --help -p, --port [port] HTTP port ``` -#### Developing and testing packages - -Download and build the source code: - -``` -$ git clone https://github.com/CodeboxIDE/codebox.git -$ cd ./codebox -$ npm install . -$ grunt -``` - -Then you can easily link packages for testing by creating a folder that will contains all your packages (each should start with the prefix `package-`), then run the command `grunt link --origin=../mypackages`. This command will create symlinks between all the packages in `../mypackages` and the folder where are stored packages used by codebox. - -Everytime you update the code of your package, simply run `grunt resetPkg --pkg=mypackage` in it and restart codebox. - #### Need help? The IDE's documentation can be found at [help.codebox.io](http://help.codebox.io). Feel free to ask any questions or signal problems by adding issues. diff --git a/bin/codebox.js b/bin/codebox.js index 1f3f6a56..4710a5d6 100755 --- a/bin/codebox.js +++ b/bin/codebox.js @@ -10,67 +10,110 @@ var codebox = require("../lib"); var gitconfig = require('../lib/utils/gitconfig'); +function printError(err) { + console.log(err.stack || err.message || err); + process.exit(1); +} + program .version(pkg.version) -.option('-r, --root [path]', 'Root folder for the workspace, default is current directory', "./") -.option('-t, --templates [list]', 'Configuration templates, separated by commas', "") -.option('-p, --port [port]', 'HTTP port', 3000) -.option('-o, --open', 'Open the IDE in your favorite browser') -.option('-e, --email [email address]', 'Email address to use as a default authentication') -.option('-u, --users [list users]', 'List of coma seperated users and password (formatted as "username:password")'); - - -program.on('--help', function(){ +.on('--help', function(){ console.log(' Examples:'); console.log(''); - console.log(' $ codebox --root=./myfolder'); + console.log(' $ codebox run ./myfolder'); console.log(''); }); -program.parse(process.argv); - -// Parse auth users -var users = !program.users ? {} : _.object(_.map(program.users.split(','), function(x) { - // x === 'username:password' - return x.split(':', 2); -})); +//// Run Codebox +//// +program +.command('run [root]') +.description('run codebox') +.option('-t, --templates [list]', 'Configuration templates, separated by commas', "") +.option('-p, --port [port]', 'HTTP port', 3000) +.option('-o, --open', 'Open the IDE in your favorite browser') +.option('-e, --email [email address]', 'Email address to use as a default authentication') +.option('-u, --users [list users]', 'List of coma seperated users and password (formatted as "username:password")', function (val) { + return _.object(_.map((val || "").split(','), function(x) { + // x === 'username:password' + return x.split(':', 2); + })); +}, {}) +.action(function(root, opts) { + // Generate configration + var options = { + root: path.resolve(process.cwd(), root || "./"), + port: opts.port, + auth: { + users: opts.users + } + }; -// Generate configration -var options = { - root: path.resolve(process.cwd(), program.root), - port: program.port, - auth: { - users: users - } -}; + codebox.start(options) + .then(function() { + if (program.email) return program.email; + // Path to user's .gitconfig file + var configPath = path.join( + process.env.HOME, + '.gitconfig' + ); -codebox.start(options) -.then(function() { - if (program.email) return program.email; + // Codebox git repo: use to identify the user + return gitconfig(configPath) + .get("user") + .get("email") + .fail(function() { + return ""; + }); + }) + .then(function(email) { + var token = opts.users[email] || Math.random().toString(36).substring(7); + var url = "http://localhost:"+options.port; - // Path to user's .gitconfig file - var configPath = path.join( - process.env.HOME, - '.gitconfig' - ); + console.log("\nCodebox is running at", url); - // Codebox git repo: use to identify the user - return gitconfig(configPath) - .get("user") - .get("email") - .fail(function() { - return ""; - }); -}) -.then(function(email) { - var token = users[email] || Math.random().toString(36).substring(7); - var url = "http://localhost:"+program.port; + if (program.open) open(url+"/?email="+email+"&token="+token); + }) + .fail(printError); +}); - console.log("\nCodebox is running at", url); +//// Install packages +//// +program +.command('install') +.description('pre-install packages') +.option('-r, --root [path]', 'Root folder to store packages') +.option('-p, --packages ', 'Comma separated list of packages to install', function (val) { + return _.chain(val.split(",")) + .compact() + .map(function(pkgref) { + var parts = pkgref.split(":"); + var name = _.first(parts); + var url = parts.slice(1).join(":"); + if (!name || !url) throw "Packages need to be formatted as 'name:url'"; - if (program.open) open(url+"/?email="+email+"&token="+token); -}) -.fail(function(err) { - console.log(err.stack || err.message || err); + return [name,url]; + }) + .object() + .value() +}, []) +.action(function(opts) { + codebox.prepare({ + packages: { + root: opts.root? path.resolve(process.cwd(), opts.root) : undefined, + install: opts.packages, + defaults: null + } + }) + .then(function() { + process.exit(0); + }) + .fail(printError); }); + +program.parse(process.argv); + +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/bower.json b/bower.json deleted file mode 100644 index 16923081..00000000 --- a/bower.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "Codebox", - "dependencies": { - "mousetrap": "1.4.6", - "octicons": "2.0.2", - "bower-sockjs-client": "0.3.4", - "moment": "2.7.0" - } -} \ No newline at end of file diff --git a/editor/collections/commands.js b/editor/collections/commands.js index 60eebd28..75d11248 100644 --- a/editor/collections/commands.js +++ b/editor/collections/commands.js @@ -1,50 +1,62 @@ -define([ - "hr/hr", - "hr/utils", - "hr/promise", - "models/command" -], function(hr, _, Q, Command) { - var logging = hr.Logger.addNamespace("commands"); - - var Commands = hr.Collection.extend({ - model: Command, - - // Initialize - initialize: function() { - Commands.__super__.initialize.apply(this, arguments); - - this.context = {}; - }, - - // Register a new command - register: function(cmd) { - if (_.isArray(cmd)) return _.map(cmd, this.register, this); - - var c = this.get(cmd.id); - if (c) this.remove(c); - - return this.add(cmd); - }, - - // Run a command - run: function(cmd, args) { - - cmd = this.get(cmd); - if (!cmd) return Q.reject(new Error("Command not found: '"+cmd+"'")); - - return cmd.run(args); - }, - - // Set context - setContext: function(id, data) { - logging.log("update context", id); - this.context = { - 'type': id, - 'data': data - }; - this.trigger("context", this.context); - } - }); - - return Commands; -}); \ No newline at end of file +var Q = require("q"); +var _ = require("hr.utils"); +var Collection = require("hr.collection"); +var logger = require("hr.logger")("commands"); + +var Command = require("../models/command"); + +var Commands = Collection.extend({ + model: Command, + + // Initialize + initialize: function() { + Commands.__super__.initialize.apply(this, arguments); + + this.context = {}; + }, + + // Register a new command + register: function(cmd) { + if (_.isArray(cmd)) return _.map(cmd, this.register, this); + + var c = this.get(cmd.id); + if (c) this.remove(c); + + return this.add(cmd); + }, + + // Run a command + run: function(_cmd, args) { + var cmd = this.resolve(_cmd); + if (!cmd) return Q.reject(new Error("Command not found: '"+_cmd+"'")); + + return cmd.run(args); + }, + + // Resolve a command + resolve: function(_cmd) { + return _.chain(this.models) + .map(function(m) { + return { + cmd: m, + score: m.resolve(_cmd) + }; + }) + .filter(function(r) { + return r.score > 0; + }) + .sortBy("score") + .pluck("cmd") + .last() + .value(); + }, + + // Set context + setContext: function(ctx) { + logger.log("update context", _.keys(ctx)); + this.context = ctx || {}; + this.trigger("context", this.context); + } +}); + +module.exports = Commands; diff --git a/editor/collections/packages.js b/editor/collections/packages.js index c1bc6c7a..f461ceeb 100644 --- a/editor/collections/packages.js +++ b/editor/collections/packages.js @@ -1,22 +1,30 @@ -define([ - "hr/hr", - "hr/utils", - "hr/promise", - "models/package", - "core/rpc" -], function(hr, _, Q, Package, rpc) { - var Packages = hr.Collection.extend({ - model: Package, - - listAll: function() { - return rpc.execute("packages/list") - .then(this.reset.bind(this)); - }, - - loadAll: function() { - var that = this; - var errors = []; +var Q = require("q"); +var _ = require("hr.utils"); +var $ = require("jquery"); +var Collection = require("hr.collection"); +var logger = require("hr.logger")("packages"); +var Package = require("../models/package"); +var rpc = require("../core/rpc"); + + +var Packages = Collection.extend({ + model: Package, + + // Get packages list from backend + listAll: function() { + return rpc.execute("packages/list") + .then(this.reset.bind(this)); + }, + + // Load all plugins from backend (using bundle) + loadAll: function(bundle) { + var that = this; + var errors = []; + + if (bundle) { + return Q($.getScript("/packages.js")); + } else { return this.listAll() .then(function() { return that.reduce(function(prev, pkg) { @@ -45,7 +53,7 @@ define([ } }); } - }); + } +}); - return Packages; -}); \ No newline at end of file +module.exports = Packages; diff --git a/editor/collections/users.js b/editor/collections/users.js index 573eea76..8d7a60f0 100644 --- a/editor/collections/users.js +++ b/editor/collections/users.js @@ -1,20 +1,18 @@ -define([ - "hr/hr", - "hr/utils", - "hr/promise", - "models/user", - "core/rpc" -], function(hr, _, Q, User, rpc) { - var logging = hr.Logger.addNamespace("users"); +var Q = require("q"); +var _ = require("hr.utils"); +var Collection = require("hr.collection"); +var logger = require("hr.logger")("users"); - var Users = hr.Collection.extend({ - model: User, +var User = require("../models/user"); +var rpc = require("../core/rpc"); - listAll: function() { - return rpc.execute("users/list") - .then(this.reset.bind(this)); - }, - }); +var Users = Collection.extend({ + model: User, - return Users; -}); \ No newline at end of file + listAll: function() { + return rpc.execute("users/list") + .then(this.reset.bind(this)); + }, +}); + +module.exports = Users; diff --git a/editor/core/application.js b/editor/core/application.js index d3197311..8b94f87d 100644 --- a/editor/core/application.js +++ b/editor/core/application.js @@ -1,44 +1,52 @@ -define([ - "hr/utils", - "hr/dom", - "hr/promise", - "hr/hr", - "views/grid", - "core/commands", - "core/packages" -], function(_, $, Q, hr, GridView, commands, packages) { - // Define base application - var Application = hr.Application.extend({ - el: null, - className: "main-application", - name: "Codebox", - events: { - - }, - routes: {}, - - initialize: function() { - Application.__super__.initialize.apply(this, arguments); - - this.grid = new GridView({ - columns: 10 - }, this); - this.grid.$el.addClass("main-grid"); - this.grid.appendTo(this); - }, - - render: function() { - return this.ready(); - }, - - run: function() { - $(".main-authentication").remove(); - this.$el.appendTo($("body")); - - return Application.__super__.run.apply(this, arguments); - }, - }); - - var app = new Application(); - return app; +var _ = require("hr.utils"); +var $ = require("jquery"); +var Q = require("q"); +var logger = require("hr.logger")("app"); +var Application = require("hr.app"); +var GridView = require("hr.gridview"); + +var dialogs = require("../utils/dialogs"); +var workspace = require("./workspace"); + +// Define base application +var CodeboxApplication = Application.extend({ + el: null, + className: "main-application", + name: "Codebox", + events: {}, + routes: {}, + + initialize: function() { + CodeboxApplication.__super__.initialize.apply(this, arguments); + + this.grid = new GridView({ + columns: 10 + }, this); + this.grid.$el.addClass("main-grid"); + this.grid.appendTo(this); + + // Signal offline + function updateOnlineStatus(event) { + logger.log("connection changed", navigator.onLine); + if (!navigator.onLine) { + dialogs.alert("It looks like you lost your internet connection. The IDE requires an internet connection."); + } else { + dialogs.alert("Your internet connection is up again. Restart your navigator tab to ensure that codebox works perfectly."); + } + } + window.addEventListener('online', updateOnlineStatus); + window.addEventListener('offline', updateOnlineStatus); + }, + + render: function() { + this.head.title(workspace.get('title')); + return this.ready(); + }, + + start: function() { + this.$el.appendTo($("body")); + return this.update(); + }, }); + +module.exports = new CodeboxApplication(); diff --git a/editor/core/commands.js b/editor/core/commands.js index 336c1595..9bca2cd6 100644 --- a/editor/core/commands.js +++ b/editor/core/commands.js @@ -1,7 +1,3 @@ -define([ - "collections/commands" -], function(Commands) { - var commands = new Commands(); +var Commands = require("../collections/commands"); - return commands; -}); \ No newline at end of file +module.exports = new Commands(); diff --git a/editor/core/events.js b/editor/core/events.js index 815bcc62..2b7bb28d 100644 --- a/editor/core/events.js +++ b/editor/core/events.js @@ -1,16 +1,14 @@ -define([ - "hr/hr", - "core/socket" -], function(hr, Socket) { - var logging = hr.Logger.addNamespace("events"); +var logger = require("hr.logger")("events"); - var events = new Socket({ - service: "events" - }); +var Socket = require("./socket"); - events.on("do:report", function(e) { - events.trigger("e:"+e.event, e.data); - }); +// Create a socket connected to the events namespace from backend +var events = new Socket({ + service: "events" +}); - return events; -}); \ No newline at end of file +events.on("do:report", function(e) { + events.trigger("e:"+e.event, e.data); +}); + +module.exports = events; diff --git a/editor/core/packages.js b/editor/core/packages.js index fe3afe58..a52a9d33 100644 --- a/editor/core/packages.js +++ b/editor/core/packages.js @@ -1,20 +1,20 @@ -define([ - "collections/packages", - "core/events", - "utils/dialogs" -], function(Packages, events, dialogs) { - var packages = new Packages(); +var Packages = require("../collections/packages"); +var dialogs = require("../utils/dialogs"); +var events = require("./events"); - events.on("e:packages:add", function(pkg) { - pkg = packages.add(pkg); - pkg.load() - .fail(dialogs.error); - }); +var packages = new Packages(); - events.on("e:packages:remove", function(pkg) { - pkg = packages.get(pkg.name); - if (pkg) pkg.destroy(); - }); +// Load new installed packages +events.on("e:packages:add", function(pkg) { + pkg = packages.add(pkg); + pkg.load() + .fail(dialogs.error); +}); - return packages; -}); \ No newline at end of file +// Unload removed packages +events.on("e:packages:remove", function(pkg) { + pkg = packages.get(pkg.name); + if (pkg) pkg.destroy(); +}); + +module.exports = packages; diff --git a/editor/core/rpc.js b/editor/core/rpc.js index 0f0e37c4..ccc75be7 100644 --- a/editor/core/rpc.js +++ b/editor/core/rpc.js @@ -1,35 +1,23 @@ -define([ - "hr/hr" -], function(hr) { - var rpc = new hr.Backend({ - prefix: "rpc" - }); +var Q = require("q"); +var axios = require("axios"); +var Backend = require("hr.backend"); - rpc.defaultMethod({ - execute: function(args, options, method) { - options = _.defaults({}, options || {}, { - dataType: "json", - options: { - 'headers': { - 'Content-type': 'application/json' - } - } - }); +var rpc = new Backend({ + prefix: "rpc" +}); - return hr.Requests.post("rpc/"+method, JSON.stringify(args), options).then(function(data) { - return data.result; - }, function(err) { - try { - var errContent = JSON.parse(err.httpRes); - var e = new Error(errContent.error || err.message); - e.code = errContent.code || err.status || 500; - return Q.reject(e); - } catch(e) { - return Q.reject(err); - } - }); - } - }); +rpc.defaultMethod({ + execute: function(args, options, method) { + return Q(axios.post("rpc/"+method, args)) + .then(function(res) { + return res.data.result || {}; + }, function(err) { + var e = new Error(err.data.error || err); + e.code = err.status; - return rpc; -}); \ No newline at end of file + return Q.reject(e); + }); + } +}); + +module.exports = rpc; diff --git a/editor/core/settings.js b/editor/core/settings.js index e5eee997..93b4c4ee 100644 --- a/editor/core/settings.js +++ b/editor/core/settings.js @@ -1,123 +1,123 @@ -define([ - "hr/utils", - "hr/promise", - "hr/hr", - "core/rpc", - "models/file", - "models/schema" -], function(_, Q, hr, rpc, File, Schema) { - var Manager = hr.Class.extend({ - initialize: function() { - Manager.__super__.initialize.apply(this, arguments); - - this.settings = {}; - this.schemas = new (hr.Collection.extend({ model: Schema })); - }, - - // Add/Get a schema - schema: function(id, infos) { - var that = this; - - if (!infos) return this.schemas.get(id); - - this.schemas.add({ - 'id': id, - 'schema': infos - }); - this.importJSON(this.settings, { - save: false, - silent: false - }); - var sch = this.schemas.get(id); - - this.listenTo(sch.data, "change", function() { - this.trigger("change"); - }); - - return sch; - }, - - // Export - exportJson: function() { - var that = this; - return _.object( - this.schemas.map(function(schema) { - return [ - schema.id, - schema.getData() - ]; - }) - ); - }, - - // Import - importJSON: function(data, options) { - options = _.defaults(options || {}, { - save: true, - silent: false - }); - - this.settings = data; - - this.schemas.each(function(schema) { - schema.data.clear({ silent: true }); - schema.data.set(_.defaults(this.settings[schema.id] || {}, schema.getDefaults()), { - silent: options.silent - }); - }, this); - - if (options.save) { - return this.save(); - } - - return Q(); - }, - - // Get file - getFile: function() { - // Generate the string content - var code = JSON.stringify(this.exportJson(), null, 4); - - // Build the file buffer - var f = File.buffer("Codebox Settings.json", code, "codebox-settings.json", { - saveAsFile: false - }); - - // Handle write operations - this.listenTo(f, "write", function(data) { - this.importJSON(JSON.parse(data)); +var Q = require("q"); +var _ = require("hr.utils"); +var Class = require("hr.class"); +var Collection = require("hr.collection"); + +var File = require("../models/file"); +var Schema = require("../models/schema"); + +var rpc = require("./rpc"); + +var Manager = Class.extend({ + initialize: function() { + Manager.__super__.initialize.apply(this, arguments); + + this.settings = {}; + this.schemas = new (Collection.extend({ model: Schema })); + }, + + // Add/Get a schema + schema: function(id, infos) { + var that = this; + + if (!infos) return this.schemas.get(id); + + this.schemas.add({ + 'id': id, + 'schema': infos + }); + this.importJSON(this.settings, { + save: false, + silent: false + }); + var sch = this.schemas.get(id); + + this.listenTo(sch.data, "change", function() { + this.trigger("change"); + }); + + return sch; + }, + + // Export + exportJson: function() { + var that = this; + return _.object( + this.schemas.map(function(schema) { + return [ + schema.id, + schema.getData() + ]; + }) + ); + }, + + // Import + importJSON: function(data, options) { + options = _.defaults(options || {}, { + save: true, + silent: false + }); + + this.settings = data; + + this.schemas.each(function(schema) { + schema.data.clear({ silent: true }); + schema.data.set(_.defaults(this.settings[schema.id] || {}, schema.getDefaults()), { + silent: options.silent }); + }, this); - return f; - }, - - // Save settings on the server - save: function() { - return rpc.execute("settings/set", this.exportJson()); - }, - - // Load settings from the server - load: function() { - return rpc.execute("settings/get") - .then(_.partialRight(this.importJSON, { save: false }).bind(this)); - }, - - // Return as a schema - toSchema: function() { - return { - title: "Settings", - type: "object", - properties: _.chain(this.schemas.models) - .map(function(sch) { - sch = sch.toJSON(); - return [sch.id, sch.schema]; - }) - .object() - .value() - }; + if (options.save) { + return this.save(); } - }); - var settings = new Manager(); - return settings; -}); \ No newline at end of file + return Q(); + }, + + // Get file + getFile: function() { + // Generate the string content + var code = JSON.stringify(this.exportJson(), null, 4); + + // Build the file buffer + var f = File.buffer("Codebox Settings.json", code, "codebox-settings.json", { + saveAsFile: false + }); + + // Handle write operations + this.listenTo(f, "write", function(data) { + this.importJSON(JSON.parse(data)); + }); + + return f; + }, + + // Save settings on the server + save: function() { + return rpc.execute("settings/set", this.exportJson()); + }, + + // Load settings from the server + load: function() { + return rpc.execute("settings/get") + .then(_.partialRight(this.importJSON, { save: false }).bind(this)); + }, + + // Return as a schema + toSchema: function() { + return { + title: "Settings", + type: "object", + properties: _.chain(this.schemas.models) + .map(function(sch) { + sch = sch.toJSON(); + return [sch.id, sch.schema]; + }) + .object() + .value() + }; + } +}); + +module.exports = new Manager(); diff --git a/editor/core/socket.js b/editor/core/socket.js index 703100de..356e44b6 100644 --- a/editor/core/socket.js +++ b/editor/core/socket.js @@ -1,52 +1,49 @@ -define([ - "hr/hr", - "sockjs" -], function(hr, sockjs) { - var logging = hr.Logger.addNamespace("socket"); +var Class = require("hr.class"); +var SockJS = require("sockjs-client"); +var logger = require("hr.logger")("socket"); - var Socket = hr.Class.extend({ - initialize: function() { - var that = this; - Socket.__super__.initialize.apply(this, arguments); +var Socket = Class.extend({ + initialize: function() { + var that = this; + Socket.__super__.initialize.apply(this, arguments); - logging.log("connecting to service", this.options.service); - this.sock = new SockJS(window.location.origin+window.location.pathname+"socket/"+this.options.service); - this.sock.onopen = function() { - that.trigger("open"); - }; - this.sock.onmessage = function(e) { - var data = JSON.parse(e.data); + logger.log("connecting to service", this.options.service); + this.sock = new SockJS(window.location.origin+window.location.pathname+"socket/"+this.options.service); + this.sock.onopen = function() { + that.trigger("open"); + }; + this.sock.onmessage = function(e) { + var data = JSON.parse(e.data); - that.trigger('data', data); - if (data.method) { - that.trigger('do:'+data.method, data.data || {}); - } - }; - this.sock.onclose = function() { - that.trigger("close"); - }; - }, + that.trigger('data', data); + if (data.method) { + that.trigger('do:'+data.method, data.data || {}); + } + }; + this.sock.onclose = function() { + that.trigger("close"); + }; + }, - // Send a message - send: function(message) { - this.sock.send(JSON.stringify(message)); - return this; - }, + // Send a message + send: function(message) { + this.sock.send(JSON.stringify(message)); + return this; + }, - // Call a method - do: function(method, data) { - return this.send({ - 'method': method, - 'data': data - }); - }, + // Call a method + do: function(method, data) { + return this.send({ + 'method': method, + 'data': data + }); + }, - // Close the connection - close: function() { - this.sock.close(); - return this; - } - }); + // Close the connection + close: function() { + this.sock.close(); + return this; + } +}); - return Socket; -}); \ No newline at end of file +module.exports = Socket; diff --git a/editor/core/statusbar.js b/editor/core/statusbar.js deleted file mode 100644 index 3df3f6fb..00000000 --- a/editor/core/statusbar.js +++ /dev/null @@ -1,12 +0,0 @@ -define([ - "hr/hr" -], function(hr) { - var StatusBar = hr.View.extend({ - - }); - - var status = new hr.Model(); - status.view = new StatusBar(); - - return status; -}); \ No newline at end of file diff --git a/editor/core/user.js b/editor/core/user.js index ac8e5520..49e13c8c 100644 --- a/editor/core/user.js +++ b/editor/core/user.js @@ -1,7 +1,3 @@ -define([ - "models/user" -], function(User) { - var user = new User(); +var User = require("../models/user"); - return user; -}); \ No newline at end of file +module.exports = new User(); diff --git a/editor/core/users.js b/editor/core/users.js index ce72da4f..6f8f8676 100644 --- a/editor/core/users.js +++ b/editor/core/users.js @@ -1,13 +1,11 @@ -define([ - "collections/users", - "core/events" -], function(Users, events) { - var users = new Users(); +var Users = require("../collections/users"); +var events = require("./events"); - // Listen to update - events.on("e:users", function() { - users.listAll(); - }); +var users = new Users(); - return users; -}); \ No newline at end of file +// Listen to update +events.on("e:users", function() { + users.listAll(); +}); + +module.exports = users; diff --git a/editor/core/workspace.js b/editor/core/workspace.js new file mode 100644 index 00000000..6621de79 --- /dev/null +++ b/editor/core/workspace.js @@ -0,0 +1,3 @@ +var Workspace = require("../models/workspace"); + +module.exports = new Workspace(); diff --git a/editor/index.html b/editor/index.html index 2043b60d..89491e79 100644 --- a/editor/index.html +++ b/editor/index.html @@ -4,11 +4,28 @@ Codebox - + - - - \ No newline at end of file + +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ + + diff --git a/editor/main.js b/editor/main.js index ca356c45..a173d424 100644 --- a/editor/main.js +++ b/editor/main.js @@ -1,65 +1,80 @@ -require([ - "hr/utils", - "hr/dom", - "hr/promise", - "hr/hr", - "hr/args", - "resources/init", - "core/application", - "core/commands", - "core/packages", - "core/user", - "core/users", - "core/settings", - "utils/dialogs", - "utils/menu", - "models/file", - "utils/date", - "settings/keybindings", - "utils/upload" -], function(_, $, Q, hr, args, resources, app, commands, packages, user, users, settings, dialogs, menu, File) { - // Create the global object for packages - window.codebox = { - require: require, - app: app, - user: user, - root: new File(), - settings: settings - }; +var _ = require("hr.utils"); +var $ = require("jquery"); +var Q = require("q"); - commands.register({ - id: "settings.open", - title: "Settings: Open", - icon: "gear", - shortcuts: [ - "mod+," - ], - run: function(args, context) { - return commands.run("file.open", { - file: settings.getFile() - }); - } - }); +var logger = require("hr.logger")("app"); + +Q.onerror = function (error) { + logger.exception("Uncaught Error:", error); +}; + +var app = require("./core/application"); +var commands = require("./core/commands"); +var packages = require("./core/packages"); +var user = require("./core/user"); +var workspace = require("./core/workspace"); +var users = require("./core/users"); +var settings = require("./core/settings"); +var dialogs = require("./utils/dialogs"); +var menu = require("./utils/menu"); +var File = require("./models/file"); + +var keybindings = require("./settings/keybindings"); +var upload = require("./utils/upload"); +var codeboxRequire = require("./utils/require"); - // Start running the applications - resources() - .then(codebox.user.whoami.bind(codebox.user)) - .then(codebox.root.stat.bind(codebox.root, "./")) - .then(settings.load.bind(settings)) - .then(users.listAll.bind(users)) - .then(function() { - return packages.loadAll() - .fail(function(err) { - var message = "

"+err.message+"

"; - if (err.errors) { - message += ""; - } +// Create the global object for packages +window.codebox = { + require: codeboxRequire, + app: app, + user: user, + workspace: workspace, + root: new File(), + settings: settings +}; - return dialogs.alert(message, { html: true }) +commands.register({ + id: "settings.open", + title: "Settings: Open", + icon: "gear", + shortcuts: [ + "mod+," + ], + run: function(args, context) { + return commands.run("file.open", { + file: settings.getFile() }); - }) - .then(app.run.bind(app)) + } +}); + +// Start running the applications +logger.log("start application"); +Q.delay(500) +.then(function() { + return Q.all([ + codebox.user.whoami(), + codebox.root.stat('./'), + codebox.workspace.about(), + settings.load(), + users.listAll() + ]); +}) +.then(function() { + return packages.loadAll(!codebox.workspace.get('debug')) + .fail(function(err) { + var message = "

"+err.message+"

"; + if (err.errors) { + message += ""; + } + return dialogs.alert(message, { isHtml: true }) + }); +}) +.then(app.start.bind(app)) +.fail(function(err) { + logger.error("Error:", err.message || "", err.stack || err); + return dialogs.error(err); }); + diff --git a/editor/models/command.js b/editor/models/command.js index f140e78d..f1933d81 100644 --- a/editor/models/command.js +++ b/editor/models/command.js @@ -1,94 +1,125 @@ -define([ - "hr/hr", - "hr/utils", - "utils/keyboard" -], function(hr, _, keyboard) { - var logging = hr.Logger.addNamespace("command"); - var ARGS = { - 'number': parseInt - }; - - var Command = hr.Model.extend({ - defaults: { - // Unique id for the command - id: null, - - // Title for the command - title: null, - - // Run command - run: function(context) {}, - - // Context needed for the command - context: [], - - // Arguments - arguments: [], - - // Keyboard shortcuts - shortcuts: [] - }, - - // Constructor - initialize: function() { - Command.__super__.initialize.apply(this, arguments); - - this.justRun = function(e) { - if (e) e.preventDefault(); - this.run({}, e); - }.bind(this); - - this.listenTo(this, "change:shortcuts", this.bindKeyboard); - this.listenTo(this, "destroy", this.unbindKeyboard); - this.bindKeyboard(); - }, - - // Unbind keyboard shortcuts - unbindKeyboard: function() { - if (!this._shortcuts) return; - - keyboard.unbind(this._shortcuts, this, this.justRun); - }, - - // Bind keyboard shortcuts - bindKeyboard: function() { - this.unbindKeyboard(); - this._shortcuts = _.clone(this.get("shortcuts", [])); - keyboard.bind(this._shortcuts, this.justRun, this); - }, - - // Run a command - run: function(args, origin) { - var that = this; - - // Check context - if (!this.isValidContext()) return Q(); - - logging.log("Run", this.get("id")); - - return Q() - .then(function() { - return that.get("run").apply(that, [ args || {}, that.collection.context.data, origin ]); - }) - .fail(function(err) { - logging.error("Command failed", err); - }); - }, - - // Shortcut text - shortcutText: function() { - return keyboard.toText(this.get("shortcuts")); - }, - - // Valid context - isValidContext: function() { - var context = this.get("context") || []; - return (context.length == 0 - || !this.collection - || !this.collection.context - || _.contains(context, this.collection.context.type)); - } - }); - - return Command; -}); \ No newline at end of file +var Q = require("q"); +var _ = require("hr.utils"); +var Model = require("hr.model"); +var logger = require("hr.logger")("command"); + +var keyboard = require("../utils/keyboard"); + +var ARGS = { + 'number': parseInt +}; + +var Command = Model.extend({ + defaults: { + // Unique id for the command + id: null, + + // Title for the command + title: null, + + // Run command + run: function(context) {}, + + // Context needed for the command + context: [], + + // Arguments + arguments: [], + + // Keyboard shortcuts + shortcuts: [], + + // Hidden from command palette + hidden: false, + + // Disabled (not runnable) + enabled: true + }, + + // Constructor + initialize: function() { + Command.__super__.initialize.apply(this, arguments); + + this.justRun = function(e) { + if (e) e.preventDefault(); + this.run({}, e); + }.bind(this); + + this.listenTo(this, "change:shortcuts", this.bindKeyboard); + this.listenTo(this, "destroy", this.unbindKeyboard); + this.bindKeyboard(); + }, + + // Unbind keyboard shortcuts + unbindKeyboard: function() { + if (!this._shortcuts) return; + + keyboard.unbind(this._shortcuts, this, this.justRun); + }, + + // Bind keyboard shortcuts + bindKeyboard: function() { + this.unbindKeyboard(); + this._shortcuts = _.clone(this.get("shortcuts", [])); + keyboard.bind(this._shortcuts, this.justRun, this); + }, + + // Run a command + run: function(args, origin) { + var that = this; + + // Check context + if (!this.isRunnable()) return Q(); + + logger.log("Run", this.get("id")); + + return Q() + .then(function() { + return that.get("run").apply(that, [ args || {}, that.collection.context, origin ]); + }) + .fail(function(err) { + logger.exception("Command failed", err); + }); + }, + + // Shortcut text + shortcutText: function() { + return keyboard.toText(this.get("shortcuts")); + }, + + // Valid context + hasValidContext: function() { + var context = this.get("context") || []; + var currentContext = _.keys(this.collection.context); + + return _.difference(context, currentContext).length == 0; + }, + + // Valid a command name against this command and return a match score + resolve: function(cmd) { + var score = 0; + var parts = cmd.split("."); + var thisParts = this.get("id").split("."); + + _.each(parts, function(part, i) { + if (!thisParts[i]) return false; + + var r = new RegExp(thisParts[i]); + if (part.match(r) == null) { + return false; + } + + score = score + 1; + }); + + if (score < thisParts.length) return 0; + return (score/parts.length) + (score/thisParts.length); + }, + + // Check if command is runnable + isRunnable: function() { + return this.hasValidContext() && this.get("enabled"); + } +}); + +module.exports = Command; diff --git a/editor/models/file.js b/editor/models/file.js index 6eb7c164..13babebd 100644 --- a/editor/models/file.js +++ b/editor/models/file.js @@ -1,238 +1,326 @@ -define([ - "hr/hr", - "hr/utils", - "hr/promise", - "core/rpc", - "core/commands", - "core/events", - "utils/hash", - "utils/dialogs" -], function(hr, _, Q, rpc, commands, events, hash, dialogs) { - var File = hr.Model.extend({ - defaults: { - path: null, - name: null, - directory: false, - size: 0, - mtime: 0, - atime: 0, - buffer: null - }, - idAttribute: "name", - - // Initialize - initialize: function() { - File.__super__.initialize.apply(this, arguments); - - this.options = _.defaults(this.options, { - saveAsFile: true - }); - - this.listenTo(events, "e:fs:modified", _.partial(this._dispatchFsEvent, "modified")); - this.listenTo(events, "e:fs:deleted", _.partial(this._dispatchFsEvent, "deleted")); - this.listenTo(events, "e:fs:created", _.partial(this._dispatchFsEvent, "created")); - }, - - // Dispatch fs event - _dispatchFsEvent: function(type, paths) { - var path, childs; - - if (this.isBuffer()) return; - - path = this.get("path"); - if (_.contains(paths, path)) { - this.trigger("fs:"+type); - } - if (this.isDirectory()) { - childs = _.filter(paths, this.isChild, this); - if (childs.length > 0) { - this.trigger("fs:files:"+type); - } +var path = require("path"); +var axios = require("axios"); +var Q = require("q"); +var _ = require("hr.utils"); +var Model = require("hr.model"); +var logger = require("hr.logger")("files"); + +var rpc = require("../core/rpc"); +var commands = require("../core/commands"); +var events = require("../core/events"); +var hash = require("../utils/hash"); +var dialogs = require("../utils/dialogs"); + +var File = Model.extend({ + defaults: { + path: null, + name: null, + directory: false, + size: 0, + mtime: 0, + atime: 0, + buffer: null, + mime: "text/plain" + }, + idAttribute: "name", + + // Initialize + initialize: function() { + File.__super__.initialize.apply(this, arguments); + + this.options = _.defaults(this.options, { + saveAsFile: true + }); + + this.listenTo(events, "e:fs:modified", _.partial(this._dispatchFsEvent, "modified")); + this.listenTo(events, "e:fs:deleted", _.partial(this._dispatchFsEvent, "deleted")); + this.listenTo(events, "e:fs:created", _.partial(this._dispatchFsEvent, "created")); + }, + + // Dispatch fs event + _dispatchFsEvent: function(type, paths) { + var path, childs; + + if (this.isBuffer()) return; + + path = this.get("path"); + if (_.contains(paths, path)) { + this.trigger("fs:"+type); + } + if (this.isDirectory()) { + childs = _.filter(paths, this.isChild, this); + if (childs.length > 0) { + this.trigger("fs:files:"+type); } - }, - - // Open this file - open: function() { - return commands.run("file.open", { - path: this.get("path") - }); - }, - - // Check if is a directory - isDirectory: function() { - return this.get("directory"); - }, - - // Check if a file is a buffer or exists - isBuffer: function() { - return this.get("buffer") != null; - }, - - // Test if a path is child - isChild: function(path) { - var parts1 = _.filter(path.split("/"), function(p) { return p.length > 0; }); - var parts2 = _.filter(this.get("path").split("/"), function(p) { return p.length > 0; }); - return (parts1.length == (parts2.length+1)); - }, - - // List files in this directory - list: function() { - return rpc.execute("fs/list", { - 'path': this.get("path") - }) - .then(function(files) { - return _.map(files, function(file) { - return new File({}, file); - }); + } + }, + + // Open this file + open: function() { + return commands.run("file.open."+this.getExtension().slice(1), { + path: this.get("path") + }); + }, + + // Check if is a directory + isDirectory: function() { + return this.get("directory"); + }, + + // Check if a file is a buffer or exists + isBuffer: function() { + return _.isString(this.get("buffer")); + }, + + // Test if a path is child + isChild: function(path) { + var parts1 = _.filter(path.split("/"), function(p) { return p.length > 0; }); + var parts2 = _.filter(this.get("path").split("/"), function(p) { return p.length > 0; }); + return (parts1.length == (parts2.length+1)); + }, + + // List files in this directory + list: function() { + return rpc.execute("fs/list", { + 'path': this.get("path") + }) + .then(function(files) { + return _.map(files, function(file) { + return new File({}, file); }); - }, - - // Get a specific file - stat: function(path) { - var that = this; - - return rpc.execute("fs/stat", { - 'path': path - }) - .then(function(file) { - that.del("buffer", { silent: true }); - return that.set(file); - }) - .thenResolve(that); - }, - - // Read file content - read: function() { - if (this.isBuffer()) return Q(this.get("buffer")); - - return rpc.execute("fs/read", { + }); + }, + + // Get a specific file + stat: function(path) { + var that = this; + + return rpc.execute("fs/stat", { + 'path': path + }) + .then(function(file) { + that.del("buffer", { silent: true }); + return that.set(file); + }) + .thenResolve(that); + }, + + // Read file content + read: function(opts) { + opts = _.defaults(opts || {}, { + base64: false + }); + + var p; + + if (this.isBuffer()) p = Q(hash.btoa(this.get("buffer"))); + else { + p = rpc.execute("fs/read", { 'path': this.get("path") }) - .get("content") - .then(hash.atob); - }, - - // Write file content - write: function(content) { - var that = this; - - return Q() - .then(function() { - if (that.isBuffer()) return Q(that.set("buffer", content)); + .get("content"); + } - return rpc.execute("fs/write", { - 'path': that.get("path"), - 'content': hash.btoa(content) + if (!opts.base64) p = p.then(hash.atob); + + return p; + }, + + // Access url + accessUrl: function() { + return "/fs/"+this.get("path"); + }, + + // Write file content + write: function(content, opts) { + var that = this; + opts = _.defaults(opts || {}, { + base64: false + }); + + return Q() + .then(function() { + if (that.isBuffer()) { + return File.blobToString(content) + .then(function(s) { + that.set("buffer", s); }); - }) - .then(function() { - that.trigger("write", content); - }); - }, - - // Rename - rename: function(name) { - var that = this; - if (this.isBuffer()) return Q(); - - return rpc.execute("fs/rename", { - 'from': this.get("path"), - 'name': name - }) - .then(function(f) { - that.set(f); - }); - }, - - // Remove this file - remove: function() { - if (this.isBuffer()) return Q(this.destroy()); + } - return rpc.execute("fs/remove", { - 'path': this.get("path") - }) - .then(this.destroy.bind(this)); - }, - - // Create a file in this folder - create: function(name) { - return File.create(this.get("path"), name); - }, - - // Create a folder in this folder - mkdir: function(name) { - return File.mkdir(this.get("path"), name); - }, - - // Get by extension - getExtension: function() { - return "."+this.get("name").split('.').pop(); - }, - - // Save file - save: function(content) { - var that = this; - - return Q() - .then(function() { - if (!that.isBuffer() || !that.options.saveAsFile) return that.write(content); - - return dialogs.prompt("Save as:", that.get("name")) - .then(function(_path) { - return rpc.execute("fs/write", { - 'path': _path, - 'content': hash.btoa(content), - 'override': false - }) - .then(function() { - return that.stat(_path); - }) - .fail(dialogs.error); - }); + return File.writeContent(that.get("path"), content); + }) + .then(function() { + that.trigger("write", content); + }); + }, + + // Rename + rename: function(name) { + var that = this; + if (this.isBuffer()) return Q(); + + return rpc.execute("fs/rename", { + 'from': this.get("path"), + 'name': name + }) + .then(function(f) { + that.set(f); + }); + }, + + // Remove this file + remove: function() { + if (this.isBuffer()) return Q(this.destroy()); + + return rpc.execute("fs/remove", { + 'path': this.get("path") + }) + .then(this.destroy.bind(this)); + }, + + // Create a file in this folder + create: function(name) { + return File.create(this.get("path"), name); + }, + + // Create a folder in this folder + mkdir: function(name) { + return File.mkdir(this.get("path"), name); + }, + + // Get by extension + getExtension: function() { + return "."+this.get("name").split('.').pop(); + }, + + // Save file + save: function(content, opts) { + var that = this; + + return Q() + .then(function() { + if (!that.isBuffer() || !that.options.saveAsFile) return that.write(content, opts); + + return dialogs.prompt("Save as:", that.get("name")) + .then(function(filename) { + return File.writeContent(filename, content, { + 'override': false + }) + .then(function() { + return that.stat(filename); + }) + .fail(dialogs.error); }); + }); + } +}, { + // Get a specific file + get: function(path) { + var f = new File(); + + return f.stat(path); + }, + + // Create a file buffer + buffer: function(name, content, id, options) { + var f = new File(options || {}, { + 'name': name, + 'buffer': content || "", + 'path': "buffer://"+(id || _.uniqueId("tmp")), + 'directory': false + }); + + return f; + }, + + // Create a new file + create: function(path, name) { + return rpc.execute("fs/create", { + 'path': path, + 'name': name + }) + .then(function(f) { + return new File({}, f); + }); + }, + + // Create a new folder + mkdir: function(path, name) { + return rpc.execute("fs/mkdir", { + 'path': path, + 'name': name + }) + .then(function(f) { + return new File({}, f); + }); + }, + + // Save as + saveAs: function(filename, content, opts) { + var f = File.buffer(filename); + return f.save(content, opts); + }, + + // Convert blob to string + blobToString: function(b) { + var d = Q.defer(); + + if (b instanceof Blob) { + var reader = new window.FileReader(); + reader.onerror = function(err) { + d.reject(err); + } + reader.onload = function() { + d.resolve(reader.result); + }; + reader.readAsText(b); + } else { + d.resolve(b); } - }, { - // Get a specific file - get: function(path) { - var f = new File(); - - return f.stat(path); - }, - - // Create a file buffer - buffer: function(name, content, id, options) { - var f = new File(options || {}, { - 'name': name, - 'buffer': content, - 'path': "buffer://"+(id || _.uniqueId("tmp")), - 'directory': false - }); - return f; - }, + return d.promise; + }, + + // Write content to a file (blob, arraybuffer, string) + writeContent: function(filename, content, opts) { + opts = _.defaults(opts || {}, { + base64: false + }); + var useUpload = false; + + return Q() + .then(function() { + if (_.isString(content)) { + if (opts.base64) return content; + + useUpload = (content.length > 1000); + opts.base64 = true; + return hash.btoa(content); + } else { + useUpload = true; + return content; + } + }) + .then(function(_content) { + if (useUpload) { + opts.path = path.dirname(filename); - // Create a new file - create: function(path, name) { - return rpc.execute("fs/create", { - 'path': path, - 'name': name - }) - .then(function(f) { - return new File({}, f); - }); - }, + var data = new FormData(); + var blob = new Blob([_content]); - // Create a new folder - mkdir: function(path, name) { - return rpc.execute("fs/mkdir", { - 'path': path, - 'name': name - }) - .then(function(f) { - return new File({}, f); - }); - } - }); + _.each(opts, function(value, key) { + data.append(key, JSON.stringify(value)); + }); + data.append("content", blob, path.basename(filename)); + + return Q(axios.put('/rpc/fs/upload', data)); + } else { + opts.path = filename; + opts.content = _content; + return rpc.execute("fs/write", opts); + } + }); + } +}); - return File; -}); \ No newline at end of file +module.exports = File; diff --git a/editor/models/package.js b/editor/models/package.js index 446398c8..59d97560 100644 --- a/editor/models/package.js +++ b/editor/models/package.js @@ -1,74 +1,84 @@ -define([ - "hr/hr" -], function(hr) { - var logging = hr.Logger.addNamespace("package"); - - var Package = hr.Model.extend({ - defaults: { - name: null, - errors: [] - }, - idAttribute: "name", - - /* - * Return base url for the addon - */ - url: function() { - var basePath = window.location.pathname; - basePath = basePath.substring(0, basePath.lastIndexOf('/')+1); - return basePath+"packages/"+this.get("name"); - }, - - /** - * Load the addon - */ - load: function() { - var context, main, pkgRequireConfig, pkgRequire, that = this - var d = Q.defer(); - - if (!this.get("main")) return Q(); - - logging.log("Load", this.get("name")); - context = "package."+this.get("name"); - main = this.get("main", "index"); - - // Require config - pkgRequireConfig = { - 'context': context, - 'baseUrl': this.url(), - 'waitSeconds': 200, - 'paths': {}, - 'map': { - '*': { - 'css': 'require-tools/css/css', - 'less': 'require-tools/less/less', - 'text': 'require-tools/text/text' - } - } - }; - pkgRequireConfig.paths[main] = "pkg-build"; - - // Require context - pkgRequire = require.config(pkgRequireConfig); - - // Load main module - pkgRequire([main], function(globals) { - d.resolve() - }, function(err) { - logging.error(err); - d.reject(err); - }); - - return d.promise.timeout(5000, "This addon took to long to load (> 5seconds)"); - }, - - /** - * Return version as a number - */ - version: function() { - return parseInt(this.get("version").replace(/\./g,"")); +var Q = require("q"); +var $ = require("jquery"); +var _ = require("hr.utils"); +var Model = require("hr.model"); +var logger = require("hr.logger")("package"); + +function getScript(url, callback) { + var head = document.getElementsByTagName("head")[0]; + var script = document.createElement("script"); + script.src = url; + + // Handle Script loading + { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function(){ + if ( !done && (!this.readyState || + this.readyState == "loaded" || this.readyState == "complete") ) { + done = true; + if (callback) + callback(); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; } - }); + }; + + script.onerror = function(err) { + callback(err); + }; + } + + head.appendChild(script); + + // We handle everything using the script element injection + return undefined; +} + +var Package = Model.extend({ + defaults: { + name: null, + errors: [] + }, + idAttribute: "name", + + /* + * Return base url for the addon + */ + url: function() { + var basePath = window.location.pathname; + basePath = basePath.substring(0, basePath.lastIndexOf('/')+1); + return basePath+"packages/"+this.get("name"); + }, + + /** + * Load the addon + */ + load: function() { + var context, main, pkgRequireConfig, pkgRequire, that = this + var d = Q.defer(); + + if (!this.get("browser")) return Q(); + + logger.log("Load", this.get("name")); + getScript(this.url()+"/pkg-build.js", function(err) { + if (!err) return d.resolve(); + + logger.exception("Error loading plugin:", err); + d.reject(err); + }); + + return d.promise.timeout(10000, "This addon took to long to load (> 10seconds)"); + }, + + /** + * Return version as a number + */ + version: function() { + return parseInt(this.get("version").replace(/\./g,"")); + } +}); - return Package; -}); \ No newline at end of file +module.exports = Package; diff --git a/editor/models/schema.js b/editor/models/schema.js index 8ecf5d69..4924e0fb 100644 --- a/editor/models/schema.js +++ b/editor/models/schema.js @@ -1,49 +1,48 @@ -define([ - "hr/hr" -], function(hr) { - var _getDefaults = function(schema) { - if (typeof schema['default'] !== 'undefined') { - return schema['default']; - } else if (schema.type === 'object') { - if (!schema.properties) { return {}; } - - for (var key in schema.properties) { - if (schema.properties.hasOwnProperty(key)) { - schema.properties[key] = _getDefaults(schema.properties[key]); - - if (typeof schema.properties[key] === 'undefined') { - delete schema.properties[key]; - } +var _ = require("hr.utils"); +var Model = require("hr.model"); + +var _getDefaults = function(schema) { + if (typeof schema['default'] !== 'undefined') { + return schema['default']; + } else if (schema.type === 'object') { + if (!schema.properties) { return {}; } + + for (var key in schema.properties) { + if (schema.properties.hasOwnProperty(key)) { + schema.properties[key] = _getDefaults(schema.properties[key]); + + if (typeof schema.properties[key] === 'undefined') { + delete schema.properties[key]; } } - - return schema.properties; - } else if (schema.type === 'array') { - if (!schema.items) { return []; } - return [_getDefaults(schema.items)]; } - }; - var Schema = hr.Model.extend({ - defaults: { - id: null, - schema: {} - }, + return schema.properties; + } else if (schema.type === 'array') { + if (!schema.items) { return []; } + return [_getDefaults(schema.items)]; + } +}; - initialize: function() { - Schema.__super__.initialize.apply(this, arguments); +var Schema = Model.extend({ + defaults: { + id: null, + schema: {} + }, - this.data = new hr.Model(); - }, + initialize: function() { + Schema.__super__.initialize.apply(this, arguments); - getDefaults: function(schema) { - return _getDefaults(this.toJSON().schema || {}); - }, + this.data = new Model(); + }, - getData: function() { - return _.extend({}, this.getDefaults(), this.data.toJSON()); - } - }); + getDefaults: function(schema) { + return _getDefaults(this.toJSON().schema || {}); + }, + + getData: function() { + return _.extend({}, this.getDefaults(), this.data.toJSON()); + } +}); - return Schema; -}); \ No newline at end of file +module.exports = Schema; diff --git a/editor/models/user.js b/editor/models/user.js index 95a00cf3..253be2fb 100644 --- a/editor/models/user.js +++ b/editor/models/user.js @@ -1,29 +1,28 @@ -define([ - "hr/hr", - "hr/utils", - "core/rpc" -], function(hr, _, rpc) { - var logging = hr.Logger.addNamespace("users"); +var Q = require("q"); +var _ = require("hr.utils"); +var Model = require("hr.model"); +var logger = require("hr.logger")("users"); - var User = hr.Model.extend({ - defaults: { - id: null, - name: null, - email: null, - color: null - }, +var rpc = require("../core/rpc"); - // Identify the logged in user - whoami: function() { - var that = this; +var User = Model.extend({ + defaults: { + id: null, + name: null, + email: null, + color: null + }, - return rpc.execute("users/whoami") - .then(function(data) { - return that.set(data); - }) - .thenResolve(that); - }, - }); + // Identify the logged in user + whoami: function() { + var that = this; - return User; -}); \ No newline at end of file + return rpc.execute("users/whoami") + .then(function(data) { + return that.set(data); + }) + .thenResolve(that); + }, +}); + +module.exports = User; diff --git a/editor/models/workspace.js b/editor/models/workspace.js new file mode 100644 index 00000000..ba1483c5 --- /dev/null +++ b/editor/models/workspace.js @@ -0,0 +1,26 @@ +var Q = require("q"); +var _ = require("hr.utils"); +var Model = require("hr.model"); +var logger = require("hr.logger")("workspace"); + +var rpc = require("../core/rpc"); + +var Workspace = Model.extend({ + defaults: { + id: "", + title: "" + }, + + // Identify the workspace + about: function() { + var that = this; + + return rpc.execute("codebox/about") + .then(function(data) { + return that.set(data); + }) + .thenResolve(that); + }, +}); + +module.exports = Workspace; diff --git a/editor/resources/images/icons/200.png b/editor/resources/images/icons/200.png new file mode 100644 index 00000000..941f6e56 Binary files /dev/null and b/editor/resources/images/icons/200.png differ diff --git a/editor/resources/init.js b/editor/resources/init.js deleted file mode 100644 index 1919172d..00000000 --- a/editor/resources/init.js +++ /dev/null @@ -1,12 +0,0 @@ -define([ - "hr/hr", - "hr/promise" -], function(hr, Q) { - hr.Resources.addNamespace("templates", { - loader: "text" - }); - - return function() { - return Q(); - }; -}); \ No newline at end of file diff --git a/editor/resources/stylesheets/fonts.less b/editor/resources/stylesheets/fonts.less index cdeec31d..4a3484e5 100644 --- a/editor/resources/stylesheets/fonts.less +++ b/editor/resources/stylesheets/fonts.less @@ -3,7 +3,7 @@ font-family: 'Source Code Pro'; font-style: normal; font-weight: 300; - src: local('Source Code Pro Light'),url('@{FontPath}//source-code-pro/woff/OTF/300.woff') format('woff'); + src: local('Source Code Pro Light'),url('@{FontPath}/source-code-pro/woff/OTF/300.woff') format('woff'); } @font-face { font-family: 'Source Code Pro'; diff --git a/editor/resources/stylesheets/grid.less b/editor/resources/stylesheets/grid.less new file mode 100644 index 00000000..af368336 --- /dev/null +++ b/editor/resources/stylesheets/grid.less @@ -0,0 +1,23 @@ +// Columns +.row { + .clearfix(); + margin-left: -5px; + margin-right: -5px; + + [class*='col-'] { + float: left; + width: 100%; + padding-left: 5px; + padding-right: 5px; + } + + .col-50 { + width: 50%; + } + .col-100 { + width: 100%; + } + .col-25 { + width: 25%; + } +} diff --git a/editor/resources/stylesheets/main.less b/editor/resources/stylesheets/main.less index 853e227c..37ff9fa4 100644 --- a/editor/resources/stylesheets/main.less +++ b/editor/resources/stylesheets/main.less @@ -1,9 +1,10 @@ -@import (less) "../../vendors/octicons/octicons/octicons.css"; +@import "./node_modules/octicons/octicons/octicons.less"; @import "normalize.less"; @import "variables.less"; @import "fonts.less"; +@import "grid.less"; @import "ui/dialogs.less"; @import "ui/form.less"; @@ -11,6 +12,7 @@ @import "ui/buttons.less"; @import "ui/grid.less"; @import "ui/menu.less"; +@import "ui/loading.less"; * { .box-sizing(border-box); @@ -25,11 +27,21 @@ body, html { font-size: @font-size-normal; width: 100%; height: 100%; - background: @color-0; + background: @color-0-0; color: @color-7; .user-select(none); .octicon { font-size: inherit; } + + .main-application { + position: fixed; + top: 0px; + bottom: 0px; + right: 0px; + left: 0px; + z-index: 1; + background: @color-0-0; + } } diff --git a/editor/resources/stylesheets/ui/buttons.less b/editor/resources/stylesheets/ui/buttons.less index af83bfe7..316820b9 100644 --- a/editor/resources/stylesheets/ui/buttons.less +++ b/editor/resources/stylesheets/ui/buttons.less @@ -7,8 +7,13 @@ &:hover, &:focus { outline: none; } + + &.button-block { + width: 100%; + display: block; + } } button { .button(); -} \ No newline at end of file +} diff --git a/editor/resources/stylesheets/ui/dialogs.less b/editor/resources/stylesheets/ui/dialogs.less index eecea418..00938ba2 100644 --- a/editor/resources/stylesheets/ui/dialogs.less +++ b/editor/resources/stylesheets/ui/dialogs.less @@ -1,36 +1,59 @@ .component-dialog { position: absolute; z-index: 1000; - top: 0px; - left: 50%; - - width: @dialog-size-medium; - margin-left: -@dialog-size-medium/2; - - background: @color-1; + bottom: 0px; + left: 0px; + right: 0px; + background: rgba(0, 0, 0, 0.4); + overflow-y: auto; - &.size-large { + &.size-large .dialog-wrapper { width: @dialog-size-large; margin-left: -@dialog-size-large/2; } - &.size-small { + &.size-small .dialog-wrapper { width: @dialog-size-small; margin-left: -@dialog-size-small/2; } - .dialog-list { - input { - width: 100%; - } + .dialog-wrapper { + position: absolute; + z-index: 1001; + + margin: 2% 0px; + left: 50%; + + width: @dialog-size-medium; + margin-left: -@dialog-size-medium/2; - .ui-content-list { - background: @dialog-list-background; + background: @color-0-1; + + .dialog-list { + input { + width: 100%; + } + + .ui-content-list { + background: @dialog-list-background; + } } - } - .dialog-input { - padding: 5px; + .dialog-input { + .dialog-header { + background: @color-0-2; + padding: 8px 5px; + } + + .dialog-body { + padding: 5px; + } + + .dialog-actions { + margin-top: 15px; + padding: 5px; + } + } } } diff --git a/editor/resources/stylesheets/ui/grid.less b/editor/resources/stylesheets/ui/grid.less index b2be51a8..b3e02479 100644 --- a/editor/resources/stylesheets/ui/grid.less +++ b/editor/resources/stylesheets/ui/grid.less @@ -1,4 +1,4 @@ -.component-grid { +.grid { position: absolute; top: 0px; left: 0px; diff --git a/editor/resources/stylesheets/ui/list.less b/editor/resources/stylesheets/ui/list.less index 556038aa..707211b5 100644 --- a/editor/resources/stylesheets/ui/list.less +++ b/editor/resources/stylesheets/ui/list.less @@ -5,7 +5,7 @@ padding: 0px; background: @color-7; - color: @color-1; + color: @color-0-1; overflow-y: auto; max-height: @dialog-list-maxheight; @@ -34,4 +34,4 @@ .hidden(); } } -} \ No newline at end of file +} diff --git a/editor/resources/stylesheets/ui/loading.less b/editor/resources/stylesheets/ui/loading.less new file mode 100644 index 00000000..b81855e8 --- /dev/null +++ b/editor/resources/stylesheets/ui/loading.less @@ -0,0 +1,94 @@ +.loading-welcome { + width: 70px; + height: 70px; + + position: fixed; + top: 50%; + left: 50%; + z-index: 0; + + margin-left: -35px; + margin-top: -35px; + + + .block { + position:relative; + height:20px; + width:20px; + display:inline-block; + background: @color-10; + transition:all 0.8s; + -webkit-animation: rot 3.4s linear infinite; + animation: rot 3.4s linear infinite; + } + .block:nth-child(1) { + -webkit-animation-delay:3s; + animation-delay:3s; + } + .block:nth-child(2) { + -webkit-animation-delay:1.5s; + animation-delay:1.5s; + -webkit-animation: rot 12s linear infinite; + animation: rot 15s linear infinite; + } + .block:nth-child(3) { + -webkit-animation-delay:2s; + animation-delay:2s; + } + .block:nth-child(4) { + -webkit-animation-delay:0.2s; + animation-delay:0.2s; + } + .block:nth-child(5) { + -webkit-animation-delay:4s; + animation-delay:4s; + } + .block:nth-child(6) { + -webkit-animation-delay:2s; + animation-delay:2s; + -webkit-animation: rot 7s linear infinite; + animation: rot 7s linear infinite; + } + .block:nth-child(7) { + -webkit-animation-delay:0.4s; + animation-delay:0.4s; + } + .block:nth-child(8) { + -webkit-animation-delay:1.5s; + animation-delay:1.5s; + -webkit-animation: rot 6s linear infinite; + animation: rot 6s linear infinite; + } + .block:nth-child(9) { + -webkit-animation-delay:25s; + animation-delay:25s; + -webkit-animation: rot 8s linear infinite; + animation: rot 8s linear infinite; + } + + .rot () { + 0% { + transform:none; + } + 20% { + transform:rotateZ(-90deg) rotateY(180deg); + } + 40% { + background: @color-8; + transform:none; + } + 60% { + background:white + } + 80% { + background: @color-13; + } + 90% { + transform:none; + background: @color-0-0; + } + } + + @-webkit-keyframes rot { .rot; } + @keyframes rot { .rot; } +} diff --git a/editor/resources/stylesheets/variables.less b/editor/resources/stylesheets/variables.less index 2c75bbc4..b46c9c6d 100644 --- a/editor/resources/stylesheets/variables.less +++ b/editor/resources/stylesheets/variables.less @@ -1,22 +1,27 @@ @import "./utilities.less"; // Fonts -@FontPath: "fonts/"; +@FontPath: "../fonts"; +@octicons-font-path: "@{FontPath}/octicons"; // Colors // Color palette is based on base16 ocean -@color-0: #131313; // done -@color-1: #323232; // done -@color-2: #2b2b2b; // done -@color-3: #b8b8b8; // done +// http://chriskempson.github.io/base16/#ocean +@color-0: #2b303b; //; // dark blue +@color-0-0: #1a1d24; +@color-0-1: #22262e; +@color-0-2: #2b303b; +@color-1: #343d46; +@color-2: #4f5b66; +@color-3: #65737e; @color-4: #a7adba; @color-5: #c0c5ce; @color-6: #dfe1e8; -@color-7: #9c9c9c; // done +@color-7: #c5c8c6; @color-8: #bf616a; @color-9: #d08770; @color-10: #ebcb8b; -@color-11: #1aaf5d; // done +@color-11: #a3be8c; @color-12: #96b5b4; @color-13: #8fa1b3; @color-14: #b48ead; @@ -50,10 +55,10 @@ @auth-form-height: 150px; // Forms -@input-background: @color-0; +@input-background: @color-0-0; @input-color: @color-7; @input-border-size: 2px; -@input-border-color: @color-2; +@input-border-color: @color-0-2; @input-padding: 5px; // Buttons diff --git a/editor/resources/templates/dialogs/alert.html b/editor/resources/templates/dialogs/alert.html index bc5d759d..d65608ba 100644 --- a/editor/resources/templates/dialogs/alert.html +++ b/editor/resources/templates/dialogs/alert.html @@ -1,9 +1,14 @@ -<% if (options.isHtml) { %> +
+<% if (!options.isHtml) { %>

<%- options.text %>

<% } else { %> <%= options.text %> <% } %> - -
- -
\ No newline at end of file +
+
+
+
+ +
+
+
diff --git a/editor/resources/templates/dialogs/confirm.html b/editor/resources/templates/dialogs/confirm.html index d81c01d4..443222e0 100644 --- a/editor/resources/templates/dialogs/confirm.html +++ b/editor/resources/templates/dialogs/confirm.html @@ -1,6 +1,14 @@ -

<%- options.text %>

+
+ <%- options.text %> +
-
- - -
\ No newline at end of file +
+
+
+ +
+
+ +
+
+
diff --git a/editor/resources/templates/dialogs/prompt.html b/editor/resources/templates/dialogs/prompt.html index f4f8e79c..6da9c965 100644 --- a/editor/resources/templates/dialogs/prompt.html +++ b/editor/resources/templates/dialogs/prompt.html @@ -1,9 +1,16 @@ -
- - +
+
+ + +
+
+
+
+
+ +
+
+ +
+
- -
- - -
\ No newline at end of file diff --git a/editor/resources/templates/dialogs/schema.html b/editor/resources/templates/dialogs/schema.html index d5ae55a6..70699a9d 100644 --- a/editor/resources/templates/dialogs/schema.html +++ b/editor/resources/templates/dialogs/schema.html @@ -1,5 +1,7 @@ -

<%- options.schema.title %>

- +
+ <%- options.schema.title %> +
+
<% _.each(options.schema.properties, function(property, key) { var value = options.defaultValues[key] || property.default; @@ -16,8 +18,14 @@
<% } %> <% }); %> - -
- - -
\ No newline at end of file +
+
+
+
+ +
+
+ +
+
+
diff --git a/editor/settings/keybindings.js b/editor/settings/keybindings.js index 14aa2b63..8aa3cde6 100644 --- a/editor/settings/keybindings.js +++ b/editor/settings/keybindings.js @@ -1,55 +1,54 @@ -define([ - "core/commands", - "core/settings" -], function(commands, settings) { - /* - * The key bindings configuration allow the user to - * change the default keyboard shortcuts for specific commands - */ - - var keyBindings = settings.schema("keybindings", { - title: "Key bindings", - type: "object", - properties: { - commands: { - type: "array", - items: { - "command": { - type: "string" - }, - "keys": { - type: "array" - } +var _ = require("hr.utils"); +var commands = require("../core/commands"); +var settings = require("../core/settings"); + +/* + * The key bindings configuration allow the user to + * change the default keyboard shortcuts for specific commands + */ + +var keyBindings = settings.schema("keybindings", { + title: "Key bindings", + type: "object", + properties: { + commands: { + type: "array", + items: { + "command": { + type: "string" + }, + "keys": { + type: "array" } } } - }); - - // Update a command - var updateCommand = function(cmd) { - var bindings = keyBindings.data.get("commands"); - var bind = _.find(bindings, { 'command': cmd.id }); - - if (!bind) { - if (cmd.get("originalShortcuts")) cmd.set("shortcuts", cmd.get("originalShortcuts")); - } else { - if (!cmd.get("originalShortcuts")) cmd.set("originalShortcuts", cmd.get("shortcuts")); - cmd.del("shortcuts", { silent: true }); - cmd.set("shortcuts", bind.keys); - } - }; + } +}); + +// Update a command +var updateCommand = function(cmd) { + var bindings = keyBindings.data.get("commands"); + var bind = _.find(bindings, { 'command': cmd.id }); + + if (!bind) { + if (cmd.get("originalShortcuts")) cmd.set("shortcuts", cmd.get("originalShortcuts")); + } else { + if (!cmd.get("originalShortcuts")) cmd.set("originalShortcuts", cmd.get("shortcuts")); + cmd.del("shortcuts", { silent: true }); + cmd.set("shortcuts", bind.keys); + } +}; - // Update all commands - var updateAll = function() { - commands.each(updateCommand); - }; +// Update all commands +var updateAll = function() { + commands.each(updateCommand); +}; - // Update commands everytime settings change and adapt new commands - keyBindings.data.on("change", updateAll); - commands.on("add", updateCommand); - commands.on("reset", updateAll); +// Update commands everytime settings change and adapt new commands +keyBindings.data.on("change", updateAll); +commands.on("add", updateCommand); +commands.on("reset", updateAll); - updateAll +updateAll(); - return keyBindings; -}); \ No newline at end of file +module.exports = keyBindings; diff --git a/editor/utils/date.js b/editor/utils/date.js deleted file mode 100644 index 6e7d80ad..00000000 --- a/editor/utils/date.js +++ /dev/null @@ -1,18 +0,0 @@ -define([ - 'hr/hr', - 'moment' -], function (hr, moment) { - var relativeDate = function(d) { - return moment(d).fromNow(); - }; - - var date = { - 'relative': relativeDate - }; - - hr.Template.extendContext({ - '$date': date - }); - - return date; -}); \ No newline at end of file diff --git a/editor/utils/dialogs.js b/editor/utils/dialogs.js index 3e017038..aff9513f 100644 --- a/editor/utils/dialogs.js +++ b/editor/utils/dialogs.js @@ -1,136 +1,137 @@ -define([ - "hr/utils", - "hr/promise", - "hr/hr", - "views/dialogs/container", - "views/dialogs/input", - "views/dialogs/list", - "text!resources/templates/dialogs/alert.html", - "text!resources/templates/dialogs/confirm.html", - "text!resources/templates/dialogs/prompt.html", - "text!resources/templates/dialogs/schema.html" -], function(_, Q, hr, Dialog, DialogInputView, DialogListView, -alertTemplate, confirmTemplate, promptTemplate, schemaTemplate) { - - // Open a dialog - var open = function(View, options) { - var d = Q.defer(); - - // Create the dialog - var diag = new Dialog(_.extend(options || {}, { - View: View - })); - - // Bind close - diag.on("close", function(force) { - if (force) return d.reject(new Error("Dialog was been closed")); - d.resolve(diag.view); +var _ = require("hr.utils"); +var Q = require("q"); +var Collection = require("hr.collection"); + +var Dialog = require("../views/dialogs/container"); +var DialogInputView = require("../views/dialogs/input"); +var DialogListView = require("../views/dialogs/list"); + +var alertTemplate = require("../resources/templates/dialogs/alert.html"); +var confirmTemplate = require("../resources/templates/dialogs/confirm.html"); +var promptTemplate = require("../resources/templates/dialogs/prompt.html"); +var schemaTemplate = require("../resources/templates/dialogs/schema.html"); + +// Open a dialog +var open = function(View, options) { + var d = Q.defer(); + + // Create the dialog + var diag = new Dialog(_.extend(options || {}, { + View: View + })); + + // Bind close + diag.on("close", function(force) { + if (force) return d.reject(new Error("Dialog was been closed")); + d.resolve(diag.view); + }); + + // Open it (add it to dom) + diag.update(); + + return d.promise; +}; + +// Input dialog +var openInput = function(viewOptions, options, View) { + return open(View || DialogInputView, _.extend(options || {}, { + view: viewOptions || {} + })) + .then(function(view) { + var value = view.getValue(); + + if (value == null) return Q.reject(new Error("Dialog return empty value")); + return value; + }); +}; + +// Alert +var openAlert = function(text, options) { + options = _.defaults(options || {}, { + isHtml: false + }); + return openInput({ + template: alertTemplate, + text: text, + isHtml: options.isHtml + }); +}; +var openErrorAlert = function(err) { + console.log("error", err); + return openAlert("Error: "+(err.message || err)) + .fin(function() { + return Q.reject(err); + }); +}; + +// Confirm +var openConfirm = function(text, options) { + return openInput({ + template: confirmTemplate, + text: text + }); +}; + +// Prompt +var openPrompt = function(text, value, options) { + return openInput({ + template: promptTemplate, + text: text, + defaultValue: value, + value: function(d) { return d.$("input").val(); } + }); +}; + +// List +var openList = function(source, options) { + if (_.isArray(source)) { + source = new Collection({ + models: _.map(source, function(item) { + if (!_.isObject(item)) return { value: item }; + return item; + }) }); - - // Open it (add it to dom) - diag.render(); - - return d.promise; - }; - - // Input dialog - var openInput = function(viewOptions, options, View) { - return open(View || DialogInputView, _.extend(options || {}, { - view: viewOptions || {} - })) - .then(function(view) { - var value = view.getValue(); - - if (value == null) return Q.reject(new Error("Dialog return empty value")); - return value; - }); - }; - - // Alert - var openAlert = function(text, options) { - options = _.defaults(options || {}, { - isHtml: false - }); - return openInput({ - template: alertTemplate, - text: text, - isHtml: options.isHtml - }); - }; - var openErrorAlert = function(err) { - return openAlert("Error: "+(err.message || err)) - .fin(function() { - return Q.reject(err); - }); - }; - - // Confirm - var openConfirm = function(text, options) { - return openInput({ - template: confirmTemplate, - text: text - }); - }; - - // Prompt - var openPrompt = function(text, value, options) { - return openInput({ - template: promptTemplate, - text: text, - defaultValue: value, - value: function(d) { return d.$("input").val(); } - }); - }; - - // List - var openList = function(source, options) { - if (_.isArray(source)) { - source = new hr.Collection({ - models: _.map(source, function(item) { - if (!_.isObject(item)) return { value: item }; - return item; - }) + } + + return openInput( + _.extend({ + template: "
<%- item.get('value') %>
", + placeholder: "", + filter: function() { return true; } + }, options, { + source: source + }), {}, DialogListView); +}; + +// Schema +var openSchema = function(schema, values) { + values = values || {}; + + return openInput({ + template: schemaTemplate, + schema: schema, + defaultValues: values, + value: function(d) { + var nvalues = _.clone(values); + + _.each(schema.properties, function(property, key) { + var v = d.$("*[name='"+key+"']").val(); + nvalues[key] = v; }); - } - return openInput( - _.extend({ - template: "
<%- item.get('value') %>
", - placeholder: "", - filter: function() { return true; } - }, options, { - source: source - }), {}, DialogListView); - }; - - // Schema - var openSchema = function(schema, values) { - values = values || {}; - - return openInput({ - template: schemaTemplate, - schema: schema, - defaultValues: values, - value: function(d) { - var nvalues = _.clone(values); - - _.each(schema.properties, function(property, key) { - var v = d.$("*[name='"+key+"']").val(); - nvalues[key] = v; - }); - - return nvalues; - } - }); - }; - - return { - open: open, - alert: openAlert, - error: openErrorAlert, - confirm: openConfirm, - prompt: openPrompt, - list: openList, - schema: openSchema - }; -}); \ No newline at end of file + return nvalues; + } + }); +}; + + +module.exports = { + open: open, + alert: openAlert, + error: openErrorAlert, + confirm: openConfirm, + prompt: openPrompt, + list: openList, + schema: openSchema, + input: openInput +}; diff --git a/editor/utils/dragdrop.js b/editor/utils/dragdrop.js deleted file mode 100644 index 02eea172..00000000 --- a/editor/utils/dragdrop.js +++ /dev/null @@ -1,258 +0,0 @@ -define([ - 'hr/utils', - 'hr/hr', - 'hr/dom' -], function (_, hr, $) { - // Events for tablet and desktop - var events = { - 'start': "mousedown", - 'stop': "mouseup", - 'move': "mousemove", - 'enter': "mouseenter", - 'leave': "mouseleave" - }; - - if (navigator.userAgent.search('Mobile') > 0) { - events = { - 'start': "touchstart", - 'stop': "touchend", - 'move': "touchmove", - 'enter': "touchenter", - 'leave': "touchleave" - }; - } - - // Define cursor - var storedStylesheet = null; - var setCursor = function(cs) { - // Reset cursor - if (storedStylesheet) storedStylesheet.remove(); - storedStylesheet = null; - - // Set new cursor - if (cs) storedStylesheet = $( "" ).appendTo($("body")); - }; - var resetCursor = _.partial(setCursor, null); - - var DropArea = hr.Class.extend({ - defaults: { - // View for this area - view: null, - - // Class when drop data - className: "dragover", - - // Draggable type - dragType: null, - - // Handler for drop - handler: null, - - // Constrain elastic - constrain: null - }, - - initialize: function() { - DropArea.__super__.initialize.apply(this, arguments); - var that = this; - - this.view = this.options.view; - this.$el = this.view.$el; - - this.dragType = this.options.dragType; - - this.$el.on(events["enter"], function(e) { - if (that.dragType.isDragging()) { - e.stopPropagation(); - that.dragType.enterDropArea(that); - that.$el.addClass("dragover"); - } - }); - - this.$el.on(events["leave"], function(e) { - that.$el.removeClass("dragover"); - that.dragType.exitDropArea(); - }); - - this.on("drop", function() { - that.$el.removeClass("dragover"); - }); - - if (this.options.handler) this.on("drop", this.options.handler); - } - }); - - var DraggableType = hr.Class.extend({ - initialize: function() { - DraggableType.__super__.initialize.apply(this, arguments); - - // Data transfered - this.data = null; - - // State - this.state = true; - - // Drop handler - this.drop = []; - }, - - // Toggle enable/disable drag and drop - toggle: function(st) { - this.state = st; - return this; - }, - - // Is currently dragging data - isDragging: function() { - return this.data != null; - }, - - // Get drop - getDrop: function() { - return (this.drop.length > 0)? this.drop[this.drop.length - 1] : null; - }, - - // Enter drop area - enterDropArea: function(area) { - this.drop.push(area); - }, - - // Exit drop area - exitDropArea: function() { - this.drop.pop(); - }, - - // Enable drag and drop in a object - enableDrag: function(options) { - var that = this, $document = $(document), $el, data; - - options = _.defaults(options || {}, { - // View to drag - view: null, - - // Element to drag - el: null, - - // Data to transfer - data: null, - - // Base drop area - baseDropArea: null, - - // Before dragging - start: null, - - // Cursor - cursor: "copy" - }); - if (options.el) $el = $(options.el); - if (options.view) $el = options.view.$el, data = options.view; - if (options.data) data = options.data; - - $el.on(events["start"], function(e) { - if (e.type == 'mousedown' && e.originalEvent.button != 0) return; - if (!that.state) return; - e.preventDefault(); - e.stopPropagation(); - - var dx, dy, hasMove = false; - - // origin mouse - var oX = e.pageX; - var oY = e.pageY; - - // origin element - var poX = $el.offset().left; - var poY = $el.offset().top; - - // element new position - var ex, ey, ew, eh; - ew = $el.width(); - eh = $el.height(); - - // Constrain element - var cw, ch, cx, cy; - - if (options.start && options.start() === false) return; - - that.drop = []; - if (options.baseDropArea) that.enterDropArea(options.baseDropArea); - that.data = data; - - var f = function(e) { - var _drop = that.getDrop(); - - dx = oX - e.pageX; - dy = oY - e.pageY; - - if (Math.abs(dx) > 20 || Math.abs(dy) > 20) { - if (!hasMove) { - setCursor(options.cursor); - $el.addClass("move"); - that.trigger("drag:start"); - } - hasMove = true; - } else { - return; - } - - ex = poX - dx; - ey = poY - dy; - - if (_drop && _drop.options.constrain) { - cw = _drop.$el.width(); - ch = _drop.$el.height(); - cx = _drop.$el.offset().left; - cy = _drop.$el.offset().top; - - if (Math.abs(ey - cy) < 50) ey = cy; - if (Math.abs((ey + eh) - (cy+ch)) < 50) ey = cy + ch - eh; - if (Math.abs(ex - cx) < 50) ex = cx; - if (Math.abs((ex + ew) - (cx+cw)) < 50) ex = cx + cw - ew; - } - - $el.css({ - 'left': ex, - 'top': ey - }); - }; - - $document.on(events["move"], f); - $document.one(events["stop"], function(e) { - $document.unbind(events["move"], f); - resetCursor(); - - var _drop = that.getDrop(); - - if (hasMove && (!options.baseDropArea || !_drop || (options.baseDropArea.cid != _drop.cid))) { - if (_drop) { - _drop.trigger("drop", that.data); - } - that.trigger("drop", _drop, that.data); - } - - that.trigger("drag:end"); - - that.data = null; - that.drop = []; - - $el.removeClass("move"); - $el.css({ - 'left': "auto", - 'top': "auto" - }); - }); - }); - } - }); - - return { - events: events, - cursor: { - set: setCursor, - reset: resetCursor - }, - DropArea: DropArea, - DraggableType: DraggableType - }; -}); \ No newline at end of file diff --git a/editor/utils/hash.js b/editor/utils/hash.js index bd7b21f5..bd6c53e8 100644 --- a/editor/utils/hash.js +++ b/editor/utils/hash.js @@ -1,261 +1,249 @@ -define(function () { - var crc32table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D"; - - var utf8Encode = function (string) { - string = string.replace(/\r\n/g,"\n"); - var utftext = ""; - - for (var n = 0; n < string.length; n++) { - - var c = string.charCodeAt(n); - - if (c < 128) { - utftext += String.fromCharCode(c); - } - else if((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } - else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } +var crc32table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D"; - } - - return utftext; - }; - - /** - * Convert value as 8-bit unsigned integer to 2 digit hexadecimal number. - */ - var hex8 = function(val) - { - var n = val & 0xFF, - str = n.toString(16).toUpperCase() - ; - - while(str.length < 2) - str = "0" + str; - - return str; - }; - - /** - * Convert value as 16-bit unsigned integer to 4 digit hexadecimal number. - */ - var hex16 = function(val) - { - return hex8(val >> 8) + hex8(val); - }; - - /** - * Convert value as 32-bit unsigned integer to 8 digit hexadecimal number. - */ - var hex32 = function(val) - { - return hex16(val >> 16) + hex16(val); - }; - - var crc32= function(str) { - str = utf8Encode(str); - - var crc = 0; - var x = 0; - var y = 0; - - crc = crc ^ (-1); - for (var i = 0, iTop = str.length; i < iTop; i++) { - y = (crc ^ str.charCodeAt(i)) & 0xFF; - x = "0x" + crc32table.substr(y * 9, 8); - crc = (crc >>> 8) ^ x; - } - - return (crc ^ (-1)).toString(); - }; - - /*\ - |*| - |*| Base64 / binary data / UTF-8 strings utilities - |*| - |*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding - |*| - \*/ - - /* Array of bytes to base64 string decoding */ - - function b64ToUint6 (nChr) { - - return nChr > 64 && nChr < 91 ? - nChr - 65 - : nChr > 96 && nChr < 123 ? - nChr - 71 - : nChr > 47 && nChr < 58 ? - nChr + 4 - : nChr === 43 ? - 62 - : nChr === 47 ? - 63 - : - 0; - - } - - function base64DecToArr (sBase64, nBlocksSize) { - - var - sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, - nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); +var utf8Encode = function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; - for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { - nMod4 = nInIdx & 3; - nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; - if (nMod4 === 3 || nInLen - nInIdx === 1) { - for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { - taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; - } - nUint24 = 0; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); - } + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); } - - return taBytes; } - /* Base64 string to array encoding */ - - function uint6ToB64 (nUint6) { - - return nUint6 < 26 ? - nUint6 + 65 - : nUint6 < 52 ? - nUint6 + 71 - : nUint6 < 62 ? - nUint6 - 4 - : nUint6 === 62 ? - 43 - : nUint6 === 63 ? - 47 - : - 65; - + return utftext; +}; + +/** + * Convert value as 8-bit unsigned integer to 2 digit hexadecimal number. + */ +var hex8 = function(val) { + var n = val & 0xFF, + str = n.toString(16).toUpperCase() + ; + + while(str.length < 2) + str = "0" + str; + + return str; +}; + +/** + * Convert value as 16-bit unsigned integer to 4 digit hexadecimal number. + */ +var hex16 = function(val) { + return hex8(val >> 8) + hex8(val); +}; + +/** + * Convert value as 32-bit unsigned integer to 8 digit hexadecimal number. + */ +var hex32 = function(val) { + return hex16(val >> 16) + hex16(val); +}; + +var crc32= function(str) { + str = utf8Encode(str); + + var crc = 0; + var x = 0; + var y = 0; + + crc = crc ^ (-1); + for (var i = 0, iTop = str.length; i < iTop; i++) { + y = (crc ^ str.charCodeAt(i)) & 0xFF; + x = "0x" + crc32table.substr(y * 9, 8); + crc = (crc >>> 8) ^ x; } - function base64EncArr (aBytes) { - - var nMod3, sB64Enc = ""; - - for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { - nMod3 = nIdx % 3; - if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } - nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); - if (nMod3 === 2 || aBytes.length - nIdx === 1) { - sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); - nUint24 = 0; + return (crc ^ (-1)).toString(); +}; + +/*\ +|*| +|*| Base64 / binary data / UTF-8 strings utilities +|*| +|*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding +|*| +\*/ + +/* Array of bytes to base64 string decoding */ + +function b64ToUint6 (nChr) { + return nChr > 64 && nChr < 91 ? + nChr - 65 + : nChr > 96 && nChr < 123 ? + nChr - 71 + : nChr > 47 && nChr < 58 ? + nChr + 4 + : nChr === 43 ? + 62 + : nChr === 47 ? + 63 + : + 0; +} + +function base64DecToArr (sBase64, nBlocksSize) { + var + sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, + nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); + + for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } - } - - return sB64Enc.replace(/A(?=A$|$)/g, "="); + nUint24 = 0; + } } - - /* UTF-8 array to DOMString and vice versa */ - - function UTF8ArrToStr (aBytes) { - - var sView = ""; - - for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) { - nPart = aBytes[nIdx]; - sView += String.fromCharCode( - nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ - /* (nPart - 252 << 32) is not possible in ECMAScript! So...: */ - (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ - (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ - (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ - (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ - (nPart - 192 << 6) + aBytes[++nIdx] - 128 - : /* nPart < 127 ? */ /* one byte */ - nPart - ); + return taBytes; +} + +/* Base64 string to array encoding */ + +function uint6ToB64 (nUint6) { + return nUint6 < 26 ? + nUint6 + 65 + : nUint6 < 52 ? + nUint6 + 71 + : nUint6 < 62 ? + nUint6 - 4 + : nUint6 === 62 ? + 43 + : nUint6 === 63 ? + 47 + : + 65; +} + +function base64EncArr (aBytes) { + var nMod3, sB64Enc = ""; + + for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); + nUint24 = 0; } + } - return sView; - + return sB64Enc.replace(/A(?=A$|$)/g, "="); +} + +/* UTF-8 array to DOMString and vice versa */ + +function UTF8ArrToStr (aBytes) { + + var sView = ""; + + for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) { + nPart = aBytes[nIdx]; + sView += String.fromCharCode( + nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ + /* (nPart - 252 << 32) is not possible in ECMAScript! So...: */ + (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ + (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ + (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ + (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 + : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ + (nPart - 192 << 6) + aBytes[++nIdx] - 128 + : /* nPart < 127 ? */ /* one byte */ + nPart + ); } - function strToUTF8Arr (sDOMStr) { + return sView; - var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; +} - /* mapping... */ +function strToUTF8Arr (sDOMStr) { - for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) { - nChr = sDOMStr.charCodeAt(nMapIdx); - nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; - } + var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; - aBytes = new Uint8Array(nArrLen); - - /* transcription... */ - - for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) { - nChr = sDOMStr.charCodeAt(nChrIdx); - if (nChr < 128) { - /* one byte */ - aBytes[nIdx++] = nChr; - } else if (nChr < 0x800) { - /* two bytes */ - aBytes[nIdx++] = 192 + (nChr >>> 6); - aBytes[nIdx++] = 128 + (nChr & 63); - } else if (nChr < 0x10000) { - /* three bytes */ - aBytes[nIdx++] = 224 + (nChr >>> 12); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } else if (nChr < 0x200000) { - /* four bytes */ - aBytes[nIdx++] = 240 + (nChr >>> 18); - aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } else if (nChr < 0x4000000) { - /* five bytes */ - aBytes[nIdx++] = 248 + (nChr >>> 24); - aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } else /* if (nChr <= 0x7fffffff) */ { - /* six bytes */ - aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824); - aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } - } + /* mapping... */ - return aBytes; + for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) { + nChr = sDOMStr.charCodeAt(nMapIdx); + nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; + } + aBytes = new Uint8Array(nArrLen); + + /* transcription... */ + + for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) { + nChr = sDOMStr.charCodeAt(nChrIdx); + if (nChr < 128) { + /* one byte */ + aBytes[nIdx++] = nChr; + } else if (nChr < 0x800) { + /* two bytes */ + aBytes[nIdx++] = 192 + (nChr >>> 6); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 0x10000) { + /* three bytes */ + aBytes[nIdx++] = 224 + (nChr >>> 12); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 0x200000) { + /* four bytes */ + aBytes[nIdx++] = 240 + (nChr >>> 18); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else if (nChr < 0x4000000) { + /* five bytes */ + aBytes[nIdx++] = 248 + (nChr >>> 24); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } else /* if (nChr <= 0x7fffffff) */ { + /* six bytes */ + aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824); + aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); + aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); + aBytes[nIdx++] = 128 + (nChr & 63); + } } - return { - 'crc32': crc32, - 'hex8': hex8, - 'hex16': hex16, - 'hex32': hex32, - 'atob': function(s) { - return UTF8ArrToStr(base64DecToArr(s)); - }, - 'btoa': function(s) { - return base64EncArr(strToUTF8Arr(s)); - } - }; -}); + return aBytes; + +} + +module.exports = { + 'crc32': crc32, + 'hex8': hex8, + 'hex16': hex16, + 'hex32': hex32, + 'atob': function(s) { + return UTF8ArrToStr(base64DecToArr(s)); + }, + 'btoa': function(s) { + return base64EncArr(strToUTF8Arr(s)); + }, + 'base64': { + 'encodeArray': base64EncArr + } +}; diff --git a/editor/utils/keyboard.js b/editor/utils/keyboard.js index 3f9ca0fd..219e598c 100644 --- a/editor/utils/keyboard.js +++ b/editor/utils/keyboard.js @@ -1,116 +1,117 @@ -define([ - 'hr/hr', - 'hr/utils', - 'vendors/mousetrap/mousetrap' -], function (hr, _, Mousetrap) { - var originalStopCallback = Mousetrap.stopCallback; - Mousetrap.stopCallback = function(e, element) { - if (e.mousetrap) { - return false; - } - return originalStopCallback(e, element); - }; - - var Keyboard = hr.Class.extend({ - initialize: function() { - this.bindings = {}; - return this; - }, - - /* - * Enable keyboard shortcut for a specific event - */ - enableKeyEvent: function(e) { - e.mousetrap = true; - }, - - /* - * Bind keyboard shortcuts to callback - */ - bind: function(keys, callback, context) { - if (_.isArray(keys)) { - _.each(keys, function(key) { this.bind(key, callback, context) }, this); - return; - } - - // Map shortcut -> action - if (_.isObject(keys)) { - _.each(keys, function(method, key) { - this.bind(key, method, callback); - }, this) - return; - } - - // Bind - if (this.bindings[keys] == null) { - this.bindings[keys] = new hr.Class(); - Mousetrap.bind(keys, _.bind(function(e) { - this.bindings[keys].trigger("action", e); - }, this)); - } - context.listenTo(this.bindings[keys], "action", callback); +var Class = require("hr.class"); +var _ = require("hr.utils"); + +var Mousetrap = require("mousetrap"); + +var originalStopCallback = Mousetrap.prototype.stopCallback; +Mousetrap.prototype.stopCallback = function(e, element) { + if (e.mousetrap) { + return false; + } + return originalStopCallback.call(this, e, element); +}; + +var mousetrap = Mousetrap(document); + +var Keyboard = Class.extend({ + initialize: function() { + this.bindings = {}; + return this; + }, + + /* + * Enable keyboard shortcut for a specific event + */ + enableKeyEvent: function(e) { + e.mousetrap = true; + }, + + /* + * Bind keyboard shortcuts to callback + */ + bind: function(keys, callback, context) { + if (_.isArray(keys)) { + _.each(keys, function(key) { this.bind(key, callback, context) }, this); return; - }, - - /* - * Unbind keyboard shortcuts - */ - unbind: function(keys, context, callback) { - if (_.isArray(keys)) { - _.each(keys, function(key) { this.unbind(key, context, callback) }, this); - return; - } + } - if (!this.bindings[keys]) return; + // Map shortcut -> action + if (_.isObject(keys)) { + _.each(keys, function(method, key) { + this.bind(key, method, callback); + }, this) + return; + } - context.stopListening(this.bindings[keys], "action", callback); + // Bind + if (this.bindings[keys] == null) { + this.bindings[keys] = new Class(); + mousetrap.bind(keys, _.bind(function(e) { + this.bindings[keys].trigger("action", e); + }, this)); + } + context.listenTo(this.bindings[keys], "action", callback); + return; + }, + + /* + * Unbind keyboard shortcuts + */ + unbind: function(keys, context, callback) { + if (_.isArray(keys)) { + _.each(keys, function(key) { this.unbind(key, context, callback) }, this); return; - }, + } - /* - * Prevent default browser shortcut - */ - preventDefault: function(keys) { - return this.bind(keys, function(e) { - e.preventDefault(); - }, this); - }, + if (!this.bindings[keys]) return; - /* - * Convert shortcut or list of shortcut to a string - */ - toText: function(shortcut) { - if (_.isArray(shortcut)) shortcut = _.first(shortcut); - if (!shortcut) return null; + context.stopListening(this.bindings[keys], "action", callback); + return; + }, - var isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform); + /* + * Prevent default browser shortcut + */ + preventDefault: function(keys) { + return this.bind(keys, function(e) { + e.preventDefault(); + }, this); + }, - // Replace mod by equivalent for mac or windows - shortcut = shortcut.replace("mod", isMac ? '⌘' : 'ctrl'); + /* + * Convert shortcut or list of shortcut to a string + */ + toText: function(shortcut) { + if (_.isArray(shortcut)) shortcut = _.first(shortcut); + if (!shortcut) return null; - // Replace ctrl - shortcut = shortcut.replace("ctrl", "⌃"); + var isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform); - // Replace shift - shortcut = shortcut.replace("shift", "⇧"); + // Replace mod by equivalent for mac or windows + shortcut = shortcut.replace("mod", isMac ? '⌘' : 'ctrl'); - if (isMac) { - shortcut = shortcut.replace("alt", "⌥"); - } else { - shortcut = shortcut.replace("alt", "⎇"); - } + // Replace ctrl + shortcut = shortcut.replace("ctrl", "⌃"); - // Replace + - shortcut = shortcut.replace(/\+/g, " "); + // Replace shift + shortcut = shortcut.replace("shift", "⇧"); - return shortcut.toUpperCase(); + if (isMac) { + shortcut = shortcut.replace("alt", "⌥"); + } else { + shortcut = shortcut.replace("alt", "⎇"); } - }); - var keyboard = new Keyboard(); + // Replace + + shortcut = shortcut.replace(/\+/g, " "); + + return shortcut.toUpperCase(); + } +}); + +var keyboard = new Keyboard(); - // Prevent some browser default keyboard interactions - keyboard.preventDefault("mod+r"); +// Prevent some browser default keyboard interactions +keyboard.preventDefault("mod+r"); - return keyboard; -}); \ No newline at end of file +module.exports = keyboard; diff --git a/editor/utils/menu.js b/editor/utils/menu.js index 57f66253..9bf6400a 100644 --- a/editor/utils/menu.js +++ b/editor/utils/menu.js @@ -1,141 +1,133 @@ -define([ - 'hr/dom', - 'hr/utils', - 'views/menu', - 'utils/taphold' -], function ($, _, MenuView, taphold) { +var $ = require("jquery"); +var _ = require("hr.utils"); +var MenuView = require("../views/menu"); +var taphold = require("./taphold"); + +var menu = { + lastTimeOpened: 0, + origin: null, + + /** + * Clear current context menu + */ + clear: function() { + $(".ui-context-menu").removeClass("ui-context-menu"); + $("#ui-context-menu").remove(); + }, /** - * Context menu manager + * Generate menu from menuItems or a generator function * - * @class + * @param {array} menuItems */ - var Menu = { - lastTimeOpened: 0, - origin: null, - - /** - * Clear current context menu - */ - clear: function() { - $(".ui-context-menu").removeClass("ui-context-menu"); - $("#ui-context-menu").remove(); - }, - - /** - * Generate menu from menuItems or a generator function - * - * @param {array} menuItems - */ - generateMenu: function(menuItems) { - if (_.isFunction(menuItems)) menuItems = menuItems(); - - var menu = new MenuView({ - items: menuItems - }); - menu.$el.appendTo($("body")); - menu.on("action", function() { - Menu.clear(); - }) - menu.render(); - return menu; - }, - - /** - * Create a new context menu - * - * @param {array} menuItems - * @param {object} pos position for the menu - */ - open: function(menuItems, pos) { - Menu.clear(); - Menu.lastTimeOpened = Date.now(); - - var menu = Menu.generateMenu(menuItems); - - var w = menu.$el.width(); - var h = menu.$el.height(); - - var windowW = $(window).width(); - var windowH = $(window).height(); - - if ((pos.left+w) > windowW) { - pos.left = pos.left - w; - menu.$el.addClass("submenus-right"); - } - if ((pos.top+h) > windowH) { - pos.top = pos.top - h; - menu.$el.addClass("submenus-top"); - } + generateView: function(menuItems) { + if (_.isFunction(menuItems)) menuItems = menuItems(); + + var menuView = new MenuView({ + items: menuItems + }); + menuView.$el.appendTo($("body")); + menuView.on("action", function() { + menu.clear(); + }) + menuView.render(); + return menuView; + }, - menu.$el.css(_.extend({ - 'position': "fixed", - 'z-index': 100 - }, pos)); - menu.$el.attr("id", "ui-context-menu"); - }, - - /** - * Add a context menu to an element - * the menu can be open by left click and tap hold on ipad - * - * @param {jqueryElement} el - * @param {array} menu menu items - * @param {object} options - */ - add: function(el, menu, options) { - var $el = $(el); - - options = _.defaults({}, options, { - // Menu accessible in textinput - 'textinput': false - }); + /** + * Create a new context menu + * + * @param {array} menuItems + * @param {object} pos position for the menu + */ + open: function(menuItems, pos) { + menu.clear(); + menu.lastTimeOpened = Date.now(); - var handler = function(e) { - Menu.origin = e.type; - var target = e.target || e.srcElement; + var menuView = menu.generateView(menuItems); - // Ignore Menu on textinput - if (!options.textinput && - (target.tagName == 'INPUT' || target.tagName == 'SELECT' || target.tagName == 'TEXTAREA' || target.isContentEditable)) { - return; - } + var w = menuView.$el.width(); + var h = menuView.$el.height(); - var x = e.pageX || e.originalEvent.touches[0].pageX; - var y = e.pageY || e.originalEvent.touches[0].pageY; + var windowW = $(window).width(); + var windowH = $(window).height(); - Menu.open(menu, { - 'left': x, - 'top': y - }); + if ((pos.left+w) > windowW) { + pos.left = pos.left - w; + menuView.$el.addClass("submenus-right"); + } + if ((pos.top+h) > windowH) { + pos.top = pos.top - h; + menuView.$el.addClass("submenus-top"); + } + + menuView.$el.css(_.extend({ + 'position': "fixed", + 'z-index': 100 + }, pos)); + menuView.$el.attr("id", "ui-context-menu"); + }, - $el.addClass("ui-context-menu"); - return false; + /** + * Add a context menu to an element + * the menu can be open by left click and tap hold on ipad + * + * @param {jqueryElement} el + * @param {array} menu menu items + * @param {object} options + */ + add: function(el, menuView, options) { + var $el = $(el); + + options = _.defaults({}, options, { + // Menu accessible in textinput + 'textinput': false + }); + + var handler = function(e) { + menu.origin = e.type; + var target = e.target || e.srcElement; + + // Ignore Menu on textinput + if (!options.textinput && + (target.tagName == 'INPUT' || target.tagName == 'SELECT' || target.tagName == 'TEXTAREA' || target.isContentEditable)) { + return; } - $el.on("contextmenu", handler); - if (navigator.userAgent.match(/iPad/i) != null) taphold.bind($el, handler); - }, + var x = e.pageX || e.originalEvent.touches[0].pageX; + var y = e.pageY || e.originalEvent.touches[0].pageY; - remove: function(el) { - var $el = $(el); + menu.open(menuView, { + 'left': x, + 'top': y + }); - $el.off("contextmenu"); - taphold.unbind($el); + $el.addClass("ui-context-menu"); + return false; } - }; - // Click on the page: clse context menu - $(document).on("click", function (e) { - if (Menu.lastTimeOpened > (Date.now() - 600) && Menu.origin != "contextmenu") return; - Menu.clear(); - }); + $el.on("contextmenu", handler); + if (navigator.userAgent.match(/iPad/i) != null) taphold.bind($el, handler); + }, + + remove: function(el) { + var $el = $(el); + + $el.off("contextmenu"); + taphold.unbind($el); + } +}; + +// Click on the page: clse context menu +$(document).on("click", function (e) { + if (menu.lastTimeOpened > (Date.now() - 600) && menu.origin != "contextmenu") return; + menu.clear(); +}); - // Open new Menu: close other context menu - $(document).on("contextmenu", function() { - Menu.clear(); - }); +// Open new Menu: close other context menu +$(document).on("contextmenu", function() { + menu.clear(); +}); - return Menu; -}); \ No newline at end of file +module.exports = menu; diff --git a/editor/utils/require.js b/editor/utils/require.js new file mode 100644 index 00000000..4e0300be --- /dev/null +++ b/editor/utils/require.js @@ -0,0 +1,31 @@ +var _ = require("hr.utils"); +require("q"); +require("axios"); +require("jquery"); +require("hr.storage"); +require("hr.view"); +require("hr.class"); +require("hr.model"); +require("hr.collection"); +require("hr.logger"); +require("hr.dnd"); +require("hr.list"); +require("hr.gridview"); +require("hr.queue"); + +require('../**/*.js', {glob: true}); + +module.exports = function(m) { + var r = _.reduce([ + m, + "../"+m+".js" + ], function(prev, _m) { + try { + return require(_m); + } catch(e) { + return prev; + } + }, undefined); + if (!r) throw "Module not found '"+m+"'"; + return r; +}; diff --git a/editor/utils/string.js b/editor/utils/string.js index bde74a24..067fbdd9 100644 --- a/editor/utils/string.js +++ b/editor/utils/string.js @@ -1,100 +1,96 @@ -define([ - 'hr/hr' -], function (hr) { - /** - * Scores a string against another string. - * score('Hello World', 'he'); //=> 0.5931818181818181 - * score('Hello World', 'Hello'); //=> 0.7318181818181818 - */ - var score = function(string, word, fuzziness) { - // If the string is equal to the word, perfect match. - if (string == word) return 1; - - //if it's not a perfect match and is empty return 0 - if( word == "") return 0; - - var runningScore = 0, - charScore, - finalScore, - lString = string.toLowerCase(), - strLength = string.length, - lWord = word.toLowerCase(), - wordLength = word.length, - idxOf, - startAt = 0, - fuzzies = 1, - fuzzyFactor; - - // Cache fuzzyFactor for speed increase - if (fuzziness) fuzzyFactor = 1 - fuzziness; - - // Walk through word and add up scores. - // Code duplication occurs to prevent checking fuzziness inside for loop - if (fuzziness) { - for (var i = 0; i < wordLength; ++i) { - - // Find next first case-insensitive match of a character. - idxOf = lString.indexOf(lWord[i], startAt); - - if (-1 === idxOf) { - fuzzies += fuzzyFactor; - continue; - } else if (startAt === idxOf) { - // Consecutive letter & start-of-string Bonus - charScore = 0.7; - } else { - charScore = 0.1; - - // Acronym Bonus - // Weighing Logic: Typing the first character of an acronym is as if you - // preceded it with two perfect character matches. - if (string[idxOf - 1] === ' ') charScore += 0.8; - } - - // Same case bonus. - if (string[idxOf] === word[i]) charScore += 0.1; - - // Update scores and startAt position for next round of indexOf - runningScore += charScore; - startAt = idxOf + 1; - } - } else { - for (var i = 0; i < wordLength; ++i) { - - idxOf = lString.indexOf(lWord[i], startAt); - - if (-1 === idxOf) { - return 0; - } else if (startAt === idxOf) { - charScore = 0.7; - } else { - charScore = 0.1; - if (string[idxOf - 1] === ' ') charScore += 0.8; - } - - if (string[idxOf] === word[i]) charScore += 0.1; - - runningScore += charScore; - startAt = idxOf + 1; +/** + * Scores a string against another string. + * score('Hello World', 'he'); //=> 0.5931818181818181 + * score('Hello World', 'Hello'); //=> 0.7318181818181818 + */ +var score = function(string, word, fuzziness) { + // If the string is equal to the word, perfect match. + if (string == word) return 1; + + //if it's not a perfect match and is empty return 0 + if( word == "") return 0; + + var runningScore = 0, + charScore, + finalScore, + lString = string.toLowerCase(), + strLength = string.length, + lWord = word.toLowerCase(), + wordLength = word.length, + idxOf, + startAt = 0, + fuzzies = 1, + fuzzyFactor; + + // Cache fuzzyFactor for speed increase + if (fuzziness) fuzzyFactor = 1 - fuzziness; + + // Walk through word and add up scores. + // Code duplication occurs to prevent checking fuzziness inside for loop + if (fuzziness) { + for (var i = 0; i < wordLength; ++i) { + + // Find next first case-insensitive match of a character. + idxOf = lString.indexOf(lWord[i], startAt); + + if (-1 === idxOf) { + fuzzies += fuzzyFactor; + continue; + } else if (startAt === idxOf) { + // Consecutive letter & start-of-string Bonus + charScore = 0.7; + } else { + charScore = 0.1; + + // Acronym Bonus + // Weighing Logic: Typing the first character of an acronym is as if you + // preceded it with two perfect character matches. + if (string[idxOf - 1] === ' ') charScore += 0.8; } + + // Same case bonus. + if (string[idxOf] === word[i]) charScore += 0.1; + + // Update scores and startAt position for next round of indexOf + runningScore += charScore; + startAt = idxOf + 1; } + } else { + for (var i = 0; i < wordLength; ++i) { - // Reduce penalty for longer strings. - finalScore = 0.5 * (runningScore / strLength + runningScore / wordLength) / fuzzies; - - if ((lWord[0] === lString[0]) && (finalScore < 0.85)) { - finalScore += 0.15; + idxOf = lString.indexOf(lWord[i], startAt); + + if (-1 === idxOf) { + return 0; + } else if (startAt === idxOf) { + charScore = 0.7; + } else { + charScore = 0.1; + if (string[idxOf - 1] === ' ') charScore += 0.8; + } + + if (string[idxOf] === word[i]) charScore += 0.1; + + runningScore += charScore; + startAt = idxOf + 1; } - - return finalScore; - }; - - var endsWith = function(s, suffix) { - return s.indexOf(suffix, s.length - suffix.length) !== -1; - }; - - return { - 'score': score, - 'endsWith': endsWith - }; -}); \ No newline at end of file + } + + // Reduce penalty for longer strings. + finalScore = 0.5 * (runningScore / strLength + runningScore / wordLength) / fuzzies; + + if ((lWord[0] === lString[0]) && (finalScore < 0.85)) { + finalScore += 0.15; + } + + return finalScore; +}; + +var endsWith = function(s, suffix) { + return s.indexOf(suffix, s.length - suffix.length) !== -1; +}; + +module.exports = { + 'score': score, + 'endsWith': endsWith +}; diff --git a/editor/utils/taphold.js b/editor/utils/taphold.js index 3fbf421f..f0e2f54e 100644 --- a/editor/utils/taphold.js +++ b/editor/utils/taphold.js @@ -1,98 +1,96 @@ -define([ - 'hr/dom' -], function ($) { - var $document = $(document); - - function triggerCustomEvent( obj, eventType, event, bubble ) { - var originalType = event.type; - event.type = eventType; - if ( bubble ) { - $.event.trigger( event, undefined, obj ); - } else { - $.event.dispatch.call( obj, event ); - } - event.type = originalType; +var $ = require("jquery"); + +var $document = $(document); + +function triggerCustomEvent( obj, eventType, event, bubble ) { + var originalType = event.type; + event.type = eventType; + if ( bubble ) { + $.event.trigger( event, undefined, obj ); + } else { + $.event.dispatch.call( obj, event ); } + event.type = originalType; +} - $.event.special.taphold = { - setup: function(data, namespaces){ - var thisObject = this, - $this = $( thisObject ); - var timeout = null; - var duration = 500; - var maxMove = 5; - var oX, oY; - - // mousemove or touchmove callback - function mousemove_callback(e) { - var x = e.pageX || e.originalEvent.touches[0].pageX; - var y = e.pageY || e.originalEvent.touches[0].pageY; - - if (Math.abs(oX - x) > maxMove || Math.abs(oY - y) > maxMove) { - if (timeout) clearTimeout(timeout); - } - } +$.event.special.taphold = { + setup: function(data, namespaces){ + var thisObject = this, + $this = $( thisObject ); + var timeout = null; + var duration = 500; + var maxMove = 5; + var oX, oY; - // mouseup or touchend callback - function mouseup_callback(e) { - unbindDoc(); - if (timeout) clearTimeout(timeout); - } + // mousemove or touchmove callback + function mousemove_callback(e) { + var x = e.pageX || e.originalEvent.touches[0].pageX; + var y = e.pageY || e.originalEvent.touches[0].pageY; - var bindDoc = function() { - $document.on('mousemove', mousemove_callback); - $document.on('touchmove', mousemove_callback); - $document.on('mouseup', mouseup_callback); - $document.on('touchend', mouseup_callback); + if (Math.abs(oX - x) > maxMove || Math.abs(oY - y) > maxMove) { + if (timeout) clearTimeout(timeout); } + } - var unbindDoc = function() { - $document.unbind('mousemove', mousemove_callback); - $document.unbind('touchmove', mousemove_callback); - $document.unbind('mouseup', mouseup_callback); - $document.unbind('touchend', mouseup_callback); - } + // mouseup or touchend callback + function mouseup_callback(e) { + unbindDoc(); + if (timeout) clearTimeout(timeout); + } - // mousedown or touchstart callback - function mousedown_callback(e) { - // Only accept left click - if (e.type == 'mousedown' && e.originalEvent.button != 0) return; - oX = e.pageX || e.originalEvent.touches[0].pageX; - oY = e.pageY || e.originalEvent.touches[0].pageY; + var bindDoc = function() { + $document.on('mousemove', mousemove_callback); + $document.on('touchmove', mousemove_callback); + $document.on('mouseup', mouseup_callback); + $document.on('touchend', mouseup_callback); + } - bindDoc(); + var unbindDoc = function() { + $document.unbind('mousemove', mousemove_callback); + $document.unbind('touchmove', mousemove_callback); + $document.unbind('mouseup', mouseup_callback); + $document.unbind('touchend', mouseup_callback); + } - // set a timeout to call the longpress callback when time elapses - timeout = setTimeout(function() { - unbindDoc(); + // mousedown or touchstart callback + function mousedown_callback(e) { + // Only accept left click + if (e.type == 'mousedown' && e.originalEvent.button != 0) return; + oX = e.pageX || e.originalEvent.touches[0].pageX; + oY = e.pageY || e.originalEvent.touches[0].pageY; - triggerCustomEvent(thisObject, "taphold", $.Event( "taphold", { - target: e.target, - pageX: oX, - pageY: oY - } )); - }, duration); + bindDoc(); - e.stopPropagation(); - } + // set a timeout to call the longpress callback when time elapses + timeout = setTimeout(function() { + unbindDoc(); - // Browser Support - $this.on('mousedown', mousedown_callback); + triggerCustomEvent(thisObject, "taphold", $.Event( "taphold", { + target: e.target, + pageX: oX, + pageY: oY + } )); + }, duration); - // Mobile Support - $this.on('touchstart', mousedown_callback); - }, - teardown: function(namespaces){ - $(this).unbind(namespaces) - } - }; - - return { - bind: function(el, fn ) { - return el.bind("taphold", fn ); - }, - unbind: function() { - return el.unbind("taphold"); + e.stopPropagation(); } - }; -}); \ No newline at end of file + + // Browser Support + $this.on('mousedown', mousedown_callback); + + // Mobile Support + $this.on('touchstart', mousedown_callback); + }, + teardown: function(namespaces){ + $(this).unbind(namespaces) + } +}; + +module.exports = { + bind: function(el, fn ) { + return el.bind("taphold", fn ); + }, + unbind: function() { + return el.unbind("taphold"); + } +}; diff --git a/editor/utils/upload.js b/editor/utils/upload.js index 59fc3ed3..fb7757ca 100644 --- a/editor/utils/upload.js +++ b/editor/utils/upload.js @@ -1,226 +1,225 @@ -define([ - 'hr/hr', - 'hr/promise', - 'hr/dom', - 'hr/utils' -],function(hr, Q, $, _) { - var logging = hr.Logger.addNamespace("uploader"); - - try { - if (XMLHttpRequest.prototype.sendAsBinary){} else { - XMLHttpRequest.prototype.sendAsBinary = function(datastr) { - function byteValue(x) { - return x.charCodeAt(0) & 0xff; - } - var ords = Array.prototype.map.call(datastr, byteValue); - var ui8a = new Uint8Array(ords); - this.send(ui8a); - } - } - } catch(e) {} - - var Uploader = hr.Class.extend({ - defaults: { - url: "", - data: {} - }, - - /* - * Constructor for the uploader - */ - initialize: function(){ - Uploader.__super__.initialize.apply(this, arguments); - - this.lock = false; - this.maxFileSize = 10*1048576; - - return this; - }, - - /* - * Connect to a input file - */ - connect: function(input) { - var self = this; - input.on("change", function(e) { - e.preventDefault(); - self.upload(this.files); - }); - }, +var $ = require("jquery"); +var Q = require("q"); +var _ = require("hr.utils"); +var Class = require("hr.class"); - /* - * Run upload - * @files : html5 files - */ - upload: function(files) { - var d = Q.defer(); - var totalFilesSize = 0; - var that = this; +var logger = require("hr.logger")("uploader"); - if (that.lock == true) { - return Q.reject("Upload already in progress"); +try { + if (XMLHttpRequest.prototype.sendAsBinary){} else { + XMLHttpRequest.prototype.sendAsBinary = function(datastr) { + function byteValue(x) { + return x.charCodeAt(0) & 0xff; } + var ords = Array.prototype.map.call(datastr, byteValue); + var ui8a = new Uint8Array(ords); + this.send(ui8a); + } + } +} catch(e) {} + +var Uploader = Class.extend({ + defaults: { + url: "", + data: {} + }, + + /* + * Constructor for the uploader + */ + initialize: function(){ + Uploader.__super__.initialize.apply(this, arguments); + + this.lock = false; + this.maxFileSize = 10*1048576; + + return this; + }, + + /* + * Connect to a input file + */ + connect: function(input) { + var self = this; + input.on("change", function(e) { + e.preventDefault(); + self.upload(this.files); + }); + }, + + + /* + * Run upload + * @files : html5 files + */ + upload: function(files) { + var d = Q.defer(); + var totalFilesSize = 0; + var that = this; + + if (that.lock == true) { + return Q.reject("Upload already in progress"); + } - // Calcul total files size - totalFilesSize = _.reduce(files, function(memo, file){ return memo + (file.size != null ? file.size : 0); }, 0); + // Calcul total files size + totalFilesSize = _.reduce(files, function(memo, file){ return memo + (file.size != null ? file.size : 0); }, 0); - _.reduce(files, function(d, file) { - return d.then(function() { - return that.uploadFile(file); - }); - }, Q({})).then(function() { - d.resolve(); - }, function(err) { - d.reject(err); - }, function(progress) { - d.notify(progress); + _.reduce(files, function(d, file) { + return d.then(function() { + return that.uploadFile(file); }); + }, Q({})).then(function() { + d.resolve(); + }, function(err) { + d.reject(err); + }, function(progress) { + d.notify(progress); + }); + + return d.promise; + }, + + /* + * Upload one file + */ + uploadFile: function(file) { + var that = this; + var d = Q.defer(); + var filename = file.webkitRelativePath || file.name; + + var error = function(err) { + logger.error("error uploading", filename, ":", err); + that.trigger("error", err); + that.lock = false; + d.reject(err); + } - return d.promise; - }, - - /* - * Upload one file - */ - uploadFile: function(file) { - var that = this; - var d = Q.defer(); - var filename = file.webkitRelativePath || file.name; - - var error = function(err) { - logging.error("error uploading", filename, ":", err); - that.trigger("error", err); - that.lock = false; - d.reject(err); - } + var progress = function(percent) { + logger.log("notify ", filename, percent); + that.trigger("state", percent); + d.notify({ + 'filename': filename, + 'percent': percent + }); + }; - var progress = function(percent) { - logging.log("notify ", filename, percent); - that.trigger("state", percent); - d.notify({ - 'filename': filename, - 'percent': percent - }); - }; - - var end = function(text) { - logging.log("end", text); - that.trigger("end", text); - that.lock = false; - d.resolve(text); - }; - - if (file.name == "." || file.name == "..") { - return Q(); - } + var end = function(text) { + logger.log("end", text); + that.trigger("end", text); + that.lock = false; + d.resolve(text); + }; - if (file.name == null || file.size == null || file.size >= that.maxFileSize) { - return Q.reject(new Error("Invalid file or file too big")); - } + if (file.name == "." || file.name == "..") { + return Q(); + } - that.lock = true; - - logging.log("upload file ", filename, " in ", that.options.url, file.size,"/", file.size); - - var xhr = new XMLHttpRequest(), - upload = xhr.upload, - start_time = new Date().getTime(), - uploadurl = that.options.url.replace(":file", filename); - - logging.log("start uploading ", filename); - - upload.file = file; - upload.downloadStartTime = start_time; - upload.currentStart = start_time; - upload.currentProgress = 0; - upload.startData = 0; - upload.addEventListener("progress",function(e){ - if (e.lengthComputable) { - var percentage = Math.round((e.loaded * 100) / file.size); - progress(percentage); - } - }, false); - - xhr.open("PUT", uploadurl, true); - xhr.onreadystatechange = function(e){ - if (xhr.status != 200) { - error(new Error(xhr.status+": "+xhr.responseText)); - e.preventDefault(); - return; - } - }; - - var formData = new FormData(); - formData.append(filename, file); - _.each(that.options.data, function(v, k) { - formData.append(k, v); - }); + if (file.name == null || file.size == null || file.size >= that.maxFileSize) { + return Q.reject(new Error("Invalid file or file too big")); + } - progress(0); - xhr.onload = function() { - if (xhr.status == 200) { - end(xhr.responseText || ""); - } - } + that.lock = true; - xhr.send(formData); + logger.log("upload file ", filename, " in ", that.options.url, file.size,"/", file.size); - return d.promise; - } - }, { - // Upload file - upload: function(options) { - var d = Q.defer(); - - options = _.defaults({}, options || {}, { - 'url': null, - 'data': {}, - 'directory': false, - 'multiple': true - }); + var xhr = new XMLHttpRequest(), + upload = xhr.upload, + start_time = new Date().getTime(), + uploadurl = that.options.url.replace(":file", filename); - // Uploader - var uploader = new Uploader(options); + logger.log("start uploading ", filename); - var $f = $("input.cb-file-uploader"); - if ($f.length == 0) { - var $f = $("", { - "type": "file", - "class": "cb-file-uploader" - }); - $f.appendTo($("body")); + upload.file = file; + upload.downloadStartTime = start_time; + upload.currentStart = start_time; + upload.currentProgress = 0; + upload.startData = 0; + upload.addEventListener("progress",function(e){ + if (e.lengthComputable) { + var percentage = Math.round((e.loaded * 100) / file.size); + progress(percentage); } + }, false); - $f.hide(); + xhr.open("PUT", uploadurl, true); + xhr.onreadystatechange = function(e){ + if (xhr.status != 200) { + error(new Error(xhr.status+": "+xhr.responseText)); + e.preventDefault(); + return; + } + }; + + var formData = new FormData(); + formData.append(filename, file); + _.each(that.options.data, function(v, k) { + formData.append(k, JSON.stringify(v)); + }); + + progress(0); + xhr.onload = function() { + if (xhr.status == 200) { + end(xhr.responseText || ""); + } + } - $f.prop("webkitdirectory", options.directory); - $f.prop("directory", options.directory); - $f.prop("multiple", options.multiple); + xhr.send(formData); + + return d.promise; + } +}, { + // Upload file + upload: function(options) { + var d = Q.defer(); + + options = _.defaults({}, options || {}, { + 'url': null, + 'data': {}, + 'directory': false, + 'multiple': true + }); + + // Uploader + var uploader = new Uploader(options); + + var $f = $("input.cb-file-uploader"); + if ($f.length == 0) { + var $f = $("", { + "type": "file", + "class": "cb-file-uploader" + }); + $f.appendTo($("body")); + } - // Create file element for selection - $f.change(function(e) { - e.preventDefault(); + $f.hide(); + + $f.prop("webkitdirectory", options.directory); + $f.prop("directory", options.directory); + $f.prop("multiple", options.multiple); + + // Create file element for selection + $f.change(function(e) { + e.preventDefault(); - uploader.upload(e.currentTarget.files) - .progress(function(p) { - d.notify(p.percent); - }) - .then(function() { - d.resolve() - }, function(err) { - d.reject(err); - }) - .fin(function() { - $f.remove(); - }); + uploader.upload(e.currentTarget.files) + .progress(function(p) { + d.notify(p.percent); + }) + .then(function() { + d.resolve() + }, function(err) { + d.reject(err); + }) + .fin(function() { + $f.remove(); }); - $f.trigger('click'); + }); + $f.trigger('click'); - return d.promise; - } + return d.promise; + } - }); +}); - return Uploader; -}); \ No newline at end of file +module.exports = Uploader; diff --git a/editor/views/dialogs/container.js b/editor/views/dialogs/container.js index 29f9cc83..13139ace 100644 --- a/editor/views/dialogs/container.js +++ b/editor/views/dialogs/container.js @@ -1,89 +1,104 @@ -define([ - "hr/utils", - "hr/dom", - "hr/hr" -], function(_, $, hr) { - var DialogView = hr.View.extend({ - className: "component-dialog", - defaults: { - keyboard: true, - keyboardEnter: true, - - View: hr.View, - view: {}, - size: "medium" - }, - events: { - "keydown": "keydown" - }, - - initialize: function(options) { - DialogView.__super__.initialize.apply(this, arguments); - - // Bind keyboard - this.keydownHandler = _.bind(this.keydown, this) - if (this.options.keyboard) $(document).bind("keydown", this.keydownHandler); - - // Adapt style - this.$el.addClass("size-"+this.options.size); - - // Build view - this.view = new options.View(this.options.view, this); - }, - - render: function() { - this.view.render(); - this.view.appendTo(this); - - return this.ready(); - }, - - finish: function() { - this.open(); - return DialogView.__super__.finish.apply(this, arguments); - }, - - open: function() { - if (DialogView.current != null) DialogView.current.close(); - - this.$el.appendTo($("body")); - DialogView.current = this; - - this.trigger("open"); - - return this; - }, - - close: function(e, force) { - if (e) e.preventDefault(); - - // Unbind document keydown - $(document).unbind("keydown", this.keydownHandler); - - // Hide modal - this.trigger("close", force); - this.remove(); - - DialogView.current = null; - }, - - keydown: function(e) { - if (!this.options.keyboard) return; - - var key = e.keyCode || e.which; - - // Enter: valid - if (key == 13 && this.options.keyboardEnter) { - this.close(e); - } else - // Esc: close - if (key == 27) { - this.close(e, true); - } - } - }, { - current: null, - }); +var _ = require("hr.utils"); +var $ = require("jquery"); +var View = require("hr.view"); + +var DialogView = View.extend({ + className: "component-dialog", + defaults: { + keyboard: true, + keyboardEnter: true, + + View: View, + view: {}, + size: "medium" + }, + events: { + "click": "onClick", + "click .dialog-wrapper": "onWrapperClick", + "keydown": "keydown" + }, + + initialize: function(options) { + DialogView.__super__.initialize.apply(this, arguments); + + this.$wrapper = $("
", { + 'class': "dialog-wrapper" + }); + + // Bind keyboard + this.keydownHandler = _.bind(this.keydown, this) + if (this.options.keyboard) $(document).bind("keydown", this.keydownHandler); + + // Adapt style + this.$el.addClass("size-"+this.options.size); + + // Build view + this.view = new options.View(this.options.view, this); + this.view.appendTo(this.$wrapper); + this.$wrapper.appendTo(this.$el); + }, + + render: function() { + this.view.update(); + + return this.ready(); + }, + + finish: function() { + this.open(); + return DialogView.__super__.finish.apply(this, arguments); + }, - return DialogView; -}); \ No newline at end of file + open: function() { + if (DialogView.current != null) DialogView.current.close(); + + this.$el.appendTo($("body")); + DialogView.current = this; + + this.trigger("open"); + + return this; + }, + + close: function(e, force) { + if (e) e.preventDefault(); + + // Unbind document keydown + $(document).unbind("keydown", this.keydownHandler); + + // Hide modal + this.trigger("close", force); + this.remove(); + + DialogView.current = null; + }, + + keydown: function(e) { + if (!this.options.keyboard) return; + + var key = e.keyCode || e.which; + + // Enter: valid + if (key == 13 && this.options.keyboardEnter) { + this.close(e); + } else + // Esc: close + if (key == 27) { + this.close(e, true); + } + }, + + onWrapperClick: function(e) { + e.stopPropagation(); + }, + onClick: function(e) { + e.preventDefault(); + e.stopPropagation(); + + this.close(null, true); + } +}, { + current: null, +}); + +module.exports = DialogView; diff --git a/editor/views/dialogs/input.js b/editor/views/dialogs/input.js index 9100623a..5d605269 100644 --- a/editor/views/dialogs/input.js +++ b/editor/views/dialogs/input.js @@ -1,66 +1,69 @@ -define([ - "hr/utils", - "hr/dom", - "hr/hr", - "views/form" -], function(_, $, hr, FormView) { - var DialogInputView = hr.View.extend({ - className: "dialog-input", - defaults: { - className: "", - template: "", - value: true - }, - events: { - "click .do-close": "onClose", - "click .do-confirm": "onConfirm" - }, +var _ = require("hr.utils"); +var $ = require("jquery"); +var View = require("hr.view"); - initialize: function(options) { - DialogInputView.__super__.initialize.apply(this, arguments); +var FormView = require("../form"); - // Adapt style - this.$el.addClass(this.options.className); - // Value - this.value = this.options.value; - }, +var DialogInputView = View.Template.extend({ + className: "dialog-input", + defaults: { + className: "", + template: "", + value: true + }, + events: { + "click .do-close": "onClose", + "click .do-confirm": "onConfirm" + }, - finish: function() { - this.$("input").first().select(); - return DialogInputView.__super__.finish.apply(this, arguments); - }, + initialize: function(options) { + DialogInputView.__super__.initialize.apply(this, arguments); - template: function() { - return this.options.template; - }, - templateContext: function() { - return { - options: this.options - }; - }, + // Adapt style + this.$el.addClass(this.options.className); - getValue: function() { - var selector = this.options.value; - if (_.isFunction(selector)) { - this.value = selector(this); - } else if (_.isString(selector)) { - this.value = this[selector](); - } + // Value + this.value = this.options.value; + }, - return this.value; - }, + finish: function() { + var that = this; + _.defer(function() { + that.$("input").first().focus(); + }); + return DialogInputView.__super__.finish.apply(this, arguments); + }, - onConfirm: function(e) { - if (e) e.preventDefault(); + template: function() { + return this.options.template; + }, + templateContext: function() { + return { + options: this.options + }; + }, - this.parent.close(e); - }, - onClose: function(e) { - if (e) e.preventDefault(); - this.parent.close(null, true); + getValue: function() { + var selector = this.options.value; + if (_.isFunction(selector)) { + this.value = selector(this); + } else if (_.isString(selector)) { + this.value = this[selector](); } - }); - return DialogInputView; -}); \ No newline at end of file + return this.value; + }, + + onConfirm: function(e) { + if (e) e.preventDefault(); + + this.parent.close(e); + }, + onClose: function(e) { + if (e) e.preventDefault(); + this.parent.close(null, true); + } +}); + +module.exports = DialogInputView; diff --git a/editor/views/dialogs/list.js b/editor/views/dialogs/list.js index 3bc6e1b3..6b13adca 100644 --- a/editor/views/dialogs/list.js +++ b/editor/views/dialogs/list.js @@ -1,213 +1,216 @@ -define([ - "hr/utils", - "hr/dom", - "hr/hr", - "utils/string", - "views/dialogs/input" -], function(_, $, hr, string, DialogInputView) { - var ListItem = hr.List.Item.extend({ - className: "list-item", - - initialize: function(options) { - ListItem.__super__.initialize.apply(this, arguments); - - this.dialogContent = this.list.parent; - }, - - template: function() { - return this.dialogContent.options.template; - }, - templateContext: function() { - return { - item: this.model - }; +var Q = require("q"); +var _ = require("hr.utils"); +var $ = require("jquery"); +var View = require("hr.view"); +var ListView = require("hr.list"); +var Collection = require("hr.collection"); + +var string = require("../../utils/string"); +var DialogInputView = require("./input"); + + +var ListItem = ListView.Item.inherit(View.Template).extend({ + className: "list-item", + + initialize: function(options) { + ListItem.__super__.initialize.apply(this, arguments); + + this.dialogContent = this.list.parent; + }, + + template: function() { + return this.dialogContent.options.template; + }, + templateContext: function() { + return { + item: this.model + }; + } +}); + +var ListView = ListView.extend({ + Item: ListItem, + className: "ui-content-list" +}); + +var DialogListView = DialogInputView.extend({ + defaults: { + textIndex: function(model) { return JSON.stringify(model.toJSON()); } + }, + className: "dialog-list", + + initialize: function() { + DialogListView.__super__.initialize.apply(this, arguments); + + // Source for items + this.results = new Collection(); + this.source = this.options.source; + + if (this.options.source instanceof Collection) { + // Source is a collection + this.results = new this.options.source.constructor() + this.source = function(q) { + return this.options.source.filter(function(model) { + return this.searchText(model, q); + }, this); + }.bind(this); } - }); - - var ListView = hr.List.extend({ - Item: ListItem, - className: "ui-content-list" - }); - - var DialogListView = DialogInputView.extend({ - defaults: { - textIndex: function(model) { return JSON.stringify(model.toJSON()); } - }, - className: "dialog-list", - - initialize: function() { - DialogListView.__super__.initialize.apply(this, arguments); - - // Source for items - this.results = new hr.Collection(); - this.source = this.options.source; - - if (this.options.source instanceof hr.Collection) { - // Source is a collection - this.results = new this.options.source.constructor() - this.source = function(q) { - return this.options.source.filter(function(model) { - return this.searchText(model, q); - }, this); - }.bind(this); - } - // Filter for items - this.$filterInput = $("", { - 'type': "text", - 'placeholder': this.options.placeholder - }); - this.keydownInterval = null; - this.$filterInput.on("keydown", this.onFilterKeydown.bind(this)); - this.$filterInput.on("keyup", this.onFilterKeyup.bind(this)); - this.$filterInput.appendTo(this.$el); - - // Items list - this.list = new ListView({ - collection: this.results - }, this); - this.list.appendTo(this.$el); - - // Focus input - this.listenTo(this.parent, "open", function() { - this.$filterInput.focus(); - this.doSearch(""); - this.selectItem(0); + // Filter for items + this.$filterInput = $("", { + 'type': "text", + 'placeholder': this.options.placeholder + }); + this.keydownInterval = null; + this.$filterInput.on("keydown", this.onFilterKeydown.bind(this)); + this.$filterInput.on("keyup", this.onFilterKeyup.bind(this)); + this.$filterInput.appendTo(this.$el); + + // Items list + this.list = new ListView({ + collection: this.results + }, this); + this.list.appendTo(this.$el); + + // Focus input + this.listenTo(this.parent, "open", function() { + this.$filterInput.focus(); + this.doSearch(""); + this.selectItem(0); + }); + }, + + render: function() { + return this.ready(); + }, + + searchText: function(model, q) { + var t = this.options.textIndex(model); + + var words = q.split("") + + t = t.toLowerCase(); + q = q.toLowerCase(); + + return _.every(q.split(" "), function(_q) { + return t.search(_q) !== -1; + }); + }, + + doSearch: function(query) { + var that = this, toRemove = []; + if (this.list.collection.query == query) return; + + if (this.list.collection.query && query + && query.indexOf(this.list.collection.query) == 0) { + // Continue current search + this.list.collection.query = query; + this.list.collection.each(function(model) { + if (!that.searchText(model, query)) { + toRemove.push(model); + } }); - }, - - render: function() { - return this.ready(); - }, - - searchText: function(model, q) { - var t = this.options.textIndex(model); - - var words = q.split("") - - t = t.toLowerCase(); - q = q.toLowerCase(); + this.list.collection.remove(toRemove); + //this.list.collection.sort(); + this.selectItem(this.getSelectedItem()); + } else { + // Different search + this.list.collection.query = query; + this.list.collection.reset([]); + + Q() + .then(function() { + return that.source(query); + }) + .then(function(result) { + that.list.collection.add(_.filter(result, that.options.filter)); + that.selectItem(that.getSelectedItem()); + }, console.error.bind(console)); + } + }, - return _.every(q.split(" "), function(_q) { - return t.search(_q) !== -1; - }); - }, - - doSearch: function(query) { - var that = this, toRemove = []; - if (this.list.collection.query == query) return; - - if (this.list.collection.query && query - && query.indexOf(this.list.collection.query) == 0) { - // Continue current search - this.list.collection.query = query; - this.list.collection.each(function(model) { - if (!that.searchText(model, query)) { - toRemove.push(model); - } - }); - this.list.collection.remove(toRemove); - //this.list.collection.sort(); - this.selectItem(this.getSelectedItem()); - } else { - // Different search - this.list.collection.query = query; - this.list.collection.reset([]); - - Q() - .then(function() { - return that.source(query); - }) - .then(function(result) { - that.list.collection.add(_.filter(result, that.options.filter)); - that.selectItem(that.getSelectedItem()); - }, console.error.bind(console)); - } - }, + selectItem: function(i) { + var i, boxH = this.list.$el.height(); - selectItem: function(i) { - var i, boxH = this.list.$el.height(); + this.selected = i; - this.selected = i; + if (this.selected >= this.list.collection.size()) this.selected = this.list.collection.size() - 1; + if (this.selected < 0) this.selected = 0; - if (this.selected >= this.list.collection.size()) this.selected = this.list.collection.size() - 1; - if (this.selected < 0) this.selected = 0; + i = 0; + this.list.collection.each(function(model) { + var y, h, item = this.list.items[model.id]; + item.$el.toggleClass("active", i == this.selected); - i = 0; - this.list.collection.each(function(model) { - var y, h, item = this.list.items[model.id]; - item.$el.toggleClass("active", i == this.selected); - - if (i == this.selected) { - h = item.$el.outerHeight(); - y = item.$el.position().top; - - if (y > (boxH-(h/2))) { - this.list.$el.scrollTop((i+1)*h - boxH) - } else if (y <= (h/2)) { - this.list.$el.scrollTop((i)*h) - } - } + if (i == this.selected) { + h = item.$el.outerHeight(); + y = item.$el.position().top; - i = i + 1; - }, this); - }, - - getSelectedItem: function() { - var _ret = 0; - this.list.collection.each(function(model, i) { - var item = this.list.items[model.id]; - if (item.$el.hasClass("active")) { - _ret = i; - return false; + if (y > (boxH-(h/2))) { + this.list.$el.scrollTop((i+1)*h - boxH) + } else if (y <= (h/2)) { + this.list.$el.scrollTop((i)*h) } - }, this); - return _ret; - }, + } - getValue: function() { - return this.list.collection.at(this.getSelectedItem()); - }, + i = i + 1; + }, this); + }, + + getSelectedItem: function() { + var _ret = 0; + this.list.collection.each(function(model, i) { + var item = this.list.items[model.id]; + if (item.$el.hasClass("active")) { + _ret = i; + return false; + } + }, this); + return _ret; + }, - onFilterKeydown: function(e) { - var key = e.which || e.keyCode; + getValue: function() { + return this.list.collection.at(this.getSelectedItem()); + }, - if (key == 38 || key == 40 || key == 13) { - e.preventDefault(); - } + onFilterKeydown: function(e) { + var key = e.which || e.keyCode; - var interval = function() { - var selected = this.getSelectedItem(); - var pSelected = selected; + if (key == 38 || key == 40 || key == 13) { + e.preventDefault(); + } - if (key == 38) { - /* UP */ - selected = selected - 1; - } else if (key == 40) { - /* DOWN */ - selected = selected + 1; - } - if (selected != pSelected) this.selectItem(selected); - }.bind(this); + var interval = function() { + var selected = this.getSelectedItem(); + var pSelected = selected; - if (this.keydownInterval) { - clearInterval(this.keydownInterval); - this.keydownInterval = null; + if (key == 38) { + /* UP */ + selected = selected - 1; + } else if (key == 40) { + /* DOWN */ + selected = selected + 1; } - interval(); - this.keydownInterval = setInterval(interval, 600); - }, - onFilterKeyup: function(e) { - var q = this.$filterInput.val().toLowerCase(); + if (selected != pSelected) this.selectItem(selected); + }.bind(this); + + if (this.keydownInterval) { + clearInterval(this.keydownInterval); + this.keydownInterval = null; + } + interval(); + this.keydownInterval = setInterval(interval, 600); + }, + onFilterKeyup: function(e) { + var q = this.$filterInput.val().toLowerCase(); - this.doSearch(q); + this.doSearch(q); - if (this.keydownInterval) { - clearInterval(this.keydownInterval); - this.keydownInterval = null; - } + if (this.keydownInterval) { + clearInterval(this.keydownInterval); + this.keydownInterval = null; } - }); + } +}); - return DialogListView; -}); \ No newline at end of file +module.exports = DialogListView; diff --git a/editor/views/form.js b/editor/views/form.js index 225772d2..bc0d278c 100644 --- a/editor/views/form.js +++ b/editor/views/form.js @@ -1,181 +1,179 @@ -define([ - "hr/utils", - "hr/dom", - "hr/hr" -], function(_, $, hr) { - - var GETTER = { - 'boolean': function() { - return $(this).is(":checked"); - }, - 'string': function() { - return $(this).val(); - }, - 'number': function() { - return parseInt($(this).val()); - } - }; - - var RENDERER = { - 'boolean': function(propertyName, property, value) { - return $("", { - 'type': "checkbox", - 'checked': value === true, - 'name': propertyName - }); - }, - - 'number': function(propertyName, property, value) { - return $("", { - 'type': "number", - 'value': value, - 'name': propertyName, - 'min': property.minimum, - 'max': property.maximum, - 'step': property.multipleOf || 1 - }); - }, - - 'string': function(propertyName, property, value) { - if (property['enum']) return RENDERER.select.apply(this, arguments); - - return $("", { - 'type': "text", - 'value': value, - 'name': propertyName - }); - }, - - 'select': function(propertyName, property, value) { - var $select = $("", { + 'type': "checkbox", + 'checked': value === true, + 'name': propertyName + }); + }, + + 'number': function(propertyName, property, value) { + return $("", { + 'type': "number", + 'value': value, + 'name': propertyName, + 'min': property.minimum, + 'max': property.maximum, + 'step': property.multipleOf || 1 + }); + }, + + 'string': function(propertyName, property, value) { + if (property['enum']) return RENDERER.select.apply(this, arguments); + + return $("", { + 'type': "text", + 'value': value, + 'name': propertyName + }); + }, + + 'select': function(propertyName, property, value) { + var $select = $("