diff --git a/.eslintrc.json b/.eslintrc.json index c929e87b..a1d707ab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,6 @@ "react/jsx-indent": [2, 2], "react/jsx-indent-props": [2, 2], "react/no-set-state": 1, - "react/prefer-es6-class": 1, "react/require-optimization": 0 } } diff --git a/NOTICE.txt b/NOTICE.txt index 98d476f5..edfba9f6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -428,7 +428,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## react This product contains a modified portion of 'react', a declarative, efficient, and flexible JavaScript library for building user interfaces, -'create-react-class', a drop-in replacement for React.createClass, and 'react-dom', the entry point of the DOM-related +and 'react-dom', the entry point of the DOM-related rendering paths, by Facebook, Inc. * HOMEPAGE: diff --git a/src/browser/components/MainPage.jsx b/src/browser/components/MainPage.jsx index e4b8bed9..3899dcb2 100644 --- a/src/browser/components/MainPage.jsx +++ b/src/browser/components/MainPage.jsx @@ -1,11 +1,13 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. + +/* eslint-disable react/no-set-state */ + import url from 'url'; import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {CSSTransition, TransitionGroup} from 'react-transition-group'; import {Grid, Row} from 'react-bootstrap'; @@ -21,21 +23,10 @@ import PermissionRequestDialog from './PermissionRequestDialog.jsx'; import Finder from './Finder.jsx'; import NewTeamModal from './NewTeamModal.jsx'; -const MainPage = createReactClass({ - propTypes: { - onUnreadCountChange: PropTypes.func.isRequired, - teams: PropTypes.array.isRequired, - onTeamConfigChange: PropTypes.func.isRequired, - initialIndex: PropTypes.number.isRequired, - useSpellChecker: PropTypes.bool.isRequired, - onSelectSpellCheckerLocale: PropTypes.func.isRequired, - deeplinkingUrl: PropTypes.string, - showAddServerButton: PropTypes.bool.isRequired, - requestingPermission: TabBar.propTypes.requestingPermission, - onClickPermissionDialog: PropTypes.func, - }, +export default class MainPage extends React.Component { + constructor(props) { + super(props); - getInitialState() { let key = this.props.initialIndex; if (this.props.deeplinkingUrl !== null) { for (let i = 0; i < this.props.teams.length; i++) { @@ -46,7 +37,7 @@ const MainPage = createReactClass({ } } - return { + this.state = { key, unreadCounts: new Array(this.props.teams.length), mentionCounts: new Array(this.props.teams.length), @@ -55,7 +46,22 @@ const MainPage = createReactClass({ loginQueue: [], targetURL: '', }; - }, + + this.activateFinder = this.activateFinder.bind(this); + this.addServer = this.addServer.bind(this); + this.closeFinder = this.closeFinder.bind(this); + this.focusOnWebView = this.focusOnWebView.bind(this); + this.handleLogin = this.handleLogin.bind(this); + this.handleLoginCancel = this.handleLoginCancel.bind(this); + this.handleOnTeamFocused = this.handleOnTeamFocused.bind(this); + this.handleSelect = this.handleSelect.bind(this); + this.handleTargetURLChange = this.handleTargetURLChange.bind(this); + this.handleUnreadCountChange = this.handleUnreadCountChange.bind(this); + this.handleUnreadCountTotalChange = this.handleUnreadCountTotalChange.bind(this); + this.inputBlur = this.inputBlur.bind(this); + this.markReadAtActive = this.markReadAtActive.bind(this); + } + componentDidMount() { const self = this; ipcRenderer.on('login-request', (event, request, authInfo) => { @@ -146,12 +152,14 @@ const MainPage = createReactClass({ ipcRenderer.on('toggle-find', () => { this.activateFinder(true); }); - }, + } + componentDidUpdate(prevProps, prevState) { if (prevState.key !== this.state.key) { // i.e. When tab has been changed this.refs[`mattermostView${this.state.key}`].focusOnWebView(); } - }, + } + handleSelect(key) { const newKey = (this.props.teams.length + key) % this.props.teams.length; this.setState({ @@ -163,7 +171,7 @@ const MainPage = createReactClass({ title: webview.getTitle(), }); this.handleOnTeamFocused(newKey); - }, + } handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned) { const unreadCounts = this.state.unreadCounts; @@ -187,7 +195,8 @@ const MainPage = createReactClass({ mentionAtActiveCounts, }); this.handleUnreadCountTotalChange(); - }, + } + markReadAtActive(index) { const unreadAtActive = this.state.unreadAtActive; const mentionAtActiveCounts = this.state.mentionAtActiveCounts; @@ -198,7 +207,8 @@ const MainPage = createReactClass({ mentionAtActiveCounts, }); this.handleUnreadCountTotalChange(); - }, + } + handleUnreadCountTotalChange() { if (this.props.onUnreadCountChange) { let allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => { @@ -217,23 +227,26 @@ const MainPage = createReactClass({ }); this.props.onUnreadCountChange(allUnreadCount, allMentionCount); } - }, + } + handleOnTeamFocused(index) { // Turn off the flag to indicate whether unread message of active channel contains at current tab. this.markReadAtActive(index); - }, + } handleLogin(request, username, password) { ipcRenderer.send('login-credentials', request, username, password); const loginQueue = this.state.loginQueue; loginQueue.shift(); this.setState({loginQueue}); - }, + } + handleLoginCancel() { const loginQueue = this.state.loginQueue; loginQueue.shift(); this.setState({loginQueue}); - }, + } + handleTargetURLChange(targetURL) { clearTimeout(this.targetURLDisappearTimeout); if (targetURL === '') { @@ -244,37 +257,38 @@ const MainPage = createReactClass({ } else { this.setState({targetURL}); } - }, + } + addServer() { this.setState({ showNewTeamModal: true, }); - }, + } focusOnWebView(e) { if (e.target.className !== 'finder-input') { this.refs[`mattermostView${this.state.key}`].focusOnWebView(); } - }, + } activateFinder() { this.setState({ finderVisible: true, focusFinder: true, }); - }, + } closeFinder() { this.setState({ finderVisible: false, }); - }, + } inputBlur() { this.setState({ focusFinder: false, }); - }, + } render() { const self = this; @@ -419,7 +433,18 @@ const MainPage = createReactClass({ ); - }, -}); + } +} -export default MainPage; +MainPage.propTypes = { + onUnreadCountChange: PropTypes.func.isRequired, + teams: PropTypes.array.isRequired, + onTeamConfigChange: PropTypes.func.isRequired, + initialIndex: PropTypes.number.isRequired, + useSpellChecker: PropTypes.bool.isRequired, + onSelectSpellCheckerLocale: PropTypes.func.isRequired, + deeplinkingUrl: PropTypes.string, + showAddServerButton: PropTypes.bool.isRequired, + requestingPermission: TabBar.propTypes.requestingPermission, + onClickPermissionDialog: PropTypes.func, +}; diff --git a/src/browser/components/MattermostView.jsx b/src/browser/components/MattermostView.jsx index ad4d5b2a..03c0e27c 100644 --- a/src/browser/components/MattermostView.jsx +++ b/src/browser/components/MattermostView.jsx @@ -9,7 +9,6 @@ import url from 'url'; import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {findDOMNode} from 'react-dom'; import {ipcRenderer, remote, shell} from 'electron'; @@ -43,33 +42,34 @@ function isNetworkDrive(fileURL) { return false; } -const MattermostView = createReactClass({ - propTypes: { - name: PropTypes.string, - id: PropTypes.string, - onTargetURLChange: PropTypes.func, - onUnreadCountChange: PropTypes.func, - src: PropTypes.string, - active: PropTypes.bool, - withTab: PropTypes.bool, - useSpellChecker: PropTypes.bool, - onSelectSpellCheckerLocale: PropTypes.func, - }, +export default class MattermostView extends React.Component { + constructor(props) { + super(props); - getInitialState() { - return { + this.state = { errorInfo: null, isContextMenuAdded: false, reloadTimeoutID: null, isLoaded: false, }; - }, + + this.handleUnreadCountChange = this.handleUnreadCountChange.bind(this); + this.reload = this.reload.bind(this); + this.clearCacheAndReload = this.clearCacheAndReload.bind(this); + this.focusOnWebView = this.focusOnWebView.bind(this); + this.canGoBack = this.canGoBack.bind(this); + this.canGoForward = this.canGoForward.bind(this); + this.goBack = this.goBack.bind(this); + this.goForward = this.goForward.bind(this); + this.getSrc = this.getSrc.bind(this); + this.handleDeepLink = this.handleDeepLink.bind(this); + } handleUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned) { if (this.props.onUnreadCountChange) { this.props.onUnreadCountChange(unreadCount, mentionCount, isUnread, isMentioned); } - }, + } componentDidMount() { const self = this; @@ -209,7 +209,7 @@ const MattermostView = createReactClass({ break; } }); - }, + } reload() { clearTimeout(this.state.reloadTimeoutID); @@ -220,7 +220,7 @@ const MattermostView = createReactClass({ }); const webview = findDOMNode(this.refs.webview); webview.reload(); - }, + } clearCacheAndReload() { this.setState({ @@ -230,7 +230,7 @@ const MattermostView = createReactClass({ webContents.session.clearCache(() => { webContents.reload(); }); - }, + } focusOnWebView() { const webview = findDOMNode(this.refs.webview); @@ -239,32 +239,32 @@ const MattermostView = createReactClass({ webview.focus(); webContents.focus(); } - }, + } canGoBack() { const webview = findDOMNode(this.refs.webview); return webview.getWebContents().canGoBack(); - }, + } canGoForward() { const webview = findDOMNode(this.refs.webview); return webview.getWebContents().canGoForward(); - }, + } goBack() { const webview = findDOMNode(this.refs.webview); webview.getWebContents().goBack(); - }, + } goForward() { const webview = findDOMNode(this.refs.webview); webview.getWebContents().goForward(); - }, + } getSrc() { const webview = findDOMNode(this.refs.webview); return webview.src; - }, + } handleDeepLink(relativeUrl) { const webview = findDOMNode(this.refs.webview); @@ -274,7 +274,7 @@ const MattermostView = createReactClass({ webview.executeJavaScript( 'dispatchEvent(new PopStateEvent("popstate", null));' ); - }, + } render() { const errorView = this.state.errorInfo ? ( @@ -318,7 +318,17 @@ const MattermostView = createReactClass({ /> { loadingImage } ); - }, -}); + } +} -export default MattermostView; +MattermostView.propTypes = { + name: PropTypes.string, + id: PropTypes.string, + onTargetURLChange: PropTypes.func, + onUnreadCountChange: PropTypes.func, + src: PropTypes.string, + active: PropTypes.bool, + withTab: PropTypes.bool, + useSpellChecker: PropTypes.bool, + onSelectSpellCheckerLocale: PropTypes.func, +}; diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 21f3b64c..ba2c75eb 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -1,9 +1,11 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. + +/* eslint-disable react/no-set-state */ + import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import ReactDOM from 'react-dom'; import {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} from 'react-bootstrap'; @@ -31,20 +33,16 @@ function backToIndex(index) { const CONFIG_TYPE_SERVERS = 'servers'; const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; -const SettingsPage = createReactClass({ - propTypes: { - configFile: PropTypes.string, - enableServerManagement: PropTypes.bool, - }, +export default class SettingsPage extends React.Component { + constructor(props) { + super(props); - getInitialState() { let initialState; try { initialState = settings.readFileSync(this.props.configFile); } catch (e) { initialState = settings.loadDefault(); } - initialState.showAddTeamForm = false; initialState.trayWasVisible = remote.getCurrentWindow().trayWasVisible; if (initialState.teams.length === 0) { @@ -54,9 +52,30 @@ const SettingsPage = createReactClass({ appOptions: AutoSaveIndicator.SAVING_STATE_DONE, servers: AutoSaveIndicator.SAVING_STATE_DONE, }; + this.state = initialState; + + this.startSaveConfig = this.startSaveConfig.bind(this); + this.didSaveConfig = this.didSaveConfig.bind(this); + this.handleTeamsChange = this.handleTeamsChange.bind(this); + this.saveConfig = this.saveConfig.bind(this); + this.saveAutoStart = this.saveAutoStart.bind(this); + this.handleCancel = this.handleCancel.bind(this); + this.handleChangeShowTrayIcon = this.handleChangeShowTrayIcon.bind(this); + this.handleChangeTrayIconTheme = this.handleChangeTrayIconTheme.bind(this); + this.handleChangeAutoStart = this.handleChangeAutoStart.bind(this); + this.handleChangeMinimizeToTray = this.handleChangeMinimizeToTray.bind(this); + this.toggleShowTeamForm = this.toggleShowTeamForm.bind(this); + this.setShowTeamFormVisibility = this.setShowTeamFormVisibility.bind(this); + this.handleFlashWindow = this.handleFlashWindow.bind(this); + this.handleBounceIcon = this.handleBounceIcon.bind(this); + this.handleBounceIconType = this.handleBounceIconType.bind(this); + this.handleShowUnreadBadge = this.handleShowUnreadBadge.bind(this); + this.handleChangeUseSpellChecker = this.handleChangeUseSpellChecker.bind(this); + this.handleChangeEnableHardwareAcceleration = this.handleChangeEnableHardwareAcceleration.bind(this); + this.updateTeam = this.updateTeam.bind(this); + this.addServer = this.addServer.bind(this); + } - return initialState; - }, componentDidMount() { if (process.platform === 'win32' || process.platform === 'linux') { const self = this; @@ -74,7 +93,7 @@ const SettingsPage = createReactClass({ ipcRenderer.on('switch-tab', (event, key) => { backToIndex(key); }); - }, + } startSaveConfig(configType) { if (!this.startSaveConfigImpl) { @@ -97,7 +116,7 @@ const SettingsPage = createReactClass({ savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVING; this.setState({savingState}); this.startSaveConfigImpl[configType](); - }, + } didSaveConfig(configType) { if (!this.didSaveConfigImpl) { @@ -111,7 +130,7 @@ const SettingsPage = createReactClass({ }, 2000); } this.didSaveConfigImpl[configType](); - }, + } handleTeamsChange(teams) { this.setState({ @@ -122,7 +141,7 @@ const SettingsPage = createReactClass({ this.setState({showAddTeamForm: true}); } setImmediate(this.startSaveConfig, CONFIG_TYPE_SERVERS); - }, + } saveConfig(callback) { const config = { @@ -156,7 +175,7 @@ const SettingsPage = createReactClass({ callback(); } }); - }, + } saveAutoStart(autostart, callback) { appLauncher.isEnabled().then((enabled) => { @@ -172,11 +191,12 @@ const SettingsPage = createReactClass({ callback(); } }).catch(callback); - }, + } handleCancel() { backToIndex(); - }, + } + handleChangeShowTrayIcon() { const shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked; this.setState({ @@ -190,19 +210,22 @@ const SettingsPage = createReactClass({ } setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + handleChangeTrayIconTheme() { this.setState({ trayIconTheme: ReactDOM.findDOMNode(this.refs.trayIconTheme).value, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + handleChangeAutoStart() { this.setState({ autostart: !this.refs.autostart.props.checked, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + handleChangeMinimizeToTray() { const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked; @@ -210,18 +233,21 @@ const SettingsPage = createReactClass({ minimizeToTray: shouldMinimizeToTray, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + toggleShowTeamForm() { this.setState({ showAddTeamForm: !this.state.showAddTeamForm, }); document.activeElement.blur(); - }, + } + setShowTeamFormVisibility(val) { this.setState({ showAddTeamForm: val, }); - }, + } + handleFlashWindow() { this.setState({ notifications: { @@ -230,7 +256,8 @@ const SettingsPage = createReactClass({ }, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + handleBounceIcon() { this.setState({ notifications: { @@ -239,7 +266,8 @@ const SettingsPage = createReactClass({ }, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + handleBounceIconType(event) { this.setState({ notifications: { @@ -248,27 +276,28 @@ const SettingsPage = createReactClass({ }, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } + handleShowUnreadBadge() { this.setState({ showUnreadBadge: !this.refs.showUnreadBadge.props.checked, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } handleChangeUseSpellChecker() { this.setState({ useSpellChecker: !this.refs.useSpellChecker.props.checked, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } handleChangeEnableHardwareAcceleration() { this.setState({ enableHardwareAcceleration: !this.refs.enableHardwareAcceleration.props.checked, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_APP_OPTIONS); - }, + } updateTeam(index, newData) { const teams = this.state.teams; @@ -277,7 +306,7 @@ const SettingsPage = createReactClass({ teams, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_SERVERS); - }, + } addServer(team) { const teams = this.state.teams; @@ -286,7 +315,7 @@ const SettingsPage = createReactClass({ teams, }); setImmediate(this.startSaveConfig, CONFIG_TYPE_SERVERS); - }, + } render() { const settingsPage = { @@ -625,7 +654,10 @@ const SettingsPage = createReactClass({ ); - }, -}); + } +} -export default SettingsPage; +SettingsPage.propTypes = { + configFile: PropTypes.string, + enableServerManagement: PropTypes.bool, +}; diff --git a/src/browser/components/TeamList.jsx b/src/browser/components/TeamList.jsx index cc7222a6..53daa34b 100644 --- a/src/browser/components/TeamList.jsx +++ b/src/browser/components/TeamList.jsx @@ -3,27 +3,17 @@ // See LICENSE.txt for license information. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {ListGroup} from 'react-bootstrap'; import TeamListItem from './TeamListItem.jsx'; import NewTeamModal from './NewTeamModal.jsx'; import RemoveServerModal from './RemoveServerModal.jsx'; -const TeamList = createReactClass({ - propTypes: { - onTeamsChange: PropTypes.func, - showAddTeamForm: PropTypes.bool, - teams: PropTypes.array, - addServer: PropTypes.func, - updateTeam: PropTypes.func, - toggleAddTeamForm: PropTypes.func, - setAddTeamFormVisibility: PropTypes.func, - onTeamClick: PropTypes.func, - }, +export default class TeamList extends React.Component { + constructor(props) { + super(props); - getInitialState() { - return { + this.state = { showEditTeamForm: false, indexToRemoveServer: -1, team: { @@ -32,13 +22,21 @@ const TeamList = createReactClass({ index: false, }, }; - }, + + this.handleTeamRemove = this.handleTeamRemove.bind(this); + this.handleTeamAdd = this.handleTeamAdd.bind(this); + this.handleTeamEditing = this.handleTeamEditing.bind(this); + this.openServerRemoveModal = this.openServerRemoveModal.bind(this); + this.closeServerRemoveModal = this.closeServerRemoveModal.bind(this); + } + handleTeamRemove(index) { console.log(index); const teams = this.props.teams; teams.splice(index, 1); this.props.onTeamsChange(teams); - }, + } + handleTeamAdd(team) { const teams = this.props.teams; @@ -60,7 +58,8 @@ const TeamList = createReactClass({ }); this.props.onTeamsChange(teams); - }, + } + handleTeamEditing(teamName, teamUrl, teamIndex) { this.setState({ showEditTeamForm: true, @@ -70,15 +69,15 @@ const TeamList = createReactClass({ index: teamIndex, }, }); - }, + } openServerRemoveModal(indexForServer) { this.setState({indexToRemoveServer: indexForServer}); - }, + } closeServerRemoveModal() { this.setState({indexToRemoveServer: -1}); - }, + } render() { const self = this; @@ -171,7 +170,16 @@ const TeamList = createReactClass({ { removeServerModal} ); - }, -}); + } +} -export default TeamList; +TeamList.propTypes = { + onTeamsChange: PropTypes.func, + showAddTeamForm: PropTypes.bool, + teams: PropTypes.array, + addServer: PropTypes.func, + updateTeam: PropTypes.func, + toggleAddTeamForm: PropTypes.func, + setAddTeamFormVisibility: PropTypes.func, + onTeamClick: PropTypes.func, +}; diff --git a/src/package.json b/src/package.json index 351fcf12..be1a7fc4 100644 --- a/src/package.json +++ b/src/package.json @@ -11,7 +11,6 @@ "dependencies": { "auto-launch": "^5.0.5", "bootstrap": "^3.3.7", - "create-react-class": "^15.6.3", "electron-context-menu": "0.9.0", "electron-devtools-installer": "^2.2.4", "electron-is-dev": "^0.3.0", diff --git a/src/yarn.lock b/src/yarn.lock index 9dafddc6..a8d4fb7b 100644 --- a/src/yarn.lock +++ b/src/yarn.lock @@ -107,14 +107,6 @@ core-js@^2.4.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" -create-react-class@^15.6.3: - version "15.6.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-unzip@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/cross-unzip/-/cross-unzip-0.0.2.tgz#5183bc47a09559befcf98cc4657964999359372f"