Merge pull request #425 from yuya-oc/issue400-part2
Open the corresponding tab when a server is clicked in settings page
This commit is contained in:
commit
f234f1bc95
41
src/browser/components/AutoSaveIndicator.jsx
Normal file
41
src/browser/components/AutoSaveIndicator.jsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
const React = require('react');
|
||||
const {Alert} = require('react-bootstrap');
|
||||
|
||||
const baseClassName = 'AutoSaveIndicator';
|
||||
const leaveClassName = `${baseClassName}-Leave`;
|
||||
|
||||
function getClassNameAndMessage(savingState, errorMessage) {
|
||||
switch (savingState) {
|
||||
case 'saving':
|
||||
return {className: baseClassName, message: 'Saving...'};
|
||||
case 'saved':
|
||||
return {className: baseClassName, message: 'Saved'};
|
||||
case 'error':
|
||||
return {className: `${baseClassName}`, message: errorMessage};
|
||||
case 'done':
|
||||
return {className: `${baseClassName} ${leaveClassName}`, message: 'Saved'};
|
||||
default:
|
||||
return {className: `${baseClassName} ${leaveClassName}`, message: ''};
|
||||
}
|
||||
}
|
||||
|
||||
function AutoSaveIndicator(props) {
|
||||
const {savingState, errorMessage, ...rest} = props;
|
||||
const {className, message} = getClassNameAndMessage(savingState, errorMessage);
|
||||
return (
|
||||
<Alert
|
||||
className={className}
|
||||
{...rest}
|
||||
bsStyle={savingState === 'error' ? 'danger' : 'info'}
|
||||
>
|
||||
{message}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
AutoSaveIndicator.propTypes = {
|
||||
savingState: React.PropTypes.string.isRequired,
|
||||
errorMessage: React.PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = AutoSaveIndicator;
|
|
@ -39,12 +39,13 @@ const MainPage = React.createClass({
|
|||
disablewebsecurity: React.PropTypes.bool.isRequired,
|
||||
onUnreadCountChange: React.PropTypes.func.isRequired,
|
||||
teams: React.PropTypes.array.isRequired,
|
||||
onTeamConfigChange: React.PropTypes.func.isRequired
|
||||
onTeamConfigChange: React.PropTypes.func.isRequired,
|
||||
initialIndex: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
key: 0,
|
||||
key: this.props.initialIndex,
|
||||
unreadCounts: new Array(this.props.teams.length),
|
||||
mentionCounts: new Array(this.props.teams.length),
|
||||
unreadAtActive: new Array(this.props.teams.length),
|
||||
|
|
|
@ -4,18 +4,21 @@ const {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} =
|
|||
|
||||
const {ipcRenderer, remote} = require('electron');
|
||||
const AutoLaunch = require('auto-launch');
|
||||
const {debounce} = require('underscore');
|
||||
|
||||
const settings = require('../../common/settings');
|
||||
|
||||
const TeamList = require('./TeamList.jsx');
|
||||
const AutoSaveIndicator = require('./AutoSaveIndicator.jsx');
|
||||
|
||||
const appLauncher = new AutoLaunch({
|
||||
name: 'Mattermost',
|
||||
isHidden: true
|
||||
});
|
||||
|
||||
function backToIndex() {
|
||||
remote.getCurrentWindow().loadURL('file://' + __dirname + '/index.html');
|
||||
function backToIndex(index) {
|
||||
const target = typeof index === 'undefined' ? 0 : index;
|
||||
remote.getCurrentWindow().loadURL(`file://${__dirname}/index.html?index=${target}`);
|
||||
}
|
||||
|
||||
const SettingsPage = React.createClass({
|
||||
|
@ -37,6 +40,7 @@ const SettingsPage = React.createClass({
|
|||
if (initialState.teams.length === 0) {
|
||||
initialState.showAddTeamForm = true;
|
||||
}
|
||||
initialState.savingState = 'done';
|
||||
|
||||
return initialState;
|
||||
},
|
||||
|
@ -55,6 +59,34 @@ const SettingsPage = React.createClass({
|
|||
});
|
||||
});
|
||||
},
|
||||
|
||||
setSavingState(state) {
|
||||
if (!this.setSavingStateSaved) {
|
||||
this.setSavingStateSaved = debounce(() => {
|
||||
this.saveConfig((err) => {
|
||||
if (err) {
|
||||
this.setState({savingState: 'error'});
|
||||
} else {
|
||||
this.setState({savingState: 'saved'});
|
||||
}
|
||||
this.setSavingStateDoneTimer = setTimeout(this.setState.bind(this, {savingState: 'done'}), 2000);
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
if (this.setSavingStateDoneTimer) {
|
||||
clearTimeout(this.setSavingStateDoneTimer);
|
||||
this.setSavingStateDoneTimer = null;
|
||||
}
|
||||
this.setState({savingState: state});
|
||||
if (state === 'saving') {
|
||||
this.setSavingStateSaved();
|
||||
}
|
||||
},
|
||||
|
||||
startSaveConfig() {
|
||||
this.setSavingState('saving');
|
||||
},
|
||||
|
||||
handleTeamsChange(teams) {
|
||||
this.setState({
|
||||
showAddTeamForm: false,
|
||||
|
@ -63,8 +95,10 @@ const SettingsPage = React.createClass({
|
|||
if (teams.length === 0) {
|
||||
this.setState({showAddTeamForm: true});
|
||||
}
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
handleSave() {
|
||||
|
||||
saveConfig(callback) {
|
||||
var config = {
|
||||
teams: this.state.teams,
|
||||
showTrayIcon: this.state.showTrayIcon,
|
||||
|
@ -77,23 +111,39 @@ const SettingsPage = React.createClass({
|
|||
},
|
||||
showUnreadBadge: this.state.showUnreadBadge
|
||||
};
|
||||
settings.writeFileSync(this.props.configFile, config);
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
var autostart = this.state.autostart;
|
||||
appLauncher.isEnabled().then((enabled) => {
|
||||
if (enabled && !autostart) {
|
||||
appLauncher.disable();
|
||||
} else if (!enabled && autostart) {
|
||||
appLauncher.enable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.send('update-menu', config);
|
||||
ipcRenderer.send('update-config');
|
||||
|
||||
backToIndex();
|
||||
settings.writeFile(this.props.configFile, config, (err) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
const autostart = this.state.autostart;
|
||||
this.saveAutoStart(autostart, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveAutoStart(autostart, callback) {
|
||||
appLauncher.isEnabled().then((enabled) => {
|
||||
if (enabled && !autostart) {
|
||||
appLauncher.disable().then(() => {
|
||||
callback();
|
||||
}).catch(callback);
|
||||
} else if (!enabled && autostart) {
|
||||
appLauncher.enable().then(() => {
|
||||
callback();
|
||||
}).catch(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}).catch(callback);
|
||||
},
|
||||
|
||||
handleCancel() {
|
||||
backToIndex();
|
||||
},
|
||||
|
@ -101,6 +151,7 @@ const SettingsPage = React.createClass({
|
|||
this.setState({
|
||||
disablewebsecurity: this.refs.disablewebsecurity.props.checked
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
handleChangeShowTrayIcon() {
|
||||
var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked;
|
||||
|
@ -113,16 +164,20 @@ const SettingsPage = React.createClass({
|
|||
minimizeToTray: false
|
||||
});
|
||||
}
|
||||
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
handleChangeTrayIconTheme() {
|
||||
this.setState({
|
||||
trayIconTheme: ReactDOM.findDOMNode(this.refs.trayIconTheme).value
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
handleChangeAutoStart() {
|
||||
this.setState({
|
||||
autostart: !this.refs.autostart.props.checked
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
handleChangeMinimizeToTray() {
|
||||
const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked;
|
||||
|
@ -130,6 +185,7 @@ const SettingsPage = React.createClass({
|
|||
this.setState({
|
||||
minimizeToTray: shouldMinimizeToTray
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
toggleShowTeamForm() {
|
||||
this.setState({
|
||||
|
@ -147,11 +203,13 @@ const SettingsPage = React.createClass({
|
|||
flashWindow: this.refs.flashWindow.props.checked ? 0 : 2
|
||||
}
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
handleShowUnreadBadge() {
|
||||
this.setState({
|
||||
showUnreadBadge: !this.refs.showUnreadBadge.props.checked
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
|
||||
updateTeam(index, newData) {
|
||||
|
@ -160,6 +218,7 @@ const SettingsPage = React.createClass({
|
|||
this.setState({
|
||||
teams
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
|
||||
addServer(team) {
|
||||
|
@ -168,6 +227,7 @@ const SettingsPage = React.createClass({
|
|||
this.setState({
|
||||
teams
|
||||
});
|
||||
setImmediate(this.startSaveConfig);
|
||||
},
|
||||
|
||||
render() {
|
||||
|
@ -182,6 +242,7 @@ const SettingsPage = React.createClass({
|
|||
onTeamsChange={this.handleTeamsChange}
|
||||
updateTeam={this.updateTeam}
|
||||
addServer={this.addServer}
|
||||
onTeamClick={backToIndex}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -369,9 +430,16 @@ const SettingsPage = React.createClass({
|
|||
className='navbar-fixed-top'
|
||||
style={settingsPage.navbar}
|
||||
>
|
||||
<div className='IndicatorContainer'>
|
||||
<AutoSaveIndicator
|
||||
savingState={this.state.savingState}
|
||||
errorMessage={'Can\'t save your changes. Please try again.'}
|
||||
/>
|
||||
</div>
|
||||
<div style={{position: 'relative'}}>
|
||||
<h1 style={settingsPage.heading}>{'Settings'}</h1>
|
||||
<Button
|
||||
id='btnClose'
|
||||
bsStyle='link'
|
||||
style={settingsPage.close}
|
||||
onClick={this.handleCancel}
|
||||
|
@ -383,7 +451,7 @@ const SettingsPage = React.createClass({
|
|||
</Navbar>
|
||||
<Grid
|
||||
className='settingsPage'
|
||||
style={{padding: '100px 15px'}}
|
||||
style={{paddingTop: '100px'}}
|
||||
>
|
||||
<Row>
|
||||
<Col
|
||||
|
@ -410,26 +478,6 @@ const SettingsPage = React.createClass({
|
|||
<hr/>
|
||||
{ optionsRow }
|
||||
</Grid>
|
||||
<Navbar className='navbar-fixed-bottom'>
|
||||
<div
|
||||
className='text-right'
|
||||
style={settingsPage.footer}
|
||||
>
|
||||
<Button
|
||||
id='btnCancel'
|
||||
className='btn-link'
|
||||
onClick={this.handleCancel}
|
||||
>{'Cancel'}</Button>
|
||||
{ ' ' }
|
||||
<Button
|
||||
id='btnSave'
|
||||
className='navbar-btn'
|
||||
bsStyle='primary'
|
||||
onClick={this.handleSave}
|
||||
disabled={this.state.teams.length === 0}
|
||||
>{'Save'}</Button>
|
||||
</div>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ const TeamList = React.createClass({
|
|||
addServer: React.PropTypes.func,
|
||||
updateTeam: React.PropTypes.func,
|
||||
toggleAddTeamForm: React.PropTypes.func,
|
||||
setAddTeamFormVisibility: React.PropTypes.func
|
||||
setAddTeamFormVisibility: React.PropTypes.func,
|
||||
onTeamClick: React.PropTypes.func
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -84,6 +85,10 @@ const TeamList = React.createClass({
|
|||
self.handleTeamEditing(team.name, team.url, i);
|
||||
}
|
||||
|
||||
function handleTeamClick() {
|
||||
self.props.onTeamClick(i);
|
||||
}
|
||||
|
||||
return (
|
||||
<TeamListItem
|
||||
index={i}
|
||||
|
@ -92,6 +97,7 @@ const TeamList = React.createClass({
|
|||
url={team.url}
|
||||
onTeamRemove={handleTeamRemove}
|
||||
onTeamEditing={handleTeamEditing}
|
||||
onTeamClick={handleTeamClick}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -16,12 +16,17 @@ class TeamListItem extends React.Component {
|
|||
render() {
|
||||
var style = {
|
||||
left: {
|
||||
display: 'inline-block'
|
||||
display: 'inline-block',
|
||||
width: 'calc(100% - 100px)',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='teamListItem list-group-item'>
|
||||
<div style={style.left}>
|
||||
<div
|
||||
style={style.left}
|
||||
onClick={this.props.onTeamClick}
|
||||
>
|
||||
<h4 className='list-group-item-heading'>{ this.props.name }</h4>
|
||||
<p className='list-group-item-text'>
|
||||
{ this.props.url }
|
||||
|
@ -47,6 +52,7 @@ TeamListItem.propTypes = {
|
|||
name: React.PropTypes.string,
|
||||
onTeamEditing: React.PropTypes.func,
|
||||
onTeamRemove: React.PropTypes.func,
|
||||
onTeamClick: React.PropTypes.func,
|
||||
url: React.PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,26 @@
|
|||
|
||||
.teamListItem:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.IndicatorContainer {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.AutoSaveIndicator {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.AutoSaveIndicator.AutoSaveIndicator-Leave {
|
||||
opacity: 0;
|
||||
transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
.checkbox > label {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ const {remote, ipcRenderer} = require('electron');
|
|||
const MainPage = require('./components/MainPage.jsx');
|
||||
|
||||
const AppConfig = require('./config/AppConfig.js');
|
||||
const url = require('url');
|
||||
|
||||
const badge = require('./js/badge');
|
||||
|
||||
remote.getCurrentWindow().removeAllListeners('focus');
|
||||
|
@ -86,10 +88,14 @@ function teamConfigChange(teams) {
|
|||
AppConfig.set('teams', teams);
|
||||
}
|
||||
|
||||
const parsedURL = url.parse(window.location.href, true);
|
||||
const initialIndex = parsedURL.query.index ? parseInt(parsedURL.query.index, 10) : 0;
|
||||
|
||||
ReactDOM.render(
|
||||
<MainPage
|
||||
disablewebsecurity={AppConfig.data.disablewebsecurity}
|
||||
teams={AppConfig.data.teams}
|
||||
initialIndex={initialIndex}
|
||||
onUnreadCountChange={showUnreadBadge}
|
||||
onTeamConfigChange={teamConfigChange}
|
||||
/>,
|
||||
|
|
|
@ -67,6 +67,14 @@ module.exports = {
|
|||
return config;
|
||||
},
|
||||
|
||||
writeFile(configFile, config, callback) {
|
||||
if (config.version !== settingsVersion) {
|
||||
throw new Error('version ' + config.version + ' is not equal to ' + settingsVersion);
|
||||
}
|
||||
var data = JSON.stringify(config, null, ' ');
|
||||
fs.writeFile(configFile, data, 'utf8', callback);
|
||||
},
|
||||
|
||||
writeFileSync(configFile, config) {
|
||||
if (config.version !== settingsVersion) {
|
||||
throw new Error('version ' + config.version + ' is not equal to ' + settingsVersion);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"react-addons-css-transition-group": "^15.4.1",
|
||||
"react-bootstrap": "~0.30.7",
|
||||
"react-dom": "^15.4.1",
|
||||
"underscore": "^1.8.3",
|
||||
"yargs": "^3.32.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,22 +31,13 @@ describe('browser/settings.html', function desc() {
|
|||
return true;
|
||||
});
|
||||
|
||||
it('should show index.html when Cancel button is clicked', () => {
|
||||
it('should show index.html when Close button is clicked', () => {
|
||||
env.addClientCommands(this.app.client);
|
||||
return this.app.client.
|
||||
loadSettingsPage().
|
||||
click('#btnCancel').
|
||||
click('#btnClose').
|
||||
pause(1000).
|
||||
getUrl().should.eventually.match(/\/index.html$/);
|
||||
});
|
||||
|
||||
it('should show index.html when Save button is clicked', () => {
|
||||
env.addClientCommands(this.app.client);
|
||||
return this.app.client.
|
||||
loadSettingsPage().
|
||||
click('#btnSave').
|
||||
pause(1000).
|
||||
getUrl().should.eventually.match(/\/index.html$/);
|
||||
getUrl().should.eventually.match(/\/index.html(\?.+)?$/);
|
||||
});
|
||||
|
||||
it('should show NewServerModal after all servers are removed', () => {
|
||||
|
@ -65,6 +56,28 @@ describe('browser/settings.html', function desc() {
|
|||
isExisting('#newServerModal').should.eventually.equal(true);
|
||||
});
|
||||
|
||||
describe('Server list', () => {
|
||||
it('should open the corresponding tab when a server list item is clicked', () => {
|
||||
env.addClientCommands(this.app.client);
|
||||
return this.app.client.
|
||||
loadSettingsPage().
|
||||
click('h4=example_1').
|
||||
pause(100).
|
||||
waitUntilWindowLoaded().
|
||||
getUrl().should.eventually.match(/\/index.html(\?.+)?$/).
|
||||
isVisible('#mattermostView0').should.eventually.be.true.
|
||||
isVisible('#mattermostView1').should.eventually.be.false.
|
||||
|
||||
loadSettingsPage().
|
||||
click('h4=example_2').
|
||||
pause(100).
|
||||
waitUntilWindowLoaded().
|
||||
getUrl().should.eventually.match(/\/index.html(\?.+)?$/).
|
||||
isVisible('#mattermostView0').should.eventually.be.false.
|
||||
isVisible('#mattermostView1').should.eventually.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Options', () => {
|
||||
describe.skip('Hide Menu Bar', () => {
|
||||
it('should appear on win32 or linux', () => {
|
||||
|
@ -87,7 +100,8 @@ describe('browser/settings.html', function desc() {
|
|||
}
|
||||
return true;
|
||||
}).
|
||||
click('#btnSave').
|
||||
pause(600).
|
||||
click('#btnClose').
|
||||
pause(1000).then(() => {
|
||||
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
||||
savedConfig.hideMenuBar.should.equal(v);
|
||||
|
@ -120,7 +134,8 @@ describe('browser/settings.html', function desc() {
|
|||
}
|
||||
return true;
|
||||
}).
|
||||
click('#btnSave').
|
||||
pause(600).
|
||||
click('#btnClose').
|
||||
pause(1000).then(() => {
|
||||
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
||||
savedConfig.disablewebsecurity.should.equal(!v);
|
||||
|
@ -226,7 +241,7 @@ describe('browser/settings.html', function desc() {
|
|||
element('.modal-dialog').click('.btn=Remove').
|
||||
pause(500).
|
||||
isExisting(modalTitleSelector).should.eventually.false.
|
||||
click('#btnSave').
|
||||
click('#btnClose').
|
||||
pause(500).then(() => {
|
||||
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
||||
savedConfig.teams.should.deep.equal(config.teams.slice(1));
|
||||
|
@ -239,7 +254,7 @@ describe('browser/settings.html', function desc() {
|
|||
element('.modal-dialog').click('.btn=Cancel').
|
||||
pause(500).
|
||||
isExisting(modalTitleSelector).should.eventually.false.
|
||||
click('#btnSave').
|
||||
click('#btnClose').
|
||||
pause(500).then(() => {
|
||||
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
||||
savedConfig.teams.should.deep.equal(config.teams);
|
||||
|
@ -352,7 +367,7 @@ describe('browser/settings.html', function desc() {
|
|||
this.app.client.
|
||||
click('#saveNewServerModal').
|
||||
pause(1000). // Animation
|
||||
click('#btnSave').
|
||||
click('#btnClose').
|
||||
pause(1000).then(() => {
|
||||
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
||||
savedConfig.teams.should.contain({
|
||||
|
|
Loading…
Reference in a new issue