├── .buildpacks ├── .dockerignore ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Aptfile ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── app ├── cache │ └── index.js ├── controllers │ ├── home.js │ ├── session.js │ ├── static.js │ ├── visualization.js │ └── visualizationTypes.js ├── models │ ├── index.js │ ├── session.js │ ├── visualization.js │ └── visualizationtype.js ├── utils.js └── views │ ├── 404.jade │ ├── 500.jade │ ├── embed-layout.jade │ ├── index.jade │ ├── layout.jade │ ├── session │ ├── feed-public.jade │ ├── feed.jade │ ├── includes │ │ └── viz.jade │ ├── index.jade │ ├── visualization-embed.jade │ ├── visualization-iframe.jade │ ├── visualization-public.jade │ ├── visualization-pym.jade │ ├── visualization.jade │ ├── viz-data-public.jade │ └── viz-data.jade │ └── viz-types │ ├── advanced.jade │ ├── editor.jade │ ├── preview-editor.jade │ └── show.jade ├── bin └── lightning-server.js ├── common.yml ├── config ├── config.js ├── database.js ├── env │ ├── development.js │ ├── production.js │ └── test.js ├── express.js ├── passport.js ├── passport │ ├── facebook.js │ └── local.js └── routes.js ├── docker-compose.yml ├── electron ├── Icon.png ├── Menu.svg ├── icons.icns ├── index.html └── index.js ├── gulpfile.js ├── migrations ├── 20141201124151-visualizatino-settings-migration.js └── 20150808144909-npm-updates.js ├── package.json ├── postgres.yml ├── production.yml ├── public ├── css │ └── app.css ├── images │ ├── close.png │ ├── favicon.ico │ └── lightning.svg └── js │ ├── basic.js │ ├── editor.js │ ├── embed.js │ ├── feed.js │ ├── ipython-comm.js │ ├── lib │ ├── bigSlide.js │ ├── codemirror │ │ ├── codemirror.js │ │ ├── jade.js │ │ ├── javascript.js │ │ └── sass.js │ ├── dropit.js │ └── modal.js │ ├── preview.js │ ├── public.js │ ├── pym.js │ └── visualization-types.js ├── server.js ├── tasks ├── get_default_visualizations.js ├── index.js └── startup.js ├── test └── test.js ├── tmp └── .gitkeep └── ui ├── images ├── close.png ├── favicon.ico ├── lightning.svg └── uploads │ └── .gitkeep ├── js ├── components │ ├── editor │ │ ├── content-editable.js │ │ ├── data.js │ │ ├── editor.js │ │ ├── index.js │ │ ├── options.js │ │ └── viz.js │ └── visualization-importer │ │ ├── index.js │ │ └── sources │ │ ├── index.js │ │ └── npm.js ├── emitter.js ├── etc │ └── standalone.js ├── lib │ ├── bigSlide.js │ ├── codemirror │ │ ├── codemirror.js │ │ ├── jade.js │ │ ├── javascript.js │ │ └── sass.js │ ├── dropit.js │ └── modal.js ├── pages │ ├── basic.js │ ├── editor.js │ ├── embed.js │ ├── feed.js │ ├── ipython-comm.js │ ├── preview.js │ ├── public.js │ ├── pym.js │ └── visualization-types.js └── utils │ └── index.js ├── stylesheets ├── app.scss ├── lib │ ├── codemirror.scss │ ├── dropit.scss │ ├── highlightjs.scss │ ├── modal.scss │ ├── shelves.scss │ ├── skeleton.scss │ └── solarized.scss ├── pages │ ├── editor.scss │ └── viz-types.scss ├── responsive.scss └── sidebar.scss └── templates └── feed-item.jade /.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/ddollar/heroku-buildpack-apt 2 | https://github.com/srbartlett/heroku-buildpack-phantomjs-2.0.git 3 | https://github.com/lightning-viz/heroku-buildpack-nodejs.git 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Standard ignore file for Docker builds. 2 | # Here we include the Dockerfile and fig.yml 3 | # as they are not helpful within the app. 4 | 5 | node_modules 6 | .keys 7 | public 8 | 9 | npm-debug.log 10 | *ipynb* 11 | tmp/** 12 | ui/**/imported/** 13 | mocks/ 14 | 15 | *.db 16 | *.sqlite 17 | 18 | logs/ 19 | fig*.yml 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .keys 4 | public 5 | 6 | npm-debug.log 7 | *ipynb* 8 | tmp/** 9 | ui/**/imported/** 10 | mocks/ 11 | 12 | *.db 13 | *.sqlite 14 | 15 | .idea 16 | /standalone.js 17 | 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": false, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .keys 4 | 5 | npm-debug.log 6 | *ipynb* 7 | tmp/** 8 | ui/**/imported/** 9 | mocks/ 10 | 11 | *.db 12 | *.sqlite 13 | 14 | .idea 15 | /standalone.js 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "4" 5 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | libicu52 2 | libjpeg8 3 | libfontconfig 4 | libwebp5 5 | libpq-dev 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6 2 | 3 | MAINTAINER Lighning Viz 4 | 5 | RUN mkdir -p /usr/src/app 6 | WORKDIR /usr/src/app 7 | 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | COPY . /usr/src/app/ 12 | 13 | RUN npm run build 14 | RUN npm run fetch-visualizations 15 | 16 | ENV DEBUG lightning:* 17 | 18 | EXPOSE 3000 19 | 20 | CMD ["npm","start"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Matthew Conlen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: NODE_ENV=production node server.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightning 2 | 3 | 4 | [![Build Status](https://travis-ci.org/lightning-viz/lightning.svg)](https://travis-ci.org/lightning-viz/lightning) 5 | 6 | Lightning is a data-visualization server providing API-based access to reproducible, web-based, interactive visualizations. It includes a core set of visualization types, but is built for extendability and customization. Lightning supports modern libraries like d3.js, three.js, and leaflet, and is designed for interactivity over large data sets and continuously updating data streams. 7 | 8 | If you are just getting started, please see the [project page](http://lightning-viz.github.io/), see the [available visualizations](http://lightning-viz.github.io/visualizations/) or check out an [IPython demo](http://nbviewer.ipython.org/github/lightning-viz/lightning-example-notebooks/blob/master/index.ipynb) 9 | 10 | 11 | 12 | ## installation 13 | 14 | ### local 15 | 16 | #### via npm 17 | 18 | ``` 19 | $ npm install -g lightning-server 20 | $ lightning-server 21 | ``` 22 | 23 | #### clone this repo 24 | 25 | ``` 26 | $ git clone git@github.com:lightning-viz/lightning.git 27 | $ cd lightning 28 | $ npm install 29 | $ npm start 30 | ``` 31 | 32 | ### host a server 33 | 34 | 35 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy?template=https://github.com/lightning-viz/lightning) 36 | 37 | 38 | ## client libraries 39 | 40 | ### Official 41 | 42 | Python 43 | * https://github.com/lightning-viz/lightning-python 44 | 45 | Scala 46 | * https://github.com/lightning-viz/lightning-scala 47 | 48 | Javascript (node + browser) 49 | * https://github.com/lightning-viz/lightning.js 50 | 51 | ### Community 52 | 53 | R 54 | * https://github.com/Ermlab/lightning-rstat 55 | 56 | 57 | ## complete documentation 58 | 59 | http://lightning-viz.github.io/documentation/ 60 | 61 | ## visualizations 62 | 63 | http://lightning-viz.github.io/visualizations/ 64 | 65 | ## help 66 | 67 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/lightning-viz/lightning?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 68 | 69 | We maintain a chatroom on gitter. If there's no response there: file an issue or reach out on twitter [@lightningviz](https://twitter.com/lightningviz) 70 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lightning", 3 | "description": "Lighning data visualization notebooks.", 4 | "keywords": [ 5 | "visualization", 6 | "data", 7 | "dataviz" 8 | ], 9 | "repository": "https://github.com/mathisonian/lightning", 10 | "env": { 11 | "NODE_ENV": "production", 12 | "BUILDPACK_URL": "https://github.com/lightning-viz/heroku-buildpack-nodejs", 13 | "LD_LIBRARY_PATH": "/usr/local/lib:/usr/lib:/lib:/app/vendor/phantomjs/lib", 14 | "PATH": "/usr/local/bin:/usr/bin:/bin:/app/vendor/phantomjs/bin", 15 | "LIGHTNING_USERNAME": { 16 | "description": "Username to secure this instance with basic auth. (optional)", 17 | "required": false 18 | }, 19 | "LIGHTNING_PASSWORD": { 20 | "description": "Password to secure this instance with basic auth. (optional)", 21 | "required": false 22 | }, 23 | "S3_BUCKET": { 24 | "description": "The name of the s3 bucket to use for hosting images. (optional)", 25 | "required": false 26 | }, 27 | "S3_KEY": { 28 | "description": "The s3 key. (optional)", 29 | "required": false 30 | }, 31 | "S3_SECRET": { 32 | "description": "The s3 secret. (optional)", 33 | "required": false 34 | } 35 | }, 36 | "addons": [ 37 | "heroku-postgresql:hobby-dev" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /app/cache/index.js: -------------------------------------------------------------------------------- 1 | // taken from https://github.com/ptarjan/node-cache 2 | 3 | var cache = {} 4 | function now() { return (new Date).getTime(); } 5 | var debug = false; 6 | var hitCount = 0; 7 | var missCount = 0; 8 | 9 | exports.put = function(key, value, time, timeoutCallback) { 10 | if (debug) console.log('caching: '+key+' = '+value+' (@'+time+')'); 11 | var oldRecord = cache[key]; 12 | if (oldRecord) { 13 | clearTimeout(oldRecord.timeout); 14 | } 15 | 16 | var expire = time + now(); 17 | var record = {value: value, expire: expire}; 18 | 19 | if (!isNaN(expire)) { 20 | var timeout = setTimeout(function() { 21 | exports.del(key); 22 | if (typeof timeoutCallback === 'function') { 23 | timeoutCallback(key); 24 | } 25 | }, time); 26 | record.timeout = timeout; 27 | } 28 | 29 | cache[key] = record; 30 | } 31 | 32 | exports.del = function(key) { 33 | delete cache[key]; 34 | } 35 | 36 | exports.clear = function() { 37 | cache = {}; 38 | } 39 | 40 | exports.get = function(key) { 41 | var data = cache[key]; 42 | if (typeof data != "undefined") { 43 | if (isNaN(data.expire) || data.expire >= now()) { 44 | if (debug) hitCount++; 45 | return data.value; 46 | } else { 47 | // free some space 48 | if (debug) missCount++; 49 | exports.del(key); 50 | } 51 | } else if (debug) { 52 | missCount++; 53 | } 54 | return null; 55 | } 56 | 57 | exports.size = function() { 58 | var size = 0, key; 59 | for (key in cache) { 60 | if (cache.hasOwnProperty(key)) 61 | if (exports.get(key) !== null) 62 | size++; 63 | } 64 | return size; 65 | } 66 | 67 | exports.memsize = function() { 68 | var size = 0, key; 69 | for (key in cache) { 70 | if (cache.hasOwnProperty(key)) 71 | size++; 72 | } 73 | return size; 74 | } 75 | 76 | exports.debug = function(bool) { 77 | debug = bool; 78 | } 79 | 80 | exports.hits = function() { 81 | return hitCount; 82 | } 83 | 84 | exports.misses = function() { 85 | return missCount; 86 | } 87 | 88 | exports.keys = function() { 89 | return Object.keys(cache); 90 | }; 91 | -------------------------------------------------------------------------------- /app/controllers/home.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module dependencies. 4 | */ 5 | 6 | var env = process.env.NODE_ENV || 'development'; 7 | var dbConfig = require('../../config/database')[env]; 8 | var models = require('../models'); 9 | var Q = require('q'); 10 | var _ = require('lodash'); 11 | 12 | 13 | exports.index = function (req, res) { 14 | res.render('index'); 15 | }; 16 | 17 | exports.status = function (req, res) { 18 | 19 | Q.all([ 20 | models.Visualization.count(), 21 | models.Session.count(), 22 | models.VisualizationType.findAll(), 23 | ]).spread(function(vizCount, sessionCount, vizTypes) { 24 | return res.json({ 25 | database: dbConfig.dialect, 26 | visualizations: vizCount, 27 | sessions: sessionCount, 28 | visualizationTypes: _.map(vizTypes, function(vt) {return vt.name;}) 29 | }); 30 | }); 31 | 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /app/controllers/static.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var _ = require('lodash'); 3 | var browserify = require('browserify'); 4 | var path = require('path'); 5 | var cache = require('../cache'); 6 | var uuid = require('node-uuid'); 7 | var Q = require('q'); 8 | var debug = require('debug')('lightning:server:controllers:static'); 9 | var config = require('../../config/config'); 10 | 11 | 12 | exports.getDynamicVizBundle = function (req, res, next) { 13 | 14 | res.set('Content-Type', 'application/javascript'); 15 | res.set('Access-Control-Allow-Origin', "*"); 16 | 17 | // Get all vizTypes in array 18 | var visualizationTypes = _.uniq(req.query.visualizations).sort(); 19 | 20 | var cacheHit = false; 21 | 22 | if(!req.query.cachemiss) { 23 | var bundle = cache.get('js/' + visualizationTypes.toString()); 24 | 25 | if(bundle) { 26 | cacheHit = true; 27 | res.send(bundle); 28 | } 29 | } 30 | 31 | debug('building viz bundle with ' + visualizationTypes); 32 | var tmpPath = path.resolve(__dirname + '/../../tmp/js-build/' + uuid.v4() + '/'); 33 | 34 | req.session.lastBundlePath = tmpPath; 35 | var b = browserify({ 36 | paths: [ config.root + '/node_modules'] 37 | }); 38 | 39 | models.VisualizationType 40 | .findAll().then(function(vizTypes) { 41 | 42 | debug('Found ' + vizTypes.length + ' visualization types'); 43 | 44 | _.each(_.filter(vizTypes, function(vizType) { return (visualizationTypes.indexOf(vizType.name) > -1); }), function(vizType) { 45 | debug(vizType.moduleName); 46 | b.require(vizType.moduleName, { 47 | expose: vizType.moduleName 48 | }); 49 | }); 50 | 51 | b.bundle(function(err, buf) { 52 | if(err) { 53 | return next(err); 54 | } 55 | 56 | var out = buf.toString('utf8'); 57 | cache.put('js/' + visualizationTypes.toString(), out, 1000 * 60 * 10); 58 | if(!cacheHit) { 59 | res.send(out); 60 | } 61 | }); 62 | 63 | }).error(function(err) { 64 | return res.status(500).send(err.message).end(); 65 | }); 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , path = require('path') 3 | , Sequelize = require('sequelize') 4 | , lodash = require('lodash') 5 | , env = process.env.NODE_ENV || 'development' 6 | , config = require(__dirname + '/../../config/database')[env] 7 | , sequelize = new Sequelize(config.database, config.username || process.env.USER, config.password, config) 8 | , db = {} 9 | 10 | fs 11 | .readdirSync(__dirname) 12 | .filter(function(file) { 13 | return (file.indexOf('.') !== 0) && (file !== 'index.js') 14 | }) 15 | .forEach(function(file) { 16 | var model = sequelize.import(path.join(__dirname, file)) 17 | db[model.name] = model 18 | }) 19 | 20 | Object.keys(db).forEach(function(modelName) { 21 | if ('associate' in db[modelName]) { 22 | db[modelName].associate(db) 23 | } 24 | }) 25 | 26 | module.exports = lodash.extend({ 27 | sequelize: sequelize, 28 | Sequelize: Sequelize 29 | }, db) 30 | -------------------------------------------------------------------------------- /app/models/session.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(sequelize, DataTypes) { 3 | 4 | var Session = sequelize.define('Session', { 5 | name: DataTypes.STRING, 6 | 'id': { 7 | type: DataTypes.UUID, 8 | primaryKey: true, 9 | defaultValue: DataTypes.UUIDV4, 10 | }, 11 | }, { 12 | classMethods: { 13 | associate: function(models) { 14 | Session.hasMany(models.Visualization); 15 | } 16 | }, 17 | 18 | instanceMethods: { 19 | getDisplayName: function() { 20 | var identifier = '' + this.id; 21 | return this.name || ('Session ' + identifier.substring(0, 5)); 22 | }, 23 | 24 | getSocketNamespace: function() { 25 | return '/session' + this.id.split('-').join(''); 26 | } 27 | }, 28 | 29 | hooks: { 30 | beforeCreate: function(session, options, next) { 31 | next(); 32 | } 33 | } 34 | }); 35 | 36 | return Session; 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /app/models/visualization.js: -------------------------------------------------------------------------------- 1 | var validator = require('validator'); 2 | var _ = require('lodash'); 3 | var env = process.env.NODE_ENV || 'development'; 4 | var config = require(__dirname + '/../../config/database')[env]; 5 | var isPostgres = config.dialect === 'postgres'; 6 | 7 | module.exports = function(sequelize, DataTypes) { 8 | 9 | 10 | var schema; 11 | if(isPostgres) { 12 | schema = { 13 | 'id': { 14 | type: DataTypes.UUID, 15 | primaryKey: true, 16 | defaultValue: DataTypes.UUIDV4, 17 | }, 18 | data: DataTypes.JSON, 19 | opts: DataTypes.JSON, 20 | settings: DataTypes.JSON, 21 | name: DataTypes.STRING, 22 | description: DataTypes.TEXT, 23 | images: DataTypes.ARRAY(DataTypes.STRING) 24 | }; 25 | } else { 26 | schema = { 27 | data: { 28 | type: DataTypes.TEXT, 29 | get: function() { 30 | return JSON.parse(this.getDataValue('data') || '{}'); 31 | }, 32 | set: function(val) { 33 | return this.setDataValue('data', JSON.stringify(val)); 34 | } 35 | }, 36 | opts: { 37 | type: DataTypes.TEXT, 38 | get: function() { 39 | return JSON.parse(this.getDataValue('opts') || '{}'); 40 | }, 41 | set: function(val) { 42 | return this.setDataValue('opts', JSON.stringify(val)); 43 | } 44 | }, 45 | settings: { 46 | type: DataTypes.TEXT, 47 | get: function() { 48 | return JSON.parse(this.getDataValue('settings') || '{}'); 49 | }, 50 | set: function(val) { 51 | return this.setDataValue('settings', JSON.stringify(val)); 52 | } 53 | }, 54 | name: DataTypes.STRING, 55 | description: DataTypes.TEXT, 56 | 'id': { 57 | type: DataTypes.UUID, 58 | primaryKey: true, 59 | defaultValue: DataTypes.UUIDV4, 60 | }, 61 | images: { 62 | type: DataTypes.TEXT, 63 | get: function() { 64 | return JSON.parse(this.getDataValue('images') || '[]'); 65 | }, 66 | set: function(val) { 67 | return this.setDataValue('images', JSON.stringify(val)); 68 | } 69 | } 70 | }; 71 | } 72 | 73 | var Visualization = sequelize.define('Visualization', schema, { 74 | classMethods: { 75 | associate: function(models) { 76 | // associations can be defined here 77 | Visualization.belongsTo(models.Session); 78 | Visualization.belongsTo(models.VisualizationType); 79 | }, 80 | 81 | getNamedObjectForVisualization: function(vid, name) { 82 | name = validator.escape(name); 83 | 84 | 85 | if(isPostgres) { 86 | return sequelize 87 | .query('SELECT data->\'' + name + '\'' + ' AS ' + name + ' FROM "Visualizations" WHERE id=' + vid); 88 | } 89 | 90 | 91 | return this.findById(vid).then(function(viz) { 92 | var data = viz.data; 93 | var retObj = {}; 94 | retObj[name] = data[name]; 95 | return [retObj]; 96 | }); 97 | 98 | }, 99 | 100 | getNamedObjectAtIndexForVisualization: function(vid, name, index) { 101 | 102 | name = validator.escape(name); 103 | if(!validator.isNumeric(index)) { 104 | throw Error('Must provide numeric index'); 105 | } 106 | 107 | if(isPostgres) { 108 | return sequelize 109 | .query('SELECT data->\'' + name + '\'->' + index + ' AS ' + name + ' FROM "Visualizations" WHERE id=' + vid); 110 | } 111 | 112 | 113 | return this.findById(vid).then(function(viz) { 114 | var data = viz.data; 115 | var retObj = {}; 116 | retObj[name] = data[name][index]; 117 | return [retObj]; 118 | }); 119 | }, 120 | 121 | getDataForVisualization: function(vid) { 122 | return sequelize.query('SELECT * from "Visualizations" WHERE id=' + vid); 123 | }, 124 | 125 | queryDataForVisualization: function(vid, keys) { 126 | 127 | if(isPostgres) { 128 | var qs = _.chain(keys) 129 | .map(validator.escape) 130 | .map(function(key) { 131 | if(!validator.isNumeric(key)) { 132 | return '\'' + key + '\''; 133 | } 134 | return key; 135 | }) 136 | .value() 137 | .join('->'); 138 | 139 | return sequelize 140 | .query('SELECT data->' + qs + ' AS data FROM "Visualizations" WHERE id=' + vid); 141 | 142 | } 143 | 144 | 145 | return this.findById(vid).then(function(viz) { 146 | 147 | var data = viz.data; 148 | _.each(keys, function(key) { 149 | data = data[key]; 150 | }); 151 | 152 | return [{ 153 | data: data 154 | }]; 155 | }); 156 | 157 | 158 | 159 | }, 160 | 161 | querySettingsForVisualization: function(vid, keys) { 162 | 163 | if(isPostgres) { 164 | var qs = _.chain(keys) 165 | .map(validator.escape) 166 | .map(function(key) { 167 | if(!validator.isNumeric(key)) { 168 | return '\'' + key + '\''; 169 | } 170 | return key; 171 | }) 172 | .value() 173 | .join('->'); 174 | 175 | return sequelize 176 | .query('SELECT settings->' + qs + ' AS settings FROM "Visualizations" WHERE id=' + vid); 177 | 178 | } 179 | 180 | 181 | return this.findById(vid).then(function(viz) { 182 | 183 | var settings = viz.settings; 184 | _.each(keys, function(key) { 185 | settings = settings[key]; 186 | }); 187 | 188 | return [{ 189 | settings: settings 190 | }]; 191 | }); 192 | } 193 | }, 194 | 195 | instanceMethods: { 196 | getNamedObjectAtIndex: function(name, index) { 197 | return Visualization.getNamedObjectAtIndexForVisualization(this.id, name, index); 198 | }, 199 | getNamedObject: function(name) { 200 | return Visualization.getNamedObjectForVisualization(this.id, name); 201 | }, 202 | getData: function() { 203 | return Visualization.getDataForVisualization(this.id); 204 | }, 205 | queryData: function(keys) { 206 | return Visualization.queryDataForVisualization(this.id, keys); 207 | }, 208 | getInitialData: function(type) { 209 | if (type.initialDataFields && type.initialDataFields.length) { 210 | var ret = {}; 211 | _.each(type.initialDataFields, function(field) { 212 | if(!_.isUndefined(this.data[field])) { 213 | ret[field] = this.data[field]; 214 | } 215 | }, this); 216 | return ret; 217 | } 218 | 219 | return this.data; 220 | }, 221 | 222 | getSessionSocketNamespace: function() { 223 | return '/session' + this.SessionId.split('-').join(''); 224 | } 225 | } 226 | }); 227 | 228 | return Visualization; 229 | }; 230 | -------------------------------------------------------------------------------- /app/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _ = require('lodash'); 3 | var titleCase = require('title-case'); 4 | var config = require('../config/config'); 5 | var path = require('path'); 6 | var knox = require('knox'); 7 | var Q = require('q'); 8 | var randomstring = require('randomstring'); 9 | 10 | 11 | module.exports = { 12 | 13 | getNPMConfig: function() { 14 | // electron 15 | if(process.type) { 16 | return { 17 | loglevel: 'error', 18 | prefix: config.root 19 | }; 20 | } 21 | 22 | return { 23 | loglevel: 'error' 24 | }; 25 | }, 26 | 27 | sortByKey: function(list, key) { 28 | return _.sortBy(list, key); 29 | }, 30 | 31 | getRandomArbitrary: function(min, max) { 32 | var val = Math.random() * (max - min) + min; 33 | return val; 34 | }, 35 | 36 | groupByN: function(arr, size) { 37 | var arrays = []; 38 | 39 | while (arr.length > 0) { 40 | arrays.push(arr.splice(0, size)); 41 | } 42 | 43 | return arrays; 44 | }, 45 | 46 | titleCase: function(str) { 47 | return titleCase(str) 48 | }, 49 | 50 | getStaticUrl: function() { 51 | var baseUrl = config.baseURL; 52 | var static_url = baseUrl; 53 | if(config.url) { 54 | static_url = config.url + baseUrl; 55 | } 56 | 57 | return static_url; 58 | }, 59 | 60 | uploadToS3: function(filepath) { 61 | 62 | // check if thumbnailing exists, 63 | // and if s3 creds exist 64 | var s3Exists = !!config.s3.key; 65 | var s3Client = knox.createClient(_.extend(config.s3), { 66 | secure: false 67 | }); 68 | 69 | if(!s3Exists) { 70 | throw new Error('Could not find s3 credentials'); 71 | } 72 | 73 | // Image file info 74 | var imgPath = filepath; 75 | var extension = path.extname(imgPath).toLowerCase(); 76 | var filenameWithoutExtension = path.basename(imgPath, extension); 77 | 78 | // Upload paths for s3 79 | var uploadName = randomstring.generate(); 80 | var destPath = '/viz-type-thumbnails/'; 81 | var s3Path = destPath + uploadName; 82 | 83 | // s3 headers 84 | var headers = { 85 | 'x-amz-acl': 'public-read', 86 | 'Access-Control-Allow-Origin': '*', 87 | }; 88 | if( extension === '.jpg' || extension === '.jpeg' ) { 89 | headers['Content-Type'] = 'image/jpeg'; 90 | } else if (extension === '.png') { 91 | headers['Content-Type'] = 'image/png'; 92 | } else if (extension === '.gif') { 93 | headers['Content-Type'] = 'image/gif'; 94 | } 95 | 96 | return Q.ninvoke(s3Client, 'putFile', imgPath, s3Path, headers); 97 | }, 98 | 99 | 100 | getASCIILogo: function() { 101 | var logo = "\n\n\n ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,` \n"; 102 | logo += " ,` ,.\n"; 103 | logo += ",` ,\n"; 104 | logo += ", ,\n"; 105 | logo += ", . ,\n"; 106 | logo += ", , ,\n"; 107 | logo += ", `, ,\n"; 108 | logo += ", ,. ,\n"; 109 | logo += ", ,, ,\n"; 110 | logo += ", ,,, ,\n"; 111 | logo += ", ,,,. ,\n"; 112 | logo += ", .,,, ,\n"; 113 | logo += ", `,,,, ,\n"; 114 | logo += ", ,,,,` ,\n"; 115 | logo += ", ,,,,, ,\n"; 116 | logo += ", ,,,,,, ,\n"; 117 | logo += ", ,,,,,,,,,,,,,,. ,\n"; 118 | logo += ", .,,,,,,,,,,,,,, ,\n"; 119 | logo += ", ,,,,,,,,,,,,,, ,\n"; 120 | logo += ", ,,,,,,,,,,,,,, ,\n"; 121 | logo += ", ,,,,,,,,,,,,,, ,\n"; 122 | logo += ", ,,,,,` ,\n"; 123 | logo += ", ,,,,,, ,\n"; 124 | logo += ", ,,,,, ,\n"; 125 | logo += ", ,,,, ,\n"; 126 | logo += ", ,,,, ,\n"; 127 | logo += ", ,,,` ,\n"; 128 | logo += ", `,,. ,\n"; 129 | logo += ", ,,, ,\n"; 130 | logo += ", ,, ,\n"; 131 | logo += ", `, ,\n"; 132 | logo += ", , ,\n"; 133 | logo += ", ` ,\n"; 134 | logo += ", ` ,\n"; 135 | logo += ", ,\n"; 136 | logo += "`, .,\n"; 137 | logo += " .,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, \n\n\n\n"; 138 | return logo; 139 | } 140 | 141 | }; -------------------------------------------------------------------------------- /app/views/404.jade: -------------------------------------------------------------------------------- 1 | extends ./layout 2 | 3 | block content 4 | .row 5 | .one-third.column 6 | h2 7 | | 404 -------------------------------------------------------------------------------- /app/views/500.jade: -------------------------------------------------------------------------------- 1 | extends ./layout 2 | 3 | block content 4 | .row 5 | .one-third.column 6 | h2 7 | | 500 -------------------------------------------------------------------------------- /app/views/embed-layout.jade: -------------------------------------------------------------------------------- 1 | 2 | meta(name='viewport', content='width=device-width, initial-scale=1, maximum-scale=1') 3 | meta(http-equiv="X-UA-Compatible" content="IE=10; IE=9; IE=8; IE=7; IE=EDGE") 4 | meta(charset="UTF-8") 5 | block meta 6 | link(href='//fonts.googleapis.com/css?family=Open+Sans:400,700', rel='stylesheet', type='text/css') 7 | link(rel='stylesheet', href='#{STATIC_URL}css/app.css') 8 | block head-js 9 | 10 | block header 11 | block sidebar 12 | nav(role="navigation")#menu.panel 13 | 14 | .button-container 15 | ul 16 | li 17 | a(href="#{BASE_URL}") 18 | | Home 19 | li 20 | a(href="#{BASE_URL}sessions/create") 21 | | New Session 22 | li 23 | a(href="#{BASE_URL}sessions") 24 | | All Sessions 25 | li 26 | a(href="https://github.com/lightning-viz/lightning", target="_blank") 27 | | Github 28 | 29 | .side-color.menu-link 30 | .logo 31 | img(src="#{STATIC_URL}images/lightning.svg") 32 | 33 | .container.content.wrap.push#lightning-body 34 | block content 35 | 36 | block scripts 37 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 38 | script(src='#{STATIC_URL}js/app.js') 39 | -------------------------------------------------------------------------------- /app/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | 4 | //- block header 5 | include includes/header 6 | 7 | block content 8 | .row 9 | .sixteen.columns 10 | .lightning-header 11 | h2 12 | | WELCOME TO LIGHTNING 13 | 14 | 15 | .row 16 | .four.columns.offset-by-six 17 | a(href="#{BASE_URL}sessions/create") 18 | .button 19 | | Create New Session 20 | 21 | 22 | //- form(action="/sessions", method="post") 23 | input(type="submit", value="Create New Session") 24 | -------------------------------------------------------------------------------- /app/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | link(rel="shortcut icon", href="/images/favicon.ico", type="image/x-icon") 5 | meta(name='viewport', content='width=device-width, initial-scale=1, maximum-scale=1') 6 | meta(http-equiv="X-UA-Compatible" content="IE=10; IE=9; IE=8; IE=7; IE=EDGE") 7 | meta(charset="UTF-8") 8 | block meta 9 | link(href='//fonts.googleapis.com/css?family=Open+Sans:400,700', rel='stylesheet', type='text/css') 10 | link(rel='stylesheet', href='#{STATIC_URL}css/app.css') 11 | 12 | body 13 | #lightning-body 14 | block header 15 | block sidebar 16 | nav(role="navigation")#menu.panel 17 | 18 | .button-container 19 | ul 20 | li 21 | a(href="#{BASE_URL}") 22 | | Home 23 | li 24 | a(href="#{BASE_URL}sessions/create") 25 | | New Session 26 | li 27 | a(href="#{BASE_URL}sessions") 28 | | All Sessions 29 | li 30 | a(href="#{BASE_URL}visualization-types") 31 | | Visualization Types 32 | li 33 | a(href="https://github.com/lightning-viz/lightning", target="_blank") 34 | | Github 35 | 36 | .side-color.menu-link 37 | .logo 38 | img(src="#{STATIC_URL}images/lightning.svg") 39 | 40 | block container 41 | .container.content.wrap.push 42 | block content 43 | 44 | block container-full 45 | 46 | block absolute 47 | 48 | script. 49 | window.lightning = window.lightning || {}; 50 | window.lightning.baseURL = "#{BASE_URL}"; 51 | 52 | block scripts 53 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 54 | script(src='#{STATIC_URL}js/basic.js') 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/views/session/feed-public.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block sidebar 4 | 5 | 6 | block content 7 | 8 | .row 9 | .six.columns 10 | h2 11 | | #{session.getDisplayName()} 12 | .subheader 13 | p 14 | | Created #{moment(session.createdAt).fromNow()} 15 | 16 | 17 | .row 18 | .sixteen.columns 19 | .feed-container 20 | 21 | if visualizations.length 22 | for viz in visualizations.reverse() 23 | include ./viz-data-public 24 | hr 25 | 26 | else 27 | p.empty 28 | | No data found. Post data to 29 | span.code 30 | | #{BASE_URL}sessions/#{session.id}/visualizations 31 | 32 | 33 | 34 | 35 | block append scripts 36 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 37 | script(src="/socket.io/socket.io.js") 38 | script(src='#{STATIC_URL}js/basic.js') 39 | if visualizations.length 40 | script(src="#{STATIC_URL}js/dynamic/viz/?visualizations[]=#{_.pluck(_.pluck(visualizations, 'VisualizationType'), 'name').join('&visualizations[]=')}") 41 | script(src='#{STATIC_URL}js/feed.js') 42 | -------------------------------------------------------------------------------- /app/views/session/feed.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | 5 | .row 6 | .six.columns 7 | h2(data-editable, data-model="session", data-model-id="#{session.id}", data-key="name") 8 | | #{session.getDisplayName()} 9 | .subheader 10 | p 11 | | Created #{moment(session.createdAt).fromNow()} 12 | p 13 | small 14 | a(href="#{BASE_URL}sessions/#{session.id}/delete", data-confirm="Are you sure you want to delete this session?") 15 | | Delete Session 16 | 17 | p 18 | small 19 | a(href="#add-data-modal", rel="modal:open") 20 | | Create Visualization 21 | p 22 | small 23 | a(href="../public") 24 | | Public Link 25 | 26 | 27 | 28 | .row 29 | .sixteen.columns 30 | .feed-container 31 | 32 | if visualizations.length 33 | for viz in visualizations.reverse() 34 | include ./viz-data 35 | hr 36 | 37 | else 38 | p.empty 39 | | No data found. Post data to 40 | span.code 41 | | #{BASE_URL}sessions/#{session.id}/visualizations 42 | 43 | 44 | 45 | .modal#add-data-modal 46 | form#data-input-form(method="post", action="../visualizations") 47 | h3 48 | | Manually Create Visualization 49 | select(name="type") 50 | each type, typeName in vizTypes 51 | if typeName === 'line' 52 | option(value="#{typeName}", selected="selected") 53 | | #{typeName} 54 | else 55 | option(value="#{typeName}") 56 | | #{typeName} 57 | 58 | textarea(name="data", placeholder='e.g. {"series": [1, 3, 5, 2, 4, 5, 7, 9]}') 59 | 60 | input(type="submit", value="Submit") 61 | 62 | 63 | 64 | 65 | block scripts 66 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 67 | script(src="/socket.io/socket.io.js") 68 | script(src='#{STATIC_URL}js/basic.js') 69 | if visualizations.length 70 | script(src="#{STATIC_URL}js/dynamic/viz/?visualizations[]=#{_.pluck(_.pluck(visualizations, 'VisualizationType'), 'name').join('&visualizations[]=')}") 71 | script(src='#{STATIC_URL}js/feed.js') 72 | -------------------------------------------------------------------------------- /app/views/session/includes/viz.jade: -------------------------------------------------------------------------------- 1 | .feed-item(data-type="#{viz.VisualizationType.moduleName || viz.VisualizationType.name}", data-data="#{ JSON.stringify(viz.getInitialData(viz.VisualizationType) || {}) }", data-images="#{JSON.stringify(viz.images)}", data-options="#{JSON.stringify(viz.opts)}", id="viz-#{viz.id}", data-initialized="false") -------------------------------------------------------------------------------- /app/views/session/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | 4 | block content 5 | .row 6 | .one-third.column 7 | h2 8 | | Saved Sessions 9 | 10 | .row 11 | .two-thirds.column 12 | ul 13 | for session in sessions 14 | li 15 | a(href="#{BASE_URL}sessions/#{session.id}/feed") 16 | | #{session.getDisplayName()} 17 | 18 | small.session-details 19 | //- | #{session.visualizations.length} visualizations 20 | //- | | 21 | | Created #{moment(session.createdAt).fromNow()} 22 | 23 | .row 24 | .four.columns 25 | a(href="#{BASE_URL}sessions/create") 26 | .button 27 | | Create New Session 28 | 29 | -------------------------------------------------------------------------------- /app/views/session/visualization-embed.jade: -------------------------------------------------------------------------------- 1 | extends ../embed-layout 2 | 3 | block sidebar 4 | 5 | block content 6 | .feed-container 7 | .feed-item-container(data-model="visualization", data-model-id="#{viz.id}") 8 | include ./includes/viz 9 | 10 | 11 | 12 | block scripts 13 | script. 14 | window.lightning = window.lightning || {}; 15 | window.lightning.host = "#{STATIC_URL}" || 'http://127.0.0.1:3000/'; 16 | window.lightning.vizCount = (window.lightning.vizCount + 1) || 1; 17 | window.lightning.requiredVizTypes = window.lightning.requiredVizTypes || []; 18 | if(window.lightning.requiredVizTypes.indexOf("#{viz.VisualizationType.name}") === -1) { 19 | window.lightning.requiredVizTypes.push("#{viz.VisualizationType.name}"); 20 | } 21 | window._require = window.require; 22 | window.require = undefined; 23 | window._define = window.define; 24 | window.define = undefined; 25 | script(src='#{STATIC_URL}js/embed.js') 26 | -------------------------------------------------------------------------------- /app/views/session/visualization-iframe.jade: -------------------------------------------------------------------------------- 1 | extends ../embed-layout 2 | 3 | block sidebar 4 | 5 | block content 6 | 7 | .full-screen.feed-item-container(data-model="visualization", data-model-id="#{viz.id}") 8 | include ./includes/viz 9 | 10 | 11 | 12 | block scripts 13 | script. 14 | window.lightning = window.lightning || {sid: "#{viz.SessionId}"}; 15 | window.lightning.host = window.lightning.host || "#{STATIC_URL}" || 'http://127.0.0.1:3000/'; 16 | window.lightning.requiredVizTypes = window.lightning.requiredVizTypes || []; 17 | if(window.lightning.requiredVizTypes.indexOf("#{viz.VisualizationType.name}") === -1) { 18 | window.lightning.requiredVizTypes.push("#{viz.VisualizationType.name}"); 19 | } 20 | window._require = window.require; 21 | window.require = undefined; 22 | window._define = window.define; 23 | window.define = undefined; 24 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 25 | script(src="/socket.io/socket.io.js") 26 | script(src='#{STATIC_URL}js/embed.js') 27 | -------------------------------------------------------------------------------- /app/views/session/visualization-public.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block meta 4 | meta(content="#{STATIC_URL}visualizations/#{viz.id}/screenshot/?width=800&height=600", property="og:image") 5 | meta(content="#{STATIC_URL}visualizations/#{viz.id}/screenshot/?width=800&height=600", property="og:image:url") 6 | 7 | block sidebar 8 | 9 | block content 10 | //- .row 11 | .two-thirds.column 12 | .feed-container 13 | include ./viz-data-public 14 | 15 | 16 | block scripts 17 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 18 | script(src='#{STATIC_URL}js/basic.js') 19 | script(src="#{STATIC_URL}js/dynamic/viz/?visualizations[]=#{viz.VisualizationType.name}") 20 | script(src='#{STATIC_URL}js/public.js') 21 | -------------------------------------------------------------------------------- /app/views/session/visualization-pym.jade: -------------------------------------------------------------------------------- 1 | extends ../embed-layout 2 | 3 | block sidebar 4 | 5 | block content 6 | 7 | .feed-item-container(data-model="visualization", data-model-id="#{viz.id}") 8 | include ./includes/viz 9 | 10 | 11 | 12 | block scripts 13 | script. 14 | window.lightning = window.lightning || {}; 15 | window.lightning.host = window.lightning.host || "#{STATIC_URL}" || 'http://127.0.0.1:3000/'; 16 | window.lightning.requiredVizTypes = window.lightning.requiredVizTypes || []; 17 | if(window.lightning.requiredVizTypes.indexOf("#{viz.VisualizationType.name}") === -1) { 18 | window.lightning.requiredVizTypes.push("#{viz.VisualizationType.name}"); 19 | } 20 | window.lightning.sid = '#{viz.SessionId}'; 21 | window._require = window.require; 22 | window.require = undefined; 23 | window._define = window.define; 24 | window.define = undefined; 25 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 26 | script(src="/socket.io/socket.io.js") 27 | script(src='#{STATIC_URL}js/pym.js') 28 | -------------------------------------------------------------------------------- /app/views/session/visualization.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block sidebar 4 | 5 | block content 6 | //- .row 7 | .two-thirds.column 8 | .feed-container 9 | include ./viz-data 10 | 11 | 12 | block scripts 13 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 14 | script(src="/socket.io/socket.io.js") 15 | script(src='#{STATIC_URL}js/basic.js') 16 | script(src="#{STATIC_URL}js/dynamic/viz/?visualizations[]=#{viz.VisualizationType.name}") 17 | script. 18 | window.lightning = window.lightning || {} 19 | window.lightning.sid = "#{viz.SessionId}"; 20 | script(src='#{STATIC_URL}js/feed.js') 21 | -------------------------------------------------------------------------------- /app/views/session/viz-data-public.jade: -------------------------------------------------------------------------------- 1 | 2 | .feed-item-container(data-model="visualization", data-model-id="#{viz.id}") 3 | include ./includes/viz 4 | 5 | .description-container 6 | .description 7 | if viz.description 8 | | !{marked(viz.description)} 9 | -------------------------------------------------------------------------------- /app/views/session/viz-data.jade: -------------------------------------------------------------------------------- 1 | 2 | .feed-item-container(data-model="visualization", data-model-id="#{viz.id}") 3 | include ./includes/viz 4 | 5 | .description-container 6 | .description 7 | if viz.description 8 | | !{marked(viz.description)} 9 | 10 | .description-editor 11 | textarea 12 | | #{viz.description} 13 | 14 | 15 | 16 | .bottom-container 17 | .edit-description 18 | a(href="#") 19 | if viz.description 20 | | edit description 21 | else 22 | | add description 23 | 24 | 25 | .permalink 26 | ul(data-dropit).menu 27 | li 28 | a(href="#") 29 | | Actions 30 | ul 31 | li 32 | a(href="#{BASE_URL}visualizations/#{viz.id}/data") 33 | | Raw Data 34 | li 35 | a(href="#{BASE_URL}visualizations/#{viz.id}/screenshot?width=1024&height=768") 36 | | Take Screenshot 37 | li 38 | a(href="#{BASE_URL}visualizations/#{viz.id}", data-attribute="permalink") 39 | | Internal Permalink 40 | li 41 | a(href="#{BASE_URL}visualizations/#{viz.id}/public") 42 | | Public Permalink 43 | li 44 | a(href="#{BASE_URL}visualizations/#{viz.id}/iframe") 45 | | Iframe Link 46 | li 47 | a(href="#{BASE_URL}visualizations/#{viz.id}/pym") 48 | | Pym enabled iframe Link 49 | li 50 | a(href="#{BASE_URL}visualizations/#{viz.id}/delete", data-confirm="Are you sure you want to delete this visualization?") 51 | | Delete 52 | -------------------------------------------------------------------------------- /app/views/viz-types/advanced.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | 4 | block content 5 | 6 | .row 7 | .six.columns 8 | h2 9 | | Visualization Types 10 | 11 | h4 12 | a(href="#import-visualization-modal", rel="modal:open") 13 | | Import Visualization 14 | 15 | 16 | .row 17 | .six.columns 18 | h4 19 | | Fetch Default Visualizations 20 | br 21 | small 22 | | (will not overwrite existing) 23 | a(href="./fetch-defaults") 24 | .button 25 | | Fetch Default Visualizations 26 | 27 | .row 28 | .six.columns 29 | h4 30 | | Reset Default Visualizations 31 | br 32 | small 33 | | (will overwrite existing) 34 | a(href="./reset-defaults", data-confirm="Warning this will delete all local visualization types. Are you sure?") 35 | .button 36 | | Reset Default Visualizations 37 | 38 | .row 39 | .six.columns 40 | h4 41 | | Refresh from NPM. 42 | a(href="./refresh-npm", data-confirm="Warning re-install current visualizations from npm. It WILL NOT delete the underlying models. Are you sure?") 43 | .button 44 | | Refresh from npm 45 | 46 | 47 | 48 | block append scripts 49 | script(src='#{STATIC_URL}js/visualization-types.js') 50 | 51 | -------------------------------------------------------------------------------- /app/views/viz-types/editor.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block container 4 | 5 | .editor-outer-section.wrap.push 6 | .editor-inner-section 7 | .top-matter 8 | .info-container 9 | a(href="#{BASE_URL}visualization-types/") 10 | | ← back 11 | 12 | .title-container 13 | h1 14 | | #{utils.titleCase(vizType.name)} 15 | 16 | .action-container 17 | a(href="#{BASE_URL}visualization-types/#{vizType.id}/delete", data-confirm="Are you sure you want to delete the #{vizType.name} visualization?") 18 | .button.danger 19 | | Delete Visualization 20 | 21 | 22 | #editor-component 23 | 24 | 25 | .saved 26 | | Saved! 27 | .problem-saving 28 | | Problem Saving... 29 | 30 | 31 | block scripts 32 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 33 | script(src="#{STATIC_URL}js/dynamic/viz/?visualizations[]=#{vizType.name}&cachemiss=true") 34 | script. 35 | window.lightning = window.lightning || {}; 36 | window.lightning.editor = { 37 | datasets: !{JSON.stringify(vizType.sampleData)}, 38 | name: "#{vizType.name}", 39 | moduleName: "#{vizType.moduleName}", 40 | sampleImages: !{JSON.stringify(vizType.sampleImages)}, 41 | sampleOptions: !{JSON.stringify(vizType.sampleOptions)}, 42 | codeExamples: !{JSON.stringify(vizType.codeExamples)} 43 | }; 44 | script(src='#{STATIC_URL}js/editor.js') 45 | -------------------------------------------------------------------------------- /app/views/viz-types/preview-editor.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block container 4 | 5 | .editor-outer-section.wrap.push 6 | .editor-inner-section 7 | .top-matter 8 | .info-container 9 | a(href="#{BASE_URL}visualization-types/") 10 | | ← back 11 | 12 | .title-container 13 | h1 14 | | #{utils.titleCase(vizType.name)} 15 | 16 | .action-container 17 | a(href="#import-visualization-modal", rel="modal:open") 18 | .button 19 | | Import 20 | 21 | 22 | 23 | #editor-component 24 | 25 | .modal#import-visualization-modal 26 | #visualization-importer 27 | 28 | .saved 29 | | Saved! 30 | .problem-saving 31 | | Problem Saving... 32 | 33 | 34 | block scripts 35 | script(src='//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js') 36 | script. 37 | !{javascript} 38 | script. 39 | window.lightning = window.lightning || {}; 40 | window.lightning.editor = { 41 | datasets: !{JSON.stringify(vizType.sampleData)}, 42 | sampleOptions: !{JSON.stringify(vizType.sampleOptions)}, 43 | sampleImages: !{JSON.stringify(vizType.sampleImages)}, 44 | name: "#{vizType.name}", 45 | moduleName: "#{vizType.moduleName}", 46 | location: "#{location}", 47 | source: "#{source}", 48 | url: "#{url}", 49 | path: "#{path}" 50 | }; 51 | script(src='#{STATIC_URL}js/preview.js') 52 | -------------------------------------------------------------------------------- /app/views/viz-types/show.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | 4 | block content 5 | 6 | .row 7 | .six.columns 8 | h2 9 | | Visualization Types 10 | 11 | h4 12 | a(href="#import-visualization-modal", rel="modal:open") 13 | | Import Visualization 14 | h4 15 | a(href="./advanced") 16 | | Advanced Settings 17 | 18 | .row 19 | .six.columns 20 | h4 21 | | Installed Visualizations 22 | 23 | for vizTypeGroup in utils.groupByN(vizTypes, 4) 24 | .row 25 | for vizType in vizTypeGroup 26 | .four.columns 27 | 28 | a(href="./edit/#{vizType.id}").preview-container 29 | .image-container 30 | if vizType.thumbnailLocation 31 | .viz-preview(style="background-image:url(#{vizType.getThumbnailURL()});") 32 | else 33 | .viz-preview.noimg 34 | 35 | .title-container 36 | | #{utils.titleCase(vizType.name)} 37 | 38 | 39 | .modal#import-visualization-modal 40 | #visualization-importer 41 | 42 | 43 | block append scripts 44 | script(src='#{STATIC_URL}js/visualization-types.js') 45 | 46 | -------------------------------------------------------------------------------- /bin/lightning-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var forever = require('forever'); 4 | var path = require('path'); 5 | 6 | var script = path.resolve(__dirname + '/../server.js'); 7 | forever.start(script, {}); 8 | -------------------------------------------------------------------------------- /common.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: lightningviz/lightning 3 | ports: 4 | - "3000:3000" -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var path = require('path'); 7 | var extend = require('util')._extend; 8 | var argv = require('yargs').argv; 9 | 10 | var development = require('./env/development'); 11 | var test = require('./env/test'); 12 | var production = require('./env/production'); 13 | 14 | 15 | var defaultVisualizations = argv.default_visualizations ? argv.default_visualizations : process.env.LIGHTNING_DEFAULT_VISUALIZATIONS; 16 | if(defaultVisualizations) { 17 | defaultVisualizations = defaultVisualizations.join(','); 18 | } else { 19 | defaultVisualizations = [ 20 | 'lightning-adjacency', 21 | 'lightning-circle', 22 | 'lightning-force', 23 | 'lightning-gallery', 24 | 'lightning-graph', 25 | 'lightning-graph-bundled', 26 | 'lightning-histogram', 27 | 'lightning-image', 28 | 'lightning-image-poly', 29 | 'lightning-line', 30 | 'lightning-line-streaming', 31 | 'lightning-map', 32 | 'lightning-matrix', 33 | 'lightning-scatter', 34 | 'lightning-scatter-streaming', 35 | 'lightning-scatter-3', 36 | 'lightning-vega-lite', 37 | 'lightning-volume' 38 | ]; 39 | } 40 | 41 | var defaults = { 42 | root: path.normalize(__dirname + '/..'), 43 | auth: { 44 | username: argv.username ? '' + argv.username : process.env.LIGHTNING_USERNAME, 45 | password: argv.password ? '' + argv.password : process.env.LIGHTNING_PASSWORD, 46 | }, 47 | defaultVisualizations: defaultVisualizations 48 | }; 49 | 50 | 51 | var formatBaseUrl = function(baseUrl) { 52 | baseUrl = baseUrl || '/'; 53 | if(baseUrl.slice(-1) !== '/') { 54 | baseUrl += '/'; 55 | } 56 | 57 | if(baseUrl.slice(0, 1) !== '/') { 58 | baseUrl = '/' + baseUrl; 59 | } 60 | 61 | if(baseUrl === '//') { 62 | baseUrl = '/'; 63 | } 64 | return baseUrl; 65 | } 66 | 67 | /** 68 | * Expose 69 | */ 70 | 71 | var config = { 72 | development: extend(development, defaults), 73 | test: extend(test, defaults), 74 | production: extend(production, defaults) 75 | }[process.env.NODE_ENV || 'development']; 76 | 77 | config.baseURL = formatBaseUrl(config.baseURL); 78 | 79 | module.exports = config; 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | 2 | var url = require('url'); 3 | var config = require('./config'); 4 | 5 | var dbUrl = null; 6 | if(process.env.DATABASE_URL) { 7 | dbUrl = url.parse(process.env.DATABASE_URL); 8 | } 9 | 10 | module.exports = { 11 | 'development': { 12 | database: (dbUrl) ? dbUrl.path.replace('/', '') : 'lightning-viz', 13 | username: (dbUrl) ? (dbUrl.auth.split(':') || [false])[0] : null, 14 | password: (dbUrl) ? (dbUrl.auth.split(':') || [false])[1] : null, 15 | 'host': '127.0.0.1', 16 | 'dialect': 'sqlite', 17 | 'port': 5432, 18 | 'sync': {'force': true}, 19 | 'storage': config.root + '/database.sqlite', 20 | 'logging': false 21 | }, 22 | 'test': { 23 | database: (dbUrl) ? dbUrl.path.replace('/', '') : 'lightning-viz', 24 | username: (dbUrl) ? (dbUrl.auth.split(':') || [false])[0] : null, 25 | password: (dbUrl) ? (dbUrl.auth.split(':') || [false])[1] : null, 26 | 'host': '127.0.0.1', 27 | 'dialect': 'postgres', 28 | 'port': 5432, 29 | 'sync': {'force': true}, 30 | 'storage': config.root + '/database.sqlite', 31 | 'logging': false 32 | }, 33 | 'test-sqlite': { 34 | database: (dbUrl) ? dbUrl.path.replace('/', '') : 'lightning-viz', 35 | username: (dbUrl) ? (dbUrl.auth.split(':') || [false])[0] : null, 36 | password: (dbUrl) ? (dbUrl.auth.split(':') || [false])[1] : null, 37 | 'host': '127.0.0.1', 38 | 'dialect': 'sqlite', 39 | 'port': 5432, 40 | 'sync': {'force': true}, 41 | 'storage': config.root + '/database.sqlite', 42 | 'logging': false 43 | }, 44 | 'production': { 45 | database: (dbUrl) ? dbUrl.path.replace('/', '') : 'lightning-viz', 46 | username: (dbUrl) ? (dbUrl.auth.split(':') || [false])[0] : 'lightning', 47 | password: (dbUrl) ? (dbUrl.auth.split(':') || [false])[1] : null, 48 | host: (dbUrl) ? dbUrl.hostname : '127.0.0.1', 49 | port: (dbUrl) ? dbUrl.port : 5432, 50 | 'sync': {'force': true}, 51 | 'logging': false, 52 | native: true, 53 | ssl: true, 54 | 'dialect': 'postgres' 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expose 4 | */ 5 | 6 | 7 | module.exports = { 8 | url: process.env.LIGHTNING_URL || process.env.URL, 9 | baseURL: process.env.LIGHTNING_BASE_URL || '/', 10 | s3: { 11 | key: process.env.S3_KEY, 12 | secret: process.env.S3_SECRET, 13 | bucket: process.env.S3_BUCKET, 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expose 4 | */ 5 | 6 | module.exports = { 7 | 8 | url: process.env.LIGHTNING_URL || process.env.URL, 9 | baseURL: process.env.LIGHTNING_BASE_URL || '/', 10 | s3: { 11 | key: process.env.S3_KEY, 12 | secret: process.env.S3_SECRET, 13 | bucket: process.env.S3_BUCKET, 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /config/env/test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expose 4 | */ 5 | 6 | module.exports = { 7 | 8 | url: 'localhost:3000', 9 | baseURL: process.env.LIGHTNING_BASE_URL || '/', 10 | s3: { 11 | key: process.env.S3_KEY, 12 | secret: process.env.S3_SECRET, 13 | bucket: process.env.S3_BUCKET, 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | var session = require('cookie-session'); 6 | var compression = require('compression'); 7 | var cookieParser = require('cookie-parser'); 8 | var bodyParser = require('body-parser'); 9 | var methodOverride = require('method-override'); 10 | var serveStatic = require('serve-static'); 11 | var slashes = require('connect-slashes'); 12 | var favicon = require('serve-favicon'); 13 | 14 | var config = require('./config'); 15 | var pkg = require('../package.json'); 16 | var moment = require('moment'); 17 | var path = require('path'); 18 | var cors = require('cors'); 19 | 20 | var env = process.env.NODE_ENV || 'development'; 21 | 22 | /** 23 | * Expose 24 | */ 25 | 26 | module.exports = function (app, io) { 27 | 28 | // Compression middleware (should be placed before express.static) 29 | app.use(compression({ 30 | threshold: 512 31 | })); 32 | 33 | // Static files middleware 34 | app.use(favicon(path.resolve(__dirname + '/../public/images/favicon.ico'))); 35 | app.use(serveStatic(config.root + '/public')); 36 | 37 | 38 | // Don't log during tests 39 | // Logging middleware 40 | app.set('trust proxy', 'loopback'); 41 | app.use(cors()); 42 | app.use(slashes()); 43 | 44 | // set views path, template engine and default layout 45 | app.set('views', config.root + '/app/views'); 46 | app.set('view engine', 'jade'); 47 | 48 | app.use(function (req, res, next) { 49 | req.io = io; 50 | next(); 51 | }); 52 | 53 | 54 | var baseUrl = config.baseURL; 55 | var static_url = baseUrl; 56 | if(config.url) { 57 | static_url = config.url + baseUrl; 58 | } 59 | 60 | // cookieParser should be above session 61 | app.use(cookieParser()); 62 | 63 | // bodyParser should be above methodOverride 64 | app.use(bodyParser.json({limit: '50mb'})); 65 | app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); 66 | 67 | 68 | var getRequestStaticUrl = function(req) { 69 | if(req.query.host) { 70 | return req.query.host; 71 | } 72 | 73 | if(req.headers.host) { 74 | var reqType = req.headers['x-forwarded-proto']; 75 | return ((req.secure || reqType === 'https') ? 'https' : 'http') + '://' + req.headers.host + baseUrl; 76 | } 77 | 78 | return static_url + baseUrl; 79 | }; 80 | 81 | 82 | // expose package.json to views 83 | app.use(function (req, res, next) { 84 | 85 | var staticUrl = getRequestStaticUrl(req); 86 | if(staticUrl.slice(-1) !== '/') { 87 | staticUrl += '/'; 88 | } 89 | 90 | res.locals.pkg = pkg; 91 | res.locals.env = env; 92 | res.locals.moment = moment; 93 | res.locals._ = require('lodash'); 94 | res.locals.marked = require('marked'); 95 | res.locals.utils = require('../app/utils'); 96 | res.locals.STATIC_URL = staticUrl; 97 | res.locals.BASE_URL = baseUrl; 98 | next(); 99 | }); 100 | 101 | 102 | app.use(methodOverride(function (req, res) { 103 | if (req.body && typeof req.body === 'object' && '_method' in req.body) { 104 | // look in urlencoded POST bodies and delete it 105 | var method = req.body._method; 106 | delete req.body._method; 107 | return method; 108 | } 109 | })); 110 | 111 | // express/mongo session storage 112 | app.use(session({ 113 | secret: pkg.name, 114 | // store: new mongoStore({ 115 | // url: config.db, 116 | // collection : 'sessions' 117 | // }), 118 | cookie: { 119 | maxAge: 1000*60*60 120 | } 121 | })); 122 | 123 | // adds CSRF support 124 | if (process.env.NODE_ENV !== 'test') { 125 | // app.use(csrf()); 126 | 127 | // // This could be moved to view-helpers :-) 128 | // app.use(function(req, res, next){ 129 | // res.locals.csrf_token = req.csrfToken(); 130 | // next(); 131 | // }); 132 | } 133 | }; 134 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Module dependencies. 4 | */ 5 | 6 | var mongoose = require('mongoose'); 7 | var User = mongoose.model('User'); 8 | 9 | // var facebook = require('./passport/facebook'); 10 | 11 | /** 12 | * Expose 13 | */ 14 | 15 | module.exports = function (passport, config) { 16 | // serialize sessions 17 | passport.serializeUser(function(user, done) { 18 | done(null, user.id) 19 | }) 20 | 21 | passport.deserializeUser(function(id, done) { 22 | User.findOne({ _id: id }, function (err, user) { 23 | done(err, user) 24 | }) 25 | }) 26 | 27 | // use these strategies 28 | // passport.use(facebook); 29 | }; 30 | -------------------------------------------------------------------------------- /config/passport/facebook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | // // var mongoose = require('mongoose'); 7 | // var FacebookStrategy = require('passport-facebook').Strategy; 8 | // // var User = mongoose.model('User'); 9 | // var graph = require('fbgraph'); 10 | 11 | // /** 12 | // * Expose 13 | // */ 14 | 15 | // module.exports = new FacebookStrategy({ 16 | // clientID: process.env.FACEBOOK_APP_ID, 17 | // clientSecret: process.env.FACEBOOK_APP_SECRET, 18 | // callbackURL: ((process.env.NODE_ENV === 'production') ? 'http://boiling-crag-2021.herokuapp.com' : 'http://localhost:3000') + '/auth/facebook/callback', 19 | // enableProof: false 20 | // }, 21 | // function(accessToken, refreshToken, profile, done) { 22 | 23 | 24 | // graph.extendAccessToken({ 25 | // 'access_token': accessToken, 26 | // 'client_id': process.env.FACEBOOK_APP_ID, 27 | // 'client_secret': process.env.FACEBOOK_APP_SECRET 28 | // }, function (err, facebookRes) { 29 | // if(err) { 30 | // return done(err); 31 | // } 32 | 33 | // User.findOrCreate({ 34 | // accessToken: facebookRes.access_token 35 | // }, function(err, user) { 36 | // return done(err, user); 37 | // }); 38 | 39 | 40 | 41 | // }); 42 | // } 43 | // ); 44 | -------------------------------------------------------------------------------- /config/passport/local.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | // var mongoose = require('mongoose'); 7 | // var LocalStrategy = require('passport-local').Strategy; 8 | // var config = require('config'); 9 | // var User = mongoose.model('User'); 10 | 11 | // /** 12 | // * Expose 13 | // */ 14 | 15 | // module.exports = new LocalStrategy({ 16 | // usernameField: 'email', 17 | // passwordField: 'password' 18 | // }, 19 | // function(email, password, done) { 20 | // var options = { 21 | // criteria: { email: email } 22 | // }; 23 | // User.load(options, function (err, user) { 24 | // if (err) return done(err) 25 | // if (!user) { 26 | // return done(null, false, { message: 'Unknown user' }); 27 | // } 28 | // if (!user.authenticate(password)) { 29 | // return done(null, false, { message: 'Invalid password' }); 30 | // } 31 | // return done(null, user); 32 | // }); 33 | // } 34 | // ); 35 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // controllers 4 | var home = require('../app/controllers/home'); 5 | var session = require('../app/controllers/session'); 6 | var visualizationTypes = require('../app/controllers/visualizationTypes'); 7 | var visualization = require('../app/controllers/visualization'); 8 | var staticController = require('../app/controllers/static'); 9 | var basicAuth = require('basic-auth-connect'); 10 | var config = require('./config'); 11 | 12 | /** 13 | * Expose 14 | */ 15 | 16 | module.exports = function (app) { 17 | 18 | var auth = config.auth || {}; 19 | 20 | var authMiddleware = function(req, res, next) {next()}; 21 | 22 | 23 | if(auth.username && auth.password) { 24 | authMiddleware = basicAuth(auth.username, auth.password); 25 | } 26 | 27 | 28 | app.options('*', function (req, res) { 29 | res.set("Access-Control-Allow-Origin", "*"); 30 | res.set("Access-Control-Allow-Headers", "X-Requested-With"); 31 | res.set("Access-Control-Allow-Headers", "Content-Type"); 32 | res.set("Access-Control-Allow-Methods", "GET, OPTIONS, PUT, POST"); 33 | res.status(200).send(); 34 | }); 35 | 36 | app.get('/js/dynamic/viz', staticController.getDynamicVizBundle); 37 | app.get('/visualizations/:vid/public', visualization.publicRead); 38 | app.get('/visualizations/:vid/screenshot', visualization.screenshot); 39 | app.get('/sessions/:sid/public', session.publicRead); 40 | app.get('/visualizations/:vid/iframe', visualization.iframe); 41 | app.get('/visualizations/:vid/pym', visualization.pym); 42 | 43 | // visualizations 44 | app.post('/visualizations/types', authMiddleware, visualizationTypes.create); 45 | app.put('/visualizations/types/:vid', authMiddleware, visualizationTypes.edit); 46 | app.get('/visualizations/types', authMiddleware, visualizationTypes.index); 47 | 48 | app.get('/visualization-types', authMiddleware, visualizationTypes.show); 49 | app.get('/visualization-types/advanced/', authMiddleware, visualizationTypes.advanced); 50 | app.get('/visualization-types/advanced/fetch-defaults', authMiddleware, visualizationTypes.fetchDefaults); 51 | app.get('/visualization-types/advanced/reset-defaults', authMiddleware, visualizationTypes.resetDefaults); 52 | app.get('/visualization-types/advanced/refresh-npm', authMiddleware, visualizationTypes.refreshNPM); 53 | 54 | app.get('/visualization-types/edit/:vid', authMiddleware, visualizationTypes.editor); 55 | app.get('/visualization-types/load/:importPreview/:location', authMiddleware, visualizationTypes.importPreviewHandler); 56 | app.get('/visualization-types/:vizName.json', authMiddleware, visualizationTypes.json); 57 | 58 | app.get('/visualization-types/:vid/thumbnail', authMiddleware, visualizationTypes.thumbnail); 59 | 60 | app.get('/', authMiddleware, home.index); 61 | app.get('/sessions', authMiddleware, session.index); 62 | app.get('/sessions/create/', authMiddleware, session.getCreate); 63 | app.get('/status', authMiddleware, home.status); 64 | 65 | app.get('/sessions/:sid/delete/', authMiddleware, session.getDelete); 66 | app.delete('/sessions/:sid/', authMiddleware, session.delete); 67 | app.delete('/visualization-types/:vid/', authMiddleware, visualizationTypes.delete); 68 | app.get('/visualization-types/:vid/delete', authMiddleware, visualizationTypes.getDelete); 69 | 70 | app.get('/sessions/:sid', authMiddleware, session.feed); 71 | app.get('/sessions/:sid/feed', authMiddleware, session.feed); 72 | 73 | app.put('/sessions/:sid', authMiddleware, session.update); 74 | app.put('/visualizations/:vid', authMiddleware, visualization.update); 75 | 76 | app.post('/sessions', authMiddleware, session.create); 77 | app.post('/sessions/:sid/visualizations', authMiddleware, session.addData); 78 | app.get('/visualizations/:vid', authMiddleware, visualization.read); 79 | app.delete('/visualizations/:vid', authMiddleware, visualization.delete); 80 | app.get('/visualizations/:vid/delete', authMiddleware, visualization.getDelete); 81 | app.get('/visualizations/:vid/embed', authMiddleware, visualization.embed); 82 | app.get('/sessions/:sid/visualizations', authMiddleware, session.listVisualizations); 83 | 84 | 85 | app.post('/sessions/:sid/visualizations/:vid/data', authMiddleware, session.appendData); 86 | app.post('/sessions/:sid/visualizations/:vid/data/:field', authMiddleware, session.appendData); 87 | 88 | 89 | app.put('/sessions/:sid/visualizations/:vid/data', authMiddleware, session.updateData); 90 | app.put('/sessions/:sid/visualizations/:vid/data/:field', authMiddleware, session.updateData); 91 | 92 | 93 | 94 | // public / userland stuff 95 | app.get('/sessions/:sid/visualizations/:vid/data', visualization.getData); 96 | app.get('/sessions/:sid/visualizations/:vid/settings', visualization.getSettings); 97 | app.put('/visualizations/:vid/settings', visualization.updateSettings); 98 | app.post('/visualizations/:vid/settings', visualization.updateSettings); 99 | app.get('/visualizations/:vid/settings', visualization.getSettings); 100 | app.get('/visualizations/:vid/data', visualization.getData); 101 | app.get(/^\/visualizations\/(\d+)\/data\/([^ ]+)/, visualization.getDataWithKeys); 102 | app.get(/^\/visualizations\/(\d+)\/settings\/([^ ]+)/, visualization.getDataWithKeys); 103 | app.get(/^\/sessions\/\d+\/visualizations\/(\d+)\/data\/([^ ]+)/, visualization.getDataWithKeys); 104 | app.get(/^\/sessions\/\d+\/visualizations\/(\d+)\/settings\/([^ ]+)/, visualization.getSettingsWithKeys); 105 | // app.post('/sessions/:sid/visualizations/:vid/images', session.addImage); 106 | 107 | 108 | 109 | 110 | app.use(function (err, req, res, next) { 111 | // treat as 404 112 | if (err.message 113 | && (~err.message.indexOf('not found') 114 | || (~err.message.indexOf('Cast to ObjectId failed')))) { 115 | return next(); 116 | } 117 | console.error(err.stack); 118 | // error page 119 | res.status(500).render('500', { error: err.stack }); 120 | }); 121 | 122 | // assume 404 since no middleware responded 123 | app.use(function (req, res, next) { 124 | res.status(404).render('404', { 125 | url: req.originalUrl, 126 | error: 'Not found' 127 | }); 128 | }); 129 | }; 130 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | # 3 | # docker-compose.yml 4 | # 5 | # author: Rion Dooley 6 | # 7 | # This is the base file to start the lightning viz server 8 | # in development mode. It will run on your docker host, port 3000. 9 | # To add authentication to the server, or leverage S3 storage, 10 | # fill in the environment variables with the appropriate values. 11 | # 12 | # Run with 13 | # 14 | # docker-compose up 15 | # 16 | ################################################################### 17 | web: 18 | extends: 19 | file: common.yml 20 | service: web 21 | environment: 22 | NODE_ENV: development 23 | LIGHTNING_URL: 24 | LIGHTNING_USERNAME: 25 | LIGHTNING_PASSWORD: 26 | S3_KEY: 27 | S3_BUCKET: 28 | S3_SECRET: -------------------------------------------------------------------------------- /electron/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightning-viz/lightning/d4d2d5fc6971a660df587950852d0384d459128d/electron/Icon.png -------------------------------------------------------------------------------- /electron/Menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /electron/icons.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightning-viz/lightning/d4d2d5fc6971a660df587950852d0384d459128d/electron/icons.icns -------------------------------------------------------------------------------- /electron/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 90 | 91 | 92 |
93 | 94 | Lightning server running at localhost:3000 95 | 96 | 97 | 98 | 99 |
100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /electron/index.js: -------------------------------------------------------------------------------- 1 | var menubar = require('menubar'); 2 | var ipc = require('ipc'); 3 | var shell = require('shell') 4 | 5 | var mb = menubar({ 6 | dir: __dirname, 7 | width: 280, 8 | height: 87, 9 | icon: __dirname + '/Icon.png' 10 | }); 11 | 12 | mb.on('ready', function ready () { 13 | require('../server'); 14 | }) 15 | 16 | ipc.on('browser', function browser (ev) { 17 | shell.openExternal('http://localhost:3000') 18 | }) 19 | 20 | ipc.on('terminate', function terminate (ev) { 21 | canQuit = true 22 | mb.app.terminate() 23 | }) 24 | 25 | ipc.on('homepage', function homepage (ev) { 26 | shell.openExternal('http://lightning-viz.org') 27 | }) -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var browserify = require('gulp-browserify'); 5 | var sass = require('gulp-sass'); 6 | var csso = require('gulp-csso'); 7 | var livereload = require('gulp-livereload'); 8 | var tinylr = require('tiny-lr'); 9 | var server = tinylr(); 10 | var gutil = require('gulp-util'); 11 | var gulpif = require('gulp-if'); 12 | var uglify = require('gulp-uglify'); 13 | var srcDir = 'ui/'; 14 | var source = require('vinyl-source-stream'); 15 | var buffer = require('vinyl-buffer'); 16 | var browserifyProtect = require('gulp-browserify-protect'); 17 | 18 | var PRODUCTION_MODE = gutil.env.production; 19 | 20 | gulp.task('js-lib', function() { 21 | return gulp.src(srcDir + 'js/lib/**/*.js') 22 | .pipe(gulpif(PRODUCTION_MODE, uglify())) 23 | .pipe(gulp.dest('./public/js/lib/')) 24 | .pipe( livereload( server )); 25 | }); 26 | 27 | gulp.task('browserify', function() { 28 | 29 | gulp.src(srcDir + 'js/pages/*.js') 30 | .pipe(browserify({ 31 | debug: !PRODUCTION_MODE, 32 | external: ['scatter', 'roi'] 33 | })) 34 | .on('error', gutil.log) 35 | .on('error', gutil.beep) 36 | .pipe(gulpif(PRODUCTION_MODE, uglify())) 37 | .pipe(gulp.dest('./public/js/')) 38 | .pipe( livereload( server )); 39 | 40 | return gulp.src(srcDir + 'js/app.js') 41 | .pipe(browserify({ 42 | debug : !PRODUCTION_MODE 43 | })) 44 | .on('error', gutil.log) 45 | .on('error', gutil.beep) 46 | .pipe(gulpif(PRODUCTION_MODE, uglify())) 47 | .pipe(gulp.dest('./public/js/')) 48 | .pipe( livereload( server )); 49 | }); 50 | 51 | gulp.task('standalone-js', function() { 52 | var browserify = require('browserify'); 53 | var b = browserify({ 54 | entries: srcDir + 'js/etc/standalone.js', 55 | debug: false 56 | }); 57 | 58 | return b.bundle() 59 | .pipe(source('standalone.js')) 60 | .pipe(buffer()) 61 | .pipe(browserifyProtect()) 62 | .pipe(uglify()) 63 | // .on('error', gutil.log) 64 | .pipe(gulp.dest('.')); 65 | 66 | }); 67 | 68 | 69 | gulp.task('css', function() { 70 | return gulp 71 | .src(srcDir + 'stylesheets/app.scss') 72 | .pipe( 73 | sass({ 74 | includePaths: ['src/stylesheets'], 75 | errLogToConsole: true 76 | })) 77 | .pipe( gulpif(PRODUCTION_MODE, csso()) ) 78 | .pipe( gulp.dest('./public/css/') ) 79 | .pipe( livereload( server )); 80 | }); 81 | 82 | 83 | gulp.task('fonts', function() { 84 | return gulp 85 | .src(srcDir + 'fonts/**/*.{otf,svg,ttf,woff,eot}') 86 | .pipe( gulp.dest('./public/fonts/') ) 87 | .pipe( livereload( server )); 88 | }); 89 | 90 | gulp.task('images', function() { 91 | return gulp 92 | .src(srcDir + 'images/**/*') 93 | .pipe( gulp.dest('./public/images/') ) 94 | .pipe( livereload( server )); 95 | }); 96 | 97 | gulp.task('watch', function () { 98 | server.listen(35729, function (err) { 99 | if (err) { 100 | return console.log(err); 101 | } 102 | 103 | gulp.watch(srcDir + 'stylesheets/**/*.{scss,css}',['css']); 104 | gulp.watch(srcDir + 'js/**/*.js',['js']); 105 | }); 106 | }); 107 | 108 | 109 | gulp.task('js', ['browserify','js-lib']); 110 | gulp.task('static', ['js', 'css', 'fonts', 'images']); 111 | gulp.task('default', ['static','watch']); 112 | gulp.task('build', ['static']); 113 | gulp.task('heroku:production', ['static']); 114 | -------------------------------------------------------------------------------- /migrations/20141201124151-visualizatino-settings-migration.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: function(migration, DataTypes, done) { 3 | // add altering commands here, calling 'done' when finished 4 | 5 | migration.addColumn('Visualizations', 'settings', 'JSON'); 6 | 7 | 8 | done(); 9 | }, 10 | down: function(migration, DataTypes, done) { 11 | // add reverting commands here, calling 'done' when finished 12 | migration.removeColumn('Visualizations', 'settings'); 13 | done(); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20150808144909-npm-updates.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | up: function(migration, DataTypes, done) { 5 | // add altering commands here, calling 'done' when finished 6 | migration.addColumn('VisualizationTypes', 'isModule', DataTypes.BOOLEAN); 7 | migration.addColumn('VisualizationTypes', 'isStreaming', DataTypes.BOOLEAN); 8 | done(); 9 | }, 10 | 11 | down: function(migration, DataTypes, done) { 12 | migration.removeColumn('VisualizationTypes', 'isModule'); 13 | migration.removeColumn('VisualizationTypes', 'isStreaming'); 14 | done(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightning-server", 3 | "description": "lightning dataviz notebooks", 4 | "version": "1.3.0", 5 | "author": { 6 | "name": "Matthew Conlen", 7 | "email": "code@mathisonian.com" 8 | }, 9 | "window": { 10 | "toolbar": true 11 | }, 12 | "main": "server.js", 13 | "keywords": [ 14 | "lightning", 15 | "data visualization", 16 | "dataviz", 17 | "d3", 18 | "three.js", 19 | "data viz", 20 | "lightning-viz", 21 | "data server" 22 | ], 23 | "bin": { 24 | "lightning-server": "bin/lightning-server.js" 25 | }, 26 | "engines": { 27 | "node": "6.3.0" 28 | }, 29 | "homepage": "http://lightning-viz.org", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/lightning-viz/lightning" 33 | }, 34 | "scripts": { 35 | "start": "node bin/lightning-server.js", 36 | "dev": "NODE_ENV=development nodemon server.js --watch server.js ", 37 | "electron": "electron electron/", 38 | "test": "mocha --timeout 200000", 39 | "build": "gulp build", 40 | "watch": "gulp", 41 | "migrate": "./node_modules/.bin/sequelize db:migrate --config ./config/database.js", 42 | "createdb": "psql -c 'create database \"lightning-viz\";'", 43 | "postinstall": "npm run fetch-visualizations", 44 | "electron-rebuild": "npm run rebuild-sqlite3;", 45 | "rebuild-sqlite3": "cd node_modules/sqlite3 && npm run prepublish && node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/node-v44-darwin-x64 && node-gyp rebuild --target=0.35.4 --arch=x64 --target_platform=darwin --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/node-v44-darwin-x64", 46 | "fetch-visualizations": "node ./tasks/get_default_visualizations.js", 47 | "generate-standalone-js": "gulp standalone-js" 48 | }, 49 | "browserify": { 50 | "transform": [ 51 | "reactify", 52 | "jadeify" 53 | ] 54 | }, 55 | "license": "MIT", 56 | "dependencies": { 57 | "async": "^0.9.0", 58 | "basic-auth-connect": "^1.0.0", 59 | "body-parser": "^1.4.3", 60 | "browserify": "^11.0.1", 61 | "child-process-promise": "^1.1.0", 62 | "colors": "~0.6.2", 63 | "command-exists": "^1.0.1", 64 | "compression": "^1.0.8", 65 | "concat-stream": "^1.5.1", 66 | "connect-slashes": "^1.2.0", 67 | "cookie-parser": "^1.3.2", 68 | "cookie-session": "^1.0.2", 69 | "cors": "^2.5.2", 70 | "debug": "^2.2.0", 71 | "easyimage": "^1.0.1", 72 | "express": "^4.13.1", 73 | "forever": "^0.11.1", 74 | "fs-extra": "^0.11.1", 75 | "jade": "^1.11.0", 76 | "knox": "^0.9.0", 77 | "lightning-adjacency": "0.0.14", 78 | "lightning-circle": "0.0.6", 79 | "lightning-force": "0.0.14", 80 | "lightning-gallery": "0.0.8", 81 | "lightning-graph": "^0.1.8", 82 | "lightning-graph-bundled": "0.0.9", 83 | "lightning-histogram": "^0.2.1", 84 | "lightning-image": "0.0.8", 85 | "lightning-image-poly": "0.0.10", 86 | "lightning-line": "^1.0.1", 87 | "lightning-line-streaming": "0.0.8", 88 | "lightning-map": "0.0.11", 89 | "lightning-matrix": "0.0.15", 90 | "lightning-scatter": "^0.1.10", 91 | "lightning-scatter-3": "0.0.8", 92 | "lightning-scatter-streaming": "0.0.9", 93 | "lightning-vega-lite": "0.0.8", 94 | "lightning-volume": "0.0.7", 95 | "lodash": "^3.8.0", 96 | "marked": "~0.3.2", 97 | "method-override": "^2.1.1", 98 | "moment": "~2.6.0", 99 | "multiparty": "^3.3.1", 100 | "node-uuid": "^1.4.1", 101 | "npm": "^3.10.5", 102 | "pg": "^6.0.3", 103 | "pg-hstore": "^2.3.2", 104 | "q": "~1.0.1", 105 | "randomstring": "^1.0.3", 106 | "sequelize": "^3.23.6", 107 | "serve-favicon": "^2.1.6", 108 | "serve-static": "^1.10.0", 109 | "socket.io": "^1.3.6", 110 | "sqlite3": "^3.1.4", 111 | "title-case": "^1.1.1", 112 | "validator": "~3.17.0", 113 | "webshot": "^0.15.3", 114 | "yargs": "^1.3.3" 115 | }, 116 | "devDependencies": { 117 | "electron": "^0.4.1", 118 | "expect.js": "^0.3.1", 119 | "gulp": "~3.8.0", 120 | "gulp-browserify": "~0.5.0", 121 | "gulp-browserify-protect": "^0.1.0", 122 | "gulp-csso": "~0.2.7", 123 | "gulp-if": "0.0.5", 124 | "gulp-livereload": "~1.3.1", 125 | "gulp-sass": "^2.1.0", 126 | "gulp-uglify": "~0.2.1", 127 | "gulp-util": "~2.2.14", 128 | "jadeify": "^4.4.0", 129 | "menubar": "^2.0.16", 130 | "mocha": "^1.21.5", 131 | "nodemon": "*", 132 | "reactify": "^1.1.1", 133 | "tiny-lr": "^0.1.6", 134 | "vinyl-buffer": "^1.0.0", 135 | "vinyl-source-stream": "^1.1.0", 136 | "react": "^0.13.3", 137 | "react-highlight": "^0.5.0", 138 | "react-radio-group": "^2.0.2", 139 | "react-simpletabs": "^0.6.1", 140 | "highlight.js": "^8.2.0", 141 | "immutable": "^3.7.4", 142 | "superagent": "^0.18.2", 143 | "pym.js": "^0.4.1", 144 | "sequelize-cli": "^1.7.4" 145 | }, 146 | "optionalDependencies": { 147 | "pg-native": "^1.10.0" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /postgres.yml: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | # 3 | # postgres.yml 4 | # 5 | # author: Rion Dooley 6 | # 7 | # This is a docker compose file to run the Lightning data 8 | # vizualization server with a PostgreSQL db. It will run on your 9 | # Docker host, port 3000. To add authentication to the server, or 10 | # leverage Amazon S3 storage, fill in the environment variables 11 | # with the appropriate values. 12 | # 13 | # Run with 14 | # 15 | # docker-compose -f postgres.yml up 16 | # 17 | ################################################################### 18 | web: 19 | extends: 20 | file: common.yml 21 | service: web 22 | command: bash -c "node server.js" 23 | links: 24 | - db 25 | environment: 26 | DATABASE_URL: lightning:changeit@db:5432/postgres 27 | NODE_ENV: production 28 | LIGHTNING_USERNAME: 29 | LIGHTNING_PASSWORD: 30 | S3_KEY: 31 | S3_BUCKET: 32 | S3_SECRET: 33 | db: 34 | image: postgres 35 | environment: 36 | POSTGRES_USER: lightning 37 | POSTGRES_PASSWORD: changeit 38 | ports: 39 | - 5432 -------------------------------------------------------------------------------- /production.yml: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | # 3 | # production.yml 4 | # 5 | # author: Rion Dooley 6 | # 7 | # This is a docker compose file for running the Lightning data 8 | # vizualization server in moderate production outside of Heroku 9 | # using a PostgreSQL database, reverse proxy, and horizontal scaling. 10 | # The server will be available on ports 80 and 443 on your Docker 11 | # host. 12 | # 13 | # To add authentication to the server, or leverage Amazon S3 storage, 14 | # fill in the environment variables with the appropriate values. 15 | # 16 | # Run with: 17 | # 18 | # docker-compose -f production.yml 19 | # 20 | # Notes: 21 | # 22 | # - Log management, database persistence, and monitoring are 23 | # intentionally left out of this orchestration so you can use 24 | # your favorite solutions. 25 | # 26 | # - To add ssl to the above proxy, replace the %%SSL_CERTIFICATE_PATH%% 27 | # value with the path to the directory containing your SSL certs 28 | # and set SSL: TRUE in the proxy environment section 29 | # ssl cert folder SSL add a proxy server such as. 30 | # 31 | # - To scale the lightning server, use fig to start up more web 32 | # containers. The following command should be sufficient. 33 | # 34 | # docker-compose -f production.yml scale web=2 35 | # 36 | ################################################################### 37 | proxy: 38 | image: jwilder/nginx-proxy 39 | volumes: 40 | - "/var/run/docker.sock:/tmp/docker.sock" 41 | ports: 42 | - "80:80" 43 | - "443:443" 44 | environment: 45 | SSL: FALSE 46 | web: 47 | extends: 48 | file: common.yml 49 | service: web 50 | command: bash -c "node server.js" 51 | links: 52 | - db 53 | environment: 54 | VIRTUAL_PORT: 3000 55 | VIRTUAL_HOST: docker.example.com 56 | NODE_ENV: production 57 | DATABASE_URL: lightning:changeit@db:5432/postgres 58 | LIGHTNING_USERNAME: 59 | LIGHTNING_PASSWORD: 60 | S3_KEY: 61 | S3_BUCKET: 62 | S3_SECRET: 63 | db: 64 | image: postgres 65 | environment: 66 | POSTGRES_USER: lightning 67 | POSTGRES_PASSWORD: changeit 68 | ports: 69 | - 5432 -------------------------------------------------------------------------------- /public/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightning-viz/lightning/d4d2d5fc6971a660df587950852d0384d459128d/public/images/close.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightning-viz/lightning/d4d2d5fc6971a660df587950852d0384d459128d/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/lightning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/js/ipython-comm.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o'+ settings.triggerParentEl +':has('+ settings.submenuEl +')').addClass('dropit-trigger') 26 | .find(settings.submenuEl).addClass('dropit-submenu').hide(); 27 | 28 | // Open on click 29 | $el.on(settings.action, settings.triggerParentEl +':has('+ settings.submenuEl +') > '+ settings.triggerEl +'', function(){ 30 | // Close click menu's if clicked again 31 | if(settings.action == 'click' && $(this).parents(settings.triggerParentEl).hasClass('dropit-open')){ 32 | settings.beforeHide.call(this); 33 | $(this).parents(settings.triggerParentEl).removeClass('dropit-open').find(settings.submenuEl).hide(); 34 | settings.afterHide.call(this); 35 | return false; 36 | } 37 | 38 | // Hide open menus 39 | settings.beforeHide.call(this); 40 | $('.dropit-open').removeClass('dropit-open').find('.dropit-submenu').hide(); 41 | settings.afterHide.call(this); 42 | 43 | // Open this menu 44 | settings.beforeShow.call(this); 45 | $(this).parents(settings.triggerParentEl).addClass('dropit-open').find(settings.submenuEl).show(); 46 | settings.afterShow.call(this); 47 | 48 | return false; 49 | }); 50 | 51 | // Close if outside click 52 | $(document).on('click', function(){ 53 | settings.beforeHide.call(this); 54 | $('.dropit-open').removeClass('dropit-open').find('.dropit-submenu').hide(); 55 | settings.afterHide.call(this); 56 | }); 57 | 58 | // If hover 59 | if(settings.action == 'mouseenter'){ 60 | $el.on('mouseleave', function(){ 61 | settings.beforeHide.call(this); 62 | $(this).removeClass('dropit-open').find(settings.submenuEl).hide(); 63 | settings.afterHide.call(this); 64 | }); 65 | } 66 | 67 | settings.afterLoad.call(this); 68 | }); 69 | } 70 | 71 | }; 72 | 73 | if (methods[method]) { 74 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 75 | } else if (typeof method === 'object' || !method) { 76 | return methods.init.apply(this, arguments); 77 | } else { 78 | $.error( 'Method "' + method + '" does not exist in dropit plugin!'); 79 | } 80 | 81 | }; 82 | 83 | $.fn.dropit.defaults = { 84 | action: 'click', // The open action for the trigger 85 | submenuEl: 'ul', // The submenu element 86 | triggerEl: 'a', // The trigger element 87 | triggerParentEl: 'li', // The trigger parent element 88 | afterLoad: function(){}, // Triggers when plugin has loaded 89 | beforeShow: function(){}, // Triggers before submenu is shown 90 | afterShow: function(){}, // Triggers after submenu is shown 91 | beforeHide: function(){}, // Triggers before submenu is hidden 92 | afterHide: function(){} // Triggers before submenu is hidden 93 | }; 94 | 95 | $.fn.dropit.settings = {}; 96 | 97 | })(jQuery); -------------------------------------------------------------------------------- /public/js/lib/modal.js: -------------------------------------------------------------------------------- 1 | /* 2 | A simple jQuery modal (http://github.com/kylefox/jquery-modal) 3 | Version 0.5.5 4 | */ 5 | (function($) { 6 | 7 | var current = null; 8 | 9 | $.modal = function(el, options) { 10 | $.modal.close(); // Close any open modals. 11 | var remove, target; 12 | this.$body = $('body'); 13 | this.options = $.extend({}, $.modal.defaults, options); 14 | this.options.doFade = !isNaN(parseInt(this.options.fadeDuration, 10)); 15 | if (el.is('a')) { 16 | target = el.attr('href'); 17 | //Select element by id from href 18 | if (/^#/.test(target)) { 19 | this.$elm = $(target); 20 | if (this.$elm.length !== 1) return null; 21 | this.open(); 22 | //AJAX 23 | } else { 24 | this.$elm = $('
'); 25 | this.$body.append(this.$elm); 26 | remove = function(event, modal) { modal.elm.remove(); }; 27 | this.showSpinner(); 28 | el.trigger($.modal.AJAX_SEND); 29 | $.get(target).done(function(html) { 30 | if (!current) return; 31 | el.trigger($.modal.AJAX_SUCCESS); 32 | current.$elm.empty().append(html).on($.modal.CLOSE, remove); 33 | current.hideSpinner(); 34 | current.open(); 35 | el.trigger($.modal.AJAX_COMPLETE); 36 | }).fail(function() { 37 | el.trigger($.modal.AJAX_FAIL); 38 | current.hideSpinner(); 39 | el.trigger($.modal.AJAX_COMPLETE); 40 | }); 41 | } 42 | } else { 43 | this.$elm = el; 44 | this.open(); 45 | } 46 | }; 47 | 48 | $.modal.prototype = { 49 | constructor: $.modal, 50 | 51 | open: function() { 52 | var m = this; 53 | if(this.options.doFade) { 54 | this.block(); 55 | setTimeout(function() { 56 | m.show(); 57 | }, this.options.fadeDuration * this.options.fadeDelay); 58 | } else { 59 | this.block(); 60 | this.show(); 61 | } 62 | if (this.options.escapeClose) { 63 | $(document).on('keydown.modal', function(event) { 64 | if (event.which == 27) $.modal.close(); 65 | }); 66 | } 67 | if (this.options.clickClose) this.blocker.click($.modal.close); 68 | }, 69 | 70 | close: function() { 71 | 72 | console.log('closing modal'); 73 | this.unblock(); 74 | this.hide(); 75 | $(document).off('keydown.modal'); 76 | }, 77 | 78 | block: function() { 79 | var initialOpacity = this.options.doFade ? 0 : this.options.opacity; 80 | this.$elm.trigger($.modal.BEFORE_BLOCK, [this._ctx()]); 81 | this.blocker = $('
').css({ 82 | top: 0, right: 0, bottom: 0, left: 0, 83 | width: "100%", height: "100%", 84 | position: "fixed", 85 | zIndex: this.options.zIndex, 86 | background: this.options.overlay, 87 | opacity: initialOpacity 88 | }); 89 | this.$body.append(this.blocker); 90 | if(this.options.doFade) { 91 | this.blocker.animate({opacity: this.options.opacity}, this.options.fadeDuration); 92 | } 93 | this.$elm.trigger($.modal.BLOCK, [this._ctx()]); 94 | }, 95 | 96 | unblock: function() { 97 | if(this.options.doFade) { 98 | this.blocker.fadeOut(this.options.fadeDuration, function() { 99 | $(this).remove(); 100 | }); 101 | } else { 102 | this.blocker.remove(); 103 | } 104 | }, 105 | 106 | show: function() { 107 | this.$elm.trigger($.modal.BEFORE_OPEN, [this._ctx()]); 108 | if (this.options.showClose) { 109 | this.closeButton = $('' + this.options.closeText + ''); 110 | this.$elm.append(this.closeButton); 111 | } 112 | this.$elm.addClass(this.options.modalClass + ' current'); 113 | this.center(); 114 | if(this.options.doFade) { 115 | this.$elm.fadeIn(this.options.fadeDuration); 116 | } else { 117 | this.$elm.show(); 118 | } 119 | this.$elm.trigger($.modal.OPEN, [this._ctx()]); 120 | }, 121 | 122 | hide: function() { 123 | this.$elm.trigger($.modal.BEFORE_CLOSE, [this._ctx()]); 124 | if (this.closeButton) this.closeButton.remove(); 125 | this.$elm.removeClass('current'); 126 | 127 | if(this.options.doFade) { 128 | this.$elm.fadeOut(this.options.fadeDuration); 129 | } else { 130 | this.$elm.hide(); 131 | } 132 | this.$elm.trigger($.modal.CLOSE, [this._ctx()]); 133 | }, 134 | 135 | showSpinner: function() { 136 | if (!this.options.showSpinner) return; 137 | this.spinner = this.spinner || $('
') 138 | .append(this.options.spinnerHtml); 139 | this.$body.append(this.spinner); 140 | this.spinner.show(); 141 | }, 142 | 143 | hideSpinner: function() { 144 | if (this.spinner) this.spinner.remove(); 145 | }, 146 | 147 | center: function() { 148 | this.$elm.css({ 149 | position: 'fixed', 150 | top: "50%", 151 | left: "50%", 152 | marginTop: - (this.$elm.outerHeight() / 2), 153 | marginLeft: - (this.$elm.outerWidth() / 2), 154 | zIndex: this.options.zIndex + 1 155 | }); 156 | }, 157 | 158 | //Return context for custom events 159 | _ctx: function() { 160 | return { elm: this.$elm, blocker: this.blocker, options: this.options }; 161 | } 162 | }; 163 | 164 | //resize is alias for center for now 165 | $.modal.prototype.resize = $.modal.prototype.center; 166 | 167 | $.modal.close = function(event) { 168 | if (!current) return; 169 | if (event) event.preventDefault(); 170 | current.close(); 171 | var that = current.$elm; 172 | current = null; 173 | return that; 174 | }; 175 | 176 | $.modal.resize = function() { 177 | if (!current) return; 178 | current.resize(); 179 | }; 180 | 181 | // Returns if there currently is an active modal 182 | $.modal.isActive = function () { 183 | return current ? true : false; 184 | } 185 | 186 | $.modal.defaults = { 187 | overlay: "#000", 188 | opacity: 0.75, 189 | zIndex: 1, 190 | escapeClose: true, 191 | clickClose: true, 192 | closeText: 'Close', 193 | closeClass: '', 194 | modalClass: "modal", 195 | spinnerHtml: null, 196 | showSpinner: true, 197 | showClose: true, 198 | fadeDuration: null, // Number of milliseconds the fade animation takes. 199 | fadeDelay: 1.0 // Point during the overlay's fade-in that the modal begins to fade in (.5 = 50%, 1.5 = 150%, etc.) 200 | }; 201 | 202 | // Event constants 203 | $.modal.BEFORE_BLOCK = 'modal:before-block'; 204 | $.modal.BLOCK = 'modal:block'; 205 | $.modal.BEFORE_OPEN = 'modal:before-open'; 206 | $.modal.OPEN = 'modal:open'; 207 | $.modal.BEFORE_CLOSE = 'modal:before-close'; 208 | $.modal.CLOSE = 'modal:close'; 209 | $.modal.AJAX_SEND = 'modal:ajax:send'; 210 | $.modal.AJAX_SUCCESS = 'modal:ajax:success'; 211 | $.modal.AJAX_FAIL = 'modal:ajax:fail'; 212 | $.modal.AJAX_COMPLETE = 'modal:ajax:complete'; 213 | 214 | $.fn.modal = function(options){ 215 | if (this.length === 1) { 216 | current = new $.modal(this, options); 217 | } 218 | return this; 219 | }; 220 | 221 | // Automatically bind links with rel="modal:close" to, well, close the modal. 222 | $(document).on('click.modal', 'a[rel="modal:close"]', $.modal.close); 223 | $(document).on('click.modal', 'a[rel="modal:open"]', function(event) { 224 | event.preventDefault(); 225 | $(this).modal(); 226 | }); 227 | })(jQuery); -------------------------------------------------------------------------------- /public/js/public.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 2 | 3 | -------------------------------------------------------------------------------- /ui/images/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightning-viz/lightning/d4d2d5fc6971a660df587950852d0384d459128d/ui/images/uploads/.gitkeep -------------------------------------------------------------------------------- /ui/js/components/editor/content-editable.js: -------------------------------------------------------------------------------- 1 | // taken from https://github.com/lovasoa/react-contenteditable 2 | // due to the npm version not compiling 3 | var React = require('react'); 4 | 5 | var ContentEditable = React.createClass({ 6 | render: function(){ 7 | return
; 12 | }, 13 | 14 | shouldComponentUpdate: function(nextProps){ 15 | return nextProps.html !== this.getDOMNode().innerHTML; 16 | }, 17 | 18 | componentDidUpdate: function() { 19 | console.log('this.props.valueProp'); 20 | console.log(this.props.valueProp); 21 | if ( this.props.html !== this.getDOMNode().innerHTML ) { 22 | this.getDOMNode().innerHTML = this.props.html; 23 | } 24 | }, 25 | 26 | emitChange: function(evt){ 27 | var html = this.getDOMNode().innerHTML; 28 | if (this.props.onChange && html !== this.lastHtml) { 29 | evt.target = { value: html }; 30 | this.props.onChange(evt); 31 | } 32 | this.lastHtml = html; 33 | } 34 | }); 35 | 36 | module.exports = ContentEditable; 37 | -------------------------------------------------------------------------------- /ui/js/components/editor/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | var _ = require('lodash'); 4 | var Highlight = require('react-highlight'); 5 | var Editor = require('./editor'); 6 | var Immutable = require('immutable'); 7 | 8 | var styles = { 9 | }; 10 | 11 | 12 | var Data = React.createClass({ 13 | 14 | getDefaultProps: function() { 15 | return { 16 | initialSelectedData: {}, 17 | datasets: [], 18 | }; 19 | }, 20 | 21 | getInitialState: function() { 22 | return { 23 | selectedData: this.props.initialSelectedData, 24 | selectedIndex: 0 25 | }; 26 | }, 27 | 28 | handleEdit: function(val) { 29 | try { 30 | var selectedData = JSON.parse(val); 31 | } catch(e) { 32 | console.log(e); 33 | return; 34 | } 35 | this.setState({ 36 | selectedData: selectedData 37 | }); 38 | this.props.onChange(selectedData); 39 | }, 40 | 41 | handleSelectDataset: function(i) { 42 | var selectedData = this.props.datasets[i].data; 43 | this.props.onChange(selectedData); 44 | this.setState({ 45 | selectedIndex: i, 46 | selectedData: selectedData 47 | }); 48 | }, 49 | 50 | renderDataset: function(dataset, i) { 51 | return ( 52 |
53 | 56 |
57 | ); 58 | }, 59 | 60 | renderDatasets: function() { 61 | if(this.props.datasets.length > 1) { 62 | return _.map(this.props.datasets, this.renderDataset, this); 63 | } 64 | return null; 65 | }, 66 | 67 | formatData: function(data) { 68 | var d = data; 69 | if(data instanceof Immutable.Map) { 70 | d = data.toObject(); 71 | } else if (data instanceof Immutable.List) { 72 | d = data.toArray(); 73 | } 74 | return '{\n' + _.map(d, function(val, key) { 75 | return ' \"' + key + "\": " + JSON.stringify(val).split(',').join(', '); 76 | }).join(',\n') + '\n}'; 77 | }, 78 | 79 | render: function() { 80 | return ( 81 |
82 |
83 | 84 |
85 |
86 | {this.renderDatasets()} 87 |
88 |
89 | ); 90 | }, 91 | }); 92 | 93 | module.exports = Data; 94 | 95 | 96 | -------------------------------------------------------------------------------- /ui/js/components/editor/editor.js: -------------------------------------------------------------------------------- 1 | 2 | var React = require('react'); 3 | var _ = require('lodash'); 4 | var Highlight = require('react-highlight'); 5 | var ContentEditable = require('./content-editable'); 6 | 7 | var styles = { 8 | }; 9 | 10 | 11 | var Editor = React.createClass({ 12 | 13 | getDefaultProps: function() { 14 | return { 15 | value: '' 16 | }; 17 | }, 18 | 19 | getInitialState: function() { 20 | return { 21 | isEditing: false, 22 | onChange: _.debounce(this.props.onChange, 500) 23 | }; 24 | }, 25 | 26 | handleChange: function(evt){ 27 | var val = $(evt.target.value).text(); 28 | this.state.onChange(val); 29 | }, 30 | 31 | handleClick: function() { 32 | this.setState({ 33 | isEditing: true 34 | }); 35 | }, 36 | 37 | handleBlur: function() { 38 | this.setState({ 39 | isEditing: false 40 | }); 41 | }, 42 | 43 | componentDidUpdate: function() { 44 | if(this.state.isEditing) { 45 | React.findDOMNode(this.refs.contentEditable).focus();; 46 | } 47 | }, 48 | 49 | renderInnerComponent: function() { 50 | if(this.state.isEditing) { 51 | var html = $('#inner-editor pre').parent().html(); 52 | return ; 53 | } 54 | 55 | return ( 56 | 57 | {this.props.value} 58 | 59 | ); 60 | }, 61 | 62 | render: function() { 63 | return ( 64 |
65 | {this.renderInnerComponent()} 66 |
67 | ); 68 | }, 69 | }); 70 | 71 | module.exports = Editor; 72 | 73 | 74 | -------------------------------------------------------------------------------- /ui/js/components/editor/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | var _ = require('lodash'); 4 | var Immutable = require('immutable'); 5 | var Highlight = require('react-highlight'); 6 | 7 | var Tabs = require('react-simpletabs'); 8 | var DataComponent = require('./data'); 9 | var OptionsComponent = require('./options'); 10 | var VizComponent = require('./viz'); 11 | 12 | var capitalize = function(s) { 13 | return s && s[0].toUpperCase() + s.slice(1); 14 | }; 15 | 16 | var styles = { 17 | }; 18 | 19 | var parseData = function(d) { 20 | if(_.isArray(d)) { 21 | d = Immutable.List(d); 22 | } else if(_.isObject(d)) { 23 | d = Immutable.Map(d); 24 | } 25 | return d; 26 | } 27 | 28 | var Editor = React.createClass({ 29 | 30 | getDefaultProps: function() { 31 | return { 32 | datasets: window.lightning.editor.datasets || [], 33 | images: window.lightning.editor.sampleImages || [], 34 | moduleName: window.lightning.editor.moduleName || '', 35 | initialOptions: window.lightning.editor.sampleOptions || {}, 36 | codeExamples: window.lightning.editor.codeExamples || {}, 37 | }; 38 | }, 39 | 40 | getInitialState: function() { 41 | return { 42 | data: parseData(this.props.datasets.length ? this.props.datasets[0].data : []), 43 | options: parseData(this.props.initialOptions), 44 | selectedTab: 1 45 | }; 46 | }, 47 | 48 | handleDataChange: function(data) { 49 | this.setState({ 50 | data: parseData(data) 51 | }); 52 | }, 53 | 54 | handleOptionsChange: function(options) { 55 | this.setState({ 56 | options: parseData(options) 57 | }); 58 | }, 59 | 60 | tabSelected: function(i) { 61 | this.setState({ 62 | selectedTab: i 63 | }); 64 | }, 65 | 66 | // renderCodeExample: function(example, language) { 67 | // return ( 68 | // 69 | 70 | // 71 | // ); 72 | // }, 73 | 74 | // renderCodeExamples: function() { 75 | // if(_.keys(this.props.codeExamples).length) { 76 | // return _.map(this.props.codeExamples, this.renderCodeExample, this); 77 | // } 78 | // return ''; 79 | // }, 80 | 81 | getTabComponents: function() { 82 | var components = { 83 | 'Data': , 84 | 'Options': , 85 | }; 86 | 87 | _.each(this.props.codeExamples, function(example, language) { 88 | components[capitalize(language) + ' Example'] = {example} 89 | }); 90 | 91 | return components; 92 | }, 93 | 94 | renderTabComponents: function() { 95 | var components = this.getTabComponents(); 96 | var tabComponents = []; 97 | _.each(components, function(val, title) { 98 | tabComponents.push({val}) 99 | }); 100 | return tabComponents; 101 | }, 102 | 103 | render: function() { 104 | return ( 105 |
106 |
107 |
108 |
109 | 110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | 118 | {this.renderTabComponents()} 119 | 120 |
121 |
122 |
123 |
124 |
125 | ); 126 | }, 127 | }); 128 | 129 | module.exports = Editor; 130 | -------------------------------------------------------------------------------- /ui/js/components/editor/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | var _ = require('lodash'); 4 | var Highlight = require('react-highlight'); 5 | var Editor = require('./editor'); 6 | var Immutable = require('immutable'); 7 | 8 | var styles = { 9 | }; 10 | 11 | 12 | var Options = React.createClass({ 13 | 14 | getDefaultProps: function() { 15 | return { 16 | initialOptions: {} 17 | }; 18 | }, 19 | 20 | getInitialState: function() { 21 | return { 22 | options: this.props.initialOptions 23 | }; 24 | }, 25 | 26 | handleEdit: function(val) { 27 | var selectedData; 28 | try { 29 | selectedData = JSON.parse(val); 30 | } catch(e) { 31 | return; 32 | } 33 | this.setState({ 34 | options: selectedData 35 | }); 36 | this.props.onChange(selectedData); 37 | }, 38 | 39 | formatData: function(data) { 40 | var d = data; 41 | if(data instanceof Immutable.Map) { 42 | d = data.toObject(); 43 | } else if (data instanceof Immutable.List) { 44 | d = data.toArray(); 45 | } 46 | return '{\n' + _.map(d, function(val, key) { 47 | return ' \"' + key + '\": ' + JSON.stringify(val).split(',').join(', '); 48 | }).join(',\n') + '\n}'; 49 | }, 50 | 51 | render: function() { 52 | return ( 53 |
54 | 55 |
56 | ); 57 | }, 58 | }); 59 | 60 | module.exports = Options; 61 | 62 | 63 | -------------------------------------------------------------------------------- /ui/js/components/editor/viz.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | var _ = require('lodash'); 4 | 5 | 6 | var Viz = React.createClass({ 7 | 8 | getInitialState: function() { 9 | return { 10 | viz: null 11 | }; 12 | }, 13 | 14 | createViz: function() { 15 | var Viz = require(this.props.moduleName); 16 | this.setState({ 17 | viz: new Viz('#live-visualization-in-editor', this.props.data.toJS(), this.props.images, this.props.options.toJS()) 18 | }); 19 | }, 20 | 21 | componentDidMount: function() { 22 | // initialize the viz 23 | this.createViz(); 24 | }, 25 | 26 | componentDidUpdate: function(prevProps, prevState) { 27 | console.log('componentDidUpdate'); 28 | if(prevState.viz) { 29 | if(prevProps.data !== this.props.data || prevProps.options !== this.props.options) { 30 | var vizEl = $('#live-visualization-in-editor'); 31 | vizEl.css('min-height', vizEl.height()).html(''); 32 | this.createViz(); 33 | } 34 | } 35 | }, 36 | 37 | render: function() { 38 | return ( 39 |
40 |
41 | ); 42 | }, 43 | }); 44 | 45 | module.exports = Viz; 46 | 47 | 48 | -------------------------------------------------------------------------------- /ui/js/components/visualization-importer/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var _ = require('lodash'); 5 | var RadioGroup = require('react-radio-group'); 6 | var Sources = require('./sources'); 7 | 8 | 9 | var styles = { 10 | radioLabel: { 11 | display: 'inline-block', 12 | }, 13 | 14 | radioInput: { 15 | display: 'none' 16 | } 17 | }; 18 | 19 | var Importer = React.createClass({ 20 | 21 | getDefaultProps: function() { 22 | return { 23 | initialSource: 'npm', 24 | }; 25 | }, 26 | 27 | getInitialState: function() { 28 | return { 29 | source: this.props.initialSource 30 | } 31 | }, 32 | 33 | handleSelectSource: function(source) { 34 | this.setState({ 35 | source: source 36 | }); 37 | }, 38 | 39 | render: function() { 40 | 41 | var Source = Sources[this.state.source]; 42 | return ( 43 |
44 |
45 | 52 |
53 |
54 | ); 55 | }, 56 | }); 57 | 58 | module.exports = Importer; 59 | -------------------------------------------------------------------------------- /ui/js/components/visualization-importer/sources/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | npm: require('./npm'), 5 | }; 6 | -------------------------------------------------------------------------------- /ui/js/components/visualization-importer/sources/npm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var _ = require('lodash'); 5 | var RadioGroup = require('react-radio-group'); 6 | var request = require('superagent'); 7 | 8 | 9 | var styles = { 10 | radioInput: { 11 | display: 'none' 12 | } 13 | }; 14 | 15 | var isEmpty = function(str) { 16 | return str.replace(/^\s+|\s+$/g, '').length == 0; 17 | } 18 | 19 | var locations = { 20 | remote: { 21 | label: 'Remote', 22 | inputLabel: 'Module Name', 23 | inputHelp: 'Use username/reponame to import module from github', 24 | key: 'name' 25 | }, 26 | local: { 27 | label: 'Local', 28 | inputLabel: 'Folder Path', 29 | inputHelp: 'e.g. /Users/username/my-visualization', 30 | key: 'path' 31 | }, 32 | 33 | } 34 | 35 | 36 | var NPM = React.createClass({ 37 | 38 | getDefaultProps: function() { 39 | return { 40 | initialLocation: 'remote', 41 | initialImportPreview: 'import', 42 | initialTextVal: '' 43 | }; 44 | }, 45 | 46 | getInitialState: function() { 47 | return { 48 | location: this.props.initialLocation, 49 | importPreview: this.props.initialImportPreview, 50 | textVal: this.props.initialTextVal 51 | }; 52 | }, 53 | 54 | serialize: function(obj) { 55 | var str = []; 56 | for(var p in obj) 57 | if (obj.hasOwnProperty(p)) { 58 | str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); 59 | } 60 | return str.join("&"); 61 | }, 62 | 63 | handleSelectLocation: function(location) { 64 | this.setState({ 65 | location: location 66 | }); 67 | }, 68 | 69 | handleSelectImportPreview: function(importPreview) { 70 | this.setState({ 71 | importPreview: importPreview 72 | }); 73 | }, 74 | 75 | handleTextValChange: function(event) { 76 | this.setState({ 77 | textVal: event.target.value 78 | }); 79 | }, 80 | 81 | handleSubmit: function() { 82 | if(isEmpty(this.state.textVal)) { 83 | return; 84 | } 85 | 86 | var url = '/visualization-types/load/' + this.state.importPreview + '/' + this.state.location; 87 | 88 | var serializeObj = {}; 89 | serializeObj[locations[this.state.location].key] = this.state.textVal; 90 | url += '?' + this.serialize(serializeObj); 91 | 92 | window.location.href = url; 93 | }, 94 | 95 | renderLocationRadioGroup: function(Radio) { 96 | var radios = [{ 97 | name: 'remote', 98 | label: 'Remote' 99 | }, { 100 | name: 'local', 101 | label: 'Local' 102 | }]; 103 | 104 | var styleRadio = function(radio, i) { 105 | var rLen = Object.keys(radios).length; 106 | return ( 107 | 113 | ); 114 | }; 115 | 116 | return ( 117 |
118 | {radios.map(styleRadio.bind(this))} 119 |
120 | ); 121 | }, 122 | 123 | renderImportPreviewRadioGroup: function(Radio) { 124 | var radios = [{ 125 | name: 'import', 126 | label: 'Import' 127 | }, { 128 | name: 'preview', 129 | label: 'Preview' 130 | }]; 131 | 132 | var styleRadio = function(radio, i) { 133 | var rLen = Object.keys(radios).length; 134 | return ( 135 | 141 | ); 142 | 143 | }; 144 | 145 | return ( 146 |
147 | {radios.map(styleRadio.bind(this))} 148 |
149 | ); 150 | }, 151 | 152 | render: function() { 153 | 154 | return ( 155 |
156 |
157 | Select Location: 158 | 159 | {this.renderLocationRadioGroup} 160 | 161 |
162 |
163 | Preview or Import? 164 | 165 | {this.renderImportPreviewRadioGroup} 166 | 167 |
168 |
169 | {locations[this.state.location].inputLabel}: 170 |
171 | 172 | 173 |
174 |
175 | Submit 176 |
177 | 178 |
179 | ); 180 | }, 181 | }); 182 | 183 | module.exports = NPM; 184 | -------------------------------------------------------------------------------- /ui/js/emitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // 3 | // This module is used to share a global eventEmmiter 4 | // object across all the files 5 | // 6 | 7 | var events = require('events'); 8 | var eventEmitter = new events.EventEmitter(); 9 | 10 | module.exports = eventEmitter; 11 | -------------------------------------------------------------------------------- /ui/js/etc/standalone.js: -------------------------------------------------------------------------------- 1 | 2 | var jQueryURL = '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'; 3 | 4 | // require all the default viz's 5 | require('lightning-adjacency'); 6 | require('lightning-circle'); 7 | require('lightning-force'); 8 | require('lightning-gallery'); 9 | require('lightning-graph'); 10 | require('lightning-graph-bundled'); 11 | require('lightning-image'); 12 | require('lightning-image-poly'); 13 | require('lightning-line'); 14 | require('lightning-line-streaming'); 15 | require('lightning-map'); 16 | require('lightning-matrix'); 17 | require('lightning-scatter'); 18 | require('lightning-scatter-streaming'); 19 | require('lightning-scatter-3'); 20 | require('lightning-volume'); 21 | require('lightning-vega-lite'); 22 | 23 | function loadJS(src, callback) { 24 | var s = document.createElement('script'); 25 | s.src = src; 26 | s.async = true; 27 | s.onreadystatechange = s.onload = function() { 28 | var state = s.readyState; 29 | if (!callback.done && (!state || /loaded|complete/.test(state))) { 30 | callback.done = true; 31 | callback(); 32 | } 33 | }; 34 | document.getElementsByTagName('head')[0].appendChild(s); 35 | } 36 | 37 | function init() { 38 | $('.feed-item[data-initialized=false]').each(function() { 39 | var $this = $(this); 40 | var type = $this.data('type'); 41 | var data = $this.data('data'); 42 | var images = $this.data('images'); 43 | var options = $this.data('options'); 44 | var vid = $this.attr('id'); 45 | 46 | var Viz; 47 | try { 48 | require(type); 49 | } catch(e) { 50 | Viz = require('lightning-' + type); 51 | } 52 | 53 | new Viz('#' + vid, data, images, options); 54 | $this.data('initialized', true); 55 | $this.attr('data-initialized', true); 56 | 57 | $('.feed-container').animate({opacity: 1}); 58 | }); 59 | } 60 | 61 | if(!window.$) { 62 | loadJS(jQueryURL, init); 63 | } else { 64 | init(); 65 | } 66 | 67 | window.lightning = window.lightning || {}; 68 | window.lightning.initVisualizations = init; 69 | 70 | -------------------------------------------------------------------------------- /ui/js/lib/bigSlide.js: -------------------------------------------------------------------------------- 1 | /*! bigSlide - v0.4.3 - 2014-01-25 2 | * http://ascott1.github.io/bigSlide.js/ 3 | * Copyright (c) 2014 Adam D. Scott; Licensed MIT */ 4 | (function($) { 5 | 'use strict'; 6 | 7 | $.fn.bigSlide = function(options) { 8 | 9 | var settings = $.extend({ 10 | 'menu': ('#menu'), 11 | 'push': ('.push'), 12 | 'side': 'left', 13 | 'menuWidth': '16.625em', 14 | 'menuOffscreen': '12.625em', 15 | 'speed': '300' 16 | }, options); 17 | 18 | var menuLink = this, 19 | menu = $(settings.menu), 20 | push = $(settings.push), 21 | width = settings.menuWidth; 22 | 23 | var positionOffScreen = { 24 | 'position': 'fixed', 25 | 'top': '0', 26 | 'bottom': '0', 27 | 'width': settings.menuWidth, 28 | 'height': '100%' 29 | }; 30 | 31 | var animateSlide = { 32 | '-webkit-transition': settings.side + ' ' + settings.speed + 'ms ease', 33 | '-moz-transition': settings.side + ' ' + settings.speed + 'ms ease', 34 | '-ms-transition': settings.side + ' ' + settings.speed + 'ms ease', 35 | '-o-transition': settings.side + ' ' + settings.speed + 'ms ease', 36 | 'transition': settings.side + ' ' + settings.speed + 'ms ease' 37 | }; 38 | 39 | menu.css(positionOffScreen); 40 | menu.css(settings.side, '-' + settings.menuOffscreen); 41 | push.css(settings.side, '0'); 42 | menu.css(animateSlide); 43 | push.css(animateSlide); 44 | 45 | menu._state = 'closed'; 46 | 47 | menu.open = function() { 48 | menu._state = 'open'; 49 | menu.css(settings.side, '0'); 50 | push.css(settings.side, width); 51 | }; 52 | 53 | menu.close = function() { 54 | menu._state = 'closed'; 55 | menu.css(settings.side, '-' + settings.menuOffscreen); 56 | push.css(settings.side, '0'); 57 | }; 58 | 59 | menuLink.on('click.bigSlide', function(e) { 60 | e.preventDefault(); 61 | if (menu._state === 'closed') { 62 | menu.open(); 63 | } else { 64 | menu.close(); 65 | } 66 | }); 67 | 68 | menuLink.on('touchend', function(e){ 69 | menuLink.trigger('click.bigSlide'); 70 | e.preventDefault(); 71 | }); 72 | 73 | return menu; 74 | 75 | }; 76 | 77 | }(jQuery)); -------------------------------------------------------------------------------- /ui/js/lib/dropit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Dropit v1.1.0 3 | * http://dev7studios.com/dropit 4 | * 5 | * Copyright 2012, Dev7studios 6 | * Free to use and abuse under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | ;(function($) { 11 | 12 | $.fn.dropit = function(method) { 13 | 14 | var methods = { 15 | 16 | init : function(options) { 17 | this.dropit.settings = $.extend({}, this.dropit.defaults, options); 18 | return this.each(function() { 19 | var $el = $(this), 20 | el = this, 21 | settings = $.fn.dropit.settings; 22 | 23 | // Hide initial submenus 24 | $el.addClass('dropit') 25 | .find('>'+ settings.triggerParentEl +':has('+ settings.submenuEl +')').addClass('dropit-trigger') 26 | .find(settings.submenuEl).addClass('dropit-submenu').hide(); 27 | 28 | // Open on click 29 | $el.on(settings.action, settings.triggerParentEl +':has('+ settings.submenuEl +') > '+ settings.triggerEl +'', function(){ 30 | // Close click menu's if clicked again 31 | if(settings.action == 'click' && $(this).parents(settings.triggerParentEl).hasClass('dropit-open')){ 32 | settings.beforeHide.call(this); 33 | $(this).parents(settings.triggerParentEl).removeClass('dropit-open').find(settings.submenuEl).hide(); 34 | settings.afterHide.call(this); 35 | return false; 36 | } 37 | 38 | // Hide open menus 39 | settings.beforeHide.call(this); 40 | $('.dropit-open').removeClass('dropit-open').find('.dropit-submenu').hide(); 41 | settings.afterHide.call(this); 42 | 43 | // Open this menu 44 | settings.beforeShow.call(this); 45 | $(this).parents(settings.triggerParentEl).addClass('dropit-open').find(settings.submenuEl).show(); 46 | settings.afterShow.call(this); 47 | 48 | return false; 49 | }); 50 | 51 | // Close if outside click 52 | $(document).on('click', function(){ 53 | settings.beforeHide.call(this); 54 | $('.dropit-open').removeClass('dropit-open').find('.dropit-submenu').hide(); 55 | settings.afterHide.call(this); 56 | }); 57 | 58 | // If hover 59 | if(settings.action == 'mouseenter'){ 60 | $el.on('mouseleave', function(){ 61 | settings.beforeHide.call(this); 62 | $(this).removeClass('dropit-open').find(settings.submenuEl).hide(); 63 | settings.afterHide.call(this); 64 | }); 65 | } 66 | 67 | settings.afterLoad.call(this); 68 | }); 69 | } 70 | 71 | }; 72 | 73 | if (methods[method]) { 74 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 75 | } else if (typeof method === 'object' || !method) { 76 | return methods.init.apply(this, arguments); 77 | } else { 78 | $.error( 'Method "' + method + '" does not exist in dropit plugin!'); 79 | } 80 | 81 | }; 82 | 83 | $.fn.dropit.defaults = { 84 | action: 'click', // The open action for the trigger 85 | submenuEl: 'ul', // The submenu element 86 | triggerEl: 'a', // The trigger element 87 | triggerParentEl: 'li', // The trigger parent element 88 | afterLoad: function(){}, // Triggers when plugin has loaded 89 | beforeShow: function(){}, // Triggers before submenu is shown 90 | afterShow: function(){}, // Triggers after submenu is shown 91 | beforeHide: function(){}, // Triggers before submenu is hidden 92 | afterHide: function(){} // Triggers before submenu is hidden 93 | }; 94 | 95 | $.fn.dropit.settings = {}; 96 | 97 | })(jQuery); -------------------------------------------------------------------------------- /ui/js/lib/modal.js: -------------------------------------------------------------------------------- 1 | /* 2 | A simple jQuery modal (http://github.com/kylefox/jquery-modal) 3 | Version 0.5.5 4 | */ 5 | (function($) { 6 | 7 | var current = null; 8 | 9 | $.modal = function(el, options) { 10 | $.modal.close(); // Close any open modals. 11 | var remove, target; 12 | this.$body = $('body'); 13 | this.options = $.extend({}, $.modal.defaults, options); 14 | this.options.doFade = !isNaN(parseInt(this.options.fadeDuration, 10)); 15 | if (el.is('a')) { 16 | target = el.attr('href'); 17 | //Select element by id from href 18 | if (/^#/.test(target)) { 19 | this.$elm = $(target); 20 | if (this.$elm.length !== 1) return null; 21 | this.open(); 22 | //AJAX 23 | } else { 24 | this.$elm = $('
'); 25 | this.$body.append(this.$elm); 26 | remove = function(event, modal) { modal.elm.remove(); }; 27 | this.showSpinner(); 28 | el.trigger($.modal.AJAX_SEND); 29 | $.get(target).done(function(html) { 30 | if (!current) return; 31 | el.trigger($.modal.AJAX_SUCCESS); 32 | current.$elm.empty().append(html).on($.modal.CLOSE, remove); 33 | current.hideSpinner(); 34 | current.open(); 35 | el.trigger($.modal.AJAX_COMPLETE); 36 | }).fail(function() { 37 | el.trigger($.modal.AJAX_FAIL); 38 | current.hideSpinner(); 39 | el.trigger($.modal.AJAX_COMPLETE); 40 | }); 41 | } 42 | } else { 43 | this.$elm = el; 44 | this.open(); 45 | } 46 | }; 47 | 48 | $.modal.prototype = { 49 | constructor: $.modal, 50 | 51 | open: function() { 52 | var m = this; 53 | if(this.options.doFade) { 54 | this.block(); 55 | setTimeout(function() { 56 | m.show(); 57 | }, this.options.fadeDuration * this.options.fadeDelay); 58 | } else { 59 | this.block(); 60 | this.show(); 61 | } 62 | if (this.options.escapeClose) { 63 | $(document).on('keydown.modal', function(event) { 64 | if (event.which == 27) $.modal.close(); 65 | }); 66 | } 67 | if (this.options.clickClose) this.blocker.click($.modal.close); 68 | }, 69 | 70 | close: function() { 71 | 72 | console.log('closing modal'); 73 | this.unblock(); 74 | this.hide(); 75 | $(document).off('keydown.modal'); 76 | }, 77 | 78 | block: function() { 79 | var initialOpacity = this.options.doFade ? 0 : this.options.opacity; 80 | this.$elm.trigger($.modal.BEFORE_BLOCK, [this._ctx()]); 81 | this.blocker = $('
').css({ 82 | top: 0, right: 0, bottom: 0, left: 0, 83 | width: "100%", height: "100%", 84 | position: "fixed", 85 | zIndex: this.options.zIndex, 86 | background: this.options.overlay, 87 | opacity: initialOpacity 88 | }); 89 | this.$body.append(this.blocker); 90 | if(this.options.doFade) { 91 | this.blocker.animate({opacity: this.options.opacity}, this.options.fadeDuration); 92 | } 93 | this.$elm.trigger($.modal.BLOCK, [this._ctx()]); 94 | }, 95 | 96 | unblock: function() { 97 | if(this.options.doFade) { 98 | this.blocker.fadeOut(this.options.fadeDuration, function() { 99 | $(this).remove(); 100 | }); 101 | } else { 102 | this.blocker.remove(); 103 | } 104 | }, 105 | 106 | show: function() { 107 | this.$elm.trigger($.modal.BEFORE_OPEN, [this._ctx()]); 108 | if (this.options.showClose) { 109 | this.closeButton = $('' + this.options.closeText + ''); 110 | this.$elm.append(this.closeButton); 111 | } 112 | this.$elm.addClass(this.options.modalClass + ' current'); 113 | this.center(); 114 | if(this.options.doFade) { 115 | this.$elm.fadeIn(this.options.fadeDuration); 116 | } else { 117 | this.$elm.show(); 118 | } 119 | this.$elm.trigger($.modal.OPEN, [this._ctx()]); 120 | }, 121 | 122 | hide: function() { 123 | this.$elm.trigger($.modal.BEFORE_CLOSE, [this._ctx()]); 124 | if (this.closeButton) this.closeButton.remove(); 125 | this.$elm.removeClass('current'); 126 | 127 | if(this.options.doFade) { 128 | this.$elm.fadeOut(this.options.fadeDuration); 129 | } else { 130 | this.$elm.hide(); 131 | } 132 | this.$elm.trigger($.modal.CLOSE, [this._ctx()]); 133 | }, 134 | 135 | showSpinner: function() { 136 | if (!this.options.showSpinner) return; 137 | this.spinner = this.spinner || $('
') 138 | .append(this.options.spinnerHtml); 139 | this.$body.append(this.spinner); 140 | this.spinner.show(); 141 | }, 142 | 143 | hideSpinner: function() { 144 | if (this.spinner) this.spinner.remove(); 145 | }, 146 | 147 | center: function() { 148 | this.$elm.css({ 149 | position: 'fixed', 150 | top: "50%", 151 | left: "50%", 152 | marginTop: - (this.$elm.outerHeight() / 2), 153 | marginLeft: - (this.$elm.outerWidth() / 2), 154 | zIndex: this.options.zIndex + 1 155 | }); 156 | }, 157 | 158 | //Return context for custom events 159 | _ctx: function() { 160 | return { elm: this.$elm, blocker: this.blocker, options: this.options }; 161 | } 162 | }; 163 | 164 | //resize is alias for center for now 165 | $.modal.prototype.resize = $.modal.prototype.center; 166 | 167 | $.modal.close = function(event) { 168 | if (!current) return; 169 | if (event) event.preventDefault(); 170 | current.close(); 171 | var that = current.$elm; 172 | current = null; 173 | return that; 174 | }; 175 | 176 | $.modal.resize = function() { 177 | if (!current) return; 178 | current.resize(); 179 | }; 180 | 181 | // Returns if there currently is an active modal 182 | $.modal.isActive = function () { 183 | return current ? true : false; 184 | } 185 | 186 | $.modal.defaults = { 187 | overlay: "#000", 188 | opacity: 0.75, 189 | zIndex: 1, 190 | escapeClose: true, 191 | clickClose: true, 192 | closeText: 'Close', 193 | closeClass: '', 194 | modalClass: "modal", 195 | spinnerHtml: null, 196 | showSpinner: true, 197 | showClose: true, 198 | fadeDuration: null, // Number of milliseconds the fade animation takes. 199 | fadeDelay: 1.0 // Point during the overlay's fade-in that the modal begins to fade in (.5 = 50%, 1.5 = 150%, etc.) 200 | }; 201 | 202 | // Event constants 203 | $.modal.BEFORE_BLOCK = 'modal:before-block'; 204 | $.modal.BLOCK = 'modal:block'; 205 | $.modal.BEFORE_OPEN = 'modal:before-open'; 206 | $.modal.OPEN = 'modal:open'; 207 | $.modal.BEFORE_CLOSE = 'modal:before-close'; 208 | $.modal.CLOSE = 'modal:close'; 209 | $.modal.AJAX_SEND = 'modal:ajax:send'; 210 | $.modal.AJAX_SUCCESS = 'modal:ajax:success'; 211 | $.modal.AJAX_FAIL = 'modal:ajax:fail'; 212 | $.modal.AJAX_COMPLETE = 'modal:ajax:complete'; 213 | 214 | $.fn.modal = function(options){ 215 | if (this.length === 1) { 216 | current = new $.modal(this, options); 217 | } 218 | return this; 219 | }; 220 | 221 | // Automatically bind links with rel="modal:close" to, well, close the modal. 222 | $(document).on('click.modal', 'a[rel="modal:close"]', $.modal.close); 223 | $(document).on('click.modal', 'a[rel="modal:open"]', function(event) { 224 | event.preventDefault(); 225 | $(this).modal(); 226 | }); 227 | })(jQuery); -------------------------------------------------------------------------------- /ui/js/pages/basic.js: -------------------------------------------------------------------------------- 1 | require('../lib/bigSlide'); 2 | $('.menu-link').bigSlide(); 3 | 4 | var hljs = require('highlight.js'); 5 | hljs.initHighlightingOnLoad(); 6 | 7 | 8 | $('[data-confirm]').click(function(e) { 9 | if(!confirm($(this).data('confirm'))) { 10 | e.preventDefault(); 11 | } 12 | }); 13 | 14 | $('[data-link]').click(function(e) { 15 | window.location.href = $(this).data('link'); 16 | }); 17 | 18 | 19 | require('../lib/dropit'); 20 | $('[data-dropit]').dropit(); 21 | -------------------------------------------------------------------------------- /ui/js/pages/editor.js: -------------------------------------------------------------------------------- 1 | require('../lib/bigSlide'); 2 | $('.menu-link').bigSlide(); 3 | 4 | 5 | $('[data-confirm]').click(function(e) { 6 | if(!confirm($(this).data('confirm'))) { 7 | e.preventDefault(); 8 | } 9 | }); 10 | 11 | var React = require('react'); 12 | 13 | var Editor = require('../components/editor'); 14 | React.render( , document.getElementById('editor-component')); 15 | -------------------------------------------------------------------------------- /ui/js/pages/embed.js: -------------------------------------------------------------------------------- 1 | var jQueryURL = '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'; 2 | 3 | 4 | var sid = document.URL.substring(document.URL.lastIndexOf('/sessions/') + '/sessions/'.length); 5 | sid = sid.slice(0, sid.indexOf('/')); 6 | sid = (window.lightning || {}).sid || sid; 7 | 8 | var vizs = {}; 9 | 10 | 11 | var utils = require('../utils'); 12 | 13 | var socket; 14 | io = window.io || false 15 | 16 | if(io) { 17 | var namespace = utils.getNamespaceForSession(sid); 18 | console.log('connecting to ' + namespace); 19 | socket = io.connect(namespace); 20 | } else { 21 | socket = { 22 | on: function(){} 23 | } 24 | } 25 | 26 | 27 | socket.on('append', function(message) { 28 | 29 | var vizId = message.vizId; 30 | var data = message.data; 31 | 32 | if(vizs[vizId].appendData) { 33 | vizs[vizId].appendData(data); 34 | } 35 | }); 36 | 37 | socket.on('update', function(message) { 38 | var vizId = message.vizId; 39 | var data = message.data; 40 | 41 | if(vizs[vizId].updateData) { 42 | vizs[vizId].updateData(data); 43 | } 44 | }); 45 | 46 | 47 | function loadJS(src, callback) { 48 | var s = document.createElement('script'); 49 | s.src = src; 50 | s.async = true; 51 | s.onreadystatechange = s.onload = function() { 52 | var state = s.readyState; 53 | if (!callback.done && (!state || /loaded|complete/.test(state))) { 54 | callback.done = true; 55 | callback(); 56 | } 57 | }; 58 | document.getElementsByTagName('head')[0].appendChild(s); 59 | } 60 | 61 | 62 | function init() { 63 | 64 | loadJS(window.lightning.host + 'js/dynamic/viz/?visualizations[]=' + window.lightning.requiredVizTypes.join('&visualizations[]='), function() { 65 | 66 | $('.feed-item[data-initialized=false]').each(function() { 67 | 68 | var type = $(this).data('type'); 69 | var data = $(this).data('data'); 70 | var images = $(this).data('images'); 71 | var options = $(this).data('options'); 72 | 73 | var $this = $(this); 74 | 75 | 76 | utils.requireOrFetchViz({name: type}, function(err, Viz) { 77 | if(err) { 78 | return console.log(err); 79 | } 80 | 81 | var vid = $this.attr('id'); 82 | vizs[vid.slice(vid.indexOf('-') + 1)] = new Viz('#' + $this.attr('id'), data, images, options); 83 | $this.data('initialized', true); 84 | $this.attr('data-initialized', true); 85 | }); 86 | }); 87 | 88 | $('.feed-container').animate({opacity: 1}); 89 | }); 90 | 91 | 92 | // window.require = window._require; 93 | // window.define = window._define; 94 | } 95 | 96 | if(!window.$) { 97 | loadJS(jQueryURL, init); 98 | } else { 99 | init(); 100 | } 101 | -------------------------------------------------------------------------------- /ui/js/pages/feed.js: -------------------------------------------------------------------------------- 1 | 2 | window.define = undefined; 3 | window.lightningDebug = require('debug'); 4 | 5 | require('../lib/modal'); 6 | 7 | 8 | var hasSesssion = document.URL.indexOf('/sessions/') > -1 ? true : false; 9 | var sid = document.URL.substring(document.URL.lastIndexOf('/sessions/') + '/sessions/'.length); 10 | sid = sid.slice(0, sid.indexOf('/')); 11 | sid = (window.lightning || {}).sid || sid; 12 | 13 | 14 | var feedItemHTML = require('../../templates/feed-item.jade'); 15 | 16 | var request = require('superagent'); 17 | var marked = require('marked'); 18 | 19 | var utils = require('../utils'); 20 | var debug = require('debug')('lightning:ui:pages:feed'); 21 | debug('sid: ' + sid); 22 | 23 | var socket; 24 | io = window.io || false 25 | 26 | if(io) { 27 | var namespace = utils.getNamespaceForSession(sid); 28 | debug('connecting to ' + namespace); 29 | socket = io.connect(namespace); 30 | } else { 31 | socket = { 32 | on: function(){} 33 | } 34 | } 35 | 36 | var vizs = {}; 37 | 38 | socket.on('viz', function (viz) { 39 | $('.feed-container .empty').remove(); 40 | utils.requireOrFetchViz(viz.visualizationType, function(err, Viz) { 41 | if(err) { 42 | return debug(err); 43 | } 44 | 45 | $('.feed-container').prepend(feedItemHTML({ 46 | sid: sid, 47 | vid: viz.id 48 | })); 49 | 50 | vizs[viz.id] = new Viz('.feed-container .feed-item', viz.data, viz.images, viz.opts); 51 | 52 | $('.edit-description').unbind().click(editDesctiption); 53 | $('[data-dropit]').unbind().dropit(); 54 | }); 55 | 56 | }); 57 | 58 | socket.on('viz:delete' , function(vizId) { 59 | debug('viz:delete'); 60 | $('.feed-container .feed-item-container[data-model-id="' + vizId + '"]').remove(); 61 | }); 62 | 63 | 64 | socket.on('append', function(message) { 65 | debug('append'); 66 | var vizId = message.vizId; 67 | var data = message.data; 68 | 69 | if(vizs[vizId].appendData) { 70 | vizs[vizId].appendData(data); 71 | } 72 | }); 73 | 74 | socket.on('update', function(message) { 75 | debug('update'); 76 | var vizId = message.vizId; 77 | var data = message.data; 78 | 79 | if(vizs[vizId].updateData) { 80 | vizs[vizId].updateData(data); 81 | } 82 | }); 83 | 84 | 85 | setTimeout(function() { 86 | $('.feed-item[data-initialized=false]').each(function() { 87 | 88 | var type = $(this).data('type'); 89 | var data = $(this).data('data'); 90 | var images = $(this).data('images'); 91 | var options = $(this).data('options'); 92 | var Viz = require(type); 93 | 94 | var vid = $(this).attr('id'); 95 | vizs[vid.slice(vid.indexOf('-') + 1)] = new Viz('#' + $(this).attr('id'), data, images, options); 96 | $(this).data('initialized', true); 97 | $(this).attr('data-initialized', true); 98 | }); 99 | 100 | $('.feed-container').animate({opacity: 1}); 101 | 102 | 103 | $('[data-editable]').each(function() { 104 | 105 | var $this = $(this); 106 | 107 | // append a hidden input after 108 | var $input = $(''); 109 | $this.after($input); 110 | 111 | // allow editable 112 | $this.click(function() { 113 | var text = $this.text(); 114 | $this.hide(); 115 | $input.val(text); 116 | $input.show().focus(); 117 | 118 | $input.unbind('blur').blur(function() { 119 | var url = '/' + $this.data('model').toLowerCase() + 's' + '/' + $this.data('model-id'); 120 | var params = {}; 121 | params[$this.data('key')] = $input.val(); 122 | 123 | $this.text($input.val()); 124 | $this.show(); 125 | $input.hide(); 126 | 127 | request.put(url, params, function(error, res){ 128 | if(error) { 129 | return debug(error); 130 | } 131 | }); 132 | }); 133 | }); 134 | }); 135 | 136 | }, 0); 137 | 138 | 139 | var editDesctiption = function(e) { 140 | 141 | e.preventDefault(); 142 | 143 | var $this = $(this); 144 | var $itemContainer = $this.closest('.feed-item-container'); 145 | var h = Math.max($itemContainer.find('.description').height(), 300); 146 | 147 | var $editor = $itemContainer.find('.description-editor'); 148 | var $description = $itemContainer.find('.description'); 149 | 150 | $itemContainer.find('.edit-description a').hide(); 151 | $description.hide(); 152 | $editor.height(h).show(); 153 | 154 | 155 | $editor.find('textarea').focus().unbind('blur').blur(function() { 156 | 157 | var text = $editor.find('textarea').val(); 158 | $description.html(marked(text)).show(); 159 | $editor.hide(); 160 | $itemContainer.find('.edit-description a').show(); 161 | 162 | var url = '/' + $itemContainer.data('model').toLowerCase() + 's' + '/' + $itemContainer.data('model-id'); 163 | var params = {}; 164 | 165 | params.description = text; 166 | 167 | request.put(url, params, function(error, res){ 168 | if(error) { 169 | return debug(error); 170 | } else { 171 | $('pre code').each(function(i, block) { 172 | hljs.highlightBlock(block); 173 | }); 174 | } 175 | }); 176 | }); 177 | }; 178 | 179 | $('.edit-description').click(editDesctiption); 180 | 181 | $('#data-input-form').submit(function(e) { 182 | e.preventDefault(); 183 | 184 | var url = $(this).attr('action'); 185 | 186 | var params = {}; 187 | var inputs = $(this).serializeArray(); 188 | $.each(inputs, function (i, input) { 189 | params[input.name] = input.value; 190 | }); 191 | 192 | params.data = JSON.parse(params.data); 193 | 194 | request.post(url, params, function(error, res){ 195 | if(error) { 196 | return debug(error); 197 | } else { 198 | debug('success'); 199 | $.modal.close(); 200 | } 201 | }); 202 | }) 203 | -------------------------------------------------------------------------------- /ui/js/pages/ipython-comm.js: -------------------------------------------------------------------------------- 1 | window.lightning = window.lightning || {}; 2 | var lightningCommMap = {}; 3 | var IPython = window.IPython; 4 | 5 | var readCommData = function(commData, field) { 6 | try { 7 | return commData.content.data[field]; 8 | } catch (err) { 9 | return; 10 | } 11 | }; 12 | 13 | 14 | var init_comm = function() { 15 | IPython.notebook.kernel.comm_manager.register_target('lightning', function(comm, data) { 16 | var id = readCommData(data, 'id'); 17 | lightningCommMap[id] = comm; 18 | }); 19 | 20 | window.lightning.comm_map = lightningCommMap; 21 | } 22 | 23 | 24 | if(IPython && IPython.notebook) { 25 | 26 | if(IPython.notebook.kernel) { 27 | init_comm(); 28 | } 29 | 30 | IPython.notebook.events.on('kernel_connected.Kernel', init_comm); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /ui/js/pages/preview.js: -------------------------------------------------------------------------------- 1 | require('../lib/bigSlide'); 2 | $('.menu-link').bigSlide(); 3 | 4 | 5 | $('[data-confirm]').click(function(e) { 6 | if(!confirm($(this).data('confirm'))) { 7 | e.preventDefault(); 8 | } 9 | }); 10 | 11 | var React = require('react'); 12 | 13 | var Editor = require('../components/editor'); 14 | React.render( , document.getElementById('editor-component')); 15 | 16 | var VisualizationImporter = require('../components/visualization-importer'); 17 | 18 | var defaults = window.lightning.editor; 19 | React.render( , document.getElementById('visualization-importer')); 20 | 21 | require('../lib/modal'); 22 | -------------------------------------------------------------------------------- /ui/js/pages/public.js: -------------------------------------------------------------------------------- 1 | 2 | window.define = undefined; 3 | 4 | var vizs = {}; 5 | 6 | 7 | setTimeout(function() { 8 | $('.feed-item[data-initialized=false]').each(function() { 9 | 10 | var type = $(this).data('type'); 11 | var data = $(this).data('data'); 12 | var images = $(this).data('images'); 13 | var options = $(this).data('options'); 14 | var Viz = require(type); 15 | 16 | var vid = $(this).attr('id'); 17 | vizs[vid.slice(vid.indexOf('-') + 1)] = new Viz('#' + $(this).attr('id'), data, images, options); 18 | $(this).data('initialized', true); 19 | $(this).attr('data-initialized', true); 20 | }); 21 | 22 | $('.feed-container').animate({opacity: 1}); 23 | 24 | }, 0); 25 | 26 | 27 | 28 | var socket; 29 | var io = window.io || false 30 | 31 | if(io) { 32 | var namespace = utils.getNamespaceForSession(sid); 33 | debug('connecting to ' + namespace); 34 | socket = io.connect(namespace); 35 | } else { 36 | socket = { 37 | on: function(){} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ui/js/pages/pym.js: -------------------------------------------------------------------------------- 1 | 2 | var utils = require('../utils'); 3 | 4 | var sid = document.URL.substring(document.URL.lastIndexOf('/sessions/') + '/sessions/'.length); 5 | sid = sid.slice(0, sid.indexOf('/')); 6 | sid = (window.lightning || {}).sid || sid; 7 | 8 | var vizs = {}; 9 | 10 | var pym = require('pym.js'); 11 | var pymChild = new pym.Child(); 12 | 13 | var socket; 14 | io = window.io || false 15 | 16 | if(io) { 17 | var namespace = utils.getNamespaceForSession(sid); 18 | console.log('connecting to ' + namespace); 19 | socket = io.connect(namespace); 20 | } else { 21 | socket = { 22 | on: function(){} 23 | } 24 | } 25 | 26 | 27 | function loadJS(src, callback) { 28 | var s = document.createElement('script'); 29 | s.src = src; 30 | s.async = true; 31 | s.onreadystatechange = s.onload = function() { 32 | var state = s.readyState; 33 | if (!callback.done && (!state || /loaded|complete/.test(state))) { 34 | callback.done = true; 35 | callback(); 36 | } 37 | }; 38 | document.getElementsByTagName('head')[0].appendChild(s); 39 | } 40 | 41 | loadJS(window.lightning.host + 'js/dynamic/viz/?visualizations[]=' + window.lightning.requiredVizTypes.join('&visualizations[]='), function() { 42 | 43 | $('.feed-item[data-initialized=false]').each(function() { 44 | 45 | var type = $(this).data('type'); 46 | var data = $(this).data('data'); 47 | var images = $(this).data('images'); 48 | var options = $(this).data('options'); 49 | 50 | var $this = $(this); 51 | 52 | 53 | utils.requireOrFetchViz({name: type}, function(err, Viz) { 54 | if(err) { 55 | return console.log(err); 56 | } 57 | 58 | var vid = $this.attr('id'); 59 | 60 | var viz = new Viz('#' + $this.attr('id'), data, images, options); 61 | 62 | vizs[vid.slice(vid.indexOf('-') + 1)] = viz; 63 | 64 | if(viz.on) { 65 | 66 | viz.on('image:loaded', function() { 67 | pymChild.sendHeight(); 68 | }); 69 | 70 | viz.on('size:updated', function() { 71 | pymChild.sendHeight(); 72 | }) 73 | } 74 | 75 | 76 | $this.data('initialized', true); 77 | $this.attr('data-initialized', true); 78 | 79 | pymChild.sendHeight(); 80 | 81 | }); 82 | }); 83 | 84 | $('.feed-container').animate({opacity: 1}); 85 | 86 | 87 | socket.on('append', function(message) { 88 | 89 | var vizId = message.vizId; 90 | var data = message.data; 91 | 92 | if(vizs[vizId].appendData) { 93 | vizs[vizId].appendData(data); 94 | } 95 | }); 96 | 97 | socket.on('update', function(message) { 98 | 99 | var vizId = message.vizId; 100 | var data = message.data; 101 | 102 | if(vizs[vizId].updateData) { 103 | vizs[vizId].updateData(data); 104 | } 105 | }); 106 | 107 | 108 | 109 | 110 | }); 111 | 112 | -------------------------------------------------------------------------------- /ui/js/pages/visualization-types.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var $vizPreview = $('.viz-preview'); 4 | $vizPreview.css('height', $vizPreview.width()); 5 | 6 | var VisualizationImporter = require('../components/visualization-importer'); 7 | React.render( , document.getElementById('visualization-importer')); 8 | 9 | require('../lib/modal'); 10 | -------------------------------------------------------------------------------- /ui/js/utils/index.js: -------------------------------------------------------------------------------- 1 | var request = require('superagent'); 2 | var baseURL = window.lightning.baseURL || window.lightning.host || '/'; 3 | var debug = require('debug')('lightning:ui:utils'); 4 | debug(baseURL); 5 | 6 | module.exports = { 7 | 8 | getNamespaceForSession: function(sid) { 9 | debug('getting namespace'); 10 | debug(baseURL); 11 | debug(sid); 12 | return window.location.origin + '/session' + sid.split('-').join(''); 13 | }, 14 | 15 | requireOrFetchViz: function(viz, cb) { 16 | debug(viz); 17 | var self = this; 18 | try { 19 | var Viz = require(viz.moduleName || viz.name); 20 | cb(null, Viz); 21 | } catch(e) { 22 | request(baseURL + 'js/dynamic/viz/?visualizations[]=' + viz.name, function(err, res) { 23 | if(err) { 24 | return cb(err); 25 | } 26 | eval(res.text); 27 | var Viz = require(viz.moduleName || viz.name); 28 | cb(null, Viz); 29 | }); 30 | } 31 | 32 | } 33 | 34 | }; 35 | -------------------------------------------------------------------------------- /ui/stylesheets/app.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | body { 4 | margin: 0; 5 | } 6 | #lightning-body { 7 | 8 | $purple: #9175f0; 9 | $blue: #68a1e5; 10 | $gray: #626c7c; 11 | $darkGrayText: #545E6B; 12 | 13 | @import "lib/skeleton"; 14 | @import "lib/modal"; 15 | @import "sidebar"; 16 | 17 | max-width: 100%; 18 | 19 | font-family: 'Open Sans', sans-serif; 20 | 21 | .left { 22 | float: left; 23 | } 24 | .right { 25 | float: right; 26 | } 27 | 28 | 29 | .loading { 30 | text-align: center; 31 | } 32 | .viz-container { 33 | 34 | position: relative; 35 | .loading { 36 | width: 100%; 37 | height: 100%; 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | } 42 | } 43 | .full-width { 44 | width: 100%; 45 | } 46 | 47 | .fixed { 48 | position: fixed; 49 | } 50 | 51 | @import "lib/highlightjs"; 52 | @import "lib/codemirror"; 53 | @import "lib/solarized"; 54 | @import "lib/dropit"; 55 | 56 | .CodeMirror { 57 | height: auto; 58 | overflow-x: hidden; 59 | overflow-y: hidden; 60 | } 61 | 62 | .feed-container { 63 | opacity: 0; 64 | } 65 | 66 | 67 | input[type="text"] { 68 | display: block; 69 | width: 100%; 70 | padding: 6px 12px; 71 | font-size: 14px; 72 | line-height: 1.42857143; 73 | color: #555; 74 | background-color: #fff; 75 | background-image: none; 76 | border: 1px solid #ccc; 77 | border-radius: 4px; 78 | box-sizing: border-box; 79 | } 80 | 81 | .button, 82 | button, 83 | input[type=submit] { 84 | border-radius: 2px; 85 | border: 1px solid #719BEC; 86 | background-color: white; 87 | 88 | font-size: 15px; 89 | color: $blue; 90 | line-height: 20px; 91 | cursor: pointer; 92 | text-align: center; 93 | padding: 5px 0; 94 | 95 | &:hover { 96 | background-color: darken(white, 5%); 97 | color: darken($blue, 15%); 98 | } 99 | 100 | &:focus { 101 | outline:0; 102 | } 103 | } 104 | 105 | input[type=submit], 106 | button { 107 | padding: 5px 10px; 108 | } 109 | 110 | 111 | h1, 112 | h2, 113 | h3, 114 | h4, 115 | h5, 116 | h6 { 117 | font-family: 'Open Sans', sans-serif; 118 | font-weight: normal; 119 | color: #545E6B; 120 | } 121 | 122 | p { 123 | color: #545E6B; 124 | } 125 | 126 | a { 127 | text-decoration: none; 128 | font-size: 14px; 129 | color: #8F7BD9; 130 | line-height: 19px; 131 | 132 | &:hover { 133 | color: #A997EB; 134 | } 135 | 136 | &:active { 137 | color: #59A2E6; 138 | } 139 | } 140 | 141 | li { 142 | list-style: none; 143 | } 144 | ul, li { 145 | margin: 0; 146 | padding: 0; 147 | } 148 | .subheader { color: #75808F; } 149 | 150 | .code, 151 | pre { 152 | background-color: #f0f0f0; 153 | font-family: monospace; 154 | padding: 10px; 155 | border-radius: 4px; 156 | } 157 | 158 | 159 | .lightning-header { 160 | padding-top: 33%; 161 | text-align: center; 162 | h1, h2, h3 { 163 | // font-family: 'Oswald', sans-serif; 164 | font-weight: normal; 165 | } 166 | } 167 | 168 | .session-details { 169 | color: gray; 170 | font-family: 'Open Sans', sans-serif; 171 | margin-left: 10px; 172 | } 173 | 174 | .session-id { 175 | text-align: right; 176 | } 177 | 178 | .feed-item { 179 | &:not(:first-child) { 180 | // padding-top: 60px; 181 | } 182 | 183 | &:after { 184 | content: " "; 185 | display: block; 186 | height: 0; 187 | clear: both; 188 | } 189 | 190 | } 191 | 192 | .feed-item, .feed-item-container, svg, canvas { 193 | &:focus { 194 | outline:0; 195 | } 196 | } 197 | 198 | 199 | input { 200 | color: #545E6B; 201 | 202 | &.editable { 203 | display: none; 204 | width: 100%; 205 | 206 | &.h1 { 207 | 208 | } 209 | 210 | &.h2 { 211 | font-size: 1.5em; 212 | -webkit-margin-before: 0.83em; 213 | } 214 | } 215 | } 216 | 217 | .permalink { 218 | display: inline-block; 219 | width: 50%; 220 | text-align: right; 221 | } 222 | 223 | .edit-description { 224 | display: inline-block; 225 | width: 50%; 226 | } 227 | 228 | .description-editor { 229 | display: none; 230 | textarea { 231 | width: 100%; 232 | height: 100%; 233 | } 234 | padding-bottom: 25px; 235 | } 236 | 237 | .bottom-container { 238 | margin-top: 30px; 239 | } 240 | 241 | .saved, 242 | .problem-saving { 243 | display: none; 244 | color: white; 245 | position: fixed; 246 | top: 0; 247 | right: 10%; 248 | background: rgba(0, 0, 0, .8); 249 | font-family: 'Open Sans', sans-serif; 250 | padding: 5px 10px; 251 | } 252 | 253 | .full-screen { 254 | position: absolute; 255 | top: 0; 256 | left: 0; 257 | right: 0; 258 | bottom: 0; 259 | width: 100%; 260 | height: 100%; 261 | overflow: hidden; 262 | } 263 | 264 | 265 | @import "pages/editor"; 266 | @import "pages/viz-types"; 267 | @import "responsive"; 268 | 269 | .feed-item-container { 270 | .row { 271 | margin-left: 0 !important; 272 | } 273 | } 274 | 275 | 276 | .d3-tip { 277 | line-height: 1; 278 | font-weight: bold; 279 | padding: 6px; 280 | padding-bottom: 8px; 281 | background: rgba(0, 0, 0, 0.6); 282 | color: #fff; 283 | border-radius: 1px; 284 | pointer-events: none; 285 | font-family: monospace; 286 | font-size: 12px; 287 | } 288 | 289 | /* Creates a small triangle extender for the tooltip */ 290 | .d3-tip:after { 291 | box-sizing: border-box; 292 | display: inline; 293 | font-size: 10px; 294 | width: 100%; 295 | line-height: 1; 296 | color: rgba(0, 0, 0, 0.6); 297 | position: absolute; 298 | pointer-events: none; 299 | } 300 | 301 | /* Northward tooltips */ 302 | .d3-tip.n:after { 303 | content: "\25BC"; 304 | margin: -1px 0 0 0; 305 | top: 100%; 306 | left: 0; 307 | text-align: center; 308 | } 309 | 310 | /* Eastward tooltips */ 311 | .d3-tip.e:after { 312 | content: "\25C0"; 313 | margin: -4px 0 0 0; 314 | top: 50%; 315 | left: -8px; 316 | } 317 | 318 | /* Southward tooltips */ 319 | .d3-tip.s:after { 320 | content: "\25B2"; 321 | margin: 0 0 1px 0; 322 | top: 0; 323 | left: 0; 324 | text-align: center; 325 | } 326 | 327 | /* Westward tooltips */ 328 | .d3-tip.w:after { 329 | content: "\25B6"; 330 | margin: -4px 0 0 -1px; 331 | top: 50%; 332 | left: 100%; 333 | } 334 | 335 | } 336 | -------------------------------------------------------------------------------- /ui/stylesheets/lib/dropit.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Dropit v1.1.0 3 | * http://dev7studios.com/dropit 4 | * 5 | * Copyright 2012, Dev7studios 6 | * Free to use and abuse under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | /* These styles assume you are using ul and li */ 11 | .dropit { 12 | list-style: none; 13 | padding: 0; 14 | margin: 0; 15 | } 16 | .dropit .dropit-trigger { position: relative; } 17 | .dropit .dropit-submenu { 18 | position: absolute; 19 | top: 100%; 20 | right: 0; /* dropdown left or right */ 21 | z-index: 1000; 22 | display: none; 23 | min-width: 150px; 24 | list-style: none; 25 | padding: 0; 26 | margin: 0; 27 | } 28 | .dropit .dropit-open .dropit-submenu { display: block; } 29 | 30 | 31 | .menu ul { display: none; } /* Hide before plugin loads */ 32 | .menu ul.dropit-submenu { 33 | background-color: #fff; 34 | border: 1px solid #b2b2b2; 35 | padding: 6px 0; 36 | margin: 3px 0 0 1px; 37 | -webkit-border-radius: 3px; 38 | -moz-border-radius: 3px; 39 | border-radius: 3px; 40 | -webkit-box-shadow: 0px 1px 3px rgba(0,0,0,0.15); 41 | -moz-box-shadow: 0px 1px 3px rgba(0,0,0,0.15); 42 | box-shadow: 0px 1px 3px rgba(0,0,0,0.15); 43 | } 44 | .menu ul.dropit-submenu a { 45 | display: block; 46 | font-size: 14px; 47 | line-height: 25px; 48 | color: #7a868e; 49 | padding: 0 18px; 50 | } 51 | .menu ul.dropit-submenu a:hover { 52 | background: #248fc1; 53 | color: #fff; 54 | text-decoration: none; 55 | } -------------------------------------------------------------------------------- /ui/stylesheets/lib/highlightjs.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | -webkit-text-size-adjust: none; 13 | } 14 | 15 | .hljs-comment, 16 | .bash .hljs-shebang, 17 | .java .hljs-javadoc, 18 | .javascript .hljs-javadoc, 19 | .rust .hljs-preprocessor { 20 | color: #969896; 21 | } 22 | 23 | .hljs-string, 24 | .apache .hljs-sqbracket, 25 | .coffeescript .hljs-subst, 26 | .coffeescript .hljs-regexp, 27 | .cpp .hljs-preprocessor, 28 | .c .hljs-preprocessor, 29 | .javascript .hljs-regexp, 30 | .json .hljs-attribute, 31 | .makefile .hljs-variable, 32 | .markdown .hljs-value, 33 | .markdown .hljs-link_label, 34 | .markdown .hljs-strong, 35 | .markdown .hljs-emphasis, 36 | .markdown .hljs-blockquote, 37 | .nginx .hljs-regexp, 38 | .nginx .hljs-number, 39 | .objectivec .hljs-preprocessor .hljs-title, 40 | .perl .hljs-regexp, 41 | .php .hljs-regexp, 42 | .xml .hljs-value, 43 | .less .hljs-built_in, 44 | .scss .hljs-built_in { 45 | color: #df5000; 46 | } 47 | 48 | .hljs-keyword, 49 | .css .hljs-at_rule, 50 | .css .hljs-important, 51 | .http .hljs-request, 52 | .ini .hljs-setting, 53 | .haskell .hljs-type, 54 | .java .hljs-javadoctag, 55 | .javascript .hljs-tag, 56 | .javascript .hljs-javadoctag, 57 | .nginx .hljs-title, 58 | .objectivec .hljs-preprocessor, 59 | .php .hljs-phpdoc, 60 | .sql .hljs-built_in, 61 | .less .hljs-tag, 62 | .less .hljs-at_rule, 63 | .scss .hljs-tag, 64 | .scss .hljs-at_rule, 65 | .scss .hljs-important, 66 | .stylus .hljs-at_rule, 67 | .go .hljs-typename, 68 | .swift .hljs-preprocessor { 69 | color: #a71d5d; 70 | } 71 | 72 | .apache .hljs-common, 73 | .apache .hljs-cbracket, 74 | .apache .hljs-keyword, 75 | .bash .hljs-literal, 76 | .bash .hljs-built_in, 77 | .coffeescript .hljs-literal, 78 | .coffeescript .hljs-built_in, 79 | .coffeescript .hljs-number, 80 | .cpp .hljs-number, 81 | .cpp .hljs-built_in, 82 | .c .hljs-number, 83 | .c .hljs-built_in, 84 | .cs .hljs-number, 85 | .cs .hljs-built_in, 86 | .css .hljs-attribute, 87 | .css .hljs-hexcolor, 88 | .css .hljs-number, 89 | .css .hljs-function, 90 | .haskell .hljs-number, 91 | .http .hljs-literal, 92 | .http .hljs-attribute, 93 | .java .hljs-number, 94 | .javascript .hljs-built_in, 95 | .javascript .hljs-literal, 96 | .javascript .hljs-number, 97 | .json .hljs-number, 98 | .makefile .hljs-keyword, 99 | .markdown .hljs-link_reference, 100 | .nginx .hljs-built_in, 101 | .objectivec .hljs-literal, 102 | .objectivec .hljs-number, 103 | .objectivec .hljs-built_in, 104 | .php .hljs-literal, 105 | .php .hljs-number, 106 | .python .hljs-number, 107 | .ruby .hljs-prompt, 108 | .ruby .hljs-constant, 109 | .ruby .hljs-number, 110 | .ruby .hljs-subst .hljs-keyword, 111 | .ruby .hljs-symbol, 112 | .rust .hljs-number, 113 | .sql .hljs-number, 114 | .puppet .hljs-function, 115 | .less .hljs-number, 116 | .less .hljs-hexcolor, 117 | .less .hljs-function, 118 | .less .hljs-attribute, 119 | .scss .hljs-preprocessor, 120 | .scss .hljs-number, 121 | .scss .hljs-hexcolor, 122 | .scss .hljs-function, 123 | .scss .hljs-attribute, 124 | .stylus .hljs-number, 125 | .stylus .hljs-hexcolor, 126 | .stylus .hljs-attribute, 127 | .stylus .hljs-params, 128 | .go .hljs-built_in, 129 | .go .hljs-constant, 130 | .swift .hljs-built_in, 131 | .swift .hljs-number { 132 | color: #0086b3; 133 | } 134 | 135 | .apache .hljs-tag, 136 | .cs .hljs-xmlDocTag, 137 | .css .hljs-tag, 138 | .xml .hljs-title, 139 | .stylus .hljs-tag { 140 | color: #63a35c; 141 | } 142 | 143 | .bash .hljs-variable, 144 | .cs .hljs-preprocessor, 145 | .cs .hljs-preprocessor .hljs-keyword, 146 | .css .hljs-attr_selector, 147 | .css .hljs-value, 148 | .ini .hljs-value, 149 | .ini .hljs-keyword, 150 | .javascript .hljs-tag .hljs-title, 151 | .makefile .hljs-constant, 152 | .nginx .hljs-variable, 153 | .xml .hljs-tag, 154 | .scss .hljs-variable { 155 | color: #333333; 156 | } 157 | 158 | .bash .hljs-title, 159 | .coffeescript .hljs-title, 160 | .cpp .hljs-title, 161 | .c .hljs-title, 162 | .cs .hljs-title, 163 | .css .hljs-id, 164 | .css .hljs-class, 165 | .css .hljs-pseudo, 166 | .ini .hljs-title, 167 | .haskell .hljs-title, 168 | .haskell .hljs-pragma, 169 | .java .hljs-title, 170 | .javascript .hljs-title, 171 | .makefile .hljs-title, 172 | .objectivec .hljs-title, 173 | .perl .hljs-sub, 174 | .php .hljs-title, 175 | .python .hljs-decorator, 176 | .python .hljs-title, 177 | .ruby .hljs-parent, 178 | .ruby .hljs-title, 179 | .rust .hljs-title, 180 | .xml .hljs-attribute, 181 | .puppet .hljs-title, 182 | .less .hljs-id, 183 | .less .hljs-pseudo, 184 | .less .hljs-class, 185 | .scss .hljs-id, 186 | .scss .hljs-pseudo, 187 | .scss .hljs-class, 188 | .stylus .hljs-class, 189 | .stylus .hljs-id, 190 | .stylus .hljs-pseudo, 191 | .stylus .hljs-title, 192 | .swift .hljs-title, 193 | .diff .hljs-chunk { 194 | color: #795da3; 195 | } 196 | 197 | .coffeescript .hljs-reserved, 198 | .coffeescript .hljs-attribute { 199 | color: #1d3e81; 200 | } 201 | 202 | .diff .hljs-chunk { 203 | font-weight: bold; 204 | } 205 | 206 | .diff .hljs-addition { 207 | color: #55a532; 208 | background-color: #eaffea; 209 | } 210 | 211 | .diff .hljs-deletion { 212 | color: #bd2c00; 213 | background-color: #ffecec; 214 | } 215 | 216 | .markdown .hljs-link_url { 217 | text-decoration: underline; 218 | } -------------------------------------------------------------------------------- /ui/stylesheets/lib/modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | display: none; 3 | min-width: 400px; 4 | background: #fff; 5 | padding: 15px 30px; 6 | -webkit-border-radius: 8px; 7 | -moz-border-radius: 8px; 8 | -o-border-radius: 8px; 9 | -ms-border-radius: 8px; 10 | border-radius: 8px; 11 | -webkit-box-shadow: 0 0 10px #000; 12 | -moz-box-shadow: 0 0 10px #000; 13 | -o-box-shadow: 0 0 10px #000; 14 | -ms-box-shadow: 0 0 10px #000; 15 | box-shadow: 0 0 10px #000; 16 | } 17 | 18 | .modal a.close-modal { 19 | position: absolute; 20 | top: -12.5px; 21 | right: -12.5px; 22 | display: block; 23 | width: 30px; 24 | height: 30px; 25 | text-indent: -9999px; 26 | background: url(/images/close.png) no-repeat 0 0; 27 | } 28 | 29 | .modal-spinner { 30 | display: none; 31 | width: 64px; 32 | height: 64px; 33 | position: fixed; 34 | top: 50%; 35 | left: 50%; 36 | margin-right: -32px; 37 | margin-top: -32px; 38 | background: url(spinner.gif) #111 no-repeat center center; 39 | -webkit-border-radius: 8px; 40 | -moz-border-radius: 8px; 41 | -o-border-radius: 8px; 42 | -ms-border-radius: 8px; 43 | border-radius: 8px; 44 | } -------------------------------------------------------------------------------- /ui/stylesheets/lib/solarized.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Solarized theme for code-mirror 3 | http://ethanschoonover.com/solarized 4 | */ 5 | 6 | /* 7 | Solarized color pallet 8 | http://ethanschoonover.com/solarized/img/solarized-palette.png 9 | */ 10 | 11 | .solarized.base03 { color: #002b36; } 12 | .solarized.base02 { color: #073642; } 13 | .solarized.base01 { color: #586e75; } 14 | .solarized.base00 { color: #657b83; } 15 | .solarized.base0 { color: #839496; } 16 | .solarized.base1 { color: #93a1a1; } 17 | .solarized.base2 { color: #eee8d5; } 18 | .solarized.base3 { color: #fdf6e3; } 19 | .solarized.solar-yellow { color: #b58900; } 20 | .solarized.solar-orange { color: #cb4b16; } 21 | .solarized.solar-red { color: #dc322f; } 22 | .solarized.solar-magenta { color: #d33682; } 23 | .solarized.solar-violet { color: #6c71c4; } 24 | .solarized.solar-blue { color: #268bd2; } 25 | .solarized.solar-cyan { color: #2aa198; } 26 | .solarized.solar-green { color: #859900; } 27 | 28 | /* Color scheme for code-mirror */ 29 | 30 | .cm-s-solarized { 31 | line-height: 1.45em; 32 | color-profile: sRGB; 33 | rendering-intent: auto; 34 | } 35 | .cm-s-solarized.cm-s-dark { 36 | color: #839496; 37 | background-color: #002b36; 38 | text-shadow: #002b36 0 1px; 39 | } 40 | .cm-s-solarized.cm-s-light { 41 | // background-color: #fdf6e3; 42 | color: #657b83; 43 | text-shadow: #eee8d5 0 1px; 44 | } 45 | 46 | .cm-s-solarized .CodeMirror-widget { 47 | text-shadow: none; 48 | } 49 | 50 | 51 | .cm-s-solarized .cm-keyword { color: #cb4b16 } 52 | .cm-s-solarized .cm-atom { color: #d33682; } 53 | .cm-s-solarized .cm-number { color: #d33682; } 54 | .cm-s-solarized .cm-def { color: #2aa198; } 55 | 56 | .cm-s-solarized .cm-variable { color: #268bd2; } 57 | .cm-s-solarized .cm-variable-2 { color: #b58900; } 58 | .cm-s-solarized .cm-variable-3 { color: #6c71c4; } 59 | 60 | .cm-s-solarized .cm-property { color: #2aa198; } 61 | .cm-s-solarized .cm-operator {color: #6c71c4;} 62 | 63 | .cm-s-solarized .cm-comment { color: #586e75; font-style:italic; } 64 | 65 | .cm-s-solarized .cm-string { color: #859900; } 66 | .cm-s-solarized .cm-string-2 { color: #b58900; } 67 | 68 | .cm-s-solarized .cm-meta { color: #859900; } 69 | .cm-s-solarized .cm-qualifier { color: #b58900; } 70 | .cm-s-solarized .cm-builtin { color: #d33682; } 71 | .cm-s-solarized .cm-bracket { color: #cb4b16; } 72 | .cm-s-solarized .CodeMirror-matchingbracket { color: #859900; } 73 | .cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; } 74 | .cm-s-solarized .cm-tag { color: #93a1a1 } 75 | .cm-s-solarized .cm-attribute { color: #2aa198; } 76 | .cm-s-solarized .cm-header { color: #586e75; } 77 | .cm-s-solarized .cm-quote { color: #93a1a1; } 78 | .cm-s-solarized .cm-hr { 79 | color: transparent; 80 | border-top: 1px solid #586e75; 81 | display: block; 82 | } 83 | .cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; } 84 | .cm-s-solarized .cm-special { color: #6c71c4; } 85 | .cm-s-solarized .cm-em { 86 | color: #999; 87 | text-decoration: underline; 88 | text-decoration-style: dotted; 89 | } 90 | .cm-s-solarized .cm-strong { color: #eee; } 91 | .cm-s-solarized .cm-tab:before { 92 | content: "➤"; /*visualize tab character*/ 93 | color: #586e75; 94 | position:absolute; 95 | } 96 | .cm-s-solarized .cm-error, 97 | .cm-s-solarized .cm-invalidchar { 98 | color: #586e75; 99 | border-bottom: 1px dotted #dc322f; 100 | } 101 | 102 | .cm-s-solarized.cm-s-dark .CodeMirror-selected { 103 | background: #073642; 104 | } 105 | 106 | .cm-s-solarized.cm-s-light .CodeMirror-selected { 107 | background: #eee8d5; 108 | } 109 | 110 | /* Editor styling */ 111 | 112 | 113 | 114 | /* Little shadow on the view-port of the buffer view */ 115 | 116 | /* Gutter border and some shadow from it */ 117 | .cm-s-solarized .CodeMirror-gutters { 118 | border-right: 1px solid; 119 | } 120 | 121 | /* Gutter colors and line number styling based of color scheme (dark / light) */ 122 | 123 | /* Dark */ 124 | .cm-s-solarized.cm-s-dark .CodeMirror-gutters { 125 | background-color: #002b36; 126 | border-color: #00232c; 127 | } 128 | 129 | .cm-s-solarized.cm-s-dark .CodeMirror-linenumber { 130 | text-shadow: #021014 0 -1px; 131 | } 132 | 133 | /* Light */ 134 | .cm-s-solarized.cm-s-light .CodeMirror-gutters { 135 | background-color: #fdf6e3; 136 | border-color: #eee8d5; 137 | } 138 | 139 | /* Common */ 140 | .cm-s-solarized .CodeMirror-linenumber { 141 | color: #586e75; 142 | padding: 0 5px; 143 | } 144 | .cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; } 145 | .cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; } 146 | .cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; } 147 | 148 | .cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { 149 | color: #586e75; 150 | } 151 | 152 | .cm-s-solarized .CodeMirror-lines .CodeMirror-cursor { 153 | border-left: 1px solid #819090; 154 | } 155 | 156 | /* 157 | Active line. Negative margin compensates left padding of the text in the 158 | view-port 159 | */ 160 | .cm-s-solarized.cm-s-dark .CodeMirror-activeline-background { 161 | background: rgba(255, 255, 255, 0.10); 162 | } 163 | .cm-s-solarized.cm-s-light .CodeMirror-activeline-background { 164 | background: rgba(0, 0, 0, 0.10); 165 | } -------------------------------------------------------------------------------- /ui/stylesheets/pages/editor.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .editor-outer-section { 5 | width: 100%; 6 | border-size: 'border-box'; 7 | clear: both; 8 | 9 | .editor-inner-section { 10 | padding-left: 4em; 11 | 12 | 13 | .top-matter { 14 | 15 | clear: both; 16 | padding-left: 20px; 17 | padding-top: 20px; 18 | .info-container { 19 | float: left; 20 | width: 23%; 21 | a { 22 | display: inline-block; 23 | position: relative; 24 | top: -7px; 25 | padding-left: 10px; 26 | } 27 | } 28 | 29 | .title-container { 30 | text-align: center; 31 | width: 54%; 32 | float: left; 33 | margin-bottom: 60px; 34 | h1 { 35 | display: inline-block; 36 | margin-top: 0; 37 | margin-bottom: 0; 38 | margin-left: 20px; 39 | 40 | } 41 | } 42 | .action-container { 43 | // margin-top: 15px; 44 | float: left; 45 | width: 20%; 46 | text-align: right; 47 | box-sizing: 'border-box'; 48 | margin-right: 3%; 49 | margin-top: 1%; 50 | .button { 51 | width: 70%; 52 | display: inline; 53 | padding-left: 3%; 54 | padding-right: 3%; 55 | 56 | &.pad-left { 57 | margin-left: 7.5%; 58 | } 59 | } 60 | 61 | } 62 | } 63 | } 64 | 65 | &.data-component { 66 | width: 80%; 67 | margin: 0 auto; 68 | /*background: #f0f0f0;*/ 69 | /*min-height: 300px;*/ 70 | /* 71 | .editor-inner-section { 72 | padding-top: 30px; 73 | }*/ 74 | } 75 | 76 | pre { 77 | padding: 0; 78 | border: none; 79 | } 80 | 81 | 82 | .editor-viz-container { 83 | width: 60%; 84 | margin: 0 auto; 85 | 86 | &.full { 87 | width: 60%; 88 | margin-bottom: 30px; 89 | } 90 | } 91 | 92 | .data-button { 93 | /*margin-top: 13px;*/ 94 | 95 | margin-right: 10px; 96 | margin-top: 10px; 97 | 98 | &.active { 99 | color: #fff; 100 | background-color: $blue; 101 | } 102 | } 103 | 104 | code { 105 | border-radius: 4px; 106 | padding: 1.2em; 107 | } 108 | 109 | 110 | .tab-container { 111 | margin-top: 15px; 112 | margin-bottom: 20px; 113 | 114 | nav ul { 115 | margin: 0; padding: 0; 116 | li { 117 | display: inline-block; 118 | zoom:1; *display:inline; 119 | cursor: pointer; 120 | 121 | a { 122 | font-size: 14px; 123 | line-height: 2em; 124 | display: block; 125 | padding: 0 10px; 126 | outline: none; 127 | color: #444; 128 | 129 | &:visited { 130 | color: #444; 131 | } 132 | } 133 | 134 | &.is-active { 135 | background-color: #f6f7fa;; 136 | padding-top: 6px; 137 | position: relative; 138 | top: 1px; 139 | border-top: solid 2px $purple; 140 | 141 | a { 142 | font-weight: bold; 143 | } 144 | } 145 | } 146 | } 147 | 148 | .tab-panel { 149 | 150 | background-color: #f6f7fa; 151 | padding: 10px; 152 | } 153 | 154 | .code, pre { 155 | margin: 0; 156 | } 157 | 158 | .code, pre, code { 159 | background: white; 160 | } 161 | } 162 | 163 | 164 | } -------------------------------------------------------------------------------- /ui/stylesheets/pages/viz-types.scss: -------------------------------------------------------------------------------- 1 | 2 | $bg-color: #eee; 3 | $stops: 100; 4 | $time: 20s; 5 | $hue-range: 35; 6 | 7 | @mixin bg-size($bg-size) { 8 | -webkit-background-size: $bg-size; 9 | -moz-background-size: $bg-size; 10 | -ms-background-size: $bg-size; 11 | -o-background-size: $bg-size; 12 | background-size: $bg-size; 13 | } 14 | 15 | 16 | .preview-container { 17 | display: block; 18 | padding-bottom: 17px; 19 | margin-bottom: 20px; 20 | 21 | cursor: pointer; 22 | transition: opacity .25s ease-in-out; 23 | -moz-transition: opacity .25s ease-in-out; 24 | -webkit-transition: opacity .25s ease-in-out; 25 | 26 | &:hover { 27 | opacity: 0.6; 28 | } 29 | } 30 | 31 | .viz-preview { 32 | display: block; 33 | width: 100%; 34 | background-position: 50% 50%; 35 | background-size: 100%; 36 | 37 | @include bg-size(cover); 38 | 39 | margin-top: 10px; 40 | margin-bottom: 10px; 41 | background-color: $bg-color; 42 | 43 | &.noimg { 44 | background-color: $bg-color; 45 | -webkit-animation: colorChange $time linear 0s infinite; 46 | animation: colorChange $time linear 0s infinite; 47 | 48 | } 49 | 50 | } 51 | 52 | $border: solid 1px $gray; 53 | 54 | #visualization-importer { 55 | padding: 30px 0; 56 | // background-color: #eee; 57 | // border: dotted 1px $gray; 58 | border-radius: 5px; 59 | -webkit-transition: max-height 1s; 60 | -moz-transition: max-height 1s; 61 | -ms-transition: max-height 1s; 62 | -o-transition: max-height 1s; 63 | transition: max-height 1s; 64 | 65 | } 66 | 67 | .importer { 68 | .button-group { 69 | margin-top: 10px; 70 | margin-bottom: 20px; 71 | } 72 | 73 | input[type="text"] { 74 | margin-top: 10px; 75 | margin-bottom: 20px; 76 | } 77 | } 78 | 79 | 80 | .button-group-label { 81 | display: inline-block; 82 | background-color: white; 83 | color: $gray; 84 | padding-left: 10px; 85 | padding-right: 10px; 86 | padding-top: 5px; 87 | padding-bottom: 5px; 88 | text-align: center; 89 | box-sizing: border-box; 90 | border-top: $border; 91 | border-bottom: $border; 92 | border-left: $border; 93 | cursor: pointer; 94 | // 95 | // border-right: $border; 96 | 97 | &:first-child { 98 | border-top-left-radius: 5px; 99 | border-bottom-left-radius: 5px; 100 | // border-left: $border; 101 | } 102 | 103 | &:last-child { 104 | border-right: $border; 105 | border-top-right-radius: 5px; 106 | border-bottom-right-radius: 5px; 107 | } 108 | 109 | &.selected { 110 | background-color: $gray; 111 | color: white; 112 | } 113 | } 114 | 115 | .title-container { 116 | text-align: center; 117 | color: $gray; 118 | } 119 | 120 | 121 | @-webkit-keyframes colorChange{ 122 | @for $i from 0 through $stops{ 123 | $old-color: adjust-hue($bg-color, (360/$stops)*$i); 124 | $new-color: adjust-hue($bg-color, (360/$stops)*$i + $hue-range); 125 | #{$i}% { 126 | background: -webkit-linear-gradient(left, $old-color, $new-color); 127 | background: linear-gradient(to right, $old-color, $new-color); 128 | } 129 | } 130 | } 131 | 132 | @keyframes colorChange{ 133 | @for $i from 0 through $stops{ 134 | $old-color: adjust-hue($bg-color, (360/$stops)*$i); 135 | $new-color: adjust-hue($bg-color, (360/$stops)*$i + $hue-range); 136 | #{$i}% { 137 | background: linear-gradient(to right, $old-color, $new-color); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /ui/stylesheets/responsive.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | @media (max-width: 1100px) { 4 | .container { 5 | padding-left: 75px !important; 6 | } 7 | } 8 | 9 | 10 | @media (max-width: 430px) { 11 | .container { 12 | padding-left: 75px !important; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ui/stylesheets/sidebar.scss: -------------------------------------------------------------------------------- 1 | 2 | .panel { 3 | position: fixed; 4 | left: -12.625em; /*left or right and the width of your navigation panel*/ 5 | width: 16.625em; /*should match the above value*/ 6 | background-color: #626c7c; 7 | z-index: 10; 8 | 9 | .button-container { 10 | 11 | width: 12.625em; 12 | float: left; 13 | 14 | a { 15 | font-family: 'Open Sans', sans-serif; 16 | 17 | display: block; 18 | // border-bottom: 2px solid #34495D; 19 | padding: 1em; 20 | // background-color: #68a1e5; 21 | color: white; 22 | text-decoration: none; 23 | 24 | &:hover { 25 | color: black; 26 | background-color: #cfd8e0; 27 | } 28 | } 29 | 30 | } 31 | 32 | .side-color { 33 | position: relative; 34 | height: 100%; 35 | /* width: 33%; */ 36 | background: #424b58; 37 | width: 4.0em; 38 | float: right; 39 | text-align: center; 40 | 41 | cursor: pointer; 42 | } 43 | 44 | .logo { 45 | img { 46 | width: 80%; 47 | } 48 | 49 | padding-top: 10px; 50 | } 51 | 52 | } 53 | .wrap { 54 | position: relative; 55 | // left: 3.0em; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /ui/templates/feed-item.jade: -------------------------------------------------------------------------------- 1 | 2 | .feed-item-container(data-model="visualization", data-model-id="#{vid}") 3 | .feed-item 4 | 5 | .description-container 6 | .description 7 | 8 | .description-editor 9 | textarea 10 | 11 | 12 | .bottom-container 13 | .edit-description 14 | a(href="#") 15 | | add description 16 | 17 | 18 | .permalink 19 | ul(data-dropit).menu 20 | li 21 | a(href="#") 22 | | Actions 23 | ul 24 | li 25 | a(href="/visualizations/#{vid}/data") 26 | | Raw Data 27 | li 28 | a(href="/visualizations/#{vid}/screenshot?width=1024&height=768") 29 | | Take Screenshot 30 | li 31 | a(href="/visualizations/#{vid}", data-attribute="permalink") 32 | | Internal Permalink 33 | li 34 | a(href="/visualizations/#{vid}/public") 35 | | Public Permalink 36 | li 37 | a(href="/visualizations/#{vid}/iframe") 38 | | Iframe Link 39 | li 40 | a(href="/visualizations/#{vid}/pym") 41 | | Pym enabled iframe Link 42 | li 43 | a(href="/visualizations/#{vid}/delete", data-confirm="Are you sure you want to delete this visualization?") 44 | | Delete 45 | 46 | hr 47 | --------------------------------------------------------------------------------