Repository: pboyer/flood Branch: master Commit: c86ab0b96034 Files: 194 Total size: 1.2 MB Directory structure: gitextract_88nis6rk/ ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── app/ │ ├── .htaccess │ ├── 404.html │ ├── app.html │ ├── customizer.html │ ├── index.html │ ├── package.json │ ├── robots.txt │ ├── scripts/ │ │ ├── collections/ │ │ │ ├── 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/ │ │ ├── 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 ├── 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/ │ │ │ │ │ ├── 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 │ │ │ │ └── bootstrap-social.less │ │ │ ├── styles.less │ │ │ └── themes/ │ │ │ ├── default.less │ │ │ ├── flatly.less │ │ │ └── ios7.less │ │ ├── fonts/ │ │ │ └── FontAwesome.otf │ │ └── js/ │ │ ├── application.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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bowerrc ================================================ { "directory": "app/bower_components" } ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = false [*] # Change these settings to your own preference indent_style = space indent_size = 2 # We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .gitattributes ================================================ * text=auto ================================================ FILE: .gitignore ================================================ node_modules temp .sass-cache app/bower_components .tmp/ dist_desktop .DS_Store dist ================================================ FILE: Gruntfile.js ================================================ 'use strict'; var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet; var mountFolder = function (connect, dir) { return connect.static(require('path').resolve(dir)); }; // # Globbing // for performance reasons we're only matching one level down: // 'test/spec/{,*/}*.js' // use this if you want to match all subfolders: // 'test/spec/**/*.js' // templateFramework: 'lodash' module.exports = function (grunt) { // load all grunt tasks require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); // configurable paths var yeomanConfig = { app: 'app', dist: 'dist' }; grunt.initConfig({ yeoman: yeomanConfig, clean: { dist: ['.tmp', '<%= yeoman.dist %>/*'], server: '.tmp' }, // This task uses James Burke's excellent r.js AMD builder to take all // modules and concatenate them into a single file. requirejs: { release: { options: { mainConfigFile: "app/scripts/config.js", include: ["main"], insertRequire: ["main"], out: "dist/source.min.js", optimize: "uglify", // Since we bootstrap with nested `require` calls this option allows // R.js to find them. findNestedDependencies: true, // Include a minimal AMD implementation shim. name: "almond", // Setting the base url to the distribution directory allows the // Uglify minification process to correctly map paths for Source // Maps. baseUrl: "app/scripts", // Wrap everything in an IIFE. wrap: true, // Do not preserve any license comments when working with source // maps. These options are incompatible. preserveLicenseComments: true } }, customizer: { options: { mainConfigFile: "app/scripts/config.js", include: ["customizer"], insertRequire: ["customizer"], out: "dist/customizer.min.js", optimize: "uglify", findNestedDependencies: true, name: "almond", baseUrl: "app/scripts", wrap: true, preserveLicenseComments: true } } }, imagemin: { dist: { files: [{ expand: true, cwd: '<%= yeoman.app %>/images', src: '{,*/}*.{png,jpg,jpeg}', dest: '<%= yeoman.dist %>/images' }] }, jqueryui: { files: [{ expand: true, flatten: true, cwd: '<%= yeoman.app %>', src: 'bower_components/jquery.ui/themes/base/images/*.png', dest: '<%= yeoman.dist %>/images' }] } }, cssmin: { dist: { files: { '<%= yeoman.dist %>/app.min.css': [ '.tmp/styles/{,*/}*.css', '<%= yeoman.app %>/bower_components/jquery.ui/themes/base/*.css', '<%= yeoman.app %>/bower_components/components-font-awesome/css/font-awesome.min.css', '<%= yeoman.app %>/styles/bootstrap.css', '<%= yeoman.app %>/bower_components/CodeMirror/lib/codemirror.css', '<%= yeoman.app %>/bower_components/CodeMirror/addon/hint/show-hint.css', '<%= yeoman.app %>/styles/main.css', ], '<%= yeoman.dist %>/customizer.min.css': [ '.tmp/styles/{,*/}*.css', '<%= yeoman.app %>/bower_components/jquery.ui/themes/base/*.css', '<%= yeoman.app %>/bower_components/components-font-awesome/css/font-awesome.min.css', '<%= yeoman.app %>/styles/bootstrap.css', '<%= yeoman.app %>/styles/main.css', '<%= yeoman.app %>/styles/customizer.css' ] } } }, copy: { dist: { files: [{ expand: true, dot: true, cwd: '<%= yeoman.app %>', dest: '<%= yeoman.dist %>', src: [ '*.{ico,txt}', '.htaccess', '*.html', '*.json', 'images/{,*/}*.{webp,gif,png}', 'bower_components/jquery/jquery.js', 'bower_components/components-font-awesome/css/font-awesome.min.css', 'bower_components/components-font-awesome/fonts/*.{ttf,eot,svg,woff,otf}', 'scripts/lib/flood/*.js' ] }] }, fonts: { files: [{ expand: true, dot: true, flatten: true, cwd: '<%= yeoman.app %>', dest: '<%= yeoman.dist %>/fonts', src: [ 'bower_components/components-font-awesome/fonts/*.{ttf,eot,svg,woff,otf}' ] }] } }, bower: { all: { rjsConfig: '<%= yeoman.app %>/scripts/main.js' } }, processhtml: { release: { files: { "dist/app.html": ["app/app.html"], "dist/customizer.html": ["app/customizer.html"] } } }, nodewebkit: { options: { version: '0.8.1', build_dir: './dist_desktop', // Where the build version of my node-webkit app is saved mac: true, // We want to build it for mac win: true, // We want to build it for win linux32: false, // We don't need linux32 linux64: false // We don't need linux64 }, src: ['./dist/**'] // Your node-wekit app } }); grunt.registerTask('build', [ 'clean:dist', 'requirejs', 'imagemin', 'cssmin', 'copy', 'processhtml' ]); grunt.registerTask('desktop', [ 'clean:dist', 'requirejs', 'imagemin', 'cssmin', 'copy', 'processhtml', 'nodewebkit' ]); grunt.registerTask('default', [ 'build' ]); }; ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) Peter Boyer 2013 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![Image](https://raw.github.com/pboyer/flood/master/extra/screenshot.png) ## flood ### What is it? 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). ### Features * Constructive solid geometry - Cube, cylinder, sphere, union, intersect, subtract * Formula node - evaluate javascript in a node * Never save your work * Fast node library search * Undo/redo across user sessions * Copy/paste * Multiple workspaces * Async evaluation * Partial function application * "Always on" continuous execution 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. ### Getting started 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. flood uses [require.js](http://requirejs.org/) to manage dependencies between JavaScript files and [backbone.js](http://backbonejs.org/) to stick it all together. flood also has a server written in node.js that handles user authentication and model synchronization. #### Installing dependencies for the app To install all of the dependencies for the flood app, run the following commands in the root directory: bower install npm install This will install all of the development dependencies for Grunt and all of the public dependencies with bower. #### Installing dependencies for the server To install all of the node.js dependencies for the flood server, run the following commands in the "server" directory: npm install 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). #### Running the server For development, I recommend using the great nodemon tool: npm install -g nodemon Go to the "server" directory and run: nodemon app.js You can also run the server using: node app.js #### Building for the web (outdated) The entire app can be compressed into lightweight, minified, and concatenated css, js, and html files using Grunt: grunt #### Building for the desktop flood can be used as a standalone application via node-webkit. Just do this: grunt desktop This will generate binaries for use on Mac and Windows in the dist_desktop folder. ### License The MIT License (MIT) Copyright (c) Peter Boyer 2014-2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: app/.htaccess ================================================ # Apache configuration file # httpd.apache.org/docs/2.2/mod/quickreference.html # Note .htaccess files are an overhead, this logic should be in your Apache # config if possible: httpd.apache.org/docs/2.2/howto/htaccess.html # Techniques in here adapted from all over, including: # Kroc Camen: camendesign.com/.htaccess # perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/ # Sample .htaccess file of CMS MODx: modxcms.com # ---------------------------------------------------------------------- # Better website experience for IE users # ---------------------------------------------------------------------- # Force the latest IE version, in various cases when it may fall back to IE7 mode # github.com/rails/rails/commit/123eb25#commitcomment-118920 # Use ChromeFrame if it's installed for a better experience for the poor IE folk Header set X-UA-Compatible "IE=Edge,chrome=1" # mod_headers can't match by content-type, but we don't want to send this header on *everything*... Header unset X-UA-Compatible # ---------------------------------------------------------------------- # Cross-domain AJAX requests # ---------------------------------------------------------------------- # Serve cross-domain Ajax requests, disabled by default. # enable-cors.org # code.google.com/p/html5security/wiki/CrossOriginRequestSecurity # # Header set Access-Control-Allow-Origin "*" # # ---------------------------------------------------------------------- # CORS-enabled images (@crossorigin) # ---------------------------------------------------------------------- # Send CORS headers if browsers request them; enabled by default for images. # developer.mozilla.org/en/CORS_Enabled_Image # blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html # hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ # wiki.mozilla.org/Security/Reviews/crossoriginAttribute # mod_headers, y u no match by Content-Type?! SetEnvIf Origin ":" IS_CORS Header set Access-Control-Allow-Origin "*" env=IS_CORS # ---------------------------------------------------------------------- # Webfont access # ---------------------------------------------------------------------- # Allow access from all domains for webfonts. # Alternatively you could only whitelist your # subdomains like "subdomain.example.com". Header set Access-Control-Allow-Origin "*" # ---------------------------------------------------------------------- # Proper MIME type for all files # ---------------------------------------------------------------------- # JavaScript # Normalize to standard type (it's sniffed in IE anyways) # tools.ietf.org/html/rfc4329#section-7.2 AddType application/javascript js jsonp AddType application/json json # Audio AddType audio/ogg oga ogg AddType audio/mp4 m4a f4a f4b # Video AddType video/ogg ogv AddType video/mp4 mp4 m4v f4v f4p AddType video/webm webm AddType video/x-flv flv # SVG # Required for svg webfonts on iPad # twitter.com/FontSquirrel/status/14855840545 AddType image/svg+xml svg svgz AddEncoding gzip svgz # Webfonts AddType application/vnd.ms-fontobject eot AddType application/x-font-ttf ttf ttc AddType font/opentype otf AddType application/x-font-woff woff # Assorted types AddType image/x-icon ico AddType image/webp webp AddType text/cache-manifest appcache manifest AddType text/x-component htc AddType application/xml rss atom xml rdf AddType application/x-chrome-extension crx AddType application/x-opera-extension oex AddType application/x-xpinstall xpi AddType application/octet-stream safariextz AddType application/x-web-app-manifest+json webapp AddType text/x-vcard vcf AddType application/x-shockwave-flash swf AddType text/vtt vtt # ---------------------------------------------------------------------- # Allow concatenation from within specific js and css files # ---------------------------------------------------------------------- # e.g. Inside of script.combined.js you could have # # # and they would be included into this single file. # This is not in use in the boilerplate as it stands. You may # choose to use this technique if you do not have a build process. # # Options +Includes # AddOutputFilterByType INCLUDES application/javascript application/json # SetOutputFilter INCLUDES # # # Options +Includes # AddOutputFilterByType INCLUDES text/css # SetOutputFilter INCLUDES # # ---------------------------------------------------------------------- # Gzip compression # ---------------------------------------------------------------------- # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding # HTML, TXT, CSS, JavaScript, JSON, XML, HTC: FilterDeclare COMPRESS FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml FilterProvider COMPRESS DEFLATE resp=Content-Type $text/x-component FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml FilterProvider COMPRESS DEFLATE resp=Content-Type $application/vnd.ms-fontobject FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml FilterProvider COMPRESS DEFLATE resp=Content-Type $image/x-icon FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype FilterChain COMPRESS FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no # Legacy versions of Apache AddOutputFilterByType DEFLATE text/html text/plain text/css application/json AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE text/xml application/xml text/x-component AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml AddOutputFilterByType DEFLATE image/x-icon image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype # ---------------------------------------------------------------------- # Expires headers (for better cache control) # ---------------------------------------------------------------------- # These are pretty far-future expires headers. # They assume you control versioning with filename-based cache busting # Additionally, consider that outdated proxies may miscache # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ # If you don't use filenames to version, lower the CSS and JS to something like # "access plus 1 week". ExpiresActive on # Perhaps better to whitelist expires rules? Perhaps. ExpiresDefault "access plus 1 month" # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) ExpiresByType text/cache-manifest "access plus 0 seconds" # Your document html ExpiresByType text/html "access plus 0 seconds" # Data ExpiresByType text/xml "access plus 0 seconds" ExpiresByType application/xml "access plus 0 seconds" ExpiresByType application/json "access plus 0 seconds" # Feed ExpiresByType application/rss+xml "access plus 1 hour" ExpiresByType application/atom+xml "access plus 1 hour" # Favicon (cannot be renamed) ExpiresByType image/x-icon "access plus 1 week" # Media: images, video, audio ExpiresByType image/gif "access plus 1 month" ExpiresByType image/png "access plus 1 month" ExpiresByType image/jpeg "access plus 1 month" ExpiresByType video/ogg "access plus 1 month" ExpiresByType audio/ogg "access plus 1 month" ExpiresByType video/mp4 "access plus 1 month" ExpiresByType video/webm "access plus 1 month" # HTC files (css3pie) ExpiresByType text/x-component "access plus 1 month" # Webfonts ExpiresByType application/x-font-ttf "access plus 1 month" ExpiresByType font/opentype "access plus 1 month" ExpiresByType application/x-font-woff "access plus 1 month" ExpiresByType image/svg+xml "access plus 1 month" ExpiresByType application/vnd.ms-fontobject "access plus 1 month" # CSS and JavaScript ExpiresByType text/css "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" # ---------------------------------------------------------------------- # Prevent mobile network providers from modifying your site # ---------------------------------------------------------------------- # The following header prevents modification of your code over 3G on some # European providers. # This is the official 'bypass' suggested by O2 in the UK. # # Header set Cache-Control "no-transform" # # ---------------------------------------------------------------------- # ETag removal # ---------------------------------------------------------------------- # FileETag None is not enough for every server. Header unset ETag # Since we're sending far-future expires, we don't need ETags for # static content. # developer.yahoo.com/performance/rules.html#etags FileETag None # ---------------------------------------------------------------------- # Stop screen flicker in IE on CSS rollovers # ---------------------------------------------------------------------- # The following directives stop screen flicker in IE on CSS rollovers - in # combination with the "ExpiresByType" rules for images (see above). # BrowserMatch "MSIE" brokenvary=1 # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 # BrowserMatch "Opera" !brokenvary # SetEnvIf brokenvary 1 force-no-vary # ---------------------------------------------------------------------- # Set Keep-Alive Header # ---------------------------------------------------------------------- # Keep-Alive allows the server to send multiple requests through one # TCP-connection. Be aware of possible disadvantages of this setting. Turn on # if you serve a lot of static content. # # Header set Connection Keep-Alive # # ---------------------------------------------------------------------- # Cookie setting from iframes # ---------------------------------------------------------------------- # Allow cookies to be set from iframes (for IE only) # If needed, specify a path or regex in the Location directive. # # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" # # ---------------------------------------------------------------------- # Start rewrite engine # ---------------------------------------------------------------------- # Turning on the rewrite engine is necessary for the following rules and # features. FollowSymLinks must be enabled for this to work. # Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN # If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where # 'foo' is your directory. # If your web host doesn't allow the FollowSymlinks option, you may need to # comment it out and use `Options +SymLinksOfOwnerMatch`, but be aware of the # performance impact: http://goo.gl/Mluzd Options +FollowSymlinks # Options +SymLinksIfOwnerMatch Options +FollowSymlinks RewriteEngine On # RewriteBase / # ---------------------------------------------------------------------- # Suppress or force the "www." at the beginning of URLs # ---------------------------------------------------------------------- # The same content should never be available under two different URLs - # especially not with and without "www." at the beginning, since this can cause # SEO problems (duplicate content). That's why you should choose one of the # alternatives and redirect the other one. # By default option 1 (no "www.") is activated. # no-www.org/faq.php?q=class_b # If you'd prefer to use option 2, just comment out all option 1 lines # and uncomment option 2. # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! # ---------------------------------------------------------------------- # Option 1: # Rewrite "www.example.com -> example.com". RewriteCond %{HTTPS} !=on RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] # ---------------------------------------------------------------------- # Option 2: # Rewrite "example.com -> www.example.com". # Be aware that the following rule might not be a good idea if you use "real" # subdomains for certain parts of your website. # # RewriteCond %{HTTPS} !=on # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] # # ---------------------------------------------------------------------- # Built-in filename-based cache busting # ---------------------------------------------------------------------- # If you're not using the build script to manage your filename version revving, # you might want to consider enabling this, which will route requests for # /css/style.20110203.css to /css/style.css # To understand why this is important and a better idea than all.css?v1231, # read: github.com/h5bp/html5-boilerplate/wiki/cachebusting # # RewriteCond %{REQUEST_FILENAME} !-f # RewriteCond %{REQUEST_FILENAME} !-d # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] # # ---------------------------------------------------------------------- # Prevent SSL cert warnings # ---------------------------------------------------------------------- # Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent # https://www.example.com when your cert only allows https://secure.example.com # # RewriteCond %{SERVER_PORT} !^443 # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] # # ---------------------------------------------------------------------- # Prevent 404 errors for non-existing redirected folders # ---------------------------------------------------------------------- # without -MultiViews, Apache will give a 404 for a rewrite if a folder of the # same name does not exist. # webmasterworld.com/apache/3808792.htm Options -MultiViews # ---------------------------------------------------------------------- # Custom 404 page # ---------------------------------------------------------------------- # You can add custom pages to handle 500 or 403 pretty easily, if you like. # If you are hosting your site in subdirectory, adjust this accordingly # e.g. ErrorDocument 404 /subdir/404.html ErrorDocument 404 /404.html # ---------------------------------------------------------------------- # UTF-8 encoding # ---------------------------------------------------------------------- # Use UTF-8 encoding for anything served text/plain or text/html AddDefaultCharset utf-8 # Force UTF-8 for a number of file formats AddCharset utf-8 .atom .css .js .json .rss .vtt .xml # ---------------------------------------------------------------------- # A little more security # ---------------------------------------------------------------------- # To avoid displaying the exact version number of Apache being used, add the # following to httpd.conf (it will not work in .htaccess): # ServerTokens Prod # "-Indexes" will have Apache block users from browsing folders without a # default document Usually you should leave this activated, because you # shouldn't allow everybody to surf through every folder on your server (which # includes rather private places like CMS system folders). Options -Indexes # Block access to "hidden" directories or files whose names begin with a # period. This includes directories used by version control systems such as # Subversion or Git. RewriteCond %{SCRIPT_FILENAME} -d [OR] RewriteCond %{SCRIPT_FILENAME} -f RewriteRule "(^|/)\." - [F] # Block access to backup and source files. These files may be left by some # text/html editors and pose a great security danger, when anyone can access # them. Order allow,deny Deny from all Satisfy All # If your server is not already configured as such, the following directive # should be uncommented in order to set PHP's register_globals option to OFF. # This closes a major security hole that is abused by most XSS (cross-site # scripting) attacks. For more information: http://php.net/register_globals # # IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS: # # Your server does not allow PHP directives to be set via .htaccess. In that # case you must make this change in your php.ini file instead. If you are # using a commercial web host, contact the administrators for assistance in # doing this. Not all servers allow local php.ini files, and they should # include all PHP configurations (not just this one), or you will effectively # reset everything to PHP defaults. Consult www.php.net for more detailed # information about setting PHP directives. # php_flag register_globals Off # Rename session cookie to something else, than PHPSESSID # php_value session.name sid # Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.) # php_flag magic_quotes_gpc Off # Do not show you are using PHP # Note: Move this line to php.ini since it won't work in .htaccess # php_flag expose_php Off # Level of log detail - log all errors # php_value error_reporting -1 # Write errors to log file # php_flag log_errors On # Do not display errors in browser (production - Off, development - On) # php_flag display_errors Off # Do not display startup errors (production - Off, development - On) # php_flag display_startup_errors Off # Format errors in plain text # Note: Leave this setting 'On' for xdebug's var_dump() output # php_flag html_errors Off # Show multiple occurrence of error # php_flag ignore_repeated_errors Off # Show same errors from different sources # php_flag ignore_repeated_source Off # Size limit for error messages # php_value log_errors_max_len 1024 # Don't precede error with string (doesn't accept empty string, use whitespace if you need) # php_value error_prepend_string " " # Don't prepend to error (doesn't accept empty string, use whitespace if you need) # php_value error_append_string " " # Increase cookie security php_value session.cookie_httponly true ================================================ FILE: app/404.html ================================================ Page Not Found :(

Not found :(

Sorry, but the page you were trying to view does not exist.

It looks like this was the result of either:

================================================ FILE: app/app.html ================================================ Editor Beta - Flood
  • Project
  • Custom node
3D
flood
flood
EDITOR
================================================ FILE: app/customizer.html ================================================ Customizer - Flood
Powered by FLOOD
================================================ FILE: app/index.html ================================================ Welcome - Flood
flood
flood
EDITOR
3D Modeling & Printing
Visual Programming
Cloud-Based

Parametric solid modeling for 3D printing

Flood beta is a parametric solid modeler in your browser. It provides:

  • An intuitive, familiar modeling process
  • Watertight meshes suitable for 3D printing
  • STL export (accepted by Shapeways)

Further, because Flood is a fully-fledged programming language, you can create your own solid types and combine them however you want.

================================================ FILE: app/package.json ================================================ { "name": "flood", "version": "0.0.4", "description": "A visual programming language for JavaScript, based on Scheme", "main": "index.html", "window": { "toolbar": false, "frame": true, "width": 1280, "height": 960 } } ================================================ FILE: app/robots.txt ================================================ # robotstxt.org User-agent: * ================================================ FILE: app/scripts/collections/Connections.js ================================================ define(['backbone', 'Connection'], function(Backbone, Connection) { return Backbone.Collection.extend({ model: Connection }); }); ================================================ FILE: app/scripts/collections/Nodes.js ================================================ define(['backbone', 'Node'], function(Backbone, Node) { return Backbone.Collection.extend({ model: Node, initialDragPositions: [], toJSON: function(){ return this.models.map(function(x){ return x.serialize(); }); }, selectAll: function() { this.where({selected: false}).forEach( function(e){ e.set({selected: true}) } ); }, deselectAll: function() { this.where({selected: true}).forEach( function(e){ e.set('selected', false); }); }, moveSelected: function(offset, masterEle) { var that = this; var count = 0; this.where({selected: true}).forEach( function(e, i){ if (e === masterEle) return; var initialPos = that.initialDragPositions[count++]; e.set('position', [initialPos[0] + offset[0], initialPos[1] + offset[1]]); }); }, startDragging: function(masterEle){ this.initialDragPositions = []; var that = this; this.where({selected: true}).forEach( function(e){ if (e === masterEle) return; that.initialDragPositions.push(e.get('position')); }); } }); }); ================================================ FILE: app/scripts/collections/SearchElements.js ================================================ define(['backbone', 'SearchElement', 'FLOOD'], function(Backbone, SearchElement, FLOOD) { return Backbone.Collection.extend({ model: SearchElement, initialize: function(atts) { this.app = atts.app; }, addCustomNode: function(customNode){ var match = this.where({ functionId: customNode.functionId }); if (match) this.remove( match ); this.add(new SearchElement({name: customNode.functionName, functionName: customNode.functionName, isCustomNode: true, functionId: customNode.functionId, app: this.app, numInputs: customNode.inputs.length, numOutputs: customNode.outputs.length })); }, fetch: function() { this.models.length = 0; for (var key in FLOOD.nodeTypes){ this.models.push( new SearchElement({name: key, app: this.app}) ); } } }); }); ================================================ FILE: app/scripts/collections/WorkspaceBrowserElements.js ================================================ define(['backbone', 'WorkspaceBrowserElement'], function(Backbone, WorkspaceBrowserElement) { return Backbone.Collection.extend({ url: '/ws', model: WorkspaceBrowserElement }); }); ================================================ FILE: app/scripts/collections/Workspaces.js ================================================ define(['backbone', 'Workspace'], function(Backbone, Workspace) { return Backbone.Collection.extend({ model: Workspace }); }); ================================================ FILE: app/scripts/config.js ================================================ /*global require*/ 'use strict'; require.config({ shim: { underscore: { exports: '_' }, backbone: { deps: [ 'underscore', 'jquery' ], exports: 'Backbone' }, List: { deps: [ 'jquery' ], exports: 'List' }, Three: { exports: 'Three' }, CSG: { exports: 'CSG' }, FLOODCSG: { deps: ['CSG'], exports: 'FLOODCSG' }, Viewport: { deps: [ 'Three', 'OrbitControls' ], exports: 'Viewport' }, OrbitControls: { deps: [ 'Three' ], exports: 'OrbitControls' }, jqueryuicore: { deps: [ 'jquery' ], exports: 'jqueryuicore' }, jqueryuiwidget: { deps: [ 'jquery' ], exports: 'jqueryuiwidget' }, jqueryuimouse: { deps: [ 'jquery', 'jqueryuiwidget' ], exports: 'jqueryuimouse' }, jqueryuitouchpunch: { deps: [ 'jquery', 'jqueryuicore', 'jqueryuimouse' ], exports: 'jqueryuitouchpunch' }, jqueryuislider: { deps: [ 'jquery', 'jqueryuitouchpunch', 'jqueryuimouse', 'jqueryuicore', 'jqueryuiwidget' ], exports: 'jqueryuislider' }, jqueryuidraggable: { deps: [ 'jquery', 'jqueryuitouchpunch', 'jqueryuimouse', 'jqueryuicore', 'jqueryuiwidget' ], exports: 'jqueryuidraggable' }, bootstrap: { deps: [ 'jquery' ], exports: 'bootstrap' }, almond: { deps: [ ], exports: 'almond' } }, packages: [{ name: "codemirror", location: "../bower_components/CodeMirror/", main: "lib/codemirror" }], paths: { // backbone collections Connections: 'collections/Connections', SearchElements: 'collections/SearchElements', Nodes: 'collections/Nodes', Workspaces: 'collections/Workspaces', WorkspaceBrowserElements: 'collections/WorkspaceBrowserElements', // backbone models // customizer CustomizerApp: 'models/customizer/CustomizerApp', // editor App: 'models/App', Connection: 'models/Connection', Marquee: 'models/Marquee', Node: 'models/Node', Search: 'models/Search', SearchElement: 'models/SearchElement', Workspace: 'models/Workspace', Runner: 'models/Runner', Help: 'models/Help', Feedback: 'models/Feedback', Share: 'models/Share', Login: 'models/Login', GeometryExport: 'models/GeometryExport', WorkspaceBrowserElement: 'models/WorkspaceBrowserElement', WorkspaceBrowser: 'models/WorkspaceBrowser', WorkspaceResolver: 'models/WorkspaceResolver', // backbone views // customizer BaseWidgetView: 'views/customizer/widgets/Base', GeometryWidgetView: 'views/customizer/widgets/Geometry', NumberWidgetView: 'views/customizer/widgets/Number', CustomizerAppView: 'views/customizer/CustomizerAppView', CustomizerHeaderView: 'views/customizer/CustomizerHeaderView', CustomizerViewerView: 'views/customizer/CustomizerViewerView', CustomizerWorkspaceView: 'views/customizer/CustomizerWorkspaceView', // editor AppView: 'views/AppView', ConnectionView: 'views/ConnectionView', MarqueeView: 'views/MarqueeView', SearchView: 'views/SearchView', WorkspaceControlsView: 'views/WorkspaceControlsView', SearchElementView: 'views/SearchElementView', WorkspaceView: 'views/WorkspaceView', WorkspaceTabView: 'views/WorkspaceTabView', HelpView: 'views/HelpView', FeedbackView: 'views/FeedbackView', ShareView: 'views/ShareView', LoginView: 'views/LoginView', WorkspaceBrowserElementView: 'views/WorkspaceBrowserElementView', WorkspaceBrowserView: 'views/WorkspaceBrowserView', // node backbone views NodeViewTypes: 'views/nodeviews/NodeViews', BaseNodeView: 'views/nodeviews/Base', WatchNodeView: 'views/nodeviews/Watch', NumNodeView: 'views/nodeviews/Num', ScriptView: 'views/nodeviews/Script', InputView: 'views/nodeviews/Input', OutputView: 'views/nodeviews/Output', CustomNodeView: 'views/nodeviews/CustomNode', ThreeCSGNodeView: 'views/nodeviews/ThreeCSG', OrbitControls: 'lib/OrbitControls', Viewport: 'lib/Viewport', FLOODCSG: 'lib/flood/flood_csg', FLOOD: 'lib/flood/flood', CSG: 'lib/flood/csg', scheme: 'lib/flood/scheme', // bower Hammer: '../bower_components/hammerjs/hammer', almond: '../bower_components/almond/almond', bootstrap: '../bower_components/bootstrap/dist/js/bootstrap', List: '../bower_components/listjs/dist/list.min', Three: '../bower_components/threejs/build/three.min', jqueryuitouchpunch: '../bower_components/jqueryui-touch-punch/jquery.ui.touch-punch', jqueryuislider: '../bower_components/jquery.ui/ui/jquery.ui.slider', jqueryuidraggable: '../bower_components/jquery.ui/ui/jquery.ui.draggable', jqueryuicore: '../bower_components/jquery.ui/ui/jquery.ui.core', jqueryuimouse: '../bower_components/jquery.ui/ui/jquery.ui.mouse', jqueryuiwidget: '../bower_components/jquery.ui/ui/jquery.ui.widget', jquery: '../bower_components/jquery/jquery.min', backbone: '../bower_components/backbone-amd/backbone-min', underscore: '../bower_components/underscore-amd/underscore-min', fastclick: '../bower_components/fastclick/lib/fastclick', FileSaver: '../bower_components/FileSaver/FileSaver' } }); ================================================ FILE: app/scripts/customizer.js ================================================ require(["config"], function() { require(['backbone', 'CustomizerApp', 'CustomizerAppView', 'Three', 'FLOODCSG', 'bootstrap' ], function (Backbone, CustomizerApp, CustomizerAppView, THREE) { var app = new CustomizerApp(); app.fetch({ error: function(result) { console.error('error'); console.error(result); }, success: function(){ } }); var appView = new CustomizerAppView({model: app}); }); }); ================================================ FILE: app/scripts/lib/OrbitControls.js ================================================ /** * @author qiao / https://github.com/qiao * @author mrdoob / http://mrdoob.com * @author alteredq / http://alteredqualia.com/ * @author WestLangley / http://github.com/WestLangley * @author erich666 / http://erichaines.com */ /*global THREE, console */ // This set of controls performs orbiting, dollying (zooming), and panning. It maintains // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is // supported. // // Orbit - left mouse / touch: one finger move // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish // Pan - right mouse, or arrow keys / touch: three finter swipe // // This is a drop-in replacement for (most) TrackballControls used in examples. // That is, include this js file and wherever you see: // controls = new THREE.TrackballControls( camera ); // controls.target.z = 150; // Simple substitute "OrbitControls" and the control should work as-is. THREE.OrbitControls = function ( object, domElement ) { this.object = object; this.domElement = ( domElement !== undefined ) ? domElement : document; // API // Set to false to disable this control this.enabled = true; // "target" sets the location of focus, where the control orbits around // and where it pans with respect to. this.target = new THREE.Vector3(); // center is old, deprecated; use "target" instead this.center = this.target; // This option actually enables dollying in and out; left as "zoom" for // backwards compatibility this.noZoom = false; this.zoomSpeed = 1.0; // Limits to how far you can dolly in and out this.minDistance = 0; this.maxDistance = Infinity; // Set to true to disable this control this.noRotate = false; this.rotateSpeed = 1.0; // Set to true to disable this control this.noPan = false; this.keyPanSpeed = 7.0; // pixels moved per arrow key push // Set to true to automatically rotate around the target this.autoRotate = false; this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 // How far you can orbit vertically, upper and lower limits. // Range is 0 to Math.PI radians. this.minPolarAngle = 0; // radians this.maxPolarAngle = Math.PI; // radians // Set to true to disable use of the keys this.noKeys = true; // The four arrow keys this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; //////////// // internals var scope = this; var EPS = 0.000001; var rotateStart = new THREE.Vector2(); var rotateEnd = new THREE.Vector2(); var rotateDelta = new THREE.Vector2(); var panStart = new THREE.Vector2(); var panEnd = new THREE.Vector2(); var panDelta = new THREE.Vector2(); var panOffset = new THREE.Vector3(); var offset = new THREE.Vector3(); var dollyStart = new THREE.Vector2(); var dollyEnd = new THREE.Vector2(); var dollyDelta = new THREE.Vector2(); var phiDelta = 0; var thetaDelta = 0; var scale = 1; var pan = new THREE.Vector3(); var lastPosition = new THREE.Vector3(); var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; var state = STATE.NONE; // for reset this.target0 = this.target.clone(); this.position0 = this.object.position.clone(); // so camera.up is the orbit axis var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); var quatInverse = quat.clone().inverse(); // events var changeEvent = { type: 'change' }; var startEvent = { type: 'start'}; var endEvent = { type: 'end'}; this.rotateLeft = function ( angle ) { if ( angle === undefined ) { angle = getAutoRotationAngle(); } thetaDelta -= angle; }; this.rotateUp = function ( angle ) { if ( angle === undefined ) { angle = getAutoRotationAngle(); } phiDelta -= angle; }; // pass in distance in world space to move left this.panLeft = function ( distance ) { var te = this.object.matrix.elements; // get X column of matrix panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); panOffset.multiplyScalar( - distance ); pan.add( panOffset ); }; // pass in distance in world space to move up this.panUp = function ( distance ) { var te = this.object.matrix.elements; // get Y column of matrix panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); panOffset.multiplyScalar( distance ); pan.add( panOffset ); }; // pass in x,y of change desired in pixel space, // right and down are positive this.pan = function ( deltaX, deltaY ) { var element = scope.domElement === document ? scope.domElement.body : scope.domElement; if ( scope.object.fov !== undefined ) { // perspective var position = scope.object.position; var offset = position.clone().sub( scope.target ); var targetDistance = offset.length(); // half of the fov is center to top of screen targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); // we actually don't use screenWidth, since perspective camera is fixed to screen height scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); } else if ( scope.object.top !== undefined ) { // orthographic scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); } else { // camera neither orthographic or perspective console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); } }; this.dollyIn = function ( dollyScale ) { if ( dollyScale === undefined ) { dollyScale = getZoomScale(); } scale /= dollyScale; }; this.dollyOut = function ( dollyScale ) { if ( dollyScale === undefined ) { dollyScale = getZoomScale(); } scale *= dollyScale; }; this.update = function () { var position = this.object.position; offset.copy( position ).sub( this.target ); // rotate offset to "y-axis-is-up" space offset.applyQuaternion( quat ); // angle from z-axis around y-axis var theta = Math.atan2( offset.x, offset.z ); // angle from y-axis var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); if ( this.autoRotate ) { this.rotateLeft( getAutoRotationAngle() ); } theta += thetaDelta; phi += phiDelta; // restrict phi to be between desired limits phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); // restrict phi to be betwee EPS and PI-EPS phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); var radius = offset.length() * scale; // restrict radius to be between desired limits radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); // move target to panned location this.target.add( pan ); offset.x = radius * Math.sin( phi ) * Math.sin( theta ); offset.y = radius * Math.cos( phi ); offset.z = radius * Math.sin( phi ) * Math.cos( theta ); // rotate offset back to "camera-up-vector-is-up" space offset.applyQuaternion( quatInverse ); position.copy( this.target ).add( offset ); this.object.lookAt( this.target ); thetaDelta = 0; phiDelta = 0; scale = 1; pan.set( 0, 0, 0 ); if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) { this.dispatchEvent( changeEvent ); lastPosition.copy( this.object.position ); } }; this.reset = function () { state = STATE.NONE; this.target.copy( this.target0 ); this.object.position.copy( this.position0 ); this.update(); }; function getAutoRotationAngle() { return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; } function getZoomScale() { return Math.pow( 0.95, scope.zoomSpeed ); } function onMouseDown( event ) { if ( scope.enabled === false ) return; event.preventDefault(); if ( event.button === 0 && !event.ctrlKey ) { if ( scope.noRotate === true ) return; state = STATE.ROTATE; rotateStart.set( event.clientX, event.clientY ); } else if ( event.button === 2 ) { if ( scope.noZoom === true ) return; state = STATE.DOLLY; dollyStart.set( event.clientX, event.clientY ); } else if ( event.button === 1 || (event.button === 0 && event.ctrlKey) ) { if ( scope.noPan === true ) return; state = STATE.PAN; panStart.set( event.clientX, event.clientY ); } scope.domElement.addEventListener( 'mousemove', onMouseMove, false ); scope.domElement.addEventListener( 'mouseup', onMouseUp, false ); scope.dispatchEvent( startEvent ); } function onMouseMove( event ) { if ( scope.enabled === false ) return; event.preventDefault(); var element = scope.domElement === document ? scope.domElement.body : scope.domElement; if ( state === STATE.ROTATE ) { if ( scope.noRotate === true ) return; rotateEnd.set( event.clientX, event.clientY ); rotateDelta.subVectors( rotateEnd, rotateStart ); // rotating across whole screen goes 360 degrees around scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); // rotating up and down along whole screen attempts to go 360, but limited to 180 scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); rotateStart.copy( rotateEnd ); } else if ( state === STATE.DOLLY ) { if ( scope.noZoom === true ) return; dollyEnd.set( event.clientX, event.clientY ); dollyDelta.subVectors( dollyEnd, dollyStart ); if ( dollyDelta.y > 0 ) { scope.dollyIn(); } else { scope.dollyOut(); } dollyStart.copy( dollyEnd ); } else if ( state === STATE.PAN ) { if ( scope.noPan === true ) return; panEnd.set( event.clientX, event.clientY ); panDelta.subVectors( panEnd, panStart ); scope.pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); } scope.update(); } function onMouseUp( /* event */ ) { if ( scope.enabled === false ) return; scope.domElement.removeEventListener( 'mousemove', onMouseMove, false ); scope.domElement.removeEventListener( 'mouseup', onMouseUp, false ); scope.dispatchEvent( endEvent ); state = STATE.NONE; } function onMouseWheel( event ) { if ( scope.enabled === false || scope.noZoom === true ) return; event.preventDefault(); event.stopPropagation(); var delta = 0; if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 delta = event.wheelDelta; } else if ( event.detail !== undefined ) { // Firefox delta = - event.detail; } if ( delta > 0 ) { scope.dollyOut(); } else { scope.dollyIn(); } scope.update(); scope.dispatchEvent( startEvent ); scope.dispatchEvent( endEvent ); } function onKeyDown( event ) { if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; switch ( event.keyCode ) { case scope.keys.UP: scope.pan( 0, scope.keyPanSpeed ); scope.update(); break; case scope.keys.BOTTOM: scope.pan( 0, - scope.keyPanSpeed ); scope.update(); break; case scope.keys.LEFT: scope.pan( scope.keyPanSpeed, 0 ); scope.update(); break; case scope.keys.RIGHT: scope.pan( - scope.keyPanSpeed, 0 ); scope.update(); break; } } function touchstart( event ) { if ( scope.enabled === false ) return; switch ( event.touches.length ) { case 1: // one-fingered touch: rotate if ( scope.noRotate === true ) return; state = STATE.TOUCH_ROTATE; rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); break; case 2: // two-fingered touch: dolly if ( scope.noZoom === true ) return; state = STATE.TOUCH_DOLLY; var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; var distance = Math.sqrt( dx * dx + dy * dy ); dollyStart.set( 0, distance ); break; case 3: // three-fingered touch: pan if ( scope.noPan === true ) return; state = STATE.TOUCH_PAN; panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); break; default: state = STATE.NONE; } scope.dispatchEvent( startEvent ); } function touchmove( event ) { if ( scope.enabled === false ) return; event.preventDefault(); event.stopPropagation(); var element = scope.domElement === document ? scope.domElement.body : scope.domElement; switch ( event.touches.length ) { case 1: // one-fingered touch: rotate if ( scope.noRotate === true ) return; if ( state !== STATE.TOUCH_ROTATE ) return; rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); rotateDelta.subVectors( rotateEnd, rotateStart ); // rotating across whole screen goes 360 degrees around scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); // rotating up and down along whole screen attempts to go 360, but limited to 180 scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); rotateStart.copy( rotateEnd ); scope.update(); break; case 2: // two-fingered touch: dolly if ( scope.noZoom === true ) return; if ( state !== STATE.TOUCH_DOLLY ) return; var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; var distance = Math.sqrt( dx * dx + dy * dy ); dollyEnd.set( 0, distance ); dollyDelta.subVectors( dollyEnd, dollyStart ); if ( dollyDelta.y > 0 ) { scope.dollyOut(); } else { scope.dollyIn(); } dollyStart.copy( dollyEnd ); scope.update(); break; case 3: // three-fingered touch: pan if ( scope.noPan === true ) return; if ( state !== STATE.TOUCH_PAN ) return; panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); panDelta.subVectors( panEnd, panStart ); scope.pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); scope.update(); break; default: state = STATE.NONE; } } function touchend( /* event */ ) { if ( scope.enabled === false ) return; scope.dispatchEvent( endEvent ); state = STATE.NONE; } this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); this.domElement.addEventListener( 'mousedown', onMouseDown, false ); this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox this.domElement.addEventListener( 'touchstart', touchstart, false ); this.domElement.addEventListener( 'touchend', touchend, false ); this.domElement.addEventListener( 'touchmove', touchmove, false ); window.addEventListener( 'keydown', onKeyDown, false ); // force an update at start this.update(); }; THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); ================================================ FILE: app/scripts/lib/Viewport.js ================================================ var container, $container; var camera, controls, scene, renderer; var geometry, group; var mouse = new THREE.Vector2(), offset = new THREE.Vector3(), INTERSECTED, SELECTED; var objects = [], plane; var mouseX = 0, mouseY = 0; var windowHalfX = window.innerWidth / 2; var windowHalfY = window.innerHeight / 2; init(); render(); function init() { container = document.getElementById("viewer"); $container = $(container); camera = new THREE.PerspectiveCamera( 30, $container.width() / $container.height(), 1, 10000 ); camera.position.set( 140, 140, 140 ); camera.up.set( 0, 0, 1 ); camera.lookAt( new THREE.Vector3(0,0,0) ); scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setClearColor( 0xffffff, 1 ); renderer.setSize( $container.width(), $container.height() ); renderer.sortObjects = false; container.appendChild( renderer.domElement ); renderer.domElement.setAttribute("id", "renderer_canvas"); // add subtle ambient lighting var ambientLight = new THREE.AmbientLight(0x555555); scene.add(ambientLight); // add directional light source var directionalLight = new THREE.DirectionalLight(0xbbbbbb); directionalLight.position.set(50, 30, 50); scene.add(directionalLight); var directionalLight = new THREE.DirectionalLight(0xaaaaaa); directionalLight.position.set(-0.2, -0.8, 1).normalize(); scene.add(directionalLight); makeGrid(); controls = new THREE.OrbitControls(camera, container); window.addEventListener( 'resize', onWindowResize, false ); // full screen blur // composer = new THREE.EffectComposer( renderer ); // composer.addPass( new THREE.RenderPass( scene, camera ) ); // hblur = new THREE.ShaderPass( THREE.HorizontalBlurShader ); // composer.addPass( hblur ); // vblur = new THREE.ShaderPass( THREE.VerticalBlurShader ); // // set this shader pass to render to screen so we can see the effects // vblur.renderToScreen = true; // composer.addPass( vblur ); animate(); } function makeGrid(){ var l = 60; var axisHelper = new THREE.AxisHelper( l ); scene.add( axisHelper ); var geometry = new THREE.Geometry(); var geometryThick = new THREE.Geometry(); var n = l; var inc = 2 * l / n; var rate = 10; for (var i = 0; i < n + 1; i++){ var v1 = new THREE.Vector3(-l, -l + i * inc, 0); var v2 = new THREE.Vector3(l, -l + i * inc, 0); geometry.vertices.push(v1); geometry.vertices.push(v2); if (i % rate == 0){ geometryThick.vertices.push(v1); geometryThick.vertices.push(v2); } } for (var i = 0; i < n + 1; i++){ var v1 = new THREE.Vector3(-l + i * inc, l, 0); var v2 = new THREE.Vector3(-l + i * inc, -l, 0); geometry.vertices.push(v1); geometry.vertices.push(v2); if (i % rate == 0){ geometryThick.vertices.push(v1); geometryThick.vertices.push(v2); } } var material = new THREE.LineBasicMaterial({ color: 0xeeeeee, linewidth: 0.1 }); var materialThick = new THREE.LineBasicMaterial({ color: 0xeeeeee, linewidth: 2 }); var line = new THREE.Line(geometry, material, THREE.LinePieces); var lineThick = new THREE.Line(geometryThick, materialThick, THREE.LinePieces); scene.add(line); scene.add(lineThick); } function onWindowResize() { windowHalfX = $container.width() / 2; windowHalfY = $container.height() / 2; camera.aspect = windowHalfX/ windowHalfY; camera.updateProjectionMatrix(); renderer.setSize( 2*windowHalfX, 2*windowHalfY ); render(); } function animate() { requestAnimationFrame( animate ); render(); } var doBlur = true; function render() { controls.update(); renderer.render( scene, camera ); if (doBlur){ // composer.render(); } } ================================================ FILE: app/scripts/lib/flood/async.js ================================================ /*! * async * https://github.com/caolan/async * * Copyright 2010-2014 Caolan McMahon * Released under the MIT license */ /*jshint onevar: false, indent:4 */ /*global setImmediate: false, setTimeout: false, console: false */ (function () { var async = {}; // global on the server, window in the browser var root, previous_async; root = this; if (root != null) { previous_async = root.async; } async.noConflict = function () { root.async = previous_async; return async; }; function only_once(fn) { var called = false; return function() { if (called) throw new Error("Callback was already called."); called = true; fn.apply(root, arguments); } } //// cross-browser compatiblity functions //// var _toString = Object.prototype.toString; var _isArray = Array.isArray || function (obj) { return _toString.call(obj) === '[object Array]'; }; var _each = function (arr, iterator) { if (arr.forEach) { return arr.forEach(iterator); } for (var i = 0; i < arr.length; i += 1) { iterator(arr[i], i, arr); } }; var _map = function (arr, iterator) { if (arr.map) { return arr.map(iterator); } var results = []; _each(arr, function (x, i, a) { results.push(iterator(x, i, a)); }); return results; }; var _reduce = function (arr, iterator, memo) { if (arr.reduce) { return arr.reduce(iterator, memo); } _each(arr, function (x, i, a) { memo = iterator(memo, x, i, a); }); return memo; }; var _keys = function (obj) { if (Object.keys) { return Object.keys(obj); } var keys = []; for (var k in obj) { if (obj.hasOwnProperty(k)) { keys.push(k); } } return keys; }; //// exported async module functions //// //// nextTick implementation with browser-compatible fallback //// if (typeof process === 'undefined' || !(process.nextTick)) { if (typeof setImmediate === 'function') { async.nextTick = function (fn) { // not a direct alias for IE10 compatibility setImmediate(fn); }; async.setImmediate = async.nextTick; } else { async.nextTick = function (fn) { setTimeout(fn, 0); }; async.setImmediate = async.nextTick; } } else { async.nextTick = process.nextTick; if (typeof setImmediate !== 'undefined') { async.setImmediate = function (fn) { // not a direct alias for IE10 compatibility setImmediate(fn); }; } else { async.setImmediate = async.nextTick; } } async.each = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; _each(arr, function (x) { iterator(x, only_once(done) ); }); function done(err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed >= arr.length) { callback(); } } } }; async.forEach = async.each; async.eachSeries = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; var iterate = function () { iterator(arr[completed], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed >= arr.length) { callback(); } else { iterate(); } } }); }; iterate(); }; async.forEachSeries = async.eachSeries; async.eachLimit = function (arr, limit, iterator, callback) { var fn = _eachLimit(limit); fn.apply(null, [arr, iterator, callback]); }; async.forEachLimit = async.eachLimit; var _eachLimit = function (limit) { return function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length || limit <= 0) { return callback(); } var completed = 0; var started = 0; var running = 0; (function replenish () { if (completed >= arr.length) { return callback(); } while (running < limit && started < arr.length) { started += 1; running += 1; iterator(arr[started - 1], function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; running -= 1; if (completed >= arr.length) { callback(); } else { replenish(); } } }); } })(); }; }; var doParallel = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [async.each].concat(args)); }; }; var doParallelLimit = function(limit, fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [_eachLimit(limit)].concat(args)); }; }; var doSeries = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); return fn.apply(null, [async.eachSeries].concat(args)); }; }; var _asyncMap = function (eachfn, arr, iterator, callback) { arr = _map(arr, function (x, i) { return {index: i, value: x}; }); if (!callback) { eachfn(arr, function (x, callback) { iterator(x.value, function (err) { callback(err); }); }); } else { var results = []; eachfn(arr, function (x, callback) { iterator(x.value, function (err, v) { results[x.index] = v; callback(err); }); }, function (err) { callback(err, results); }); } }; async.map = doParallel(_asyncMap); async.mapSeries = doSeries(_asyncMap); async.mapLimit = function (arr, limit, iterator, callback) { return _mapLimit(limit)(arr, iterator, callback); }; var _mapLimit = function(limit) { return doParallelLimit(limit, _asyncMap); }; // reduce only has a series version, as doing reduce in parallel won't // work in many situations. async.reduce = function (arr, memo, iterator, callback) { async.eachSeries(arr, function (x, callback) { iterator(memo, x, function (err, v) { memo = v; callback(err); }); }, function (err) { callback(err, memo); }); }; // inject alias async.inject = async.reduce; // foldl alias async.foldl = async.reduce; async.reduceRight = function (arr, memo, iterator, callback) { var reversed = _map(arr, function (x) { return x; }).reverse(); async.reduce(reversed, memo, iterator, callback); }; // foldr alias async.foldr = async.reduceRight; var _filter = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (v) { if (v) { results.push(x); } callback(); }); }, function (err) { callback(_map(results.sort(function (a, b) { return a.index - b.index; }), function (x) { return x.value; })); }); }; async.filter = doParallel(_filter); async.filterSeries = doSeries(_filter); // select alias async.select = async.filter; async.selectSeries = async.filterSeries; var _reject = function (eachfn, arr, iterator, callback) { var results = []; arr = _map(arr, function (x, i) { return {index: i, value: x}; }); eachfn(arr, function (x, callback) { iterator(x.value, function (v) { if (!v) { results.push(x); } callback(); }); }, function (err) { callback(_map(results.sort(function (a, b) { return a.index - b.index; }), function (x) { return x.value; })); }); }; async.reject = doParallel(_reject); async.rejectSeries = doSeries(_reject); var _detect = function (eachfn, arr, iterator, main_callback) { eachfn(arr, function (x, callback) { iterator(x, function (result) { if (result) { main_callback(x); main_callback = function () {}; } else { callback(); } }); }, function (err) { main_callback(); }); }; async.detect = doParallel(_detect); async.detectSeries = doSeries(_detect); async.some = function (arr, iterator, main_callback) { async.each(arr, function (x, callback) { iterator(x, function (v) { if (v) { main_callback(true); main_callback = function () {}; } callback(); }); }, function (err) { main_callback(false); }); }; // any alias async.any = async.some; async.every = function (arr, iterator, main_callback) { async.each(arr, function (x, callback) { iterator(x, function (v) { if (!v) { main_callback(false); main_callback = function () {}; } callback(); }); }, function (err) { main_callback(true); }); }; // all alias async.all = async.every; async.sortBy = function (arr, iterator, callback) { async.map(arr, function (x, callback) { iterator(x, function (err, criteria) { if (err) { callback(err); } else { callback(null, {value: x, criteria: criteria}); } }); }, function (err, results) { if (err) { return callback(err); } else { var fn = function (left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }; callback(null, _map(results.sort(fn), function (x) { return x.value; })); } }); }; async.auto = function (tasks, callback) { callback = callback || function () {}; var keys = _keys(tasks); var remainingTasks = keys.length if (!remainingTasks) { return callback(); } var results = {}; var listeners = []; var addListener = function (fn) { listeners.unshift(fn); }; var removeListener = function (fn) { for (var i = 0; i < listeners.length; i += 1) { if (listeners[i] === fn) { listeners.splice(i, 1); return; } } }; var taskComplete = function () { remainingTasks-- _each(listeners.slice(0), function (fn) { fn(); }); }; addListener(function () { if (!remainingTasks) { var theCallback = callback; // prevent final callback from calling itself if it errors callback = function () {}; theCallback(null, results); } }); _each(keys, function (k) { var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]]; var taskCallback = function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } if (err) { var safeResults = {}; _each(_keys(results), function(rkey) { safeResults[rkey] = results[rkey]; }); safeResults[k] = args; callback(err, safeResults); // stop subsequent errors hitting callback multiple times callback = function () {}; } else { results[k] = args; async.setImmediate(taskComplete); } }; var requires = task.slice(0, Math.abs(task.length - 1)) || []; var ready = function () { return _reduce(requires, function (a, x) { return (a && results.hasOwnProperty(x)); }, true) && !results.hasOwnProperty(k); }; if (ready()) { task[task.length - 1](taskCallback, results); } else { var listener = function () { if (ready()) { removeListener(listener); task[task.length - 1](taskCallback, results); } }; addListener(listener); } }); }; async.retry = function(times, task, callback) { var DEFAULT_TIMES = 5; var attempts = []; // Use defaults if times not passed if (typeof times === 'function') { callback = task; task = times; times = DEFAULT_TIMES; } // Make sure times is a number times = parseInt(times, 10) || DEFAULT_TIMES; var wrappedTask = function(wrappedCallback, wrappedResults) { var retryAttempt = function(task, finalAttempt) { return function(seriesCallback) { task(function(err, result){ seriesCallback(!err || finalAttempt, {err: err, result: result}); }, wrappedResults); }; }; while (times) { attempts.push(retryAttempt(task, !(times-=1))); } async.series(attempts, function(done, data){ data = data[data.length - 1]; (wrappedCallback || callback)(data.err, data.result); }); } // If a callback is passed, run this as a controll flow return callback ? wrappedTask() : wrappedTask }; async.waterfall = function (tasks, callback) { callback = callback || function () {}; if (!_isArray(tasks)) { var err = new Error('First argument to waterfall must be an array of functions'); return callback(err); } if (!tasks.length) { return callback(); } var wrapIterator = function (iterator) { return function (err) { if (err) { callback.apply(null, arguments); callback = function () {}; } else { var args = Array.prototype.slice.call(arguments, 1); var next = iterator.next(); if (next) { args.push(wrapIterator(next)); } else { args.push(callback); } async.setImmediate(function () { iterator.apply(null, args); }); } }; }; wrapIterator(async.iterator(tasks))(); }; var _parallel = function(eachfn, tasks, callback) { callback = callback || function () {}; if (_isArray(tasks)) { eachfn.map(tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } callback.call(null, err, args); }); } }, callback); } else { var results = {}; eachfn.each(_keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } results[k] = args; callback(err); }); }, function (err) { callback(err, results); }); } }; async.parallel = function (tasks, callback) { _parallel({ map: async.map, each: async.each }, tasks, callback); }; async.parallelLimit = function(tasks, limit, callback) { _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); }; async.series = function (tasks, callback) { callback = callback || function () {}; if (_isArray(tasks)) { async.mapSeries(tasks, function (fn, callback) { if (fn) { fn(function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } callback.call(null, err, args); }); } }, callback); } else { var results = {}; async.eachSeries(_keys(tasks), function (k, callback) { tasks[k](function (err) { var args = Array.prototype.slice.call(arguments, 1); if (args.length <= 1) { args = args[0]; } results[k] = args; callback(err); }); }, function (err) { callback(err, results); }); } }; async.iterator = function (tasks) { var makeCallback = function (index) { var fn = function () { if (tasks.length) { tasks[index].apply(null, arguments); } return fn.next(); }; fn.next = function () { return (index < tasks.length - 1) ? makeCallback(index + 1): null; }; return fn; }; return makeCallback(0); }; async.apply = function (fn) { var args = Array.prototype.slice.call(arguments, 1); return function () { return fn.apply( null, args.concat(Array.prototype.slice.call(arguments)) ); }; }; var _concat = function (eachfn, arr, fn, callback) { var r = []; eachfn(arr, function (x, cb) { fn(x, function (err, y) { r = r.concat(y || []); cb(err); }); }, function (err) { callback(err, r); }); }; async.concat = doParallel(_concat); async.concatSeries = doSeries(_concat); async.whilst = function (test, iterator, callback) { if (test()) { iterator(function (err) { if (err) { return callback(err); } async.whilst(test, iterator, callback); }); } else { callback(); } }; async.doWhilst = function (iterator, test, callback) { iterator(function (err) { if (err) { return callback(err); } var args = Array.prototype.slice.call(arguments, 1); if (test.apply(null, args)) { async.doWhilst(iterator, test, callback); } else { callback(); } }); }; async.until = function (test, iterator, callback) { if (!test()) { iterator(function (err) { if (err) { return callback(err); } async.until(test, iterator, callback); }); } else { callback(); } }; async.doUntil = function (iterator, test, callback) { iterator(function (err) { if (err) { return callback(err); } var args = Array.prototype.slice.call(arguments, 1); if (!test.apply(null, args)) { async.doUntil(iterator, test, callback); } else { callback(); } }); }; async.queue = function (worker, concurrency) { if (concurrency === undefined) { concurrency = 1; } function _insert(q, data, pos, callback) { if (!q.started){ q.started = true; } if (!_isArray(data)) { data = [data]; } if(data.length == 0) { // call drain immediately if there are no tasks return async.setImmediate(function() { if (q.drain) { q.drain(); } }); } _each(data, function(task) { var item = { data: task, callback: typeof callback === 'function' ? callback : null }; if (pos) { q.tasks.unshift(item); } else { q.tasks.push(item); } if (q.saturated && q.tasks.length === q.concurrency) { q.saturated(); } async.setImmediate(q.process); }); } var workers = 0; var q = { tasks: [], concurrency: concurrency, saturated: null, empty: null, drain: null, started: false, paused: false, push: function (data, callback) { _insert(q, data, false, callback); }, kill: function () { q.drain = null; q.tasks = []; }, unshift: function (data, callback) { _insert(q, data, true, callback); }, process: function () { if (!q.paused && workers < q.concurrency && q.tasks.length) { var task = q.tasks.shift(); if (q.empty && q.tasks.length === 0) { q.empty(); } workers += 1; var next = function () { workers -= 1; if (task.callback) { task.callback.apply(task, arguments); } if (q.drain && q.tasks.length + workers === 0) { q.drain(); } q.process(); }; var cb = only_once(next); worker(task.data, cb); } }, length: function () { return q.tasks.length; }, running: function () { return workers; }, idle: function() { return q.tasks.length + workers === 0; }, pause: function () { if (q.paused === true) { return; } q.paused = true; }, resume: function () { if (q.paused === false) { return; } q.paused = false; // Need to call q.process once per concurrent // worker to preserve full concurrency after pause for (var w = 1; w <= q.concurrency; w++) { async.setImmediate(q.process); } } }; return q; }; async.priorityQueue = function (worker, concurrency) { function _compareTasks(a, b){ return a.priority - b.priority; }; function _binarySearch(sequence, item, compare) { var beg = -1, end = sequence.length - 1; while (beg < end) { var mid = beg + ((end - beg + 1) >>> 1); if (compare(item, sequence[mid]) >= 0) { beg = mid; } else { end = mid - 1; } } return beg; } function _insert(q, data, priority, callback) { if (!q.started){ q.started = true; } if (!_isArray(data)) { data = [data]; } if(data.length == 0) { // call drain immediately if there are no tasks return async.setImmediate(function() { if (q.drain) { q.drain(); } }); } _each(data, function(task) { var item = { data: task, priority: priority, callback: typeof callback === 'function' ? callback : null }; q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item); if (q.saturated && q.tasks.length === q.concurrency) { q.saturated(); } async.setImmediate(q.process); }); } // Start with a normal queue var q = async.queue(worker, concurrency); // Override push to accept second parameter representing priority q.push = function (data, priority, callback) { _insert(q, data, priority, callback); }; // Remove unshift function delete q.unshift; return q; }; async.cargo = function (worker, payload) { var working = false, tasks = []; var cargo = { tasks: tasks, payload: payload, saturated: null, empty: null, drain: null, drained: true, push: function (data, callback) { if (!_isArray(data)) { data = [data]; } _each(data, function(task) { tasks.push({ data: task, callback: typeof callback === 'function' ? callback : null }); cargo.drained = false; if (cargo.saturated && tasks.length === payload) { cargo.saturated(); } }); async.setImmediate(cargo.process); }, process: function process() { if (working) return; if (tasks.length === 0) { if(cargo.drain && !cargo.drained) cargo.drain(); cargo.drained = true; return; } var ts = typeof payload === 'number' ? tasks.splice(0, payload) : tasks.splice(0, tasks.length); var ds = _map(ts, function (task) { return task.data; }); if(cargo.empty) cargo.empty(); working = true; worker(ds, function () { working = false; var args = arguments; _each(ts, function (data) { if (data.callback) { data.callback.apply(null, args); } }); process(); }); }, length: function () { return tasks.length; }, running: function () { return working; } }; return cargo; }; var _console_fn = function (name) { return function (fn) { var args = Array.prototype.slice.call(arguments, 1); fn.apply(null, args.concat([function (err) { var args = Array.prototype.slice.call(arguments, 1); if (typeof console !== 'undefined') { if (err) { if (console.error) { console.error(err); } } else if (console[name]) { _each(args, function (x) { console[name](x); }); } } }])); }; }; async.log = _console_fn('log'); async.dir = _console_fn('dir'); /*async.info = _console_fn('info'); async.warn = _console_fn('warn'); async.error = _console_fn('error');*/ async.memoize = function (fn, hasher) { var memo = {}; var queues = {}; hasher = hasher || function (x) { return x; }; var memoized = function () { var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var key = hasher.apply(null, args); if (key in memo) { async.nextTick(function () { callback.apply(null, memo[key]); }); } else if (key in queues) { queues[key].push(callback); } else { queues[key] = [callback]; fn.apply(null, args.concat([function () { memo[key] = arguments; var q = queues[key]; delete queues[key]; for (var i = 0, l = q.length; i < l; i++) { q[i].apply(null, arguments); } }])); } }; memoized.memo = memo; memoized.unmemoized = fn; return memoized; }; async.unmemoize = function (fn) { return function () { return (fn.unmemoized || fn).apply(null, arguments); }; }; async.times = function (count, iterator, callback) { var counter = []; for (var i = 0; i < count; i++) { counter.push(i); } return async.map(counter, iterator, callback); }; async.timesSeries = function (count, iterator, callback) { var counter = []; for (var i = 0; i < count; i++) { counter.push(i); } return async.mapSeries(counter, iterator, callback); }; async.seq = function (/* functions... */) { var fns = arguments; return function () { var that = this; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); async.reduce(fns, args, function (newargs, fn, cb) { fn.apply(that, newargs.concat([function () { var err = arguments[0]; var nextargs = Array.prototype.slice.call(arguments, 1); cb(err, nextargs); }])) }, function (err, results) { callback.apply(that, [err].concat(results)); }); }; }; async.compose = function (/* functions... */) { return async.seq.apply(null, Array.prototype.reverse.call(arguments)); }; var _applyEach = function (eachfn, fns /*args...*/) { var go = function () { var that = this; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); return eachfn(fns, function (fn, cb) { fn.apply(that, args.concat([cb])); }, callback); }; if (arguments.length > 2) { var args = Array.prototype.slice.call(arguments, 2); return go.apply(this, args); } else { return go; } }; async.applyEach = doParallel(_applyEach); async.applyEachSeries = doSeries(_applyEach); async.forever = function (fn, callback) { function next(err) { if (err) { if (callback) { return callback(err); } throw err; } fn(next); } next(); }; // Node.js if (typeof module !== 'undefined' && module.exports) { module.exports = async; } // AMD / RequireJS else if (typeof define !== 'undefined' && define.amd) { define([], function () { return async; }); } // included directly via ================================================ FILE: app/scripts/lib/flood/test/flood_test.js ================================================ var FLOOD = new require('../flood.js') , assert = require('assert') , scheme = require('../scheme.js'); // mapApply - applyCartesian without nesting (function(scheme, FLOOD) { var add = new FLOOD.nodeTypes.Add(); // expected_arg_types var options = {}; options.expected_arg_types = [[Number], [Number]]; options.replication = "applyCartesian"; var arr0 = new FLOOD.QuotedArray(); arr0.push(-10); arr0.push(2); var arr1 = new FLOOD.QuotedArray(); arr1.push(2); arr1.push(3); var res = add.eval.mapApply(add.eval, [arr0, arr1], options); assert.equal( 4, res.length ); assert.equal( "[ -8, -7, 4, 5 ]", res.toString() ); var res = add.eval.mapApply(add.eval, [arr0, 1], options); assert.equal( 2, res.length ); assert.equal( "[ -9, 3 ]", res.toString() ); var res = add.eval.mapApply(add.eval, [1, 1], options); assert.equal( "2", res.toString() ); var res = add.eval.mapApply(add.eval, [1, arr1], options); assert.equal( 2, res.length ); assert.equal( "[ 3, 4 ]", res.toString() ); })(scheme, FLOOD); // mapApply - applyLongest with nesting (function(scheme, FLOOD) { var add = new FLOOD.nodeTypes.Add(); // expected_arg_types var options = {}; options.expected_arg_types = [[Number], [Number]]; options.replication = "applyLongest"; var arrA = new FLOOD.QuotedArray(); var arr0 = new FLOOD.QuotedArray(); arrA.push(arr0); arr0.push(1); var arrB = new FLOOD.QuotedArray(); var arr1 = new FLOOD.QuotedArray(); arrB.push(arr1); arr1.push(2); var res = add.eval.mapApply(eval, [arrA, arrB], options); assert.equal( 1, res.length ); assert.equal( "[ [ 3 ] ]", res.toString() ); var res = add.eval.mapApply(eval, [4, arrB], options); assert.equal( 1, res.length ); assert.equal( "[ [ 6 ] ]", res.toString() ); var res = add.eval.mapApply(eval, [arrA, 2], options); assert.equal( 1, res.length ); assert.equal( "[ [ 3 ] ]", res.toString() ); arr0.push(2); var res = add.eval.mapApply(eval, [arrA, 2], options); assert.equal( 1, res.length ); assert.equal( "[ [ 3, 4 ] ]", res.toString() ); var res = add.eval.mapApply(eval, [arrA, arrA], options); assert.equal( 1, res.length ); assert.equal( "[ [ 2, 4 ] ]", res.toString() ); })(scheme, FLOOD); // mapApply - applyLongest without nesting (function(scheme, FLOOD) { var add = new FLOOD.nodeTypes.Add(); // expected_arg_types var options = {}; options.expected_arg_types = [[Number], [Number]]; options.replication = "applyLongest"; var arr = new FLOOD.QuotedArray(); arr.push(8); arr.push(9); arr.push(10); var res = add.eval.mapApply(eval, [1, arr], options); assert.equal( 3, res.length ); assert.equal( "[ 9, 10, 11 ]", res.toString() ); var res = add.eval.mapApply(eval, [arr, arr], options); assert.equal( 3, res.length ); assert.equal( "[ 16, 18, 20 ]", res.toString() ); arr.push(11); var res = add.eval.mapApply(eval, [1, arr], options); assert.equal( 4, res.length ); assert.equal( "[ 9, 10, 11, 12 ]", res.toString() ); })(scheme, FLOOD); // doAllTypesMatch (function(scheme, FLOOD) { var arg = [null], arg_type = [[ FLOOD.AnyType ]]; assert.equal( true, FLOOD.doAllTypesMatch( arg, arg_type ) ); var arg = [8], arg_type = [[ Number ]]; assert.equal( true, FLOOD.doAllTypesMatch( arg, arg_type ) ); var arrNum = new FLOOD.QuotedArray(); arrNum.push(8); var arg = [ 8, arrNum ], arg_type = [[ Number ], [ FLOOD.QuotedArray, Number ]]; assert.equal( true, FLOOD.doAllTypesMatch( arg, arg_type ) ); var arrString = new FLOOD.QuotedArray(); arrString.push("frog"); var arg = [ 8, arrNum, arrString ], arg_type = [ [ Number ], [ FLOOD.QuotedArray, Number ], [ FLOOD.QuotedArray, String ] ]; assert.equal( true, FLOOD.doAllTypesMatch( arg, arg_type ) ); var arg = [ 8, arrString, arrNum ], arg_type = [ [ Number ], [ FLOOD.QuotedArray, Number ], [ FLOOD.QuotedArray, String ] ]; assert.equal( false, FLOOD.doAllTypesMatch( arg, arg_type ) ); })(scheme, FLOOD); // isFastTypeMatch (function(scheme, FLOOD) { var arg = null, arg_type = [ FLOOD.AnyType ]; assert.equal( true, FLOOD.isFastTypeMatch(arg, arg_type) ); var arg = new FLOOD.QuotedArray(null), arg_type = [ FLOOD.QuotedArray, FLOOD.AnyType ]; assert.equal( true, FLOOD.isFastTypeMatch(arg, arg_type) ); var arg = null, arg_type = [ FLOOD.QuotedArray, FLOOD.AnyType ]; assert.equal( false, FLOOD.isFastTypeMatch(arg, arg_type) ); var arg = new FLOOD.QuotedArray(), arg_type = [ FLOOD.QuotedArray, Number ]; arg.push(8); assert.equal( true, FLOOD.isFastTypeMatch(arg, arg_type) ); var arg = new FLOOD.QuotedArray(), arg_type = [ FLOOD.QuotedArray, FLOOD.QuotedArray, Number ]; arg.push("peter"); arg.push(8); assert.equal( false, FLOOD.isFastTypeMatch(arg, arg_type) ); var arg = [[8, 8]], arg_type = [Array, Array, Number]; assert.equal( true, FLOOD.isFastTypeMatch(arg, arg_type) ); var arg = [[[8]]], arg_type = [Array, Array, Array, Number]; assert.equal( true, FLOOD.isFastTypeMatch(arg, arg_type) ); // new constructor function function Turtle(){}; var arg = [[[new Turtle()]]], arg_type = [Array, Array, Array, Turtle]; assert.equal( true, FLOOD.isFastTypeMatch(arg, arg_type) ); })(scheme, FLOOD); // isObjectTypeMatch (function(scheme, FLOOD) { var arg = null, arg_type = FLOOD.AnyType; assert.equal( true, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = 8, arg_type = Number; assert.equal( true, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = -8.23, arg_type = Number; assert.equal( true, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = "peter", arg_type = String; assert.equal( true, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = new FLOOD.QuotedArray(), arg_type = FLOOD.QuotedArray; assert.equal( true, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = [], arg_type = Number; assert.equal( false, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = "peter", arg_type = Number; assert.equal( false, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = function(){}, arg_type = Function; assert.equal( true, FLOOD.isObjectTypeMatch(arg, arg_type) ); var arg = function(){}, arg_type = Number; assert.equal( false, FLOOD.isObjectTypeMatch(arg, arg_type) ); })(scheme, FLOOD); // test case 1 (function(scheme, FLOOD) { var add = new FLOOD.nodeTypes.Add(); var num5 = new FLOOD.nodeTypes.Number(); num5.value = 5; var num2 = new FLOOD.nodeTypes.Number(); num2.value = 2; add.inputs[0].connect( num5 ); add.inputs[1].connect( num2 ); assert.equal( add.printExpression() , '(+ 5 2)' ); var S = new scheme.Interpreter(); assert.equal( S.eval( add.compile() ) , 7 ); })(scheme, FLOOD); // test case 2 (function(scheme, FLOOD) { var add = new FLOOD.nodeTypes.Add(); var num5 = new FLOOD.nodeTypes.Number(); num5.value = 5; var num2 = new FLOOD.nodeTypes.Number(); num2.value = 2; add.inputs[0].connect( num5 ); add.inputs[1].connect( num2 ); var divide = new FLOOD.nodeTypes.Div(); divide.inputs[0].connect( num5 ); divide.inputs[1].connect( add ); assert.equal( divide.printExpression() , '(/ 5 (+ 5 2))' ); var S = new scheme.Interpreter(); assert.equal( S.eval( divide.compile() ), 5/7); })(scheme, FLOOD); // test case 3 (function(scheme, FLOOD) { var add = new FLOOD.nodeTypes.Add(); var num5 = new FLOOD.nodeTypes.Number(); num5.value = 5; var num2 = new FLOOD.nodeTypes.Number(); num2.value = 2; add.inputs[0].connect( num5 ); add.inputs[1].connect( num2 ); var divide = new FLOOD.nodeTypes.Div(); divide.inputs[0].connect( num5 ); divide.inputs[1].connect( add ); var multiply = new FLOOD.nodeTypes.Mult(); multiply.inputs[0].connect( num5 ); multiply.inputs[1].connect( add ); var begin = new FLOOD.nodeTypes.Begin(); begin.inputs.push( multiply.outputs[0].asInputPort(begin, 0) ); begin.inputs.push( divide.outputs[0].asInputPort(begin, 0) ); begin.inputs[0].connect( multiply ); begin.inputs[1].connect( divide ); assert.equal( begin.printExpression(), '(begin (* 5 (+ 5 2)) (/ 5 (+ 5 2)))' ); var S = new scheme.Interpreter(); begin.markDirty(); assert.equal( S.eval( begin.compile() ), 5/7); })(scheme, FLOOD); // // test case 4, check that add if curried correctly // (function(scheme, FLOOD) { // var add = new FLOOD.nodeTypes.Add(); // add.inputs[0].defaultVal = 1; // add.inputs[1].useDefault = false; // assert.equal( add.printExpression(), '(+ 1 _)' ); // var S = new scheme.Interpreter(); // add.markDirty(); // var add1 = S.eval( add.compile() ); // assert.equal( add1(6), 7); // })(scheme, FLOOD); // // test case 4, use a curried function with map // (function(scheme, FLOOD) { // var num5 = new FLOOD.nodeTypes.Num(); // num5.value = 5; // var add = new FLOOD.nodeTypes.Add(); // add.inputs[0].useDefault = false; // add.inputs[1].connect( num5 ); // var map = new FLOOD.nodeTypes.Map(); // map.inputs[0].connect( add ); // map.inputs[1].defaultVal = ["quote", [0, 1, 2]]; // var S = new scheme.Interpreter(); // map.markDirty(); // var mapVal = S.eval( map.compile() ); // assert.equal( map.value.length, 3); // assert.equal( map.value[0], 5); // assert.equal( map.value[1], 6); // assert.equal( map.value[2], 7); // })(scheme, FLOOD); ================================================ FILE: app/scripts/lib/flood/test/scheme_async_test.html ================================================ ================================================ FILE: app/scripts/lib/flood/test/scheme_eval_async_test.js ================================================ var assert = require('assert') , async = require('../async.js') , scheme = require('../scheme.js'); (function(scheme) { var S = new scheme.Interpreter(); S.eval_async( ["begin", 1, 2, 4], undefined, function(res){ console.log(res); }); S.eval_async( [function(x){ return x * 3; }, [ function(){ return 2; } ] ], undefined, function(res){ console.log(res); }); S.eval_async( [ "quote", "cool yo" ], undefined, function(res){ console.log(res); }); // allows interpreter execution to not block! })(scheme); ================================================ FILE: app/scripts/main.js ================================================ require(["config"], function() { require(['backbone', 'App', 'AppView', 'Three', 'Viewport', 'FLOODCSG', 'bootstrap'], function (Backbone, App, AppView) { var app = new App(); app.fetch({ error: function(result) { console.error('error'); console.error(result); }, success: function(){ app.enableAutosave(); } }); var appView = new AppView({model: app}); }); }); ================================================ FILE: app/scripts/models/App.js ================================================ define(['backbone', 'Workspaces', 'Node', 'Login', 'Workspace', 'SearchElements'], function(Backbone, Workspaces, Node, Login, Workspace, SearchElements){ return Backbone.Model.extend({ idAttribute: "_id", url: function() { return '/mys'; }, defaults: { name: "DefaultSession", workspaces: new Workspaces(), backgroundWorkspaces: [], currentWorkspace: null, showingBrowser: false, showingSearch: false, showingFeedback: false, showingShare: false, showingHelp: false, isFirstExperience: false, clipBoard: {} }, initialize: function(args, options){ this.on('change:currentWorkspace', this.updateCurrentWorkspace, this); this.updateCurrentWorkspace(); this.login = new Login({}, { app: this }); this.SearchElements = new SearchElements({app:this}); this.SearchElements.fetch(); this.get('workspaces').on('remove', this.workspaceRemoved, this); }, workspaceIdsAwaitingParse : [], parse : function(resp) { var old = this.get('workspaces').slice(); this.workspaceIdsAwaitingParse = _.pluck( resp.workspaces, '_id'); this.get('workspaces').add(resp.workspaces, {app: this}); this.get('workspaces').remove(old); this.workspaceIdsAwaitingParse = []; resp.workspaces = this.get('workspaces'); return resp; }, fetch : function(options){ this.login.fetch(); Backbone.Model.prototype.fetch.call(this, options); }, toJSON : function() { if (this._isSerializing) { return this.id || this.cid; } this._isSerializing = true; var json = _.clone(this.attributes); _.each(json, function(value, name) { _.isFunction(value.toJSON) && (json[name] = value.toJSON()); }); this._isSerializing = false; // dont save the background workspaces, they will be dynamically // loaded on startup var backWs = this.get('backgroundWorkspaces'); json.workspaces = json.workspaces.filter(function(x){ return !_.contains( backWs, x._id ); }); return json; }, enableAutosave: function(){ this.get('workspaces').on('add remove', function(){ this.sync("update", this); }, this ); this.on('change:currentWorkspace', function(){ this.sync("update", this); }, this); this.on('change:isFirstExperience', function(){ this.sync("update", this); }, this); this.on('change:backgroundWorkspaces', function(){ this.sync("update", this); }, this); }, newNodePosition: [0,0], makeId: function(){ return Math.floor(Math.random() * 1e9); }, getCurrentWorkspace: function(){ return this.get('workspaces').get( this.get('currentWorkspace') ); }, getLoadedWorkspace: function(id){ return this.get('workspaces').get(id); }, newWorkspace: function( callback ){ var that = this; $.get("/nws", function(data, status){ var ws = new Workspace(data, {app: that }); that.get('workspaces').add( ws ); that.set('currentWorkspace', ws.get('_id') ); if (callback) callback( ws ); }).fail(function(){ console.error("failed to get new workspace"); }); }, newNodeWorkspace: function( callback ){ var that = this; $.get("/nws", function(data, status){ data.isCustomNode = true; var ws = new Workspace(data, { app: that }); that.get('workspaces').add( ws ); that.set('currentWorkspace', ws.get('_id') ); if (callback) callback( ws ); }).fail(function(){ console.error("failed to get new workspace"); }); }, loadWorkspaceDependency: function(id){ if ( _.contains( this.workspaceIdsAwaitingParse, id ) ) return; this.setWorkspaceToBackground( id ); this.loadWorkspace( id ); }, loadWorkspace: function( id, callback ){ var that = this; $.get("/ws/" + id, function(data, status){ console.log('workspace loaded ' + data.name ) var ws = new Workspace(data, {app: that}); that.get('workspaces').add( ws ); if (callback) callback( ws ); }).fail(function(){ console.error("failed to get workspace with id: " + id); }); }, isBackgroundWorkspace: function(id){ return this.get('backgroundWorkspaces').indexOf(id) != -1; }, setWorkspaceToBackground: function(id){ if ( !this.isBackgroundWorkspace( id ) ){ var copy = this.get('backgroundWorkspaces').slice(0); copy.push(id); this.set('backgroundWorkspaces', copy); } }, removeWorkspaceFromBackground: function( id ){ if ( _.contains( this.get('backgroundWorkspaces'), id) ){ var copy = this.get('backgroundWorkspaces').slice(0); copy.remove(copy.indexOf(id)); this.set('backgroundWorkspaces', copy); } }, openWorkspace: function( id, callback ){ this.removeWorkspaceFromBackground( id ); var ws = this.get('workspaces').get(id); if ( ws ){ this.set('currentWorkspace', id); } var that = this; this.loadWorkspace( id, function(ws){ that.set('currentWorkspace', ws.get('_id') ); if (callback) callback( ws ); }); }, updateCurrentWorkspace: function(){ if (this.get('workspaces').length === 0) return; this.get('workspaces').each(function(ele){ ele.set('current', false); }); if ( this.get('currentWorkspace') === null || !this.get('workspaces').get(this.get('currentWorkspace'))) { var ele = this.get('workspaces').at(0); this.set('currentWorkspace', ele.get('_id') ); } this.get('workspaces').get(this.get('currentWorkspace')).set('current', true); }, }); }) ================================================ FILE: app/scripts/models/Connection.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ idAttribute: "_id", defaults: { startNodeId: 0 , endNodeId: 0 , startPortIndex: 0 , endPortIndex: 0 , startProxy: false , endProxy: false , startProxyPosition: [0,0] , endProxyPosition: [0,0] , hidden: false }, workspace : null, startNode: null, endNode: null, initialize: function(args, options){ this.workspace = options.workspace; // proxyconnections bind to the proxyMove event on the workspace if ( args.startProxy || args.endProxy ) { this.workspace.bind('proxyMove', this.proxyMove, this); } else { // bind to end nodes this.startNode = this.workspace.get('nodes').get(args.startNodeId); this.endNode = this.workspace.get('nodes').get(args.endNodeId); } }, getOpposite: function(startNode){ if ( startNode === this.startNode ) { return {node: this.endNode, portIndex: this.get('endPortIndex')}; } if ( startNode === this.endNode ) { return {node: this.startNode, portIndex: this.get('startPortIndex')}; } return {}; } }); }); ================================================ FILE: app/scripts/models/Feedback.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ defaults: { showing: false, failure: false, failureMessage: "Unidentified error" }, send: function(data){ if ( !data.subject ){ this.set('failureMessage', 'Please supply a subject for your feedback!'); this.set('failure', true); return; } var that = this; $.post('/feedback', data, function(e){ if (e.length && e.length > 0 ) { that.set('failureMessage', e[0].msg); return that.set('failure', true); } that.set('failure', false); that.trigger('success'); }); } }); }); ================================================ FILE: app/scripts/models/GeometryExport.js ================================================ define(['FileSaver'], function(FileSaver) { /// Adapted from: https://github.com/stephomi/sculptgl/blob/master/src/misc/ExportSTL.js 'use strict'; var Export = {}; var Utils = {}; /** Export STL file */ Export.toSTL = function (scene, filename) { // merge all Geometry in three.js scene into one big bag of triangles var numTris = 0; var vertices = []; var faces = []; var faceNormals = []; var vertOffset = 0; scene.traverse(function(ele) { if (!ele.visible || !(ele instanceof THREE.Mesh) ) return; // collect vertices ele.geometry.vertices.forEach(function(v){ vertices.push( v.x ); vertices.push( v.y ); vertices.push( v.z ); }); // collect faces, face normals ele.geometry.faces.forEach(function(face){ faces.push(vertOffset + face.a); faces.push(vertOffset + face.b); faces.push(vertOffset + face.c); faceNormals.push( face.normal.x ); faceNormals.push( face.normal.y ); faceNormals.push( face.normal.z ); numTris += 1; }); vertOffset += ele.geometry.vertices.length; }); var blob = Export.toAsciiSTL(vertices, faces, faceNormals, numTris ); FileSaver( blob, filename ); }; Utils.normalizeArrayVec3 = function (array, out) { var arrayOut = out || array; for (var i = 0, l = array.length; i < l; ++i) { var j = i * 3; var nx = array[j]; var ny = array[j + 1]; var nz = array[j + 2]; var len = 1.0 / Math.sqrt(nx * nx + ny * ny + nz * nz); arrayOut[j] = nx * len; arrayOut[j + 1] = ny * len; arrayOut[j + 2] = nz * len; } return arrayOut; }; /** Return a buffer array which is at least nbBytes long */ Utils.getMemory = (function () { var pool = new ArrayBuffer(100000); return function (nbBytes) { if (pool.byteLength >= nbBytes) return pool; pool = new ArrayBuffer(nbBytes); return pool; }; })(); /** Export Ascii STL file */ Export.toAsciiSTL = function (vAr, iAr, origFN, nbTriangles) { var faceNormals = new Float32Array(Utils.getMemory(origFN.length * 4), 0, origFN.length); Utils.normalizeArrayVec3(origFN, faceNormals); var data = 'solid mesh\n'; for (var i = 0; i < nbTriangles; ++i) { var j = i * 3; data += ' facet normal ' + faceNormals[j] + ' ' + faceNormals[j + 1] + ' ' + faceNormals[j + 2] + '\n'; data += ' outer loop\n'; var iv1 = iAr[j] * 3; var iv2 = iAr[j + 1] * 3; var iv3 = iAr[j + 2] * 3; data += ' vertex ' + vAr[iv1] + ' ' + vAr[iv1 + 1] + ' ' + vAr[iv1 + 2] + '\n'; data += ' vertex ' + vAr[iv2] + ' ' + vAr[iv2 + 1] + ' ' + vAr[iv2 + 2] + '\n'; data += ' vertex ' + vAr[iv3] + ' ' + vAr[iv3 + 1] + ' ' + vAr[iv3 + 2] + '\n'; data += ' endloop\n'; data += ' endfacet\n'; } data += 'endsolid mesh\n'; return new Blob([data]); }; return Export; }); ================================================ FILE: app/scripts/models/Help.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ defaults: { sections: [ { title: "Workspace Tabs", targetId : "add-workspace-button", offset: [5, 5], text: "Switch between or add new active workspaces" }, { title: "Node Library", targetId : "bottom-search", offset: [5, -110], text: "This is one place where you can add new nodes. You can also double click on the workspace to add nodes." }, { title: "3D Focus Control", targetId : "workspace_hide", offset: [-170,-100], text: "Toggle between the 3D view and your workspace" }, { title: "Workspace Browser", targetId : "workspace-browser-button", offset: [-170,0], text: "You can find all of your past work in the Workspace Browser" }, { title: "Export STL", targetId : "export-button", offset: [20,-110], text: "Export all visible geometry to STL for 3D printing" }, { title: "Share a customizer", targetId : "share-button", offset: [-200,10], text: "Share a customizer with anyone on the internet!" }, // { title: "Zoom", // targetId : "zoomreset-button", // offset: [20,-110], // text: "Use this control the workspace zoom, or use Ctrl +, Ctrl -" // } ] } }); }); ================================================ FILE: app/scripts/models/Login.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ defaults: { isLoggedIn : false, failed : false, failureMessage : "", showing: false, email : "" }, initialize: function(atts, vals) { this.app = vals.app; }, fetch : function(){ var that = this; $.get('/email', function(e){ if (e.email) { that.set('email', e.email); that.set('isLoggedIn', true); } else { that.set('isLoggedIn', false); } }); }, toggle: function(){ return this.get('showing') ? this.hide() : this.show(); }, show: function() { this.set('showing', true); }, hide: function() { this.set('showing', false); }, signup: function(data){ var that = this; $.post('/signup', data, function(e){ if (e.length && e.length > 0 ) { that.set('failureMessage', e[0].msg); return that.set('failed', true); } that.set('failed', false); that.app.fetch(); }); }, login: function(data){ var that = this; $.post('/login', data, function(e){ if (e.length && e.length > 0 ) { that.set('failureMessage', e[0].msg); return that.set('failed', true); } that.set('failed', false); that.app.fetch(); }); }, logout: function(){ var that = this; $.get('/logout', {}, function(e){ that.app.fetch(); }); } }); }); ================================================ FILE: app/scripts/models/Marquee.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ idAttribute: "_id", defaults: { x: 10 , y: 10 , width: 50 , height: 50 , hidden: true }, startCorner: [10,10], endCorner: [20, 20], initialize: function(args, options){ this.workspace = options.workspace; }, updateRawValues: function(){ this.set('width', Math.abs( this.startCorner[0] - this.endCorner[0] ) ); this.set('height', Math.abs( this.startCorner[1] - this.endCorner[1] ) ); this.set('x', Math.min( this.startCorner[0], this.endCorner[0] ) ); this.set('y', Math.min( this.startCorner[1], this.endCorner[1] ) ); }, setStartCorner: function( posInWorkspace ){ this.startCorner = posInWorkspace; this.endCorner = posInWorkspace; this.updateRawValues(); }, setEndCorner: function( posInWorkspace ){ this.endCorner = posInWorkspace; this.updateRawValues(); } }); }); ================================================ FILE: app/scripts/models/Node.js ================================================ var app = app || {}; define(['backbone', 'FLOOD'], function(Backbone, FLOOD) { return Backbone.Model.extend({ idAttribute: "_id", defaults: { name: 'DefaultNodeName' , position: [10, 10] , typeName: 'Add' , type: null , inputConnections: [] , outputConnections: [] , selected: true , lastValue: null , failureMessage: null , visible: true , replication: "applyLongest" , extra: {} , ignoreDefaults: [] , isEvaluating: false }, initialize: function(atts, vals) { // we need to know the type in order to create the node if ( atts.typeName != null && FLOOD.nodeTypes[atts.typeName] != undefined){ this.set( 'type', new FLOOD.nodeTypes[ atts.typeName ]() ); } else if ( atts.typeName != null && FLOOD.internalNodeTypes[atts.typeName] != undefined ) { this.set( 'type', new FLOOD.internalNodeTypes[ atts.typeName ]() ); } else { this.set( 'type', new FLOOD.nodeTypes.Add() ); } if (atts.extra){ this.get('type').extend( atts.extra ); } if (atts.lastValue){ this.get('type').value = atts.lastValue; } if (atts.ignoreDefaults && atts.ignoreDefaults.length > 0){ for (var i = 0; i < this.get('type').inputs.length; i++){ this.get('type').inputs[i].useDefault = !atts.ignoreDefaults[i]; } } else { atts.ignoreDefaults = this.get('type').inputs.map(function(x){ return !x.useDefault; }); } this.set('ignoreDefaults', atts.ignoreDefaults ); var that = this; this.on('connection', this.onConnectPort); this.on('disconnection', this.onDisconnectPort); this.workspace = vals.workspace; this.on('remove', this.onRemove); this.initializePorts(); }, // called when saving the node to server serialize : function() { var vals = { name: this.get("name") , position: this.get('position') , typeName: this.get('typeName') , selected: this.get('selected') , visible: this.get('visible') , ignoreDefaults: this.get('ignoreDefaults') , _id: this.get('_id') , replication: this.get('replication') , extra: this.get('extra') }; return vals; }, initializePorts: function() { var type = this.get('type'); this.set('inputConnections', new Array( type.inputs.length )); this.set('outputConnections', new Array( type.outputs.length )); }, onRemove: function(){ this.trigger('removed'); }, onEvalFailed: function(ex){ this.trigger('evalFailed', ex); }, onEvalBegin: function(isNew){ if (!isNew) return; this.trigger('evalBegin'); this.set('isEvaluating', true); }, onEvalComplete: function(isNew, value, prettyValue){ if (!isNew) return; this.set('lastValue', value); this.set('prettyLastValue', prettyValue); this.set('isEvaluating', false); this.trigger('evalComplete'); }, select: function() { this.set('selected', false); }, deselect: function() { this.set('selected', true); }, // get the input or output ports of a node getPorts: function(isOutput){ return isOutput ? this.get('outputConnections') : this.get('inputConnections'); }, // determine if a given port is connected or not isPortConnected: function(index, isOutput){ if ( !this.isValidPort(index, isOutput) ) { return true; } var ports = this.getPorts(isOutput); return ports[index] != null && ports[index].length > 0; }, isPartialFunctionApplication: function(){ var numInPorts = this.get('inputConnections').length; for (var i = 0; i < numInPorts; i++){ if (!this.isPortConnected(i, false) && !this.isInputPortUsingDefault(i) ){ return true; } } return false; }, isInputPortUsingDefault: function(index){ if ( !this.isValidPort(index, false) ) { return false; } return !this.get('ignoreDefaults')[index]; }, // get the type of a given node port getPortType: function(index, isOutput){ if (index < 0) return null; var type = this.get('type') , ports = isOutput ? type.outputs : type.inputs; if ( ports.length > index ) return ports[index].type return null; }, isValidPort: function(index, isOutput){ return this.getPortType(index, isOutput) != null; }, // get the node and index of the opposite end of a port // returns object containing "node", "portIndex" fields // if out of range, returns null getOppositeNodeAndPort: function(index, isOutput){ if ( !this.isValidPort(index, isOutput) ) { return null; } return this.getPorts(isOutput)[index]; }, isOutputNode: function(){ return this.get('outputConnections').reduce(function(memo, ele){ return memo && (ele.length === 0); }, true); }, connectPort: function( portIndex, isOutput, connection ) { if ( !this.isValidPort(portIndex, isOutput) ) { return null; // the port doesn't exist } // initialize if necessary if ( this.getPorts( isOutput )[portIndex] === undefined ) this.getPorts( isOutput )[portIndex] = []; // add the connection to the array this.getPorts( isOutput )[portIndex].push(connection); // listen for deletion or update of the connection var that = this; this.listenTo( connection, 'remove', (function(){ return function(){ that.disconnectPort( portIndex, connection, isOutput ); }; })()); this.trigger('connection', portIndex, isOutput, connection); this.trigger('change'); return this; }, getConnectionAtIndex: function( portIndex, isOutput, connectionIndex ){ var ports = this.getAllConnectionsAtPort( portIndex, isOutput ); if (ports == null) return null; if ( connectionIndex === undefined ) connectionIndex = 0; if ( connectionIndex >= ports.length || connectionIndex < 0) return null; return ports[connectionIndex]; }, getAllConnectionsAtPort: function( portIndex, isOutput ){ if (!this.isValidPort(portIndex, isOutput)) return null; if (isOutput === undefined) isOutput = false; var ports = this.getPorts(isOutput)[portIndex]; return ports; }, disconnectPort: function( portIndex, connection, isOutput ){ if (!this.isValidPort(portIndex, isOutput)){ return; } var port = this.getPorts(isOutput)[portIndex]; if (port == null) return; var index = -1; if ( connection ){ var index = port.indexOf(connection); } else if (!isOutput && port.length > 0) { index = 0; connection = port[0]; isOutput = false; this.workspace.get('connections').remove( connection ); } if (index === -1) return; // remove the requested connection on the port port.remove(index); this.trigger('disconnection', portIndex, isOutput, connection); this.trigger('change'); }, onConnectPort: function( portIndex, isOutput, connection){ if (isOutput) return; // connect the logic nodes var type = this.get('type') , opp = connection.getOpposite( this ) , oppType = opp.node.get('type') , oppIndex = opp.portIndex; type.inputs[portIndex].connect( oppType, oppIndex ); }, onDisconnectPort: function( portIndex, isOutput, connection ){ if (isOutput){ return; } if (!isOutput){ this.get('type').inputs[portIndex].disconnect(); } if (this.workspace) this.workspace.run(); } }); }); ================================================ FILE: app/scripts/models/Runner.js ================================================ define(['backbone','underscore'], function(Backbone, _) { return Backbone.Model.extend({ defaults: { isRunning : false }, initialize: function(atts, vals) { this.app = vals.app; var ws = vals.workspace; this.workspace = ws; this.set('id', ws.get('_id') ); this.reset(); vals.workspace.get('connections').on('add', this.addConnection, this ); vals.workspace.get('connections').on('remove', this.removeConnection, this ); vals.workspace.get('nodes').on('add', this.addNode, this ); vals.workspace.get('nodes').on('remove', this.removeNode, this ); this.runCount = 0; this.averageRunTime = 0; this.run = _.throttle(this.run, 120); }, post: function(data, quiet){ if (!data.workspace_id) data.workspace_id = this.workspace.get('_id'); this.worker.postMessage(data); if (quiet) return; this.trigger('post', data ); }, onWorkerMessage: function(data){ var cb = this["on_" + data.kind]; if ( cb ) cb.call(this, data); }, initWorker: function(){ this.worker = new Worker("scripts/lib/flood/flood_runner.js"); var that = this; this.worker.addEventListener('message', function(e) { return that.onWorkerMessage.call(that, e.data); }, false); }, initWorkspace: function(){ this.post({kind: 'addWorkspace'}); var wsc = this.workspace.toJSON(); var ncb = function(){ this.updateNode( node ); }; var that = this; this.workspace.get('nodes').each(function(x){ that.watchNodeEvents.call(that, x); }); wsc.kind = "setWorkspaceContents"; this.post( wsc ); }, on_nodeEvalComplete: function(data){ var node = this.workspace.get('nodes').get( data._id ); if (node) node.onEvalComplete( data.isNew, data.value, data.prettyValue ); }, on_nodeEvalFailed: function(data){ var node = this.workspace.get('nodes').get( data._id ); if (node) this.workspace.get('nodes').get( data._id ).onEvalFailed(data.exception); }, on_nodeEvalBegin: function(data){ var node = this.workspace.get('nodes').get( data._id ); if (node) this.workspace.get('nodes').get( data._id ).onEvalBegin( data.isNew ); }, on_run: function(data){ console.log( data ); this.set('isRunning', false); this.runCount++; this.trigger('runComplete'); }, cancel: function(){ this.set('isRunning', false); this.worker.terminate(); this.reset(); }, runQueued : false, run: function( bottomIds ){ this.post({ kind: "run", bottom_ids: bottomIds }); this.set('isRunning', true); }, watchNodeEvents: function( node ){ var u = function(){ this.updateNode( node ); }; node.on('change:replication', u, this ); node.on('change:ignoreDefaults', u, this); node.on('updateRunner', u, this ); }, updateNode: function( node ){ var n = node.serialize(); n.kind = "updateNode"; n.workspace_id = node.workspace.id; this.post( n ); }, addNode: function(node){ var n = node.serialize(); n.kind = "addNode"; n.workspace_id = node.workspace.id; this.watchNodeEvents( node ); this.post(n); }, removeNode: function(node){ var n = node.serialize(); n.kind = "removeNode"; n.workspace_id = node.workspace.id; this.post( n ); }, addConnection: function(connection, workspace){ var c = connection.toJSON(); c.kind = "addConnection"; c.id = connection.get('_id'); c.workspace_id = connection.workspace.id; this.post(c); }, removeConnection: function(connection){ var c = connection.toJSON(); c.kind = "removeConnection"; c.id = connection.get('endNodeId'); c.portIndex = connection.get('endPortIndex'); c.workspace_id = connection.workspace.id; this.post( c ); }, recompile: function(workspace){ var c = workspace.toJSON(); c.kind = "recompile"; this.post( c ); }, addDefinition: function(workspace){ var c = workspace.toJSON(); c.kind = "addDefinition"; c.workspace_id = c._id; var that = this; workspace.get('nodes').each(function(x){ that.watchNodeEvents.call(that, x); }); workspace.get('connections').on('add', function(x){ this.addConnection(x); this.recompile(workspace); this.workspace.trigger('requestRun'); }, this ); workspace.get('connections').on('remove', function(x){ this.removeConnection(x); this.recompile(workspace); this.workspace.trigger('requestRun'); }, this ); workspace.get('nodes').on('add', function(x){ this.addNode(x); this.recompile(workspace); this.workspace.trigger('requestRun'); }, this ); workspace.get('nodes').on('remove', function(x){ this.removeNode(x); this.recompile(workspace); this.workspace.trigger('requestRun'); }, this ); this.post( c ); }, on_recompile: function(data){ console.log(data); }, reset: function(){ this.initWorker(); this.initWorkspace(); } }); }); ================================================ FILE: app/scripts/models/Search.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ defaults: { }, initialize: function(atts, vals) { } }); }); ================================================ FILE: app/scripts/models/SearchElement.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ defaults: { name: null, isCustomNode: false, functionId: -1 }, initialize: function(a, b) { this.app = a.app; } }); }); ================================================ FILE: app/scripts/models/Share.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ defaults: { isProjectWorkspace: false, }, initialize: function(atts, vals) { this.app = atts.app; // this.app.on('change:currentWorkspace', function(x){ // console.log( this.app.getCurrentWorkspace() ); // }, this); }, }); }); ================================================ FILE: app/scripts/models/Workspace.js ================================================ define(['backbone', 'Nodes', 'Connection', 'Connections', 'scheme', 'FLOOD', 'Runner', 'Node', 'Marquee', 'WorkspaceResolver', 'GeometryExport'], function(Backbone, Nodes, Connection, Connections, scheme, FLOOD, Runner, Node, Marquee, WorkspaceResolver, GeometryExport) { return Backbone.Model.extend({ idAttribute: "_id", url: function(){ return '/ws/' + this.get('_id'); }, defaults: { name: "Unnamed Workspace", nodes: null, connections: null, zoom: 1, current: false, isPublic: false, isRunning: false, lastSaved: Date.now(), offset: [4000,4000], // undo/redo stack undoStack: [], redoStack: [], clipBoard: [], // for custom nodes workspaceDependencyIds: [], isCustomNode: false, isCustomizer: false }, // connection creation draggingProxy: false, proxyConnection: null, // marquee selection dragSelect: false, runAllowed: false, initialize: function(atts, arr) { atts = atts || {}; // if offset is not defined if (!atts.offset || isNaN( atts.offset[0] ) || isNaN( atts.offset[1] )){ atts.offset = this.defaults.offset; this.set( 'offset', this.defaults.offset ); } this.app = arr.app; this.set('nodes', new Nodes( atts.nodes, { workspace: this }) ); this.set('connections', new Connections( atts.connections, { workspace: this}) ); // tell all nodes about connections _.each( this.get('connections').where({startProxy: false, endProxy: false}), function(ele, i) { this.get('nodes').get(ele.get('startNodeId')).connectPort( ele.get('startPortIndex'), true, ele); this.get('nodes').get(ele.get('endNodeId')).connectPort(ele.get('endPortIndex'), false, ele); }, this); // updates to connections and nodes are emitted to listeners var that = this; this.get('connections').on('add remove', function(){ that.trigger('change:connections'); that.trigger('requestRun'); }); this.get('nodes').on('add remove', function(){ that.trigger('change:nodes'); that.trigger('requestRun'); }); this.proxyConnection = new Connection({ _id: -1, startProxy: true, endProxy: true, startProxyPosition: [0,0], endProxyPosition: [0,0], hidden: true }, { workspace: this }); this.marquee = new Marquee({ _id: -1, hidden: true }, { workspace: this }); this.runAllowed = true; if ( !this.get('isCustomNode') ) this.initializeRunner(); this.sync = _.throttle(this.sync, 2000); // save on every change var throttledSync = _.throttle(function(){ this.sync('update', this); }, 1000); this.on('runCommand', throttledSync, this); this.on('change:name', throttledSync, this); this.on('change:zoom', throttledSync, this); this.on('change:offset', throttledSync, this); this.on('change:workspaceDependencyIds', throttledSync, this); this.on('requestRun', this.run, this); // this should not be throttled this.on('change:isCustomizer', function(){ this.sync('update', this); }, this); if ( this.get('isCustomNode') ) this.initializeCustomNode(); this.resolver = new WorkspaceResolver(null, { app : this.app, workspace : this }); this.resolver.resolveAll(); this.app.trigger('workspaceLoaded', this); }, getCustomizerUrl: function(){ // if (!this.get('isCustomizer')){ // return "none"; // } var domain = document.URL.match(/:\/\/(.[^/]+)/)[1]; return domain + "/customize-" + this.id; }, exportSTL: function(){ GeometryExport.toSTL(scene, this.get('name') + ".stl" ); }, customNode : null, initializeCustomNode: function(){ this.customNode = new FLOOD.internalNodeTypes.CustomNode( this.get('name'), this.get('_id') ); var ni = this.get('nodes').where({typeName: "Input"}).length; var no = this.get('nodes').where({typeName: "Output"}).length; this.customNode.setNumInputs(ni); this.customNode.setNumOutputs(no); this.app.SearchElements.addCustomNode( this.customNode ); var that = this; this.on('change:name', function(){ that.customNode.functionName = that.get('name'); that.app.SearchElements.addCustomNode( that.customNode ); }, this); }, toJSON : function() { this.set('undoStack', _.last( this.get('undoStack'), 10) ); this.set('redoStack', _.last( this.get('redoStack'), 10) ); if (this._isSerializing) { return this.id || this.cid; } this._isSerializing = true; var json = _.clone(this.attributes); _.each(json, function(value, name) { _.isFunction(value.toJSON) && (json[name] = value.toJSON()); }); this._isSerializing = false; return json; }, initializeRunner: function(){ this.runner = new Runner({id : this.get('_id') }, { workspace: this }); var that = this; this.runner.on('change:isRunning', function(v){ that.set('isRunning', v.get('isRunning')); }); }, getCustomNodeInputsOutputs: function(getOutputs){ var typeName = getOutputs ? "Output" : "Input"; return this.get('nodes').filter(function(x){ return x.get('type').typeName === typeName; }); }, getCustomNodes: function(){ return this.get('nodes').filter(function(x){ return x.get('type') instanceof FLOOD.internalNodeTypes.CustomNode; }); }, getCustomNodesWithId: function(functionId){ return this.getCustomNodes().filter(function(x){ return x.get('type').functionId === functionId; }); }, zoomIn: function(){ if ( this.get('zoom') + 0.2 > 1 ){ return this.set('zoom', 1); } this.set('zoom', this.get('zoom') + 0.2); }, zoomOut: function(){ if ( this.get('zoom') - 0.2 < 0.2 ){ return this.set('zoom', 0.2); } this.set('zoom', this.get('zoom') - 0.2); }, parse : function(resp) { resp.nodes = new Nodes( resp.nodes ); resp.connections = new Connections( resp.connections ) return resp; }, printModel: function(){ console.log(this.toJSON()); }, addToUndoAndClearRedo: function(cmd){ this.get('undoStack').push(cmd); this.get('redoStack').length = 0; }, removeSelected: function(){ // get all selected nodes var that = this; var nodeFound = false; var nodesToRemove = {}; this.get('nodes') .each(function(x){ if ( x.get('selected') ){ nodeFound = true; nodesToRemove[ x.get('_id') ] = x.serialize(); } }); if (!nodeFound) return; // get all relevant connections var connsToRemove = {}; this.get('connections') .each(function(x){ if ( nodesToRemove[ x.get('startNodeId') ] || nodesToRemove[ x.get('endNodeId') ] ){ if ( !connsToRemove[ x.get('_id') ] ){ connsToRemove[ x.get('_id') ] = x.toJSON(); } } }); // construct composite command var multipleCmd = { kind: "multiple", commands: [] }; // first remove all connections for (var connId in connsToRemove){ var connToRemove = connsToRemove[connId]; connToRemove.kind = "removeConnection"; multipleCmd.commands.push( connToRemove ); } // then remove all nodes for (var nodeId in nodesToRemove){ var nodeToRemove = nodesToRemove[nodeId]; nodeToRemove.kind = "removeNode"; multipleCmd.commands.push( nodeToRemove ); } this.runInternalCommand( multipleCmd ); this.addToUndoAndClearRedo( multipleCmd ); }, makeId: function(){ return this.app.makeId(); }, copy: function(){ // get all selected nodes var that = this; var nodeFound = false; var copyNodes = {}; this.get('nodes') .each(function(x){ if ( x.get('selected') ){ nodeFound = true; copyNodes[ x.get('_id') ] = x.serialize(); } }); // TODO: clear the clipboard! if (!nodeFound) return; // get all relevant connections var copyConns = {}; var connCount = 0; this.get('connections') .each(function(x){ if (x.get('_id') === -1 || x.get('startProxy') || x.get('endProxy')) return; if ( ( copyNodes[ x.get('startNodeId') ] && copyNodes[ x.get('endNodeId') ] ) || copyNodes[ x.get('endNodeId') ] ){ if ( !copyConns[ x.get('_id') ] ){ connCount++; copyConns[ x.get('_id') ] = x.toJSON(); } } }); this.app.set('clipboard', { nodes: copyNodes, connections: copyConns }); }, paste: function(){ // build the command var cb = JSON.parse( JSON.stringify( this.app.get('clipboard') ) ); var that = this; var nodes = {}; var centerX = (1 / this.get('zoom')) * (this.get('offset')[0] + 80); var centerY = (1 / this.get('zoom')) * (this.get('offset')[1] + 80); var topLeft = _.reduce(cb.nodes, function(a, x){ return [ Math.min(a[0], x.position[0] ), Math.min(a[1], x.position[1] )]; }, [ 1e8, 1e8 ] ); var nodeCount = 0; _.each(cb.nodes, function(x){ // give new id for building the paste nodes[x._id] = x; var posX = x.position[0] - topLeft[0] + centerX; var posY = x.position[1] - topLeft[1] + centerY; nodes[x._id].position = [ posX, posY ]; nodes[x._id]._id = that.makeId(); nodeCount++; }); if (nodeCount > 0) this.get('nodes').deselectAll(); var connections = {}; _.each(cb.connections, function(x){ if ( nodes[ x.endNodeId ] ){ x.endNodeId = nodes[ x.endNodeId ]._id; } if ( nodes[x.startNodeId]){ x.startNodeId = nodes[ x.startNodeId ]._id; } connections[x._id] = x; connections[x._id]._id = that.makeId(); }); // build the command var multipleCmd = { kind: "multiple", commands: [] }; // build all of the nodes for (var id in nodes){ var cpnode = cb.nodes[id]; cpnode.kind = "addNode"; multipleCmd.commands.push( cpnode ); } // then builds the connections for (var id in connections){ var cpConn = connections[id]; cpConn.kind = "addConnection"; multipleCmd.commands.push( cpConn ); } this.runInternalCommand( multipleCmd ); this.addToUndoAndClearRedo( multipleCmd ); }, addNodeByNameAndPosition: function(name, position){ if (name === undefined || position === undefined ) return; var se = this.app.SearchElements.where({ name: name })[0]; if (!se) { console.warn('Could not find node with name in Library: ' + name) return; } if (se.get('isCustomNode')){ var sec = { typeName: "CustomNode" , position: position , _id: this.makeId() }; sec.extra = { functionId: se.get('functionId') , functionName: se.get('functionName') , numInputs: se.get('numInputs') , numOutputs: se.get('numOutputs') }; return this.addNode( sec ); } this.addNode({ typeName: name, position: position, _id: this.makeId() }); }, regenerateDependencies: function(){ var that = this; var directDependencies = this.getCustomNodes().map(function(x){ return x.get('type').functionId; }); var indirectDependencies = directDependencies.map(function(x){ return that.app.get('workspaces').get(x); }).map(function(x){ return x.get('workspaceDependencyIds'); }); var allDependencyLists = directDependencies.concat( indirectDependencies ); return _.union.apply(null, allDependencyLists ); }, addNode: function(data){ this.get('nodes').deselectAll(); if ( data.typeName === "CustomNode" ){ var id = data.extra.functionId; // do not allow recursion if (id === this.get('_id')) return; this.addWorkspaceDependency( id, true ); this.sendCompleteDefinitionRunner( id ); } var datac = JSON.parse( JSON.stringify( data ) ); datac.kind = "addNode"; this.runInternalCommand(datac); this.addToUndoAndClearRedo( datac ); if ( data.typeName === "CustomNode" ){ this.syncCustomNodesWithWorkspace( id ); } this.trigger('requestRun'); }, addWorkspaceDependency: function(id, watchDependency){ this.resolver.addWorkspaceDependency(id, watchDependency); }, syncCustomNodesWithWorkspace: function(workspace){ this.resolver.syncCustomNodesWithWorkspace(workspace); }, sendCompleteDefinitionRunner: function( id ){ // custom node workspace if (!this.runner) { return; } var ws = this.app.getLoadedWorkspace( id ); var allDeps = ws.regenerateDependencies().concat([id]); var that = this; allDeps.forEach(function(depId){ that.sendDefinitionToRunner( depId ); }); }, sendDefinitionToRunner: function( id ){ // custom node workspace if (!this.runner) { return; } var ws = this.app.getLoadedWorkspace( id ); this.runner.addDefinition( ws ); }, removeNode: function(data){ var datac = JSON.parse( JSON.stringify( data ) ); datac.kind = "removeNode"; this.runInternalCommand(datac); this.addToUndoAndClearRedo( datac ); }, addConnection: function(data){ var datac = JSON.parse( JSON.stringify( data ) ); datac.kind = "addConnection"; this.runInternalCommand(datac); this.addToUndoAndClearRedo( datac ); }, addConnectionAndRemoveExisting : function(startNodeId, startPort, endNodeId, endPort) { var multiCmd = { kind: "multiple", commands: [] }; // remove any existing connection var endNode = this.get('nodes').get(endNodeId) if ( !endNode ) return this; var existingConnection = endNode.getConnectionAtIndex( endPort ); if (existingConnection != null){ var rmConn = existingConnection.toJSON(); rmConn.kind = "removeConnection"; multiCmd.commands.push( rmConn ); } var newConn = { kind: "addConnection", startNodeId: startNodeId, startPortIndex: startPort, endNodeId: endNodeId, endPortIndex: endPort, _id: this.app.makeId() }; multiCmd.commands.push( newConn ); this.runInternalCommand( multiCmd ); this.addToUndoAndClearRedo( multiCmd ); return this; }, removeConnection: function(data){ var datac = JSON.parse( JSON.stringify( data ) ); datac.kind = "removeConnection"; this.runInternalCommand(datac); this.addToUndoAndClearRedo( datac ); }, setNodeProperty: function(data){ var datac = JSON.parse( JSON.stringify( data ) ); datac.kind = "setNodeProperty"; this.runInternalCommand(datac); this.addToUndoAndClearRedo( datac ); }, internalCommands: { multiple: function(data){ // we prevent runs until all of the changes have been committed var previousRunAllowedState = this.runAllowed; this.runAllowed = false; // run all of the commands var that = this; data.commands.forEach(function(x){ that.runInternalCommand.call(that, x); }); // restore previous runAllowed state and, if necessary, do run this.runAllowed = previousRunAllowedState; if (this.runRejected) this.run(); }, addNode: function(data){ var node = new Node( data, { workspace: this }); this.get('nodes').add( node ); }, removeNode: function(data){ var node = this.get('nodes').get(data._id); this.get('nodes').remove( node ); }, addConnection: function(data){ var nodes = this.get('nodes'); if ( !nodes.get( data.startNodeId ) || !nodes.get( data.endNodeId ) ) return; var conn = new Connection(data, { workspace: this }); this.get('connections').add( conn ); this.get('nodes').get(conn.get('startNodeId')).connectPort( conn.get('startPortIndex'), true, conn); this.get('nodes').get(conn.get('endNodeId')).connectPort(conn.get('endPortIndex'), false, conn); }, removeConnection: function(data){ var conn = this.get('connections').get(data._id); if (conn) this.get('connections').remove( conn ); }, setNodeProperty: function(data){ var node = this.get('nodes').get( data._id ); var prop = data.property; if (!data.oldValue) data.oldValue = JSON.parse( JSON.stringify( node.get(prop) ) ); node.set( prop, data.newValue ); } }, runInternalCommand: function(commandData){ var cmd = this.internalCommands[ commandData.kind ]; if (cmd){ cmd.call(this, commandData); this.trigger('runCommand'); return; } console.warn('Could not find the command: ' + cmd.kind); }, redo: function(){ var rs = this.get('redoStack'); if (rs.length === 0) { return console.warn("Nothing to redo!"); } var data = rs.pop(); this.get('undoStack').push(data); this.runInternalCommand(data); }, undo: function(){ var us = this.get('undoStack'); if (us.length === 0) { return; } var command = us.pop(); var undoCommand = this.invertCommand( command ); this.get('redoStack').push( command ); this.runInternalCommand(undoCommand); }, invertCommand: function(cmd){ var inverter = this.commandInversions[cmd.kind]; if ( inverter ){ return inverter.call(this, cmd); } return {}; }, commandInversions: { addNode: function( cmd ){ var cmdcop = JSON.parse( JSON.stringify( cmd ) ); cmdcop.kind = "removeNode"; return cmdcop; }, multiple: function( cmd ){ var cmdcop = JSON.parse( JSON.stringify( cmd ) ); var that = this; cmdcop.commands = cmdcop.commands.map(function(x){ return that.invertCommand.call(that, x); }); cmdcop.commands.reverse(); return cmdcop; }, removeNode: function( cmd ){ var cmdcop = JSON.parse( JSON.stringify( cmd ) ); cmdcop.kind = "addNode"; return cmdcop; }, addConnection: function(cmd){ var cmdcop = JSON.parse( JSON.stringify( cmd ) ); cmdcop.kind = "removeConnection"; return cmdcop; }, removeConnection: function(cmd){ var cmdcop = JSON.parse( JSON.stringify( cmd ) ); cmdcop.kind = "addConnection"; return cmdcop; }, setNodeProperty: function(cmd){ var cmdcop = JSON.parse( JSON.stringify( cmd) ); var temp = cmdcop.oldValue; cmdcop.oldValue = cmdcop.newValue; cmdcop.newValue = temp; return cmdcop; } }, run: function() { if ( !this.runAllowed || this.get('isCustomNode') ){ this.runRejected = true; return; } this.runReject = false; if (this.get('nodes').length === 0){ return; } var bottomNodes = this.get('nodes') .filter(function(ele){ return ele.isOutputNode() && ele.get('type').outputs.length > 0; }).map(function(ele){ return ele.get('_id'); }); this.runner.run( bottomNodes ); }, startMarqueeSelect: function(startPosition) { this.set('marqueeStart', startPosition ); this.set('marqueeEnd', startPosition ); this.set('marqueeSelectEnabled', true); return this; }, endMarqueeSelect: function() { this.set('marqueeSelectEnabled', false); return this; }, startProxyConnection: function(startNodeId, nodePort, startPosition) { // Note: this is a quick fix for when the proxy connection this.set('proxyStartId', startNodeId); this.set('proxyStartPortIndex', nodePort); // set the initial properties for a dragging proxy this.proxyConnection.set('hidden', false); this.proxyConnection.set('startNodeId', startNodeId); this.proxyConnection.set('startPortIndex', nodePort ); this.proxyConnection.set('startProxy', false ); this.proxyConnection.set('endProxy', true ); this.proxyConnection.set('endProxyPosition', startPosition); this.draggingProxy = true; this.trigger('startProxyDrag'); return this; }, completeProxyConnection: function(endNodeId, endPortIndex) { this.draggingProxy = false; this.trigger('endProxyDrag'); var startNodeId = this.proxyConnection.get('startNodeId') , startPortIndex = this.proxyConnection.get('startPortIndex'); this.addConnectionAndRemoveExisting(startNodeId, startPortIndex, endNodeId, endPortIndex); return this; }, endProxyConnection: function() { this.proxyConnection.set('hidden', true); this.draggingProxy = false; return this; } }); }); ================================================ FILE: app/scripts/models/WorkspaceBrowser.js ================================================ define(['backbone', 'WorkspaceBrowserElements'], function(Backbone, WorkspaceBrowserElements) { return Backbone.Model.extend({ defaults: { workspaces: new WorkspaceBrowserElements() }, initialize: function(atts, vals) { this.get('workspaces').fetch(); }, refresh: function(){ this.get('workspaces').reset(); this.get('workspaces').fetch(); } }); }); ================================================ FILE: app/scripts/models/WorkspaceBrowserElement.js ================================================ define(['backbone'], function(Backbone) { return Backbone.Model.extend({ idAttribute: "_id", defaults: { name: "Unnamed Workspace", isPublic: false, isCustomNode: false, lastSaved: Date.now() }, initialize: function(atts, vals) { } }); }); ================================================ FILE: app/scripts/models/WorkspaceResolver.js ================================================ define(['backbone', 'FLOOD'], function(Backbone, FLOOD) { return Backbone.Model.extend({ initialize: function(atts, arr) { this.workspace = arr.workspace; this.app = arr.app; }, resolveAll: function(){ this.initializeDependencies( this.workspace.get('workspaceDependencyIds') ); }, initializeDependencies: function(depIds){ if (depIds.length === 0 || !depIds ) { this.workspace.trigger('requestRun'); return; } this.awaitedWorkspaceDependencyIds = []; var that = this; this.app.get('workspaces').on('add', function(ws){ that.resolveDependency.call(that, ws); }, this); depIds.forEach(function(x){ that.awaitOrResolveDependency.call(that, x); }); }, cleanupDependencies: function(){ this.workspace.set( 'workspaceDependencyIds', this.workspace.regenerateDependencies() ); }, awaitOrResolveDependency: function(id){ var ws = this.app.getLoadedWorkspace(id); if (ws) return this.resolveDependency(ws); this.awaitedWorkspaceDependencyIds.push(id); this.app.loadWorkspaceDependency( id ); }, resolveDependency: function(workspace){ if (workspace.id === this.id) return; var index = this.awaitedWorkspaceDependencyIds.indexOf( workspace.id ); if (index < 0) return; this.awaitedWorkspaceDependencyIds.remove(index); this.workspace.sendDefinitionToRunner( workspace.id ); this.watchOneDependency( workspace ); this.syncCustomNodesWithWorkspace( workspace ); if (this.awaitedWorkspaceDependencyIds.length === 0) { this.workspace.run(); this.cleanupDependencies(); } }, addWorkspaceDependency: function( id, watch ){ var ws = this.app.getLoadedWorkspace(id); if (!ws) throw new Error("You tried to add an unloaded workspace as a dependency!") var depDeps = ws.get('workspaceDependencyIds') , currentDeps = this.workspace.get('workspaceDependencyIds') , unionDeps = _.union( [id], currentDeps, depDeps ); this.workspace.set( 'workspaceDependencyIds', unionDeps ); if (watch) this.watchDependency( id ); }, watchDependency: function( id ){ var ws = this.app.getLoadedWorkspace(id); var allDepWorkspaces = ws.get('workspaceDependencyIds').concat( id ); allDepWorkspaces.forEach(function(depWs){ this.watchOneDependency( depWs ); }.bind( this ) ); }, watchedDependencies: {}, watchOneDependency: function( customNodeWorkspace ){ if ( !customNodeWorkspace.id ){ customNodeWorkspace = this.app.getLoadedWorkspace( customNodeWorkspace ); } if ( this.watchedDependencies[ customNodeWorkspace.id ] ) return; this.watchedDependencies[ customNodeWorkspace.id ] = true; var that = this; var sync = function(){ that.syncCustomNodesWithWorkspace.call(that, customNodeWorkspace) } , syncAndRequestRun = function(){ that.syncCustomNodesWithWorkspace.call(that, customNodeWorkspace); that.workspace.trigger('requestRun'); } , syncAndUpdateRunner = function(){ that.syncCustomNodesWithWorkspace.call(that, customNodeWorkspace); that.workspace.trigger('updateRunner'); }; customNodeWorkspace.on('change:name', sync, this); customNodeWorkspace.on('change:workspaceDependencyIds', sync, this); customNodeWorkspace.on('requestRun', syncAndRequestRun, this ); customNodeWorkspace.on('updateRunner', syncAndUpdateRunner, this ); }, syncCustomNodesWithWorkspace: function(workspace){ if (typeof workspace === "string") workspace = this.app.getLoadedWorkspace(workspace); this.syncDependencies( workspace ); this.syncDirectlyAffectedCustomNodesWithWorkspace( workspace ); this.syncIndirectlyAffectedCustomNodesWithWorkspace( workspace ); }, syncDependencies: function( depWorkspace ){ // if a new dependency is now added, make sure we add it to this workspace's runner var currDeps = this.workspace.get('workspaceDependencyIds'); var depDeps = depWorkspace.get('workspaceDependencyIds'); var newDeps = _.difference( depDeps, currDeps ); newDeps.forEach(function(id){ this.workspace.sendDefinitionToRunner( this.app.getLoadedWorkspace( id ) ); this.addWorkspaceDependency( id, true ); }.bind(this)); }, syncDirectlyAffectedCustomNodesWithWorkspace: function(workspace){ // get the nodes directly affected by this change var directlyAffectedCustomNodes = this.workspace.getCustomNodesWithId(workspace.id); // get the workspace inputs/outputs var inputNodes = workspace.getCustomNodeInputsOutputs(); var outputNodes = workspace.getCustomNodeInputsOutputs(true); directlyAffectedCustomNodes.forEach(function(x){ // cleanup hanging input connections var inputConns = x.get('inputConnections'); var diff = inputNodes.length - inputConns.length; if (diff > 0){ for (var i = 0; i < diff; i++){ inputConns.push([]); } } else { for (var i = 0; i < -diff; i++){ var inConn = x.getConnectionAtIndex(inputConns.length - 1); if (inConn != null){ x.workspace.removeConnection(inConn); } inputConns.pop(); } } // clean up hanging output connections var outputConns = x.get('outputConnections'); var diff2 = outputNodes.length - outputConns.length; if (diff2 > 0){ for (var i = 0; i < diff2; i++){ outputConns.push( [] ); } } else { for (var i = 0; i < -diff2; i++){ var ocs = x.get('outputConnections') .last(); if (ocs){ ocs.slice(0).forEach(function(outConn){ x.workspace.removeConnection(outConn); }) } outputConns.pop(); } } // set the type x.get('type').functionName = workspace.get('name'); x.get('type').setNumInputs(inputNodes.length); x.get('type').setNumOutputs(outputNodes.length); // save to extra var extraCop = JSON.parse( JSON.stringify( x.get('extra') ) ); extraCop.numInputs = inputNodes.length; extraCop.numOutputs = outputNodes.length; extraCop.functionName = workspace.get('name'); // sync the input names ------------------------ extraCop.inputNames = inputNodes.map(function(inputNode, ind){ var ex = inputNode.get('extra'); if (ex === undefined || !ex.name || ex.name === "" ){ return String.fromCharCode(97 + ind); } return ex.name; }); extraCop.inputNames.forEach(function(name, ind) { x.get('type').inputs[ind].name = name; }); // sync the output names ------------------------ extraCop.outputNames = outputNodes.map(function(outputNode, ind){ var ex = outputNode.get('extra'); if (ex === undefined || !ex.name || ex.name === "" ){ return String.fromCharCode(97 + ind); } return ex.name; }); extraCop.outputNames.forEach(function(name, ind) { x.get('type').outputs[ind].name = name; }); // silently set for serialization x.set('extra', extraCop); // triggers a redraw x.trigger('requestRender'); // update runner x.trigger('updateRunner'); }); if (directlyAffectedCustomNodes.length > 0) this.workspace.sync('update', this.workspace); }, syncIndirectlyAffectedCustomNodesWithWorkspace: function(workspace){ var indirectlyAffectedNodes = this.getIndirectlyAffectedCustomNodes( workspace.id ); indirectlyAffectedNodes.forEach(function(x){ x.trigger('updateRunner'); }); if (indirectlyAffectedNodes.length > 0) this.workspace.sync('update', this.workspace ); }, getIndirectlyAffectedCustomNodes: function(functionId){ var cns = this.workspace.getCustomNodes(); var thisApp = this.app; return cns.filter(function(cn){ var id = cn.get('type').functionId , ws = thisApp.getLoadedWorkspace( id ); if (!ws) return false; var wsd = ws.get('workspaceDependencyIds'); return id != functionId && wsd.indexOf( functionId ) != -1; }); }, }); }); ================================================ FILE: app/scripts/models/customizer/CustomizerApp.js ================================================ define(['backbone', 'App'], function(Backbone, App){ return App.extend({ url: function() { // get the url from the page // customize this url var comps = document.URL.split('/customize-'); return '/custdata/' + comps[ comps.length - 1]; }, parse : function(resp) { resp._id = 1; this.get('workspaces').add(resp, {app: this}); var modresp = {}; modresp.name = resp.name; modresp.currentWorkspace = 1; return modresp; }, fetch : function(options){ // this.login.fetch(); Backbone.Model.prototype.fetch.call(this, options); }, enableAutosave: function(){ // this.get('workspaces').on('add remove', function(){ this.sync("update", this); }, this ); // this.on('change:currentWorkspace', function(){ this.sync("update", this); }, this); // this.on('change:isFirstExperience', function(){ this.sync("update", this); }, this); // this.on('change:backgroundWorkspaces', function(){ this.sync("update", this); }, this); } }); }) ================================================ FILE: app/scripts/views/AppView.js ================================================ define([ 'backbone', 'App', 'WorkspaceView', 'Search', 'SearchView', 'WorkspaceControlsView', 'WorkspaceTabView', 'Workspace', 'WorkspaceBrowser', 'WorkspaceBrowserView', 'HelpView', 'Help', 'LoginView', 'Login', 'FeedbackView', 'Feedback', 'ShareView', 'Share', 'fastclick' ], function(Backbone, App, WorkspaceView, Search, SearchView, WorkspaceControlsView, WorkspaceTabView, Workspace, WorkspaceBrowser, WorkspaceBrowserView, HelpView, Help, LoginView, Login, FeedbackView, Feedback, ShareView, Share, fastclick ) { return Backbone.View.extend({ el: '#app', initialize: function() { var f = new fastclick(document.body); this.listenTo(this.model, 'change', this.render); this.$workspace_tabs = this.$('#workspace-tabs'); this.model.get('workspaces').on('add', this.workspaceAdded, this); this.model.get('workspaces').on('remove', this.workspaceRemoved, this); this.model.on('change:showingSettings', this.viewSettings, this); this.model.on('change:showingFeedback', this.viewFeedback, this); this.model.on('change:showingShare', this.viewShare, this); this.model.on('change:showingHelp', this.viewHelp, this); this.model.on('change:showingBrowser', this.viewBrowser, this); this.model.login.on('change:isLoggedIn', this.showHelpOnFirstExperience, this ); this.model.login.on('change:isFirstExperience', this.showHelpOnFirstExperience, this ); $(document).bind('keydown', $.proxy( this.keydownHandler, this) ); // deactivate the context menu $(document).bind("contextmenu",function(e){ return false; }); }, events: { 'click #save-button' : 'saveClick', 'click .workspaces_curtain' : 'endSearch', 'click #help-button': 'toggleHelp', 'click #settings-button': 'showSettings', 'click #workspace_hide' : 'toggleViewer', 'click #workspace-browser-button': 'toggleBrowser', 'click #feedback-button': 'toggleFeedback', 'click #share-button': 'toggleShare', 'click #add-project-workspace' : 'newWorkspace', 'click #add-node-workspace' : 'newNodeWorkspace', 'mouseover #add-workspace-button': 'showAddWorkspaceSelect', 'mouseout #add-workspace-button': 'hideAddWorkspaceSelect', 'mouseover #add-workspace-select-element': 'showAddWorkspaceSelect', 'mouseout #add-workspace-select-element': 'hideAddWorkspaceSelect', // touch 'touchstart #add-workspace-button': 'toggleAddWorkspaceSelect' }, toggleAddWorkspaceSelect: function(){ $('#add-workspace-select-element').toggle(); }, showHelpOnFirstExperience: function(){ var that = this; setTimeout(function(){ if (that.model.login.get('isLoggedIn') && that.model.get('isFirstExperience')){ that.model.set( 'showingHelp', true); } else { that.model.set( 'showingHelp', false); } }, 800); }, showAddWorkspaceSelect: function(){ $('#add-workspace-select-element').show(); }, hideAddWorkspaceSelect: function(){ $('#add-workspace-select-element').hide(); }, newWorkspace: function(){ this.model.newWorkspace(); this.hideAddWorkspaceSelect(); }, newNodeWorkspace: function(){ this.model.newNodeWorkspace(); this.hideAddWorkspaceSelect(); }, keydownHandler: function(e){ var isBackspaceOrDelete = e.keyCode === 46 || e.keyCode === 8; if ( !(e.metaKey || e.ctrlKey) && !isBackspaceOrDelete ) return; // do not capture from input if (e.originalEvent.srcElement && e.originalEvent.srcElement.nodeName === "INPUT" ) return; if (e.target.nodeName === "INPUT") return; // do not capture from textarea if (e.originalEvent.srcElement && e.originalEvent.srcElement.nodeName === "TEXTAREA" ) return; if (e.target.nodeName === "TEXTAREA") return; // keycodes: http://css-tricks.com/snippets/javascript/javascript-keycodes/ switch (e.keyCode) { case 78: this.newWorkspace(); return e.preventDefault(); } this.currentWorkspaceView.keydownHandler(e); }, saveClick: function(e){ this.model.sync("update", this.model); }, endSearch: function() { this.model.set('showingSearch', false); }, toggleBrowser: function(e){ if (this.model.get('showingBrowser') === true){ $(e.currentTarget).removeClass('workspace-browser-button-active'); this.model.set('showingBrowser', false); } else { $(e.currentTarget).addClass('workspace-browser-button-active'); this.model.set('showingBrowser', true); } }, viewBrowser: function(){ if (!this.browserView){ this.browserView = new WorkspaceBrowserView({model: new WorkspaceBrowser() }, { app: this.model }); this.browserView.render(); } if (this.model.get('showingBrowser') === true){ this.browserView.$el.show(); } else { this.browserView.$el.hide(); } }, viewHelp: function(){ if (!this.helpView){ this.helpView = new HelpView({model: new Help() }, { app: this.model }); } if (this.model.get('showingHelp') === true){ this.focusWorkspace(); this.helpView.render(); this.helpView.$el.fadeIn(); } else { this.helpView.$el.fadeOut(); } }, viewFeedback: function(){ if (!this.feedbackView){ this.feedbackView = new FeedbackView({model: new Feedback() }, { app: this.model }); this.feedbackView.render(); } if (this.model.get('showingFeedback') === true){ this.feedbackView.$el.fadeIn(); } else { this.feedbackView.$el.fadeOut(); } }, viewShare: function(){ if (!this.shareView){ this.shareView = new ShareView({model: new Share({ app : this.model }) }, { app: this.model }); } if (this.model.get('showingShare') === true){ this.shareView.render(); this.shareView.$el.fadeIn(); } else { this.shareView.$el.fadeOut(); } }, toggleHelp: function(){ this.model.set('showingHelp', !this.model.get('showingHelp')); }, toggleFeedback: function(){ this.model.set('showingFeedback', !this.model.get('showingFeedback')); }, toggleShare: function(){ this.model.set('showingShare', !this.model.get('showingShare')); }, showHelp: function(){ this.model.set('showingHelp', true); }, hideHelp: function(){ this.model.set('showingHelp', false); }, showFeedback: function(){ this.model.set('showingFeedback', true); }, hideFeedback: function(){ this.model.set('showingFeedback', false); }, showLogin: function(){ this.model.set('showingLogin', true); }, showSettings: function(){ this.model.showSettings(); }, showSearch: function() { if (this.model.get('showingSearch') === true) { // darken the workspace container this.$el.find('.workspaces_curtain').css('display', 'block'); // if we haven't already, create the search view element and add to the ui if (this.searchView === undefined){ this.searchView = new SearchView( { model: new Search() }, {app: this.model, appView: this } ); this.searchView.render(); this.$el.find('#workspaces').prepend(this.searchView.$el); } else { this.searchView.$el.css('display', 'block'); } $('.workspace_container').addClass('blur'); this.searchView.$el.find('.library-search-input').focus(); } else { $('.workspace_container').removeClass('blur'); this.$el.find('.workspaces_curtain').css('display', 'none'); if (this.searchView != undefined) { this.searchView.$el.css('display', 'none'); } } }, showingHelp: false, showingBrowser: false, showingSettings: false, showingSearch: false, showingLogin: false, currentWorkspaceView: null, currentWorkspaceId: null, workspaceTabViews: {}, workspaceViews: {}, workspaceCounter: 1, workspaceAdded: function(workspace){ if ( this.model.isBackgroundWorkspace(workspace.id) ) return; if ( this.workspaceTabViews[workspace.get('_id')] != undefined) return; var view = new WorkspaceTabView({ model: workspace }); this.workspaceTabViews[workspace.get('_id')] = view; view.render(); this.$workspace_tabs.append( view.$el ); }, workspaceRemoved: function(workspace){ // The Workspace can no longer be current workspace.set('current', false); // check if the removed workspace is the current one if (workspace.get('_id') == this.model.get('currentWorkspace') ){ // are there any more workspaces? if ( this.model.get('workspaces').length != 0 ){ this.model.set('currentWorkspace', this.model.get('workspaces').first().get('_id') ); // if we're out of workspaces, just add a new one } else { var that = this; this.newWorkspace(); } } this.workspaceTabViews[workspace.get('_id')].$el.remove(); delete this.workspaceTabViews[workspace.get('_id')]; delete this.workspaceViews[workspace.get('_id')]; }, getCurrentWorkspaceCenter: function(){ var w = this.currentWorkspaceView.$el.width() , h = this.currentWorkspaceView.$el.height() , ho = this.currentWorkspaceView.$el.scrollTop() , wo = this.currentWorkspaceView.$el.scrollLeft() , zoom = 1 / this.model.getCurrentWorkspace().get('zoom'); return [zoom * (wo + w / 2), zoom * (ho + h / 2)]; }, getWorkspaceView: function(workspaceModel) { if (!workspaceModel) return; var workspaceId = workspaceModel.get('_id'); if ( !this.workspaceViewIsInstantiated( workspaceId )){ var workspaceView = new WorkspaceView( { model: workspaceModel, app: this }); this.workspaceViews[workspaceId] = workspaceView; } else { var workspaceView = this.workspaceViews[ workspaceId ]; } return workspaceView; }, workspaceViewIsInstantiated: function(workspaceId){ return (this.workspaceViews[workspaceId] != undefined); }, hideWorkspace: function(workspaceView){ if (workspaceView != undefined) workspaceView.$el.css('display','none'); }, showWorkspace: function(workspaceView){ // if the workspace tab does not exist this.model.removeWorkspaceFromBackground( workspaceView.model.id ); this.workspaceAdded( workspaceView.model ); if (!$.contains(document.documentElement, workspaceView.$el[0])){ this.$el.children('#workspaces').append( this.currentWorkspaceView.$el ); } workspaceView.$el.css('display','block'); }, render: function(arg) { var model = this.model; var workspaces = this.model.get('workspaces') var currentWorkspaceId = this.model.get('currentWorkspace'); if (!currentWorkspaceId){ var currentWorkspace = workspaces.first(); } else { var currentWorkspace = workspaces.get(currentWorkspaceId); } this.model.updateCurrentWorkspace(); // render search if (!this.workspaceControlsView){ this.workspaceControlsView = new WorkspaceControlsView( { model: new Search() }, {app: this.model, appView : this } ); this.workspaceControlsView.render(); this.$el.find('#workspaces').prepend( this.workspaceControlsView.$el ); } // render tabs if (!this.workspaceTabViews){ this.workspaceTabViews = {}; workspaces.each( this.workspaceAdded, this ); } // hide current workspace, show workspace if (this.model.changed.currentWorkspace && currentWorkspace){ this.hideWorkspace(this.currentWorkspaceView); this.currentWorkspaceView = this.getWorkspaceView( currentWorkspace ); this.showWorkspace( this.currentWorkspaceView ); this.currentWorkspaceView.render(); this.currentWorkspaceId = currentWorkspaceId; this.focusWorkspace(); } this.showSearch(); this.renderLogin(); return this; }, renderLogin: function(){ if (!this.loginView){ this.loginView = new LoginView({model: this.model.login }, { app: this.model }); this.loginView.render(); } }, lookingAtViewer: false, focusWorkspace: function(){ this.$el.find('#workspace_hide').removeClass('leftside'); this.$el.find('#workspace_hide').addClass('rightside'); this.$el.find('#workspace_hide i').removeClass('icon-arrow-right'); this.$el.find('#workspace_hide i').addClass('icon-arrow-left'); // change whether workspace_container is visible or not this.currentWorkspaceView.$el.css('display','block'); this.workspaceControlsView.$el.css('display','block'); $('#viewer').addClass('blur'); }, focusViewer: function(){ this.$el.find('#workspace_hide').addClass('leftside'); this.$el.find('#workspace_hide').removeClass('rightside'); this.$el.find('#workspace_hide i').removeClass('icon-arrow-left'); this.$el.find('#workspace_hide i').addClass('icon-arrow-right'); this.currentWorkspaceView.$el.hide(); this.workspaceControlsView.$el.hide(); $('#viewer').removeClass('blur'); }, getCurrentWorkspace: function(){ return this.get('workspaces').get(this.get('currentWorkspace')); }, toggleViewer: function(event) { this.lookingAtViewer = !this.lookingAtViewer; if ( this.lookingAtViewer ){ this.focusViewer(); } else { this.focusWorkspace(); } } }); }); ================================================ FILE: app/scripts/views/ConnectionView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ template: _.template( $('#connection-template').html() ), workspaceView : null, initialize: function( args ) { this.model = args.model; if (args.isProxy ){ this.isProxy = true; } this.workspace = args.workspace; this.workspaceView = args.workspaceView; if (this.model.startNode){ this.model.startNode.on('change:ignoreDefaults', this.render, this); } }, delegateEvents: function() { Backbone.View.prototype.delegateEvents.apply(this, arguments); if (!this.isProxy ){ this.startId = this.model.get('startNodeId'); this.endId = this.model.get('endNodeId'); var nodes = this.workspace.get('nodes'); if (nodes.get(this.endId) != undefined ){ this.listenTo( nodes.get(this.endId), 'change:position resized', this.render); } if (nodes.get(this.startId) != undefined ){ this.listenTo( nodes.get(this.startId), 'change:position resized', this.render); } } this.listenTo(this.model, 'change', this.render); }, render: function() { this.makeCurveOnce(); return this.updateControlPoints() .updateHidden() .updateColor(); }, updateControlPoints: function(){ this.el.setAttribute('d', this.template( this.getControlPoints() )) return this; }, updateHidden: function(){ if (this.model.get('hidden')) { this.el.setAttribute('class','connection collapsed'); } else { this.el.setAttribute('class','connection'); } return this; }, updateColor: function(){ var startNode = this.model.startNode; if (!startNode) return this; if (startNode.isPartialFunctionApplication()){ this.el.setAttribute('class','partial-function-connection'); } else { this.el.setAttribute('class','connection'); } return this; }, curveInit: false, makeCurveOnce: function() { if (!this.curveInit) { var crv = document.createElementNS('http://www.w3.org/2000/svg','path'); crv.setAttribute('class','connection'); this.el = crv; this.$el = $(crv); this.curveInit = true; } return this.el; }, // construct the control points for a bezier curve getControlPoints: function() { if (!this.model.get('startProxy') || !this.model.get('endProxy')) { var nodeViews = this.workspaceView.nodeViews , startId = this.model.get('startNodeId') , endId = this.model.get('endNodeId') , startPortIndex = this.model.get('startPortIndex') , endPortIndex = this.model.get('endPortIndex') } if (!this.model.get('startProxy')) { if (!this.workspaceView.nodeViews[startId] ){ startId = this.model.workspace.get('proxyStartId'); startPortIndex = this.model.workspace.get('proxyStartPortIndex'); } startPos = this.workspaceView.nodeViews[startId].getPortPosition(startPortIndex, true); } else { startPos = this.model.get('startProxyPosition'); } if (!this.model.get('endProxy')) { endPos = nodeViews[endId].getPortPosition(endPortIndex, false); } else { endPos = this.model.get('endProxyPosition'); } var offset = 0.65 * Math.sqrt( Math.pow( endPos[0]-startPos[0], 2 ) + Math.pow( startPos[1]-endPos[1], 2 ) ); return { aX : startPos[0] , aY : startPos[1] , bX : startPos[0] + offset , bY : startPos[1] , dX : endPos[0] , dY : endPos[1] , cX : endPos[0] - offset , cY : endPos[1] }; } }); }); ================================================ FILE: app/scripts/views/FeedbackView.js ================================================ define(['backbone'], function(Backbone) { 'use strict'; return Backbone.View.extend({ el: '#feedback', events: { 'click #exit-feedback' : 'clickExit', 'submit #submit-feedback' : 'clickSend', 'click #submit-feedback' : 'clickSend', 'click' : 'clickExit', 'click .modal-box': 'stopPropagation' }, template: _.template( $('#feedback-template').html() ), initialize: function( args, atts ) { this.app = atts.app; this.model.on('change:failure', this.fail, this ); this.model.on('success', this.success, this ); }, render: function() { this.$el.html( this.template( this.model.toJSON() ) ); this.subject = this.$el.find('#feedback-subject'); this.message = this.$el.find('#feedback-message'); this.failureView = this.$el.find('#feedback-failure-message'); this.successView = this.$el.find('#feedback-success-message'); this.sendingView = this.$el.find('#feedback-sending-message'); return this; }, stopPropagation: function(e){ e.stopPropagation(); }, fail: function(){ this.sendingView.hide(); if (!this.failureView) return; if (!this.model.get('failure')) { this.failureView.hide(); return; } this.failureView.html( this.model.get('failureMessage') ); this.failureView.show(); }, success: function(){ this.sendingView.hide(); this.successView.show(); var that = this; setTimeout(function(){ that.app.set("showingFeedback", false); that.subject.val(""); that.message.val(""); that.successView.fadeOut(); }, 800); }, clickExit: function(e){ this.app.set("showingFeedback", false); }, clickSend: function(e) { e.preventDefault(); this.sendingView.show(); this.model.send({ subject: this.subject.val(), message: this.message.val() }); } }); }); ================================================ FILE: app/scripts/views/HelpView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ el: '#help', events: { "click .exit-help": 'hide', 'click .enter-help-section' : 'clickEnterHelpSection', 'click .exit-all-help' : 'hide', 'click .exit-help-section' : 'clickExitHelpSection' }, initialize: function( args, atts ) { this.app = atts.app; }, render: function() { var template = _.template( $('#help-section-template').html() ); var that = this; this.$el.empty(); this.model.get('sections').forEach(function(section){ var el = $('#' + section.targetId ); if (!el) return; var offset = el.offset(); var height = el.height(); var width = el.width(); if (section.offset[0] < 0) { width = -10; } if (section.offset[1] < 0) { height = -10; width -= 10; } section.elementPosition = [ offset.left + width, offset.top + height ]; that.$el.append(template( section )); }); return this; }, getHelpSection: function(e){ var ui = $(e.target); var attr = ui.attr('data-target-id'); return this.$el.find(".help-section[data-target-id='" + attr + "']"); }, clickEnterHelpSection: function(e){ var ele = this.getHelpSection(e); var ui = $(e.target); ele.fadeIn(); ui.fadeOut(); }, clickExitHelpSection: function(e){ var ele = this.getHelpSection(e); ele.fadeOut(); }, hide: function() { this.app.set('showingHelp', false); } }); }); ================================================ FILE: app/scripts/views/LoginView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ el: '#login', template: _.template( $('#login-template').html() ), events: { 'submit #login-form' : 'login', 'submit #signup-form' : 'signup', 'click #logout' : 'logout', "click .exit-login": 'hide', 'click #signup-tab-button': 'focusSignup', 'click #login-tab-button': 'focusLogin'}, initialize: function( args, atts ) { this.app = atts.app; this.model.on('change:showing', this.render, this); this.model.on('change:isLoggedIn', this.render, this); this.model.on('change:failed', this.render, this); this.model.on('change:failureMessage', this.render, this); var that = this; $('#login-button').click(function(){ that.tabClick.call(that); }); }, rendered : false, render: function() { if ( !this.rendered ) { this.$el.find('.login-container').html( this.template( this.model.toJSON() ) ); this.rendered = true; this.$el.find('.login-container').show(); } var failureMessage = this.$el.find('#login-failure-message'); if (this.model.get('failed')){ failureMessage.html( this.model.get('failureMessage') ); failureMessage.show(); } else { failureMessage.hide(); } if (this.model.get('showing') === true){ this.$el.show(); } else { this.$el.hide(); } this.renderLoginState(); return this; }, focusSignup: function(e){ this.model.set('failed', false); this.$el.find('#login-tab-button').removeClass('tab-button-hilite'); this.$el.find('#signup-tab-button').addClass('tab-button-hilite'); this.$el.find('#login-form').hide(); this.$el.find('#signup-form').show(); }, focusLogin: function(e){ this.model.set('failed', false); this.$el.find('#signup-tab-button').removeClass('tab-button-hilite'); this.$el.find('#login-tab-button').addClass('tab-button-hilite'); this.$el.find('#signup-form').hide(); this.$el.find('#login-form').show(); }, renderLoginState: function(){ if( this.model.get('isLoggedIn') ){ this.model.hide(); } else { this.$el.show(); } return this; }, tabClick: function(){ if (this.model.get('isLoggedIn')){ this.model.logout(); } else { this.model.toggle(); } }, signup: function(e) { e.preventDefault(); this.model.signup( this.$('#signup-form').serialize()); }, login: function(e) { e.preventDefault(); this.model.login( this.$('#login-form').serialize() ); } }); }); ================================================ FILE: app/scripts/views/MarqueeView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ workspaceView : null, initialize: function( args ) { this.model = args.model; this.workspace = args.workspace; this.workspaceView = args.workspaceView; this.listenTo(this.model, 'change', this.render); }, render: function() { this.makeElementOnce(); return this.updateCorners() .updateHidden() .updateColor() .updateStroke(); }, updateCorners: function(){ this.el.setAttribute('d', this.template( this.getCorners() )) return this; }, updateHidden: function(){ if (this.model.get('hidden')) { this.el.setAttribute('class','marquee collapsed'); } else { this.el.setAttribute('class','marquee'); } return this; }, updateColor: function(){ return this; }, curveInit: false, makeElementOnce: function() { if (!this.curveInit) { var crv = document.createElementNS('http://www.w3.org/2000/svg','rect'); crv.setAttribute('class','marquee'); this.updateCorners(); this.updateStroke(); this.el = crv; this.$el = $(crv); this.curveInit = true; } return this.el; }, updateCorners: function() { this.el.setAttribute('x',this.model.get('x')); this.el.setAttribute('y',this.model.get('y')); this.el.setAttribute('width',this.model.get('width')); this.el.setAttribute('height',this.model.get('height')); return this; }, updateStroke: function() { // we scale the stroke to avoid the marquee being overly thin or thick // when zoomed var zoom = 1 / this.workspace.get('zoom'); this.el.setAttribute('stroke-dasharray', (zoom * 4) + "," + (zoom * 4) ); this.el.setAttribute('stroke', zoom * 2 ); return this; } }); }); ================================================ FILE: app/scripts/views/NodeViews/Base.js ================================================ define(['backbone', 'jqueryuidraggable', 'bootstrap', 'Hammer'], function(Backbone, jqueryuidraggable, bootstrap, Hammer) { return Backbone.View.extend({ tagName: 'div', className: 'node', template: _.template( $('#node-template').html() ), portHeight: 29, events: { 'mousedown .node-port-output': 'beginPortConnection', 'mousedown .node-port-input': 'beginPortDisconnection', 'mouseup .node-port-input': 'endPortConnection', 'touchstart .node-port-output': 'beginTouchConnection', 'touchstart .node-port-input': 'beginTouchDisconnection', 'touchstart': 'touchstart', 'click': 'selectThis', 'change .use-default-checkbox': 'useDefaultClick', 'blur .name-input': 'updateName', 'click .toggle-vis': 'toggleGeomVis', 'click .rep-type': 'replicationClick' }, initialize: function(args) { this.workspace = args.workspace; this.workspaceView = args.workspaceView; this.listenTo(this.model, 'requestRender', this.render ); this.listenTo(this.model, 'change:position', this.move ); this.listenTo(this.model, 'change:lastValue', this.renderLastValue ); this.listenTo(this.model, 'change:failureMessage', this.renderLastValue ); this.listenTo(this.model, 'change:ignoreDefaults', this.colorPorts ); this.listenTo(this.model, 'change:name', this.render ); this.listenTo(this.model, 'connection', this.colorPorts); this.listenTo(this.model, 'disconnection', this.colorPorts); this.listenTo(this.model, 'change:selected', this.colorSelected); this.listenTo(this.model, 'change:visible', this.render); this.listenTo(this.model, 'change:isEvaluating', this.colorEvaluating); this.listenTo(this.model, 'evalFailed', this.onEvalFailed ); this.listenTo(this.model, 'evalBegin', this.onEvalBegin ); this.makeDraggable(); this.$workspace_canvas = $('#workspace_canvas'); this.position = this.model.get('position'); }, updateName: function(e){ var val = this.$el.find('.name-input').val(); if (this.model.get('name') === val) return; var cmd = { property: 'name', _id: this.model.get('_id'), newValue : val }; this.model.workspace.setNodeProperty(cmd); }, onEvalFailed: function(exception){ this.$el.addClass('node-failed'); this.model.set('failureMessage', exception); }, onEvalBegin: function(){ this.$el.removeClass('node-failed'); }, useDefaultClick : function(e){ var index = parseInt( $(e.currentTarget).attr('data-index') ); var ign = JSON.parse( JSON.stringify( this.model.get('ignoreDefaults') ) ); ign[index] = !$(e.target).is(':checked'); var cmd = { property: 'ignoreDefaults', _id: this.model.get('_id'), newValue : ign }; this.model.workspace.setNodeProperty(cmd); this.model.workspace.run(); }, replicationClick : function(e){ var cmd = { property: 'replication', _id: this.model.get('_id'), newValue : $(e.target).attr('data-rep-type') }; this.model.workspace.setNodeProperty(cmd); this.model.workspace.run(); }, // should be part of nodeView subclass toggleGeomVis: function(e) { this.model.set('visible', !this.model.get('visible') ); e.stopPropagation() }, makeDraggable: function() { var that = this; this.initPos = []; this.$el.draggable( { drag : function(e, ui) { var zoom = 1 / that.workspace.get('zoom'); ui.position.left = zoom * ui.position.left; ui.position.top = zoom * ui.position.top; that.workspace.get('nodes').moveSelected([ ui.position.left - that.initPos[0], ui.position.top - that.initPos[1]], that); that.model.set('position', [ ui.position.left, ui.position.top ]); }, start : function(startEvent) { if ( !that.model.get('selected') ){ that.model.workspace.get('nodes').deselectAll(); } that.model.set('selected', true ); that.workspace.get('nodes').startDragging(that); var zoom = 1 / that.model.workspace.get('zoom'); var pos = that.model.get('position'); that.initPos = [ pos[0], pos[1] ]; }, stop : function() { var start = [ that.initPos[0], that.initPos[1] ]; var pos = that.model.get('position'); var end = [ pos[0], pos[1] ]; var cmd = { property: 'position', _id: that.model.get('_id'), oldValue: start, newValue : end }; that.model.workspace.setNodeProperty( cmd ); } }); this.$el.css('position', 'absolute'); }, beginPortDisconnection: function(e){ var index = parseInt( $(e.currentTarget).attr('data-index') ); if ( !this.model.isPortConnected(index, false) ) return; var inputConnections = this.model.get('inputConnections') , connection = inputConnections[index][0] , oppos = connection.getOpposite( this.model ); this.workspace.startProxyConnection( oppos.node.get('_id'), oppos.portIndex, this.getPortPosition(index, false)); this.workspace.removeConnection( connection.toJSON() ); e.stopPropagation(); }, beginPortConnection: function(e){ var index = parseInt( $(e.currentTarget).attr('data-index') ); this.workspace.startProxyConnection(this.model.get('_id'), index, this.getPortPosition(index, true)); e.stopPropagation(); }, endPortConnection: function(e){ if ( !this.workspace.draggingProxy ) return; var index = parseInt( $(e.currentTarget).attr('data-index') ); this.workspace.completeProxyConnection(this.model.get('_id'), index ); e.stopPropagation(); }, // touch-specific handlers // simply kill the connection beginTouchDisconnection: function(e){ var index = parseInt( $(e.currentTarget).attr('data-index') ); if ( !this.model.isPortConnected(index, false) ) return; var inputConnections = this.model.get('inputConnections') , connection = inputConnections[index][0] , oppos = connection.getOpposite( this.model ); this.workspace.removeConnection( connection.toJSON() ); e.stopPropagation(); e.preventDefault(); }, // special handling of touch events beginTouchConnection: function(e){ $('body').one('touchend', function(e){ var changedTouches = e.originalEvent.changedTouches[0]; var elem = $( document.elementFromPoint(changedTouches.pageX, changedTouches.pageY) ); if (!elem.hasClass("node-port-input")){ elem = elem.parent('.node-port-input') } if (!elem.attr('data-index')) return; this.workspace.draggingProxy = true; var e = $.Event( "mouseup" ); elem.trigger(e); this.workspace.draggingProxy = false; }.bind( this )); }, select: function() { this.model.set( 'selected', true ); }, touchstart: function(event){ // is the user pressing on an input? var shouldIgnore = event.target != null && ( event.target.tagName.toLowerCase() == "input" || event.target.tagName.toLowerCase() == "textarea" ); if(shouldIgnore) { // need to focus the input - on iOS the input never gets focussed event.target.focus(); // we prevent the node from being selected this.selectable = false; setTimeout(function(){ this.selectable = true; }.bind(this), 300); } }, selectable: true, selectThis: function(event) { if (!this.selectable) return; if ( !this.model.get('selected') ){ if (!event.shiftKey) this.workspace.get('nodes').deselectAll(); this.model.set('selected', true ); } else { this.workspace.get('nodes').deselectAll(); } }, render: function() { this .renderNode() .colorSelected() .colorEvaluating() .moveNode() .renderPorts(); return this; }, renderNode: function() { var json = this.model.toJSON(); json.preview = this.formatPreview( json.lastValue ); this.$el.html( this.template( json ) ); if (this.getCustomContents){ this.$el.find('.node-data-container').html( this.getCustomContents() ); } return this; }, truncatePreview: function( value, maxElements ){ if (typeof value === "string") return value; if (!value || value.length === undefined || value.length < 100) { return value; } if ( maxElements === undefined ) maxElements = 20; var shortVal = {}; var count = 0; for (var k in value){ if (count > maxElements) break; shortVal[k] = value[k]; count++; } shortVal.length = value.length; return shortVal; }, formatPreview: function( value ){ var that = this; return JSON.stringify( this.truncatePreview( value ), function(k, v){ return that.prettyPrint.call(that, k, v);} ); }, prettyPrint: function(key, val){ if (typeof val === "number"){ return val.toPrecision(4); } if (typeof val === "string"){ return val.replace(new RegExp("\t", 'g'), "").replace(new RegExp("\n", 'g'), "") } if (val && val.length != undefined && val.length > 100) { return this.truncatePreview( val ); } return val; }, renderLastValue: function() { return this.renderNode(); }, colorEvaluating: function() { if ( this.model.get('isEvaluating') ){ this.$el.addClass('node-evaluating'); } else { this.$el.removeClass('node-evaluating'); } return this; }, colorSelected: function() { if ( this.model.get('selected') ){ this.$el.addClass('node-selected'); } else { this.$el.removeClass('node-selected'); } return this; }, svgTransform: function() { return 'translate(' + this.position[0] + ' ' + this.position[1] +')'; }, // Get the position of a particular port. Only valid after render() // has been called getPortPosition: function( index, isOutputPort ) { var x = this.position[0] , y = this.position[1]; if (isOutputPort) { try { x += parseInt( this.outputPorts[index].getAttribute('cx')) + 5; y += parseInt( this.outputPorts[index].getAttribute('cy')); } catch (e){ } } else { try { x += parseInt( this.inputPorts[index].getAttribute('cx')) - 5; y += parseInt( this.inputPorts[index].getAttribute('cy')); } catch (e){ } } return [x, y]; }, colorPorts: function() { // update port colors var that = this; var isPartial = false; this.inputPorts.forEach(function(ele, ind){ ele.setAttribute('stroke','black'); if (that.model.isPortConnected(ind, false) ){ ele.setAttribute('fill','black'); } else if (that.model.isInputPortUsingDefault(ind)){ ele.setAttribute('fill','white'); } else { isPartial = true; ele.setAttribute('fill','grey'); ele.setAttribute('stroke','white'); } }); this.outputPorts.forEach(function(ele, ind){ ele.setAttribute('stroke','black'); if (that.model.isPortConnected(ind, true)){ ele.setAttribute('fill','black'); } else if (isPartial) { ele.setAttribute('fill','grey'); ele.setAttribute('stroke','white'); } else { ele.setAttribute('fill','white'); } }); return this; }, move: function() { return this.moveNode().movePorts(); }, moveNode: function() { var pos = this.model.get('position'); if (pos[0] < 0) pos[0] = 0; if (pos[1] < 0) pos[1] = 0; this.position = pos; this.$el.css("left", this.position[0] ); this.$el.css("top", this.position[1] ); return this; }, movePorts: function(){ if (this.portGroup) { this.portGroup.setAttribute( 'transform', this.svgTransform() ); } return this; }, renderPorts: function() { if ( this.portGroup ) { // we need to redraw the ports $(this.portGroup).empty(); } else { // create an svg group to hold the port circles this.portGroup = document.createElementNS('http://www.w3.org/2000/svg','g'); this.portGroup.setAttribute( 'transform', this.svgTransform() ); } // create data structures to store the input ports this.inputPorts = []; this.outputPorts = []; // draw the circles var that = this; var inIndex = 0; var outIndex = 0; // set the zoom from the workspace container var zoom = 1.0 , ele = this.$el.parent().css('transform'); if (ele != "none") zoom = ele.match(/-?[0-9\.]+/g)[0]; this.$el.find('.node-port').each(function(index, ele) { var nodeCircle = document.createElementNS('http://www.w3.org/2000/svg','circle'); // assign default appearance nodeCircle.setAttribute('r',3); nodeCircle.setAttribute('stroke','black'); nodeCircle.setAttribute('fill','white'); nodeCircle.setAttribute('stroke-width','1.5'); // position input ports on left side, output ports on right side if ( $(ele).hasClass('node-port-input') ) { nodeCircle.setAttribute('cx', 0); nodeCircle.setAttribute('cy', that.portHeight / 2 + 1/zoom * $(ele).position().top ); that.inputPorts.push(nodeCircle); inIndex++; } else { nodeCircle.setAttribute('cx', that.$el.width() + 2.5 ); nodeCircle.setAttribute('cy', that.portHeight / 2 + 1/zoom * $(ele).position().top ); that.outputPorts.push(nodeCircle); outIndex++; } // append that.portGroup.appendChild(nodeCircle); }); this.colorPorts(); return this; }, remove: function() { this.$el.remove(); if (this.portGroup.parentNode) this.portGroup.parentNode.removeChild(this.portGroup); } }); }); ================================================ FILE: app/scripts/views/NodeViews/CustomNode.js ================================================ define(['underscore', 'jquery', 'ThreeCSGNodeView'], function(_, $, ThreeCSGNodeView) { return ThreeCSGNodeView.extend({ innerTemplate : _.template( $('#node-custom-template').html() ), getCustomContents: function() { var that = this; // open the parent workspace on double click this.$el.bind('dblclick', function(){ this.model.workspace.app.openWorkspace( this.model.get('type').functionId ); }.bind(this) ); var js = this.model.toJSON() ; if (!js.extra.script) js.extra.script = this.model.get('type').script; return this.innerTemplate( js ); } }); }); ================================================ FILE: app/scripts/views/NodeViews/Input.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { return BaseNodeView.extend({ template: _.template( $('#node-input-template').html() ), initialize: function(args) { BaseNodeView.prototype.initialize.apply(this, arguments); this.model.on('change:extra', function() { var ex = this.model.get('extra') ; var name = ex != undefined ? ex.name : ""; this.silentSyncUI( name ); this.model.trigger('updateRunner'); }, this); }, render: function(){ BaseNodeView.prototype.render.apply(this, arguments); this.$el.addClass('input-node'); var that = this; var extra = this.model.get('extra'); var name = extra.name != undefined ? extra.name : ""; this.inputText = this.$el.find(".text-input"); this.inputText.val( name ); this.inputText.change( function(e){ that.nameChanged.call(that, e); e.stopPropagation(); }); return this; }, nameChanged: function(){ this.inputSet(); this.model.workspace.trigger('updateRunner'); }, silentSyncUI: function(name){ this.silent = true; this.inputText.val( name ); this.silent = false; }, inputSet: function(e,ui) { if ( this.silent ) return; var newValue = { name: this.inputText.val() }; this.model.workspace.setNodeProperty({property: 'extra', _id: this.model.get('_id'), newValue: newValue }); } }); }); ================================================ FILE: app/scripts/views/NodeViews/NodeViews.js ================================================ define(['BaseNodeView', 'WatchNodeView', 'NumNodeView', 'ThreeCSGNodeView', 'ScriptView', 'OutputView', 'InputView','CustomNodeView'], function(BaseNodeView, WatchNodeView, NumNodeView, ThreeCSGNodeView, ScriptView, OutputView, InputView, CustomNodeView){ var nodeViewTypes = {}; nodeViewTypes.Base = ThreeCSGNodeView; nodeViewTypes.Show = WatchNodeView; nodeViewTypes.Number = NumNodeView; nodeViewTypes.CustomNode = CustomNodeView; nodeViewTypes.Script = ScriptView; nodeViewTypes.Input = InputView; nodeViewTypes.Output = OutputView; return nodeViewTypes; }); ================================================ FILE: app/scripts/views/NodeViews/Num.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseNodeView', 'jqueryuislider'], function(Backbone, _, $, BaseNodeView) { return BaseNodeView.extend({ template: _.template( $('#node-num-template').html() ), initialize: function(args) { BaseNodeView.prototype.initialize.apply(this, arguments); this.rendered = false; this.model.on('change:extra', function() { var ex = this.model.get('extra') ; this.silentSyncUI( ex ); this.model.trigger('updateRunner'); this.model.workspace.trigger('requestRun'); }, this); }, render: function() { BaseNodeView.prototype.render.apply(this, arguments); if (this.rendered) return this; // make the slider this.slider = this.$el.find('.slider'); if (!this.slider) return; var extra = this.model.get('extra'); var min = extra.min != undefined ? extra.min : -150; var max = extra.max != undefined ? extra.max : 150; var step = extra.step != undefined ? extra.step : 0.1; var lock = extra.lock != undefined ? extra.lock : false; var value = extra.value != undefined ? extra.value : 0; if (value === undefined ) value = this.model.get('lastValue'); var that = this; this.slider.slider( { min: min, max: max, step: step, value: value, change: function(e, ui){ that.inputSet.call(that, e, ui); }, slide: function(e, ui){ that.inputChanged.call(that, e, ui); } }); this.currentValueInput = this.$el.find('.currentValue'); this.currentValueInput.val( value ); this.currentValueInput.change( function(e){ that.valChanged.call(that, e); e.stopPropagation(); }); this.minInput = this.$el.find('.num-min'); this.minInput.val(min); this.minInput.change( function(e){ that.minChanged.call(that, e); e.stopPropagation(); }); this.maxInput = this.$el.find('.num-max'); this.maxInput.val(max); this.maxInput.change( function(e){ that.maxChanged.call(that, e); e.stopPropagation(); }); this.stepInput = this.$el.find('.num-step'); this.stepInput.val(step); this.stepInput.change( function(e){ that.stepChanged.call(that, e); e.stopPropagation(); }); this.lockInput = this.$el.find('.lock-input'); this.lockInput.val( lock ); this.lockInput.change( function(e){ that.lockChanged.call(that, e); e.stopPropagation(); }); // adjust settings dropdown so that it stays open while editing // doesn't select the node when you're editing $('.dropdown.keep-open').on({ "shown.bs.dropdown": function() { that.selectable = false; that.model.set('selected', false); $(this).data('closable', false); }, "mouseleave": function() { $(this).data('closable', true); }, "click": function() { $(this).data('closable', false); }, "hide.bs.dropdown": function() { if ( $(this).data('closable') ) that.selectable = true; return $(this).data('closable'); } }); // this.rendered = true; return this; }, silentSyncUI: function(data){ this.silent = true; this.currentValueInput.val( data.value ); this.setSliderValue( data.value ); this.minInput.html( data.min ); this.maxInput.html( data.max ); this.stepInput.html( data.step ); this.lockInput.val( data.lock ); this.silent = false; }, currentValue: function(){ return this.slider.slider("option", "value"); }, setSliderValue: function(val){ return this.slider.slider("option", "value", val); }, valChanged: function(val){ var val = parseFloat( this.currentValueInput.val() ); if (isNaN(val)) return; return this.setSliderValue( val ); }, minChanged: function(e, u){ var val = parseFloat( this.minInput.val() ); if (isNaN(val)) return; if (this.currentValue < val) this.setSliderValue(val); this.slider.slider("option", "min", val); }, maxChanged: function(e){ var val = parseFloat( this.maxInput.val() ); if (isNaN(val)) return; if (this.currentValue > val) this.setSliderValue(val); this.slider.slider("option", "max", val); }, stepChanged: function(e){ var val = parseFloat( this.stepInput.val() ); if (isNaN(val)) return; this.slider.slider("option", "step", val); }, lockChanged: function(e){ this.inputSet(); }, inputChanged: function(e,ui) { var val = ui.value; this.$el.find('.currentValue').html(val); }, inputSet: function() { if ( this.silent ) return; var newValue = { value: this.slider.slider("option", "value"), min: this.slider.slider("option", "min"), step: this.slider.slider("option", "step"), max: this.slider.slider("option", "max"), lock: this.lockInput.is(':checked') }; this.model.workspace.setNodeProperty({property: 'extra', _id: this.model.get('_id'), newValue: newValue }); } }); }); ================================================ FILE: app/scripts/views/NodeViews/Output.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { return BaseNodeView.extend({ template: _.template( $('#node-output-template').html() ), initialize: function(args) { BaseNodeView.prototype.initialize.apply(this, arguments); this.model.on('change:extra', function() { var ex = this.model.get('extra') ; var name = ex != undefined ? ex.name : ""; this.silentSyncUI( name ); this.model.trigger('updateRunner'); }, this); }, render: function(){ BaseNodeView.prototype.render.apply(this, arguments); this.$el.addClass('output-node'); var that = this; var extra = this.model.get('extra'); var name = extra.name != undefined ? extra.name : ""; this.inputText = this.$el.find(".text-input"); this.inputText.val( name ); this.inputText.change( function(e){ that.nameChanged.call(that, e); e.stopPropagation(); }); return this; }, nameChanged: function(){ this.inputSet(); this.model.workspace.trigger('updateRunner'); }, silentSyncUI: function(name){ this.silent = true; this.inputText.val( name ); this.silent = false; }, inputSet: function(e,ui) { if ( this.silent ) return; var newValue = { name: this.inputText.val() }; this.model.workspace.setNodeProperty({property: 'extra', _id: this.model.get('_id'), newValue: newValue }); } }); }); ================================================ FILE: app/scripts/views/NodeViews/Script.js ================================================ 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) { return ThreeCSGNodeView.extend({ initialize: function(args) { ThreeCSGNodeView.prototype.initialize.apply(this, arguments); this.listenTo(this.model,'change:extra', this.onChangedExtra); }, innerTemplate : _.template( $('#node-script-template').html() ), getCustomContents: function() { var js = this.model.toJSON() ; if (!js.extra.script) js.extra.script = this.model.get('type').script; return this.innerTemplate( js ); }, onChangedExtra: function(){ var ex = this.model.get('extra') || {}; if (ex.numInputs != undefined ){ this.setNumInputConnections( ex.numInputs ) this.model.get('type').setNumInputs( ex.numInputs ); } this.render(); this.model.trigger('updateRunner'); this.model.workspace.run(); }, setNumInputConnections: function(num){ if (num === undefined) return; var inputConns = this.model.get('inputConnections'); var diff = num - inputConns.length; if (diff === 0) return; if (diff > 0){ for (var i = 0; i < diff; i++){ inputConns.push([]); } } else { for (var i = 0; i < -diff; i++){ var conn = this.model.getConnectionAtIndex(inputConns.length - 1); if (conn != null){ this.model.workspace.removeConnection(conn); } inputConns.pop(); } } }, extendScope: _.once(function(){ var or = CodeMirror.hint.javascript; CodeMirror.hint.javascript = function(editor, options){ options.globalScope = FLOOD.nodeTypes; return or(editor, options); } }), renderNode: function() { ThreeCSGNodeView.prototype.renderNode.apply(this, arguments); this.extendScope(); var ta = this.$el.find('.script-text-input'); var cm = CodeMirror.fromTextArea(ta[0], { extraKeys: {"Ctrl-Space": "autocomplete"}, mode: { name :"javascript" } }); var that = this; cm.on("focus",function(e){ that.selectable = false; that.model.set('selected', false); }); cm.on("change", function(){ // we need to update the ports and connectors after moving the node setTimeout(function(){ // update the position of the node ports that.renderPorts(); // update the position of the connections that.model.trigger('resized'); }, 0); }); cm.on("blur",function(){ var ex = JSON.parse( JSON.stringify( that.model.get('extra') ) ); if ( ex.script === cm.getValue() ) return; ex.script = cm.getValue(); that.model.workspace.setNodeProperty({property: "extra", _id: that.model.get('_id'), newValue: ex }); that.selectable = true; }); this.$el.find('.add-input').click(function(){ that.addInput.call(that); }); this.$el.find('.remove-input').click(function(){ that.removeInput.call(that); }); return this; }, setNumInputsProperty: function(numInputs){ if (numInputs === undefined) return; var ex = this.model.get('extra'); var exCopy = JSON.parse( JSON.stringify( ex ) ); exCopy.numInputs = numInputs; this.model.workspace.setNodeProperty({property: "extra", _id: this.model.get('_id'), newValue: exCopy, oldValue: ex }); }, addInput: function(){ var type = this.model.get('type'); var ex = this.model.get('extra'); var numInputs = ex.numInputs; if (numInputs === undefined) numInputs = 0; if (type.inputs.length === 26) return; this.setNumInputsProperty(numInputs + 1); }, removeInput: function(){ var type = this.model.get('type'); var ex = this.model.get('extra'); var numInputs = ex.numInputs; if (numInputs === undefined) numInputs = 1; if (type.inputs.length === 0) return; this.setNumInputsProperty(numInputs - 1); } }); }); ================================================ FILE: app/scripts/views/NodeViews/ThreeCSG.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { return BaseNodeView.extend({ initialize: function(args) { BaseNodeView.prototype.initialize.apply(this, arguments); this.model.on('change:selected', this.colorSelected, this); this.model.on('change:visible', this.changeVisibility, this); this.model.on('remove', this.onRemove, this); this.model.on('change:prettyLastValue', this.onEvalComplete, this ); this.model.workspace.on('change:current', this.changeVisibility, this); this.onEvalComplete(); }, setMaterials: function(partMat, meshMat, lineMat){ this.threeGeom.traverse(function(ele) { if (ele instanceof THREE.Mesh) { ele.material = meshMat; } else if (ele instanceof THREE.Line) { ele.material = lineMat; } else { ele.material = partMat; } }); }, colorSelected: function(){ BaseNodeView.prototype.colorSelected.apply(this, arguments); if ( !( this.threeGeom && this.model.get('visible')) ) return this; if (this.model.get('selected')) { var meshMat = new THREE.MeshPhongMaterial({color: 0x66d6ff }); var partMat = new THREE.ParticleBasicMaterial({color: 0x66d6ff, size: 5, sizeAttenuation: false}); var lineMat = new THREE.LineBasicMaterial({ color: 0x66d6ff }); } else { var meshMat = new THREE.MeshPhongMaterial({color: 0x999999}); var partMat = new THREE.ParticleBasicMaterial({color: 0x999999, size: 5, sizeAttenuation: false}); var lineMat = new THREE.LineBasicMaterial({ color: 0x000000 }); } this.setMaterials(partMat, meshMat, lineMat); return this; }, formatPreview: function(data){ // ugh this is terrible code if (data == null || data === undefined) return BaseNodeView.prototype.formatPreview.apply(this, arguments); if (data.length === undefined && !data.normal && !data.polygons && !data.vertices) return BaseNodeView.prototype.formatPreview.apply(this, arguments); if (data.length > 0 && !data[0].normal && !data[0].polygons && !data[0].vertices) return BaseNodeView.prototype.formatPreview.apply(this, arguments); if (data.normal) return "Plane"; if (data.polygons) return "Solid"; if (data.vertices) return "Polygon"; if (data.length) { var solidCount = 0; var polyCount = 0; var planeCount = 0; for (var i = 0; i < data.length; i++) { if ( data[i].polygons ) solidCount++; if ( data[i].normal ) planeCount++; if ( data[i].vertices ) polyCount++; } var solidString = solidCount + " Solids"; var polyString = polyCount + " Polygons"; var planeString = planeCount + " Planes"; var stringArr = []; if (solidCount > 0) stringArr.push(solidString); if (planeCount > 0) stringArr.push(planeString); if (polyCount > 0) stringArr.push(polyString); if (planeCount === 0 && solidCount === 0 && polyCount === 0) return "Nothing"; return stringArr.join(','); } return "Nothing"; }, // 3D move to node subclass onRemove: function(){ this.model.workspace.off('change:current', this.changeVisibility, this); scene.remove(this.threeGeom); }, evaluated: false, toThreeGeom: function( rawGeom ) { var threeGeom = new THREE.Geometry(), face; if (!rawGeom) return threeGeom; if (!rawGeom.vertices && !rawGeom.linestrip ) return threeGeom; if (rawGeom.linestrip) return this.addLineStrip( rawGeom, threeGeom ); if (rawGeom.vertices && !rawGeom.faces) return this.addPoints( rawGeom, threeGeom ); for ( var i = 0; i < rawGeom.vertices.length; i++ ) { var v = rawGeom.vertices[i]; threeGeom.vertices.push( new THREE.Vector3( v[0], v[1], v[2] ) ); } if (!rawGeom.faces) return threeGeom; for ( var i = 0; i < rawGeom.faces.length; i++ ) { var f = rawGeom.faces[i]; face = new THREE.Face3( f[0], f[1], f[2], new THREE.Vector3( f[3][0], f[3][1], f[3][2] ) ); threeGeom.faces.push( face ); } threeGeom._floodType = 0; return threeGeom; }, addPoints: function( rawGeom, threeGeom ){ for ( var i = 0; i < rawGeom.vertices.length; i++ ) { var v = rawGeom.vertices[i]; threeGeom.vertices.push( new THREE.Vector3( v[0], v[1], v[2] ) ); } threeGeom._floodType = 1; return threeGeom; }, addLineStrip: function( rawGeom, threeGeom ){ for ( var i = 0; i < rawGeom.linestrip.length; i++ ) { var v = rawGeom.linestrip[i]; threeGeom.vertices.push( new THREE.Vector3( v[0], v[1], v[2] ) ); } threeGeom._floodType = 2; return threeGeom; }, onEvalComplete: function(a, b, newValue){ if (!newValue && this.evaluated) return; this.evaluated = true; var lastValue = this.model.get('prettyLastValue'); var temp; if ( !lastValue ) return; if ( lastValue.vertices || lastValue.linestrip ){ temp = []; temp.push(lastValue); } else { temp = lastValue; // extract the list } var threeTemp = new THREE.Object3D(); this.drawChunked( threeTemp, temp, function() { if ( this.threeGeom ){ scene.remove( this.threeGeom ); } this.threeGeom = threeTemp; scene.add( this.threeGeom ); this.changeVisibility(); }, this ); }, // creating this data may be quite slow, we'll need to be careful drawChunked: function(geom, list, callback, that){ var i = 0; var tick = function() { var start = new Date().getTime(); for (; i < list.length && (new Date().getTime()) - start < 50; i++) { var g3 = that.toThreeGeom( list[i] ); if (that.model.get('selected')){ var color = 0x66d6ff; } else { var color = 0x999999; } switch (g3._floodType) { case 0: geom.add( new THREE.Mesh(g3, new THREE.MeshPhongMaterial({color: color})) ); break; case 1: geom.add( new THREE.ParticleSystem(g3, new THREE.ParticleBasicMaterial({color: color, size: 5, sizeAttenuation: false}) )); break; case 2: geom.add( new THREE.Line(g3, new THREE.LineBasicMaterial({ color: 0x000000 }))); break; } } if (i < list.length) { setTimeout(tick, 25); } else { callback.call(that); } }; setTimeout(tick, 0); }, changeVisibility: function(){ if ( !this.threeGeom ){ return; } if (!this.model.get('visible') || !this.model.workspace.get('current') ) { this.threeGeom.traverse(function(e) { e.visible = false; }); } else if ( this.model.get('visible') ) { this.threeGeom.traverse(function(e) { e.visible = true; }); } }, renderNode: function() { BaseNodeView.prototype.renderNode.apply(this, arguments); this.$toggleVis = this.$el.find('.toggle-vis'); this.$toggleVis.show(); var icon = this.$toggleVis.find('i'); var label = this.$toggleVis.find('span'); if (this.model.get('visible')){ icon.addClass('fa-eye'); icon.removeClass('fa-eye-slash'); label.html('Hide geometry'); } else { icon.removeClass('fa-eye'); icon.addClass('fa-eye-slash'); label.html('Show geometry'); } return this; }, }); }); ================================================ FILE: app/scripts/views/NodeViews/Watch.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseNodeView'], function(Backbone, _, $, BaseNodeView) { return BaseNodeView.extend({ template: _.template( $('#node-watch-template').html() ), initialize: function(args) { BaseNodeView.prototype.initialize.apply(this, arguments); this.model.on( 'change:lastValue', this.renderNode, this ); this.model.on( 'disconnection', this.renderNode, this ); }, renderNode: function(){ var pretty = this.model.get('lastValue') != undefined ? JSON.stringify(this.model.get('lastValue'), this.prettyPrint, 2) : this.model.get('lastValue'); this.model.set('prettyValue', pretty ); return BaseNodeView.prototype.renderNode.apply(this, arguments); }, prettyPrint: function(key, val){ if (typeof val === "number"){ return val.toPrecision(4); } if (typeof val === "string"){ return val.replace(new RegExp("\t", 'g'), "").replace(new RegExp("\n", 'g'), "
") } return val; } }); }); ================================================ FILE: app/scripts/views/SearchElementView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ tagName: 'li', className: 'search-element', template: _.template( $('#search-element-template').html() ), events: { 'click': 'click' }, initialize: function(a, arr){ this.app = arr.app; this.appView = arr.appView; this.elementClick = arr.click; }, render: function() { this.$el.html( this.template( this.model.toJSON() ) ); }, click: function(e) { if (!this.elementClick) return; this.elementClick(this); } }); }); ================================================ FILE: app/scripts/views/SearchView.js ================================================ define(['backbone', 'List', 'SearchElementView'], function(Backbone, List, SearchElementView) { return Backbone.View.extend({ tagName: 'div', className: 'search-container row', initialize: function(atts, arr) { this.app = arr.app; this.app.on('change:showingSearch', this.highlightInput, this ); this.app.SearchElements.on('add remove', this.render, this); }, template: _.template( $('#search-template').html() ), events: { 'keyup .library-search-input': 'searchKeyup' }, render: function(arg) { this.$el.html( this.template( this.model.toJSON() ) ); this.$input = this.$('.library-search-input'); this.$list = this.$('.search-list'); this.$list.empty(); var that = this; this.app.SearchElements.forEach(function(ele) { if (ele.name === null) return; var eleView = new SearchElementView({ model: ele }, { app: that.app, click: function(e){ that.elementClick.call(that, e) } }); eleView.render(); that.$list.append( eleView.$el ); }); var options = { valueNames: [ 'name' ] }; this.list = new List(this.el, options); }, highlightInput: function(){ if (this.app.get('showingSearch')) { this.$input.focus().select(); } else { this.$input.blur(); } }, addNode: function(name){ this.app.getCurrentWorkspace().addNodeByNameAndPosition( name, this.app.newNodePosition ); }, elementClick: function(ele){ this.addNode(ele.model.get('name')); this.app.set('showingSearch', false); }, searchKeyup: function(event) { if ( event.keyCode === 13) { // enter key causes first result to be inserted var nodeName = this.$list.find('.search-element').first().find('.name').first().html(); if (nodeName === undefined ) return; this.addNode( nodeName ); this.app.set('showingSearch', false); } else if ( event.keyCode === 27) { // esc key exits search this.app.set('showingSearch', false); } } }); }); ================================================ FILE: app/scripts/views/ShareView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ el: '#share', events: { 'click #exit-share' : 'clickExit', 'click' : 'clickExit', 'click .modal-box': 'stopPropagation', 'click .is-customizer-checkbox': 'checkboxChanged' }, template: _.template( $('#share-template').html() ), initialize: function( args, atts ) { this.app = atts.app; this.model.on('success', this.success, this ); }, stopPropagation: function(e){ e.stopPropagation(); }, render: function() { var ws = this.app.getCurrentWorkspace(); this.$el.html( this.template({ name: ws.get('name'), isCustomizer: ws.get('isCustomizer'), url: ws.getCustomizerUrl() }) ); if (!ws.isCustomizer){ // we defer this as the input isn't necessarily yet in the dom _.defer(function(){ this.$el.find('.customizer-url').focus().select(); }.bind(this) ); } this.updatePublicPrivate(); return this; }, updatePublicPrivate: function(){ if ( this.app.getCurrentWorkspace().get('isCustomizer') ){ this.$el.find('.is-customizer-checkbox').removeClass("fa-toggle-off"); this.$el.find('.is-customizer-checkbox').addClass("fa-toggle-on"); this.$el.find('.public').show(); this.$el.find('.private').hide(); } else { this.$el.find('.is-customizer-checkbox').removeClass("fa-toggle-on"); this.$el.find('.is-customizer-checkbox').addClass("fa-toggle-off"); this.$el.find('.public').hide(); this.$el.find('.private').show(); } }, checkboxChanged: function(){ var ws = this.app.getCurrentWorkspace(); ws.set('isCustomizer', !ws.get('isCustomizer') ); this.updatePublicPrivate(); }, clickExit: function(e){ this.app.set("showingShare", false); } }); }); ================================================ FILE: app/scripts/views/WorkspaceBrowserElementView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ tagName: 'li', className: 'workspace-browser-element', template: _.template( $('#workspace-browser-element-template').html() ), events: { 'click': 'clickOpen', 'click .delete-workspace': 'clickDelete', 'mouseenter': 'mouseenter', 'mouseleave': 'mouseleave' }, initialize: function(_, vals){ this.app = vals.app; }, render: function() { var v = this.model.toJSON(); var lastSave = this.model.get('lastSaved'); if (typeof lastSave === "string") { v["prettyDate"] = this.prettyDate( new Date(lastSave) ); } else { v["prettyDate"] = "Unknown"; } this.$el.html( this.template( v ) ); this.$open = this.$el.find('.workspace-browser-element-open'); }, prettyDate: function(date){ var diff = (((new Date()).getTime() - date.getTime()) / 1000), day_diff = Math.floor(diff / 86400); if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) return; return day_diff == 0 && ( diff < 60 && "just now" || diff < 120 && "1 minute ago" || diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || diff < 7200 && "1 hour ago" || diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; }, clickOpen: function(e) { this.app.openWorkspace( this.model.get('_id') ); }, clickDelete: function(e){ } }); }); ================================================ FILE: app/scripts/views/WorkspaceBrowserView.js ================================================ define(['backbone', 'WorkspaceBrowserElementView'], function(Backbone, WorkspaceBrowserElementView) { return Backbone.View.extend({ el: '#workspace-browser', initialize: function(atts, arr) { this.app = arr.app; this.model.get('workspaces').on('reset', this.render, this ); this.model.get('workspaces').on('add', this.addWorkspaceElement, this ); this.model.get('workspaces').on('remove', this.removeWorkspaceElement, this ); this.render(); }, template: _.template( $('#workspace-browser-template').html() ), events: { 'click .workspace-browser-refresh': "refreshClick", 'click #workspace-browser-header-custom-nodes': "customNodeHeaderClick", 'click #workspace-browser-header-projects': "projectHeaderClick" }, render: function(arg) { if (!this.rendered) { this.$el.html( this.template( this.model.toJSON() ) ); this.contents = this.$el.find('#workspace-browser-contents'); this.customNodes = this.$el.find('#workspace-browser-custom-nodes'); this.projects = this.$el.find('#workspace-browser-projects'); } this.rendered = true; this.customNodes.empty(); this.projects.empty(); }, refreshClick: function(e){ this.model.refresh(); e.stopPropagation(); }, customNodeHeaderClick: function(e){ // do not resize when refresh is clicked if ( $(e.target).hasClass('workspace-browser-refresh') ) return; this.projects.hide(); this.customNodes.show(); $('#workspace-browser-header-custom-nodes').css('bottom','').css('top','40px'); }, projectHeaderClick: function(e){ if ( $(e.target).hasClass('workspace-browser-refresh') ) return; this.customNodes.hide(); this.projects.show(); $('#workspace-browser-header-custom-nodes').css('bottom','0').css('top',''); }, addWorkspaceElement: function(x){ if (!this.contents) this.render(); var v = new WorkspaceBrowserElementView( { model: x }, { app : this.app } ); v.render(); if ( x.get('isCustomNode') ){ this.customNodes.append( v.$el ); } else { this.projects.append( v.$el ); } }, removeWorkspaceElement: function(ws){ if (!this.contents) return; this.contents.find('.workspace-browser-element[data-id*=' + ws.get('_id') + ']').remove(); } }); }); ================================================ FILE: app/scripts/views/WorkspaceControlsView.js ================================================ define(['backbone', 'List', 'SearchElementView', 'bootstrap', 'jquery'], function(Backbone, List, SearchElementView, bootstrap, $) { return Backbone.View.extend({ tagName: 'div', className: 'workspace-search', initialize: function(atts, arr) { this.app = arr.app; this.appView = arr.appView; this.app.SearchElements.on('add remove', this.render, this); }, template: _.template( $('#workspace-search-template').html() ), events: { 'keyup .library-search-input': 'searchKeyup', 'focus .library-search-input': 'focus', 'blur .library-search-input': 'blur', 'click #delete-button': 'deleteClick', 'click #undo-button': 'undoClick', 'click #redo-button': 'redoClick', 'click #copy-button': 'copyClick', 'click #paste-button': 'pasteClick', 'click #zoomin-button': 'zoominClick', 'click #zoomout-button': 'zoomoutClick', 'click #zoomreset-button': 'zoomresetClick', 'click #export-button': 'exportClick' }, render: function(arg) { this.$el.html( this.template( this.model.toJSON() ) ); this.$input = this.$('.library-search-input'); this.$list = this.$('.search-list'); // highlight text in the search input when focussed this.$input.focus(function(){ this.select(); }); this.$list.empty(); var that = this; this.app.SearchElements.forEach(function(ele) { var eleView = new SearchElementView({ model: ele }, { appView: that.appView, app: that.app, click: function(e){ that.elementClick.call(that, e); } }); eleView.render(); that.$list.append( eleView.$el ); }); var options = { valueNames: [ 'name' ] }; this.list = new List(this.el, options); // build button tooltips this.$el.find('#undo-button').tooltip({title: "Undo"}); this.$el.find('#redo-button').tooltip({title: "Redo"}); this.$el.find('#copy-button').tooltip({title: "Copy"}); this.$el.find('#paste-button').tooltip({title: "Paste"}); this.$el.find('#delete-button').tooltip({title: "Delete"}); this.$el.find('#zoomin-button').tooltip({title: "Zoom in"}); this.$el.find('#zoomout-button').tooltip({title: "Zoom out"}); this.$el.find('#zoomreset-button').tooltip({title: "Zoom fit"}); this.$el.find('#export-button').tooltip({title: "Export as STL"}); $('#workspace_hide').tooltip({title: "Switch between 3D view and nodes"}); $('#help-button').tooltip({title: "Help", placement: "left"}); $('#feedback-button').tooltip({title: "Feedback", placement: "left"}); $('#share-button').tooltip({title: "Share", placement: "left"}); }, focus: function(event){ this.$('.search-list').show(); this.$('.library-search-input').select(); }, blur: function(event){ var that = this; window.setTimeout(function(){ that.$('.search-list').hide(); }, 100); }, deleteClick: function(){ this.app.getCurrentWorkspace().removeSelected(); }, copyClick: function(){ this.app.getCurrentWorkspace().copy(); }, pasteClick: function(){ this.app.getCurrentWorkspace().paste(); }, undoClick: function(){ this.app.getCurrentWorkspace().undo(); }, redoClick: function(){ this.app.getCurrentWorkspace().redo(); }, zoomresetClick: function(){ this.appView.currentWorkspaceView.zoomAll(); }, zoominClick: function(){ this.app.getCurrentWorkspace().zoomIn(); }, zoomoutClick: function(){ this.app.getCurrentWorkspace().zoomOut(); }, addNode: function(name){ if (name === undefined ) return; this.app.getCurrentWorkspace().addNodeByNameAndPosition( name, this.appView.getCurrentWorkspaceCenter() ); }, exportClick: function(e){ this.app.getCurrentWorkspace().exportSTL(); }, elementClick: function(ele){ this.addNode( ele.model.get('name') ); }, searchKeyup: function(event) { // enter key causes first result to be inserted if ( event.keyCode === 13) { var nodeName = this.$list.find('.search-element').first().find('.name').first().html(); if (nodeName === undefined ) return; this.addNode( nodeName ); } } }); }); ================================================ FILE: app/scripts/views/WorkspaceTabView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ tagName: 'div', className: 'workspace-tab', initialize: function(atts) { this.app = this.model.app; this.listenTo( this.model, 'change:name', this.render); this.listenTo( this.model, 'change:current', this.render); }, template: _.template( $('#workspace-tab-template').html() ), events: { 'click': 'click', 'click .remove-button': 'remove', 'mouseover': 'showEditButton', 'mouseout': 'hideEditButton', 'click .edit-button': 'startEdit', 'blur .workspace-name': 'endEdit', // touch 'touchstart .edit-button': 'startEdit', 'touchstart': 'toggleShowingEditButton' }, render: function() { this.$el.html( this.template( this.model.toJSON() ) ); if (this.model.get('current') === true){ this.$el.addClass('current-workspace') } else { this.$el.removeClass('current-workspace') } this.$input = this.$('.workspace-name'); }, toggleShowingEditButton: function(){ if ( this.editButtonShown ){ this.editButtonShown = false; this.hideEditButton(); } else { this.editButtonShown = true; this.showEditButton(); } }, showEditButton: function() { this.$('.edit-button').css('visibility', 'visible'); }, hideEditButton: function() { this.$('.edit-button').css('visibility', 'hidden'); }, startEdit: function(e) { this.$input.prop('disabled', false); this.$input.focus().select(); this.$input.css('pointer-events', 'auto'); e.stopPropagation(); }, endEdit: function() { // the edit button is still visible on touch devices this.hideEditButton(); this.$input.prop('disabled', true); this.$input.css('pointer-events', 'none'); this.model.set('name', this.$input.val() ); }, click: function(e) { this.model.app.set('currentWorkspace', this.model.get('_id')); }, remove: function(e){ this.model.app.get('workspaces').remove(this.model); e.stopPropagation(); } }); }); ================================================ FILE: app/scripts/views/WorkspaceView.js ================================================ define(['backbone', 'Workspace', 'ConnectionView', 'MarqueeView', 'NodeViewTypes', 'Hammer'], function(Backbone, Workspace, ConnectionView, MarqueeView, NodeViewTypes, Hammer){ return Backbone.View.extend({ tagName: 'div', className: 'workspace_container row', events: { 'mousedown .workspace_back': 'deselectAll', 'touchstart .workspace_back': 'deselectAll', 'mousedown .workspace_back': 'startWorkspaceDrag', 'dblclick .workspace_back': 'showNodeSearch', // mousewheel 'mousewheel': 'mousewheelMove', 'DOMMouseScroll': 'mousewheelMove', 'MozMousePixelScroll': 'mousewheelMove', }, initialize: function(atts) { this.nodeViews = {}; this.connectionViews = {}; this.app = this.model.app; this.$workspace = $('
', {class: 'workspace'}); this.$workspace_back = $('
', {class: 'workspace_back'}); this.$workspace_canvas = $(''); this.$el.append( this.$workspace ); this.$workspace.append( this.$workspace_back ); this.$workspace.append( this.$workspace_canvas ); var that = this; this.listenTo(this.model, 'change:connections', function() { that.cleanup().renderConnections(); }); this.model.on('change:zoom', this.updateZoom, this ); this.model.on('change:offset', this.updateOffset, this ); this.model.on('change:isRunning', this.renderRunnerStatus, this); this.listenTo(this.model, 'change:nodes', function() { that.cleanup().renderNodes(); }); this.listenTo(this.model, 'change:current', this.onChangeCurrent ); this.listenTo(this.model, 'startProxyDrag', this.startProxyDrag); this.listenTo(this.model, 'endProxyDrag', this.endProxyDrag); this.renderProxyConnection(); this.renderMarquee(); this.renderRunnerStatus(); }, render: function() { return this .cleanup() .renderNodes() .renderConnections() .renderNodes() .renderRunnerStatus() .updateZoom() .updateOffset() .setupHammer(); }, hammerSetupDone: false, inWorkspaceCoordinates: function(x, y){ var offset = this.$workspace.offset() , zoom = this.model.get('zoom'); return [ (1 / zoom) * (x - offset.left), (1 / zoom) * ( y - offset.top) ]; }, setupHammer: function(){ if (this.hammerSetupDone) return this; this.hammerSetupDone = true; function isTouchDevice() { return 'ontouchstart' in window // works on most browsers || 'onmsgesturechange' in window; // works on ie10 } if (!isTouchDevice()) return; this.$workspace_back.on('touchmove', function(e){ e.preventDefault(); }) // marquee - single finger on .workspace_back var mcm = new Hammer.Manager(this.$workspace_back.get(0)); mcm.add( new Hammer.Pan({ direction: Hammer.DIRECTION_ALL, pointers: 1 }) ); // start marquee mcm.on('panstart', function(event){ this.model.marquee.setStartCorner( this.inWorkspaceCoordinates(event.center.x, event.center.y) ); }.bind(this)); // marquee mcm.on('pan', function(event) { this.model.marquee.set('hidden', false); this.model.marquee.setEndCorner( this.inWorkspaceCoordinates(event.center.x, event.center.y) ); this.doMarqueeSelect(); }.bind(this)); // end marquee mcm.on('panend', function(){ this.model.marquee.set('hidden', true); }.bind(this)); var mc = new Hammer.Manager(this.el); mc.add( new Hammer.Pan({ direction: Hammer.DIRECTION_ALL, pointers: 3 }) ); // start pan mc.on('panstart', function(){ this.scrollStart = [ this.$el.scrollLeft(), this.$el.scrollTop() ]; }.bind(this)); // pan mc.on('pan', function(ev) { this.model.set('offset', [ this.scrollStart[0] - ev.deltaX, this.scrollStart[1] - ev.deltaY ] ); }.bind(this)); // end pan mc.on('panend', function(){ this.zoomDisabled = true; setTimeout(function(){ this.zoomDisabled = false; }.bind(this), 200) }.bind(this)); // pinching mc.add( new Hammer.Pinch({ threshold: 0.1 }) ); mc.on('pinchstart', function(ev) { this.zoomStart = this.model.get('zoom'); }.bind( this )); mc.on('pinch', function(ev) { if (this.zoomDisabled) return; var val = this.zoomStart * ev.scale; if (val < 0.25) val = 0.25; if (val > 1.2) val = 1.2; this.model.set('zoom', val ); }.bind( this )); // tap mc.add( new Hammer.Tap({ taps: 2 }) ); mc.on('tap', function(e) { this.showNodeSearch({ clientX: e.center.x, clientY: e.center.y }); }.bind( this )); return this; }, mousewheelMove: function(e){ this.isMouseWheel = true; this.clientX = e.clientX; this.clientY = e.clientY; var delta = 0; if ( e.originalEvent.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 delta = e.originalEvent.wheelDelta; } else if ( e.originalEvent.detail !== undefined ) { // Firefox delta = -e.originalEvent.detail; } if( delta > 0 ) { if (this.model.get('zoom') < 1) this.model.set('zoom', Math.min(1, this.model.get('zoom') + delta * 0.0005 )); } else { if (this.model.get('zoom') > 0.25) this.model.set('zoom', Math.max( 0.25, this.model.get('zoom') + delta * 0.0005 )); } this.isMouseWheel = false; return false; }, startWorkspaceDrag: function(event){ this.deselectAll(); if (event.ctrlKey || event.which === 2){ this.startWorkspacePan(event); } else { this.startMarqueeDrag(event); } }, // workspace panning panStart: [0,0], startWorkspacePan: function(event){ event.preventDefault(); this.panStart = [ event.pageX, event.pageY ]; this.scrollStart = [ this.$el.scrollLeft(), this.$el.scrollTop() ]; this.$workspace.bind('mousemove', $.proxy( this.workspacePan, this) ); this.$workspace.bind('mouseup', $.proxy( this.endWorkspacePan, this) ); }, endWorkspacePan: function(event){ this.model.set('offset', [this.$el.scrollLeft(), this.$el.scrollTop()]) this.$workspace.unbind('mousemove', this.workspacePan); this.$workspace.unbind('mouseup', this.endWorkspacePan); }, workspacePan: function(event){ this.$el.scrollLeft( this.scrollStart[0] + this.panStart[0] - event.pageX ); this.$el.scrollTop( this.scrollStart[1] + this.panStart[1] - event.pageY ); }, // marquee drag startMarqueeDrag: function(event){ this.model.marquee.setStartCorner( this.inWorkspaceCoordinates(event.pageX, event.pageY) ); this.$workspace.bind('mousemove', $.proxy( this.marqueeDrag, this) ); this.$workspace.bind('mouseup', $.proxy( this.endMarqueeDrag, this) ); }, endMarqueeDrag: function(event){ this.model.marquee.set('hidden', true); this.$workspace.unbind('mousemove', this.marqueeDrag); this.$workspace.unbind('mouseup', this.endMarqueeDrag); }, marqueeDrag: function(event){ this.model.marquee.set('hidden', false); this.model.marquee.setEndCorner( this.inWorkspaceCoordinates(event.pageX, event.pageY) ); this.doMarqueeSelect(); }, doMarqueeSelect: function(){ var x1 = this.model.marquee.get('x') , y1 = this.model.marquee.get('y') , x2 = this.model.marquee.get('x') + this.model.marquee.get('width') , y2 = this.model.marquee.get('y') + this.model.marquee.get('height'); for (var nodeId in this.nodeViews ){ var node = this.nodeViews[nodeId]; var w = node.$el.width(); var h = node.$el.height(); var px = node.model.get('position'); var x = px[0], y = px[1]; var corners = [ [x, y], [x + w, y], [x + w, y + h], [x, y + h] ]; var cornerIn = false; corners.forEach(function(c){ var cx = c[0], cy = c[1]; if ( cx < x2 && cx > x1 && cy > y1 && cy < y2 ) { cornerIn = true; } }); if (cornerIn && !node.model.get('selected') ){ node.model.set('selected', true); } else if ( !cornerIn && node.model.get('selected') ){ node.model.set('selected', false); } } }, getCenter: function(){ var w = this.$el.width() , h = this.$el.height() , ho = this.$el.scrollTop() , wo = this.$el.scrollLeft() , zoom = 1 / this.model.get('zoom'); return [zoom * (wo + w / 2), zoom * (ho + h / 2)]; }, updateZoom: function(){ if (this.model.get('zoom') < 0) { this.model.set('zoom', 0.25); return this; } if (this.cachedZoom === this.model.get('zoom')) { return this; } this.zoomFactor = this.model.get('zoom') / (this.cachedZoom ? this.cachedZoom : this.model.get('zoom') ); this.cachedZoom = this.model.get('zoom'); this.$workspace.css('transform', 'scale(' + this.model.get('zoom') + ')' ); this.$workspace.css('transform-origin', "0 0" ); // get scroll here, because it gets removed by the following lines if (this.isMouseWheel){ var s = this.getNewScroll( this.clientX, this.clientY ); } else { var s = this.getNewScroll(); } this.model.set('offset', s); return this; }, getNewScroll: function(x, y){ var z = this.zoomFactor; var ox = this.model.get('offset')[0]; var oy = this.model.get('offset')[1]; if (!x || !y){ var w = this.$el.width(); var h = this.$el.height(); // this is the offset from the center in document coordinates var centerOffset = [w / 2, h / 2]; } else { var centerOffset = [x,y]; } var sx = z * ( ox + centerOffset[0] ) - centerOffset[0]; var sy = z * ( oy + centerOffset[1] ) - centerOffset[1]; if (sx < 0) sx = 0; if (sy < 0) sy = 0; return [sx,sy]; }, boundingBox: function(){ // build list of nodeViews in ws var nvs = []; for (var nv in this.nodeViews){ nvs.push( this.nodeViews[nv] ); } return nvs.reduce(function(a, x){ var p = x.model.get('position'); var nw = x.$el.width(); var nh = x.$el.height(); return [ Math.min(p[0], a[0]), Math.max(p[0] + nw, a[1]), Math.min(p[1], a[2]), Math.max(p[1] + nh, a[3]) ]; }, [ Number.MAX_VALUE, 0, Number.MAX_VALUE, 0 ] ); // minx, maxx, miny, maxy }, zoomAll: function(){ var bb = this.boundingBox(); // this is the min offset we expect from the bounding box in document space var o = 20 * (1 / this.model.get('zoom') ); bb = [bb[0] - o, bb[1] + o, bb[2] - o, bb[3] + o ]; // calculate zoom // we do this by first determining the width and height of the ws var wsw = this.$el.width(); var wsh = this.$el.height(); // now we determine the width of the node collection var ntw = bb[1] - bb[0]; var nth = bb[3] - bb[2]; // we calculate the zoom from the ratio between the node bbox and workspace size var zx = wsw / ntw; var zy = wsh / nth; var nz = Math.min( zx, zy ); // the new zoom is the lesser of these two - TODO account for zoom min nz = Math.min(1, nz); // set the zoom this.model.set('zoom', nz ); // set the offset, taking into account the new zoom var offset = [ nz * bb[0], nz * bb[2] ]; this.model.set('offset', offset); }, updateOffset: function(){ var s = this.model.get('offset'); this.$el.scrollLeft( s[0] ); this.$el.scrollTop( s[1] ); return this; }, $runnerStatus : undefined, runnerTemplate : _.template( $('#workspace-runner-status-template').html() ), renderRunnerStatus: function(){ // placeholder for future work return this; }, showNodeSearch: function(e){ this.app.set('showingSearch', true); // the position of the click in workspace coordinates var z = 1 / this.model.get('zoom'); var offX = z * (e.clientX + this.$el.scrollLeft()); var offY = z * (e.clientY + this.$el.scrollTop()); this.app.newNodePosition = [offX, offY]; }, startProxyDrag: function(event){ this.$workspace.bind('touchmove', $.proxy( this.proxyTouchDrag, this) ); this.$workspace.bind('mousemove', $.proxy( this.proxyDrag, this) ); this.$workspace.bind('touchend', $.proxy( this.endProxyDrag, this) ); this.$workspace.bind('mouseup', $.proxy( this.endProxyDrag, this) ); }, endProxyDrag: function(event){ this.$workspace.unbind('touchmove', this.proxyTouchDrag ); this.$workspace.unbind('mousemove', this.proxyDrag); this.$workspace.unbind('touchend', this.endProxyDrag); this.$workspace.unbind('mouseup', this.endProxyDrag); this.model.endProxyConnection(); }, proxyTouchDrag: function(event){ this.model.proxyConnection.set('endProxyPosition', this.inWorkspaceCoordinates(event.originalEvent.touches[0].pageX, event.originalEvent.touches[0].pageY)); event.preventDefault(); // prevents mousemove }, proxyDrag: function(event){ this.model.proxyConnection.set('endProxyPosition', this.inWorkspaceCoordinates(event.pageX, event.pageY)); }, renderProxyConnection: function() { var view = new ConnectionView({ model: this.model.proxyConnection, workspaceView: this, workspace: this.model, isProxy: true }); view.render(); this.$workspace_canvas.prepend( view.el ); }, renderMarquee: function() { var view = new MarqueeView({ model: this.model.marquee, workspaceView: this, workspace: this.model }); view.render(); this.$workspace_canvas.prepend( view.el ); }, cleanup: function() { this.clearDeadNodes(); this.clearDeadConnections(); return this; }, renderNodes: function() { var this_ = this; this.model.get('nodes').each(function(nodeModel) { var nodeView = this_.nodeViews[nodeModel.get('_id')]; // if NodeView has not been drawn if ( nodeView === undefined){ var NodeView = NodeViewTypes.Base; if ( NodeViewTypes[ nodeModel.get('typeName') ] != undefined) { NodeView = NodeViewTypes[ nodeModel.get('typeName') ]; } nodeView = new NodeView({ model: nodeModel, workspaceView: this_, workspace: this_.model }); this_.nodeViews[ nodeView.model.get('_id') ] = nodeView; this_.$workspace.prepend( nodeView.$el ); nodeView.render(); nodeView.makeDraggable(); nodeView.delegateEvents(); this_.$workspace_canvas.append( nodeView.portGroup ); } }); return this; }, keydownHandler: function(e){ var isBackspaceOrDelete = e.keyCode === 46 || e.keyCode === 8; if ( !(e.metaKey || e.ctrlKey) && !isBackspaceOrDelete ) return; // do not capture from input if (e.originalEvent.srcElement && e.originalEvent.srcElement.nodeName === "INPUT") return; if (e.target.nodeName === "INPUT") return; // keycodes: http://css-tricks.com/snippets/javascript/javascript-keycodes/ switch (e.keyCode) { case 8: this.model.removeSelected(); return e.preventDefault(); case 46: this.model.removeSelected(); return e.preventDefault(); case 61: case 187: this.model.zoomIn(); return e.preventDefault(); case 189: case 173: this.model.zoomOut(); return e.preventDefault(); case 67: this.model.copy(); return e.preventDefault(); case 86: this.model.paste(); return e.preventDefault(); case 88: this.model.copy(); this.model.removeSelected(); return e.preventDefault(); case 89: this.model.redo(); return e.preventDefault(); case 90: this.model.undo(); return e.preventDefault(); } }, renderConnections: function() { var this_ = this; this.model.get('connections').forEach( function( cntn ) { var view = this_.connectionViews[cntn.get('_id')] if ( this_.connectionViews[cntn.get('_id')] === undefined){ view = new ConnectionView({ model: cntn, workspaceView: this_, workspace: this_.model }); } view.delegateEvents(); if (!view.el.parentNode){ view.render(); this_.$workspace_canvas.prepend( view.el ); this_.connectionViews[ view.model.get('_id') ] = view; } }); return this; }, clearDeadNodes: function() { for (var key in this.nodeViews){ if (this.model.get('nodes').get(key) === undefined){ this.nodeViews[key].remove(); delete this.nodeViews[key]; } } }, clearDeadConnections: function() { for (var key in this.connectionViews){ if (this.model.get('connections').get(key) === undefined){ this.connectionViews[key].remove(); delete this.connectionViews[key]; } } }, onRemove: function(){ this.get('nodes').forEach(function(n){ if (n.onRemove) n.onRemove(); }) }, deselectAll: function() { this.model.get('nodes').deselectAll(); }, }); }); ================================================ FILE: app/scripts/views/customizer/CustomizerAppView.js ================================================ // the application is constituted by // CustomizerAppView, CustomizerApp - controls visibility of widget // CustomizerWorkspaceView - collection of node widgets, similar to a nodeview, includes mask for which widgets will be visible // CustomizerNodeView - similar to a nodeview, but no inputs/outputs, simplified controls, potentially multiple options with labels // CustomizerViewer - allows you to view your customized geometry, with saved camera position define(['backbone', 'CustomizerViewerView', 'CustomizerHeaderView', 'CustomizerWorkspaceView'], function(Backbone, CustomizerViewer, CustomizerHeaderView, CustomizerWorkspaceView ) { 'use strict'; return Backbone.View.extend({ el: '#customizer-app ', events: { }, initialize: function( args, atts ) { this.listenTo(this.model, 'change', this.render); }, render: _.once(function() { (new CustomizerViewer()).render(); var hv = new CustomizerHeaderView({model: this.model.getCurrentWorkspace() }) this.listenTo( hv, "download-stl", function(){ this.model.getCurrentWorkspace().exportSTL() } );; hv.render(); (new CustomizerWorkspaceView({model: this.model.getCurrentWorkspace() })).render(); return this; }), }); }); ================================================ FILE: app/scripts/views/customizer/CustomizerHeaderView.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ el: '#customizer-header', template: _.template( $('#header-template').html() ), events: { "click .stl-download" : "downloadStl" }, initialize: function( args, atts ) { this.listenTo(this.model, 'change', this.render); }, render: function() { this.$el.html( this.template( this.model.toJSON() ) ); return this; }, downloadStl: function() { this.trigger("download-stl") } }); }); ================================================ FILE: app/scripts/views/customizer/CustomizerViewerView.js ================================================ define(['backbone', 'Three', 'OrbitControls'], function(Backbone) { var container, $container; var camera, controls, renderer; var geometry, group; scene = {}; var mouse = new THREE.Vector2(), offset = new THREE.Vector3(), INTERSECTED, SELECTED; var objects = [], plane; var mouseX = 0, mouseY = 0; var animate; var windowHalfX = window.innerWidth / 2; var windowHalfY = window.innerHeight / 2; return Backbone.View.extend({ el: '#customizer-viewer', events: { }, initialize: function( args, atts ) { }, done: false, render: _.once(function() { this.init(); this.renderView(); }), init: function() { container = document.getElementById("customizer-viewer"); $container = $(container); camera = new THREE.PerspectiveCamera( 30, $container.width() / $container.height(), 1, 10000 ); camera.position.set( 140, 140, 140 ); camera.up.set( 0, 0, 1 ); camera.lookAt( new THREE.Vector3(0,0,0) ); scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setClearColor( 0xffffff, 1 ); renderer.setSize( $container.width(), $container.height() ); renderer.sortObjects = false; container.appendChild( renderer.domElement ); renderer.domElement.setAttribute("id", "renderer_canvas"); // add subtle ambient lighting var ambientLight = new THREE.AmbientLight(0x555555); scene.add(ambientLight); // add directional light source var directionalLight = new THREE.DirectionalLight(0xbbbbbb); directionalLight.position.set(50, 30, 50); scene.add(directionalLight); var directionalLight = new THREE.DirectionalLight(0xaaaaaa); directionalLight.position.set(-0.2, -0.8, 1).normalize(); scene.add(directionalLight); this.makeGrid(); controls = new THREE.OrbitControls(camera, container); var that = this; animate = function(){ requestAnimationFrame( animate ); that.renderView(); }; requestAnimationFrame(animate); window.addEventListener( 'resize', this.onWindowResize.bind(this), false ); }, onWindowResize : function() { windowHalfX = $container.width() / 2; windowHalfY = $container.height() / 2; camera.aspect = windowHalfX/ windowHalfY; camera.updateProjectionMatrix(); renderer.setSize( 2*windowHalfX, 2*windowHalfY ); this.render(); }, renderView: function() { controls.update(); renderer.render( scene, camera ); }, makeGrid: function(){ var l = 60; var axisHelper = new THREE.AxisHelper( l ); scene.add( axisHelper ); var geometry = new THREE.Geometry(); var geometryThick = new THREE.Geometry(); var n = l; var inc = 2 * l / n; var rate = 10; for (var i = 0; i < n + 1; i++){ var v1 = new THREE.Vector3(-l, -l + i * inc, 0); var v2 = new THREE.Vector3(l, -l + i * inc, 0); geometry.vertices.push(v1); geometry.vertices.push(v2); if (i % rate == 0){ geometryThick.vertices.push(v1); geometryThick.vertices.push(v2); } } for (var i = 0; i < n + 1; i++){ var v1 = new THREE.Vector3(-l + i * inc, l, 0); var v2 = new THREE.Vector3(-l + i * inc, -l, 0); geometry.vertices.push(v1); geometry.vertices.push(v2); if (i % rate == 0){ geometryThick.vertices.push(v1); geometryThick.vertices.push(v2); } } var material = new THREE.LineBasicMaterial({ color: 0xeeeeee, linewidth: 0.1 }); var materialThick = new THREE.LineBasicMaterial({ color: 0xeeeeee, linewidth: 2 }); var line = new THREE.Line(geometry, material, THREE.LinePieces); var lineThick = new THREE.Line(geometryThick, materialThick, THREE.LinePieces); scene.add(line); scene.add(lineThick); } }); }); ================================================ FILE: app/scripts/views/customizer/CustomizerWorkspaceView.js ================================================ define(['backbone', 'BaseWidgetView', 'GeometryWidgetView', 'NumberWidgetView'], function(Backbone, BaseWidgetView, GeometryWidgetView, NumberWidgetView) { return Backbone.View.extend({ el: '#customizer-workspace', events: { "click #hide-workspace" : "hideWorkspace" }, initialize: function( args, atts ) { }, map: { "Number" : NumberWidgetView }, hasWidgets: false, buildWidget: function(x){ if (x.get('extra') != undefined && x.get('extra').lock) return; var widgetView = GeometryWidgetView; if (x.get('type').typeName in this.map){ widgetView = this.map[x.get('type').typeName]; } var widget = new widgetView({model: x}); if (x.get('type').typeName in this.map){ this.$el.append( widget.render().$el ); this.hasWidgets = true; } }, visible: true, hideWorkspace: function(){ if (this.visible){ this.$el.addClass('workspace-contracted'); this.$el.find('.widget').css('visibility', 'hidden'); this.$el.find('#hide-workspace i').removeClass('fa-arrow-circle-left'); this.$el.find('#hide-workspace i').addClass('fa-arrow-circle-right'); this.visible = false; } else { this.$el.removeClass('workspace-contracted'); this.$el.find('.widget').css('visibility', 'visible'); this.$el.find('#hide-workspace i').removeClass('fa-arrow-circle-right'); this.$el.find('#hide-workspace i').addClass('fa-arrow-circle-left'); this.visible = true; } }, render: function() { this.model.get('nodes').each(this.buildWidget.bind(this)); if (!this.hasWidgets) this.$el.hide(); return this; } }); }); ================================================ FILE: app/scripts/views/customizer/widgets/Base.js ================================================ define(['backbone'], function(Backbone) { return Backbone.View.extend({ tagName: 'div', className: 'widget', template: _.template( $('#widget-template').html() ), initialize: function(args) { this.model.on('evalFailed', this.onEvalFailed, this ); this.model.on('evalBegin', this.onEvalBegin, this ); this.listenTo(this.model, 'requestRender', this.render ); this.listenTo(this.model, 'change:position', this.move ); this.listenTo(this.model, 'change:lastValue', this.renderLastValue ); this.listenTo(this.model, 'change:failureMessage', this.renderLastValue ); this.listenTo(this.model, 'change:ignoreDefaults', this.colorPorts ); this.listenTo(this.model, 'change:selected', this.colorSelected); this.listenTo(this.model, 'change:visible', this.render); this.listenTo(this.model, 'change:isEvaluating', this.colorEvaluating); this.model.on('evalFailed', this.onEvalFailed, this ); this.model.on('evalBegin', this.onEvalBegin, this ); }, colorSelected: function(){ }, render: function() { this.$el.html( this.template( this.model.toJSON() ) ); return this; } }); }); ================================================ FILE: app/scripts/views/customizer/widgets/Geometry.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseWidgetView'], function(Backbone, _, $, BaseWidgetView) { return BaseWidgetView.extend({ initialize: function(args) { BaseWidgetView.prototype.initialize.apply(this, arguments); this.model.on('change:selected', this.colorSelected, this); this.model.on('change:visible', this.changeVisibility, this); this.model.on('remove', this.onRemove, this); this.model.on('change:prettyLastValue', this.onEvalComplete, this ); this.model.workspace.on('change:current', this.changeVisibility, this); this.onEvalComplete(); }, setMaterials: function(partMat, meshMat, lineMat){ this.threeGeom.traverse(function(ele) { if (ele instanceof THREE.Mesh) { ele.material = meshMat; } else if (ele instanceof THREE.Line) { ele.material = lineMat; } else { ele.material = partMat; } }); }, colorSelected: function(){ BaseWidgetView.prototype.colorSelected.apply(this, arguments); if ( !( this.threeGeom && this.model.get('visible')) ) return this; if (this.model.get('selected')) { var meshMat = new THREE.MeshPhongMaterial({color: 0x66d6ff }); var partMat = new THREE.ParticleBasicMaterial({color: 0x66d6ff, size: 5, sizeAttenuation: false}); var lineMat = new THREE.LineBasicMaterial({ color: 0x66d6ff }); } else { var meshMat = new THREE.MeshPhongMaterial({color: 0x999999}); var partMat = new THREE.ParticleBasicMaterial({color: 0x999999, size: 5, sizeAttenuation: false}); var lineMat = new THREE.LineBasicMaterial({ color: 0x000000 }); } this.setMaterials(partMat, meshMat, lineMat); return this; }, // 3D move to node subclass onRemove: function(){ this.model.workspace.off('change:current', this.changeVisibility, this); scene.remove(this.threeGeom); }, evaluated: false, toThreeGeom: function( rawGeom ) { var threeGeom = new THREE.Geometry(), face; if (!rawGeom) return threeGeom; if (!rawGeom.vertices && !rawGeom.linestrip ) return threeGeom; if (rawGeom.linestrip) return this.addLineStrip( rawGeom, threeGeom ); if (rawGeom.vertices && !rawGeom.faces) return this.addPoints( rawGeom, threeGeom ); for ( var i = 0; i < rawGeom.vertices.length; i++ ) { var v = rawGeom.vertices[i]; threeGeom.vertices.push( new THREE.Vector3( v[0], v[1], v[2] ) ); } if (!rawGeom.faces) return threeGeom; for ( var i = 0; i < rawGeom.faces.length; i++ ) { var f = rawGeom.faces[i]; face = new THREE.Face3( f[0], f[1], f[2], new THREE.Vector3( f[3][0], f[3][1], f[3][2] ) ); threeGeom.faces.push( face ); } threeGeom._floodType = 0; return threeGeom; }, addPoints: function( rawGeom, threeGeom ){ for ( var i = 0; i < rawGeom.vertices.length; i++ ) { var v = rawGeom.vertices[i]; threeGeom.vertices.push( new THREE.Vector3( v[0], v[1], v[2] ) ); } threeGeom._floodType = 1; return threeGeom; }, addLineStrip: function( rawGeom, threeGeom ){ for ( var i = 0; i < rawGeom.linestrip.length; i++ ) { var v = rawGeom.linestrip[i]; threeGeom.vertices.push( new THREE.Vector3( v[0], v[1], v[2] ) ); } threeGeom._floodType = 2; return threeGeom; }, onEvalComplete: function(a, b, newValue){ if (!newValue && this.evaluated) return; this.evaluated = true; var lastValue = this.model.get('prettyLastValue'); var temp; if ( !lastValue ) return; if ( lastValue.vertices || lastValue.linestrip ){ temp = []; temp.push(lastValue); } else { temp = lastValue; // extract the list } var threeTemp = new THREE.Object3D(); this.drawChunked( threeTemp, temp, function() { if ( this.threeGeom ){ scene.remove( this.threeGeom ); } this.threeGeom = threeTemp; scene.add( this.threeGeom ); this.changeVisibility(); }, this ); }, // creating this data may be quite slow, we'll need to be careful drawChunked: function(geom, list, callback, that){ var i = 0; var tick = function() { var start = new Date().getTime(); for (; i < list.length && (new Date().getTime()) - start < 50; i++) { var g3 = that.toThreeGeom( list[i] ); if (that.model.get('selected')){ var color = 0x66d6ff; } else { var color = 0x999999; } switch (g3._floodType) { case 0: geom.add( new THREE.Mesh(g3, new THREE.MeshPhongMaterial({color: color})) ); break; case 1: geom.add( new THREE.ParticleSystem(g3, new THREE.ParticleBasicMaterial({color: color, size: 5, sizeAttenuation: false}) )); break; case 2: geom.add( new THREE.Line(g3, new THREE.LineBasicMaterial({ color: 0x000000 }))); break; } } if (i < list.length) { setTimeout(tick, 25); } else { callback.call(that); } }; setTimeout(tick, 0); }, changeVisibility: function(){ if ( !this.threeGeom ){ return; } if (!this.model.get('visible') || !this.model.workspace.get('current') ) { this.threeGeom.traverse(function(e) { e.visible = false; }); } else if ( this.model.get('visible') ) { this.threeGeom.traverse(function(e) { e.visible = true; }); } }, renderNode: function() { BaseWidgetView.prototype.renderNode.apply(this, arguments); // this.$toggleVis = this.$el.find('.toggle-vis'); // this.$toggleVis.show(); // var icon = this.$toggleVis.find('i'); // var label = this.$toggleVis.find('span'); // if (this.model.get('visible')){ // icon.addClass('icon-eye-open'); // icon.removeClass('icon-eye-close'); // label.html('Hide geometry'); // } else { // icon.removeClass('icon-eye-open'); // icon.addClass('icon-eye-close'); // label.html('Show geometry'); // } return this; }, }); }); ================================================ FILE: app/scripts/views/customizer/widgets/Number.js ================================================ define(['backbone', 'underscore', 'jquery', 'BaseWidgetView', 'jqueryuislider'], function(Backbone, _, $, BaseWidgetView) { return BaseWidgetView.extend({ template: _.template( $('#widget-number-template').html() ), initialize: function(args) { BaseWidgetView.prototype.initialize.apply(this, arguments); this.rendered = false; this.model.on('change:extra', function() { var ex = this.model.get('extra') ; this.silentSyncUI( ex ); this.model.trigger('updateRunner'); this.model.workspace.trigger('requestRun'); }, this); }, render: function() { BaseWidgetView.prototype.render.apply(this, arguments); if (this.rendered) return this; // make the slider this.slider = this.$el.find('.slider'); if (!this.slider) return; var extra = this.model.get('extra'); var min = extra.min != undefined ? extra.min : -150; var max = extra.max != undefined ? extra.max : 150; var step = extra.step != undefined ? extra.step : 0.1; var value = extra.value != undefined ? extra.value : 0; if (value === undefined ) value = this.model.get('lastValue'); var that = this; this.slider.slider( { min: min, max: max, step: step, value: value, change: function(e, ui){ that.inputSet.call(that, e, ui); }, slide: function(e, ui){ that.inputChanged.call(that, e, ui); } }); // this.currentValueInput = this.$el.find('.currentValue'); // this.currentValueInput.html( value ); this.currentValueInput = this.$el.find('.currentValue'); this.currentValueInput.val( value ); this.currentValueInput.change( function(e){ that.valChanged.call(that, e); e.stopPropagation(); }); this.minInput = this.$el.find('.num-min'); this.minInput.val(min); this.minInput.change( function(e){ that.minChanged.call(that, e); e.stopPropagation(); }); this.maxInput = this.$el.find('.num-max'); this.maxInput.val(max); this.maxInput.change( function(e){ that.maxChanged.call(that, e); e.stopPropagation(); }); this.stepInput = this.$el.find('.num-step'); this.stepInput.val(step); this.stepInput.change( function(e){ that.stepChanged.call(that, e); e.stopPropagation(); }); // adjust settings dropdown so that it stays open while editing // doesn't select the node when you're editing $('.dropdown.keep-open').on({ "shown.bs.dropdown": function() { that.selectable = false; that.model.set('selected', false); $(this).data('closable', false); }, "mouseleave": function() { $(this).data('closable', true); }, "click": function() { $(this).data('closable', false); }, "hide.bs.dropdown": function() { if ( $(this).data('closable') ) that.selectable = true; return $(this).data('closable'); } }); // this.rendered = true; return this; }, silentSyncUI: function(data){ this.silent = true; this.currentValueInput.val( data.value ); this.setSliderValue( data.value ); this.minInput.html( data.min ); this.maxInput.html( data.max ); this.stepInput.html( data.step ); this.silent = false; }, currentValue: function(){ return this.slider.slider("option", "value"); }, setSliderValue: function(val){ return this.slider.slider("option", "value", val); }, valChanged: function(val){ var val = parseFloat( this.currentValueInput.val() ); if (isNaN(val)) return; return this.setSliderValue( val ); }, minChanged: function(e, u){ var val = parseFloat( this.minInput.val() ); if (isNaN(val)) return; if (this.currentValue < val) this.setSliderValue(val); this.slider.slider("option", "min", val); }, maxChanged: function(e){ var val = parseFloat( this.maxInput.val() ); if (isNaN(val)) return; if (this.currentValue > val) this.setSliderValue(val); this.slider.slider("option", "max", val); }, stepChanged: function(e){ var val = parseFloat( this.stepInput.val() ); if (isNaN(val)) return; this.slider.slider("option", "step", val); }, inputChanged: function(e,ui) { var val = ui.value; this.$el.find('.currentValue').html(val); }, inputSet: function(e,ui) { if ( this.silent ) return; var newValue = { value: this.slider.slider("option", "value"), min: this.slider.slider("option", "min"), step: this.slider.slider("option", "step"), max: this.slider.slider("option", "max") }; this.model.workspace.setNodeProperty({property: 'extra', _id: this.model.get('_id'), newValue: newValue }); } }); }); ================================================ FILE: app/styles/bootstrap.css ================================================ /*! * Bootstrap v3.0.3 (http://getbootstrap.com) * Copyright 2013 Twitter, Inc. * Licensed under http://www.apache.org/licenses/LICENSE-2.0 */ /*! normalize.css v2.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,samp{font-family:monospace,serif;font-size:1em}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h1 small,h2 small,h3 small,h1 .small,h2 .small,h3 .small{font-size:65%}h4,h5,h6{margin-top:10px;margin-bottom:10px}h4 small,h5 small,h6 small,h4 .small,h5 .small,h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-primary:hover{color:#3071a9}.text-warning{color:#8a6d3b}.text-warning:hover{color:#66512c}.text-danger{color:#a94442}.text-danger:hover{color:#843534}.text-success{color:#3c763d}.text-success:hover{color:#2b542c}.text-info{color:#31708f}.text-info:hover{color:#245269}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}.list-inline>li:first-child{padding-left:0}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small,blockquote .small{display:block;line-height:1.428571429;color:#999}blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small,blockquote.pull-right .small{text-align:right}blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.428571429}code,kbd,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}@media(min-width:768px){.container{width:750px}}@media(min-width:992px){.container{width:970px}}@media(min-width:1200px){.container{width:1170px}}.row{}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media(min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media(min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media(min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>.active,.table>tbody>tr>.active,.table>tfoot>tr>.active,.table>thead>.active>td,.table>tbody>.active>td,.table>tfoot>.active>td,.table>thead>.active>th,.table>tbody>.active>th,.table>tfoot>.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>.active:hover,.table-hover>tbody>.active:hover>td,.table-hover>tbody>.active:hover>th{background-color:#e8e8e8}.table>thead>tr>.success,.table>tbody>tr>.success,.table>tfoot>tr>.success,.table>thead>.success>td,.table>tbody>.success>td,.table>tfoot>.success>td,.table>thead>.success>th,.table>tbody>.success>th,.table>tfoot>.success>th{background-color:#dff0d8}.table-hover>tbody>tr>.success:hover,.table-hover>tbody>.success:hover>td,.table-hover>tbody>.success:hover>th{background-color:#d0e9c6}.table>thead>tr>.danger,.table>tbody>tr>.danger,.table>tfoot>tr>.danger,.table>thead>.danger>td,.table>tbody>.danger>td,.table>tfoot>.danger>td,.table>thead>.danger>th,.table>tbody>.danger>th,.table>tfoot>.danger>th{background-color:#f2dede}.table-hover>tbody>tr>.danger:hover,.table-hover>tbody>.danger:hover>td,.table-hover>tbody>.danger:hover>th{background-color:#ebcccc}.table>thead>tr>.warning,.table>tbody>tr>.warning,.table>tfoot>tr>.warning,.table>thead>.warning>td,.table>tbody>.warning>td,.table>tfoot>.warning>td,.table>thead>.warning>th,.table>tbody>.warning>th,.table>tfoot>.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>.warning:hover,.table-hover>tbody>.warning:hover>td,.table-hover>tbody>.warning:hover>th{background-color:#faf2cc}@media(max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}output{display:block;padding-top:7px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:normal;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline select.form-control{width:auto}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-control-static{padding-top:7px}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#fff}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-link{font-weight:normal;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1;-moz-osx-font-smoothing:grayscale}.glyphicon:empty{width:1em}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn:first-child>.btn{margin-right:-1px}.input-group-btn:last-child>.btn{margin-left:-1px}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media(min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media(min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form select.form-control{width:auto}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;height:auto;max-width:100%;margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child th,.panel>.table>tbody:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:last-child>th,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:last-child>td,.panel>.table-responsive>.table-bordered>thead>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;z-index:1050;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;outline:0;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicons-chevron-left,.carousel-control .glyphicons-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,tr.visible-xs,th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}table.visible-xs.visible-sm{display:table}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}table.visible-xs.visible-md{display:table}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}table.visible-xs.visible-lg{display:table}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm,tr.visible-sm,th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}table.visible-sm.visible-xs{display:table}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}table.visible-sm.visible-md{display:table}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}table.visible-sm.visible-lg{display:table}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md,tr.visible-md,th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}table.visible-md.visible-xs{display:table}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}table.visible-md.visible-sm{display:table}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}table.visible-md.visible-lg{display:table}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg,tr.visible-lg,th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}table.visible-lg.visible-xs{display:table}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}table.visible-lg.visible-sm{display:table}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}table.visible-lg.visible-md{display:table}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}table.hidden-xs{display:table}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs,tr.hidden-xs,th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm,tr.hidden-xs.hidden-sm,th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md,tr.hidden-xs.hidden-md,th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg,tr.hidden-xs.hidden-lg,th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}table.hidden-sm{display:table}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs,tr.hidden-sm.hidden-xs,th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm,tr.hidden-sm,th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md,tr.hidden-sm.hidden-md,th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg,tr.hidden-sm.hidden-lg,th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}table.hidden-md{display:table}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs,tr.hidden-md.hidden-xs,th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm,tr.hidden-md.hidden-sm,th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md,tr.hidden-md,th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg,tr.hidden-md.hidden-lg,th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}table.hidden-lg{display:table}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs,tr.hidden-lg.hidden-xs,th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm,tr.hidden-lg.hidden-sm,th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md,tr.hidden-lg.hidden-md,th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg,tr.hidden-lg,th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print,tr.visible-print,th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print,tr.hidden-print,th.hidden-print,td.hidden-print{display:none!important}}` ================================================ FILE: app/styles/customizer.css ================================================ #customizer-app { background: #e3e3e3; top: 0; right: 0; left: 0; bottom: 0; position: absolute; } #customizer-workspace-header { position: absolute; bottom: -12px; right: -12px; color: #444; height: 20px; padding: 10px; font-size: 18px; text-shadow: 0px 1px 1px whitesmoke; text-align: right; } #customizer-workspace { color: black; position: absolute; top: 100px; left: 0; width: 380px; z-index: 1; background: rgba(0, 0, 0, 0.14); } #customizer-workspace.workspace-contracted { width: 20px; } #customizer-viewer { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: black; z-index: 0; } #customizer-header { position: absolute; top: 0; right: 0; left: 0; height: 50px; z-index: 2; font-size: 20px; display: inline-block; padding: 20px; padding-left: 30px; font-color: #222; text-shadow: 0px 1px 1px whitesmoke; } .indicator { margin-right: 30px; } #customize-header-tag { text-transform: uppercase; font-size: 13px; color: grey; font-weight: bold; } #powered-by { position: absolute; z-index: 2; right: 0; bottom: 0; height: 36px; padding: 10px; font-size: 12px; color: black; text-shadow: 0px 1px 0px grey; } #customize-header-title { padding-left: 20px; font-size: 24px; } #customizer-header-controls { padding: 30px; position: absolute; top: 0; right: 0; } .widget { width:370px; margin: 20px; } .widget .name { font-weight: bold; text-transform: uppercase; color: #222; display: inline-block; width: 130px; text-align: right; padding-right: 15px; font-size: 11px; text-shadow: 0px 1px 1px white; } ================================================ FILE: app/styles/main.css ================================================ body { width: 100%; height: 100%; margin: 0px; margin: 0; -webkit-backface-visibility: hidden; } .row, .col { overflow: hidden; position: absolute; } .row { left: 0; right: 0; } .col { top: 0; bottom: 0; } .scroll-x { overflow-x: auto; } .scroll-y { overflow-y: auto; } .btn-block { margin-top: 10px; margin-bottom: 10px; margin-left: auto; margin-right: auto; display: block; width: 200px; padding-left: 0; padding-right: 0; } .btn-block + .btn-block { margin-top: 5px; } /* * Social Buttons for Bootstrap * * Copyright 2013-2014 Panayiotis Lipiridis * Licensed under the MIT License * * https://github.com/lipis/bootstrap-social */ .btn-social { position: relative; padding-left: 44px; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .btn-social :first-child { position: absolute; left: 0; top: 0; bottom: 0; width: 32px; line-height: 34px; font-size: 1.6em; text-align: center; border-right: 1px solid rgba(0, 0, 0, 0.2); } .btn-social.btn-lg { padding-left: 61px; } .btn-social.btn-lg :first-child { line-height: 45px; width: 45px; font-size: 1.8em; } .btn-social.btn-sm { padding-left: 38px; } .btn-social.btn-sm :first-child { line-height: 28px; width: 28px; font-size: 1.4em; } .btn-social.btn-xs { padding-left: 30px; } .btn-social.btn-xs :first-child { line-height: 20px; width: 20px; font-size: 1.2em; } .btn-social-icon { position: relative; padding-left: 44px; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-radius: 2px; height: 34px; width: 34px; padding: 0; } .btn-social-icon :first-child { position: absolute; left: 0; top: 0; bottom: 0; width: 32px; line-height: 34px; font-size: 1.6em; text-align: center; border-right: 1px solid rgba(0, 0, 0, 0.2); } .btn-social-icon.btn-lg { padding-left: 61px; } .btn-social-icon.btn-lg :first-child { line-height: 45px; width: 45px; font-size: 1.8em; } .btn-social-icon.btn-sm { padding-left: 38px; } .btn-social-icon.btn-sm :first-child { line-height: 28px; width: 28px; font-size: 1.4em; } .btn-social-icon.btn-xs { padding-left: 30px; } .btn-social-icon.btn-xs :first-child { line-height: 20px; width: 20px; font-size: 1.2em; } .btn-social-icon :first-child { border: none; text-align: center; width: 100% !important; } .btn-social-icon.btn-lg { height: 45px; width: 45px; padding-left: 0; padding-right: 0; } .btn-social-icon.btn-sm { height: 30px; width: 30px; padding-left: 0; padding-right: 0; } .btn-social-icon.btn-xs { height: 22px; width: 22px; padding-left: 0; padding-right: 0; } .btn-facebook { color: #ffffff; background-color: #3b5998; border-color: rgba(0, 0, 0, 0.2); } .btn-facebook:hover, .btn-facebook:focus, .btn-facebook:active, .btn-facebook.active, .open .dropdown-toggle .btn-facebook { color: #ffffff; background-color: #30487b; border-color: rgba(0, 0, 0, 0.2); } .btn-facebook:active, .btn-facebook.active, .open .dropdown-toggle .btn-facebook { background-image: none; } .btn-flood { margin-top: 40px; color: #ffffff; background-color: #AAA; border-color: rgba(0, 0, 0, 0.2); } .btn-flood:hover, .btn-flood:focus, .btn-flood:active, .btn-flood.active, .open .dropdown-toggle .btn-flood { color: #ffffff; background-color: #999; border-color: rgba(0, 0, 0, 0.2); } .btn-google-plus { margin-top: 40px; color: #ffffff; background-color: #dd4b39; border-color: rgba(0, 0, 0, 0.2); } .btn-google-plus:hover, .btn-google-plus:focus, .btn-google-plus:active, .btn-google-plus.active, .open .dropdown-toggle .btn-google-plus { color: #ffffff; background-color: #ca3523; border-color: rgba(0, 0, 0, 0.2); } .btn-google-plus:active, .btn-google-plus.active, .open .dropdown-toggle .btn-google-plus { background-image: none; } .btn-google-plus.disabled, .btn-google-plus[disabled], fieldset[disabled] .btn-google-plus, .btn-google-plus.disabled:hover, .btn-google-plus[disabled]:hover, fieldset[disabled] .btn-google-plus:hover, .btn-google-plus.disabled:focus, .btn-google-plus[disabled]:focus, fieldset[disabled] .btn-google-plus:focus, .btn-google-plus.disabled:active, .btn-google-plus[disabled]:active, fieldset[disabled] .btn-google-plus:active, .btn-google-plus.disabled.active, .btn-google-plus[disabled].active, fieldset[disabled] .btn-google-plus.active { background-color: #dd4b39; border-color: rgba(0, 0, 0, 0.2); } .btn-google-plus .badge { color: #dd4b39; background-color: #ffffff; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .dropdown-menu { font-size: 10px; } .CodeMirror { font-family: monospace; height: auto; border: 1px solid rgba(0,0,0,0.2); } .CodeMirror-hints { z-index: 150; font-size: 10px; } .workspace-runner-status { top: 90px; right: 60px; z-index: 100; position: absolute; background: none; } .workspace-search { bottom: 0px; left: 0px; font-size: 11px; position: absolute; z-index: 100; } .workspace-tab-function { font-style: italic; font-family: serif; color: gray; font-size: 15px; padding-left: 7px; } #workspace-search-input-container { padding-left: 10px; box-shadow: 0 10px 12px #444; } .workspace-search-container ul { max-height: 500px; overflow-y: scroll; list-style: none; padding: 0; margin: 0; } .workspace-search-container { color: #BBB; display: inline-block; background-color: #222; } .workspace-search input { padding: 0; margin: 0; outline: 0; font-size: 11px; height: 32px; display:inline-block; } .workspace-search-container:last-child { border-right: none; } .workspace-search-button-active { color: #BBB; background-color: #222; border-right: 1px solid #333; } .workspace-search-button-active:hover { color: white; background-color: #333; } .workspace-search-button-active:hover i { opacity: 1; } .workspace-search-button-inactive { color: #444; } .workspace-search-button { height: 32px; padding: 8px; margin-left: 0; border-right: 1px solid #333; display:inline-block; } .workspace-search input:focus { outline:none; } #present-button { border-left: 1px solid #444; color: #444; } .connection { fill:none; stroke-width: 3.2; stroke:#333; } .partial-function-connection { fill:none; stroke-width: 2.6; stroke: white; } .node:hover .node-last-value-container { display: block; height: 26px; bottom: -26px; padding: 3px; border: 1px solid #999; transition-delay: 0.3s; -webkit-transition-delay: 0.3s; /* Safari */ transition-duration: 0.1s; -webkit-transition-duration: 0.1s; /* Safari */ } .node:hover .node-last-value-container:hover { right: -100px; left: -100px; height: 60px; bottom: -60px; overflow-x: scroll; overflow-y: scroll; } .node:hover .node-last-value-container:hover .node-last-value { width: 100%; word-wrap: break-word; } .node-failed .node-last-value { display: none; } .node-failed .node-failure-message { display: block; width: 100%; color: red; } .node-last-value-container { height: 0; bottom: 0; padding: 0; transition-duration: 0.2s; -webkit-transition-duration: 0.2s; /* Safari */ opacity: 0.8; position: absolute; left: -1px; right: -1px; background: white; color: #444; overflow-x: hidden; overflow-y: hidden; } #login-failure-message { padding: 10px; margin: 10px; background-color: #FF7A7A; color: white; font-weight: bold; } #extra-menu .menu-element { height: 38px; width: 38px; font-size: 14px; background: #222; border:none; } #extra-menu #help-button:hover { color: #92d199; } #extra-menu #feedback-button:hover { color: #589cc8; } #feedback-form textarea { max-width: 100%; } .node-dropdown-menu .dropdown-header { text-transform: uppercase; font-size: 9px; padding: 2px; } .node-dropown-menu-item { display: inline-block; margin: 5px 0px 5px 10px; color: black; } .node-dropown-menu-item:hover { color: black; } .node-dropdown-menu { padding: 10px; } #extra-menu .menu-element:hover { background: #444; color: white; } #extra-menu { z-index: 99; position: absolute; width: 38px; top: 80px; right: 0px; } .modal-box p { font-style: 12px; padding: 5px; } #feedback-sending-message { display: none; padding: 10px; margin: 10px; background-color: lightblue; color: white; font-weight: bold; } #feedback-success-message { display: none; padding: 10px; margin: 10px; background-color: green; color: white; font-weight: bold; } .script-add-remove-ports { float: left; } #feedback-failure-message { display: none; padding: 10px; margin: 10px; background-color: #FF7A7A; color: white; font-weight: bold; } .node-failure-message { display: none; padding: 2px; left: 0; right: 0; } .node-last-value { display: inline-block; padding: 2px; left: 0; right: 0; } .tab-button { display: inline-block; padding: 5px; } .menu-element i { margin-left: 2px; margin-right: 2px; } .tab-button a { color: grey; } .tab-button:hover a { color: #222; } .tab-button-hilite a { color: black; } #tab-button-container { margin-left: auto; margin-right: auto; width: 300px; } #workspace-browser { background: #e0e0e0; color: #666; display: none; z-index: 105; right: 0; width: 200px; bottom: 0; top: 30px; } .workspace-browser-header { background: #DDD; color: #444; padding: 10px; display: block; width: 100%; height: 40px; border-bottom: 1px solid #c7bbb1; } .workspace-browser-header:hover { background: white; } #workspace-browser-header-projects { top: 0px; z-index: 1; } #workspace-browser-header-custom-nodes { bottom: 0px; } .workspace-browser-element-list { list-style: none; margin: 0; padding : 0; background: #222; } #workspace-browser-projects { top: 40px; bottom: 40px; } #workspace-browser-custom-nodes { display: none; top: 80px; bottom: 0px; } .workspace-name { pointer-events: none; } .workspace-browser-element-ellipsis { float: right; } .workspace-browser-element-open { float: right; color: #666; } .workspace-browser-element-open:hover { color: white; } .workspace-browser-element { font-size: 12px; } .workspace-browser-element-date { font-size: 10px; } .workspace-browser-element { margin: 0; padding: 5px; padding-left: 14px; background: #333; color: white; border-top: solid 1px #444; border-bottom: solid 1px #222; } .workspace-browser-header-refresh { margin: 5px; float: right; } .workspace-browser-element:hover { background: #444; color: white; } #help { display: none; z-index: 100; overflow: visible; } .help-section { font-size: 11px; display: none; position: absolute; background: whitesmoke; border: 1px solid #AAA; width: 200px; padding: 10px; box-shadow: 1px 1px 10px #444; } .help-section span { color: grey; font-size: 13px; display: block; padding-bottom: 8px; } .exit-help-section { display: inline-block; font-size: 12px; padding: 2px 10px 2px 30px; } .exit-help { display: inline-block; color: grey; padding: 2px 10px 2px 10px; } .enter-help-section { box-shadow: 0 0 10px white; color: white; position: absolute; height: 24px; width: 24px; padding: 3px; padding-left: 8px; border-radius: 15px; background: #92d199; } .help-container { margin: 30px; } #share .inner-box { padding-top: 8px; padding-bottom: 15px; } #share .inner-box .label { font-weight: bold; color: grey; } #share .is-customizer-checkbox { padding: 8px; margin: 10px; margin-right: 24px; font-size: 20px; } .indicator { border-radius: 2px; color: white; padding: 8px; font-size: 14px; text-transform: uppercase; } .private { background-color: #666; } .public { background-color: green; } #share .customizer-url { width: 100%; font-size: 14px; color: black; padding: 4px; } #share span { text-transform: uppercase; padding: 5px; font-size: 14px; } #exit-share { position: absolute; right: 10px; top: 10px; } .modal { z-index: 130; display: none; top: 0; right: 0; left: 0; bottom: 0; background-color: rgba(0,0,0,0.6); } .white-icon { color: white; } .modal-box { background: whitesmoke; border: 1px solid #AAA; position: relative; padding: 18px; top: 70px; width: 400px; margin-left: auto; margin-right: auto; margin-bottom: auto; } .modal p { font-size: 18px; color: #444; padding-top: 20px; padding-bottom: 10px; text-shadow: 0px 1px 0px white; } .modal-box h3 { color: #444; text-shadow: 0px 1px 0px white; font-size: 35px; } #exit-feedback { position: absolute; right: 10px; top: 10px; } #top_container { z-index: 100; color: black; font-size: 11px; top: 0; height: 30px; background: #e3e3e3; width: 100%; border-bottom: 1px solid #c7bbb1; } #menus { float: right; padding-left: 20px; padding-top: 10px; } .menu { padding-right: 15px; color: gray; } #file span { font-weight: 100; padding-top: 50px; margin-left: 20px; } #bottom-search { } .search-container { z-index: 105; top: 40%; left: 50%; max-height: 500px; margin-top: -150px; margin-left: -100px; width: 130px; background: none; } .search-title { color: white; padding: 5px; margin: 0px; } .search-list { max-height: 300px; overflow-y: scroll; overflow-x: hidden; list-style: none; padding: 0; margin: 0; } .search-element { background: #333; color: white; display: block; width: 100%; margin: 0; font-size: 11px; border-top: solid 1px #444; border-bottom: solid 1px #222; padding: 5px; } .search-element:hover { background: #444; } .library-search-input { color: whitesmoke; padding: 10px; width: 130px; background: #222; border: none; font-size: 15px; } .library-search-input:focus { outline:none; } #workspaces { overflow:hidden; /*hide the scrollbar*/ } #viewer { position: absolute; top: 0px; bottom: 0px; } .workspace_container { position: absolute; top: 26px; overflow: auto; /* hide the scrollbar */ bottom: -18px; right: -18px; } .menu-element { color: #666; font-size: 11px; font-weight: bold; height: 30px; float: right; padding: 7px; display: inline-block; border-left: 1px solid #c7bbb1; } .menu-element:hover { color: black; background: #FFF; } #add-workspace-button { padding: 5px; border-right: 1px solid #c7bbb1; } #add-workspace-button i { color: black; font-size: 12px; padding-top: 3px; } #add-workspace-select-element { float: left; overflow: visible; display: none; } #add-workspace-select-container { z-index: 200; position:relative; top: 30px; left: -25px; width: 80px; color: #DDD } #add-workspace-select-container ul { background: #444; list-style: none; padding: 0; margin: 0; } #add-workspace-select-container li { padding: 5px; background: #333; color: white; border-top: solid 1px #444; border-bottom: solid 1px #222; } #add-workspace-select-container li:hover { background: #444; color: white; } .workspace-browser-button-active { color: black; background: #C6c6c6; } .dim-button { opacity: 0.4; } .dim-button:hover{ opacity: 1; } .workspace-tab { font-size: 11px; float: left; height: 30px; color: #e3e3e3; display: inline-block; border-left: 1px solid #c7bbb1; } .workspace-tab i { color: black; font-size: 12px; } .workspace-tab input{ border: none; margin: 0px 5px 0px 0px; padding: 8px 0 5px 5px; display: inline-block; background: rgba(0,0,0,0); } .workspace-tab input:focus{ outline: none; background: rgba(0,0,0,0.1); } .current-workspace { color: black; background: #C6c6c6; } .workspace-tab:hover { color: black; background: #FFF; } .workspaces_curtain { display: none; opacity: 0.4; background: black; top: 0; bottom: 0; z-index: 95; } .collapsed { opacity: 0; } .marquee { fill: none; stroke: black; } .workspace { z-index: 90; position: absolute; width: 20000px; height: 20000px; } .workspace_back { z-index: 50; position: absolute; width: 20000px; height: 20000px; } .workspace_canvas { position: absolute; width: 20000px; height: 20000px; background-color: rgba(0,0,0,0.3); } #workspace_hide { font-size: 12px; bottom: 0px; background: #222; position: absolute; z-index: 100; padding: 15px; } #workspace_hide span { font-weight: bold; } #workspace_hide:hover { background: #333; color: white; } .rightside { right: 0; } .leftside { right: 0; } .remove-button { margin: 0 10px 0 0; } .node { font-size: 10px; -webkit-user-select: none; /* Chrome/Safari */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE10+ */ /* Rules below not implemented in browsers yet */ -o-user-select: none; user-select: none; z-index: 98; position: absolute; background: #e3e3e3; padding-top: 2px; padding-bottom: 2px; color: #111; border: 1px solid #999; } .node a { outline: 0; color: #a5a5a5; text-decoration: none; } .node a:hover{ outline: 0; text-decoration: none; } .node a:link{ outline: 0; text-decoration: none; } .node a:visited{ outline: 0; text-decoration: none; } .node a:active{ outline: 0; text-decoration: none; } .currentValue { color:black; } .node-settings { position: absolute; bottom: 0; right: 0; opacity: 0.3; padding: 1px 2px 1px 1px; } .node-settings:hover { opacity: 1; } .toggle-vis { display: none; } .toggle-vis i { font-size: 13px; } .node-number-input { padding:2px; margin:2px; } .currentValue { box-shadow: none; } .currentValue:-moz-placeholder, .currentValue:focus { box-shadow:none !important; } .node-evaluating { color: #444; background-color: #C6c6c6; } .node-failed { background-color: #FF7A7A; } .node-selected { z-index: 99; border: 1px solid #0BB; color: black; background-color: #aed1d1; } .node-data-container { color: #555; font-weight: bold; display: inline-block; float: left; padding: 5px 10px 14px 5px; } .node-data-container input { font-family: monospace; font-weight: normal; } .watch-data-container{ max-width: 300px; max-height: 200px; margin: 0px 5px 0px 10px; padding: 2px; color: black; overflow-y: scroll; overflow-x: scroll; background: #f6f6f6; border: 1px solid #BBB; } .watch-data-container pre { padding: none; margin: none; text-align: left; } .num-data-container input{ width: 60px; margin-left: 5px; } .input-node { background: #f7e5a3; } .output-node { background: #f7e5a3; } .node-inputs, .node-outputs { list-style: none; margin: 0; padding: 0; display: inline-block; border-bottom: 1px solid rgba(0,0,0,0.2); float: left; } .node-outputs { margin-bottom: 8px; /* particularly useful for node output collections */ } .node-inputs-extra { list-style: none; margin: 0; padding: 0; display: inline-block; float: left; } .node-port-extra { padding: 5px; margin: 0; color: #AAA; border: 1px solid rgba(0,0,0,0); border-bottom: none; } .node-port-extra:hover { color: #FFF; } .node-port { padding: 5px; margin: 0; border: 1px solid rgba(0,0,0,0.2); border-bottom: none; } .node-port:hover { color: black; background: #FFF; } .node-port-output { border-right: none; } .node-port-input { border-left: none; } body { margin: 0; padding: 0; } .btn-group-xs { font-size: 20px; } /*! * jQuery UI Slider @VERSION * http://jqueryui.com * * Copyright 2013 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://docs.jquery.com/UI/Slider#theming */ .ui-slider { position: relative; text-align: left; } .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } /* For IE8 - See #6727 */ .ui-slider.ui-state-disabled .ui-slider-handle, .ui-slider.ui-state-disabled .ui-slider-range { filter: inherit; } .ui-slider-horizontal { height: .8em; } .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } .ui-slider-horizontal .ui-slider-range-min { left: 0; } .ui-slider-horizontal .ui-slider-range-max { right: 0; } .ui-slider-vertical { width: .8em; height: 100px; } .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } .ui-slider-vertical .ui-slider-range-min { bottom: 0; } .ui-slider-vertical .ui-slider-range-max { top: 0; } ================================================ FILE: bower.json ================================================ { "name": "flood", "version": "0.0.0", "dependencies": { "jquery": "1.9.1", "requirejs": "~2.1.5", "requirejs-text": "~2.0.5", "backbone-amd": "~1.0.0", "almond": "0.2.6", "bootstrap": "3.0.3", "underscore-amd": "~1.4.4", "threejs": "r67", "jquery.ui": "1.10.3", "listjs": "1.0.0", "modernizr": "~2.6.2", "q": "1.0", "jqueryui-touch-punch": "furf/jquery-ui-touch-punch", "pace": "0.5.1", "components-font-awesome": "4.3.0", "hammerjs": "~2.0.4", "fastclick": "~1.0.3", "FileSaver": "eliGrey/FileSaver.js", "CodeMirror": "~5.3.0" }, "devDependencies": {} } ================================================ FILE: package.json ================================================ { "name": "flood", "version": "0.0.4", "description": "A visual programming language for JavaScript, based on Scheme", "dependencies": { }, "devDependencies": { "grunt-node-webkit-builder": "~0.1.13", "grunt": "0.4.2", "grunt-processhtml": "~0.2.6", "grunt-contrib-copy": "0.4.1", "grunt-contrib-concat": "0.3.0", "grunt-contrib-uglify": "0.2.7", "grunt-contrib-compass": "0.7.0", "grunt-contrib-jshint": "0.7.2", "grunt-contrib-cssmin": "0.7.0", "grunt-contrib-connect": "0.3.0", "grunt-contrib-clean": "0.5.0", "grunt-contrib-imagemin": "0.4.0", "grunt-contrib-livereload": "0.1.2", "grunt-mocha": "0.4.7", "grunt-bower-requirejs": "0.8.0", "grunt-requirejs": "0.4.0", "grunt-regarde": "0.1.1", "grunt-open": "0.2.2", "matchdep": "0.3.0", "amdefine": ">=0.0.5", "webworker": "*" }, "engines": { "node": ">=0.10.0" } } ================================================ FILE: server/.gitignore ================================================ lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz *.swp pids logs results tmp npm-debug.log node_modules .idea *.iml .DS_Store Thumbs.db public/css/styles.css builtAssets ssl ================================================ FILE: server/.travis.yml ================================================ language: node_js services: - mongodb node_js: - '0.10' ================================================ FILE: server/app.js ================================================ /** * Module dependencies. */ var express = require('express'); var cookieParser = require('cookie-parser'); var compress = require('compression'); var session = require('express-session'); var bodyParser = require('body-parser'); var logger = require('morgan'); var errorHandler = require('errorhandler'); // var csrf = require('csurf'); var methodOverride = require('method-override'); var MongoStore = require('connect-mongo')({ session: session }); var flash = require('express-flash'); var path = require('path'); var mongoose = require('mongoose'); var passport = require('passport'); var expressValidator = require('express-validator'); var connectAssets = require('connect-assets'); var https = require('https'); var http = require('http'); var fs = require('fs'); /** * Load controllers. */ var feedbackController = require('./controllers/feedback'); var floodController = require('./controllers/flood'); var homeController = require('./controllers/home'); var userController = require('./controllers/user'); var apiController = require('./controllers/api'); var contactController = require('./controllers/contact'); /** * API keys + Passport configuration. */ var secrets = require('./config/secrets'); var passportConf = require('./config/passport'); /** * Create Express server. */ var app = express(); /** * Mongoose configuration. */ mongoose.connect(secrets.db); mongoose.connection.on('error', function() { console.error('✗ MongoDB Connection Error. Please make sure MongoDB is running.'); }); /** * Express configuration. */ var hour = 3600000; var day = hour * 24; var week = day * 7; app.set('port', process.env.PORT || 3000); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.use(connectAssets({ paths: ['public/css', 'public/js'], helperContext: app.locals })); app.use(compress()); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded()); app.use(expressValidator()); app.use(methodOverride()); app.use(cookieParser()); app.use(session({ secret: secrets.sessionSecret, store: new MongoStore({ url: secrets.db, auto_reconnect: true }) })); // app.use(csrf()); app.use(passport.initialize()); app.use(passport.session()); app.use(function(req, res, next) { res.locals.user = req.user; // res.locals._csrf = req.csrfToken(); res.locals.secrets = secrets; next(); }); app.use(flash()); var staticFolder = process.env.FLOOD_STATIC || '../app'; app.use(express.static(path.join(__dirname, staticFolder ))); app.use(function(req, res, next) { // Keep track of previous URL if (req.method !== 'GET') return next(); var path = req.path.split('/')[1]; if (/(auth|login|logout|signup)$/i.test(path)) return next(); req.session.returnTo = req.path; next(); }); /** * Application routes. */ // flood routes // customizer (experimental) app.get('/customize-:id',function(req,res){ var fileStream = fs.createReadStream(staticFolder + '/customizer.html'); fileStream.on('open', function () { fileStream.pipe(res); }); }); app.get('/custdata/:id', floodController.getCustomizerWorkspace); app.get('/mys', floodController.getMySession); app.put('/mys', floodController.putMySession); app.get('/nws', floodController.getNewWorkspace ); app.get('/ws', floodController.getWorkspaces); app.get('/ws/:id', floodController.getWorkspace); app.put('/ws/:id', floodController.putWorkspace); app.get('/', homeController.index); app.get('/email', userController.getEmail ); app.post('/feedback', feedbackController.postFeedback ); app.get('/login', userController.getLogin); app.get('/login2', userController.getLogin); app.post('/login', userController.postLogin); app.get('/logout', userController.logout); app.get('/forgot', userController.getForgot); app.post('/forgot', userController.postForgot); app.get('/reset/:token', userController.getReset); app.post('/reset/:token', userController.postReset); app.get('/signup', userController.getSignup); app.post('/signup', userController.postSignup); app.get('/contact', contactController.getContact); app.post('/contact', contactController.postContact); app.post('/contact', contactController.postContact); app.get('/account', passportConf.isAuthenticated, userController.getAccount); app.post('/account/profile', passportConf.isAuthenticated, userController.postUpdateProfile); app.post('/account/password', passportConf.isAuthenticated, userController.postUpdatePassword); app.post('/account/delete', passportConf.isAuthenticated, userController.postDeleteAccount); app.get('/account/unlink/:provider', passportConf.isAuthenticated, userController.getOauthUnlink); app.get('/api', apiController.getApi); app.get('/api/lastfm', apiController.getLastfm); app.get('/api/nyt', apiController.getNewYorkTimes); app.get('/api/aviary', apiController.getAviary); app.get('/api/paypal', apiController.getPayPal); app.get('/api/paypal/success', apiController.getPayPalSuccess); app.get('/api/paypal/cancel', apiController.getPayPalCancel); app.get('/api/steam', apiController.getSteam); app.get('/api/scraping', apiController.getScraping); app.get('/api/twilio', apiController.getTwilio); app.post('/api/twilio', apiController.postTwilio); app.get('/api/clockwork', apiController.getClockwork); app.post('/api/clockwork', apiController.postClockwork); app.get('/api/foursquare', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getFoursquare); app.get('/api/tumblr', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getTumblr); app.get('/api/facebook', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getFacebook); app.get('/api/github', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getGithub); app.get('/api/twitter', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getTwitter); app.get('/api/venmo', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getVenmo); app.post('/api/venmo', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.postVenmo); app.get('/api/linkedin', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getLinkedin); /** * OAuth routes for sign-in. */ app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'user_location'] })); app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), function(req, res) { res.redirect('/app.html'); }); app.get('/auth/github', passport.authenticate('github')); app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), function(req, res) { res.redirect(req.session.returnTo || '/'); }); app.get('/auth/google', passport.authenticate('google', { scope: 'profile email' })); app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), function(req, res) { res.redirect('/app.html'); }); app.get('/auth/twitter', passport.authenticate('twitter')); app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/login' }), function(req, res) { res.redirect(req.session.returnTo || '/'); }); app.get('/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' })); app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { failureRedirect: '/login' }), function(req, res) { res.redirect(req.session.returnTo || '/'); }); /** * OAuth routes for API examples that require authorization. */ app.get('/auth/foursquare', passport.authorize('foursquare')); app.get('/auth/foursquare/callback', passport.authorize('foursquare', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/foursquare'); }); app.get('/auth/tumblr', passport.authorize('tumblr')); app.get('/auth/tumblr/callback', passport.authorize('tumblr', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/tumblr'); }); app.get('/auth/venmo', passport.authorize('venmo', { scope: 'make_payments access_profile access_balance access_email access_phone' })); app.get('/auth/venmo/callback', passport.authorize('venmo', { failureRedirect: '/api' }), function(req, res) { res.redirect('/api/venmo'); }); // 404 error handler app.use(function(req, res) { res.status(404); res.render('404'); }); // 500 error handler app.use(errorHandler()); /** * Start Express server. */ var keyfn = 'ssl/server.key'; var crtfn = 'ssl/server.crt'; var intafn = 'ssl/int_ssl_a.pem'; var intbfn = 'ssl/int_ssl_b.pem'; var intcfn = 'ssl/int_ssl_c.pem'; if ( fs.existsSync( keyfn ) && fs.existsSync( crtfn ) && fs.existsSync( intafn ) && fs.existsSync( intbfn ) && fs.existsSync( intcfn )){ var key = fs.readFileSync(keyfn, 'utf8'); var crt = fs.readFileSync(crtfn, 'utf8'); var inta = fs.readFileSync(intafn, 'utf8'); var intb = fs.readFileSync(intbfn, 'utf8'); var intc = fs.readFileSync(intcfn, 'utf8'); var cred = { key: key, cert: crt, ca: [ inta, intb, intc ] }; https.createServer(cred, app).listen(443, function() { console.log("✔ Secure Express server listening on port %d in %s mode", 443, app.get('env')); }); } app.listen( app.get('port'), function() { console.log("✔ Express server listening on port %d in %s mode", app.get('port'), app.get('env')); }); module.exports = app; ================================================ FILE: server/cluster_app.js ================================================ /** * Module dependencies. */ var os = require('os'); var cluster = require('cluster'); /** * Cluster setup. */ // Setup the cluster to use app.js cluster.setupMaster({ exec: 'app.js' }); // Listen for dying workers cluster.on('exit', function(worker) { console.log('Worker ' + worker.id + ' died'); // Replace the dead worker cluster.fork(); }); // Fork a worker for each available CPU for (var i = 0; i < os.cpus().length; i++) { cluster.fork(); } ================================================ FILE: server/config/passport.js ================================================ var _ = require('underscore'); var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; var FacebookStrategy = require('passport-facebook').Strategy; var TwitterStrategy = require('passport-twitter').Strategy; var GitHubStrategy = require('passport-github').Strategy; var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy; var OAuthStrategy = require('passport-oauth').OAuthStrategy; // Tumblr var OAuth2Strategy = require('passport-oauth').OAuth2Strategy; // Venmo, Foursquare var User = require('../models/User'); var secrets = require('./secrets'); passport.serializeUser(function(user, done) { done(null, user.id); }); passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); }); /** * Sign in using Email and Password. */ passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) { User.findOne({ email: email }, function(err, user) { if (!user) return done(null, false, { message: 'Email ' + email + ' not found'}); user.comparePassword(password, function(err, isMatch) { if (isMatch) { return done(null, user); } else { return done(null, false, { message: 'Invalid email or password.' }); } }); }); })); /** * OAuth Strategy Overview * * - User is already logged in. * - Check if there is an existing account with a provider id or email. * - If there is, return an error message. (Account merging not supported) * - Else link new OAuth account with currently logged-in user. * - User is not logged in. * - Check if it's a returning user. * - If returning user, sign in and we are done. * - Else check if there is an existing account with user's email. * - If there is, return an error message. * - Else create a new account. */ /** * Sign in with Facebook. */ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ $or: [{ facebook: profile.id }, { email: profile.email }] }, function(err, existingUser) { if (existingUser) { req.flash('errors', { msg: 'There is already a Facebook account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(err); } else { User.findById(req.user.id, function(err, user) { user.facebook = profile.id; user.tokens.push({ kind: 'facebook', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; user.profile.gender = user.profile.gender || profile._json.gender; user.profile.picture = user.profile.picture || 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; user.save(function(err) { req.flash('info', { msg: 'Facebook account has been linked.' }); done(err, user); }); }); } }); } else { User.findOne({ facebook: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' }); done(err); } else { var user = new User(); user.email = profile._json.email; user.facebook = profile.id; user.tokens.push({ kind: 'facebook', accessToken: accessToken }); user.profile.name = profile.displayName; user.profile.gender = profile._json.gender; user.profile.picture = 'https://graph.facebook.com/' + profile.id + '/picture?type=large'; user.profile.location = (profile._json.location) ? profile._json.location.name : ''; user.save(function(err) { done(err, user); }); } }); }); } })); /** * Sign in with GitHub. */ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ $or: [{ github: profile.id }, { email: profile.email }] }, function(err, existingUser) { if (existingUser) { req.flash('errors', { msg: 'There is already a GitHub account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(err); } else { User.findById(req.user.id, function(err, user) { user.github = profile.id; user.tokens.push({ kind: 'github', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; user.profile.picture = user.profile.picture || profile._json.avatar_url; user.profile.location = user.profile.location || profile._json.location; user.profile.website = user.profile.website || profile._json.blog; user.save(function(err) { req.flash('info', { msg: 'GitHub account has been linked.' }); done(err, user); }); }); } }); } else { User.findOne({ github: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.' }); done(err); } else { var user = new User(); user.email = profile._json.email; user.github = profile.id; user.tokens.push({ kind: 'github', accessToken: accessToken }); user.profile.name = profile.displayName; user.profile.picture = profile._json.avatar_url; user.profile.location = profile._json.location; user.profile.website = profile._json.blog; user.save(function(err) { done(err, user); }); } }); }); } })); /** * Sign in with Twitter. */ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tokenSecret, profile, done) { if (req.user) { User.findOne({ twitter: profile.id }, function(err, existingUser) { if (existingUser) { req.flash('errors', { msg: 'There is already a Twitter account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(err); } else { User.findById(req.user.id, function(err, user) { user.twitter = profile.id; user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); user.profile.name = user.profile.name || profile.displayName; user.profile.location = user.profile.location || profile._json.location; user.profile.picture = user.profile.picture || profile._json.profile_image_url; user.save(function(err) { req.flash('info', { msg: 'Twitter account has been linked.' }); done(err, user); }); }); } }); } else { User.findOne({ twitter: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); var user = new User(); // Twitter will not provide an email address. Period. // But a person’s twitter username is guaranteed to be unique // so we can "fake" a twitter email address as follows: user.email = profile.username + "@twitter.com"; user.twitter = profile.id; user.tokens.push({ kind: 'twitter', accessToken: accessToken, tokenSecret: tokenSecret }); user.profile.name = profile.displayName; user.profile.location = profile._json.location; user.profile.picture = profile._json.profile_image_url; user.save(function(err) { done(err, user); }); }); } })); /** * Sign in with Google. */ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ $or: [{ google: profile.id }, { email: profile.email }] }, function(err, existingUser) { if (existingUser) { req.flash('errors', { msg: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(err); } else { User.findById(req.user.id, function(err, user) { user.google = profile.id; user.tokens.push({ kind: 'google', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; user.profile.gender = user.profile.gender || profile._json.gender; user.profile.picture = user.profile.picture || profile._json.picture; user.save(function(err) { req.flash('info', { msg: 'Google account has been linked.' }); done(err, user); }); }); } }); } else { User.findOne({ google: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.email }, function(err, existingEmailUser) { if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' }); done(err); } else { var user = new User(); user.email = profile._json.email; user.google = profile.id; user.tokens.push({ kind: 'google', accessToken: accessToken }); user.profile.name = profile.displayName; user.profile.gender = profile._json.gender; user.profile.picture = profile._json.picture; user.save(function(err) { done(err, user); }); } }); }); } })); /** * Sign in with LinkedIn. */ passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) { if (req.user) { User.findOne({ $or: [ { linkedin: profile.id }, { email: profile._json.emailAddress } ] }, function(err, existingUser) { if (existingUser) { req.flash('errors', { msg: 'There is already a LinkedIn account that belongs to you. Sign in with that account or delete it, then link it with your current account.' }); done(err); } else { User.findById(req.user.id, function(err, user) { user.linkedin = profile.id; user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); user.profile.name = user.profile.name || profile.displayName; user.profile.location = user.profile.location || profile._json.location.name; user.profile.picture = user.profile.picture || profile._json.pictureUrl; user.profile.website = user.profile.website || profile._json.publicProfileUrl; user.save(function(err) { req.flash('info', { msg: 'LinkedIn account has been linked.' }); done(err, user); }); }); } }); } else { User.findOne({ linkedin: profile.id }, function(err, existingUser) { if (existingUser) return done(null, existingUser); User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) { if (existingEmailUser) { req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with LinkedIn manually from Account Settings.' }); done(err); } else { var user = new User(); user.linkedin = profile.id; user.tokens.push({ kind: 'linkedin', accessToken: accessToken }); user.email = profile._json.emailAddress; user.profile.name = profile.displayName; user.profile.location = profile._json.location.name; user.profile.picture = profile._json.pictureUrl; user.profile.website = profile._json.publicProfileUrl; user.save(function(err) { done(err, user); }); } }); }); } })); /** * Tumblr API * Uses OAuth 1.0a Strategy. */ passport.use('tumblr', new OAuthStrategy({ requestTokenURL: 'http://www.tumblr.com/oauth/request_token', accessTokenURL: 'http://www.tumblr.com/oauth/access_token', userAuthorizationURL: 'http://www.tumblr.com/oauth/authorize', consumerKey: secrets.tumblr.consumerKey, consumerSecret: secrets.tumblr.consumerSecret, callbackURL: secrets.tumblr.callbackURL, passReqToCallback: true }, function(req, token, tokenSecret, profile, done) { User.findById(req.user._id, function(err, user) { user.tokens.push({ kind: 'tumblr', accessToken: token, tokenSecret: tokenSecret }); user.save(function(err) { done(err, user); }); }); } )); /** * Foursquare API * Uses OAuth 2.0 Strategy. */ passport.use('foursquare', new OAuth2Strategy({ authorizationURL: 'https://foursquare.com/oauth2/authorize', tokenURL: 'https://foursquare.com/oauth2/access_token', clientID: secrets.foursquare.clientId, clientSecret: secrets.foursquare.clientSecret, callbackURL: secrets.foursquare.redirectUrl, passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { User.findById(req.user._id, function(err, user) { user.tokens.push({ kind: 'foursquare', accessToken: accessToken }); user.save(function(err) { done(err, user); }); }); } )); /** * Venmo API * Uses OAuth 2.0 Strategy. */ passport.use('venmo', new OAuth2Strategy({ authorizationURL: 'https://api.venmo.com/v1/oauth/authorize', tokenURL: 'https://api.venmo.com/v1/oauth/access_token', clientID: secrets.venmo.clientId, clientSecret: secrets.venmo.clientSecret, callbackURL: secrets.venmo.redirectUrl, passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { User.findById(req.user._id, function(err, user) { user.tokens.push({ kind: 'venmo', accessToken: accessToken }); user.save(function(err) { done(err, user); }); }); } )); /** * Login Required middleware. */ exports.isAuthenticated = function(req, res, next) { if (req.isAuthenticated()) return next(); res.redirect('/login'); }; /** * Authorization Required middleware. */ exports.isAuthorized = function(req, res, next) { var provider = req.path.split('/').slice(-1)[0]; if (_.findWhere(req.user.tokens, { kind: provider })) next(); else res.redirect('/auth/' + provider); }; ================================================ FILE: server/config/secrets.js ================================================ module.exports = { db: process.env.MONGODB|| 'mongodb://localhost:27017/test', sessionSecret: process.env.SESSION_SECRET || 'Your Session Secret goes here', localAuth: true, feedback: { email: process.env.FEEDBACK_EMAIL || "Your email" }, mailgun: { login: process.env.MAILGUN_LOGIN || 'Your Mailgun SMTP Username', password: process.env.MAILGUN_PASSWORD || 'Your Mailgun SMTP Password' }, sendgrid: { user: process.env.SENDGRID_USER || 'Your SendGrid Username', password: process.env.SENDGRID_PASSWORD || 'Your SendGrid Password' }, nyt: { key: process.env.NYT_KEY || 'Your New York Times API Key' }, lastfm: { api_key: process.env.LASTFM_KEY || 'Your API Key', secret: process.env.LASTFM_SECRET || 'Your API Secret' }, facebookAuth: true, facebook: { clientID: process.env.FACEBOOK_ID || 'Your App ID', clientSecret: process.env.FACEBOOK_SECRET || 'Your App Secret', callbackURL: '/auth/facebook/callback', passReqToCallback: true }, githubAuth: false, github: { clientID: process.env.GITHUB_ID || 'Your Client ID', clientSecret: process.env.GITHUB_SECRET || 'Your Client Secret', callbackURL: '/auth/github/callback', passReqToCallback: true }, twitterAuth: false, twitter: { consumerKey: process.env.TWITTER_KEY || 'Your Consumer Key', consumerSecret: process.env.TWITTER_SECRET || 'Your Consumer Secret', callbackURL: '/auth/twitter/callback', passReqToCallback: true }, googleAuth: true, google: { clientID: process.env.GOOGLE_ID || 'Your Client ID', clientSecret: process.env.GOOGLE_SECRET || 'Your Client Secret', callbackURL: '/auth/google/callback', passReqToCallback: true }, linkedinAuth: false, linkedin: { clientID: process.env.LINKEDIN_ID || 'Your Client ID', clientSecret: process.env.LINKEDIN_SECRET || 'Your Client Secret', callbackURL: '/auth/linkedin/callback', scope: ['r_fullprofile', 'r_emailaddress', 'r_network'], passReqToCallback: true }, steam: { apiKey: process.env.STEAM_KEY || 'Your Steam API Key' }, twilio: { sid: process.env.TWILIO_SID || 'Your Twilio SID', token: process.env.TWILIO_TOKEN || 'Your Twilio token' }, clockwork: { apiKey: process.env.CLOCKWORK_KEY || 'Your Clockwork SMS API Key' }, tumblr: { consumerKey: process.env.TUMBLR_KEY || 'Your Consumer Key', consumerSecret: process.env.TUMBLR_SECRET || 'Your Consumer Secret', callbackURL: '/auth/tumblr/callback' }, foursquare: { clientId: process.env.FOURSQUARE_ID || 'Your Client ID', clientSecret: process.env.FOURSQUARE_SECRET || 'Your Client Secret', redirectUrl: process.env.FOURSQUARE_REDIRECT_URL || 'http://localhost:3000/auth/foursquare/callback' }, venmo: { clientId: process.env.VENMO_ID || 'Your Venmo Client ID', clientSecret: process.env.VENMO_SECRET || 'Your Venmo Client Secret', redirectUrl: process.env.VENMO_REDIRECT_URL || 'http://localhost:3000/auth/venmo/callback' }, paypal: { host: process.env.PAYPAL_HOST || 'api.sandbox.paypal.com', client_id: process.env.PAYPAL_ID || 'Your Client ID', client_secret: process.env.PAYPAL_SECRET || 'Your Client Secret', returnUrl: process.env.PAYPAL_RETURN_URL || 'http://localhost:3000/api/paypal/success', cancelUrl: process.env.PAYPAL_CANCEL_URL || 'http://localhost:3000/api/paypal/cancel' } }; ================================================ FILE: server/controllers/api.js ================================================ var secrets = require('../config/secrets'); var User = require('../models/User'); var querystring = require('querystring'); var validator = require('validator'); var async = require('async'); var cheerio = require('cheerio'); var request = require('request'); var _ = require('underscore'); var graph = require('fbgraph'); var LastFmNode = require('lastfm').LastFmNode; var tumblr = require('tumblr.js'); var foursquare = require('node-foursquare')({ secrets: secrets.foursquare }); var Github = require('github-api'); var Twit = require('twit'); var paypal = require('paypal-rest-sdk'); var twilio = require('twilio')(secrets.twilio.sid, secrets.twilio.token); var Linkedin = require('node-linkedin')(secrets.linkedin.clientID, secrets.linkedin.clientSecret, secrets.linkedin.callbackURL); var clockwork = require('clockwork')({key: secrets.clockwork.apiKey}); /** * GET /api * List of API examples. */ exports.getApi = function(req, res) { res.render('api/index', { title: 'API Browser' }); }; /** * GET /api/foursquare * Foursquare API example. */ exports.getFoursquare = function(req, res, next) { var token = _.findWhere(req.user.tokens, { kind: 'foursquare' }); async.parallel({ trendingVenues: function(callback) { foursquare.Venues.getTrending('40.7222756', '-74.0022724', { limit: 50 }, token.accessToken, function(err, results) { callback(err, results); }); }, venueDetail: function(callback) { foursquare.Venues.getVenue('49da74aef964a5208b5e1fe3', token.accessToken, function(err, results) { callback(err, results); }); }, userCheckins: function(callback) { foursquare.Users.getCheckins('self', null, token.accessToken, function(err, results) { callback(err, results); }); } }, function(err, results) { if (err) return next(err); res.render('api/foursquare', { title: 'Foursquare API', trendingVenues: results.trendingVenues, venueDetail: results.venueDetail, userCheckins: results.userCheckins }); }); }; /** * GET /api/tumblr * Tumblr API example. */ exports.getTumblr = function(req, res) { var token = _.findWhere(req.user.tokens, { kind: 'tumblr' }); var client = tumblr.createClient({ consumer_key: secrets.tumblr.consumerKey, consumer_secret: secrets.tumblr.consumerSecret, token: token.accessToken, token_secret: token.tokenSecret }); client.posts('goddess-of-imaginary-light.tumblr.com', { type: 'photo' }, function(err, data) { res.render('api/tumblr', { title: 'Tumblr API', blog: data.blog, photoset: data.posts[0].photos }); }); }; /** * GET /api/facebook * Facebook API example. */ exports.getFacebook = function(req, res, next) { var token = _.findWhere(req.user.tokens, { kind: 'facebook' }); graph.setAccessToken(token.accessToken); async.parallel({ getMe: function(done) { graph.get(req.user.facebook, function(err, me) { done(err, me); }); }, getMyFriends: function(done) { graph.get(req.user.facebook + '/friends', function(err, friends) { done(err, friends.data); }); } }, function(err, results) { if (err) return next(err); res.render('api/facebook', { title: 'Facebook API', me: results.getMe, friends: results.getMyFriends }); }); }; /** * GET /api/scraping * Web scraping example using Cheerio library. */ exports.getScraping = function(req, res, next) { request.get('https://news.ycombinator.com/', function(err, request, body) { if (err) return next(err); var $ = cheerio.load(body); var links = []; $(".title a[href^='http'], a[href^='https']").each(function() { links.push($(this)); }); res.render('api/scraping', { title: 'Web Scraping', links: links }); }); }; /** * GET /api/github * GitHub API Example. */ exports.getGithub = function(req, res) { var token = _.findWhere(req.user.tokens, { kind: 'github' }); var github = new Github({ token: token.accessToken }); var repo = github.getRepo('sahat', 'requirejs-library'); repo.show(function(err, repo) { res.render('api/github', { title: 'GitHub API', repo: repo }); }); }; /** * GET /api/aviary * Aviary image processing example. */ exports.getAviary = function(req, res) { res.render('api/aviary', { title: 'Aviary API' }); }; /** * GET /api/nyt * New York Times API example. */ exports.getNewYorkTimes = function(req, res, next) { var query = querystring.stringify({ 'api-key': secrets.nyt.key, 'list-name': 'young-adult' }); var url = 'http://api.nytimes.com/svc/books/v2/lists?' + query; request.get(url, function(error, request, body) { if (request.statusCode === 403) return next(Error('Missing or Invalid New York Times API Key')); var bestsellers = JSON.parse(body); res.render('api/nyt', { title: 'New York Times API', books: bestsellers.results }); }); }; /** * GET /api/lastfm * Last.fm API example. */ exports.getLastfm = function(req, res, next) { var lastfm = new LastFmNode(secrets.lastfm); async.parallel({ artistInfo: function(done) { lastfm.request("artist.getInfo", { artist: 'Epica', handlers: { success: function(data) { done(null, data); }, error: function(err) { done(err); } } }); }, artistTopAlbums: function(done) { lastfm.request("artist.getTopAlbums", { artist: 'Epica', handlers: { success: function(data) { var albums = []; _.each(data.topalbums.album, function(album) { albums.push(album.image.slice(-1)[0]['#text']); }); done(null, albums.slice(0, 4)); }, error: function(err) { done(err); } } }); } }, function(err, results) { if (err) return next(err.message); var artist = { name: results.artistInfo.artist.name, image: results.artistInfo.artist.image.slice(-1)[0]['#text'], tags: results.artistInfo.artist.tags.tag, bio: results.artistInfo.artist.bio.summary, stats: results.artistInfo.artist.stats, similar: results.artistInfo.artist.similar.artist, topAlbums: results.artistTopAlbums }; res.render('api/lastfm', { title: 'Last.fm API', artist: artist }); }); }; /** * GET /api/twitter * Twiter API example. */ exports.getTwitter = function(req, res, next) { var token = _.findWhere(req.user.tokens, { kind: 'twitter' }); var T = new Twit({ consumer_key: secrets.twitter.consumerKey, consumer_secret: secrets.twitter.consumerSecret, access_token: token.accessToken, access_token_secret: token.tokenSecret }); T.get('search/tweets', { q: 'hackathon since:2013-01-01', geocode: '40.71448,-74.00598,5mi', count: 50 }, function(err, reply) { if (err) return next(err); res.render('api/twitter', { title: 'Twitter API', tweets: reply.statuses }); }); }; /** * GET /api/paypal * PayPal SDK example. */ exports.getPayPal = function(req, res, next) { paypal.configure(secrets.paypal); var paymentDetails = { intent: 'sale', payer: { payment_method: 'paypal' }, redirect_urls: { return_url: secrets.paypal.returnUrl, cancel_url: secrets.paypal.cancelUrl }, transactions: [ { description: 'Node.js Boilerplate', amount: { currency: 'USD', total: '2.99' } } ] }; paypal.payment.create(paymentDetails, function(err, payment) { if (err) return next(err); req.session.paymentId = payment.id; var links = payment.links; for (var i = 0; i < links.length; i++) { if (links[i].rel === 'approval_url') { res.render('api/paypal', { approval_url: links[i].href }); } } }); }; /** * GET /api/paypal/success * PayPal SDK example. */ exports.getPayPalSuccess = function(req, res, next) { var paymentId = req.session.paymentId; var paymentDetails = { 'payer_id': req.query.PayerID }; paypal.payment.execute(paymentId, paymentDetails, function(err, payment) { if (err) { res.render('api/paypal', { result: true, success: false }); } else { res.render('api/paypal', { result: true, success: true }); } }); }; /** * GET /api/paypal/cancel * PayPal SDK example. */ exports.getPayPalCancel = function(req, res, next) { req.session.payment_id = null; res.render('api/paypal', { result: true, canceled: true }); }; /** * GET /api/steam * Steam API example. */ exports.getSteam = function(req, res, next) { var steamId = '76561197982488301'; var query = { l: 'english', steamid: steamId, key: secrets.steam.apiKey }; async.parallel({ playerAchievements: function(done) { query.appid = '49520'; var qs = querystring.stringify(query); request.get({ url: 'http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?' + qs, json: true }, function(error, request, body) { if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key')); done(error, body); }); }, playerSummaries: function(done) { query.steamids = steamId; var qs = querystring.stringify(query); request.get({ url: 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' + qs, json: true }, function(error, request, body) { if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key')); done(error, body); }); }, ownedGames: function(done) { query.include_appinfo = 1; query.include_played_free_games = 1; var qs = querystring.stringify(query); request.get({ url: 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?' + qs, json: true }, function(error, request, body) { if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key')); done(error, body); }); } }, function(err, results) { if (err) return next(err); res.render('api/steam', { title: 'Steam Web API', ownedGames: results.ownedGames.response.games, playerAchievemments: results.playerAchievements.playerstats, playerSummary: results.playerSummaries.response.players[0] }); }); }; /** * GET /api/twilio * Twilio API example. */ exports.getTwilio = function(req, res, next) { res.render('api/twilio', { title: 'Twilio API' }); }; /** * POST /api/twilio * Twilio API example. * @param telephone */ exports.postTwilio = function(req, res, next) { var message = { to: req.body.telephone, from: '+13472235148', body: 'Hello from the Hackathon Starter' }; twilio.sendMessage(message, function(err, responseData) { if (err) return next(err.message); req.flash('success', { msg: 'Text sent to ' + responseData.to + '.'}) res.redirect('/api/twilio'); }); }; /** * GET /api/clockwork * Clockwork SMS API example. */ exports.getClockwork = function(req, res) { res.render('api/clockwork', { title: 'Clockwork SMS API' }); }; /** * POST /api/clockwork * Clockwork SMS API example. * @param telephone */ exports.postClockwork = function(req, res, next) { var message = { To: req.body.telephone, From: 'Hackathon', Content: 'Hello from the Hackathon Starter' }; clockwork.sendSms(message, function(err, responseData) { if (err) return next(err.errDesc); req.flash('success', { msg: 'Text sent to ' + responseData.responses[0].to }); res.redirect('/api/clockwork'); }); }; /** * GET /api/venmo * Venmo API example. */ exports.getVenmo = function(req, res, next) { var token = _.findWhere(req.user.tokens, { kind: 'venmo' }); var query = querystring.stringify({ access_token: token.accessToken }); async.parallel({ getProfile: function(done) { request.get({ url: 'https://api.venmo.com/v1/me?' + query, json: true }, function(err, request, body) { done(err, body); }); }, getRecentPayments: function(done) { request.get({ url: 'https://api.venmo.com/v1/payments?' + query, json: true }, function(err, request, body) { done(err, body); }); } }, function(err, results) { if (err) return next(err); res.render('api/venmo', { title: 'Venmo API', profile: results.getProfile.data, recentPayments: results.getRecentPayments.data }); }); }; /** * POST /api/venmo * @param user * @param note * @param amount * Send money. */ exports.postVenmo = function(req, res, next) { req.assert('user', 'Phone, Email or Venmo User ID cannot be blank').notEmpty(); req.assert('note', 'Please enter a message to accompany the payment').notEmpty(); req.assert('amount', 'The amount you want to pay cannot be blank').notEmpty(); var errors = req.validationErrors(); if (errors) { req.flash('errors', errors); return res.redirect('/api/venmo'); } var token = _.findWhere(req.user.tokens, { kind: 'venmo' }); var formData = { access_token: token.accessToken, note: req.body.note, amount: req.body.amount }; if (validator.isEmail(req.body.user)) { formData.email = req.body.user; } else if (validator.isNumeric(req.body.user) && validator.isLength(req.body.user, 10, 11)) { formData.phone = req.body.user; } else { formData.user_id = req.body.user; } request.post('https://api.venmo.com/v1/payments', { form: formData }, function(err, request, body) { if (err) return next(err); if (request.statusCode !== 200) { req.flash('errors', { msg: JSON.parse(body).error.message }); return res.redirect('/api/venmo'); } req.flash('success', { msg: 'Venmo money transfer complete' }); res.redirect('/api/venmo'); }); }; /** * GET /api/linkedin * LinkedIn API example. */ exports.getLinkedin = function(req, res, next) { var token = _.findWhere(req.user.tokens, { kind: 'linkedin' }); var linkedin = Linkedin.init(token.accessToken); linkedin.people.me(function(err, $in) { if (err) return next(err); res.render('api/linkedin', { title: 'LinkedIn API', profile: $in }); }); }; ================================================ FILE: server/controllers/contact.js ================================================ var secrets = require('../config/secrets'); var nodemailer = require("nodemailer"); var smtpTransport = nodemailer.createTransport('SMTP', { // service: 'Mailgun', // auth: { // user: secrets.mailgun.login, // pass: secrets.mailgun.password // } service: 'SendGrid', auth: { user: secrets.sendgrid.user, pass: secrets.sendgrid.password } }); /** * GET /contact * Contact form page. */ exports.getContact = function(req, res) { res.render('contact', { title: 'Contact' }); }; /** * POST /contact * Send a contact form via Nodemailer. * @param email * @param name * @param message */ exports.postContact = function(req, res) { req.assert('name', 'Name cannot be blank').notEmpty(); req.assert('email', 'Email is not valid').isEmail(); req.assert('message', 'Message cannot be blank').notEmpty(); var errors = req.validationErrors(); if (errors) { req.flash('errors', errors); return res.redirect('/contact'); } var from = req.body.email; var name = req.body.name; var body = req.body.message; var to = 'your@email.com'; var subject = 'Contact Form | Flood'; var mailOptions = { to: to, from: from, subject: subject, text: body }; smtpTransport.sendMail(mailOptions, function(err) { if (err) { req.flash('errors', { msg: err.message }); return res.redirect('/contact'); } req.flash('success', { msg: 'Email has been sent successfully!' }); res.redirect('/contact'); }); }; ================================================ FILE: server/controllers/exampleWorkspaces.js ================================================ exports.myFirstProject = { "connections":[ { "kind":"addConnection", "startNodeId":398809118, "startPortIndex":0, "endNodeId":23624844, "endPortIndex":0, "_id":964734507, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":604762864, "startPortIndex":0, "endNodeId":398809118, "endPortIndex":0, "_id":647386019, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":598617458, "startPortIndex":0, "endNodeId":23624844, "endPortIndex":1, "_id":564971549, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":3271050, "startPortIndex":0, "endNodeId":598617458, "endPortIndex":2, "_id":742143501, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":440238000, "startPortIndex":0, "endNodeId":42665510, "endPortIndex":0, "_id":661808583, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":42665510, "startPortIndex":0, "endNodeId":945248858, "endPortIndex":0, "_id":359041827, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":604762864, "startPortIndex":0, "endNodeId":398809118, "endPortIndex":1, "_id":899758279, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "kind":"addConnection", "startNodeId":823934643, "startPortIndex":0, "endNodeId":42665510, "endPortIndex":1, "_id":764520692, "startProxy":false, "endProxy":false, "startProxyPosition":[ 0, 0 ], "endProxyPosition":[ 0, 0 ], "hidden":false }, { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":794491759, "endPortIndex":1, "endNodeId":440238000, "startPortIndex":0, "startNodeId":282252545, "kind":"addConnection" }, { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":965267809, "endPortIndex":2, "endNodeId":440238000, "startPortIndex":0, "startNodeId":498266200, "kind":"addConnection" }, { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":267211310, "endPortIndex":0, "endNodeId":317753978, "startPortIndex":0, "startNodeId":440238000, "kind":"addConnection" }, { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":573083411, "endPortIndex":2, "endNodeId":42665510, "startPortIndex":0, "startNodeId":317753978, "kind":"addConnection" } ], "nodes":[ { "name":"DefaultNodeName", "position":[ 4596, 4119 ], "typeName":"SolidExtrusion", "selected":false, "visible":true, "ignoreDefaults":[ true, false ], "_id":23624844, "replication":"applyLongest", "extra":{ "script":"A;" } }, { "name":"DefaultNodeName", "position":[ 4350, 4067 ], "typeName":"RegularPolygon", "selected":false, "visible":true, "ignoreDefaults":[ false, false, false ], "_id":398809118, "replication":"applyLongest", "extra":{ "script":"A;" } }, { "name":"DefaultNodeName", "position":[ 4062, 4085 ], "typeName":"Number", "selected":false, "visible":true, "ignoreDefaults":[ ], "_id":604762864, "replication":"applyLongest", "extra":{ "value":8, "min":0, "step":0.1, "max":20 } }, { "name":"DefaultNodeName", "position":[ 4351, 4181 ], "typeName":"Vector", "selected":false, "visible":true, "ignoreDefaults":[ false, false, false ], "_id":598617458, "replication":"applyLongest", "extra":{ "script":"A;" } }, { "name":"DefaultNodeName", "position":[ 4063, 4230 ], "typeName":"Number", "selected":false, "visible":true, "ignoreDefaults":[ ], "_id":3271050, "replication":"applyLongest", "extra":{ "max":40, "step":0.1, "min":-40, "value":15 } }, { "name":"DefaultNodeName", "position":[ 4346, 4449 ], "typeName":"Range", "selected":false, "visible":true, "ignoreDefaults":[ false, false, false ], "_id":440238000, "replication":"applyLongest", "extra":{ "script":"A;" } }, { "name":"DefaultNodeName", "position":[ 4859, 4452 ], "typeName":"Vector", "selected":false, "visible":true, "ignoreDefaults":[ false, false, false ], "_id":42665510, "replication":"applyLongest", "extra":{ "script":"A;" } }, { "name":"DefaultNodeName", "position":[ 5006, 4451 ], "typeName":"Point", "selected":false, "visible":true, "ignoreDefaults":[ false ], "_id":945248858, "replication":"applyLongest", "extra":{ "script":"A;" } }, { "name":"DefaultNodeName", "position":[ 4047, 4442 ], "typeName":"Number", "selected":false, "visible":true, "ignoreDefaults":[ ], "_id":282252545, "replication":"applyLongest", "extra":{ "max":100, "step":0.1, "min":0, "value":20 } }, { "name":"DefaultNodeName", "position":[ 4534, 4486 ], "typeName":"Number", "selected":false, "visible":true, "ignoreDefaults":[ ], "_id":823934643, "replication":"applyLongest", "extra":{ "max":150, "step":0.1, "min":-150, "value":30 } }, { "name":"DefaultNodeName", "position":[ 4050, 4532 ], "typeName":"Number", "selected":false, "visible":true, "ignoreDefaults":[ ], "_id":498266200, "replication":"applyLongest", "extra":{ "max":150, "step":0.1, "min":0, "value":150 } }, { "name":"DefaultNodeName", "position":[ 4501, 4561 ], "typeName":"Script", "selected":false, "visible":true, "ignoreDefaults":[ false ], "_id":317753978, "replication":"applyLongest", "extra":{ "script":"10 * Sin(A);" } } ], "name":"Example Project", }; exports.myFirstCustomNode = { "connections":[ { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":137881318, "endPortIndex":0, "endNodeId":263728761, "startPortIndex":0, "startNodeId":253762878, "kind":"addConnection" }, { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":525878839, "endPortIndex":1, "endNodeId":263728761, "startPortIndex":0, "startNodeId":253762878, "kind":"addConnection" }, { "hidden":false, "endProxyPosition":[ 0, 0 ], "startProxyPosition":[ 0, 0 ], "endProxy":false, "startProxy":false, "_id":144876797, "endPortIndex":0, "endNodeId":892555475, "startPortIndex":0, "startNodeId":263728761, "kind":"addConnection" } ], "nodes":[ { "name":"DefaultNodeName", "position":[ 4107, 4161 ], "typeName":"Input", "selected":false, "visible":true, "ignoreDefaults":[ ], "_id":253762878, "replication":"applyLongest", "extra":{ "name":"X" } }, { "name":"DefaultNodeName", "position":[ 4548, 4156 ], "typeName":"Output", "selected":false, "visible":true, "ignoreDefaults":[ true ], "_id":892555475, "replication":"applyLongest", "extra":{ "name":"X * X" } }, { "name":"DefaultNodeName", "position":[ 4359, 4152 ], "typeName":"Multiply", "selected":false, "visible":true, "ignoreDefaults":[ false, false ], "_id":263728761, "replication":"applyLongest", "extra":{ } } ], "name":"Example Custom Node", "isCustomNode": true }; ================================================ FILE: server/controllers/feedback.js ================================================ var _ = require('underscore') , nodemailer = require('nodemailer') , secrets = require('../config/secrets'); var smtpTransport = nodemailer.createTransport('SMTP', { service: 'Mailgun', auth: { user: secrets.mailgun.login, pass: secrets.mailgun.password } }); var emailTarget = secrets.feedback.email; exports.postFeedback = function(req, res) { if (!req.user) return res.status(401).send("You are not logged in"); if (!req.body) return res.status(500).send('Malformed body'); var ns = req.body; if (!emailTarget) return res.status(500).send({ msg: "Feedback temporarily unsupported" }); smtpTransport.sendMail({ from: req.user.email, to: emailTarget, subject: "[FLOOD FEEDBACK] : \"" + ns.subject ? ns.subject : "Empty subject" + "\"", text: ns.message ? ns.message : "Empty body", }, function(err) { if (err) { return res.status(500).send({ msg: "Could not send feedback! Try again later!" }); } return res.send({ msg: 'Feedback has been sent successfully!' }); }); }; ================================================ FILE: server/controllers/flood.js ================================================ var mongoose = require('mongoose') , Session = require('../models/Workspace').SessionModel , Workspace = require('../models/Workspace').WorkspaceModel , User = require('../models/User') , async = require('async') , _ = require('underscore') , ExampleWorkspaces = require('./exampleWorkspaces'); var initNonUserSession = function(req, res){ var user = req.user; var nws = new Workspace({name : "My first workspace"}); var newSesh = new Session({name : "Empty session", workspaces : [ nws ]}); nws.save(function(errWs){ if (errWs) return res.status(500).send("Failed to initialize user workspace"); newSesh.save(function(errSesh){ if (errSesh) return res.status(500).send("Failed to initialize user session"); Session .findById(newSesh.id) .populate('workspaces') .exec( function(err, sesh){ if (err || !sesh ) return res.status(500).send("Failed to obtain user session"); return res.send(sesh); }); }); }); } var initUserSession = function(req, res){ var user = req.user; var nws = new Workspace(ExampleWorkspaces.myFirstProject); var nws1 = new Workspace(ExampleWorkspaces.myFirstCustomNode); var newSesh = new Session({name : "Session", workspaces : [ nws, nws1 ]}); nws.maintainers = [ req.user ]; nws1.maintainers = [ req.user ]; nws.save(function(errWs){ if (errWs) return res.status(500).send("Failed to initialize user workspace"); nws1.save(function(errWs1){ if (errWs1) return res.status(500).send("Failed to initialize user workspace"); newSesh.save(function(errSesh){ if (errSesh) return res.status(500).send("Failed to initialize user session"); user.lastSession = newSesh; user.workspaces = [ nws, nws1 ]; user.markModified("lastSession workspaces"); user.save(function(err){ if (err) return res.status(500).send("Failed to save user session"); Session .findById(user.lastSession ) .populate('workspaces') .exec( function(err, sesh){ if (err || !sesh ) return res.status(500).send("Failed to obtain user session"); return res.send(sesh); }); }); }); }); }); }; exports.getCustomizerWorkspace = function(req, res){ Workspace.findById( req.params.id, function(err, ws){ if (err || !ws) res.status(404).send("Workspace not found!"); // its a public customizable workspace if (ws.isCustomizer === true) return res.send( ws ); // its the users workspace, so they can see it if (req.user && ws.maintainers.indexOf(req.user._id) != -1) return res.send( ws ); return res.status(401).send("This workspace is either not public or you are not authorized to access it.") }); } exports.getMySession = function(req, res) { var user = req.user; if (!req.user) { return initNonUserSession(req,res); } if (!user.lastSession){ return initUserSession(req, res); } Session .findById(user.lastSession ) .populate('workspaces') .exec( function(err, sesh){ if (err || !sesh || !sesh.workspaces || sesh.workspaces.length == 0 ) { return initUserSession(req, res); } sesh.isFirstExperience = false; return res.send(sesh); }); }; exports.putMySession = function(req, res) { if (!req.user) { return res.status(401).send("You are not logged in"); } if (!req.body || !req.body._id) return res.status(500).send('Malformed body'); var ns = req.body; var sid = ns._id; // build all of the workspace saves var workspaceSaves = ns.workspaces.map(function(x){ return function(callback){ Workspace.findById(x._id, function(e, w){ if (e || !w) return callback("Failed to find workspace"); // todo optimize this w.name = x.name || w.name; w.nodes = x.nodes || w.nodes; w.connections = x.connections || w.connections; w.currentWorkspace = x.currentWorkspace || w.currentWorkspace; w.selectedNodes = x.selectedNodes || w.selectedNodes; w.zoom = x.zoom || w.zoom; w.offset = x.offset || w.offset; w.lastSaved = Date.now(); w.redoStack = x.redoStack || w.redoStack; w.undoStack = x.undoStack || w.undoStack; w.workspaceDependencyIds = x.workspaceDependencyIds || w.workspaceDependencyIds; w.isCustomNode = ( x.isCustomNode != undefined ) ? x.isCustomNode : w.isCustomNode; w.isEdited = true; w.isCustomizer = ( x.isCustomizer != undefined ) ? x.isCustomizer : w.isCustomizer; w.markModified("workspaceDependencyIds offset isCustomNode isCustomizer isEditor name nodes connections currentWorkspace selectedNodes zoom lastSaved undoStack redoStack"); w.save(function(se){ if (se) return callback(se); return callback(); }); }); }; }); async.waterfall(workspaceSaves, function(err) { if (err) return res.status(500).send(err); // save session Session.findById( sid, function(e, s) { if (e || !s) return res.status(500).send('Invalid session id'); s.name = ns.name || s.name; // TODO: validate ids s.workspaces = ns.workspaces.map(function(x){ return x._id; }); s.currentWorkspace = ns.currentWorkspace; s.isFirstExperience = false; s.lastSaved = Date.now(); s.markModified('isFirstExperience workspaces currentWorkspace lastSave name'); s.save(function(e){ if (e) return res.status(500).send("Failed to save session"); return res.send("Success"); }); }); }); }; exports.getNewWorkspace = function(req, res){ var user = req.user; if (!user) return res.status(403).send("Must be logged in to create a new workspace") var nws = new Workspace({name : "New workspace", maintainers : [ user._id ] }); nws.save(function(e){ if (e) return res.status(500).send("Failed to create new workspace"); user.workspaces.push(nws); user.markModified('workspaces'); user.save(function(eu){ if (eu) return res.status(500).send("Failed to update user profile"); return res.send(nws); }) }); }; var dateSort = function(a, b){ if (a.lastSaved && !b.lastSaved){ return -1; } if (b.lastSaved && !a.lastSaved){ return 1; } if ( !b.lastSaved && !a.lastSaved ) return 0; if ( a.lastSaved > b.lastSaved ) return -1; if ( a.lastSaved < b.lastSaved ) return 1; return 0; } exports.getWorkspaces = function(req, res) { var user = req.user; if (!user) return res.status(403).send("Must be logged in to list your workspaces") User.findById( user._id ) .populate('workspaces', 'name lastSaved isPublic maintainers isEdited isCustomNode') .exec(function(e, u) { return res.send(u.workspaces ); }); }; exports.getWorkspace = function(req, res) { var wid = req.params.id; Workspace.findById( wid , function(e, ws) { if (e) { return res.send('Workspace not found'); } return res.send(ws); }); }; exports.putWorkspace = function(req, res) { var wid = req.params.id; var x = req.body; if (!req.user) { return res.status(401).send("You need to be logged in to save a workspace"); } Workspace.findById( wid , function(e, w) { if (e) { return res.status(404).send('Workspace not found'); } w.name = x.name || w.name; w.nodes = x.nodes || w.nodes; w.connections = x.connections || w.connections; w.currentWorkspace = x.currentWorkspace || w.currentWorkspace; w.selectedNodes = x.selectedNodes || w.selectedNodes; w.zoom = x.zoom || w.zoom; w.lastSaved = Date.now(); w.redoStack = x.redoStack || w.redoStack; w.undoStack = x.undoStack || w.undoStack; w.isCustomNode = ( x.isCustomNode != undefined ) ? x.isCustomNode : w.isCustomNode; w.isCustomizer = ( x.isCustomizer != undefined ) ? x.isCustomizer : w.isCustomizer; w.workspaceDependencyIds = x.workspaceDependencyIds || w.workspaceDependencyIds; w.offset = x.offset || w.offset; w.isEdited = true; w.markModified("workspaceDependencyIds name isCustomNode isCustomizer nodes connections currentWorkspace selectedNodes zoom offset lastSaved undoStack redoStack isEdited"); w.save(function(se){ if (se) return res.status(500).send('Could not save the workspace'); return res.status(200).send('Saved workspace'); }); }); }; ================================================ FILE: server/controllers/home.js ================================================ var flood = require('../models/Workspace'); /** * GET / * Home page. */ exports.index = function(req, res) { res.render('home', { title: 'Home' }); }; ================================================ FILE: server/controllers/user.js ================================================ var _ = require('underscore'); var async = require('async'); var crypto = require('crypto'); var nodemailer = require('nodemailer'); var passport = require('passport'); var User = require('../models/User'); var secrets = require('../config/secrets'); /** * GET /email * Get user email */ exports.getEmail = function(req, res ){ if (!req.user) { return res.send("Not logged in"); } return res.send({ email: req.user.email }); } /** * GET /login * Login page. */ exports.getLogin = function(req, res) { if (req.user) return res.redirect('/'); res.render('account/login', { title: 'Login' }); }; /** * POST /login * Sign in using email and password. * @param email * @param password */ exports.postLogin = function(req, res, next) { req.assert('email', 'Email is not valid').isEmail(); req.assert('password', 'Password cannot be blank').notEmpty(); var errors = req.validationErrors(); if (errors) { // req.flash('errors', errors); // return res.redirect('/login'); return res.send(errors); } passport.authenticate('local', function(err, user, info) { if (err) return next(err); if (!user) { return res.send([{ msg: info.message }]); } req.logIn(user, function(err) { if (err) return next(err); return res.send({ msg: 'Success! You are logged in.' }); }); })(req, res, next); }; /** * GET /logout * Log out. */ exports.logout = function(req, res) { req.logout(); res.send({ msg: 'Success! You are logged out.' }); }; /** * GET /signup * Signup page. */ exports.getSignup = function(req, res) { if (req.user) return res.redirect('/'); res.render('account/signup', { title: 'Create Account' }); }; /** * POST /signup * Create a new local account. * @param email * @param password */ exports.postSignup = function(req, res, next) { req.assert('email', 'Email is not valid').isEmail(); req.assert('password', 'Password must be at least 4 characters long').len(4); req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); var errors = req.validationErrors(); if (errors) { return res.send(errors); } var user = new User({ email: req.body.email, password: req.body.password }); user.save(function(err) { if (err) { if (err.code === 11000) { return res.send([{ msg: 'User with that email already exists.' }]); } res.status(500).send({msg: 'Unknown error on signup'}); } req.logIn(user, function(err) { if (err) return next(err); return res.send({ msg: 'Successful signup' }); }); }); }; /** * GET /account * Profile page. */ exports.getAccount = function(req, res) { res.render('account/profile', { title: 'Account Management' }); }; /** * POST /account/profile * Update profile information. */ exports.postUpdateProfile = function(req, res, next) { User.findById(req.user.id, function(err, user) { if (err) return next(err); user.email = req.body.email || ''; user.profile.name = req.body.name || ''; user.profile.gender = req.body.gender || ''; user.profile.location = req.body.location || ''; user.profile.website = req.body.website || ''; user.save(function(err) { if (err) return next(err); req.flash('success', { msg: 'Profile information updated.' }); res.redirect('/account'); }); }); }; /** * POST /account/password * Update current password. * @param password */ exports.postUpdatePassword = function(req, res, next) { req.assert('password', 'Password must be at least 4 characters long').len(4); req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); var errors = req.validationErrors(); if (errors) { req.flash('errors', errors); return res.redirect('/account'); } User.findById(req.user.id, function(err, user) { if (err) return next(err); user.password = req.body.password; user.save(function(err) { if (err) return next(err); req.flash('success', { msg: 'Password has been changed.' }); res.redirect('/account'); }); }); }; /** * POST /account/delete * Delete user account. * @param id - User ObjectId */ exports.postDeleteAccount = function(req, res, next) { User.remove({ _id: req.user.id }, function(err) { if (err) return next(err); req.logout(); res.redirect('/'); }); }; /** * GET /account/unlink/:provider * Unlink OAuth2 provider from the current user. * @param provider * @param id - User ObjectId */ exports.getOauthUnlink = function(req, res, next) { var provider = req.params.provider; User.findById(req.user.id, function(err, user) { if (err) return next(err); user[provider] = undefined; user.tokens = _.reject(user.tokens, function(token) { return token.kind === provider; }); user.save(function(err) { if (err) return next(err); req.flash('info', { msg: provider + ' account has been unlinked.' }); res.redirect('/account'); }); }); }; /** * GET /reset/:token * Reset Password page. */ exports.getReset = function(req, res) { if (req.isAuthenticated()) { return res.redirect('/'); } User .findOne({ resetPasswordToken: req.params.token }) .where('resetPasswordExpires').gt(Date.now()) .exec(function(err, user) { if (!user) { req.flash('errors', { msg: 'Password reset token is invalid or has expired.' }); return res.redirect('/forgot'); } res.render('account/reset', { title: 'Password Reset' }); }); }; /** * POST /reset/:token * Process the reset password request. */ exports.postReset = function(req, res, next) { req.assert('password', 'Password must be at least 4 characters long.').len(4); req.assert('confirm', 'Passwords must match.').equals(req.body.password); var errors = req.validationErrors(); if (errors) { req.flash('errors', errors); return res.redirect('back'); } async.waterfall([ function(done) { User .findOne({ resetPasswordToken: req.params.token }) .where('resetPasswordExpires').gt(Date.now()) .exec(function(err, user) { if (!user) { req.flash('errors', { msg: 'Password reset token is invalid or has expired.' }); return res.redirect('back'); } user.password = req.body.password; user.resetPasswordToken = undefined; user.resetPasswordExpires = undefined; user.save(function(err) { if (err) return next(err); req.logIn(user, function(err) { done(err, user); }); }); }); }, function(user, done) { var smtpTransport = nodemailer.createTransport('SMTP', { service: 'SendGrid', auth: { user: secrets.sendgrid.user, pass: secrets.sendgrid.password } }); var mailOptions = { to: user.email, from: 'hackathon@starter.com', subject: 'Your Hackathon Starter password has been changed', text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n' }; smtpTransport.sendMail(mailOptions, function(err) { req.flash('success', { msg: 'Success! Your password has been changed.' }); done(err); }); } ], function(err) { if (err) return next(err); res.redirect('/'); }); }; /** * GET /forgot * Forgot Password page. */ exports.getForgot = function(req, res) { if (req.isAuthenticated()) { return res.redirect('/'); } res.render('account/forgot', { title: 'Forgot Password' }); }; /** * POST /forgot * Create a random token, then the send user an email with a reset link. * @param email */ exports.postForgot = function(req, res, next) { req.assert('email', 'Please enter a valid email address.').isEmail(); var errors = req.validationErrors(); if (errors) { req.flash('errors', errors); return res.redirect('/forgot'); } async.waterfall([ function(done) { crypto.randomBytes(16, function(err, buf) { var token = buf.toString('hex'); done(err, token); }); }, function(token, done) { User.findOne({ email: req.body.email.toLowerCase() }, function(err, user) { if (!user) { req.flash('errors', { msg: 'No account with that email address exists.' }); return res.redirect('/forgot'); } user.resetPasswordToken = token; user.resetPasswordExpires = Date.now() + 3600000; // 1 hour user.save(function(err) { done(err, token, user); }); }); }, function(token, user, done) { var smtpTransport = nodemailer.createTransport('SMTP', { service: 'SendGrid', auth: { user: secrets.sendgrid.user, pass: secrets.sendgrid.password } }); var mailOptions = { to: user.email, from: 'hackathon@starter.com', subject: 'Reset your password on Hackathon Starter', text: 'You are receiving this email because you (or someone else) have requested the reset of the password for your account.\n\n' + 'Please click on the following link, or paste this into your browser to complete the process:\n\n' + 'http://' + req.headers.host + '/reset/' + token + '\n\n' + 'If you did not request this, please ignore this email and your password will remain unchanged.\n' }; smtpTransport.sendMail(mailOptions, function(err) { req.flash('info', { msg: 'An e-mail has been sent to ' + user.email + ' with further instructions.' }); done(err, 'done'); }); } ], function(err) { if (err) return next(err); res.redirect('/forgot'); }); }; ================================================ FILE: server/controllers/workspaces.js ================================================ ================================================ FILE: server/models/Session.js ================================================ ================================================ FILE: server/models/User.js ================================================ var mongoose = require('mongoose'); var bcrypt = require('bcrypt-nodejs'); var crypto = require('crypto'); var models = require('./Workspace') var userSchema = new mongoose.Schema({ email: { type: String, unique: true, lowercase: true }, password: String, facebook: String, twitter: String, google: String, github: String, linkedin: String, tokens: Array, profile: { name: { type: String, default: '' }, gender: { type: String, default: '' }, location: { type: String, default: '' }, website: { type: String, default: '' }, picture: { type: String, default: '' } }, resetPasswordToken: String, resetPasswordExpires: Date, lastSession: {type: mongoose.Schema.ObjectId, ref: 'Session' }, workspaces: [{type: mongoose.Schema.ObjectId, ref: 'Workspace' }] }); /** * Hash the password for security. * "Pre" is a Mongoose middleware that executes before each user.save() call. */ userSchema.pre('save', function(next) { var user = this; if (!user.isModified('password')) return next(); bcrypt.genSalt(5, function(err, salt) { if (err) return next(err); bcrypt.hash(user.password, salt, null, function(err, hash) { if (err) return next(err); user.password = hash; next(); }); }); }); /** * Validate user's password. * Used by Passport-Local Strategy for password validation. */ userSchema.methods.comparePassword = function(candidatePassword, cb) { bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { if (err) return cb(err); cb(null, isMatch); }); }; /** * Get URL to a user's gravatar. * Used in Navbar and Account Management page. */ userSchema.methods.gravatar = function(size, defaults) { if (!size) size = 200; if (!defaults) defaults = 'retro'; if (!this.email) { return 'https://gravatar.com/avatar/?s=' + size + '&d=' + defaults; } var md5 = crypto.createHash('md5').update(this.email); return 'https://gravatar.com/avatar/' + md5.digest('hex').toString() + '?s=' + size + '&d=' + defaults; }; module.exports = mongoose.model('User', userSchema); ================================================ FILE: server/models/Workspace.js ================================================ var mongoose = require('mongoose') , Schema = mongoose.Schema; var sessionSchema = new Schema({ name: { type: String, default: '' } , currentWorkspace: {type: Schema.ObjectId, ref: 'Workspace' } , workspaces: [ {type: Schema.ObjectId, ref: 'Workspace' } ] , lastSaved: Date , isFirstExperience: { type: Boolean, default: true } }); var workspaceSchema = new Schema({ name: { type: String, default: '' } , nodes: [ Schema.Types.Mixed ] , connections: [ Schema.Types.Mixed ] , selectedNodes: [ Schema.Types.Mixed ] , isPublic: { type: Boolean, default: false } , zoom: { type: Number, default: 1 } , offset: [{ type: Number }] , lastSaved: Date , maintainers: [{type: Schema.ObjectId, ref: 'User' }] , undoStack: [ Schema.Types.Mixed ] , redoStack: [ Schema.Types.Mixed ] , isEdited: { type: Boolean, default: false } , isCustomNode: { type: Boolean, default: false } , isCustomizer: { type: Boolean, default: false } , workspaceDependencyIds: [{type: Schema.ObjectId, ref: 'Workspace' }] }); exports.SessionModel = mongoose.model('Session', sessionSchema); exports.WorkspaceModel = mongoose.model('Workspace', workspaceSchema); ================================================ FILE: server/package.json ================================================ { "name": "flood-server", "version": "0.0.0", "repository": { "type": "git", "url": "" }, "scripts": { "start": "node app.js", "test": "mocha" }, "dependencies": { "async": "^0.7.0", "bcrypt-nodejs": "^0.0.3", "body-parser": "^1.0.1", "cheerio": "^0.15.0", "clockwork": "^0.1.1", "compression": "^1.0.1", "connect-assets": "^3.0.0-beta2", "connect-mongo": "^0.4.0", "cookie-parser": "^1.0.1", "csso": "^1.3.11", "csurf": "^1.1.0", "errorhandler": "^1.0.0", "express": "^4.0.0", "express-flash": "^0.0.2", "express-session": "^1.0.2", "express-validator": "^2.1.1", "fbgraph": "^0.2.10", "github-api": "^0.7.0", "jade": "^1.3.0", "lastfm": "^0.9.0", "less": "^1.7.0", "method-override": "^1.0.0", "mongoose": "4.0.5", "morgan": "^1.0.0", "node-foursquare": "^0.2.0", "node-linkedin": "^0.1.5", "nodemailer": "^0.6.1", "passport": "^0.2.0", "passport-facebook": "^1.0.3", "passport-github": "^0.1.5", "passport-google-oauth": "^0.1.5", "passport-linkedin-oauth2": "^1.1.1", "passport-local": "^1.0.0", "passport-oauth": "^1.0.0", "passport-twitter": "^1.0.2", "paypal-rest-sdk": "^0.7.0", "request": "^2.34.0", "static-favicon": "^1.0.2", "tumblr.js": "^0.0.4", "twilio": "^1.6.0", "twit": "^1.1.12", "uglify-js": "^2.4.12", "underscore": "^1.6.0", "validator": "^3.8.0" }, "devDependencies": { "mocha": "^1.18.2", "chai": "^1.9.1", "supertest": "^0.10.0" } } ================================================ FILE: server/public/css/lib/animate.css ================================================ @charset "UTF-8"; /*! Animate.css - http://daneden.me/animate Licensed under the MIT license Copyright (c) 2013 Daniel Eden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ .animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .animated.hinge { -webkit-animation-duration: 2s; animation-duration: 2s; } @-webkit-keyframes bounce { 0%, 20%, 50%, 80%, 100% { -webkit-transform: translateY(0); transform: translateY(0); } 40% { -webkit-transform: translateY(-30px); transform: translateY(-30px); } 60% { -webkit-transform: translateY(-15px); transform: translateY(-15px); } } @keyframes bounce { 0%, 20%, 50%, 80%, 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 40% { -webkit-transform: translateY(-30px); -ms-transform: translateY(-30px); transform: translateY(-30px); } 60% { -webkit-transform: translateY(-15px); -ms-transform: translateY(-15px); transform: translateY(-15px); } } .bounce { -webkit-animation-name: bounce; animation-name: bounce; } @-webkit-keyframes flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } } @keyframes flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } } .flash { -webkit-animation-name: flash; animation-name: flash; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes pulse { 0% { -webkit-transform: scale(1); transform: scale(1); } 50% { -webkit-transform: scale(1.1); transform: scale(1.1); } 100% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes pulse { 0% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } 50% { -webkit-transform: scale(1.1); -ms-transform: scale(1.1); transform: scale(1.1); } 100% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } } .pulse { -webkit-animation-name: pulse; animation-name: pulse; } @-webkit-keyframes shake { 0%, 100% { -webkit-transform: translateX(0); transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translateX(-10px); transform: translateX(-10px); } 20%, 40%, 60%, 80% { -webkit-transform: translateX(10px); transform: translateX(10px); } } @keyframes shake { 0%, 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translateX(-10px); -ms-transform: translateX(-10px); transform: translateX(-10px); } 20%, 40%, 60%, 80% { -webkit-transform: translateX(10px); -ms-transform: translateX(10px); transform: translateX(10px); } } .shake { -webkit-animation-name: shake; animation-name: shake; } @-webkit-keyframes swing { 20% { -webkit-transform: rotate(15deg); transform: rotate(15deg); } 40% { -webkit-transform: rotate(-10deg); transform: rotate(-10deg); } 60% { -webkit-transform: rotate(5deg); transform: rotate(5deg); } 80% { -webkit-transform: rotate(-5deg); transform: rotate(-5deg); } 100% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } } @keyframes swing { 20% { -webkit-transform: rotate(15deg); -ms-transform: rotate(15deg); transform: rotate(15deg); } 40% { -webkit-transform: rotate(-10deg); -ms-transform: rotate(-10deg); transform: rotate(-10deg); } 60% { -webkit-transform: rotate(5deg); -ms-transform: rotate(5deg); transform: rotate(5deg); } 80% { -webkit-transform: rotate(-5deg); -ms-transform: rotate(-5deg); transform: rotate(-5deg); } 100% { -webkit-transform: rotate(0deg); -ms-transform: rotate(0deg); transform: rotate(0deg); } } .swing { -webkit-transform-origin: top center; -ms-transform-origin: top center; transform-origin: top center; -webkit-animation-name: swing; animation-name: swing; } @-webkit-keyframes tada { 0% { -webkit-transform: scale(1); transform: scale(1); } 10%, 20% { -webkit-transform: scale(0.9) rotate(-3deg); transform: scale(0.9) rotate(-3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale(1.1) rotate(3deg); transform: scale(1.1) rotate(3deg); } 40%, 60%, 80% { -webkit-transform: scale(1.1) rotate(-3deg); transform: scale(1.1) rotate(-3deg); } 100% { -webkit-transform: scale(1) rotate(0); transform: scale(1) rotate(0); } } @keyframes tada { 0% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } 10%, 20% { -webkit-transform: scale(0.9) rotate(-3deg); -ms-transform: scale(0.9) rotate(-3deg); transform: scale(0.9) rotate(-3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale(1.1) rotate(3deg); -ms-transform: scale(1.1) rotate(3deg); transform: scale(1.1) rotate(3deg); } 40%, 60%, 80% { -webkit-transform: scale(1.1) rotate(-3deg); -ms-transform: scale(1.1) rotate(-3deg); transform: scale(1.1) rotate(-3deg); } 100% { -webkit-transform: scale(1) rotate(0); -ms-transform: scale(1) rotate(0); transform: scale(1) rotate(0); } } .tada { -webkit-animation-name: tada; animation-name: tada; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes wobble { 0% { -webkit-transform: translateX(0%); transform: translateX(0%); } 15% { -webkit-transform: translateX(-25%) rotate(-5deg); transform: translateX(-25%) rotate(-5deg); } 30% { -webkit-transform: translateX(20%) rotate(3deg); transform: translateX(20%) rotate(3deg); } 45% { -webkit-transform: translateX(-15%) rotate(-3deg); transform: translateX(-15%) rotate(-3deg); } 60% { -webkit-transform: translateX(10%) rotate(2deg); transform: translateX(10%) rotate(2deg); } 75% { -webkit-transform: translateX(-5%) rotate(-1deg); transform: translateX(-5%) rotate(-1deg); } 100% { -webkit-transform: translateX(0%); transform: translateX(0%); } } @keyframes wobble { 0% { -webkit-transform: translateX(0%); -ms-transform: translateX(0%); transform: translateX(0%); } 15% { -webkit-transform: translateX(-25%) rotate(-5deg); -ms-transform: translateX(-25%) rotate(-5deg); transform: translateX(-25%) rotate(-5deg); } 30% { -webkit-transform: translateX(20%) rotate(3deg); -ms-transform: translateX(20%) rotate(3deg); transform: translateX(20%) rotate(3deg); } 45% { -webkit-transform: translateX(-15%) rotate(-3deg); -ms-transform: translateX(-15%) rotate(-3deg); transform: translateX(-15%) rotate(-3deg); } 60% { -webkit-transform: translateX(10%) rotate(2deg); -ms-transform: translateX(10%) rotate(2deg); transform: translateX(10%) rotate(2deg); } 75% { -webkit-transform: translateX(-5%) rotate(-1deg); -ms-transform: translateX(-5%) rotate(-1deg); transform: translateX(-5%) rotate(-1deg); } 100% { -webkit-transform: translateX(0%); -ms-transform: translateX(0%); transform: translateX(0%); } } .wobble { -webkit-animation-name: wobble; animation-name: wobble; } @-webkit-keyframes bounceIn { 0% { opacity: 0; -webkit-transform: scale(.3); transform: scale(.3); } 50% { opacity: 1; -webkit-transform: scale(1.05); transform: scale(1.05); } 70% { -webkit-transform: scale(.9); transform: scale(.9); } 100% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes bounceIn { 0% { opacity: 0; -webkit-transform: scale(.3); -ms-transform: scale(.3); transform: scale(.3); } 50% { opacity: 1; -webkit-transform: scale(1.05); -ms-transform: scale(1.05); transform: scale(1.05); } 70% { -webkit-transform: scale(.9); -ms-transform: scale(.9); transform: scale(.9); } 100% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } } .bounceIn { -webkit-animation-name: bounceIn; animation-name: bounceIn; } @-webkit-keyframes bounceInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } 60% { opacity: 1; -webkit-transform: translateY(30px); transform: translateY(30px); } 80% { -webkit-transform: translateY(-10px); transform: translateY(-10px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes bounceInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } 60% { opacity: 1; -webkit-transform: translateY(30px); -ms-transform: translateY(30px); transform: translateY(30px); } 80% { -webkit-transform: translateY(-10px); -ms-transform: translateY(-10px); transform: translateY(-10px); } 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .bounceInDown { -webkit-animation-name: bounceInDown; animation-name: bounceInDown; } @-webkit-keyframes bounceInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } 60% { opacity: 1; -webkit-transform: translateX(30px); transform: translateX(30px); } 80% { -webkit-transform: translateX(-10px); transform: translateX(-10px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes bounceInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } 60% { opacity: 1; -webkit-transform: translateX(30px); -ms-transform: translateX(30px); transform: translateX(30px); } 80% { -webkit-transform: translateX(-10px); -ms-transform: translateX(-10px); transform: translateX(-10px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .bounceInLeft { -webkit-animation-name: bounceInLeft; animation-name: bounceInLeft; } @-webkit-keyframes bounceInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } 60% { opacity: 1; -webkit-transform: translateX(-30px); transform: translateX(-30px); } 80% { -webkit-transform: translateX(10px); transform: translateX(10px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes bounceInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } 60% { opacity: 1; -webkit-transform: translateX(-30px); -ms-transform: translateX(-30px); transform: translateX(-30px); } 80% { -webkit-transform: translateX(10px); -ms-transform: translateX(10px); transform: translateX(10px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .bounceInRight { -webkit-animation-name: bounceInRight; animation-name: bounceInRight; } @-webkit-keyframes bounceInUp { 0% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } 60% { opacity: 1; -webkit-transform: translateY(-30px); transform: translateY(-30px); } 80% { -webkit-transform: translateY(10px); transform: translateY(10px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes bounceInUp { 0% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } 60% { opacity: 1; -webkit-transform: translateY(-30px); -ms-transform: translateY(-30px); transform: translateY(-30px); } 80% { -webkit-transform: translateY(10px); -ms-transform: translateY(10px); transform: translateY(10px); } 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .bounceInUp { -webkit-animation-name: bounceInUp; animation-name: bounceInUp; } @-webkit-keyframes bounceOut { 0% { -webkit-transform: scale(1); transform: scale(1); } 25% { -webkit-transform: scale(.95); transform: scale(.95); } 50% { opacity: 1; -webkit-transform: scale(1.1); transform: scale(1.1); } 100% { opacity: 0; -webkit-transform: scale(.3); transform: scale(.3); } } @keyframes bounceOut { 0% { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); } 25% { -webkit-transform: scale(.95); -ms-transform: scale(.95); transform: scale(.95); } 50% { opacity: 1; -webkit-transform: scale(1.1); -ms-transform: scale(1.1); transform: scale(1.1); } 100% { opacity: 0; -webkit-transform: scale(.3); -ms-transform: scale(.3); transform: scale(.3); } } .bounceOut { -webkit-animation-name: bounceOut; animation-name: bounceOut; } @-webkit-keyframes bounceOutDown { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } } @keyframes bounceOutDown { 0% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } } .bounceOutDown { -webkit-animation-name: bounceOutDown; animation-name: bounceOutDown; } @-webkit-keyframes bounceOutLeft { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } } @keyframes bounceOutLeft { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(20px); -ms-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } } .bounceOutLeft { -webkit-animation-name: bounceOutLeft; animation-name: bounceOutLeft; } @-webkit-keyframes bounceOutRight { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } } @keyframes bounceOutRight { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 20% { opacity: 1; -webkit-transform: translateX(-20px); -ms-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } } .bounceOutRight { -webkit-animation-name: bounceOutRight; animation-name: bounceOutRight; } @-webkit-keyframes bounceOutUp { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } } @keyframes bounceOutUp { 0% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 20% { opacity: 1; -webkit-transform: translateY(20px); -ms-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } } .bounceOutUp { -webkit-animation-name: bounceOutUp; animation-name: bounceOutUp; } @-webkit-keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } .fadeIn { -webkit-animation-name: fadeIn; animation-name: fadeIn; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInDownBig { -webkit-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } @-webkit-keyframes fadeInLeft { 0% { opacity: 0; -webkit-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInLeft { 0% { opacity: 0; -webkit-transform: translateX(-20px); -ms-transform: translateX(-20px); transform: translateX(-20px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInLeft { -webkit-animation-name: fadeInLeft; animation-name: fadeInLeft; } @-webkit-keyframes fadeInLeftBig { 0% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInLeftBig { 0% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInLeftBig { -webkit-animation-name: fadeInLeftBig; animation-name: fadeInLeftBig; } @-webkit-keyframes fadeInRight { 0% { opacity: 0; -webkit-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInRight { 0% { opacity: 0; -webkit-transform: translateX(20px); -ms-transform: translateX(20px); transform: translateX(20px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInRight { -webkit-animation-name: fadeInRight; animation-name: fadeInRight; } @-webkit-keyframes fadeInRightBig { 0% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes fadeInRightBig { 0% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } 100% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .fadeInRightBig { -webkit-animation-name: fadeInRightBig; animation-name: fadeInRightBig; } @-webkit-keyframes fadeInUp { 0% { opacity: 0; -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInUp { 0% { opacity: 0; -webkit-transform: translateY(20px); -ms-transform: translateY(20px); transform: translateY(20px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInUp { -webkit-animation-name: fadeInUp; animation-name: fadeInUp; } @-webkit-keyframes fadeInUpBig { 0% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes fadeInUpBig { 0% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } 100% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .fadeInUpBig { -webkit-animation-name: fadeInUpBig; animation-name: fadeInUpBig; } @-webkit-keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .fadeOut { -webkit-animation-name: fadeOut; animation-name: fadeOut; } @-webkit-keyframes fadeOutDown { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(20px); transform: translateY(20px); } } @keyframes fadeOutDown { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(20px); -ms-transform: translateY(20px); transform: translateY(20px); } } .fadeOutDown { -webkit-animation-name: fadeOutDown; animation-name: fadeOutDown; } @-webkit-keyframes fadeOutDownBig { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(2000px); transform: translateY(2000px); } } @keyframes fadeOutDownBig { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(2000px); -ms-transform: translateY(2000px); transform: translateY(2000px); } } .fadeOutDownBig { -webkit-animation-name: fadeOutDownBig; animation-name: fadeOutDownBig; } @-webkit-keyframes fadeOutLeft { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-20px); transform: translateX(-20px); } } @keyframes fadeOutLeft { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-20px); -ms-transform: translateX(-20px); transform: translateX(-20px); } } .fadeOutLeft { -webkit-animation-name: fadeOutLeft; animation-name: fadeOutLeft; } @-webkit-keyframes fadeOutLeftBig { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } } @keyframes fadeOutLeftBig { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } } .fadeOutLeftBig { -webkit-animation-name: fadeOutLeftBig; animation-name: fadeOutLeftBig; } @-webkit-keyframes fadeOutRight { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(20px); transform: translateX(20px); } } @keyframes fadeOutRight { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(20px); -ms-transform: translateX(20px); transform: translateX(20px); } } .fadeOutRight { -webkit-animation-name: fadeOutRight; animation-name: fadeOutRight; } @-webkit-keyframes fadeOutRightBig { 0% { opacity: 1; -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } } @keyframes fadeOutRightBig { 0% { opacity: 1; -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } } .fadeOutRightBig { -webkit-animation-name: fadeOutRightBig; animation-name: fadeOutRightBig; } @-webkit-keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); transform: translateY(-20px); } } @keyframes fadeOutUp { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px); } } .fadeOutUp { -webkit-animation-name: fadeOutUp; animation-name: fadeOutUp; } @-webkit-keyframes fadeOutUpBig { 0% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } } @keyframes fadeOutUpBig { 0% { opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } } .fadeOutUpBig { -webkit-animation-name: fadeOutUpBig; animation-name: fadeOutUpBig; } @-webkit-keyframes flip { 0% { -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); transform: perspective(400px) translateZ(0) rotateY(0) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 100% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } @keyframes flip { 0% { -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); -ms-transform: perspective(400px) translateZ(0) rotateY(0) scale(1); transform: perspective(400px) translateZ(0) rotateY(0) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); -ms-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); -ms-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 100% { -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); -ms-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } .animated.flip { -webkit-backface-visibility: visible; -ms-backface-visibility: visible; backface-visibility: visible; -webkit-animation-name: flip; animation-name: flip; } @-webkit-keyframes flipInX { 0% { -webkit-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateX(-10deg); transform: perspective(400px) rotateX(-10deg); } 70% { -webkit-transform: perspective(400px) rotateX(10deg); transform: perspective(400px) rotateX(10deg); } 100% { -webkit-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } } @keyframes flipInX { 0% { -webkit-transform: perspective(400px) rotateX(90deg); -ms-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateX(-10deg); -ms-transform: perspective(400px) rotateX(-10deg); transform: perspective(400px) rotateX(-10deg); } 70% { -webkit-transform: perspective(400px) rotateX(10deg); -ms-transform: perspective(400px) rotateX(10deg); transform: perspective(400px) rotateX(10deg); } 100% { -webkit-transform: perspective(400px) rotateX(0deg); -ms-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } } .flipInX { -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInX; animation-name: flipInX; } @-webkit-keyframes flipInY { 0% { -webkit-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateY(-10deg); transform: perspective(400px) rotateY(-10deg); } 70% { -webkit-transform: perspective(400px) rotateY(10deg); transform: perspective(400px) rotateY(10deg); } 100% { -webkit-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } } @keyframes flipInY { 0% { -webkit-transform: perspective(400px) rotateY(90deg); -ms-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } 40% { -webkit-transform: perspective(400px) rotateY(-10deg); -ms-transform: perspective(400px) rotateY(-10deg); transform: perspective(400px) rotateY(-10deg); } 70% { -webkit-transform: perspective(400px) rotateY(10deg); -ms-transform: perspective(400px) rotateY(10deg); transform: perspective(400px) rotateY(10deg); } 100% { -webkit-transform: perspective(400px) rotateY(0deg); -ms-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } } .flipInY { -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInY; animation-name: flipInY; } @-webkit-keyframes flipOutX { 0% { -webkit-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } } @keyframes flipOutX { 0% { -webkit-transform: perspective(400px) rotateX(0deg); -ms-transform: perspective(400px) rotateX(0deg); transform: perspective(400px) rotateX(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateX(90deg); -ms-transform: perspective(400px) rotateX(90deg); transform: perspective(400px) rotateX(90deg); opacity: 0; } } .flipOutX { -webkit-animation-name: flipOutX; animation-name: flipOutX; -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; } @-webkit-keyframes flipOutY { 0% { -webkit-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } } @keyframes flipOutY { 0% { -webkit-transform: perspective(400px) rotateY(0deg); -ms-transform: perspective(400px) rotateY(0deg); transform: perspective(400px) rotateY(0deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotateY(90deg); -ms-transform: perspective(400px) rotateY(90deg); transform: perspective(400px) rotateY(90deg); opacity: 0; } } .flipOutY { -webkit-backface-visibility: visible !important; -ms-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipOutY; animation-name: flipOutY; } @-webkit-keyframes lightSpeedIn { 0% { -webkit-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: translateX(-20%) skewX(30deg); transform: translateX(-20%) skewX(30deg); opacity: 1; } 80% { -webkit-transform: translateX(0%) skewX(-15deg); transform: translateX(0%) skewX(-15deg); opacity: 1; } 100% { -webkit-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } } @keyframes lightSpeedIn { 0% { -webkit-transform: translateX(100%) skewX(-30deg); -ms-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: translateX(-20%) skewX(30deg); -ms-transform: translateX(-20%) skewX(30deg); transform: translateX(-20%) skewX(30deg); opacity: 1; } 80% { -webkit-transform: translateX(0%) skewX(-15deg); -ms-transform: translateX(0%) skewX(-15deg); transform: translateX(0%) skewX(-15deg); opacity: 1; } 100% { -webkit-transform: translateX(0%) skewX(0deg); -ms-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } } .lightSpeedIn { -webkit-animation-name: lightSpeedIn; animation-name: lightSpeedIn; -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } @-webkit-keyframes lightSpeedOut { 0% { -webkit-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } 100% { -webkit-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } } @keyframes lightSpeedOut { 0% { -webkit-transform: translateX(0%) skewX(0deg); -ms-transform: translateX(0%) skewX(0deg); transform: translateX(0%) skewX(0deg); opacity: 1; } 100% { -webkit-transform: translateX(100%) skewX(-30deg); -ms-transform: translateX(100%) skewX(-30deg); transform: translateX(100%) skewX(-30deg); opacity: 0; } } .lightSpeedOut { -webkit-animation-name: lightSpeedOut; animation-name: lightSpeedOut; -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } @-webkit-keyframes rotateIn { 0% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(-200deg); transform: rotate(-200deg); opacity: 0; } 100% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateIn { 0% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(-200deg); -ms-transform: rotate(-200deg); transform: rotate(-200deg); opacity: 0; } 100% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateIn { -webkit-animation-name: rotateIn; animation-name: rotateIn; } @-webkit-keyframes rotateInDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInDownLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInDownLeft { -webkit-animation-name: rotateInDownLeft; animation-name: rotateInDownLeft; } @-webkit-keyframes rotateInDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInDownRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInDownRight { -webkit-animation-name: rotateInDownRight; animation-name: rotateInDownRight; } @-webkit-keyframes rotateInUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInUpLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInUpLeft { -webkit-animation-name: rotateInUpLeft; animation-name: rotateInUpLeft; } @-webkit-keyframes rotateInUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } } @keyframes rotateInUpRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } } .rotateInUpRight { -webkit-animation-name: rotateInUpRight; animation-name: rotateInUpRight; } @-webkit-keyframes rotateOut { 0% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(200deg); transform: rotate(200deg); opacity: 0; } } @keyframes rotateOut { 0% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: center center; -ms-transform-origin: center center; transform-origin: center center; -webkit-transform: rotate(200deg); -ms-transform: rotate(200deg); transform: rotate(200deg); opacity: 0; } } .rotateOut { -webkit-animation-name: rotateOut; animation-name: rotateOut; } @-webkit-keyframes rotateOutDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } @keyframes rotateOutDownLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } .rotateOutDownLeft { -webkit-animation-name: rotateOutDownLeft; animation-name: rotateOutDownLeft; } @-webkit-keyframes rotateOutDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } } @keyframes rotateOutDownRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); opacity: 0; } } .rotateOutDownRight { -webkit-animation-name: rotateOutDownRight; animation-name: rotateOutDownRight; } @-webkit-keyframes rotateOutUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -transform-origin: left bottom; -transform: rotate(-90deg); opacity: 0; } } @keyframes rotateOutUpLeft { 0% { -webkit-transform-origin: left bottom; -ms-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -transform-origin: left bottom; -transform: rotate(-90deg); opacity: 0; } } .rotateOutUpLeft { -webkit-animation-name: rotateOutUpLeft; animation-name: rotateOutUpLeft; } @-webkit-keyframes rotateOutUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } @keyframes rotateOutUpRight { 0% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); opacity: 1; } 100% { -webkit-transform-origin: right bottom; -ms-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); opacity: 0; } } .rotateOutUpRight { -webkit-animation-name: rotateOutUpRight; animation-name: rotateOutUpRight; } @-webkit-keyframes slideInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes slideInDown { 0% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } 100% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } } .slideInDown { -webkit-animation-name: slideInDown; animation-name: slideInDown; } @-webkit-keyframes slideInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideInLeft { 0% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .slideInLeft { -webkit-animation-name: slideInLeft; animation-name: slideInLeft; } @-webkit-keyframes slideInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideInRight { 0% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } 100% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } } .slideInRight { -webkit-animation-name: slideInRight; animation-name: slideInRight; } @-webkit-keyframes slideOutLeft { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); transform: translateX(-2000px); } } @keyframes slideOutLeft { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(-2000px); -ms-transform: translateX(-2000px); transform: translateX(-2000px); } } .slideOutLeft { -webkit-animation-name: slideOutLeft; animation-name: slideOutLeft; } @-webkit-keyframes slideOutRight { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); transform: translateX(2000px); } } @keyframes slideOutRight { 0% { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } 100% { opacity: 0; -webkit-transform: translateX(2000px); -ms-transform: translateX(2000px); transform: translateX(2000px); } } .slideOutRight { -webkit-animation-name: slideOutRight; animation-name: slideOutRight; } @-webkit-keyframes slideOutUp { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); transform: translateY(-2000px); } } @keyframes slideOutUp { 0% { -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(-2000px); -ms-transform: translateY(-2000px); transform: translateY(-2000px); } } .slideOutUp { -webkit-animation-name: slideOutUp; animation-name: slideOutUp; } @-webkit-keyframes hinge { 0% { -webkit-transform: rotate(0); transform: rotate(0); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate(80deg); transform: rotate(80deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40% { -webkit-transform: rotate(60deg); transform: rotate(60deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 80% { -webkit-transform: rotate(60deg) translateY(0); transform: rotate(60deg) translateY(0); opacity: 1; -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 100% { -webkit-transform: translateY(700px); transform: translateY(700px); opacity: 0; } } @keyframes hinge { 0% { -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate(80deg); -ms-transform: rotate(80deg); transform: rotate(80deg); -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40% { -webkit-transform: rotate(60deg); -ms-transform: rotate(60deg); transform: rotate(60deg); -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 80% { -webkit-transform: rotate(60deg) translateY(0); -ms-transform: rotate(60deg) translateY(0); transform: rotate(60deg) translateY(0); opacity: 1; -webkit-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 100% { -webkit-transform: translateY(700px); -ms-transform: translateY(700px); transform: translateY(700px); opacity: 0; } } .hinge { -webkit-animation-name: hinge; animation-name: hinge; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollIn { 0% { opacity: 0; -webkit-transform: translateX(-100%) rotate(-120deg); transform: translateX(-100%) rotate(-120deg); } 100% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } } @keyframes rollIn { 0% { opacity: 0; -webkit-transform: translateX(-100%) rotate(-120deg); -ms-transform: translateX(-100%) rotate(-120deg); transform: translateX(-100%) rotate(-120deg); } 100% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); -ms-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } } .rollIn { -webkit-animation-name: rollIn; animation-name: rollIn; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollOut { 0% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } 100% { opacity: 0; -webkit-transform: translateX(100%) rotate(120deg); transform: translateX(100%) rotate(120deg); } } @keyframes rollOut { 0% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); -ms-transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg); } 100% { opacity: 0; -webkit-transform: translateX(100%) rotate(120deg); -ms-transform: translateX(100%) rotate(120deg); transform: translateX(100%) rotate(120deg); } } .rollOut { -webkit-animation-name: rollOut; animation-name: rollOut; } ================================================ FILE: server/public/css/lib/bootstrap/alerts.less ================================================ // // Alerts // -------------------------------------------------- // Base styles // ------------------------- .alert { padding: @alert-padding; margin-bottom: @line-height-computed; border: 1px solid transparent; border-radius: @alert-border-radius; // Headings for larger alerts h4 { margin-top: 0; // Specified for the h4 to prevent conflicts of changing @headings-color color: inherit; } // Provide class for links that match alerts .alert-link { font-weight: @alert-link-font-weight; } // Improve alignment and spacing of inner content > p, > ul { margin-bottom: 0; } > p + p { margin-top: 5px; } } // Dismissable alerts // // Expand the right padding and account for the close button's positioning. .alert-dismissable { padding-right: (@alert-padding + 20); // Adjust close link position .close { position: relative; top: -2px; right: -21px; color: inherit; } } // Alternate styles // // Generate contextual modifier classes for colorizing the alert. .alert-success { .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); } .alert-info { .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); } .alert-warning { .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); } .alert-danger { .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); } ================================================ FILE: server/public/css/lib/bootstrap/badges.less ================================================ // // Badges // -------------------------------------------------- // Base classes .badge { display: inline-block; min-width: 10px; padding: 3px 7px; font-size: @font-size-small; font-weight: @badge-font-weight; color: @badge-color; line-height: @badge-line-height; vertical-align: baseline; white-space: nowrap; text-align: center; background-color: @badge-bg; border-radius: @badge-border-radius; // Empty badges collapse automatically (not available in IE8) &:empty { display: none; } // Quick fix for badges in buttons .btn & { position: relative; top: -1px; } .btn-xs & { top: 0; padding: 1px 5px; } } // Hover state, but only for links a.badge { &:hover, &:focus { color: @badge-link-hover-color; text-decoration: none; cursor: pointer; } } // Account for counters in navs a.list-group-item.active > .badge, .nav-pills > .active > a > .badge { color: @badge-active-color; background-color: @badge-active-bg; } .nav-pills > li > a > .badge { margin-left: 3px; } ================================================ FILE: server/public/css/lib/bootstrap/bootstrap.less ================================================ // Core variables and mixins @import "variables.less"; @import "mixins.less"; // Reset @import "normalize.less"; @import "print.less"; // Core CSS @import "scaffolding.less"; @import "type.less"; @import "code.less"; @import "grid.less"; @import "tables.less"; @import "forms.less"; @import "buttons.less"; // Components @import "component-animations.less"; @import "glyphicons.less"; @import "dropdowns.less"; @import "button-groups.less"; @import "input-groups.less"; @import "navs.less"; @import "navbar.less"; @import "breadcrumbs.less"; @import "pagination.less"; @import "pager.less"; @import "labels.less"; @import "badges.less"; @import "jumbotron.less"; @import "thumbnails.less"; @import "alerts.less"; @import "progress-bars.less"; @import "media.less"; @import "list-group.less"; @import "panels.less"; @import "wells.less"; @import "close.less"; // Components w/ JavaScript @import "modals.less"; @import "tooltip.less"; @import "popovers.less"; @import "carousel.less"; // Utility classes @import "utilities.less"; @import "responsive-utilities.less"; ================================================ FILE: server/public/css/lib/bootstrap/breadcrumbs.less ================================================ // // Breadcrumbs // -------------------------------------------------- .breadcrumb { padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; margin-bottom: @line-height-computed; list-style: none; background-color: @breadcrumb-bg; border-radius: @border-radius-base; > li { display: inline-block; + li:before { content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space padding: 0 5px; color: @breadcrumb-color; } } > .active { color: @breadcrumb-active-color; } } ================================================ FILE: server/public/css/lib/bootstrap/button-groups.less ================================================ // // Button groups // -------------------------------------------------- // Make the div behave like a button .btn-group, .btn-group-vertical { position: relative; display: inline-block; vertical-align: middle; // match .btn alignment given font-size hack above > .btn { position: relative; float: left; // Bring the "active" button to the front &:hover, &:focus, &:active, &.active { z-index: 2; } &:focus { // Remove focus outline when dropdown JS adds it after closing the menu outline: none; } } } // Prevent double borders when buttons are next to each other .btn-group { .btn + .btn, .btn + .btn-group, .btn-group + .btn, .btn-group + .btn-group { margin-left: -1px; } } // Optional: Group multiple button groups together for a toolbar .btn-toolbar { margin-left: -5px; // Offset the first child's margin &:extend(.clearfix all) ; .btn-group, .input-group { float: left; } > .btn, > .btn-group, > .input-group { margin-left: 5px; } } .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { border-radius: 0; } // Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match .btn-group > .btn:first-child { margin-left: 0; &:not(:last-child):not(.dropdown-toggle) { .border-right-radius(0); } } // Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it .btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { .border-left-radius(0); } // Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) .btn-group > .btn-group { float: left; } .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group > .btn-group:first-child { > .btn:last-child, > .dropdown-toggle { .border-right-radius(0); } } .btn-group > .btn-group:last-child > .btn:first-child { .border-left-radius(0); } // On active and open, don't show outline .btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { outline: 0; } // Sizing // // Remix the default button sizing classes into new ones for easier manipulation. .btn-group-xs > .btn { &:extend(.btn-xs) ; } .btn-group-sm > .btn { &:extend(.btn-sm) ; } .btn-group-lg > .btn { &:extend(.btn-lg) ; } // Split button dropdowns // ---------------------- // Give the line between buttons some depth .btn-group > .btn + .dropdown-toggle { padding-left: 8px; padding-right: 8px; } .btn-group > .btn-lg + .dropdown-toggle { padding-left: 12px; padding-right: 12px; } // The clickable button for toggling the menu // Remove the gradient and set the same inset shadow as the :active state .btn-group.open .dropdown-toggle { .box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125)); // Show no shadow for `.btn-link` since it has no other button styles. &.btn-link { .box-shadow(none); } } // Reposition the caret .btn .caret { margin-left: 0; } // Carets in other button sizes .btn-lg .caret { border-width: @caret-width-large @caret-width-large 0; border-bottom-width: 0; } // Upside down carets for .dropup .dropup .btn-lg .caret { border-width: 0 @caret-width-large @caret-width-large; } // Vertical button groups // ---------------------- .btn-group-vertical { > .btn, > .btn-group, > .btn-group > .btn { display: block; float: none; width: 100%; max-width: 100%; } // Clear floats so dropdown menus can be properly placed > .btn-group { &:extend(.clearfix all) ; > .btn { float: none; } } > .btn + .btn, > .btn + .btn-group, > .btn-group + .btn, > .btn-group + .btn-group { margin-top: -1px; margin-left: 0; } } .btn-group-vertical > .btn { &:not(:first-child):not(:last-child) { border-radius: 0; } &:first-child:not(:last-child) { border-top-right-radius: @border-radius-base; .border-bottom-radius(0); } &:last-child:not(:first-child) { border-bottom-left-radius: @border-radius-base; .border-top-radius(0); } } .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group-vertical > .btn-group:first-child:not(:last-child) { > .btn:last-child, > .dropdown-toggle { .border-bottom-radius(0); } } .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { .border-top-radius(0); } // Justified button groups // ---------------------- .btn-group-justified { display: table; width: 100%; table-layout: fixed; border-collapse: separate; > .btn, > .btn-group { float: none; display: table-cell; width: 1%; } > .btn-group .btn { width: 100%; } } // Checkbox and radio options [data-toggle="buttons"] > .btn > input[type="radio"], [data-toggle="buttons"] > .btn > input[type="checkbox"] { display: none; } ================================================ FILE: server/public/css/lib/bootstrap/buttons.less ================================================ // // Buttons // -------------------------------------------------- // Base styles // -------------------------------------------------- .btn { display: inline-block; margin-bottom: 0; // For input.btn font-weight: @btn-font-weight; text-align: center; vertical-align: middle; cursor: pointer; background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 border: 1px solid transparent; white-space: nowrap; .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base); .user-select(none); &, &:active, &.active { &:focus { .tab-focus(); } } &:hover, &:focus { color: @btn-default-color; text-decoration: none; } &:active, &.active { outline: 0; background-image: none; .box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125)); } &.disabled, &[disabled], fieldset[disabled] & { cursor: not-allowed; pointer-events: none; // Future-proof disabling of clicks .opacity(.65); .box-shadow(none); } } // Alternate buttons // -------------------------------------------------- .btn-default { .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); } .btn-primary { .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); } // Success appears as green .btn-success { .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); } // Info appears as blue-green .btn-info { .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); } // Warning appears as orange .btn-warning { .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); } // Danger and error appear as red .btn-danger { .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); } // Link buttons // ------------------------- // Make a button look and behave like a link .btn-link { color: @link-color; font-weight: normal; cursor: pointer; border-radius: 0; &, &:active, &[disabled], fieldset[disabled] & { background-color: transparent; .box-shadow(none); } &, &:hover, &:focus, &:active { border-color: transparent; } &:hover, &:focus { color: @link-hover-color; text-decoration: underline; background-color: transparent; } &[disabled], fieldset[disabled] & { &:hover, &:focus { color: @btn-link-disabled-color; text-decoration: none; } } } // Button Sizes // -------------------------------------------------- .btn-lg { // line-height: ensure even-numbered height of button next to large input .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); } .btn-sm { // line-height: ensure proper height of button next to small input .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); } .btn-xs { .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small); } // Block button // -------------------------------------------------- .btn-block { display: block; width: 100%; padding-left: 0; padding-right: 0; } // Vertically space out multiple block buttons .btn-block + .btn-block { margin-top: 5px; } // Specificity overrides input[type="submit"], input[type="reset"], input[type="button"] { &.btn-block { width: 100%; } } ================================================ FILE: server/public/css/lib/bootstrap/carousel.less ================================================ // // Carousel // -------------------------------------------------- // Wrapper for the slide container and indicators .carousel { position: relative; } .carousel-inner { position: relative; overflow: hidden; width: 100%; > .item { display: none; position: relative; .transition(.6s ease-in-out left); // Account for jankitude on images > img, > a > img { &:extend(.img-responsive) ; line-height: 1; } } > .active, > .next, > .prev { display: block; } > .active { left: 0; } > .next, > .prev { position: absolute; top: 0; width: 100%; } > .next { left: 100%; } > .prev { left: -100%; } > .next.left, > .prev.right { left: 0; } > .active.left { left: -100%; } > .active.right { left: 100%; } } // Left/right controls for nav // --------------------------- .carousel-control { position: absolute; top: 0; left: 0; bottom: 0; width: @carousel-control-width; .opacity(@carousel-control-opacity); font-size: @carousel-control-font-size; color: @carousel-control-color; text-align: center; text-shadow: @carousel-text-shadow; // We can't have this transition here because WebKit cancels the carousel // animation if you trip this while in the middle of another animation. // Set gradients for backgrounds &.left { #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001)); } &.right { left: auto; right: 0; #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5)); } // Hover/focus state &:hover, &:focus { outline: none; color: @carousel-control-color; text-decoration: none; .opacity(.9); } // Toggles .icon-prev, .icon-next, .glyphicon-chevron-left, .glyphicon-chevron-right { position: absolute; top: 50%; z-index: 5; display: inline-block; } .icon-prev, .glyphicon-chevron-left { left: 50%; } .icon-next, .glyphicon-chevron-right { right: 50%; } .icon-prev, .icon-next { width: 20px; height: 20px; margin-top: -10px; margin-left: -10px; font-family: serif; } .icon-prev { &:before { content: '\2039'; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) } } .icon-next { &:before { content: '\203a'; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) } } } // Optional indicator pips // // Add an unordered list with the following class and add a list item for each // slide your carousel holds. .carousel-indicators { position: absolute; bottom: 10px; left: 50%; z-index: 15; width: 60%; margin-left: -30%; padding-left: 0; list-style: none; text-align: center; li { display: inline-block; width: 10px; height: 10px; margin: 1px; text-indent: -999px; border: 1px solid @carousel-indicator-border-color; border-radius: 10px; cursor: pointer; // IE8-9 hack for event handling // // Internet Explorer 8-9 does not support clicks on elements without a set // `background-color`. We cannot use `filter` since that's not viewed as a // background color by the browser. Thus, a hack is needed. // // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we // set alpha transparency for the best results possible. background-color: #000 \9; // IE8 background-color: rgba(0, 0, 0, 0); // IE9 } .active { margin: 0; width: 12px; height: 12px; background-color: @carousel-indicator-active-bg; } } // Optional captions // ----------------------------- // Hidden by default for smaller viewports .carousel-caption { position: absolute; left: 15%; right: 15%; bottom: 20px; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: @carousel-caption-color; text-align: center; text-shadow: @carousel-text-shadow; & .btn { text-shadow: none; // No shadow for button elements in carousel-caption } } // Scale up controls for tablets and up @media screen and (min-width: @screen-sm-min) { // Scale up the controls a smidge .carousel-control { .glyphicon-chevron-left, .glyphicon-chevron-right, .icon-prev, .icon-next { width: 30px; height: 30px; margin-top: -15px; margin-left: -15px; font-size: 30px; } } // Show and left align the captions .carousel-caption { left: 20%; right: 20%; padding-bottom: 30px; } // Move up the indicators .carousel-indicators { bottom: 20px; } } ================================================ FILE: server/public/css/lib/bootstrap/close.less ================================================ // // Close icons // -------------------------------------------------- .close { float: right; font-size: (@font-size-base * 1.5); font-weight: @close-font-weight; line-height: 1; color: @close-color; text-shadow: @close-text-shadow; .opacity(.2); &:hover, &:focus { color: @close-color; text-decoration: none; cursor: pointer; .opacity(.5); } // Additional properties for button version // iOS requires the button element instead of an anchor tag. // If you want the anchor version, it requires `href="#"`. button& { padding: 0; cursor: pointer; background: transparent; border: 0; -webkit-appearance: none; } } ================================================ FILE: server/public/css/lib/bootstrap/code.less ================================================ // // Code (inline and block) // -------------------------------------------------- // Inline and block code styles code, kbd, pre, samp { font-family: @font-family-monospace; } // Inline code code { padding: 2px 4px; font-size: 90%; color: @code-color; background-color: @code-bg; white-space: nowrap; border-radius: @border-radius-base; } // User input typically entered via keyboard kbd { padding: 2px 4px; font-size: 90%; color: @kbd-color; background-color: @kbd-bg; border-radius: @border-radius-small; box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); } // Blocks of code pre { display: block; padding: ((@line-height-computed - 1) / 2); margin: 0 0 (@line-height-computed / 2); font-size: (@font-size-base - 1); // 14px to 13px line-height: @line-height-base; word-break: break-all; word-wrap: break-word; color: @pre-color; background-color: @pre-bg; border: 1px solid @pre-border-color; border-radius: @border-radius-base; // Account for some code outputs that place code tags in pre tags code { padding: 0; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0; } } // Enable scrollable blocks of code .pre-scrollable { max-height: @pre-scrollable-max-height; overflow-y: scroll; } ================================================ FILE: server/public/css/lib/bootstrap/component-animations.less ================================================ // // Component animations // -------------------------------------------------- // Heads up! // // We don't use the `.opacity()` mixin here since it causes a bug with text // fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552. .fade { opacity: 0; .transition(opacity .15s linear); &.in { opacity: 1; } } .collapse { display: none; &.in { display: block; } } .collapsing { position: relative; height: 0; overflow: hidden; .transition(height .35s ease); } ================================================ FILE: server/public/css/lib/bootstrap/dropdowns.less ================================================ // // Dropdown menus // -------------------------------------------------- // Dropdown arrow/caret .caret { display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle; border-top: @caret-width-base solid; border-right: @caret-width-base solid transparent; border-left: @caret-width-base solid transparent; } // The dropdown wrapper (div) .dropdown { position: relative; } // Prevent the focus on the dropdown toggle when closing dropdowns .dropdown-toggle:focus { outline: 0; } // The dropdown menu (ul) .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: @zindex-dropdown; display: none; // none by default, but block on "open" of the menu float: left; min-width: 160px; padding: 5px 0; margin: 2px 0 0; // override default ul list-style: none; font-size: @font-size-base; background-color: @dropdown-bg; border: 1px solid @dropdown-fallback-border; // IE8 fallback border: 1px solid @dropdown-border; border-radius: @border-radius-base; .box-shadow(0 6px 12px rgba(0,0,0,.175)); background-clip: padding-box; // Aligns the dropdown menu to right // // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]` &.pull-right { right: 0; left: auto; } // Dividers (basically an hr) within the dropdown .divider { .nav-divider(@dropdown-divider-bg); } // Links within the dropdown menu > li > a { display: block; padding: 3px 20px; clear: both; font-weight: normal; line-height: @line-height-base; color: @dropdown-link-color; white-space: nowrap; // prevent links from randomly breaking onto new lines } } // Hover/Focus state .dropdown-menu > li > a { &:hover, &:focus { text-decoration: none; color: @dropdown-link-hover-color; background-color: @dropdown-link-hover-bg; } } // Active state .dropdown-menu > .active > a { &, &:hover, &:focus { color: @dropdown-link-active-color; text-decoration: none; outline: 0; background-color: @dropdown-link-active-bg; } } // Disabled state // // Gray out text and ensure the hover/focus state remains gray .dropdown-menu > .disabled > a { &, &:hover, &:focus { color: @dropdown-link-disabled-color; } } // Nuke hover/focus effects .dropdown-menu > .disabled > a { &:hover, &:focus { text-decoration: none; background-color: transparent; background-image: none; // Remove CSS gradient .reset-filter(); cursor: not-allowed; } } // Open state for the dropdown .open { // Show the menu > .dropdown-menu { display: block; } // Remove the outline when :focus is triggered > a { outline: 0; } } // Menu positioning // // Add extra class to `.dropdown-menu` to flip the alignment of the dropdown // menu with the parent. .dropdown-menu-right { left: auto; // Reset the default from `.dropdown-menu` right: 0; } // With v3, we enabled auto-flipping if you have a dropdown within a right // aligned nav component. To enable the undoing of that, we provide an override // to restore the default dropdown menu alignment. // // This is only for left-aligning a dropdown menu within a `.navbar-right` or // `.pull-right` nav component. .dropdown-menu-left { left: 0; right: auto; } // Dropdown section headers .dropdown-header { display: block; padding: 3px 20px; font-size: @font-size-small; line-height: @line-height-base; color: @dropdown-header-color; } // Backdrop to catch body clicks on mobile, etc. .dropdown-backdrop { position: fixed; left: 0; right: 0; bottom: 0; top: 0; z-index: (@zindex-dropdown - 10); } // Right aligned dropdowns .pull-right > .dropdown-menu { right: 0; left: auto; } // Allow for dropdowns to go bottom up (aka, dropup-menu) // // Just add .dropup after the standard .dropdown class and you're set, bro. // TODO: abstract this so that the navbar fixed styles are not placed here? .dropup, .navbar-fixed-bottom .dropdown { // Reverse the caret .caret { border-top: 0; border-bottom: @caret-width-base solid; content: ""; } // Different positioning for bottom up menu .dropdown-menu { top: auto; bottom: 100%; margin-bottom: 1px; } } // Component alignment // // Reiterate per navbar.less and the modified component alignment there. @media (min-width: @grid-float-breakpoint) { .navbar-right { .dropdown-menu { .dropdown-menu-right(); } // Necessary for overrides of the default right aligned menu. // Will remove come v4 in all likelihood. .dropdown-menu-left { .dropdown-menu-left(); } } } ================================================ FILE: server/public/css/lib/bootstrap/forms.less ================================================ // // Forms // -------------------------------------------------- // Normalize non-controls // // Restyle and baseline non-control form elements. fieldset { padding: 0; margin: 0; border: 0; // Chrome and Firefox set a `min-width: -webkit-min-content;` on fieldsets, // so we reset that to ensure it behaves more like a standard block element. // See https://github.com/twbs/bootstrap/issues/12359. min-width: 0; } legend { display: block; width: 100%; padding: 0; margin-bottom: @line-height-computed; font-size: (@font-size-base * 1.5); line-height: inherit; color: @legend-color; border: 0; border-bottom: 1px solid @legend-border-color; } label { display: inline-block; margin-bottom: 5px; font-weight: bold; } // Normalize form controls // // While most of our form styles require extra classes, some basic normalization // is required to ensure optimum display with or without those classes to better // address browser inconsistencies. // Override content-box in Normalize (* isn't specific enough) input[type="search"] { .box-sizing(border-box); } // Position radios and checkboxes better input[type="radio"], input[type="checkbox"] { margin: 4px 0 0; margin-top: 1px \9; /* IE8-9 */ line-height: normal; } // Set the height of file controls to match text inputs input[type="file"] { display: block; } // Make range inputs behave like textual form controls input[type="range"] { display: block; width: 100%; } // Make multiple select elements height not fixed select[multiple], select[size] { height: auto; } // Focus for file, radio, and checkbox input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { .tab-focus(); } // Adjust output element output { display: block; padding-top: (@padding-base-vertical + 1); font-size: @font-size-base; line-height: @line-height-base; color: @input-color; } // Common form controls // // Shared size and type resets for form controls. Apply `.form-control` to any // of the following form controls: // // select // textarea // input[type="text"] // input[type="password"] // input[type="datetime"] // input[type="datetime-local"] // input[type="date"] // input[type="month"] // input[type="time"] // input[type="week"] // input[type="number"] // input[type="email"] // input[type="url"] // input[type="search"] // input[type="tel"] // input[type="color"] .form-control { display: block; width: 100%; height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) padding: @padding-base-vertical @padding-base-horizontal; font-size: @font-size-base; line-height: @line-height-base; color: @input-color; background-color: @input-bg; background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 border: 1px solid @input-border; border-radius: @input-border-radius; .box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); // Customize the `:focus` state to imitate native WebKit styles. .form-control-focus(); // Placeholder .placeholder(); // Disabled and read-only inputs // // HTML5 says that controls under a fieldset > legend:first-child won't be // disabled if the fieldset is disabled. Due to implementation difficulty, we // don't honor that edge case; we style them as disabled anyway. &[disabled], &[readonly], fieldset[disabled] & { cursor: not-allowed; background-color: @input-bg-disabled; opacity: 1; // iOS fix for unreadable disabled content } // Reset height for `textarea`s textarea& { height: auto; } } // Search inputs in iOS // // This overrides the extra rounded corners on search inputs in iOS so that our // `.form-control` class can properly style them. Note that this cannot simply // be added to `.form-control` as it's not specific enough. For details, see // https://github.com/twbs/bootstrap/issues/11586. input[type="search"] { -webkit-appearance: none; } // Special styles for iOS date input // // In Mobile Safari, date inputs require a pixel line-height that matches the // given height of the input. input[type="date"] { line-height: @input-height-base; } // Form groups // // Designed to help with the organization and spacing of vertical forms. For // horizontal forms, use the predefined grid classes. .form-group { margin-bottom: 15px; } // Checkboxes and radios // // Indent the labels to position radios/checkboxes as hanging controls. .radio, .checkbox { display: block; min-height: @line-height-computed; // clear the floating input if there is no label text margin-top: 10px; margin-bottom: 10px; padding-left: 20px; label { display: inline; font-weight: normal; cursor: pointer; } } .radio input[type="radio"], .radio-inline input[type="radio"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { float: left; margin-left: -20px; } .radio + .radio, .checkbox + .checkbox { margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing } // Radios and checkboxes on same line .radio-inline, .checkbox-inline { display: inline-block; padding-left: 20px; margin-bottom: 0; vertical-align: middle; font-weight: normal; cursor: pointer; } .radio-inline + .radio-inline, .checkbox-inline + .checkbox-inline { margin-top: 0; margin-left: 10px; // space out consecutive inline controls } // Apply same disabled cursor tweak as for inputs // // Note: Neither radios nor checkboxes can be readonly. input[type="radio"], input[type="checkbox"], .radio, .radio-inline, .checkbox, .checkbox-inline { &[disabled], fieldset[disabled] & { cursor: not-allowed; } } // Form control sizing // // Build on `.form-control` with modifier classes to decrease or increase the // height and font-size of form controls. .input-sm { .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); } .input-lg { .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); } // Form control feedback states // // Apply contextual and semantic states to individual form controls. .has-feedback { // Enable absolute positioning position: relative; // Ensure icons don't overlap text .form-control { padding-right: (@input-height-base * 1.25); } // Feedback icon (requires .glyphicon classes) .form-control-feedback { position: absolute; top: (@line-height-computed + 5); // Height of the `label` and its margin right: 0; display: block; width: @input-height-base; height: @input-height-base; line-height: @input-height-base; text-align: center; } } // Feedback states .has-success { .form-control-validation(@state-success-text; @state-success-text; @state-success-bg); } .has-warning { .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg); } .has-error { .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg); } // Static form control text // // Apply class to a `p` element to make any string of text align with labels in // a horizontal form layout. .form-control-static { margin-bottom: 0; // Remove default margin from `p` } // Help text // // Apply to any element you wish to create light text for placement immediately // below a form control. Use for general help, formatting, or instructional text. .help-block { display: block; // account for any element using help-block margin-top: 5px; margin-bottom: 10px; color: lighten(@text-color, 25%); // lighten the text some for contrast } // Inline forms // // Make forms appear inline(-block) by adding the `.form-inline` class. Inline // forms begin stacked on extra small (mobile) devices and then go inline when // viewports reach <768px. // // Requires wrapping inputs and labels with `.form-group` for proper display of // default HTML form controls and our custom form controls (e.g., input groups). // // Heads up! This is mixin-ed into `.navbar-form` in navbars.less. .form-inline { // Kick in the inline @media (min-width: @screen-sm-min) { // Inline-block all the things for "inline" .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } // In navbar-form, allow folks to *not* use `.form-group` .form-control { display: inline-block; width: auto; // Prevent labels from stacking above inputs in `.form-group` vertical-align: middle; } // Input groups need that 100% width though .input-group > .form-control { width: 100%; } .control-label { margin-bottom: 0; vertical-align: middle; } // Remove default margin on radios/checkboxes that were used for stacking, and // then undo the floating of radios and checkboxes to match (which also avoids // a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969). .radio, .checkbox { display: inline-block; margin-top: 0; margin-bottom: 0; padding-left: 0; vertical-align: middle; } .radio input[type="radio"], .checkbox input[type="checkbox"] { float: none; margin-left: 0; } // Validation states // // Reposition the icon because it's now within a grid column and columns have // `position: relative;` on them. Also accounts for the grid gutter padding. .has-feedback .form-control-feedback { top: 0; } } } // Horizontal forms // // Horizontal forms are built on grid classes and allow you to create forms with // labels on the left and inputs on the right. .form-horizontal { // Consistent vertical alignment of labels, radios, and checkboxes .control-label, .radio, .checkbox, .radio-inline, .checkbox-inline { margin-top: 0; margin-bottom: 0; padding-top: (@padding-base-vertical + 1); // Default padding plus a border } // Account for padding we're adding to ensure the alignment and of help text // and other content below items .radio, .checkbox { min-height: (@line-height-computed + (@padding-base-vertical + 1)); } // Make form groups behave like rows .form-group { .make-row(); } .form-control-static { padding-top: (@padding-base-vertical + 1); } // Only right align form labels here when the columns stop stacking @media (min-width: @screen-sm-min) { .control-label { text-align: right; } } // Validation states // // Reposition the icon because it's now within a grid column and columns have // `position: relative;` on them. Also accounts for the grid gutter padding. .has-feedback .form-control-feedback { top: 0; right: (@grid-gutter-width / 2); } } ================================================ FILE: server/public/css/lib/bootstrap/glyphicons.less ================================================ // // Glyphicons for Bootstrap // // Since icons are fonts, they can be placed anywhere text is placed and are // thus automatically sized to match the surrounding child. To use, create an // inline element with the appropriate classes, like so: // // Star // Import the fonts @font-face { font-family: 'Glyphicons Halflings'; src: ~"url('@{icon-font-path}@{icon-font-name}.eot')"; src: ~"url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype')", ~"url('@{icon-font-path}@{icon-font-name}.woff') format('woff')", ~"url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype')", ~"url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg')"; } // Catchall baseclass .glyphicon { position: relative; top: 1px; display: inline-block; font-family: 'Glyphicons Halflings'; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } // Individual icons .glyphicon-asterisk { &:before { content: "\2a"; } } .glyphicon-plus { &:before { content: "\2b"; } } .glyphicon-euro { &:before { content: "\20ac"; } } .glyphicon-minus { &:before { content: "\2212"; } } .glyphicon-cloud { &:before { content: "\2601"; } } .glyphicon-envelope { &:before { content: "\2709"; } } .glyphicon-pencil { &:before { content: "\270f"; } } .glyphicon-glass { &:before { content: "\e001"; } } .glyphicon-music { &:before { content: "\e002"; } } .glyphicon-search { &:before { content: "\e003"; } } .glyphicon-heart { &:before { content: "\e005"; } } .glyphicon-star { &:before { content: "\e006"; } } .glyphicon-star-empty { &:before { content: "\e007"; } } .glyphicon-user { &:before { content: "\e008"; } } .glyphicon-film { &:before { content: "\e009"; } } .glyphicon-th-large { &:before { content: "\e010"; } } .glyphicon-th { &:before { content: "\e011"; } } .glyphicon-th-list { &:before { content: "\e012"; } } .glyphicon-ok { &:before { content: "\e013"; } } .glyphicon-remove { &:before { content: "\e014"; } } .glyphicon-zoom-in { &:before { content: "\e015"; } } .glyphicon-zoom-out { &:before { content: "\e016"; } } .glyphicon-off { &:before { content: "\e017"; } } .glyphicon-signal { &:before { content: "\e018"; } } .glyphicon-cog { &:before { content: "\e019"; } } .glyphicon-trash { &:before { content: "\e020"; } } .glyphicon-home { &:before { content: "\e021"; } } .glyphicon-file { &:before { content: "\e022"; } } .glyphicon-time { &:before { content: "\e023"; } } .glyphicon-road { &:before { content: "\e024"; } } .glyphicon-download-alt { &:before { content: "\e025"; } } .glyphicon-download { &:before { content: "\e026"; } } .glyphicon-upload { &:before { content: "\e027"; } } .glyphicon-inbox { &:before { content: "\e028"; } } .glyphicon-play-circle { &:before { content: "\e029"; } } .glyphicon-repeat { &:before { content: "\e030"; } } .glyphicon-refresh { &:before { content: "\e031"; } } .glyphicon-list-alt { &:before { content: "\e032"; } } .glyphicon-lock { &:before { content: "\e033"; } } .glyphicon-flag { &:before { content: "\e034"; } } .glyphicon-headphones { &:before { content: "\e035"; } } .glyphicon-volume-off { &:before { content: "\e036"; } } .glyphicon-volume-down { &:before { content: "\e037"; } } .glyphicon-volume-up { &:before { content: "\e038"; } } .glyphicon-qrcode { &:before { content: "\e039"; } } .glyphicon-barcode { &:before { content: "\e040"; } } .glyphicon-tag { &:before { content: "\e041"; } } .glyphicon-tags { &:before { content: "\e042"; } } .glyphicon-book { &:before { content: "\e043"; } } .glyphicon-bookmark { &:before { content: "\e044"; } } .glyphicon-print { &:before { content: "\e045"; } } .glyphicon-camera { &:before { content: "\e046"; } } .glyphicon-font { &:before { content: "\e047"; } } .glyphicon-bold { &:before { content: "\e048"; } } .glyphicon-italic { &:before { content: "\e049"; } } .glyphicon-text-height { &:before { content: "\e050"; } } .glyphicon-text-width { &:before { content: "\e051"; } } .glyphicon-align-left { &:before { content: "\e052"; } } .glyphicon-align-center { &:before { content: "\e053"; } } .glyphicon-align-right { &:before { content: "\e054"; } } .glyphicon-align-justify { &:before { content: "\e055"; } } .glyphicon-list { &:before { content: "\e056"; } } .glyphicon-indent-left { &:before { content: "\e057"; } } .glyphicon-indent-right { &:before { content: "\e058"; } } .glyphicon-facetime-video { &:before { content: "\e059"; } } .glyphicon-picture { &:before { content: "\e060"; } } .glyphicon-map-marker { &:before { content: "\e062"; } } .glyphicon-adjust { &:before { content: "\e063"; } } .glyphicon-tint { &:before { content: "\e064"; } } .glyphicon-edit { &:before { content: "\e065"; } } .glyphicon-share { &:before { content: "\e066"; } } .glyphicon-check { &:before { content: "\e067"; } } .glyphicon-move { &:before { content: "\e068"; } } .glyphicon-step-backward { &:before { content: "\e069"; } } .glyphicon-fast-backward { &:before { content: "\e070"; } } .glyphicon-backward { &:before { content: "\e071"; } } .glyphicon-play { &:before { content: "\e072"; } } .glyphicon-pause { &:before { content: "\e073"; } } .glyphicon-stop { &:before { content: "\e074"; } } .glyphicon-forward { &:before { content: "\e075"; } } .glyphicon-fast-forward { &:before { content: "\e076"; } } .glyphicon-step-forward { &:before { content: "\e077"; } } .glyphicon-eject { &:before { content: "\e078"; } } .glyphicon-chevron-left { &:before { content: "\e079"; } } .glyphicon-chevron-right { &:before { content: "\e080"; } } .glyphicon-plus-sign { &:before { content: "\e081"; } } .glyphicon-minus-sign { &:before { content: "\e082"; } } .glyphicon-remove-sign { &:before { content: "\e083"; } } .glyphicon-ok-sign { &:before { content: "\e084"; } } .glyphicon-question-sign { &:before { content: "\e085"; } } .glyphicon-info-sign { &:before { content: "\e086"; } } .glyphicon-screenshot { &:before { content: "\e087"; } } .glyphicon-remove-circle { &:before { content: "\e088"; } } .glyphicon-ok-circle { &:before { content: "\e089"; } } .glyphicon-ban-circle { &:before { content: "\e090"; } } .glyphicon-arrow-left { &:before { content: "\e091"; } } .glyphicon-arrow-right { &:before { content: "\e092"; } } .glyphicon-arrow-up { &:before { content: "\e093"; } } .glyphicon-arrow-down { &:before { content: "\e094"; } } .glyphicon-share-alt { &:before { content: "\e095"; } } .glyphicon-resize-full { &:before { content: "\e096"; } } .glyphicon-resize-small { &:before { content: "\e097"; } } .glyphicon-exclamation-sign { &:before { content: "\e101"; } } .glyphicon-gift { &:before { content: "\e102"; } } .glyphicon-leaf { &:before { content: "\e103"; } } .glyphicon-fire { &:before { content: "\e104"; } } .glyphicon-eye-open { &:before { content: "\e105"; } } .glyphicon-eye-close { &:before { content: "\e106"; } } .glyphicon-warning-sign { &:before { content: "\e107"; } } .glyphicon-plane { &:before { content: "\e108"; } } .glyphicon-calendar { &:before { content: "\e109"; } } .glyphicon-random { &:before { content: "\e110"; } } .glyphicon-comment { &:before { content: "\e111"; } } .glyphicon-magnet { &:before { content: "\e112"; } } .glyphicon-chevron-up { &:before { content: "\e113"; } } .glyphicon-chevron-down { &:before { content: "\e114"; } } .glyphicon-retweet { &:before { content: "\e115"; } } .glyphicon-shopping-cart { &:before { content: "\e116"; } } .glyphicon-folder-close { &:before { content: "\e117"; } } .glyphicon-folder-open { &:before { content: "\e118"; } } .glyphicon-resize-vertical { &:before { content: "\e119"; } } .glyphicon-resize-horizontal { &:before { content: "\e120"; } } .glyphicon-hdd { &:before { content: "\e121"; } } .glyphicon-bullhorn { &:before { content: "\e122"; } } .glyphicon-bell { &:before { content: "\e123"; } } .glyphicon-certificate { &:before { content: "\e124"; } } .glyphicon-thumbs-up { &:before { content: "\e125"; } } .glyphicon-thumbs-down { &:before { content: "\e126"; } } .glyphicon-hand-right { &:before { content: "\e127"; } } .glyphicon-hand-left { &:before { content: "\e128"; } } .glyphicon-hand-up { &:before { content: "\e129"; } } .glyphicon-hand-down { &:before { content: "\e130"; } } .glyphicon-circle-arrow-right { &:before { content: "\e131"; } } .glyphicon-circle-arrow-left { &:before { content: "\e132"; } } .glyphicon-circle-arrow-up { &:before { content: "\e133"; } } .glyphicon-circle-arrow-down { &:before { content: "\e134"; } } .glyphicon-globe { &:before { content: "\e135"; } } .glyphicon-wrench { &:before { content: "\e136"; } } .glyphicon-tasks { &:before { content: "\e137"; } } .glyphicon-filter { &:before { content: "\e138"; } } .glyphicon-briefcase { &:before { content: "\e139"; } } .glyphicon-fullscreen { &:before { content: "\e140"; } } .glyphicon-dashboard { &:before { content: "\e141"; } } .glyphicon-paperclip { &:before { content: "\e142"; } } .glyphicon-heart-empty { &:before { content: "\e143"; } } .glyphicon-link { &:before { content: "\e144"; } } .glyphicon-phone { &:before { content: "\e145"; } } .glyphicon-pushpin { &:before { content: "\e146"; } } .glyphicon-usd { &:before { content: "\e148"; } } .glyphicon-gbp { &:before { content: "\e149"; } } .glyphicon-sort { &:before { content: "\e150"; } } .glyphicon-sort-by-alphabet { &:before { content: "\e151"; } } .glyphicon-sort-by-alphabet-alt { &:before { content: "\e152"; } } .glyphicon-sort-by-order { &:before { content: "\e153"; } } .glyphicon-sort-by-order-alt { &:before { content: "\e154"; } } .glyphicon-sort-by-attributes { &:before { content: "\e155"; } } .glyphicon-sort-by-attributes-alt { &:before { content: "\e156"; } } .glyphicon-unchecked { &:before { content: "\e157"; } } .glyphicon-expand { &:before { content: "\e158"; } } .glyphicon-collapse-down { &:before { content: "\e159"; } } .glyphicon-collapse-up { &:before { content: "\e160"; } } .glyphicon-log-in { &:before { content: "\e161"; } } .glyphicon-flash { &:before { content: "\e162"; } } .glyphicon-log-out { &:before { content: "\e163"; } } .glyphicon-new-window { &:before { content: "\e164"; } } .glyphicon-record { &:before { content: "\e165"; } } .glyphicon-save { &:before { content: "\e166"; } } .glyphicon-open { &:before { content: "\e167"; } } .glyphicon-saved { &:before { content: "\e168"; } } .glyphicon-import { &:before { content: "\e169"; } } .glyphicon-export { &:before { content: "\e170"; } } .glyphicon-send { &:before { content: "\e171"; } } .glyphicon-floppy-disk { &:before { content: "\e172"; } } .glyphicon-floppy-saved { &:before { content: "\e173"; } } .glyphicon-floppy-remove { &:before { content: "\e174"; } } .glyphicon-floppy-save { &:before { content: "\e175"; } } .glyphicon-floppy-open { &:before { content: "\e176"; } } .glyphicon-credit-card { &:before { content: "\e177"; } } .glyphicon-transfer { &:before { content: "\e178"; } } .glyphicon-cutlery { &:before { content: "\e179"; } } .glyphicon-header { &:before { content: "\e180"; } } .glyphicon-compressed { &:before { content: "\e181"; } } .glyphicon-earphone { &:before { content: "\e182"; } } .glyphicon-phone-alt { &:before { content: "\e183"; } } .glyphicon-tower { &:before { content: "\e184"; } } .glyphicon-stats { &:before { content: "\e185"; } } .glyphicon-sd-video { &:before { content: "\e186"; } } .glyphicon-hd-video { &:before { content: "\e187"; } } .glyphicon-subtitles { &:before { content: "\e188"; } } .glyphicon-sound-stereo { &:before { content: "\e189"; } } .glyphicon-sound-dolby { &:before { content: "\e190"; } } .glyphicon-sound-5-1 { &:before { content: "\e191"; } } .glyphicon-sound-6-1 { &:before { content: "\e192"; } } .glyphicon-sound-7-1 { &:before { content: "\e193"; } } .glyphicon-copyright-mark { &:before { content: "\e194"; } } .glyphicon-registration-mark { &:before { content: "\e195"; } } .glyphicon-cloud-download { &:before { content: "\e197"; } } .glyphicon-cloud-upload { &:before { content: "\e198"; } } .glyphicon-tree-conifer { &:before { content: "\e199"; } } .glyphicon-tree-deciduous { &:before { content: "\e200"; } } ================================================ FILE: server/public/css/lib/bootstrap/grid.less ================================================ // // Grid system // -------------------------------------------------- // Container widths // // Set the container width, and override it for fixed navbars in media queries. .container { .container-fixed(); @media (min-width: @screen-sm-min) { width: @container-sm; } @media (min-width: @screen-md-min) { width: @container-md; } @media (min-width: @screen-lg-min) { width: @container-lg; } } // Fluid container // // Utilizes the mixin meant for fixed width containers, but without any defined // width for fluid, full width layouts. .container-fluid { .container-fixed(); } // Row // // Rows contain and clear the floats of your columns. .row { .make-row(); } // Columns // // Common styles for small and large grid columns .make-grid-columns(); // Extra small grid // // Columns, offsets, pushes, and pulls for extra small devices like // smartphones. .make-grid(xs); // Small grid // // Columns, offsets, pushes, and pulls for the small device range, from phones // to tablets. @media (min-width: @screen-sm-min) { .make-grid(sm); } // Medium grid // // Columns, offsets, pushes, and pulls for the desktop device range. @media (min-width: @screen-md-min) { .make-grid(md); } // Large grid // // Columns, offsets, pushes, and pulls for the large desktop device range. @media (min-width: @screen-lg-min) { .make-grid(lg); } ================================================ FILE: server/public/css/lib/bootstrap/input-groups.less ================================================ // // Input groups // -------------------------------------------------- // Base styles // ------------------------- .input-group { position: relative; // For dropdowns display: table; border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table // Undo padding and float of grid classes &[class*="col-"] { float: none; padding-left: 0; padding-right: 0; } .form-control { // Ensure that the input is always above the *appended* addon button for // proper border colors. position: relative; z-index: 2; // IE9 fubars the placeholder attribute in text inputs and the arrows on // select elements in input groups. To fix it, we float the input. Details: // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 float: left; width: 100%; margin-bottom: 0; } } // Sizing options // // Remix the default form control sizing classes into new ones for easier // manipulation. .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { .input-lg(); } .input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { .input-sm(); } // Display as table-cell // ------------------------- .input-group-addon, .input-group-btn, .input-group .form-control { display: table-cell; &:not(:first-child):not(:last-child) { border-radius: 0; } } // Addon and addon wrapper for buttons .input-group-addon, .input-group-btn { width: 1%; white-space: nowrap; vertical-align: middle; // Match the inputs } // Text input groups // ------------------------- .input-group-addon { padding: @padding-base-vertical @padding-base-horizontal; font-size: @font-size-base; font-weight: normal; line-height: 1; color: @input-color; text-align: center; background-color: @input-group-addon-bg; border: 1px solid @input-group-addon-border-color; border-radius: @border-radius-base; // Sizing &.input-sm { padding: @padding-small-vertical @padding-small-horizontal; font-size: @font-size-small; border-radius: @border-radius-small; } &.input-lg { padding: @padding-large-vertical @padding-large-horizontal; font-size: @font-size-large; border-radius: @border-radius-large; } // Nuke default margins from checkboxes and radios to vertically center within. input[type="radio"], input[type="checkbox"] { margin-top: 0; } } // Reset rounded corners .input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group-btn:last-child > .btn-group:not(:last-child) > .btn { .border-right-radius(0); } .input-group-addon:first-child { border-right: 0; } .input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:first-child > .btn-group:not(:first-child) > .btn { .border-left-radius(0); } .input-group-addon:last-child { border-left: 0; } // Button input groups // ------------------------- .input-group-btn { position: relative; // Jankily prevent input button groups from wrapping with `white-space` and // `font-size` in combination with `inline-block` on buttons. font-size: 0; white-space: nowrap; // Negative margin for spacing, position for bringing hovered/focused/actived // element above the siblings. > .btn { position: relative; + .btn { margin-left: -1px; } // Bring the "active" button to the front &:hover, &:focus, &:active { z-index: 2; } } // Negative margin to only have a 1px border between the two &:first-child { > .btn, > .btn-group { margin-right: -1px; } } &:last-child { > .btn, > .btn-group { margin-left: -1px; } } } ================================================ FILE: server/public/css/lib/bootstrap/jumbotron.less ================================================ // // Jumbotron // -------------------------------------------------- .jumbotron { padding: @jumbotron-padding; margin-bottom: @jumbotron-padding; color: @jumbotron-color; background-color: @jumbotron-bg; h1, .h1 { color: @jumbotron-heading-color; } p { margin-bottom: (@jumbotron-padding / 2); font-size: @jumbotron-font-size; font-weight: 200; } .container & { border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container } .container { max-width: 100%; } @media screen and (min-width: @screen-sm-min) { padding-top: (@jumbotron-padding * 1.6); padding-bottom: (@jumbotron-padding * 1.6); .container & { padding-left: (@jumbotron-padding * 2); padding-right: (@jumbotron-padding * 2); } h1, .h1 { font-size: (@font-size-base * 4.5); } } } ================================================ FILE: server/public/css/lib/bootstrap/labels.less ================================================ // // Labels // -------------------------------------------------- .label { display: inline; padding: .2em .6em .3em; font-size: 75%; font-weight: bold; line-height: 1; color: @label-color; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; // Add hover effects, but only for links &[href] { &:hover, &:focus { color: @label-link-hover-color; text-decoration: none; cursor: pointer; } } // Empty labels collapse automatically (not available in IE8) &:empty { display: none; } // Quick fix for labels in buttons .btn & { position: relative; top: -1px; } } // Colors // Contextual variations (linked labels get darker on :hover) .label-default { .label-variant(@label-default-bg); } .label-primary { .label-variant(@label-primary-bg); } .label-success { .label-variant(@label-success-bg); } .label-info { .label-variant(@label-info-bg); } .label-warning { .label-variant(@label-warning-bg); } .label-danger { .label-variant(@label-danger-bg); } ================================================ FILE: server/public/css/lib/bootstrap/list-group.less ================================================ // // List groups // -------------------------------------------------- // Base class // // Easily usable on