├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── app ├── .htaccess ├── 404.html ├── app.html ├── customizer.html ├── images │ ├── cloudbased.png │ ├── csg.png │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ ├── math.png │ ├── programming.png │ ├── thumb.csg copy.png │ ├── thumb.csg.png │ ├── thumb.math copy.png │ ├── thumb.math.png │ ├── thumb.programming copy.png │ ├── thumb.programming.png │ ├── toggle_vis.png │ └── vis_off.png ├── index.html ├── package.json ├── robots.txt ├── scripts │ ├── collections │ │ ├── .DS_Store │ │ ├── Connections.js │ │ ├── Nodes.js │ │ ├── SearchElements.js │ │ ├── WorkspaceBrowserElements.js │ │ └── Workspaces.js │ ├── config.js │ ├── customizer.js │ ├── lib │ │ ├── OrbitControls.js │ │ ├── Viewport.js │ │ └── flood │ │ │ ├── async.js │ │ │ ├── csg.js │ │ │ ├── flood.js │ │ │ ├── flood_csg.js │ │ │ ├── flood_runner.js │ │ │ ├── scheme.js │ │ │ ├── scheme_async.js │ │ │ └── test │ │ │ ├── flood_csg_test.js │ │ │ ├── flood_lambda_test.js │ │ │ ├── flood_runner_test.html │ │ │ ├── flood_test.js │ │ │ ├── scheme_async_test.html │ │ │ └── scheme_eval_async_test.js │ ├── main.js │ ├── models │ │ ├── App.js │ │ ├── Connection.js │ │ ├── Feedback.js │ │ ├── GeometryExport.js │ │ ├── Help.js │ │ ├── Login.js │ │ ├── Marquee.js │ │ ├── Node.js │ │ ├── Runner.js │ │ ├── Search.js │ │ ├── SearchElement.js │ │ ├── Share.js │ │ ├── Workspace.js │ │ ├── WorkspaceBrowser.js │ │ ├── WorkspaceBrowserElement.js │ │ ├── WorkspaceResolver.js │ │ └── customizer │ │ │ └── CustomizerApp.js │ └── views │ │ ├── .DS_Store │ │ ├── AppView.js │ │ ├── ConnectionView.js │ │ ├── FeedbackView.js │ │ ├── HelpView.js │ │ ├── LoginView.js │ │ ├── MarqueeView.js │ │ ├── NodeViews │ │ ├── Base.js │ │ ├── CustomNode.js │ │ ├── Input.js │ │ ├── NodeViews.js │ │ ├── Num.js │ │ ├── Output.js │ │ ├── Script.js │ │ ├── ThreeCSG.js │ │ └── Watch.js │ │ ├── SearchElementView.js │ │ ├── SearchView.js │ │ ├── ShareView.js │ │ ├── WorkspaceBrowserElementView.js │ │ ├── WorkspaceBrowserView.js │ │ ├── WorkspaceControlsView.js │ │ ├── WorkspaceTabView.js │ │ ├── WorkspaceView.js │ │ └── customizer │ │ ├── CustomizerAppView.js │ │ ├── CustomizerHeaderView.js │ │ ├── CustomizerViewerView.js │ │ ├── CustomizerWorkspaceView.js │ │ └── widgets │ │ ├── Base.js │ │ ├── Geometry.js │ │ └── Number.js └── styles │ ├── bootstrap.css │ ├── customizer.css │ └── main.css ├── bower.json ├── extra └── screenshot.png ├── package.json ├── server ├── .gitignore ├── .travis.yml ├── app.js ├── cluster_app.js ├── config │ ├── passport.js │ └── secrets.js ├── controllers │ ├── api.js │ ├── contact.js │ ├── exampleWorkspaces.js │ ├── feedback.js │ ├── flood.js │ ├── home.js │ ├── user.js │ └── workspaces.js ├── models │ ├── Session.js │ ├── User.js │ └── Workspace.js ├── package.json ├── public │ ├── css │ │ ├── lib │ │ │ ├── animate.css │ │ │ ├── bootstrap-social.less │ │ │ ├── bootstrap │ │ │ │ ├── alerts.less │ │ │ │ ├── badges.less │ │ │ │ ├── bootstrap.less │ │ │ │ ├── breadcrumbs.less │ │ │ │ ├── button-groups.less │ │ │ │ ├── buttons.less │ │ │ │ ├── carousel.less │ │ │ │ ├── close.less │ │ │ │ ├── code.less │ │ │ │ ├── component-animations.less │ │ │ │ ├── dropdowns.less │ │ │ │ ├── forms.less │ │ │ │ ├── glyphicons.less │ │ │ │ ├── grid.less │ │ │ │ ├── input-groups.less │ │ │ │ ├── jumbotron.less │ │ │ │ ├── labels.less │ │ │ │ ├── list-group.less │ │ │ │ ├── media.less │ │ │ │ ├── mixins.less │ │ │ │ ├── modals.less │ │ │ │ ├── navbar.less │ │ │ │ ├── navs.less │ │ │ │ ├── normalize.less │ │ │ │ ├── pager.less │ │ │ │ ├── pagination.less │ │ │ │ ├── panels.less │ │ │ │ ├── popovers.less │ │ │ │ ├── print.less │ │ │ │ ├── progress-bars.less │ │ │ │ ├── responsive-utilities.less │ │ │ │ ├── scaffolding.less │ │ │ │ ├── tables.less │ │ │ │ ├── theme.less │ │ │ │ ├── thumbnails.less │ │ │ │ ├── tooltip.less │ │ │ │ ├── type.less │ │ │ │ ├── utilities.less │ │ │ │ ├── variables.less │ │ │ │ └── wells.less │ │ │ └── font-awesome.min.css │ │ ├── styles.less │ │ └── themes │ │ │ ├── default.less │ │ │ ├── flatly.less │ │ │ └── ios7.less │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── img │ │ ├── npm-logo.png │ │ └── paypal.png │ └── js │ │ ├── application.js │ │ ├── lib │ │ ├── bootstrap.min.js │ │ └── jquery-2.1.0.min.js │ │ └── main.js ├── test │ ├── app.js │ ├── mocha.opts │ └── models.js └── views │ ├── 404.jade │ ├── account │ ├── forgot.jade │ ├── login.jade │ ├── profile.jade │ ├── reset.jade │ └── signup.jade │ ├── api │ ├── aviary.jade │ ├── clockwork.jade │ ├── facebook.jade │ ├── foursquare.jade │ ├── github.jade │ ├── index.jade │ ├── lastfm.jade │ ├── linkedin.jade │ ├── nyt.jade │ ├── paypal.jade │ ├── scraping.jade │ ├── steam.jade │ ├── tumblr.jade │ ├── twilio.jade │ ├── twitter.jade │ └── venmo.jade │ ├── contact.jade │ ├── home.jade │ ├── layout.jade │ └── partials │ ├── flash.jade │ ├── footer.jade │ └── navigation.jade ├── test ├── index.html ├── lib │ ├── chai.js │ ├── expect.js │ └── mocha │ │ ├── mocha.css │ │ └── mocha.js └── spec │ └── test.js └── todo.txt /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = false 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp 3 | .sass-cache 4 | app/bower_components 5 | .tmp/ 6 | dist_desktop 7 | .DS_Store 8 | dist 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Peter Boyer 2013 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Image](https://raw.github.com/pboyer/flood/master/extra/screenshot.png) 2 | 3 | 4 | ## flood 5 | 6 | ### What is it? 7 | 8 | flood is a [dataflow](http://en.wikipedia.org/wiki/Dataflow_programming)-style visual programming language based on Scheme written in JavaScript. flood runs in a browser and as a standalone application on all platforms via [node-webkit](https://github.com/rogerwang/node-webkit). 9 | 10 | ### Features 11 | 12 | * Constructive solid geometry - Cube, cylinder, sphere, union, intersect, subtract 13 | * Formula node - evaluate javascript in a node 14 | * Never save your work 15 | * Fast node library search 16 | * Undo/redo across user sessions 17 | * Copy/paste 18 | * Multiple workspaces 19 | * Async evaluation 20 | * Partial function application 21 | * "Always on" continuous execution 22 | 23 | flood, like early versions of [Dynamo](http://github.com/ikeough/Dynamo), is based on Scheme and thus has many of the features of that language. It uses a [lightweight scheme interpreter](http://github.com/pboyer/scheme.js) I wrote called scheme.js. 24 | 25 | ### Getting started 26 | 27 | The flood app is scaffolded with [Yeoman](http://yeoman.io/), uses [Grunt](http://gruntjs.com/) for task management and [Bower](http://bower.io/) for web package management. If you're not familiar with these tools, you should take a look at the docs and get them installed. 28 | 29 | flood uses [require.js](http://requirejs.org/) to manage dependencies between JavaScript files and [backbone.js](http://backbonejs.org/) to stick it all together. 30 | 31 | flood also has a server written in node.js that handles user authentication and model synchronization. 32 | 33 | #### Installing dependencies for the app 34 | 35 | To install all of the dependencies for the flood app, run the following commands in the root directory: 36 | 37 | bower install 38 | npm install 39 | 40 | This will install all of the development dependencies for Grunt and all of the public dependencies with bower. 41 | 42 | #### Installing dependencies for the server 43 | 44 | To install all of the node.js dependencies for the flood server, run the following commands in the "server" directory: 45 | 46 | npm install 47 | 48 | You will also need to install MongoDB and run an instance on port 27017, the default port for MongoDB. You can get MongoDB [here](http://www.mongodb.org/downloads). 49 | 50 | 51 | #### Running the server 52 | 53 | For development, I recommend using the great nodemon tool: 54 | 55 | npm install -g nodemon 56 | 57 | Go to the "server" directory and run: 58 | 59 | nodemon app.js 60 | 61 | You can also run the server using: 62 | 63 | node app.js 64 | 65 | 66 | #### Building for the web (outdated) 67 | 68 | The entire app can be compressed into lightweight, minified, and concatenated css, js, and html files using Grunt: 69 | 70 | grunt 71 | 72 | 73 | #### Building for the desktop 74 | 75 | flood can be used as a standalone application via node-webkit. Just do this: 76 | 77 | grunt desktop 78 | 79 | This will generate binaries for use on Mac and Windows in the dist_desktop folder. 80 | 81 | 82 | ### License 83 | 84 | The MIT License (MIT) 85 | 86 | Copyright (c) Peter Boyer 2014-2020 87 | 88 | Permission is hereby granted, free of charge, to any person obtaining a copy 89 | of this software and associated documentation files (the "Software"), to deal 90 | in the Software without restriction, including without limitation the rights 91 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 92 | copies of the Software, and to permit persons to whom the Software is 93 | furnished to do so, subject to the following conditions: 94 | 95 | The above copyright notice and this permission notice shall be included in 96 | all copies or substantial portions of the Software. 97 | 98 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 99 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 100 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 101 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 102 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 103 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 104 | THE SOFTWARE. 105 | 106 | -------------------------------------------------------------------------------- /app/images/cloudbased.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/cloudbased.png -------------------------------------------------------------------------------- /app/images/csg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/csg.png -------------------------------------------------------------------------------- /app/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /app/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /app/images/math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/math.png -------------------------------------------------------------------------------- /app/images/programming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/programming.png -------------------------------------------------------------------------------- /app/images/thumb.csg copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/thumb.csg copy.png -------------------------------------------------------------------------------- /app/images/thumb.csg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/thumb.csg.png -------------------------------------------------------------------------------- /app/images/thumb.math copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/thumb.math copy.png -------------------------------------------------------------------------------- /app/images/thumb.math.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/thumb.math.png -------------------------------------------------------------------------------- /app/images/thumb.programming copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/thumb.programming copy.png -------------------------------------------------------------------------------- /app/images/thumb.programming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/thumb.programming.png -------------------------------------------------------------------------------- /app/images/toggle_vis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/toggle_vis.png -------------------------------------------------------------------------------- /app/images/vis_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/images/vis_off.png -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flood", 3 | "version": "0.0.4", 4 | "description": "A visual programming language for JavaScript, based on Scheme", 5 | "main": "index.html", 6 | "window": { 7 | "toolbar": false, 8 | "frame": true, 9 | "width": 1280, 10 | "height": 960 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /app/scripts/collections/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/scripts/collections/.DS_Store -------------------------------------------------------------------------------- /app/scripts/collections/Connections.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'Connection'], function(Backbone, Connection) { 2 | 3 | return Backbone.Collection.extend({ 4 | 5 | model: Connection 6 | 7 | }); 8 | 9 | }); -------------------------------------------------------------------------------- /app/scripts/collections/Nodes.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'Node'], function(Backbone, Node) { 2 | 3 | return Backbone.Collection.extend({ 4 | 5 | model: Node, 6 | 7 | initialDragPositions: [], 8 | 9 | toJSON: function(){ 10 | 11 | return this.models.map(function(x){ 12 | return x.serialize(); 13 | }); 14 | 15 | }, 16 | 17 | selectAll: function() { 18 | this.where({selected: false}).forEach( function(e){ e.set({selected: true}) } ); 19 | }, 20 | 21 | deselectAll: function() { 22 | this.where({selected: true}).forEach( function(e){ 23 | e.set('selected', false); 24 | }); 25 | }, 26 | 27 | moveSelected: function(offset, masterEle) { 28 | var that = this; 29 | var count = 0; 30 | this.where({selected: true}).forEach( function(e, i){ 31 | if (e === masterEle) return; 32 | var initialPos = that.initialDragPositions[count++]; 33 | e.set('position', [initialPos[0] + offset[0], initialPos[1] + offset[1]]); 34 | }); 35 | }, 36 | 37 | startDragging: function(masterEle){ 38 | this.initialDragPositions = []; 39 | var that = this; 40 | this.where({selected: true}).forEach( function(e){ 41 | if (e === masterEle) return; 42 | that.initialDragPositions.push(e.get('position')); 43 | }); 44 | } 45 | 46 | }); 47 | 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /app/scripts/collections/SearchElements.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'SearchElement', 'FLOOD'], function(Backbone, SearchElement, FLOOD) { 2 | 3 | return Backbone.Collection.extend({ 4 | 5 | model: SearchElement, 6 | 7 | initialize: function(atts) { 8 | this.app = atts.app; 9 | }, 10 | 11 | addCustomNode: function(customNode){ 12 | 13 | var match = this.where({ functionId: customNode.functionId }); 14 | if (match) this.remove( match ); 15 | 16 | this.add(new SearchElement({name: customNode.functionName, functionName: customNode.functionName, 17 | isCustomNode: true, functionId: customNode.functionId, app: this.app, numInputs: customNode.inputs.length, 18 | numOutputs: customNode.outputs.length })); 19 | 20 | }, 21 | 22 | fetch: function() { 23 | 24 | this.models.length = 0; 25 | 26 | for (var key in FLOOD.nodeTypes){ 27 | this.models.push( new SearchElement({name: key, app: this.app}) ); 28 | } 29 | 30 | } 31 | 32 | }); 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /app/scripts/collections/WorkspaceBrowserElements.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'WorkspaceBrowserElement'], function(Backbone, WorkspaceBrowserElement) { 2 | 3 | return Backbone.Collection.extend({ 4 | 5 | url: '/ws', 6 | model: WorkspaceBrowserElement 7 | 8 | }); 9 | 10 | }); -------------------------------------------------------------------------------- /app/scripts/collections/Workspaces.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'Workspace'], function(Backbone, Workspace) { 2 | 3 | return Backbone.Collection.extend({ 4 | 5 | model: Workspace 6 | 7 | }); 8 | 9 | }); 10 | -------------------------------------------------------------------------------- /app/scripts/customizer.js: -------------------------------------------------------------------------------- 1 | require(["config"], function() { 2 | 3 | require(['backbone', 'CustomizerApp', 'CustomizerAppView', 'Three', 'FLOODCSG', 'bootstrap' ], function (Backbone, CustomizerApp, CustomizerAppView, THREE) { 4 | 5 | var app = new CustomizerApp(); 6 | 7 | app.fetch({ 8 | error: function(result) { 9 | console.error('error'); 10 | console.error(result); 11 | }, 12 | success: function(){ 13 | } 14 | }); 15 | 16 | var appView = new CustomizerAppView({model: app}); 17 | 18 | }); 19 | 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /app/scripts/lib/Viewport.js: -------------------------------------------------------------------------------- 1 | var container, $container; 2 | 3 | var camera, controls, scene, renderer; 4 | 5 | var geometry, group; 6 | 7 | var mouse = new THREE.Vector2(), 8 | offset = new THREE.Vector3(), 9 | INTERSECTED, SELECTED; 10 | 11 | var objects = [], plane; 12 | 13 | var mouseX = 0, mouseY = 0; 14 | 15 | var windowHalfX = window.innerWidth / 2; 16 | var windowHalfY = window.innerHeight / 2; 17 | 18 | init(); 19 | render(); 20 | 21 | function init() { 22 | 23 | container = document.getElementById("viewer"); 24 | $container = $(container); 25 | 26 | camera = new THREE.PerspectiveCamera( 30, $container.width() / $container.height(), 1, 10000 ); 27 | 28 | camera.position.set( 140, 140, 140 ); 29 | camera.up.set( 0, 0, 1 ); 30 | camera.lookAt( new THREE.Vector3(0,0,0) ); 31 | 32 | scene = new THREE.Scene(); 33 | 34 | renderer = new THREE.WebGLRenderer({antialias: true}); 35 | renderer.setClearColor( 0xffffff, 1 ); 36 | renderer.setSize( $container.width(), $container.height() ); 37 | renderer.sortObjects = false; 38 | 39 | container.appendChild( renderer.domElement ); 40 | renderer.domElement.setAttribute("id", "renderer_canvas"); 41 | 42 | // add subtle ambient lighting 43 | var ambientLight = new THREE.AmbientLight(0x555555); 44 | scene.add(ambientLight); 45 | 46 | // add directional light source 47 | var directionalLight = new THREE.DirectionalLight(0xbbbbbb); 48 | directionalLight.position.set(50, 30, 50); 49 | scene.add(directionalLight); 50 | 51 | var directionalLight = new THREE.DirectionalLight(0xaaaaaa); 52 | directionalLight.position.set(-0.2, -0.8, 1).normalize(); 53 | scene.add(directionalLight); 54 | 55 | makeGrid(); 56 | 57 | controls = new THREE.OrbitControls(camera, container); 58 | 59 | window.addEventListener( 'resize', onWindowResize, false ); 60 | 61 | // full screen blur 62 | // composer = new THREE.EffectComposer( renderer ); 63 | // composer.addPass( new THREE.RenderPass( scene, camera ) ); 64 | 65 | // hblur = new THREE.ShaderPass( THREE.HorizontalBlurShader ); 66 | // composer.addPass( hblur ); 67 | 68 | // vblur = new THREE.ShaderPass( THREE.VerticalBlurShader ); 69 | // // set this shader pass to render to screen so we can see the effects 70 | // vblur.renderToScreen = true; 71 | // composer.addPass( vblur ); 72 | 73 | 74 | animate(); 75 | 76 | } 77 | 78 | function makeGrid(){ 79 | 80 | var l = 60; 81 | 82 | var axisHelper = new THREE.AxisHelper( l ); 83 | scene.add( axisHelper ); 84 | 85 | var geometry = new THREE.Geometry(); 86 | var geometryThick = new THREE.Geometry(); 87 | 88 | var n = l; 89 | var inc = 2 * l / n; 90 | var rate = 10; 91 | 92 | for (var i = 0; i < n + 1; i++){ 93 | 94 | var v1 = new THREE.Vector3(-l, -l + i * inc, 0); 95 | var v2 = new THREE.Vector3(l, -l + i * inc, 0); 96 | 97 | geometry.vertices.push(v1); 98 | geometry.vertices.push(v2); 99 | 100 | if (i % rate == 0){ 101 | geometryThick.vertices.push(v1); 102 | geometryThick.vertices.push(v2); 103 | } 104 | } 105 | 106 | for (var i = 0; i < n + 1; i++){ 107 | var v1 = new THREE.Vector3(-l + i * inc, l, 0); 108 | var v2 = new THREE.Vector3(-l + i * inc, -l, 0); 109 | 110 | geometry.vertices.push(v1); 111 | geometry.vertices.push(v2); 112 | 113 | if (i % rate == 0){ 114 | geometryThick.vertices.push(v1); 115 | geometryThick.vertices.push(v2); 116 | } 117 | } 118 | 119 | var material = new THREE.LineBasicMaterial({ 120 | color: 0xeeeeee, 121 | linewidth: 0.1 122 | }); 123 | 124 | var materialThick = new THREE.LineBasicMaterial({ 125 | color: 0xeeeeee, 126 | linewidth: 2 127 | }); 128 | 129 | var line = new THREE.Line(geometry, material, THREE.LinePieces); 130 | var lineThick = new THREE.Line(geometryThick, materialThick, THREE.LinePieces); 131 | 132 | scene.add(line); 133 | scene.add(lineThick); 134 | 135 | } 136 | 137 | function onWindowResize() { 138 | 139 | windowHalfX = $container.width() / 2; 140 | windowHalfY = $container.height() / 2; 141 | 142 | camera.aspect = windowHalfX/ windowHalfY; 143 | camera.updateProjectionMatrix(); 144 | 145 | renderer.setSize( 2*windowHalfX, 2*windowHalfY ); 146 | 147 | render(); 148 | 149 | } 150 | 151 | function animate() { 152 | 153 | requestAnimationFrame( animate ); 154 | render(); 155 | 156 | } 157 | 158 | var doBlur = true; 159 | 160 | function render() { 161 | 162 | controls.update(); 163 | renderer.render( scene, camera ); 164 | 165 | if (doBlur){ 166 | // composer.render(); 167 | } 168 | 169 | } 170 | 171 | 172 | -------------------------------------------------------------------------------- /app/scripts/lib/flood/test/flood_lambda_test.js: -------------------------------------------------------------------------------- 1 | var FLOOD = new require('../flood.js') 2 | , assert = require('assert') 3 | , scheme = require('../scheme.js'); 4 | 5 | 6 | (function(scheme, FLOOD) { 7 | 8 | var nodes = []; 9 | 10 | var input0 = new FLOOD.nodeTypes.Input("A"); 11 | var input1 = new FLOOD.nodeTypes.Input("B"); 12 | 13 | nodes.push(input0); 14 | nodes.push(input1); 15 | 16 | var add = new FLOOD.nodeTypes.Add(); 17 | nodes.push(add); 18 | 19 | add.inputs[0].connect( input0 ); 20 | add.inputs[1].connect( input1 ); 21 | 22 | var mult = new FLOOD.nodeTypes.Multiply(); 23 | nodes.push(mult); 24 | 25 | mult.inputs[0].connect( input0 ); 26 | mult.inputs[1].connect( input1 ); 27 | 28 | var output = new FLOOD.nodeTypes.Output("A"); 29 | var output1 = new FLOOD.nodeTypes.Output("B"); 30 | nodes.push(output); 31 | nodes.push(output1); 32 | 33 | // compile this into a function 34 | output.inputs[0].connect( add ); 35 | output1.inputs[0].connect( mult ); 36 | 37 | var lambda = FLOOD.compileNodesToLambda( nodes ); 38 | 39 | var S = new scheme.Interpreter(); 40 | 41 | var eres = S.eval( [ lambda, 5, 7 ] ); 42 | 43 | assert.equal( eres[0], 12 ); 44 | assert.equal( eres[1], 35 ); 45 | 46 | })(scheme, FLOOD); -------------------------------------------------------------------------------- /app/scripts/lib/flood/test/flood_runner_test.html: -------------------------------------------------------------------------------- 1 | 30 | 31 | -------------------------------------------------------------------------------- /app/scripts/lib/flood/test/scheme_async_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 151 | 152 | -------------------------------------------------------------------------------- /app/scripts/lib/flood/test/scheme_eval_async_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , async = require('../async.js') 3 | , scheme = require('../scheme.js'); 4 | 5 | 6 | (function(scheme) { 7 | 8 | var S = new scheme.Interpreter(); 9 | 10 | S.eval_async( ["begin", 1, 2, 4], undefined, function(res){ 11 | console.log(res); 12 | }); 13 | 14 | S.eval_async( [function(x){ return x * 3; }, [ function(){ return 2; } ] ], undefined, function(res){ 15 | console.log(res); 16 | }); 17 | 18 | S.eval_async( [ "quote", "cool yo" ], undefined, function(res){ 19 | console.log(res); 20 | }); 21 | 22 | // allows interpreter execution to not block! 23 | 24 | })(scheme); -------------------------------------------------------------------------------- /app/scripts/main.js: -------------------------------------------------------------------------------- 1 | require(["config"], function() { 2 | 3 | require(['backbone', 'App', 'AppView', 'Three', 'Viewport', 'FLOODCSG', 'bootstrap'], function (Backbone, App, AppView) { 4 | 5 | var app = new App(); 6 | 7 | app.fetch({ 8 | error: function(result) { 9 | console.error('error'); 10 | console.error(result); 11 | }, 12 | success: function(){ 13 | app.enableAutosave(); 14 | } 15 | }); 16 | 17 | var appView = new AppView({model: app}); 18 | 19 | }); 20 | 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /app/scripts/models/Connection.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | idAttribute: "_id", 6 | 7 | defaults: { 8 | startNodeId: 0 9 | , endNodeId: 0 10 | , startPortIndex: 0 11 | , endPortIndex: 0 12 | , startProxy: false 13 | , endProxy: false 14 | , startProxyPosition: [0,0] 15 | , endProxyPosition: [0,0] 16 | , hidden: false 17 | }, 18 | 19 | workspace : null, 20 | startNode: null, 21 | endNode: null, 22 | 23 | initialize: function(args, options){ 24 | 25 | this.workspace = options.workspace; 26 | 27 | // proxyconnections bind to the proxyMove event on the workspace 28 | if ( args.startProxy || args.endProxy ) { 29 | this.workspace.bind('proxyMove', this.proxyMove, this); 30 | } else { 31 | 32 | // bind to end nodes 33 | this.startNode = this.workspace.get('nodes').get(args.startNodeId); 34 | this.endNode = this.workspace.get('nodes').get(args.endNodeId); 35 | } 36 | 37 | }, 38 | 39 | getOpposite: function(startNode){ 40 | 41 | if ( startNode === this.startNode ) 42 | { 43 | return {node: this.endNode, portIndex: this.get('endPortIndex')}; 44 | } 45 | 46 | if ( startNode === this.endNode ) 47 | { 48 | return {node: this.startNode, portIndex: this.get('startPortIndex')}; 49 | } 50 | 51 | return {}; 52 | 53 | } 54 | 55 | }); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /app/scripts/models/Feedback.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | showing: false, 7 | failure: false, 8 | failureMessage: "Unidentified error" 9 | }, 10 | 11 | send: function(data){ 12 | 13 | if ( !data.subject ){ 14 | this.set('failureMessage', 'Please supply a subject for your feedback!'); 15 | this.set('failure', true); 16 | return; 17 | } 18 | 19 | var that = this; 20 | $.post('/feedback', data, function(e){ 21 | 22 | if (e.length && e.length > 0 ) { 23 | that.set('failureMessage', e[0].msg); 24 | return that.set('failure', true); 25 | } 26 | 27 | that.set('failure', false); 28 | that.trigger('success'); 29 | }); 30 | 31 | } 32 | 33 | }); 34 | 35 | }); 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/scripts/models/GeometryExport.js: -------------------------------------------------------------------------------- 1 | define(['FileSaver'], function(FileSaver) { 2 | 3 | /// Adapted from: https://github.com/stephomi/sculptgl/blob/master/src/misc/ExportSTL.js 4 | 5 | 'use strict'; 6 | 7 | var Export = {}; 8 | var Utils = {}; 9 | 10 | /** Export STL file */ 11 | Export.toSTL = function (scene, filename) { 12 | 13 | // merge all Geometry in three.js scene into one big bag of triangles 14 | var numTris = 0; 15 | var vertices = []; 16 | var faces = []; 17 | var faceNormals = []; 18 | var vertOffset = 0; 19 | 20 | scene.traverse(function(ele) { 21 | 22 | if (!ele.visible || !(ele instanceof THREE.Mesh) ) return; 23 | 24 | // collect vertices 25 | ele.geometry.vertices.forEach(function(v){ 26 | vertices.push( v.x ); 27 | vertices.push( v.y ); 28 | vertices.push( v.z ); 29 | }); 30 | 31 | // collect faces, face normals 32 | ele.geometry.faces.forEach(function(face){ 33 | faces.push(vertOffset + face.a); 34 | faces.push(vertOffset + face.b); 35 | faces.push(vertOffset + face.c); 36 | 37 | faceNormals.push( face.normal.x ); 38 | faceNormals.push( face.normal.y ); 39 | faceNormals.push( face.normal.z ); 40 | 41 | numTris += 1; 42 | }); 43 | 44 | vertOffset += ele.geometry.vertices.length; 45 | 46 | }); 47 | 48 | var blob = Export.toAsciiSTL(vertices, faces, faceNormals, numTris ); 49 | 50 | FileSaver( blob, filename ); 51 | 52 | }; 53 | 54 | Utils.normalizeArrayVec3 = function (array, out) { 55 | var arrayOut = out || array; 56 | for (var i = 0, l = array.length; i < l; ++i) { 57 | var j = i * 3; 58 | var nx = array[j]; 59 | var ny = array[j + 1]; 60 | var nz = array[j + 2]; 61 | var len = 1.0 / Math.sqrt(nx * nx + ny * ny + nz * nz); 62 | arrayOut[j] = nx * len; 63 | arrayOut[j + 1] = ny * len; 64 | arrayOut[j + 2] = nz * len; 65 | } 66 | return arrayOut; 67 | }; 68 | 69 | /** Return a buffer array which is at least nbBytes long */ 70 | Utils.getMemory = (function () { 71 | var pool = new ArrayBuffer(100000); 72 | return function (nbBytes) { 73 | if (pool.byteLength >= nbBytes) 74 | return pool; 75 | pool = new ArrayBuffer(nbBytes); 76 | return pool; 77 | }; 78 | })(); 79 | 80 | /** Export Ascii STL file */ 81 | Export.toAsciiSTL = function (vAr, iAr, origFN, nbTriangles) { 82 | 83 | var faceNormals = new Float32Array(Utils.getMemory(origFN.length * 4), 0, origFN.length); 84 | Utils.normalizeArrayVec3(origFN, faceNormals); 85 | var data = 'solid mesh\n'; 86 | 87 | for (var i = 0; i < nbTriangles; ++i) { 88 | var j = i * 3; 89 | data += ' facet normal ' + faceNormals[j] + ' ' + faceNormals[j + 1] + ' ' + faceNormals[j + 2] + '\n'; 90 | data += ' outer loop\n'; 91 | var iv1 = iAr[j] * 3; 92 | var iv2 = iAr[j + 1] * 3; 93 | var iv3 = iAr[j + 2] * 3; 94 | data += ' vertex ' + vAr[iv1] + ' ' + vAr[iv1 + 1] + ' ' + vAr[iv1 + 2] + '\n'; 95 | data += ' vertex ' + vAr[iv2] + ' ' + vAr[iv2 + 1] + ' ' + vAr[iv2 + 2] + '\n'; 96 | data += ' vertex ' + vAr[iv3] + ' ' + vAr[iv3 + 1] + ' ' + vAr[iv3 + 2] + '\n'; 97 | data += ' endloop\n'; 98 | data += ' endfacet\n'; 99 | } 100 | data += 'endsolid mesh\n'; 101 | 102 | return new Blob([data]); 103 | }; 104 | 105 | return Export; 106 | 107 | }); -------------------------------------------------------------------------------- /app/scripts/models/Help.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | sections: 7 | [ { title: "Workspace Tabs", 8 | targetId : "add-workspace-button", 9 | offset: [5, 5], 10 | text: "Switch between or add new active workspaces" 11 | }, 12 | { title: "Node Library", 13 | targetId : "bottom-search", 14 | offset: [5, -110], 15 | text: "This is one place where you can add new nodes. You can also double click on the workspace to add nodes." 16 | }, 17 | { title: "3D Focus Control", 18 | targetId : "workspace_hide", 19 | offset: [-170,-100], 20 | text: "Toggle between the 3D view and your workspace" 21 | }, 22 | { title: "Workspace Browser", 23 | targetId : "workspace-browser-button", 24 | offset: [-170,0], 25 | text: "You can find all of your past work in the Workspace Browser" 26 | }, 27 | { title: "Export STL", 28 | targetId : "export-button", 29 | offset: [20,-110], 30 | text: "Export all visible geometry to STL for 3D printing" 31 | }, 32 | { title: "Share a customizer", 33 | targetId : "share-button", 34 | offset: [-200,10], 35 | text: "Share a customizer with anyone on the internet!" 36 | }, 37 | // { title: "Zoom", 38 | // targetId : "zoomreset-button", 39 | // offset: [20,-110], 40 | // text: "Use this control the workspace zoom, or use Ctrl +, Ctrl -" 41 | // } 42 | ] 43 | } 44 | 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /app/scripts/models/Login.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | isLoggedIn : false, 7 | failed : false, 8 | failureMessage : "", 9 | showing: false, 10 | email : "" 11 | }, 12 | 13 | initialize: function(atts, vals) { 14 | this.app = vals.app; 15 | }, 16 | 17 | fetch : function(){ 18 | 19 | var that = this; 20 | $.get('/email', function(e){ 21 | if (e.email) { 22 | that.set('email', e.email); 23 | that.set('isLoggedIn', true); 24 | } else { 25 | that.set('isLoggedIn', false); 26 | } 27 | }); 28 | 29 | }, 30 | 31 | toggle: function(){ 32 | return this.get('showing') ? this.hide() : this.show(); 33 | }, 34 | 35 | show: function() { 36 | this.set('showing', true); 37 | }, 38 | 39 | hide: function() { 40 | this.set('showing', false); 41 | }, 42 | 43 | signup: function(data){ 44 | 45 | var that = this; 46 | $.post('/signup', data, function(e){ 47 | 48 | if (e.length && e.length > 0 ) { 49 | that.set('failureMessage', e[0].msg); 50 | return that.set('failed', true); 51 | } 52 | 53 | that.set('failed', false); 54 | that.app.fetch(); 55 | }); 56 | 57 | }, 58 | 59 | login: function(data){ 60 | 61 | var that = this; 62 | $.post('/login', data, function(e){ 63 | 64 | if (e.length && e.length > 0 ) { 65 | that.set('failureMessage', e[0].msg); 66 | return that.set('failed', true); 67 | } 68 | 69 | that.set('failed', false); 70 | that.app.fetch(); 71 | }); 72 | 73 | }, 74 | 75 | logout: function(){ 76 | 77 | var that = this; 78 | $.get('/logout', {}, function(e){ 79 | that.app.fetch(); 80 | }); 81 | 82 | } 83 | 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /app/scripts/models/Marquee.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | idAttribute: "_id", 6 | 7 | defaults: { 8 | x: 10 9 | , y: 10 10 | , width: 50 11 | , height: 50 12 | , hidden: true 13 | }, 14 | 15 | startCorner: [10,10], 16 | 17 | endCorner: [20, 20], 18 | 19 | initialize: function(args, options){ 20 | 21 | this.workspace = options.workspace; 22 | 23 | }, 24 | 25 | updateRawValues: function(){ 26 | 27 | this.set('width', Math.abs( this.startCorner[0] - this.endCorner[0] ) ); 28 | this.set('height', Math.abs( this.startCorner[1] - this.endCorner[1] ) ); 29 | this.set('x', Math.min( this.startCorner[0], this.endCorner[0] ) ); 30 | this.set('y', Math.min( this.startCorner[1], this.endCorner[1] ) ); 31 | 32 | }, 33 | 34 | setStartCorner: function( posInWorkspace ){ 35 | 36 | this.startCorner = posInWorkspace; 37 | this.endCorner = posInWorkspace; 38 | 39 | this.updateRawValues(); 40 | 41 | }, 42 | 43 | setEndCorner: function( posInWorkspace ){ 44 | 45 | this.endCorner = posInWorkspace; 46 | 47 | this.updateRawValues(); 48 | 49 | } 50 | 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /app/scripts/models/Search.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | 7 | }, 8 | 9 | initialize: function(atts, vals) { 10 | 11 | } 12 | 13 | }); 14 | 15 | }); 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/scripts/models/SearchElement.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | name: null, 7 | isCustomNode: false, 8 | functionId: -1 9 | }, 10 | 11 | initialize: function(a, b) { 12 | this.app = a.app; 13 | } 14 | 15 | }); 16 | 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/scripts/models/Share.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | isProjectWorkspace: false, 7 | }, 8 | 9 | initialize: function(atts, vals) { 10 | 11 | this.app = atts.app; 12 | 13 | // this.app.on('change:currentWorkspace', function(x){ 14 | // console.log( this.app.getCurrentWorkspace() ); 15 | // }, this); 16 | 17 | }, 18 | 19 | }); 20 | 21 | }); -------------------------------------------------------------------------------- /app/scripts/models/WorkspaceBrowser.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'WorkspaceBrowserElements'], function(Backbone, WorkspaceBrowserElements) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | defaults: { 6 | workspaces: new WorkspaceBrowserElements() 7 | }, 8 | 9 | initialize: function(atts, vals) { 10 | this.get('workspaces').fetch(); 11 | }, 12 | 13 | refresh: function(){ 14 | this.get('workspaces').reset(); 15 | this.get('workspaces').fetch(); 16 | } 17 | 18 | }); 19 | }); -------------------------------------------------------------------------------- /app/scripts/models/WorkspaceBrowserElement.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.Model.extend({ 4 | 5 | idAttribute: "_id", 6 | 7 | defaults: { 8 | name: "Unnamed Workspace", 9 | isPublic: false, 10 | isCustomNode: false, 11 | lastSaved: Date.now() 12 | }, 13 | 14 | initialize: function(atts, vals) { 15 | 16 | } 17 | 18 | }); 19 | }); -------------------------------------------------------------------------------- /app/scripts/models/customizer/CustomizerApp.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'App'], 2 | function(Backbone, App){ 3 | 4 | return App.extend({ 5 | 6 | url: function() { 7 | 8 | // get the url from the page 9 | // customize this url 10 | 11 | var comps = document.URL.split('/customize-'); 12 | return '/custdata/' + comps[ comps.length - 1]; 13 | }, 14 | 15 | parse : function(resp) { 16 | 17 | resp._id = 1; 18 | 19 | this.get('workspaces').add(resp, {app: this}); 20 | 21 | var modresp = {}; 22 | 23 | modresp.name = resp.name; 24 | modresp.currentWorkspace = 1; 25 | 26 | return modresp; 27 | 28 | }, 29 | 30 | fetch : function(options){ 31 | 32 | // this.login.fetch(); 33 | Backbone.Model.prototype.fetch.call(this, options); 34 | 35 | }, 36 | 37 | enableAutosave: function(){ 38 | 39 | // this.get('workspaces').on('add remove', function(){ this.sync("update", this); }, this ); 40 | // this.on('change:currentWorkspace', function(){ this.sync("update", this); }, this); 41 | // this.on('change:isFirstExperience', function(){ this.sync("update", this); }, this); 42 | // this.on('change:backgroundWorkspaces', function(){ this.sync("update", this); }, this); 43 | 44 | } 45 | 46 | }); 47 | 48 | }) 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/scripts/views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/app/scripts/views/.DS_Store -------------------------------------------------------------------------------- /app/scripts/views/ConnectionView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | template: _.template( $('#connection-template').html() ), 6 | 7 | workspaceView : null, 8 | 9 | initialize: function( args ) { 10 | 11 | this.model = args.model; 12 | 13 | if (args.isProxy ){ 14 | this.isProxy = true; 15 | } 16 | 17 | this.workspace = args.workspace; 18 | this.workspaceView = args.workspaceView; 19 | 20 | if (this.model.startNode){ 21 | this.model.startNode.on('change:ignoreDefaults', this.render, this); 22 | } 23 | 24 | }, 25 | 26 | delegateEvents: function() { 27 | 28 | Backbone.View.prototype.delegateEvents.apply(this, arguments); 29 | 30 | if (!this.isProxy ){ 31 | this.startId = this.model.get('startNodeId'); 32 | this.endId = this.model.get('endNodeId'); 33 | var nodes = this.workspace.get('nodes'); 34 | 35 | if (nodes.get(this.endId) != undefined ){ 36 | this.listenTo( nodes.get(this.endId), 'change:position resized', this.render); 37 | } 38 | 39 | if (nodes.get(this.startId) != undefined ){ 40 | this.listenTo( nodes.get(this.startId), 'change:position resized', this.render); 41 | } 42 | } 43 | 44 | this.listenTo(this.model, 'change', this.render); 45 | 46 | }, 47 | 48 | render: function() { 49 | 50 | this.makeCurveOnce(); 51 | 52 | return this.updateControlPoints() 53 | .updateHidden() 54 | .updateColor(); 55 | 56 | }, 57 | 58 | updateControlPoints: function(){ 59 | this.el.setAttribute('d', this.template( this.getControlPoints() )) 60 | return this; 61 | }, 62 | 63 | updateHidden: function(){ 64 | 65 | if (this.model.get('hidden')) { 66 | this.el.setAttribute('class','connection collapsed'); 67 | } else { 68 | this.el.setAttribute('class','connection'); 69 | } 70 | 71 | return this; 72 | 73 | }, 74 | 75 | updateColor: function(){ 76 | 77 | var startNode = this.model.startNode; 78 | 79 | if (!startNode) return this; 80 | 81 | if (startNode.isPartialFunctionApplication()){ 82 | this.el.setAttribute('class','partial-function-connection'); 83 | } else { 84 | this.el.setAttribute('class','connection'); 85 | } 86 | 87 | return this; 88 | }, 89 | 90 | curveInit: false, 91 | 92 | makeCurveOnce: function() { 93 | 94 | if (!this.curveInit) { 95 | var crv = document.createElementNS('http://www.w3.org/2000/svg','path'); 96 | crv.setAttribute('class','connection'); 97 | this.el = crv; 98 | this.$el = $(crv); 99 | this.curveInit = true; 100 | } 101 | return this.el; 102 | 103 | }, 104 | 105 | // construct the control points for a bezier curve 106 | getControlPoints: function() { 107 | 108 | if (!this.model.get('startProxy') || !this.model.get('endProxy')) { 109 | 110 | var nodeViews = this.workspaceView.nodeViews 111 | , startId = this.model.get('startNodeId') 112 | , endId = this.model.get('endNodeId') 113 | , startPortIndex = this.model.get('startPortIndex') 114 | , endPortIndex = this.model.get('endPortIndex') 115 | } 116 | 117 | if (!this.model.get('startProxy')) { 118 | if (!this.workspaceView.nodeViews[startId] ){ 119 | startId = this.model.workspace.get('proxyStartId'); 120 | startPortIndex = this.model.workspace.get('proxyStartPortIndex'); 121 | } 122 | 123 | startPos = this.workspaceView.nodeViews[startId].getPortPosition(startPortIndex, true); 124 | } else { 125 | startPos = this.model.get('startProxyPosition'); 126 | } 127 | 128 | if (!this.model.get('endProxy')) { 129 | endPos = nodeViews[endId].getPortPosition(endPortIndex, false); 130 | } else { 131 | endPos = this.model.get('endProxyPosition'); 132 | } 133 | 134 | var offset = 0.65 * Math.sqrt( Math.pow( endPos[0]-startPos[0], 2 ) + Math.pow( startPos[1]-endPos[1], 2 ) ); 135 | 136 | return { 137 | aX : startPos[0] 138 | , aY : startPos[1] 139 | , bX : startPos[0] + offset 140 | , bY : startPos[1] 141 | , dX : endPos[0] 142 | , dY : endPos[1] 143 | , cX : endPos[0] - offset 144 | , cY : endPos[1] 145 | }; 146 | } 147 | 148 | }); 149 | 150 | }); 151 | -------------------------------------------------------------------------------- /app/scripts/views/FeedbackView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | 'use strict'; 4 | 5 | return Backbone.View.extend({ 6 | 7 | el: '#feedback', 8 | 9 | events: { 'click #exit-feedback' : 'clickExit', 10 | 'submit #submit-feedback' : 'clickSend', 11 | 'click #submit-feedback' : 'clickSend', 12 | 'click' : 'clickExit', 13 | 'click .modal-box': 'stopPropagation' 14 | }, 15 | 16 | template: _.template( $('#feedback-template').html() ), 17 | 18 | initialize: function( args, atts ) { 19 | this.app = atts.app; 20 | 21 | this.model.on('change:failure', this.fail, this ); 22 | this.model.on('success', this.success, this ); 23 | }, 24 | 25 | render: function() { 26 | 27 | this.$el.html( this.template( this.model.toJSON() ) ); 28 | 29 | this.subject = this.$el.find('#feedback-subject'); 30 | this.message = this.$el.find('#feedback-message'); 31 | this.failureView = this.$el.find('#feedback-failure-message'); 32 | this.successView = this.$el.find('#feedback-success-message'); 33 | this.sendingView = this.$el.find('#feedback-sending-message'); 34 | 35 | return this; 36 | 37 | }, 38 | 39 | stopPropagation: function(e){ 40 | e.stopPropagation(); 41 | }, 42 | 43 | fail: function(){ 44 | 45 | this.sendingView.hide(); 46 | 47 | if (!this.failureView) return; 48 | 49 | if (!this.model.get('failure')) { 50 | this.failureView.hide(); 51 | return; 52 | } 53 | 54 | this.failureView.html( this.model.get('failureMessage') ); 55 | this.failureView.show(); 56 | 57 | }, 58 | 59 | success: function(){ 60 | 61 | this.sendingView.hide(); 62 | this.successView.show(); 63 | 64 | var that = this; 65 | 66 | setTimeout(function(){ 67 | that.app.set("showingFeedback", false); 68 | 69 | that.subject.val(""); 70 | that.message.val(""); 71 | 72 | that.successView.fadeOut(); 73 | }, 800); 74 | 75 | }, 76 | 77 | clickExit: function(e){ 78 | this.app.set("showingFeedback", false); 79 | }, 80 | 81 | clickSend: function(e) { 82 | 83 | e.preventDefault(); 84 | this.sendingView.show(); 85 | this.model.send({ subject: this.subject.val(), message: this.message.val() }); 86 | 87 | } 88 | 89 | }); 90 | }); -------------------------------------------------------------------------------- /app/scripts/views/HelpView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | el: '#help', 6 | 7 | events: { "click .exit-help": 'hide', 8 | 'click .enter-help-section' : 'clickEnterHelpSection', 9 | 'click .exit-all-help' : 'hide', 10 | 'click .exit-help-section' : 'clickExitHelpSection' 11 | }, 12 | 13 | initialize: function( args, atts ) { 14 | this.app = atts.app; 15 | }, 16 | 17 | render: function() { 18 | 19 | var template = _.template( $('#help-section-template').html() ); 20 | 21 | var that = this; 22 | 23 | this.$el.empty(); 24 | 25 | this.model.get('sections').forEach(function(section){ 26 | 27 | var el = $('#' + section.targetId ); 28 | 29 | if (!el) return; 30 | 31 | var offset = el.offset(); 32 | var height = el.height(); 33 | var width = el.width(); 34 | 35 | if (section.offset[0] < 0) { 36 | width = -10; 37 | } 38 | 39 | if (section.offset[1] < 0) { 40 | height = -10; 41 | width -= 10; 42 | } 43 | 44 | section.elementPosition = [ offset.left + width, offset.top + height ]; 45 | that.$el.append(template( section )); 46 | 47 | }); 48 | 49 | return this; 50 | 51 | }, 52 | 53 | getHelpSection: function(e){ 54 | 55 | var ui = $(e.target); 56 | var attr = ui.attr('data-target-id'); 57 | 58 | return this.$el.find(".help-section[data-target-id='" + attr + "']"); 59 | 60 | }, 61 | 62 | clickEnterHelpSection: function(e){ 63 | 64 | var ele = this.getHelpSection(e); 65 | var ui = $(e.target); 66 | 67 | ele.fadeIn(); 68 | ui.fadeOut(); 69 | 70 | }, 71 | 72 | clickExitHelpSection: function(e){ 73 | 74 | var ele = this.getHelpSection(e); 75 | ele.fadeOut(); 76 | 77 | }, 78 | 79 | hide: function() { 80 | this.app.set('showingHelp', false); 81 | } 82 | 83 | }); 84 | }); -------------------------------------------------------------------------------- /app/scripts/views/LoginView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | el: '#login', 6 | 7 | template: _.template( $('#login-template').html() ), 8 | 9 | events: { 'submit #login-form' : 'login', 10 | 'submit #signup-form' : 'signup', 11 | 'click #logout' : 'logout', 12 | "click .exit-login": 'hide', 13 | 'click #signup-tab-button': 'focusSignup', 14 | 'click #login-tab-button': 'focusLogin'}, 15 | 16 | initialize: function( args, atts ) { 17 | 18 | this.app = atts.app; 19 | this.model.on('change:showing', this.render, this); 20 | this.model.on('change:isLoggedIn', this.render, this); 21 | this.model.on('change:failed', this.render, this); 22 | this.model.on('change:failureMessage', this.render, this); 23 | 24 | var that = this; 25 | $('#login-button').click(function(){ that.tabClick.call(that); }); 26 | }, 27 | 28 | rendered : false, 29 | 30 | render: function() { 31 | 32 | if ( !this.rendered ) { 33 | this.$el.find('.login-container').html( this.template( this.model.toJSON() ) ); 34 | this.rendered = true; 35 | 36 | this.$el.find('.login-container').show(); 37 | } 38 | 39 | var failureMessage = this.$el.find('#login-failure-message'); 40 | 41 | if (this.model.get('failed')){ 42 | failureMessage.html( this.model.get('failureMessage') ); 43 | failureMessage.show(); 44 | } else { 45 | failureMessage.hide(); 46 | } 47 | 48 | if (this.model.get('showing') === true){ 49 | this.$el.show(); 50 | } else { 51 | this.$el.hide(); 52 | } 53 | 54 | this.renderLoginState(); 55 | 56 | return this; 57 | }, 58 | 59 | focusSignup: function(e){ 60 | 61 | this.model.set('failed', false); 62 | this.$el.find('#login-tab-button').removeClass('tab-button-hilite'); 63 | this.$el.find('#signup-tab-button').addClass('tab-button-hilite'); 64 | this.$el.find('#login-form').hide(); 65 | this.$el.find('#signup-form').show(); 66 | 67 | }, 68 | 69 | focusLogin: function(e){ 70 | 71 | this.model.set('failed', false); 72 | this.$el.find('#signup-tab-button').removeClass('tab-button-hilite'); 73 | this.$el.find('#login-tab-button').addClass('tab-button-hilite'); 74 | this.$el.find('#signup-form').hide(); 75 | this.$el.find('#login-form').show(); 76 | }, 77 | 78 | renderLoginState: function(){ 79 | 80 | if( this.model.get('isLoggedIn') ){ 81 | this.model.hide(); 82 | } else { 83 | this.$el.show(); 84 | } 85 | 86 | return this; 87 | }, 88 | 89 | tabClick: function(){ 90 | 91 | if (this.model.get('isLoggedIn')){ 92 | this.model.logout(); 93 | } else { 94 | this.model.toggle(); 95 | } 96 | 97 | }, 98 | 99 | signup: function(e) { 100 | e.preventDefault(); 101 | this.model.signup( this.$('#signup-form').serialize()); 102 | }, 103 | 104 | login: function(e) { 105 | e.preventDefault(); 106 | this.model.login( this.$('#login-form').serialize() ); 107 | } 108 | 109 | }); 110 | }); -------------------------------------------------------------------------------- /app/scripts/views/MarqueeView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | workspaceView : null, 6 | 7 | initialize: function( args ) { 8 | 9 | this.model = args.model; 10 | this.workspace = args.workspace; 11 | this.workspaceView = args.workspaceView; 12 | 13 | this.listenTo(this.model, 'change', this.render); 14 | 15 | }, 16 | 17 | render: function() { 18 | 19 | this.makeElementOnce(); 20 | 21 | return this.updateCorners() 22 | .updateHidden() 23 | .updateColor() 24 | .updateStroke(); 25 | 26 | }, 27 | 28 | updateCorners: function(){ 29 | this.el.setAttribute('d', this.template( this.getCorners() )) 30 | return this; 31 | }, 32 | 33 | updateHidden: function(){ 34 | 35 | if (this.model.get('hidden')) { 36 | this.el.setAttribute('class','marquee collapsed'); 37 | } else { 38 | this.el.setAttribute('class','marquee'); 39 | } 40 | 41 | return this; 42 | 43 | }, 44 | 45 | updateColor: function(){ 46 | return this; 47 | }, 48 | 49 | curveInit: false, 50 | 51 | makeElementOnce: function() { 52 | 53 | if (!this.curveInit) { 54 | 55 | var crv = document.createElementNS('http://www.w3.org/2000/svg','rect'); 56 | crv.setAttribute('class','marquee'); 57 | 58 | this.updateCorners(); 59 | this.updateStroke(); 60 | 61 | this.el = crv; 62 | this.$el = $(crv); 63 | this.curveInit = true; 64 | 65 | } 66 | return this.el; 67 | 68 | }, 69 | 70 | updateCorners: function() { 71 | 72 | this.el.setAttribute('x',this.model.get('x')); 73 | this.el.setAttribute('y',this.model.get('y')); 74 | this.el.setAttribute('width',this.model.get('width')); 75 | this.el.setAttribute('height',this.model.get('height')); 76 | 77 | return this; 78 | 79 | }, 80 | 81 | updateStroke: function() { 82 | 83 | // we scale the stroke to avoid the marquee being overly thin or thick 84 | // when zoomed 85 | var zoom = 1 / this.workspace.get('zoom'); 86 | 87 | this.el.setAttribute('stroke-dasharray', (zoom * 4) + "," + (zoom * 4) ); 88 | this.el.setAttribute('stroke', zoom * 2 ); 89 | 90 | return this; 91 | } 92 | 93 | }); 94 | 95 | }); -------------------------------------------------------------------------------- /app/scripts/views/NodeViews/CustomNode.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'jquery', 'ThreeCSGNodeView'], function(_, $, ThreeCSGNodeView) { 2 | 3 | return ThreeCSGNodeView.extend({ 4 | 5 | innerTemplate : _.template( $('#node-custom-template').html() ), 6 | 7 | getCustomContents: function() { 8 | 9 | var that = this; 10 | 11 | // open the parent workspace on double click 12 | this.$el.bind('dblclick', function(){ 13 | this.model.workspace.app.openWorkspace( this.model.get('type').functionId ); 14 | }.bind(this) ); 15 | 16 | var js = this.model.toJSON() ; 17 | if (!js.extra.script) js.extra.script = this.model.get('type').script; 18 | 19 | return this.innerTemplate( js ); 20 | 21 | } 22 | 23 | }); 24 | 25 | }); -------------------------------------------------------------------------------- /app/scripts/views/NodeViews/Input.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { 2 | 3 | return BaseNodeView.extend({ 4 | 5 | template: _.template( $('#node-input-template').html() ), 6 | 7 | initialize: function(args) { 8 | BaseNodeView.prototype.initialize.apply(this, arguments); 9 | 10 | this.model.on('change:extra', function() { 11 | 12 | var ex = this.model.get('extra') ; 13 | var name = ex != undefined ? ex.name : ""; 14 | 15 | this.silentSyncUI( name ); 16 | this.model.trigger('updateRunner'); 17 | 18 | }, this); 19 | 20 | }, 21 | 22 | render: function(){ 23 | 24 | BaseNodeView.prototype.render.apply(this, arguments); 25 | 26 | this.$el.addClass('input-node'); 27 | 28 | var that = this; 29 | var extra = this.model.get('extra'); 30 | var name = extra.name != undefined ? extra.name : ""; 31 | 32 | this.inputText = this.$el.find(".text-input"); 33 | this.inputText.val( name ); 34 | this.inputText.change( function(e){ that.nameChanged.call(that, e); e.stopPropagation(); }); 35 | 36 | return this; 37 | 38 | }, 39 | 40 | nameChanged: function(){ 41 | this.inputSet(); 42 | this.model.workspace.trigger('updateRunner'); 43 | }, 44 | 45 | silentSyncUI: function(name){ 46 | 47 | this.silent = true; 48 | this.inputText.val( name ); 49 | this.silent = false; 50 | 51 | }, 52 | 53 | inputSet: function(e,ui) { 54 | 55 | if ( this.silent ) return; 56 | 57 | var newValue = { name: this.inputText.val() }; 58 | this.model.workspace.setNodeProperty({property: 'extra', _id: this.model.get('_id'), newValue: newValue }); 59 | 60 | } 61 | 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /app/scripts/views/NodeViews/NodeViews.js: -------------------------------------------------------------------------------- 1 | define(['BaseNodeView', 'WatchNodeView', 'NumNodeView', 'ThreeCSGNodeView', 'ScriptView', 'OutputView', 'InputView','CustomNodeView'], 2 | function(BaseNodeView, WatchNodeView, NumNodeView, ThreeCSGNodeView, ScriptView, OutputView, InputView, CustomNodeView){ 3 | 4 | var nodeViewTypes = {}; 5 | 6 | nodeViewTypes.Base = ThreeCSGNodeView; 7 | nodeViewTypes.Show = WatchNodeView; 8 | nodeViewTypes.Number = NumNodeView; 9 | nodeViewTypes.CustomNode = CustomNodeView; 10 | 11 | nodeViewTypes.Script = ScriptView; 12 | nodeViewTypes.Input = InputView; 13 | nodeViewTypes.Output = OutputView; 14 | 15 | return nodeViewTypes; 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /app/scripts/views/NodeViews/Output.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { 2 | 3 | return BaseNodeView.extend({ 4 | 5 | template: _.template( $('#node-output-template').html() ), 6 | 7 | initialize: function(args) { 8 | BaseNodeView.prototype.initialize.apply(this, arguments); 9 | 10 | this.model.on('change:extra', function() { 11 | 12 | var ex = this.model.get('extra') ; 13 | var name = ex != undefined ? ex.name : ""; 14 | 15 | this.silentSyncUI( name ); 16 | this.model.trigger('updateRunner'); 17 | 18 | }, this); 19 | 20 | }, 21 | 22 | render: function(){ 23 | 24 | BaseNodeView.prototype.render.apply(this, arguments); 25 | 26 | this.$el.addClass('output-node'); 27 | 28 | var that = this; 29 | var extra = this.model.get('extra'); 30 | var name = extra.name != undefined ? extra.name : ""; 31 | 32 | this.inputText = this.$el.find(".text-input"); 33 | this.inputText.val( name ); 34 | this.inputText.change( function(e){ that.nameChanged.call(that, e); e.stopPropagation(); }); 35 | 36 | return this; 37 | 38 | }, 39 | 40 | nameChanged: function(){ 41 | this.inputSet(); 42 | this.model.workspace.trigger('updateRunner'); 43 | }, 44 | 45 | silentSyncUI: function(name){ 46 | this.silent = true; 47 | this.inputText.val( name ); 48 | this.silent = false; 49 | }, 50 | 51 | inputSet: function(e,ui) { 52 | if ( this.silent ) return; 53 | 54 | var newValue = { name: this.inputText.val() }; 55 | this.model.workspace.setNodeProperty({property: 'extra', _id: this.model.get('_id'), newValue: newValue }); 56 | } 57 | 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /app/scripts/views/NodeViews/Script.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'underscore', 'jquery', 'ThreeCSGNodeView', 'FLOOD', 'codemirror', 'codemirror/mode/javascript/javascript','codemirror/addon/hint/show-hint', 'codemirror/addon/hint/javascript-hint' ], function(Backbone, _, $, ThreeCSGNodeView, FLOOD, CodeMirror) { 2 | 3 | return ThreeCSGNodeView.extend({ 4 | 5 | initialize: function(args) { 6 | ThreeCSGNodeView.prototype.initialize.apply(this, arguments); 7 | this.listenTo(this.model,'change:extra', this.onChangedExtra); 8 | }, 9 | 10 | innerTemplate : _.template( $('#node-script-template').html() ), 11 | 12 | getCustomContents: function() { 13 | 14 | var js = this.model.toJSON() ; 15 | if (!js.extra.script) js.extra.script = this.model.get('type').script; 16 | 17 | return this.innerTemplate( js ); 18 | 19 | }, 20 | 21 | onChangedExtra: function(){ 22 | 23 | var ex = this.model.get('extra') || {}; 24 | 25 | if (ex.numInputs != undefined ){ 26 | this.setNumInputConnections( ex.numInputs ) 27 | this.model.get('type').setNumInputs( ex.numInputs ); 28 | } 29 | 30 | this.render(); 31 | this.model.trigger('updateRunner'); 32 | this.model.workspace.run(); 33 | }, 34 | 35 | setNumInputConnections: function(num){ 36 | 37 | if (num === undefined) return; 38 | 39 | var inputConns = this.model.get('inputConnections'); 40 | 41 | var diff = num - inputConns.length; 42 | 43 | if (diff === 0) return; 44 | 45 | if (diff > 0){ 46 | for (var i = 0; i < diff; i++){ 47 | inputConns.push([]); 48 | } 49 | } else { 50 | for (var i = 0; i < -diff; i++){ 51 | 52 | var conn = this.model.getConnectionAtIndex(inputConns.length - 1); 53 | 54 | if (conn != null){ 55 | this.model.workspace.removeConnection(conn); 56 | } 57 | 58 | inputConns.pop(); 59 | } 60 | 61 | } 62 | 63 | }, 64 | 65 | extendScope: _.once(function(){ 66 | 67 | var or = CodeMirror.hint.javascript; 68 | CodeMirror.hint.javascript = function(editor, options){ 69 | options.globalScope = FLOOD.nodeTypes; 70 | return or(editor, options); 71 | } 72 | }), 73 | 74 | renderNode: function() { 75 | ThreeCSGNodeView.prototype.renderNode.apply(this, arguments); 76 | 77 | this.extendScope(); 78 | 79 | var ta = this.$el.find('.script-text-input'); 80 | var cm = CodeMirror.fromTextArea(ta[0], 81 | { extraKeys: {"Ctrl-Space": "autocomplete"}, 82 | mode: { name :"javascript" } 83 | }); 84 | 85 | var that = this; 86 | 87 | cm.on("focus",function(e){ 88 | that.selectable = false; 89 | that.model.set('selected', false); 90 | }); 91 | 92 | cm.on("change", function(){ 93 | // we need to update the ports and connectors after moving the node 94 | setTimeout(function(){ 95 | // update the position of the node ports 96 | that.renderPorts(); 97 | // update the position of the connections 98 | that.model.trigger('resized'); 99 | }, 0); 100 | }); 101 | 102 | cm.on("blur",function(){ 103 | var ex = JSON.parse( JSON.stringify( that.model.get('extra') ) ); 104 | if ( ex.script === cm.getValue() ) return; 105 | 106 | ex.script = cm.getValue(); 107 | 108 | that.model.workspace.setNodeProperty({property: "extra", _id: that.model.get('_id'), newValue: ex }); 109 | that.selectable = true; 110 | }); 111 | 112 | this.$el.find('.add-input').click(function(){ that.addInput.call(that); }); 113 | this.$el.find('.remove-input').click(function(){ that.removeInput.call(that); }); 114 | 115 | return this; 116 | }, 117 | 118 | setNumInputsProperty: function(numInputs){ 119 | if (numInputs === undefined) return; 120 | 121 | var ex = this.model.get('extra'); 122 | var exCopy = JSON.parse( JSON.stringify( ex ) ); 123 | 124 | exCopy.numInputs = numInputs; 125 | this.model.workspace.setNodeProperty({property: "extra", _id: this.model.get('_id'), newValue: exCopy, oldValue: ex }); 126 | }, 127 | 128 | addInput: function(){ 129 | 130 | var type = this.model.get('type'); 131 | var ex = this.model.get('extra'); 132 | var numInputs = ex.numInputs; 133 | 134 | if (numInputs === undefined) numInputs = 0; 135 | if (type.inputs.length === 26) return; 136 | 137 | this.setNumInputsProperty(numInputs + 1); 138 | 139 | }, 140 | 141 | removeInput: function(){ 142 | 143 | var type = this.model.get('type'); 144 | var ex = this.model.get('extra'); 145 | var numInputs = ex.numInputs; 146 | 147 | if (numInputs === undefined) numInputs = 1; 148 | if (type.inputs.length === 0) return; 149 | 150 | this.setNumInputsProperty(numInputs - 1); 151 | 152 | } 153 | 154 | }); 155 | 156 | }); 157 | -------------------------------------------------------------------------------- /app/scripts/views/NodeViews/Watch.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { 2 | 3 | return BaseNodeView.extend({ 4 | 5 | template: _.template( $('#node-watch-template').html() ), 6 | 7 | initialize: function(args) { 8 | 9 | BaseNodeView.prototype.initialize.apply(this, arguments); 10 | this.model.on( 'change:lastValue', this.renderNode, this ); 11 | this.model.on( 'disconnection', this.renderNode, this ); 12 | 13 | }, 14 | 15 | renderNode: function(){ 16 | 17 | var pretty = this.model.get('lastValue') != undefined ? JSON.stringify(this.model.get('lastValue'), this.prettyPrint, 2) : this.model.get('lastValue'); 18 | this.model.set('prettyValue', pretty ); 19 | 20 | return BaseNodeView.prototype.renderNode.apply(this, arguments); 21 | 22 | }, 23 | 24 | prettyPrint: function(key, val){ 25 | 26 | if (typeof val === "number"){ 27 | return val.toPrecision(4); 28 | } 29 | 30 | if (typeof val === "string"){ 31 | return val.replace(new RegExp("\t", 'g'), "").replace(new RegExp("\n", 'g'), "
") 32 | } 33 | 34 | return val; 35 | } 36 | 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /app/scripts/views/SearchElementView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | tagName: 'li', 6 | className: 'search-element', 7 | 8 | template: _.template( $('#search-element-template').html() ), 9 | 10 | events: { 11 | 'click': 'click' 12 | }, 13 | 14 | initialize: function(a, arr){ 15 | this.app = arr.app; 16 | this.appView = arr.appView; 17 | this.elementClick = arr.click; 18 | }, 19 | 20 | render: function() { 21 | this.$el.html( this.template( this.model.toJSON() ) ); 22 | }, 23 | 24 | click: function(e) { 25 | if (!this.elementClick) return; 26 | this.elementClick(this); 27 | } 28 | 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /app/scripts/views/SearchView.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'List', 'SearchElementView'], function(Backbone, List, SearchElementView) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | tagName: 'div', 6 | className: 'search-container row', 7 | 8 | initialize: function(atts, arr) { 9 | this.app = arr.app; 10 | this.app.on('change:showingSearch', this.highlightInput, this ); 11 | this.app.SearchElements.on('add remove', this.render, this); 12 | }, 13 | 14 | template: _.template( $('#search-template').html() ), 15 | 16 | events: { 17 | 'keyup .library-search-input': 'searchKeyup' 18 | }, 19 | 20 | render: function(arg) { 21 | 22 | this.$el.html( this.template( this.model.toJSON() ) ); 23 | 24 | this.$input = this.$('.library-search-input'); 25 | this.$list = this.$('.search-list'); 26 | 27 | this.$list.empty(); 28 | 29 | var that = this; 30 | 31 | this.app.SearchElements.forEach(function(ele) { 32 | 33 | if (ele.name === null) return; 34 | 35 | var eleView = new SearchElementView({ model: ele }, { app: that.app, click: function(e){ that.elementClick.call(that, e) } }); 36 | 37 | eleView.render(); 38 | that.$list.append( eleView.$el ); 39 | 40 | }); 41 | 42 | var options = { 43 | valueNames: [ 'name' ] 44 | }; 45 | 46 | this.list = new List(this.el, options); 47 | 48 | }, 49 | 50 | highlightInput: function(){ 51 | 52 | if (this.app.get('showingSearch')) { 53 | this.$input.focus().select(); 54 | } else { 55 | this.$input.blur(); 56 | } 57 | }, 58 | 59 | addNode: function(name){ 60 | this.app.getCurrentWorkspace().addNodeByNameAndPosition( name, this.app.newNodePosition ); 61 | }, 62 | 63 | elementClick: function(ele){ 64 | 65 | this.addNode(ele.model.get('name')); 66 | this.app.set('showingSearch', false); 67 | 68 | }, 69 | 70 | searchKeyup: function(event) { 71 | 72 | if ( event.keyCode === 13) { // enter key causes first result to be inserted 73 | var nodeName = this.$list.find('.search-element').first().find('.name').first().html(); 74 | if (nodeName === undefined ) return; 75 | 76 | this.addNode( nodeName ); 77 | this.app.set('showingSearch', false); 78 | 79 | } else if ( event.keyCode === 27) { // esc key exits search 80 | this.app.set('showingSearch', false); 81 | } 82 | } 83 | 84 | }); 85 | 86 | }); 87 | 88 | -------------------------------------------------------------------------------- /app/scripts/views/ShareView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | el: '#share', 6 | 7 | events: { 8 | 'click #exit-share' : 'clickExit', 9 | 'click' : 'clickExit', 10 | 'click .modal-box': 'stopPropagation', 11 | 'click .is-customizer-checkbox': 'checkboxChanged' 12 | }, 13 | 14 | template: _.template( $('#share-template').html() ), 15 | 16 | initialize: function( args, atts ) { 17 | this.app = atts.app; 18 | 19 | this.model.on('success', this.success, this ); 20 | }, 21 | 22 | stopPropagation: function(e){ 23 | e.stopPropagation(); 24 | }, 25 | 26 | render: function() { 27 | 28 | var ws = this.app.getCurrentWorkspace(); 29 | 30 | this.$el.html( this.template({ 31 | name: ws.get('name'), 32 | isCustomizer: ws.get('isCustomizer'), 33 | url: ws.getCustomizerUrl() 34 | }) ); 35 | 36 | if (!ws.isCustomizer){ 37 | // we defer this as the input isn't necessarily yet in the dom 38 | _.defer(function(){ this.$el.find('.customizer-url').focus().select(); }.bind(this) ); 39 | } 40 | 41 | this.updatePublicPrivate(); 42 | 43 | return this; 44 | 45 | }, 46 | 47 | updatePublicPrivate: function(){ 48 | 49 | if ( this.app.getCurrentWorkspace().get('isCustomizer') ){ 50 | this.$el.find('.is-customizer-checkbox').removeClass("fa-toggle-off"); 51 | this.$el.find('.is-customizer-checkbox').addClass("fa-toggle-on"); 52 | this.$el.find('.public').show(); 53 | this.$el.find('.private').hide(); 54 | } else { 55 | this.$el.find('.is-customizer-checkbox').removeClass("fa-toggle-on"); 56 | this.$el.find('.is-customizer-checkbox').addClass("fa-toggle-off"); 57 | this.$el.find('.public').hide(); 58 | this.$el.find('.private').show(); 59 | } 60 | 61 | }, 62 | 63 | checkboxChanged: function(){ 64 | 65 | var ws = this.app.getCurrentWorkspace(); 66 | ws.set('isCustomizer', !ws.get('isCustomizer') ); 67 | 68 | this.updatePublicPrivate(); 69 | 70 | }, 71 | 72 | clickExit: function(e){ 73 | this.app.set("showingShare", false); 74 | } 75 | 76 | }); 77 | }); -------------------------------------------------------------------------------- /app/scripts/views/WorkspaceBrowserElementView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | tagName: 'li', 6 | className: 'workspace-browser-element', 7 | 8 | template: _.template( $('#workspace-browser-element-template').html() ), 9 | 10 | events: { 11 | 'click': 'clickOpen', 12 | 'click .delete-workspace': 'clickDelete', 13 | 'mouseenter': 'mouseenter', 14 | 'mouseleave': 'mouseleave' 15 | }, 16 | 17 | initialize: function(_, vals){ 18 | this.app = vals.app; 19 | }, 20 | 21 | render: function() { 22 | 23 | var v = this.model.toJSON(); 24 | var lastSave = this.model.get('lastSaved'); 25 | 26 | if (typeof lastSave === "string") { 27 | v["prettyDate"] = this.prettyDate( new Date(lastSave) ); 28 | } else { 29 | v["prettyDate"] = "Unknown"; 30 | } 31 | 32 | this.$el.html( this.template( v ) ); 33 | this.$open = this.$el.find('.workspace-browser-element-open'); 34 | 35 | }, 36 | 37 | prettyDate: function(date){ 38 | 39 | var diff = (((new Date()).getTime() - date.getTime()) / 1000), 40 | day_diff = Math.floor(diff / 86400); 41 | 42 | if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) 43 | return; 44 | 45 | return day_diff == 0 && ( 46 | diff < 60 && "just now" || 47 | diff < 120 && "1 minute ago" || 48 | diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || 49 | diff < 7200 && "1 hour ago" || 50 | diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || 51 | day_diff == 1 && "Yesterday" || 52 | day_diff < 7 && day_diff + " days ago" || 53 | day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; 54 | }, 55 | 56 | clickOpen: function(e) { 57 | this.app.openWorkspace( this.model.get('_id') ); 58 | }, 59 | 60 | clickDelete: function(e){ 61 | 62 | } 63 | 64 | }); 65 | 66 | }); -------------------------------------------------------------------------------- /app/scripts/views/WorkspaceBrowserView.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'WorkspaceBrowserElementView'], function(Backbone, WorkspaceBrowserElementView) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | el: '#workspace-browser', 6 | 7 | initialize: function(atts, arr) { 8 | this.app = arr.app; 9 | 10 | this.model.get('workspaces').on('reset', this.render, this ); 11 | this.model.get('workspaces').on('add', this.addWorkspaceElement, this ); 12 | this.model.get('workspaces').on('remove', this.removeWorkspaceElement, this ); 13 | 14 | this.render(); 15 | }, 16 | 17 | template: _.template( $('#workspace-browser-template').html() ), 18 | 19 | events: { 20 | 'click .workspace-browser-refresh': "refreshClick", 21 | 'click #workspace-browser-header-custom-nodes': "customNodeHeaderClick", 22 | 'click #workspace-browser-header-projects': "projectHeaderClick" 23 | }, 24 | 25 | render: function(arg) { 26 | 27 | if (!this.rendered) { 28 | this.$el.html( this.template( this.model.toJSON() ) ); 29 | this.contents = this.$el.find('#workspace-browser-contents'); 30 | this.customNodes = this.$el.find('#workspace-browser-custom-nodes'); 31 | this.projects = this.$el.find('#workspace-browser-projects'); 32 | } 33 | 34 | this.rendered = true; 35 | 36 | this.customNodes.empty(); 37 | this.projects.empty(); 38 | 39 | }, 40 | 41 | refreshClick: function(e){ 42 | this.model.refresh(); 43 | e.stopPropagation(); 44 | }, 45 | 46 | customNodeHeaderClick: function(e){ 47 | 48 | // do not resize when refresh is clicked 49 | if ( $(e.target).hasClass('workspace-browser-refresh') ) return; 50 | 51 | this.projects.hide(); 52 | this.customNodes.show(); 53 | 54 | $('#workspace-browser-header-custom-nodes').css('bottom','').css('top','40px'); 55 | 56 | }, 57 | 58 | projectHeaderClick: function(e){ 59 | 60 | if ( $(e.target).hasClass('workspace-browser-refresh') ) return; 61 | 62 | this.customNodes.hide(); 63 | this.projects.show(); 64 | 65 | $('#workspace-browser-header-custom-nodes').css('bottom','0').css('top',''); 66 | 67 | }, 68 | 69 | addWorkspaceElement: function(x){ 70 | 71 | if (!this.contents) this.render(); 72 | 73 | var v = new WorkspaceBrowserElementView( { model: x }, { app : this.app } ); 74 | v.render(); 75 | 76 | if ( x.get('isCustomNode') ){ 77 | this.customNodes.append( v.$el ); 78 | } else { 79 | this.projects.append( v.$el ); 80 | } 81 | 82 | }, 83 | 84 | removeWorkspaceElement: function(ws){ 85 | 86 | if (!this.contents) return; 87 | this.contents.find('.workspace-browser-element[data-id*=' + ws.get('_id') + ']').remove(); 88 | 89 | } 90 | 91 | }); 92 | 93 | }); 94 | 95 | -------------------------------------------------------------------------------- /app/scripts/views/WorkspaceTabView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | tagName: 'div', 6 | className: 'workspace-tab', 7 | 8 | initialize: function(atts) { 9 | 10 | this.app = this.model.app; 11 | this.listenTo( this.model, 'change:name', this.render); 12 | this.listenTo( this.model, 'change:current', this.render); 13 | 14 | }, 15 | 16 | template: _.template( $('#workspace-tab-template').html() ), 17 | 18 | events: { 19 | 20 | 'click': 'click', 21 | 'click .remove-button': 'remove', 22 | 'mouseover': 'showEditButton', 23 | 'mouseout': 'hideEditButton', 24 | 'click .edit-button': 'startEdit', 25 | 'blur .workspace-name': 'endEdit', 26 | 27 | // touch 28 | 'touchstart .edit-button': 'startEdit', 29 | 'touchstart': 'toggleShowingEditButton' 30 | 31 | }, 32 | 33 | render: function() { 34 | 35 | this.$el.html( this.template( this.model.toJSON() ) ); 36 | 37 | if (this.model.get('current') === true){ 38 | this.$el.addClass('current-workspace') 39 | } else { 40 | this.$el.removeClass('current-workspace') 41 | } 42 | 43 | this.$input = this.$('.workspace-name'); 44 | 45 | }, 46 | 47 | toggleShowingEditButton: function(){ 48 | 49 | if ( this.editButtonShown ){ 50 | this.editButtonShown = false; 51 | this.hideEditButton(); 52 | } else { 53 | this.editButtonShown = true; 54 | this.showEditButton(); 55 | } 56 | 57 | }, 58 | 59 | showEditButton: function() { 60 | this.$('.edit-button').css('visibility', 'visible'); 61 | }, 62 | 63 | hideEditButton: function() { 64 | this.$('.edit-button').css('visibility', 'hidden'); 65 | }, 66 | 67 | startEdit: function(e) { 68 | 69 | this.$input.prop('disabled', false); 70 | this.$input.focus().select(); 71 | this.$input.css('pointer-events', 'auto'); 72 | 73 | e.stopPropagation(); 74 | }, 75 | 76 | endEdit: function() { 77 | 78 | // the edit button is still visible on touch devices 79 | this.hideEditButton(); 80 | 81 | this.$input.prop('disabled', true); 82 | this.$input.css('pointer-events', 'none'); 83 | this.model.set('name', this.$input.val() ); 84 | }, 85 | 86 | click: function(e) { 87 | this.model.app.set('currentWorkspace', this.model.get('_id')); 88 | }, 89 | 90 | remove: function(e){ 91 | this.model.app.get('workspaces').remove(this.model); 92 | e.stopPropagation(); 93 | } 94 | 95 | }); 96 | 97 | }); -------------------------------------------------------------------------------- /app/scripts/views/customizer/CustomizerAppView.js: -------------------------------------------------------------------------------- 1 | // the application is constituted by 2 | 3 | // CustomizerAppView, CustomizerApp - controls visibility of widget 4 | // CustomizerWorkspaceView - collection of node widgets, similar to a nodeview, includes mask for which widgets will be visible 5 | // CustomizerNodeView - similar to a nodeview, but no inputs/outputs, simplified controls, potentially multiple options with labels 6 | // CustomizerViewer - allows you to view your customized geometry, with saved camera position 7 | 8 | define(['backbone', 'CustomizerViewerView', 'CustomizerHeaderView', 'CustomizerWorkspaceView'], 9 | function(Backbone, CustomizerViewer, CustomizerHeaderView, CustomizerWorkspaceView ) { 10 | 11 | 'use strict'; 12 | 13 | return Backbone.View.extend({ 14 | 15 | el: '#customizer-app ', 16 | 17 | events: { }, 18 | 19 | initialize: function( args, atts ) { 20 | this.listenTo(this.model, 'change', this.render); 21 | }, 22 | 23 | render: _.once(function() { 24 | 25 | (new CustomizerViewer()).render(); 26 | var hv = new CustomizerHeaderView({model: this.model.getCurrentWorkspace() }) 27 | this.listenTo( hv, "download-stl", function(){ this.model.getCurrentWorkspace().exportSTL() } );; 28 | hv.render(); 29 | (new CustomizerWorkspaceView({model: this.model.getCurrentWorkspace() })).render(); 30 | 31 | return this; 32 | 33 | }), 34 | 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/scripts/views/customizer/CustomizerHeaderView.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | el: '#customizer-header', 6 | 7 | template: _.template( $('#header-template').html() ), 8 | 9 | events: { "click .stl-download" : "downloadStl" }, 10 | 11 | initialize: function( args, atts ) { 12 | this.listenTo(this.model, 'change', this.render); 13 | }, 14 | 15 | render: function() { 16 | 17 | this.$el.html( this.template( this.model.toJSON() ) ); 18 | return this; 19 | 20 | }, 21 | 22 | downloadStl: function() { 23 | this.trigger("download-stl") 24 | } 25 | 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/scripts/views/customizer/CustomizerViewerView.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'Three', 'OrbitControls'], function(Backbone) { 2 | 3 | var container, $container; 4 | var camera, controls, renderer; 5 | var geometry, group; 6 | 7 | scene = {}; 8 | 9 | var mouse = new THREE.Vector2(), 10 | offset = new THREE.Vector3(), 11 | INTERSECTED, SELECTED; 12 | 13 | var objects = [], plane; 14 | 15 | var mouseX = 0, mouseY = 0; 16 | var animate; 17 | 18 | var windowHalfX = window.innerWidth / 2; 19 | var windowHalfY = window.innerHeight / 2; 20 | 21 | return Backbone.View.extend({ 22 | 23 | el: '#customizer-viewer', 24 | 25 | events: { }, 26 | 27 | initialize: function( args, atts ) { 28 | 29 | }, 30 | 31 | done: false, 32 | 33 | render: _.once(function() { 34 | 35 | this.init(); 36 | this.renderView(); 37 | 38 | }), 39 | 40 | init: function() { 41 | 42 | container = document.getElementById("customizer-viewer"); 43 | $container = $(container); 44 | 45 | camera = new THREE.PerspectiveCamera( 30, $container.width() / $container.height(), 1, 10000 ); 46 | 47 | camera.position.set( 140, 140, 140 ); 48 | camera.up.set( 0, 0, 1 ); 49 | camera.lookAt( new THREE.Vector3(0,0,0) ); 50 | 51 | scene = new THREE.Scene(); 52 | 53 | renderer = new THREE.WebGLRenderer({antialias: true}); 54 | renderer.setClearColor( 0xffffff, 1 ); 55 | renderer.setSize( $container.width(), $container.height() ); 56 | renderer.sortObjects = false; 57 | 58 | container.appendChild( renderer.domElement ); 59 | renderer.domElement.setAttribute("id", "renderer_canvas"); 60 | 61 | // add subtle ambient lighting 62 | var ambientLight = new THREE.AmbientLight(0x555555); 63 | scene.add(ambientLight); 64 | 65 | // add directional light source 66 | var directionalLight = new THREE.DirectionalLight(0xbbbbbb); 67 | directionalLight.position.set(50, 30, 50); 68 | scene.add(directionalLight); 69 | 70 | var directionalLight = new THREE.DirectionalLight(0xaaaaaa); 71 | directionalLight.position.set(-0.2, -0.8, 1).normalize(); 72 | scene.add(directionalLight); 73 | 74 | this.makeGrid(); 75 | 76 | controls = new THREE.OrbitControls(camera, container); 77 | 78 | var that = this; 79 | 80 | animate = function(){ 81 | requestAnimationFrame( animate ); 82 | that.renderView(); 83 | }; 84 | 85 | requestAnimationFrame(animate); 86 | 87 | window.addEventListener( 'resize', this.onWindowResize.bind(this), false ); 88 | 89 | }, 90 | 91 | onWindowResize : function() { 92 | 93 | windowHalfX = $container.width() / 2; 94 | windowHalfY = $container.height() / 2; 95 | 96 | camera.aspect = windowHalfX/ windowHalfY; 97 | camera.updateProjectionMatrix(); 98 | 99 | renderer.setSize( 2*windowHalfX, 2*windowHalfY ); 100 | 101 | this.render(); 102 | 103 | }, 104 | 105 | renderView: function() { 106 | 107 | controls.update(); 108 | renderer.render( scene, camera ); 109 | 110 | }, 111 | 112 | makeGrid: function(){ 113 | 114 | var l = 60; 115 | 116 | var axisHelper = new THREE.AxisHelper( l ); 117 | scene.add( axisHelper ); 118 | 119 | var geometry = new THREE.Geometry(); 120 | var geometryThick = new THREE.Geometry(); 121 | 122 | var n = l; 123 | var inc = 2 * l / n; 124 | var rate = 10; 125 | 126 | for (var i = 0; i < n + 1; i++){ 127 | 128 | var v1 = new THREE.Vector3(-l, -l + i * inc, 0); 129 | var v2 = new THREE.Vector3(l, -l + i * inc, 0); 130 | 131 | geometry.vertices.push(v1); 132 | geometry.vertices.push(v2); 133 | 134 | if (i % rate == 0){ 135 | geometryThick.vertices.push(v1); 136 | geometryThick.vertices.push(v2); 137 | } 138 | } 139 | 140 | for (var i = 0; i < n + 1; i++){ 141 | var v1 = new THREE.Vector3(-l + i * inc, l, 0); 142 | var v2 = new THREE.Vector3(-l + i * inc, -l, 0); 143 | 144 | geometry.vertices.push(v1); 145 | geometry.vertices.push(v2); 146 | 147 | if (i % rate == 0){ 148 | geometryThick.vertices.push(v1); 149 | geometryThick.vertices.push(v2); 150 | } 151 | } 152 | 153 | var material = new THREE.LineBasicMaterial({ 154 | color: 0xeeeeee, 155 | linewidth: 0.1 156 | }); 157 | 158 | var materialThick = new THREE.LineBasicMaterial({ 159 | color: 0xeeeeee, 160 | linewidth: 2 161 | }); 162 | 163 | var line = new THREE.Line(geometry, material, THREE.LinePieces); 164 | var lineThick = new THREE.Line(geometryThick, materialThick, THREE.LinePieces); 165 | 166 | scene.add(line); 167 | scene.add(lineThick); 168 | 169 | } 170 | 171 | }); 172 | }); 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /app/scripts/views/customizer/CustomizerWorkspaceView.js: -------------------------------------------------------------------------------- 1 | define(['backbone', 'BaseWidgetView', 'GeometryWidgetView', 'NumberWidgetView'], 2 | function(Backbone, BaseWidgetView, GeometryWidgetView, NumberWidgetView) { 3 | 4 | return Backbone.View.extend({ 5 | 6 | el: '#customizer-workspace', 7 | 8 | events: { 9 | "click #hide-workspace" : "hideWorkspace" 10 | }, 11 | 12 | initialize: function( args, atts ) { 13 | 14 | }, 15 | 16 | map: { 17 | "Number" : NumberWidgetView 18 | }, 19 | 20 | hasWidgets: false, 21 | 22 | buildWidget: function(x){ 23 | 24 | if (x.get('extra') != undefined && x.get('extra').lock) return; 25 | 26 | var widgetView = GeometryWidgetView; 27 | 28 | if (x.get('type').typeName in this.map){ 29 | widgetView = this.map[x.get('type').typeName]; 30 | } 31 | 32 | var widget = new widgetView({model: x}); 33 | 34 | if (x.get('type').typeName in this.map){ 35 | this.$el.append( widget.render().$el ); 36 | this.hasWidgets = true; 37 | } 38 | 39 | }, 40 | 41 | visible: true, 42 | 43 | hideWorkspace: function(){ 44 | 45 | if (this.visible){ 46 | this.$el.addClass('workspace-contracted'); 47 | this.$el.find('.widget').css('visibility', 'hidden'); 48 | this.$el.find('#hide-workspace i').removeClass('fa-arrow-circle-left'); 49 | this.$el.find('#hide-workspace i').addClass('fa-arrow-circle-right'); 50 | this.visible = false; 51 | } else { 52 | this.$el.removeClass('workspace-contracted'); 53 | this.$el.find('.widget').css('visibility', 'visible'); 54 | this.$el.find('#hide-workspace i').removeClass('fa-arrow-circle-right'); 55 | this.$el.find('#hide-workspace i').addClass('fa-arrow-circle-left'); 56 | this.visible = true; 57 | } 58 | 59 | }, 60 | 61 | render: function() { 62 | 63 | this.model.get('nodes').each(this.buildWidget.bind(this)); 64 | 65 | if (!this.hasWidgets) this.$el.hide(); 66 | 67 | return this; 68 | 69 | } 70 | 71 | }); 72 | }); -------------------------------------------------------------------------------- /app/scripts/views/customizer/widgets/Base.js: -------------------------------------------------------------------------------- 1 | define(['backbone'], function(Backbone) { 2 | 3 | return Backbone.View.extend({ 4 | 5 | tagName: 'div', 6 | className: 'widget', 7 | 8 | template: _.template( $('#widget-template').html() ), 9 | 10 | initialize: function(args) { 11 | 12 | this.model.on('evalFailed', this.onEvalFailed, this ); 13 | this.model.on('evalBegin', this.onEvalBegin, this ); 14 | 15 | this.listenTo(this.model, 'requestRender', this.render ); 16 | this.listenTo(this.model, 'change:position', this.move ); 17 | this.listenTo(this.model, 'change:lastValue', this.renderLastValue ); 18 | this.listenTo(this.model, 'change:failureMessage', this.renderLastValue ); 19 | this.listenTo(this.model, 'change:ignoreDefaults', this.colorPorts ); 20 | this.listenTo(this.model, 'change:selected', this.colorSelected); 21 | this.listenTo(this.model, 'change:visible', this.render); 22 | this.listenTo(this.model, 'change:isEvaluating', this.colorEvaluating); 23 | 24 | this.model.on('evalFailed', this.onEvalFailed, this ); 25 | this.model.on('evalBegin', this.onEvalBegin, this ); 26 | 27 | }, 28 | 29 | colorSelected: function(){ 30 | 31 | }, 32 | 33 | render: function() { 34 | 35 | this.$el.html( this.template( this.model.toJSON() ) ); 36 | return this; 37 | 38 | } 39 | 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /app/styles/customizer.css: -------------------------------------------------------------------------------- 1 | #customizer-app { 2 | background: #e3e3e3; 3 | top: 0; 4 | right: 0; 5 | left: 0; 6 | bottom: 0; 7 | position: absolute; 8 | } 9 | 10 | #customizer-workspace-header { 11 | position: absolute; 12 | bottom: -12px; 13 | right: -12px; 14 | color: #444; 15 | height: 20px; 16 | padding: 10px; 17 | font-size: 18px; 18 | text-shadow: 0px 1px 1px whitesmoke; 19 | text-align: right; 20 | } 21 | 22 | 23 | #customizer-workspace { 24 | color: black; 25 | position: absolute; 26 | top: 100px; 27 | left: 0; 28 | width: 380px; 29 | z-index: 1; 30 | background: rgba(0, 0, 0, 0.14); 31 | } 32 | 33 | #customizer-workspace.workspace-contracted { 34 | width: 20px; 35 | } 36 | 37 | #customizer-viewer { 38 | position: absolute; 39 | top: 0; 40 | right: 0; 41 | bottom: 0; 42 | left: 0; 43 | background: black; 44 | z-index: 0; 45 | } 46 | 47 | #customizer-header { 48 | position: absolute; 49 | top: 0; 50 | right: 0; 51 | left: 0; 52 | height: 50px; 53 | z-index: 2; 54 | font-size: 20px; 55 | display: inline-block; 56 | padding: 20px; 57 | padding-left: 30px; 58 | font-color: #222; 59 | text-shadow: 0px 1px 1px whitesmoke; 60 | } 61 | 62 | .indicator { 63 | margin-right: 30px; 64 | } 65 | 66 | #customize-header-tag { 67 | text-transform: uppercase; 68 | font-size: 13px; 69 | color: grey; 70 | font-weight: bold; 71 | } 72 | 73 | #powered-by { 74 | position: absolute; 75 | z-index: 2; 76 | right: 0; 77 | bottom: 0; 78 | height: 36px; 79 | padding: 10px; 80 | font-size: 12px; 81 | color: black; 82 | text-shadow: 0px 1px 0px grey; 83 | } 84 | 85 | #customize-header-title { 86 | padding-left: 20px; 87 | font-size: 24px; 88 | } 89 | 90 | #customizer-header-controls { 91 | padding: 30px; 92 | position: absolute; 93 | top: 0; 94 | right: 0; 95 | } 96 | 97 | .widget { 98 | width:370px; 99 | margin: 20px; 100 | } 101 | 102 | .widget .name { 103 | font-weight: bold; 104 | text-transform: uppercase; 105 | color: #222; 106 | display: inline-block; 107 | width: 130px; 108 | text-align: right; 109 | padding-right: 15px; 110 | font-size: 11px; 111 | text-shadow: 0px 1px 1px white; 112 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flood", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "jquery": "1.9.1", 6 | "requirejs": "~2.1.5", 7 | "requirejs-text": "~2.0.5", 8 | "backbone-amd": "~1.0.0", 9 | "almond": "0.2.6", 10 | "bootstrap": "3.0.3", 11 | "underscore-amd": "~1.4.4", 12 | "threejs": "r67", 13 | "jquery.ui": "1.10.3", 14 | "listjs": "1.0.0", 15 | "modernizr": "~2.6.2", 16 | "q": "1.0", 17 | "jqueryui-touch-punch": "furf/jquery-ui-touch-punch", 18 | "pace": "0.5.1", 19 | "components-font-awesome": "4.3.0", 20 | "hammerjs": "~2.0.4", 21 | "fastclick": "~1.0.3", 22 | "FileSaver": "eliGrey/FileSaver.js", 23 | "CodeMirror": "~5.3.0" 24 | }, 25 | "devDependencies": {} 26 | } 27 | -------------------------------------------------------------------------------- /extra/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/extra/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flood", 3 | "version": "0.0.4", 4 | "description": "A visual programming language for JavaScript, based on Scheme", 5 | "dependencies": { 6 | 7 | }, 8 | "devDependencies": { 9 | "grunt-node-webkit-builder": "~0.1.13", 10 | "grunt": "0.4.2", 11 | "grunt-processhtml": "~0.2.6", 12 | "grunt-contrib-copy": "0.4.1", 13 | "grunt-contrib-concat": "0.3.0", 14 | "grunt-contrib-uglify": "0.2.7", 15 | "grunt-contrib-compass": "0.7.0", 16 | "grunt-contrib-jshint": "0.7.2", 17 | "grunt-contrib-cssmin": "0.7.0", 18 | "grunt-contrib-connect": "0.3.0", 19 | "grunt-contrib-clean": "0.5.0", 20 | "grunt-contrib-imagemin": "0.4.0", 21 | "grunt-contrib-livereload": "0.1.2", 22 | "grunt-mocha": "0.4.7", 23 | "grunt-bower-requirejs": "0.8.0", 24 | "grunt-requirejs": "0.4.0", 25 | "grunt-regarde": "0.1.1", 26 | "grunt-open": "0.2.2", 27 | "matchdep": "0.3.0", 28 | "amdefine": ">=0.0.5", 29 | "webworker": "*" 30 | }, 31 | "engines": { 32 | "node": ">=0.10.0" 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | tmp 15 | 16 | npm-debug.log 17 | node_modules 18 | .idea 19 | *.iml 20 | .DS_Store 21 | Thumbs.db 22 | 23 | public/css/styles.css 24 | 25 | builtAssets 26 | 27 | ssl 28 | -------------------------------------------------------------------------------- /server/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | services: 4 | - mongodb 5 | 6 | node_js: 7 | - '0.10' -------------------------------------------------------------------------------- /server/cluster_app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var os = require('os'); 6 | var cluster = require('cluster'); 7 | 8 | /** 9 | * Cluster setup. 10 | */ 11 | 12 | // Setup the cluster to use app.js 13 | cluster.setupMaster({ 14 | exec: 'app.js' 15 | }); 16 | 17 | // Listen for dying workers 18 | cluster.on('exit', function(worker) { 19 | console.log('Worker ' + worker.id + ' died'); 20 | // Replace the dead worker 21 | cluster.fork(); 22 | }); 23 | 24 | // Fork a worker for each available CPU 25 | for (var i = 0; i < os.cpus().length; i++) { 26 | cluster.fork(); 27 | } 28 | -------------------------------------------------------------------------------- /server/config/secrets.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | db: process.env.MONGODB|| 'mongodb://localhost:27017/test', 3 | 4 | sessionSecret: process.env.SESSION_SECRET || 'Your Session Secret goes here', 5 | 6 | localAuth: true, 7 | 8 | feedback: { 9 | email: process.env.FEEDBACK_EMAIL || "Your email" 10 | }, 11 | 12 | mailgun: { 13 | login: process.env.MAILGUN_LOGIN || 'Your Mailgun SMTP Username', 14 | password: process.env.MAILGUN_PASSWORD || 'Your Mailgun SMTP Password' 15 | }, 16 | 17 | sendgrid: { 18 | user: process.env.SENDGRID_USER || 'Your SendGrid Username', 19 | password: process.env.SENDGRID_PASSWORD || 'Your SendGrid Password' 20 | }, 21 | 22 | nyt: { 23 | key: process.env.NYT_KEY || 'Your New York Times API Key' 24 | }, 25 | 26 | lastfm: { 27 | api_key: process.env.LASTFM_KEY || 'Your API Key', 28 | secret: process.env.LASTFM_SECRET || 'Your API Secret' 29 | }, 30 | 31 | facebookAuth: true, 32 | facebook: { 33 | clientID: process.env.FACEBOOK_ID || 'Your App ID', 34 | clientSecret: process.env.FACEBOOK_SECRET || 'Your App Secret', 35 | callbackURL: '/auth/facebook/callback', 36 | passReqToCallback: true 37 | }, 38 | 39 | githubAuth: false, 40 | github: { 41 | clientID: process.env.GITHUB_ID || 'Your Client ID', 42 | clientSecret: process.env.GITHUB_SECRET || 'Your Client Secret', 43 | callbackURL: '/auth/github/callback', 44 | passReqToCallback: true 45 | }, 46 | 47 | twitterAuth: false, 48 | twitter: { 49 | consumerKey: process.env.TWITTER_KEY || 'Your Consumer Key', 50 | consumerSecret: process.env.TWITTER_SECRET || 'Your Consumer Secret', 51 | callbackURL: '/auth/twitter/callback', 52 | passReqToCallback: true 53 | }, 54 | 55 | googleAuth: true, 56 | google: { 57 | clientID: process.env.GOOGLE_ID || 'Your Client ID', 58 | clientSecret: process.env.GOOGLE_SECRET || 'Your Client Secret', 59 | callbackURL: '/auth/google/callback', 60 | passReqToCallback: true 61 | }, 62 | 63 | linkedinAuth: false, 64 | linkedin: { 65 | clientID: process.env.LINKEDIN_ID || 'Your Client ID', 66 | clientSecret: process.env.LINKEDIN_SECRET || 'Your Client Secret', 67 | callbackURL: '/auth/linkedin/callback', 68 | scope: ['r_fullprofile', 'r_emailaddress', 'r_network'], 69 | passReqToCallback: true 70 | }, 71 | 72 | steam: { 73 | apiKey: process.env.STEAM_KEY || 'Your Steam API Key' 74 | }, 75 | 76 | twilio: { 77 | sid: process.env.TWILIO_SID || 'Your Twilio SID', 78 | token: process.env.TWILIO_TOKEN || 'Your Twilio token' 79 | }, 80 | 81 | clockwork: { 82 | apiKey: process.env.CLOCKWORK_KEY || 'Your Clockwork SMS API Key' 83 | }, 84 | 85 | tumblr: { 86 | consumerKey: process.env.TUMBLR_KEY || 'Your Consumer Key', 87 | consumerSecret: process.env.TUMBLR_SECRET || 'Your Consumer Secret', 88 | callbackURL: '/auth/tumblr/callback' 89 | }, 90 | 91 | foursquare: { 92 | clientId: process.env.FOURSQUARE_ID || 'Your Client ID', 93 | clientSecret: process.env.FOURSQUARE_SECRET || 'Your Client Secret', 94 | redirectUrl: process.env.FOURSQUARE_REDIRECT_URL || 'http://localhost:3000/auth/foursquare/callback' 95 | }, 96 | 97 | venmo: { 98 | clientId: process.env.VENMO_ID || 'Your Venmo Client ID', 99 | clientSecret: process.env.VENMO_SECRET || 'Your Venmo Client Secret', 100 | redirectUrl: process.env.VENMO_REDIRECT_URL || 'http://localhost:3000/auth/venmo/callback' 101 | }, 102 | 103 | paypal: { 104 | host: process.env.PAYPAL_HOST || 'api.sandbox.paypal.com', 105 | client_id: process.env.PAYPAL_ID || 'Your Client ID', 106 | client_secret: process.env.PAYPAL_SECRET || 'Your Client Secret', 107 | returnUrl: process.env.PAYPAL_RETURN_URL || 'http://localhost:3000/api/paypal/success', 108 | cancelUrl: process.env.PAYPAL_CANCEL_URL || 'http://localhost:3000/api/paypal/cancel' 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /server/controllers/contact.js: -------------------------------------------------------------------------------- 1 | var secrets = require('../config/secrets'); 2 | var nodemailer = require("nodemailer"); 3 | var smtpTransport = nodemailer.createTransport('SMTP', { 4 | // service: 'Mailgun', 5 | // auth: { 6 | // user: secrets.mailgun.login, 7 | // pass: secrets.mailgun.password 8 | // } 9 | service: 'SendGrid', 10 | auth: { 11 | user: secrets.sendgrid.user, 12 | pass: secrets.sendgrid.password 13 | } 14 | }); 15 | 16 | /** 17 | * GET /contact 18 | * Contact form page. 19 | */ 20 | 21 | exports.getContact = function(req, res) { 22 | res.render('contact', { 23 | title: 'Contact' 24 | }); 25 | }; 26 | 27 | /** 28 | * POST /contact 29 | * Send a contact form via Nodemailer. 30 | * @param email 31 | * @param name 32 | * @param message 33 | */ 34 | 35 | exports.postContact = function(req, res) { 36 | req.assert('name', 'Name cannot be blank').notEmpty(); 37 | req.assert('email', 'Email is not valid').isEmail(); 38 | req.assert('message', 'Message cannot be blank').notEmpty(); 39 | 40 | var errors = req.validationErrors(); 41 | 42 | if (errors) { 43 | req.flash('errors', errors); 44 | return res.redirect('/contact'); 45 | } 46 | 47 | var from = req.body.email; 48 | var name = req.body.name; 49 | var body = req.body.message; 50 | var to = 'your@email.com'; 51 | var subject = 'Contact Form | Flood'; 52 | 53 | var mailOptions = { 54 | to: to, 55 | from: from, 56 | subject: subject, 57 | text: body 58 | }; 59 | 60 | smtpTransport.sendMail(mailOptions, function(err) { 61 | if (err) { 62 | req.flash('errors', { msg: err.message }); 63 | return res.redirect('/contact'); 64 | } 65 | req.flash('success', { msg: 'Email has been sent successfully!' }); 66 | res.redirect('/contact'); 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /server/controllers/feedback.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore') 2 | , nodemailer = require('nodemailer') 3 | , secrets = require('../config/secrets'); 4 | 5 | 6 | var smtpTransport = nodemailer.createTransport('SMTP', { 7 | service: 'Mailgun', 8 | auth: { 9 | user: secrets.mailgun.login, 10 | pass: secrets.mailgun.password 11 | } 12 | }); 13 | 14 | var emailTarget = secrets.feedback.email; 15 | 16 | exports.postFeedback = function(req, res) { 17 | 18 | if (!req.user) return res.status(401).send("You are not logged in"); 19 | 20 | if (!req.body) return res.status(500).send('Malformed body'); 21 | 22 | var ns = req.body; 23 | 24 | if (!emailTarget) return res.status(500).send({ msg: "Feedback temporarily unsupported" }); 25 | 26 | smtpTransport.sendMail({ 27 | from: req.user.email, 28 | to: emailTarget, 29 | subject: "[FLOOD FEEDBACK] : \"" + ns.subject ? ns.subject : "Empty subject" + "\"", 30 | text: ns.message ? ns.message : "Empty body", 31 | }, function(err) { 32 | if (err) { 33 | return res.status(500).send({ msg: "Could not send feedback! Try again later!" }); 34 | } 35 | return res.send({ msg: 'Feedback has been sent successfully!' }); 36 | }); 37 | 38 | }; -------------------------------------------------------------------------------- /server/controllers/home.js: -------------------------------------------------------------------------------- 1 | var flood = require('../models/Workspace'); 2 | 3 | /** 4 | * GET / 5 | * Home page. 6 | */ 7 | 8 | exports.index = function(req, res) { 9 | res.render('home', { 10 | title: 'Home' 11 | }); 12 | }; -------------------------------------------------------------------------------- /server/controllers/workspaces.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/server/controllers/workspaces.js -------------------------------------------------------------------------------- /server/models/Session.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pboyer/flood/c86ab0b96034130c82c8efd92a62914f2d771311/server/models/Session.js -------------------------------------------------------------------------------- /server/models/User.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var bcrypt = require('bcrypt-nodejs'); 3 | var crypto = require('crypto'); 4 | var models = require('./Workspace') 5 | 6 | var userSchema = new mongoose.Schema({ 7 | 8 | email: { type: String, unique: true, lowercase: true }, 9 | password: String, 10 | 11 | facebook: String, 12 | twitter: String, 13 | google: String, 14 | github: String, 15 | linkedin: String, 16 | tokens: Array, 17 | 18 | profile: { 19 | name: { type: String, default: '' }, 20 | gender: { type: String, default: '' }, 21 | location: { type: String, default: '' }, 22 | website: { type: String, default: '' }, 23 | picture: { type: String, default: '' } 24 | }, 25 | 26 | resetPasswordToken: String, 27 | resetPasswordExpires: Date, 28 | 29 | lastSession: {type: mongoose.Schema.ObjectId, ref: 'Session' }, 30 | workspaces: [{type: mongoose.Schema.ObjectId, ref: 'Workspace' }] 31 | 32 | }); 33 | 34 | /** 35 | * Hash the password for security. 36 | * "Pre" is a Mongoose middleware that executes before each user.save() call. 37 | */ 38 | 39 | userSchema.pre('save', function(next) { 40 | var user = this; 41 | 42 | if (!user.isModified('password')) return next(); 43 | 44 | bcrypt.genSalt(5, function(err, salt) { 45 | if (err) return next(err); 46 | 47 | bcrypt.hash(user.password, salt, null, function(err, hash) { 48 | if (err) return next(err); 49 | user.password = hash; 50 | next(); 51 | }); 52 | }); 53 | }); 54 | 55 | /** 56 | * Validate user's password. 57 | * Used by Passport-Local Strategy for password validation. 58 | */ 59 | 60 | userSchema.methods.comparePassword = function(candidatePassword, cb) { 61 | bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { 62 | if (err) return cb(err); 63 | cb(null, isMatch); 64 | }); 65 | }; 66 | 67 | /** 68 | * Get URL to a user's gravatar. 69 | * Used in Navbar and Account Management page. 70 | */ 71 | 72 | userSchema.methods.gravatar = function(size, defaults) { 73 | if (!size) size = 200; 74 | if (!defaults) defaults = 'retro'; 75 | 76 | if (!this.email) { 77 | return 'https://gravatar.com/avatar/?s=' + size + '&d=' + defaults; 78 | } 79 | 80 | var md5 = crypto.createHash('md5').update(this.email); 81 | return 'https://gravatar.com/avatar/' + md5.digest('hex').toString() + '?s=' + size + '&d=' + defaults; 82 | }; 83 | 84 | module.exports = mongoose.model('User', userSchema); 85 | -------------------------------------------------------------------------------- /server/models/Workspace.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | , Schema = mongoose.Schema; 3 | 4 | var sessionSchema = new Schema({ 5 | name: { type: String, default: '' } 6 | , currentWorkspace: {type: Schema.ObjectId, ref: 'Workspace' } 7 | , workspaces: [ {type: Schema.ObjectId, ref: 'Workspace' } ] 8 | , lastSaved: Date 9 | , isFirstExperience: { type: Boolean, default: true } 10 | }); 11 | 12 | var workspaceSchema = new Schema({ 13 | name: { type: String, default: '' } 14 | , nodes: [ Schema.Types.Mixed ] 15 | , connections: [ Schema.Types.Mixed ] 16 | , selectedNodes: [ Schema.Types.Mixed ] 17 | , isPublic: { type: Boolean, default: false } 18 | , zoom: { type: Number, default: 1 } 19 | , offset: [{ type: Number }] 20 | , lastSaved: Date 21 | , maintainers: [{type: Schema.ObjectId, ref: 'User' }] 22 | , undoStack: [ Schema.Types.Mixed ] 23 | , redoStack: [ Schema.Types.Mixed ] 24 | , isEdited: { type: Boolean, default: false } 25 | , isCustomNode: { type: Boolean, default: false } 26 | , isCustomizer: { type: Boolean, default: false } 27 | , workspaceDependencyIds: [{type: Schema.ObjectId, ref: 'Workspace' }] 28 | }); 29 | 30 | exports.SessionModel = mongoose.model('Session', sessionSchema); 31 | exports.WorkspaceModel = mongoose.model('Workspace', workspaceSchema); 32 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flood-server", 3 | "version": "0.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "" 7 | }, 8 | "scripts": { 9 | "start": "node app.js", 10 | "test": "mocha" 11 | }, 12 | "dependencies": { 13 | "async": "^0.7.0", 14 | "bcrypt-nodejs": "^0.0.3", 15 | "body-parser": "^1.0.1", 16 | "cheerio": "^0.15.0", 17 | "clockwork": "^0.1.1", 18 | "compression": "^1.0.1", 19 | "connect-assets": "^3.0.0-beta2", 20 | "connect-mongo": "^0.4.0", 21 | "cookie-parser": "^1.0.1", 22 | "csso": "^1.3.11", 23 | "csurf": "^1.1.0", 24 | "errorhandler": "^1.0.0", 25 | "express": "^4.0.0", 26 | "express-flash": "^0.0.2", 27 | "express-session": "^1.0.2", 28 | "express-validator": "^2.1.1", 29 | "fbgraph": "^0.2.10", 30 | "github-api": "^0.7.0", 31 | "jade": "^1.3.0", 32 | "lastfm": "^0.9.0", 33 | "less": "^1.7.0", 34 | "method-override": "^1.0.0", 35 | "mongoose": "4.0.5", 36 | "morgan": "^1.0.0", 37 | "node-foursquare": "^0.2.0", 38 | "node-linkedin": "^0.1.5", 39 | "nodemailer": "^0.6.1", 40 | "passport": "^0.2.0", 41 | "passport-facebook": "^1.0.3", 42 | "passport-github": "^0.1.5", 43 | "passport-google-oauth": "^0.1.5", 44 | "passport-linkedin-oauth2": "^1.1.1", 45 | "passport-local": "^1.0.0", 46 | "passport-oauth": "^1.0.0", 47 | "passport-twitter": "^1.0.2", 48 | "paypal-rest-sdk": "^0.7.0", 49 | "request": "^2.34.0", 50 | "static-favicon": "^1.0.2", 51 | "tumblr.js": "^0.0.4", 52 | "twilio": "^1.6.0", 53 | "twit": "^1.1.12", 54 | "uglify-js": "^2.4.12", 55 | "underscore": "^1.6.0", 56 | "validator": "^3.8.0" 57 | }, 58 | "devDependencies": { 59 | "mocha": "^1.18.2", 60 | "chai": "^1.9.1", 61 | "supertest": "^0.10.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap-social.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Social Buttons for Bootstrap 3 | * 4 | * Copyright 2013-2014 Panayiotis Lipiridis 5 | * Licensed under the MIT License 6 | * 7 | * https://github.com/lipis/bootstrap-social 8 | */ 9 | 10 | .btn-social { 11 | @bs-height-base: (@line-height-computed + @padding-base-vertical * 2); 12 | @bs-height-lg: (floor(@font-size-large * @line-height-base) + @padding-large-vertical * 2); 13 | @bs-height-sm: (floor(@font-size-small * 1.5) + @padding-small-vertical * 2); 14 | @bs-height-xs: (floor(@font-size-small * 1.2) + @padding-small-vertical + 1); 15 | 16 | position: relative; 17 | padding-left: @bs-height-base + @padding-base-horizontal; 18 | text-align: left; 19 | white-space: nowrap; 20 | overflow: hidden; 21 | text-overflow: ellipsis; 22 | :first-child { 23 | position: absolute; 24 | left: 0; 25 | top: 0; 26 | bottom: 0; 27 | width: @bs-height-base; 28 | line-height: (@bs-height-base + 2); 29 | font-size: 1.6em; 30 | text-align: center; 31 | border-right: 1px solid rgba(0, 0, 0, 0.2); 32 | } 33 | &.btn-lg { 34 | padding-left: @bs-height-lg + @padding-large-horizontal; 35 | :first-child { 36 | line-height: @bs-height-lg; 37 | width: @bs-height-lg; 38 | font-size: 1.8em; 39 | } 40 | } 41 | &.btn-sm { 42 | padding-left: @bs-height-sm + @padding-small-horizontal; 43 | :first-child { 44 | line-height: @bs-height-sm; 45 | width: @bs-height-sm; 46 | font-size: 1.4em; 47 | } 48 | } 49 | &.btn-xs { 50 | padding-left: @bs-height-xs + @padding-small-horizontal; 51 | :first-child { 52 | line-height: @bs-height-xs; 53 | width: @bs-height-xs; 54 | font-size: 1.2em; 55 | } 56 | } 57 | } 58 | 59 | .btn-social-icon { 60 | .btn-social; 61 | height: (@bs-height-base + 2); 62 | width: (@bs-height-base + 2); 63 | padding: 0; 64 | :first-child { 65 | border: none; 66 | text-align: center; 67 | width: 100% !important; 68 | } 69 | &.btn-lg { 70 | height: @bs-height-lg; 71 | width: @bs-height-lg; 72 | padding-left: 0; 73 | padding-right: 0; 74 | } 75 | &.btn-sm { 76 | height: (@bs-height-sm + 2); 77 | width: (@bs-height-sm + 2); 78 | padding-left: 0; 79 | padding-right: 0; 80 | } 81 | &.btn-xs { 82 | height: (@bs-height-xs + 2); 83 | width: (@bs-height-xs + 2); 84 | padding-left: 0; 85 | padding-right: 0; 86 | } 87 | } 88 | 89 | .btn-social(@color-bg, @color: white) { 90 | background-color: @color-bg; 91 | .button-variant(@color, @color-bg, rgba(0, 0, 0, 0.2)); 92 | } 93 | 94 | .btn-bitbucket { 95 | .btn-social(#205081); 96 | } 97 | 98 | .btn-dropbox { 99 | .btn-social(#1087dd); 100 | } 101 | 102 | .btn-facebook { 103 | .btn-social(#3b5998); 104 | } 105 | 106 | .btn-flickr { 107 | .btn-social(#ff0084); 108 | } 109 | 110 | .btn-foursquare { 111 | .btn-social(#0072b1); 112 | } 113 | 114 | .btn-github { 115 | .btn-social(#444444); 116 | } 117 | 118 | .btn-google-plus { 119 | .btn-social(#dd4b39); 120 | } 121 | 122 | .btn-instagram { 123 | .btn-social(#3f729b); 124 | } 125 | 126 | .btn-linkedin { 127 | .btn-social(#007bb6); 128 | } 129 | 130 | .btn-tumblr { 131 | .btn-social(#2c4762); 132 | } 133 | 134 | .btn-twitter { 135 | .btn-social(#55acee); 136 | } 137 | 138 | .btn-vk { 139 | .btn-social(#587ea3); 140 | } 141 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/alerts.less: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: @alert-padding; 11 | margin-bottom: @line-height-computed; 12 | border: 1px solid transparent; 13 | border-radius: @alert-border-radius; 14 | 15 | // Headings for larger alerts 16 | h4 { 17 | margin-top: 0; 18 | // Specified for the h4 to prevent conflicts of changing @headings-color 19 | color: inherit; 20 | } 21 | // Provide class for links that match alerts 22 | .alert-link { 23 | font-weight: @alert-link-font-weight; 24 | } 25 | 26 | // Improve alignment and spacing of inner content 27 | > p, 28 | > ul { 29 | margin-bottom: 0; 30 | } 31 | > p + p { 32 | margin-top: 5px; 33 | } 34 | } 35 | 36 | // Dismissable alerts 37 | // 38 | // Expand the right padding and account for the close button's positioning. 39 | 40 | .alert-dismissable { 41 | padding-right: (@alert-padding + 20); 42 | 43 | // Adjust close link position 44 | .close { 45 | position: relative; 46 | top: -2px; 47 | right: -21px; 48 | color: inherit; 49 | } 50 | } 51 | 52 | // Alternate styles 53 | // 54 | // Generate contextual modifier classes for colorizing the alert. 55 | 56 | .alert-success { 57 | .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); 58 | } 59 | .alert-info { 60 | .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); 61 | } 62 | .alert-warning { 63 | .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); 64 | } 65 | .alert-danger { 66 | .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); 67 | } 68 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/badges.less: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base classes 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: @font-size-small; 12 | font-weight: @badge-font-weight; 13 | color: @badge-color; 14 | line-height: @badge-line-height; 15 | vertical-align: baseline; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: @badge-bg; 19 | border-radius: @badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | 26 | // Quick fix for badges in buttons 27 | .btn & { 28 | position: relative; 29 | top: -1px; 30 | } 31 | .btn-xs & { 32 | top: 0; 33 | padding: 1px 5px; 34 | } 35 | } 36 | 37 | // Hover state, but only for links 38 | a.badge { 39 | &:hover, 40 | &:focus { 41 | color: @badge-link-hover-color; 42 | text-decoration: none; 43 | cursor: pointer; 44 | } 45 | } 46 | 47 | // Account for counters in navs 48 | a.list-group-item.active > .badge, 49 | .nav-pills > .active > a > .badge { 50 | color: @badge-active-color; 51 | background-color: @badge-active-bg; 52 | } 53 | .nav-pills > li > a > .badge { 54 | margin-left: 3px; 55 | } 56 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/bootstrap.less: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "variables.less"; 3 | @import "mixins.less"; 4 | 5 | // Reset 6 | @import "normalize.less"; 7 | @import "print.less"; 8 | 9 | // Core CSS 10 | @import "scaffolding.less"; 11 | @import "type.less"; 12 | @import "code.less"; 13 | @import "grid.less"; 14 | @import "tables.less"; 15 | @import "forms.less"; 16 | @import "buttons.less"; 17 | 18 | // Components 19 | @import "component-animations.less"; 20 | @import "glyphicons.less"; 21 | @import "dropdowns.less"; 22 | @import "button-groups.less"; 23 | @import "input-groups.less"; 24 | @import "navs.less"; 25 | @import "navbar.less"; 26 | @import "breadcrumbs.less"; 27 | @import "pagination.less"; 28 | @import "pager.less"; 29 | @import "labels.less"; 30 | @import "badges.less"; 31 | @import "jumbotron.less"; 32 | @import "thumbnails.less"; 33 | @import "alerts.less"; 34 | @import "progress-bars.less"; 35 | @import "media.less"; 36 | @import "list-group.less"; 37 | @import "panels.less"; 38 | @import "wells.less"; 39 | @import "close.less"; 40 | 41 | // Components w/ JavaScript 42 | @import "modals.less"; 43 | @import "tooltip.less"; 44 | @import "popovers.less"; 45 | @import "carousel.less"; 46 | 47 | // Utility classes 48 | @import "utilities.less"; 49 | @import "responsive-utilities.less"; 50 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/breadcrumbs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; 8 | margin-bottom: @line-height-computed; 9 | list-style: none; 10 | background-color: @breadcrumb-bg; 11 | border-radius: @border-radius-base; 12 | 13 | > li { 14 | display: inline-block; 15 | 16 | + li:before { 17 | content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space 18 | padding: 0 5px; 19 | color: @breadcrumb-color; 20 | } 21 | } 22 | 23 | > .active { 24 | color: @breadcrumb-active-color; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/buttons.less: -------------------------------------------------------------------------------- 1 | // 2 | // Buttons 3 | // -------------------------------------------------- 4 | 5 | // Base styles 6 | // -------------------------------------------------- 7 | 8 | .btn { 9 | display: inline-block; 10 | margin-bottom: 0; // For input.btn 11 | font-weight: @btn-font-weight; 12 | text-align: center; 13 | vertical-align: middle; 14 | cursor: pointer; 15 | background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 16 | border: 1px solid transparent; 17 | white-space: nowrap; 18 | .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base); 19 | .user-select(none); 20 | 21 | &, 22 | &:active, 23 | &.active { 24 | &:focus { 25 | .tab-focus(); 26 | } 27 | } 28 | 29 | &:hover, 30 | &:focus { 31 | color: @btn-default-color; 32 | text-decoration: none; 33 | } 34 | 35 | &:active, 36 | &.active { 37 | outline: 0; 38 | background-image: none; 39 | .box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125)); 40 | } 41 | 42 | &.disabled, 43 | &[disabled], 44 | fieldset[disabled] & { 45 | cursor: not-allowed; 46 | pointer-events: none; // Future-proof disabling of clicks 47 | .opacity(.65); 48 | .box-shadow(none); 49 | } 50 | } 51 | 52 | // Alternate buttons 53 | // -------------------------------------------------- 54 | 55 | .btn-default { 56 | .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); 57 | } 58 | 59 | .btn-primary { 60 | .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); 61 | } 62 | 63 | // Success appears as green 64 | .btn-success { 65 | .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); 66 | } 67 | 68 | // Info appears as blue-green 69 | .btn-info { 70 | .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); 71 | } 72 | 73 | // Warning appears as orange 74 | .btn-warning { 75 | .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); 76 | } 77 | 78 | // Danger and error appear as red 79 | .btn-danger { 80 | .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); 81 | } 82 | 83 | // Link buttons 84 | // ------------------------- 85 | 86 | // Make a button look and behave like a link 87 | .btn-link { 88 | color: @link-color; 89 | font-weight: normal; 90 | cursor: pointer; 91 | border-radius: 0; 92 | 93 | &, 94 | &:active, 95 | &[disabled], 96 | fieldset[disabled] & { 97 | background-color: transparent; 98 | .box-shadow(none); 99 | } 100 | &, 101 | &:hover, 102 | &:focus, 103 | &:active { 104 | border-color: transparent; 105 | } 106 | &:hover, 107 | &:focus { 108 | color: @link-hover-color; 109 | text-decoration: underline; 110 | background-color: transparent; 111 | } 112 | &[disabled], 113 | fieldset[disabled] & { 114 | &:hover, 115 | &:focus { 116 | color: @btn-link-disabled-color; 117 | text-decoration: none; 118 | } 119 | } 120 | } 121 | 122 | // Button Sizes 123 | // -------------------------------------------------- 124 | 125 | .btn-lg { 126 | // line-height: ensure even-numbered height of button next to large input 127 | .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); 128 | } 129 | 130 | .btn-sm { 131 | // line-height: ensure proper height of button next to small input 132 | .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); 133 | } 134 | 135 | .btn-xs { 136 | .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small); 137 | } 138 | 139 | // Block button 140 | // -------------------------------------------------- 141 | 142 | .btn-block { 143 | display: block; 144 | width: 100%; 145 | padding-left: 0; 146 | padding-right: 0; 147 | } 148 | 149 | // Vertically space out multiple block buttons 150 | .btn-block + .btn-block { 151 | margin-top: 5px; 152 | } 153 | 154 | // Specificity overrides 155 | input[type="submit"], 156 | input[type="reset"], 157 | input[type="button"] { 158 | &.btn-block { 159 | width: 100%; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/close.less: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: (@font-size-base * 1.5); 9 | font-weight: @close-font-weight; 10 | line-height: 1; 11 | color: @close-color; 12 | text-shadow: @close-text-shadow; 13 | .opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: @close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | .opacity(.5); 21 | } 22 | 23 | // Additional properties for button version 24 | // iOS requires the button element instead of an anchor tag. 25 | // If you want the anchor version, it requires `href="#"`. 26 | button& { 27 | padding: 0; 28 | cursor: pointer; 29 | background: transparent; 30 | border: 0; 31 | -webkit-appearance: none; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/code.less: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and block) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | kbd, 9 | pre, 10 | samp { 11 | font-family: @font-family-monospace; 12 | } 13 | 14 | // Inline code 15 | code { 16 | padding: 2px 4px; 17 | font-size: 90%; 18 | color: @code-color; 19 | background-color: @code-bg; 20 | white-space: nowrap; 21 | border-radius: @border-radius-base; 22 | } 23 | 24 | // User input typically entered via keyboard 25 | kbd { 26 | padding: 2px 4px; 27 | font-size: 90%; 28 | color: @kbd-color; 29 | background-color: @kbd-bg; 30 | border-radius: @border-radius-small; 31 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); 32 | } 33 | 34 | // Blocks of code 35 | pre { 36 | display: block; 37 | padding: ((@line-height-computed - 1) / 2); 38 | margin: 0 0 (@line-height-computed / 2); 39 | font-size: (@font-size-base - 1); // 14px to 13px 40 | line-height: @line-height-base; 41 | word-break: break-all; 42 | word-wrap: break-word; 43 | color: @pre-color; 44 | background-color: @pre-bg; 45 | border: 1px solid @pre-border-color; 46 | border-radius: @border-radius-base; 47 | 48 | // Account for some code outputs that place code tags in pre tags 49 | code { 50 | padding: 0; 51 | font-size: inherit; 52 | color: inherit; 53 | white-space: pre-wrap; 54 | background-color: transparent; 55 | border-radius: 0; 56 | } 57 | } 58 | 59 | // Enable scrollable blocks of code 60 | .pre-scrollable { 61 | max-height: @pre-scrollable-max-height; 62 | overflow-y: scroll; 63 | } 64 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/component-animations.less: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | .transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | &.in { 21 | display: block; 22 | } 23 | } 24 | .collapsing { 25 | position: relative; 26 | height: 0; 27 | overflow: hidden; 28 | .transition(height .35s ease); 29 | } 30 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/grid.less: -------------------------------------------------------------------------------- 1 | // 2 | // Grid system 3 | // -------------------------------------------------- 4 | 5 | // Container widths 6 | // 7 | // Set the container width, and override it for fixed navbars in media queries. 8 | 9 | .container { 10 | .container-fixed(); 11 | 12 | @media (min-width: @screen-sm-min) { 13 | width: @container-sm; 14 | } 15 | @media (min-width: @screen-md-min) { 16 | width: @container-md; 17 | } 18 | @media (min-width: @screen-lg-min) { 19 | width: @container-lg; 20 | } 21 | } 22 | 23 | // Fluid container 24 | // 25 | // Utilizes the mixin meant for fixed width containers, but without any defined 26 | // width for fluid, full width layouts. 27 | 28 | .container-fluid { 29 | .container-fixed(); 30 | } 31 | 32 | // Row 33 | // 34 | // Rows contain and clear the floats of your columns. 35 | 36 | .row { 37 | .make-row(); 38 | } 39 | 40 | // Columns 41 | // 42 | // Common styles for small and large grid columns 43 | 44 | .make-grid-columns(); 45 | 46 | // Extra small grid 47 | // 48 | // Columns, offsets, pushes, and pulls for extra small devices like 49 | // smartphones. 50 | 51 | .make-grid(xs); 52 | 53 | // Small grid 54 | // 55 | // Columns, offsets, pushes, and pulls for the small device range, from phones 56 | // to tablets. 57 | 58 | @media (min-width: @screen-sm-min) { 59 | .make-grid(sm); 60 | } 61 | 62 | // Medium grid 63 | // 64 | // Columns, offsets, pushes, and pulls for the desktop device range. 65 | 66 | @media (min-width: @screen-md-min) { 67 | .make-grid(md); 68 | } 69 | 70 | // Large grid 71 | // 72 | // Columns, offsets, pushes, and pulls for the large desktop device range. 73 | 74 | @media (min-width: @screen-lg-min) { 75 | .make-grid(lg); 76 | } 77 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/jumbotron.less: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding: @jumbotron-padding; 8 | margin-bottom: @jumbotron-padding; 9 | color: @jumbotron-color; 10 | background-color: @jumbotron-bg; 11 | 12 | h1, 13 | .h1 { 14 | color: @jumbotron-heading-color; 15 | } 16 | p { 17 | margin-bottom: (@jumbotron-padding / 2); 18 | font-size: @jumbotron-font-size; 19 | font-weight: 200; 20 | } 21 | 22 | .container & { 23 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container 24 | } 25 | 26 | .container { 27 | max-width: 100%; 28 | } 29 | 30 | @media screen and (min-width: @screen-sm-min) { 31 | padding-top: (@jumbotron-padding * 1.6); 32 | padding-bottom: (@jumbotron-padding * 1.6); 33 | 34 | .container & { 35 | padding-left: (@jumbotron-padding * 2); 36 | padding-right: (@jumbotron-padding * 2); 37 | } 38 | 39 | h1, 40 | .h1 { 41 | font-size: (@font-size-base * 4.5); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/labels.less: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: @label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // Add hover effects, but only for links 18 | &[href] { 19 | &:hover, 20 | &:focus { 21 | color: @label-link-hover-color; 22 | text-decoration: none; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | // Empty labels collapse automatically (not available in IE8) 28 | &:empty { 29 | display: none; 30 | } 31 | 32 | // Quick fix for labels in buttons 33 | .btn & { 34 | position: relative; 35 | top: -1px; 36 | } 37 | } 38 | 39 | // Colors 40 | // Contextual variations (linked labels get darker on :hover) 41 | 42 | .label-default { 43 | .label-variant(@label-default-bg); 44 | } 45 | 46 | .label-primary { 47 | .label-variant(@label-primary-bg); 48 | } 49 | 50 | .label-success { 51 | .label-variant(@label-success-bg); 52 | } 53 | 54 | .label-info { 55 | .label-variant(@label-info-bg); 56 | } 57 | 58 | .label-warning { 59 | .label-variant(@label-warning-bg); 60 | } 61 | 62 | .label-danger { 63 | .label-variant(@label-danger-bg); 64 | } 65 | -------------------------------------------------------------------------------- /server/public/css/lib/bootstrap/list-group.less: -------------------------------------------------------------------------------- 1 | // 2 | // List groups 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | // 8 | // Easily usable on