[MM-10586] Desktop App Window/Tabs Update (#1056)
* [MM-19054] Added new server tab look and feel, still missing proper hover states and session expired icon * [MM-19055] Added window controls and removed border for macOS * [MM-19055] Add dark mode for macOS * [MM-19054] Added session expired icon * Test windows titlebar * Fixed the menu issue and added non-macOS dark mode * Blank commit * Fixed a lint issue * Fixed more lint issues * Fixed more issues * New tray icons * [MM-19603] Drag and drop tabs * Fixed some assets and fixed build output to include missing assets * Fixed a couple small issues * Only show tabs for only 1 server on Mac * Fixed some more tests * Fixed another test * Revert "Fixed another test" This reverts commit 36040294a71a68663d06996d71eecc5ed23d7014. * Fixed another test * Trial and error! * A bunch of additional fixes * Fixed a lint issue * Fixed restore focus on add server tab causing bad UX * Trial and error on flaky test again * Fixed some bugs based on PR feedback. * blank commit to push tests * Revert "Test windows titlebar" This reverts commit 9cd46b71b1427b75942434ac49185870d2437b85. * Remove the rest of the old new titlebar and fixes * Added three-dot link * New menu * Rest of new windows menu and other fixes * Fixed lint errors * Added windows 10 style title bar buttons for non mac OS * Lint fixes and enabled the tab bar regardless of number of servers * Missed one * Fixed unicode characters * Commenting out test that should no longer be applicable * Removed Windows 10 style titlebar icons and used material design instead * Fixed a lint issue * Some small UX fixes * blank commit * Fixed an issue where dropping the first tab moves it too far over before snapping into place * Additional style fixes * Another small issue fix * Back to Windows 10 style * Lint fixes * Accessible three dot menu * Lint fixes * Shrinking tabs when window is too small * Gradient between tabs and title bar buttons when window is too small * Add drag to gradient * Replaced icons, drag and drop cursor sticking fix, slight tab change * Lint and some mac fixes * Light theme fix to three dot menu * Hack for tab sticking to cursor on macOS * Fixes for the find utility * Fix for Catalina dark mode * Revert "Fix for Catalina dark mode" This reverts commit 45da05dd0f17f46efd1c53fafb92e9c1fd9dd8d9. * Fixed a couple issues Dean found * More fixes * Three dot hover effect to circle * PR feedback * Test fixes * Test and config fixes * Disable dragging when there are GPO servers * [MM-20757] Fixed dark mode on debug when running macOS Catalina * Allow future config versions to use v2 config if launching this version of the app * Oops * New titlebar icons, blur for titlebar on inactive * Lint fix * Set unfocused opacity to 0.4 * Final FINAL icons * Fixed closing menu not returning focus to the app * Lint fix * Update src/browser/components/TabBar.jsx Co-Authored-By: Guillermo Vayá <guivaya@gmail.com> * Update src/main/Validator.js Co-Authored-By: Guillermo Vayá <guivaya@gmail.com> * Lint fixes * Moved react-smooth-dnd fork to MM org and fixed another merge issue Co-authored-by: mattermod <mattermod@users.noreply.github.com> Co-authored-by: Guillermo Vayá <guivaya@gmail.com>
4
.gitignore
vendored
@@ -14,3 +14,7 @@ test-results.xml
|
|||||||
test_config.json
|
test_config.json
|
||||||
.idea
|
.idea
|
||||||
testUserData
|
testUserData
|
||||||
|
|
||||||
|
src/browser/*.png
|
||||||
|
src/browser/*.svg
|
||||||
|
src/browser/*.woff2
|
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"main_bundle.js",
|
"main_bundle.js",
|
||||||
"browser/**/*{.html,.css,_bundle.js}",
|
"browser/**/*{.html,.css,_bundle.js,.svg,.png}",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"node_modules/bootstrap/dist/**",
|
"node_modules/bootstrap/dist/**",
|
||||||
"node_modules/simple-spellchecker/dict/*.dic"
|
"node_modules/simple-spellchecker/dict/*.dic"
|
||||||
|
1624
package-lock.json
generated
@@ -14,7 +14,8 @@
|
|||||||
"url": "git://github.com/mattermost/desktop.git"
|
"url": "git://github.com/mattermost/desktop.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "electron-builder install-app-deps && npm run extract-dict",
|
"postinstall": "electron-builder install-app-deps && npm run extract-dict && npm run fix-catalina-dark-mode",
|
||||||
|
"fix-catalina-dark-mode": "node scripts/fix_catalina_dark_mode_debug.js",
|
||||||
"extract-dict": "node scripts/extract_dict.js src/node_modules/simple-spellchecker/dict",
|
"extract-dict": "node scripts/extract_dict.js src/node_modules/simple-spellchecker/dict",
|
||||||
"build": "npm-run-all build:*",
|
"build": "npm-run-all build:*",
|
||||||
"build:main": "webpack-cli --bail --config webpack.config.main.js",
|
"build:main": "webpack-cli --bail --config webpack.config.main.js",
|
||||||
@@ -64,11 +65,14 @@
|
|||||||
"eslint-plugin-import": "^2.14.0",
|
"eslint-plugin-import": "^2.14.0",
|
||||||
"eslint-plugin-react": "^7.11.1",
|
"eslint-plugin-react": "^7.11.1",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
|
"image-webpack-loader": "5.0.0",
|
||||||
|
"mdi-react": "^6.2.0",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"mocha-circleci-reporter": "0.0.3",
|
"mocha-circleci-reporter": "0.0.3",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"react": "^16.6.3",
|
"react": "^16.6.3",
|
||||||
"react-dom": "^16.6.3",
|
"react-dom": "^16.6.3",
|
||||||
|
"react-smooth-dnd": "github:mattermost/react-smooth-dnd#af6b471295007274560a375799622c1cd52d678a",
|
||||||
"spectron": "^6.0.0",
|
"spectron": "^6.0.0",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"url-loader": "^1.1.2",
|
"url-loader": "^1.1.2",
|
||||||
|
13
scripts/fix_catalina_dark_mode_debug.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
const {exec} = require('child_process');
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
exec('plutil -insert NSRequiresAquaSystemAppearance -bool NO ./node_modules/electron/dist/Electron.app/Contents/Info.plist', (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
3
src/assets/icon-session-expired.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path fill="#3D3C40" fill-opacity=".7" fill-rule="evenodd" d="M12,4 C16.4152,4 20,7.5848 20,12 C20,16.4152 16.4152,20 12,20 C7.5848,20 4,16.4152 4,12 C4,7.5848 7.5848,4 12,4 Z M12,5 C8.1367,5 5,8.1367 5,12 C5,15.8633 8.1367,19 12,19 C15.8633,19 19,15.8633 19,12 C19,8.1367 15.8633,5 12,5 Z M13,14 L13,16 L11,16 L11,14 L13,14 Z M13,8 L13,13 L11,13 L11,8 L13,8 Z" transform="translate(-4 -4)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 486 B |
BIN
src/assets/linux/dark/MenuIconMentionTemplate.png
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 732 B |
BIN
src/assets/linux/dark/MenuIconTemplate.png
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 649 B |
BIN
src/assets/linux/dark/MenuIconUnreadTemplate.png
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 730 B |
BIN
src/assets/linux/light/MenuIconMentionTemplate.png
Normal file → Executable file
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 768 B |
BIN
src/assets/linux/light/MenuIconTemplate.png
Normal file → Executable file
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 692 B |
BIN
src/assets/linux/light/MenuIconUnreadTemplate.png
Normal file → Executable file
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 766 B |
BIN
src/assets/osx/ClickedMenuIcon.png
Normal file → Executable file
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 447 B |
BIN
src/assets/osx/ClickedMenuIcon@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 598 B After Width: | Height: | Size: 956 B |
BIN
src/assets/osx/ClickedMenuIconMention.png
Normal file → Executable file
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 519 B |
BIN
src/assets/osx/ClickedMenuIconMention@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/osx/ClickedMenuIconUnread.png
Normal file → Executable file
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 513 B |
BIN
src/assets/osx/ClickedMenuIconUnread@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/osx/MenuIcon.png
Normal file → Executable file
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 426 B |
BIN
src/assets/osx/MenuIcon@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 883 B |
BIN
src/assets/osx/MenuIconMention.png
Normal file → Executable file
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 482 B |
BIN
src/assets/osx/MenuIconMention@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 1012 B |
BIN
src/assets/osx/MenuIconUnread.png
Normal file → Executable file
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 478 B |
BIN
src/assets/osx/MenuIconUnread@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 760 B After Width: | Height: | Size: 1001 B |
3
src/assets/titlebar/chrome-close.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.16763 0.00233459L9.98984 0.825763L5.8259 4.99587L9.99767 9.16147L9.17424 9.98368L5.0037 5.8193L0.831506 9.99768L0.0092966 9.17425L4.18027 4.99709L0.00232887 0.82533L0.825757 0.00311899L5.00248 4.17366L9.16763 0.00233459Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 392 B |
3
src/assets/titlebar/chrome-maximize.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0.5" y="0.5" width="9" height="9" stroke="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 163 B |
3
src/assets/titlebar/chrome-minimize.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect y="4" width="10" height="1" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 152 B |
3
src/assets/titlebar/chrome-restore.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 8V10H0V2H2V0H10V8H8ZM7 3H1V9H7V3ZM3 2H8V7H9V1H3V2Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -14,9 +14,6 @@ export default function ErrorView(props) {
|
|||||||
if (!props.active) {
|
if (!props.active) {
|
||||||
classNames.push('ErrorView-hidden');
|
classNames.push('ErrorView-hidden');
|
||||||
}
|
}
|
||||||
if (props.withTab) {
|
|
||||||
classNames.push('ErrorView-with-tab');
|
|
||||||
}
|
|
||||||
function handleClick(event) {
|
function handleClick(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
shell.openExternal(props.errorInfo.validatedURL);
|
shell.openExternal(props.errorInfo.validatedURL);
|
||||||
@@ -84,5 +81,4 @@ ErrorView.propTypes = {
|
|||||||
errorInfo: PropTypes.object,
|
errorInfo: PropTypes.object,
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
withTab: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
@@ -77,7 +77,7 @@ export default class Finder extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div id='finder'>
|
<div id='finder'>
|
||||||
<div className='finder'>
|
<div className={`finder${process.platform === 'darwin' ? ' macOS' : ''}`}>
|
||||||
<div className='finder-input-wrapper'>
|
<div className='finder-input-wrapper'>
|
||||||
<input
|
<input
|
||||||
className='finder-input'
|
className='finder-input'
|
||||||
|
@@ -11,9 +11,15 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {CSSTransition, TransitionGroup} from 'react-transition-group';
|
import {CSSTransition, TransitionGroup} from 'react-transition-group';
|
||||||
import {Grid, Row} from 'react-bootstrap';
|
import {Grid, Row} from 'react-bootstrap';
|
||||||
|
import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon';
|
||||||
|
|
||||||
import {ipcRenderer, remote} from 'electron';
|
import {ipcRenderer, remote} from 'electron';
|
||||||
|
|
||||||
|
import restoreButton from '../../assets/titlebar/chrome-restore.svg';
|
||||||
|
import maximizeButton from '../../assets/titlebar/chrome-maximize.svg';
|
||||||
|
import minimizeButton from '../../assets/titlebar/chrome-minimize.svg';
|
||||||
|
import closeButton from '../../assets/titlebar/chrome-close.svg';
|
||||||
|
|
||||||
import LoginModal from './LoginModal.jsx';
|
import LoginModal from './LoginModal.jsx';
|
||||||
import MattermostView from './MattermostView.jsx';
|
import MattermostView from './MattermostView.jsx';
|
||||||
import TabBar from './TabBar.jsx';
|
import TabBar from './TabBar.jsx';
|
||||||
@@ -33,6 +39,8 @@ export default class MainPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.topBar = React.createRef();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
key,
|
key,
|
||||||
sessionsExpired: new Array(this.props.teams.length),
|
sessionsExpired: new Array(this.props.teams.length),
|
||||||
@@ -42,6 +50,7 @@ export default class MainPage extends React.Component {
|
|||||||
mentionAtActiveCounts: new Array(this.props.teams.length),
|
mentionAtActiveCounts: new Array(this.props.teams.length),
|
||||||
loginQueue: [],
|
loginQueue: [],
|
||||||
targetURL: '',
|
targetURL: '',
|
||||||
|
maximized: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +88,29 @@ export default class MainPage extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
// Due to a bug in Chrome on macOS, mousemove events from the webview won't register when the webview isn't in focus,
|
||||||
|
// thus you can't drag tabs unless you're right on the container.
|
||||||
|
// this makes it so your tab won't get stuck to your cursor no matter where you mouse up
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
self.topBar.current.addEventListener('mouseleave', () => {
|
||||||
|
if (event.target === self.topBar.current) {
|
||||||
|
const upEvent = document.createEvent('MouseEvents');
|
||||||
|
upEvent.initMouseEvent('mouseup');
|
||||||
|
document.dispatchEvent(upEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hack for when it leaves the electron window because apparently mouseleave isn't good enough there...
|
||||||
|
self.topBar.current.addEventListener('mousemove', () => {
|
||||||
|
if (event.clientY === 0 || event.clientX === 0 || event.clientX >= window.innerWidth) {
|
||||||
|
const upEvent = document.createEvent('MouseEvents');
|
||||||
|
upEvent.initMouseEvent('mouseup');
|
||||||
|
document.dispatchEvent(upEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ipcRenderer.on('login-request', (event, request, authInfo) => {
|
ipcRenderer.on('login-request', (event, request, authInfo) => {
|
||||||
self.setState({
|
self.setState({
|
||||||
loginRequired: true,
|
loginRequired: true,
|
||||||
@@ -115,14 +147,32 @@ export default class MainPage extends React.Component {
|
|||||||
function focusListener() {
|
function focusListener() {
|
||||||
self.handleOnTeamFocused(self.state.key);
|
self.handleOnTeamFocused(self.state.key);
|
||||||
self.refs[`mattermostView${self.state.key}`].focusOnWebView();
|
self.refs[`mattermostView${self.state.key}`].focusOnWebView();
|
||||||
|
self.setState({unfocused: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
function blurListener() {
|
||||||
|
self.setState({unfocused: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentWindow = remote.getCurrentWindow();
|
const currentWindow = remote.getCurrentWindow();
|
||||||
currentWindow.on('focus', focusListener);
|
currentWindow.on('focus', focusListener);
|
||||||
|
currentWindow.on('blur', blurListener);
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
currentWindow.removeListener('focus', focusListener);
|
currentWindow.removeListener('focus', focusListener);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (currentWindow.isMaximized()) {
|
||||||
|
self.setState({maximized: true});
|
||||||
|
}
|
||||||
|
currentWindow.on('maximize', this.handleMaximizeState);
|
||||||
|
currentWindow.on('unmaximize', this.handleMaximizeState);
|
||||||
|
|
||||||
|
if (currentWindow.isFullScreen()) {
|
||||||
|
self.setState({fullScreen: true});
|
||||||
|
}
|
||||||
|
currentWindow.on('enter-full-screen', this.handleFullScreenState);
|
||||||
|
currentWindow.on('leave-full-screen', this.handleFullScreenState);
|
||||||
|
|
||||||
// https://github.com/mattermost/desktop/pull/371#issuecomment-263072803
|
// https://github.com/mattermost/desktop/pull/371#issuecomment-263072803
|
||||||
currentWindow.webContents.on('devtools-closed', () => {
|
currentWindow.webContents.on('devtools-closed', () => {
|
||||||
focusListener();
|
focusListener();
|
||||||
@@ -246,6 +296,32 @@ export default class MainPage extends React.Component {
|
|||||||
ipcRenderer.on('toggle-find', () => {
|
ipcRenderer.on('toggle-find', () => {
|
||||||
this.activateFinder(true);
|
this.activateFinder(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
self.setState({
|
||||||
|
isDarkMode: remote.systemPreferences.isDarkMode(),
|
||||||
|
});
|
||||||
|
remote.systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => {
|
||||||
|
self.setState({
|
||||||
|
isDarkMode: remote.systemPreferences.isDarkMode(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.setState({
|
||||||
|
isDarkMode: this.props.getDarkMode(),
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('set-dark-mode', () => {
|
||||||
|
this.setDarkMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.threeDotMenu = React.createRef();
|
||||||
|
ipcRenderer.on('focus-three-dot-menu', () => {
|
||||||
|
if (this.threeDotMenu.current) {
|
||||||
|
this.threeDotMenu.current.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
@@ -254,6 +330,16 @@ export default class MainPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMaximizeState = () => {
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
this.setState({maximized: win.isMaximized()});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFullScreenState = () => {
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
this.setState({fullScreen: win.isFullScreen()});
|
||||||
|
}
|
||||||
|
|
||||||
handleSelect = (key) => {
|
handleSelect = (key) => {
|
||||||
const newKey = (this.props.teams.length + key) % this.props.teams.length;
|
const newKey = (this.props.teams.length + key) % this.props.teams.length;
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -269,6 +355,14 @@ export default class MainPage extends React.Component {
|
|||||||
this.handleOnTeamFocused(newKey);
|
this.handleOnTeamFocused(newKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDragAndDrop = (dropResult) => {
|
||||||
|
const {removedIndex, addedIndex} = dropResult;
|
||||||
|
if (removedIndex !== addedIndex) {
|
||||||
|
const teamIndex = this.props.moveTabs(removedIndex, addedIndex < this.props.teams.length ? addedIndex : this.props.teams.length - 1);
|
||||||
|
this.handleSelect(teamIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleBadgeChange = (index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) => {
|
handleBadgeChange = (index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) => {
|
||||||
const sessionsExpired = this.state.sessionsExpired;
|
const sessionsExpired = this.state.sessionsExpired;
|
||||||
const unreadCounts = this.state.unreadCounts;
|
const unreadCounts = this.state.unreadCounts;
|
||||||
@@ -362,16 +456,52 @@ export default class MainPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClose = () => {
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
win.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMinimize = () => {
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
win.minimize();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMaximize = () => {
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
win.maximize();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRestore = () => {
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
win.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
openMenu = () => {
|
||||||
|
// @eslint-ignore
|
||||||
|
this.threeDotMenu.current.blur();
|
||||||
|
this.props.openMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDoubleClick = () => {
|
||||||
|
const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
|
||||||
|
const win = remote.getCurrentWindow();
|
||||||
|
if (doubleClickAction === 'Minimize') {
|
||||||
|
win.minimize();
|
||||||
|
} else if (doubleClickAction === 'Maximize' && !win.isMaximized()) {
|
||||||
|
win.maximize();
|
||||||
|
} else if (doubleClickAction === 'Maximize' && win.isMaximized()) {
|
||||||
|
win.unmaximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addServer = () => {
|
addServer = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showNewTeamModal: true,
|
showNewTeamModal: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
focusOnWebView = (e) => {
|
focusOnWebView = () => {
|
||||||
if (e.target.className !== 'finder-input') {
|
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
||||||
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activateFinder = () => {
|
activateFinder = () => {
|
||||||
@@ -393,29 +523,116 @@ export default class MainPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDarkMode() {
|
||||||
|
this.setState({
|
||||||
|
isDarkMode: this.props.setDarkMode(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const self = this;
|
const self = this;
|
||||||
let tabsRow;
|
const tabsRow = (
|
||||||
if (this.props.teams.length > 1) {
|
<TabBar
|
||||||
tabsRow = (
|
id='tabBar'
|
||||||
<Row>
|
isDarkMode={this.state.isDarkMode}
|
||||||
<TabBar
|
teams={this.props.teams}
|
||||||
id='tabBar'
|
sessionsExpired={this.state.sessionsExpired}
|
||||||
teams={this.props.teams}
|
unreadCounts={this.state.unreadCounts}
|
||||||
sessionsExpired={this.state.sessionsExpired}
|
mentionCounts={this.state.mentionCounts}
|
||||||
unreadCounts={this.state.unreadCounts}
|
unreadAtActive={this.state.unreadAtActive}
|
||||||
mentionCounts={this.state.mentionCounts}
|
mentionAtActiveCounts={this.state.mentionAtActiveCounts}
|
||||||
unreadAtActive={this.state.unreadAtActive}
|
activeKey={this.state.key}
|
||||||
mentionAtActiveCounts={this.state.mentionAtActiveCounts}
|
onSelect={this.handleSelect}
|
||||||
activeKey={this.state.key}
|
onAddServer={this.addServer}
|
||||||
onSelect={this.handleSelect}
|
showAddServerButton={this.props.showAddServerButton}
|
||||||
onAddServer={this.addServer}
|
onDrop={this.handleDragAndDrop}
|
||||||
showAddServerButton={this.props.showAddServerButton}
|
/>
|
||||||
/>
|
);
|
||||||
</Row>
|
|
||||||
|
let topBarClassName = 'topBar';
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
topBarClassName += ' macOS';
|
||||||
|
}
|
||||||
|
if (this.state.isDarkMode) {
|
||||||
|
topBarClassName += ' darkMode';
|
||||||
|
}
|
||||||
|
if (this.state.fullScreen) {
|
||||||
|
topBarClassName += ' fullScreen';
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxButton;
|
||||||
|
if (this.state.maximized) {
|
||||||
|
maxButton = (
|
||||||
|
<div
|
||||||
|
className='button restore-button'
|
||||||
|
onClick={this.handleRestore}
|
||||||
|
>
|
||||||
|
<img src={restoreButton}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
maxButton = (
|
||||||
|
<div
|
||||||
|
className='button max-button'
|
||||||
|
onClick={this.handleMaximize}
|
||||||
|
>
|
||||||
|
<img src={maximizeButton}/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let overlayGradient;
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
overlayGradient = (
|
||||||
|
<span className='overlay-gradient'/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleBarButtons;
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
titleBarButtons = (
|
||||||
|
<span className='title-bar-btns'>
|
||||||
|
<div
|
||||||
|
className='button min-button'
|
||||||
|
onClick={this.handleMinimize}
|
||||||
|
>
|
||||||
|
<img src={minimizeButton}/>
|
||||||
|
</div>
|
||||||
|
{maxButton}
|
||||||
|
<div
|
||||||
|
className='button close-button'
|
||||||
|
onClick={this.handleClose}
|
||||||
|
>
|
||||||
|
<img src={closeButton}/>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const topRow = (
|
||||||
|
<Row
|
||||||
|
className={topBarClassName}
|
||||||
|
onDoubleClick={this.handleDoubleClick}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={this.topBar}
|
||||||
|
className={`topBar-bg${this.state.unfocused ? ' unfocused' : ''}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className='three-dot-menu'
|
||||||
|
onClick={this.openMenu}
|
||||||
|
tabIndex={0}
|
||||||
|
ref={this.threeDotMenu}
|
||||||
|
>
|
||||||
|
<DotsVerticalIcon/>
|
||||||
|
</button>
|
||||||
|
{tabsRow}
|
||||||
|
{overlayGradient}
|
||||||
|
{titleBarButtons}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
|
||||||
const views = this.props.teams.map((team, index) => {
|
const views = this.props.teams.map((team, index) => {
|
||||||
function handleBadgeChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) {
|
function handleBadgeChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) {
|
||||||
self.handleBadgeChange(index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned);
|
self.handleBadgeChange(index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned);
|
||||||
@@ -439,7 +656,7 @@ export default class MainPage extends React.Component {
|
|||||||
<MattermostView
|
<MattermostView
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
withTab={this.props.teams.length > 1}
|
|
||||||
useSpellChecker={this.props.useSpellChecker}
|
useSpellChecker={this.props.useSpellChecker}
|
||||||
onSelectSpellCheckerLocale={this.props.onSelectSpellCheckerLocale}
|
onSelectSpellCheckerLocale={this.props.onSelectSpellCheckerLocale}
|
||||||
src={teamUrl}
|
src={teamUrl}
|
||||||
@@ -467,7 +684,9 @@ export default class MainPage extends React.Component {
|
|||||||
}
|
}
|
||||||
const modal = (
|
const modal = (
|
||||||
<NewTeamModal
|
<NewTeamModal
|
||||||
|
currentOrder={this.props.teams.length}
|
||||||
show={this.state.showNewTeamModal}
|
show={this.state.showNewTeamModal}
|
||||||
|
restoreFocus={false}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showNewTeamModal: false,
|
showNewTeamModal: false,
|
||||||
@@ -498,7 +717,7 @@ export default class MainPage extends React.Component {
|
|||||||
onCancel={this.handleLoginCancel}
|
onCancel={this.handleLoginCancel}
|
||||||
/>
|
/>
|
||||||
<Grid fluid={true}>
|
<Grid fluid={true}>
|
||||||
{ tabsRow }
|
{ topRow }
|
||||||
{ viewsRow }
|
{ viewsRow }
|
||||||
{ this.state.finderVisible ? (
|
{ this.state.finderVisible ? (
|
||||||
<Finder
|
<Finder
|
||||||
@@ -540,6 +759,10 @@ MainPage.propTypes = {
|
|||||||
onSelectSpellCheckerLocale: PropTypes.func.isRequired,
|
onSelectSpellCheckerLocale: PropTypes.func.isRequired,
|
||||||
deeplinkingUrl: PropTypes.string,
|
deeplinkingUrl: PropTypes.string,
|
||||||
showAddServerButton: PropTypes.bool.isRequired,
|
showAddServerButton: PropTypes.bool.isRequired,
|
||||||
|
getDarkMode: PropTypes.func.isRequired,
|
||||||
|
setDarkMode: PropTypes.func.isRequired,
|
||||||
|
moveTabs: PropTypes.func.isRequired,
|
||||||
|
openMenu: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* eslint-enable react/no-set-state */
|
/* eslint-enable react/no-set-state */
|
||||||
|
@@ -179,6 +179,12 @@ export default class MattermostView extends React.Component {
|
|||||||
case 'onNotificationClick':
|
case 'onNotificationClick':
|
||||||
self.props.onNotificationClick();
|
self.props.onNotificationClick();
|
||||||
break;
|
break;
|
||||||
|
case 'mouse-move':
|
||||||
|
this.handleMouseMove(event.args[0]);
|
||||||
|
break;
|
||||||
|
case 'mouse-up':
|
||||||
|
this.handleMouseUp();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -243,12 +249,24 @@ export default class MattermostView extends React.Component {
|
|||||||
focusOnWebView = () => {
|
focusOnWebView = () => {
|
||||||
const webview = this.webviewRef.current;
|
const webview = this.webviewRef.current;
|
||||||
const webContents = webview.getWebContents(); // webContents might not be created yet.
|
const webContents = webview.getWebContents(); // webContents might not be created yet.
|
||||||
if (webContents && !webContents.isFocused()) {
|
if (webContents) {
|
||||||
webview.focus();
|
webview.focus();
|
||||||
webContents.focus();
|
webContents.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseMove = (event) => {
|
||||||
|
const moveEvent = document.createEvent('MouseEvents');
|
||||||
|
moveEvent.initMouseEvent('mousemove', null, null, null, null, null, null, event.clientX, event.clientY);
|
||||||
|
document.dispatchEvent(moveEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp = () => {
|
||||||
|
const upEvent = document.createEvent('MouseEvents');
|
||||||
|
upEvent.initMouseEvent('mouseup');
|
||||||
|
document.dispatchEvent(upEvent);
|
||||||
|
}
|
||||||
|
|
||||||
canGoBack = () => {
|
canGoBack = () => {
|
||||||
const webview = this.webviewRef.current;
|
const webview = this.webviewRef.current;
|
||||||
return webview.getWebContents().canGoBack();
|
return webview.getWebContents().canGoBack();
|
||||||
@@ -301,7 +319,6 @@ export default class MattermostView extends React.Component {
|
|||||||
className='errorView'
|
className='errorView'
|
||||||
errorInfo={this.state.errorInfo}
|
errorInfo={this.state.errorInfo}
|
||||||
active={this.props.active}
|
active={this.props.active}
|
||||||
withTab={this.props.withTab}
|
|
||||||
/>) : null;
|
/>) : null;
|
||||||
|
|
||||||
// Need to keep webview mounted when failed to load.
|
// Need to keep webview mounted when failed to load.
|
||||||
@@ -309,7 +326,7 @@ export default class MattermostView extends React.Component {
|
|||||||
if (this.props.withTab) {
|
if (this.props.withTab) {
|
||||||
classNames.push('mattermostView-with-tab');
|
classNames.push('mattermostView-with-tab');
|
||||||
}
|
}
|
||||||
if (!this.props.active) {
|
if (!this.props.active || this.state.errorInfo) {
|
||||||
classNames.push('mattermostView-hidden');
|
classNames.push('mattermostView-hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,11 +359,11 @@ export default class MattermostView extends React.Component {
|
|||||||
MattermostView.propTypes = {
|
MattermostView.propTypes = {
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
withTab: PropTypes.bool,
|
||||||
onTargetURLChange: PropTypes.func,
|
onTargetURLChange: PropTypes.func,
|
||||||
onBadgeChange: PropTypes.func,
|
onBadgeChange: PropTypes.func,
|
||||||
src: PropTypes.string,
|
src: PropTypes.string,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
withTab: PropTypes.bool,
|
|
||||||
useSpellChecker: PropTypes.bool,
|
useSpellChecker: PropTypes.bool,
|
||||||
onSelectSpellCheckerLocale: PropTypes.func,
|
onSelectSpellCheckerLocale: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@@ -9,13 +9,18 @@ import {Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock} from 're
|
|||||||
import Utils from '../../utils/util';
|
import Utils from '../../utils/util';
|
||||||
|
|
||||||
export default class NewTeamModal extends React.Component {
|
export default class NewTeamModal extends React.Component {
|
||||||
constructor() {
|
static defaultProps = {
|
||||||
super();
|
restoreFocus: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.wasShown = false;
|
this.wasShown = false;
|
||||||
this.state = {
|
this.state = {
|
||||||
teamName: '',
|
teamName: '',
|
||||||
teamUrl: '',
|
teamUrl: '',
|
||||||
|
teamOrder: props.currentOrder || 0,
|
||||||
saveStarted: false,
|
saveStarted: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -25,6 +30,7 @@ export default class NewTeamModal extends React.Component {
|
|||||||
teamName: this.props.team ? this.props.team.name : '',
|
teamName: this.props.team ? this.props.team.name : '',
|
||||||
teamUrl: this.props.team ? this.props.team.url : '',
|
teamUrl: this.props.team ? this.props.team.url : '',
|
||||||
teamIndex: this.props.team ? this.props.team.index : false,
|
teamIndex: this.props.team ? this.props.team.index : false,
|
||||||
|
teamOrder: this.props.team ? this.props.team.order : (this.props.currentOrder || 0),
|
||||||
saveStarted: false,
|
saveStarted: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -100,6 +106,7 @@ export default class NewTeamModal extends React.Component {
|
|||||||
url: this.state.teamUrl,
|
url: this.state.teamUrl,
|
||||||
name: this.state.teamName,
|
name: this.state.teamName,
|
||||||
index: this.state.teamIndex,
|
index: this.state.teamIndex,
|
||||||
|
order: this.state.teamOrder,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -132,6 +139,8 @@ export default class NewTeamModal extends React.Component {
|
|||||||
show={this.props.show}
|
show={this.props.show}
|
||||||
id='newServerModal'
|
id='newServerModal'
|
||||||
onHide={this.props.onClose}
|
onHide={this.props.onClose}
|
||||||
|
container={this.props.modalContainer}
|
||||||
|
restoreFocus={this.props.restoreFocus}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
@@ -221,4 +230,7 @@ NewTeamModal.propTypes = {
|
|||||||
team: PropTypes.object,
|
team: PropTypes.object,
|
||||||
editMode: PropTypes.bool,
|
editMode: PropTypes.bool,
|
||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
|
modalContainer: PropTypes.object,
|
||||||
|
restoreFocus: PropTypes.bool,
|
||||||
|
currentOrder: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
@@ -342,6 +342,7 @@ export default class SettingsPage extends React.Component {
|
|||||||
onTeamClick={(index) => {
|
onTeamClick={(index) => {
|
||||||
backToIndex(index + this.state.buildTeams.length + this.state.registryTeams.length);
|
backToIndex(index + this.state.buildTeams.length + this.state.registryTeams.length);
|
||||||
}}
|
}}
|
||||||
|
modalContainer={this}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -605,7 +606,7 @@ export default class SettingsPage extends React.Component {
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='modal-container'>
|
||||||
<Navbar
|
<Navbar
|
||||||
className='navbar-fixed-top'
|
className='navbar-fixed-top'
|
||||||
style={settingsPage.navbar}
|
style={settingsPage.navbar}
|
||||||
|
@@ -2,12 +2,17 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {remote} from 'electron';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Glyphicon, Nav, NavItem} from 'react-bootstrap';
|
import {Nav, NavItem} from 'react-bootstrap';
|
||||||
|
import {Container, Draggable} from 'react-smooth-dnd';
|
||||||
|
import PlusIcon from 'mdi-react/PlusIcon';
|
||||||
|
|
||||||
export default class TabBar extends React.Component { // need "this"
|
export default class TabBar extends React.Component { // need "this"
|
||||||
render() {
|
render() {
|
||||||
const tabs = this.props.teams.map((team, index) => {
|
const orderedTabs = this.props.teams.concat().sort((a, b) => a.order - b.order);
|
||||||
|
const tabs = orderedTabs.map((team) => {
|
||||||
|
const index = this.props.teams.indexOf(team);
|
||||||
const sessionExpired = this.props.sessionsExpired[index];
|
const sessionExpired = this.props.sessionsExpired[index];
|
||||||
|
|
||||||
let unreadCount = 0;
|
let unreadCount = 0;
|
||||||
@@ -29,7 +34,7 @@ export default class TabBar extends React.Component { // need "this"
|
|||||||
let badgeDiv;
|
let badgeDiv;
|
||||||
if (sessionExpired) {
|
if (sessionExpired) {
|
||||||
badgeDiv = (
|
badgeDiv = (
|
||||||
<div className='TabBar-badge TabBar-badge-nomention'/>
|
<div className='TabBar-expired'/>
|
||||||
);
|
);
|
||||||
} else if (mentionCount !== 0) {
|
} else if (mentionCount !== 0) {
|
||||||
badgeDiv = (
|
badgeDiv = (
|
||||||
@@ -37,29 +42,45 @@ export default class TabBar extends React.Component { // need "this"
|
|||||||
{mentionCount}
|
{mentionCount}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (unreadCount !== 0) {
|
||||||
|
badgeDiv = (
|
||||||
|
<div className='TabBar-dot'/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const id = 'teamTabItem' + index;
|
|
||||||
|
|
||||||
// draggable=false is a workaround for https://github.com/mattermost/desktop/issues/667
|
const id = `teamTabItem${index}`;
|
||||||
// It would obstruct https://github.com/mattermost/desktop/issues/478
|
const navItem = () => (
|
||||||
return (
|
|
||||||
<NavItem
|
<NavItem
|
||||||
className='teamTabItem'
|
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
eventKey={index}
|
eventKey={index}
|
||||||
ref={id}
|
|
||||||
draggable={false}
|
draggable={false}
|
||||||
|
ref={id}
|
||||||
|
active={this.props.activeKey === index}
|
||||||
|
activeKey={this.props.activeKey}
|
||||||
|
onMouseDown={() => {
|
||||||
|
this.props.onSelect(index);
|
||||||
|
}}
|
||||||
|
onSelect={() => {
|
||||||
|
this.props.onSelect(index);
|
||||||
|
}}
|
||||||
|
title={team.name}
|
||||||
>
|
>
|
||||||
<span
|
<div className='TabBar-tabSeperator'>
|
||||||
title={team.name}
|
<span>
|
||||||
className={unreadCount === 0 ? '' : 'teamTabItem-unread'}
|
{team.name}
|
||||||
>
|
</span>
|
||||||
{team.name}
|
{ badgeDiv }
|
||||||
</span>
|
</div>
|
||||||
{ ' ' }
|
</NavItem>
|
||||||
{ badgeDiv }
|
);
|
||||||
</NavItem>);
|
|
||||||
|
return (
|
||||||
|
<Draggable
|
||||||
|
key={id}
|
||||||
|
render={navItem}
|
||||||
|
className='teamTabItem'
|
||||||
|
/>);
|
||||||
});
|
});
|
||||||
if (this.props.showAddServerButton === true) {
|
if (this.props.showAddServerButton === true) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
@@ -68,36 +89,50 @@ export default class TabBar extends React.Component { // need "this"
|
|||||||
key='addServerButton'
|
key='addServerButton'
|
||||||
id='addServerButton'
|
id='addServerButton'
|
||||||
eventKey='addServerButton'
|
eventKey='addServerButton'
|
||||||
title='Add new server'
|
|
||||||
draggable={false}
|
draggable={false}
|
||||||
|
title='Add new server'
|
||||||
|
activeKey={this.props.activeKey}
|
||||||
|
onSelect={() => {
|
||||||
|
this.props.onAddServer();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Glyphicon glyph='plus'/>
|
<div className='TabBar-tabSeperator'>
|
||||||
|
<PlusIcon size={20}/>
|
||||||
|
</div>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
|
const navContainer = (ref) => (
|
||||||
<Nav
|
<Nav
|
||||||
className='TabBar'
|
ref={ref}
|
||||||
|
className={`smooth-dnd-container TabBar${this.props.isDarkMode ? ' darkMode' : ''}`}
|
||||||
id={this.props.id}
|
id={this.props.id}
|
||||||
bsStyle='tabs'
|
bsStyle='tabs'
|
||||||
activeKey={this.props.activeKey}
|
|
||||||
onSelect={(eventKey) => {
|
|
||||||
if (eventKey === 'addServerButton') {
|
|
||||||
this.props.onAddServer();
|
|
||||||
} else {
|
|
||||||
this.props.onSelect(eventKey);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{ tabs }
|
{ tabs }
|
||||||
</Nav>
|
</Nav>
|
||||||
);
|
);
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
ref={this.container}
|
||||||
|
render={navContainer}
|
||||||
|
orientation='horizontal'
|
||||||
|
lockAxis={'x'}
|
||||||
|
onDrop={this.props.onDrop}
|
||||||
|
animationDuration={300}
|
||||||
|
shouldAcceptDrop={() => {
|
||||||
|
return !(remote.getCurrentWindow().registryConfigData.teams && remote.getCurrentWindow().registryConfigData.teams.length > 0);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TabBar.propTypes = {
|
TabBar.propTypes = {
|
||||||
activeKey: PropTypes.number,
|
activeKey: PropTypes.number,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
isDarkMode: PropTypes.bool,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
teams: PropTypes.array,
|
teams: PropTypes.array,
|
||||||
sessionsExpired: PropTypes.array,
|
sessionsExpired: PropTypes.array,
|
||||||
@@ -107,4 +142,5 @@ TabBar.propTypes = {
|
|||||||
mentionAtActiveCounts: PropTypes.array,
|
mentionAtActiveCounts: PropTypes.array,
|
||||||
showAddServerButton: PropTypes.bool,
|
showAddServerButton: PropTypes.bool,
|
||||||
onAddServer: PropTypes.func,
|
onAddServer: PropTypes.func,
|
||||||
|
onDrop: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@@ -20,6 +20,7 @@ export default class TeamList extends React.Component {
|
|||||||
url: '',
|
url: '',
|
||||||
name: '',
|
name: '',
|
||||||
index: false,
|
index: false,
|
||||||
|
order: props.teams.length,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -27,7 +28,13 @@ export default class TeamList extends React.Component {
|
|||||||
handleTeamRemove = (index) => {
|
handleTeamRemove = (index) => {
|
||||||
console.log(index);
|
console.log(index);
|
||||||
const teams = this.props.teams;
|
const teams = this.props.teams;
|
||||||
|
const removedOrder = this.props.teams[index].order;
|
||||||
teams.splice(index, 1);
|
teams.splice(index, 1);
|
||||||
|
teams.forEach((value) => {
|
||||||
|
if (value.order > removedOrder) {
|
||||||
|
value.order--;
|
||||||
|
}
|
||||||
|
});
|
||||||
this.props.onTeamsChange(teams);
|
this.props.onTeamsChange(teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +45,7 @@ export default class TeamList extends React.Component {
|
|||||||
if ((typeof team.index !== 'undefined') && teams[team.index]) {
|
if ((typeof team.index !== 'undefined') && teams[team.index]) {
|
||||||
teams[team.index].name = team.name;
|
teams[team.index].name = team.name;
|
||||||
teams[team.index].url = team.url;
|
teams[team.index].url = team.url;
|
||||||
|
teams[team.index].order = team.order;
|
||||||
} else {
|
} else {
|
||||||
teams.push(team);
|
teams.push(team);
|
||||||
}
|
}
|
||||||
@@ -48,19 +56,21 @@ export default class TeamList extends React.Component {
|
|||||||
url: '',
|
url: '',
|
||||||
name: '',
|
name: '',
|
||||||
index: false,
|
index: false,
|
||||||
|
order: teams.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onTeamsChange(teams);
|
this.props.onTeamsChange(teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTeamEditing = (teamName, teamUrl, teamIndex) => {
|
handleTeamEditing = (teamName, teamUrl, teamIndex, teamOrder) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showEditTeamForm: true,
|
showEditTeamForm: true,
|
||||||
team: {
|
team: {
|
||||||
url: teamUrl,
|
url: teamUrl,
|
||||||
name: teamName,
|
name: teamName,
|
||||||
index: teamIndex,
|
index: teamIndex,
|
||||||
|
order: teamOrder,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -83,7 +93,7 @@ export default class TeamList extends React.Component {
|
|||||||
|
|
||||||
function handleTeamEditing() {
|
function handleTeamEditing() {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
self.handleTeamEditing(team.name, team.url, i);
|
self.handleTeamEditing(team.name, team.url, i, team.order);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTeamClick() {
|
function handleTeamClick() {
|
||||||
@@ -105,6 +115,7 @@ export default class TeamList extends React.Component {
|
|||||||
|
|
||||||
const addServerForm = (
|
const addServerForm = (
|
||||||
<NewTeamModal
|
<NewTeamModal
|
||||||
|
currentOrder={this.props.teams.length}
|
||||||
show={this.props.showAddTeamForm || this.state.showEditTeamForm}
|
show={this.props.showAddTeamForm || this.state.showEditTeamForm}
|
||||||
editMode={this.state.showEditTeamForm}
|
editMode={this.state.showEditTeamForm}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -114,6 +125,7 @@ export default class TeamList extends React.Component {
|
|||||||
name: '',
|
name: '',
|
||||||
url: '',
|
url: '',
|
||||||
index: false,
|
index: false,
|
||||||
|
order: this.props.teams.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.props.setAddTeamFormVisibility(false);
|
this.props.setAddTeamFormVisibility(false);
|
||||||
@@ -122,6 +134,7 @@ export default class TeamList extends React.Component {
|
|||||||
const teamData = {
|
const teamData = {
|
||||||
name: newTeam.name,
|
name: newTeam.name,
|
||||||
url: newTeam.url,
|
url: newTeam.url,
|
||||||
|
order: newTeam.order,
|
||||||
};
|
};
|
||||||
if (this.props.showAddTeamForm) {
|
if (this.props.showAddTeamForm) {
|
||||||
this.props.addServer(teamData);
|
this.props.addServer(teamData);
|
||||||
@@ -135,12 +148,14 @@ export default class TeamList extends React.Component {
|
|||||||
name: '',
|
name: '',
|
||||||
url: '',
|
url: '',
|
||||||
index: false,
|
index: false,
|
||||||
|
order: newTeam.order + 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.render();
|
this.render();
|
||||||
this.props.setAddTeamFormVisibility(false);
|
this.props.setAddTeamFormVisibility(false);
|
||||||
}}
|
}}
|
||||||
team={this.state.team}
|
team={this.state.team}
|
||||||
|
modalContainer={this.props.modalContainer}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
const removeServer = this.props.teams[this.state.indexToRemoveServer];
|
const removeServer = this.props.teams[this.state.indexToRemoveServer];
|
||||||
@@ -154,6 +169,7 @@ export default class TeamList extends React.Component {
|
|||||||
this.handleTeamRemove(this.state.indexToRemoveServer);
|
this.handleTeamRemove(this.state.indexToRemoveServer);
|
||||||
this.closeServerRemoveModal();
|
this.closeServerRemoveModal();
|
||||||
}}
|
}}
|
||||||
|
modalContainer={this.props.modalContainer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -176,4 +192,5 @@ TeamList.propTypes = {
|
|||||||
toggleAddTeamForm: PropTypes.func,
|
toggleAddTeamForm: PropTypes.func,
|
||||||
setAddTeamFormVisibility: PropTypes.func,
|
setAddTeamFormVisibility: PropTypes.func,
|
||||||
onTeamClick: PropTypes.func,
|
onTeamClick: PropTypes.func,
|
||||||
|
modalContainer: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
.ErrorView {
|
.ErrorView {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 32px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ErrorView-with-tab {
|
|
||||||
top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ErrorView-hidden {
|
.ErrorView-hidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
border: none;
|
border: none;
|
||||||
background: #d2d2d2;
|
background: #d2d2d2;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ div[id*="-permissionDialog"] {
|
|||||||
.finder {
|
.finder {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 20px;
|
right: 200px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
border: 1px solid #d7d7d7;
|
border: 1px solid #d7d7d7;
|
||||||
@@ -24,4 +24,9 @@ div[id*="-permissionDialog"] {
|
|||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 5px;
|
||||||
border-bottom-right-radius: 5px;
|
border-bottom-right-radius: 5px;
|
||||||
font-size: 0px;
|
font-size: 0px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finder.macOS {
|
||||||
|
right: 20px;
|
||||||
}
|
}
|
||||||
|
@@ -8,16 +8,12 @@
|
|||||||
|
|
||||||
.mattermostView webview, .mattermostView .mattermostView-loadingScreen {
|
.mattermostView webview, .mattermostView .mattermostView-loadingScreen {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 40px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mattermostView-with-tab webview, .mattermostView-with-tab .mattermostView-loadingScreen {
|
|
||||||
top: 31px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mattermostView-hidden webview {
|
.mattermostView-hidden webview {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
@@ -1,27 +1,189 @@
|
|||||||
|
.TabBar {
|
||||||
|
border: none;
|
||||||
|
max-height: 36px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode {
|
||||||
|
background-color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
.TabBar .teamTabItem span {
|
.TabBar .teamTabItem span {
|
||||||
display: inline-block;
|
flex: 0 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 170px;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
min-width: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabBar>li>a {
|
.TabBar>li>a {
|
||||||
background: rgba(0, 0, 0, 0.05);
|
border: none;
|
||||||
border-radius: 2px 2px 0 0;
|
border-radius: 0;
|
||||||
border: 1px solid #ddd;
|
height: 32px;
|
||||||
color: #888;
|
max-height: 32px;
|
||||||
height: 31px;
|
line-height: 16px;
|
||||||
line-height: 29px;
|
|
||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
margin-top: 0px;
|
margin-top: 4px;
|
||||||
padding: 0 15px;
|
padding: 6px 0;
|
||||||
|
color: rgba(61,60,64,0.7);
|
||||||
|
font-family: Arial;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: -0.2px;
|
||||||
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabBar>li.teamTabItem:not(.active)>a:hover {
|
.TabBar.darkMode>li>a {
|
||||||
background-color: #e6e6e6;
|
color: rgba(243,243,243,0.7);
|
||||||
border: 1px solid #ddd;
|
}
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
|
.TabBar>li>a:hover {
|
||||||
|
background-color: rgba(255,255,255,0.4);
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li>a:hover {
|
||||||
|
background-color: rgba(50, 54, 57, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li>a:focus {
|
||||||
|
background-color: #fff;
|
||||||
|
color: rgba(61,60,64,1);
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li>a:focus {
|
||||||
|
background-color: #323639;
|
||||||
|
color: rgba(243,243,243,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li:before, .TabBar>li:after {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -1px;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
content: "";
|
||||||
|
background-color: inherit;
|
||||||
|
z-index: 9;
|
||||||
|
flex: 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem.active:before, .TabBar>li.teamTabItem.smooth-dnd-ghost:before {
|
||||||
|
left: -4px;
|
||||||
|
border-bottom-right-radius: 6px;
|
||||||
|
border-right: 2px solid #fff;
|
||||||
|
border-bottom: 2px solid #fff;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li.teamTabItem.active:before, .TabBar.darkMode>li.teamTabItem.smooth-dnd-ghost:before {
|
||||||
|
border-right: 2px solid #323639;
|
||||||
|
border-bottom: 2px solid #323639;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem.active:after, .TabBar>li.teamTabItem.smooth-dnd-ghost:after {
|
||||||
|
border-bottom-left-radius: 6px;
|
||||||
|
right: -5px;
|
||||||
|
border-left: 2px solid #fff;
|
||||||
|
border-bottom: 2px solid #fff;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li.teamTabItem.active:after, .TabBar.darkMode>li.teamTabItem.smooth-dnd-ghost:after {
|
||||||
|
border-left: 2px solid #323639;
|
||||||
|
border-bottom: 2px solid #323639;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li>a>div.TabBar-tabSeperator {
|
||||||
|
padding: 2px 16px;
|
||||||
|
max-height: 20px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.TabBar-addServerButton{
|
||||||
|
transition: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.TabBar-addServerButton>a{
|
||||||
|
color: rgba(61,60,64,0.7);
|
||||||
|
transition: opacity 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.TabBar-addServerButton svg{
|
||||||
|
margin: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li.TabBar-addServerButton>a{
|
||||||
|
color: rgba(243,243,243,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem.active>a, .TabBar>li.teamTabItem.smooth-dnd-ghost>a {
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
color: rgba(61,60,64,1);
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smooth-dnd-no-user-select li.TabBar-addServerButton>a {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li.teamTabItem.active>a, .TabBar.darkMode>li.teamTabItem.smooth-dnd-ghost>a {
|
||||||
|
color: #f3f3f3;
|
||||||
|
background-color: #323639;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem:not(.active)+.TabBar-addServerButton>a>div.TabBar-tabSeperator {
|
||||||
|
border-left: 1px solid rgba(61,60,64,0.2);
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem:not(.active)+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||||
|
border-left: 1px solid rgba(61,60,64,0.2);
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li.teamTabItem:not(.active)+.TabBar-addServerButton>a>div.TabBar-tabSeperator {
|
||||||
|
border-left: 1px solid rgba(243,243,243,0.2);
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode>li.teamTabItem:not(.active)+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||||
|
border-left: 1px solid rgba(243,243,243,0.2);
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem:not(.active):hover+.TabBar-addServerButton>a>div.TabBar-tabSeperator {
|
||||||
|
border-left: none;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem:not(.active):hover+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||||
|
border-left: none;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem:not(.active)+.TabBar-addServerButton>a:hover>div.TabBar-tabSeperator {
|
||||||
|
border-left: none;
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar>li.teamTabItem:not(.active)+li.teamTabItem:not(.active)>a:hover>div.TabBar-tabSeperator {
|
||||||
|
border-left: none;
|
||||||
|
margin-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabBar .TabBar-addServerButton>a {
|
.TabBar .TabBar-addServerButton>a {
|
||||||
@@ -29,34 +191,59 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 31px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabBar .TabBar-addServerButton>a:hover {
|
.TabBar.darkMode .TabBar-addServerButton>a {
|
||||||
color: #333;
|
color: rgba(243,243,243,0.7);
|
||||||
background-color: #e6e6e6;
|
}
|
||||||
border-color: #adadad;
|
|
||||||
transition: background-color 0.2s ease;
|
.TabBar .TabBar-dot {
|
||||||
|
background: #579EFF;
|
||||||
|
float: right;
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex: 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar .TabBar-expired {
|
||||||
|
float: right;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-left: 8px;
|
||||||
|
background-image: url(../../../assets/icon-session-expired.svg);
|
||||||
|
flex: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TabBar.darkMode .TabBar-expired {
|
||||||
|
filter: invert(100%);
|
||||||
|
-webkit-filter: invert(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabBar .TabBar-badge {
|
.TabBar .TabBar-badge {
|
||||||
background: #FF1744;
|
background: #CB2431;
|
||||||
float: right;
|
float: right;
|
||||||
color: white;
|
color: white;
|
||||||
min-width: 18px;
|
font-size: 11px;
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 20px;
|
line-height: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-left: 5px;
|
margin-left: 8px;
|
||||||
margin-top: 5px;
|
border-radius: 100px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
border-radius: 9px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
font-weight: 600;
|
font-weight: bold;
|
||||||
|
min-width: 18px;
|
||||||
|
margin-top: -1px;
|
||||||
|
letter-spacing: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
flex: 1 0 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TabBar .TabBar-badge.TabBar-badge-nomention:after {
|
.TabBar .TabBar-badge.TabBar-badge-nomention:after {
|
||||||
|
@@ -5,5 +5,4 @@
|
|||||||
.TeamListItem-left {
|
.TeamListItem-left {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: calc(100% - 100px);
|
width: calc(100% - 100px);
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,19 @@
|
|||||||
@import url("components/index.css");
|
@import url("components/index.css");
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.hovering-enter {
|
.hovering-enter {
|
||||||
opacity: 0.01;
|
opacity: 0.01;
|
||||||
}
|
}
|
||||||
@@ -26,3 +40,177 @@
|
|||||||
.modal-error {
|
.modal-error {
|
||||||
color: #a94442;
|
color: #a94442;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topBar {
|
||||||
|
height: 40px;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar>.topBar-bg {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
height: 36px;
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar>.topBar-bg.unfocused {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode {
|
||||||
|
background-color: #323639;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode>.topBar-bg {
|
||||||
|
background-color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .three-dot-menu {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
height: 36px;
|
||||||
|
float: left;
|
||||||
|
padding-top: 5px;
|
||||||
|
border: none;
|
||||||
|
flex: 0 0 40px;
|
||||||
|
z-index: 9;
|
||||||
|
color: rgba(61,60,64,0.7);
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
background-color: rgba(229, 229, 229, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .three-dot-menu svg {
|
||||||
|
border-radius: 100px;
|
||||||
|
padding: 4px;
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .three-dot-menu:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .three-dot-menu:hover svg, .topBar .three-dot-menu:focus svg, .topBar .three-dot-menu:active svg {
|
||||||
|
outline: none;
|
||||||
|
background-color: #c8c8c8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .three-dot-menu:hover svg, .topBar.darkMode .three-dot-menu:focus svg, .topBar.darkMode .three-dot-menu:active svg {
|
||||||
|
background-color: #383A3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.macOS .three-dot-menu:hover svg, .topBar.macOS .three-dot-menu:focus svg, .topBar.macOS .three-dot-menu:active svg {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.macOS .three-dot-menu {
|
||||||
|
flex-basis: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.macOS.fullScreen .three-dot-menu {
|
||||||
|
flex-basis: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.macOS .three-dot-menu>svg {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .three-dot-menu {
|
||||||
|
background-color: #202124;
|
||||||
|
color: rgba(243,243,243,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .title-bar-btns {
|
||||||
|
color: rgba(243,243,243,0.7);
|
||||||
|
background-color: #202124;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns {
|
||||||
|
position: relative;
|
||||||
|
line-height: 36px;
|
||||||
|
height: 36px;
|
||||||
|
z-index: 9;
|
||||||
|
color: rgba(61,60,64,0.7);
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 46px);
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.button {
|
||||||
|
grid-row: 1 / span 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .title-bar-btns>.button:hover {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .title-bar-btns>.button:active {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.button:hover {
|
||||||
|
background: rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.button:active {
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.close-button:hover {
|
||||||
|
background: #E81123 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.close-button:hover>img {
|
||||||
|
filter: invert(100%);
|
||||||
|
-webkit-filter: invert(100%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.close-button:active {
|
||||||
|
background: #f1707a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.close-button:active>img {
|
||||||
|
filter: invert(100%);
|
||||||
|
-webkit-filter: invert(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns img {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .title-bar-btns img {
|
||||||
|
filter: invert(100%);
|
||||||
|
-webkit-filter: invert(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.min-button {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
.topBar .title-bar-btns>.max-button, .topBar .title-bar-btns>.restore-button {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .title-bar-btns>.close-button {
|
||||||
|
grid-column: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar .overlay-gradient {
|
||||||
|
flex: 0 0 40px;
|
||||||
|
z-index: 9;
|
||||||
|
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, #e5e5e5 100%);
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBar.darkMode .overlay-gradient {
|
||||||
|
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, #202124 100%);
|
||||||
|
}
|
@@ -26,6 +26,14 @@ const config = new Config(remote.app.getPath('userData') + '/config.json', remot
|
|||||||
|
|
||||||
const teams = config.teams;
|
const teams = config.teams;
|
||||||
|
|
||||||
|
// Make sure teams have an order
|
||||||
|
if (teams.every((team) => !team.order)) {
|
||||||
|
teams.forEach((team, index) => {
|
||||||
|
team.order = index;
|
||||||
|
});
|
||||||
|
teamConfigChange(teams);
|
||||||
|
}
|
||||||
|
|
||||||
remote.getCurrentWindow().removeAllListeners('focus');
|
remote.getCurrentWindow().removeAllListeners('focus');
|
||||||
|
|
||||||
if (teams.length === 0) {
|
if (teams.length === 0) {
|
||||||
@@ -33,7 +41,7 @@ if (teams.length === 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parsedURL = url.parse(window.location.href, true);
|
const parsedURL = url.parse(window.location.href, true);
|
||||||
const initialIndex = parsedURL.query.index ? parseInt(parsedURL.query.index, 10) : 0;
|
const initialIndex = parsedURL.query.index ? parseInt(parsedURL.query.index, 10) : getInitialIndex();
|
||||||
|
|
||||||
let deeplinkingUrl = null;
|
let deeplinkingUrl = null;
|
||||||
if (!parsedURL.query.index || parsedURL.query.index === null) {
|
if (!parsedURL.query.index || parsedURL.query.index === null) {
|
||||||
@@ -52,6 +60,11 @@ ipcRenderer.on('reload-config', () => {
|
|||||||
config.reload();
|
config.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getInitialIndex() {
|
||||||
|
const element = teams.find((e) => e.order === 0);
|
||||||
|
return element ? teams.indexOf(element) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
function showBadgeWindows(sessionExpired, unreadCount, mentionCount) {
|
function showBadgeWindows(sessionExpired, unreadCount, mentionCount) {
|
||||||
function sendBadge(dataURL, description) {
|
function sendBadge(dataURL, description) {
|
||||||
// window.setOverlayIcon() does't work with NativeImage across remote boundaries.
|
// window.setOverlayIcon() does't work with NativeImage across remote boundaries.
|
||||||
@@ -136,6 +149,50 @@ function handleSelectSpellCheckerLocale(locale) {
|
|||||||
ipcRenderer.send('update-dict', locale);
|
ipcRenderer.send('update-dict', locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveTabs(originalOrder, newOrder) {
|
||||||
|
const tabOrder = teams.concat().map((team, index) => {
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
order: team.order,
|
||||||
|
};
|
||||||
|
}).sort((a, b) => (a.order - b.order));
|
||||||
|
|
||||||
|
const team = tabOrder.splice(originalOrder, 1);
|
||||||
|
tabOrder.splice(newOrder, 0, team[0]);
|
||||||
|
|
||||||
|
let teamIndex;
|
||||||
|
tabOrder.forEach((t, order) => {
|
||||||
|
if (order === newOrder) {
|
||||||
|
teamIndex = t.index;
|
||||||
|
}
|
||||||
|
teams[t.index].order = order;
|
||||||
|
});
|
||||||
|
teamConfigChange(teams);
|
||||||
|
return teamIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDarkMode() {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
return config.darkMode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDarkMode() {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
const darkMode = Boolean(config.darkMode);
|
||||||
|
config.set('darkMode', !darkMode);
|
||||||
|
return !darkMode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMenu() {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
ipcRenderer.send('open-app-menu');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<MainPage
|
<MainPage
|
||||||
teams={teams}
|
teams={teams}
|
||||||
@@ -146,6 +203,10 @@ ReactDOM.render(
|
|||||||
onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale}
|
onSelectSpellCheckerLocale={handleSelectSpellCheckerLocale}
|
||||||
deeplinkingUrl={deeplinkingUrl}
|
deeplinkingUrl={deeplinkingUrl}
|
||||||
showAddServerButton={config.enableServerManagement}
|
showAddServerButton={config.enableServerManagement}
|
||||||
|
getDarkMode={getDarkMode}
|
||||||
|
setDarkMode={setDarkMode}
|
||||||
|
moveTabs={moveTabs}
|
||||||
|
openMenu={openMenu}
|
||||||
/>,
|
/>,
|
||||||
document.getElementById('content')
|
document.getElementById('content')
|
||||||
);
|
);
|
||||||
|
@@ -47,6 +47,15 @@ window.addEventListener('load', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sent for drag and drop tabs to work properly
|
||||||
|
document.addEventListener('mousemove', (event) => {
|
||||||
|
ipcRenderer.sendToHost('mouse-move', {clientX: event.clientX, clientY: event.clientY});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
ipcRenderer.sendToHost('mouse-up');
|
||||||
|
});
|
||||||
|
|
||||||
// listen for messages from the webapp
|
// listen for messages from the webapp
|
||||||
window.addEventListener('message', ({origin, data: {type, message = {}} = {}} = {}) => {
|
window.addEventListener('message', ({origin, data: {type, message = {}} = {}} = {}) => {
|
||||||
if (origin !== window.location.origin) {
|
if (origin !== window.location.origin) {
|
||||||
|
@@ -72,6 +72,7 @@ export default class RegistryConfig extends EventEmitter {
|
|||||||
teams.push({
|
teams.push({
|
||||||
name: team.name,
|
name: team.name,
|
||||||
url: team.value,
|
url: team.value,
|
||||||
|
order: team.order,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return teams;
|
return teams;
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
* @param {number} version - Scheme version. (Not application version)
|
* @param {number} version - Scheme version. (Not application version)
|
||||||
*/
|
*/
|
||||||
const defaultPreferences = {
|
const defaultPreferences = {
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [],
|
teams: [],
|
||||||
showTrayIcon: false,
|
showTrayIcon: false,
|
||||||
trayIconTheme: 'light',
|
trayIconTheme: 'light',
|
||||||
@@ -22,6 +22,7 @@ const defaultPreferences = {
|
|||||||
enableHardwareAcceleration: true,
|
enableHardwareAcceleration: true,
|
||||||
autostart: true,
|
autostart: true,
|
||||||
spellCheckerLocale: 'en-US',
|
spellCheckerLocale: 'en-US',
|
||||||
|
darkMode: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defaultPreferences;
|
export default defaultPreferences;
|
||||||
|
@@ -144,6 +144,9 @@ export default class Config extends EventEmitter {
|
|||||||
get teams() {
|
get teams() {
|
||||||
return this.combinedData.teams;
|
return this.combinedData.teams;
|
||||||
}
|
}
|
||||||
|
get darkMode() {
|
||||||
|
return this.combinedData.darkMode;
|
||||||
|
}
|
||||||
get localTeams() {
|
get localTeams() {
|
||||||
return this.localConfigData.teams;
|
return this.localConfigData.teams;
|
||||||
}
|
}
|
||||||
@@ -209,10 +212,16 @@ export default class Config extends EventEmitter {
|
|||||||
configData = this.readFileSync(this.configFilePath);
|
configData = this.readFileSync(this.configFilePath);
|
||||||
|
|
||||||
// validate based on config file version
|
// validate based on config file version
|
||||||
if (configData.version > 0) {
|
if (configData.version > 1) {
|
||||||
configData = Validator.validateV1ConfigData(configData);
|
configData = Validator.validateV2ConfigData(configData);
|
||||||
} else {
|
} else {
|
||||||
configData = Validator.validateV0ConfigData(configData);
|
switch (configData.version) {
|
||||||
|
case 1:
|
||||||
|
configData = Validator.validateV1ConfigData(configData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
configData = Validator.validateV0ConfigData(configData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!configData) {
|
if (!configData) {
|
||||||
throw new Error('Provided configuration file does not validate, using defaults instead.');
|
throw new Error('Provided configuration file does not validate, using defaults instead.');
|
||||||
|
@@ -7,6 +7,23 @@ const pastDefaultPreferences = {
|
|||||||
0: {
|
0: {
|
||||||
url: '',
|
url: '',
|
||||||
},
|
},
|
||||||
|
1: {
|
||||||
|
version: 1,
|
||||||
|
teams: [],
|
||||||
|
showTrayIcon: false,
|
||||||
|
trayIconTheme: 'light',
|
||||||
|
minimizeToTray: false,
|
||||||
|
notifications: {
|
||||||
|
flashWindow: 0,
|
||||||
|
bounceIcon: false,
|
||||||
|
bounceIconType: 'informational',
|
||||||
|
},
|
||||||
|
showUnreadBadge: true,
|
||||||
|
useSpellChecker: true,
|
||||||
|
enableHardwareAcceleration: true,
|
||||||
|
autostart: true,
|
||||||
|
spellCheckerLocale: 'en-US',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pastDefaultPreferences[`${defaultPreferences.version}`] = defaultPreferences;
|
pastDefaultPreferences[`${defaultPreferences.version}`] = defaultPreferences;
|
||||||
|
@@ -19,9 +19,21 @@ function upgradeV0toV1(configV0) {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upgradeV1toV2(configV1) {
|
||||||
|
const config = deepCopy(configV1);
|
||||||
|
config.version = 2;
|
||||||
|
config.teams.forEach((value, index) => {
|
||||||
|
value.order = index;
|
||||||
|
});
|
||||||
|
config.darkMode = false;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
export default function upgradeToLatest(config) {
|
export default function upgradeToLatest(config) {
|
||||||
const configVersion = config.version ? config.version : 0;
|
const configVersion = config.version ? config.version : 0;
|
||||||
switch (configVersion) {
|
switch (configVersion) {
|
||||||
|
case 1:
|
||||||
|
return upgradeToLatest(upgradeV1toV2(config));
|
||||||
case 0:
|
case 0:
|
||||||
return upgradeToLatest(upgradeV0toV1(config));
|
return upgradeToLatest(upgradeV0toV1(config));
|
||||||
default:
|
default:
|
||||||
|
29
src/main.js
@@ -66,6 +66,7 @@ let registryConfig = null;
|
|||||||
let config = null;
|
let config = null;
|
||||||
let trayIcon = null;
|
let trayIcon = null;
|
||||||
let trayImages = null;
|
let trayImages = null;
|
||||||
|
let altLastPressed = false;
|
||||||
|
|
||||||
// supported custom login paths (oath, saml)
|
// supported custom login paths (oath, saml)
|
||||||
const customLoginRegexPaths = [
|
const customLoginRegexPaths = [
|
||||||
@@ -216,6 +217,9 @@ function initializeInterCommunicationEventListeners() {
|
|||||||
if (shouldShowTrayIcon()) {
|
if (shouldShowTrayIcon()) {
|
||||||
ipcMain.on('update-unread', handleUpdateUnreadEvent);
|
ipcMain.on('update-unread', handleUpdateUnreadEvent);
|
||||||
}
|
}
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
ipcMain.on('open-app-menu', handleOpenAppMenu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeMainWindowListeners() {
|
function initializeMainWindowListeners() {
|
||||||
@@ -462,6 +466,19 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||||||
|
|
||||||
// implemented to temporarily help solve for https://community-daily.mattermost.com/core/pl/b95bi44r4bbnueqzjjxsi46qiw
|
// implemented to temporarily help solve for https://community-daily.mattermost.com/core/pl/b95bi44r4bbnueqzjjxsi46qiw
|
||||||
contents.on('before-input-event', (event, input) => {
|
contents.on('before-input-event', (event, input) => {
|
||||||
|
if (input.key === 'Alt' && input.type === 'keyUp' && altLastPressed) {
|
||||||
|
altLastPressed = false;
|
||||||
|
mainWindow.webContents.send('focus-three-dot-menu');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hack to detect keyPress so that alt+<key> combinations don't default back to the 3-dot menu
|
||||||
|
if (input.key === 'Alt' && input.type === 'keyDown') {
|
||||||
|
altLastPressed = true;
|
||||||
|
} else {
|
||||||
|
altLastPressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!input.shift && !input.control && !input.alt && !input.meta) {
|
if (!input.shift && !input.control && !input.alt && !input.meta) {
|
||||||
// hacky fix for https://mattermost.atlassian.net/browse/MM-19226
|
// hacky fix for https://mattermost.atlassian.net/browse/MM-19226
|
||||||
if ((input.key === 'Escape' || input.key === 'f') && input.type === 'keyDown') {
|
if ((input.key === 'Escape' || input.key === 'f') && input.type === 'keyDown') {
|
||||||
@@ -755,9 +772,21 @@ function handleUpdateUnreadEvent(event, arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleOpenAppMenu() {
|
||||||
|
Menu.getApplicationMenu().popup({
|
||||||
|
x: 18,
|
||||||
|
y: 18,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCloseAppMenu(event) {
|
||||||
|
mainWindow.webContents.send('focus-on-webview', event);
|
||||||
|
}
|
||||||
|
|
||||||
function handleUpdateMenuEvent(event, configData) {
|
function handleUpdateMenuEvent(event, configData) {
|
||||||
const aMenu = appMenu.createMenu(mainWindow, configData, global.isDev);
|
const aMenu = appMenu.createMenu(mainWindow, configData, global.isDev);
|
||||||
Menu.setApplicationMenu(aMenu);
|
Menu.setApplicationMenu(aMenu);
|
||||||
|
aMenu.addListener('menu-will-close', handleCloseAppMenu);
|
||||||
|
|
||||||
// set up context menu for tray icon
|
// set up context menu for tray icon
|
||||||
if (shouldShowTrayIcon()) {
|
if (shouldShowTrayIcon()) {
|
||||||
|
@@ -61,6 +61,29 @@ const configDataSchemaV1 = Joi.object({
|
|||||||
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const configDataSchemaV2 = Joi.object({
|
||||||
|
version: Joi.number().min(2).default(2),
|
||||||
|
teams: Joi.array().items(Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
url: Joi.string().required(),
|
||||||
|
order: Joi.number().integer().min(0),
|
||||||
|
})).default([]),
|
||||||
|
showTrayIcon: Joi.boolean().default(false),
|
||||||
|
trayIconTheme: Joi.any().allow('').valid('light', 'dark').default('light'),
|
||||||
|
minimizeToTray: Joi.boolean().default(false),
|
||||||
|
notifications: Joi.object({
|
||||||
|
flashWindow: Joi.any().valid(0, 2).default(0),
|
||||||
|
bounceIcon: Joi.boolean().default(false),
|
||||||
|
bounceIconType: Joi.any().allow('').valid('informational', 'critical').default('informational'),
|
||||||
|
}),
|
||||||
|
showUnreadBadge: Joi.boolean().default(true),
|
||||||
|
useSpellChecker: Joi.boolean().default(true),
|
||||||
|
enableHardwareAcceleration: Joi.boolean().default(true),
|
||||||
|
autostart: Joi.boolean().default(true),
|
||||||
|
spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'),
|
||||||
|
darkMode: Joi.boolean().default(false),
|
||||||
|
});
|
||||||
|
|
||||||
// eg. data['community.mattermost.com'] = { data: 'certificate data', issuerName: 'COMODO RSA Domain Validation Secure Server CA'};
|
// eg. data['community.mattermost.com'] = { data: 'certificate data', issuerName: 'COMODO RSA Domain Validation Secure Server CA'};
|
||||||
const certificateStoreSchema = Joi.object().pattern(
|
const certificateStoreSchema = Joi.object().pattern(
|
||||||
Joi.string().uri(),
|
Joi.string().uri(),
|
||||||
@@ -113,6 +136,26 @@ export function validateV1ConfigData(data) {
|
|||||||
return validateAgainstSchema(data, configDataSchemaV1);
|
return validateAgainstSchema(data, configDataSchemaV1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateV2ConfigData(data) {
|
||||||
|
if (Array.isArray(data.teams) && data.teams.length) {
|
||||||
|
// first replace possible backslashes with forward slashes
|
||||||
|
let teams = data.teams.map(({name, url, order}) => {
|
||||||
|
let updatedURL = url;
|
||||||
|
if (updatedURL.includes('\\')) {
|
||||||
|
updatedURL = updatedURL.toLowerCase().replace(/\\/gi, '/');
|
||||||
|
}
|
||||||
|
return {name, url: updatedURL, order};
|
||||||
|
});
|
||||||
|
|
||||||
|
// next filter out urls that are still invalid so all is not lost
|
||||||
|
teams = teams.filter(({url}) => Utils.isValidURL(url));
|
||||||
|
|
||||||
|
// replace original teams
|
||||||
|
data.teams = teams;
|
||||||
|
}
|
||||||
|
return validateAgainstSchema(data, configDataSchemaV2);
|
||||||
|
}
|
||||||
|
|
||||||
// validate certificate.json
|
// validate certificate.json
|
||||||
export function validateCertificateStore(data) {
|
export function validateCertificateStore(data) {
|
||||||
return validateAgainstSchema(data, certificateStoreSchema);
|
return validateAgainstSchema(data, certificateStoreSchema);
|
||||||
|
@@ -51,7 +51,9 @@ function createMainWindow(config, options) {
|
|||||||
show: hideOnStartup || false,
|
show: hideOnStartup || false,
|
||||||
minWidth: minimumWindowWidth,
|
minWidth: minimumWindowWidth,
|
||||||
minHeight: minimumWindowHeight,
|
minHeight: minimumWindowHeight,
|
||||||
|
frame: false,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
|
titleBarStyle: 'hiddenInset',
|
||||||
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
@@ -156,10 +158,6 @@ function createMainWindow(config, options) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.on('sheet-end', () => {
|
|
||||||
mainWindow.webContents.send('focus-on-webview');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register keyboard shortcuts
|
// Register keyboard shortcuts
|
||||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
// Add Alt+Cmd+(Right|Left) as alternative to switch between servers
|
// Add Alt+Cmd+(Right|Left) as alternative to switch between servers
|
||||||
|
@@ -116,79 +116,92 @@ function createTemplate(mainWindow, config, isDev) {
|
|||||||
role: 'selectall',
|
role: 'selectall',
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const viewSubMenu = [{
|
||||||
|
label: 'Find..',
|
||||||
|
accelerator: 'CmdOrCtrl+F',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
focusedWindow.webContents.send('toggle-find');
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
label: 'Reload',
|
||||||
|
accelerator: 'CmdOrCtrl+R',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
if (focusedWindow === mainWindow) {
|
||||||
|
mainWindow.webContents.send('reload-tab');
|
||||||
|
} else {
|
||||||
|
focusedWindow.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
label: 'Clear Cache and Reload',
|
||||||
|
accelerator: 'Shift+CmdOrCtrl+R',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
if (focusedWindow === mainWindow) {
|
||||||
|
mainWindow.webContents.send('clear-cache-and-reload-tab');
|
||||||
|
} else {
|
||||||
|
focusedWindow.webContents.session.clearCache(() => {
|
||||||
|
focusedWindow.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
role: 'togglefullscreen',
|
||||||
|
}, separatorItem, {
|
||||||
|
label: 'Actual Size',
|
||||||
|
accelerator: 'CmdOrCtrl+0',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('zoom-reset');
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
label: 'Zoom In',
|
||||||
|
accelerator: 'CmdOrCtrl+SHIFT+=',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('zoom-in');
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
label: 'Zoom Out',
|
||||||
|
accelerator: 'CmdOrCtrl+-',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('zoom-out');
|
||||||
|
},
|
||||||
|
}, separatorItem, {
|
||||||
|
label: 'Developer Tools for Application Wrapper',
|
||||||
|
accelerator: (() => {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return 'Alt+Command+I';
|
||||||
|
}
|
||||||
|
return 'Ctrl+Shift+I';
|
||||||
|
})(),
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
if (focusedWindow) {
|
||||||
|
focusedWindow.toggleDevTools();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
label: 'Developer Tools for Current Server',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('open-devtool');
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
viewSubMenu.push(separatorItem);
|
||||||
|
viewSubMenu.push({
|
||||||
|
label: 'Toggle Dark Mode',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.send('set-dark-mode');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
template.push({
|
template.push({
|
||||||
label: '&View',
|
label: '&View',
|
||||||
submenu: [{
|
submenu: viewSubMenu,
|
||||||
label: 'Find..',
|
|
||||||
accelerator: 'CmdOrCtrl+F',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
focusedWindow.webContents.send('toggle-find');
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
label: 'Reload',
|
|
||||||
accelerator: 'CmdOrCtrl+R',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
if (focusedWindow === mainWindow) {
|
|
||||||
mainWindow.webContents.send('reload-tab');
|
|
||||||
} else {
|
|
||||||
focusedWindow.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
label: 'Clear Cache and Reload',
|
|
||||||
accelerator: 'Shift+CmdOrCtrl+R',
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
if (focusedWindow === mainWindow) {
|
|
||||||
mainWindow.webContents.send('clear-cache-and-reload-tab');
|
|
||||||
} else {
|
|
||||||
focusedWindow.webContents.session.clearCache(() => {
|
|
||||||
focusedWindow.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
role: 'togglefullscreen',
|
|
||||||
}, separatorItem, {
|
|
||||||
label: 'Actual Size',
|
|
||||||
accelerator: 'CmdOrCtrl+0',
|
|
||||||
click() {
|
|
||||||
mainWindow.webContents.send('zoom-reset');
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
label: 'Zoom In',
|
|
||||||
accelerator: 'CmdOrCtrl+SHIFT+=',
|
|
||||||
click() {
|
|
||||||
mainWindow.webContents.send('zoom-in');
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
label: 'Zoom Out',
|
|
||||||
accelerator: 'CmdOrCtrl+-',
|
|
||||||
click() {
|
|
||||||
mainWindow.webContents.send('zoom-out');
|
|
||||||
},
|
|
||||||
}, separatorItem, {
|
|
||||||
label: 'Developer Tools for Application Wrapper',
|
|
||||||
accelerator: (() => {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
return 'Alt+Command+I';
|
|
||||||
}
|
|
||||||
return 'Ctrl+Shift+I';
|
|
||||||
})(),
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
focusedWindow.toggleDevTools();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
label: 'Developer Tools for Current Server',
|
|
||||||
click() {
|
|
||||||
mainWindow.webContents.send('open-devtool');
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
});
|
});
|
||||||
template.push({
|
template.push({
|
||||||
label: '&History',
|
label: '&History',
|
||||||
|
@@ -72,13 +72,15 @@ describe('application', function desc() {
|
|||||||
|
|
||||||
it('should show index.html when there is config file', async () => {
|
it('should show index.html when there is config file', async () => {
|
||||||
const config = {
|
const config = {
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'example',
|
name: 'example',
|
||||||
url: env.mattermostURL,
|
url: env.mattermostURL,
|
||||||
|
order: 0,
|
||||||
}, {
|
}, {
|
||||||
name: 'github',
|
name: 'github',
|
||||||
url: 'https://github.com/',
|
url: 'https://github.com/',
|
||||||
|
order: 1,
|
||||||
}],
|
}],
|
||||||
showTrayIcon: false,
|
showTrayIcon: false,
|
||||||
trayIconTheme: 'light',
|
trayIconTheme: 'light',
|
||||||
@@ -92,6 +94,7 @@ describe('application', function desc() {
|
|||||||
useSpellChecker: true,
|
useSpellChecker: true,
|
||||||
enableHardwareAcceleration: true,
|
enableHardwareAcceleration: true,
|
||||||
autostart: true,
|
autostart: true,
|
||||||
|
darkMode: false,
|
||||||
};
|
};
|
||||||
fs.writeFileSync(env.configFilePath, JSON.stringify(config));
|
fs.writeFileSync(env.configFilePath, JSON.stringify(config));
|
||||||
await this.app.restart();
|
await this.app.restart();
|
||||||
|
@@ -14,13 +14,15 @@ describe('browser/index.html', function desc() {
|
|||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'example',
|
name: 'example',
|
||||||
url: env.mattermostURL,
|
url: env.mattermostURL,
|
||||||
|
order: 0,
|
||||||
}, {
|
}, {
|
||||||
name: 'github',
|
name: 'github',
|
||||||
url: 'https://github.com/',
|
url: 'https://github.com/',
|
||||||
|
order: 1,
|
||||||
}],
|
}],
|
||||||
showTrayIcon: false,
|
showTrayIcon: false,
|
||||||
trayIconTheme: 'light',
|
trayIconTheme: 'light',
|
||||||
@@ -34,6 +36,7 @@ describe('browser/index.html', function desc() {
|
|||||||
useSpellChecker: true,
|
useSpellChecker: true,
|
||||||
enableHardwareAcceleration: true,
|
enableHardwareAcceleration: true,
|
||||||
autostart: true,
|
autostart: true,
|
||||||
|
darkMode: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const serverPort = 8181;
|
const serverPort = 8181;
|
||||||
@@ -65,16 +68,6 @@ describe('browser/index.html', function desc() {
|
|||||||
this.server.close(done);
|
this.server.close(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT show tabs when there is one team', async () => {
|
|
||||||
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
|
||||||
url: env.mattermostURL,
|
|
||||||
}));
|
|
||||||
await this.app.restart();
|
|
||||||
|
|
||||||
const existing = await this.app.client.isExisting('#tabBar');
|
|
||||||
existing.should.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set src of webview from config file', async () => {
|
it('should set src of webview from config file', async () => {
|
||||||
const src0 = await this.app.client.getAttribute('#mattermostView0', 'src');
|
const src0 = await this.app.client.getAttribute('#mattermostView0', 'src');
|
||||||
src0.should.equal(config.teams[0].url);
|
src0.should.equal(config.teams[0].url);
|
||||||
@@ -107,10 +100,11 @@ describe('browser/index.html', function desc() {
|
|||||||
it.skip('should show error when using incorrect URL', async () => {
|
it.skip('should show error when using incorrect URL', async () => {
|
||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'error_1',
|
name: 'error_1',
|
||||||
url: 'http://false',
|
url: 'http://false',
|
||||||
|
order: 0,
|
||||||
}],
|
}],
|
||||||
}));
|
}));
|
||||||
await this.app.restart();
|
await this.app.restart();
|
||||||
@@ -120,10 +114,11 @@ describe('browser/index.html', function desc() {
|
|||||||
|
|
||||||
it('should set window title by using webview\'s one', async () => {
|
it('should set window title by using webview\'s one', async () => {
|
||||||
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'title_test',
|
name: 'title_test',
|
||||||
url: `http://localhost:${serverPort}`,
|
url: `http://localhost:${serverPort}`,
|
||||||
|
order: 0,
|
||||||
}],
|
}],
|
||||||
}));
|
}));
|
||||||
await this.app.restart();
|
await this.app.restart();
|
||||||
@@ -135,13 +130,15 @@ describe('browser/index.html', function desc() {
|
|||||||
// Skip because it's very unstable in CI
|
// Skip because it's very unstable in CI
|
||||||
it.skip('should update window title when the activated tab\'s title is updated', async () => {
|
it.skip('should update window title when the activated tab\'s title is updated', async () => {
|
||||||
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'title_test_0',
|
name: 'title_test_0',
|
||||||
url: `http://localhost:${serverPort}`,
|
url: `http://localhost:${serverPort}`,
|
||||||
|
order: 0,
|
||||||
}, {
|
}, {
|
||||||
name: 'title_test_1',
|
name: 'title_test_1',
|
||||||
url: `http://localhost:${serverPort}`,
|
url: `http://localhost:${serverPort}`,
|
||||||
|
order: 1,
|
||||||
}],
|
}],
|
||||||
}));
|
}));
|
||||||
await this.app.restart();
|
await this.app.restart();
|
||||||
@@ -171,13 +168,15 @@ describe('browser/index.html', function desc() {
|
|||||||
// Skip because it's very unstable in CI
|
// Skip because it's very unstable in CI
|
||||||
it.skip('should update window title when a tab is selected', async () => {
|
it.skip('should update window title when a tab is selected', async () => {
|
||||||
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
fs.writeFileSync(env.configFilePath, JSON.stringify({
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'title_test_0',
|
name: 'title_test_0',
|
||||||
url: `http://localhost:${serverPort}`,
|
url: `http://localhost:${serverPort}`,
|
||||||
|
order: 0,
|
||||||
}, {
|
}, {
|
||||||
name: 'title_test_1',
|
name: 'title_test_1',
|
||||||
url: `http://localhost:${serverPort}`,
|
url: `http://localhost:${serverPort}`,
|
||||||
|
order: 1,
|
||||||
}],
|
}],
|
||||||
}));
|
}));
|
||||||
await this.app.restart();
|
await this.app.restart();
|
||||||
|
@@ -12,13 +12,15 @@ describe('browser/settings.html', function desc() {
|
|||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'example',
|
name: 'example',
|
||||||
url: env.mattermostURL,
|
url: env.mattermostURL,
|
||||||
|
order: 0,
|
||||||
}, {
|
}, {
|
||||||
name: 'github',
|
name: 'github',
|
||||||
url: 'https://github.com/',
|
url: 'https://github.com/',
|
||||||
|
order: 1,
|
||||||
}],
|
}],
|
||||||
showTrayIcon: false,
|
showTrayIcon: false,
|
||||||
trayIconTheme: 'light',
|
trayIconTheme: 'light',
|
||||||
@@ -32,6 +34,7 @@ describe('browser/settings.html', function desc() {
|
|||||||
useSpellChecker: true,
|
useSpellChecker: true,
|
||||||
enableHardwareAcceleration: true,
|
enableHardwareAcceleration: true,
|
||||||
autostart: true,
|
autostart: true,
|
||||||
|
darkMode: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -357,8 +360,13 @@ describe('browser/settings.html', function desc() {
|
|||||||
|
|
||||||
await this.app.client.waitForVisible('#serversSaveIndicator', 10000, true);
|
await this.app.client.waitForVisible('#serversSaveIndicator', 10000, true);
|
||||||
|
|
||||||
|
const expectedConfig = JSON.parse(JSON.stringify(config.teams.slice(1)));
|
||||||
|
expectedConfig.forEach((value) => {
|
||||||
|
value.order--;
|
||||||
|
});
|
||||||
|
|
||||||
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
|
||||||
savedConfig.teams.should.deep.equal(config.teams.slice(1));
|
savedConfig.teams.should.deep.equal(expectedConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT remove existing team on click Cancel', async () => {
|
it('should NOT remove existing team on click Cancel', async () => {
|
||||||
@@ -512,6 +520,7 @@ describe('browser/settings.html', function desc() {
|
|||||||
savedConfig.teams.should.deep.contain({
|
savedConfig.teams.should.deep.contain({
|
||||||
name: 'TestTeam',
|
name: 'TestTeam',
|
||||||
url: 'http://example.org',
|
url: 'http://example.org',
|
||||||
|
order: 2,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -16,13 +16,15 @@ describe.skip('security', function desc() {
|
|||||||
const testURL = `http://localhost:${serverPort}`;
|
const testURL = `http://localhost:${serverPort}`;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
version: 1,
|
version: 2,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'example_1',
|
name: 'example_1',
|
||||||
url: testURL,
|
url: testURL,
|
||||||
|
order: 0,
|
||||||
}, {
|
}, {
|
||||||
name: 'example_2',
|
name: 'example_2',
|
||||||
url: testURL,
|
url: testURL,
|
||||||
|
order: 1,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -41,6 +41,18 @@ module.exports = merge(base, {
|
|||||||
use: {
|
use: {
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
test: /\.(svg|woff2)$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[hash].[ext]',
|
||||||
|
publicPath: './',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{loader: 'image-webpack-loader'},
|
||||||
|
],
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
|