React bootstrap upgrade to v1, migrate to react-beautiful-dnd (#1639)

* Upgrade packages, fix errors, still WIP

* WIP

* Bootstrap v4 upgrade

* Switch to react-beautiful-dnd

* Fixed some issues

* Added to generate signed Mac build for UX

* PR feedback

* Missed one

* PR feedback
This commit is contained in:
Devin Binnie
2021-07-05 21:19:11 -04:00
committed by GitHub
parent 86a3f13a6b
commit 4130c2c37d
27 changed files with 869 additions and 554 deletions

View File

@@ -446,6 +446,7 @@ workflows:
branches: branches:
only: only:
- /^release-\d+(\.\d+){1,2}(-rc.*)?/ - /^release-\d+(\.\d+){1,2}(-rc.*)?/
- react_bootstrap_upgrade
- store_artifacts: - store_artifacts:
# for master/PR builds # for master/PR builds
@@ -453,6 +454,7 @@ workflows:
- build-linux - build-linux
- build-win-no-installer - build-win-no-installer
- build-mac-no-dmg - build-mac-no-dmg
- mac_installer
filters: filters:
branches: branches:
ignore: ignore:

714
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,7 @@
"@types/electron-devtools-installer": "^2.2.0", "@types/electron-devtools-installer": "^2.2.0",
"@types/hapi__joi": "^17.1.6", "@types/hapi__joi": "^17.1.6",
"@types/react": "^17.0.11", "@types/react": "^17.0.11",
"@types/react-bootstrap": "^0.32.25", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "^17.0.8", "@types/react-dom": "^17.0.8",
"@types/underscore": "^1.11.2", "@types/underscore": "^1.11.2",
"@types/valid-url": "^1.0.3", "@types/valid-url": "^1.0.3",
@@ -121,7 +121,7 @@
"dependencies": { "dependencies": {
"@hapi/joi": "^16.1.8", "@hapi/joi": "^16.1.8",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"bootstrap": "^3.3.7", "bootstrap": "^4.6.0",
"brace-expansion": "^2.0.0", "brace-expansion": "^2.0.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"electron-context-menu": "^2.5.0", "electron-context-menu": "^2.5.0",
@@ -132,9 +132,9 @@
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.14.0", "react": "^16.14.0",
"react-bootstrap": "~0.32.4", "react-beautiful-dnd": "^13.1.0",
"react-bootstrap": "^1.6.1",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"react-smooth-dnd": "github:mattermost/react-smooth-dnd#af6b471295007274560a375799622c1cd52d678a",
"react-transition-group": "^2.5.0", "react-transition-group": "^2.5.0",
"semver": "^5.5.0", "semver": "^5.5.0",
"underscore": "^1.9.1", "underscore": "^1.9.1",

View File

@@ -43,7 +43,7 @@ export default function AutoSaveIndicator(props: Props) {
<Alert <Alert
className={className} className={className}
{...rest} {...rest}
bsStyle={savingState === 'error' ? 'danger' : 'info'} variant={savingState === 'error' ? 'danger' : 'info'}
> >
{message} {message}
</Alert> </Alert>

View File

@@ -14,15 +14,15 @@ storiesOf('Button', module).
<Button onClick={action('clicked default')}>{'Default'}</Button> <Button onClick={action('clicked default')}>{'Default'}</Button>
<Button <Button
onClick={action('clicked primary')} onClick={action('clicked primary')}
bsStyle='primary' variant='primary'
>{'Primary'}</Button> >{'Primary'}</Button>
<Button <Button
onClick={action('clicked danger')} onClick={action('clicked danger')}
bsStyle='danger' variant='danger'
>{'Danger'}</Button> >{'Danger'}</Button>
<Button <Button
onClick={action('clicked link')} onClick={action('clicked link')}
bsStyle='link' variant='link'
>{'Link'}</Button> >{'Link'}</Button>
</ButtonToolbar> </ButtonToolbar>
)); ));

View File

@@ -11,8 +11,8 @@ type Props = {
acceptLabel: string; acceptLabel: string;
cancelLabel: string; cancelLabel: string;
onHide: () => void; onHide: () => void;
onAccept: React.MouseEventHandler<Button>; onAccept: React.MouseEventHandler<HTMLButtonElement>;
onCancel: React.MouseEventHandler<Button>; onCancel: React.MouseEventHandler<HTMLButtonElement>;
}; };
export default function DestructiveConfirmationModal(props: Props) { export default function DestructiveConfirmationModal(props: Props) {
@@ -36,11 +36,11 @@ export default function DestructiveConfirmationModal(props: Props) {
{body} {body}
<Modal.Footer> <Modal.Footer>
<Button <Button
bsStyle='link' variant='link'
onClick={onCancel} onClick={onCancel}
>{cancelLabel}</Button> >{cancelLabel}</Button>
<Button <Button
bsStyle='danger' variant='danger'
onClick={onAccept} onClick={onAccept}
>{acceptLabel}</Button> >{acceptLabel}</Button>
</Modal.Footer> </Modal.Footer>

View File

@@ -5,7 +5,7 @@
// ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h // ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h
import React from 'react'; import React from 'react';
import {Grid, Row, Col} from 'react-bootstrap'; import {Container, Row, Col} from 'react-bootstrap';
type Props = { type Props = {
errorInfo?: string; errorInfo?: string;
@@ -22,9 +22,8 @@ export default function ErrorView(props: Props) {
} }
return ( return (
<Grid <Container
id={props.id} id={props.id}
bsClass={classNames.join(' ')}
> >
<div className='ErrorView-table'> <div className='ErrorView-table'>
<div className='ErrorView-cell'> <div className='ErrorView-cell'>
@@ -82,6 +81,6 @@ export default function ErrorView(props: Props) {
</Row> </Row>
</div> </div>
</div> </div>
</Grid> </Container>
); );
} }

View File

@@ -34,8 +34,8 @@ export default class ExtraBar extends React.PureComponent<Props> {
onClick={this.handleBack} onClick={this.handleBack}
> >
<Button <Button
bsStyle={'link'} variant={'link'}
bsSize={'xsmall'} size={'sm'}
> >
<span className={'backIcon fa fa-1x fa-angle-left'}/> <span className={'backIcon fa fa-1x fa-angle-left'}/>
<span className={'backLabel'}> <span className={'backLabel'}>

View File

@@ -3,10 +3,10 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {Fragment} from 'react'; import React, {Fragment} from 'react';
import {Grid, Row} from 'react-bootstrap'; import {Container, Row} from 'react-bootstrap';
import {DropResult} from 'react-beautiful-dnd';
import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'; import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon';
import {IpcRendererEvent} from 'electron/renderer'; import {IpcRendererEvent} from 'electron/renderer';
import {DropResult} from 'react-smooth-dnd';
import {Team} from 'types/config'; import {Team} from 'types/config';
@@ -60,7 +60,7 @@ enum Status {
type Props = { type Props = {
teams: Team[]; teams: Team[];
showAddServerButton: boolean; showAddServerButton: boolean;
moveTabs: (originalOrder: number, newOrder: number) => Promise<number | undefined>; moveTabs: (originalOrder: number, newOrder: number) => number | undefined;
openMenu: () => void; openMenu: () => void;
darkMode: boolean; darkMode: boolean;
appName: string; appName: string;
@@ -256,19 +256,18 @@ export default class MainPage extends React.PureComponent<Props, State> {
} }
handleDragAndDrop = async (dropResult: DropResult) => { handleDragAndDrop = async (dropResult: DropResult) => {
const {removedIndex, addedIndex} = dropResult; const removedIndex = dropResult.source.index;
if (removedIndex === null || addedIndex === null) { const addedIndex = dropResult.destination?.index;
if (addedIndex === undefined || removedIndex === addedIndex) {
return; return;
} }
if (removedIndex !== addedIndex) { const teamIndex = this.props.moveTabs(removedIndex, addedIndex < this.props.teams.length ? addedIndex : this.props.teams.length - 1);
const teamIndex = await this.props.moveTabs(removedIndex, addedIndex < this.props.teams.length ? addedIndex : this.props.teams.length - 1);
if (!teamIndex) { if (!teamIndex) {
return; return;
} }
const name = this.props.teams[teamIndex].name; const name = this.props.teams[teamIndex].name;
this.handleSelect(name, teamIndex); this.handleSelect(name, teamIndex);
} }
}
handleClose = (e: React.MouseEvent<HTMLDivElement>) => { handleClose = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back. e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back.
@@ -471,10 +470,10 @@ export default class MainPage extends React.PureComponent<Props, State> {
className='MainPage' className='MainPage'
onClick={this.focusOnWebView} onClick={this.focusOnWebView}
> >
<Grid fluid={true}> <Container fluid={true}>
{topRow} {topRow}
{viewsRow} {viewsRow}
</Grid> </Container>
</div> </div>
); );
} }

View File

@@ -3,7 +3,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock} from 'react-bootstrap'; import {Modal, Button, FormGroup, FormControl, FormLabel, FormText} from 'react-bootstrap';
import {TeamWithIndex} from 'types/config'; import {TeamWithIndex} from 'types/config';
@@ -69,7 +69,7 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
return this.getTeamNameValidationError() === null ? null : 'error'; return this.getTeamNameValidationError() === null ? null : 'error';
} }
handleTeamNameChange = (e: React.ChangeEvent<FormControl & HTMLInputElement>) => { handleTeamNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
teamName: e.target.value, teamName: e.target.value,
}); });
@@ -95,7 +95,7 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
return this.getTeamUrlValidationError() === null ? null : 'error'; return this.getTeamUrlValidationError() === null ? null : 'error';
} }
handleTeamUrlChange = (e: React.ChangeEvent<FormControl & HTMLInputElement>) => { handleTeamUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
teamUrl: e.target.value, teamUrl: e.target.value,
}); });
@@ -165,7 +165,7 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
onEntered={() => this.teamNameInputRef?.focus()} onEntered={() => this.teamNameInputRef?.focus()}
onHide={this.props.onClose} onHide={this.props.onClose}
restoreFocus={this.props.restoreFocus} restoreFocus={this.props.restoreFocus}
onKeyDown={(e) => { onKeyDown={(e: React.KeyboardEvent) => {
switch (e.key) { switch (e.key) {
case 'Enter': case 'Enter':
this.save(); this.save();
@@ -186,47 +186,46 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
<Modal.Body> <Modal.Body>
<form> <form>
<FormGroup <FormGroup>
validationState={this.getTeamNameValidationState()} <FormLabel>{'Server Display Name'}</FormLabel>
>
<ControlLabel>{'Server Display Name'}</ControlLabel>
<FormControl <FormControl
id='teamNameInput' id='teamNameInput'
type='text' type='text'
value={this.state.teamName} value={this.state.teamName}
placeholder='Server Name' placeholder='Server Name'
onChange={this.handleTeamNameChange} onChange={this.handleTeamNameChange}
inputRef={(ref) => { ref={(ref: HTMLInputElement) => {
this.teamNameInputRef = ref; this.teamNameInputRef = ref;
if (this.props.setInputRef) { if (this.props.setInputRef) {
this.props.setInputRef(ref); this.props.setInputRef(ref);
} }
}} }}
onClick={(e) => { onClick={(e: React.MouseEvent<HTMLInputElement>) => {
e.stopPropagation(); e.stopPropagation();
}} }}
autoFocus={true} autoFocus={true}
isInvalid={Boolean(this.getTeamNameValidationState())}
/> />
<FormControl.Feedback/> <FormControl.Feedback/>
<HelpBlock>{'The name of the server displayed on your desktop app tab bar.'}</HelpBlock> <FormText>{'The name of the server displayed on your desktop app tab bar.'}</FormText>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
className='NewTeamModal-noBottomSpace' className='NewTeamModal-noBottomSpace'
validationState={this.getTeamUrlValidationState()}
> >
<ControlLabel>{'Server URL'}</ControlLabel> <FormLabel>{'Server URL'}</FormLabel>
<FormControl <FormControl
id='teamUrlInput' id='teamUrlInput'
type='text' type='text'
value={this.state.teamUrl} value={this.state.teamUrl}
placeholder='https://example.com' placeholder='https://example.com'
onChange={this.handleTeamUrlChange} onChange={this.handleTeamUrlChange}
onClick={(e) => { onClick={(e: React.MouseEvent<HTMLInputElement>) => {
e.stopPropagation(); e.stopPropagation();
}} }}
isInvalid={Boolean(this.getTeamUrlValidationState())}
/> />
<FormControl.Feedback/> <FormControl.Feedback/>
<HelpBlock className='NewTeamModal-noBottomSpace'>{'The URL of your Mattermost server. Must start with http:// or https://.'}</HelpBlock> <FormText className='NewTeamModal-noBottomSpace'>{'The URL of your Mattermost server. Must start with http:// or https://.'}</FormText>
</FormGroup> </FormGroup>
</form> </form>
</Modal.Body> </Modal.Body>
@@ -241,12 +240,13 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
<Button <Button
id='cancelNewServerModal' id='cancelNewServerModal'
onClick={this.props.onClose} onClick={this.props.onClose}
variant='link'
>{'Cancel'}</Button> >{'Cancel'}</Button>
<Button <Button
id='saveNewServerModal' id='saveNewServerModal'
onClick={this.save} onClick={this.save}
disabled={!this.validateForm()} disabled={!this.validateForm()}
bsStyle='primary' variant='primary'
>{this.getSaveButtonLabel()}</Button> >{this.getSaveButtonLabel()}</Button>
</Modal.Footer> </Modal.Footer>

View File

@@ -3,7 +3,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {Button, Modal} from 'react-bootstrap'; import {Modal} from 'react-bootstrap';
import DestructiveConfirmationModal from './DestructiveConfirmModal'; import DestructiveConfirmationModal from './DestructiveConfirmModal';
@@ -11,8 +11,8 @@ type Props = {
show: boolean; show: boolean;
serverName: string; serverName: string;
onHide: () => void; onHide: () => void;
onAccept: React.MouseEventHandler<Button>; onAccept: React.MouseEventHandler<HTMLButtonElement>;
onCancel: React.MouseEventHandler<Button>; onCancel: React.MouseEventHandler<HTMLButtonElement>;
} }
export default function RemoveServerModal(props: Props) { export default function RemoveServerModal(props: Props) {

View File

@@ -7,7 +7,7 @@
import 'renderer/css/settings.css'; import 'renderer/css/settings.css';
import React from 'react'; import React from 'react';
import {Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row, Button} from 'react-bootstrap'; import {FormCheck, Col, FormGroup, FormText, Container, Row, Button} from 'react-bootstrap';
import {debounce} from 'underscore'; import {debounce} from 'underscore';
@@ -52,16 +52,16 @@ function backToIndex(serverName: string) {
} }
export default class SettingsPage extends React.PureComponent<Record<string, never>, State> { export default class SettingsPage extends React.PureComponent<Record<string, never>, State> {
trayIconThemeRef: React.RefObject<FormGroup>; trayIconThemeRef: React.RefObject<HTMLDivElement>;
downloadLocationRef: React.RefObject<HTMLInputElement>; downloadLocationRef: React.RefObject<HTMLInputElement>;
showTrayIconRef: React.RefObject<Checkbox>; showTrayIconRef: React.RefObject<HTMLInputElement>;
autostartRef: React.RefObject<Checkbox>; autostartRef: React.RefObject<HTMLInputElement>;
minimizeToTrayRef: React.RefObject<Checkbox>; minimizeToTrayRef: React.RefObject<HTMLInputElement>;
flashWindowRef: React.RefObject<Checkbox>; flashWindowRef: React.RefObject<HTMLInputElement>;
bounceIconRef: React.RefObject<Checkbox>; bounceIconRef: React.RefObject<HTMLInputElement>;
showUnreadBadgeRef: React.RefObject<Checkbox>; showUnreadBadgeRef: React.RefObject<HTMLInputElement>;
useSpellCheckerRef: React.RefObject<Checkbox>; useSpellCheckerRef: React.RefObject<HTMLInputElement>;
enableHardwareAccelerationRef: React.RefObject<Checkbox>; enableHardwareAccelerationRef: React.RefObject<HTMLInputElement>;
saveQueue: SaveQueueItem[]; saveQueue: SaveQueueItem[];
@@ -186,7 +186,7 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
} }
handleChangeShowTrayIcon = () => { handleChangeShowTrayIcon = () => {
const shouldShowTrayIcon = !this.showTrayIconRef.current?.props.checked; const shouldShowTrayIcon = this.showTrayIconRef.current?.checked;
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon});
this.setState({ this.setState({
showTrayIcon: shouldShowTrayIcon, showTrayIcon: shouldShowTrayIcon,
@@ -207,14 +207,14 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
} }
handleChangeAutoStart = () => { handleChangeAutoStart = () => {
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.autostartRef.current?.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: this.autostartRef.current?.checked});
this.setState({ this.setState({
autostart: !this.autostartRef.current?.props.checked, autostart: this.autostartRef.current?.checked,
}); });
} }
handleChangeMinimizeToTray = () => { handleChangeMinimizeToTray = () => {
const shouldMinimizeToTray = this.state.showTrayIcon && !this.minimizeToTrayRef.current?.props.checked; const shouldMinimizeToTray = this.state.showTrayIcon && !this.minimizeToTrayRef.current?.checked;
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray});
this.setState({ this.setState({
@@ -240,13 +240,13 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
key: 'notifications', key: 'notifications',
data: { data: {
...this.state.notifications, ...this.state.notifications,
flashWindow: this.flashWindowRef.current?.props.checked ? 0 : 2, flashWindow: this.flashWindowRef.current?.checked ? 0 : 2,
}, },
}); });
this.setState({ this.setState({
notifications: { notifications: {
...this.state.notifications, ...this.state.notifications,
flashWindow: this.flashWindowRef.current?.props.checked ? 0 : 2, flashWindow: this.flashWindowRef.current?.checked ? 0 : 2,
}, },
}); });
} }
@@ -256,18 +256,18 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
key: 'notifications', key: 'notifications',
data: { data: {
...this.state.notifications, ...this.state.notifications,
bounceIcon: !this.bounceIconRef.current?.props.checked, bounceIcon: this.bounceIconRef.current?.checked,
}, },
}); });
this.setState({ this.setState({
notifications: { notifications: {
...this.state.notifications, ...this.state.notifications,
bounceIcon: !this.bounceIconRef.current?.props.checked, bounceIcon: this.bounceIconRef.current?.checked,
}, },
}); });
} }
handleBounceIconType = (event: React.ChangeEvent<Radio & HTMLInputElement>) => { handleBounceIconType = (event: React.ChangeEvent<HTMLInputElement>) => {
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications', key: 'notifications',
data: { data: {
@@ -284,23 +284,23 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
} }
handleShowUnreadBadge = () => { handleShowUnreadBadge = () => {
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.showUnreadBadgeRef.current?.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: this.showUnreadBadgeRef.current?.checked});
this.setState({ this.setState({
showUnreadBadge: !this.showUnreadBadgeRef.current?.props.checked, showUnreadBadge: this.showUnreadBadgeRef.current?.checked,
}); });
} }
handleChangeUseSpellChecker = () => { handleChangeUseSpellChecker = () => {
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.useSpellCheckerRef.current?.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: this.useSpellCheckerRef.current?.checked});
this.setState({ this.setState({
useSpellChecker: !this.useSpellCheckerRef.current?.props.checked, useSpellChecker: this.useSpellCheckerRef.current?.checked,
}); });
} }
handleChangeEnableHardwareAcceleration = () => { handleChangeEnableHardwareAcceleration = () => {
window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.enableHardwareAccelerationRef.current?.props.checked}); window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: this.enableHardwareAccelerationRef.current?.checked});
this.setState({ this.setState({
enableHardwareAcceleration: !this.enableHardwareAccelerationRef.current?.props.checked, enableHardwareAcceleration: this.enableHardwareAccelerationRef.current?.checked,
}); });
} }
@@ -350,10 +350,6 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
render() { render() {
const settingsPage = { const settingsPage = {
navbar: {
backgroundColor: '#fff',
position: 'relative' as const,
},
close: { close: {
textDecoration: 'none', textDecoration: 'none',
position: 'absolute', position: 'absolute',
@@ -424,7 +420,6 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
const serversRow = ( const serversRow = (
<Row> <Row>
<Col <Col
md={10}
xs={8} xs={8}
> >
<h2 style={settingsPage.sectionHeading}>{'Server Management'}</h2> <h2 style={settingsPage.sectionHeading}>{'Server Management'}</h2>
@@ -437,7 +432,6 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
</div> </div>
</Col> </Col>
<Col <Col
md={2}
xs={4} xs={4}
> >
<p className='text-right'> <p className='text-right'>
@@ -468,66 +462,74 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
// MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX // MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX
if (window.process.platform === 'win32' || window.process.platform === 'linux') { if (window.process.platform === 'win32' || window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <FormCheck>
<FormCheck.Input
type='checkbox'
key='inputAutoStart' key='inputAutoStart'
id='inputAutoStart' id='inputAutoStart'
ref={this.autostartRef} ref={this.autostartRef}
checked={this.state.autostart} checked={this.state.autostart}
onChange={this.handleChangeAutoStart} onChange={this.handleChangeAutoStart}
> />
{'Start app on login'} {'Start app on login'}
<HelpBlock> <FormText>
{'If enabled, the app starts automatically when you log in to your machine.'} {'If enabled, the app starts automatically when you log in to your machine.'}
</HelpBlock> </FormText>
</Checkbox>); </FormCheck>);
} }
options.push( options.push(
<Checkbox <FormCheck>
<FormCheck.Input
type='checkbox'
key='inputSpellChecker' key='inputSpellChecker'
id='inputSpellChecker' id='inputSpellChecker'
ref={this.useSpellCheckerRef} ref={this.useSpellCheckerRef}
checked={this.state.useSpellChecker} checked={this.state.useSpellChecker}
onChange={this.handleChangeUseSpellChecker} onChange={this.handleChangeUseSpellChecker}
> />
{'Check spelling'} {'Check spelling'}
<HelpBlock> <FormText>
{'Highlight misspelled words in your messages based on your system language configuration. '} {'Highlight misspelled words in your messages based on your system language configuration. '}
{'Setting takes effect after restarting the app.'} {'Setting takes effect after restarting the app.'}
</HelpBlock> </FormText>
</Checkbox>); </FormCheck>);
if (window.process.platform === 'darwin' || window.process.platform === 'win32') { if (window.process.platform === 'darwin' || window.process.platform === 'win32') {
const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock'; const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock';
options.push( options.push(
<Checkbox <FormCheck>
<FormCheck.Input
type='checkbox'
key='inputShowUnreadBadge' key='inputShowUnreadBadge'
id='inputShowUnreadBadge' id='inputShowUnreadBadge'
ref={this.showUnreadBadgeRef} ref={this.showUnreadBadgeRef}
checked={this.state.showUnreadBadge} checked={this.state.showUnreadBadge}
onChange={this.handleShowUnreadBadge} onChange={this.handleShowUnreadBadge}
> />
{`Show red badge on ${TASKBAR} icon to indicate unread messages`} {`Show red badge on ${TASKBAR} icon to indicate unread messages`}
<HelpBlock> <FormText>
{`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`} {`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`}
</HelpBlock> </FormText>
</Checkbox>); </FormCheck>);
} }
if (window.process.platform === 'win32' || window.process.platform === 'linux') { if (window.process.platform === 'win32' || window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <FormCheck>
<FormCheck.Input
type='checkbox'
key='flashWindow' key='flashWindow'
id='inputflashWindow' id='inputflashWindow'
ref={this.flashWindowRef} ref={this.flashWindowRef}
checked={!this.state.notifications || this.state.notifications.flashWindow === 2} checked={!this.state.notifications || this.state.notifications.flashWindow === 2}
onChange={this.handleFlashWindow} onChange={this.handleFlashWindow}
> />
{'Flash app window and taskbar icon when a new message is received'} {'Flash app window and taskbar icon when a new message is received'}
<HelpBlock> <FormText>
{'If enabled, app window and taskbar icon flash for a few seconds when a new message is received.'} {'If enabled, app window and taskbar icon flash for a few seconds when a new message is received.'}
</HelpBlock> </FormText>
</Checkbox>); </FormCheck>);
} }
if (window.process.platform === 'darwin') { if (window.process.platform === 'darwin') {
@@ -535,7 +537,8 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
<FormGroup <FormGroup
key='OptionsForm' key='OptionsForm'
> >
<Checkbox <FormCheck
type='checkbox'
inline={true} inline={true}
key='bounceIcon' key='bounceIcon'
id='inputBounceIcon' id='inputBounceIcon'
@@ -543,10 +546,10 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
checked={this.state.notifications ? this.state.notifications.bounceIcon : false} checked={this.state.notifications ? this.state.notifications.bounceIcon : false}
onChange={this.handleBounceIcon} onChange={this.handleBounceIcon}
style={{marginRight: '10px'}} style={{marginRight: '10px'}}
> label='Bounce the Dock icon'
{'Bounce the Dock icon'} />
</Checkbox> <FormCheck
<Radio type='radio'
inline={true} inline={true}
name='bounceIconType' name='bounceIconType'
value='informational' value='informational'
@@ -557,43 +560,44 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
this.state.notifications.bounceIconType === 'informational' this.state.notifications.bounceIconType === 'informational'
} }
onChange={this.handleBounceIconType} onChange={this.handleBounceIconType}
> label='once'
{'once'} />
</Radio>
{' '} {' '}
<Radio <FormCheck
type='radio'
inline={true} inline={true}
name='bounceIconType' name='bounceIconType'
value='critical' value='critical'
disabled={!this.state.notifications || !this.state.notifications.bounceIcon} disabled={!this.state.notifications || !this.state.notifications.bounceIcon}
defaultChecked={this.state.notifications && this.state.notifications.bounceIconType === 'critical'} defaultChecked={this.state.notifications && this.state.notifications.bounceIconType === 'critical'}
onChange={this.handleBounceIconType} onChange={this.handleBounceIconType}
> label={'until I open the app'}
{'until I open the app'} />
</Radio> <FormText
<HelpBlock
style={{marginLeft: '20px'}} style={{marginLeft: '20px'}}
> >
{'If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.'} {'If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.'}
</HelpBlock> </FormText>
</FormGroup>, </FormGroup>,
); );
} }
if (window.process.platform === 'darwin' || window.process.platform === 'linux') { if (window.process.platform === 'darwin' || window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <FormCheck>
<FormCheck.Input
type='checkbox'
key='inputShowTrayIcon' key='inputShowTrayIcon'
id='inputShowTrayIcon' id='inputShowTrayIcon'
ref={this.showTrayIconRef} ref={this.showTrayIconRef}
checked={this.state.showTrayIcon} checked={this.state.showTrayIcon}
onChange={this.handleChangeShowTrayIcon} onChange={this.handleChangeShowTrayIcon}
> />
{window.process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'} {window.process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'}
<HelpBlock> <FormText>
{'Setting takes effect after restarting the app.'} {'Setting takes effect after restarting the app.'}
</HelpBlock> </FormText>
</Checkbox>); </FormCheck>);
} }
if (window.process.platform === 'linux') { if (window.process.platform === 'linux') {
@@ -604,30 +608,33 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
style={{marginLeft: '20px'}} style={{marginLeft: '20px'}}
> >
{'Icon theme: '} {'Icon theme: '}
<Radio <FormCheck
type='radio'
inline={true} inline={true}
name='trayIconTheme' name='trayIconTheme'
value='light' value='light'
defaultChecked={this.state.trayIconTheme === 'light' || !this.state.trayIconTheme} defaultChecked={this.state.trayIconTheme === 'light' || !this.state.trayIconTheme}
onChange={() => this.handleChangeTrayIconTheme('light')} onChange={() => this.handleChangeTrayIconTheme('light')}
> label={'Light'}
{'Light'} />
</Radio>
{' '} {' '}
<Radio <FormCheck
type='radio'
inline={true} inline={true}
name='trayIconTheme' name='trayIconTheme'
value='dark' value='dark'
defaultChecked={this.state.trayIconTheme === 'dark'} defaultChecked={this.state.trayIconTheme === 'dark'}
onChange={() => this.handleChangeTrayIconTheme('dark')} onChange={() => this.handleChangeTrayIconTheme('dark')}
>{'Dark'}</Radio> label={'Dark'}
/>
</FormGroup>, </FormGroup>,
); );
} }
if (window.process.platform === 'linux') { if (window.process.platform === 'linux') {
options.push( options.push(
<Checkbox <FormCheck
type='radio'
key='inputMinimizeToTray' key='inputMinimizeToTray'
id='inputMinimizeToTray' id='inputMinimizeToTray'
ref={this.minimizeToTrayRef} ref={this.minimizeToTrayRef}
@@ -636,27 +643,29 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
onChange={this.handleChangeMinimizeToTray} onChange={this.handleChangeMinimizeToTray}
> >
{'Leave app running in notification area when application window is closed'} {'Leave app running in notification area when application window is closed'}
<HelpBlock> <FormText>
{'If enabled, the app stays running in the notification area after app window is closed.'} {'If enabled, the app stays running in the notification area after app window is closed.'}
{this.state.trayWasVisible || !this.state.showTrayIcon ? '' : ' Setting takes effect after restarting the app.'} {this.state.trayWasVisible || !this.state.showTrayIcon ? '' : ' Setting takes effect after restarting the app.'}
</HelpBlock> </FormText>
</Checkbox>); </FormCheck>);
} }
options.push( options.push(
<Checkbox <FormCheck>
<FormCheck.Input
type='checkbox'
key='inputEnableHardwareAcceleration' key='inputEnableHardwareAcceleration'
id='inputEnableHardwareAcceleration' id='inputEnableHardwareAcceleration'
ref={this.enableHardwareAccelerationRef} ref={this.enableHardwareAccelerationRef}
checked={this.state.enableHardwareAcceleration} checked={this.state.enableHardwareAcceleration}
onChange={this.handleChangeEnableHardwareAcceleration} onChange={this.handleChangeEnableHardwareAcceleration}
> />
{'Use GPU hardware acceleration'} {'Use GPU hardware acceleration'}
<HelpBlock> <FormText>
{'If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.'} {'If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.'}
{' Setting takes effect after restarting the app.'} {' Setting takes effect after restarting the app.'}
</HelpBlock> </FormText>
</Checkbox>, </FormCheck>,
); );
options.push( options.push(
@@ -679,9 +688,9 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
> >
<span>{'Change'}</span> <span>{'Change'}</span>
</Button> </Button>
<HelpBlock> <FormText>
{'Specify the folder where files will download.'} {'Specify the folder where files will download.'}
</HelpBlock> </FormText>
</div>, </div>,
); );
@@ -733,20 +742,16 @@ export default class SettingsPage extends React.PureComponent<Record<string, nev
height: '100%', height: '100%',
margin: '0 -15px', margin: '0 -15px',
}} }}
>
<Navbar
className='navbar-fixed-top'
style={settingsPage.navbar}
> >
<div style={{position: 'relative'}}> <div style={{position: 'relative'}}>
<h1 style={settingsPage.heading}>{'Settings'}</h1> <h1 style={settingsPage.heading}>{'Settings'}</h1>
<hr/>
</div> </div>
</Navbar> <Container
<Grid
className='settingsPage' className='settingsPage'
> >
{waitForIpc} {waitForIpc}
</Grid> </Container>
</div> </div>
</div> </div>
); );

View File

@@ -3,9 +3,10 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React from 'react';
import {Nav, NavItem} from 'react-bootstrap'; import {Nav, NavItem, NavLink} from 'react-bootstrap';
import {Container, Draggable, OnDropCallback} from 'react-smooth-dnd'; import {DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDraggingStyle} from 'react-beautiful-dnd';
import PlusIcon from 'mdi-react/PlusIcon'; import PlusIcon from 'mdi-react/PlusIcon';
import classNames from 'classnames';
import {Team} from 'types/config'; import {Team} from 'types/config';
@@ -22,7 +23,7 @@ type Props = {
mentionCounts: Record<string, number>; mentionCounts: Record<string, number>;
showAddServerButton: boolean; showAddServerButton: boolean;
onAddServer: () => void; onAddServer: () => void;
onDrop: OnDropCallback; onDrop: (result: DropResult) => void;
tabsDisabled?: boolean; tabsDisabled?: boolean;
}; };
@@ -30,9 +31,18 @@ type State = {
hasGPOTeams: boolean; hasGPOTeams: boolean;
}; };
export default class TabBar extends React.PureComponent<Props, State> { // need "this" function getStyle(style?: DraggingStyle | NotDraggingStyle) {
container?: React.RefObject<Container>; if (style?.transform) {
const axisLockX = `${style.transform.slice(0, style.transform.indexOf(','))}, 0px)`;
return {
...style,
transform: axisLockX,
};
}
return style;
}
export default class TabBar extends React.PureComponent<Props, State> { // need "this"
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@@ -48,7 +58,7 @@ export default class TabBar extends React.PureComponent<Props, State> { // need
render() { render() {
const orderedTabs = this.props.teams.concat().sort((a, b) => a.order - b.order); const orderedTabs = this.props.teams.concat().sort((a, b) => a.order - b.order);
const tabs = orderedTabs.map((team) => { const tabs = orderedTabs.map((team, orderedIndex) => {
const index = this.props.teams.indexOf(team); const index = this.props.teams.indexOf(team);
const sessionExpired = this.props.sessionsExpired[index]; const sessionExpired = this.props.sessionsExpired[index];
@@ -76,23 +86,35 @@ export default class TabBar extends React.PureComponent<Props, State> { // need
); );
} }
const id = `teamTabItem${index}`; return (
const navItem = () => ( <Draggable
<NavItem
key={index} key={index}
id={id} draggableId={`teamTabItem${index}`}
index={orderedIndex}
>
{(provided, snapshot) => (
<NavItem
ref={provided.innerRef}
as='li'
id={`teamTabItem${index}`}
draggable={false}
title={team.name}
className={classNames('teamTabItem', {
active: this.props.activeKey === index,
dragging: snapshot.isDragging,
})}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getStyle(provided.draggableProps.style)}
>
<NavLink
eventKey={index} eventKey={index}
draggable={false} draggable={false}
ref={id}
active={this.props.activeKey === index} active={this.props.activeKey === index}
onMouseDown={() => { disabled={this.props.tabsDisabled}
this.props.onSelect(team.name, index);
}}
onSelect={() => { onSelect={() => {
this.props.onSelect(team.name, index); this.props.onSelect(team.name, index);
}} }}
title={team.name}
disabled={this.props.tabsDisabled}
> >
<div className='TabBar-tabSeperator'> <div className='TabBar-tabSeperator'>
<span> <span>
@@ -100,59 +122,70 @@ export default class TabBar extends React.PureComponent<Props, State> { // need
</span> </span>
{ badgeDiv } { badgeDiv }
</div> </div>
</NavLink>
</NavItem> </NavItem>
)}
</Draggable>
); );
return (
<Draggable
key={id}
render={navItem}
className='teamTabItem'
/>);
}); });
if (this.props.showAddServerButton === true) { if (this.props.showAddServerButton === true) {
tabs.push( tabs.push(
<Draggable
draggableId={'TabBar-addServerButton'}
index={this.props.teams.length}
isDragDisabled={true}
>
{(provided) => (
<NavItem <NavItem
ref={provided.innerRef}
as='li'
className='TabBar-addServerButton' className='TabBar-addServerButton'
key='addServerButton' key='addServerButton'
id='addServerButton' id='addServerButton'
eventKey='addServerButton'
draggable={false} draggable={false}
title='Add new server' title='Add new server'
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<NavLink
eventKey='addServerButton'
draggable={false}
disabled={this.props.tabsDisabled}
onSelect={() => { onSelect={() => {
this.props.onAddServer(); this.props.onAddServer();
}} }}
disabled={this.props.tabsDisabled}
> >
<div className='TabBar-tabSeperator'> <div className='TabBar-tabSeperator'>
<PlusIcon size={20}/> <PlusIcon size={20}/>
</div> </div>
</NavItem>, </NavLink>
</NavItem>
)}
</Draggable>,
); );
} }
const navContainer = (ref: React.RefObject<Nav>) => ( return (
<DragDropContext onDragEnd={this.props.onDrop}>
<Droppable
isDropDisabled={this.state.hasGPOTeams || this.props.tabsDisabled}
droppableId='tabBar'
direction='horizontal'
>
{(provided) => (
<Nav <Nav
ref={ref} ref={provided.innerRef}
className={`smooth-dnd-container TabBar${this.props.isDarkMode ? ' darkMode' : ''}`} className={`TabBar${this.props.isDarkMode ? ' darkMode' : ''}`}
id={this.props.id} id={this.props.id}
bsStyle='tabs' variant='tabs'
{...provided.droppableProps}
> >
{tabs} {tabs}
{provided.placeholder}
</Nav> </Nav>
); )}
return ( </Droppable>
<Container </DragDropContext>
ref={this.container}
render={navContainer}
orientation='horizontal'
lockAxis={'x'}
onDrop={this.props.onDrop}
animationDuration={300}
shouldAcceptDrop={() => {
return !this.state.hasGPOTeams && !this.props.tabsDisabled;
}}
/>
); );
} }
} }

View File

@@ -31,7 +31,7 @@ export default class TeamListItem extends React.PureComponent<Props> {
{ this.props.url } { this.props.url }
</p> </p>
</div> </div>
<div className='pull-right'> <div>
<a <a
href='#' href='#'
onClick={this.handleTeamEditing} onClick={this.handleTeamEditing}

View File

@@ -7,22 +7,22 @@ import {Button, Navbar, ProgressBar} from 'react-bootstrap';
type InstallButtonProps = { type InstallButtonProps = {
notifyOnly?: boolean; notifyOnly?: boolean;
onClickInstall?: React.MouseEventHandler<Button>; onClickInstall?: React.MouseEventHandler<HTMLButtonElement>;
onClickDownload?: React.MouseEventHandler<Button>; onClickDownload?: React.MouseEventHandler<HTMLButtonElement>;
}; };
function InstallButton(props: InstallButtonProps) { function InstallButton(props: InstallButtonProps) {
if (props.notifyOnly) { if (props.notifyOnly) {
return ( return (
<Button <Button
bsStyle='primary' variant='primary'
onClick={props.onClickDownload} onClick={props.onClickDownload}
>{'Download Update'}</Button> >{'Download Update'}</Button>
); );
} }
return ( return (
<Button <Button
bsStyle='primary' variant='primary'
onClick={props.onClickInstall} onClick={props.onClickInstall}
>{'Install Update'}</Button> >{'Install Update'}</Button>
); );
@@ -33,12 +33,12 @@ type UpdaterPageProps = {
notifyOnly?: boolean; notifyOnly?: boolean;
isDownloading?: boolean; isDownloading?: boolean;
progress?: number; progress?: number;
onClickInstall?: React.MouseEventHandler<Button>; onClickInstall?: React.MouseEventHandler<HTMLButtonElement>;
onClickDownload?: React.MouseEventHandler<Button>; onClickDownload?: React.MouseEventHandler<HTMLButtonElement>;
onClickReleaseNotes?: React.MouseEventHandler<HTMLAnchorElement>; onClickReleaseNotes?: React.MouseEventHandler<HTMLAnchorElement>;
onClickRemind?: React.MouseEventHandler<Button>; onClickRemind?: React.MouseEventHandler<HTMLButtonElement>;
onClickSkip?: React.MouseEventHandler<Button>; onClickSkip?: React.MouseEventHandler<HTMLButtonElement>;
onClickCancel?: React.MouseEventHandler<Button>; onClickCancel?: React.MouseEventHandler<HTMLButtonElement>;
}; };
function UpdaterPage(props: UpdaterPageProps) { function UpdaterPage(props: UpdaterPageProps) {
@@ -47,11 +47,10 @@ function UpdaterPage(props: UpdaterPageProps) {
footer = ( footer = (
<Navbar <Navbar
className='UpdaterPage-footer' className='UpdaterPage-footer'
fixedBottom={true} fixed='bottom'
fluid={true}
> >
<ProgressBar <ProgressBar
active={true} animated={true}
now={props.progress} now={props.progress}
label={`${props.progress}%`} label={`${props.progress}%`}
/> />
@@ -66,17 +65,16 @@ function UpdaterPage(props: UpdaterPageProps) {
footer = ( footer = (
<Navbar <Navbar
className='UpdaterPage-footer' className='UpdaterPage-footer'
fixedBottom={true} fixed='bottom'
fluid={true}
> >
<Button <Button
className='UpdaterPage-skipButton' className='UpdaterPage-skipButton'
bsStyle='link' variant='link'
onClick={props.onClickSkip} onClick={props.onClickSkip}
>{'Skip this version'}</Button> >{'Skip this version'}</Button>
<div className='pull-right'> <div className='pull-right'>
<Button <Button
bsStyle='link' variant='link'
onClick={props.onClickRemind} onClick={props.onClickRemind}
>{'Remind me in 2 days'}</Button> >{'Remind me in 2 days'}</Button>
<InstallButton <InstallButton
@@ -91,7 +89,7 @@ function UpdaterPage(props: UpdaterPageProps) {
return ( return (
<div className='UpdaterPage'> <div className='UpdaterPage'>
<Navbar fluid={true} > <Navbar>
<h1 className='UpdaterPage-heading'>{'New update is available'}</h1> <h1 className='UpdaterPage-heading'>{'New update is available'}</h1>
</Navbar> </Navbar>
<div className='container-fluid'> <div className='container-fluid'>

View File

@@ -105,6 +105,7 @@ export default class ShowCertificateModal extends React.PureComponent<Props, Sta
<Row> <Row>
<Col> <Col>
<Button <Button
variant='primary'
onClick={this.handleOk} onClick={this.handleOk}
className={'primary'} className={'primary'}
>{'Close'}</Button> >{'Close'}</Button>

View File

@@ -72,7 +72,7 @@ label {
outline: 0; outline: 0;
} }
.help-block { .form-text {
display: block; display: block;
margin-top: 5px; margin-top: 5px;
margin-bottom: 10px; margin-bottom: 10px;
@@ -173,7 +173,7 @@ button.default:focus {
color: #a94442; color: #a94442;
} }
.has-error .form-control { :invalid .form-control {
border-color: #a94442; border-color: #a94442;
} }

View File

@@ -10,7 +10,7 @@
} }
#extra-bar.hidden { #extra-bar.hidden {
max-height: 0px; display: none;
} }
span.backLabel { span.backLabel {
@@ -25,7 +25,6 @@ span.backLabel {
span.backIcon { span.backIcon {
margin-right: 4px; margin-right: 4px;
font-size: 1.6rem;
} }
.container-fluid button:first-child { .container-fluid button:first-child {

View File

@@ -26,6 +26,7 @@
-webkit-user-select: none; -webkit-user-select: none;
min-width: 48px; min-width: 48px;
max-width: 224px; max-width: 224px;
position: relative;
} }
.TabBar>li>a { .TabBar>li>a {
@@ -41,6 +42,7 @@
font-size: 14px; font-size: 14px;
letter-spacing: -0.2px; letter-spacing: -0.2px;
transition: 0.3s; transition: 0.3s;
border: none !important;
} }
.TabBar.darkMode>li>a { .TabBar.darkMode>li>a {
@@ -80,7 +82,7 @@
flex: 0 0 6px; flex: 0 0 6px;
} }
.TabBar>li.teamTabItem.active:before, .TabBar>li.teamTabItem.smooth-dnd-ghost:before { .TabBar>li.teamTabItem.active:before, .TabBar>li.teamTabItem.dragging:before {
left: -4px; left: -4px;
border-bottom-right-radius: 6px; border-bottom-right-radius: 6px;
border-right: 2px solid #fff; border-right: 2px solid #fff;
@@ -88,12 +90,12 @@
z-index: 9; z-index: 9;
} }
.TabBar.darkMode>li.teamTabItem.active:before, .TabBar.darkMode>li.teamTabItem.smooth-dnd-ghost:before { .TabBar.darkMode>li.teamTabItem.active:before, .TabBar.darkMode>li.teamTabItem.dragging:before {
border-right: 2px solid #323639; border-right: 2px solid #323639;
border-bottom: 2px solid #323639; border-bottom: 2px solid #323639;
} }
.TabBar>li.teamTabItem.active:after, .TabBar>li.teamTabItem.smooth-dnd-ghost:after { .TabBar>li.teamTabItem.active:after, .TabBar>li.teamTabItem.dragging:after {
border-bottom-left-radius: 6px; border-bottom-left-radius: 6px;
right: -5px; right: -5px;
border-left: 2px solid #fff; border-left: 2px solid #fff;
@@ -101,7 +103,7 @@
z-index: 9; z-index: 9;
} }
.TabBar.darkMode>li.teamTabItem.active:after, .TabBar.darkMode>li.teamTabItem.smooth-dnd-ghost:after { .TabBar.darkMode>li.teamTabItem.active:after, .TabBar.darkMode>li.teamTabItem.dragging:after {
border-left: 2px solid #323639; border-left: 2px solid #323639;
border-bottom: 2px solid #323639; border-bottom: 2px solid #323639;
} }
@@ -113,8 +115,6 @@
} }
.TabBar>li.TabBar-addServerButton{ .TabBar>li.TabBar-addServerButton{
transition: none !important;
transform: none !important;
flex: 0 0 auto; flex: 0 0 auto;
min-width: 40px; min-width: 40px;
} }
@@ -124,6 +124,10 @@
transition: opacity 0.3s ease-in; transition: opacity 0.3s ease-in;
} }
.TabBar>li.TabBar-addServerButton>a.active{
background-color: transparent;
}
.TabBar>li.TabBar-addServerButton svg{ .TabBar>li.TabBar-addServerButton svg{
margin: -2px; margin: -2px;
} }
@@ -132,7 +136,7 @@
color: rgba(243,243,243,0.7); color: rgba(243,243,243,0.7);
} }
.TabBar>li.teamTabItem.active>a, .TabBar>li.teamTabItem.smooth-dnd-ghost>a { .TabBar>li.teamTabItem.active>a, .TabBar>li.teamTabItem.dragging>a {
border: none; border: none;
border-radius: 6px 6px 0 0; border-radius: 6px 6px 0 0;
color: rgba(61,60,64,1); color: rgba(61,60,64,1);
@@ -140,11 +144,7 @@
z-index: 9; z-index: 9;
} }
.smooth-dnd-no-user-select li.TabBar-addServerButton>a { .TabBar.darkMode>li.teamTabItem.active>a, .TabBar.darkMode>li.teamTabItem.dragging>a {
opacity: 0;
}
.TabBar.darkMode>li.teamTabItem.active>a, .TabBar.darkMode>li.teamTabItem.smooth-dnd-ghost>a {
color: #f3f3f3; color: #f3f3f3;
background-color: #323639; background-color: #323639;
} }

View File

@@ -1,8 +1,21 @@
.TeamListItem {
display: flex;
}
.TeamListItem:hover { .TeamListItem:hover {
background: #eee; background: #eee;
} }
.TeamListItem-left { .TeamListItem-left {
display: inline-block; flex-grow: 1;
width: calc(100% - 100px);
} }
.TeamListItem p {
overflow-wrap: break-word;
margin-bottom: 0;
}
.TeamListItem h4 {
overflow: hidden;
text-overflow: ellipsis;
};

View File

@@ -45,6 +45,7 @@
html { html {
height: 100%; height: 100%;
font-size: 14px;
} }
body { body {
min-height: 100%; min-height: 100%;
@@ -68,8 +69,8 @@ body {
transition: opacity 500ms ease-in-out; transition: opacity 500ms ease-in-out;
} }
.has-error .control-label, :invalid .col-form-label,
.has-error .help-block { :invalid .form-text {
color: #333; color: #333;
} }
@@ -86,6 +87,7 @@ body {
overflow: hidden; overflow: hidden;
height: 36px; height: 36px;
background-color: rgba(0,0,0,0.1); background-color: rgba(0,0,0,0.1);
width: 100%;
} }
.topBar>.topBar-bg.unfocused { .topBar>.topBar-bg.unfocused {

View File

@@ -1,3 +1,8 @@
body { body {
background-color: transparent; background-color: transparent;
} }
.btn-primary {
background-color: #166de0;
border-color: #166de0;
}

View File

@@ -17,15 +17,6 @@
transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1); transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1);
} }
.TeamListItem p {
overflow-wrap: break-word;
}
.TeamListItem h4 {
overflow: hidden;
text-overflow: ellipsis;
};
.checkbox > label { .checkbox > label {
width: 100%; width: 100%;
} }
@@ -39,3 +30,8 @@ body {
#content { #content {
height: 100%; height: 100%;
} }
.btn-primary {
background-color: #166de0;
border-color: #166de0;
}

View File

@@ -46,7 +46,7 @@ class Root extends React.PureComponent<Record<string, never>, State> {
this.setState({config}); this.setState({config});
} }
moveTabs = async (originalOrder: number, newOrder: number): Promise<number | undefined> => { moveTabs = (originalOrder: number, newOrder: number): number | undefined => {
if (!this.state.config) { if (!this.state.config) {
throw new Error('No config'); throw new Error('No config');
} }
@@ -61,20 +61,27 @@ class Root extends React.PureComponent<Record<string, never>, State> {
const team = tabOrder.splice(originalOrder, 1); const team = tabOrder.splice(originalOrder, 1);
tabOrder.splice(newOrder, 0, team[0]); tabOrder.splice(newOrder, 0, team[0]);
let teamIndex; let teamIndex: number | undefined;
tabOrder.forEach((t, order) => { tabOrder.forEach((t, order) => {
if (order === newOrder) { if (order === newOrder) {
teamIndex = t.index; teamIndex = t.index;
} }
teams[t.index].order = order; teams[t.index].order = order;
}); });
await this.teamConfigChange(teams); this.setState({
config: {
...this.state.config,
teams,
},
});
this.teamConfigChange(teams);
return teamIndex; return teamIndex;
}; };
teamConfigChange = async (updatedTeams: Team[]) => { teamConfigChange = async (updatedTeams: Team[]) => {
await window.ipcRenderer.invoke(UPDATE_TEAMS, updatedTeams); window.ipcRenderer.invoke(UPDATE_TEAMS, updatedTeams).then(() => {
await this.reloadConfig(); this.reloadConfig();
});
}; };
reloadConfig = async () => { reloadConfig = async () => {

View File

@@ -164,6 +164,7 @@ export default class SelectCertificateModal extends React.PureComponent<Props, S
<Row> <Row>
<Col sm={4}> <Col sm={4}>
<Button <Button
variant='info'
disabled={this.state.selectedIndex === null} disabled={this.state.selectedIndex === null}
onClick={this.handleCertificateInfo} onClick={this.handleCertificateInfo}
className={'info'} className={'info'}
@@ -171,10 +172,12 @@ export default class SelectCertificateModal extends React.PureComponent<Props, S
</Col> </Col>
<Col sm={8}> <Col sm={8}>
<Button <Button
variant='link'
onClick={this.props.onCancel} onClick={this.props.onCancel}
className={'secondary'} className={'secondary'}
>{'Cancel'}</Button> >{'Cancel'}</Button>
<Button <Button
variant='primary'
onClick={this.handleOk} onClick={this.handleOk}
disabled={this.state.selectedIndex === null} disabled={this.state.selectedIndex === null}
className={'primary'} className={'primary'}

View File

@@ -3,7 +3,7 @@
// Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react'; import React from 'react';
import {Button, Col, ControlLabel, Form, FormGroup, FormControl, Modal} from 'react-bootstrap'; import {Button, Col, FormLabel, Form, FormGroup, FormControl, Modal} from 'react-bootstrap';
import {LoginModalData} from 'types/auth'; import {LoginModalData} from 'types/auth';
import {ModalMessage} from 'types/modals'; import {ModalMessage} from 'types/modals';
@@ -56,7 +56,7 @@ export default class LoginModal extends React.PureComponent<Props, State> {
} }
} }
handleSubmit = (event: React.MouseEvent<Button>) => { handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
this.props.onLogin(this.state.request!, this.state.username, this.state.password); this.props.onLogin(this.state.request!, this.state.username, this.state.password);
this.setState({ this.setState({
@@ -67,7 +67,7 @@ export default class LoginModal extends React.PureComponent<Props, State> {
}); });
} }
handleCancel = (event: React.MouseEvent<Button>) => { handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault(); event.preventDefault();
this.props.onCancel(this.state.request!); this.props.onCancel(this.state.request!);
this.setState({ this.setState({
@@ -78,11 +78,11 @@ export default class LoginModal extends React.PureComponent<Props, State> {
}); });
} }
setUsername = (e: React.ChangeEvent<FormControl & HTMLInputElement>) => { setUsername = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({username: e.target.value}); this.setState({username: e.target.value});
} }
setPassword = (e: React.ChangeEvent<FormControl & HTMLInputElement>) => { setPassword = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({password: e.target.value}); this.setState({password: e.target.value});
} }
@@ -100,7 +100,6 @@ export default class LoginModal extends React.PureComponent<Props, State> {
return ( return (
<Modal <Modal
show={Boolean(this.state.request && this.state.authInfo)} show={Boolean(this.state.request && this.state.authInfo)}
onHide={() => {}}
> >
<Modal.Header> <Modal.Header>
<Modal.Title>{'Authentication Required'}</Modal.Title> <Modal.Title>{'Authentication Required'}</Modal.Title>
@@ -110,12 +109,11 @@ export default class LoginModal extends React.PureComponent<Props, State> {
{ message } { message }
</p> </p>
<Form <Form
horizontal={true}
onSubmit={this.handleSubmit} onSubmit={this.handleSubmit}
> >
<FormGroup> <FormGroup>
<Col <Col
componentClass={ControlLabel} as={FormLabel}
sm={2} sm={2}
>{'User Name'}</Col> >{'User Name'}</Col>
<Col sm={10}> <Col sm={10}>
@@ -124,7 +122,7 @@ export default class LoginModal extends React.PureComponent<Props, State> {
placeholder='User Name' placeholder='User Name'
onChange={this.setUsername} onChange={this.setUsername}
value={this.state.username} value={this.state.username}
onClick={(e) => { onClick={(e: React.MouseEvent<HTMLInputElement>) => {
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
@@ -132,7 +130,7 @@ export default class LoginModal extends React.PureComponent<Props, State> {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<Col <Col
componentClass={ControlLabel} as={FormLabel}
sm={2} sm={2}
>{'Password'}</Col> >{'Password'}</Col>
<Col sm={10}> <Col sm={10}>
@@ -141,7 +139,7 @@ export default class LoginModal extends React.PureComponent<Props, State> {
placeholder='Password' placeholder='Password'
onChange={this.setPassword} onChange={this.setPassword}
value={this.state.password} value={this.state.password}
onClick={(e) => { onClick={(e: React.MouseEvent<HTMLInputElement>) => {
e.stopPropagation(); e.stopPropagation();
}} }}
/> />
@@ -152,10 +150,13 @@ export default class LoginModal extends React.PureComponent<Props, State> {
<div className='pull-right'> <div className='pull-right'>
<Button <Button
type='submit' type='submit'
bsStyle='primary' variant='primary'
>{'Login'}</Button> >{'Login'}</Button>
{ ' ' } { ' ' }
<Button onClick={this.handleCancel}>{'Cancel'}</Button> <Button
variant='link'
onClick={this.handleCancel}
>{'Cancel'}</Button>
</div> </div>
</Col> </Col>
</FormGroup> </FormGroup>

View File

@@ -13,8 +13,8 @@ import {MODAL_INFO} from 'common/communication';
import {PERMISSION_DESCRIPTION} from 'common/permissions'; import {PERMISSION_DESCRIPTION} from 'common/permissions';
type Props = { type Props = {
handleDeny: React.MouseEventHandler<Button>; handleDeny: React.MouseEventHandler<HTMLButtonElement>;
handleGrant: React.MouseEventHandler<Button>; handleGrant: React.MouseEventHandler<HTMLButtonElement>;
getPermissionInfo: () => void; getPermissionInfo: () => void;
openExternalLink: (protocol: string, url: string) => void; openExternalLink: (protocol: string, url: string) => void;
}; };
@@ -107,7 +107,7 @@ export default class PermissionModal extends React.PureComponent<Props, State> {
onClick={this.props.handleDeny} onClick={this.props.handleDeny}
>{'Cancel'}</Button> >{'Cancel'}</Button>
<Button <Button
bsStyle='primary' variant='primary'
onClick={this.props.handleGrant} onClick={this.props.handleGrant}
>{'Accept'}</Button> >{'Accept'}</Button>
</div> </div>