├── .gitignore ├── LICENSE ├── README.md ├── dist ├── app.js ├── index.html ├── polyfills.js ├── vendor-3thparty.js └── vendor-ng.js ├── package.json ├── public └── src │ ├── _all-theme.scss │ ├── prebuilt │ ├── deeppurple-amber.scss │ ├── indigo-pink.scss │ ├── pink-bluegrey.scss │ └── purple-green.scss │ └── styles.scss ├── src ├── app │ ├── app-demo │ │ ├── bottom-nav │ │ │ ├── demo-bottom-nav.html │ │ │ ├── demo-bottom-nav.scss │ │ │ └── demo-bottom-nav.ts │ │ ├── datatable │ │ │ ├── demo-datatable.html │ │ │ ├── demo-datatable.scss │ │ │ └── demo-datatable.ts │ │ ├── demo.home.ts │ │ ├── error-messages │ │ │ ├── demo-error-messages.html │ │ │ ├── demo-error-messages.scss │ │ │ └── demo-error-messages.ts │ │ └── fab-speed-dial │ │ │ ├── demo-fab-speed-dial.html │ │ │ ├── demo-fab-speed-dial.scss │ │ │ └── demo-fab-speed-dial.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.routes.ts │ └── shared │ │ ├── component │ │ ├── index.ts │ │ ├── smd-bottom-nav │ │ │ ├── _smd-bottom-nav-theme.scss │ │ │ ├── index.ts │ │ │ ├── smd-bottom-nav.component.html │ │ │ ├── smd-bottom-nav.component.scss │ │ │ └── smd-bottom-nav.component.ts │ │ ├── smd-datatable │ │ │ ├── README.md │ │ │ ├── _datatable-theme.scss │ │ │ ├── datatable.component.html │ │ │ ├── datatable.component.scss │ │ │ ├── datatable.component.ts │ │ │ └── index.ts │ │ ├── smd-error-messages │ │ │ ├── _smd-error-message-theme.scss │ │ │ ├── index.ts │ │ │ ├── smd-error-message.component.scss │ │ │ └── smd-error-message.component.ts │ │ ├── smd-fab-speed-dial │ │ │ ├── README.md │ │ │ ├── fab-speed-dial.scss │ │ │ ├── fab-speed-dial.ts │ │ │ └── index.ts │ │ └── smd-paginator │ │ │ ├── _paginator-theme.scss │ │ │ ├── index.ts │ │ │ ├── paginator.component.html │ │ │ ├── paginator.component.scss │ │ │ └── paginator.component.ts │ │ └── components.module.ts ├── index.html ├── main.ts ├── polyfills.ts ├── vendor-3thparty.ts └── vendor-angular.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.idea/* 3 | /target/ 4 | */target 5 | /target 6 | /bin 7 | 8 | *.settings 9 | *.classpath 10 | *.project 11 | */bin 12 | *rebel.xml 13 | *.iml 14 | .tern-project 15 | *.*~ 16 | node_modules 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jeferson Estevo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Material Design for Angular 2 | 3 | This is a simple project to design angular material components that are not yet implemented on the official angular material repository: 4 | 5 | [Material Design for Angular](https://github.com/angular/material2). 6 | 7 | ### Components 8 | 9 | | Component | Docs | 10 | |-----------------------|--------------| 11 | | Datatable | [README][1] | 12 | | FAB Speed Dial | [README][2] | 13 | 14 | [1]: https://github.com/jefersonestevo/angular-smd/blob/master/src/app/shared/component/smd-datatable/README.md 15 | [2]: https://github.com/jefersonestevo/angular-smd/blob/master/src/app/shared/component/smd-fab-speed-dial/README.md 16 | 17 | ### Demo App 18 | 19 | [Demo App](https://rawgit.com/jefersonestevo/angular-smd/master/dist/index.html) 20 | 21 | ### How to run the project 22 | `git clone https://github.com/jefersonestevo/angular-smd.git` 23 | 24 | `cd angular-smd` 25 | 26 | `npm install` 27 | 28 | `npm start` 29 | 30 | Then you can access [http://localhost:9000](http://localhost:9000) -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Simple Material Design 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Loading... 21 | 22 | 23 | -------------------------------------------------------------------------------- /dist/vendor-3thparty.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | eval("\"use strict\";\n\n__webpack_require__(437);\n__webpack_require__(440);//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvdmVuZG9yLTN0aHBhcnR5LnRzP2Y1MzUiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBTyxvQkFBVTtBQUNWLG9CQUFlIiwiZmlsZSI6IjAuanMiLCJzb3VyY2VzQ29udGVudCI6WyJyZXF1aXJlKFwiZGVidWdcIik7XG5yZXF1aXJlKFwiZGF0ZWZvcm1hdFwiKTtcblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gLi9+L2FuZ3VsYXIyLXRlbXBsYXRlLWxvYWRlciEuL3NyYy92ZW5kb3ItM3RocGFydHkudHMiXSwic291cmNlUm9vdCI6IiJ9"); 7 | 8 | /***/ }, 9 | 10 | /***/ 437: 11 | /***/ function(module, exports, __webpack_require__) { 12 | 13 | eval("/* WEBPACK VAR INJECTION */(function(process) {/**\n * This is the web browser implementation of `debug()`.\n *\n * Expose `debug()` as the module.\n */\n\nexports = module.exports = __webpack_require__(438);\nexports.log = log;\nexports.formatArgs = formatArgs;\nexports.save = save;\nexports.load = load;\nexports.useColors = useColors;\nexports.storage = 'undefined' != typeof chrome\n && 'undefined' != typeof chrome.storage\n ? chrome.storage.local\n : localstorage();\n\n/**\n * Colors.\n */\n\nexports.colors = [\n 'lightseagreen',\n 'forestgreen',\n 'goldenrod',\n 'dodgerblue',\n 'darkorchid',\n 'crimson'\n];\n\n/**\n * Currently only WebKit-based Web Inspectors, Firefox >= v31,\n * and the Firebug extension (any Firefox version) are known\n * to support \"%c\" CSS customizations.\n *\n * TODO: add a `localStorage` variable to explicitly enable/disable colors\n */\n\nfunction useColors() {\n // NB: In an Electron preload script, document will be defined but not fully\n // initialized. Since we know we're in Chrome, we'll just detect this case\n // explicitly\n if (typeof window !== 'undefined' && typeof window.process !== 'undefined' && window.process.type === 'renderer') {\n return true;\n }\n\n // is webkit? http://stackoverflow.com/a/16459606/376773\n // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632\n return (typeof document !== 'undefined' && 'WebkitAppearance' in document.documentElement.style) ||\n // is firebug? http://stackoverflow.com/a/398120/376773\n (typeof window !== 'undefined' && window.console && (console.firebug || (console.exception && console.table))) ||\n // is firefox >= v31?\n // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages\n (navigator && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\\/(\\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||\n // double check webkit in userAgent just in case we are in a worker\n (navigator && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\\/(\\d+)/));\n}\n\n/**\n * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.\n */\n\nexports.formatters.j = function(v) {\n try {\n return JSON.stringify(v);\n } catch (err) {\n return '[UnexpectedJSONParseError]: ' + err.message;\n }\n};\n\n\n/**\n * Colorize log arguments if enabled.\n *\n * @api public\n */\n\nfunction formatArgs(args) {\n var useColors = this.useColors;\n\n args[0] = (useColors ? '%c' : '')\n + this.namespace\n + (useColors ? ' %c' : ' ')\n + args[0]\n + (useColors ? '%c ' : ' ')\n + '+' + exports.humanize(this.diff);\n\n if (!useColors) return;\n\n var c = 'color: ' + this.color;\n args.splice(1, 0, c, 'color: inherit')\n\n // the final \"%c\" is somewhat tricky, because there could be other\n // arguments passed either before or after the %c, so we need to\n // figure out the correct index to insert the CSS into\n var index = 0;\n var lastC = 0;\n args[0].replace(/%[a-zA-Z%]/g, function(match) {\n if ('%%' === match) return;\n index++;\n if ('%c' === match) {\n // we only are interested in the *last* %c\n // (the user may have provided their own)\n lastC = index;\n }\n });\n\n args.splice(lastC, 0, c);\n}\n\n/**\n * Invokes `console.log()` when available.\n * No-op when `console.log` is not a \"function\".\n *\n * @api public\n */\n\nfunction log() {\n // this hackery is required for IE8/9, where\n // the `console.log` function doesn't have 'apply'\n return 'object' === typeof console\n && console.log\n && Function.prototype.apply.call(console.log, console, arguments);\n}\n\n/**\n * Save `namespaces`.\n *\n * @param {String} namespaces\n * @api private\n */\n\nfunction save(namespaces) {\n try {\n if (null == namespaces) {\n exports.storage.removeItem('debug');\n } else {\n exports.storage.debug = namespaces;\n }\n } catch(e) {}\n}\n\n/**\n * Load `namespaces`.\n *\n * @return {String} returns the previously persisted debug modes\n * @api private\n */\n\nfunction load() {\n try {\n return exports.storage.debug;\n } catch(e) {}\n\n // If debug isn't set in LS, and we're in Electron, try to load $DEBUG\n if (typeof process !== 'undefined' && 'env' in process) {\n return ({\"VERSION\":1489762813811}).DEBUG;\n }\n}\n\n/**\n * Enable namespaces listed in `localStorage.debug` initially.\n */\n\nexports.enable(load());\n\n/**\n * Localstorage attempts to return the localstorage.\n *\n * This is necessary because safari throws\n * when a user disables cookies/localstorage\n * and you attempt to access it.\n *\n * @return {LocalStorage}\n * @api private\n */\n\nfunction localstorage() {\n try {\n return window.localStorage;\n } catch (e) {}\n}\n\n/** Attach to Window*/\nif (typeof window !== 'undefined') {\n window.debug = exports;\n}\n\n/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(122)))//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9+L2RlYnVnL3NyYy9icm93c2VyLmpzPzEzZjkiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHOztBQUVIO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsWUFBWSxPQUFPO0FBQ25CO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsR0FBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiI0MzcuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoaXMgaXMgdGhlIHdlYiBicm93c2VyIGltcGxlbWVudGF0aW9uIG9mIGBkZWJ1ZygpYC5cbiAqXG4gKiBFeHBvc2UgYGRlYnVnKClgIGFzIHRoZSBtb2R1bGUuXG4gKi9cblxuZXhwb3J0cyA9IG1vZHVsZS5leHBvcnRzID0gcmVxdWlyZSgnLi9kZWJ1ZycpO1xuZXhwb3J0cy5sb2cgPSBsb2c7XG5leHBvcnRzLmZvcm1hdEFyZ3MgPSBmb3JtYXRBcmdzO1xuZXhwb3J0cy5zYXZlID0gc2F2ZTtcbmV4cG9ydHMubG9hZCA9IGxvYWQ7XG5leHBvcnRzLnVzZUNvbG9ycyA9IHVzZUNvbG9ycztcbmV4cG9ydHMuc3RvcmFnZSA9ICd1bmRlZmluZWQnICE9IHR5cGVvZiBjaHJvbWVcbiAgICAgICAgICAgICAgICYmICd1bmRlZmluZWQnICE9IHR5cGVvZiBjaHJvbWUuc3RvcmFnZVxuICAgICAgICAgICAgICAgICAgPyBjaHJvbWUuc3RvcmFnZS5sb2NhbFxuICAgICAgICAgICAgICAgICAgOiBsb2NhbHN0b3JhZ2UoKTtcblxuLyoqXG4gKiBDb2xvcnMuXG4gKi9cblxuZXhwb3J0cy5jb2xvcnMgPSBbXG4gICdsaWdodHNlYWdyZWVuJyxcbiAgJ2ZvcmVzdGdyZWVuJyxcbiAgJ2dvbGRlbnJvZCcsXG4gICdkb2RnZXJibHVlJyxcbiAgJ2RhcmtvcmNoaWQnLFxuICAnY3JpbXNvbidcbl07XG5cbi8qKlxuICogQ3VycmVudGx5IG9ubHkgV2ViS2l0LWJhc2VkIFdlYiBJbnNwZWN0b3JzLCBGaXJlZm94ID49IHYzMSxcbiAqIGFuZCB0aGUgRmlyZWJ1ZyBleHRlbnNpb24gKGFueSBGaXJlZm94IHZlcnNpb24pIGFyZSBrbm93blxuICogdG8gc3VwcG9ydCBcIiVjXCIgQ1NTIGN1c3RvbWl6YXRpb25zLlxuICpcbiAqIFRPRE86IGFkZCBhIGBsb2NhbFN0b3JhZ2VgIHZhcmlhYmxlIHRvIGV4cGxpY2l0bHkgZW5hYmxlL2Rpc2FibGUgY29sb3JzXG4gKi9cblxuZnVuY3Rpb24gdXNlQ29sb3JzKCkge1xuICAvLyBOQjogSW4gYW4gRWxlY3Ryb24gcHJlbG9hZCBzY3JpcHQsIGRvY3VtZW50IHdpbGwgYmUgZGVmaW5lZCBidXQgbm90IGZ1bGx5XG4gIC8vIGluaXRpYWxpemVkLiBTaW5jZSB3ZSBrbm93IHdlJ3JlIGluIENocm9tZSwgd2UnbGwganVzdCBkZXRlY3QgdGhpcyBjYXNlXG4gIC8vIGV4cGxpY2l0bHlcbiAgaWYgKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnICYmIHR5cGVvZiB3aW5kb3cucHJvY2VzcyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LnByb2Nlc3MudHlwZSA9PT0gJ3JlbmRlcmVyJykge1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgLy8gaXMgd2Via2l0PyBodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vYS8xNjQ1OTYwNi8zNzY3NzNcbiAgLy8gZG9jdW1lbnQgaXMgdW5kZWZpbmVkIGluIHJlYWN0LW5hdGl2ZTogaHR0cHM6Ly9naXRodWIuY29tL2ZhY2Vib29rL3JlYWN0LW5hdGl2ZS9wdWxsLzE2MzJcbiAgcmV0dXJuICh0eXBlb2YgZG9jdW1lbnQgIT09ICd1bmRlZmluZWQnICYmICdXZWJraXRBcHBlYXJhbmNlJyBpbiBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUpIHx8XG4gICAgLy8gaXMgZmlyZWJ1Zz8gaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL2EvMzk4MTIwLzM3Njc3M1xuICAgICh0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJyAmJiB3aW5kb3cuY29uc29sZSAmJiAoY29uc29sZS5maXJlYnVnIHx8IChjb25zb2xlLmV4Y2VwdGlvbiAmJiBjb25zb2xlLnRhYmxlKSkpIHx8XG4gICAgLy8gaXMgZmlyZWZveCA+PSB2MzE/XG4gICAgLy8gaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9Ub29scy9XZWJfQ29uc29sZSNTdHlsaW5nX21lc3NhZ2VzXG4gICAgKG5hdmlnYXRvciAmJiBuYXZpZ2F0b3IudXNlckFnZW50ICYmIG5hdmlnYXRvci51c2VyQWdlbnQudG9Mb3dlckNhc2UoKS5tYXRjaCgvZmlyZWZveFxcLyhcXGQrKS8pICYmIHBhcnNlSW50KFJlZ0V4cC4kMSwgMTApID49IDMxKSB8fFxuICAgIC8vIGRvdWJsZSBjaGVjayB3ZWJraXQgaW4gdXNlckFnZW50IGp1c3QgaW4gY2FzZSB3ZSBhcmUgaW4gYSB3b3JrZXJcbiAgICAobmF2aWdhdG9yICYmIG5hdmlnYXRvci51c2VyQWdlbnQgJiYgbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpLm1hdGNoKC9hcHBsZXdlYmtpdFxcLyhcXGQrKS8pKTtcbn1cblxuLyoqXG4gKiBNYXAgJWogdG8gYEpTT04uc3RyaW5naWZ5KClgLCBzaW5jZSBubyBXZWIgSW5zcGVjdG9ycyBkbyB0aGF0IGJ5IGRlZmF1bHQuXG4gKi9cblxuZXhwb3J0cy5mb3JtYXR0ZXJzLmogPSBmdW5jdGlvbih2KSB7XG4gIHRyeSB7XG4gICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHYpO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICByZXR1cm4gJ1tVbmV4cGVjdGVkSlNPTlBhcnNlRXJyb3JdOiAnICsgZXJyLm1lc3NhZ2U7XG4gIH1cbn07XG5cblxuLyoqXG4gKiBDb2xvcml6ZSBsb2cgYXJndW1lbnRzIGlmIGVuYWJsZWQuXG4gKlxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5mdW5jdGlvbiBmb3JtYXRBcmdzKGFyZ3MpIHtcbiAgdmFyIHVzZUNvbG9ycyA9IHRoaXMudXNlQ29sb3JzO1xuXG4gIGFyZ3NbMF0gPSAodXNlQ29sb3JzID8gJyVjJyA6ICcnKVxuICAgICsgdGhpcy5uYW1lc3BhY2VcbiAgICArICh1c2VDb2xvcnMgPyAnICVjJyA6ICcgJylcbiAgICArIGFyZ3NbMF1cbiAgICArICh1c2VDb2xvcnMgPyAnJWMgJyA6ICcgJylcbiAgICArICcrJyArIGV4cG9ydHMuaHVtYW5pemUodGhpcy5kaWZmKTtcblxuICBpZiAoIXVzZUNvbG9ycykgcmV0dXJuO1xuXG4gIHZhciBjID0gJ2NvbG9yOiAnICsgdGhpcy5jb2xvcjtcbiAgYXJncy5zcGxpY2UoMSwgMCwgYywgJ2NvbG9yOiBpbmhlcml0JylcblxuICAvLyB0aGUgZmluYWwgXCIlY1wiIGlzIHNvbWV3aGF0IHRyaWNreSwgYmVjYXVzZSB0aGVyZSBjb3VsZCBiZSBvdGhlclxuICAvLyBhcmd1bWVudHMgcGFzc2VkIGVpdGhlciBiZWZvcmUgb3IgYWZ0ZXIgdGhlICVjLCBzbyB3ZSBuZWVkIHRvXG4gIC8vIGZpZ3VyZSBvdXQgdGhlIGNvcnJlY3QgaW5kZXggdG8gaW5zZXJ0IHRoZSBDU1MgaW50b1xuICB2YXIgaW5kZXggPSAwO1xuICB2YXIgbGFzdEMgPSAwO1xuICBhcmdzWzBdLnJlcGxhY2UoLyVbYS16QS1aJV0vZywgZnVuY3Rpb24obWF0Y2gpIHtcbiAgICBpZiAoJyUlJyA9PT0gbWF0Y2gpIHJldHVybjtcbiAgICBpbmRleCsrO1xuICAgIGlmICgnJWMnID09PSBtYXRjaCkge1xuICAgICAgLy8gd2Ugb25seSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgKmxhc3QqICVjXG4gICAgICAvLyAodGhlIHVzZXIgbWF5IGhhdmUgcHJvdmlkZWQgdGhlaXIgb3duKVxuICAgICAgbGFzdEMgPSBpbmRleDtcbiAgICB9XG4gIH0pO1xuXG4gIGFyZ3Muc3BsaWNlKGxhc3RDLCAwLCBjKTtcbn1cblxuLyoqXG4gKiBJbnZva2VzIGBjb25zb2xlLmxvZygpYCB3aGVuIGF2YWlsYWJsZS5cbiAqIE5vLW9wIHdoZW4gYGNvbnNvbGUubG9nYCBpcyBub3QgYSBcImZ1bmN0aW9uXCIuXG4gKlxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5mdW5jdGlvbiBsb2coKSB7XG4gIC8vIHRoaXMgaGFja2VyeSBpcyByZXF1aXJlZCBmb3IgSUU4LzksIHdoZXJlXG4gIC8vIHRoZSBgY29uc29sZS5sb2dgIGZ1bmN0aW9uIGRvZXNuJ3QgaGF2ZSAnYXBwbHknXG4gIHJldHVybiAnb2JqZWN0JyA9PT0gdHlwZW9mIGNvbnNvbGVcbiAgICAmJiBjb25zb2xlLmxvZ1xuICAgICYmIEZ1bmN0aW9uLnByb3RvdHlwZS5hcHBseS5jYWxsKGNvbnNvbGUubG9nLCBjb25zb2xlLCBhcmd1bWVudHMpO1xufVxuXG4vKipcbiAqIFNhdmUgYG5hbWVzcGFjZXNgLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lc3BhY2VzXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBzYXZlKG5hbWVzcGFjZXMpIHtcbiAgdHJ5IHtcbiAgICBpZiAobnVsbCA9PSBuYW1lc3BhY2VzKSB7XG4gICAgICBleHBvcnRzLnN0b3JhZ2UucmVtb3ZlSXRlbSgnZGVidWcnKTtcbiAgICB9IGVsc2Uge1xuICAgICAgZXhwb3J0cy5zdG9yYWdlLmRlYnVnID0gbmFtZXNwYWNlcztcbiAgICB9XG4gIH0gY2F0Y2goZSkge31cbn1cblxuLyoqXG4gKiBMb2FkIGBuYW1lc3BhY2VzYC5cbiAqXG4gKiBAcmV0dXJuIHtTdHJpbmd9IHJldHVybnMgdGhlIHByZXZpb3VzbHkgcGVyc2lzdGVkIGRlYnVnIG1vZGVzXG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBsb2FkKCkge1xuICB0cnkge1xuICAgIHJldHVybiBleHBvcnRzLnN0b3JhZ2UuZGVidWc7XG4gIH0gY2F0Y2goZSkge31cblxuICAvLyBJZiBkZWJ1ZyBpc24ndCBzZXQgaW4gTFMsIGFuZCB3ZSdyZSBpbiBFbGVjdHJvbiwgdHJ5IHRvIGxvYWQgJERFQlVHXG4gIGlmICh0eXBlb2YgcHJvY2VzcyAhPT0gJ3VuZGVmaW5lZCcgJiYgJ2VudicgaW4gcHJvY2Vzcykge1xuICAgIHJldHVybiBwcm9jZXNzLmVudi5ERUJVRztcbiAgfVxufVxuXG4vKipcbiAqIEVuYWJsZSBuYW1lc3BhY2VzIGxpc3RlZCBpbiBgbG9jYWxTdG9yYWdlLmRlYnVnYCBpbml0aWFsbHkuXG4gKi9cblxuZXhwb3J0cy5lbmFibGUobG9hZCgpKTtcblxuLyoqXG4gKiBMb2NhbHN0b3JhZ2UgYXR0ZW1wdHMgdG8gcmV0dXJuIHRoZSBsb2NhbHN0b3JhZ2UuXG4gKlxuICogVGhpcyBpcyBuZWNlc3NhcnkgYmVjYXVzZSBzYWZhcmkgdGhyb3dzXG4gKiB3aGVuIGEgdXNlciBkaXNhYmxlcyBjb29raWVzL2xvY2Fsc3RvcmFnZVxuICogYW5kIHlvdSBhdHRlbXB0IHRvIGFjY2VzcyBpdC5cbiAqXG4gKiBAcmV0dXJuIHtMb2NhbFN0b3JhZ2V9XG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBsb2NhbHN0b3JhZ2UoKSB7XG4gIHRyeSB7XG4gICAgcmV0dXJuIHdpbmRvdy5sb2NhbFN0b3JhZ2U7XG4gIH0gY2F0Y2ggKGUpIHt9XG59XG5cbi8qKiBBdHRhY2ggdG8gV2luZG93Ki9cbmlmICh0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJykge1xuICB3aW5kb3cuZGVidWcgPSBleHBvcnRzO1xufVxuXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9+L2RlYnVnL3NyYy9icm93c2VyLmpzXG4vLyBtb2R1bGUgaWQgPSA0Mzdcbi8vIG1vZHVsZSBjaHVua3MgPSAyIl0sInNvdXJjZVJvb3QiOiIifQ=="); 14 | 15 | /***/ }, 16 | 17 | /***/ 438: 18 | /***/ function(module, exports, __webpack_require__) { 19 | 20 | eval("\n/**\n * This is the common logic for both the Node.js and web browser\n * implementations of `debug()`.\n *\n * Expose `debug()` as the module.\n */\n\nexports = module.exports = createDebug.debug = createDebug.default = createDebug;\nexports.coerce = coerce;\nexports.disable = disable;\nexports.enable = enable;\nexports.enabled = enabled;\nexports.humanize = __webpack_require__(439);\n\n/**\n * The currently active debug mode names, and names to skip.\n */\n\nexports.names = [];\nexports.skips = [];\n\n/**\n * Map of special \"%n\" handling functions, for the debug \"format\" argument.\n *\n * Valid key names are a single, lower or upper-case letter, i.e. \"n\" and \"N\".\n */\n\nexports.formatters = {};\n\n/**\n * Previous log timestamp.\n */\n\nvar prevTime;\n\n/**\n * Select a color.\n * @param {String} namespace\n * @return {Number}\n * @api private\n */\n\nfunction selectColor(namespace) {\n var hash = 0, i;\n\n for (i in namespace) {\n hash = ((hash << 5) - hash) + namespace.charCodeAt(i);\n hash |= 0; // Convert to 32bit integer\n }\n\n return exports.colors[Math.abs(hash) % exports.colors.length];\n}\n\n/**\n * Create a debugger with the given `namespace`.\n *\n * @param {String} namespace\n * @return {Function}\n * @api public\n */\n\nfunction createDebug(namespace) {\n\n function debug() {\n // disabled?\n if (!debug.enabled) return;\n\n var self = debug;\n\n // set `diff` timestamp\n var curr = +new Date();\n var ms = curr - (prevTime || curr);\n self.diff = ms;\n self.prev = prevTime;\n self.curr = curr;\n prevTime = curr;\n\n // turn the `arguments` into a proper Array\n var args = new Array(arguments.length);\n for (var i = 0; i < args.length; i++) {\n args[i] = arguments[i];\n }\n\n args[0] = exports.coerce(args[0]);\n\n if ('string' !== typeof args[0]) {\n // anything else let's inspect with %O\n args.unshift('%O');\n }\n\n // apply any `formatters` transformations\n var index = 0;\n args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {\n // if we encounter an escaped % then don't increase the array index\n if (match === '%%') return match;\n index++;\n var formatter = exports.formatters[format];\n if ('function' === typeof formatter) {\n var val = args[index];\n match = formatter.call(self, val);\n\n // now we need to remove `args[index]` since it's inlined in the `format`\n args.splice(index, 1);\n index--;\n }\n return match;\n });\n\n // apply env-specific formatting (colors, etc.)\n exports.formatArgs.call(self, args);\n\n var logFn = debug.log || exports.log || console.log.bind(console);\n logFn.apply(self, args);\n }\n\n debug.namespace = namespace;\n debug.enabled = exports.enabled(namespace);\n debug.useColors = exports.useColors();\n debug.color = selectColor(namespace);\n\n // env-specific initialization logic for debug instances\n if ('function' === typeof exports.init) {\n exports.init(debug);\n }\n\n return debug;\n}\n\n/**\n * Enables a debug mode by namespaces. This can include modes\n * separated by a colon and wildcards.\n *\n * @param {String} namespaces\n * @api public\n */\n\nfunction enable(namespaces) {\n exports.save(namespaces);\n\n var split = (namespaces || '').split(/[\\s,]+/);\n var len = split.length;\n\n for (var i = 0; i < len; i++) {\n if (!split[i]) continue; // ignore empty strings\n namespaces = split[i].replace(/\\*/g, '.*?');\n if (namespaces[0] === '-') {\n exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));\n } else {\n exports.names.push(new RegExp('^' + namespaces + '$'));\n }\n }\n}\n\n/**\n * Disable debug output.\n *\n * @api public\n */\n\nfunction disable() {\n exports.enable('');\n}\n\n/**\n * Returns true if the given mode name is enabled, false otherwise.\n *\n * @param {String} name\n * @return {Boolean}\n * @api public\n */\n\nfunction enabled(name) {\n var i, len;\n for (i = 0, len = exports.skips.length; i < len; i++) {\n if (exports.skips[i].test(name)) {\n return false;\n }\n }\n for (i = 0, len = exports.names.length; i < len; i++) {\n if (exports.names[i].test(name)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Coerce `val`.\n *\n * @param {Mixed} val\n * @return {Mixed}\n * @api private\n */\n\nfunction coerce(val) {\n if (val instanceof Error) return val.stack || val.message;\n return val;\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9+L2RlYnVnL3NyYy9kZWJ1Zy5qcz8yZDhlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQSxXQUFXLE9BQU87QUFDbEIsWUFBWTtBQUNaO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsY0FBYztBQUNkOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsV0FBVyxPQUFPO0FBQ2xCLFlBQVk7QUFDWjtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsbUJBQW1CLGlCQUFpQjtBQUNwQztBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLOztBQUVMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLE9BQU87QUFDbEI7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUEsaUJBQWlCLFNBQVM7QUFDMUIsNEJBQTRCO0FBQzVCO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLE9BQU87QUFDbEIsWUFBWTtBQUNaO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLHlDQUF5QyxTQUFTO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLFNBQVM7QUFDbEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsTUFBTTtBQUNqQixZQUFZO0FBQ1o7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiI0MzguanMiLCJzb3VyY2VzQ29udGVudCI6WyJcbi8qKlxuICogVGhpcyBpcyB0aGUgY29tbW9uIGxvZ2ljIGZvciBib3RoIHRoZSBOb2RlLmpzIGFuZCB3ZWIgYnJvd3NlclxuICogaW1wbGVtZW50YXRpb25zIG9mIGBkZWJ1ZygpYC5cbiAqXG4gKiBFeHBvc2UgYGRlYnVnKClgIGFzIHRoZSBtb2R1bGUuXG4gKi9cblxuZXhwb3J0cyA9IG1vZHVsZS5leHBvcnRzID0gY3JlYXRlRGVidWcuZGVidWcgPSBjcmVhdGVEZWJ1Zy5kZWZhdWx0ID0gY3JlYXRlRGVidWc7XG5leHBvcnRzLmNvZXJjZSA9IGNvZXJjZTtcbmV4cG9ydHMuZGlzYWJsZSA9IGRpc2FibGU7XG5leHBvcnRzLmVuYWJsZSA9IGVuYWJsZTtcbmV4cG9ydHMuZW5hYmxlZCA9IGVuYWJsZWQ7XG5leHBvcnRzLmh1bWFuaXplID0gcmVxdWlyZSgnbXMnKTtcblxuLyoqXG4gKiBUaGUgY3VycmVudGx5IGFjdGl2ZSBkZWJ1ZyBtb2RlIG5hbWVzLCBhbmQgbmFtZXMgdG8gc2tpcC5cbiAqL1xuXG5leHBvcnRzLm5hbWVzID0gW107XG5leHBvcnRzLnNraXBzID0gW107XG5cbi8qKlxuICogTWFwIG9mIHNwZWNpYWwgXCIlblwiIGhhbmRsaW5nIGZ1bmN0aW9ucywgZm9yIHRoZSBkZWJ1ZyBcImZvcm1hdFwiIGFyZ3VtZW50LlxuICpcbiAqIFZhbGlkIGtleSBuYW1lcyBhcmUgYSBzaW5nbGUsIGxvd2VyIG9yIHVwcGVyLWNhc2UgbGV0dGVyLCBpLmUuIFwiblwiIGFuZCBcIk5cIi5cbiAqL1xuXG5leHBvcnRzLmZvcm1hdHRlcnMgPSB7fTtcblxuLyoqXG4gKiBQcmV2aW91cyBsb2cgdGltZXN0YW1wLlxuICovXG5cbnZhciBwcmV2VGltZTtcblxuLyoqXG4gKiBTZWxlY3QgYSBjb2xvci5cbiAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lc3BhY2VcbiAqIEByZXR1cm4ge051bWJlcn1cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmZ1bmN0aW9uIHNlbGVjdENvbG9yKG5hbWVzcGFjZSkge1xuICB2YXIgaGFzaCA9IDAsIGk7XG5cbiAgZm9yIChpIGluIG5hbWVzcGFjZSkge1xuICAgIGhhc2ggID0gKChoYXNoIDw8IDUpIC0gaGFzaCkgKyBuYW1lc3BhY2UuY2hhckNvZGVBdChpKTtcbiAgICBoYXNoIHw9IDA7IC8vIENvbnZlcnQgdG8gMzJiaXQgaW50ZWdlclxuICB9XG5cbiAgcmV0dXJuIGV4cG9ydHMuY29sb3JzW01hdGguYWJzKGhhc2gpICUgZXhwb3J0cy5jb2xvcnMubGVuZ3RoXTtcbn1cblxuLyoqXG4gKiBDcmVhdGUgYSBkZWJ1Z2dlciB3aXRoIHRoZSBnaXZlbiBgbmFtZXNwYWNlYC5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gbmFtZXNwYWNlXG4gKiBAcmV0dXJuIHtGdW5jdGlvbn1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZnVuY3Rpb24gY3JlYXRlRGVidWcobmFtZXNwYWNlKSB7XG5cbiAgZnVuY3Rpb24gZGVidWcoKSB7XG4gICAgLy8gZGlzYWJsZWQ/XG4gICAgaWYgKCFkZWJ1Zy5lbmFibGVkKSByZXR1cm47XG5cbiAgICB2YXIgc2VsZiA9IGRlYnVnO1xuXG4gICAgLy8gc2V0IGBkaWZmYCB0aW1lc3RhbXBcbiAgICB2YXIgY3VyciA9ICtuZXcgRGF0ZSgpO1xuICAgIHZhciBtcyA9IGN1cnIgLSAocHJldlRpbWUgfHwgY3Vycik7XG4gICAgc2VsZi5kaWZmID0gbXM7XG4gICAgc2VsZi5wcmV2ID0gcHJldlRpbWU7XG4gICAgc2VsZi5jdXJyID0gY3VycjtcbiAgICBwcmV2VGltZSA9IGN1cnI7XG5cbiAgICAvLyB0dXJuIHRoZSBgYXJndW1lbnRzYCBpbnRvIGEgcHJvcGVyIEFycmF5XG4gICAgdmFyIGFyZ3MgPSBuZXcgQXJyYXkoYXJndW1lbnRzLmxlbmd0aCk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhcmdzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBhcmdzW2ldID0gYXJndW1lbnRzW2ldO1xuICAgIH1cblxuICAgIGFyZ3NbMF0gPSBleHBvcnRzLmNvZXJjZShhcmdzWzBdKTtcblxuICAgIGlmICgnc3RyaW5nJyAhPT0gdHlwZW9mIGFyZ3NbMF0pIHtcbiAgICAgIC8vIGFueXRoaW5nIGVsc2UgbGV0J3MgaW5zcGVjdCB3aXRoICVPXG4gICAgICBhcmdzLnVuc2hpZnQoJyVPJyk7XG4gICAgfVxuXG4gICAgLy8gYXBwbHkgYW55IGBmb3JtYXR0ZXJzYCB0cmFuc2Zvcm1hdGlvbnNcbiAgICB2YXIgaW5kZXggPSAwO1xuICAgIGFyZ3NbMF0gPSBhcmdzWzBdLnJlcGxhY2UoLyUoW2EtekEtWiVdKS9nLCBmdW5jdGlvbihtYXRjaCwgZm9ybWF0KSB7XG4gICAgICAvLyBpZiB3ZSBlbmNvdW50ZXIgYW4gZXNjYXBlZCAlIHRoZW4gZG9uJ3QgaW5jcmVhc2UgdGhlIGFycmF5IGluZGV4XG4gICAgICBpZiAobWF0Y2ggPT09ICclJScpIHJldHVybiBtYXRjaDtcbiAgICAgIGluZGV4Kys7XG4gICAgICB2YXIgZm9ybWF0dGVyID0gZXhwb3J0cy5mb3JtYXR0ZXJzW2Zvcm1hdF07XG4gICAgICBpZiAoJ2Z1bmN0aW9uJyA9PT0gdHlwZW9mIGZvcm1hdHRlcikge1xuICAgICAgICB2YXIgdmFsID0gYXJnc1tpbmRleF07XG4gICAgICAgIG1hdGNoID0gZm9ybWF0dGVyLmNhbGwoc2VsZiwgdmFsKTtcblxuICAgICAgICAvLyBub3cgd2UgbmVlZCB0byByZW1vdmUgYGFyZ3NbaW5kZXhdYCBzaW5jZSBpdCdzIGlubGluZWQgaW4gdGhlIGBmb3JtYXRgXG4gICAgICAgIGFyZ3Muc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgaW5kZXgtLTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBtYXRjaDtcbiAgICB9KTtcblxuICAgIC8vIGFwcGx5IGVudi1zcGVjaWZpYyBmb3JtYXR0aW5nIChjb2xvcnMsIGV0Yy4pXG4gICAgZXhwb3J0cy5mb3JtYXRBcmdzLmNhbGwoc2VsZiwgYXJncyk7XG5cbiAgICB2YXIgbG9nRm4gPSBkZWJ1Zy5sb2cgfHwgZXhwb3J0cy5sb2cgfHwgY29uc29sZS5sb2cuYmluZChjb25zb2xlKTtcbiAgICBsb2dGbi5hcHBseShzZWxmLCBhcmdzKTtcbiAgfVxuXG4gIGRlYnVnLm5hbWVzcGFjZSA9IG5hbWVzcGFjZTtcbiAgZGVidWcuZW5hYmxlZCA9IGV4cG9ydHMuZW5hYmxlZChuYW1lc3BhY2UpO1xuICBkZWJ1Zy51c2VDb2xvcnMgPSBleHBvcnRzLnVzZUNvbG9ycygpO1xuICBkZWJ1Zy5jb2xvciA9IHNlbGVjdENvbG9yKG5hbWVzcGFjZSk7XG5cbiAgLy8gZW52LXNwZWNpZmljIGluaXRpYWxpemF0aW9uIGxvZ2ljIGZvciBkZWJ1ZyBpbnN0YW5jZXNcbiAgaWYgKCdmdW5jdGlvbicgPT09IHR5cGVvZiBleHBvcnRzLmluaXQpIHtcbiAgICBleHBvcnRzLmluaXQoZGVidWcpO1xuICB9XG5cbiAgcmV0dXJuIGRlYnVnO1xufVxuXG4vKipcbiAqIEVuYWJsZXMgYSBkZWJ1ZyBtb2RlIGJ5IG5hbWVzcGFjZXMuIFRoaXMgY2FuIGluY2x1ZGUgbW9kZXNcbiAqIHNlcGFyYXRlZCBieSBhIGNvbG9uIGFuZCB3aWxkY2FyZHMuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IG5hbWVzcGFjZXNcbiAqIEBhcGkgcHVibGljXG4gKi9cblxuZnVuY3Rpb24gZW5hYmxlKG5hbWVzcGFjZXMpIHtcbiAgZXhwb3J0cy5zYXZlKG5hbWVzcGFjZXMpO1xuXG4gIHZhciBzcGxpdCA9IChuYW1lc3BhY2VzIHx8ICcnKS5zcGxpdCgvW1xccyxdKy8pO1xuICB2YXIgbGVuID0gc3BsaXQubGVuZ3RoO1xuXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICBpZiAoIXNwbGl0W2ldKSBjb250aW51ZTsgLy8gaWdub3JlIGVtcHR5IHN0cmluZ3NcbiAgICBuYW1lc3BhY2VzID0gc3BsaXRbaV0ucmVwbGFjZSgvXFwqL2csICcuKj8nKTtcbiAgICBpZiAobmFtZXNwYWNlc1swXSA9PT0gJy0nKSB7XG4gICAgICBleHBvcnRzLnNraXBzLnB1c2gobmV3IFJlZ0V4cCgnXicgKyBuYW1lc3BhY2VzLnN1YnN0cigxKSArICckJykpO1xuICAgIH0gZWxzZSB7XG4gICAgICBleHBvcnRzLm5hbWVzLnB1c2gobmV3IFJlZ0V4cCgnXicgKyBuYW1lc3BhY2VzICsgJyQnKSk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogRGlzYWJsZSBkZWJ1ZyBvdXRwdXQuXG4gKlxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5mdW5jdGlvbiBkaXNhYmxlKCkge1xuICBleHBvcnRzLmVuYWJsZSgnJyk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0cnVlIGlmIHRoZSBnaXZlbiBtb2RlIG5hbWUgaXMgZW5hYmxlZCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBuYW1lXG4gKiBAcmV0dXJuIHtCb29sZWFufVxuICogQGFwaSBwdWJsaWNcbiAqL1xuXG5mdW5jdGlvbiBlbmFibGVkKG5hbWUpIHtcbiAgdmFyIGksIGxlbjtcbiAgZm9yIChpID0gMCwgbGVuID0gZXhwb3J0cy5za2lwcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgIGlmIChleHBvcnRzLnNraXBzW2ldLnRlc3QobmFtZSkpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gIH1cbiAgZm9yIChpID0gMCwgbGVuID0gZXhwb3J0cy5uYW1lcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgIGlmIChleHBvcnRzLm5hbWVzW2ldLnRlc3QobmFtZSkpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG5cbi8qKlxuICogQ29lcmNlIGB2YWxgLlxuICpcbiAqIEBwYXJhbSB7TWl4ZWR9IHZhbFxuICogQHJldHVybiB7TWl4ZWR9XG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBjb2VyY2UodmFsKSB7XG4gIGlmICh2YWwgaW5zdGFuY2VvZiBFcnJvcikgcmV0dXJuIHZhbC5zdGFjayB8fCB2YWwubWVzc2FnZTtcbiAgcmV0dXJuIHZhbDtcbn1cblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vfi9kZWJ1Zy9zcmMvZGVidWcuanNcbi8vIG1vZHVsZSBpZCA9IDQzOFxuLy8gbW9kdWxlIGNodW5rcyA9IDIiXSwic291cmNlUm9vdCI6IiJ9"); 21 | 22 | /***/ }, 23 | 24 | /***/ 439: 25 | /***/ function(module, exports) { 26 | 27 | eval("/**\n * Helpers.\n */\n\nvar s = 1000\nvar m = s * 60\nvar h = m * 60\nvar d = h * 24\nvar y = d * 365.25\n\n/**\n * Parse or format the given `val`.\n *\n * Options:\n *\n * - `long` verbose formatting [false]\n *\n * @param {String|Number} val\n * @param {Object} options\n * @throws {Error} throw an error if val is not a non-empty string or a number\n * @return {String|Number}\n * @api public\n */\n\nmodule.exports = function (val, options) {\n options = options || {}\n var type = typeof val\n if (type === 'string' && val.length > 0) {\n return parse(val)\n } else if (type === 'number' && isNaN(val) === false) {\n return options.long ?\n\t\t\tfmtLong(val) :\n\t\t\tfmtShort(val)\n }\n throw new Error('val is not a non-empty string or a valid number. val=' + JSON.stringify(val))\n}\n\n/**\n * Parse the given `str` and return milliseconds.\n *\n * @param {String} str\n * @return {Number}\n * @api private\n */\n\nfunction parse(str) {\n str = String(str)\n if (str.length > 10000) {\n return\n }\n var match = /^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str)\n if (!match) {\n return\n }\n var n = parseFloat(match[1])\n var type = (match[2] || 'ms').toLowerCase()\n switch (type) {\n case 'years':\n case 'year':\n case 'yrs':\n case 'yr':\n case 'y':\n return n * y\n case 'days':\n case 'day':\n case 'd':\n return n * d\n case 'hours':\n case 'hour':\n case 'hrs':\n case 'hr':\n case 'h':\n return n * h\n case 'minutes':\n case 'minute':\n case 'mins':\n case 'min':\n case 'm':\n return n * m\n case 'seconds':\n case 'second':\n case 'secs':\n case 'sec':\n case 's':\n return n * s\n case 'milliseconds':\n case 'millisecond':\n case 'msecs':\n case 'msec':\n case 'ms':\n return n\n default:\n return undefined\n }\n}\n\n/**\n * Short format for `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api private\n */\n\nfunction fmtShort(ms) {\n if (ms >= d) {\n return Math.round(ms / d) + 'd'\n }\n if (ms >= h) {\n return Math.round(ms / h) + 'h'\n }\n if (ms >= m) {\n return Math.round(ms / m) + 'm'\n }\n if (ms >= s) {\n return Math.round(ms / s) + 's'\n }\n return ms + 'ms'\n}\n\n/**\n * Long format for `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api private\n */\n\nfunction fmtLong(ms) {\n return plural(ms, d, 'day') ||\n plural(ms, h, 'hour') ||\n plural(ms, m, 'minute') ||\n plural(ms, s, 'second') ||\n ms + ' ms'\n}\n\n/**\n * Pluralization helper.\n */\n\nfunction plural(ms, n, name) {\n if (ms < n) {\n return\n }\n if (ms < n * 1.5) {\n return Math.floor(ms / n) + ' ' + name\n }\n return Math.ceil(ms / n) + ' ' + name + 's'\n}\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9+L21zL2luZGV4LmpzPzZkMzYiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLGNBQWM7QUFDekIsV0FBVyxPQUFPO0FBQ2xCLFlBQVksTUFBTTtBQUNsQixZQUFZO0FBQ1o7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLE9BQU87QUFDbEIsWUFBWTtBQUNaO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLE9BQU87QUFDbEIsWUFBWTtBQUNaO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsT0FBTztBQUNsQixZQUFZO0FBQ1o7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6IjQzOS5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogSGVscGVycy5cbiAqL1xuXG52YXIgcyA9IDEwMDBcbnZhciBtID0gcyAqIDYwXG52YXIgaCA9IG0gKiA2MFxudmFyIGQgPSBoICogMjRcbnZhciB5ID0gZCAqIDM2NS4yNVxuXG4vKipcbiAqIFBhcnNlIG9yIGZvcm1hdCB0aGUgZ2l2ZW4gYHZhbGAuXG4gKlxuICogT3B0aW9uczpcbiAqXG4gKiAgLSBgbG9uZ2AgdmVyYm9zZSBmb3JtYXR0aW5nIFtmYWxzZV1cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ3xOdW1iZXJ9IHZhbFxuICogQHBhcmFtIHtPYmplY3R9IG9wdGlvbnNcbiAqIEB0aHJvd3Mge0Vycm9yfSB0aHJvdyBhbiBlcnJvciBpZiB2YWwgaXMgbm90IGEgbm9uLWVtcHR5IHN0cmluZyBvciBhIG51bWJlclxuICogQHJldHVybiB7U3RyaW5nfE51bWJlcn1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAodmFsLCBvcHRpb25zKSB7XG4gIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9XG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbFxuICBpZiAodHlwZSA9PT0gJ3N0cmluZycgJiYgdmFsLmxlbmd0aCA+IDApIHtcbiAgICByZXR1cm4gcGFyc2UodmFsKVxuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdudW1iZXInICYmIGlzTmFOKHZhbCkgPT09IGZhbHNlKSB7XG4gICAgcmV0dXJuIG9wdGlvbnMubG9uZyA/XG5cdFx0XHRmbXRMb25nKHZhbCkgOlxuXHRcdFx0Zm10U2hvcnQodmFsKVxuICB9XG4gIHRocm93IG5ldyBFcnJvcigndmFsIGlzIG5vdCBhIG5vbi1lbXB0eSBzdHJpbmcgb3IgYSB2YWxpZCBudW1iZXIuIHZhbD0nICsgSlNPTi5zdHJpbmdpZnkodmFsKSlcbn1cblxuLyoqXG4gKiBQYXJzZSB0aGUgZ2l2ZW4gYHN0cmAgYW5kIHJldHVybiBtaWxsaXNlY29uZHMuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHN0clxuICogQHJldHVybiB7TnVtYmVyfVxuICogQGFwaSBwcml2YXRlXG4gKi9cblxuZnVuY3Rpb24gcGFyc2Uoc3RyKSB7XG4gIHN0ciA9IFN0cmluZyhzdHIpXG4gIGlmIChzdHIubGVuZ3RoID4gMTAwMDApIHtcbiAgICByZXR1cm5cbiAgfVxuICB2YXIgbWF0Y2ggPSAvXigoPzpcXGQrKT9cXC4/XFxkKykgKihtaWxsaXNlY29uZHM/fG1zZWNzP3xtc3xzZWNvbmRzP3xzZWNzP3xzfG1pbnV0ZXM/fG1pbnM/fG18aG91cnM/fGhycz98aHxkYXlzP3xkfHllYXJzP3x5cnM/fHkpPyQvaS5leGVjKHN0cilcbiAgaWYgKCFtYXRjaCkge1xuICAgIHJldHVyblxuICB9XG4gIHZhciBuID0gcGFyc2VGbG9hdChtYXRjaFsxXSlcbiAgdmFyIHR5cGUgPSAobWF0Y2hbMl0gfHwgJ21zJykudG9Mb3dlckNhc2UoKVxuICBzd2l0Y2ggKHR5cGUpIHtcbiAgICBjYXNlICd5ZWFycyc6XG4gICAgY2FzZSAneWVhcic6XG4gICAgY2FzZSAneXJzJzpcbiAgICBjYXNlICd5cic6XG4gICAgY2FzZSAneSc6XG4gICAgICByZXR1cm4gbiAqIHlcbiAgICBjYXNlICdkYXlzJzpcbiAgICBjYXNlICdkYXknOlxuICAgIGNhc2UgJ2QnOlxuICAgICAgcmV0dXJuIG4gKiBkXG4gICAgY2FzZSAnaG91cnMnOlxuICAgIGNhc2UgJ2hvdXInOlxuICAgIGNhc2UgJ2hycyc6XG4gICAgY2FzZSAnaHInOlxuICAgIGNhc2UgJ2gnOlxuICAgICAgcmV0dXJuIG4gKiBoXG4gICAgY2FzZSAnbWludXRlcyc6XG4gICAgY2FzZSAnbWludXRlJzpcbiAgICBjYXNlICdtaW5zJzpcbiAgICBjYXNlICdtaW4nOlxuICAgIGNhc2UgJ20nOlxuICAgICAgcmV0dXJuIG4gKiBtXG4gICAgY2FzZSAnc2Vjb25kcyc6XG4gICAgY2FzZSAnc2Vjb25kJzpcbiAgICBjYXNlICdzZWNzJzpcbiAgICBjYXNlICdzZWMnOlxuICAgIGNhc2UgJ3MnOlxuICAgICAgcmV0dXJuIG4gKiBzXG4gICAgY2FzZSAnbWlsbGlzZWNvbmRzJzpcbiAgICBjYXNlICdtaWxsaXNlY29uZCc6XG4gICAgY2FzZSAnbXNlY3MnOlxuICAgIGNhc2UgJ21zZWMnOlxuICAgIGNhc2UgJ21zJzpcbiAgICAgIHJldHVybiBuXG4gICAgZGVmYXVsdDpcbiAgICAgIHJldHVybiB1bmRlZmluZWRcbiAgfVxufVxuXG4vKipcbiAqIFNob3J0IGZvcm1hdCBmb3IgYG1zYC5cbiAqXG4gKiBAcGFyYW0ge051bWJlcn0gbXNcbiAqIEByZXR1cm4ge1N0cmluZ31cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmZ1bmN0aW9uIGZtdFNob3J0KG1zKSB7XG4gIGlmIChtcyA+PSBkKSB7XG4gICAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBkKSArICdkJ1xuICB9XG4gIGlmIChtcyA+PSBoKSB7XG4gICAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBoKSArICdoJ1xuICB9XG4gIGlmIChtcyA+PSBtKSB7XG4gICAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBtKSArICdtJ1xuICB9XG4gIGlmIChtcyA+PSBzKSB7XG4gICAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBzKSArICdzJ1xuICB9XG4gIHJldHVybiBtcyArICdtcydcbn1cblxuLyoqXG4gKiBMb25nIGZvcm1hdCBmb3IgYG1zYC5cbiAqXG4gKiBAcGFyYW0ge051bWJlcn0gbXNcbiAqIEByZXR1cm4ge1N0cmluZ31cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmZ1bmN0aW9uIGZtdExvbmcobXMpIHtcbiAgcmV0dXJuIHBsdXJhbChtcywgZCwgJ2RheScpIHx8XG4gICAgcGx1cmFsKG1zLCBoLCAnaG91cicpIHx8XG4gICAgcGx1cmFsKG1zLCBtLCAnbWludXRlJykgfHxcbiAgICBwbHVyYWwobXMsIHMsICdzZWNvbmQnKSB8fFxuICAgIG1zICsgJyBtcydcbn1cblxuLyoqXG4gKiBQbHVyYWxpemF0aW9uIGhlbHBlci5cbiAqL1xuXG5mdW5jdGlvbiBwbHVyYWwobXMsIG4sIG5hbWUpIHtcbiAgaWYgKG1zIDwgbikge1xuICAgIHJldHVyblxuICB9XG4gIGlmIChtcyA8IG4gKiAxLjUpIHtcbiAgICByZXR1cm4gTWF0aC5mbG9vcihtcyAvIG4pICsgJyAnICsgbmFtZVxuICB9XG4gIHJldHVybiBNYXRoLmNlaWwobXMgLyBuKSArICcgJyArIG5hbWUgKyAncydcbn1cblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vfi9tcy9pbmRleC5qc1xuLy8gbW9kdWxlIGlkID0gNDM5XG4vLyBtb2R1bGUgY2h1bmtzID0gMiJdLCJzb3VyY2VSb290IjoiIn0="); 28 | 29 | /***/ }, 30 | 31 | /***/ 440: 32 | /***/ function(module, exports, __webpack_require__) { 33 | 34 | eval("var __WEBPACK_AMD_DEFINE_RESULT__;/*\n * Date Format 1.2.3\n * (c) 2007-2009 Steven Levithan \n * MIT license\n *\n * Includes enhancements by Scott Trenda \n * and Kris Kowal \n *\n * Accepts a date, a mask, or a date and a mask.\n * Returns a formatted version of the given date.\n * The date defaults to the current date/time.\n * The mask defaults to dateFormat.masks.default.\n */\n\n(function(global) {\n 'use strict';\n\n var dateFormat = (function() {\n var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\\1?|[LloSZWN]|'[^']*'|'[^']*'/g;\n var timezone = /\\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\\d{4})?)\\b/g;\n var timezoneClip = /[^-+\\dA-Z]/g;\n \n // Regexes and supporting functions are cached through closure\n return function (date, mask, utc, gmt) {\n \n // You can't provide utc if you skip other args (use the 'UTC:' mask prefix)\n if (arguments.length === 1 && kindOf(date) === 'string' && !/\\d/.test(date)) {\n mask = date;\n date = undefined;\n }\n \n date = date || new Date;\n \n if(!(date instanceof Date)) {\n date = new Date(date);\n }\n \n if (isNaN(date)) {\n throw TypeError('Invalid date');\n }\n \n mask = String(dateFormat.masks[mask] || mask || dateFormat.masks['default']);\n \n // Allow setting the utc/gmt argument via the mask\n var maskSlice = mask.slice(0, 4);\n if (maskSlice === 'UTC:' || maskSlice === 'GMT:') {\n mask = mask.slice(4);\n utc = true;\n if (maskSlice === 'GMT:') {\n gmt = true;\n }\n }\n \n var _ = utc ? 'getUTC' : 'get';\n var d = date[_ + 'Date']();\n var D = date[_ + 'Day']();\n var m = date[_ + 'Month']();\n var y = date[_ + 'FullYear']();\n var H = date[_ + 'Hours']();\n var M = date[_ + 'Minutes']();\n var s = date[_ + 'Seconds']();\n var L = date[_ + 'Milliseconds']();\n var o = utc ? 0 : date.getTimezoneOffset();\n var W = getWeek(date);\n var N = getDayOfWeek(date);\n var flags = {\n d: d,\n dd: pad(d),\n ddd: dateFormat.i18n.dayNames[D],\n dddd: dateFormat.i18n.dayNames[D + 7],\n m: m + 1,\n mm: pad(m + 1),\n mmm: dateFormat.i18n.monthNames[m],\n mmmm: dateFormat.i18n.monthNames[m + 12],\n yy: String(y).slice(2),\n yyyy: y,\n h: H % 12 || 12,\n hh: pad(H % 12 || 12),\n H: H,\n HH: pad(H),\n M: M,\n MM: pad(M),\n s: s,\n ss: pad(s),\n l: pad(L, 3),\n L: pad(Math.round(L / 10)),\n t: H < 12 ? 'a' : 'p',\n tt: H < 12 ? 'am' : 'pm',\n T: H < 12 ? 'A' : 'P',\n TT: H < 12 ? 'AM' : 'PM',\n Z: gmt ? 'GMT' : utc ? 'UTC' : (String(date).match(timezone) || ['']).pop().replace(timezoneClip, ''),\n o: (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),\n S: ['th', 'st', 'nd', 'rd'][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10],\n W: W,\n N: N\n };\n \n return mask.replace(token, function (match) {\n if (match in flags) {\n return flags[match];\n }\n return match.slice(1, match.length - 1);\n });\n };\n })();\n\n dateFormat.masks = {\n 'default': 'ddd mmm dd yyyy HH:MM:ss',\n 'shortDate': 'm/d/yy',\n 'mediumDate': 'mmm d, yyyy',\n 'longDate': 'mmmm d, yyyy',\n 'fullDate': 'dddd, mmmm d, yyyy',\n 'shortTime': 'h:MM TT',\n 'mediumTime': 'h:MM:ss TT',\n 'longTime': 'h:MM:ss TT Z',\n 'isoDate': 'yyyy-mm-dd',\n 'isoTime': 'HH:MM:ss',\n 'isoDateTime': 'yyyy-mm-dd\\'T\\'HH:MM:sso',\n 'isoUtcDateTime': 'UTC:yyyy-mm-dd\\'T\\'HH:MM:ss\\'Z\\'',\n 'expiresHeaderFormat': 'ddd, dd mmm yyyy HH:MM:ss Z'\n };\n\n // Internationalization strings\n dateFormat.i18n = {\n dayNames: [\n 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',\n 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'\n ],\n monthNames: [\n 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\n 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'\n ]\n };\n\nfunction pad(val, len) {\n val = String(val);\n len = len || 2;\n while (val.length < len) {\n val = '0' + val;\n }\n return val;\n}\n\n/**\n * Get the ISO 8601 week number\n * Based on comments from\n * http://techblog.procurios.nl/k/n618/news/view/33796/14863/Calculate-ISO-8601-week-and-year-in-javascript.html\n *\n * @param {Object} `date`\n * @return {Number}\n */\nfunction getWeek(date) {\n // Remove time components of date\n var targetThursday = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n\n // Change date to Thursday same week\n targetThursday.setDate(targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3);\n\n // Take January 4th as it is always in week 1 (see ISO 8601)\n var firstThursday = new Date(targetThursday.getFullYear(), 0, 4);\n\n // Change date to Thursday same week\n firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3);\n\n // Check if daylight-saving-time-switch occured and correct for it\n var ds = targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset();\n targetThursday.setHours(targetThursday.getHours() - ds);\n\n // Number of weeks between target Thursday and first Thursday\n var weekDiff = (targetThursday - firstThursday) / (86400000*7);\n return 1 + Math.floor(weekDiff);\n}\n\n/**\n * Get ISO-8601 numeric representation of the day of the week\n * 1 (for Monday) through 7 (for Sunday)\n * \n * @param {Object} `date`\n * @return {Number}\n */\nfunction getDayOfWeek(date) {\n var dow = date.getDay();\n if(dow === 0) {\n dow = 7;\n }\n return dow;\n}\n\n/**\n * kind-of shortcut\n * @param {*} val\n * @return {String}\n */\nfunction kindOf(val) {\n if (val === null) {\n return 'null';\n }\n\n if (val === undefined) {\n return 'undefined';\n }\n\n if (typeof val !== 'object') {\n return typeof val;\n }\n\n if (Array.isArray(val)) {\n return 'array';\n }\n\n return {}.toString.call(val)\n .slice(8, -1).toLowerCase();\n};\n\n\n\n if (true) {\n !(__WEBPACK_AMD_DEFINE_RESULT__ = function () {\n return dateFormat;\n }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n } else if (typeof exports === 'object') {\n module.exports = dateFormat;\n } else {\n global.dateFormat = dateFormat;\n }\n})(this);\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9+L2RhdGVmb3JtYXQvbGliL2RhdGVmb3JtYXQuanM/YzIzZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0EscUJBQXFCLElBQUksR0FBRyxJQUFJO0FBQ2hDLGtKQUFrSixFQUFFO0FBQ3BKOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsS0FBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLE9BQU87QUFDbkIsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVksT0FBTztBQUNuQixZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsWUFBWSxFQUFFO0FBQ2QsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUEsV0FBVztBQUNYO0FBQ0E7Ozs7QUFJQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsR0FBRztBQUNIO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQSxDQUFDIiwiZmlsZSI6IjQ0MC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiBEYXRlIEZvcm1hdCAxLjIuM1xuICogKGMpIDIwMDctMjAwOSBTdGV2ZW4gTGV2aXRoYW4gPHN0ZXZlbmxldml0aGFuLmNvbT5cbiAqIE1JVCBsaWNlbnNlXG4gKlxuICogSW5jbHVkZXMgZW5oYW5jZW1lbnRzIGJ5IFNjb3R0IFRyZW5kYSA8c2NvdHQudHJlbmRhLm5ldD5cbiAqIGFuZCBLcmlzIEtvd2FsIDxjaXhhci5jb20vfmtyaXMua293YWwvPlxuICpcbiAqIEFjY2VwdHMgYSBkYXRlLCBhIG1hc2ssIG9yIGEgZGF0ZSBhbmQgYSBtYXNrLlxuICogUmV0dXJucyBhIGZvcm1hdHRlZCB2ZXJzaW9uIG9mIHRoZSBnaXZlbiBkYXRlLlxuICogVGhlIGRhdGUgZGVmYXVsdHMgdG8gdGhlIGN1cnJlbnQgZGF0ZS90aW1lLlxuICogVGhlIG1hc2sgZGVmYXVsdHMgdG8gZGF0ZUZvcm1hdC5tYXNrcy5kZWZhdWx0LlxuICovXG5cbihmdW5jdGlvbihnbG9iYWwpIHtcbiAgJ3VzZSBzdHJpY3QnO1xuXG4gIHZhciBkYXRlRm9ybWF0ID0gKGZ1bmN0aW9uKCkge1xuICAgICAgdmFyIHRva2VuID0gL2R7MSw0fXxtezEsNH18eXkoPzp5eSk/fChbSGhNc1R0XSlcXDE/fFtMbG9TWldOXXwnW14nXSonfCdbXiddKicvZztcbiAgICAgIHZhciB0aW1lem9uZSA9IC9cXGIoPzpbUE1DRUFdW1NEUF1UfCg/OlBhY2lmaWN8TW91bnRhaW58Q2VudHJhbHxFYXN0ZXJufEF0bGFudGljKSAoPzpTdGFuZGFyZHxEYXlsaWdodHxQcmV2YWlsaW5nKSBUaW1lfCg/OkdNVHxVVEMpKD86Wy0rXVxcZHs0fSk/KVxcYi9nO1xuICAgICAgdmFyIHRpbWV6b25lQ2xpcCA9IC9bXi0rXFxkQS1aXS9nO1xuICBcbiAgICAgIC8vIFJlZ2V4ZXMgYW5kIHN1cHBvcnRpbmcgZnVuY3Rpb25zIGFyZSBjYWNoZWQgdGhyb3VnaCBjbG9zdXJlXG4gICAgICByZXR1cm4gZnVuY3Rpb24gKGRhdGUsIG1hc2ssIHV0YywgZ210KSB7XG4gIFxuICAgICAgICAvLyBZb3UgY2FuJ3QgcHJvdmlkZSB1dGMgaWYgeW91IHNraXAgb3RoZXIgYXJncyAodXNlIHRoZSAnVVRDOicgbWFzayBwcmVmaXgpXG4gICAgICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAxICYmIGtpbmRPZihkYXRlKSA9PT0gJ3N0cmluZycgJiYgIS9cXGQvLnRlc3QoZGF0ZSkpIHtcbiAgICAgICAgICBtYXNrID0gZGF0ZTtcbiAgICAgICAgICBkYXRlID0gdW5kZWZpbmVkO1xuICAgICAgICB9XG4gIFxuICAgICAgICBkYXRlID0gZGF0ZSB8fCBuZXcgRGF0ZTtcbiAgXG4gICAgICAgIGlmKCEoZGF0ZSBpbnN0YW5jZW9mIERhdGUpKSB7XG4gICAgICAgICAgZGF0ZSA9IG5ldyBEYXRlKGRhdGUpO1xuICAgICAgICB9XG4gIFxuICAgICAgICBpZiAoaXNOYU4oZGF0ZSkpIHtcbiAgICAgICAgICB0aHJvdyBUeXBlRXJyb3IoJ0ludmFsaWQgZGF0ZScpO1xuICAgICAgICB9XG4gIFxuICAgICAgICBtYXNrID0gU3RyaW5nKGRhdGVGb3JtYXQubWFza3NbbWFza10gfHwgbWFzayB8fCBkYXRlRm9ybWF0Lm1hc2tzWydkZWZhdWx0J10pO1xuICBcbiAgICAgICAgLy8gQWxsb3cgc2V0dGluZyB0aGUgdXRjL2dtdCBhcmd1bWVudCB2aWEgdGhlIG1hc2tcbiAgICAgICAgdmFyIG1hc2tTbGljZSA9IG1hc2suc2xpY2UoMCwgNCk7XG4gICAgICAgIGlmIChtYXNrU2xpY2UgPT09ICdVVEM6JyB8fCBtYXNrU2xpY2UgPT09ICdHTVQ6Jykge1xuICAgICAgICAgIG1hc2sgPSBtYXNrLnNsaWNlKDQpO1xuICAgICAgICAgIHV0YyA9IHRydWU7XG4gICAgICAgICAgaWYgKG1hc2tTbGljZSA9PT0gJ0dNVDonKSB7XG4gICAgICAgICAgICBnbXQgPSB0cnVlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICBcbiAgICAgICAgdmFyIF8gPSB1dGMgPyAnZ2V0VVRDJyA6ICdnZXQnO1xuICAgICAgICB2YXIgZCA9IGRhdGVbXyArICdEYXRlJ10oKTtcbiAgICAgICAgdmFyIEQgPSBkYXRlW18gKyAnRGF5J10oKTtcbiAgICAgICAgdmFyIG0gPSBkYXRlW18gKyAnTW9udGgnXSgpO1xuICAgICAgICB2YXIgeSA9IGRhdGVbXyArICdGdWxsWWVhciddKCk7XG4gICAgICAgIHZhciBIID0gZGF0ZVtfICsgJ0hvdXJzJ10oKTtcbiAgICAgICAgdmFyIE0gPSBkYXRlW18gKyAnTWludXRlcyddKCk7XG4gICAgICAgIHZhciBzID0gZGF0ZVtfICsgJ1NlY29uZHMnXSgpO1xuICAgICAgICB2YXIgTCA9IGRhdGVbXyArICdNaWxsaXNlY29uZHMnXSgpO1xuICAgICAgICB2YXIgbyA9IHV0YyA/IDAgOiBkYXRlLmdldFRpbWV6b25lT2Zmc2V0KCk7XG4gICAgICAgIHZhciBXID0gZ2V0V2VlayhkYXRlKTtcbiAgICAgICAgdmFyIE4gPSBnZXREYXlPZldlZWsoZGF0ZSk7XG4gICAgICAgIHZhciBmbGFncyA9IHtcbiAgICAgICAgICBkOiAgICBkLFxuICAgICAgICAgIGRkOiAgIHBhZChkKSxcbiAgICAgICAgICBkZGQ6ICBkYXRlRm9ybWF0LmkxOG4uZGF5TmFtZXNbRF0sXG4gICAgICAgICAgZGRkZDogZGF0ZUZvcm1hdC5pMThuLmRheU5hbWVzW0QgKyA3XSxcbiAgICAgICAgICBtOiAgICBtICsgMSxcbiAgICAgICAgICBtbTogICBwYWQobSArIDEpLFxuICAgICAgICAgIG1tbTogIGRhdGVGb3JtYXQuaTE4bi5tb250aE5hbWVzW21dLFxuICAgICAgICAgIG1tbW06IGRhdGVGb3JtYXQuaTE4bi5tb250aE5hbWVzW20gKyAxMl0sXG4gICAgICAgICAgeXk6ICAgU3RyaW5nKHkpLnNsaWNlKDIpLFxuICAgICAgICAgIHl5eXk6IHksXG4gICAgICAgICAgaDogICAgSCAlIDEyIHx8IDEyLFxuICAgICAgICAgIGhoOiAgIHBhZChIICUgMTIgfHwgMTIpLFxuICAgICAgICAgIEg6ICAgIEgsXG4gICAgICAgICAgSEg6ICAgcGFkKEgpLFxuICAgICAgICAgIE06ICAgIE0sXG4gICAgICAgICAgTU06ICAgcGFkKE0pLFxuICAgICAgICAgIHM6ICAgIHMsXG4gICAgICAgICAgc3M6ICAgcGFkKHMpLFxuICAgICAgICAgIGw6ICAgIHBhZChMLCAzKSxcbiAgICAgICAgICBMOiAgICBwYWQoTWF0aC5yb3VuZChMIC8gMTApKSxcbiAgICAgICAgICB0OiAgICBIIDwgMTIgPyAnYScgIDogJ3AnLFxuICAgICAgICAgIHR0OiAgIEggPCAxMiA/ICdhbScgOiAncG0nLFxuICAgICAgICAgIFQ6ICAgIEggPCAxMiA/ICdBJyAgOiAnUCcsXG4gICAgICAgICAgVFQ6ICAgSCA8IDEyID8gJ0FNJyA6ICdQTScsXG4gICAgICAgICAgWjogICAgZ210ID8gJ0dNVCcgOiB1dGMgPyAnVVRDJyA6IChTdHJpbmcoZGF0ZSkubWF0Y2godGltZXpvbmUpIHx8IFsnJ10pLnBvcCgpLnJlcGxhY2UodGltZXpvbmVDbGlwLCAnJyksXG4gICAgICAgICAgbzogICAgKG8gPiAwID8gJy0nIDogJysnKSArIHBhZChNYXRoLmZsb29yKE1hdGguYWJzKG8pIC8gNjApICogMTAwICsgTWF0aC5hYnMobykgJSA2MCwgNCksXG4gICAgICAgICAgUzogICAgWyd0aCcsICdzdCcsICduZCcsICdyZCddW2QgJSAxMCA+IDMgPyAwIDogKGQgJSAxMDAgLSBkICUgMTAgIT0gMTApICogZCAlIDEwXSxcbiAgICAgICAgICBXOiAgICBXLFxuICAgICAgICAgIE46ICAgIE5cbiAgICAgICAgfTtcbiAgXG4gICAgICAgIHJldHVybiBtYXNrLnJlcGxhY2UodG9rZW4sIGZ1bmN0aW9uIChtYXRjaCkge1xuICAgICAgICAgIGlmIChtYXRjaCBpbiBmbGFncykge1xuICAgICAgICAgICAgcmV0dXJuIGZsYWdzW21hdGNoXTtcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV0dXJuIG1hdGNoLnNsaWNlKDEsIG1hdGNoLmxlbmd0aCAtIDEpO1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfSkoKTtcblxuICBkYXRlRm9ybWF0Lm1hc2tzID0ge1xuICAgICdkZWZhdWx0JzogICAgICAgICAgICAgICAnZGRkIG1tbSBkZCB5eXl5IEhIOk1NOnNzJyxcbiAgICAnc2hvcnREYXRlJzogICAgICAgICAgICAgJ20vZC95eScsXG4gICAgJ21lZGl1bURhdGUnOiAgICAgICAgICAgICdtbW0gZCwgeXl5eScsXG4gICAgJ2xvbmdEYXRlJzogICAgICAgICAgICAgICdtbW1tIGQsIHl5eXknLFxuICAgICdmdWxsRGF0ZSc6ICAgICAgICAgICAgICAnZGRkZCwgbW1tbSBkLCB5eXl5JyxcbiAgICAnc2hvcnRUaW1lJzogICAgICAgICAgICAgJ2g6TU0gVFQnLFxuICAgICdtZWRpdW1UaW1lJzogICAgICAgICAgICAnaDpNTTpzcyBUVCcsXG4gICAgJ2xvbmdUaW1lJzogICAgICAgICAgICAgICdoOk1NOnNzIFRUIFonLFxuICAgICdpc29EYXRlJzogICAgICAgICAgICAgICAneXl5eS1tbS1kZCcsXG4gICAgJ2lzb1RpbWUnOiAgICAgICAgICAgICAgICdISDpNTTpzcycsXG4gICAgJ2lzb0RhdGVUaW1lJzogICAgICAgICAgICd5eXl5LW1tLWRkXFwnVFxcJ0hIOk1NOnNzbycsXG4gICAgJ2lzb1V0Y0RhdGVUaW1lJzogICAgICAgICdVVEM6eXl5eS1tbS1kZFxcJ1RcXCdISDpNTTpzc1xcJ1pcXCcnLFxuICAgICdleHBpcmVzSGVhZGVyRm9ybWF0JzogICAnZGRkLCBkZCBtbW0geXl5eSBISDpNTTpzcyBaJ1xuICB9O1xuXG4gIC8vIEludGVybmF0aW9uYWxpemF0aW9uIHN0cmluZ3NcbiAgZGF0ZUZvcm1hdC5pMThuID0ge1xuICAgIGRheU5hbWVzOiBbXG4gICAgICAnU3VuJywgJ01vbicsICdUdWUnLCAnV2VkJywgJ1RodScsICdGcmknLCAnU2F0JyxcbiAgICAgICdTdW5kYXknLCAnTW9uZGF5JywgJ1R1ZXNkYXknLCAnV2VkbmVzZGF5JywgJ1RodXJzZGF5JywgJ0ZyaWRheScsICdTYXR1cmRheSdcbiAgICBdLFxuICAgIG1vbnRoTmFtZXM6IFtcbiAgICAgICdKYW4nLCAnRmViJywgJ01hcicsICdBcHInLCAnTWF5JywgJ0p1bicsICdKdWwnLCAnQXVnJywgJ1NlcCcsICdPY3QnLCAnTm92JywgJ0RlYycsXG4gICAgICAnSmFudWFyeScsICdGZWJydWFyeScsICdNYXJjaCcsICdBcHJpbCcsICdNYXknLCAnSnVuZScsICdKdWx5JywgJ0F1Z3VzdCcsICdTZXB0ZW1iZXInLCAnT2N0b2JlcicsICdOb3ZlbWJlcicsICdEZWNlbWJlcidcbiAgICBdXG4gIH07XG5cbmZ1bmN0aW9uIHBhZCh2YWwsIGxlbikge1xuICB2YWwgPSBTdHJpbmcodmFsKTtcbiAgbGVuID0gbGVuIHx8IDI7XG4gIHdoaWxlICh2YWwubGVuZ3RoIDwgbGVuKSB7XG4gICAgdmFsID0gJzAnICsgdmFsO1xuICB9XG4gIHJldHVybiB2YWw7XG59XG5cbi8qKlxuICogR2V0IHRoZSBJU08gODYwMSB3ZWVrIG51bWJlclxuICogQmFzZWQgb24gY29tbWVudHMgZnJvbVxuICogaHR0cDovL3RlY2hibG9nLnByb2N1cmlvcy5ubC9rL242MTgvbmV3cy92aWV3LzMzNzk2LzE0ODYzL0NhbGN1bGF0ZS1JU08tODYwMS13ZWVrLWFuZC15ZWFyLWluLWphdmFzY3JpcHQuaHRtbFxuICpcbiAqIEBwYXJhbSAge09iamVjdH0gYGRhdGVgXG4gKiBAcmV0dXJuIHtOdW1iZXJ9XG4gKi9cbmZ1bmN0aW9uIGdldFdlZWsoZGF0ZSkge1xuICAvLyBSZW1vdmUgdGltZSBjb21wb25lbnRzIG9mIGRhdGVcbiAgdmFyIHRhcmdldFRodXJzZGF5ID0gbmV3IERhdGUoZGF0ZS5nZXRGdWxsWWVhcigpLCBkYXRlLmdldE1vbnRoKCksIGRhdGUuZ2V0RGF0ZSgpKTtcblxuICAvLyBDaGFuZ2UgZGF0ZSB0byBUaHVyc2RheSBzYW1lIHdlZWtcbiAgdGFyZ2V0VGh1cnNkYXkuc2V0RGF0ZSh0YXJnZXRUaHVyc2RheS5nZXREYXRlKCkgLSAoKHRhcmdldFRodXJzZGF5LmdldERheSgpICsgNikgJSA3KSArIDMpO1xuXG4gIC8vIFRha2UgSmFudWFyeSA0dGggYXMgaXQgaXMgYWx3YXlzIGluIHdlZWsgMSAoc2VlIElTTyA4NjAxKVxuICB2YXIgZmlyc3RUaHVyc2RheSA9IG5ldyBEYXRlKHRhcmdldFRodXJzZGF5LmdldEZ1bGxZZWFyKCksIDAsIDQpO1xuXG4gIC8vIENoYW5nZSBkYXRlIHRvIFRodXJzZGF5IHNhbWUgd2Vla1xuICBmaXJzdFRodXJzZGF5LnNldERhdGUoZmlyc3RUaHVyc2RheS5nZXREYXRlKCkgLSAoKGZpcnN0VGh1cnNkYXkuZ2V0RGF5KCkgKyA2KSAlIDcpICsgMyk7XG5cbiAgLy8gQ2hlY2sgaWYgZGF5bGlnaHQtc2F2aW5nLXRpbWUtc3dpdGNoIG9jY3VyZWQgYW5kIGNvcnJlY3QgZm9yIGl0XG4gIHZhciBkcyA9IHRhcmdldFRodXJzZGF5LmdldFRpbWV6b25lT2Zmc2V0KCkgLSBmaXJzdFRodXJzZGF5LmdldFRpbWV6b25lT2Zmc2V0KCk7XG4gIHRhcmdldFRodXJzZGF5LnNldEhvdXJzKHRhcmdldFRodXJzZGF5LmdldEhvdXJzKCkgLSBkcyk7XG5cbiAgLy8gTnVtYmVyIG9mIHdlZWtzIGJldHdlZW4gdGFyZ2V0IFRodXJzZGF5IGFuZCBmaXJzdCBUaHVyc2RheVxuICB2YXIgd2Vla0RpZmYgPSAodGFyZ2V0VGh1cnNkYXkgLSBmaXJzdFRodXJzZGF5KSAvICg4NjQwMDAwMCo3KTtcbiAgcmV0dXJuIDEgKyBNYXRoLmZsb29yKHdlZWtEaWZmKTtcbn1cblxuLyoqXG4gKiBHZXQgSVNPLTg2MDEgbnVtZXJpYyByZXByZXNlbnRhdGlvbiBvZiB0aGUgZGF5IG9mIHRoZSB3ZWVrXG4gKiAxIChmb3IgTW9uZGF5KSB0aHJvdWdoIDcgKGZvciBTdW5kYXkpXG4gKiBcbiAqIEBwYXJhbSAge09iamVjdH0gYGRhdGVgXG4gKiBAcmV0dXJuIHtOdW1iZXJ9XG4gKi9cbmZ1bmN0aW9uIGdldERheU9mV2VlayhkYXRlKSB7XG4gIHZhciBkb3cgPSBkYXRlLmdldERheSgpO1xuICBpZihkb3cgPT09IDApIHtcbiAgICBkb3cgPSA3O1xuICB9XG4gIHJldHVybiBkb3c7XG59XG5cbi8qKlxuICoga2luZC1vZiBzaG9ydGN1dFxuICogQHBhcmFtICB7Kn0gdmFsXG4gKiBAcmV0dXJuIHtTdHJpbmd9XG4gKi9cbmZ1bmN0aW9uIGtpbmRPZih2YWwpIHtcbiAgaWYgKHZhbCA9PT0gbnVsbCkge1xuICAgIHJldHVybiAnbnVsbCc7XG4gIH1cblxuICBpZiAodmFsID09PSB1bmRlZmluZWQpIHtcbiAgICByZXR1cm4gJ3VuZGVmaW5lZCc7XG4gIH1cblxuICBpZiAodHlwZW9mIHZhbCAhPT0gJ29iamVjdCcpIHtcbiAgICByZXR1cm4gdHlwZW9mIHZhbDtcbiAgfVxuXG4gIGlmIChBcnJheS5pc0FycmF5KHZhbCkpIHtcbiAgICByZXR1cm4gJ2FycmF5JztcbiAgfVxuXG4gIHJldHVybiB7fS50b1N0cmluZy5jYWxsKHZhbClcbiAgICAuc2xpY2UoOCwgLTEpLnRvTG93ZXJDYXNlKCk7XG59O1xuXG5cblxuICBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgZGVmaW5lKGZ1bmN0aW9uICgpIHtcbiAgICAgIHJldHVybiBkYXRlRm9ybWF0O1xuICAgIH0pO1xuICB9IGVsc2UgaWYgKHR5cGVvZiBleHBvcnRzID09PSAnb2JqZWN0Jykge1xuICAgIG1vZHVsZS5leHBvcnRzID0gZGF0ZUZvcm1hdDtcbiAgfSBlbHNlIHtcbiAgICBnbG9iYWwuZGF0ZUZvcm1hdCA9IGRhdGVGb3JtYXQ7XG4gIH1cbn0pKHRoaXMpO1xuXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9+L2RhdGVmb3JtYXQvbGliL2RhdGVmb3JtYXQuanNcbi8vIG1vZHVsZSBpZCA9IDQ0MFxuLy8gbW9kdWxlIGNodW5rcyA9IDIiXSwic291cmNlUm9vdCI6IiJ9"); 35 | 36 | /***/ } 37 | 38 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-smd", 3 | "description": "Simple Material Design components for Angular", 4 | "version": "1.0.0-SNAPSHOT", 5 | "license": "", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jefersonestevo/angular-smd.git" 9 | }, 10 | "scripts": { 11 | "start": "webpack-dev-server --inline --progress", 12 | "test": "karma start", 13 | "build": "rimraf dist && webpack --progress --profile --bail" 14 | }, 15 | "dependencies": { 16 | "@angular/common": "2.4.2", 17 | "@angular/compiler": "2.4.2", 18 | "@angular/core": "2.4.2", 19 | "@angular/forms": "2.4.2", 20 | "@angular/http": "2.4.2", 21 | "@angular/material": "2.0.0-beta.2", 22 | "@angular/platform-browser": "2.4.2", 23 | "@angular/platform-browser-dynamic": "2.4.2", 24 | "@angular/router": "3.4.2", 25 | "babel-core": "6.21.0", 26 | "babel-polyfill": "6.20.0", 27 | "dateformat": "2.0.0", 28 | "debug": "2.5.2", 29 | "hammerjs": "2.0.8", 30 | "reflect-metadata": "0.1.9", 31 | "rxjs": "5.0.3", 32 | "zone.js": "0.7.4" 33 | }, 34 | "devDependencies": { 35 | "@types/hammerjs": "2.0.34", 36 | "@types/jasmine": "2.5.40", 37 | "@types/node": "6.0.54", 38 | "@types/rx": "2.5.34", 39 | "angular2-template-loader": "0.6.0", 40 | "awesome-typescript-loader": "3.0.0-beta.17", 41 | "css-loader": "0.26.1", 42 | "extract-text-webpack-plugin": "1.0.1", 43 | "file-loader": "0.9.0", 44 | "html-loader": "0.4.4", 45 | "html-webpack-plugin": "2.24.1", 46 | "jasmine-core": "2.5.2", 47 | "karma": "1.3.0", 48 | "karma-jasmine": "1.1.0", 49 | "karma-phantomjs-launcher": "1.0.2", 50 | "karma-sourcemap-loader": "0.3.7", 51 | "karma-webpack": "1.8.1", 52 | "null-loader": "0.1.1", 53 | "phantomjs-prebuilt": "2.1.14", 54 | "raw-loader": "0.5.1", 55 | "style-loader": "0.13.1", 56 | "webpack": "1.14.0", 57 | "webpack-dev-server": "1.16.2", 58 | "webpack-merge": "2.0.0", 59 | "node-sass": "4.1.1", 60 | "sass-loader": "4.1.1", 61 | "babel-loader": "6.2.10", 62 | "babel-preset-es2015": "6.18.0", 63 | "rimraf": "2.5.4", 64 | "typescript": "2.1.4" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/src/_all-theme.scss: -------------------------------------------------------------------------------- 1 | @import '../../src/app/shared/component/smd-datatable/datatable-theme'; 2 | @import "../../src/app/shared/component/smd-paginator/paginator-theme"; 3 | @import "../../src/app/shared/component/smd-bottom-nav/smd-bottom-nav-theme"; 4 | @import "../../src/app/shared/component/smd-error-messages/smd-error-message-theme"; 5 | 6 | @mixin smd-material-theme($theme) { 7 | @include smd-datatable-theme($theme); 8 | @include smd-paginator-theme($theme); 9 | @include smd-bottom-nav-theme($theme); 10 | @include smd-error-message-theme($theme); 11 | } -------------------------------------------------------------------------------- /public/src/prebuilt/deeppurple-amber.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/all-theme"; 2 | @import "../all-theme"; 3 | 4 | // Define a theme. 5 | $primary: mat-palette($mat-deep-purple); 6 | $accent: mat-palette($mat-amber, A200, A100, A400); 7 | 8 | $theme: mat-light-theme($primary, $accent); 9 | 10 | // Include all theme styles for the components. 11 | @include angular-material-theme($theme); 12 | @include smd-material-theme($theme); 13 | 14 | $background: map-get($theme, background); 15 | .smd-content { 16 | background-color: mat-color($background, background); 17 | } -------------------------------------------------------------------------------- /public/src/prebuilt/indigo-pink.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/all-theme"; 2 | @import "../all-theme"; 3 | 4 | // Include non-theme styles for core. 5 | @include mat-core(); 6 | 7 | // Define a theme. 8 | $primary: mat-palette($mat-indigo); 9 | $accent: mat-palette($mat-pink, A200, A100, A400); 10 | 11 | $theme: mat-light-theme($primary, $accent); 12 | 13 | // Include all theme styles for the components. 14 | @include angular-material-theme($theme); 15 | @include smd-material-theme($theme); 16 | 17 | $background: map-get($theme, background); 18 | .smd-content { 19 | background-color: mat-color($background, background) !important; 20 | } -------------------------------------------------------------------------------- /public/src/prebuilt/pink-bluegrey.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/all-theme"; 2 | @import "../all-theme"; 3 | 4 | // Include non-theme styles for core. 5 | @include mat-core(); 6 | 7 | // Define a theme. 8 | $primary: mat-palette($mat-pink, 700, 500, 900); 9 | $accent: mat-palette($mat-blue-grey, A200, A100, A400); 10 | 11 | $theme: mat-dark-theme($primary, $accent); 12 | 13 | // Include all theme styles for the components. 14 | @include angular-material-theme($theme); 15 | @include smd-material-theme($theme); 16 | 17 | $background: map-get($theme, background); 18 | .smd-content { 19 | background-color: mat-color($background, background) !important; 20 | } -------------------------------------------------------------------------------- /public/src/prebuilt/purple-green.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/all-theme"; 2 | @import "../all-theme"; 3 | 4 | // Include non-theme styles for core. 5 | @include mat-core(); 6 | 7 | // Define a theme. 8 | $primary: mat-palette($mat-purple, 700, 500, 800); 9 | $accent: mat-palette($mat-green, A200, A100, A400); 10 | 11 | $theme: mat-dark-theme($primary, $accent); 12 | 13 | // Include all theme styles for the components. 14 | @include angular-material-theme($theme); 15 | @include smd-material-theme($theme); 16 | 17 | $background: map-get($theme, background); 18 | .smd-content { 19 | background-color: mat-color($background, background) !important; 20 | } -------------------------------------------------------------------------------- /public/src/styles.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/theming"; 2 | 3 | @import './prebuilt/indigo-pink'; 4 | 5 | .deeppurple-amber { 6 | @import './prebuilt/deeppurple-amber'; 7 | } 8 | 9 | .pink-bluegrey { 10 | @import './prebuilt/pink-bluegrey'; 11 | } 12 | 13 | .purple-green { 14 | @import './prebuilt/purple-green'; 15 | } -------------------------------------------------------------------------------- /src/app/app-demo/bottom-nav/demo-bottom-nav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | Test Search 11 | Card Test Search 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | Test Add 23 | Card Test Add 24 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | Test Place 35 | Card Test Place 36 | 37 | 38 | 39 | 40 | 44 | 45 | 46 | Test Close 47 | 48 | 49 | 50 | Item: {{item}} 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/app/app-demo/bottom-nav/demo-bottom-nav.scss: -------------------------------------------------------------------------------- 1 | smd-bottom-nav-group { 2 | height: 100vh; 3 | 4 | &.smd-myCustomColor { 5 | 6 | .smd-bottom-nav-actions { 7 | background-color: #d3d3d3; 8 | 9 | .smd-bottom-nav-action { 10 | color: #000000; 11 | 12 | &.smd-bottom-nav-active { 13 | color: #ffffff; 14 | background-color: #838383; 15 | } 16 | } 17 | } 18 | 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/app/app-demo/bottom-nav/demo-bottom-nav.ts: -------------------------------------------------------------------------------- 1 | import {Component, ViewEncapsulation} from "@angular/core"; 2 | 3 | // Using ViewEncapsulation.None so we can create a custom color for the bottom nav component 4 | @Component({ 5 | selector: 'demo-bottom-nav', 6 | templateUrl: './demo-bottom-nav.html', 7 | styleUrls: ['./demo-bottom-nav.scss'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class DemoBottomNav { 11 | items = Array.apply(null, {length: 80}).map((v: any, i: number) => i); 12 | } -------------------------------------------------------------------------------- /src/app/app-demo/datatable/demo-datatable.html: -------------------------------------------------------------------------------- 1 | 2 | Component - Datatable 3 | 4 | 5 | 6 | Responsive 7 | 8 | 9 | 10 | 15 | 16 | 19 | 20 | 21 | 24 | 26 | 27 | 28 | 29 | 31 | 32 | 34 | 35 | 36 | 37 | 40 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 |
62 |                     {{htmlExample}}
63 |                 
64 |
65 |
66 | 67 |
68 |
69 |                     {{tsExample}}
70 |                 
71 |
72 |
73 |
74 |
-------------------------------------------------------------------------------- /src/app/app-demo/datatable/demo-datatable.scss: -------------------------------------------------------------------------------- 1 | body { 2 | width: 100%; 3 | } 4 | 5 | .example-card { 6 | margin: 20px 80px 20px 80px; 7 | width: calc(100% - 160px); 8 | 9 | &.datatable-card { 10 | // FIXME - Card really needs to use a 20px padding as default? 11 | padding: 0 !important; 12 | } 13 | 14 | &:not(.datatable-card) { 15 | width: calc(100% - 205px); 16 | } 17 | } 18 | 19 | .demo-tab-group { 20 | border: 1px solid #e0e0e0; 21 | margin-bottom: 40px; 22 | .mat-tab-header { 23 | background: #f9f9f9; 24 | } 25 | .mat-tab-body-content { 26 | padding: 12px; 27 | } 28 | 29 | .tab-content { 30 | width: 100%; 31 | max-height: 800px; 32 | display: block; 33 | overflow: auto; 34 | } 35 | } 36 | 37 | @media screen and (max-width: 60em) { 38 | .example-card { 39 | margin: 20px 5px 20px 5px; 40 | width: calc(100% - 20px); 41 | 42 | &:not(.datatable-card) { 43 | width: calc(100% - 45px); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/app/app-demo/datatable/demo-datatable.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from "@angular/core"; 2 | import {DatePipe} from "@angular/common"; 3 | 4 | export class SampleModel { 5 | constructor(public id?: number, 6 | public name?: string, 7 | public surname?: string, 8 | public birthDate?: Date, 9 | public avatar?: string, 10 | public comment?: string) { 11 | } 12 | } 13 | 14 | @Component({ 15 | selector: 'demo-datatable', 16 | templateUrl: './demo-datatable.html', 17 | styleUrls: ['./demo-datatable.scss'] 18 | }) 19 | export class DemoDatatable implements OnInit { 20 | 21 | models: SampleModel[]; 22 | 23 | responsive: boolean = true; 24 | 25 | ngOnInit(): void { 26 | // Using a promisse here so angular will start another detect lifecycle 27 | Promise.resolve(null).then(() => { 28 | let count = 32; 29 | this.models = Array.apply(0, Array(count)) 30 | .map(function (element: any, index: any) { 31 | return { 32 | id: index, 33 | name: 'Name ' + index, 34 | surname: 'Surname ' + index, 35 | birthDate: (new Date().getTime() + (index * 10000010)), 36 | avatar: (index % 2 == 1 ? 'search' : 'add'), 37 | comment: (index <= 5 ? 'comment ' + index : null) 38 | }; 39 | }); 40 | }); 41 | } 42 | 43 | _sortByBirthDate(a: SampleModel, b:SampleModel, sortDir: string) { 44 | let dir = sortDir == 'asc' ? 1 : -1; 45 | if (a.birthDate < b.birthDate) return -1 * dir; 46 | if (a.birthDate > b.birthDate) return 1 * dir; 47 | return 0; 48 | } 49 | 50 | _filterByBirthDate(a: SampleModel, text: string) { 51 | let datePipe = new DatePipe("pt"); 52 | let value = datePipe.transform(a.birthDate,'dd/MM/yyyy'); 53 | return value.toString().toUpperCase().indexOf(text.toUpperCase()) > -1; 54 | } 55 | 56 | addSample() { 57 | console.log('add sample'); 58 | } 59 | 60 | editSample(samples: SampleModel[]) { 61 | console.log('editing sample: ' + JSON.stringify(samples)); 62 | } 63 | 64 | removeSample(samples: SampleModel[]) { 65 | console.log('removing sample: ' + JSON.stringify(samples)); 66 | } 67 | 68 | fieldChanged(event: any) { 69 | console.log('field changed'); 70 | console.log(event); 71 | } 72 | 73 | tsExample: string = ` 74 | export class DemoDatatableView implements OnInit { 75 | 76 | models: SampleModel[]; 77 | responsive: boolean = true; 78 | 79 | ngOnInit(): void { 80 | // Some server long processing query 81 | Promise.resolve(null).then(() => { 82 | this.models = Array.apply(0, Array(count)).map(function (element: any, index: any) { 83 | return { 84 | id: index, 85 | name: 'Name ' + index, 86 | surname: 'Surname ' + index, 87 | birthDate: (new Date().getTime() + (index * 10000010)), 88 | avatar: (index % 2 == 1 ? 'search' : 'add'), 89 | comment: (index <= 5 ? 'comment ' + index : null) 90 | }; 91 | }); 92 | }); 93 | } 94 | 95 | _sortByBirthDate(a: SampleModel, b:SampleModel, sortDir: string) { 96 | let dir = sortDir == 'asc' ? 1 : -1; 97 | if (a.birthDate < b.birthDate) return -1 * dir; 98 | if (a.birthDate > b.birthDate) return 1 * dir; 99 | return 0; 100 | } 101 | 102 | _filterByBirthDate(a: SampleModel, text: string) { 103 | let datePipe = new DatePipe("pt"); 104 | let value = datePipe.transform(a.birthDate,'dd/MM/yyyy'); 105 | return value.toString().toUpperCase().indexOf(text.toUpperCase()) > -1; 106 | } 107 | 108 | addSample() { 109 | console.log('add sample'); 110 | } 111 | 112 | editSample(samples: SampleModel[]) { 113 | console.log('editing sample: ' + JSON.stringify(samples)); 114 | } 115 | 116 | removeSample(samples: SampleModel[]) { 117 | console.log('removing sample: ' + JSON.stringify(samples)); 118 | } 119 | 120 | fieldChanged(event: any) { 121 | console.log('field changed'); 122 | console.log(event); 123 | } 124 | } 125 | `; 126 | 127 | htmlExample: string = ` 128 | 133 | 134 | 137 | 138 | 139 | 141 | 143 | 144 | 145 | 147 | 148 | 150 | 151 | 152 | 153 | 156 | 159 | 160 | 161 | 162 | 165 | 166 | 167 | 170 | 171 | `; 172 | } -------------------------------------------------------------------------------- /src/app/app-demo/demo.home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from "@angular/core"; 2 | 3 | @Component({ 4 | selector: 'angular-smd-demo-home', 5 | template: ``, 6 | styles: [``] 7 | }) 8 | export class DemoHomeComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/app-demo/error-messages/demo-error-messages.html: -------------------------------------------------------------------------------- 1 | 2 | Component - Error Messages 3 | 4 | 5 | 6 | Template Form 7 |
8 |
9 | 10 | 12 | 13 | 14 |
15 |
16 | 17 | 19 | 20 | 21 |
22 |
23 | 24 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | Reactive Form 36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 |
69 |
70 |
71 | 72 | 73 | 74 | 75 |
76 |
77 |                     {{htmlExample}}
78 |                 
79 |
80 |
81 | 82 |
83 |
84 |                     {{tsExample}}
85 |                 
86 |
87 |
88 |
89 |
-------------------------------------------------------------------------------- /src/app/app-demo/error-messages/demo-error-messages.scss: -------------------------------------------------------------------------------- 1 | md-card { 2 | margin: 10px; 3 | 4 | .row { 5 | display: flex; 6 | flex-direction: row; 7 | flex-wrap: wrap; 8 | align-content: flex-start; 9 | align-items: flex-start; 10 | justify-content: flex-start; 11 | 12 | .column { 13 | flex: 1 0 400px; 14 | margin: 0 10px; 15 | 16 | md-input-container { 17 | width: calc(100% - 20px); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/app/app-demo/error-messages/demo-error-messages.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from "@angular/core"; 2 | import {Validators, FormBuilder, FormGroup} from "@angular/forms"; 3 | 4 | @Component({ 5 | selector: 'demo-fab-speed-dial', 6 | templateUrl: './demo-error-messages.html', 7 | styleUrls: ['./demo-error-messages.scss'] 8 | }) 9 | export class DemoErrorMessages implements OnInit { 10 | requiredField: string; 11 | minLengthField: string; 12 | requiredFieldCustomMessage: string; 13 | 14 | myReactiveForm: FormGroup; 15 | 16 | constructor(private fb: FormBuilder) { 17 | } 18 | 19 | ngOnInit(): void { 20 | this.myReactiveForm = this.fb.group({ 21 | 'requiredFieldReactive': ['', Validators.required], 22 | 'minLengthFieldReactive': ['', Validators.minLength(3)], 23 | 'requiredFieldCustomMessageReactive': ['', Validators.required], 24 | 'requiredFieldCustomValidationReactive': ['', this.myCustomReactiveValidation()] 25 | }) 26 | } 27 | 28 | myCustomReactiveValidation(): any { 29 | return (control: any): {[key: string]: any} => { 30 | if (!control.value) { 31 | return; 32 | } 33 | return control.value == 'AAA' ? null : {myValidationError: {validItem: 'AAA'}}; 34 | }; 35 | } 36 | 37 | tsExample: string = ` 38 | export class DemoErrorMessages implements OnInit { 39 | requiredField: string; 40 | minLengthField: string; 41 | requiredFieldCustomMessage: string; 42 | 43 | myReactiveForm: FormGroup; 44 | 45 | constructor(private fb: FormBuilder) { 46 | } 47 | 48 | ngOnInit(): void { 49 | this.myReactiveForm = this.fb.group({ 50 | 'requiredFieldReactive': ['', Validators.required], 51 | 'minLengthFieldReactive': ['', Validators.minLength(3)], 52 | 'requiredFieldCustomMessageReactive': ['', Validators.required], 53 | 'requiredFieldCustomValidationReactive': ['', this.myCustomReactiveValidation()] 54 | }) 55 | } 56 | 57 | myCustomReactiveValidation(): any { 58 | return (control: any): {[key: string]: any} => { 59 | if (!control.value) { 60 | return; 61 | } 62 | return control.value == 'AAA' ? null : {myValidationError: {validItem: 'AAA'}}; 63 | }; 64 | } 65 | } 66 | `; 67 | 68 | htmlExample: string = ` 69 | 70 | Template Form 71 |
72 |
73 | 74 | 76 | 77 | 78 |
79 |
80 | 81 | 83 | 84 | 85 |
86 |
87 | 88 | 90 | 91 | 92 | 93 | 94 |
95 |
96 |
97 | 98 | 99 | Reactive Form 100 |
101 |
102 |
103 | 104 | 105 | 106 | 107 |
108 |
109 | 110 | 111 | 112 | 113 |
114 |
115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 |
124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 |
133 |
134 |
135 | `; 136 | 137 | } -------------------------------------------------------------------------------- /src/app/app-demo/fab-speed-dial/demo-fab-speed-dial.html: -------------------------------------------------------------------------------- 1 | 2 | Component - Fab Speed Dial 3 | 4 | 5 | 6 |
7 | Direction: 8 | 9 | Up 10 | Down 11 | Left 12 | Right 13 | 14 |
15 |
16 | Animation Mode: 17 | 18 | fling 19 | scale 20 | 21 |
22 |
23 | Fixed 24 | Enable Spinning 25 |
26 |
27 | 28 |
29 | 30 | Click me!! 31 | Open 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Hover me!! 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | 64 |
65 |
66 |                     {{htmlExample}}
67 |                 
68 |
69 |
70 | 71 |
72 |
73 |                     {{tsExample}}
74 |                 
75 |
76 |
77 |
78 |
-------------------------------------------------------------------------------- /src/app/app-demo/fab-speed-dial/demo-fab-speed-dial.scss: -------------------------------------------------------------------------------- 1 | md-card { 2 | margin: 30px; 3 | 4 | &.fab-demo-actions { 5 | display:flex; 6 | flex-direction: column; 7 | flex-wrap: wrap; 8 | justify-content: flex-start; 9 | align-content: flex-start; 10 | align-items: flex-start; 11 | 12 | & > div { 13 | min-height: 50px; 14 | 15 | & md-slide-toggle { 16 | display: inline-block; 17 | margin-right: 10px; 18 | } 19 | } 20 | } 21 | } 22 | 23 | .container-fab-demo { 24 | width: 100%; 25 | display:flex; 26 | flex-direction: row; 27 | flex-wrap: wrap; 28 | justify-content: flex-start; 29 | align-content: flex-start; 30 | align-items: flex-start; 31 | 32 | & md-card { 33 | height: 300px; 34 | padding: 20px; 35 | flex-grow: 1; 36 | flex-shrink: 0; 37 | flex-basis: 300px; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/app/app-demo/fab-speed-dial/demo-fab-speed-dial.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from "@angular/core"; 2 | 3 | @Component({ 4 | selector: 'demo-fab-speed-dial', 5 | templateUrl: './demo-fab-speed-dial.html', 6 | styleUrls: ['./demo-fab-speed-dial.scss'] 7 | }) 8 | export class DemoFabSpeedDial { 9 | 10 | private _fixed: boolean = false; 11 | 12 | open: boolean = false; 13 | spin: boolean = false; 14 | direction: string = 'up'; 15 | animationMode: string = 'fling'; 16 | 17 | get fixed() { return this._fixed; } 18 | set fixed(fixed: boolean) { 19 | this._fixed = fixed; 20 | if (this._fixed) { 21 | this.open = true; 22 | } 23 | } 24 | 25 | _click(event: any) { 26 | console.log(event); 27 | } 28 | 29 | tsExample: string = ` 30 | export class DemoFabSpeedDial { 31 | open: boolean = false; 32 | fixed: boolean = false; 33 | spin: boolean = false; 34 | direction: string = 'up'; 35 | animationMode: string = 'fling'; 36 | 37 | _click(event: any) { 38 | console.log(event); 39 | } 40 | } 41 | `; 42 | 43 | htmlExample: string = ` 44 | 45 | Click me!! 46 | Open 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Hover me!! 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | `; 75 | } -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, HostBinding} from "@angular/core"; 2 | 3 | import "../../public/src/styles.scss"; 4 | 5 | @Component({ 6 | selector: 'angular-smd', 7 | template: ` 8 | 9 |
10 | 11 | 14 | Angular SMD 15 |
16 | 17 | indigo-pink 18 | deeppurple-amber 19 | pink-bluegrey 20 | purple-green 21 | 22 |
23 |
24 |
25 | 26 |
27 | 28 | 29 | 30 | Components 31 | 32 | 33 | 34 | 35 |

DataTable

36 |
37 | 38 |

Fab Speed Dial

39 |
40 | 41 |

Bottom Nav

42 |
43 | 44 |

Error Messages

45 |
46 |
47 |
48 |
49 | `, 50 | styles: [` 51 | md-sidenav-container { 52 | top: 0; 53 | left: 0; 54 | position: fixed; 55 | width: 100%; 56 | height: 100%; 57 | display: block; 58 | } 59 | 60 | md-sidenav { 61 | min-width: 250px; 62 | } 63 | 64 | md-list-item { 65 | cursor: pointer; 66 | margin: 5px 3px; 67 | } 68 | 69 | md-list-item:hover { 70 | background-color: #e3e3e3; 71 | } 72 | 73 | .smd-header { 74 | width: 100%; 75 | z-index: 100; 76 | } 77 | 78 | .smd-header span { 79 | margin-right: 30px; 80 | } 81 | 82 | .smd-content { 83 | width: 100%; 84 | height: 100%; 85 | overflow: auto; 86 | } 87 | 88 | .separator { 89 | flex: 1 1 auto; 90 | } 91 | `] 92 | }) 93 | export class AppComponent { 94 | 95 | @HostBinding('class') theme:string = 'indigo-pink'; 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from "@angular/core"; 2 | import {routing, appRoutingProviders} from "./app.routes"; 3 | import {AppComponent} from "./app.component"; 4 | import {ComponentsModule} from "./shared/components.module"; 5 | import {DemoDatatable} from "./app-demo/datatable/demo-datatable"; 6 | import {DemoHomeComponent} from "./app-demo/demo.home"; 7 | import {DemoFabSpeedDial} from "./app-demo/fab-speed-dial/demo-fab-speed-dial"; 8 | import {DemoBottomNav} from "./app-demo/bottom-nav/demo-bottom-nav"; 9 | import {DemoErrorMessages} from "./app-demo/error-messages/demo-error-messages"; 10 | 11 | let COMPONENTS = [ 12 | DemoHomeComponent, 13 | DemoDatatable, 14 | DemoFabSpeedDial, 15 | DemoBottomNav, 16 | DemoErrorMessages, 17 | AppComponent 18 | ]; 19 | 20 | @NgModule({ 21 | imports: ComponentsModule.forRoot(routing), 22 | declarations: COMPONENTS, 23 | providers: [appRoutingProviders], 24 | bootstrap: [AppComponent], 25 | schemas : [CUSTOM_ELEMENTS_SCHEMA], 26 | entryComponents: [] 27 | }) 28 | export class AppModule { 29 | } -------------------------------------------------------------------------------- /src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders} from "@angular/core"; 2 | import {Routes, RouterModule} from "@angular/router"; 3 | import {APP_BASE_HREF} from "@angular/common"; 4 | import {DemoDatatable} from "./app-demo/datatable/demo-datatable"; 5 | import {DemoFabSpeedDial} from "./app-demo/fab-speed-dial/demo-fab-speed-dial"; 6 | import {DemoHomeComponent} from "./app-demo/demo.home"; 7 | import {DemoBottomNav} from "./app-demo/bottom-nav/demo-bottom-nav"; 8 | import {DemoErrorMessages} from "./app-demo/error-messages/demo-error-messages"; 9 | 10 | const appRoutes: Routes = [ 11 | { path: '', redirectTo:'/demo-home', pathMatch: 'full' }, 12 | { path: 'demo-home', component: DemoHomeComponent }, 13 | { path: 'demo-datatable', component: DemoDatatable }, 14 | { path: 'demo-fab-speed-dial', component: DemoFabSpeedDial }, 15 | { path: 'demo-bottom-nav', component: DemoBottomNav }, 16 | { path: 'demo-error-messages', component: DemoErrorMessages } 17 | ]; 18 | 19 | export const appRoutingProviders: any[] = [ 20 | { provide: APP_BASE_HREF, useValue: '/angular-smd' } 21 | ]; 22 | 23 | export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { useHash: true} ); -------------------------------------------------------------------------------- /src/app/shared/component/index.ts: -------------------------------------------------------------------------------- 1 | export * from './smd-paginator'; 2 | export * from './smd-datatable'; 3 | export * from './smd-fab-speed-dial'; 4 | export * from './smd-bottom-nav'; 5 | export * from './smd-error-messages'; 6 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-bottom-nav/_smd-bottom-nav-theme.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/palette"; 2 | 3 | @mixin smd-bottom-nav-color($color, $default: 300, $darker: 500) { 4 | .smd-bottom-nav-actions { 5 | background-color: mat-color($color, $default); 6 | 7 | .smd-bottom-nav-action { 8 | color: mat-contrast($color, $darker); 9 | 10 | &.smd-bottom-nav-active { 11 | background-color: mat-color($color, $darker); 12 | } 13 | } 14 | } 15 | } 16 | 17 | @mixin smd-bottom-nav-theme($theme) { 18 | $primary: map-get($theme, primary); 19 | $accent: map-get($theme, accent); 20 | $warn: map-get($theme, warn); 21 | $background: map-get($theme, background); 22 | $foreground: map-get($theme, foreground); 23 | 24 | smd-bottom-nav-group { 25 | 26 | &.smd-primary { 27 | @include smd-bottom-nav-color($primary, 500, 700); 28 | } 29 | 30 | &.smd-accent { 31 | @include smd-bottom-nav-color($accent, 500, 700); 32 | } 33 | 34 | &.smd-warn { 35 | @include smd-bottom-nav-color($warn, 500, 700); 36 | } 37 | 38 | &.smd-green { 39 | @include smd-bottom-nav-color($mat-green); 40 | } 41 | 42 | &.smd-blue { 43 | @include smd-bottom-nav-color($mat-blue); 44 | } 45 | 46 | &.smd-red { 47 | @include smd-bottom-nav-color($mat-red); 48 | } 49 | 50 | &.smd-yellow { 51 | @include smd-bottom-nav-color($mat-yellow); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-bottom-nav/index.ts: -------------------------------------------------------------------------------- 1 | export * from './smd-bottom-nav.component'; -------------------------------------------------------------------------------- /src/app/shared/component/smd-bottom-nav/smd-bottom-nav.component.html: -------------------------------------------------------------------------------- 1 |
2 | 9 |
10 |
11 | 18 |
19 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-bottom-nav/smd-bottom-nav.component.scss: -------------------------------------------------------------------------------- 1 | smd-bottom-nav-group { 2 | font-family: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | 4 | display: flex; 5 | flex-direction: column; 6 | flex-wrap: nowrap; 7 | justify-content: flex-start; 8 | align-content: stretch; 9 | align-items: stretch; 10 | 11 | .smd-bottom-nav-content { 12 | flex: 1 1 auto; 13 | z-index: 1; 14 | position: relative; 15 | overflow-x: hidden; 16 | overflow-y: auto; 17 | display: block; 18 | height: 100%; 19 | } 20 | 21 | .smd-bottom-nav-actions { 22 | flex: 0 0 auto; 23 | 24 | display:flex; 25 | flex-direction: row; 26 | flex-wrap: wrap; 27 | justify-content: center; 28 | align-content: stretch; 29 | align-items: stretch; 30 | 31 | height: 56px; 32 | 33 | transition: background-color 0.3s ease-in; 34 | 35 | .smd-bottom-nav-action { 36 | position: relative; 37 | cursor: pointer; 38 | 39 | .smd-ripple-area { 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | bottom: 0; 44 | right: 0; 45 | } 46 | 47 | flex: 1 0 80px; 48 | max-width: 168px; 49 | 50 | display:flex; 51 | flex-direction: column; 52 | flex-wrap: wrap; 53 | justify-content: center; 54 | align-content: center; 55 | align-items: center; 56 | 57 | padding: 8px 12px 0 12px; 58 | 59 | & > span { 60 | font-size: 12px; 61 | } 62 | 63 | &.smd-bottom-nav-active { 64 | padding-top: 6px; 65 | 66 | & > span { 67 | font-size: 14px; 68 | } 69 | } 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/app/shared/component/smd-bottom-nav/smd-bottom-nav.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ViewEncapsulation, 4 | Directive, 5 | ContentChild, 6 | TemplateRef, 7 | ContentChildren, 8 | QueryList, 9 | ViewChild, 10 | trigger, 11 | state, 12 | style, 13 | transition, 14 | animate, OnInit, Input, 15 | } from "@angular/core"; 16 | 17 | @Directive({ 18 | selector: "template[smdBottomNavLabel]", 19 | }) 20 | export class SmdBottomNavLabelDirective { 21 | 22 | constructor(public template: TemplateRef) {} 23 | 24 | } 25 | 26 | @Component({ 27 | selector: "smd-bottom-nav", 28 | template: ` 29 | 30 | 33 | ` 34 | }) 35 | export class SmdBottomNavComponent { 36 | 37 | public state:'active' | 'inactive'; 38 | 39 | @Input() public color: string = 'primary'; 40 | 41 | @ContentChild(SmdBottomNavLabelDirective) label: SmdBottomNavLabelDirective; 42 | @ViewChild('contentTemplate') content: TemplateRef; 43 | 44 | } 45 | 46 | @Component({ 47 | selector: "smd-bottom-nav-group", 48 | templateUrl: './smd-bottom-nav.component.html', 49 | styleUrls: ['./smd-bottom-nav.component.scss'], 50 | encapsulation: ViewEncapsulation.None, 51 | host: { 52 | '[class]': '_getClasses()' 53 | }, 54 | animations: [ 55 | trigger('itemState', [ 56 | state('inactive', style({opacity: '0'})), 57 | state('active', style({opacity: '1'})), 58 | transition('* => *', [ 59 | animate('1s ease-in-out') 60 | ]) 61 | ]) 62 | ] 63 | }) 64 | export class SmdBottomNavGroupComponent implements OnInit { 65 | _currentIndex:number; 66 | 67 | get currentIndex() { 68 | return this._currentIndex; 69 | } 70 | set currentIndex(index:number) { 71 | this._currentIndex = index; 72 | Promise.resolve(null).then(() => { 73 | this.navs.forEach((item, i) => { 74 | if (i == this.currentIndex) { 75 | item.state = 'active'; 76 | } else { 77 | item.state = 'inactive'; 78 | } 79 | }); 80 | }); 81 | } 82 | 83 | @ContentChildren(SmdBottomNavComponent) navs: QueryList; 84 | 85 | ngOnInit(): void { 86 | this.currentIndex = 0; 87 | } 88 | 89 | setActiveIndex(index:number) { 90 | let previousIndex = this.currentIndex; 91 | this.currentIndex = index; 92 | } 93 | 94 | _getClasses() { 95 | return `smd-${this._getSelectedItem(this.currentIndex).color}` 96 | } 97 | 98 | _getSelectedItem(index:number) { 99 | return this.navs.find((item, i) => i == index); 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /src/app/shared/component/smd-datatable/README.md: -------------------------------------------------------------------------------- 1 | # Simple Material Design Datatable 2 | 3 | Angular 2 datatable based on [Material Design Data Table](https://material.io/guidelines/components/data-tables.html) 4 | 5 | ### Usage 6 | 7 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | ### Properties 41 | 42 | #### smd-datatable 43 | 44 | | Property | Type | Default | Description | 45 | |------------------|--------------|---------------------|-------------------------------------------------| 46 | | models | any[] | [] | List of models to be shown | 47 | | paginated | boolean | true | If this datatable is paginated | 48 | | paginatorRanges | number[] | \[10, 25, 50, 100] | List of avaiable pages sizes for the datatable paginator | 49 | | responsive | boolean | false | If this datatable is responsible | 50 | 51 | 52 | #### smd-datatable-header 53 | 54 | | Property | Type | Default | Description | 55 | |------------------|--------------|---------------------|-------------------------------------------------| 56 | | enableFilter | boolean | true | Show the quick filter | 57 | | filterLabel | string | 'Filter' | The quickfilter label | 58 | 59 | #### smd-datatable-action-button 60 | 61 | | Property | Type | Default | Description | 62 | |------------------|--------------|---------------------|-------------------------------------------------| 63 | | label | string | | The label for the action button | 64 | | onClick | event | | The event sent when the user click on the action button | 65 | 66 | #### smd-datatable-contextual-button 67 | 68 | | Property | Type | Default | Description | 69 | |------------------|--------------|---------------------|-------------------------------------------------| 70 | | icon | string | | The icon for the contextual button | 71 | | onClick | event | | The event sent when the user click on the contextual button | 72 | | minimunSelected | number | | The minimun number of rows selected to show this contextual button | 73 | | maxSelected | number | | The maximun number of rows selected to show this contextual button | 74 | 75 | 76 | #### smd-datatable-column 77 | 78 | | Property | Type | Default | Description | 79 | |-----------------------|--------------|---------------------|-------------------------------------------------| 80 | | title | string | | The header of this column | 81 | | field | string | | The field (from models) to represent this column (when a template is not used, this field will be shown in the datatable cell| 82 | | numeric | boolean | false | If this column should be treated as numeric | 83 | | titleTooltip | string | | The tooltip for the header of this column | 84 | | sortable | boolean | false | If this column is sorted (default sort by this column field value) | 85 | | sortFn | Function | | If sortable, a custom function to sort this column | 86 | | filterFn | Function | | When filter is enabled, a custom function to filter this column | 87 | | editable | boolean | false | If this column can be edited | 88 | | editablePlaceholder | string | | The placeholder when the value of this editable column is empty | 89 | | onFieldChange | event | | The event sent when the user changes a editable field value | 90 | 91 | The smd-datatable-column enables the user to use a template to define the cell content: 92 | Example: 93 | 94 | 97 | 98 | ### TODO List 99 | 100 | - Enable inline text edit 101 | - Inline Menu 102 | - Enable lazy loading 103 | - Review css according to the spec 104 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-datatable/_datatable-theme.scss: -------------------------------------------------------------------------------- 1 | 2 | @mixin smd-datatable-theme($theme) { 3 | $primary: map-get($theme, primary); 4 | $accent: map-get($theme, accent); 5 | $warn: map-get($theme, warn); 6 | $background: map-get($theme, background); 7 | $foreground: map-get($theme, foreground); 8 | $base-border-color: mat-color($foreground, divider); 9 | $base-sorted-icon-selected: mat-color($foreground, text); 10 | 11 | smd-datatable { 12 | .smd-data-table { 13 | border-color: $base-border-color; 14 | background-color: transparent; 15 | 16 | tr { 17 | color: mat-color($foreground, text); 18 | } 19 | 20 | thead { 21 | border-bottom-color: $base-border-color; 22 | border-top-color: $base-border-color; 23 | 24 | tr { 25 | th { 26 | color: mat-color($foreground, text); 27 | 28 | &.smd-datatable-column-sortable { 29 | & > span { 30 | &:before { 31 | color: mat-color($foreground, hint-text); 32 | } 33 | 34 | &.smd-sorted-asc { 35 | &:before { 36 | color: $base-sorted-icon-selected; 37 | } 38 | } 39 | 40 | &.smd-sorted-desc { 41 | &:before { 42 | color: $base-sorted-icon-selected; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | } 50 | } 51 | 52 | tbody { 53 | tr { 54 | color: mat-color($foreground, text); 55 | 56 | border-top-color: $base-border-color; 57 | border-bottom-color: $base-border-color; 58 | 59 | &:hover { 60 | background-color: mat-color($background, hover); 61 | } 62 | 63 | &.is-selected { 64 | background-color: #F5F5F5; 65 | color: mat-color($mat-light-theme-foreground, text); 66 | } 67 | 68 | & .smd-column-title { 69 | color: mat-color($foreground, text); 70 | } 71 | 72 | td { 73 | &.smd-editable { 74 | & .smd-editable-field-placeholder { 75 | color: mat-color($foreground, hint-text); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | tfoot { 83 | tr { 84 | td { 85 | smd-paginator { 86 | color: mat-color($foreground, secondary-text); 87 | } 88 | } 89 | } 90 | } 91 | 92 | smd-datatable-header { 93 | background-color: transparent; 94 | 95 | & > div { 96 | button { 97 | color: mat-color($foreground, text); 98 | } 99 | } 100 | 101 | &.is-selected { 102 | background-color: mat-color($accent, 50); 103 | color: mat-color($primary); 104 | } 105 | } 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-datatable/datatable.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 | 8 | 13 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | 59 | 60 | 61 |
4 | 5 |
9 | 11 | 12 | 17 | 21 | {{column.title}} 22 | 23 |
27 | 29 | 30 |
44 | 45 | 49 | 50 | 51 |
55 | 56 | Não existem itens 57 | 58 |
62 |
63 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-datatable/datatable.component.scss: -------------------------------------------------------------------------------- 1 | smd-datatable { 2 | .smd-table-container { 3 | max-width: 100%; 4 | display: block; 5 | overflow-x: auto; 6 | 7 | .smd-data-table { 8 | width: 100%; 9 | overflow: hidden; 10 | 11 | position: relative; 12 | border-width: 1px; 13 | border-style: solid; 14 | border-collapse: collapse; 15 | white-space: nowrap; 16 | font-size: 13px; 17 | 18 | text-align: center; 19 | 20 | tr { 21 | position: relative; 22 | text-overflow: ellipsis; 23 | line-height: 24px; 24 | letter-spacing: 0; 25 | font-size: 12px; 26 | box-sizing: border-box; 27 | } 28 | 29 | thead { 30 | tr { 31 | height: 56px; 32 | border: none; 33 | 34 | th { 35 | border: none; 36 | 37 | cursor: default; 38 | font-size: 12px; 39 | 40 | font-weight: 700; 41 | text-align: left; 42 | padding: 0 12px; 43 | 44 | &:first-of-type { 45 | width: 30px; 46 | padding-left: 24px; 47 | text-align: left; 48 | } 49 | 50 | .smd-sortable-icon { 51 | display: none; 52 | } 53 | 54 | &.smd-numeric-column { 55 | text-align: right; 56 | padding: 0 12px 0 5px; 57 | } 58 | 59 | &.smd-datatable-header-checkbox { 60 | width: 30px; 61 | } 62 | 63 | &.smd-datatable-column-sortable { 64 | cursor: pointer; 65 | 66 | & > span { 67 | position: relative; 68 | 69 | &:before { 70 | position: absolute; 71 | transform: translateX(-100%); 72 | width: 13px; 73 | left: -6px; 74 | visibility: hidden; 75 | 76 | font-size: 16px; 77 | vertical-align: top; 78 | display: inline-block; 79 | font-family: 'Material Icons'; 80 | font-weight: normal; 81 | font-style: normal; 82 | margin: 0 6px 0 3px; 83 | -webkit-font-smoothing: antialiased; 84 | content: '\E5DB'; 85 | 86 | } 87 | 88 | &.smd-sorted-asc { 89 | &:before { 90 | visibility: visible; 91 | } 92 | } 93 | 94 | &.smd-sorted-desc { 95 | &:before { 96 | content: '\E5D8'; 97 | visibility: visible; 98 | } 99 | } 100 | } 101 | 102 | &:hover { 103 | span:before { 104 | visibility: visible; 105 | } 106 | } 107 | } 108 | } 109 | 110 | &.smd-datatable-responsive-header { 111 | display: none; 112 | } 113 | } 114 | } 115 | 116 | tbody { 117 | tr { 118 | height: 48px; 119 | font-size: 12px; 120 | 121 | position: relative; 122 | transition-duration: .28s; 123 | transition-timing-function: cubic-bezier(.4, 0, .2, 1); 124 | transition-property: background-color; 125 | 126 | border-bottom-width: 1px; 127 | border-bottom-style: solid; 128 | border-top-width: 1px; 129 | border-top-style: solid; 130 | 131 | td { 132 | vertical-align: middle; 133 | position: relative; 134 | box-sizing: border-box; 135 | text-align: left; 136 | padding: 0 10px; 137 | 138 | &:first-of-type { 139 | padding-left: 24px; 140 | text-align: left; 141 | } 142 | 143 | &:last-of-type { 144 | padding-right: 24px; 145 | } 146 | 147 | &.smd-numeric-column { 148 | text-align: right; 149 | padding: 0 12px 0 5px; 150 | } 151 | 152 | &.smd-editable { 153 | cursor: pointer; 154 | } 155 | 156 | & .smd-column-title { 157 | display: none; 158 | font-size: 12px; 159 | font-weight: 700; 160 | } 161 | } 162 | } 163 | } 164 | 165 | tfoot { 166 | tr { 167 | height: 56px; 168 | text-align: center; 169 | 170 | td { 171 | smd-paginator { 172 | float: right; 173 | } 174 | } 175 | } 176 | } 177 | 178 | smd-datatable-header { 179 | display: flex; 180 | flex-direction: row; 181 | flex-wrap: wrap; 182 | justify-content: space-between; 183 | align-content: flex-end; 184 | align-items: flex-start; 185 | 186 | min-height: 64px; 187 | padding: 5px 15px 0 15px; 188 | width: calc(100% - 30px); 189 | 190 | transition-duration: .28s; 191 | transition-timing-function: cubic-bezier(.4, 0, .2, 1); 192 | transition-property: background-color; 193 | 194 | smd-datatable-action-button, smd-datatable-contextual-button { 195 | margin: 12px 0; 196 | 197 | button { 198 | text-transform: uppercase; 199 | } 200 | } 201 | 202 | md-input-container { 203 | & .mat-input-wrapper { 204 | margin: 0; 205 | } 206 | } 207 | 208 | & > div, & > span { 209 | height: 100%; 210 | display: flex; 211 | flex-direction: column; 212 | flex-wrap: wrap; 213 | justify-content: center; 214 | align-content: flex-start; 215 | align-items: flex-start; 216 | } 217 | 218 | & > div { 219 | font-size: 20px; 220 | 221 | & > span { 222 | padding-left: 20px; 223 | } 224 | } 225 | 226 | & > span { 227 | font-size: 22px; 228 | 229 | & > div > * { 230 | display: inline-block; 231 | } 232 | } 233 | 234 | &.is-selected { 235 | 236 | & > span { 237 | padding-right: 10px; 238 | } 239 | } 240 | } 241 | } 242 | } 243 | 244 | } 245 | 246 | @media screen and (max-width: 35em) { 247 | smd-datatable { 248 | .smd-data-table { 249 | smd-datatable-header { 250 | & > span { 251 | font-size: 18px; 252 | } 253 | } 254 | } 255 | 256 | &.smd-responsive { 257 | .smd-data-table { 258 | thead { 259 | tr { 260 | display: none; 261 | 262 | &.smd-datatable-responsive-header { 263 | display: table-row; 264 | } 265 | } 266 | } 267 | 268 | tbody { 269 | tr { 270 | td { 271 | &.smd-datatable-body-checkbox { 272 | width: 25px; 273 | } 274 | 275 | &:not(.smd-datatable-body-checkbox) { 276 | min-height: 25px; 277 | vertical-align: middle; 278 | padding: 1px 0; 279 | 280 | text-align: left; 281 | display: block; 282 | width: 100%; 283 | -webkit-box-sizing: border-box; 284 | -moz-box-sizing: border-box; 285 | box-sizing: border-box; 286 | float: left; 287 | clear: left; 288 | 289 | & .smd-column-title { 290 | min-width: 30%; 291 | display: inline-block; 292 | padding-left: 10px; 293 | } 294 | } 295 | } 296 | } 297 | } 298 | } 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-datatable/datatable.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Directive, 4 | ViewEncapsulation, 5 | Input, 6 | Output, 7 | Inject, 8 | forwardRef, 9 | EventEmitter, 10 | DoCheck, 11 | IterableDiffers, 12 | ViewChild, 13 | ContentChild, 14 | ContentChildren, 15 | QueryList, 16 | EmbeddedViewRef, 17 | TemplateRef, 18 | ViewContainerRef, 19 | ElementRef, 20 | AfterContentInit, 21 | OnInit, 22 | OnDestroy, 23 | ChangeDetectorRef 24 | } from "@angular/core"; 25 | import {isNullOrUndefined} from "util"; 26 | import {SmdPaginatorComponent} from "../smd-paginator/paginator.component"; 27 | import {Subscription} from "rxjs/Subscription"; 28 | import {MdDialogRef, MdDialog, MdDialogConfig} from '@angular/material'; 29 | 30 | let columnIds = 0; 31 | 32 | export class SmdDataRowModel { 33 | originalOrder?: number; 34 | 35 | constructor(public model: any, 36 | public checked?: boolean) { 37 | } 38 | } 39 | 40 | @Component({ 41 | selector: "smd-change-value-dialog", 42 | template: ` 43 |

{{title}}

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | `, 54 | styles: [` 55 | * { 56 | font-family: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; 57 | } 58 | 59 | md-dialog-actions { 60 | float: right; 61 | } 62 | 63 | md-dialog-content { 64 | min-width: 150px; 65 | padding: 5px 30px; 66 | } 67 | `] 68 | }) 69 | export class SmdDatatableDialogChangeValue { 70 | 71 | public title: string; 72 | public placeholder: string; 73 | public value: string; 74 | 75 | constructor(public dialogRef: MdDialogRef) { 76 | } 77 | 78 | _save() { 79 | this.dialogRef.close(this.value ? this.value : ''); 80 | } 81 | 82 | _cancel() { 83 | this.dialogRef.close(); 84 | } 85 | } 86 | 87 | @Directive({ 88 | selector: '[smd-data-cell]' 89 | }) 90 | export class SmdDataTableCellComponent implements OnInit, OnDestroy { 91 | @Input() column: SmdDataTableColumnComponent; 92 | @Input() data: any; 93 | @Input() templ: TemplateRef; 94 | 95 | childView: EmbeddedViewRef; 96 | 97 | constructor(private _viewContainer: ViewContainerRef, private _elementRef: ElementRef) { } 98 | 99 | ngOnInit(): void { 100 | if (this._viewContainer && this.templ) { 101 | this.childView = this._viewContainer.createEmbeddedView(this.templ, this); 102 | } 103 | } 104 | 105 | ngOnDestroy(): void { 106 | this.childView.destroy(); 107 | } 108 | } 109 | 110 | @Component({ 111 | selector: "[smd-datatable-row]", 112 | template: ` 113 | 114 |
115 | 116 | 117 |
118 | 119 | 123 | 124 | {{column.title}} 125 | 126 | 127 | 128 | {{column.editablePlaceholder}} 129 | 130 | 131 | ` 132 | }) 133 | export class SmdDataTableRowComponent { 134 | @Input() row: SmdDataRowModel; 135 | @Input() renderCheckbox: boolean; 136 | @Input() columns: SmdDataTableColumnComponent[]; 137 | 138 | constructor(@Inject(forwardRef(() => SmdDataTable)) private _parent: SmdDataTable, private dialog: MdDialog, private viewContainerRef: ViewContainerRef) { 139 | } 140 | 141 | _onClick(column: SmdDataTableColumnComponent, model: any) { 142 | if (column.editable) { 143 | let dialogRef: MdDialogRef; 144 | let dialogConfig = new MdDialogConfig(); 145 | dialogConfig.viewContainerRef = this.viewContainerRef; 146 | 147 | dialogRef = this.dialog.open(SmdDatatableDialogChangeValue, dialogConfig); 148 | 149 | dialogRef.componentInstance.title = column.editablePlaceholder; 150 | dialogRef.componentInstance.placeholder = column.title; 151 | dialogRef.componentInstance.value = model[column.field]; 152 | 153 | dialogRef.afterClosed().subscribe((result) => { 154 | if (typeof result == 'string') { 155 | let oldValue = model[column.field]; 156 | if (oldValue != result) { 157 | model[column.field] = result; 158 | column.onFieldChange.emit({ 159 | model: model, 160 | field: column.field, 161 | oldValue: oldValue, 162 | newValue: result 163 | }); 164 | } 165 | } 166 | }); 167 | } 168 | } 169 | 170 | } 171 | 172 | @Component({ 173 | selector: "smd-datatable-column", 174 | template: ` 175 | 176 | 179 | ` 180 | }) 181 | export class SmdDataTableColumnComponent implements OnInit { 182 | sortDir?: 'asc' | 'desc' = null; 183 | id: string = '' + ++columnIds; 184 | 185 | @Input() title: string; 186 | @Input() titleTooltip: string; 187 | @Input() field: string; 188 | @Input() numeric: boolean = false; 189 | @Input() sortable: boolean = false; 190 | @Input() sortFn: (a:any, b:any, sortDir: string) => number; 191 | @Input() filterFn: (a:any, text: string) => boolean; 192 | @Input() editable: boolean = false; 193 | @Input() editablePlaceholder: string; 194 | 195 | @ContentChild(TemplateRef) _customTemplate: TemplateRef; 196 | @ViewChild('internalTemplate') _internalTemplate: TemplateRef; 197 | 198 | @Output() onFieldChange: EventEmitter = new EventEmitter(); 199 | 200 | get template() { 201 | return this._customTemplate ? this._customTemplate : this._internalTemplate; 202 | } 203 | 204 | get hasCustomTemplate():boolean { 205 | return !!this._customTemplate; 206 | } 207 | 208 | constructor(private _viewContainer: ViewContainerRef, private elementRef: ElementRef) { 209 | } 210 | 211 | ngOnInit(): void { 212 | if (!this.title) { 213 | throw new Error('Title is mandatory on smd-datatable-column'); 214 | } 215 | if (!this.field) { 216 | throw new Error('Field is mandatory on smd-datatable-column'); 217 | } 218 | } 219 | 220 | getFieldValue(model: any) { 221 | return model[this.field]; 222 | } 223 | } 224 | 225 | @Component({ 226 | selector: "smd-datatable-action-button", 227 | template: ` 228 | 234 | ` 235 | }) 236 | export class SmdDatatableActionButton { 237 | @Input() label: string; 238 | @Output() onClick: EventEmitter = new EventEmitter(); 239 | 240 | constructor(@Inject(forwardRef(() => SmdDataTable)) private _parent: SmdDataTable) { 241 | } 242 | 243 | _onButtonClick(event: Event) { 244 | this.onClick.emit(); 245 | } 246 | 247 | _checkButtonIsVisible() { 248 | return this._parent.selectedRows().length == 0; 249 | } 250 | } 251 | 252 | @Component({ 253 | selector: "smd-datatable-contextual-button", 254 | template: ` 255 | 260 | ` 261 | }) 262 | export class SmdContextualDatatableButton { 263 | @Input() icon: string; 264 | @Input() minimunSelected: number = -1; 265 | @Input() maxSelected: number = -1; 266 | @Output() onClick: EventEmitter = new EventEmitter(); 267 | 268 | constructor(@Inject(forwardRef(() => SmdDataTable)) private _parent: SmdDataTable) { 269 | } 270 | 271 | _onButtonClick(event: Event) { 272 | this.onClick.emit(this._parent.selectedModels()); 273 | } 274 | 275 | _checkButtonIsVisible() { 276 | let shouldShow = true; 277 | if (this.minimunSelected != null && this.minimunSelected > 0 && this._parent.selectedRows().length < this.minimunSelected) { 278 | shouldShow = false; 279 | } 280 | if (shouldShow && this.maxSelected > 0 && this._parent.selectedRows().length > this.maxSelected) { 281 | shouldShow = false; 282 | } 283 | return shouldShow; 284 | } 285 | } 286 | 287 | @Component({ 288 | selector: "smd-datatable-header", 289 | template: ` 290 |
291 | {{title}} 292 | 293 | 294 | {{_selectedRowsLength()}} {{_selectedRowsLength() == 1 ? 'item selected' : 'items selected'}} 295 | 296 |
297 | 298 |
299 | 300 | 301 | 302 | 303 |
304 |
305 | `, 306 | host: { 307 | '[class.is-selected]': '_hasRowsSelected()' 308 | } 309 | }) 310 | export class SmdDatatableHeader implements AfterContentInit, OnDestroy { 311 | 312 | private filterTimeout: any; 313 | public filterValue: string; 314 | 315 | @Input() title: string = null; 316 | @Input() enableFilter: boolean = false; 317 | @Input() filterLabel: string = "Filter"; 318 | @Input() filterDelay: number = 500; 319 | 320 | @ContentChildren(SmdDatatableActionButton) actionButtons: QueryList; 321 | @ContentChildren(SmdContextualDatatableButton) contextualButtons: QueryList; 322 | 323 | constructor(@Inject(forwardRef(() => SmdDataTable)) private _parent: SmdDataTable) { 324 | } 325 | 326 | public shouldRenderCheckbox() { 327 | return this.contextualButtons && this.contextualButtons.toArray().filter((button: SmdContextualDatatableButton) => button.minimunSelected > 0).length > 0; 328 | } 329 | 330 | private _hasRowsSelected(): boolean { 331 | return this._parent.selectedRows().length > 0; 332 | } 333 | 334 | private _selectedRowsLength(): number { 335 | return this._parent.selectedRows().length; 336 | } 337 | 338 | private _onFilter(event : any) : void { 339 | if(this.filterTimeout) { 340 | clearTimeout(this.filterTimeout); 341 | } 342 | 343 | this.filterTimeout = setTimeout(() => { 344 | this._parent._onFilter(event); 345 | this.filterTimeout = null; 346 | }, this.filterDelay); 347 | } 348 | 349 | ngAfterContentInit(): void { 350 | if (this.title && this.actionButtons.length > 0) { 351 | throw new Error('You must either define a title or action buttons to the datatable-header, not both'); 352 | } 353 | } 354 | 355 | ngOnDestroy(): void { 356 | if (this.filterTimeout) { 357 | clearTimeout(this.filterTimeout); 358 | } 359 | } 360 | } 361 | 362 | @Component({ 363 | selector: "smd-datatable", 364 | templateUrl: "./datatable.component.html", 365 | styleUrls: ["./datatable.component.scss"], 366 | encapsulation: ViewEncapsulation.None, 367 | host: { 368 | '[class.smd-responsive]': 'responsive' 369 | } 370 | }) 371 | export class SmdDataTable implements DoCheck, AfterContentInit, OnDestroy { 372 | 373 | private rows: SmdDataRowModel[] = []; 374 | private visibleRows: SmdDataRowModel[] = []; 375 | private differ: any; 376 | private _columnsSubscription: Subscription; 377 | 378 | get rowCount(): number { 379 | return this.rows.length; 380 | } 381 | 382 | @ViewChild(SmdPaginatorComponent) paginatorComponent: SmdPaginatorComponent; 383 | @ContentChild(SmdDatatableHeader) header: SmdDatatableHeader; 384 | @ContentChildren(SmdDataTableColumnComponent) columns: QueryList; 385 | 386 | @Input() models: any[] = []; 387 | @Input() checked: boolean = false; 388 | @Input() paginated: boolean = true; 389 | @Input() paginatorRanges: number[] = [10, 25, 50, 100]; 390 | @Input() responsive: boolean = false; 391 | 392 | @Output() onRowSelected: EventEmitter<{model: any, checked: boolean}> = new EventEmitter<{model: any, checked: boolean}>(); 393 | @Output() onAllRowsSelected: EventEmitter = new EventEmitter(); 394 | 395 | constructor(differs: IterableDiffers, private _viewContainer: ViewContainerRef, public changeDetector: ChangeDetectorRef) { 396 | this.differ = differs.find([]).create(null); 397 | } 398 | 399 | ngAfterContentInit() { 400 | this._updateRows(); 401 | this._columnsSubscription = this.columns.changes.subscribe(() => { 402 | this._updateRows(); 403 | this.changeDetector.markForCheck(); 404 | }); 405 | } 406 | 407 | ngDoCheck(): void { 408 | let changes = this.differ.diff(this.models); 409 | if (changes) { 410 | if (this.columns) { 411 | this._updateRows(); 412 | } 413 | } 414 | } 415 | 416 | ngOnDestroy(): void { 417 | this._columnsSubscription.unsubscribe(); 418 | } 419 | 420 | _updateRows() { 421 | if (this.models) { 422 | this.rows.length = 0; 423 | this.models.forEach((model: any, index: number) => this.rows[index] = new SmdDataRowModel(model, this.checked)); 424 | this.rows = this.rows.filter((row: SmdDataRowModel) => this._matches(row, this.columns.toArray(), this.header.filterValue)); 425 | this.rows.forEach((row, index) => row.originalOrder = index); 426 | this._updateVisibleRows(); 427 | } 428 | } 429 | 430 | _matches(row: SmdDataRowModel, columns: SmdDataTableColumnComponent[], text: string):boolean { 431 | if(isNullOrUndefined(text) || text.trim() == '') { 432 | return true; 433 | } 434 | 435 | let subtexts : string[] = text.trim().split(" "); 436 | for(let subtext of subtexts) { 437 | for (let column of columns) { 438 | let filterFn = this._filterValue; 439 | let value = column.getFieldValue(row.model); 440 | if (column.hasCustomTemplate) { 441 | value = row.model; 442 | filterFn = column.filterFn ? column.filterFn : (value: any, text: string) => false; 443 | } 444 | if (filterFn(value, subtext)) { 445 | return true; 446 | } 447 | } 448 | } 449 | return false; 450 | } 451 | 452 | private _filterValue(value: any, text: string):boolean { 453 | return value && value.toString().toUpperCase().indexOf(text.toString().toUpperCase()) > -1; 454 | } 455 | 456 | selectedRows(): SmdDataRowModel[] { 457 | return this.rows.filter(row => row.checked); 458 | } 459 | 460 | selectedModels(): any[] { 461 | return this.selectedRows().map(row => row.model); 462 | } 463 | 464 | _onMasterCheckChange() { 465 | this.rows 466 | .forEach( 467 | (row : SmdDataRowModel) => { 468 | if(row.checked != this.checked) { 469 | row.checked = this.checked; 470 | } 471 | } 472 | ); 473 | this.onAllRowsSelected.emit(this.checked); 474 | } 475 | 476 | _onRowCheckChange(row: SmdDataRowModel) { 477 | let isMasterChecked = this.checked; 478 | if (row.checked) { 479 | if (this.rows.filter((row) => row.checked).length == this.rows.length) { 480 | this.checked = true; 481 | } 482 | } else { 483 | if (this.checked) { 484 | this.checked = false; 485 | } 486 | } 487 | this.onRowSelected.emit({ 488 | model: row.model, 489 | checked: row.checked 490 | }); 491 | 492 | if (this.checked != isMasterChecked) { 493 | this.onAllRowsSelected.emit(this.checked); 494 | } 495 | } 496 | 497 | _onFilter(event : any) : void { 498 | this.paginatorComponent.reset(); 499 | this._updateRows(); 500 | } 501 | 502 | _sortColumn(column: SmdDataTableColumnComponent) { 503 | if (column.sortable) { 504 | this.columns.filter((col) => col.id != column.id).forEach((col) => col.sortDir = null); 505 | 506 | if (!column.sortDir) { 507 | column.sortDir = 'asc'; 508 | } else { 509 | column.sortDir = column.sortDir == 'asc' ? 'desc' : null; 510 | } 511 | 512 | if (column.sortDir != null) { 513 | this.rows.sort((itemA: SmdDataRowModel, itemB: SmdDataRowModel) => { 514 | let sortFn = column.sortFn ? column.sortFn : this._sortRows; 515 | let a = itemA.model; 516 | let b = itemB.model; 517 | if (!column.sortFn) { 518 | a = column.getFieldValue(itemA.model); 519 | b = column.getFieldValue(itemB.model); 520 | } 521 | return sortFn(a, b, column.sortDir); 522 | }); 523 | } else { 524 | this.rows.sort((itemA: SmdDataRowModel, itemB: SmdDataRowModel) => { 525 | return this._sortRows(itemA.originalOrder, itemB.originalOrder); 526 | }); 527 | } 528 | this._updateVisibleRows(); 529 | } 530 | } 531 | 532 | _sortRows(a: any, b: any, sortDir: string = 'asc') { 533 | let dir = (sortDir == 'asc' ? 1 : -1); 534 | if (a > b) { 535 | return 1 * dir; 536 | } 537 | if (a < b) { 538 | return -1 * dir; 539 | } 540 | return 0; 541 | } 542 | 543 | _onPageChange() { 544 | this._updateVisibleRows() 545 | } 546 | 547 | _columnTemplates() { 548 | return this.columns.toArray().map((c) => c.template); 549 | } 550 | 551 | public refresh() { 552 | this._updateRows(); 553 | } 554 | 555 | private _updateVisibleRows() { 556 | if (this.paginated) { 557 | this.visibleRows = this.rows.filter((value: SmdDataRowModel, index: number) => this.paginatorComponent.currentPage.isInsidePage(index)); 558 | } else { 559 | this.visibleRows = this.rows; 560 | } 561 | } 562 | 563 | private _shouldRenderCheckbox() { 564 | return this.rows.length > 0 && this.header.shouldRenderCheckbox(); 565 | } 566 | } -------------------------------------------------------------------------------- /src/app/shared/component/smd-datatable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './datatable.component'; -------------------------------------------------------------------------------- /src/app/shared/component/smd-error-messages/_smd-error-message-theme.scss: -------------------------------------------------------------------------------- 1 | 2 | @mixin smd-error-message-theme($theme) { 3 | $warn: map-get($theme, warn); 4 | 5 | smd-error-messages { 6 | color: mat-color($warn); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-error-messages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './smd-error-message.component'; -------------------------------------------------------------------------------- /src/app/shared/component/smd-error-messages/smd-error-message.component.scss: -------------------------------------------------------------------------------- 1 | smd-error-messages { 2 | display: block; 3 | font-size: 12px; 4 | min-height: 16px; 5 | position: relative; 6 | 7 | .smd-error-message { 8 | display: none; 9 | 10 | &.smd-visible { 11 | display: block; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/app/shared/component/smd-error-messages/smd-error-message.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | OnInit, 5 | ContentChildren, 6 | QueryList, 7 | ViewEncapsulation, 8 | Directive, 9 | ViewContainerRef, 10 | TemplateRef, 11 | ViewChildren, 12 | OnDestroy 13 | } from "@angular/core"; 14 | import {FormControl, NgModel} from "@angular/forms"; 15 | import {Subscription} from "rxjs"; 16 | 17 | @Directive({ 18 | selector: "template[smdErrorMessage]", 19 | host: { 20 | '[class.smd-error-message]': 'true', 21 | '[class.smd-visible]': 'visible' 22 | } 23 | }) 24 | export class SmdErrorMessageComponent { 25 | private visible:boolean = false; 26 | 27 | @Input() for: string | string[]; 28 | @Input() error: any; 29 | 30 | get forList():string[] { 31 | if (typeof this.for === 'string') { 32 | this.for = [this.for]; 33 | } 34 | return this.for; 35 | } 36 | 37 | constructor(private viewContainer: ViewContainerRef, private template: TemplateRef) { 38 | } 39 | 40 | contains(key: string):boolean { 41 | return this.forList && !!this.forList.find((elem) => elem == key); 42 | } 43 | 44 | show(error: any) { 45 | this.error = error; 46 | if (!this.visible) { 47 | this.viewContainer.createEmbeddedView(this.template, this); 48 | } 49 | this.visible = true; 50 | } 51 | 52 | hide() { 53 | if (this.visible) { 54 | this.viewContainer.clear(); 55 | } 56 | this.visible = false; 57 | } 58 | } 59 | 60 | @Component({ 61 | selector: "smd-error-messages", 62 | template: ` 63 | 64 | 65 | 66 | 67 | `, 68 | styleUrls: ['smd-error-message.component.scss'], 69 | encapsulation: ViewEncapsulation.None 70 | }) 71 | export class SmdErrorMessagesComponent implements OnInit, OnDestroy { 72 | private _subscriptions: Subscription[] = []; 73 | private formControl: FormControl; 74 | 75 | @Input() control: NgModel | FormControl; 76 | 77 | @ContentChildren(SmdErrorMessageComponent) _messages: QueryList; 78 | @ViewChildren(SmdErrorMessageComponent) _internalMessages: QueryList; 79 | 80 | ngOnInit(): void { 81 | if ((this.control).control) { 82 | this.formControl = (this.control).control; 83 | } else { 84 | this.formControl = this.control as FormControl; 85 | } 86 | 87 | // Wrap markAsTouched to update this component when the component is first touched 88 | let originalMarkAsTouched = this.formControl.markAsTouched; 89 | this.formControl.markAsTouched = (elem: any) => { 90 | let wasTouched = this.formControl.touched; 91 | originalMarkAsTouched.apply(this.formControl, [elem]); 92 | if (!wasTouched) { 93 | this.updateErrorMessages(); 94 | } 95 | }; 96 | 97 | this._subscriptions.push(this.formControl.valueChanges.subscribe(() => { 98 | this.updateErrorMessages(); 99 | })); 100 | this._subscriptions.push(this.formControl.statusChanges.subscribe(() => { 101 | this.updateErrorMessages(); 102 | })); 103 | } 104 | 105 | ngOnDestroy(): void { 106 | this._subscriptions.forEach((sub) => sub.unsubscribe()); 107 | } 108 | 109 | private updateErrorMessages() { 110 | if (this.formControl.touched) { 111 | let messages = this.mergeMessages(); 112 | messages.forEach((message) => { 113 | let error:any = null; 114 | 115 | for (let key in this.formControl.errors) { 116 | if (message.contains(key)) { 117 | error = this.formControl.errors[key]; 118 | break; 119 | } 120 | } 121 | 122 | if (error) { 123 | message.show(error); 124 | } else { 125 | message.hide(); 126 | } 127 | }); 128 | } 129 | } 130 | 131 | private mergeMessages() { 132 | let newMessages: SmdErrorMessageComponent[] = []; 133 | let keys = {}; 134 | 135 | this._messages.forEach((message) => { 136 | newMessages.push(message); 137 | message.forList.forEach((key) => { 138 | keys[key] = 1; 139 | }); 140 | }); 141 | 142 | this._internalMessages.forEach((message) => { 143 | if (!message.forList.find((elem) => keys[elem])) { 144 | newMessages.push(message); 145 | message.forList.forEach((key) => { 146 | keys[key] = 1; 147 | }); 148 | } 149 | }); 150 | 151 | return newMessages; 152 | } 153 | } -------------------------------------------------------------------------------- /src/app/shared/component/smd-fab-speed-dial/README.md: -------------------------------------------------------------------------------- 1 | # Simple Material Design FAB Speed Dial 2 | 3 | Angular 2 FAB Speed Dial based on [AngularJS FAB Speed Dial](https://material.angularjs.org/latest/demo/fabSpeedDial) 4 | 5 | ### Usage 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ### Properties 20 | 21 | #### smd-fab-speed-dial 22 | 23 | | Property | Type | Default | Description | 24 | |------------------|--------------|---------------------|-------------------------------------------------| 25 | | open | boolean | false | Indicates if this FAB Speed Dial is opened | 26 | | direction | up, down, left or right | up | The direction to open the action buttons | 27 | | animationMode | fling or scale | fling | The animation to apply when opening the action buttons | 28 | | fixed | boolean | false | Indicates if this FAB Speed Dial is fixed (user cannot change the open state on click) | 29 | 30 | #### smd-fab-trigger 31 | 32 | | Property | Type | Default | Description | 33 | |------------------|--------------|---------------------|-------------------------------------------------| 34 | | spin | boolean | false | Enables the rotation (360dg) of the trigger action when the speed dial is opening | 35 | 36 | ### TODO List 37 | 38 | - Change color of the fab buttons on hover/selection 39 | - Make the trigger button change icon when the user open the speed dial (configurable) 40 | - Let the speed dial open a "sheet" of material instead of just mini-fab action buttons 41 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-fab-speed-dial/fab-speed-dial.scss: -------------------------------------------------------------------------------- 1 | @mixin smd-fab-speed-dial-container($box-orient, $flex-direction) { 2 | -webkit-box-orient: $box-orient; 3 | -webkit-box-direction: normal; 4 | -webkit-flex-direction: $flex-direction; 5 | flex-direction: $flex-direction; 6 | } 7 | 8 | @mixin smd-fab-speed-dial-box-order($ordinal-group, $order) { 9 | -webkit-box-ordinal-group: $ordinal-group; 10 | -webkit-order: $order; 11 | order: $order; 12 | } 13 | 14 | @mixin smd-fab-speed-dial-actions($box-orient, $box-direction, $flex-direction, $ordinal-group, $order, $action-item-margin-direction) { 15 | -webkit-box-orient: $box-orient; 16 | -webkit-box-direction: $box-direction; 17 | -webkit-flex-direction: $flex-direction; 18 | flex-direction: $flex-direction; 19 | 20 | @include smd-fab-speed-dial-box-order($ordinal-group, $order); 21 | 22 | & .smd-fab-action-item { 23 | margin-#{$action-item-margin-direction}: 10px; 24 | } 25 | } 26 | 27 | smd-fab-speed-dial { 28 | display: inline-block; 29 | 30 | &.smd-opened { 31 | .smd-fab-speed-dial-container { 32 | smd-fab-trigger { 33 | &.smd-spin { 34 | -webkit-transform: rotate(360deg); 35 | transform: rotate(360deg); 36 | } 37 | } 38 | } 39 | } 40 | 41 | .smd-fab-speed-dial-container { 42 | position: relative; 43 | display: -webkit-box; 44 | display: -webkit-flex; 45 | display: flex; 46 | -webkit-box-align: center; 47 | -webkit-align-items: center; 48 | align-items: center; 49 | z-index: 20; 50 | 51 | smd-fab-trigger { 52 | pointer-events: auto; 53 | z-index: 24; 54 | 55 | &.smd-spin { 56 | -webkit-transition: all .6s cubic-bezier(.4,0,.2,1); 57 | transition: all .6s cubic-bezier(.4,0,.2,1); 58 | } 59 | } 60 | 61 | smd-fab-actions { 62 | display: -webkit-box; 63 | display: -webkit-flex; 64 | display: flex; 65 | height: auto; 66 | } 67 | } 68 | 69 | &.smd-fling { 70 | .smd-fab-speed-dial-container { 71 | smd-fab-actions { 72 | & .smd-fab-action-item { 73 | display: block; 74 | opacity: 1; 75 | -webkit-transition: all .3s cubic-bezier(.55, 0, .55, .2); 76 | transition: all .3s cubic-bezier(.55, 0, .55, .2); 77 | } 78 | } 79 | } 80 | } 81 | 82 | &.smd-scale { 83 | .smd-fab-speed-dial-container { 84 | smd-fab-actions { 85 | & .smd-fab-action-item { 86 | -webkit-transform: scale(0); 87 | transform: scale(0); 88 | -webkit-transition: all .3s cubic-bezier(.55, 0, .55, .2); 89 | transition: all .3s cubic-bezier(.55, 0, .55, .2); 90 | -webkit-transition-duration: .14286s; 91 | transition-duration: .14286s; 92 | } 93 | } 94 | } 95 | } 96 | 97 | &.smd-down { 98 | .smd-fab-speed-dial-container { 99 | @include smd-fab-speed-dial-container(vertical, column); 100 | 101 | & smd-fab-trigger { 102 | @include smd-fab-speed-dial-box-order(2, 1); 103 | } 104 | 105 | & smd-fab-actions { 106 | @include smd-fab-speed-dial-actions(vertical, normal, column, 3, 2, top); 107 | } 108 | } 109 | } 110 | 111 | &.smd-up { 112 | .smd-fab-speed-dial-container { 113 | @include smd-fab-speed-dial-container(vertical, column); 114 | 115 | & smd-fab-trigger { 116 | @include smd-fab-speed-dial-box-order(3, 2); 117 | } 118 | 119 | & smd-fab-actions { 120 | @include smd-fab-speed-dial-actions(vertical, reverse, column-reverse, 2, 1, bottom); 121 | } 122 | } 123 | } 124 | 125 | &.smd-left { 126 | .smd-fab-speed-dial-container { 127 | @include smd-fab-speed-dial-container(horizontal, row); 128 | 129 | & smd-fab-trigger { 130 | @include smd-fab-speed-dial-box-order(3, 2); 131 | } 132 | 133 | & smd-fab-actions { 134 | @include smd-fab-speed-dial-actions(horizontal, normal, row-reverse, 2, 1, right); 135 | } 136 | } 137 | } 138 | 139 | &.smd-right { 140 | .smd-fab-speed-dial-container { 141 | @include smd-fab-speed-dial-container(horizontal, row); 142 | 143 | & smd-fab-trigger { 144 | @include smd-fab-speed-dial-box-order(2, 1); 145 | } 146 | 147 | & smd-fab-actions { 148 | @include smd-fab-speed-dial-actions(horizontal, normal, row, 3, 2, left); 149 | } 150 | } 151 | } 152 | 153 | } 154 | 155 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-fab-speed-dial/fab-speed-dial.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | Output, 5 | EventEmitter, 6 | ViewEncapsulation, 7 | AfterContentInit, 8 | ElementRef, 9 | Renderer, 10 | Inject, 11 | forwardRef, 12 | ContentChildren, 13 | QueryList, 14 | ContentChild, 15 | HostBinding, 16 | HostListener 17 | } from "@angular/core"; 18 | import {MdButton} from "@angular/material"; 19 | 20 | const Z_INDEX_ITEM: number = 23; 21 | 22 | @Component({ 23 | selector: 'smd-fab-trigger', 24 | template: ` 25 | 26 | ` 27 | }) 28 | export class SmdFabSpeedDialTrigger { 29 | 30 | /** 31 | * Whether this trigger should spin (360dg) while opening the speed dial 32 | */ 33 | @HostBinding('class.smd-spin') 34 | @Input() spin: boolean = false; 35 | 36 | constructor(@Inject(forwardRef(() => SmdFabSpeedDialComponent)) private _parent: SmdFabSpeedDialComponent) { 37 | } 38 | 39 | @HostListener('click', ['$event']) 40 | _onClick(event: any) { 41 | if (!this._parent.fixed) { 42 | this._parent.toggle(); 43 | event.stopPropagation(); 44 | } 45 | } 46 | 47 | } 48 | 49 | @Component({ 50 | selector: 'smd-fab-actions', 51 | template: ` 52 | 53 | ` 54 | }) 55 | export class SmdFabSpeedDialActions implements AfterContentInit { 56 | 57 | @ContentChildren(MdButton) _buttons: QueryList; 58 | 59 | constructor(@Inject(forwardRef(() => SmdFabSpeedDialComponent)) private _parent: SmdFabSpeedDialComponent, private renderer: Renderer) { 60 | } 61 | 62 | ngAfterContentInit(): void { 63 | this._buttons.changes.subscribe(() => { 64 | this.initButtonStates(); 65 | this._parent.setActionsVisibility(); 66 | }); 67 | 68 | this.initButtonStates(); 69 | } 70 | 71 | private initButtonStates() { 72 | this._buttons.toArray().forEach((button, i) => { 73 | this.renderer.setElementClass(button._getHostElement(), 'smd-fab-action-item', true); 74 | this.changeElementStyle(button._getHostElement(), 'z-index', '' + (Z_INDEX_ITEM - i)); 75 | }) 76 | } 77 | 78 | show() { 79 | if (this._buttons) { 80 | this._buttons.toArray().forEach((button, i) => { 81 | let transitionDelay = 0; 82 | let transform; 83 | if (this._parent.animationMode == 'scale') { 84 | // Incremental transition delay of 65ms for each action button 85 | transitionDelay = 3 + (65 * i); 86 | transform = 'scale(1)'; 87 | } else { 88 | transform = this.getTranslateFunction('0'); 89 | } 90 | this.changeElementStyle(button._getHostElement(), 'transition-delay', transitionDelay + 'ms'); 91 | this.changeElementStyle(button._getHostElement(), 'opacity', '1'); 92 | this.changeElementStyle(button._getHostElement(), 'transform', transform); 93 | }) 94 | } 95 | } 96 | 97 | hide() { 98 | if (this._buttons) { 99 | this._buttons.toArray().forEach((button, i) => { 100 | let opacity = '1'; 101 | let transitionDelay = 0; 102 | let transform; 103 | if (this._parent.animationMode == 'scale') { 104 | transitionDelay = 3 - (65 * i); 105 | transform = 'scale(0)'; 106 | opacity = '0'; 107 | } else { 108 | transform = this.getTranslateFunction((55 * (i + 1) - (i * 5)) + 'px'); 109 | } 110 | this.changeElementStyle(button._getHostElement(), 'transition-delay', transitionDelay + 'ms'); 111 | this.changeElementStyle(button._getHostElement(), 'opacity', opacity); 112 | this.changeElementStyle(button._getHostElement(), 'transform', transform); 113 | }) 114 | } 115 | } 116 | 117 | private getTranslateFunction(value: string) { 118 | let dir = this._parent.direction; 119 | let translateFn = (dir == 'up' || dir == 'down') ? 'translateY' : 'translateX'; 120 | let sign = (dir == 'down' || dir == 'right') ? '-' : ''; 121 | return translateFn + '(' + sign + value + ')'; 122 | } 123 | 124 | private changeElementStyle(elem: any, style: string, value: string) { 125 | // FIXME - Find a way to create a "wrapper" around the action button(s) provided by the user, so we don't change it's style tag 126 | this.renderer.setElementStyle(elem, style, value); 127 | } 128 | } 129 | 130 | @Component({ 131 | selector: 'smd-fab-speed-dial', 132 | template: ` 133 |
134 | 135 | 136 |
137 | `, 138 | styleUrls: ['fab-speed-dial.scss'], 139 | encapsulation: ViewEncapsulation.None 140 | }) 141 | export class SmdFabSpeedDialComponent implements AfterContentInit { 142 | private isInitialized: boolean = false; 143 | private _direction: string = 'up'; 144 | private _open: boolean = false; 145 | private _animationMode: string = 'fling'; 146 | 147 | /** 148 | * Whether this speed dial is fixed on screen (user cannot change it by clicking) 149 | */ 150 | @Input() fixed: boolean = false; 151 | 152 | /** 153 | * Whether this speed dial is opened 154 | */ 155 | @HostBinding('class.smd-opened') 156 | @Input() get open() { 157 | return this._open; 158 | } 159 | 160 | set open(open: boolean) { 161 | let previousOpen = this._open; 162 | this._open = open; 163 | if (previousOpen != this._open) { 164 | this.openChange.emit(this._open); 165 | if (this.isInitialized) { 166 | this.setActionsVisibility(); 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * The direction of the speed dial. Can be 'up', 'down', 'left' or 'right' 173 | */ 174 | @Input() get direction() { 175 | return this._direction; 176 | } 177 | 178 | set direction(direction: string) { 179 | let previousDir = this._direction; 180 | this._direction = direction; 181 | if (previousDir != this.direction) { 182 | this._setElementClass(previousDir, false); 183 | this._setElementClass(this.direction, true); 184 | 185 | if (this.isInitialized) { 186 | this.setActionsVisibility(); 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * The animation mode to open the speed dial. Can be 'fling' or 'scale' 193 | */ 194 | @Input() get animationMode() { 195 | return this._animationMode; 196 | } 197 | 198 | set animationMode(animationMode: string) { 199 | let previousAnimationMode = this._animationMode; 200 | this._animationMode = animationMode; 201 | if (previousAnimationMode != this._animationMode) { 202 | this._setElementClass(previousAnimationMode, false); 203 | this._setElementClass(this.animationMode, true); 204 | 205 | if (this.isInitialized) { 206 | // To start another detect lifecycle and force the "close" on the action buttons 207 | Promise.resolve(null).then(() => this.open = false); 208 | } 209 | } 210 | } 211 | 212 | @Output() openChange: EventEmitter = new EventEmitter(); 213 | 214 | @ContentChild(SmdFabSpeedDialActions) _childActions: SmdFabSpeedDialActions; 215 | 216 | constructor(private elementRef: ElementRef, private renderer: Renderer) { 217 | } 218 | 219 | ngAfterContentInit(): void { 220 | this.isInitialized = true; 221 | this.setActionsVisibility(); 222 | this._setElementClass(this.direction, true); 223 | this._setElementClass(this.animationMode, true); 224 | } 225 | 226 | /** 227 | * Toggle the open state of this speed dial 228 | */ 229 | public toggle() { 230 | this.open = !this.open; 231 | } 232 | 233 | @HostListener('click') 234 | _onClick() { 235 | if (!this.fixed && this.open) { 236 | this.open = false; 237 | } 238 | } 239 | 240 | setActionsVisibility() { 241 | if (this.open) { 242 | this._childActions.show(); 243 | } else { 244 | this._childActions.hide(); 245 | } 246 | } 247 | 248 | private _setElementClass(elemClass:string , isAdd:boolean) { 249 | this.renderer.setElementClass(this.elementRef.nativeElement, `smd-${elemClass}`, isAdd); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-fab-speed-dial/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fab-speed-dial'; -------------------------------------------------------------------------------- /src/app/shared/component/smd-paginator/_paginator-theme.scss: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/core/theming/theming"; 2 | 3 | @mixin smd-paginator-theme($theme) { 4 | $primary: map-get($theme, primary); 5 | $accent: map-get($theme, accent); 6 | $warn: map-get($theme, warn); 7 | $background: map-get($theme, background); 8 | $foreground: map-get($theme, foreground); 9 | 10 | .smd-paginator { 11 | display: flex; 12 | flex-direction: row; 13 | flex-wrap: wrap; 14 | justify-content: flex-start; 15 | align-content: center; 16 | align-items: center; 17 | 18 | .smd-paginator-range { 19 | select { 20 | border: none; 21 | border-bottom-color: mat-color($foreground, divider); 22 | 23 | option { 24 | color: #000000; 25 | } 26 | } 27 | } 28 | 29 | .smd-paginator-navigation { 30 | 31 | button { 32 | color: mat-color($foreground, text); 33 | background: transparent; 34 | 35 | &[disabled] { 36 | color: mat-color($foreground, disabled-text); 37 | } 38 | } 39 | 40 | } 41 | 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/app/shared/component/smd-paginator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './paginator.component'; -------------------------------------------------------------------------------- /src/app/shared/component/smd-paginator/paginator.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Rows per page: 4 | 5 | 6 | {{range}} 7 | 8 | 9 |
10 |
11 | {{pageStart}}-{{pageEnd}} of {{count}} 12 |
13 |
14 | 17 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-paginator/paginator.component.scss: -------------------------------------------------------------------------------- 1 | .smd-paginator { 2 | display:flex; 3 | flex-direction: row; 4 | flex-wrap: wrap; 5 | justify-content: flex-start; 6 | align-content: center; 7 | align-items: center; 8 | 9 | & .smd-paginator-range { 10 | display:flex; 11 | flex-direction: row; 12 | flex-wrap: wrap; 13 | justify-content: flex-start; 14 | align-content: flex-start; 15 | align-items: center; 16 | 17 | & .mat-select-trigger { 18 | min-width: 50px !important; 19 | width: 50px !important; 20 | left: 10px; 21 | } 22 | } 23 | 24 | .smd-paginator-current-page { 25 | margin-left: 5px; 26 | min-width: 100px; 27 | } 28 | 29 | .smd-paginator-navigation { 30 | margin-left: 5px; 31 | display:flex; 32 | flex-direction: row; 33 | flex-wrap: wrap; 34 | justify-content: flex-start; 35 | align-content: flex-start; 36 | align-items: flex-start; 37 | 38 | button { 39 | display:flex; 40 | flex-direction: column; 41 | flex-wrap: wrap; 42 | justify-content: center; 43 | align-content: flex-start; 44 | align-items: flex-start; 45 | 46 | width: 30px; 47 | height: 30px; 48 | cursor: pointer; 49 | padding: 0; 50 | min-width: 0; 51 | flex-shrink: 0; 52 | line-height: 40px; 53 | border-radius: 50%; 54 | 55 | outline: none; 56 | border: none; 57 | white-space: nowrap; 58 | text-decoration: none; 59 | vertical-align: middle; 60 | font-size: 14px; 61 | font-family: Roboto,"Helvetica Neue",sans-serif; 62 | font-weight: 500; 63 | text-align: center; 64 | margin: 0; 65 | 66 | -webkit-user-select: none; 67 | -moz-user-select: none; 68 | -ms-user-select: none; 69 | user-select: none; 70 | 71 | &:focus, &:active, &:hover, &::-moz-focus-inner, &::-moz-selection { 72 | outline:0; 73 | } 74 | 75 | &[disabled] { 76 | cursor: default; 77 | } 78 | } 79 | 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/app/shared/component/smd-paginator/paginator.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnInit, ViewEncapsulation} from "@angular/core"; 2 | 3 | export class SmdPaginationModel { 4 | constructor(public page : number, 5 | public size : number) { 6 | } 7 | 8 | public isInsidePage(index : number):boolean { 9 | let end = (this.page * this.size) - 1; 10 | let begin = end - this.size + 1; 11 | return index >= begin && index <= end; 12 | } 13 | } 14 | 15 | @Component({ 16 | selector: "smd-paginator", 17 | templateUrl: "./paginator.component.html", 18 | styleUrls: ["./paginator.component.scss"], 19 | encapsulation: ViewEncapsulation.None 20 | }) 21 | export class SmdPaginatorComponent implements OnInit { 22 | 23 | private _selectedRange : number; 24 | 25 | @Input() selectedPage : number = 1; 26 | @Input() count : number = 0; 27 | @Input() ranges : number[] = [10, 25, 50, 100]; 28 | @Input() set selectedRange(selectedRange: number) { 29 | let current = this._selectedRange; 30 | this._selectedRange = selectedRange; 31 | 32 | if (current != this._selectedRange) { 33 | this.reset(); 34 | } 35 | } 36 | 37 | get selectedRange(): number { 38 | return this._selectedRange; 39 | } 40 | 41 | @Output() pageChange : EventEmitter = new EventEmitter(); 42 | 43 | ngOnInit(): void { 44 | if (!this.selectedRange) { 45 | this.selectedRange = this.ranges[0]; 46 | } 47 | } 48 | 49 | onPreviousClick() { 50 | if (this.selectedPage > 1) { 51 | this.selectedPage -= 1; 52 | this.pageChange.emit(this.currentPage); 53 | } 54 | } 55 | 56 | onNextClick() { 57 | if (this.selectedPage < this.pageCount) { 58 | this.selectedPage += 1; 59 | this.pageChange.emit(this.currentPage); 60 | } 61 | } 62 | 63 | public reset() { 64 | this.selectedPage = 1; 65 | this.pageChange.emit(this.currentPage); 66 | } 67 | 68 | get pageCount():number { 69 | let pageCount = (this.count / this.selectedRange) + ((this.count % this.selectedRange) > 0 ? 1 : 0); 70 | return pageCount ? parseInt('' + pageCount) : 0; 71 | } 72 | 73 | get pageStart():number { 74 | return parseInt('' + ((this.selectedPage * this.selectedRange) - this.selectedRange + 1)); 75 | } 76 | 77 | get pageEnd():number { 78 | return parseInt('' + Math.min((this.selectedPage * this.selectedRange), this.count)); 79 | } 80 | 81 | get currentPage() { 82 | return new SmdPaginationModel(this.selectedPage, this.selectedRange); 83 | } 84 | } -------------------------------------------------------------------------------- /src/app/shared/components.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule, CUSTOM_ELEMENTS_SCHEMA} from "@angular/core"; 2 | import {BrowserModule} from "@angular/platform-browser"; 3 | import {FormsModule, ReactiveFormsModule} from "@angular/forms"; 4 | import {HttpModule} from "@angular/http"; 5 | import {CommonModule} from "@angular/common"; 6 | import {MaterialModule} from "@angular/material"; 7 | 8 | import { 9 | SmdDataTable, 10 | SmdDatatableHeader, 11 | SmdDatatableActionButton, 12 | SmdContextualDatatableButton, 13 | SmdDataTableColumnComponent, 14 | SmdDataTableRowComponent, 15 | SmdDataTableCellComponent, 16 | SmdDatatableDialogChangeValue, 17 | SmdPaginatorComponent, 18 | SmdFabSpeedDialTrigger, 19 | SmdFabSpeedDialActions, 20 | SmdFabSpeedDialComponent, 21 | SmdBottomNavLabelDirective, 22 | SmdBottomNavGroupComponent, 23 | SmdBottomNavComponent, 24 | SmdErrorMessageComponent, 25 | SmdErrorMessagesComponent 26 | } from "./component"; 27 | 28 | let COMPONENTS = [ 29 | SmdDataTable, 30 | SmdDatatableHeader, 31 | SmdDatatableActionButton, 32 | SmdContextualDatatableButton, 33 | SmdDataTableColumnComponent, 34 | SmdDataTableRowComponent, 35 | SmdDataTableCellComponent, 36 | SmdDatatableDialogChangeValue, 37 | SmdPaginatorComponent, 38 | SmdFabSpeedDialTrigger, 39 | SmdFabSpeedDialActions, 40 | SmdFabSpeedDialComponent, 41 | SmdBottomNavLabelDirective, 42 | SmdBottomNavGroupComponent, 43 | SmdBottomNavComponent, 44 | SmdErrorMessageComponent, 45 | SmdErrorMessagesComponent 46 | ]; 47 | 48 | let IMPORTS = [ 49 | CommonModule, 50 | HttpModule, 51 | FormsModule, 52 | ReactiveFormsModule, 53 | BrowserModule, 54 | MaterialModule.forRoot() 55 | ]; 56 | 57 | @NgModule({ 58 | imports: IMPORTS, 59 | declarations: COMPONENTS, 60 | exports: COMPONENTS, 61 | providers: [], 62 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 63 | entryComponents: [SmdDatatableDialogChangeValue] 64 | }) 65 | export class ComponentsModule { 66 | 67 | static forRoot(...imports: any[]): any[] { 68 | return [ 69 | CommonModule, 70 | HttpModule, 71 | FormsModule, 72 | ReactiveFormsModule, 73 | BrowserModule, 74 | MaterialModule.forRoot(), 75 | ComponentsModule, 76 | ...imports 77 | ] 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Simple Material Design 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Loading... 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { AppModule } from './app/app.module'; 3 | 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import "babel-polyfill"; 2 | import "reflect-metadata"; 3 | import 'zone.js/dist/zone'; 4 | import 'hammerjs' 5 | 6 | Error['stackTraceLimit'] = Infinity; 7 | require('zone.js/dist/long-stack-trace-zone'); 8 | -------------------------------------------------------------------------------- /src/vendor-3thparty.ts: -------------------------------------------------------------------------------- 1 | require("debug"); 2 | require("dateformat"); -------------------------------------------------------------------------------- /src/vendor-angular.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 2 | import '@angular/common'; 3 | import '@angular/core'; 4 | import '@angular/forms'; 5 | import '@angular/http'; 6 | import '@angular/platform-browser'; 7 | import '@angular/platform-browser-dynamic'; 8 | import '@angular/router'; 9 | 10 | // Material + Flex Layout 11 | // import '@angular/flex-layout'; 12 | import '@angular/material'; 13 | 14 | // RxJS 15 | import 'rxjs'; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": false, 10 | "noImplicitAny": true, 11 | "suppressImplicitAnyIndexErrors": true, 12 | "outDir": "target/js" 13 | }, 14 | 15 | "typeRoots": [ 16 | "./node_modules/@types" 17 | ], 18 | 19 | "exclude": [ 20 | "node_modules" 21 | ] 22 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | var path = require('path'); 6 | var _root = path.resolve(__dirname, '.'); 7 | function root(args) { 8 | args = Array.prototype.slice.call(arguments, 0); 9 | return path.join.apply(path, [_root].concat(args)); 10 | } 11 | exports.root = root; 12 | 13 | module.exports = { 14 | entry: { 15 | 'polyfills': './src/polyfills.ts', 16 | 'vendor-3thparty': './src/vendor-3thparty.ts', 17 | 'vendor-ng': './src/vendor-angular.ts', 18 | 'app': './src/main.ts' 19 | }, 20 | resolve: { 21 | extensions: ['', '.js', '.ts', '.scss'] 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.ts$/, 27 | loader: 'babel-loader?presets[]=es2015!awesome-typescript-loader!angular2-template-loader' 28 | }, 29 | { 30 | test: /\.html$/, 31 | loader: 'html' 32 | }, 33 | { 34 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, 35 | loader: 'file?name=assets_[name].[hash].[ext]' 36 | }, 37 | { 38 | test: /\.scss$/, 39 | exclude: root('src'), 40 | loader: 'style!css!sass?sourceMap' 41 | }, 42 | { 43 | test: /\.scss$/, 44 | include: root('src'), 45 | loaders: ['raw-loader', 'sass-loader'] // sass-loader not scss-loader 46 | }, 47 | { 48 | test: /\.css$/, 49 | exclude: root('src', 'app'), 50 | loader: ExtractTextPlugin.extract('style', 'css?sourceMap') 51 | }, 52 | { 53 | test: /\.css$/, 54 | include: root('src', 'app'), 55 | loader: 'raw' 56 | } 57 | ] 58 | }, 59 | devtool: 'eval-source-map', 60 | output: { 61 | path: root('dist'), 62 | filename: '[name].js', 63 | chunkFilename: '[id].chunk.js' 64 | }, 65 | plugins: [ 66 | new webpack.optimize.CommonsChunkPlugin({ 67 | name: ['vendor-ng', 'polyfills'] 68 | }), 69 | new HtmlWebpackPlugin({ 70 | template: 'src/index.html' 71 | }), 72 | new webpack.ProvidePlugin({ 73 | jQuery: 'jquery', 74 | $: 'jquery', 75 | jquery: 'jquery' 76 | }), 77 | new webpack.NoErrorsPlugin(), 78 | new webpack.optimize.DedupePlugin(), 79 | new webpack.HotModuleReplacementPlugin(), 80 | new ExtractTextPlugin('[name].css'), 81 | new webpack.DefinePlugin({ 82 | 'process.env': { 83 | 'VERSION': new Date().getTime() 84 | } 85 | }) 86 | ], 87 | devServer: { 88 | compress: true, 89 | historyApiFallback: true, 90 | stats: 'minimal', 91 | port: 9000, 92 | watchOptions: { 93 | aggregateTimeout: 2000, 94 | poll: 2000 95 | } 96 | } 97 | }; 98 | 99 | 100 | --------------------------------------------------------------------------------