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:
@@ -38,19 +38,14 @@ jest.mock('main/i18nManager', () => ({
|
||||
localizeMessage: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/tray/tray', () => ({}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
showMainWindow: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
show: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
getView: jest.fn(),
|
||||
getViewByWebContentsId: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('main/app/app', () => {
|
||||
describe('handleAppWillFinishLaunching', () => {
|
||||
|
@@ -10,7 +10,6 @@ import updateManager from 'main/autoUpdater';
|
||||
import CertificateStore from 'main/certificateStore';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import {destroyTray} from 'main/tray/tray';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
@@ -30,8 +29,10 @@ export function handleAppSecondInstance(event: Event, argv: string[]) {
|
||||
|
||||
// Protocol handler for win32
|
||||
// argv: An array of the second instance’s (command line / deep linked) arguments
|
||||
const deeplinkingUrl = getDeeplinkingURL(argv);
|
||||
WindowManager.showMainWindow(deeplinkingUrl);
|
||||
const deeplinkingURL = getDeeplinkingURL(argv);
|
||||
if (deeplinkingURL) {
|
||||
openDeepLink(deeplinkingURL);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleAppWindowAllClosed() {
|
||||
|
@@ -10,7 +10,7 @@ import {setLoggingLevel} from 'common/log';
|
||||
import {handleConfigUpdate} from 'main/app/config';
|
||||
import {handleMainWindowIsShown} from 'main/app/intercom';
|
||||
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import AutoLauncher from 'main/AutoLauncher';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
@@ -47,8 +47,7 @@ jest.mock('main/views/viewManager', () => ({
|
||||
reloadConfiguration: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/loadingScreen', () => ({}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
handleUpdateConfig: jest.fn(),
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
sendToRenderer: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -65,11 +64,11 @@ describe('main/app/config', () => {
|
||||
|
||||
it('should reload renderer config only when app is ready', () => {
|
||||
handleConfigUpdate({});
|
||||
expect(WindowManager.sendToRenderer).not.toBeCalled();
|
||||
expect(MainWindow.sendToRenderer).not.toBeCalled();
|
||||
|
||||
app.isReady.mockReturnValue(true);
|
||||
handleConfigUpdate({});
|
||||
expect(WindowManager.sendToRenderer).toBeCalledWith(RELOAD_CONFIGURATION);
|
||||
expect(MainWindow.sendToRenderer).toBeCalledWith(RELOAD_CONFIGURATION);
|
||||
});
|
||||
|
||||
it('should set download path if applicable', () => {
|
||||
|
@@ -13,7 +13,8 @@ import AutoLauncher from 'main/AutoLauncher';
|
||||
import {setUnreadBadgeSetting} from 'main/badge';
|
||||
import {refreshTrayImages} from 'main/tray/tray';
|
||||
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 {handleUpdateMenuEvent, updateSpellCheckerLocales} from './utils';
|
||||
@@ -72,7 +73,8 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
|
||||
}
|
||||
|
||||
if (app.isReady()) {
|
||||
WindowManager.sendToRenderer(RELOAD_CONFIGURATION);
|
||||
MainWindow.sendToRenderer(RELOAD_CONFIGURATION);
|
||||
SettingsWindow.sendToRenderer(RELOAD_CONFIGURATION);
|
||||
}
|
||||
|
||||
setUnreadBadgeSetting(newConfig && newConfig.showUnreadBadge);
|
||||
@@ -111,7 +113,8 @@ export function handleDarkModeChange(darkMode: boolean) {
|
||||
log.debug('handleDarkModeChange', darkMode);
|
||||
|
||||
refreshTrayImages(Config.trayIconTheme);
|
||||
WindowManager.sendToRenderer(DARK_MODE_CHANGE, darkMode);
|
||||
MainWindow.sendToRenderer(DARK_MODE_CHANGE, darkMode);
|
||||
SettingsWindow.sendToRenderer(DARK_MODE_CHANGE, darkMode);
|
||||
LoadingScreen.setDarkMode(darkMode);
|
||||
|
||||
ipcMain.emit(EMIT_CONFIGURATION, true, Config.data);
|
||||
|
@@ -5,6 +5,11 @@
|
||||
|
||||
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) {
|
||||
module.hot.accept();
|
||||
}
|
||||
|
@@ -9,8 +9,8 @@ import Config from 'common/config';
|
||||
import urlUtils from 'common/utils/url';
|
||||
|
||||
import parseArgs from 'main/ParseArgs';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
|
||||
import {initialize} from './initialize';
|
||||
import {clearAppCache, getDeeplinkingURL, wasUpdated} from './utils';
|
||||
@@ -119,6 +119,7 @@ jest.mock('main/app/config', () => ({
|
||||
jest.mock('main/app/intercom', () => ({
|
||||
handleMainWindowIsShown: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/app/servers', () => ({}));
|
||||
jest.mock('main/app/utils', () => ({
|
||||
clearAppCache: jest.fn(),
|
||||
getDeeplinkingURL: jest.fn(),
|
||||
@@ -168,18 +169,17 @@ jest.mock('main/UserActivityMonitor', () => ({
|
||||
jest.mock('main/windows/callsWidgetWindow', () => ({
|
||||
isCallsWidget: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
showMainWindow: jest.fn(),
|
||||
sendToRenderer: jest.fn(),
|
||||
getServerNameByWebContentsId: jest.fn(),
|
||||
getServerURLFromWebContentsId: jest.fn(),
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
getViewByWebContentsId: jest.fn(),
|
||||
handleDeepLink: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/viewManager', () => ({}));
|
||||
jest.mock('main/windows/settingsWindow', () => ({
|
||||
show: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
show: jest.fn(),
|
||||
sendToRenderer: jest.fn(),
|
||||
}));
|
||||
const originalProcess = process;
|
||||
describe('main/app/initialize', () => {
|
||||
@@ -272,11 +272,17 @@ describe('main/app/initialize', () => {
|
||||
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 () => {
|
||||
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();
|
||||
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
|
||||
cb({id: 1, getURL: () => 'http://server-1.com'}, 'bad-permission', callback);
|
||||
|
@@ -36,6 +36,12 @@ import {
|
||||
GET_ORDERED_SERVERS,
|
||||
GET_ORDERED_TABS_FOR_SERVER,
|
||||
SERVERS_URL_MODIFIED,
|
||||
GET_DARK_MODE,
|
||||
WINDOW_CLOSE,
|
||||
WINDOW_MAXIMIZE,
|
||||
WINDOW_MINIMIZE,
|
||||
WINDOW_RESTORE,
|
||||
DOUBLE_CLICK_ON_WINDOW,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
@@ -59,7 +65,6 @@ import {refreshTrayImages, setupTray} from 'main/tray/tray';
|
||||
import UserActivityMonitor from 'main/UserActivityMonitor';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {protocols} from '../../../electron-builder.json';
|
||||
@@ -84,22 +89,21 @@ import {
|
||||
import {
|
||||
handleMainWindowIsShown,
|
||||
handleAppVersion,
|
||||
handleCloseTab,
|
||||
handleEditServerModal,
|
||||
handleMentionNotification,
|
||||
handleNewServerModal,
|
||||
handleOpenAppMenu,
|
||||
handleOpenTab,
|
||||
handleQuit,
|
||||
handleRemoveServerModal,
|
||||
handleSelectDownload,
|
||||
handleSwitchServer,
|
||||
handleSwitchTab,
|
||||
handlePingDomain,
|
||||
handleGetOrderedServers,
|
||||
handleGetOrderedTabsForServer,
|
||||
handleGetLastActive,
|
||||
} from './intercom';
|
||||
import {
|
||||
handleEditServerModal,
|
||||
handleNewServerModal,
|
||||
handleRemoveServerModal,
|
||||
switchServer,
|
||||
} from './servers';
|
||||
import {
|
||||
handleCloseTab, handleGetLastActive, handleGetOrderedTabsForServer, handleOpenTab,
|
||||
} from './tabs';
|
||||
import {
|
||||
clearAppCache,
|
||||
getDeeplinkingURL,
|
||||
@@ -111,6 +115,14 @@ import {
|
||||
migrateMacAppStore,
|
||||
updateServerInfos,
|
||||
} from './utils';
|
||||
import {
|
||||
handleClose,
|
||||
handleDoubleClick,
|
||||
handleGetDarkMode,
|
||||
handleMaximize,
|
||||
handleMinimize,
|
||||
handleRestore,
|
||||
} from './windows';
|
||||
|
||||
export const mainProtocol = protocols?.[0]?.schemes?.[0];
|
||||
|
||||
@@ -203,7 +215,7 @@ function initializeAppEventListeners() {
|
||||
app.on('second-instance', handleAppSecondInstance);
|
||||
app.on('window-all-closed', handleAppWindowAllClosed);
|
||||
app.on('browser-window-created', handleAppBrowserWindowCreated);
|
||||
app.on('activate', () => WindowManager.showMainWindow());
|
||||
app.on('activate', () => MainWindow.show());
|
||||
app.on('before-quit', handleAppBeforeQuit);
|
||||
app.on('certificate-error', handleAppCertificateError);
|
||||
app.on('select-client-certificate', CertificateManager.handleSelectCertificate);
|
||||
@@ -267,8 +279,8 @@ function initializeInterCommunicationEventListeners() {
|
||||
ipcMain.on(OPEN_APP_MENU, handleOpenAppMenu);
|
||||
}
|
||||
|
||||
ipcMain.on(SWITCH_SERVER, handleSwitchServer);
|
||||
ipcMain.on(SWITCH_TAB, handleSwitchTab);
|
||||
ipcMain.on(SWITCH_SERVER, (event, serverId) => switchServer(serverId));
|
||||
ipcMain.on(SWITCH_TAB, (event, viewId) => ViewManager.showById(viewId));
|
||||
ipcMain.on(CLOSE_TAB, handleCloseTab);
|
||||
ipcMain.on(OPEN_TAB, handleOpenTab);
|
||||
|
||||
@@ -289,8 +301,15 @@ function initializeInterCommunicationEventListeners() {
|
||||
ipcMain.on(UPDATE_SERVER_ORDER, (event, serverOrder) => ServerManager.updateServerOrder(serverOrder));
|
||||
ipcMain.on(UPDATE_TAB_ORDER, (event, serverId, tabOrder) => ServerManager.updateTabOrder(serverId, tabOrder));
|
||||
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_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() {
|
||||
@@ -364,6 +383,9 @@ async function initializeAfterAppReady() {
|
||||
catch((err) => log.error('An error occurred: ', err));
|
||||
}
|
||||
|
||||
initCookieManager(defaultSession);
|
||||
MainWindow.show();
|
||||
|
||||
let deeplinkingURL;
|
||||
|
||||
// Protocol handler for win32
|
||||
@@ -371,13 +393,12 @@ async function initializeAfterAppReady() {
|
||||
const args = process.argv.slice(1);
|
||||
if (Array.isArray(args) && args.length > 0) {
|
||||
deeplinkingURL = getDeeplinkingURL(args);
|
||||
if (deeplinkingURL) {
|
||||
ViewManager.handleDeepLink(deeplinkingURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initCookieManager(defaultSession);
|
||||
|
||||
WindowManager.showMainWindow(deeplinkingURL);
|
||||
|
||||
// listen for status updates and pass on to renderer
|
||||
UserActivityMonitor.on('status', (status) => {
|
||||
log.debug('UserActivityMonitor.on(status)', status);
|
||||
@@ -440,7 +461,7 @@ async function initializeAfterAppReady() {
|
||||
}
|
||||
|
||||
const requestingURL = webContents.getURL();
|
||||
const serverURL = WindowManager.getServerURLFromWebContentsId(webContents.id);
|
||||
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
|
||||
|
||||
if (!serverURL) {
|
||||
callback(false);
|
||||
|
@@ -1,20 +1,12 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||
|
||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
|
||||
import {
|
||||
handleOpenTab,
|
||||
handleCloseTab,
|
||||
handleNewServerModal,
|
||||
handleEditServerModal,
|
||||
handleRemoveServerModal,
|
||||
handleWelcomeScreenModal,
|
||||
handleMainWindowIsShown,
|
||||
} from './intercom';
|
||||
@@ -22,9 +14,6 @@ import {
|
||||
jest.mock('common/config', () => ({
|
||||
setServers: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getDefaultConfigTeamFromTeam: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/notifications', () => ({}));
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
setTabIsOpen: jest.fn(),
|
||||
@@ -45,224 +34,13 @@ jest.mock('main/views/viewManager', () => ({}));
|
||||
jest.mock('main/views/modalManager', () => ({
|
||||
addModal: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
switchServer: jest.fn(),
|
||||
switchTab: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
}));
|
||||
|
||||
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('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', () => {
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
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 Config from 'common/config';
|
||||
@@ -14,10 +14,10 @@ import {displayMention} from 'main/notifications';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {handleAppBeforeQuit} from './app';
|
||||
import {handleNewServerModal, switchServer} from './servers';
|
||||
|
||||
const log = new Logger('App.Intercom');
|
||||
|
||||
@@ -35,49 +35,6 @@ export function handleQuit(e: IpcMainEvent, reason: string, stack: string) {
|
||||
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) {
|
||||
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() {
|
||||
log.debug('handleWelcomeScreenModal');
|
||||
|
||||
@@ -236,7 +98,7 @@ export function handleWelcomeScreenModal() {
|
||||
if (modalPromise) {
|
||||
modalPromise.then((data) => {
|
||||
const newTeam = ServerManager.addServer(data);
|
||||
WindowManager.switchServer(newTeam.id, true);
|
||||
switchServer(newTeam.id, true);
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
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/views/viewManager', () => ({}));
|
||||
jest.mock('main/windows/mainWindow', () => ({}));
|
||||
jest.mock('main/windows/windowManager', () => ({}));
|
||||
|
||||
jest.mock('./initialize', () => ({
|
||||
mainProtocol: 'mattermost',
|
||||
|
@@ -27,7 +27,6 @@ import {createMenu as createTrayMenu} from 'main/menus/tray';
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
import {setTrayMenu} from 'main/tray/tray';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {mainProtocol} from './initialize';
|
||||
@@ -39,7 +38,8 @@ const log = new Logger('App.Utils');
|
||||
|
||||
export function openDeepLink(deeplinkingUrl: string) {
|
||||
try {
|
||||
WindowManager.showMainWindow(deeplinkingUrl);
|
||||
MainWindow.show();
|
||||
ViewManager.handleDeepLink(deeplinkingUrl);
|
||||
} catch (err) {
|
||||
log.error(`There was an error opening the deeplinking url: ${err}`);
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export function handleUpdateMenuEvent() {
|
||||
Menu.setApplicationMenu(aMenu);
|
||||
aMenu.addListener('menu-will-close', () => {
|
||||
ViewManager.focusCurrentView();
|
||||
WindowManager.sendToRenderer(APP_MENU_WILL_CLOSE);
|
||||
MainWindow.sendToRenderer(APP_MENU_WILL_CLOSE);
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user