Implement auto-saving

This commit is contained in:
Yuya Ochiai
2017-02-15 22:07:10 +09:00
parent 90623bcf84
commit 3447d49cbd
4 changed files with 125 additions and 29 deletions

View File

@@ -0,0 +1,33 @@
const React = require('react');
const {Alert} = require('react-bootstrap');
function AutoSaveIndicator(props) {
const {savingState, errorMessage, ...rest} = props;
return (
<Alert
className='AutoSaveIndicator'
{...rest}
bsStyle={props.savingState === 'error' ? 'danger' : 'info'}
>
{(() => {
switch (props.savingState) {
case 'saving':
return 'Saving...';
case 'saved':
return 'Saved!';
case 'error':
return props.errorMessage;
default:
return '';
}
})()}
</Alert>
);
}
AutoSaveIndicator.propTypes = {
savingState: React.PropTypes.string.isRequired,
errorMessage: React.PropTypes.string
};
module.exports = AutoSaveIndicator;

View File

@@ -1,13 +1,16 @@
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
const ReactCSSTransitionGroup = require('react-addons-css-transition-group');
const {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} = require('react-bootstrap'); const {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} = require('react-bootstrap');
const {ipcRenderer, remote} = require('electron'); const {ipcRenderer, remote} = require('electron');
const AutoLaunch = require('auto-launch'); const AutoLaunch = require('auto-launch');
const {debounce} = require('underscore');
const settings = require('../../common/settings'); const settings = require('../../common/settings');
const TeamList = require('./TeamList.jsx'); const TeamList = require('./TeamList.jsx');
const AutoSaveIndicator = require('./AutoSaveIndicator.jsx');
const appLauncher = new AutoLaunch({ const appLauncher = new AutoLaunch({
name: 'Mattermost', name: 'Mattermost',
@@ -38,6 +41,7 @@ const SettingsPage = React.createClass({
if (initialState.teams.length === 0) { if (initialState.teams.length === 0) {
initialState.showAddTeamForm = true; initialState.showAddTeamForm = true;
} }
initialState.savingState = 'done';
return initialState; return initialState;
}, },
@@ -56,6 +60,19 @@ const SettingsPage = React.createClass({
}); });
}); });
}, },
setSavingState(state) {
if (!this.setSavingStateDone) {
this.setSavingStateDone = debounce(() => {
this.setState({savingState: 'done'});
}, 2000);
}
this.setState({savingState: state});
if (state === 'saved') {
this.setSavingStateDone();
}
},
handleTeamsChange(teams) { handleTeamsChange(teams) {
this.setState({ this.setState({
showAddTeamForm: false, showAddTeamForm: false,
@@ -64,8 +81,10 @@ const SettingsPage = React.createClass({
if (teams.length === 0) { if (teams.length === 0) {
this.setState({showAddTeamForm: true}); this.setState({showAddTeamForm: true});
} }
setImmediate(this.saveConfig);
}, },
handleSave(index) { saveConfig() {
this.setSavingState('saving');
var config = { var config = {
teams: this.state.teams, teams: this.state.teams,
showTrayIcon: this.state.showTrayIcon, showTrayIcon: this.state.showTrayIcon,
@@ -83,28 +102,38 @@ const SettingsPage = React.createClass({
var autostart = this.state.autostart; var autostart = this.state.autostart;
appLauncher.isEnabled().then((enabled) => { appLauncher.isEnabled().then((enabled) => {
if (enabled && !autostart) { if (enabled && !autostart) {
appLauncher.disable(); appLauncher.disable().then(() => {
this.setSavingState('saved');
});
} else if (!enabled && autostart) { } else if (!enabled && autostart) {
appLauncher.enable(); appLauncher.enable().then(() => {
this.setSavingState('saved');
});
} else {
this.setSavingState('saved');
} }
}); });
} else {
this.setSavingState('saved');
} }
ipcRenderer.send('update-menu', config); ipcRenderer.send('update-menu', config);
ipcRenderer.send('update-config'); ipcRenderer.send('update-config');
backToIndex(index);
}, },
handleCancel() { handleCancel() {
backToIndex(); backToIndex();
}, },
backToIndexWithSave(index) {
this.handleSave(index);
},
handleChangeDisableWebSecurity() { handleChangeDisableWebSecurity() {
this.setState({ this.setState({
disablewebsecurity: this.refs.disablewebsecurity.props.checked disablewebsecurity: this.refs.disablewebsecurity.props.checked
}); });
setImmediate(this.saveConfig);
},
handleChangeHideMenuBar() {
this.setState({
hideMenuBar: this.refs.hideMenuBar.props.checked
});
setImmediate(this.saveConfig);
}, },
handleChangeShowTrayIcon() { handleChangeShowTrayIcon() {
var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked; var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked;
@@ -117,16 +146,20 @@ const SettingsPage = React.createClass({
minimizeToTray: false minimizeToTray: false
}); });
} }
setImmediate(this.saveConfig);
}, },
handleChangeTrayIconTheme() { handleChangeTrayIconTheme() {
this.setState({ this.setState({
trayIconTheme: ReactDOM.findDOMNode(this.refs.trayIconTheme).value trayIconTheme: ReactDOM.findDOMNode(this.refs.trayIconTheme).value
}); });
setImmediate(this.saveConfig);
}, },
handleChangeAutoStart() { handleChangeAutoStart() {
this.setState({ this.setState({
autostart: !this.refs.autostart.props.checked autostart: !this.refs.autostart.props.checked
}); });
setImmediate(this.saveConfig);
}, },
handleChangeMinimizeToTray() { handleChangeMinimizeToTray() {
const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked; const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked;
@@ -134,16 +167,19 @@ const SettingsPage = React.createClass({
this.setState({ this.setState({
minimizeToTray: shouldMinimizeToTray minimizeToTray: shouldMinimizeToTray
}); });
setImmediate(this.saveConfig);
}, },
toggleShowTeamForm() { toggleShowTeamForm() {
this.setState({ this.setState({
showAddTeamForm: !this.state.showAddTeamForm showAddTeamForm: !this.state.showAddTeamForm
}); });
setImmediate(this.saveConfig);
}, },
setShowTeamFormVisibility(val) { setShowTeamFormVisibility(val) {
this.setState({ this.setState({
showAddTeamForm: val showAddTeamForm: val
}); });
setImmediate(this.saveConfig);
}, },
handleFlashWindow() { handleFlashWindow() {
this.setState({ this.setState({
@@ -151,11 +187,13 @@ const SettingsPage = React.createClass({
flashWindow: this.refs.flashWindow.props.checked ? 0 : 2 flashWindow: this.refs.flashWindow.props.checked ? 0 : 2
} }
}); });
setImmediate(this.saveConfig);
}, },
handleShowUnreadBadge() { handleShowUnreadBadge() {
this.setState({ this.setState({
showUnreadBadge: !this.refs.showUnreadBadge.props.checked showUnreadBadge: !this.refs.showUnreadBadge.props.checked
}); });
setImmediate(this.saveConfig);
}, },
updateTeam(index, newData) { updateTeam(index, newData) {
@@ -164,6 +202,7 @@ const SettingsPage = React.createClass({
this.setState({ this.setState({
teams teams
}); });
setImmediate(this.saveConfig);
}, },
addServer(team) { addServer(team) {
@@ -172,6 +211,7 @@ const SettingsPage = React.createClass({
this.setState({ this.setState({
teams teams
}); });
setImmediate(this.saveConfig);
}, },
render() { render() {
@@ -186,7 +226,7 @@ const SettingsPage = React.createClass({
onTeamsChange={this.handleTeamsChange} onTeamsChange={this.handleTeamsChange}
updateTeam={this.updateTeam} updateTeam={this.updateTeam}
addServer={this.addServer} addServer={this.addServer}
onTeamClick={this.backToIndexWithSave} onTeamClick={backToIndex}
/> />
</Col> </Col>
</Row> </Row>
@@ -388,7 +428,7 @@ const SettingsPage = React.createClass({
</Navbar> </Navbar>
<Grid <Grid
className='settingsPage' className='settingsPage'
style={{padding: '100px 15px'}} style={{paddingTop: '100px'}}
> >
<Row> <Row>
<Col <Col
@@ -415,26 +455,15 @@ const SettingsPage = React.createClass({
<hr/> <hr/>
{ optionsRow } { optionsRow }
</Grid> </Grid>
<Navbar className='navbar-fixed-bottom'> <div className='IndicatorContainer'>
<div <ReactCSSTransitionGroup
className='text-right' transitionName='AutoSaveIndicator'
style={settingsPage.footer} transitionEnterTimeout={500}
transitionLeaveTimeout={1000}
> >
<Button { this.state.savingState === 'done' ? null : <AutoSaveIndicator savingState={this.state.savingState}/> }
id='btnCancel' </ReactCSSTransitionGroup>
className='btn-link' </div>
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> </div>
); );
} }

View File

@@ -3,6 +3,39 @@
background: #eee; background: #eee;
} }
.IndicatorContainer {
pointer-events: none;
position: fixed;
top: 100px;
left: 0;
right: 0;
display: flex;
flex-flow: row;
justify-content: center;
}
.IndicatorContainer * {
pointer-events: auto;
}
.AutoSaveIndicator-enter {
opacity: 0.01;
}
.AutoSaveIndicator-enter.AutoSaveIndicator-enter-active {
opacity: 1;
transition: opacity 0ms;
}
.AutoSaveIndicator-leave {
opacity: 1;
}
.AutoSaveIndicator-leave.AutoSaveIndicator-leave-active {
opacity: 0.01;
transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1);
}
.checkbox > label { .checkbox > label {
width: 100%; width: 100%;
} }

View File

@@ -24,6 +24,7 @@
"react-addons-css-transition-group": "^15.4.1", "react-addons-css-transition-group": "^15.4.1",
"react-bootstrap": "~0.30.7", "react-bootstrap": "~0.30.7",
"react-dom": "^15.4.1", "react-dom": "^15.4.1",
"underscore": "^1.8.3",
"yargs": "^3.32.0" "yargs": "^3.32.0"
} }
} }