diff --git a/.babel_cache/.gitkeep b/.babel_cache/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 716bd20a3623f09e7214bffe1b89c9cfc8deb4d5..0000000000000000000000000000000000000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "resources/frontend_client/app/bower_components" -} diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000000000000000000000000000000000..150f92b28905693528e2ccee5f0b48eb7d976f61 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,42 @@ +{ + "parser": "babel-eslint", + "rules": { + "quotes": 0, + "camelcase": 0, + "no-unused-vars": 0, + "eqeqeq": 0, + "key-spacing": 0, + "no-underscore-dangle": 0, + "curly": 0, + "no-use-before-define": 0, + "comma-dangle": 0, + "space-infix-ops": 0, + "no-shadow": 0, + "no-empty": 0, + "no-extra-bind": 0, + "eol-last": 0, + "consistent-return": 0, + "yoda": 0, + "no-multi-spaces": 0, + "no-mixed-spaces-and-tabs": 0, + "no-alert": 0, + "dot-notation": 0, + "space-unary-ops": 0, + "semi": 0, + "no-console": 0, + "global-strict": 0 + }, + "plugins": [ + "react" + ], + "ecmaFeatures": { + "jsx": true + }, + "globals": { + "angular": false, + "React": false + }, + "env": { + "browser": true + } +} diff --git a/.gitignore b/.gitignore index 041c862dafb25eb7ee988e2dace5e1cbc17a2f54..8308bd954d53b85be3743c03d1dac283deb64bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ profiles.clj /*.mv.db /*.lock.db /*.trace.db -/resources/frontend_client/app/bower_components /resources/frontend_client/app/dist/ /node_modules/ /.js_hint_output/ +/.babel_cache diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 5fec253525d7255e86fd5584711d3857cdf694c0..0000000000000000000000000000000000000000 --- a/.jshintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "globalstrict": true, - "globals": { - "angular": false, - "console": false, - "React": false, - "Set": false - } -} diff --git a/Gulpfile.js b/Gulpfile.js deleted file mode 100644 index 26fe35a6e304e708dd42e534cb5763688c2b9670..0000000000000000000000000000000000000000 --- a/Gulpfile.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -var gulp = require('gulp'), - concat = require('gulp-concat'), - react = require('gulp-react'), - myth = require('gulp-myth'); - -var basePath = 'resources/frontend_client/app/'; - -var SRC = { - css: [basePath + 'css/**/*.css', basePath + 'components/**/*.css'], - jsx: [basePath + 'query_builder/*.js'], - appJS: [basePath + '**/*.js', '!' + basePath + 'bower_components/**/*.js', '!' + basePath + 'dist/*.js', '!' + basePath + 'query_builder/*.js', '!' + basePath + '/test/**/*.js'] -}; - -var DEST = { - css: '' + basePath + '/dist', - js: '' + basePath + '/dist', -}; - - -/* - CSS compilation - 1. get all css files in components directory specified in SRC.css - -------------------- - this way we don't need to have a single 'app.css' or similar - to combine all our css (our build system should do this for us) - - 2. run the css through myth to generate browse compatible css - 3. minify the css - 4. write to DEST.css directory -*/ - -gulp.task('css', function(){ - return gulp.src(SRC.css) - .pipe(concat('corvus.css')) - .pipe(myth()) - .pipe(gulp.dest(DEST.css)); -}); - -gulp.task('jsx', function () { - return gulp.src(SRC.jsx) - .pipe(concat('query_builder.js')) - .pipe(react()) - .pipe(gulp.dest(DEST.js)); -}); - -gulp.task('build-js', function () { - return gulp.src(SRC.appJS) - .pipe(concat('app.js')) - .pipe(gulp.dest(DEST.js)); -}); - -gulp.task('watch', function(){ - gulp.watch(SRC.css, ['css']); - gulp.watch(SRC.jsx, ['jsx']); - gulp.watch(SRC.appJS, ['build-js']); -}); - -gulp.task('build', ['css', 'jsx', 'build-js']); - -gulp.task('default', ['build', 'watch']); diff --git a/bower.json b/bower.json deleted file mode 100644 index c78fffe3006d97a75d4ec8b34b324c1d3f8b4f9e..0000000000000000000000000000000000000000 --- a/bower.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "corvus", - "description": "Expa Analytics", - "version": "0.0.0", - "homepage": "https://github.com/expa/data", - "license": "private", - "private": true, - "dependencies": { - "ace-builds": "1.1.7", - "angular": "1.2.26", - "angular-animate": "1.2.26", - "angular-bootstrap": "0.11.0", - "angular-cookie": "4.0.6", - "angular-cookies": "1.2.26", - "angular-gridster": "0.11.7", - "angular-http-auth": "1.2.1", - "angular-loader": "1.2.26", - "angular-mocks": "1.2.26", - "angular-readable-time": "~0.1.1", - "angular-resource": "1.2.26", - "angular-route": "1.2.26", - "angular-sanitize": "1.2.26", - "angular-ui-ace": "0.1.1", - "angular-xeditable": "0.1.8", - "angularytics": "0.3.0", - "crossfilter": "1.3.11", - "d3": "3.5.3", - "dc.js": "2.0.0-beta.1", - "fastclick": "~1.0.3", - "jquery": "2.1.1", - "moment": "~2.9.0", - "ng-sortable": "1.1.6", - "ngReact": "~0.1.3", - "react": "~0.12.2", - "react-date-picker": "~0.5.1", - "react-onclickoutside": "~0.2.2", - "underscore": "1.8.2" - }, - "resolutions": { - "react-onclickoutside": "~0.2.2", - "moment": "2.8.2" - } -} diff --git a/docs/DEVELOPERS.md b/docs/DEVELOPERS.md index 3c3fdbe5be1957277d6b92a7b487d0886e1fc643..211e86ef6d1997390cc244addc994dd56fa28e7b 100644 --- a/docs/DEVELOPERS.md +++ b/docs/DEVELOPERS.md @@ -9,20 +9,21 @@ ## Build -Install clojure + npm/bower requirements with +Install clojure + npm requirements with lein deps lein npm Build the application JS and CSS with - lein gulp + lein webpack When developing the frontend client, you'll want to watch for changes, -so run the default gulp task. +so run webpack with the '-w' flag. - ./node_modules/gulp/bin/gulp.js + ./node_modules/webpack/bin/webpack.js -w +Note that changes to CSS variables will only be picked up when webpack is restarted. ## Usage diff --git a/lein_tasks/leiningen/gulp.clj b/lein_tasks/leiningen/gulp.clj deleted file mode 100644 index 79769423c704543a30ad6991228da94c036799c0..0000000000000000000000000000000000000000 --- a/lein_tasks/leiningen/gulp.clj +++ /dev/null @@ -1,11 +0,0 @@ -(ns leiningen.gulp - (:use clojure.java.shell)) - - -(defn gulp [projects & args] - ;; TODO - some better validations such as checking that we have gulp available - (println "Running `gulp build` to assemble frontend assets into a better format") - (let [result (sh (str (:root projects) "/node_modules/gulp/bin/gulp.js") "build")] - (if (= 0 (:exit result)) - (println (:out result)) - (println (:err result))))) \ No newline at end of file diff --git a/lein_tasks/leiningen/webpack.clj b/lein_tasks/leiningen/webpack.clj new file mode 100644 index 0000000000000000000000000000000000000000..5b5b5c6884061a1862835375f66221e2fe2a9889 --- /dev/null +++ b/lein_tasks/leiningen/webpack.clj @@ -0,0 +1,11 @@ +(ns leiningen.webpack + (:use clojure.java.shell)) + + +(defn webpack [projects & args] + ;; TODO - some better validations such as checking that we have webpack available + (println "Running `webpack -p` to assemble and minify frontend assets") + (let [result (sh (str (:root projects) "/node_modules/webpack/bin/webpack.js") "-p")] + (if (= 0 (:exit result)) + (println (:out result)) + (println (:err result))))) diff --git a/lint_js.sh b/lint_js.sh index 7048eaebdf1641c969b315e67c2856509ec3f049..23b64e6141a6a556042801dbabc474fa39a1a5e1 100755 --- a/lint_js.sh +++ b/lint_js.sh @@ -2,8 +2,7 @@ # Simple shell script for running jshint and nicely formatting the output :heart_eyes_cat: -JS_HINT=./node_modules/jsxhint/cli.js -JS_HINT_OPTS='--config .jshintrc' +ESLINT='./node_modules/eslint/bin/eslint.js' JS_FILES=`find resources/frontend_client/app -name "*.js" | grep -v bower_components | grep -v 'app/test/' | grep -v '\#' | grep -v 'app/dist/'` BOLD='\033[1;30m' @@ -47,7 +46,7 @@ file_modified () { run_js_lint () { file=$1 output_file=$2 - $JS_HINT $JS_HINT_OPTS $file | perl -pe 's/^.*(line.*)$/$1/' | sort > $output_file + $ESLINT $file | perl -pe 's/^.*(line.*)$/$1/' | sort > $output_file } for file in $JS_FILES; do diff --git a/package.json b/package.json index 1ae2d45295baf1af193a9d009334e91d589e320e..6bee8ff11e8e9c3472e12cbd2d47a822a4922207 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,65 @@ { - "name": "corvus", - "private": true, - "version": "0.0.0", - "description": "Expa Analytics", - "repository": "https://github.com/expa/data", - "license": "private", - "engines": { - "node": "0.10.25" - }, - "dependencies": { - "bower": "1.3.12", - "gulp": "^3.8.8", - "gulp-concat": "^2.4.0", - "gulp-myth": "^1.0.1", - "gulp-jsx": "^0.7.0", - "gulp-react": "^2.0.0" - }, - "devDependencies": { - "http-server": "^0.6.1", - "jsxhint": "0.13.3", - "karma": "~0.10", - "karma-junit-reporter": "^0.2.2", - "protractor": "~0.20.1", - "shelljs": "^0.2.6" - }, - "scripts": { - "postinstall": "./node_modules/bower/bin/bower install --allow-root --config.interactive=false" - } + "name": "metabase", + "private": true, + "version": "0.0.0", + "description": "Metabase Analytics Report Server", + "repository": "https://github.com/metabase/metabase-init", + "license": "private", + "engines": { + "node": "0.10.25" + }, + "dependencies": { + "ace-builds": "git://github.com/ajaxorg/ace-builds", + "angular": "^1.2.28", + "angular-animate": "^1.2.28", + "angular-bootstrap": "^0.12.0", + "angular-cookie": "git://github.com/ivpusic/angular-cookie#v4.0.6", + "angular-cookies": "^1.2.28", + "angular-gridster": "^0.11.7", + "angular-http-auth": "git://github.com/tamlyn/angular-http-auth#1eb4a08d08b400956c0848f84d8fcaa9ad1357a2", + "angular-readable-time": "git://github.com/wildlyinaccurate/angular-readable-time#0.1.1", + "angular-resource": "^1.2.28", + "angular-route": "^1.2.28", + "angular-sanitize": "^1.2.28", + "angular-ui-ace": "^0.2.3", + "angular-xeditable": "git://github.com/vitalets/angular-xeditable#0.1.9", + "angularytics": "^0.3.0", + "crossfilter": "^1.3.11", + "d3": "^3.5.3", + "dc": "^2.0.0-beta.1", + "jquery": "^2.1.4", + "moment": "^2.9.0", + "ng-sortable": "^1.2.0", + "ngreact": "^0.1.5", + "react": "^0.12.2", + "react-datepicker": "^0.5.1", + "react-onclickoutside": "^0.2.2", + "tether": "^0.6.5", + "underscore": "^1.8.3" + }, + "devDependencies": { + "angular-mocks": "^1.2.28", + "babel": "^5.4.7", + "babel-core": "^5.4.7", + "babel-eslint": "^3.1.14", + "babel-loader": "^5.1.3", + "css-loader": "^0.14.4", + "cssnext-loader": "^1.0.1", + "eslint": "^0.22.1", + "eslint-loader": "^0.12.0", + "eslint-plugin-react": "^2.5.0", + "extract-text-webpack-plugin": "^0.8.1", + "glob": "^5.0.10", + "http-server": "^0.6.1", + "karma": "~0.10", + "karma-junit-reporter": "^0.2.2", + "ng-annotate-webpack-plugin": "^0.1.2", + "node-libs-browser": "^0.5.2", + "protractor": "~0.20.1", + "shelljs": "^0.2.6", + "style-loader": "^0.12.3", + "webpack": "^1.9.10", + "webpack-postcss-tools": "^1.1.1" + }, + "scripts": {} } diff --git a/project.clj b/project.clj index 341867fb069e73e03ea3e98daebb72a1f712d270..51708e6f32157fa08641051cf16d59ca8bd102ed 100644 --- a/project.clj +++ b/project.clj @@ -87,4 +87,4 @@ "-Dmb.jetty.port=3001" "-Dmb.api.key=test-api-key"]} :uberjar {:aot :all - :prep-tasks ["npm" "gulp" "javac" "compile"]}}) + :prep-tasks ["npm" "webpack" "javac" "compile"]}}) diff --git a/resources/frontend_client/app/auth/auth.controllers.js b/resources/frontend_client/app/auth/auth.controllers.js index 4d64fa9294137c4119ff867fd0f8e3d485d279d5..c59b37310bb80913b3afcbf3d82d21cd3ddd3eeb 100644 --- a/resources/frontend_client/app/auth/auth.controllers.js +++ b/resources/frontend_client/app/auth/auth.controllers.js @@ -2,7 +2,6 @@ /*jslint browser:true*/ /*jslint devel:true */ /*global _*/ -/*global $*/ var AuthControllers = angular.module('corvus.auth.controllers', [ 'ipCookie', @@ -127,4 +126,4 @@ AuthControllers.controller('PasswordReset', ['$scope', '$routeParams', '$locatio }); }; -}]); \ No newline at end of file +}]); diff --git a/resources/frontend_client/app/card/card.charting.js b/resources/frontend_client/app/card/card.charting.js index 665281431c74145d949ced2acd3eea1957e020ac..edb4c8d709fc24fd9cc5ae6026a1721ed812d0c7 100644 --- a/resources/frontend_client/app/card/card.charting.js +++ b/resources/frontend_client/app/card/card.charting.js @@ -1,6 +1,11 @@ 'use strict'; /*jslint browser:true */ -/*global document,$,jQuery,_,google,d3,crossfilter,dc,console,vs*/ +/*global document,_,google,console,vs*/ + +import $ from 'jquery'; +import crossfilter from 'crossfilter'; +import d3 from 'd3'; +import dc from 'dc'; // ---------------------------------------- TODO - Maybe. Lots of these things never worked in the first place. ---------------------------------------- // IMPORTANT @@ -565,7 +570,7 @@ function GeoHeatmapChartRenderer(id, card, result) { }); } -var CardRenderer = { +export var CardRenderer = { /// get the size render settings for card if applicable _getSizeSettings: function(cardOrDimension) { if (typeof cardOrDimension === "object") { diff --git a/resources/frontend_client/app/card/card.controllers.js b/resources/frontend_client/app/card/card.controllers.js index 2d44aff654d1da7288a5eb35ddd30acd24b9d27d..e6909bf262e27ed89314736b477d5fb9ba86e84c 100644 --- a/resources/frontend_client/app/card/card.controllers.js +++ b/resources/frontend_client/app/card/card.controllers.js @@ -1,5 +1,10 @@ 'use strict'; -/*global _, document, confirm, QueryHeader, NativeQueryEditor, GuiQueryEditor, ResultQueryEditor, QueryVisualization*/ +/*global _, document, confirm*/ + +import GuiQueryEditor from '../query_builder/gui_query_editor.react'; +import NativeQueryEditor from '../query_builder/native_query_editor.react'; +import QueryHeader from '../query_builder/header.react'; +import QueryVisualization from '../query_builder/visualization.react'; // Card Controllers var CardControllers = angular.module('corvus.card.controllers', []); diff --git a/resources/frontend_client/app/card/card.directives.js b/resources/frontend_client/app/card/card.directives.js index 34b15166e8a34abb78975fb379dad0c5c06062ba..aebb5dfd763885ad6bd886f9e7c60acf7d60c903 100644 --- a/resources/frontend_client/app/card/card.directives.js +++ b/resources/frontend_client/app/card/card.directives.js @@ -1,11 +1,7 @@ 'use strict'; -/*global setTimeout*/ -/*global $*/ -/*global CardRenderer*/ -/* global React */ -/* global document */ -/* global QueryBuilder */ +/*global setTimeout, React */ +import { CardRenderer } from './card.charting'; var CardDirectives = angular.module('corvus.card.directives', []); @@ -458,9 +454,7 @@ CardDirectives.directive('cvLatlongHeatmap', ['CardRenderer', function(CardRende scope.$watch('cvLatlongHeatmap', function(value) { if (value) { - $(function() { - CardRenderer.latlongHeatmap('map-canvas', 'whatever', value); - }); + CardRenderer.latlongHeatmap('map-canvas', 'whatever', value); } }); } diff --git a/resources/frontend_client/app/components/card/card.css b/resources/frontend_client/app/components/card/card.css index 787de6c095e452959f5bf69a7f17b038cc7a937e..85d8ac7cdbb670507c637fbc6d14a2366e8d09bc 100644 --- a/resources/frontend_client/app/components/card/card.css +++ b/resources/frontend_client/app/components/card/card.css @@ -103,4 +103,4 @@ .Dash-card .Card { overflow-y: scroll; -} \ No newline at end of file +} diff --git a/resources/frontend_client/app/components/icons/icons.js b/resources/frontend_client/app/components/icons/icons.js index c461c3639a9ee13535cab8dacdea08086b534e7c..1a162ab3e982fba03bce4f5661aab7d31354d7fb 100644 --- a/resources/frontend_client/app/components/icons/icons.js +++ b/resources/frontend_client/app/components/icons/icons.js @@ -1,5 +1,6 @@ 'use strict'; -/* global ICON_PATHS */ + +import ICON_PATHS from 'metabase/icon_paths'; /* GENERIC ICONS diff --git a/resources/frontend_client/app/controllers.js b/resources/frontend_client/app/controllers.js index a018e9e6d649f008bd5563b643c6d5948fdaf08f..0f963735a2bc8992cb5024cde2097e44bb932f40 100644 --- a/resources/frontend_client/app/controllers.js +++ b/resources/frontend_client/app/controllers.js @@ -1,8 +1,6 @@ 'use strict'; /*jslint browser:true*/ /*jslint devel:true */ -/*global _*/ -/*global $*/ // Global Controllers var CorvusControllers = angular.module('corvus.controllers', ['corvus.services', 'corvus.navbar.directives']); diff --git a/resources/frontend_client/app/css/core/grid.css b/resources/frontend_client/app/css/core/grid.css index 0032e8f30ca7dfdcaa02adb1fc277e4d353eef71..1e40fc9090a46211e7bc4d102add46f85cea0935 100644 --- a/resources/frontend_client/app/css/core/grid.css +++ b/resources/frontend_client/app/css/core/grid.css @@ -70,7 +70,7 @@ flex: 0 0 25%; } -@media (break-sm) { +@media (--breakpoint-min-sm) { .small-Grid--fit > .Grid-cell { flex: 1; } @@ -88,7 +88,7 @@ } } -@media (--break-md) { +@media (--breakpoint-min-md) { .med-Grid--fit > .Grid-cell { flex: 1; } @@ -106,7 +106,7 @@ } } -@media (--break-lg) { +@media (--breakpoint-min-lg) { .large-Grid--fit > .Grid-cell { flex: 1; } @@ -145,7 +145,7 @@ padding: 2em 0 0 2em; } -@media (break-sm) { +@media (--breakpoint-min-sm) { .small-Grid--gutters { margin: -1em 0 1em -1em; } @@ -166,7 +166,7 @@ } } -@media (--break-md) { +@media (--breakpoint-min-md) { .med-Grid--gutters { margin: -1em 0 1em -1em; } @@ -187,7 +187,7 @@ } } -@media (--break-lg) { +@media (--breakpoint-min-lg) { .large-Grid--gutters { margin: -1em 0 1em -1em; } diff --git a/resources/frontend_client/app/directives.js b/resources/frontend_client/app/directives.js index 6cd4f2971e846a3a01f19e485236deb955a75901..a2325fe0f7b62a991b09d816b39501110ea80ec6 100644 --- a/resources/frontend_client/app/directives.js +++ b/resources/frontend_client/app/directives.js @@ -1,14 +1,10 @@ +'use strict'; + /*jslint browser:true */ /*jslint devel:true */ -/*global _*/ -/*global CardRenderer*/ -/*global $*/ /*global ace*/ -'use strict'; /* Directives */ - - var CorvusDirectives = angular.module('corvus.directives', []); CorvusDirectives.directive('deleteConfirm', [function() { diff --git a/resources/frontend_client/app/icon_paths.js b/resources/frontend_client/app/icon_paths.js index 4a4543178d7164396c321c8446b98db309ff9899..1672bbf40f8dfc4f779752512864c871ee2034ed 100644 --- a/resources/frontend_client/app/icon_paths.js +++ b/resources/frontend_client/app/icon_paths.js @@ -11,7 +11,7 @@ */ -var ICON_PATHS = { +export default { add: 'M19,13 L19,2 L14,2 L14,13 L2,13 L2,18 L14,18 L14,30 L19,30 L19,18 L30,18 L30,13 L19,13 Z', addtodash: [ 'M16,31 L16,31 C24.2842712,31 31,24.2842712 31,16 C31,7.71572875 24.2842712,1 16,1 C7.71572875,1 1,7.71572875 1,16 C1,24.2842712 7.71572875,31 16,31 L16,31 Z M16,32 L16,32 C7.163444,32 0,24.836556 0,16 C0,7.163444 7.163444,0 16,0 C24.836556,0 32,7.163444 32,16 C32,24.836556 24.836556,32 16,32 L16,32 Z', @@ -36,5 +36,3 @@ var ICON_PATHS = { search: 'M12 0 A12 12 0 0 0 0 12 A12 12 0 0 0 12 24 A12 12 0 0 0 18.5 22.25 L28 32 L32 28 L22.25 18.5 A12 12 0 0 0 24 12 A12 12 0 0 0 12 0 M12 4 A8 8 0 0 1 12 20 A8 8 0 0 1 12 4 ', star: 'M16 0 L21 11 L32 12 L23 19 L26 31 L16 25 L6 31 L9 19 L0 12 L11 11', }; - -/* module.exports = ICON_PATHS */ diff --git a/resources/frontend_client/app/js/google_maps.js b/resources/frontend_client/app/js/google_maps.js index 6ab79307fa5a1431cddddc8ba346a385bec1da2e..95eef295681bfebaa7009892742a41d3bce9b7e8 100644 --- a/resources/frontend_client/app/js/google_maps.js +++ b/resources/frontend_client/app/js/google_maps.js @@ -134,4 +134,4 @@ google.maps = google.maps || {}; }; var loadScriptTime = (new Date()).getTime(); getScript("https://maps.gstatic.com/cat_js/intl/en_us/mapfiles/api-3/17/7/%7Bmain,visualization%7D.js"); -})(); \ No newline at end of file +})(); diff --git a/resources/frontend_client/app/query_builder/action_button.react.js b/resources/frontend_client/app/query_builder/action_button.react.js index f8df8975be10621f6f52845f5444b636de8e0d3f..db1c7627cae952809df5a1c8d839d85aced09aee 100644 --- a/resources/frontend_client/app/query_builder/action_button.react.js +++ b/resources/frontend_client/app/query_builder/action_button.react.js @@ -1,7 +1,11 @@ 'use strict'; -/*global cx, setTimeout, clearTimeout, OnClickOutside, SelectionModule, Icon*/ +/*global setTimeout, clearTimeout*/ -var ActionButton = React.createClass({ +import Icon from './icon.react'; + +var cx = React.addons.classSet; + +export default React.createClass({ displayName: 'ActionButton', propTypes: { actionFn: React.PropTypes.func.isRequired diff --git a/resources/frontend_client/app/query_builder/add_to_dashboard.react.js b/resources/frontend_client/app/query_builder/add_to_dashboard.react.js index a278fb754bff897c7610b277fac4a5363c927e3f..e8f5ba03e2bedded3830667eec85cd42292704e6 100644 --- a/resources/frontend_client/app/query_builder/add_to_dashboard.react.js +++ b/resources/frontend_client/app/query_builder/add_to_dashboard.react.js @@ -1,7 +1,12 @@ 'use strict'; -/*global cx, OnClickOutside, Popover, AddToDashboardPopover, SelectionModule, Icon, ReactCSSTransitionGroup*/ -var AddToDashboard = React.createClass({ +import AddToDashboardPopover from './add_to_dashboard_popover.react'; +import Icon from './icon.react'; +import Popover from './popover.react'; + +var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + +export default React.createClass({ displayName: 'AddToDashboard', propTypes: { card: React.PropTypes.object.isRequired, diff --git a/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js b/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js index 928a25a130cea7d09f90f8edad5959be4753f05a..ba6fa05e63ac2590798fead55605a647b0996299 100644 --- a/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js +++ b/resources/frontend_client/app/query_builder/add_to_dashboard_popover.react.js @@ -1,7 +1,14 @@ 'use strict'; -/*global cx, ReactCSSTransitionGroup, OnClickOutside, FormField, SelectionModule, Icon */ -var AddToDashboardPopover = React.createClass({ +import OnClickOutside from 'react-onclickoutside'; + +import FormField from './form_field.react'; +import Icon from './icon.react'; + +var cx = React.addons.classSet; +var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + +export default React.createClass({ displayName: 'AddToDashboardPopover', propTypes: { card: React.PropTypes.object.isRequired, diff --git a/resources/frontend_client/app/query_builder/aggregation_widget.react.js b/resources/frontend_client/app/query_builder/aggregation_widget.react.js index 04f9a76f94c51f7e597d25a801a243bb82dc1d95..6cdd2cb319a3953072e656041047c1d56140da20 100644 --- a/resources/frontend_client/app/query_builder/aggregation_widget.react.js +++ b/resources/frontend_client/app/query_builder/aggregation_widget.react.js @@ -1,7 +1,9 @@ 'use strict'; -/*global _, DateFilter, SelectionModule, Icon */ +/*global _ */ -var AggregationWidget = React.createClass({ +import SelectionModule from './selection_module.react'; + +export default React.createClass({ displayName: 'AggregationWidget', propTypes: { aggregation: React.PropTypes.array.isRequired, diff --git a/resources/frontend_client/app/query_builder/database_selector.react.js b/resources/frontend_client/app/query_builder/database_selector.react.js index 8509c7629b1f0f6b65f7d9d60b27927e4cd57ded..656dceed37b1b631b78558770bd23c42c28a74d1 100644 --- a/resources/frontend_client/app/query_builder/database_selector.react.js +++ b/resources/frontend_client/app/query_builder/database_selector.react.js @@ -1,7 +1,8 @@ 'use strict'; -/*global SelectionModule*/ -var DatabaseSelector = React.createClass({ +import SelectionModule from './selection_module.react'; + +export default React.createClass({ displayName: 'DatabaseSelector', propTypes: { currentDatabaseId: React.PropTypes.number.isRequired, diff --git a/resources/frontend_client/app/query_builder/date_filter.react.js b/resources/frontend_client/app/query_builder/date_filter.react.js index e0da7715a8753ee781d3b226e97ddb0cf36719f6..da331ad2f6389cfe7442556a7b2273b8a99f9101 100644 --- a/resources/frontend_client/app/query_builder/date_filter.react.js +++ b/resources/frontend_client/app/query_builder/date_filter.react.js @@ -1,7 +1,18 @@ 'use strict'; -/*global moment, DatePicker*/ -var DateFilter = React.createClass({ +/*global window*/ + +// import compiled version, webpack doesn't seem to be running JSX transforms on node_modules +// css imported in init.css +import DatePicker from 'react-datepicker'; +import Tether from 'tether'; +import moment from 'moment'; + +// DatePicker depedencies :( +window.Tether = Tether; +window.moment = moment; + +export default React.createClass({ displayName: 'DateFilter', propTypes: { date: React.PropTypes.string, diff --git a/resources/frontend_client/app/query_builder/filter_widget.react.js b/resources/frontend_client/app/query_builder/filter_widget.react.js index c8e9c077a1fa790247381ae9dc83b2898307059c..0caccd461660dd35ec1ab935c9574ee1cc560f7c 100644 --- a/resources/frontend_client/app/query_builder/filter_widget.react.js +++ b/resources/frontend_client/app/query_builder/filter_widget.react.js @@ -1,7 +1,10 @@ 'use strict'; -/*global DateFilter, SelectionModule, Icon */ -var FilterWidget = React.createClass({ +import DateFilter from './date_filter.react'; +import Icon from './icon.react'; +import SelectionModule from './selection_module.react'; + +export default React.createClass({ displayName: 'FilterWidget', propTypes: { filter: React.PropTypes.array.isRequired, diff --git a/resources/frontend_client/app/query_builder/form_field.react.js b/resources/frontend_client/app/query_builder/form_field.react.js index 7020380f7d9cfee544fb08c4cbf9dbcad59febd2..1aa6e030ab8ee7d4929f373f0dc4cc5de00b1022 100644 --- a/resources/frontend_client/app/query_builder/form_field.react.js +++ b/resources/frontend_client/app/query_builder/form_field.react.js @@ -1,7 +1,8 @@ 'use strict'; -/*global cx, OnClickOutside, SelectionModule*/ -var FormField = React.createClass({ +var cx = React.addons.classSet; + +export default React.createClass({ displayName: 'FormField', propTypes: { fieldName: React.PropTypes.string.isRequired, diff --git a/resources/frontend_client/app/query_builder/gui_query_editor.react.js b/resources/frontend_client/app/query_builder/gui_query_editor.react.js index 623745a775dcb6289c545af219d1c9525ce983d5..861da034cb06a48a2df612d95bc0cb31310b1059 100644 --- a/resources/frontend_client/app/query_builder/gui_query_editor.react.js +++ b/resources/frontend_client/app/query_builder/gui_query_editor.react.js @@ -1,9 +1,19 @@ 'use strict'; -/*global _, cx, AggregationWidget, FilterWidget, LimitWidget, SortWidget, RunButton, SelectionModule, DatabaseSelector, Icon*/ - +/*global _*/ + +import AggregationWidget from './aggregation_widget.react'; +import DatabaseSelector from './database_selector.react'; +import FilterWidget from './filter_widget.react'; +import Icon from './icon.react'; +import LimitWidget from './limit_widget.react'; +import RunButton from './run_button.react'; +import SelectionModule from './selection_module.react'; +import SortWidget from './sort_widget.react'; + +var cx = React.addons.classSet; var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; -var GuiQueryEditor = React.createClass({ +export default React.createClass({ displayName: 'GuiQueryEditor', propTypes: { databases: React.PropTypes.array.isRequired, @@ -111,11 +121,10 @@ var GuiQueryEditor = React.createClass({ }, canRun: function() { - var canRun = false; if (this.hasValidAggregation()) { - canRun = true; + return true; } - return canRun; + return false; }, runQuery: function() { diff --git a/resources/frontend_client/app/query_builder/header.react.js b/resources/frontend_client/app/query_builder/header.react.js index 6e33db8b1832df22c4da2178b2e1588aa2096502..98b1860910209d379893a34366c3809707ee88e3 100644 --- a/resources/frontend_client/app/query_builder/header.react.js +++ b/resources/frontend_client/app/query_builder/header.react.js @@ -1,10 +1,16 @@ 'use strict'; -/*global setTimeout, clearTimeout, Saver, ActionButton, Popover, Icon, QueryModeToggle, AddToDashboard*/ +/*global setTimeout, clearTimeout*/ -var cx = React.addons.classSet, - ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; +import ActionButton from './action_button.react'; +import AddToDashboard from './add_to_dashboard.react'; +import Icon from './icon.react'; +import Popover from './popover.react'; +import QueryModeToggle from './query_mode_toggle.react'; +import Saver from './saver.react'; -var QueryHeader = React.createClass({ +var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + +export default React.createClass({ displayName: 'QueryHeader', propTypes: { card: React.PropTypes.object.isRequired, diff --git a/resources/frontend_client/app/query_builder/icon.react.js b/resources/frontend_client/app/query_builder/icon.react.js index d94d63977200bff59d46a33dbf59f3fdc2d0a2ca..0a639c1f901e152070dd13397c2f734327ff6b4b 100644 --- a/resources/frontend_client/app/query_builder/icon.react.js +++ b/resources/frontend_client/app/query_builder/icon.react.js @@ -1,7 +1,8 @@ 'use strict'; -/* global ICON_PATHS */ -var Icon = React.createClass({ +import ICON_PATHS from 'metabase/icon_paths'; + +export default React.createClass({ displayName: 'Icon', getDefaultProps: function () { return { diff --git a/resources/frontend_client/app/query_builder/limit_widget.react.js b/resources/frontend_client/app/query_builder/limit_widget.react.js index 0c15923f65800567fea229fc294543dc967cbf77..d5988296fc47118205290d80003c1b892df90a83 100644 --- a/resources/frontend_client/app/query_builder/limit_widget.react.js +++ b/resources/frontend_client/app/query_builder/limit_widget.react.js @@ -1,7 +1,9 @@ 'use strict'; -/*global DateFilter, SelectionModule, Icon */ -var LimitWidget = React.createClass({ +import Icon from './icon.react'; +import SelectionModule from './selection_module.react'; + +export default React.createClass({ displayName: 'LimitWidget', propTypes: { limit: React.PropTypes.number, diff --git a/resources/frontend_client/app/query_builder/native_query_editor.react.js b/resources/frontend_client/app/query_builder/native_query_editor.react.js index d35eb3bb95ea48a144d9874390f7ff2aaba2e91b..fe1d8f7044d98adab32baeb2d640eaa2043cae36 100644 --- a/resources/frontend_client/app/query_builder/native_query_editor.react.js +++ b/resources/frontend_client/app/query_builder/native_query_editor.react.js @@ -1,7 +1,10 @@ 'use strict'; -/*global ace, RunButton, SelectionModule, DatabaseSelector*/ +/*global ace*/ -var NativeQueryEditor = React.createClass({ +import RunButton from './run_button.react'; +import DatabaseSelector from './database_selector.react'; + +export default React.createClass({ displayName: 'NativeQueryEditor', propTypes: { databases: React.PropTypes.array.isRequired, diff --git a/resources/frontend_client/app/query_builder/popover.react.js b/resources/frontend_client/app/query_builder/popover.react.js index 50dcd34c869f772f2e35bc530caead762d73d436..f299b87c1d2e80f53917e8d200cb37cbe9b92a64 100644 --- a/resources/frontend_client/app/query_builder/popover.react.js +++ b/resources/frontend_client/app/query_builder/popover.react.js @@ -1,7 +1,7 @@ 'use strict'; -/*global document, cx, Tether*/ +/*global document, Tether*/ -var Popover = React.createClass({ +export default React.createClass({ displayName: 'Popover', componentWillMount: function() { @@ -72,4 +72,4 @@ var Popover = React.createClass({ render: function() { return <span/>; } -}); \ No newline at end of file +}); diff --git a/resources/frontend_client/app/query_builder/popover_content.react.js b/resources/frontend_client/app/query_builder/popover_content.react.js index fdd21f52359d6ea15295d75ecdb524b93486a2b6..e4967684e9c2ce6d774958356e1ab6e4a0450859 100644 --- a/resources/frontend_client/app/query_builder/popover_content.react.js +++ b/resources/frontend_client/app/query_builder/popover_content.react.js @@ -1,10 +1,11 @@ 'use strict'; -/*global cx, OnClickOutside, Popover, AddToDashboardPopover, SelectionModule, AddToDashIcon, ReactCSSTransitionGroup*/ + +import OnClickOutside from 'react-onclickoutside'; // this feels a little silly, but we have this component ONLY so that we can add the OnClickOutside functionality on an // arbitrary set of html content. I wish we could do that more easily -var PopoverContent = React.createClass({ +export default React.createClass({ displayName: 'PopoverContent', mixins: [OnClickOutside], diff --git a/resources/frontend_client/app/query_builder/popover_with_trigger.react.js b/resources/frontend_client/app/query_builder/popover_with_trigger.react.js index b4935c4b3e9c69b18a75c1d88c34b5b1edcfcc55..050c56a1cc54cb0c184fdcec6191b570d75589f2 100644 --- a/resources/frontend_client/app/query_builder/popover_with_trigger.react.js +++ b/resources/frontend_client/app/query_builder/popover_with_trigger.react.js @@ -1,7 +1,9 @@ 'use strict'; -/*global document, cx, PopoverContent, Tether*/ +/*global document, Tether*/ -var PopoverWithTrigger = React.createClass({ +import PopoverContent from './popover_content.react' + +export default React.createClass({ displayName: 'PopoverWithTrigger', getInitialState: function() { @@ -99,4 +101,4 @@ var PopoverWithTrigger = React.createClass({ </span> ); } -}); \ No newline at end of file +}); diff --git a/resources/frontend_client/app/query_builder/query_mode_toggle.react.js b/resources/frontend_client/app/query_builder/query_mode_toggle.react.js index 4314f8644e37ef0c7499638612888eca40fe747b..58af8cb40db06aecacbcc561ff9d360ee488d31a 100644 --- a/resources/frontend_client/app/query_builder/query_mode_toggle.react.js +++ b/resources/frontend_client/app/query_builder/query_mode_toggle.react.js @@ -1,7 +1,10 @@ 'use strict'; -/*global cx, OnClickOutside, SelectionModule*/ -var QueryModeToggle = React.createClass({ +import SelectionModule from './selection_module.react'; + +var cx = React.addons.classSet; + +export default React.createClass({ displayName: 'QueryModeToggle', propTypes: { currentQueryMode: React.PropTypes.string.isRequired, diff --git a/resources/frontend_client/app/query_builder/run_button.react.js b/resources/frontend_client/app/query_builder/run_button.react.js index 6b7a2e2e3345c814aaf582dc3fc084eeb72413a4..1fcfd7e49af7ea950e1e2390561bead3d8185436 100644 --- a/resources/frontend_client/app/query_builder/run_button.react.js +++ b/resources/frontend_client/app/query_builder/run_button.react.js @@ -1,6 +1,6 @@ 'use strict'; -var RunButton = React.createClass({ +export default React.createClass({ displayName: 'RunButton', propTypes: { canRun: React.PropTypes.bool.isRequired, diff --git a/resources/frontend_client/app/query_builder/saver.react.js b/resources/frontend_client/app/query_builder/saver.react.js index e88d5890feaa5366f934e16e204a9fa87756fc71..d72f6fe5ff84e6027b6c93a0d8788ad3629ad53c 100644 --- a/resources/frontend_client/app/query_builder/saver.react.js +++ b/resources/frontend_client/app/query_builder/saver.react.js @@ -1,7 +1,12 @@ 'use strict'; -/*global cx, OnClickOutside, FormField, SelectionModule*/ -var Saver = React.createClass({ +import OnClickOutside from 'react-onclickoutside'; + +import FormField from './form_field.react'; + +var cx = React.addons.classSet; + +export default React.createClass({ displayName: 'Saver', propTypes: { card: React.PropTypes.object.isRequired, diff --git a/resources/frontend_client/app/query_builder/search_bar.react.js b/resources/frontend_client/app/query_builder/search_bar.react.js index 94249d84cbe78af145fb0df87c9507f48b5b0ad2..ca9c126475417d50dfb27cab589f7333c7f72054 100644 --- a/resources/frontend_client/app/query_builder/search_bar.react.js +++ b/resources/frontend_client/app/query_builder/search_bar.react.js @@ -1,6 +1,6 @@ 'use strict'; -var SearchBar = React.createClass({ +export default React.createClass({ displayName: 'SearchBar', propTypes: { filter: React.PropTypes.string.isRequired, diff --git a/resources/frontend_client/app/query_builder/selection_module.react.js b/resources/frontend_client/app/query_builder/selection_module.react.js index b84e4be1cd9de048da51f704d30859d6ceb76fc9..19a4c4138731660d7877c0f0a8d75ade74b04dad 100644 --- a/resources/frontend_client/app/query_builder/selection_module.react.js +++ b/resources/frontend_client/app/query_builder/selection_module.react.js @@ -1,7 +1,13 @@ 'use strict'; -/*global cx, OnClickOutside, SearchBar, Icon*/ -var SelectionModule = React.createClass({ +import OnClickOutside from 'react-onclickoutside'; + +import Icon from './icon.react'; +import SearchBar from './search_bar.react'; + +var cx = React.addons.classSet; + +export default React.createClass({ displayName:'SelectionModule', propTypes: { action: React.PropTypes.func.isRequired, diff --git a/resources/frontend_client/app/query_builder/sort_widget.react.js b/resources/frontend_client/app/query_builder/sort_widget.react.js index 4466ab05119c9ced5eda8f8f2df6dd07d54ac43d..acfc609f28ab58b6cff63bd06c29202ac9700eda 100644 --- a/resources/frontend_client/app/query_builder/sort_widget.react.js +++ b/resources/frontend_client/app/query_builder/sort_widget.react.js @@ -1,7 +1,10 @@ 'use strict'; -/*global DateFilter, SelectionModule, Icon */ -var SortWidget = React.createClass({ +import DateFilter from './date_filter.react'; +import Icon from './icon.react'; +import SelectionModule from './selection_module.react'; + +export default React.createClass({ displayName: 'SortWidget', propTypes: { sort: React.PropTypes.array.isRequired, diff --git a/resources/frontend_client/app/query_builder/visualization.react.js b/resources/frontend_client/app/query_builder/visualization.react.js index a2e8a77a3bc6ece9ce8a1ee93be5abd3d90ebb81..7645a2977d866e7d43d518d10a72518f6d3026c7 100644 --- a/resources/frontend_client/app/query_builder/visualization.react.js +++ b/resources/frontend_client/app/query_builder/visualization.react.js @@ -1,9 +1,14 @@ 'use strict'; -/*global cx, CardRenderer, PopoverWithTrigger, QueryVisualizationTable, QueryVisualizationChart*/ +import { CardRenderer } from '../card/card.charting'; +import PopoverWithTrigger from './popover_with_trigger.react'; +import QueryVisualizationTable from './visualization_table.react'; +import QueryVisualizationChart from './visualization_chart.react'; + +var cx = React.addons.classSet; var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; -var QueryVisualization = React.createClass({ +export default React.createClass({ displayName: 'QueryVisualization', propTypes: { visualizationSettingsApi: React.PropTypes.object.isRequired, diff --git a/resources/frontend_client/app/query_builder/visualization_chart.react.js b/resources/frontend_client/app/query_builder/visualization_chart.react.js index 456f113dfefd6a5fe76aab74592f613682675cad..6b88f34483c3dbc35c7ffd9e32b0fb8ae56a4b39 100644 --- a/resources/frontend_client/app/query_builder/visualization_chart.react.js +++ b/resources/frontend_client/app/query_builder/visualization_chart.react.js @@ -1,7 +1,8 @@ 'use strict'; -/*global CardRenderer*/ -var QueryVisualizationChart = React.createClass({ +import { CardRenderer } from '../card/card.charting'; + +export default React.createClass({ displayName: 'QueryVisualizationChart', propTypes: { visualizationSettingsApi: React.PropTypes.object.isRequired, diff --git a/resources/frontend_client/app/query_builder/visualization_table.react.js b/resources/frontend_client/app/query_builder/visualization_table.react.js index 4be1dda0b45f168336ad8fc3e33679d101a15273..ad1a6cb7eb010b505a3eb660dc10174a31086435 100644 --- a/resources/frontend_client/app/query_builder/visualization_table.react.js +++ b/resources/frontend_client/app/query_builder/visualization_table.react.js @@ -1,7 +1,6 @@ 'use strict'; -/*global */ -var QueryVisualizationTable = React.createClass({ +export default React.createClass({ displayName: 'QueryVisualizationTable', propTypes: { data: React.PropTypes.object diff --git a/resources/frontend_client/index.html b/resources/frontend_client/index.html index f3993006c8efba643e198fef5d6c5d0887cc9650..bd332b8c044fdfbb206096eba2af97dc39c2f5cb 100644 --- a/resources/frontend_client/index.html +++ b/resources/frontend_client/index.html @@ -7,12 +7,11 @@ <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <title>Metabase</title> - <link href="/app/bower_components/angular-xeditable/dist/css/xeditable.css" rel="stylesheet" /> - <link href="/app/bower_components/angular-gridster/dist/angular-gridster.min.css" rel="stylesheet" /> - <link href="/app/bower_components/react-date-picker/react-datepicker.css" rel="stylesheet" /> - <link href="/app/bower_components/dc.js/dc.css" rel="stylesheet" /> - <link rel="stylesheet" href="/app/dist/corvus.css" /> - <script src="/app/bower_components/angular/angular.min.js"></script> + + <link rel="stylesheet" href="/app/dist/styles.css"/> + <script charset="utf-8" src="/app/dist/styles.js"></script> + <script src="/app/dist/vendor.js"></script> + <script src="/app/dist/app.js"></script> </head> <body ng-controller="Corvus"> @@ -99,47 +98,6 @@ <div class="MainContent flex flex-column full-height" ng-view></div> </body> - <!-- JS INCLUDES --> - <!-- vendor js --> - <script src="/app/bower_components/react/react-with-addons.min.js"></script> - <script src="/app/bower_components/react-onclickoutside/index.js"></script> - <script src="/app/bower_components/moment/min/moment.min.js"></script> - <script src="/app/bower_components/tether/tether.min.js"></script> - <script src="/app/bower_components/react-date-picker/react-datepicker.js"></script> - - - <script src="/app/bower_components/jquery/dist/jquery.min.js"></script> - <script src="/app/bower_components/underscore/underscore-min.js"></script> - <script src="/app/bower_components/fastclick/lib/fastclick.js"></script> - - <script src="/app/bower_components/angular-route/angular-route.min.js"></script> - <script src="/app/bower_components/angular-resource/angular-resource.min.js"></script> - <script src="/app/bower_components/angular-cookies/angular-cookies.min.js"></script> - <script src="/app/bower_components/angular-animate/angular-animate.min.js"></script> - <script src="/app/bower_components/angular-sanitize/angular-sanitize.min.js"></script> - <script src="/app/bower_components/angular-xeditable/dist/js/xeditable.min.js"></script> - <script src="/app/bower_components/angular-cookie/angular-cookie.min.js"></script> - <script src="/app/bower_components/javascript-detect-element-resize/detect-element-resize.js"></script> - <script src="/app/bower_components/angular-gridster/dist/angular-gridster.min.js"></script> - <script src="/app/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script> - <script src="/app/bower_components/angular-http-auth/src/http-auth-interceptor.js"></script> - <script src="/app/bower_components/ace-builds/src-min-noconflict/ace.js"></script> - <script src="/app/bower_components/ace-builds/src-min-noconflict/mode-sql.js"></script> - <script src="/app/bower_components/ace-builds/src-min-noconflict/ext-language_tools.js"></script> - <script src="/app/bower_components/angular-ui-ace/ui-ace.min.js"></script> - <script src="/app/bower_components/angularytics/dist/angularytics.min.js"></script> - <script src="/app/bower_components/ng-sortable/dist/ng-sortable.min.js"></script> - <script src="/app/bower_components/angular-readable-time/angular-readable-time.min.js"></script> - - <script src="/app/bower_components/d3/d3.min.js"></script> - <script src="/app/bower_components/crossfilter/crossfilter.min.js"></script> - <script src="/app/bower_components/dc.js/dc.min.js"></script> - - <script src="/app/js/google_maps.js"></script> - - <!-- dist app --> - <script src="/app/dist/app.js"></script> - <script src="/app/dist/query_builder.js" type="text/javascript"></script> <script src="//ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"></script> <script> diff --git a/resources/frontend_client/vendor.css b/resources/frontend_client/vendor.css new file mode 100644 index 0000000000000000000000000000000000000000..1d7c86a15a706a892728c472ad5e2e4cb3307a31 --- /dev/null +++ b/resources/frontend_client/vendor.css @@ -0,0 +1,10 @@ +/* angular 3rd-party */ +@import 'angular-xeditable/dist/css/xeditable.css'; +@import 'angular-gridster/dist/angular-gridster.min.css'; +@import 'ng-sortable/dist/ng-sortable.css'; + +/* d3 */ +@import 'dc/dc.css'; + +/* react */ +@import 'react-datepicker/react-datepicker.css'; diff --git a/resources/frontend_client/vendor.js b/resources/frontend_client/vendor.js new file mode 100644 index 0000000000000000000000000000000000000000..67a47aac61a8913734299bf9bf6fb5034aff178e --- /dev/null +++ b/resources/frontend_client/vendor.js @@ -0,0 +1,36 @@ +/*global window*/ + +"use strict"; + +// angular: +import 'angular'; +import 'angular-animate'; +import 'angular-cookies'; +import 'angular-resource'; +import 'angular-route'; +import 'angular-sanitize'; + +// angular 3rd-party: +import 'angular-bootstrap'; +import 'angular-cookie'; +import 'angular-gridster'; +import 'angular-http-auth'; // currently pulled from unofficial fork: https://github.com/witoldsz/angular-http-auth/pull/100 +import 'angular-readable-time'; +import 'angular-xeditable'; +import 'ng-sortable'; +import 'angularytics'; + +// ace: +import 'angular-ui-ace'; +import 'ace/ace'; +import 'ace/ext-language_tools'; +import 'ace/mode-sql'; +import 'ace/snippets/sql'; + +// react: +import React from 'react'; +window.React = React; + +// misc: +import _ from 'underscore'; +window._ = _; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..66dd2c3b11a2824da13f85cb976c997c3710cfb5 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,129 @@ +"use strict"; +/* global __dirname */ + +var webpack = require('webpack'); +var webpackPostcssTools = require('webpack-postcss-tools'); + +var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin; +var NgAnnotatePlugin = require('ng-annotate-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +var _ = require('underscore'); +var glob = require('glob'); + +var BASE_PATH = __dirname + '/resources/frontend_client/app/'; + +// All JS files except dist and test +var JS_SRC = glob.sync(BASE_PATH + '**/*.js', { ignore: BASE_PATH + '{bower_components,dist,test}/**/*.js' }); +// All CSS files in app/css and app/components +var CSS_SRC = glob.sync(BASE_PATH + 'css/**/*.css').concat(glob.sync(BASE_PATH + 'components/**/*.css')); + +// Need to scan the CSS files for variable and custom media used across files +// NOTE: this requires "webpack -w" (watch mode) to be restarted when variables change :( +console.warn("Warning: in weback watch mode you must restart webpack if you change any CSS variables or custom media queries"); +var cssMaps = { vars: {}, media: {}, selector: {} }; +CSS_SRC.map(webpackPostcssTools.makeVarMap).forEach(function(map) { + for (var name in cssMaps) _.extend(cssMaps[name], map[name]); +}); + +module.exports = { + // output a bundle for the app JS and a bundle for styles + // eventually we should have multiple (single file) entry points for various pieces of the app to enable code splitting + entry: { + vendor: __dirname + '/resources/frontend_client/vendor.js', + app: JS_SRC, + styles: [ + __dirname + '/resources/frontend_client/vendor.css' + ].concat(CSS_SRC) + }, + + // output to "dist" + output: { + path: __dirname + '/resources/frontend_client/app/dist', + filename: '[name].js' + }, + + module: { + loaders: [ + // JavaScript + { test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { cacheDirectory: '.babel_cache' }}, + { test: /\.js$/, exclude: /node_modules/, loader: 'eslint' }, + // CSS + { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?sourceMap!cssnext-loader') } + // { test: /\.css$/, loader: 'style-loader!css-loader!cssnext-loader' } + ], + noParse: [ + /node_modules\/(angular|ng-|ace|react|moment|underscore|jquery|d3|crossfilter)/ // doesn't include 'dc' and 'tether' due to use of 'require' + ] + }, + + resolve: { + modulesDirectories: [], + alias: { + 'metabase': __dirname + '/resources/frontend_client/app', + + // angular + 'angular': __dirname + '/node_modules/angular/angular.min.js', + 'angular-animate': __dirname + '/node_modules/angular-animate/angular-animate.min.js', + 'angular-cookies': __dirname + '/node_modules/angular-cookies/angular-cookies.min.js', + 'angular-resource': __dirname + '/node_modules/angular-resource/angular-resource.min.js', + 'angular-route': __dirname + '/node_modules/angular-route/angular-route.min.js', + 'angular-sanitize': __dirname + '/node_modules/angular-sanitize/angular-sanitize.min.js', + // angular 3rd-party + 'angular-bootstrap': __dirname + '/node_modules/angular-bootstrap/dist/ui-bootstrap-tpls.min.js', + 'angular-cookie': __dirname + '/node_modules/angular-cookie/angular-cookie.min.js', + 'angular-gridster': __dirname + '/node_modules/angular-gridster/dist/angular-gridster.min.js', + 'angular-http-auth': __dirname + '/node_modules/angular-http-auth/src/http-auth-interceptor.js', + 'angular-readable-time':__dirname + '/node_modules/angular-readable-time/angular-readable-time.min.js', + 'angular-xeditable': __dirname + '/node_modules/angular-xeditable/dist/js/xeditable.min.js', + 'ng-sortable': __dirname + '/node_modules/ng-sortable/dist/ng-sortable.min.js', + 'angularytics': __dirname + '/node_modules/angularytics/dist/angularytics.min.js', + 'angular-ui-ace': __dirname + '/node_modules/angular-ui-ace/src/ui-ace.js', + // ace + 'ace/ace': __dirname + '/node_modules/ace-builds/src-min-noconflict/ace.js', + 'ace/ext-language_tools':__dirname+ '/node_modules/ace-builds/src-min-noconflict/ext-language_tools.js', + 'ace/mode-sql': __dirname + '/node_modules/ace-builds/src-min-noconflict/mode-sql.js', + 'ace/snippets/sql': __dirname + '/node_modules/ace-builds/src-min-noconflict/snippets/sql.js', + // react + 'react': __dirname + '/node_modules/react/dist/react-with-addons.js', + 'react-onclickoutside': __dirname + '/node_modules/react-onclickoutside/index.js', + 'react-datepicker': __dirname + '/node_modules/react-datepicker/react-datepicker.js', + 'moment': __dirname + '/node_modules/moment/min/moment.min.js', + 'tether': __dirname + '/node_modules/tether/tether.min.js', + 'underscore': __dirname + '/node_modules/underscore/underscore-min.js', + 'jquery': __dirname + '/node_modules/jquery/dist/jquery.min.js', + 'd3': __dirname + '/node_modules/d3/d3.min.js', + 'crossfilter': __dirname + '/node_modules/crossfilter/crossfilter.min.js', + 'dc': __dirname + '/node_modules/dc/dc.min.js', + } + }, + + plugins: [ + // Automatically annotates angular functions (from "function($foo) {}" to "['$foo', function($foo) {}]") + // so minification doesn't break dependency injections + // new NgAnnotatePlugin({ add: true }), + // Separates out modules common to multiple entry points into a single common file that should be loaded first. + // Not currently useful but necessary for code-splitting + // new CommonsChunkPlugin('vendor', 'vendor.bundle.js'), + // Extracts initial CSS into a standard stylesheet that can be loaded in parallel with JavaScript + new ExtractTextPlugin('styles.css') + ], + + // CSSNext configuration + cssnext: { + features: { + // pass in the variables and custom media we scanned for before + customProperties: { variables: cssMaps.vars }, + customMedia: { extensions: cssMaps.media } + }, + import: { + path: ['resources/frontend_client/app/css'] + } + }, + + // SourceMaps + // Normal source map works better but takes longer to build + // devtool: 'source-map' + // Eval source map doesn't work with CSS but is faster to build + // devtool: 'eval-source-map' +};