From 803366872ae1ab94b36d6dff07f56e65f8bbc0b3 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Sun, 29 Jan 2017 21:40:20 +0900 Subject: [PATCH 01/15] Open the corresponding tab when a server is clicked in settings page --- src/browser/components/MainPage.jsx | 5 +++-- src/browser/components/SettingsPage.jsx | 6 ++++-- src/browser/components/TeamList.jsx | 4 +++- src/browser/components/TeamListItem.jsx | 9 +++++++-- src/browser/index.jsx | 7 +++++++ 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/browser/components/MainPage.jsx b/src/browser/components/MainPage.jsx index 6695c2be..84efa920 100644 --- a/src/browser/components/MainPage.jsx +++ b/src/browser/components/MainPage.jsx @@ -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), diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 993b719f..a1cc125a 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -14,8 +14,9 @@ const appLauncher = new AutoLaunch({ 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({ @@ -182,6 +183,7 @@ const SettingsPage = React.createClass({ onTeamsChange={this.handleTeamsChange} updateTeam={this.updateTeam} addServer={this.addServer} + onTeamClick={backToIndex} /> diff --git a/src/browser/components/TeamList.jsx b/src/browser/components/TeamList.jsx index ddad0bb7..4a08f712 100644 --- a/src/browser/components/TeamList.jsx +++ b/src/browser/components/TeamList.jsx @@ -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() { @@ -92,6 +93,7 @@ const TeamList = React.createClass({ url={team.url} onTeamRemove={handleTeamRemove} onTeamEditing={handleTeamEditing} + onTeamClick={this.props.onTeamClick.bind(this, i)} /> ); }); diff --git a/src/browser/components/TeamListItem.jsx b/src/browser/components/TeamListItem.jsx index 7a5223ae..024d82bd 100644 --- a/src/browser/components/TeamListItem.jsx +++ b/src/browser/components/TeamListItem.jsx @@ -16,12 +16,16 @@ class TeamListItem extends React.Component { render() { var style = { left: { - display: 'inline-block' + display: 'inline-block', + cursor: 'pointer' } }; return (
-
+

{ this.props.name }

{ this.props.url } @@ -47,6 +51,7 @@ TeamListItem.propTypes = { name: React.PropTypes.string, onTeamEditing: React.PropTypes.func, onTeamRemove: React.PropTypes.func, + onTeamClick: React.PropTypes.func, url: React.PropTypes.string }; diff --git a/src/browser/index.jsx b/src/browser/index.jsx index 18a7476a..d6fa3759 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -10,6 +10,9 @@ const {remote, ipcRenderer} = require('electron'); const MainPage = require('./components/MainPage.jsx'); const AppConfig = require('./config/AppConfig.js'); +const url = require('url'); + +const settings = require('../common/settings'); const badge = require('./js/badge'); remote.getCurrentWindow().removeAllListeners('focus'); @@ -86,10 +89,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( , From efe95bb72b77e35adb9850b6f2f01473848719cf Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Sun, 29 Jan 2017 22:06:20 +0900 Subject: [PATCH 02/15] Add test on cliecking server list item --- test/specs/browser/settings_test.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/specs/browser/settings_test.js b/test/specs/browser/settings_test.js index 00641cce..5ebe9452 100644 --- a/test/specs/browser/settings_test.js +++ b/test/specs/browser/settings_test.js @@ -65,6 +65,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', () => { From e89ecf3120020faee4543598a889e34d085af74c Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Sun, 29 Jan 2017 22:57:18 +0900 Subject: [PATCH 03/15] Fix test assertion --- test/specs/browser/settings_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/specs/browser/settings_test.js b/test/specs/browser/settings_test.js index 5ebe9452..0c44f33b 100644 --- a/test/specs/browser/settings_test.js +++ b/test/specs/browser/settings_test.js @@ -37,7 +37,7 @@ describe('browser/settings.html', function desc() { loadSettingsPage(). click('#btnCancel'). pause(1000). - getUrl().should.eventually.match(/\/index.html$/); + getUrl().should.eventually.match(/\/index.html(\?.+)?$/); }); it('should show index.html when Save button is clicked', () => { @@ -46,7 +46,7 @@ describe('browser/settings.html', function desc() { 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', () => { From e3ba17570efec0bc30f726d16bf1c8e02d82ffed Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Wed, 1 Feb 2017 20:45:10 +0900 Subject: [PATCH 04/15] Fix warning for bind --- src/browser/components/TeamList.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/browser/components/TeamList.jsx b/src/browser/components/TeamList.jsx index 4a08f712..195f3529 100644 --- a/src/browser/components/TeamList.jsx +++ b/src/browser/components/TeamList.jsx @@ -85,6 +85,10 @@ const TeamList = React.createClass({ self.handleTeamEditing(team.name, team.url, i); } + function handleTeamClick() { + self.props.onTeamClick(i); + } + return ( ); }); From 66f76c1ad81aadba0818de9b1d60d72d22d38a7e Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Wed, 1 Feb 2017 20:45:32 +0900 Subject: [PATCH 05/15] Save settings when a server is clicked in settigns page --- src/browser/components/SettingsPage.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index a1cc125a..4974313a 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -65,7 +65,7 @@ const SettingsPage = React.createClass({ this.setState({showAddTeamForm: true}); } }, - handleSave() { + handleSave(index) { var config = { teams: this.state.teams, showTrayIcon: this.state.showTrayIcon, @@ -93,11 +93,14 @@ const SettingsPage = React.createClass({ ipcRenderer.send('update-menu', config); ipcRenderer.send('update-config'); - backToIndex(); + backToIndex(index); }, handleCancel() { backToIndex(); }, + backToIndexWithSave(index) { + this.handleSave(index); + }, handleChangeDisableWebSecurity() { this.setState({ disablewebsecurity: this.refs.disablewebsecurity.props.checked @@ -183,7 +186,7 @@ const SettingsPage = React.createClass({ onTeamsChange={this.handleTeamsChange} updateTeam={this.updateTeam} addServer={this.addServer} - onTeamClick={backToIndex} + onTeamClick={this.backToIndexWithSave} /> From 5388ce08ba567235bb17dca0f91882d76232250b Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Fri, 3 Feb 2017 21:38:28 +0900 Subject: [PATCH 06/15] Tweak for clickable area and hover in server list --- src/browser/components/TeamListItem.jsx | 1 + src/browser/css/settings.css | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/browser/components/TeamListItem.jsx b/src/browser/components/TeamListItem.jsx index 024d82bd..8d128c13 100644 --- a/src/browser/components/TeamListItem.jsx +++ b/src/browser/components/TeamListItem.jsx @@ -17,6 +17,7 @@ class TeamListItem extends React.Component { var style = { left: { display: 'inline-block', + width: 'calc(100% - 100px)', cursor: 'pointer' } }; diff --git a/src/browser/css/settings.css b/src/browser/css/settings.css index c3c824b0..059293a3 100644 --- a/src/browser/css/settings.css +++ b/src/browser/css/settings.css @@ -1,4 +1,8 @@ +.teamListItem:hover { + background: #eee; +} + .checkbox > label { width: 100%; } From 90623bcf84dddddde954b04ccb4f9771ea7a7ed9 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Fri, 10 Feb 2017 21:17:34 +0900 Subject: [PATCH 07/15] Fix eslint error --- src/browser/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/index.jsx b/src/browser/index.jsx index d6fa3759..ac1e0e24 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -12,7 +12,6 @@ const MainPage = require('./components/MainPage.jsx'); const AppConfig = require('./config/AppConfig.js'); const url = require('url'); -const settings = require('../common/settings'); const badge = require('./js/badge'); remote.getCurrentWindow().removeAllListeners('focus'); From 3447d49cbd47a43487d1a972cc276665769258a7 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Wed, 15 Feb 2017 22:07:10 +0900 Subject: [PATCH 08/15] Implement auto-saving --- src/browser/components/AutoSaveIndicator.jsx | 33 ++++++++ src/browser/components/SettingsPage.jsx | 87 +++++++++++++------- src/browser/css/settings.css | 33 ++++++++ src/package.json | 1 + 4 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 src/browser/components/AutoSaveIndicator.jsx diff --git a/src/browser/components/AutoSaveIndicator.jsx b/src/browser/components/AutoSaveIndicator.jsx new file mode 100644 index 00000000..92b2224b --- /dev/null +++ b/src/browser/components/AutoSaveIndicator.jsx @@ -0,0 +1,33 @@ +const React = require('react'); +const {Alert} = require('react-bootstrap'); + +function AutoSaveIndicator(props) { + const {savingState, errorMessage, ...rest} = props; + return ( + + {(() => { + switch (props.savingState) { + case 'saving': + return 'Saving...'; + case 'saved': + return 'Saved!'; + case 'error': + return props.errorMessage; + default: + return ''; + } + })()} + + ); +} + +AutoSaveIndicator.propTypes = { + savingState: React.PropTypes.string.isRequired, + errorMessage: React.PropTypes.string +}; + +module.exports = AutoSaveIndicator; diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 4974313a..fe11f07d 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -1,13 +1,16 @@ const React = require('react'); 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 {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', @@ -38,6 +41,7 @@ const SettingsPage = React.createClass({ if (initialState.teams.length === 0) { initialState.showAddTeamForm = true; } + initialState.savingState = 'done'; 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) { this.setState({ showAddTeamForm: false, @@ -64,8 +81,10 @@ const SettingsPage = React.createClass({ if (teams.length === 0) { this.setState({showAddTeamForm: true}); } + setImmediate(this.saveConfig); }, - handleSave(index) { + saveConfig() { + this.setSavingState('saving'); var config = { teams: this.state.teams, showTrayIcon: this.state.showTrayIcon, @@ -83,28 +102,38 @@ const SettingsPage = React.createClass({ var autostart = this.state.autostart; appLauncher.isEnabled().then((enabled) => { if (enabled && !autostart) { - appLauncher.disable(); + appLauncher.disable().then(() => { + this.setSavingState('saved'); + }); } 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-config'); - - backToIndex(index); }, handleCancel() { backToIndex(); }, - backToIndexWithSave(index) { - this.handleSave(index); - }, handleChangeDisableWebSecurity() { this.setState({ disablewebsecurity: this.refs.disablewebsecurity.props.checked }); + setImmediate(this.saveConfig); + }, + handleChangeHideMenuBar() { + this.setState({ + hideMenuBar: this.refs.hideMenuBar.props.checked + }); + setImmediate(this.saveConfig); }, handleChangeShowTrayIcon() { var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked; @@ -117,16 +146,20 @@ const SettingsPage = React.createClass({ minimizeToTray: false }); } + + setImmediate(this.saveConfig); }, handleChangeTrayIconTheme() { this.setState({ trayIconTheme: ReactDOM.findDOMNode(this.refs.trayIconTheme).value }); + setImmediate(this.saveConfig); }, handleChangeAutoStart() { this.setState({ autostart: !this.refs.autostart.props.checked }); + setImmediate(this.saveConfig); }, handleChangeMinimizeToTray() { const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked; @@ -134,16 +167,19 @@ const SettingsPage = React.createClass({ this.setState({ minimizeToTray: shouldMinimizeToTray }); + setImmediate(this.saveConfig); }, toggleShowTeamForm() { this.setState({ showAddTeamForm: !this.state.showAddTeamForm }); + setImmediate(this.saveConfig); }, setShowTeamFormVisibility(val) { this.setState({ showAddTeamForm: val }); + setImmediate(this.saveConfig); }, handleFlashWindow() { this.setState({ @@ -151,11 +187,13 @@ const SettingsPage = React.createClass({ flashWindow: this.refs.flashWindow.props.checked ? 0 : 2 } }); + setImmediate(this.saveConfig); }, handleShowUnreadBadge() { this.setState({ showUnreadBadge: !this.refs.showUnreadBadge.props.checked }); + setImmediate(this.saveConfig); }, updateTeam(index, newData) { @@ -164,6 +202,7 @@ const SettingsPage = React.createClass({ this.setState({ teams }); + setImmediate(this.saveConfig); }, addServer(team) { @@ -172,6 +211,7 @@ const SettingsPage = React.createClass({ this.setState({ teams }); + setImmediate(this.saveConfig); }, render() { @@ -186,7 +226,7 @@ const SettingsPage = React.createClass({ onTeamsChange={this.handleTeamsChange} updateTeam={this.updateTeam} addServer={this.addServer} - onTeamClick={this.backToIndexWithSave} + onTeamClick={backToIndex} /> @@ -388,7 +428,7 @@ const SettingsPage = React.createClass({ { optionsRow } - -

+ - - { ' ' } - -
- + { this.state.savingState === 'done' ? null : } + +
); } diff --git a/src/browser/css/settings.css b/src/browser/css/settings.css index 059293a3..c954339d 100644 --- a/src/browser/css/settings.css +++ b/src/browser/css/settings.css @@ -3,6 +3,39 @@ 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 { width: 100%; } diff --git a/src/package.json b/src/package.json index 7ce051b6..226fb297 100644 --- a/src/package.json +++ b/src/package.json @@ -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" } } From f2e26ec27663cffe7e92368370f4e2df5017c5a0 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Wed, 15 Feb 2017 22:44:51 +0900 Subject: [PATCH 09/15] Fix tests for auto-saving --- src/browser/components/SettingsPage.jsx | 1 + test/specs/browser/settings_test.js | 23 +++++++---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index fe11f07d..c20f7bb6 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -417,6 +417,7 @@ const SettingsPage = React.createClass({

{'Settings'}

); } diff --git a/src/browser/css/settings.css b/src/browser/css/settings.css index c954339d..e75ee212 100644 --- a/src/browser/css/settings.css +++ b/src/browser/css/settings.css @@ -4,18 +4,16 @@ } .IndicatorContainer { - pointer-events: none; - position: fixed; - top: 100px; - left: 0; - right: 0; + position: absolute; + height: 100%; display: flex; flex-flow: row; - justify-content: center; + justify-content: flex-start; + align-items: center; } -.IndicatorContainer * { - pointer-events: auto; +.AutoSaveIndicator { + margin: 0; } .AutoSaveIndicator-enter { From 949db4ffaea80877aa83bd5069582e527c382107 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Thu, 16 Feb 2017 21:05:28 +0900 Subject: [PATCH 12/15] Fix an issue where the indicator appears after canceling server updates --- src/browser/components/SettingsPage.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index ae6b0809..63cdd6c6 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -173,13 +173,11 @@ const SettingsPage = React.createClass({ this.setState({ showAddTeamForm: !this.state.showAddTeamForm }); - setImmediate(this.saveConfig); }, setShowTeamFormVisibility(val) { this.setState({ showAddTeamForm: val }); - setImmediate(this.saveConfig); }, handleFlashWindow() { this.setState({ From 8acd7b99e7fc82625d26d56bc3ca1d99ac31a3f1 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Thu, 16 Feb 2017 21:55:44 +0900 Subject: [PATCH 13/15] Show "Saving..." for 0.5 seconds and show error if it occurred --- src/browser/components/AutoSaveIndicator.jsx | 2 +- src/browser/components/SettingsPage.jsx | 106 ++++++++++++------- src/common/settings.js | 8 ++ test/specs/browser/settings_test.js | 2 + 4 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/browser/components/AutoSaveIndicator.jsx b/src/browser/components/AutoSaveIndicator.jsx index abcc8010..7888d953 100644 --- a/src/browser/components/AutoSaveIndicator.jsx +++ b/src/browser/components/AutoSaveIndicator.jsx @@ -14,7 +14,7 @@ function AutoSaveIndicator(props) { case 'saving': return 'Saving...'; case 'saved': - return 'Saved!'; + return 'Saved'; case 'error': return errorMessage; default: diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 63cdd6c6..3331865c 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -62,17 +62,32 @@ const SettingsPage = React.createClass({ }, setSavingState(state) { - if (!this.setSavingStateDone) { - this.setSavingStateDone = debounce(() => { - this.setState({savingState: 'done'}); - }, 2000); + 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 === 'saved') { - this.setSavingStateDone(); + if (state === 'saving') { + this.setSavingStateSaved(); } }, + startSaveConfig() { + this.setSavingState('saving'); + }, + handleTeamsChange(teams) { this.setState({ showAddTeamForm: false, @@ -81,10 +96,10 @@ const SettingsPage = React.createClass({ if (teams.length === 0) { this.setState({showAddTeamForm: true}); } - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, - saveConfig() { - this.setSavingState('saving'); + + saveConfig(callback) { var config = { teams: this.state.teams, showTrayIcon: this.state.showTrayIcon, @@ -97,29 +112,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().then(() => { - this.setSavingState('saved'); - }); - } else if (!enabled && autostart) { - appLauncher.enable().then(() => { - this.setSavingState('saved'); - }); - } else { - this.setSavingState('saved'); - } - }); - } else { - this.setSavingState('saved'); - } ipcRenderer.send('update-menu', config); ipcRenderer.send('update-config'); + 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(); }, @@ -127,13 +152,13 @@ const SettingsPage = React.createClass({ this.setState({ disablewebsecurity: this.refs.disablewebsecurity.props.checked }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, handleChangeHideMenuBar() { this.setState({ hideMenuBar: this.refs.hideMenuBar.props.checked }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, handleChangeShowTrayIcon() { var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked; @@ -147,19 +172,19 @@ const SettingsPage = React.createClass({ }); } - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, handleChangeTrayIconTheme() { this.setState({ trayIconTheme: ReactDOM.findDOMNode(this.refs.trayIconTheme).value }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, handleChangeAutoStart() { this.setState({ autostart: !this.refs.autostart.props.checked }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, handleChangeMinimizeToTray() { const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked; @@ -167,7 +192,7 @@ const SettingsPage = React.createClass({ this.setState({ minimizeToTray: shouldMinimizeToTray }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, toggleShowTeamForm() { this.setState({ @@ -185,13 +210,13 @@ const SettingsPage = React.createClass({ flashWindow: this.refs.flashWindow.props.checked ? 0 : 2 } }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, handleShowUnreadBadge() { this.setState({ showUnreadBadge: !this.refs.showUnreadBadge.props.checked }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, updateTeam(index, newData) { @@ -200,7 +225,7 @@ const SettingsPage = React.createClass({ this.setState({ teams }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, addServer(team) { @@ -209,7 +234,7 @@ const SettingsPage = React.createClass({ this.setState({ teams }); - setImmediate(this.saveConfig); + setImmediate(this.startSaveConfig); }, render() { @@ -418,7 +443,10 @@ const SettingsPage = React.createClass({ transitionEnterTimeout={500} transitionLeaveTimeout={1000} > - { this.state.savingState === 'done' ? null : } + { this.state.savingState === 'done' ? null : }
diff --git a/src/common/settings.js b/src/common/settings.js index cde9cc8c..e432c3ed 100644 --- a/src/common/settings.js +++ b/src/common/settings.js @@ -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); diff --git a/test/specs/browser/settings_test.js b/test/specs/browser/settings_test.js index c8ea1804..19953ec6 100644 --- a/test/specs/browser/settings_test.js +++ b/test/specs/browser/settings_test.js @@ -100,6 +100,7 @@ describe('browser/settings.html', function desc() { } return true; }). + pause(600). click('#btnClose'). pause(1000).then(() => { const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); @@ -133,6 +134,7 @@ describe('browser/settings.html', function desc() { } return true; }). + pause(600). click('#btnClose'). pause(1000).then(() => { const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); From 831d758f2f5cc2455a2e02ca754d772ed28b26c4 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Fri, 17 Feb 2017 21:04:23 +0900 Subject: [PATCH 14/15] Remove unnecessary handler --- src/browser/components/SettingsPage.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 3331865c..63261f00 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -154,12 +154,6 @@ const SettingsPage = React.createClass({ }); setImmediate(this.startSaveConfig); }, - handleChangeHideMenuBar() { - this.setState({ - hideMenuBar: this.refs.hideMenuBar.props.checked - }); - setImmediate(this.startSaveConfig); - }, handleChangeShowTrayIcon() { var shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked; this.setState({ From 0e32a2299c90ca5b594ea12f6629436cc8b2dcb0 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Fri, 17 Feb 2017 22:24:55 +0900 Subject: [PATCH 15/15] Make animation cancelable for auto-save indicator --- src/browser/components/AutoSaveIndicator.jsx | 34 ++++++++++++-------- src/browser/components/SettingsPage.jsx | 15 +++------ src/browser/css/settings.css | 17 ++-------- 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/browser/components/AutoSaveIndicator.jsx b/src/browser/components/AutoSaveIndicator.jsx index 7888d953..6685502e 100644 --- a/src/browser/components/AutoSaveIndicator.jsx +++ b/src/browser/components/AutoSaveIndicator.jsx @@ -1,26 +1,34 @@ 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 ( - {(() => { - switch (savingState) { - case 'saving': - return 'Saving...'; - case 'saved': - return 'Saved'; - case 'error': - return errorMessage; - default: - return ''; - } - })()} + {message} ); } diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 63261f00..02537904 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -1,6 +1,5 @@ const React = require('react'); 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 {ipcRenderer, remote} = require('electron'); @@ -432,16 +431,10 @@ const SettingsPage = React.createClass({ style={settingsPage.navbar} >
- - { this.state.savingState === 'done' ? null : } - +

{'Settings'}

diff --git a/src/browser/css/settings.css b/src/browser/css/settings.css index e75ee212..e64b4d54 100644 --- a/src/browser/css/settings.css +++ b/src/browser/css/settings.css @@ -16,21 +16,8 @@ margin: 0; } -.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; +.AutoSaveIndicator.AutoSaveIndicator-Leave { + opacity: 0; transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1); }