Merge pull request #394 from mattermost/mm-1410

MM-1410 Initial implementaiton of import from Slack
Этот коммит содержится в:
Joram Wilander
2015-08-19 12:19:02 -04:00
родитель ca6bff340d 9222e191aa
Коммит 5f43945805
16 изменённых файлов: 1439 добавлений и 816 удалений

Просмотреть файл

@@ -3,7 +3,7 @@
module.exports = React.createClass({
render: function() {
var client_error = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null;
var clientError = this.props.clientError ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.clientError }</label></div> : null;
var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null;
var inputs = this.props.inputs;
@@ -19,7 +19,7 @@ module.exports = React.createClass({
<li className="setting-list-item">
<hr />
{ server_error }
{ client_error }
{ clientError }
{ this.props.submit ? <a className="btn btn-sm btn-primary" onClick={this.props.submit}>Submit</a> : "" }
<a className="btn btn-sm theme" href="#" onClick={this.props.updateSection}>Cancel</a>
</li>

79
web/react/components/setting_upload.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,79 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
module.exports = React.createClass({
displayName: 'Setting Upload',
propTypes: {
title: React.PropTypes.string.isRequired,
submit: React.PropTypes.func.isRequired,
fileTypesAccepted: React.PropTypes.string.isRequired,
clientError: React.PropTypes.string,
serverError: React.PropTypes.string
},
getInitialState: function() {
return {
clientError: this.props.clientError,
serverError: this.props.serverError
};
},
componentWillReceiveProps: function() {
this.setState({
clientError: this.props.clientError,
serverError: this.props.serverError
});
},
doFileSelect: function(e) {
e.preventDefault();
this.setState({
clientError: '',
serverError: ''
});
},
doSubmit: function(e) {
e.preventDefault();
var inputnode = this.refs.uploadinput.getDOMNode();
if (inputnode.files && inputnode.files[0]) {
this.props.submit(inputnode.files[0]);
} else {
this.setState({clientError: 'No file selected.'});
}
},
doCancel: function(e) {
e.preventDefault();
this.refs.uploadinput.getDOMNode().value = '';
this.setState({
clientError: '',
serverError: ''
});
},
render: function() {
var clientError = null;
if (this.state.clientError) {
clientError = (
<div className='form-group has-error'><label className='control-label'>{this.state.clientError}</label></div>
);
}
var serverError = null;
if (this.state.serverError) {
serverError = (
<div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>
);
}
return (
<ul className='section-max'>
<li className='col-xs-12 section-title'>{this.props.title}</li>
<li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'>
<li className='setting-list-item'>
{serverError}
{clientError}
<span className='btn btn-sm btn-primary btn-file sel-btn'>SelectFile<input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' onChange={this.onFileSelect}/></span>
<a className={'btn btn-sm btn-primary'} onClick={this.doSubmit}>Import</a>
<a className='btn btn-sm theme' href='#' onClick={this.doCancel}>Cancel</a>
</li>
</ul>
</li>
</ul>
);
}
});

Просмотреть файл

@@ -15,7 +15,7 @@ module.exports = React.createClass({
<div className="">
<ul className="nav nav-pills nav-stacked">
{this.props.tabs.map(function(tab) {
return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.ui_name}</a></li>
return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.uiName}</a></li>
})}
</ul>
</div>

147
web/react/components/team_feature_tab.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,147 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
module.exports = React.createClass({
displayName: 'Feature Tab',
propTypes: {
updateSection: React.PropTypes.func.isRequired,
team: React.PropTypes.object.isRequired,
activeSection: React.PropTypes.string.isRequired
},
submitValetFeature: function() {
var data = {};
data.allowValet = this.state.allowValet;
client.updateValetFeature(data,
function() {
this.props.updateSection('');
AsyncClient.getMyTeam();
}.bind(this),
function(err) {
var state = this.getInitialState();
state.serverError = err;
this.setState(state);
}.bind(this)
);
},
handleValetRadio: function(val) {
this.setState({allowValet: val});
this.refs.wrapper.getDOMNode().focus();
},
componentWillReceiveProps: function(newProps) {
var team = newProps.team;
var allowValet = 'false';
if (team && team.allowValet) {
allowValet = 'true';
}
this.setState({allowValet: allowValet});
},
getInitialState: function() {
var team = this.props.team;
var allowValet = 'false';
if (team && team.allowValet) {
allowValet = 'true';
}
return {allowValet: allowValet};
},
onUpdateSection: function() {
if (this.props.activeSection === 'valet') {
self.props.updateSection('valet');
} else {
self.props.updateSection('');
}
},
render: function() {
var clientError = null;
var serverError = null;
if (this.state.clientError) {
clientError = this.state.clientError;
}
if (this.state.serverError) {
serverError = this.state.serverError;
}
var valetSection;
var self = this;
if (this.props.activeSection === 'valet') {
var valetActive = ['', ''];
if (this.state.allowValet === 'false') {
valetActive[1] = 'active';
} else {
valetActive[0] = 'active';
}
var inputs = [];
function valetActivate() {
self.handleValetRadio('true');
}
function valetDeactivate() {
self.handleValetRadio('false');
}
inputs.push(
<div>
<div className='btn-group' data-toggle='buttons-radio'>
<button className={'btn btn-default ' + valetActive[0]} onClick={valetActivate}>On</button>
<button className={'btn btn-default ' + valetActive[1]} onClick={valetDeactivate}>Off</button>
</div>
<div><br/>Valet is a preview feature for enabling a non-user account limited to basic member permissions that can be manipulated by 3rd parties.<br/><br/>IMPORTANT: The preview version of Valet should not be used without a secure connection and a trusted 3rd party, since user credentials are used to connect. OAuth2 will be used in the final release.</div>
</div>
);
valetSection = (
<SettingItemMax
title='Valet (Preview - EXPERTS ONLY)'
inputs={inputs}
submit={this.submitValetFeature}
serverError={serverError}
clientError={clientError}
updateSection={this.onUpdateSection}
/>
);
} else {
var describe = '';
if (this.state.allowValet === 'false') {
describe = 'Off';
} else {
describe = 'On';
}
valetSection = (
<SettingItemMin
title='Valet (Preview - EXPERTS ONLY)'
describe={describe}
updateSection={this.onUpdateSection}
/>
);
}
return (
<div>
<div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
<h4 className='modal-title' ref='title'><i className='modal-back'></i>Feature Settings</h4>
</div>
<div ref='wrapper' className='user-settings'>
<h3 className='tab-header'>Feature Settings</h3>
<div className='divider-dark first'/>
{valetSection}
<div className='divider-dark'/>
</div>
</div>
);
}
});

68
web/react/components/team_import_tab.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,68 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var utils = require('../utils/utils.jsx');
var SettingUpload = require('./setting_upload.jsx');
module.exports = React.createClass({
displayName: 'Import Tab',
getInitialState: function() {
return {status: 'ready', link: ''};
},
onImportFailure: function() {
this.setState({status: 'fail', link: ''});
},
onImportSuccess: function(data) {
this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(data)});
},
doImportSlack: function(file) {
this.setState({status: 'in-progress', link: ''});
utils.importSlack(file, this.onImportSuccess, this.onImportFailure);
},
render: function() {
var uploadSection = (
<SettingUpload
title='Import from Slack'
submit={this.doImportSlack}
fileTypesAccepted='.zip'/>
);
var messageSection;
switch (this.state.status) {
case 'ready':
messageSection = '';
break;
case 'in-progress':
messageSection = (
<p>Importing...</p>
);
break;
case 'done':
messageSection = (
<p>Import sucessfull: <a href={this.state.link} download='MattermostImportSummery.txt'>View Summery</a></p>
);
break;
case 'fail':
messageSection = (
<p>Import failure: <a href={this.state.link} download='MattermostImportSummery.txt'>View Summery</a></p>
);
break;
}
return (
<div>
<div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
<h4 className='modal-title' ref='title'><i className='modal-back'></i>Import</h4>
</div>
<div ref='wrapper' className='user-settings'>
<h3 className='tab-header'>Import</h3>
<div className='divider-dark first'/>
{uploadSection}
{messageSection}
<div className='divider-dark'/>
</div>
</div>
);
}
});

Просмотреть файл

@@ -1,161 +1,62 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
var SettingPicture = require('./setting_picture.jsx');
var ImportTab = require('./team_import_tab.jsx');
var FeatureTab = require('./team_feature_tab.jsx');
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
var FeatureTab = React.createClass({
submitValetFeature: function() {
data = {};
data['allow_valet'] = this.state.allow_valet;
client.updateValetFeature(data,
function(data) {
this.props.updateSection("");
AsyncClient.getMyTeam();
}.bind(this),
function(err) {
state = this.getInitialState();
state.server_error = err;
this.setState(state);
}.bind(this)
);
},
handleValetRadio: function(val) {
this.setState({ allow_valet: val });
this.refs.wrapper.getDOMNode().focus();
},
componentWillReceiveProps: function(newProps) {
var team = newProps.team;
var allow_valet = "false";
if (team && team.allow_valet) {
allow_valet = "true";
}
this.setState({ allow_valet: allow_valet });
},
getInitialState: function() {
var team = this.props.team;
var allow_valet = "false";
if (team && team.allow_valet) {
allow_valet = "true";
}
return { allow_valet: allow_valet };
},
render: function() {
var team = this.props.team;
var client_error = this.state.client_error ? this.state.client_error : null;
var server_error = this.state.server_error ? this.state.server_error : null;
var valetSection;
var self = this;
if (this.props.activeSection === 'valet') {
var valetActive = ["",""];
if (this.state.allow_valet === "false") {
valetActive[1] = "active";
} else {
valetActive[0] = "active";
}
var inputs = [];
inputs.push(
<div>
<div className="btn-group" data-toggle="buttons-radio">
<button className={"btn btn-default "+valetActive[0]} onClick={function(){self.handleValetRadio("true")}}>On</button>
<button className={"btn btn-default "+valetActive[1]} onClick={function(){self.handleValetRadio("false")}}>Off</button>
</div>
<div><br/>Valet is a preview feature for enabling a non-user account limited to basic member permissions that can be manipulated by 3rd parties.<br/><br/>IMPORTANT: The preview version of Valet should not be used without a secure connection and a trusted 3rd party, since user credentials are used to connect. OAuth2 will be used in the final release.</div>
</div>
);
valetSection = (
<SettingItemMax
title="Valet (Preview - EXPERTS ONLY)"
inputs={inputs}
submit={this.submitValetFeature}
server_error={server_error}
client_error={client_error}
updateSection={function(e){self.props.updateSection("");e.preventDefault();}}
/>
);
} else {
var describe = "";
if (this.state.allow_valet === "false") {
describe = "Off";
} else {
describe = "On";
}
valetSection = (
<SettingItemMin
title="Valet (Preview - EXPERTS ONLY)"
describe={describe}
updateSection={function(){self.props.updateSection("valet");}}
/>
);
}
return (
<div>
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 className="modal-title" ref="title"><i className="modal-back"></i>Feature Settings</h4>
</div>
<div ref="wrapper" className="user-settings">
<h3 className="tab-header">Feature Settings</h3>
<div className="divider-dark first"/>
{valetSection}
<div className="divider-dark"/>
</div>
</div>
);
}
});
module.exports = React.createClass({
displayName: 'Team Settings',
propTypes: {
activeTab: React.PropTypes.string.isRequired,
activeSection: React.PropTypes.string.isRequired,
updateSection: React.PropTypes.func.isRequired
},
componentDidMount: function() {
TeamStore.addChangeListener(this._onChange);
TeamStore.addChangeListener(this.onChange);
},
componentWillUnmount: function() {
TeamStore.removeChangeListener(this._onChange);
TeamStore.removeChangeListener(this.onChange);
},
_onChange: function () {
onChange: function() {
var team = TeamStore.getCurrent();
if (!utils.areStatesEqual(this.state.team, team)) {
this.setState({ team: team });
this.setState({team: team});
}
},
getInitialState: function() {
return { team: TeamStore.getCurrent() };
return {team: TeamStore.getCurrent()};
},
render: function() {
if (this.props.activeTab === 'general') {
return (
<div>
</div>
);
} else if (this.props.activeTab === 'feature') {
return (
<div>
<FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
</div>
);
} else {
return <div/>;
var result;
switch (this.props.activeTab) {
case 'general':
result = (
<div>
</div>
);
break;
case 'feature':
result = (
<div>
<FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
</div>
);
break;
case 'import':
result = (
<div>
<ImportTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
</div>
);
break;
default:
result = (
<div/>
);
break;
}
return result;
}
});

Просмотреть файл

@@ -5,50 +5,52 @@ var SettingsSidebar = require('./settings_sidebar.jsx');
var TeamSettings = require('./team_settings.jsx');
module.exports = React.createClass({
displayName: 'Team Settings Modal',
componentDidMount: function() {
$('body').on('click', '.modal-back', function(){
$('body').on('click', '.modal-back', function onClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
$('body').on('click', '.modal-header .close', function(){
setTimeout(function() {
$('body').on('click', '.modal-header .close', function onClick() {
setTimeout(function removeContent() {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
},
updateTab: function(tab) {
this.setState({ active_tab: tab });
this.setState({activeTab: tab});
},
updateSection: function(section) {
this.setState({ active_section: section });
this.setState({activeSection: section});
},
getInitialState: function() {
return { active_tab: "feature", active_section: "" };
return {activeTab: 'feature', activeSection: ''};
},
render: function() {
var tabs = [];
tabs.push({name: "feature", ui_name: "Features", icon: "glyphicon glyphicon-wrench"});
tabs.push({name: 'feature', uiName: 'Features', icon: 'glyphicon glyphicon-wrench'});
tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
return (
<div className="modal fade" ref="modal" id="team_settings" role="dialog" tabIndex="-1" aria-hidden="true">
<div className="modal-dialog settings-modal">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 className="modal-title" ref="title">Team Settings</h4>
<div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'>
<div className='modal-dialog settings-modal'>
<div className='modal-content'>
<div className='modal-header'>
<button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
<h4 className='modal-title' ref='title'>Team Settings</h4>
</div>
<div className="modal-body">
<div className="settings-table">
<div className="settings-links">
<div className='modal-body'>
<div className='settings-table'>
<div className='settings-links'>
<SettingsSidebar
tabs={tabs}
activeTab={this.state.active_tab}
activeTab={this.state.activeTab}
updateTab={this.updateTab}
/>
</div>
<div className="settings-content minimize-settings">
<div className='settings-content minimize-settings'>
<TeamSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
activeTab={this.state.activeTab}
activeSection={this.state.activeSection}
updateSection={this.updateSection}
/>
</div>