summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/.eslintrc3
-rw-r--r--web/react/components/admin_console/admin_controller.jsx5
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx11
-rw-r--r--web/react/components/admin_console/line_chart.jsx50
-rw-r--r--web/react/components/admin_console/team_analytics.jsx357
-rw-r--r--web/react/components/admin_console/team_users.jsx10
-rw-r--r--web/react/components/create_comment.jsx33
-rw-r--r--web/react/components/create_post.jsx33
-rw-r--r--web/react/components/file_attachment.jsx2
-rw-r--r--web/react/components/get_link_modal.jsx2
-rw-r--r--web/react/components/navbar_dropdown.jsx1
-rw-r--r--web/react/components/register_app_modal.jsx135
-rw-r--r--web/react/components/search_autocomplete.jsx23
-rw-r--r--web/react/components/setting_item_max.jsx4
-rw-r--r--web/react/components/setting_picture.jsx4
-rw-r--r--web/react/components/sidebar_header.jsx11
-rw-r--r--web/react/components/team_signup_with_email.jsx2
-rw-r--r--web/react/components/user_settings/code_theme_chooser.jsx55
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx1
-rw-r--r--web/react/components/user_settings/user_settings.jsx12
-rw-r--r--web/react/components/user_settings/user_settings_advanced.jsx169
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx23
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx1
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx4
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx1
-rw-r--r--web/react/package.json1
-rw-r--r--web/react/utils/client.jsx14
-rw-r--r--web/react/utils/constants.jsx35
-rw-r--r--web/react/utils/markdown.jsx65
-rw-r--r--web/react/utils/utils.jsx36
-rw-r--r--web/sass-files/sass/partials/_admin-console.scss56
-rw-r--r--web/sass-files/sass/partials/_base.scss6
-rw-r--r--web/sass-files/sass/partials/_modal.scss5
-rw-r--r--web/sass-files/sass/partials/_popover.scss30
-rw-r--r--web/sass-files/sass/partials/_post.scss16
-rw-r--r--web/sass-files/sass/partials/_responsive.scss12
-rw-r--r--web/sass-files/sass/partials/_search.scss42
-rw-r--r--web/sass-files/sass/partials/_settings.scss57
l---------web/static/css/highlight1
-rw-r--r--web/static/images/themes/code_themes/github.pngbin9648 -> 0 bytes
-rw-r--r--web/static/images/themes/code_themes/monokai.pngbin9303 -> 0 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized_dark.pngbin8172 -> 0 bytes
-rw-r--r--web/static/images/themes/code_themes/solarized_light.pngbin8860 -> 0 bytes
-rw-r--r--web/static/js/Chart.min.js11
-rw-r--r--web/templates/admin_console.html1
-rw-r--r--web/templates/head.html1
46 files changed, 936 insertions, 405 deletions
diff --git a/web/react/.eslintrc b/web/react/.eslintrc
index 6a35d3123..d78068882 100644
--- a/web/react/.eslintrc
+++ b/web/react/.eslintrc
@@ -20,7 +20,8 @@
"globals": {
"React": false,
"ReactDOM": false,
- "ReactBootstrap": false
+ "ReactBootstrap": false,
+ "Chart": false
},
"rules": {
"comma-dangle": [2, "never"],
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index f770d166c..d309ced2e 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -18,6 +18,7 @@ var SqlSettingsTab = require('./sql_settings.jsx');
var TeamSettingsTab = require('./team_settings.jsx');
var ServiceSettingsTab = require('./service_settings.jsx');
var TeamUsersTab = require('./team_users.jsx');
+var TeamAnalyticsTab = require('./team_analytics.jsx');
export default class AdminController extends React.Component {
constructor(props) {
@@ -149,6 +150,10 @@ export default class AdminController extends React.Component {
if (this.state.teams) {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
}
+ } else if (this.state.selected === 'team_analytics') {
+ if (this.state.teams) {
+ tab = <TeamAnalyticsTab team={this.state.teams[this.state.selectedTeam]} />;
+ }
}
}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index b0e01ff17..f2fb1c96d 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -24,7 +24,7 @@ export default class AdminSidebar extends React.Component {
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name, teamId);
- history.pushState({name: name, teamId: teamId}, null, `/admin_console/${name}/${teamId || ''}`);
+ history.pushState({name, teamId}, null, `/admin_console/${name}/${teamId || ''}`);
}
isSelected(name, teamId) {
@@ -121,6 +121,15 @@ export default class AdminSidebar extends React.Component {
{'- Users'}
</a>
</li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('team_analytics', team.id)}
+ onClick={this.handleClick.bind(this, 'team_analytics', team.id)}
+ >
+ {'- Statistics'}
+ </a>
+ </li>
</ul>
</li>
</ul>
diff --git a/web/react/components/admin_console/line_chart.jsx b/web/react/components/admin_console/line_chart.jsx
new file mode 100644
index 000000000..7e2f95c84
--- /dev/null
+++ b/web/react/components/admin_console/line_chart.jsx
@@ -0,0 +1,50 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class LineChart extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.initChart = this.initChart.bind(this);
+ this.chart = null;
+ }
+
+ componentDidMount() {
+ this.initChart(this.props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (this.chart) {
+ this.chart.destroy();
+ this.initChart(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.chart) {
+ this.chart.destroy();
+ }
+ }
+
+ initChart(props) {
+ var el = ReactDOM.findDOMNode(this);
+ var ctx = el.getContext('2d');
+ this.chart = new Chart(ctx).Line(props.data, props.options || {}); //eslint-disable-line new-cap
+ }
+
+ render() {
+ return (
+ <canvas
+ width={this.props.width}
+ height={this.props.height}
+ />
+ );
+ }
+}
+
+LineChart.propTypes = {
+ width: React.PropTypes.string,
+ height: React.PropTypes.string,
+ data: React.PropTypes.object,
+ options: React.PropTypes.object
+};
diff --git a/web/react/components/admin_console/team_analytics.jsx b/web/react/components/admin_console/team_analytics.jsx
new file mode 100644
index 000000000..a945a551c
--- /dev/null
+++ b/web/react/components/admin_console/team_analytics.jsx
@@ -0,0 +1,357 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Client = require('../../utils/client.jsx');
+var Utils = require('../../utils/utils.jsx');
+var LineChart = require('./line_chart.jsx');
+
+export default class TeamAnalytics extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getData = this.getData.bind(this);
+
+ this.state = {
+ users: null,
+ serverError: null,
+ channel_open_count: null,
+ channel_private_count: null,
+ post_count: null,
+ post_counts_day: null,
+ user_counts_with_posts_day: null,
+ recent_active_users: null,
+ newly_created_users: null
+ };
+ }
+
+ componentDidMount() {
+ this.getData(this.props.team.id);
+ }
+
+ getData(teamId) {
+ Client.getAnalytics(
+ teamId,
+ 'standard',
+ (data) => {
+ for (var index in data) {
+ if (data[index].name === 'channel_open_count') {
+ this.setState({channel_open_count: data[index].value});
+ }
+
+ if (data[index].name === 'channel_private_count') {
+ this.setState({channel_private_count: data[index].value});
+ }
+
+ if (data[index].name === 'post_count') {
+ this.setState({post_count: data[index].value});
+ }
+ }
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getAnalytics(
+ teamId,
+ 'post_counts_day',
+ (data) => {
+ data.reverse();
+
+ var chartData = {
+ labels: [],
+ datasets: [{
+ label: 'Total Posts',
+ fillColor: 'rgba(151,187,205,0.2)',
+ strokeColor: 'rgba(151,187,205,1)',
+ pointColor: 'rgba(151,187,205,1)',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: 'rgba(151,187,205,1)',
+ data: []
+ }]
+ };
+
+ for (var index in data) {
+ if (data[index]) {
+ var row = data[index];
+ chartData.labels.push(row.name);
+ chartData.datasets[0].data.push(row.value);
+ }
+ }
+
+ this.setState({post_counts_day: chartData});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getAnalytics(
+ teamId,
+ 'user_counts_with_posts_day',
+ (data) => {
+ data.reverse();
+
+ var chartData = {
+ labels: [],
+ datasets: [{
+ label: 'Active Users With Posts',
+ fillColor: 'rgba(151,187,205,0.2)',
+ strokeColor: 'rgba(151,187,205,1)',
+ pointColor: 'rgba(151,187,205,1)',
+ pointStrokeColor: '#fff',
+ pointHighlightFill: '#fff',
+ pointHighlightStroke: 'rgba(151,187,205,1)',
+ data: []
+ }]
+ };
+
+ for (var index in data) {
+ if (data[index]) {
+ var row = data[index];
+ chartData.labels.push(row.name);
+ chartData.datasets[0].data.push(row.value);
+ }
+ }
+
+ this.setState({user_counts_with_posts_day: chartData});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+
+ Client.getProfilesForTeam(
+ teamId,
+ (users) => {
+ this.setState({users});
+
+ var usersList = [];
+ for (var id in users) {
+ if (users.hasOwnProperty(id)) {
+ usersList.push(users[id]);
+ }
+ }
+
+ usersList.sort((a, b) => {
+ if (a.last_activity_at < b.last_activity_at) {
+ return 1;
+ }
+
+ if (a.last_activity_at > b.last_activity_at) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ var recentActive = [];
+ for (let i = 0; i < usersList.length; i++) {
+ recentActive.push(usersList[i]);
+ if (i > 19) {
+ break;
+ }
+ }
+
+ this.setState({recent_active_users: recentActive});
+
+ usersList.sort((a, b) => {
+ if (a.create_at < b.create_at) {
+ return 1;
+ }
+
+ if (a.create_at > b.create_at) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ var newlyCreated = [];
+ for (let i = 0; i < usersList.length; i++) {
+ newlyCreated.push(usersList[i]);
+ if (i > 19) {
+ break;
+ }
+ }
+
+ this.setState({newly_created_users: newlyCreated});
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ users: null,
+ serverError: null,
+ channel_open_count: null,
+ channel_private_count: null,
+ post_count: null,
+ post_counts_day: null,
+ user_counts_with_posts_day: null,
+ recent_active_users: null,
+ newly_created_users: null
+ });
+
+ this.getData(newProps.team.id);
+ }
+
+ componentWillUnmount() {
+ }
+
+ render() {
+ var serverError = '';
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ var totalCount = (
+ <div className='total-count text-center'>
+ <div>{'Total Users'}</div>
+ <div>{this.state.users == null ? 'Loading...' : Object.keys(this.state.users).length}</div>
+ </div>
+ );
+
+ var openChannelCount = (
+ <div className='total-count text-center'>
+ <div>{'Public Groups'}</div>
+ <div>{this.state.channel_open_count == null ? 'Loading...' : this.state.channel_open_count}</div>
+ </div>
+ );
+
+ var openPrivateCount = (
+ <div className='total-count text-center'>
+ <div>{'Private Groups'}</div>
+ <div>{this.state.channel_private_count == null ? 'Loading...' : this.state.channel_private_count}</div>
+ </div>
+ );
+
+ var postCount = (
+ <div className='total-count text-center'>
+ <div>{'Total Posts'}</div>
+ <div>{this.state.post_count == null ? 'Loading...' : this.state.post_count}</div>
+ </div>
+ );
+
+ var postCountsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Total Posts'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.post_counts_day != null) {
+ postCountsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Total Posts'}</div>
+ <LineChart
+ data={this.state.post_counts_day}
+ width='740'
+ height='225'
+ />
+ </div>
+ );
+ }
+
+ var usersWithPostsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Total Posts'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.user_counts_with_posts_day != null) {
+ usersWithPostsByDay = (
+ <div className='total-count-by-day'>
+ <div>{'Active Users With Posts'}</div>
+ <LineChart
+ data={this.state.user_counts_with_posts_day}
+ width='740'
+ height='225'
+ />
+ </div>
+ );
+ }
+
+ var recentActiveUser = (
+ <div className='recent-active-users'>
+ <div>{'Recent Active Users'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.recent_active_users != null) {
+ recentActiveUser = (
+ <div className='recent-active-users'>
+ <div>{'Recent Active Users'}</div>
+ <table width='90%'>
+ <tbody>
+ {
+ this.state.recent_active_users.map((user) => {
+ return (
+ <tr key={user.id}>
+ <td className='recent-active-users-td'>{user.email}</td>
+ <td className='recent-active-users-td'>{Utils.displayDateTime(user.last_activity_at)}</td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+
+ var newUsers = (
+ <div className='recent-active-users'>
+ <div>{'Newly Created Users'}</div>
+ <div>{'Loading...'}</div>
+ </div>
+ );
+
+ if (this.state.newly_created_users != null) {
+ newUsers = (
+ <div className='recent-active-users'>
+ <div>{'Newly Created Users'}</div>
+ <table width='90%'>
+ <tbody>
+ {
+ this.state.newly_created_users.map((user) => {
+ return (
+ <tr key={user.id}>
+ <td className='recent-active-users-td'>{user.email}</td>
+ <td className='recent-active-users-td'>{Utils.displayDateTime(user.create_at)}</td>
+ </tr>
+ );
+ })
+ }
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+
+ return (
+ <div className='wrapper--fixed'>
+ <h2>{'Statistics for ' + this.props.team.name}</h2>
+ {serverError}
+ {totalCount}
+ {postCount}
+ {openChannelCount}
+ {openPrivateCount}
+ {postCountsByDay}
+ {usersWithPostsByDay}
+ {recentActiveUser}
+ {newUsers}
+ </div>
+ );
+ }
+}
+
+TeamAnalytics.propTypes = {
+ team: React.PropTypes.object
+};
diff --git a/web/react/components/admin_console/team_users.jsx b/web/react/components/admin_console/team_users.jsx
index ffb412159..b44aba56e 100644
--- a/web/react/components/admin_console/team_users.jsx
+++ b/web/react/components/admin_console/team_users.jsx
@@ -33,14 +33,6 @@ export default class UserList extends React.Component {
this.getTeamProfiles(this.props.team.id);
}
- // this.setState({
- // teamId: this.state.teamId,
- // users: this.state.users,
- // serverError: this.state.serverError,
- // showPasswordModal: this.state.showPasswordModal,
- // user: this.state.user
- // });
-
getTeamProfiles(teamId) {
Client.getProfilesForTeam(
teamId,
@@ -95,8 +87,6 @@ export default class UserList extends React.Component {
}
doPasswordResetDismiss() {
- this.state.showPasswordModal = false;
- this.state.user = null;
this.setState({
teamId: this.state.teamId,
users: this.state.users,
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 18936e808..058594165 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -8,6 +8,7 @@ const SocketStore = require('../stores/socket_store.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
const UserStore = require('../stores/user_store.jsx');
const PostStore = require('../stores/post_store.jsx');
+const PreferenceStore = require('../stores/preference_store.jsx');
const Textbox = require('./textbox.jsx');
const MsgTyping = require('./msg_typing.jsx');
const FileUpload = require('./file_upload.jsx');
@@ -27,7 +28,7 @@ export default class CreateComment extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this);
this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
- this.handleArrowUp = this.handleArrowUp.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleUploadStart = this.handleUploadStart.bind(this);
this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
this.handleUploadError = this.handleUploadError.bind(this);
@@ -36,6 +37,7 @@ export default class CreateComment extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this);
this.getFileCount = this.getFileCount.bind(this);
this.handleResize = this.handleResize.bind(this);
+ this.onPreferenceChange = this.onPreferenceChange.bind(this);
PostStore.clearCommentDraftUploads();
@@ -45,15 +47,23 @@ export default class CreateComment extends React.Component {
uploadsInProgress: draft.uploadsInProgress,
previews: draft.previews,
submitting: false,
- windowWidth: Utils.windowWidth()
+ windowWidth: Utils.windowWidth(),
+ ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
};
}
componentDidMount() {
+ PreferenceStore.addChangeListener(this.onPreferenceChange);
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
+ PreferenceStore.removeChangeListener(this.onPreferenceChange);
window.removeEventListener('resize', this.handleResize);
}
+ onPreferenceChange() {
+ this.setState({
+ ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
+ });
+ }
handleResize() {
this.setState({windowWidth: Utils.windowWidth()});
}
@@ -140,10 +150,12 @@ export default class CreateComment extends React.Component {
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
}
commentMsgKeyPress(e) {
- if (e.which === 13 && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.textbox).blur();
- this.handleSubmit(e);
+ if (this.state.ctrlSend === 'true' && e.ctrlKey || this.state.ctrlSend === 'false') {
+ if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
+ e.preventDefault();
+ ReactDOM.findDOMNode(this.refs.textbox).blur();
+ this.handleSubmit(e);
+ }
}
const t = Date.now();
@@ -161,7 +173,12 @@ export default class CreateComment extends React.Component {
$('.post-right__scroll').perfectScrollbar('update');
this.setState({messageText: messageText});
}
- handleArrowUp(e) {
+ handleKeyDown(e) {
+ if (this.state.ctrlSend === 'true' && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
+ this.commentMsgKeyPress(e);
+ return;
+ }
+
if (e.keyCode === KeyCodes.UP && this.state.messageText === '') {
e.preventDefault();
@@ -313,7 +330,7 @@ export default class CreateComment extends React.Component {
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.commentMsgKeyPress}
- onKeyDown={this.handleArrowUp}
+ onKeyDown={this.handleKeyDown}
messageText={this.state.messageText}
createMessage='Add a comment...'
initialText=''
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 32ee31efe..cdbc3bc6d 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -8,6 +8,7 @@ const ChannelStore = require('../stores/channel_store.jsx');
const PostStore = require('../stores/post_store.jsx');
const UserStore = require('../stores/user_store.jsx');
const SocketStore = require('../stores/socket_store.jsx');
+const PreferenceStore = require('../stores/preference_store.jsx');
const MsgTyping = require('./msg_typing.jsx');
const Textbox = require('./textbox.jsx');
const FileUpload = require('./file_upload.jsx');
@@ -36,9 +37,10 @@ export default class CreatePost extends React.Component {
this.removePreview = this.removePreview.bind(this);
this.onChange = this.onChange.bind(this);
this.getFileCount = this.getFileCount.bind(this);
- this.handleArrowUp = this.handleArrowUp.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleResize = this.handleResize.bind(this);
this.sendMessage = this.sendMessage.bind(this);
+ this.onPreferenceChange = this.onPreferenceChange.bind(this);
PostStore.clearDraftUploads();
@@ -52,8 +54,16 @@ export default class CreatePost extends React.Component {
submitting: false,
initialText: draft.messageText,
windowWidth: Utils.windowWidth(),
- windowHeight: Utils.windowHeight()
+ windowHeight: Utils.windowHeight(),
+ ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
};
+
+ PreferenceStore.addChangeListener(this.onPreferenceChange);
+ }
+ onPreferenceChange() {
+ this.setState({
+ ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
+ });
}
handleResize() {
this.setState({
@@ -201,10 +211,12 @@ export default class CreatePost extends React.Component {
);
}
postMsgKeyPress(e) {
- if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.textbox).blur();
- this.handleSubmit(e);
+ if (this.state.ctrlSend === 'true' && e.ctrlKey || this.state.ctrlSend === 'false') {
+ if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
+ e.preventDefault();
+ ReactDOM.findDOMNode(this.refs.textbox).blur();
+ this.handleSubmit(e);
+ }
}
const t = Date.now();
@@ -328,7 +340,12 @@ export default class CreatePost extends React.Component {
const draft = PostStore.getDraft(channelId);
return draft.previews.length + draft.uploadsInProgress.length;
}
- handleArrowUp(e) {
+ handleKeyDown(e) {
+ if (this.state.ctrlSend === 'true' && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
+ this.postMsgKeyPress(e);
+ return;
+ }
+
if (e.keyCode === KeyCodes.UP && this.state.messageText === '') {
e.preventDefault();
@@ -393,7 +410,7 @@ export default class CreatePost extends React.Component {
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress}
- onKeyDown={this.handleArrowUp}
+ onKeyDown={this.handleKeyDown}
onHeightChange={this.resizePostHolder}
messageText={this.state.messageText}
createMessage='Write a message...'
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index 4d4e8390c..e707e32f5 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -270,7 +270,7 @@ export default class FileAttachment extends React.Component {
href={fileUrl}
download={filenameString}
data-toggle='tooltip'
- title={filenameString}
+ title={'Download ' + filenameString}
className='post-image__name'
>
{trimmedFilename}
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 325e86f3d..8839bc3c7 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -98,7 +98,7 @@ export default class GetLinkModal extends React.Component {
<br /><br />
</p>
<textarea
- className='form-control no-resize'
+ className='form-control no-resize min-height'
readOnly='true'
ref='textarea'
value={this.state.value}
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 2b68645e5..2b0f3c40e 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -58,6 +58,7 @@ export default class NavbarDropdown extends React.Component {
TeamStore.addChangeListener(this.onListenerChange);
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
+ $('.sidebar--left .dropdown-menu').scrollTop(0);
this.blockToggle = true;
setTimeout(() => {
this.blockToggle = false;
diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx
index 3d4d9bf45..c40409dcc 100644
--- a/web/react/components/register_app_modal.jsx
+++ b/web/react/components/register_app_modal.jsx
@@ -96,75 +96,74 @@ export default class RegisterAppModal extends React.Component {
var body = '';
if (this.state.clientId === '') {
body = (
- <div className='form-group user-settings'>
- <h3>{'Register a New Application'}</h3>
- <br/>
- <label className='col-sm-4 control-label'>{'Application Name'}</label>
- <div className='col-sm-7'>
- <input
- ref='name'
- className='form-control'
- type='text'
- placeholder='Required'
- />
- {nameError}
- </div>
- <br/>
- <br/>
- <label className='col-sm-4 control-label'>{'Homepage URL'}</label>
- <div className='col-sm-7'>
- <input
- ref='homepage'
- className='form-control'
- type='text'
- placeholder='Required'
- />
- {homepageError}
- </div>
- <br/>
- <br/>
- <label className='col-sm-4 control-label'>{'Description'}</label>
- <div className='col-sm-7'>
- <input
- ref='desc'
- className='form-control'
- type='text'
- placeholder='Optional'
- />
- </div>
- <br/>
- <br/>
- <label className='col-sm-4 control-label'>{'Callback URL'}</label>
- <div className='col-sm-7'>
- <textarea
- ref='callback'
- className='form-control'
- type='text'
- placeholder='Required'
- rows='5'
- />
- {callbackError}
+ <div className='settings-modal'>
+ <div className='form-horizontal user-settings'>
+ <h4 className='padding-bottom x3'>{'Register a New Application'}</h4>
+ <div className='row'>
+ <label className='col-sm-4 control-label'>{'Application Name'}</label>
+ <div className='col-sm-7'>
+ <input
+ ref='name'
+ className='form-control'
+ type='text'
+ placeholder='Required'
+ />
+ {nameError}
+ </div>
+ </div>
+ <div className='row padding-top x2'>
+ <label className='col-sm-4 control-label'>{'Homepage URL'}</label>
+ <div className='col-sm-7'>
+ <input
+ ref='homepage'
+ className='form-control'
+ type='text'
+ placeholder='Required'
+ />
+ {homepageError}
+ </div>
+ </div>
+ <div className='row padding-top x2'>
+ <label className='col-sm-4 control-label'>{'Description'}</label>
+ <div className='col-sm-7'>
+ <input
+ ref='desc'
+ className='form-control'
+ type='text'
+ placeholder='Optional'
+ />
+ </div>
+ </div>
+ <div className='row padding-top padding-bottom x2'>
+ <label className='col-sm-4 control-label'>{'Callback URL'}</label>
+ <div className='col-sm-7'>
+ <textarea
+ ref='callback'
+ className='form-control'
+ type='text'
+ placeholder='Required'
+ rows='5'
+ />
+ {callbackError}
+ </div>
+ </div>
+ {serverError}
+ <hr />
+ <a
+ className='btn btn-sm theme pull-right'
+ href='#'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ {'Cancel'}
+ </a>
+ <a
+ className='btn btn-sm btn-primary pull-right'
+ onClick={this.register}
+ >
+ {'Register'}
+ </a>
</div>
- <br/>
- <br/>
- <br/>
- <br/>
- <br/>
- {serverError}
- <a
- className='btn btn-sm theme pull-right'
- href='#'
- data-dismiss='modal'
- aria-label='Close'
- >
- {'Cancel'}
- </a>
- <a
- className='btn btn-sm btn-primary pull-right'
- onClick={this.register}
- >
- {'Register'}
- </a>
</div>
);
} else {
diff --git a/web/react/components/search_autocomplete.jsx b/web/react/components/search_autocomplete.jsx
index 03c7b894c..f7d772677 100644
--- a/web/react/components/search_autocomplete.jsx
+++ b/web/react/components/search_autocomplete.jsx
@@ -10,6 +10,7 @@ const patterns = new Map([
['channels', /\b(?:in|channel):\s*(\S*)$/i],
['users', /\bfrom:\s*(\S*)$/i]
]);
+const Popover = ReactBootstrap.Popover;
export default class SearchAutocomplete extends React.Component {
constructor(props) {
@@ -36,6 +37,11 @@ export default class SearchAutocomplete extends React.Component {
$(document).on('click', this.handleDocumentClick);
}
+ componentDidUpdate() {
+ $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content').perfectScrollbar();
+ $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content').css('max-height', $(window).height() - 200);
+ }
+
componentWillUnmount() {
$(document).off('click', this.handleDocumentClick);
}
@@ -193,7 +199,7 @@ export default class SearchAutocomplete extends React.Component {
if (this.state.mode === 'channels') {
suggestions = this.state.suggestions.map((channel, index) => {
- let className = 'search-autocomplete__channel';
+ let className = 'search-autocomplete__item';
if (this.state.selection === index) {
className += ' selected';
}
@@ -211,7 +217,7 @@ export default class SearchAutocomplete extends React.Component {
});
} else if (this.state.mode === 'users') {
suggestions = this.state.suggestions.map((user, index) => {
- let className = 'search-autocomplete__user';
+ let className = 'search-autocomplete__item';
if (this.state.selection === index) {
className += ' selected';
}
@@ -224,7 +230,7 @@ export default class SearchAutocomplete extends React.Component {
className={className}
>
<img
- className='profile-img'
+ className='profile-img rounded'
src={'/api/v1/users/' + user.id + '/image?time=' + user.update_at}
/>
{user.username}
@@ -234,12 +240,15 @@ export default class SearchAutocomplete extends React.Component {
}
return (
- <div
- ref='container'
- className='search-autocomplete'
+ <Popover
+ ref='searchPopover'
+ onShow={this.componentDidMount}
+ id='search-autocomplete__popover'
+ className='search-help-popover autocomplete visible'
+ placement='bottom'
>
{suggestions}
- </div>
+ </Popover>
);
}
}
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index 774f98a43..d6c4b0d4b 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -35,8 +35,10 @@ export default class SettingItemMax extends React.Component {
var widthClass;
if (this.props.width === 'full') {
widthClass = 'col-sm-12';
- } else {
+ } else if (this.props.width === 'medium') {
widthClass = 'col-sm-10 col-sm-offset-2';
+ } else {
+ widthClass = 'col-sm-9 col-sm-offset-3';
}
return (
diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx
index b6bcb13a6..e69412cca 100644
--- a/web/react/components/setting_picture.jsx
+++ b/web/react/components/setting_picture.jsx
@@ -42,7 +42,7 @@ export default class SettingPicture extends React.Component {
img = (
<img
ref='image'
- className='profile-img'
+ className='profile-img rounded'
src=''
/>
);
@@ -50,7 +50,7 @@ export default class SettingPicture extends React.Component {
img = (
<img
ref='image'
- className='profile-img'
+ className='profile-img rounded'
src={this.props.src}
/>
);
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index de28a8374..65e4c6d7e 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -5,6 +5,9 @@ var NavbarDropdown = require('./navbar_dropdown.jsx');
var UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx');
+const Tooltip = ReactBootstrap.Tooltip;
+const OverlayTrigger = ReactBootstrap.OverlayTrigger;
+
export default class SidebarHeader extends React.Component {
constructor(props) {
super(props);
@@ -47,7 +50,15 @@ export default class SidebarHeader extends React.Component {
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
+ <OverlayTrigger
+ trigger={['hover', 'focus']}
+ delayShow={1000}
+ placement='bottom'
+ overlay={<Tooltip id='team-name__tooltip'>{this.props.teamDisplayName}</Tooltip>}
+ ref='descriptionOverlay'
+ >
<div className='team__name'>{this.props.teamDisplayName}</div>
+ </OverlayTrigger>
</div>
</a>
<NavbarDropdown
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index ff4ccd4d8..021713f04 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -71,7 +71,7 @@ export default class EmailSignUpPage extends React.Component {
className='btn btn-md btn-primary'
type='submit'
>
- {'Sign up'}
+ {'Create Team'}
</button>
{serverError}
</div>
diff --git a/web/react/components/user_settings/code_theme_chooser.jsx b/web/react/components/user_settings/code_theme_chooser.jsx
deleted file mode 100644
index eef4b24ba..000000000
--- a/web/react/components/user_settings/code_theme_chooser.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-var Constants = require('../../utils/constants.jsx');
-
-export default class CodeThemeChooser extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
- render() {
- const theme = this.props.theme;
-
- const premadeThemes = [];
- for (const k in Constants.CODE_THEMES) {
- if (Constants.CODE_THEMES.hasOwnProperty(k)) {
- let activeClass = '';
- if (k === theme.codeTheme) {
- activeClass = 'active';
- }
-
- premadeThemes.push(
- <div
- className='col-xs-6 col-sm-3 premade-themes'
- key={'premade-theme-key' + k}
- >
- <div
- className={activeClass}
- onClick={() => this.props.updateTheme(k)}
- >
- <label>
- <img
- className='img-responsive'
- src={'/static/images/themes/code_themes/' + k + '.png'}
- />
- <div className='theme-label'>{Constants.CODE_THEMES[k]}</div>
- </label>
- </div>
- </div>
- );
- }
- }
-
- return (
- <div className='row'>
- {premadeThemes}
- </div>
- );
- }
-}
-
-CodeThemeChooser.propTypes = {
- theme: React.PropTypes.object.isRequired,
- updateTheme: React.PropTypes.func.isRequired
-};
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index 6e9b2205d..4c56db0a1 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -236,6 +236,7 @@ export default class ManageOutgoingHooks extends React.Component {
return (
<div key='addOutgoingHook'>
<label className='control-label'>{'Add a new outgoing webhook'}</label>
+ <div className='padding-top divider-light'></div>
<div className='padding-top'>
<div>
<label className='control-label'>{'Channel'}</label>
diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx
index 15bf961d6..546e26ca3 100644
--- a/web/react/components/user_settings/user_settings.jsx
+++ b/web/react/components/user_settings/user_settings.jsx
@@ -10,6 +10,7 @@ var AppearanceTab = require('./user_settings_appearance.jsx');
var DeveloperTab = require('./user_settings_developer.jsx');
var IntegrationsTab = require('./user_settings_integrations.jsx');
var DisplayTab = require('./user_settings_display.jsx');
+var AdvancedTab = require('./user_settings_advanced.jsx');
export default class UserSettings extends React.Component {
constructor(props) {
@@ -110,6 +111,17 @@ export default class UserSettings extends React.Component {
/>
</div>
);
+ } else if (this.props.activeTab === 'advanced') {
+ return (
+ <div>
+ <AdvancedTab
+ user={this.state.user}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ updateTab={this.props.updateTab}
+ />
+ </div>
+ );
}
return <div/>;
diff --git a/web/react/components/user_settings/user_settings_advanced.jsx b/web/react/components/user_settings/user_settings_advanced.jsx
new file mode 100644
index 000000000..910444735
--- /dev/null
+++ b/web/react/components/user_settings/user_settings_advanced.jsx
@@ -0,0 +1,169 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const Client = require('../../utils/client.jsx');
+const SettingItemMin = require('../setting_item_min.jsx');
+const SettingItemMax = require('../setting_item_max.jsx');
+const Constants = require('../../utils/constants.jsx');
+const PreferenceStore = require('../../stores/preference_store.jsx');
+
+export default class AdvancedSettingsDisplay extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateSection = this.updateSection.bind(this);
+ this.updateSetting = this.updateSetting.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.setupInitialState = this.setupInitialState.bind(this);
+
+ this.state = this.setupInitialState();
+ }
+
+ setupInitialState() {
+ const sendOnCtrlEnter = PreferenceStore.getPreference(
+ Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
+ 'send_on_ctrl_enter',
+ {value: 'false'}
+ ).value;
+
+ return {
+ settings: {send_on_ctrl_enter: sendOnCtrlEnter}
+ };
+ }
+
+ updateSetting(setting, value) {
+ const settings = this.state.settings;
+ settings[setting] = value;
+ this.setState(settings);
+ }
+
+ handleSubmit(setting) {
+ const preference = PreferenceStore.setPreference(
+ Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
+ setting,
+ this.state.settings[setting]
+ );
+
+ Client.savePreferences([preference],
+ () => {
+ PreferenceStore.emitChange();
+ this.updateSection('');
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ updateSection(section) {
+ this.props.updateSection(section);
+ }
+
+ handleClose() {
+ this.updateSection('');
+ }
+
+ componentDidMount() {
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
+ }
+
+ componentWillUnmount() {
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
+ }
+
+ render() {
+ const serverError = this.state.serverError || null;
+ let ctrlSendSection;
+
+ if (this.props.activeSection === 'advancedCtrlSend') {
+ const ctrlSendActive = [
+ this.state.settings.send_on_ctrl_enter === 'true',
+ this.state.settings.send_on_ctrl_enter === 'false'
+ ];
+
+ const inputs = [
+ <div key='ctrlSendSetting'>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ checked={ctrlSendActive[0]}
+ onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'true')}
+ />
+ {'On'}
+ </label>
+ <br/>
+ </div>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ checked={ctrlSendActive[1]}
+ onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'false')}
+ />
+ {'Off'}
+ </label>
+ <br/>
+ </div>
+ <div><br/>{'If enabled \'Enter\' inserts a new line and \'Ctrl + Enter\' submits the message.'}</div>
+ </div>
+ ];
+
+ ctrlSendSection = (
+ <SettingItemMax
+ title='Send messages on Ctrl + Enter'
+ inputs={inputs}
+ submit={() => this.handleSubmit('send_on_ctrl_enter')}
+ server_error={serverError}
+ updateSection={(e) => {
+ this.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ ctrlSendSection = (
+ <SettingItemMin
+ title='Send messages on Ctrl + Enter'
+ describe={this.state.settings.send_on_ctrl_enter === 'true' ? 'On' : 'Off'}
+ updateSection={() => this.props.updateSection('advancedCtrlSend')}
+ />
+ );
+ }
+
+ return (
+ <div>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>{'×'}</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ <i className='modal-back'></i>
+ {'Advanced Settings'}
+ </h4>
+ </div>
+ <div className='user-settings'>
+ <h3 className='tab-header'>{'Advanced Settings'}</h3>
+ <div className='divider-dark first'/>
+ {ctrlSendSection}
+ <div className='divider-dark'/>
+ </div>
+ </div>
+ );
+ }
+}
+
+AdvancedSettingsDisplay.propTypes = {
+ user: React.PropTypes.object,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func,
+ activeSection: React.PropTypes.string
+};
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index e94894a1d..8c62a189d 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -7,7 +7,6 @@ var Utils = require('../../utils/utils.jsx');
const CustomThemeChooser = require('./custom_theme_chooser.jsx');
const PremadeThemeChooser = require('./premade_theme_chooser.jsx');
-const CodeThemeChooser = require('./code_theme_chooser.jsx');
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
const Constants = require('../../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
@@ -19,14 +18,12 @@ export default class UserSettingsAppearance extends React.Component {
this.onChange = this.onChange.bind(this);
this.submitTheme = this.submitTheme.bind(this);
this.updateTheme = this.updateTheme.bind(this);
- this.updateCodeTheme = this.updateCodeTheme.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleImportModal = this.handleImportModal.bind(this);
this.state = this.getStateFromStores();
this.originalTheme = this.state.theme;
- this.originalCodeTheme = this.state.theme.codeTheme;
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
@@ -61,10 +58,6 @@ export default class UserSettingsAppearance extends React.Component {
type = 'custom';
}
- if (!theme.codeTheme) {
- theme.codeTheme = Constants.DEFAULT_CODE_THEME;
- }
-
return {theme, type};
}
onChange() {
@@ -100,13 +93,6 @@ export default class UserSettingsAppearance extends React.Component {
);
}
updateTheme(theme) {
- theme.codeTheme = this.state.theme.codeTheme;
- this.setState({theme});
- Utils.applyTheme(theme);
- }
- updateCodeTheme(codeTheme) {
- var theme = this.state.theme;
- theme.codeTheme = codeTheme;
this.setState({theme});
Utils.applyTheme(theme);
}
@@ -116,7 +102,6 @@ export default class UserSettingsAppearance extends React.Component {
handleClose() {
const state = this.getStateFromStores();
state.serverError = null;
- state.theme.codeTheme = this.originalCodeTheme;
Utils.applyTheme(state.theme);
@@ -185,13 +170,7 @@ export default class UserSettingsAppearance extends React.Component {
</div>
{custom}
<hr />
- <strong className='radio'>{'Code Theme'}</strong>
- <CodeThemeChooser
- theme={this.state.theme}
- updateTheme={this.updateCodeTheme}
- />
- <hr />
- {serverError}
+ {serverError}
<a
className='btn btn-sm btn-primary'
href='#'
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 1c8ce3c79..3adac197a 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -570,6 +570,7 @@ export default class UserSettingsGeneralTab extends React.Component {
/>
);
}
+
return (
<div>
<div className='modal-header'>
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index 4b1e5e532..9bee74343 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -43,6 +43,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMax
title='Incoming Webhooks'
+ width='medium'
inputs={inputs}
updateSection={(e) => {
this.updateSection('');
@@ -54,6 +55,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
incomingHooksSection = (
<SettingItemMin
title='Incoming Webhooks'
+ width='medium'
describe='Manage your incoming webhooks (Developer feature)'
updateSection={() => {
this.updateSection('incoming-hooks');
@@ -72,6 +74,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
outgoingHooksSection = (
<SettingItemMax
title='Outgoing Webhooks'
+ width='medium'
inputs={inputs}
updateSection={(e) => {
this.updateSection('');
@@ -83,6 +86,7 @@ export default class UserSettingsIntegrationsTab extends React.Component {
outgoingHooksSection = (
<SettingItemMin
title='Outgoing Webhooks'
+ width='medium'
describe='Manage your outgoing webhooks'
updateSection={() => {
this.updateSection('outgoing-hooks');
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 5449ae91e..18dd490e7 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -43,6 +43,7 @@ export default class UserSettingsModal extends React.Component {
tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
}
tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
+ tabs.push({name: 'advanced', uiName: 'Advanced', icon: 'glyphicon glyphicon-list-alt'});
return (
<div
diff --git a/web/react/package.json b/web/react/package.json
index 9af6f5880..e6a662375 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -6,7 +6,6 @@
"autolinker": "0.18.1",
"babel-runtime": "5.8.24",
"flux": "2.1.1",
- "highlight.js": "^8.9.1",
"keymirror": "0.1.1",
"marked": "0.3.5",
"object-assign": "3.0.0",
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 8b37b995f..aeb39d8a8 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -328,6 +328,20 @@ export function getConfig(success, error) {
});
}
+export function getAnalytics(teamId, name, success, error) {
+ $.ajax({
+ url: '/api/v1/admin/analytics/' + teamId + '/' + name,
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: (xhr, status, err) => {
+ var e = handleError('getAnalytics', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function saveConfig(config, success, error) {
$.ajax({
url: '/api/v1/admin/save_config',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 0e89b9470..69e3b007d 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -302,16 +302,10 @@ module.exports = {
uiName: 'Mention Highlight Link'
}
],
- CODE_THEMES: {
- github: 'GitHub',
- solarized_light: 'Solarized light',
- monokai: 'Monokai',
- solarized_dark: 'Solarized Dark'
- },
- DEFAULT_CODE_THEME: 'github',
Preferences: {
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
- CATEGORY_DISPLAY_SETTINGS: 'display_settings'
+ CATEGORY_DISPLAY_SETTINGS: 'display_settings',
+ CATEGORY_ADVANCED_SETTINGS: 'advanced_settings'
},
KeyCodes: {
UP: 38,
@@ -322,30 +316,5 @@ module.exports = {
ENTER: 13,
ESCAPE: 27,
SPACE: 32
- },
- HighlightedLanguages: {
- diff: 'Diff',
- apache: 'Apache',
- makefile: 'Makefile',
- http: 'HTTP',
- json: 'JSON',
- markdown: 'Markdown',
- javascript: 'JavaScript',
- css: 'CSS',
- nginx: 'nginx',
- objectivec: 'Objective-C',
- python: 'Python',
- xml: 'XML',
- perl: 'Perl',
- bash: 'Bash',
- php: 'PHP',
- coffeescript: 'CoffeeScript',
- cs: 'C#',
- cpp: 'C++',
- sql: 'SQL',
- go: 'Go',
- ruby: 'Ruby',
- java: 'Java',
- ini: 'ini'
}
};
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index ad11a95ac..26587dd6e 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -6,34 +6,6 @@ const Utils = require('./utils.jsx');
const marked = require('marked');
-const highlightJs = require('highlight.js/lib/highlight.js');
-const highlightJsDiff = require('highlight.js/lib/languages/diff.js');
-const highlightJsApache = require('highlight.js/lib/languages/apache.js');
-const highlightJsMakefile = require('highlight.js/lib/languages/makefile.js');
-const highlightJsHttp = require('highlight.js/lib/languages/http.js');
-const highlightJsJson = require('highlight.js/lib/languages/json.js');
-const highlightJsMarkdown = require('highlight.js/lib/languages/markdown.js');
-const highlightJsJavascript = require('highlight.js/lib/languages/javascript.js');
-const highlightJsCss = require('highlight.js/lib/languages/css.js');
-const highlightJsNginx = require('highlight.js/lib/languages/nginx.js');
-const highlightJsObjectivec = require('highlight.js/lib/languages/objectivec.js');
-const highlightJsPython = require('highlight.js/lib/languages/python.js');
-const highlightJsXml = require('highlight.js/lib/languages/xml.js');
-const highlightJsPerl = require('highlight.js/lib/languages/perl.js');
-const highlightJsBash = require('highlight.js/lib/languages/bash.js');
-const highlightJsPhp = require('highlight.js/lib/languages/php.js');
-const highlightJsCoffeescript = require('highlight.js/lib/languages/coffeescript.js');
-const highlightJsCs = require('highlight.js/lib/languages/cs.js');
-const highlightJsCpp = require('highlight.js/lib/languages/cpp.js');
-const highlightJsSql = require('highlight.js/lib/languages/sql.js');
-const highlightJsGo = require('highlight.js/lib/languages/go.js');
-const highlightJsRuby = require('highlight.js/lib/languages/ruby.js');
-const highlightJsJava = require('highlight.js/lib/languages/java.js');
-const highlightJsIni = require('highlight.js/lib/languages/ini.js');
-
-const Constants = require('../utils/constants.jsx');
-const HighlightedLanguages = Constants.HighlightedLanguages;
-
export class MattermostMarkdownRenderer extends marked.Renderer {
constructor(options, formattingOptions = {}) {
super(options);
@@ -43,43 +15,6 @@ export class MattermostMarkdownRenderer extends marked.Renderer {
this.text = this.text.bind(this);
this.formattingOptions = formattingOptions;
-
- highlightJs.registerLanguage('diff', highlightJsDiff);
- highlightJs.registerLanguage('apache', highlightJsApache);
- highlightJs.registerLanguage('makefile', highlightJsMakefile);
- highlightJs.registerLanguage('http', highlightJsHttp);
- highlightJs.registerLanguage('json', highlightJsJson);
- highlightJs.registerLanguage('markdown', highlightJsMarkdown);
- highlightJs.registerLanguage('javascript', highlightJsJavascript);
- highlightJs.registerLanguage('css', highlightJsCss);
- highlightJs.registerLanguage('nginx', highlightJsNginx);
- highlightJs.registerLanguage('objectivec', highlightJsObjectivec);
- highlightJs.registerLanguage('python', highlightJsPython);
- highlightJs.registerLanguage('xml', highlightJsXml);
- highlightJs.registerLanguage('perl', highlightJsPerl);
- highlightJs.registerLanguage('bash', highlightJsBash);
- highlightJs.registerLanguage('php', highlightJsPhp);
- highlightJs.registerLanguage('coffeescript', highlightJsCoffeescript);
- highlightJs.registerLanguage('cs', highlightJsCs);
- highlightJs.registerLanguage('cpp', highlightJsCpp);
- highlightJs.registerLanguage('sql', highlightJsSql);
- highlightJs.registerLanguage('go', highlightJsGo);
- highlightJs.registerLanguage('ruby', highlightJsRuby);
- highlightJs.registerLanguage('java', highlightJsJava);
- highlightJs.registerLanguage('ini', highlightJsIni);
- }
-
- code(code, language) {
- if (!language || highlightJs.listLanguages().indexOf(language) < 0) {
- let parsed = super.code(code, language);
- return '<code class="hljs">' + $(parsed).text() + '</code>';
- }
-
- let parsed = highlightJs.highlight(language, code);
- return '<div class="post-body--code">' +
- '<span class="post-body--code__language">' + HighlightedLanguages[language] + '</span>' +
- '<code style="white-space: pre;" class="hljs">' + parsed.value + '</code>' +
- '</div>';
}
br() {
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index fadab27a7..0d16ef82e 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -211,11 +211,15 @@ export function displayDateTime(ticks) {
}
interval = Math.floor(seconds / 60);
- if (interval > 1) {
+ if (interval >= 2) {
return interval + ' minutes ago';
}
- return '1 minute ago';
+ if (interval >= 1) {
+ return '1 minute ago';
+ }
+
+ return 'just now';
}
export function displayCommentDateTime(ticks) {
@@ -404,11 +408,6 @@ export function toTitleCase(str) {
}
export function applyTheme(theme) {
- if (!theme.codeTheme) {
- theme.codeTheme = Constants.DEFAULT_CODE_THEME;
- }
- updateCodeTheme(theme.codeTheme);
-
if (theme.sidebarBg) {
changeCss('.sidebar--left, .settings-modal .settings-table .settings-links, .sidebar--menu', 'background:' + theme.sidebarBg, 1);
}
@@ -439,6 +438,8 @@ export function applyTheme(theme) {
if (theme.sidebarTextActiveColor) {
changeCss('.sidebar--left .nav-pills__container li.active a, .sidebar--left .nav-pills__container li.active a:hover, .sidebar--left .nav-pills__container li.active a:focus, .settings-modal .nav-pills>li.active a, .settings-modal .nav-pills>li.active a:hover, .settings-modal .nav-pills>li.active a:active', 'color:' + theme.sidebarTextActiveColor, 2);
changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + changeOpacity(theme.sidebarTextActiveColor, 0.1), 1);
+ changeCss('.search-help-popover .search-autocomplete__item:hover', 'background:' + changeOpacity(theme.sidebarTextActiveColor, 0.05), 1);
+ changeCss('.search-help-popover .search-autocomplete__item.selected', 'background:' + changeOpacity(theme.sidebarTextActiveColor, 0.15), 1);
}
if (theme.sidebarHeaderBg) {
@@ -595,27 +596,6 @@ export function rgb2hex(rgbIn) {
return '#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
-export function updateCodeTheme(theme) {
- const path = '/static/css/highlight/' + theme + '.css';
- const $link = $('link.code_theme');
- if (path !== $link.attr('href')) {
- changeCss('code.hljs', 'visibility: hidden');
- var xmlHTTP = new XMLHttpRequest();
- xmlHTTP.open('GET', path, true);
- xmlHTTP.onload = function onLoad() {
- $link.attr('href', path);
- if (isBrowserFirefox()) {
- $link.one('load', () => {
- changeCss('code.hljs', 'visibility: visible');
- });
- } else {
- changeCss('code.hljs', 'visibility: visible');
- }
- };
- xmlHTTP.send();
- }
-}
-
export function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
diff --git a/web/sass-files/sass/partials/_admin-console.scss b/web/sass-files/sass/partials/_admin-console.scss
index 14f1d9c2f..9c65e2d1e 100644
--- a/web/sass-files/sass/partials/_admin-console.scss
+++ b/web/sass-files/sass/partials/_admin-console.scss
@@ -5,6 +5,62 @@
.table {
background: #fff;
}
+
+ .total-count {
+ width: 175px;
+ height: 100px;
+ border: 1px solid #ddd;
+ padding: 22px 10px 10px 10px;
+ margin: 10px 10px 10px 10px;
+ background: #fff;
+ float: left;
+
+ > div {
+ font-size: 18px;
+ font-weight: 300;
+ }
+ }
+
+ .total-count-by-day {
+ width: 760px;
+ height: 275px;
+ border: 1px solid #ddd;
+ padding: 5px 10px 10px 10px;
+ margin: 10px 10px 10px 10px;
+ background: #fff;
+ clear: both;
+
+ > div {
+ font-size: 18px;
+ font-weight: 300;
+ }
+ }
+
+ .recent-active-users {
+ width: 365px;
+ border: 1px solid #ddd;
+ padding: 5px 10px 10px 10px;
+ margin: 10px 10px 10px 10px;
+ background: #fff;
+ float: left;
+
+ > div {
+ font-size: 18px;
+ font-weight: 300;
+ }
+
+ > table {
+ margin: 10px 10px 10px 10px;
+ }
+
+ .recent-active-users-td {
+ font-size: 14px;
+ font-weight: 300;
+ border: 1px solid #ddd;
+ padding: 3px 3px 3px 5px;
+ }
+ }
+
.sidebar--left {
&.sidebar--collapsable {
background: #333;
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 6399b8fd8..635928fe3 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -37,6 +37,9 @@ body {
img {
max-width: 100%;
height: auto;
+ &.rounded {
+ @include border-radius(100%);
+ }
}
.popover {
@@ -122,6 +125,9 @@ a:focus, a:hover {
&.no-resize {
resize: none;
}
+ &.min-height {
+ min-height: 100px;
+ }
}
.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control {
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 9c6a39c94..9314b4980 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -113,11 +113,10 @@
text-align: center;
padding: 2em 1em;
.primary-message {
- font-size: 1.2em;
+ font-size: 1.25em;
}
.secondary-message {
- font-size: 1.25em;
- color: #888;
+ @include opacity(0.8);
margin: 1em 0 0;
}
}
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
index 4a2ad2748..4f5f1d215 100644
--- a/web/sass-files/sass/partials/_popover.scss
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -28,6 +28,36 @@
@include single-transition(opacity, 0.3s, ease-in);
font-size: em(13px);
+ &.autocomplete {
+ display: block;
+ .popover-content {
+ padding: 10px;
+ position: relative;
+ }
+ }
+
+ .search-autocomplete__item {
+ cursor: pointer;
+ padding: 6px 8px;
+ margin: 3px 0;
+ @include border-radius(2px);
+
+ &:hover {
+ background: rgba(black, 0.1);
+ }
+
+ &.selected {
+ background: rgba(black, 0.2);
+ }
+
+ .profile-img {
+ margin-top: -1px;
+ height: 16px;
+ margin-right: 6px;
+ width: 16px;
+ }
+ }
+
&.bottom > .arrow {
top: -18px;
border-width: 9px;
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 3fac1fed9..f5fc1631f 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -466,22 +466,6 @@ body.ios {
white-space: nowrap;
cursor: pointer;
}
- .post-body--code {
- font-size: .97em;
- position:relative;
- .post-body--code__language {
- position: absolute;
- right: 0;
- background: #fff;
- cursor: default;
- padding: 0.3em 0.5em 0.1em;
- border-bottom-left-radius: 4px;
- @include opacity(.3);
- }
- code {
- white-space: pre;
- }
- }
}
.create-reply-form-wrap {
width: 100%;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 2cd5560ef..b85fa638a 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -379,7 +379,9 @@
}
.btn {
&.btn-primary {
- margin: 8px 0 0 -10px;
+ display: block;
+ margin: 10px 0 6px;
+ width: 100%;
float: none;
}
}
@@ -401,7 +403,13 @@
&.minimize-settings {
display: block;
.section-edit {
- text-align: left;
+ position: absolute;
+ top: 7px;
+ right: 0;
+ width: 50px;
+ .fa {
+ display: inline-block;
+ }
}
}
.no-padding--left {
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index ce3563885..e50dc398a 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -108,44 +108,4 @@
.search-highlight {
background-color: #FFF2BB;
-}
-
-.search-autocomplete {
- background-color: #fff;
- border: $border-gray;
- line-height: 36px;
- overflow-x: hidden;
- overflow-y: scroll;
- position: absolute;
- text-align: left;
- width: 100%;
- z-index: 100;
- @extend %popover-box-shadow;
-}
-
-.search-autocomplete__channel {
- cursor: pointer;
- height: 36px;
- padding: 0px 6px;
-
- &.selected {
- background-color:rgba(51, 51, 51, 0.15);
- }
-}
-
-.search-autocomplete__user {
- cursor: pointer;
- height: 36px;
- padding: 0px;
-
- .profile-img {
- height: 32px;
- margin-right: 6px;
- width: 32px;
- @include border-radius(16px);
- }
-
- &.selected {
- background-color:rgba(51, 51, 51, 0.15);
- }
-}
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index c881f9073..fbbd07485 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -33,6 +33,33 @@
label {
font-weight: 600;
}
+ .no-padding--left {
+ padding-left: 0;
+ }
+ .padding-top {
+ padding-top: 7px;
+ &.x2 {
+ padding-top: 14px;
+ }
+ &.x3 {
+ padding-top: 21px;
+ }
+ }
+ .padding-bottom {
+ padding-bottom: 7px;
+ &.x2 {
+ padding-bottom: 14px;
+ }
+ &.x3 {
+ padding-bottom: 21px;
+ }
+ .control-label {
+ font-weight: 600;
+ &.text-left {
+ text-align: left;
+ }
+ }
+ }
.settings-table {
display: table;
table-layout: fixed;
@@ -44,7 +71,7 @@
.nav {
position: fixed;
top: 57px;
- width: 180px;
+ width: 179px;
}
.security-links {
margin-right: 20px;
@@ -66,6 +93,7 @@
padding: 1em 0;
margin-bottom: 0;
cursor: pointer;
+ position: relative;
@include clearfix;
&:hover {
background: #f9f9f9;
@@ -170,33 +198,6 @@
.has-error {
color: #a94442;
}
- .no-padding--left {
- padding-left: 0;
- }
- .padding-top {
- padding-top: 7px;
- &.x2 {
- padding-top: 14px;
- }
- &.x3 {
- padding-top: 21px;
- }
- }
- .padding-bottom {
- padding-bottom: 7px;
- &.x2 {
- padding-bottom: 14px;
- }
- &.x3 {
- padding-bottom: 21px;
- }
- .control-label {
- font-weight: 600;
- &.text-left {
- text-align: left;
- }
- }
- }
.file-status {
font-size: 13px;
diff --git a/web/static/css/highlight b/web/static/css/highlight
deleted file mode 120000
index c774cf397..000000000
--- a/web/static/css/highlight
+++ /dev/null
@@ -1 +0,0 @@
-../../react/node_modules/highlight.js/styles/ \ No newline at end of file
diff --git a/web/static/images/themes/code_themes/github.png b/web/static/images/themes/code_themes/github.png
deleted file mode 100644
index d0538d6c0..000000000
--- a/web/static/images/themes/code_themes/github.png
+++ /dev/null
Binary files differ
diff --git a/web/static/images/themes/code_themes/monokai.png b/web/static/images/themes/code_themes/monokai.png
deleted file mode 100644
index 8f92d2a18..000000000
--- a/web/static/images/themes/code_themes/monokai.png
+++ /dev/null
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_dark.png b/web/static/images/themes/code_themes/solarized_dark.png
deleted file mode 100644
index 76055c678..000000000
--- a/web/static/images/themes/code_themes/solarized_dark.png
+++ /dev/null
Binary files differ
diff --git a/web/static/images/themes/code_themes/solarized_light.png b/web/static/images/themes/code_themes/solarized_light.png
deleted file mode 100644
index b9595c22d..000000000
--- a/web/static/images/themes/code_themes/solarized_light.png
+++ /dev/null
Binary files differ
diff --git a/web/static/js/Chart.min.js b/web/static/js/Chart.min.js
new file mode 100644
index 000000000..3a0a2c873
--- /dev/null
+++ b/web/static/js/Chart.min.js
@@ -0,0 +1,11 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.2
+ *
+ * Copyright 2015 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n<t.length;n++)i.apply(e,[t[n],n].concat(s))}else for(var o in t)i.apply(e,[t[o],o].concat(s))},o=s.clone=function(t){var i={};return n(t,function(e,s){t.hasOwnProperty(s)&&(i[s]=e)}),i},a=s.extend=function(t){return n(Array.prototype.slice.call(arguments,1),function(i){n(i,function(e,s){i.hasOwnProperty(s)&&(t[s]=e)})}),t},h=s.merge=function(){var t=Array.prototype.slice.call(arguments,0);return t.unshift({}),a.apply(null,t)},l=s.indexOf=function(t,i){if(Array.prototype.indexOf)return t.indexOf(i);for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1},r=(s.where=function(t,i){var e=[];return s.each(t,function(t){i(t)&&e.push(t)}),e},s.findNextWhere=function(t,i,e){e||(e=-1);for(var s=e+1;s<t.length;s++){var n=t[s];if(i(n))return n}},s.findPreviousWhere=function(t,i,e){e||(e=t.length);for(var s=e-1;s>=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),-(s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)))},easeOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),s*Math.pow(2,-10*t)*Math.sin(2*(1*t-i)*Math.PI/e)+1)},easeInOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:2==(t/=.5)?1:(e||(e=.3*1.5),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),1>t?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)<Math.pow(e,2)},draw:function(){if(this.display){var t=this.ctx;t.beginPath(),t.arc(this.x,this.y,this.radius,0,2*Math.PI),t.closePath(),t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.fillStyle=this.fillColor,t.fill(),t.stroke()}}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=s.getAngleFromPoint(this,{x:t,y:i}),n=e.angle>=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),t<this.yLabelWidth&&this.calculateXLabelRotation()},calculateXLabelRotation:function(){this.ctx.font=this.font;var t,i,e=this.ctx.measureText(this.xLabels[0]).width,s=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width;if(this.xScalePaddingRight=s/2+3,this.xScalePaddingLeft=e/2>this.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;i<this.valuesCount;i++)t=this.getPointPosition(i,d),e=this.ctx.measureText(C(this.templateString,{value:this.labels[i]})).width+5,0===i||i===this.valuesCount/2?(s=e/2,t.x+s>p&&(p=t.x+s,n=i),t.x-s<g&&(g=t.x-s,a=i)):i<this.valuesCount/2?t.x+e>p&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e<g&&(g=t.x-e,a=i);l=g,r=Math.ceil(p-this.width),o=this.getIndexAngle(n),h=this.getIndexAngle(a),c=r/Math.sin(o+Math.PI/2),u=l/Math.sin(h+Math.PI/2),c=f(c)?c:0,u=f(u)?u:0,this.drawingArea=d-(u+c)/2,this.setCenterPoint(u,c)},setCenterPoint:function(t,i){var e=this.width-i-this.drawingArea,s=t+this.drawingArea;this.xCenter=(s+e)/2,this.yCenter=this.height/2},getIndexAngle:function(t){var i=2*Math.PI/this.valuesCount;return t*i-Math.PI/2},getPointPosition:function(t,i){var e=this.getIndexAngle(t);return{x:Math.cos(e)*i+this.xCenter,y:Math.sin(e)*i+this.yCenter}},draw:function(){if(this.display){var t=this.ctx;if(n(this.yLabels,function(i,e){if(e>0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a<this.valuesCount;a++)s=this.getPointPosition(a,this.calculateCenterOffset(this.min+e*this.stepValue)),0===a?t.moveTo(s.x,s.y):t.lineTo(s.x,s.y);t.closePath(),t.stroke()}if(this.showLabels){if(t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop){var h=t.measureText(i).width;t.fillStyle=this.backdropColor,t.fillRect(this.xCenter-h/2-this.backdropPaddingX,o-this.fontSize/2-this.backdropPaddingY,h+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)}t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.fontColor,t.fillText(i,this.xCenter,o)}}},this),!this.lineArc){t.lineWidth=this.angleLineWidth,t.strokeStyle=this.angleLineColor;for(var i=this.valuesCount-1;i>=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<this.datasets.length;a++)for(i=0;i<this.datasets[a].bars.length;i++)if(this.datasets[a].bars[i].inRange(n.x,n.y))return e.each(this.datasets,o),s;return s},buildScale:function(t){var i=this,s=function(){var t=[];return i.eachBars(function(i){t.push(i.value)}),t},n={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(s(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.barShowStroke?this.options.barStrokeWidth:0,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(n,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new this.ScaleClass(n)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].bars.push(new this.BarClass({value:t,label:i,x:this.scale.calculateBarX(this.datasets.length,e,this.scale.valuesCount+1),y:this.scale.endPoint,width:this.scale.calculateBarWidth(this.datasets.length),base:this.scale.endPoint,strokeColor:this.datasets[e].strokeColor,fillColor:this.datasets[e].fillColor}))
+},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.bars.shift()},this),this.update()},reflow:function(){e.extend(this.BarClass.prototype,{y:this.scale.endPoint,base:this.scale.endPoint});var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();this.chart.ctx;this.scale.draw(i),e.each(this.datasets,function(t,s){e.each(t.bars,function(t,e){t.hasValue()&&(t.base=this.scale.endPoint,t.transition({x:this.scale.calculateBarX(this.datasets.length,s,e),y:this.scale.calculateY(t.value),width:this.scale.calculateBarWidth(this.datasets.length)},i).draw())},this)},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle)},this)}}),i.types.Doughnut.extend({name:"Pie",defaults:e.merge(s,{percentageInnerCutout:0})})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,bezierCurve:!0,bezierCurveTension:.4,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)<Math.pow(this.radius+this.hitDetectionRadius,2)}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this),this.buildScale(t.labels),this.eachPoints(function(t,i){e.extend(t,{x:this.scale.calculateX(i),y:this.scale.endPoint}),t.save()},this)},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachPoints(function(t){t.save()}),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.datasets,function(t){e.each(t.points,function(t){t.inRange(s.x,s.y)&&i.push(t)})},this),i},buildScale:function(t){var s=this,n=function(){var t=[];return s.eachPoints(function(i){t.push(i.value)}),t},o={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(n(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.pointDotRadius+this.options.pointDotStrokeWidth,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(o,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new i.Scale(o)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:this.scale.calculateX(this.scale.valuesCount+1),y:this.scale.endPoint,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.points.shift()},this),this.update()},reflow:function(){var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();var s=this.chart.ctx,n=function(t){return null!==t.value},o=function(t,i,s){return e.findNextWhere(i,n,s)||t},a=function(t,i,s){return e.findPreviousWhere(i,n,s)||t};this.scale.draw(i),e.each(this.datasets,function(t){var h=e.where(t.points,n);e.each(t.points,function(t,e){t.hasValue()&&t.transition({y:this.scale.calculateY(t.value),x:this.scale.calculateX(e)},i)},this),this.options.bezierCurve&&e.each(h,function(t,i){var s=i>0&&i<h.length-1?this.options.bezierCurveTension:0;t.controlPoints=e.splineCurve(a(t,h,i),t,o(t,h,i),s),t.controlPoints.outer.y>this.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.y<this.scale.startPoint&&(t.controlPoints.outer.y=this.scale.startPoint),t.controlPoints.inner.y>this.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y<this.scale.startPoint&&(t.controlPoints.inner.y=this.scale.startPoint)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(h,function(t,i){if(0===i)s.moveTo(t.x,t.y);else if(this.options.bezierCurve){var e=a(t,h,i);s.bezierCurveTo(e.controlPoints.outer.x,e.controlPoints.outer.y,t.controlPoints.inner.x,t.controlPoints.inner.y,t.x,t.y)}else s.lineTo(t.x,t.y)},this),s.stroke(),this.options.datasetFill&&h.length>0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle),t.draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scaleShowLine:!0,angleShowLineOut:!0,scaleShowLabels:!1,scaleBeginAtZero:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:10,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this); \ No newline at end of file
diff --git a/web/templates/admin_console.html b/web/templates/admin_console.html
index 574caf730..0e37a4660 100644
--- a/web/templates/admin_console.html
+++ b/web/templates/admin_console.html
@@ -4,6 +4,7 @@
<html>
{{template "head" . }}
<body>
+<script src="/static/js/Chart.min.js"></script>
<div id='error_bar'></div>
diff --git a/web/templates/head.html b/web/templates/head.html
index fdc371af4..041831ed7 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -24,7 +24,6 @@
<link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/css/google-fonts.css">
- <link rel="stylesheet" class="code_theme" href="">
<link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/static/images/favicon.ico" type="image/x-icon">