Remove WindowManager, separate functionality into smaller modules (#2682)
* Move sendToRenderer to respective singletons * Move to using ViewManager call for getting view by webContentsId * Move show and create logic to main window, handle deep linking seperately * Move resizing logic and event handing to mainWindow * Move server switching logic to main/app * Move tab switching logic to main/app, rely on showById for most usage * Migrate remaining functions, remove windowManager objects, set up imports for self-contained singletons * Fix E2E tests * Update src/main/app/servers.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> --------- Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
a141d3cde4
commit
f4f4511cc7
|
@ -60,21 +60,12 @@ describe('file_menu/dropdown', function desc() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
it('MM-T804 Preferences in Menu Bar open the Settings page', async () => {
|
it('MM-T804 Preferences in Menu Bar open the Settings page', async () => {
|
||||||
|
//Opening the menu bar
|
||||||
const mainWindow = this.app.windows().find((window) => window.url().includes('index'));
|
const mainWindow = this.app.windows().find((window) => window.url().includes('index'));
|
||||||
mainWindow.should.not.be.null;
|
mainWindow.should.not.be.null;
|
||||||
robot.keyTap(',', [env.cmdOrCtrl]);
|
await mainWindow.click('button.three-dot-menu');
|
||||||
const settingsWindow = await this.app.waitForEvent('window', {
|
|
||||||
predicate: (window) => window.url().includes('settings'),
|
|
||||||
});
|
|
||||||
settingsWindow.should.not.be.null;
|
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
robot.keyTap('w', [env.cmdOrCtrl]);
|
|
||||||
|
|
||||||
//Opening the menu bar
|
|
||||||
robot.keyTap('alt');
|
|
||||||
robot.keyTap('enter');
|
|
||||||
robot.keyTap('f');
|
robot.keyTap('f');
|
||||||
robot.keyTap('s');
|
robot.keyTap('s');
|
||||||
robot.keyTap('enter');
|
robot.keyTap('enter');
|
||||||
|
@ -82,8 +73,8 @@ describe('file_menu/dropdown', function desc() {
|
||||||
predicate: (window) => window.url().includes('settings'),
|
predicate: (window) => window.url().includes('settings'),
|
||||||
});
|
});
|
||||||
settingsWindowFromMenu.should.not.be.null;
|
settingsWindowFromMenu.should.not.be.null;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Causes issues on Windows so skipping for Windows
|
// TODO: Causes issues on Windows so skipping for Windows
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
|
|
|
@ -139,6 +139,7 @@ describe('Add Server Modal', function desc() {
|
||||||
name: 'TestTeam',
|
name: 'TestTeam',
|
||||||
url: 'http://example.org/',
|
url: 'http://example.org/',
|
||||||
order: 2,
|
order: 2,
|
||||||
|
lastActiveTab: 0,
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
name: 'TAB_MESSAGING',
|
name: 'TAB_MESSAGING',
|
||||||
|
|
|
@ -86,6 +86,7 @@ describe('Configure Server Modal', function desc() {
|
||||||
url: 'http://example.org/',
|
url: 'http://example.org/',
|
||||||
name: 'TestTeam',
|
name: 'TestTeam',
|
||||||
order: 0,
|
order: 0,
|
||||||
|
lastActiveTab: 0,
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
name: 'TAB_MESSAGING',
|
name: 'TAB_MESSAGING',
|
||||||
|
|
|
@ -167,3 +167,6 @@ export const GET_ORDERED_TABS_FOR_SERVER = 'get-ordered-tabs-for-server';
|
||||||
export const UPDATE_APPSTATE = 'update-appstate';
|
export const UPDATE_APPSTATE = 'update-appstate';
|
||||||
export const UPDATE_APPSTATE_TOTALS = 'update-appstate-totals';
|
export const UPDATE_APPSTATE_TOTALS = 'update-appstate-totals';
|
||||||
export const UPDATE_APPSTATE_FOR_VIEW_ID = 'update-appstate-for-view-id';
|
export const UPDATE_APPSTATE_FOR_VIEW_ID = 'update-appstate-for-view-id';
|
||||||
|
|
||||||
|
export const MAIN_WINDOW_CREATED = 'main-window-created';
|
||||||
|
export const MAIN_WINDOW_RESIZED = 'main-window-resized';
|
||||||
|
|
|
@ -38,19 +38,14 @@ jest.mock('main/i18nManager', () => ({
|
||||||
localizeMessage: jest.fn(),
|
localizeMessage: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/tray/tray', () => ({}));
|
jest.mock('main/tray/tray', () => ({}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
|
||||||
showMainWindow: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
show: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/views/viewManager', () => ({
|
jest.mock('main/views/viewManager', () => ({
|
||||||
getView: jest.fn(),
|
getView: jest.fn(),
|
||||||
getViewByWebContentsId: jest.fn(),
|
getViewByWebContentsId: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
|
||||||
get: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('main/app/app', () => {
|
describe('main/app/app', () => {
|
||||||
describe('handleAppWillFinishLaunching', () => {
|
describe('handleAppWillFinishLaunching', () => {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import updateManager from 'main/autoUpdater';
|
||||||
import CertificateStore from 'main/certificateStore';
|
import CertificateStore from 'main/certificateStore';
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import {destroyTray} from 'main/tray/tray';
|
import {destroyTray} from 'main/tray/tray';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
|
@ -30,8 +29,10 @@ export function handleAppSecondInstance(event: Event, argv: string[]) {
|
||||||
|
|
||||||
// Protocol handler for win32
|
// Protocol handler for win32
|
||||||
// argv: An array of the second instance’s (command line / deep linked) arguments
|
// argv: An array of the second instance’s (command line / deep linked) arguments
|
||||||
const deeplinkingUrl = getDeeplinkingURL(argv);
|
const deeplinkingURL = getDeeplinkingURL(argv);
|
||||||
WindowManager.showMainWindow(deeplinkingUrl);
|
if (deeplinkingURL) {
|
||||||
|
openDeepLink(deeplinkingURL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleAppWindowAllClosed() {
|
export function handleAppWindowAllClosed() {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {setLoggingLevel} from 'common/log';
|
||||||
import {handleConfigUpdate} from 'main/app/config';
|
import {handleConfigUpdate} from 'main/app/config';
|
||||||
import {handleMainWindowIsShown} from 'main/app/intercom';
|
import {handleMainWindowIsShown} from 'main/app/intercom';
|
||||||
|
|
||||||
import WindowManager from 'main/windows/windowManager';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import AutoLauncher from 'main/AutoLauncher';
|
import AutoLauncher from 'main/AutoLauncher';
|
||||||
|
|
||||||
jest.mock('electron', () => ({
|
jest.mock('electron', () => ({
|
||||||
|
@ -47,8 +47,7 @@ jest.mock('main/views/viewManager', () => ({
|
||||||
reloadConfiguration: jest.fn(),
|
reloadConfiguration: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/views/loadingScreen', () => ({}));
|
jest.mock('main/views/loadingScreen', () => ({}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
handleUpdateConfig: jest.fn(),
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -65,11 +64,11 @@ describe('main/app/config', () => {
|
||||||
|
|
||||||
it('should reload renderer config only when app is ready', () => {
|
it('should reload renderer config only when app is ready', () => {
|
||||||
handleConfigUpdate({});
|
handleConfigUpdate({});
|
||||||
expect(WindowManager.sendToRenderer).not.toBeCalled();
|
expect(MainWindow.sendToRenderer).not.toBeCalled();
|
||||||
|
|
||||||
app.isReady.mockReturnValue(true);
|
app.isReady.mockReturnValue(true);
|
||||||
handleConfigUpdate({});
|
handleConfigUpdate({});
|
||||||
expect(WindowManager.sendToRenderer).toBeCalledWith(RELOAD_CONFIGURATION);
|
expect(MainWindow.sendToRenderer).toBeCalledWith(RELOAD_CONFIGURATION);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set download path if applicable', () => {
|
it('should set download path if applicable', () => {
|
||||||
|
|
|
@ -13,7 +13,8 @@ import AutoLauncher from 'main/AutoLauncher';
|
||||||
import {setUnreadBadgeSetting} from 'main/badge';
|
import {setUnreadBadgeSetting} from 'main/badge';
|
||||||
import {refreshTrayImages} from 'main/tray/tray';
|
import {refreshTrayImages} from 'main/tray/tray';
|
||||||
import LoadingScreen from 'main/views/loadingScreen';
|
import LoadingScreen from 'main/views/loadingScreen';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
import SettingsWindow from 'main/windows/settingsWindow';
|
||||||
|
|
||||||
import {handleMainWindowIsShown} from './intercom';
|
import {handleMainWindowIsShown} from './intercom';
|
||||||
import {handleUpdateMenuEvent, updateSpellCheckerLocales} from './utils';
|
import {handleUpdateMenuEvent, updateSpellCheckerLocales} from './utils';
|
||||||
|
@ -72,7 +73,8 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.isReady()) {
|
if (app.isReady()) {
|
||||||
WindowManager.sendToRenderer(RELOAD_CONFIGURATION);
|
MainWindow.sendToRenderer(RELOAD_CONFIGURATION);
|
||||||
|
SettingsWindow.sendToRenderer(RELOAD_CONFIGURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
setUnreadBadgeSetting(newConfig && newConfig.showUnreadBadge);
|
setUnreadBadgeSetting(newConfig && newConfig.showUnreadBadge);
|
||||||
|
@ -111,7 +113,8 @@ export function handleDarkModeChange(darkMode: boolean) {
|
||||||
log.debug('handleDarkModeChange', darkMode);
|
log.debug('handleDarkModeChange', darkMode);
|
||||||
|
|
||||||
refreshTrayImages(Config.trayIconTheme);
|
refreshTrayImages(Config.trayIconTheme);
|
||||||
WindowManager.sendToRenderer(DARK_MODE_CHANGE, darkMode);
|
MainWindow.sendToRenderer(DARK_MODE_CHANGE, darkMode);
|
||||||
|
SettingsWindow.sendToRenderer(DARK_MODE_CHANGE, darkMode);
|
||||||
LoadingScreen.setDarkMode(darkMode);
|
LoadingScreen.setDarkMode(darkMode);
|
||||||
|
|
||||||
ipcMain.emit(EMIT_CONFIGURATION, true, Config.data);
|
ipcMain.emit(EMIT_CONFIGURATION, true, Config.data);
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
|
|
||||||
import {initialize} from './initialize';
|
import {initialize} from './initialize';
|
||||||
|
|
||||||
|
// TODO: Singletons, we need DI :D
|
||||||
|
import('main/views/teamDropdownView');
|
||||||
|
import('main/views/downloadsDropdownMenuView');
|
||||||
|
import('main/views/downloadsDropdownView');
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production' && module.hot) {
|
if (process.env.NODE_ENV !== 'production' && module.hot) {
|
||||||
module.hot.accept();
|
module.hot.accept();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ import Config from 'common/config';
|
||||||
import urlUtils from 'common/utils/url';
|
import urlUtils from 'common/utils/url';
|
||||||
|
|
||||||
import parseArgs from 'main/ParseArgs';
|
import parseArgs from 'main/ParseArgs';
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
|
|
||||||
import {initialize} from './initialize';
|
import {initialize} from './initialize';
|
||||||
import {clearAppCache, getDeeplinkingURL, wasUpdated} from './utils';
|
import {clearAppCache, getDeeplinkingURL, wasUpdated} from './utils';
|
||||||
|
@ -119,6 +119,7 @@ jest.mock('main/app/config', () => ({
|
||||||
jest.mock('main/app/intercom', () => ({
|
jest.mock('main/app/intercom', () => ({
|
||||||
handleMainWindowIsShown: jest.fn(),
|
handleMainWindowIsShown: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('main/app/servers', () => ({}));
|
||||||
jest.mock('main/app/utils', () => ({
|
jest.mock('main/app/utils', () => ({
|
||||||
clearAppCache: jest.fn(),
|
clearAppCache: jest.fn(),
|
||||||
getDeeplinkingURL: jest.fn(),
|
getDeeplinkingURL: jest.fn(),
|
||||||
|
@ -168,18 +169,17 @@ jest.mock('main/UserActivityMonitor', () => ({
|
||||||
jest.mock('main/windows/callsWidgetWindow', () => ({
|
jest.mock('main/windows/callsWidgetWindow', () => ({
|
||||||
isCallsWidget: jest.fn(),
|
isCallsWidget: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/views/viewManager', () => ({
|
||||||
showMainWindow: jest.fn(),
|
getViewByWebContentsId: jest.fn(),
|
||||||
sendToRenderer: jest.fn(),
|
handleDeepLink: jest.fn(),
|
||||||
getServerNameByWebContentsId: jest.fn(),
|
|
||||||
getServerURLFromWebContentsId: jest.fn(),
|
|
||||||
}));
|
}));
|
||||||
jest.mock('main/views/viewManager', () => ({}));
|
|
||||||
jest.mock('main/windows/settingsWindow', () => ({
|
jest.mock('main/windows/settingsWindow', () => ({
|
||||||
show: jest.fn(),
|
show: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
show: jest.fn(),
|
||||||
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
const originalProcess = process;
|
const originalProcess = process;
|
||||||
describe('main/app/initialize', () => {
|
describe('main/app/initialize', () => {
|
||||||
|
@ -272,11 +272,17 @@ describe('main/app/initialize', () => {
|
||||||
value: originalPlatform,
|
value: originalPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(WindowManager.showMainWindow).toHaveBeenCalledWith('mattermost://server-1.com');
|
expect(ViewManager.handleDeepLink).toHaveBeenCalledWith('mattermost://server-1.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow permission requests for supported types from trusted URLs', async () => {
|
it('should allow permission requests for supported types from trusted URLs', async () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(new URL('http://server-1.com'));
|
ViewManager.getViewByWebContentsId.mockReturnValue({
|
||||||
|
tab: {
|
||||||
|
server: {
|
||||||
|
url: new URL('http://server-1.com'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
let callback = jest.fn();
|
let callback = jest.fn();
|
||||||
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
|
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
|
||||||
cb({id: 1, getURL: () => 'http://server-1.com'}, 'bad-permission', callback);
|
cb({id: 1, getURL: () => 'http://server-1.com'}, 'bad-permission', callback);
|
||||||
|
|
|
@ -36,6 +36,12 @@ import {
|
||||||
GET_ORDERED_SERVERS,
|
GET_ORDERED_SERVERS,
|
||||||
GET_ORDERED_TABS_FOR_SERVER,
|
GET_ORDERED_TABS_FOR_SERVER,
|
||||||
SERVERS_URL_MODIFIED,
|
SERVERS_URL_MODIFIED,
|
||||||
|
GET_DARK_MODE,
|
||||||
|
WINDOW_CLOSE,
|
||||||
|
WINDOW_MAXIMIZE,
|
||||||
|
WINDOW_MINIMIZE,
|
||||||
|
WINDOW_RESTORE,
|
||||||
|
DOUBLE_CLICK_ON_WINDOW,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
@ -59,7 +65,6 @@ import {refreshTrayImages, setupTray} from 'main/tray/tray';
|
||||||
import UserActivityMonitor from 'main/UserActivityMonitor';
|
import UserActivityMonitor from 'main/UserActivityMonitor';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
import {protocols} from '../../../electron-builder.json';
|
import {protocols} from '../../../electron-builder.json';
|
||||||
|
@ -84,22 +89,21 @@ import {
|
||||||
import {
|
import {
|
||||||
handleMainWindowIsShown,
|
handleMainWindowIsShown,
|
||||||
handleAppVersion,
|
handleAppVersion,
|
||||||
handleCloseTab,
|
|
||||||
handleEditServerModal,
|
|
||||||
handleMentionNotification,
|
handleMentionNotification,
|
||||||
handleNewServerModal,
|
|
||||||
handleOpenAppMenu,
|
handleOpenAppMenu,
|
||||||
handleOpenTab,
|
|
||||||
handleQuit,
|
handleQuit,
|
||||||
handleRemoveServerModal,
|
|
||||||
handleSelectDownload,
|
handleSelectDownload,
|
||||||
handleSwitchServer,
|
|
||||||
handleSwitchTab,
|
|
||||||
handlePingDomain,
|
handlePingDomain,
|
||||||
handleGetOrderedServers,
|
|
||||||
handleGetOrderedTabsForServer,
|
|
||||||
handleGetLastActive,
|
|
||||||
} from './intercom';
|
} from './intercom';
|
||||||
|
import {
|
||||||
|
handleEditServerModal,
|
||||||
|
handleNewServerModal,
|
||||||
|
handleRemoveServerModal,
|
||||||
|
switchServer,
|
||||||
|
} from './servers';
|
||||||
|
import {
|
||||||
|
handleCloseTab, handleGetLastActive, handleGetOrderedTabsForServer, handleOpenTab,
|
||||||
|
} from './tabs';
|
||||||
import {
|
import {
|
||||||
clearAppCache,
|
clearAppCache,
|
||||||
getDeeplinkingURL,
|
getDeeplinkingURL,
|
||||||
|
@ -111,6 +115,14 @@ import {
|
||||||
migrateMacAppStore,
|
migrateMacAppStore,
|
||||||
updateServerInfos,
|
updateServerInfos,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
import {
|
||||||
|
handleClose,
|
||||||
|
handleDoubleClick,
|
||||||
|
handleGetDarkMode,
|
||||||
|
handleMaximize,
|
||||||
|
handleMinimize,
|
||||||
|
handleRestore,
|
||||||
|
} from './windows';
|
||||||
|
|
||||||
export const mainProtocol = protocols?.[0]?.schemes?.[0];
|
export const mainProtocol = protocols?.[0]?.schemes?.[0];
|
||||||
|
|
||||||
|
@ -203,7 +215,7 @@ function initializeAppEventListeners() {
|
||||||
app.on('second-instance', handleAppSecondInstance);
|
app.on('second-instance', handleAppSecondInstance);
|
||||||
app.on('window-all-closed', handleAppWindowAllClosed);
|
app.on('window-all-closed', handleAppWindowAllClosed);
|
||||||
app.on('browser-window-created', handleAppBrowserWindowCreated);
|
app.on('browser-window-created', handleAppBrowserWindowCreated);
|
||||||
app.on('activate', () => WindowManager.showMainWindow());
|
app.on('activate', () => MainWindow.show());
|
||||||
app.on('before-quit', handleAppBeforeQuit);
|
app.on('before-quit', handleAppBeforeQuit);
|
||||||
app.on('certificate-error', handleAppCertificateError);
|
app.on('certificate-error', handleAppCertificateError);
|
||||||
app.on('select-client-certificate', CertificateManager.handleSelectCertificate);
|
app.on('select-client-certificate', CertificateManager.handleSelectCertificate);
|
||||||
|
@ -267,8 +279,8 @@ function initializeInterCommunicationEventListeners() {
|
||||||
ipcMain.on(OPEN_APP_MENU, handleOpenAppMenu);
|
ipcMain.on(OPEN_APP_MENU, handleOpenAppMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on(SWITCH_SERVER, handleSwitchServer);
|
ipcMain.on(SWITCH_SERVER, (event, serverId) => switchServer(serverId));
|
||||||
ipcMain.on(SWITCH_TAB, handleSwitchTab);
|
ipcMain.on(SWITCH_TAB, (event, viewId) => ViewManager.showById(viewId));
|
||||||
ipcMain.on(CLOSE_TAB, handleCloseTab);
|
ipcMain.on(CLOSE_TAB, handleCloseTab);
|
||||||
ipcMain.on(OPEN_TAB, handleOpenTab);
|
ipcMain.on(OPEN_TAB, handleOpenTab);
|
||||||
|
|
||||||
|
@ -289,8 +301,15 @@ function initializeInterCommunicationEventListeners() {
|
||||||
ipcMain.on(UPDATE_SERVER_ORDER, (event, serverOrder) => ServerManager.updateServerOrder(serverOrder));
|
ipcMain.on(UPDATE_SERVER_ORDER, (event, serverOrder) => ServerManager.updateServerOrder(serverOrder));
|
||||||
ipcMain.on(UPDATE_TAB_ORDER, (event, serverId, tabOrder) => ServerManager.updateTabOrder(serverId, tabOrder));
|
ipcMain.on(UPDATE_TAB_ORDER, (event, serverId, tabOrder) => ServerManager.updateTabOrder(serverId, tabOrder));
|
||||||
ipcMain.handle(GET_LAST_ACTIVE, handleGetLastActive);
|
ipcMain.handle(GET_LAST_ACTIVE, handleGetLastActive);
|
||||||
ipcMain.handle(GET_ORDERED_SERVERS, handleGetOrderedServers);
|
ipcMain.handle(GET_ORDERED_SERVERS, () => ServerManager.getOrderedServers().map((srv) => srv.toMattermostTeam()));
|
||||||
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, handleGetOrderedTabsForServer);
|
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, handleGetOrderedTabsForServer);
|
||||||
|
|
||||||
|
ipcMain.handle(GET_DARK_MODE, handleGetDarkMode);
|
||||||
|
ipcMain.on(WINDOW_CLOSE, handleClose);
|
||||||
|
ipcMain.on(WINDOW_MAXIMIZE, handleMaximize);
|
||||||
|
ipcMain.on(WINDOW_MINIMIZE, handleMinimize);
|
||||||
|
ipcMain.on(WINDOW_RESTORE, handleRestore);
|
||||||
|
ipcMain.on(DOUBLE_CLICK_ON_WINDOW, handleDoubleClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initializeAfterAppReady() {
|
async function initializeAfterAppReady() {
|
||||||
|
@ -364,6 +383,9 @@ async function initializeAfterAppReady() {
|
||||||
catch((err) => log.error('An error occurred: ', err));
|
catch((err) => log.error('An error occurred: ', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initCookieManager(defaultSession);
|
||||||
|
MainWindow.show();
|
||||||
|
|
||||||
let deeplinkingURL;
|
let deeplinkingURL;
|
||||||
|
|
||||||
// Protocol handler for win32
|
// Protocol handler for win32
|
||||||
|
@ -371,12 +393,11 @@ async function initializeAfterAppReady() {
|
||||||
const args = process.argv.slice(1);
|
const args = process.argv.slice(1);
|
||||||
if (Array.isArray(args) && args.length > 0) {
|
if (Array.isArray(args) && args.length > 0) {
|
||||||
deeplinkingURL = getDeeplinkingURL(args);
|
deeplinkingURL = getDeeplinkingURL(args);
|
||||||
|
if (deeplinkingURL) {
|
||||||
|
ViewManager.handleDeepLink(deeplinkingURL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initCookieManager(defaultSession);
|
|
||||||
|
|
||||||
WindowManager.showMainWindow(deeplinkingURL);
|
|
||||||
|
|
||||||
// listen for status updates and pass on to renderer
|
// listen for status updates and pass on to renderer
|
||||||
UserActivityMonitor.on('status', (status) => {
|
UserActivityMonitor.on('status', (status) => {
|
||||||
|
@ -440,7 +461,7 @@ async function initializeAfterAppReady() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestingURL = webContents.getURL();
|
const requestingURL = webContents.getURL();
|
||||||
const serverURL = WindowManager.getServerURLFromWebContentsId(webContents.id);
|
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
|
||||||
|
|
||||||
if (!serverURL) {
|
if (!serverURL) {
|
||||||
callback(false);
|
callback(false);
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
// 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 {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
|
||||||
|
|
||||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||||
import ServerManager from 'common/servers/serverManager';
|
import ServerManager from 'common/servers/serverManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import ModalManager from 'main/views/modalManager';
|
import ModalManager from 'main/views/modalManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
handleOpenTab,
|
|
||||||
handleCloseTab,
|
|
||||||
handleNewServerModal,
|
|
||||||
handleEditServerModal,
|
|
||||||
handleRemoveServerModal,
|
|
||||||
handleWelcomeScreenModal,
|
handleWelcomeScreenModal,
|
||||||
handleMainWindowIsShown,
|
handleMainWindowIsShown,
|
||||||
} from './intercom';
|
} from './intercom';
|
||||||
|
@ -22,9 +14,6 @@ import {
|
||||||
jest.mock('common/config', () => ({
|
jest.mock('common/config', () => ({
|
||||||
setServers: jest.fn(),
|
setServers: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('common/tabs/TabView', () => ({
|
|
||||||
getDefaultConfigTeamFromTeam: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('main/notifications', () => ({}));
|
jest.mock('main/notifications', () => ({}));
|
||||||
jest.mock('common/servers/serverManager', () => ({
|
jest.mock('common/servers/serverManager', () => ({
|
||||||
setTabIsOpen: jest.fn(),
|
setTabIsOpen: jest.fn(),
|
||||||
|
@ -45,224 +34,13 @@ jest.mock('main/views/viewManager', () => ({}));
|
||||||
jest.mock('main/views/modalManager', () => ({
|
jest.mock('main/views/modalManager', () => ({
|
||||||
addModal: jest.fn(),
|
addModal: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
|
||||||
switchServer: jest.fn(),
|
|
||||||
switchTab: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('./app', () => ({}));
|
jest.mock('./app', () => ({}));
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
name: 'tab-1',
|
|
||||||
order: 0,
|
|
||||||
isOpen: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tab-2',
|
|
||||||
order: 2,
|
|
||||||
isOpen: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tab-3',
|
|
||||||
order: 1,
|
|
||||||
isOpen: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const teams = [
|
|
||||||
{
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'server-1',
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
tabs,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('main/app/intercom', () => {
|
describe('main/app/intercom', () => {
|
||||||
describe('handleCloseTab', () => {
|
|
||||||
it('should close the specified tab and switch to the next open tab', () => {
|
|
||||||
ServerManager.getTab.mockReturnValue({server: {id: 'server-1'}});
|
|
||||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
|
|
||||||
handleCloseTab(null, 'tab-3');
|
|
||||||
expect(ServerManager.setTabIsOpen).toBeCalledWith('tab-3', false);
|
|
||||||
expect(WindowManager.switchTab).toBeCalledWith('tab-2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleOpenTab', () => {
|
|
||||||
it('should open the specified tab', () => {
|
|
||||||
handleOpenTab(null, 'tab-1');
|
|
||||||
expect(WindowManager.switchTab).toBeCalledWith('tab-1');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleNewServerModal', () => {
|
|
||||||
let teamsCopy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
getLocalURLString.mockReturnValue('/some/index.html');
|
|
||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
|
||||||
MainWindow.get.mockReturnValue({});
|
|
||||||
|
|
||||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
|
||||||
ServerManager.getAllServers.mockReturnValue([]);
|
|
||||||
ServerManager.addServer.mockImplementation(() => {
|
|
||||||
const newTeam = {
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'new-team',
|
|
||||||
url: 'http://new-team.com',
|
|
||||||
tabs,
|
|
||||||
};
|
|
||||||
teamsCopy = [
|
|
||||||
...teamsCopy,
|
|
||||||
newTeam,
|
|
||||||
];
|
|
||||||
return newTeam;
|
|
||||||
});
|
|
||||||
ServerManager.hasServers.mockReturnValue(Boolean(teamsCopy.length));
|
|
||||||
|
|
||||||
getDefaultConfigTeamFromTeam.mockImplementation((team) => ({
|
|
||||||
...team,
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add new team to the config', async () => {
|
|
||||||
const promise = Promise.resolve({
|
|
||||||
name: 'new-team',
|
|
||||||
url: 'http://new-team.com',
|
|
||||||
});
|
|
||||||
ModalManager.addModal.mockReturnValue(promise);
|
|
||||||
|
|
||||||
handleNewServerModal();
|
|
||||||
await promise;
|
|
||||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'new-team',
|
|
||||||
url: 'http://new-team.com',
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
expect(WindowManager.switchServer).toBeCalledWith('server-1', true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleEditServerModal', () => {
|
|
||||||
let teamsCopy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
getLocalURLString.mockReturnValue('/some/index.html');
|
|
||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
|
||||||
MainWindow.get.mockReturnValue({});
|
|
||||||
|
|
||||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
|
||||||
ServerManager.getServer.mockImplementation((id) => {
|
|
||||||
if (id !== teamsCopy[0].id) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return {...teamsCopy[0], toMattermostTeam: jest.fn()};
|
|
||||||
});
|
|
||||||
ServerManager.editServer.mockImplementation((id, team) => {
|
|
||||||
if (id !== teamsCopy[0].id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newTeam = {
|
|
||||||
...teamsCopy[0],
|
|
||||||
...team,
|
|
||||||
};
|
|
||||||
teamsCopy = [newTeam];
|
|
||||||
});
|
|
||||||
ServerManager.getAllServers.mockReturnValue(teamsCopy.map((team) => ({...team, toMattermostTeam: jest.fn()})));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing when the server cannot be found', () => {
|
|
||||||
handleEditServerModal(null, 'bad-server');
|
|
||||||
expect(ModalManager.addModal).not.toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should edit the existing team', async () => {
|
|
||||||
const promise = Promise.resolve({
|
|
||||||
name: 'new-team',
|
|
||||||
url: 'http://new-team.com',
|
|
||||||
});
|
|
||||||
ModalManager.addModal.mockReturnValue(promise);
|
|
||||||
|
|
||||||
handleEditServerModal(null, 'server-1');
|
|
||||||
await promise;
|
|
||||||
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'server-1',
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'new-team',
|
|
||||||
url: 'http://new-team.com',
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleRemoveServerModal', () => {
|
|
||||||
let teamsCopy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
getLocalURLString.mockReturnValue('/some/index.html');
|
|
||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
|
||||||
MainWindow.get.mockReturnValue({});
|
|
||||||
|
|
||||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
|
||||||
ServerManager.getServer.mockImplementation((id) => {
|
|
||||||
if (id !== teamsCopy[0].id) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return teamsCopy[0];
|
|
||||||
});
|
|
||||||
ServerManager.removeServer.mockImplementation(() => {
|
|
||||||
teamsCopy = [];
|
|
||||||
});
|
|
||||||
ServerManager.getAllServers.mockReturnValue(teamsCopy);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove the existing team', async () => {
|
|
||||||
const promise = Promise.resolve(true);
|
|
||||||
ModalManager.addModal.mockReturnValue(promise);
|
|
||||||
|
|
||||||
handleRemoveServerModal(null, 'server-1');
|
|
||||||
await promise;
|
|
||||||
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'server-1',
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not remove the existing team when clicking Cancel', async () => {
|
|
||||||
const promise = Promise.resolve(false);
|
|
||||||
ModalManager.addModal.mockReturnValue(promise);
|
|
||||||
|
|
||||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'server-1',
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
|
|
||||||
handleRemoveServerModal(null, 'server-1');
|
|
||||||
await promise;
|
|
||||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
|
||||||
id: 'server-1',
|
|
||||||
name: 'server-1',
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
tabs,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleWelcomeScreenModal', () => {
|
describe('handleWelcomeScreenModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getLocalURLString.mockReturnValue('/some/index.html');
|
getLocalURLString.mockReturnValue('/some/index.html');
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import {app, dialog, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
|
import {app, dialog, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
|
||||||
|
|
||||||
import {Team, MattermostTeam} from 'types/config';
|
import {MattermostTeam} from 'types/config';
|
||||||
import {MentionData} from 'types/notification';
|
import {MentionData} from 'types/notification';
|
||||||
|
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
|
@ -14,10 +14,10 @@ import {displayMention} from 'main/notifications';
|
||||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
import ServerManager from 'common/servers/serverManager';
|
import ServerManager from 'common/servers/serverManager';
|
||||||
import ModalManager from 'main/views/modalManager';
|
import ModalManager from 'main/views/modalManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
import {handleAppBeforeQuit} from './app';
|
import {handleAppBeforeQuit} from './app';
|
||||||
|
import {handleNewServerModal, switchServer} from './servers';
|
||||||
|
|
||||||
const log = new Logger('App.Intercom');
|
const log = new Logger('App.Intercom');
|
||||||
|
|
||||||
|
@ -35,49 +35,6 @@ export function handleQuit(e: IpcMainEvent, reason: string, stack: string) {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleSwitchServer(event: IpcMainEvent, serverId: string) {
|
|
||||||
log.silly('handleSwitchServer', serverId);
|
|
||||||
WindowManager.switchServer(serverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleSwitchTab(event: IpcMainEvent, tabId: string) {
|
|
||||||
log.silly('handleSwitchTab', {tabId});
|
|
||||||
WindowManager.switchTab(tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleCloseTab(event: IpcMainEvent, tabId: string) {
|
|
||||||
log.debug('handleCloseTab', {tabId});
|
|
||||||
|
|
||||||
const tab = ServerManager.getTab(tabId);
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ServerManager.setTabIsOpen(tabId, false);
|
|
||||||
const nextTab = ServerManager.getLastActiveTabForServer(tab.server.id);
|
|
||||||
WindowManager.switchTab(nextTab.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleOpenTab(event: IpcMainEvent, tabId: string) {
|
|
||||||
log.debug('handleOpenTab', {tabId});
|
|
||||||
|
|
||||||
ServerManager.setTabIsOpen(tabId, true);
|
|
||||||
WindowManager.switchTab(tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleGetOrderedServers() {
|
|
||||||
return ServerManager.getOrderedServers().map((srv) => srv.toMattermostTeam());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleGetOrderedTabsForServer(event: IpcMainInvokeEvent, serverId: string) {
|
|
||||||
return ServerManager.getOrderedTabsForServer(serverId).map((tab) => tab.toMattermostTab());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleGetLastActive() {
|
|
||||||
const server = ServerManager.getCurrentServer();
|
|
||||||
const tab = ServerManager.getLastActiveTabForServer(server.id);
|
|
||||||
return {server: server.id, tab: tab.id};
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleShowOnboardingScreens(showWelcomeScreen: boolean, showNewServerModal: boolean, mainWindowIsVisible: boolean) {
|
function handleShowOnboardingScreens(showWelcomeScreen: boolean, showNewServerModal: boolean, mainWindowIsVisible: boolean) {
|
||||||
log.debug('handleShowOnboardingScreens', {showWelcomeScreen, showNewServerModal, mainWindowIsVisible});
|
log.debug('handleShowOnboardingScreens', {showWelcomeScreen, showNewServerModal, mainWindowIsVisible});
|
||||||
|
|
||||||
|
@ -126,101 +83,6 @@ export function handleMainWindowIsShown() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleNewServerModal() {
|
|
||||||
log.debug('handleNewServerModal');
|
|
||||||
|
|
||||||
const html = getLocalURLString('newServer.html');
|
|
||||||
|
|
||||||
const preload = getLocalPreload('desktopAPI.js');
|
|
||||||
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (!mainWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const modalPromise = ModalManager.addModal<MattermostTeam[], Team>('newServer', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
|
|
||||||
if (modalPromise) {
|
|
||||||
modalPromise.then((data) => {
|
|
||||||
const newTeam = ServerManager.addServer(data);
|
|
||||||
WindowManager.switchServer(newTeam.id, true);
|
|
||||||
}).catch((e) => {
|
|
||||||
// e is undefined for user cancellation
|
|
||||||
if (e) {
|
|
||||||
log.error(`there was an error in the new server modal: ${e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.warn('There is already a new server modal');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleEditServerModal(e: IpcMainEvent, id: string) {
|
|
||||||
log.debug('handleEditServerModal', id);
|
|
||||||
|
|
||||||
const html = getLocalURLString('editServer.html');
|
|
||||||
|
|
||||||
const preload = getLocalPreload('desktopAPI.js');
|
|
||||||
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (!mainWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const server = ServerManager.getServer(id);
|
|
||||||
if (!server) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const modalPromise = ModalManager.addModal<{currentTeams: MattermostTeam[]; team: MattermostTeam}, Team>(
|
|
||||||
'editServer',
|
|
||||||
html,
|
|
||||||
preload,
|
|
||||||
{
|
|
||||||
currentTeams: ServerManager.getAllServers().map((team) => team.toMattermostTeam()),
|
|
||||||
team: server.toMattermostTeam(),
|
|
||||||
},
|
|
||||||
mainWindow);
|
|
||||||
if (modalPromise) {
|
|
||||||
modalPromise.then((data) => ServerManager.editServer(id, data)).catch((e) => {
|
|
||||||
// e is undefined for user cancellation
|
|
||||||
if (e) {
|
|
||||||
log.error(`there was an error in the edit server modal: ${e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.warn('There is already an edit server modal');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleRemoveServerModal(e: IpcMainEvent, id: string) {
|
|
||||||
log.debug('handleRemoveServerModal', id);
|
|
||||||
|
|
||||||
const html = getLocalURLString('removeServer.html');
|
|
||||||
|
|
||||||
const preload = getLocalPreload('desktopAPI.js');
|
|
||||||
|
|
||||||
const server = ServerManager.getServer(id);
|
|
||||||
if (!server) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (!mainWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const modalPromise = ModalManager.addModal<string, boolean>('removeServer', html, preload, server.name, mainWindow);
|
|
||||||
if (modalPromise) {
|
|
||||||
modalPromise.then((remove) => {
|
|
||||||
if (remove) {
|
|
||||||
ServerManager.removeServer(server.id);
|
|
||||||
}
|
|
||||||
}).catch((e) => {
|
|
||||||
// e is undefined for user cancellation
|
|
||||||
if (e) {
|
|
||||||
log.error(`there was an error in the edit server modal: ${e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.warn('There is already an edit server modal');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleWelcomeScreenModal() {
|
export function handleWelcomeScreenModal() {
|
||||||
log.debug('handleWelcomeScreenModal');
|
log.debug('handleWelcomeScreenModal');
|
||||||
|
|
||||||
|
@ -236,7 +98,7 @@ export function handleWelcomeScreenModal() {
|
||||||
if (modalPromise) {
|
if (modalPromise) {
|
||||||
modalPromise.then((data) => {
|
modalPromise.then((data) => {
|
||||||
const newTeam = ServerManager.addServer(data);
|
const newTeam = ServerManager.addServer(data);
|
||||||
WindowManager.switchServer(newTeam.id, true);
|
switchServer(newTeam.id, true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
// e is undefined for user cancellation
|
// e is undefined for user cancellation
|
||||||
if (e) {
|
if (e) {
|
||||||
|
|
318
src/main/app/servers.test.js
Normal file
318
src/main/app/servers.test.js
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import ServerManager from 'common/servers/serverManager';
|
||||||
|
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||||
|
|
||||||
|
import ModalManager from 'main/views/modalManager';
|
||||||
|
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||||
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
|
import * as Servers from './servers';
|
||||||
|
|
||||||
|
jest.mock('electron', () => ({
|
||||||
|
ipcMain: {
|
||||||
|
emit: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('common/servers/serverManager', () => ({
|
||||||
|
setTabIsOpen: jest.fn(),
|
||||||
|
getAllServers: jest.fn(),
|
||||||
|
hasServers: jest.fn(),
|
||||||
|
addServer: jest.fn(),
|
||||||
|
editServer: jest.fn(),
|
||||||
|
removeServer: jest.fn(),
|
||||||
|
getServer: jest.fn(),
|
||||||
|
getTab: jest.fn(),
|
||||||
|
getLastActiveTabForServer: jest.fn(),
|
||||||
|
getServerLog: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('common/tabs/TabView', () => ({
|
||||||
|
getDefaultConfigTeamFromTeam: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('main/views/modalManager', () => ({
|
||||||
|
addModal: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('main/utils', () => ({
|
||||||
|
getLocalPreload: jest.fn(),
|
||||||
|
getLocalURLString: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
|
get: jest.fn(),
|
||||||
|
show: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('main/views/viewManager', () => ({
|
||||||
|
getView: jest.fn(),
|
||||||
|
showById: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
name: 'tab-1',
|
||||||
|
order: 0,
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-2',
|
||||||
|
order: 2,
|
||||||
|
isOpen: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-3',
|
||||||
|
order: 1,
|
||||||
|
isOpen: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const teams = [
|
||||||
|
{
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'server-1',
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
tabs,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('main/app/servers', () => {
|
||||||
|
describe('switchServer', () => {
|
||||||
|
const views = new Map([
|
||||||
|
['tab-1', {id: 'tab-1'}],
|
||||||
|
['tab-2', {id: 'tab-2'}],
|
||||||
|
['tab-3', {id: 'tab-3'}],
|
||||||
|
]);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const server1 = {
|
||||||
|
id: 'server-1',
|
||||||
|
};
|
||||||
|
const server2 = {
|
||||||
|
id: 'server-2',
|
||||||
|
};
|
||||||
|
ServerManager.getServer.mockImplementation((name) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'server-1':
|
||||||
|
return server1;
|
||||||
|
case 'server-2':
|
||||||
|
return server2;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ServerManager.getServerLog.mockReturnValue({debug: jest.fn(), error: jest.fn()});
|
||||||
|
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
|
jest.clearAllTimers();
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if cannot find the server', () => {
|
||||||
|
Servers.switchServer('server-3');
|
||||||
|
expect(ViewManager.showById).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show first open tab in order when last active not defined', () => {
|
||||||
|
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
||||||
|
Servers.switchServer('server-1');
|
||||||
|
expect(ViewManager.showById).toHaveBeenCalledWith('tab-3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show last active tab of chosen server', () => {
|
||||||
|
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
|
||||||
|
Servers.switchServer('server-2');
|
||||||
|
expect(ViewManager.showById).toHaveBeenCalledWith('tab-2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for view to exist if specified', () => {
|
||||||
|
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
||||||
|
views.delete('tab-3');
|
||||||
|
Servers.switchServer('server-1', true);
|
||||||
|
expect(ViewManager.showById).not.toBeCalled();
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(200);
|
||||||
|
expect(ViewManager.showById).not.toBeCalled();
|
||||||
|
|
||||||
|
views.set('tab-3', {});
|
||||||
|
jest.advanceTimersByTime(200);
|
||||||
|
expect(ViewManager.showById).toBeCalledWith('tab-3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleNewServerModal', () => {
|
||||||
|
let teamsCopy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getLocalURLString.mockReturnValue('/some/index.html');
|
||||||
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
|
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||||
|
ServerManager.getAllServers.mockReturnValue([]);
|
||||||
|
ServerManager.addServer.mockImplementation(() => {
|
||||||
|
const newTeam = {
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'new-team',
|
||||||
|
url: 'http://new-team.com',
|
||||||
|
tabs,
|
||||||
|
};
|
||||||
|
teamsCopy = [
|
||||||
|
...teamsCopy,
|
||||||
|
newTeam,
|
||||||
|
];
|
||||||
|
return newTeam;
|
||||||
|
});
|
||||||
|
ServerManager.hasServers.mockReturnValue(Boolean(teamsCopy.length));
|
||||||
|
ServerManager.getServerLog.mockReturnValue({debug: jest.fn(), error: jest.fn()});
|
||||||
|
|
||||||
|
getDefaultConfigTeamFromTeam.mockImplementation((team) => ({
|
||||||
|
...team,
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add new team to the config', async () => {
|
||||||
|
const data = {
|
||||||
|
name: 'new-team',
|
||||||
|
url: 'http://new-team.com',
|
||||||
|
};
|
||||||
|
const promise = Promise.resolve(data);
|
||||||
|
ModalManager.addModal.mockReturnValue(promise);
|
||||||
|
|
||||||
|
Servers.handleNewServerModal();
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(ServerManager.addServer).toHaveBeenCalledWith(data);
|
||||||
|
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'new-team',
|
||||||
|
url: 'http://new-team.com',
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: For some reason jest won't recognize this as being called
|
||||||
|
//expect(spy).toHaveBeenCalledWith('server-1', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleEditServerModal', () => {
|
||||||
|
let teamsCopy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getLocalURLString.mockReturnValue('/some/index.html');
|
||||||
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
|
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||||
|
ServerManager.getServer.mockImplementation((id) => {
|
||||||
|
if (id !== teamsCopy[0].id) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {...teamsCopy[0], toMattermostTeam: jest.fn()};
|
||||||
|
});
|
||||||
|
ServerManager.editServer.mockImplementation((id, team) => {
|
||||||
|
if (id !== teamsCopy[0].id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newTeam = {
|
||||||
|
...teamsCopy[0],
|
||||||
|
...team,
|
||||||
|
};
|
||||||
|
teamsCopy = [newTeam];
|
||||||
|
});
|
||||||
|
ServerManager.getAllServers.mockReturnValue(teamsCopy.map((team) => ({...team, toMattermostTeam: jest.fn()})));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when the server cannot be found', () => {
|
||||||
|
Servers.handleEditServerModal(null, 'bad-server');
|
||||||
|
expect(ModalManager.addModal).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should edit the existing team', async () => {
|
||||||
|
const promise = Promise.resolve({
|
||||||
|
name: 'new-team',
|
||||||
|
url: 'http://new-team.com',
|
||||||
|
});
|
||||||
|
ModalManager.addModal.mockReturnValue(promise);
|
||||||
|
|
||||||
|
Servers.handleEditServerModal(null, 'server-1');
|
||||||
|
await promise;
|
||||||
|
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'server-1',
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'new-team',
|
||||||
|
url: 'http://new-team.com',
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleRemoveServerModal', () => {
|
||||||
|
let teamsCopy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getLocalURLString.mockReturnValue('/some/index.html');
|
||||||
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
|
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||||
|
ServerManager.getServer.mockImplementation((id) => {
|
||||||
|
if (id !== teamsCopy[0].id) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return teamsCopy[0];
|
||||||
|
});
|
||||||
|
ServerManager.removeServer.mockImplementation(() => {
|
||||||
|
teamsCopy = [];
|
||||||
|
});
|
||||||
|
ServerManager.getAllServers.mockReturnValue(teamsCopy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the existing team', async () => {
|
||||||
|
const promise = Promise.resolve(true);
|
||||||
|
ModalManager.addModal.mockReturnValue(promise);
|
||||||
|
|
||||||
|
Servers.handleRemoveServerModal(null, 'server-1');
|
||||||
|
await promise;
|
||||||
|
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'server-1',
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not remove the existing team when clicking Cancel', async () => {
|
||||||
|
const promise = Promise.resolve(false);
|
||||||
|
ModalManager.addModal.mockReturnValue(promise);
|
||||||
|
|
||||||
|
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'server-1',
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Servers.handleRemoveServerModal(null, 'server-1');
|
||||||
|
await promise;
|
||||||
|
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||||
|
id: 'server-1',
|
||||||
|
name: 'server-1',
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
tabs,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
134
src/main/app/servers.ts
Normal file
134
src/main/app/servers.ts
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {IpcMainEvent, ipcMain} from 'electron';
|
||||||
|
|
||||||
|
import {MattermostTeam, Team} from 'types/config';
|
||||||
|
|
||||||
|
import {UPDATE_SHORTCUT_MENU} from 'common/communication';
|
||||||
|
import {Logger} from 'common/log';
|
||||||
|
import ServerManager from 'common/servers/serverManager';
|
||||||
|
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
import ModalManager from 'main/views/modalManager';
|
||||||
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
|
|
||||||
|
const log = new Logger('App.Servers');
|
||||||
|
|
||||||
|
export const switchServer = (serverId: string, waitForViewToExist = false) => {
|
||||||
|
ServerManager.getServerLog(serverId, 'WindowManager').debug('switchServer');
|
||||||
|
MainWindow.show();
|
||||||
|
const server = ServerManager.getServer(serverId);
|
||||||
|
if (!server) {
|
||||||
|
ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nextTab = ServerManager.getLastActiveTabForServer(serverId);
|
||||||
|
if (waitForViewToExist) {
|
||||||
|
const timeout = setInterval(() => {
|
||||||
|
if (ViewManager.getView(nextTab.id)) {
|
||||||
|
ViewManager.showById(nextTab.id);
|
||||||
|
clearInterval(timeout);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
ViewManager.showById(nextTab.id);
|
||||||
|
}
|
||||||
|
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleNewServerModal = () => {
|
||||||
|
log.debug('handleNewServerModal');
|
||||||
|
|
||||||
|
const html = getLocalURLString('newServer.html');
|
||||||
|
|
||||||
|
const preload = getLocalPreload('desktopAPI.js');
|
||||||
|
|
||||||
|
const mainWindow = MainWindow.get();
|
||||||
|
if (!mainWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const modalPromise = ModalManager.addModal<MattermostTeam[], Team>('newServer', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
|
||||||
|
if (modalPromise) {
|
||||||
|
modalPromise.then((data) => {
|
||||||
|
const newTeam = ServerManager.addServer(data);
|
||||||
|
switchServer(newTeam.id, true);
|
||||||
|
}).catch((e) => {
|
||||||
|
// e is undefined for user cancellation
|
||||||
|
if (e) {
|
||||||
|
log.error(`there was an error in the new server modal: ${e}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.warn('There is already a new server modal');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleEditServerModal = (e: IpcMainEvent, id: string) => {
|
||||||
|
log.debug('handleEditServerModal', id);
|
||||||
|
|
||||||
|
const html = getLocalURLString('editServer.html');
|
||||||
|
|
||||||
|
const preload = getLocalPreload('desktopAPI.js');
|
||||||
|
|
||||||
|
const mainWindow = MainWindow.get();
|
||||||
|
if (!mainWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const server = ServerManager.getServer(id);
|
||||||
|
if (!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const modalPromise = ModalManager.addModal<{currentTeams: MattermostTeam[]; team: MattermostTeam}, Team>(
|
||||||
|
'editServer',
|
||||||
|
html,
|
||||||
|
preload,
|
||||||
|
{
|
||||||
|
currentTeams: ServerManager.getAllServers().map((team) => team.toMattermostTeam()),
|
||||||
|
team: server.toMattermostTeam(),
|
||||||
|
},
|
||||||
|
mainWindow);
|
||||||
|
if (modalPromise) {
|
||||||
|
modalPromise.then((data) => ServerManager.editServer(id, data)).catch((e) => {
|
||||||
|
// e is undefined for user cancellation
|
||||||
|
if (e) {
|
||||||
|
log.error(`there was an error in the edit server modal: ${e}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.warn('There is already an edit server modal');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleRemoveServerModal = (e: IpcMainEvent, id: string) => {
|
||||||
|
log.debug('handleRemoveServerModal', id);
|
||||||
|
|
||||||
|
const html = getLocalURLString('removeServer.html');
|
||||||
|
|
||||||
|
const preload = getLocalPreload('desktopAPI.js');
|
||||||
|
|
||||||
|
const server = ServerManager.getServer(id);
|
||||||
|
if (!server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mainWindow = MainWindow.get();
|
||||||
|
if (!mainWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const modalPromise = ModalManager.addModal<string, boolean>('removeServer', html, preload, server.name, mainWindow);
|
||||||
|
if (modalPromise) {
|
||||||
|
modalPromise.then((remove) => {
|
||||||
|
if (remove) {
|
||||||
|
ServerManager.removeServer(server.id);
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
// e is undefined for user cancellation
|
||||||
|
if (e) {
|
||||||
|
log.error(`there was an error in the edit server modal: ${e}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.warn('There is already an edit server modal');
|
||||||
|
}
|
||||||
|
};
|
39
src/main/app/tabs.test.js
Normal file
39
src/main/app/tabs.test.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import ServerManager from 'common/servers/serverManager';
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
|
import {
|
||||||
|
handleCloseTab,
|
||||||
|
handleOpenTab,
|
||||||
|
} from './tabs';
|
||||||
|
|
||||||
|
jest.mock('common/servers/serverManager', () => ({
|
||||||
|
setTabIsOpen: jest.fn(),
|
||||||
|
getTab: jest.fn(),
|
||||||
|
getLastActiveTabForServer: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('main/views/viewManager', () => ({
|
||||||
|
showById: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('main/app/tabs', () => {
|
||||||
|
describe('handleCloseTab', () => {
|
||||||
|
it('should close the specified tab and switch to the next open tab', () => {
|
||||||
|
ServerManager.getTab.mockReturnValue({server: {id: 'server-1'}});
|
||||||
|
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
|
||||||
|
handleCloseTab(null, 'tab-3');
|
||||||
|
expect(ServerManager.setTabIsOpen).toBeCalledWith('tab-3', false);
|
||||||
|
expect(ViewManager.showById).toBeCalledWith('tab-2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleOpenTab', () => {
|
||||||
|
it('should open the specified tab', () => {
|
||||||
|
handleOpenTab(null, 'tab-1');
|
||||||
|
expect(ViewManager.showById).toBeCalledWith('tab-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
73
src/main/app/tabs.ts
Normal file
73
src/main/app/tabs.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||||
|
|
||||||
|
import ServerManager from 'common/servers/serverManager';
|
||||||
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
|
const log = new Logger('App.Tabs');
|
||||||
|
|
||||||
|
export const handleCloseTab = (event: IpcMainEvent, tabId: string) => {
|
||||||
|
log.debug('handleCloseTab', {tabId});
|
||||||
|
|
||||||
|
const tab = ServerManager.getTab(tabId);
|
||||||
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ServerManager.setTabIsOpen(tabId, false);
|
||||||
|
const nextTab = ServerManager.getLastActiveTabForServer(tab.server.id);
|
||||||
|
ViewManager.showById(nextTab.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleOpenTab = (event: IpcMainEvent, tabId: string) => {
|
||||||
|
log.debug('handleOpenTab', {tabId});
|
||||||
|
|
||||||
|
ServerManager.setTabIsOpen(tabId, true);
|
||||||
|
ViewManager.showById(tabId);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectNextTab = () => {
|
||||||
|
selectTab((order) => order + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectPreviousTab = () => {
|
||||||
|
selectTab((order, length) => (length + (order - 1)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleGetOrderedTabsForServer = (event: IpcMainInvokeEvent, serverId: string) => {
|
||||||
|
return ServerManager.getOrderedTabsForServer(serverId).map((tab) => tab.toMattermostTab());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleGetLastActive = () => {
|
||||||
|
const server = ServerManager.getCurrentServer();
|
||||||
|
const tab = ServerManager.getLastActiveTabForServer(server.id);
|
||||||
|
return {server: server.id, tab: tab.id};
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectTab = (fn: (order: number, length: number) => number) => {
|
||||||
|
const currentView = ViewManager.getCurrentView();
|
||||||
|
if (!currentView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTeamTabs = ServerManager.getOrderedTabsForServer(currentView.tab.server.id).map((tab, index) => ({tab, index}));
|
||||||
|
const filteredTabs = currentTeamTabs?.filter((tab) => tab.tab.isOpen);
|
||||||
|
const currentTab = currentTeamTabs?.find((tab) => tab.tab.type === currentView.tab.type);
|
||||||
|
if (!currentTeamTabs || !currentTab || !filteredTabs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentOrder = currentTab.index;
|
||||||
|
let nextIndex = -1;
|
||||||
|
while (nextIndex === -1) {
|
||||||
|
const nextOrder = (fn(currentOrder, currentTeamTabs.length) % currentTeamTabs.length);
|
||||||
|
nextIndex = filteredTabs.findIndex((tab) => tab.index === nextOrder);
|
||||||
|
currentOrder = nextOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTab = filteredTabs[nextIndex].tab;
|
||||||
|
ViewManager.showById(newTab.id);
|
||||||
|
};
|
|
@ -52,7 +52,6 @@ jest.mock('main/menus/tray', () => ({}));
|
||||||
jest.mock('main/tray/tray', () => ({}));
|
jest.mock('main/tray/tray', () => ({}));
|
||||||
jest.mock('main/views/viewManager', () => ({}));
|
jest.mock('main/views/viewManager', () => ({}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({}));
|
jest.mock('main/windows/mainWindow', () => ({}));
|
||||||
jest.mock('main/windows/windowManager', () => ({}));
|
|
||||||
|
|
||||||
jest.mock('./initialize', () => ({
|
jest.mock('./initialize', () => ({
|
||||||
mainProtocol: 'mattermost',
|
mainProtocol: 'mattermost',
|
||||||
|
|
|
@ -27,7 +27,6 @@ import {createMenu as createTrayMenu} from 'main/menus/tray';
|
||||||
import {ServerInfo} from 'main/server/serverInfo';
|
import {ServerInfo} from 'main/server/serverInfo';
|
||||||
import {setTrayMenu} from 'main/tray/tray';
|
import {setTrayMenu} from 'main/tray/tray';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
import {mainProtocol} from './initialize';
|
import {mainProtocol} from './initialize';
|
||||||
|
@ -39,7 +38,8 @@ const log = new Logger('App.Utils');
|
||||||
|
|
||||||
export function openDeepLink(deeplinkingUrl: string) {
|
export function openDeepLink(deeplinkingUrl: string) {
|
||||||
try {
|
try {
|
||||||
WindowManager.showMainWindow(deeplinkingUrl);
|
MainWindow.show();
|
||||||
|
ViewManager.handleDeepLink(deeplinkingUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(`There was an error opening the deeplinking url: ${err}`);
|
log.error(`There was an error opening the deeplinking url: ${err}`);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ export function handleUpdateMenuEvent() {
|
||||||
Menu.setApplicationMenu(aMenu);
|
Menu.setApplicationMenu(aMenu);
|
||||||
aMenu.addListener('menu-will-close', () => {
|
aMenu.addListener('menu-will-close', () => {
|
||||||
ViewManager.focusCurrentView();
|
ViewManager.focusCurrentView();
|
||||||
WindowManager.sendToRenderer(APP_MENU_WILL_CLOSE);
|
MainWindow.sendToRenderer(APP_MENU_WILL_CLOSE);
|
||||||
});
|
});
|
||||||
|
|
||||||
// set up context menu for tray icon
|
// set up context menu for tray icon
|
||||||
|
|
57
src/main/app/windows.ts
Normal file
57
src/main/app/windows.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {BrowserWindow, IpcMainEvent, systemPreferences} from 'electron';
|
||||||
|
|
||||||
|
import {Logger} from 'common/log';
|
||||||
|
import Config from 'common/config';
|
||||||
|
|
||||||
|
const log = new Logger('App.Windows');
|
||||||
|
|
||||||
|
export const handleGetDarkMode = () => {
|
||||||
|
return Config.darkMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleClose = (event: IpcMainEvent) => BrowserWindow.fromWebContents(event.sender)?.close();
|
||||||
|
export const handleMaximize = (event: IpcMainEvent) => BrowserWindow.fromWebContents(event.sender)?.maximize();
|
||||||
|
export const handleMinimize = (event: IpcMainEvent) => BrowserWindow.fromWebContents(event.sender)?.minimize();
|
||||||
|
export const handleRestore = (event: IpcMainEvent) => {
|
||||||
|
const window = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.restore();
|
||||||
|
if (window.isFullScreen()) {
|
||||||
|
window.setFullScreen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleDoubleClick = (event: IpcMainEvent, windowType?: string) => {
|
||||||
|
log.debug('handleDoubleClick', windowType);
|
||||||
|
|
||||||
|
let action = 'Maximize';
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
|
||||||
|
}
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!win) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case 'Minimize':
|
||||||
|
if (win.isMinimized()) {
|
||||||
|
win.restore();
|
||||||
|
} else {
|
||||||
|
win.minimize();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Maximize':
|
||||||
|
default:
|
||||||
|
if (win.isMaximized()) {
|
||||||
|
win.unmaximize();
|
||||||
|
} else {
|
||||||
|
win.maximize();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
import {AuthManager} from 'main/authManager';
|
import {AuthManager} from 'main/authManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import ModalManager from 'main/views/modalManager';
|
import ModalManager from 'main/views/modalManager';
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
jest.mock('common/config', () => ({
|
jest.mock('common/config', () => ({
|
||||||
teams: [{
|
teams: [{
|
||||||
|
@ -89,8 +89,8 @@ jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn().mockImplementation(() => ({})),
|
get: jest.fn().mockImplementation(() => ({})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/views/viewManager', () => ({
|
||||||
getServerURLFromWebContentsId: jest.fn(),
|
getViewByWebContentsId: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('main/views/modalManager', () => ({
|
jest.mock('main/views/modalManager', () => ({
|
||||||
|
@ -109,42 +109,42 @@ describe('main/authManager', () => {
|
||||||
authManager.popPermissionModal = jest.fn();
|
authManager.popPermissionModal = jest.fn();
|
||||||
|
|
||||||
it('should not pop any modal on a missing server', () => {
|
it('should not pop any modal on a missing server', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(undefined);
|
ViewManager.getViewByWebContentsId.mockReturnValue(undefined);
|
||||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 0}, {url: 'http://badurl.com/'}, null, jest.fn());
|
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 0}, {url: 'http://badurl.com/'}, null, jest.fn());
|
||||||
expect(authManager.popLoginModal).not.toBeCalled();
|
expect(authManager.popLoginModal).not.toBeCalled();
|
||||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should popLoginModal when isTrustedURL', () => {
|
it('should popLoginModal when isTrustedURL', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(new URL('http://trustedurl.com/'));
|
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://trustedurl.com/')}}});
|
||||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://trustedurl.com/'}, null, jest.fn());
|
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://trustedurl.com/'}, null, jest.fn());
|
||||||
expect(authManager.popLoginModal).toBeCalled();
|
expect(authManager.popLoginModal).toBeCalled();
|
||||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should popLoginModal when isCustomLoginURL', () => {
|
it('should popLoginModal when isCustomLoginURL', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(new URL('http://customloginurl.com/'));
|
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://customloginurl.com/')}}});
|
||||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://customloginurl.com/'}, null, jest.fn());
|
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://customloginurl.com/'}, null, jest.fn());
|
||||||
expect(authManager.popLoginModal).toBeCalled();
|
expect(authManager.popLoginModal).toBeCalled();
|
||||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should popLoginModal when has permission', () => {
|
it('should popLoginModal when has permission', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(new URL('http://haspermissionurl.com/'));
|
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://haspermissionurl.com/')}}});
|
||||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://haspermissionurl.com/'}, null, jest.fn());
|
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://haspermissionurl.com/'}, null, jest.fn());
|
||||||
expect(authManager.popLoginModal).toBeCalled();
|
expect(authManager.popLoginModal).toBeCalled();
|
||||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should popPermissionModal when anything else is true', () => {
|
it('should popPermissionModal when anything else is true', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(new URL('http://someotherurl.com/'));
|
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://someotherurl.com/')}}});
|
||||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, jest.fn());
|
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, jest.fn());
|
||||||
expect(authManager.popLoginModal).not.toBeCalled();
|
expect(authManager.popLoginModal).not.toBeCalled();
|
||||||
expect(authManager.popPermissionModal).toBeCalled();
|
expect(authManager.popPermissionModal).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set login callback when logging in', () => {
|
it('should set login callback when logging in', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(new URL('http://someotherurl.com/'));
|
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://someotherurl.com/')}}});
|
||||||
const callback = jest.fn();
|
const callback = jest.fn();
|
||||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, callback);
|
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, callback);
|
||||||
expect(authManager.loginCallbackMap.get('http://someotherurl.com/')).toEqual(callback);
|
expect(authManager.loginCallbackMap.get('http://someotherurl.com/')).toEqual(callback);
|
||||||
|
|
|
@ -12,8 +12,8 @@ import urlUtils from 'common/utils/url';
|
||||||
import modalManager from 'main/views/modalManager';
|
import modalManager from 'main/views/modalManager';
|
||||||
import TrustedOriginsStore from 'main/trustedOrigins';
|
import TrustedOriginsStore from 'main/trustedOrigins';
|
||||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
const log = new Logger('AuthManager');
|
const log = new Logger('AuthManager');
|
||||||
const preload = getLocalPreload('desktopAPI.js');
|
const preload = getLocalPreload('desktopAPI.js');
|
||||||
|
@ -40,7 +40,7 @@ export class AuthManager {
|
||||||
if (!parsedURL) {
|
if (!parsedURL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const serverURL = WindowManager.getServerURLFromWebContentsId(webContents.id);
|
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
|
||||||
if (!serverURL) {
|
if (!serverURL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ jest.mock('main/notifications', () => ({
|
||||||
displayUpgrade: jest.fn(),
|
displayUpgrade: jest.fn(),
|
||||||
displayRestartToUpgrade: jest.fn(),
|
displayRestartToUpgrade: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,6 @@ jest.mock('common/appState', () => ({
|
||||||
jest.mock('./windows/mainWindow', () => ({
|
jest.mock('./windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('./windows/windowManager', () => ({
|
|
||||||
setOverlayIcon: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('main/i18nManager', () => ({
|
jest.mock('main/i18nManager', () => ({
|
||||||
localizeMessage: jest.fn().mockReturnValue(''),
|
localizeMessage: jest.fn().mockReturnValue(''),
|
||||||
|
|
|
@ -77,7 +77,7 @@ jest.mock('macos-notification-state', () => ({
|
||||||
getDoNotDisturb: jest.fn(),
|
getDoNotDisturb: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/notifications', () => ({}));
|
jest.mock('main/notifications', () => ({}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/views/viewManager', () => ({}));
|
jest.mock('main/views/viewManager', () => ({}));
|
||||||
|
|
|
@ -32,7 +32,7 @@ import * as Validator from 'common/Validator';
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import {displayDownloadCompleted} from 'main/notifications';
|
import {displayDownloadCompleted} from 'main/notifications';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
|
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
|
||||||
|
|
||||||
import appVersionManager from './AppVersionManager';
|
import appVersionManager from './AppVersionManager';
|
||||||
|
@ -335,7 +335,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
|
|
||||||
onOpen = () => {
|
onOpen = () => {
|
||||||
this.open = true;
|
this.open = true;
|
||||||
WindowManager.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
MainWindow.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
||||||
};
|
};
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
|
@ -361,7 +361,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
log.debug('openDownloadsDropdown');
|
log.debug('openDownloadsDropdown');
|
||||||
this.open = true;
|
this.open = true;
|
||||||
ipcMain.emit(OPEN_DOWNLOADS_DROPDOWN);
|
ipcMain.emit(OPEN_DOWNLOADS_DROPDOWN);
|
||||||
WindowManager.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
MainWindow.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
||||||
};
|
};
|
||||||
|
|
||||||
hasUpdate = () => {
|
hasUpdate = () => {
|
||||||
|
@ -394,7 +394,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
this.downloads = downloads;
|
this.downloads = downloads;
|
||||||
this.setJson(downloads);
|
this.setJson(downloads);
|
||||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
||||||
WindowManager?.sendToRenderer(UPDATE_DOWNLOADS_DROPDOWN, this.downloads);
|
MainWindow.sendToRenderer(UPDATE_DOWNLOADS_DROPDOWN, this.downloads);
|
||||||
};
|
};
|
||||||
|
|
||||||
private save = (key: string, item: DownloadedItem) => {
|
private save = (key: string, item: DownloadedItem) => {
|
||||||
|
@ -402,7 +402,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
this.downloads[key] = item;
|
this.downloads[key] = item;
|
||||||
this.setValue(key, item);
|
this.setValue(key, item);
|
||||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN, true, this.downloads);
|
||||||
WindowManager?.sendToRenderer(UPDATE_DOWNLOADS_DROPDOWN, this.downloads);
|
MainWindow.sendToRenderer(UPDATE_DOWNLOADS_DROPDOWN, this.downloads);
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleDownloadItemEvents = (item: DownloadItem, webContents: WebContents) => {
|
private handleDownloadItemEvents = (item: DownloadItem, webContents: WebContents) => {
|
||||||
|
@ -485,9 +485,9 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||||
log.debug('shouldShowBadge');
|
log.debug('shouldShowBadge');
|
||||||
|
|
||||||
if (this.open === true) {
|
if (this.open === true) {
|
||||||
WindowManager.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
MainWindow.sendToRenderer(HIDE_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
||||||
} else {
|
} else {
|
||||||
WindowManager.sendToRenderer(SHOW_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
MainWindow.sendToRenderer(SHOW_DOWNLOADS_DROPDOWN_BUTTON_BADGE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,15 @@ jest.mock('common/servers/serverManager', () => ({
|
||||||
getOrderedServers: jest.fn(),
|
getOrderedServers: jest.fn(),
|
||||||
getOrderedTabsForServer: jest.fn(),
|
getOrderedTabsForServer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('main/app/servers', () => ({
|
||||||
|
switchServer: jest.fn(),
|
||||||
|
}));
|
||||||
jest.mock('main/diagnostics', () => ({}));
|
jest.mock('main/diagnostics', () => ({}));
|
||||||
jest.mock('main/downloadsManager', () => ({
|
jest.mock('main/downloadsManager', () => ({
|
||||||
hasDownloads: jest.fn(),
|
hasDownloads: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/views/viewManager', () => ({}));
|
jest.mock('main/views/viewManager', () => ({}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/settingsWindow', () => ({}));
|
jest.mock('main/windows/settingsWindow', () => ({}));
|
||||||
|
|
|
@ -13,12 +13,13 @@ import {Config} from 'common/config';
|
||||||
|
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import ServerManager from 'common/servers/serverManager';
|
import ServerManager from 'common/servers/serverManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import {UpdateManager} from 'main/autoUpdater';
|
import {UpdateManager} from 'main/autoUpdater';
|
||||||
import downloadsManager from 'main/downloadsManager';
|
import downloadsManager from 'main/downloadsManager';
|
||||||
import Diagnostics from 'main/diagnostics';
|
import Diagnostics from 'main/diagnostics';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import SettingsWindow from 'main/windows/settingsWindow';
|
import SettingsWindow from 'main/windows/settingsWindow';
|
||||||
|
import {selectNextTab, selectPreviousTab} from 'main/app/tabs';
|
||||||
|
import {switchServer} from 'main/app/servers';
|
||||||
|
|
||||||
export function createTemplate(config: Config, updateManager: UpdateManager) {
|
export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||||
const separatorItem: MenuItemConstructorOptions = {
|
const separatorItem: MenuItemConstructorOptions = {
|
||||||
|
@ -265,7 +266,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||||
label: team.name,
|
label: team.name,
|
||||||
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+${i + 1}`,
|
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+${i + 1}`,
|
||||||
click() {
|
click() {
|
||||||
WindowManager.switchServer(team.id);
|
switchServer(team.id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (ServerManager.getCurrentServer().id === team.id) {
|
if (ServerManager.getCurrentServer().id === team.id) {
|
||||||
|
@ -274,7 +275,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||||
label: ` ${localizeMessage(`common.tabs.${tab.type}`, getTabDisplayName(tab.type as TabType))}`,
|
label: ` ${localizeMessage(`common.tabs.${tab.type}`, getTabDisplayName(tab.type as TabType))}`,
|
||||||
accelerator: `CmdOrCtrl+${i + 1}`,
|
accelerator: `CmdOrCtrl+${i + 1}`,
|
||||||
click() {
|
click() {
|
||||||
WindowManager.switchTab(tab.id);
|
ViewManager.showById(tab.id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -284,14 +285,14 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||||
label: localizeMessage('main.menus.app.window.selectNextTab', 'Select Next Tab'),
|
label: localizeMessage('main.menus.app.window.selectNextTab', 'Select Next Tab'),
|
||||||
accelerator: 'Ctrl+Tab',
|
accelerator: 'Ctrl+Tab',
|
||||||
click() {
|
click() {
|
||||||
WindowManager.selectNextTab();
|
selectNextTab();
|
||||||
},
|
},
|
||||||
enabled: (teams.length > 1),
|
enabled: (teams.length > 1),
|
||||||
}, {
|
}, {
|
||||||
label: localizeMessage('main.menus.app.window.selectPreviousTab', 'Select Previous Tab'),
|
label: localizeMessage('main.menus.app.window.selectPreviousTab', 'Select Previous Tab'),
|
||||||
accelerator: 'Ctrl+Shift+Tab',
|
accelerator: 'Ctrl+Shift+Tab',
|
||||||
click() {
|
click() {
|
||||||
WindowManager.selectPreviousTab();
|
selectPreviousTab();
|
||||||
},
|
},
|
||||||
enabled: (teams.length > 1),
|
enabled: (teams.length > 1),
|
||||||
}, ...(isMac ? [separatorItem, {
|
}, ...(isMac ? [separatorItem, {
|
||||||
|
|
|
@ -14,9 +14,10 @@ jest.mock('main/i18nManager', () => ({
|
||||||
jest.mock('common/servers/serverManager', () => ({
|
jest.mock('common/servers/serverManager', () => ({
|
||||||
getOrderedServers: jest.fn(),
|
getOrderedServers: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('main/app/servers', () => ({
|
||||||
|
switchServer: jest.fn(),
|
||||||
|
}));
|
||||||
jest.mock('main/windows/settingsWindow', () => ({}));
|
jest.mock('main/windows/settingsWindow', () => ({}));
|
||||||
jest.mock('main/windows/windowManager', () => ({}));
|
|
||||||
|
|
||||||
describe('main/menus/tray', () => {
|
describe('main/menus/tray', () => {
|
||||||
it('should show the first 9 servers (using order)', () => {
|
it('should show the first 9 servers (using order)', () => {
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
|
|
||||||
import {Menu, MenuItem, MenuItemConstructorOptions} from 'electron';
|
import {Menu, MenuItem, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
|
||||||
import ServerManager from 'common/servers/serverManager';
|
import ServerManager from 'common/servers/serverManager';
|
||||||
|
|
||||||
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import SettingsWindow from 'main/windows/settingsWindow';
|
import SettingsWindow from 'main/windows/settingsWindow';
|
||||||
|
import {switchServer} from 'main/app/servers';
|
||||||
|
|
||||||
export function createTemplate() {
|
export function createTemplate() {
|
||||||
const teams = ServerManager.getOrderedServers();
|
const teams = ServerManager.getOrderedServers();
|
||||||
|
@ -17,7 +18,7 @@ export function createTemplate() {
|
||||||
return {
|
return {
|
||||||
label: team.name.length > 50 ? `${team.name.slice(0, 50)}...` : team.name,
|
label: team.name.length > 50 ? `${team.name.slice(0, 50)}...` : team.name,
|
||||||
click: () => {
|
click: () => {
|
||||||
WindowManager.switchServer(team.id);
|
switchServer(team.id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}), {
|
}), {
|
||||||
|
|
|
@ -13,9 +13,8 @@ import {PLAY_SOUND} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
|
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import MainWindow from '../windows/mainWindow';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import WindowManager from '../windows/windowManager';
|
|
||||||
|
|
||||||
import getLinuxDoNotDisturb from './dnd-linux';
|
import getLinuxDoNotDisturb from './dnd-linux';
|
||||||
|
|
||||||
|
@ -83,14 +82,11 @@ jest.mock('../views/viewManager', () => ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
showById: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('../windows/mainWindow', () => ({
|
jest.mock('../windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
}));
|
|
||||||
jest.mock('../windows/windowManager', () => ({
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
flashFrame: jest.fn(),
|
|
||||||
switchTab: jest.fn(),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('main/i18nManager', () => ({
|
jest.mock('main/i18nManager', () => ({
|
||||||
|
@ -192,7 +188,7 @@ describe('main/notifications', () => {
|
||||||
{id: 1},
|
{id: 1},
|
||||||
{soundName: 'test_sound'},
|
{soundName: 'test_sound'},
|
||||||
);
|
);
|
||||||
expect(WindowManager.sendToRenderer).toHaveBeenCalledWith(PLAY_SOUND, 'test_sound');
|
expect(MainWindow.sendToRenderer).toHaveBeenCalledWith(PLAY_SOUND, 'test_sound');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove existing notification from the same channel/team on windows', () => {
|
it('should remove existing notification from the same channel/team on windows', () => {
|
||||||
|
@ -248,7 +244,7 @@ describe('main/notifications', () => {
|
||||||
);
|
);
|
||||||
const mention = mentions.find((m) => m.body === 'mention_click_body');
|
const mention = mentions.find((m) => m.body === 'mention_click_body');
|
||||||
mention.value.click();
|
mention.value.click();
|
||||||
expect(WindowManager.switchTab).toHaveBeenCalledWith('server_id');
|
expect(ViewManager.showById).toHaveBeenCalledWith('server_id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('linux/windows - should not flash frame when config item is not set', () => {
|
it('linux/windows - should not flash frame when config item is not set', () => {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {Logger} from 'common/log';
|
||||||
|
|
||||||
import ViewManager from '../views/viewManager';
|
import ViewManager from '../views/viewManager';
|
||||||
import MainWindow from '../windows/mainWindow';
|
import MainWindow from '../windows/mainWindow';
|
||||||
import WindowManager from '../windows/windowManager';
|
|
||||||
|
|
||||||
import {Mention} from './Mention';
|
import {Mention} from './Mention';
|
||||||
import {DownloadNotification} from './Download';
|
import {DownloadNotification} from './Download';
|
||||||
|
@ -67,7 +66,7 @@ export function displayMention(title: string, body: string, channel: {id: string
|
||||||
}
|
}
|
||||||
const notificationSound = mention.getNotificationSound();
|
const notificationSound = mention.getNotificationSound();
|
||||||
if (notificationSound) {
|
if (notificationSound) {
|
||||||
WindowManager.sendToRenderer(PLAY_SOUND, notificationSound);
|
MainWindow.sendToRenderer(PLAY_SOUND, notificationSound);
|
||||||
}
|
}
|
||||||
flashFrame(true);
|
flashFrame(true);
|
||||||
});
|
});
|
||||||
|
@ -75,7 +74,7 @@ export function displayMention(title: string, body: string, channel: {id: string
|
||||||
mention.on('click', () => {
|
mention.on('click', () => {
|
||||||
log.debug('notification click', serverName, mention);
|
log.debug('notification click', serverName, mention);
|
||||||
if (serverName) {
|
if (serverName) {
|
||||||
WindowManager.switchTab(view.id);
|
ViewManager.showById(view.id);
|
||||||
webcontents.send('notification-clicked', {channel, teamId, url});
|
webcontents.send('notification-clicked', {channel, teamId, url});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,10 +59,9 @@ jest.mock('main/AutoLauncher', () => ({
|
||||||
jest.mock('main/badge', () => ({
|
jest.mock('main/badge', () => ({
|
||||||
setUnreadBadgeSetting: jest.fn(),
|
setUnreadBadgeSetting: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
handleUpdateConfig: jest.fn(),
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
initializeCurrentServerName: jest.fn(),
|
on: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('main/tray', () => {
|
describe('main/tray', () => {
|
||||||
|
|
|
@ -7,12 +7,14 @@ import {app, nativeImage, Tray, systemPreferences, nativeTheme} from 'electron';
|
||||||
|
|
||||||
import AppState from 'common/appState';
|
import AppState from 'common/appState';
|
||||||
import {UPDATE_APPSTATE_TOTALS} from 'common/communication';
|
import {UPDATE_APPSTATE_TOTALS} from 'common/communication';
|
||||||
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
import SettingsWindow from 'main/windows/settingsWindow';
|
||||||
|
|
||||||
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||||
|
const log = new Logger('Tray');
|
||||||
|
|
||||||
let trayImages: Record<string, Electron.NativeImage>;
|
let trayImages: Record<string, Electron.NativeImage>;
|
||||||
let trayIcon: Tray;
|
let trayIcon: Tray;
|
||||||
|
@ -85,12 +87,12 @@ export function setupTray(iconTheme: string) {
|
||||||
|
|
||||||
trayIcon.setToolTip(app.name);
|
trayIcon.setToolTip(app.name);
|
||||||
trayIcon.on('click', () => {
|
trayIcon.on('click', () => {
|
||||||
const mainWindow = MainWindow.get(true)!;
|
const mainWindow = MainWindow.get();
|
||||||
if (mainWindow.isVisible()) {
|
if (mainWindow && mainWindow.isVisible()) {
|
||||||
mainWindow.blur(); // To move focus to the next top-level window in Windows
|
mainWindow.blur(); // To move focus to the next top-level window in Windows
|
||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
} else {
|
} else {
|
||||||
WindowManager.restoreMain();
|
restoreMain();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ export function setupTray(iconTheme: string) {
|
||||||
trayIcon.popUpContextMenu();
|
trayIcon.popUpContextMenu();
|
||||||
});
|
});
|
||||||
trayIcon.on('balloon-click', () => {
|
trayIcon.on('balloon-click', () => {
|
||||||
WindowManager.restoreMain();
|
restoreMain();
|
||||||
});
|
});
|
||||||
|
|
||||||
AppState.on(UPDATE_APPSTATE_TOTALS, (anyExpired: boolean, anyMentions: number, anyUnreads: boolean) => {
|
AppState.on(UPDATE_APPSTATE_TOTALS, (anyExpired: boolean, anyMentions: number, anyUnreads: boolean) => {
|
||||||
|
@ -114,6 +116,32 @@ export function setupTray(iconTheme: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const restoreMain = () => {
|
||||||
|
log.info('restoreMain');
|
||||||
|
MainWindow.show();
|
||||||
|
const mainWindow = MainWindow.get();
|
||||||
|
if (!mainWindow) {
|
||||||
|
throw new Error('Main window does not exist');
|
||||||
|
}
|
||||||
|
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
|
||||||
|
if (mainWindow.isMinimized()) {
|
||||||
|
mainWindow.restore();
|
||||||
|
} else {
|
||||||
|
mainWindow.show();
|
||||||
|
}
|
||||||
|
const settingsWindow = SettingsWindow.get();
|
||||||
|
if (settingsWindow) {
|
||||||
|
settingsWindow.focus();
|
||||||
|
} else {
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
} else if (SettingsWindow.get()) {
|
||||||
|
SettingsWindow.get()?.focus();
|
||||||
|
} else {
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function setTray(status: string, message: string) {
|
function setTray(status: string, message: string) {
|
||||||
if (trayIcon.isDestroyed()) {
|
if (trayIcon.isDestroyed()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {MattermostServer} from 'common/servers/MattermostServer';
|
||||||
import MessagingTabView from 'common/tabs/MessagingTabView';
|
import MessagingTabView from 'common/tabs/MessagingTabView';
|
||||||
|
|
||||||
import MainWindow from '../windows/mainWindow';
|
import MainWindow from '../windows/mainWindow';
|
||||||
import * as WindowManager from '../windows/windowManager';
|
|
||||||
import ContextMenu from '../contextMenu';
|
import ContextMenu from '../contextMenu';
|
||||||
import Utils from '../utils';
|
import Utils from '../utils';
|
||||||
|
|
||||||
|
@ -41,8 +40,6 @@ jest.mock('electron', () => ({
|
||||||
jest.mock('../windows/mainWindow', () => ({
|
jest.mock('../windows/mainWindow', () => ({
|
||||||
focusThreeDotMenu: jest.fn(),
|
focusThreeDotMenu: jest.fn(),
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
}));
|
|
||||||
jest.mock('../windows/windowManager', () => ({
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('common/appState', () => ({
|
jest.mock('common/appState', () => ({
|
||||||
|
@ -181,7 +178,7 @@ describe('main/views/MattermostView', () => {
|
||||||
await expect(promise).rejects.toThrow(error);
|
await expect(promise).rejects.toThrow(error);
|
||||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||||
expect(mattermostView.loadRetry).not.toBeCalled();
|
expect(mattermostView.loadRetry).not.toBeCalled();
|
||||||
expect(WindowManager.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.id, expect.any(String), expect.any(String));
|
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.id, expect.any(String), expect.any(String));
|
||||||
expect(mattermostView.status).toBe(-1);
|
expect(mattermostView.status).toBe(-1);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(retryInBackgroundFn).toBeCalled();
|
expect(retryInBackgroundFn).toBeCalled();
|
||||||
|
@ -426,13 +423,13 @@ describe('main/views/MattermostView', () => {
|
||||||
it('should hide back button on internal url', () => {
|
it('should hide back button on internal url', () => {
|
||||||
Utils.shouldHaveBackBar.mockReturnValue(false);
|
Utils.shouldHaveBackBar.mockReturnValue(false);
|
||||||
mattermostView.handleDidNavigate(null, 'http://server-1.com/path/to/channels');
|
mattermostView.handleDidNavigate(null, 'http://server-1.com/path/to/channels');
|
||||||
expect(WindowManager.sendToRenderer).toHaveBeenCalledWith(TOGGLE_BACK_BUTTON, false);
|
expect(MainWindow.sendToRenderer).toHaveBeenCalledWith(TOGGLE_BACK_BUTTON, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show back button on external url', () => {
|
it('should show back button on external url', () => {
|
||||||
Utils.shouldHaveBackBar.mockReturnValue(true);
|
Utils.shouldHaveBackBar.mockReturnValue(true);
|
||||||
mattermostView.handleDidNavigate(null, 'http://server-2.com/some/other/path');
|
mattermostView.handleDidNavigate(null, 'http://server-2.com/some/other/path');
|
||||||
expect(WindowManager.sendToRenderer).toHaveBeenCalledWith(TOGGLE_BACK_BUTTON, true);
|
expect(MainWindow.sendToRenderer).toHaveBeenCalledWith(TOGGLE_BACK_BUTTON, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {Logger} from 'common/log';
|
||||||
import {TabView} from 'common/tabs/TabView';
|
import {TabView} from 'common/tabs/TabView';
|
||||||
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
|
|
||||||
import ContextMenu from '../contextMenu';
|
import ContextMenu from '../contextMenu';
|
||||||
import {getWindowBoundaries, getLocalPreload, composeUserAgent, shouldHaveBackBar} from '../utils';
|
import {getWindowBoundaries, getLocalPreload, composeUserAgent, shouldHaveBackBar} from '../utils';
|
||||||
|
@ -180,7 +179,7 @@ export class MattermostView extends EventEmitter {
|
||||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||||
if (err.code && err.code.startsWith('ERR_CERT')) {
|
if (err.code && err.code.startsWith('ERR_CERT')) {
|
||||||
WindowManager.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||||
this.emit(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
this.emit(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||||
this.log.info(`Invalid certificate, stop retrying until the user decides what to do: ${err}.`);
|
this.log.info(`Invalid certificate, stop retrying until the user decides what to do: ${err}.`);
|
||||||
this.status = Status.ERROR;
|
this.status = Status.ERROR;
|
||||||
|
@ -394,7 +393,7 @@ export class MattermostView extends EventEmitter {
|
||||||
if (this.maxRetries-- > 0) {
|
if (this.maxRetries-- > 0) {
|
||||||
this.loadRetry(loadURL, err);
|
this.loadRetry(loadURL, err);
|
||||||
} else {
|
} else {
|
||||||
WindowManager.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||||
this.emit(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
this.emit(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||||
this.log.info(`Couldn't establish a connection with ${loadURL}, will continue to retry in the background`, err);
|
this.log.info(`Couldn't establish a connection with ${loadURL}, will continue to retry in the background`, err);
|
||||||
this.status = Status.ERROR;
|
this.status = Status.ERROR;
|
||||||
|
@ -419,14 +418,14 @@ export class MattermostView extends EventEmitter {
|
||||||
|
|
||||||
private loadRetry = (loadURL: string, err: Error) => {
|
private loadRetry = (loadURL: string, err: Error) => {
|
||||||
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
|
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
|
||||||
WindowManager.sendToRenderer(LOAD_RETRY, this.id, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
|
MainWindow.sendToRenderer(LOAD_RETRY, this.id, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
|
||||||
this.log.info(`failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
|
this.log.info(`failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadSuccess = (loadURL: string) => {
|
private loadSuccess = (loadURL: string) => {
|
||||||
return () => {
|
return () => {
|
||||||
this.log.verbose(`finished loading ${loadURL}`);
|
this.log.verbose(`finished loading ${loadURL}`);
|
||||||
WindowManager.sendToRenderer(LOAD_SUCCESS, this.id);
|
MainWindow.sendToRenderer(LOAD_SUCCESS, this.id);
|
||||||
this.maxRetries = MAX_SERVER_RETRIES;
|
this.maxRetries = MAX_SERVER_RETRIES;
|
||||||
if (this.status === Status.LOADING) {
|
if (this.status === Status.LOADING) {
|
||||||
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
||||||
|
@ -476,11 +475,11 @@ export class MattermostView extends EventEmitter {
|
||||||
|
|
||||||
if (shouldHaveBackBar(this.tab.url || '', url)) {
|
if (shouldHaveBackBar(this.tab.url || '', url)) {
|
||||||
this.setBounds(getWindowBoundaries(mainWindow, true));
|
this.setBounds(getWindowBoundaries(mainWindow, true));
|
||||||
WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, true);
|
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, true);
|
||||||
this.log.debug('show back button');
|
this.log.debug('show back button');
|
||||||
} else {
|
} else {
|
||||||
this.setBounds(getWindowBoundaries(mainWindow));
|
this.setBounds(getWindowBoundaries(mainWindow));
|
||||||
WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, false);
|
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, false);
|
||||||
this.log.debug('hide back button');
|
this.log.debug('hide back button');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,10 +54,9 @@ jest.mock('macos-notification-state', () => ({
|
||||||
}));
|
}));
|
||||||
jest.mock('main/downloadsManager', () => ({}));
|
jest.mock('main/downloadsManager', () => ({}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
|
on: jest.fn(),
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
getBounds: jest.fn(),
|
getBounds: jest.fn(),
|
||||||
}));
|
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('fs', () => ({
|
jest.mock('fs', () => ({
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
DOWNLOADS_DROPDOWN_MENU_OPEN_FILE,
|
DOWNLOADS_DROPDOWN_MENU_OPEN_FILE,
|
||||||
DOWNLOADS_DROPDOWN_MENU_SHOW_FILE_IN_FOLDER,
|
DOWNLOADS_DROPDOWN_MENU_SHOW_FILE_IN_FOLDER,
|
||||||
EMIT_CONFIGURATION,
|
EMIT_CONFIGURATION,
|
||||||
|
MAIN_WINDOW_CREATED,
|
||||||
|
MAIN_WINDOW_RESIZED,
|
||||||
OPEN_DOWNLOADS_DROPDOWN_MENU,
|
OPEN_DOWNLOADS_DROPDOWN_MENU,
|
||||||
REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO,
|
REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO,
|
||||||
TOGGLE_DOWNLOADS_DROPDOWN_MENU,
|
TOGGLE_DOWNLOADS_DROPDOWN_MENU,
|
||||||
|
@ -28,7 +30,6 @@ import {
|
||||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
import downloadsManager from 'main/downloadsManager';
|
import downloadsManager from 'main/downloadsManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
|
|
||||||
const log = new Logger('DownloadsDropdownMenuView');
|
const log = new Logger('DownloadsDropdownMenuView');
|
||||||
|
|
||||||
|
@ -43,6 +44,8 @@ export class DownloadsDropdownMenuView {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
|
|
||||||
|
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||||
|
MainWindow.on(MAIN_WINDOW_RESIZED, this.updateWindowBounds);
|
||||||
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN_MENU, this.handleOpen);
|
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN_MENU, this.handleOpen);
|
||||||
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN_MENU, this.handleClose);
|
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN_MENU, this.handleClose);
|
||||||
ipcMain.on(TOGGLE_DOWNLOADS_DROPDOWN_MENU, this.handleToggle);
|
ipcMain.on(TOGGLE_DOWNLOADS_DROPDOWN_MENU, this.handleToggle);
|
||||||
|
@ -55,7 +58,7 @@ export class DownloadsDropdownMenuView {
|
||||||
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN_MENU, this.updateItem);
|
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN_MENU, this.updateItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
init = () => {
|
private init = () => {
|
||||||
this.windowBounds = MainWindow.getBounds();
|
this.windowBounds = MainWindow.getBounds();
|
||||||
if (!this.windowBounds) {
|
if (!this.windowBounds) {
|
||||||
throw new Error('Cannot initialize downloadsDropdownMenuView, missing MainWindow');
|
throw new Error('Cannot initialize downloadsDropdownMenuView, missing MainWindow');
|
||||||
|
@ -79,10 +82,10 @@ export class DownloadsDropdownMenuView {
|
||||||
* This is called every time the "window" is resized so that we can position
|
* This is called every time the "window" is resized so that we can position
|
||||||
* the downloads dropdown at the correct position
|
* the downloads dropdown at the correct position
|
||||||
*/
|
*/
|
||||||
updateWindowBounds = () => {
|
private updateWindowBounds = (newBounds: Electron.Rectangle) => {
|
||||||
log.debug('updateWindowBounds');
|
log.debug('updateWindowBounds');
|
||||||
|
|
||||||
this.windowBounds = MainWindow.getBounds();
|
this.windowBounds = newBounds;
|
||||||
this.updateDownloadsDropdownMenu();
|
this.updateDownloadsDropdownMenu();
|
||||||
this.repositionDownloadsDropdownMenu();
|
this.repositionDownloadsDropdownMenu();
|
||||||
}
|
}
|
||||||
|
@ -134,7 +137,7 @@ export class DownloadsDropdownMenuView {
|
||||||
this.item = undefined;
|
this.item = undefined;
|
||||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM);
|
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM);
|
||||||
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
|
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
|
||||||
WindowManager.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
MainWindow.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleToggle = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload) => {
|
private handleToggle = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload) => {
|
||||||
|
|
|
@ -67,10 +67,9 @@ jest.mock('main/downloadsManager', () => ({
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
|
on: jest.fn(),
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
getBounds: jest.fn(),
|
getBounds: jest.fn(),
|
||||||
}));
|
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ import {
|
||||||
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
||||||
GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION,
|
GET_DOWNLOADED_IMAGE_THUMBNAIL_LOCATION,
|
||||||
DOWNLOADS_DROPDOWN_OPEN_FILE,
|
DOWNLOADS_DROPDOWN_OPEN_FILE,
|
||||||
|
MAIN_WINDOW_CREATED,
|
||||||
|
MAIN_WINDOW_RESIZED,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
|
@ -23,7 +25,6 @@ import {TAB_BAR_HEIGHT, DOWNLOADS_DROPDOWN_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, DOW
|
||||||
|
|
||||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
import downloadsManager from 'main/downloadsManager';
|
import downloadsManager from 'main/downloadsManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
const log = new Logger('DownloadsDropdownView');
|
const log = new Logger('DownloadsDropdownView');
|
||||||
|
@ -35,6 +36,8 @@ export class DownloadsDropdownView {
|
||||||
private view?: BrowserView;
|
private view?: BrowserView;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||||
|
MainWindow.on(MAIN_WINDOW_RESIZED, this.updateWindowBounds);
|
||||||
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN, this.handleOpen);
|
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN, this.handleOpen);
|
||||||
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN, this.handleClose);
|
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN, this.handleClose);
|
||||||
ipcMain.on(EMIT_CONFIGURATION, this.updateDownloadsDropdown);
|
ipcMain.on(EMIT_CONFIGURATION, this.updateDownloadsDropdown);
|
||||||
|
@ -73,10 +76,10 @@ export class DownloadsDropdownView {
|
||||||
* This is called every time the "window" is resized so that we can position
|
* This is called every time the "window" is resized so that we can position
|
||||||
* the downloads dropdown at the correct position
|
* the downloads dropdown at the correct position
|
||||||
*/
|
*/
|
||||||
updateWindowBounds = () => {
|
private updateWindowBounds = (newBounds: Electron.Rectangle) => {
|
||||||
log.debug('updateWindowBounds');
|
log.debug('updateWindowBounds');
|
||||||
|
|
||||||
this.windowBounds = MainWindow.getBounds();
|
this.windowBounds = newBounds;
|
||||||
this.updateDownloadsDropdown();
|
this.updateDownloadsDropdown();
|
||||||
this.repositionDownloadsDropdown();
|
this.repositionDownloadsDropdown();
|
||||||
}
|
}
|
||||||
|
@ -110,7 +113,7 @@ export class DownloadsDropdownView {
|
||||||
MainWindow.get()?.setTopBrowserView(this.view);
|
MainWindow.get()?.setTopBrowserView(this.view);
|
||||||
this.view.webContents.focus();
|
this.view.webContents.focus();
|
||||||
downloadsManager.onOpen();
|
downloadsManager.onOpen();
|
||||||
WindowManager.sendToRenderer(OPEN_DOWNLOADS_DROPDOWN);
|
MainWindow.sendToRenderer(OPEN_DOWNLOADS_DROPDOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClose = () => {
|
private handleClose = () => {
|
||||||
|
@ -118,7 +121,7 @@ export class DownloadsDropdownView {
|
||||||
|
|
||||||
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
|
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
|
||||||
downloadsManager.onClose();
|
downloadsManager.onClose();
|
||||||
WindowManager.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN);
|
MainWindow.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearDownloads = () => {
|
private clearDownloads = () => {
|
||||||
|
|
|
@ -13,6 +13,7 @@ jest.mock('electron', () => ({
|
||||||
|
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('main/views/loadingScreen', () => {
|
describe('main/views/loadingScreen', () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import {BrowserView, app, ipcMain} from 'electron';
|
import {BrowserView, app, ipcMain} from 'electron';
|
||||||
|
|
||||||
import {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
|
import {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, MAIN_WINDOW_RESIZED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
import {getLocalPreload, getLocalURLString, getWindowBoundaries} from 'main/utils';
|
import {getLocalPreload, getLocalURLString, getWindowBoundaries} from 'main/utils';
|
||||||
|
@ -24,6 +24,7 @@ export class LoadingScreen {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.state = LoadingScreenState.HIDDEN;
|
this.state = LoadingScreenState.HIDDEN;
|
||||||
|
|
||||||
|
MainWindow.on(MAIN_WINDOW_RESIZED, this.setBounds);
|
||||||
ipcMain.on(LOADING_SCREEN_ANIMATION_FINISHED, this.handleAnimationFinished);
|
ipcMain.on(LOADING_SCREEN_ANIMATION_FINISHED, this.handleAnimationFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,16 +32,6 @@ export class LoadingScreen {
|
||||||
* Loading Screen
|
* Loading Screen
|
||||||
*/
|
*/
|
||||||
|
|
||||||
setBounds = () => {
|
|
||||||
if (this.view) {
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (!mainWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.view.setBounds(getWindowBoundaries(mainWindow));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDarkMode = (darkMode: boolean) => {
|
setDarkMode = (darkMode: boolean) => {
|
||||||
this.view?.webContents.send(DARK_MODE_CHANGE, darkMode);
|
this.view?.webContents.send(DARK_MODE_CHANGE, darkMode);
|
||||||
}
|
}
|
||||||
|
@ -111,6 +102,16 @@ export class LoadingScreen {
|
||||||
app.emit('e2e-app-loaded');
|
app.emit('e2e-app-loaded');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setBounds = () => {
|
||||||
|
if (this.view) {
|
||||||
|
const mainWindow = MainWindow.get();
|
||||||
|
if (!mainWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.view.setBounds(getWindowBoundaries(mainWindow));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadingScreen = new LoadingScreen();
|
const loadingScreen = new LoadingScreen();
|
||||||
|
|
|
@ -25,7 +25,8 @@ jest.mock('./modalView', () => ({
|
||||||
jest.mock('main/views/viewManager', () => ({
|
jest.mock('main/views/viewManager', () => ({
|
||||||
focusCurrentView: jest.fn(),
|
focusCurrentView: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
|
on: jest.fn(),
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('process', () => ({
|
jest.mock('process', () => ({
|
||||||
|
|
|
@ -15,14 +15,14 @@ import {
|
||||||
EMIT_CONFIGURATION,
|
EMIT_CONFIGURATION,
|
||||||
DARK_MODE_CHANGE,
|
DARK_MODE_CHANGE,
|
||||||
GET_MODAL_UNCLOSEABLE,
|
GET_MODAL_UNCLOSEABLE,
|
||||||
RESIZE_MODAL,
|
MAIN_WINDOW_RESIZED,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
import {getAdjustedWindowBoundaries} from 'main/utils';
|
import {getAdjustedWindowBoundaries} from 'main/utils';
|
||||||
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WebContentsEventManager from 'main/views/webContentEvents';
|
import WebContentsEventManager from 'main/views/webContentEvents';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
|
|
||||||
import {ModalView} from './modalView';
|
import {ModalView} from './modalView';
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export class ModalManager {
|
||||||
ipcMain.handle(RETRIEVE_MODAL_INFO, this.handleInfoRequest);
|
ipcMain.handle(RETRIEVE_MODAL_INFO, this.handleInfoRequest);
|
||||||
ipcMain.on(MODAL_RESULT, this.handleModalResult);
|
ipcMain.on(MODAL_RESULT, this.handleModalResult);
|
||||||
ipcMain.on(MODAL_CANCEL, this.handleModalCancel);
|
ipcMain.on(MODAL_CANCEL, this.handleModalCancel);
|
||||||
ipcMain.on(RESIZE_MODAL, this.handleResizeModal);
|
MainWindow.on(MAIN_WINDOW_RESIZED, this.handleResizeModal);
|
||||||
|
|
||||||
ipcMain.on(EMIT_CONFIGURATION, this.handleEmitConfiguration);
|
ipcMain.on(EMIT_CONFIGURATION, this.handleEmitConfiguration);
|
||||||
}
|
}
|
||||||
|
@ -88,11 +88,11 @@ export class ModalManager {
|
||||||
const withDevTools = process.env.MM_DEBUG_MODALS || false;
|
const withDevTools = process.env.MM_DEBUG_MODALS || false;
|
||||||
this.modalQueue.forEach((modal, index) => {
|
this.modalQueue.forEach((modal, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
WindowManager.sendToRenderer(MODAL_OPEN);
|
MainWindow.sendToRenderer(MODAL_OPEN);
|
||||||
modal.show(undefined, Boolean(withDevTools));
|
modal.show(undefined, Boolean(withDevTools));
|
||||||
WebContentsEventManager.addWebContentsEventListeners(modal.view.webContents);
|
WebContentsEventManager.addWebContentsEventListeners(modal.view.webContents);
|
||||||
} else {
|
} else {
|
||||||
WindowManager.sendToRenderer(MODAL_CLOSE);
|
MainWindow.sendToRenderer(MODAL_CLOSE);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -114,7 +114,7 @@ export class ModalManager {
|
||||||
if (this.modalQueue.length) {
|
if (this.modalQueue.length) {
|
||||||
this.showModal();
|
this.showModal();
|
||||||
} else {
|
} else {
|
||||||
WindowManager.sendToRenderer(MODAL_CLOSE);
|
MainWindow.sendToRenderer(MODAL_CLOSE);
|
||||||
ViewManager.focusCurrentView();
|
ViewManager.focusCurrentView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ export class ModalManager {
|
||||||
return this.modalQueue.some((modal) => modal.isActive());
|
return this.modalQueue.some((modal) => modal.isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResizeModal = (event: IpcMainEvent, bounds: Electron.Rectangle) => {
|
handleResizeModal = (bounds: Electron.Rectangle) => {
|
||||||
log.debug('handleResizeModal', {bounds, modalQueueLength: this.modalQueue.length});
|
log.debug('handleResizeModal', {bounds, modalQueueLength: this.modalQueue.length});
|
||||||
|
|
||||||
if (this.modalQueue.length) {
|
if (this.modalQueue.length) {
|
||||||
|
|
|
@ -27,12 +27,9 @@ jest.mock('electron', () => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
|
on: jest.fn(),
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
getBounds: jest.fn(),
|
getBounds: jest.fn(),
|
||||||
addBrowserView: jest.fn(),
|
|
||||||
setTopBrowserView: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../windows/windowManager', () => ({
|
|
||||||
sendToRenderer: jest.fn(),
|
sendToRenderer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ import {
|
||||||
REQUEST_TEAMS_DROPDOWN_INFO,
|
REQUEST_TEAMS_DROPDOWN_INFO,
|
||||||
RECEIVE_DROPDOWN_MENU_SIZE,
|
RECEIVE_DROPDOWN_MENU_SIZE,
|
||||||
SERVERS_UPDATE,
|
SERVERS_UPDATE,
|
||||||
|
MAIN_WINDOW_CREATED,
|
||||||
|
MAIN_WINDOW_RESIZED,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
@ -23,7 +25,6 @@ import ServerManager from 'common/servers/serverManager';
|
||||||
|
|
||||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
|
|
||||||
import WindowManager from '../windows/windowManager';
|
|
||||||
import MainWindow from '../windows/mainWindow';
|
import MainWindow from '../windows/mainWindow';
|
||||||
|
|
||||||
const log = new Logger('TeamDropdownView');
|
const log = new Logger('TeamDropdownView');
|
||||||
|
@ -51,6 +52,9 @@ export class TeamDropdownView {
|
||||||
this.mentions = new Map();
|
this.mentions = new Map();
|
||||||
this.expired = new Map();
|
this.expired = new Map();
|
||||||
|
|
||||||
|
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||||
|
MainWindow.on(MAIN_WINDOW_RESIZED, this.updateWindowBounds);
|
||||||
|
|
||||||
ipcMain.on(OPEN_TEAMS_DROPDOWN, this.handleOpen);
|
ipcMain.on(OPEN_TEAMS_DROPDOWN, this.handleOpen);
|
||||||
ipcMain.on(CLOSE_TEAMS_DROPDOWN, this.handleClose);
|
ipcMain.on(CLOSE_TEAMS_DROPDOWN, this.handleClose);
|
||||||
ipcMain.on(RECEIVE_DROPDOWN_MENU_SIZE, this.handleReceivedMenuSize);
|
ipcMain.on(RECEIVE_DROPDOWN_MENU_SIZE, this.handleReceivedMenuSize);
|
||||||
|
@ -62,7 +66,13 @@ export class TeamDropdownView {
|
||||||
ServerManager.on(SERVERS_UPDATE, this.updateServers);
|
ServerManager.on(SERVERS_UPDATE, this.updateServers);
|
||||||
}
|
}
|
||||||
|
|
||||||
init = () => {
|
private updateWindowBounds = (newBounds: Electron.Rectangle) => {
|
||||||
|
this.windowBounds = newBounds;
|
||||||
|
this.updateDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private init = () => {
|
||||||
|
log.info('init');
|
||||||
const preload = getLocalPreload('desktopAPI.js');
|
const preload = getLocalPreload('desktopAPI.js');
|
||||||
this.view = new BrowserView({webPreferences: {
|
this.view = new BrowserView({webPreferences: {
|
||||||
preload,
|
preload,
|
||||||
|
@ -80,11 +90,6 @@ export class TeamDropdownView {
|
||||||
MainWindow.get()?.addBrowserView(this.view);
|
MainWindow.get()?.addBrowserView(this.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWindowBounds = () => {
|
|
||||||
this.windowBounds = MainWindow.getBounds();
|
|
||||||
this.updateDropdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateDropdown = () => {
|
private updateDropdown = () => {
|
||||||
log.silly('updateDropdown');
|
log.silly('updateDropdown');
|
||||||
|
|
||||||
|
@ -132,7 +137,7 @@ export class TeamDropdownView {
|
||||||
this.view.setBounds(this.bounds);
|
this.view.setBounds(this.bounds);
|
||||||
MainWindow.get()?.setTopBrowserView(this.view);
|
MainWindow.get()?.setTopBrowserView(this.view);
|
||||||
this.view.webContents.focus();
|
this.view.webContents.focus();
|
||||||
WindowManager.sendToRenderer(OPEN_TEAMS_DROPDOWN);
|
MainWindow.sendToRenderer(OPEN_TEAMS_DROPDOWN);
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +145,7 @@ export class TeamDropdownView {
|
||||||
log.debug('handleClose');
|
log.debug('handleClose');
|
||||||
|
|
||||||
this.view?.setBounds(this.getBounds(0, 0));
|
this.view?.setBounds(this.getBounds(0, 0));
|
||||||
WindowManager.sendToRenderer(CLOSE_TEAMS_DROPDOWN);
|
MainWindow.sendToRenderer(CLOSE_TEAMS_DROPDOWN);
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ jest.mock('main/views/loadingScreen', () => ({
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/mainWindow', () => ({
|
jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('common/servers/serverManager', () => ({
|
jest.mock('common/servers/serverManager', () => ({
|
||||||
getCurrentServer: jest.fn(),
|
getCurrentServer: jest.fn(),
|
||||||
|
|
|
@ -24,6 +24,8 @@ import {
|
||||||
HISTORY,
|
HISTORY,
|
||||||
GET_VIEW_INFO_FOR_TEST,
|
GET_VIEW_INFO_FOR_TEST,
|
||||||
SESSION_EXPIRED,
|
SESSION_EXPIRED,
|
||||||
|
MAIN_WINDOW_CREATED,
|
||||||
|
MAIN_WINDOW_RESIZED,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
@ -36,7 +38,7 @@ import {TabView, TAB_MESSAGING} from 'common/tabs/TabView';
|
||||||
import {localizeMessage} from 'main/i18nManager';
|
import {localizeMessage} from 'main/i18nManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
import {getLocalURLString, getLocalPreload} from '../utils';
|
import {getLocalURLString, getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
|
||||||
|
|
||||||
import {MattermostView} from './MattermostView';
|
import {MattermostView} from './MattermostView';
|
||||||
import modalManager from './modalManager';
|
import modalManager from './modalManager';
|
||||||
|
@ -57,6 +59,8 @@ export class ViewManager {
|
||||||
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
|
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
|
||||||
this.closedViews = new Map();
|
this.closedViews = new Map();
|
||||||
|
|
||||||
|
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||||
|
MainWindow.on(MAIN_WINDOW_RESIZED, this.handleSetCurrentViewBounds);
|
||||||
ipcMain.handle(GET_VIEW_INFO_FOR_TEST, this.handleGetViewInfoForTest);
|
ipcMain.handle(GET_VIEW_INFO_FOR_TEST, this.handleGetViewInfoForTest);
|
||||||
ipcMain.on(HISTORY, this.handleHistory);
|
ipcMain.on(HISTORY, this.handleHistory);
|
||||||
ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized);
|
ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized);
|
||||||
|
@ -71,7 +75,9 @@ export class ViewManager {
|
||||||
ServerManager.on(SERVERS_UPDATE, this.handleReloadConfiguration);
|
ServerManager.on(SERVERS_UPDATE, this.handleReloadConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
init = () => {
|
private init = () => {
|
||||||
|
MainWindow.onBrowserWindow?.('focus', this.focusCurrentView);
|
||||||
|
|
||||||
LoadingScreen.show();
|
LoadingScreen.show();
|
||||||
ServerManager.getAllServers().forEach((server) => this.loadServer(server));
|
ServerManager.getAllServers().forEach((server) => this.loadServer(server));
|
||||||
this.showInitial();
|
this.showInitial();
|
||||||
|
@ -323,7 +329,7 @@ export class ViewManager {
|
||||||
const localURL = getLocalURLString('urlView.html', query);
|
const localURL = getLocalURLString('urlView.html', query);
|
||||||
urlView.webContents.loadURL(localURL);
|
urlView.webContents.loadURL(localURL);
|
||||||
MainWindow.get()?.addBrowserView(urlView);
|
MainWindow.get()?.addBrowserView(urlView);
|
||||||
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? mainWindow.getBounds();
|
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? MainWindow.getBounds();
|
||||||
|
|
||||||
const hideView = () => {
|
const hideView = () => {
|
||||||
delete this.urlViewCancel;
|
delete this.urlViewCancel;
|
||||||
|
@ -343,6 +349,10 @@ export class ViewManager {
|
||||||
const adjustWidth = (event: IpcMainEvent, width: number) => {
|
const adjustWidth = (event: IpcMainEvent, width: number) => {
|
||||||
log.silly('showURLView.adjustWidth', width);
|
log.silly('showURLView.adjustWidth', width);
|
||||||
|
|
||||||
|
if (!boundaries) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const bounds = {
|
const bounds = {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: (boundaries.height + TAB_BAR_HEIGHT) - URL_VIEW_HEIGHT,
|
y: (boundaries.height + TAB_BAR_HEIGHT) - URL_VIEW_HEIGHT,
|
||||||
|
@ -526,6 +536,16 @@ export class ViewManager {
|
||||||
AppState.updateExpired(viewId, isExpired);
|
AppState.updateExpired(viewId, isExpired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleSetCurrentViewBounds = (newBounds: Electron.Rectangle) => {
|
||||||
|
log.debug('handleSetCurrentViewBounds', newBounds);
|
||||||
|
|
||||||
|
const currentView = this.getCurrentView();
|
||||||
|
if (currentView) {
|
||||||
|
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.tab.url, currentView.currentURL));
|
||||||
|
currentView.setBounds(adjustedBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper functions
|
* Helper functions
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,8 +8,8 @@ import {shell, BrowserWindow} from 'electron';
|
||||||
import urlUtils from 'common/utils/url';
|
import urlUtils from 'common/utils/url';
|
||||||
|
|
||||||
import ContextMenu from 'main/contextMenu';
|
import ContextMenu from 'main/contextMenu';
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
import * as WindowManager from '../windows/windowManager';
|
|
||||||
import allowProtocolDialog from '../allowProtocolDialog';
|
import allowProtocolDialog from '../allowProtocolDialog';
|
||||||
|
|
||||||
import {WebContentsEventManager} from './webContentEvents';
|
import {WebContentsEventManager} from './webContentEvents';
|
||||||
|
@ -30,10 +30,7 @@ jest.mock('../allowProtocolDialog', () => ({}));
|
||||||
jest.mock('main/windows/callsWidgetWindow', () => ({}));
|
jest.mock('main/windows/callsWidgetWindow', () => ({}));
|
||||||
jest.mock('main/views/viewManager', () => ({
|
jest.mock('main/views/viewManager', () => ({
|
||||||
getViewByWebContentsId: jest.fn(),
|
getViewByWebContentsId: jest.fn(),
|
||||||
}));
|
handleDeepLink: jest.fn(),
|
||||||
jest.mock('../windows/windowManager', () => ({
|
|
||||||
getServerURLFromWebContentsId: jest.fn(),
|
|
||||||
showMainWindow: jest.fn(),
|
|
||||||
}));
|
}));
|
||||||
jest.mock('../utils', () => ({
|
jest.mock('../utils', () => ({
|
||||||
composeUserAgent: jest.fn(),
|
composeUserAgent: jest.fn(),
|
||||||
|
@ -231,7 +228,7 @@ describe('main/views/webContentsEvents', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open in the browser when there is no server matching', () => {
|
it('should open in the browser when there is no server matching', () => {
|
||||||
WindowManager.getServerURLFromWebContentsId.mockReturnValue(undefined);
|
ViewManager.getViewByWebContentsId.mockReturnValue(undefined);
|
||||||
expect(newWindow({url: 'http://server-2.com/subpath'})).toStrictEqual({action: 'deny'});
|
expect(newWindow({url: 'http://server-2.com/subpath'})).toStrictEqual({action: 'deny'});
|
||||||
expect(shell.openExternal).toBeCalledWith('http://server-2.com/subpath');
|
expect(shell.openExternal).toBeCalledWith('http://server-2.com/subpath');
|
||||||
});
|
});
|
||||||
|
@ -248,7 +245,7 @@ describe('main/views/webContentsEvents', () => {
|
||||||
|
|
||||||
it('should open team links in the app', () => {
|
it('should open team links in the app', () => {
|
||||||
expect(newWindow({url: 'http://server-1.com/myteam/channels/mychannel'})).toStrictEqual({action: 'deny'});
|
expect(newWindow({url: 'http://server-1.com/myteam/channels/mychannel'})).toStrictEqual({action: 'deny'});
|
||||||
expect(WindowManager.showMainWindow).toBeCalledWith(new URL('http://server-1.com/myteam/channels/mychannel'));
|
expect(ViewManager.handleDeepLink).toBeCalledWith(new URL('http://server-1.com/myteam/channels/mychannel'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prevent admin links from opening in a new window', () => {
|
it('should prevent admin links from opening in a new window', () => {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import ContextMenu from 'main/contextMenu';
|
||||||
import ServerManager from 'common/servers/serverManager';
|
import ServerManager from 'common/servers/serverManager';
|
||||||
|
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||||
|
|
||||||
|
@ -63,7 +62,11 @@ export class WebContentsEventManager {
|
||||||
return this.popupWindow.serverURL;
|
return this.popupWindow.serverURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return WindowManager.getServerURLFromWebContentsId(webContentsId);
|
if (CallsWidgetWindow.isCallsWidget(webContentsId)) {
|
||||||
|
return CallsWidgetWindow.getURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewManager.getViewByWebContentsId(webContentsId)?.tab.server.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateWillNavigate = (webContentsId: number) => {
|
private generateWillNavigate = (webContentsId: number) => {
|
||||||
|
@ -182,7 +185,7 @@ export class WebContentsEventManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlUtils.isTeamUrl(serverURL, parsedURL, true)) {
|
if (urlUtils.isTeamUrl(serverURL, parsedURL, true)) {
|
||||||
WindowManager.showMainWindow(parsedURL);
|
ViewManager.handleDeepLink(parsedURL);
|
||||||
return {action: 'deny'};
|
return {action: 'deny'};
|
||||||
}
|
}
|
||||||
if (urlUtils.isAdminUrl(serverURL, parsedURL)) {
|
if (urlUtils.isAdminUrl(serverURL, parsedURL)) {
|
||||||
|
@ -259,7 +262,7 @@ export class WebContentsEventManager {
|
||||||
|
|
||||||
const otherServerURL = ServerManager.lookupTabByURL(parsedURL);
|
const otherServerURL = ServerManager.lookupTabByURL(parsedURL);
|
||||||
if (otherServerURL && urlUtils.isTeamUrl(otherServerURL.server.url, parsedURL, true)) {
|
if (otherServerURL && urlUtils.isTeamUrl(otherServerURL.server.url, parsedURL, true)) {
|
||||||
WindowManager.showMainWindow(parsedURL);
|
ViewManager.handleDeepLink(parsedURL);
|
||||||
return {action: 'deny'};
|
return {action: 'deny'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,10 @@ import {
|
||||||
CALLS_PLUGIN_ID,
|
CALLS_PLUGIN_ID,
|
||||||
} from 'common/utils/constants';
|
} from 'common/utils/constants';
|
||||||
import urlUtils from 'common/utils/url';
|
import urlUtils from 'common/utils/url';
|
||||||
|
|
||||||
|
import {switchServer} from 'main/app/servers';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import {
|
import {
|
||||||
resetScreensharePermissionsMacOS,
|
resetScreensharePermissionsMacOS,
|
||||||
openScreensharePermissionsSettingsMacOS,
|
openScreensharePermissionsSettingsMacOS,
|
||||||
|
@ -55,7 +56,7 @@ jest.mock('main/windows/mainWindow', () => ({
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
focus: jest.fn(),
|
focus: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/windows/windowManager', () => ({
|
jest.mock('main/app/servers', () => ({
|
||||||
switchServer: jest.fn(),
|
switchServer: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/views/viewManager', () => ({
|
jest.mock('main/views/viewManager', () => ({
|
||||||
|
@ -797,7 +798,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||||
|
|
||||||
it('should switch server', () => {
|
it('should switch server', () => {
|
||||||
callsWidgetWindow.handleDesktopSourcesModalRequest();
|
callsWidgetWindow.handleDesktopSourcesModalRequest();
|
||||||
expect(WindowManager.switchServer).toHaveBeenCalledWith('server-1');
|
expect(switchServer).toHaveBeenCalledWith('server-1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -864,7 +865,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||||
|
|
||||||
it('should switch server', () => {
|
it('should switch server', () => {
|
||||||
callsWidgetWindow.handleCallsWidgetChannelLinkClick();
|
callsWidgetWindow.handleCallsWidgetChannelLinkClick();
|
||||||
expect(WindowManager.switchServer).toHaveBeenCalledWith('server-2');
|
expect(switchServer).toHaveBeenCalledWith('server-2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -890,7 +891,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||||
|
|
||||||
it('should focus view and propagate error to main view', () => {
|
it('should focus view and propagate error to main view', () => {
|
||||||
callsWidgetWindow.handleCallsError('', {err: 'client-error'});
|
callsWidgetWindow.handleCallsError('', {err: 'client-error'});
|
||||||
expect(WindowManager.switchServer).toHaveBeenCalledWith('server-2');
|
expect(switchServer).toHaveBeenCalledWith('server-2');
|
||||||
expect(focus).toHaveBeenCalled();
|
expect(focus).toHaveBeenCalled();
|
||||||
expect(callsWidgetWindow.mainView.sendToRenderer).toHaveBeenCalledWith('calls-error', {err: 'client-error'});
|
expect(callsWidgetWindow.mainView.sendToRenderer).toHaveBeenCalledWith('calls-error', {err: 'client-error'});
|
||||||
});
|
});
|
||||||
|
@ -918,7 +919,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||||
|
|
||||||
it('should pass through the click link to browser history push', () => {
|
it('should pass through the click link to browser history push', () => {
|
||||||
callsWidgetWindow.handleCallsLinkClick('', {link: '/other/subpath'});
|
callsWidgetWindow.handleCallsLinkClick('', {link: '/other/subpath'});
|
||||||
expect(WindowManager.switchServer).toHaveBeenCalledWith('server-1');
|
expect(switchServer).toHaveBeenCalledWith('server-1');
|
||||||
expect(view.sendToRenderer).toBeCalledWith('browser-history-push', '/other/subpath');
|
expect(view.sendToRenderer).toBeCalledWith('browser-history-push', '/other/subpath');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,9 +37,10 @@ import {
|
||||||
DESKTOP_SOURCES_RESULT,
|
DESKTOP_SOURCES_RESULT,
|
||||||
DISPATCH_GET_DESKTOP_SOURCES,
|
DISPATCH_GET_DESKTOP_SOURCES,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
|
|
||||||
|
import {switchServer} from 'main/app/servers';
|
||||||
import webContentsEventManager from 'main/views/webContentEvents';
|
import webContentsEventManager from 'main/views/webContentEvents';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
import WindowManager from 'main/windows/windowManager';
|
|
||||||
import ViewManager from 'main/views/viewManager';
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
const log = new Logger('CallsWidgetWindow');
|
const log = new Logger('CallsWidgetWindow');
|
||||||
|
@ -454,7 +455,7 @@ export class CallsWidgetWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowManager.switchServer(this.serverID);
|
switchServer(this.serverID);
|
||||||
MainWindow.get()?.focus();
|
MainWindow.get()?.focus();
|
||||||
this.mainView?.sendToRenderer(DESKTOP_SOURCES_MODAL_REQUEST);
|
this.mainView?.sendToRenderer(DESKTOP_SOURCES_MODAL_REQUEST);
|
||||||
}
|
}
|
||||||
|
@ -472,7 +473,7 @@ export class CallsWidgetWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowManager.switchServer(this.serverID);
|
switchServer(this.serverID);
|
||||||
MainWindow.get()?.focus();
|
MainWindow.get()?.focus();
|
||||||
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, this.options?.channelURL);
|
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, this.options?.channelURL);
|
||||||
}
|
}
|
||||||
|
@ -484,7 +485,7 @@ export class CallsWidgetWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowManager.switchServer(this.serverID);
|
switchServer(this.serverID);
|
||||||
MainWindow.get()?.focus();
|
MainWindow.get()?.focus();
|
||||||
this.mainView?.sendToRenderer(CALLS_ERROR, msg);
|
this.mainView?.sendToRenderer(CALLS_ERROR, msg);
|
||||||
}
|
}
|
||||||
|
@ -496,7 +497,7 @@ export class CallsWidgetWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowManager.switchServer(this.serverID);
|
switchServer(this.serverID);
|
||||||
MainWindow.get()?.focus();
|
MainWindow.get()?.focus();
|
||||||
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, msg.link);
|
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, msg.link);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ jest.mock('electron', () => ({
|
||||||
BrowserWindow: jest.fn(),
|
BrowserWindow: jest.fn(),
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
handle: jest.fn(),
|
handle: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
},
|
},
|
||||||
screen: {
|
screen: {
|
||||||
getDisplayMatching: jest.fn(),
|
getDisplayMatching: jest.fn(),
|
||||||
|
@ -469,6 +470,39 @@ describe('main/windows/mainWindow', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('show', () => {
|
||||||
|
const mainWindow = new MainWindow();
|
||||||
|
mainWindow.win = {
|
||||||
|
visible: false,
|
||||||
|
isVisible: () => mainWindow.visible,
|
||||||
|
show: jest.fn(),
|
||||||
|
focus: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
once: jest.fn(),
|
||||||
|
webContents: {
|
||||||
|
setWindowOpenHandler: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mainWindow.win.show.mockImplementation(() => {
|
||||||
|
mainWindow.visible = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show main window if it exists and focus it if it is already visible', () => {
|
||||||
|
mainWindow.show();
|
||||||
|
expect(mainWindow.win.show).toHaveBeenCalled();
|
||||||
|
|
||||||
|
mainWindow.show();
|
||||||
|
expect(mainWindow.win.focus).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('onUnresponsive', () => {
|
describe('onUnresponsive', () => {
|
||||||
const mainWindow = new MainWindow();
|
const mainWindow = new MainWindow();
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,30 @@ import path from 'path';
|
||||||
|
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
|
import {EventEmitter} from 'events';
|
||||||
|
|
||||||
import {app, BrowserWindow, BrowserWindowConstructorOptions, dialog, Event, globalShortcut, Input, ipcMain, screen} from 'electron';
|
import {app, BrowserWindow, BrowserWindowConstructorOptions, dialog, Event, globalShortcut, Input, ipcMain, screen} from 'electron';
|
||||||
|
|
||||||
import {SavedWindowState} from 'types/mainWindow';
|
import {SavedWindowState} from 'types/mainWindow';
|
||||||
|
|
||||||
import AppState from 'common/appState';
|
import AppState from 'common/appState';
|
||||||
import {SELECT_NEXT_TAB, SELECT_PREVIOUS_TAB, GET_FULL_SCREEN_STATUS, FOCUS_THREE_DOT_MENU, SERVERS_UPDATE, UPDATE_APPSTATE_FOR_VIEW_ID, UPDATE_MENTIONS} from 'common/communication';
|
import {
|
||||||
|
SELECT_NEXT_TAB,
|
||||||
|
SELECT_PREVIOUS_TAB,
|
||||||
|
GET_FULL_SCREEN_STATUS,
|
||||||
|
FOCUS_THREE_DOT_MENU,
|
||||||
|
SERVERS_UPDATE,
|
||||||
|
UPDATE_APPSTATE_FOR_VIEW_ID,
|
||||||
|
UPDATE_MENTIONS,
|
||||||
|
MAXIMIZE_CHANGE,
|
||||||
|
MAIN_WINDOW_CREATED,
|
||||||
|
MAIN_WINDOW_RESIZED,
|
||||||
|
VIEW_FINISHED_RESIZING,
|
||||||
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
import ServerManager from 'common/servers/serverManager';
|
import ServerManager from 'common/servers/serverManager';
|
||||||
import {DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MINIMUM_WINDOW_HEIGHT, MINIMUM_WINDOW_WIDTH} from 'common/utils/constants';
|
import {DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MINIMUM_WINDOW_HEIGHT, MINIMUM_WINDOW_WIDTH, SECOND} from 'common/utils/constants';
|
||||||
import Utils from 'common/utils/util';
|
import Utils from 'common/utils/util';
|
||||||
import * as Validator from 'common/Validator';
|
import * as Validator from 'common/Validator';
|
||||||
|
|
||||||
|
@ -29,18 +43,23 @@ import {getLocalPreload, getLocalURLString, isInsideRectangle} from '../utils';
|
||||||
const log = new Logger('MainWindow');
|
const log = new Logger('MainWindow');
|
||||||
const ALT_MENU_KEYS = ['Alt+F', 'Alt+E', 'Alt+V', 'Alt+H', 'Alt+W', 'Alt+P'];
|
const ALT_MENU_KEYS = ['Alt+F', 'Alt+E', 'Alt+V', 'Alt+H', 'Alt+W', 'Alt+P'];
|
||||||
|
|
||||||
export class MainWindow {
|
export class MainWindow extends EventEmitter {
|
||||||
private win?: BrowserWindow;
|
private win?: BrowserWindow;
|
||||||
|
|
||||||
private savedWindowState: SavedWindowState;
|
private savedWindowState: SavedWindowState;
|
||||||
private ready: boolean;
|
private ready: boolean;
|
||||||
|
private isResizing: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
|
this.isResizing = false;
|
||||||
this.savedWindowState = this.getSavedWindowState();
|
this.savedWindowState = this.getSavedWindowState();
|
||||||
|
|
||||||
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
|
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
|
||||||
|
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
||||||
|
|
||||||
ServerManager.on(SERVERS_UPDATE, this.handleUpdateConfig);
|
ServerManager.on(SERVERS_UPDATE, this.handleUpdateConfig);
|
||||||
|
|
||||||
|
@ -108,6 +127,15 @@ export class MainWindow {
|
||||||
this.win.on('focus', this.onFocus);
|
this.win.on('focus', this.onFocus);
|
||||||
this.win.on('blur', this.onBlur);
|
this.win.on('blur', this.onBlur);
|
||||||
this.win.on('unresponsive', this.onUnresponsive);
|
this.win.on('unresponsive', this.onUnresponsive);
|
||||||
|
this.win.on('maximize', this.onMaximize);
|
||||||
|
this.win.on('unmaximize', this.onUnmaximize);
|
||||||
|
this.win.on('enter-full-screen', () => this.win?.webContents.send('enter-full-screen'));
|
||||||
|
this.win.on('leave-full-screen', () => this.win?.webContents.send('leave-full-screen'));
|
||||||
|
this.win.on('will-resize', this.onWillResize);
|
||||||
|
this.win.on('resized', this.onResized);
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
mainWindow.on('resize', this.onResize);
|
||||||
|
}
|
||||||
|
|
||||||
this.win.webContents.on('before-input-event', this.onBeforeInputEvent);
|
this.win.webContents.on('before-input-event', this.onBeforeInputEvent);
|
||||||
|
|
||||||
|
@ -119,21 +147,45 @@ export class MainWindow {
|
||||||
|
|
||||||
const contextMenu = new ContextMenu({}, this.win);
|
const contextMenu = new ContextMenu({}, this.win);
|
||||||
contextMenu.reload();
|
contextMenu.reload();
|
||||||
|
|
||||||
|
this.emit(MAIN_WINDOW_CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReady() {
|
get isReady() {
|
||||||
return this.ready;
|
return this.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
get = (ensureCreated?: boolean) => {
|
get = () => {
|
||||||
if (ensureCreated && !this.win) {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
return this.win;
|
return this.win;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBounds = () => {
|
show = () => {
|
||||||
return this.win?.getContentBounds();
|
if (this.win) {
|
||||||
|
if (this.win.isVisible()) {
|
||||||
|
this.win.focus();
|
||||||
|
} else {
|
||||||
|
this.win.show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.init();
|
||||||
|
this.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getBounds = (): Electron.Rectangle | undefined => {
|
||||||
|
if (!this.win) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround for linux maximizing/minimizing, which doesn't work properly because of these bugs:
|
||||||
|
// https://github.com/electron/electron/issues/28699
|
||||||
|
// https://github.com/electron/electron/issues/28106
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
const size = this.win.getSize();
|
||||||
|
return {...this.win.getContentBounds(), width: size[0], height: size[1]};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.win.getContentBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
focusThreeDotMenu = () => {
|
focusThreeDotMenu = () => {
|
||||||
|
@ -143,6 +195,27 @@ export class MainWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBrowserWindow = this.win?.on;
|
||||||
|
|
||||||
|
sendToRenderer = (channel: string, ...args: unknown[]) => {
|
||||||
|
this.sendToRendererWithRetry(3, channel, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendToRendererWithRetry = (maxRetries: number, channel: string, ...args: unknown[]) => {
|
||||||
|
if (!this.win || !this.isReady) {
|
||||||
|
if (maxRetries > 0) {
|
||||||
|
log.debug(`Can't send ${channel}, will retry`);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.sendToRendererWithRetry(maxRetries - 1, channel, ...args);
|
||||||
|
}, SECOND);
|
||||||
|
} else {
|
||||||
|
log.error(`Unable to send the message to the main window for message type ${channel}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.win.webContents.send(channel, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
private shouldStartFullScreen = () => {
|
private shouldStartFullScreen = () => {
|
||||||
if (global?.args?.fullscreen !== undefined) {
|
if (global?.args?.fullscreen !== undefined) {
|
||||||
return global.args.fullscreen;
|
return global.args.fullscreen;
|
||||||
|
@ -328,6 +401,64 @@ export class MainWindow {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onMaximize = () => {
|
||||||
|
this.win?.webContents.send(MAXIMIZE_CHANGE, true);
|
||||||
|
this.emit(MAIN_WINDOW_RESIZED, this.getBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onUnmaximize = () => {
|
||||||
|
this.win?.webContents.send(MAXIMIZE_CHANGE, false);
|
||||||
|
this.emit(MAIN_WINDOW_RESIZED, this.getBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizing code
|
||||||
|
*/
|
||||||
|
|
||||||
|
private onWillResize = (event: Event, newBounds: Electron.Rectangle) => {
|
||||||
|
log.silly('onWillResize', newBounds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes an issue on win11 related to Snap where the first "will-resize" event would return the same bounds
|
||||||
|
* causing the "resize" event to not fire
|
||||||
|
*/
|
||||||
|
const prevBounds = this.getBounds();
|
||||||
|
if (prevBounds?.height === newBounds.height && prevBounds?.width === newBounds.width) {
|
||||||
|
log.debug('prevented resize');
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isResizing) {
|
||||||
|
log.debug('prevented resize');
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isResizing = true;
|
||||||
|
this.emit(MAIN_WINDOW_RESIZED, newBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onResize = () => {
|
||||||
|
log.silly('onResize');
|
||||||
|
|
||||||
|
if (this.isResizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit(MAIN_WINDOW_RESIZED, this.getBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onResized = () => {
|
||||||
|
log.debug('onResized');
|
||||||
|
|
||||||
|
this.emit(MAIN_WINDOW_RESIZED, this.getBounds());
|
||||||
|
this.isResizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleViewFinishedResizing = () => {
|
||||||
|
this.isResizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server Manager update handler
|
* Server Manager update handler
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,6 +7,8 @@ import {SHOW_SETTINGS_WINDOW} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
|
import ViewManager from 'main/views/viewManager';
|
||||||
|
|
||||||
import ContextMenu from '../contextMenu';
|
import ContextMenu from '../contextMenu';
|
||||||
import {getLocalPreload, getLocalURLString} from '../utils';
|
import {getLocalPreload, getLocalURLString} from '../utils';
|
||||||
|
|
||||||
|
@ -33,8 +35,12 @@ export class SettingsWindow {
|
||||||
return this.win;
|
return this.win;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||||
|
this.win?.webContents.send(channel, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
private create = () => {
|
private create = () => {
|
||||||
const mainWindow = MainWindow.get(true);
|
const mainWindow = MainWindow.get();
|
||||||
if (!mainWindow) {
|
if (!mainWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -67,6 +73,8 @@ export class SettingsWindow {
|
||||||
|
|
||||||
this.win.on('closed', () => {
|
this.win.on('closed', () => {
|
||||||
delete this.win;
|
delete this.win;
|
||||||
|
|
||||||
|
ViewManager.focusCurrentView();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,637 +0,0 @@
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
/* eslint-disable max-lines */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import {systemPreferences} from 'electron';
|
|
||||||
|
|
||||||
import {getTabViewName} from 'common/tabs/TabView';
|
|
||||||
import ServerManager from 'common/servers/serverManager';
|
|
||||||
|
|
||||||
import {getAdjustedWindowBoundaries} from 'main/utils';
|
|
||||||
|
|
||||||
import ViewManager from '../views/viewManager';
|
|
||||||
import LoadingScreen from '../views/loadingScreen';
|
|
||||||
import TeamDropdownView from '../views/teamDropdownView';
|
|
||||||
|
|
||||||
import {WindowManager} from './windowManager';
|
|
||||||
import MainWindow from './mainWindow';
|
|
||||||
import SettingsWindow from './settingsWindow';
|
|
||||||
import CallsWidgetWindow from './callsWidgetWindow';
|
|
||||||
|
|
||||||
jest.mock('path', () => ({
|
|
||||||
resolve: jest.fn(),
|
|
||||||
join: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('electron', () => ({
|
|
||||||
ipcMain: {
|
|
||||||
handle: jest.fn(),
|
|
||||||
on: jest.fn(),
|
|
||||||
emit: jest.fn(),
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
getAppPath: jest.fn(),
|
|
||||||
quit: jest.fn(),
|
|
||||||
dock: {
|
|
||||||
show: jest.fn(),
|
|
||||||
bounce: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
systemPreferences: {
|
|
||||||
getUserDefault: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('common/config', () => ({}));
|
|
||||||
|
|
||||||
jest.mock('common/tabs/TabView', () => ({
|
|
||||||
getTabViewName: jest.fn(),
|
|
||||||
TAB_MESSAGING: 'tab-messaging',
|
|
||||||
}));
|
|
||||||
jest.mock('../utils', () => ({
|
|
||||||
getAdjustedWindowBoundaries: jest.fn(),
|
|
||||||
shouldHaveBackBar: jest.fn(),
|
|
||||||
openScreensharePermissionsSettingsMacOS: jest.fn(),
|
|
||||||
resetScreensharePermissionsMacOS: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../views/viewManager', () => ({
|
|
||||||
reloadConfiguration: jest.fn(),
|
|
||||||
showById: jest.fn(),
|
|
||||||
getCurrentView: jest.fn(),
|
|
||||||
getView: jest.fn(),
|
|
||||||
isViewClosed: jest.fn(),
|
|
||||||
openClosedTab: jest.fn(),
|
|
||||||
handleDeepLink: jest.fn(),
|
|
||||||
setLoadingScreenBounds: jest.fn(),
|
|
||||||
showByName: jest.fn(),
|
|
||||||
updateMainWindow: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../CriticalErrorHandler', () => jest.fn());
|
|
||||||
jest.mock('../views/loadingScreen', () => ({
|
|
||||||
isHidden: jest.fn(),
|
|
||||||
setBounds: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../views/teamDropdownView', () => ({
|
|
||||||
updateWindowBounds: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../views/downloadsDropdownView', () => ({
|
|
||||||
updateWindowBounds: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../views/downloadsDropdownMenuView', () => ({
|
|
||||||
updateWindowBounds: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('./settingsWindow', () => ({
|
|
||||||
show: jest.fn(),
|
|
||||||
get: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('./mainWindow', () => ({
|
|
||||||
get: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('../downloadsManager', () => ({
|
|
||||||
getDownloads: () => {},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('common/servers/serverManager', () => ({
|
|
||||||
getAllServers: jest.fn(),
|
|
||||||
getServer: jest.fn(),
|
|
||||||
getCurrentServer: jest.fn(),
|
|
||||||
on: jest.fn(),
|
|
||||||
lookupTabByURL: jest.fn(),
|
|
||||||
getOrderedTabsForServer: jest.fn(),
|
|
||||||
getLastActiveTabForServer: jest.fn(),
|
|
||||||
getServerLog: () => ({
|
|
||||||
error: jest.fn(),
|
|
||||||
warn: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
verbose: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
silly: jest.fn(),
|
|
||||||
}),
|
|
||||||
getViewLog: () => ({
|
|
||||||
error: jest.fn(),
|
|
||||||
warn: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
verbose: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
silly: jest.fn(),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
jest.mock('./callsWidgetWindow', () => ({
|
|
||||||
isCallsWidget: jest.fn(),
|
|
||||||
getURL: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('main/views/webContentEvents', () => ({}));
|
|
||||||
|
|
||||||
describe('main/windows/windowManager', () => {
|
|
||||||
describe('showMainWindow', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
windowManager.initializeViewManager = jest.fn();
|
|
||||||
|
|
||||||
const mainWindow = {
|
|
||||||
visible: false,
|
|
||||||
isVisible: () => mainWindow.visible,
|
|
||||||
show: jest.fn(),
|
|
||||||
focus: jest.fn(),
|
|
||||||
on: jest.fn(),
|
|
||||||
once: jest.fn(),
|
|
||||||
webContents: {
|
|
||||||
setWindowOpenHandler: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mainWindow.show.mockImplementation(() => {
|
|
||||||
mainWindow.visible = true;
|
|
||||||
});
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show main window if it exists and focus it if it is already visible', () => {
|
|
||||||
windowManager.showMainWindow();
|
|
||||||
expect(mainWindow.show).toHaveBeenCalled();
|
|
||||||
|
|
||||||
windowManager.showMainWindow();
|
|
||||||
expect(mainWindow.focus).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should open deep link when provided', () => {
|
|
||||||
windowManager.showMainWindow('mattermost://server-1.com/subpath');
|
|
||||||
expect(ViewManager.handleDeepLink).toHaveBeenCalledWith('mattermost://server-1.com/subpath');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleResizeMainWindow', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
const view = {
|
|
||||||
setBounds: jest.fn(),
|
|
||||||
tab: {
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
webContents: {
|
|
||||||
getURL: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mainWindow = {
|
|
||||||
getContentBounds: () => ({width: 800, height: 600}),
|
|
||||||
getSize: () => [1000, 900],
|
|
||||||
};
|
|
||||||
windowManager.teamDropdown = {
|
|
||||||
updateWindowBounds: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
jest.useFakeTimers();
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
ViewManager.getCurrentView.mockReturnValue(view);
|
|
||||||
getAdjustedWindowBoundaries.mockImplementation((width, height) => ({width, height}));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.runAllTimers();
|
|
||||||
jest.resetAllMocks();
|
|
||||||
jest.runOnlyPendingTimers();
|
|
||||||
jest.clearAllTimers();
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update loading screen and team dropdown bounds', () => {
|
|
||||||
windowManager.handleResizeMainWindow();
|
|
||||||
expect(LoadingScreen.setBounds).toHaveBeenCalled();
|
|
||||||
expect(TeamDropdownView.updateWindowBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use getSize when the platform is linux', () => {
|
|
||||||
const originalPlatform = process.platform;
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: 'linux',
|
|
||||||
});
|
|
||||||
|
|
||||||
windowManager.handleResizeMainWindow();
|
|
||||||
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(view.setBounds).not.toHaveBeenCalled();
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(view.setBounds).toHaveBeenCalledWith({width: 1000, height: 900});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleWillResizeMainWindow', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
const view = {
|
|
||||||
setBounds: jest.fn(),
|
|
||||||
tab: {
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
webContents: {
|
|
||||||
getURL: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mainWindow = {
|
|
||||||
getContentBounds: () => ({width: 1000, height: 900}),
|
|
||||||
getSize: () => [1000, 900],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
LoadingScreen.isHidden.mockReturnValue(true);
|
|
||||||
ViewManager.getCurrentView.mockReturnValue(view);
|
|
||||||
getAdjustedWindowBoundaries.mockImplementation((width, height) => ({width, height}));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
windowManager.isResizing = false;
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update loading screen and team dropdown bounds', () => {
|
|
||||||
const event = {preventDefault: jest.fn()};
|
|
||||||
windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600});
|
|
||||||
expect(LoadingScreen.setBounds).toHaveBeenCalled();
|
|
||||||
expect(TeamDropdownView.updateWindowBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not resize if the app is already resizing', () => {
|
|
||||||
windowManager.isResizing = true;
|
|
||||||
LoadingScreen.isHidden.mockReturnValue(true);
|
|
||||||
const event = {preventDefault: jest.fn()};
|
|
||||||
windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600});
|
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
|
||||||
expect(view.setBounds).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use provided bounds', () => {
|
|
||||||
const event = {preventDefault: jest.fn()};
|
|
||||||
windowManager.handleWillResizeMainWindow(event, {width: 800, height: 600});
|
|
||||||
expect(windowManager.isResizing).toBe(true);
|
|
||||||
expect(view.setBounds).toHaveBeenCalledWith({width: 800, height: 600});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleResizedMainWindow', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
const view = {
|
|
||||||
setBounds: jest.fn(),
|
|
||||||
tab: {
|
|
||||||
url: 'http://server-1.com',
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
webContents: {
|
|
||||||
getURL: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mainWindow = {
|
|
||||||
getContentBounds: () => ({width: 800, height: 600}),
|
|
||||||
getSize: () => [1000, 900],
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ViewManager.getCurrentView.mockReturnValue(view);
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
getAdjustedWindowBoundaries.mockImplementation((width, height) => ({width, height}));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
windowManager.isResizing = true;
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use getContentBounds when the platform is different to linux', () => {
|
|
||||||
const originalPlatform = process.platform;
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: 'windows',
|
|
||||||
});
|
|
||||||
|
|
||||||
windowManager.handleResizedMainWindow();
|
|
||||||
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(windowManager.isResizing).toBe(false);
|
|
||||||
expect(view.setBounds).toHaveBeenCalledWith({width: 800, height: 600});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use getSize when the platform is linux', () => {
|
|
||||||
const originalPlatform = process.platform;
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: 'linux',
|
|
||||||
});
|
|
||||||
|
|
||||||
windowManager.handleResizedMainWindow();
|
|
||||||
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(windowManager.isResizing).toBe(false);
|
|
||||||
expect(view.setBounds).toHaveBeenCalledWith({width: 1000, height: 900});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('restoreMain', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
const mainWindow = {
|
|
||||||
isVisible: jest.fn(),
|
|
||||||
isMinimized: jest.fn(),
|
|
||||||
restore: jest.fn(),
|
|
||||||
show: jest.fn(),
|
|
||||||
focus: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should restore main window if minimized', () => {
|
|
||||||
mainWindow.isMinimized.mockReturnValue(true);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(mainWindow.restore).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show main window if not visible or minimized', () => {
|
|
||||||
mainWindow.isVisible.mockReturnValue(false);
|
|
||||||
mainWindow.isMinimized.mockReturnValue(false);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(mainWindow.show).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should focus main window if visible and not minimized', () => {
|
|
||||||
mainWindow.isVisible.mockReturnValue(true);
|
|
||||||
mainWindow.isMinimized.mockReturnValue(false);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(mainWindow.focus).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should focus settings window regardless of main window state if it exists', () => {
|
|
||||||
const settingsWindow = {focus: jest.fn()};
|
|
||||||
SettingsWindow.get.mockReturnValue(settingsWindow);
|
|
||||||
|
|
||||||
mainWindow.isVisible.mockReturnValue(false);
|
|
||||||
mainWindow.isMinimized.mockReturnValue(false);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(settingsWindow.focus).toHaveBeenCalled();
|
|
||||||
settingsWindow.focus.mockClear();
|
|
||||||
|
|
||||||
mainWindow.isVisible.mockReturnValue(true);
|
|
||||||
mainWindow.isMinimized.mockReturnValue(false);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(settingsWindow.focus).toHaveBeenCalled();
|
|
||||||
settingsWindow.focus.mockClear();
|
|
||||||
|
|
||||||
mainWindow.isVisible.mockReturnValue(false);
|
|
||||||
mainWindow.isMinimized.mockReturnValue(true);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(settingsWindow.focus).toHaveBeenCalled();
|
|
||||||
settingsWindow.focus.mockClear();
|
|
||||||
|
|
||||||
mainWindow.isVisible.mockReturnValue(true);
|
|
||||||
mainWindow.isMinimized.mockReturnValue(true);
|
|
||||||
windowManager.restoreMain();
|
|
||||||
expect(settingsWindow.focus).toHaveBeenCalled();
|
|
||||||
settingsWindow.focus.mockClear();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleDoubleClick', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
const mainWindow = {
|
|
||||||
isMinimized: jest.fn(),
|
|
||||||
restore: jest.fn(),
|
|
||||||
minimize: jest.fn(),
|
|
||||||
isMaximized: jest.fn(),
|
|
||||||
unmaximize: jest.fn(),
|
|
||||||
maximize: jest.fn(),
|
|
||||||
};
|
|
||||||
const settingsWindow = {
|
|
||||||
isMinimized: jest.fn(),
|
|
||||||
restore: jest.fn(),
|
|
||||||
minimize: jest.fn(),
|
|
||||||
isMaximized: jest.fn(),
|
|
||||||
unmaximize: jest.fn(),
|
|
||||||
maximize: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
systemPreferences.getUserDefault.mockReturnValue('Maximize');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing when the windows arent set', () => {
|
|
||||||
windowManager.handleDoubleClick(null, 'settings');
|
|
||||||
expect(settingsWindow.isMaximized).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
windowManager.handleDoubleClick();
|
|
||||||
expect(mainWindow.isMaximized).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should maximize when not maximized and vice versa', () => {
|
|
||||||
MainWindow.get.mockReturnValue(mainWindow);
|
|
||||||
|
|
||||||
mainWindow.isMaximized.mockReturnValue(false);
|
|
||||||
windowManager.handleDoubleClick();
|
|
||||||
expect(mainWindow.maximize).toHaveBeenCalled();
|
|
||||||
|
|
||||||
mainWindow.isMaximized.mockReturnValue(true);
|
|
||||||
windowManager.handleDoubleClick();
|
|
||||||
expect(mainWindow.unmaximize).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('mac - should minimize when not minimized and vice versa when setting is set', () => {
|
|
||||||
const originalPlatform = process.platform;
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: 'darwin',
|
|
||||||
});
|
|
||||||
|
|
||||||
systemPreferences.getUserDefault.mockReturnValue('Minimize');
|
|
||||||
SettingsWindow.get.mockReturnValue(settingsWindow);
|
|
||||||
|
|
||||||
settingsWindow.isMinimized.mockReturnValue(false);
|
|
||||||
windowManager.handleDoubleClick(null, 'settings');
|
|
||||||
expect(settingsWindow.minimize).toHaveBeenCalled();
|
|
||||||
|
|
||||||
settingsWindow.isMinimized.mockReturnValue(true);
|
|
||||||
windowManager.handleDoubleClick(null, 'settings');
|
|
||||||
expect(settingsWindow.restore).toHaveBeenCalled();
|
|
||||||
|
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('switchServer', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
const views = new Map([
|
|
||||||
['tab-1', {id: 'tab-1'}],
|
|
||||||
['tab-2', {id: 'tab-2'}],
|
|
||||||
['tab-3', {id: 'tab-3'}],
|
|
||||||
]);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
const server1 = {
|
|
||||||
id: 'server-1',
|
|
||||||
};
|
|
||||||
const server2 = {
|
|
||||||
id: 'server-2',
|
|
||||||
};
|
|
||||||
ServerManager.getServer.mockImplementation((name) => {
|
|
||||||
switch (name) {
|
|
||||||
case 'server-1':
|
|
||||||
return server1;
|
|
||||||
case 'server-2':
|
|
||||||
return server2;
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ViewManager.getView.mockImplementation((viewId) => views.get(viewId));
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.runOnlyPendingTimers();
|
|
||||||
jest.clearAllTimers();
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing if cannot find the server', () => {
|
|
||||||
windowManager.switchServer('server-3');
|
|
||||||
expect(getTabViewName).not.toBeCalled();
|
|
||||||
expect(ViewManager.showById).not.toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show first open tab in order when last active not defined', () => {
|
|
||||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
|
||||||
windowManager.switchServer('server-1');
|
|
||||||
expect(ViewManager.showById).toHaveBeenCalledWith('tab-3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show last active tab of chosen server', () => {
|
|
||||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
|
|
||||||
windowManager.switchServer('server-2');
|
|
||||||
expect(ViewManager.showById).toHaveBeenCalledWith('tab-2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should wait for view to exist if specified', () => {
|
|
||||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
|
||||||
views.delete('tab-3');
|
|
||||||
windowManager.switchServer('server-1', true);
|
|
||||||
expect(ViewManager.showById).not.toBeCalled();
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(200);
|
|
||||||
expect(ViewManager.showById).not.toBeCalled();
|
|
||||||
|
|
||||||
views.set('tab-3', {});
|
|
||||||
jest.advanceTimersByTime(200);
|
|
||||||
expect(ViewManager.showById).toBeCalledWith('tab-3');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectTab', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
windowManager.switchTab = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
id: 'tab-1',
|
|
||||||
type: 'tab-1',
|
|
||||||
isOpen: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'tab-2',
|
|
||||||
type: 'tab-2',
|
|
||||||
isOpen: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'tab-3',
|
|
||||||
type: 'tab-3',
|
|
||||||
isOpen: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
ServerManager.getOrderedTabsForServer.mockReturnValue(tabs);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should select next server when open', () => {
|
|
||||||
ViewManager.getCurrentView.mockReturnValue({
|
|
||||||
tab: {
|
|
||||||
server: {
|
|
||||||
id: 'server-1',
|
|
||||||
},
|
|
||||||
type: 'tab-3',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
windowManager.selectTab((order) => order + 1);
|
|
||||||
expect(windowManager.switchTab).toBeCalledWith('tab-2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should select previous server when open', () => {
|
|
||||||
ViewManager.getCurrentView.mockReturnValue({
|
|
||||||
tab: {
|
|
||||||
server: {
|
|
||||||
id: 'server-1',
|
|
||||||
},
|
|
||||||
type: 'tab-2',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
windowManager.selectTab((order, length) => (length + (order - 1)));
|
|
||||||
expect(windowManager.switchTab).toBeCalledWith('tab-3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should skip over closed tab', () => {
|
|
||||||
ViewManager.getCurrentView.mockReturnValue({
|
|
||||||
tab: {
|
|
||||||
server: {
|
|
||||||
id: 'server-1',
|
|
||||||
},
|
|
||||||
type: 'tab-2',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
windowManager.selectTab((order) => order + 1);
|
|
||||||
expect(windowManager.switchTab).toBeCalledWith('tab-3');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getServerURLFromWebContentsId', () => {
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
|
|
||||||
it('should return calls widget URL', () => {
|
|
||||||
ViewManager.getView.mockReturnValue({name: 'server-1_tab-messaging'});
|
|
||||||
CallsWidgetWindow.getURL.mockReturnValue('http://server-1.com');
|
|
||||||
CallsWidgetWindow.isCallsWidget.mockReturnValue(true);
|
|
||||||
expect(windowManager.getServerURLFromWebContentsId('callsID')).toBe('http://server-1.com');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,426 +0,0 @@
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
/* eslint-disable max-lines */
|
|
||||||
|
|
||||||
import {BrowserWindow, systemPreferences, ipcMain, IpcMainEvent} from 'electron';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MAXIMIZE_CHANGE,
|
|
||||||
GET_DARK_MODE,
|
|
||||||
UPDATE_SHORTCUT_MENU,
|
|
||||||
RESIZE_MODAL,
|
|
||||||
VIEW_FINISHED_RESIZING,
|
|
||||||
WINDOW_CLOSE,
|
|
||||||
WINDOW_MAXIMIZE,
|
|
||||||
WINDOW_MINIMIZE,
|
|
||||||
WINDOW_RESTORE,
|
|
||||||
DOUBLE_CLICK_ON_WINDOW,
|
|
||||||
} from 'common/communication';
|
|
||||||
import {Logger} from 'common/log';
|
|
||||||
import {SECOND} from 'common/utils/constants';
|
|
||||||
import Config from 'common/config';
|
|
||||||
|
|
||||||
import ServerManager from 'common/servers/serverManager';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getAdjustedWindowBoundaries,
|
|
||||||
shouldHaveBackBar,
|
|
||||||
} from '../utils';
|
|
||||||
|
|
||||||
import ViewManager from '../views/viewManager';
|
|
||||||
import LoadingScreen from '../views/loadingScreen';
|
|
||||||
import {MattermostView} from '../views/MattermostView';
|
|
||||||
import TeamDropdownView from '../views/teamDropdownView';
|
|
||||||
import DownloadsDropdownView from '../views/downloadsDropdownView';
|
|
||||||
import DownloadsDropdownMenuView from '../views/downloadsDropdownMenuView';
|
|
||||||
|
|
||||||
import MainWindow from './mainWindow';
|
|
||||||
import CallsWidgetWindow from './callsWidgetWindow';
|
|
||||||
import SettingsWindow from './settingsWindow';
|
|
||||||
|
|
||||||
// singleton module to manage application's windows
|
|
||||||
|
|
||||||
const log = new Logger('WindowManager');
|
|
||||||
|
|
||||||
export class WindowManager {
|
|
||||||
private isResizing: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.isResizing = false;
|
|
||||||
|
|
||||||
ipcMain.handle(GET_DARK_MODE, this.handleGetDarkMode);
|
|
||||||
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
|
||||||
ipcMain.on(WINDOW_CLOSE, this.handleClose);
|
|
||||||
ipcMain.on(WINDOW_MAXIMIZE, this.handleMaximize);
|
|
||||||
ipcMain.on(WINDOW_MINIMIZE, this.handleMinimize);
|
|
||||||
ipcMain.on(WINDOW_RESTORE, this.handleRestore);
|
|
||||||
ipcMain.on(DOUBLE_CLICK_ON_WINDOW, this.handleDoubleClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
showMainWindow = (deeplinkingURL?: string | URL) => {
|
|
||||||
log.debug('showMainWindow', deeplinkingURL);
|
|
||||||
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (mainWindow) {
|
|
||||||
if (mainWindow.isVisible()) {
|
|
||||||
mainWindow.focus();
|
|
||||||
} else {
|
|
||||||
mainWindow.show();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.createMainWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deeplinkingURL) {
|
|
||||||
ViewManager.handleDeepLink(deeplinkingURL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createMainWindow = () => {
|
|
||||||
const mainWindow = MainWindow.get(true);
|
|
||||||
if (!mainWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// window handlers
|
|
||||||
mainWindow.on('maximize', this.handleMaximizeMainWindow);
|
|
||||||
mainWindow.on('unmaximize', this.handleUnmaximizeMainWindow);
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
mainWindow.on('resize', this.handleResizeMainWindow);
|
|
||||||
}
|
|
||||||
mainWindow.on('will-resize', this.handleWillResizeMainWindow);
|
|
||||||
mainWindow.on('resized', this.handleResizedMainWindow);
|
|
||||||
mainWindow.on('focus', ViewManager.focusCurrentView);
|
|
||||||
mainWindow.on('enter-full-screen', () => this.sendToRenderer('enter-full-screen'));
|
|
||||||
mainWindow.on('leave-full-screen', () => this.sendToRenderer('leave-full-screen'));
|
|
||||||
|
|
||||||
this.initializeViewManager();
|
|
||||||
TeamDropdownView.init();
|
|
||||||
DownloadsDropdownView.init();
|
|
||||||
DownloadsDropdownMenuView.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
// max retries allows the message to get to the renderer even if it is sent while the app is starting up.
|
|
||||||
private sendToRendererWithRetry = (maxRetries: number, channel: string, ...args: unknown[]) => {
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
|
|
||||||
if (!mainWindow || !MainWindow.isReady) {
|
|
||||||
if (maxRetries > 0) {
|
|
||||||
log.debug(`Can't send ${channel}, will retry`);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.sendToRendererWithRetry(maxRetries - 1, channel, ...args);
|
|
||||||
}, SECOND);
|
|
||||||
} else {
|
|
||||||
log.error(`Unable to send the message to the main window for message type ${channel}`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mainWindow.webContents.send(channel, ...args);
|
|
||||||
SettingsWindow.get()?.webContents.send(channel, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToRenderer = (channel: string, ...args: unknown[]) => {
|
|
||||||
this.sendToRendererWithRetry(3, channel, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreMain = () => {
|
|
||||||
log.info('restoreMain');
|
|
||||||
if (!MainWindow.get()) {
|
|
||||||
this.showMainWindow();
|
|
||||||
}
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (!mainWindow) {
|
|
||||||
throw new Error('Main window does not exist');
|
|
||||||
}
|
|
||||||
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
|
|
||||||
if (mainWindow.isMinimized()) {
|
|
||||||
mainWindow.restore();
|
|
||||||
} else {
|
|
||||||
mainWindow.show();
|
|
||||||
}
|
|
||||||
const settingsWindow = SettingsWindow.get();
|
|
||||||
if (settingsWindow) {
|
|
||||||
settingsWindow.focus();
|
|
||||||
} else {
|
|
||||||
mainWindow.focus();
|
|
||||||
}
|
|
||||||
} else if (SettingsWindow.get()) {
|
|
||||||
SettingsWindow.get()?.focus();
|
|
||||||
} else {
|
|
||||||
mainWindow.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeViewManager = () => {
|
|
||||||
ViewManager.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
switchServer = (serverId: string, waitForViewToExist = false) => {
|
|
||||||
ServerManager.getServerLog(serverId, 'WindowManager').debug('switchServer');
|
|
||||||
this.showMainWindow();
|
|
||||||
const server = ServerManager.getServer(serverId);
|
|
||||||
if (!server) {
|
|
||||||
ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nextTab = ServerManager.getLastActiveTabForServer(serverId);
|
|
||||||
if (waitForViewToExist) {
|
|
||||||
const timeout = setInterval(() => {
|
|
||||||
if (ViewManager.getView(nextTab.id)) {
|
|
||||||
ViewManager.showById(nextTab.id);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
} else {
|
|
||||||
ViewManager.showById(nextTab.id);
|
|
||||||
}
|
|
||||||
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
|
||||||
}
|
|
||||||
|
|
||||||
switchTab = (tabId: string) => {
|
|
||||||
ViewManager.showById(tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ID fetching
|
|
||||||
*/
|
|
||||||
|
|
||||||
getServerURLFromWebContentsId = (id: number) => {
|
|
||||||
if (CallsWidgetWindow.isCallsWidget(id)) {
|
|
||||||
return CallsWidgetWindow.getURL();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ViewManager.getViewByWebContentsId(id)?.tab.server.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tab switching
|
|
||||||
*/
|
|
||||||
|
|
||||||
selectNextTab = () => {
|
|
||||||
this.selectTab((order) => order + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPreviousTab = () => {
|
|
||||||
this.selectTab((order, length) => (length + (order - 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private selectTab = (fn: (order: number, length: number) => number) => {
|
|
||||||
const currentView = ViewManager.getCurrentView();
|
|
||||||
if (!currentView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTeamTabs = ServerManager.getOrderedTabsForServer(currentView.tab.server.id).map((tab, index) => ({tab, index}));
|
|
||||||
const filteredTabs = currentTeamTabs?.filter((tab) => tab.tab.isOpen);
|
|
||||||
const currentTab = currentTeamTabs?.find((tab) => tab.tab.type === currentView.tab.type);
|
|
||||||
if (!currentTeamTabs || !currentTab || !filteredTabs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentOrder = currentTab.index;
|
|
||||||
let nextIndex = -1;
|
|
||||||
while (nextIndex === -1) {
|
|
||||||
const nextOrder = (fn(currentOrder, currentTeamTabs.length) % currentTeamTabs.length);
|
|
||||||
nextIndex = filteredTabs.findIndex((tab) => tab.index === nextOrder);
|
|
||||||
currentOrder = nextOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newTab = filteredTabs[nextIndex].tab;
|
|
||||||
this.switchTab(newTab.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************
|
|
||||||
* MAIN WINDOW EVENT HANDLERS
|
|
||||||
*****************/
|
|
||||||
|
|
||||||
private handleMaximizeMainWindow = () => {
|
|
||||||
DownloadsDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownMenuView.updateWindowBounds();
|
|
||||||
this.sendToRenderer(MAXIMIZE_CHANGE, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleUnmaximizeMainWindow = () => {
|
|
||||||
DownloadsDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownMenuView.updateWindowBounds();
|
|
||||||
this.sendToRenderer(MAXIMIZE_CHANGE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleWillResizeMainWindow = (event: Event, newBounds: Electron.Rectangle) => {
|
|
||||||
log.silly('handleWillResizeMainWindow');
|
|
||||||
|
|
||||||
if (!MainWindow.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixes an issue on win11 related to Snap where the first "will-resize" event would return the same bounds
|
|
||||||
* causing the "resize" event to not fire
|
|
||||||
*/
|
|
||||||
const prevBounds = this.getBounds();
|
|
||||||
if (prevBounds.height === newBounds.height && prevBounds.width === newBounds.width) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isResizing && LoadingScreen.isHidden() && ViewManager.getCurrentView()) {
|
|
||||||
log.debug('prevented resize');
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.throttledWillResize(newBounds);
|
|
||||||
LoadingScreen.setBounds();
|
|
||||||
TeamDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownMenuView.updateWindowBounds();
|
|
||||||
ipcMain.emit(RESIZE_MODAL, null, newBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleResizedMainWindow = () => {
|
|
||||||
log.silly('handleResizedMainWindow');
|
|
||||||
|
|
||||||
const bounds = this.getBounds();
|
|
||||||
this.throttledWillResize(bounds);
|
|
||||||
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
|
||||||
TeamDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownMenuView.updateWindowBounds();
|
|
||||||
this.isResizing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private throttledWillResize = (newBounds: Electron.Rectangle) => {
|
|
||||||
log.silly('throttledWillResize', {newBounds});
|
|
||||||
|
|
||||||
this.isResizing = true;
|
|
||||||
this.setCurrentViewBounds(newBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleResizeMainWindow = () => {
|
|
||||||
log.silly('handleResizeMainWindow');
|
|
||||||
|
|
||||||
if (!MainWindow.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.isResizing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bounds = this.getBounds();
|
|
||||||
|
|
||||||
// Another workaround since the window doesn't update properly under Linux for some reason
|
|
||||||
// See above comment
|
|
||||||
setTimeout(this.setCurrentViewBounds, 10, bounds);
|
|
||||||
|
|
||||||
LoadingScreen.setBounds();
|
|
||||||
TeamDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownView.updateWindowBounds();
|
|
||||||
DownloadsDropdownMenuView.updateWindowBounds();
|
|
||||||
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
private setCurrentViewBounds = (bounds: {width: number; height: number}) => {
|
|
||||||
log.debug('setCurrentViewBounds', {bounds});
|
|
||||||
|
|
||||||
const currentView = ViewManager.getCurrentView();
|
|
||||||
if (currentView) {
|
|
||||||
const adjustedBounds = getAdjustedWindowBoundaries(bounds.width, bounds.height, shouldHaveBackBar(currentView.tab.url, currentView.currentURL));
|
|
||||||
this.setBoundsFunction(currentView, adjustedBounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private setBoundsFunction = (currentView: MattermostView, bounds: Electron.Rectangle) => {
|
|
||||||
log.silly('setBoundsFunction', bounds.width, bounds.height);
|
|
||||||
currentView.setBounds(bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
private getBounds = () => {
|
|
||||||
let bounds;
|
|
||||||
|
|
||||||
const mainWindow = MainWindow.get();
|
|
||||||
if (mainWindow) {
|
|
||||||
// Workaround for linux maximizing/minimizing, which doesn't work properly because of these bugs:
|
|
||||||
// https://github.com/electron/electron/issues/28699
|
|
||||||
// https://github.com/electron/electron/issues/28106
|
|
||||||
if (process.platform === 'linux') {
|
|
||||||
const size = mainWindow.getSize();
|
|
||||||
bounds = {width: size[0], height: size[1]};
|
|
||||||
} else {
|
|
||||||
bounds = mainWindow.getContentBounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bounds as Electron.Rectangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************
|
|
||||||
* IPC EVENT HANDLERS
|
|
||||||
*****************/
|
|
||||||
|
|
||||||
private handleGetDarkMode = () => {
|
|
||||||
return Config.darkMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleViewFinishedResizing = () => {
|
|
||||||
this.isResizing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleClose = () => {
|
|
||||||
const focused = BrowserWindow.getFocusedWindow();
|
|
||||||
focused?.close();
|
|
||||||
}
|
|
||||||
private handleMaximize = () => {
|
|
||||||
const focused = BrowserWindow.getFocusedWindow();
|
|
||||||
if (focused) {
|
|
||||||
focused.maximize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private handleMinimize = () => {
|
|
||||||
const focused = BrowserWindow.getFocusedWindow();
|
|
||||||
if (focused) {
|
|
||||||
focused.minimize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private handleRestore = () => {
|
|
||||||
const focused = BrowserWindow.getFocusedWindow();
|
|
||||||
if (focused) {
|
|
||||||
focused.restore();
|
|
||||||
}
|
|
||||||
if (focused?.isFullScreen()) {
|
|
||||||
focused.setFullScreen(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDoubleClick = (e: IpcMainEvent, windowType?: string) => {
|
|
||||||
log.debug('handleDoubleClick', windowType);
|
|
||||||
|
|
||||||
let action = 'Maximize';
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
|
|
||||||
}
|
|
||||||
const win = (windowType === 'settings') ? SettingsWindow.get() : MainWindow.get();
|
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (action) {
|
|
||||||
case 'Minimize':
|
|
||||||
if (win.isMinimized()) {
|
|
||||||
win.restore();
|
|
||||||
} else {
|
|
||||||
win.minimize();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'Maximize':
|
|
||||||
default:
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize();
|
|
||||||
} else {
|
|
||||||
win.maximize();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const windowManager = new WindowManager();
|
|
||||||
export default windowManager;
|
|
Loading…
Reference in a new issue