[MM-50485] Migrate app to ServerManager, remove view names and replace with IDs (#2672)
* Migrate app to ServerManager, remove view names and replace with IDs * Fixed a test * Fixed a bug when adding the initial server * Merge'd * Bug fixes and PR feedback
This commit is contained in:
@@ -116,7 +116,6 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
|
||||
|
||||
certificateErrorCallbacks.set(errorID, callback);
|
||||
|
||||
// TODO: should we move this to window manager or provide a handler for dialogs?
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
|
@@ -28,7 +28,6 @@ jest.mock('electron', () => ({
|
||||
jest.mock('main/app/utils', () => ({
|
||||
handleUpdateMenuEvent: jest.fn(),
|
||||
updateSpellCheckerLocales: jest.fn(),
|
||||
updateServerInfos: jest.fn(),
|
||||
setLoggingLevel: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/app/intercom', () => ({
|
||||
@@ -51,7 +50,6 @@ jest.mock('main/views/loadingScreen', () => ({}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
handleUpdateConfig: jest.fn(),
|
||||
sendToRenderer: jest.fn(),
|
||||
initializeCurrentServerName: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('main/app/config', () => {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
import {app, ipcMain, nativeTheme} from 'electron';
|
||||
|
||||
import {CombinedConfig, ConfigServer, Config as ConfigType} from 'types/config';
|
||||
import {CombinedConfig, Config as ConfigType} from 'types/config';
|
||||
|
||||
import {DARK_MODE_CHANGE, EMIT_CONFIGURATION, RELOAD_CONFIGURATION} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
@@ -12,12 +12,11 @@ import {Logger, setLoggingLevel} from 'common/log';
|
||||
import AutoLauncher from 'main/AutoLauncher';
|
||||
import {setUnreadBadgeSetting} from 'main/badge';
|
||||
import {refreshTrayImages} from 'main/tray/tray';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import LoadingScreen from 'main/views/loadingScreen';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
|
||||
import {handleMainWindowIsShown} from './intercom';
|
||||
import {handleUpdateMenuEvent, updateServerInfos, updateSpellCheckerLocales} from './utils';
|
||||
import {handleUpdateMenuEvent, updateSpellCheckerLocales} from './utils';
|
||||
|
||||
const log = new Logger('App.Config');
|
||||
|
||||
@@ -60,14 +59,6 @@ export function handleUpdateTheme() {
|
||||
Config.set('darkMode', nativeTheme.shouldUseDarkColors);
|
||||
}
|
||||
|
||||
export function handleUpdateTeams(event: Electron.IpcMainInvokeEvent, newTeams: ConfigServer[]) {
|
||||
log.debug('Config.handleUpdateTeams');
|
||||
log.silly('Config.handleUpdateTeams', newTeams);
|
||||
|
||||
Config.setServers(newTeams);
|
||||
return Config.teams;
|
||||
}
|
||||
|
||||
export function handleConfigUpdate(newConfig: CombinedConfig) {
|
||||
if (newConfig.logLevel) {
|
||||
setLoggingLevel(newConfig.logLevel);
|
||||
@@ -81,7 +72,6 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
|
||||
}
|
||||
|
||||
if (app.isReady()) {
|
||||
ViewManager.reloadConfiguration();
|
||||
WindowManager.sendToRenderer(RELOAD_CONFIGURATION);
|
||||
}
|
||||
|
||||
@@ -106,8 +96,6 @@ export function handleConfigUpdate(newConfig: CombinedConfig) {
|
||||
}
|
||||
|
||||
if (app.isReady()) {
|
||||
updateServerInfos(newConfig.teams);
|
||||
WindowManager.initializeCurrentServerName();
|
||||
handleMainWindowIsShown();
|
||||
}
|
||||
|
||||
|
@@ -124,10 +124,10 @@ jest.mock('main/app/utils', () => ({
|
||||
getDeeplinkingURL: jest.fn(),
|
||||
handleUpdateMenuEvent: jest.fn(),
|
||||
shouldShowTrayIcon: jest.fn(),
|
||||
updateServerInfos: jest.fn(),
|
||||
updateSpellCheckerLocales: jest.fn(),
|
||||
wasUpdated: jest.fn(),
|
||||
initCookieManager: jest.fn(),
|
||||
updateServerInfos: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/appState', () => ({
|
||||
on: jest.fn(),
|
||||
@@ -149,6 +149,11 @@ jest.mock('main/notifications', () => ({
|
||||
displayDownloadCompleted: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/ParseArgs', () => jest.fn());
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
reloadFromConfig: jest.fn(),
|
||||
getAllServers: jest.fn(),
|
||||
on: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/tray/tray', () => ({
|
||||
refreshTrayImages: jest.fn(),
|
||||
setupTray: jest.fn(),
|
||||
@@ -194,7 +199,6 @@ describe('main/app/initialize', () => {
|
||||
}
|
||||
});
|
||||
Config.data = {};
|
||||
Config.teams = [];
|
||||
app.whenReady.mockResolvedValue();
|
||||
app.requestSingleInstanceLock.mockReturnValue(true);
|
||||
app.getPath.mockImplementation((p) => `/basedir/${p}`);
|
||||
|
@@ -11,16 +11,9 @@ import {
|
||||
SWITCH_SERVER,
|
||||
FOCUS_BROWSERVIEW,
|
||||
QUIT,
|
||||
DOUBLE_CLICK_ON_WINDOW,
|
||||
SHOW_NEW_SERVER_MODAL,
|
||||
WINDOW_CLOSE,
|
||||
WINDOW_MAXIMIZE,
|
||||
WINDOW_MINIMIZE,
|
||||
WINDOW_RESTORE,
|
||||
NOTIFY_MENTION,
|
||||
GET_DOWNLOAD_LOCATION,
|
||||
SHOW_SETTINGS_WINDOW,
|
||||
RELOAD_CONFIGURATION,
|
||||
SWITCH_TAB,
|
||||
CLOSE_TAB,
|
||||
OPEN_TAB,
|
||||
@@ -33,13 +26,17 @@ import {
|
||||
START_UPGRADE,
|
||||
START_UPDATE_DOWNLOAD,
|
||||
PING_DOMAIN,
|
||||
MAIN_WINDOW_SHOWN,
|
||||
OPEN_APP_MENU,
|
||||
GET_CONFIGURATION,
|
||||
GET_LOCAL_CONFIGURATION,
|
||||
UPDATE_CONFIGURATION,
|
||||
UPDATE_PATHS,
|
||||
UPDATE_TEAMS,
|
||||
UPDATE_SERVER_ORDER,
|
||||
UPDATE_TAB_ORDER,
|
||||
GET_LAST_ACTIVE,
|
||||
GET_ORDERED_SERVERS,
|
||||
GET_ORDERED_TABS_FOR_SERVER,
|
||||
SERVERS_URL_MODIFIED,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
@@ -57,7 +54,7 @@ import CriticalErrorHandler from 'main/CriticalErrorHandler';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import i18nManager from 'main/i18nManager';
|
||||
import parseArgs from 'main/ParseArgs';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import TrustedOriginsStore from 'main/trustedOrigins';
|
||||
import {refreshTrayImages, setupTray} from 'main/tray/tray';
|
||||
import UserActivityMonitor from 'main/UserActivityMonitor';
|
||||
@@ -84,7 +81,6 @@ import {
|
||||
handleGetLocalConfiguration,
|
||||
handleUpdateTheme,
|
||||
updateConfiguration,
|
||||
handleUpdateTeams,
|
||||
} from './config';
|
||||
import {
|
||||
handleMainWindowIsShown,
|
||||
@@ -96,24 +92,26 @@ import {
|
||||
handleOpenAppMenu,
|
||||
handleOpenTab,
|
||||
handleQuit,
|
||||
handleReloadConfig,
|
||||
handleRemoveServerModal,
|
||||
handleSelectDownload,
|
||||
handleSwitchServer,
|
||||
handleSwitchTab,
|
||||
handleUpdateLastActive,
|
||||
handlePingDomain,
|
||||
handleGetOrderedServers,
|
||||
handleGetOrderedTabsForServer,
|
||||
handleGetLastActive,
|
||||
} from './intercom';
|
||||
import {
|
||||
clearAppCache,
|
||||
getDeeplinkingURL,
|
||||
handleUpdateMenuEvent,
|
||||
shouldShowTrayIcon,
|
||||
updateServerInfos,
|
||||
updateSpellCheckerLocales,
|
||||
wasUpdated,
|
||||
initCookieManager,
|
||||
migrateMacAppStore,
|
||||
updateServerInfos,
|
||||
} from './utils';
|
||||
|
||||
export const mainProtocol = protocols?.[0]?.schemes?.[0];
|
||||
@@ -153,7 +151,7 @@ export async function initialize() {
|
||||
|
||||
// initialization that should run once the app is ready
|
||||
initializeInterCommunicationEventListeners();
|
||||
initializeAfterAppReady();
|
||||
await initializeAfterAppReady();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -262,7 +260,6 @@ function initializeBeforeAppReady() {
|
||||
}
|
||||
|
||||
function initializeInterCommunicationEventListeners() {
|
||||
ipcMain.on(RELOAD_CONFIGURATION, handleReloadConfig);
|
||||
ipcMain.on(NOTIFY_MENTION, handleMentionNotification);
|
||||
ipcMain.handle('get-app-version', handleAppVersion);
|
||||
ipcMain.on(UPDATE_SHORTCUT_MENU, handleUpdateMenuEvent);
|
||||
@@ -280,17 +277,9 @@ function initializeInterCommunicationEventListeners() {
|
||||
|
||||
ipcMain.on(QUIT, handleQuit);
|
||||
|
||||
ipcMain.on(DOUBLE_CLICK_ON_WINDOW, WindowManager.handleDoubleClick);
|
||||
|
||||
ipcMain.on(SHOW_NEW_SERVER_MODAL, handleNewServerModal);
|
||||
ipcMain.on(SHOW_EDIT_SERVER_MODAL, handleEditServerModal);
|
||||
ipcMain.on(SHOW_REMOVE_SERVER_MODAL, handleRemoveServerModal);
|
||||
ipcMain.on(MAIN_WINDOW_SHOWN, handleMainWindowIsShown);
|
||||
ipcMain.on(WINDOW_CLOSE, WindowManager.close);
|
||||
ipcMain.on(WINDOW_MAXIMIZE, WindowManager.maximize);
|
||||
ipcMain.on(WINDOW_MINIMIZE, WindowManager.minimize);
|
||||
ipcMain.on(WINDOW_RESTORE, WindowManager.restore);
|
||||
ipcMain.on(SHOW_SETTINGS_WINDOW, SettingsWindow.show);
|
||||
ipcMain.handle(GET_AVAILABLE_SPELL_CHECKER_LANGUAGES, () => session.defaultSession.availableSpellCheckerLanguages);
|
||||
ipcMain.handle(GET_DOWNLOAD_LOCATION, handleSelectDownload);
|
||||
ipcMain.on(START_UPDATE_DOWNLOAD, handleStartDownload);
|
||||
@@ -298,12 +287,24 @@ function initializeInterCommunicationEventListeners() {
|
||||
ipcMain.handle(PING_DOMAIN, handlePingDomain);
|
||||
ipcMain.handle(GET_CONFIGURATION, handleGetConfiguration);
|
||||
ipcMain.handle(GET_LOCAL_CONFIGURATION, handleGetLocalConfiguration);
|
||||
ipcMain.handle(UPDATE_TEAMS, handleUpdateTeams);
|
||||
ipcMain.on(UPDATE_CONFIGURATION, updateConfiguration);
|
||||
|
||||
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_TABS_FOR_SERVER, handleGetOrderedTabsForServer);
|
||||
}
|
||||
|
||||
function initializeAfterAppReady() {
|
||||
updateServerInfos(Config.teams);
|
||||
async function initializeAfterAppReady() {
|
||||
ServerManager.reloadFromConfig();
|
||||
updateServerInfos(ServerManager.getAllServers());
|
||||
ServerManager.on(SERVERS_URL_MODIFIED, (serverIds?: string[]) => {
|
||||
if (serverIds && serverIds.length) {
|
||||
updateServerInfos(serverIds.map((srvId) => ServerManager.getServer(srvId)!));
|
||||
}
|
||||
});
|
||||
|
||||
app.setAppUserModelId('Mattermost.Desktop'); // Use explicit AppUserModelID
|
||||
const defaultSession = session.defaultSession;
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Config from 'common/config';
|
||||
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';
|
||||
@@ -26,6 +26,17 @@ jest.mock('common/tabs/TabView', () => ({
|
||||
getDefaultConfigTeamFromTeam: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/notifications', () => ({}));
|
||||
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(),
|
||||
}));
|
||||
jest.mock('main/utils', () => ({
|
||||
getLocalPreload: jest.fn(),
|
||||
getLocalURLString: jest.fn(),
|
||||
@@ -43,9 +54,6 @@ jest.mock('main/windows/mainWindow', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('./app', () => ({}));
|
||||
jest.mock('./utils', () => ({
|
||||
updateServerInfos: jest.fn(),
|
||||
}));
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
@@ -66,6 +74,7 @@ const tabs = [
|
||||
];
|
||||
const teams = [
|
||||
{
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
@@ -74,53 +83,46 @@ const teams = [
|
||||
|
||||
describe('main/app/intercom', () => {
|
||||
describe('handleCloseTab', () => {
|
||||
beforeEach(() => {
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
});
|
||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete Config.teams;
|
||||
});
|
||||
|
||||
it('should close the specified tab and switch to the next open tab', () => {
|
||||
handleCloseTab(null, 'server-1', 'tab-3');
|
||||
expect(WindowManager.switchTab).toBeCalledWith('server-1', 'tab-2');
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === 'tab-3').isOpen).toBe(false);
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
});
|
||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete Config.teams;
|
||||
});
|
||||
|
||||
it('should open the specified tab', () => {
|
||||
handleOpenTab(null, 'server-1', 'tab-1');
|
||||
expect(WindowManager.switchTab).toBeCalledWith('server-1', 'tab-1');
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === 'tab-1').isOpen).toBe(true);
|
||||
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({});
|
||||
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
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;
|
||||
});
|
||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||
ServerManager.hasServers.mockReturnValue(Boolean(teamsCopy.length));
|
||||
|
||||
getDefaultConfigTeamFromTeam.mockImplementation((team) => ({
|
||||
...team,
|
||||
@@ -128,10 +130,6 @@ describe('main/app/intercom', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete Config.teams;
|
||||
});
|
||||
|
||||
it('should add new team to the config', async () => {
|
||||
const promise = Promise.resolve({
|
||||
name: 'new-team',
|
||||
@@ -141,29 +139,42 @@ describe('main/app/intercom', () => {
|
||||
|
||||
handleNewServerModal();
|
||||
await promise;
|
||||
expect(Config.teams).toContainEqual(expect.objectContaining({
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
tabs,
|
||||
}));
|
||||
expect(WindowManager.switchServer).toBeCalledWith('new-team', true);
|
||||
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({});
|
||||
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||
ServerManager.getServer.mockImplementation((id) => {
|
||||
if (id !== teamsCopy[0].id) {
|
||||
return undefined;
|
||||
}
|
||||
return {...teamsCopy[0], toMattermostTeam: jest.fn()};
|
||||
});
|
||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete Config.teams;
|
||||
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', () => {
|
||||
@@ -180,12 +191,14 @@ describe('main/app/intercom', () => {
|
||||
|
||||
handleEditServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(Config.teams).not.toContainEqual(expect.objectContaining({
|
||||
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
}));
|
||||
expect(Config.teams).toContainEqual(expect.objectContaining({
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
tabs,
|
||||
@@ -194,19 +207,24 @@ describe('main/app/intercom', () => {
|
||||
});
|
||||
|
||||
describe('handleRemoveServerModal', () => {
|
||||
let teamsCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||
ServerManager.getServer.mockImplementation((id) => {
|
||||
if (id !== teamsCopy[0].id) {
|
||||
return undefined;
|
||||
}
|
||||
return teamsCopy[0];
|
||||
});
|
||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete Config.teams;
|
||||
ServerManager.removeServer.mockImplementation(() => {
|
||||
teamsCopy = [];
|
||||
});
|
||||
ServerManager.getAllServers.mockReturnValue(teamsCopy);
|
||||
});
|
||||
|
||||
it('should remove the existing team', async () => {
|
||||
@@ -215,7 +233,8 @@ describe('main/app/intercom', () => {
|
||||
|
||||
handleRemoveServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(Config.teams).not.toContainEqual(expect.objectContaining({
|
||||
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
@@ -226,7 +245,8 @@ describe('main/app/intercom', () => {
|
||||
const promise = Promise.resolve(false);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
expect(Config.teams).toContainEqual(expect.objectContaining({
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
@@ -234,7 +254,8 @@ describe('main/app/intercom', () => {
|
||||
|
||||
handleRemoveServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(Config.teams).toContainEqual(expect.objectContaining({
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
@@ -248,10 +269,8 @@ describe('main/app/intercom', () => {
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
});
|
||||
Config.teams = JSON.parse(JSON.stringify([]));
|
||||
ServerManager.getAllServers.mockReturnValue([]);
|
||||
ServerManager.hasServers.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('should show welcomeScreen modal', async () => {
|
||||
@@ -270,18 +289,7 @@ describe('main/app/intercom', () => {
|
||||
MainWindow.get.mockReturnValue({
|
||||
isVisible: () => true,
|
||||
});
|
||||
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
});
|
||||
Config.registryConfigData = {
|
||||
teams: JSON.parse(JSON.stringify([{
|
||||
name: 'test-team',
|
||||
order: 0,
|
||||
url: 'https://someurl.here',
|
||||
}])),
|
||||
};
|
||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||
ServerManager.hasServers.mockReturnValue(true);
|
||||
|
||||
handleMainWindowIsShown();
|
||||
expect(ModalManager.addModal).not.toHaveBeenCalled();
|
||||
|
@@ -3,33 +3,24 @@
|
||||
|
||||
import {app, dialog, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
|
||||
|
||||
import {Team, TeamWithIndex} from 'types/config';
|
||||
import {Team, MattermostTeam} from 'types/config';
|
||||
import {MentionData} from 'types/notification';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||
import {ping} from 'common/utils/requests';
|
||||
|
||||
import {displayMention} from 'main/notifications';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {handleAppBeforeQuit} from './app';
|
||||
import {updateServerInfos} from './utils';
|
||||
|
||||
const log = new Logger('App.Intercom');
|
||||
|
||||
export function handleReloadConfig() {
|
||||
log.debug('handleReloadConfig');
|
||||
|
||||
Config.reload();
|
||||
ViewManager.reloadConfiguration();
|
||||
}
|
||||
|
||||
export function handleAppVersion() {
|
||||
return {
|
||||
name: app.getName(),
|
||||
@@ -44,52 +35,50 @@ export function handleQuit(e: IpcMainEvent, reason: string, stack: string) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
export function handleSwitchServer(event: IpcMainEvent, serverName: string) {
|
||||
log.silly('handleSwitchServer', serverName);
|
||||
WindowManager.switchServer(serverName);
|
||||
export function handleSwitchServer(event: IpcMainEvent, serverId: string) {
|
||||
log.silly('handleSwitchServer', serverId);
|
||||
WindowManager.switchServer(serverId);
|
||||
}
|
||||
|
||||
export function handleSwitchTab(event: IpcMainEvent, serverName: string, tabName: string) {
|
||||
log.silly('handleSwitchTab', {serverName, tabName});
|
||||
WindowManager.switchTab(serverName, tabName);
|
||||
export function handleSwitchTab(event: IpcMainEvent, tabId: string) {
|
||||
log.silly('handleSwitchTab', {tabId});
|
||||
WindowManager.switchTab(tabId);
|
||||
}
|
||||
|
||||
export function handleCloseTab(event: IpcMainEvent, serverName: string, tabName: string) {
|
||||
log.debug('handleCloseTab', {serverName, tabName});
|
||||
export function handleCloseTab(event: IpcMainEvent, tabId: string) {
|
||||
log.debug('handleCloseTab', {tabId});
|
||||
|
||||
const teams = Config.teams;
|
||||
teams.forEach((team) => {
|
||||
if (team.name === serverName) {
|
||||
team.tabs.forEach((tab) => {
|
||||
if (tab.name === tabName) {
|
||||
tab.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
const nextTab = teams.find((team) => team.name === serverName)!.tabs.filter((tab) => tab.isOpen)[0].name;
|
||||
WindowManager.switchTab(serverName, nextTab);
|
||||
Config.setServers(teams);
|
||||
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, serverName: string, tabName: string) {
|
||||
log.debug('handleOpenTab', {serverName, tabName});
|
||||
export function handleOpenTab(event: IpcMainEvent, tabId: string) {
|
||||
log.debug('handleOpenTab', {tabId});
|
||||
|
||||
const teams = Config.teams;
|
||||
teams.forEach((team) => {
|
||||
if (team.name === serverName) {
|
||||
team.tabs.forEach((tab) => {
|
||||
if (tab.name === tabName) {
|
||||
tab.isOpen = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
WindowManager.switchTab(serverName, tabName);
|
||||
Config.setServers(teams);
|
||||
ServerManager.setTabIsOpen(tabId, true);
|
||||
WindowManager.switchTab(tabId);
|
||||
}
|
||||
|
||||
export function handleShowOnboardingScreens(showWelcomeScreen: boolean, showNewServerModal: boolean, mainWindowIsVisible: boolean) {
|
||||
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});
|
||||
|
||||
if (showWelcomeScreen) {
|
||||
@@ -117,8 +106,8 @@ export function handleMainWindowIsShown() {
|
||||
// eslint-disable-next-line no-undef
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const showWelcomeScreen = () => !(Boolean(__SKIP_ONBOARDING_SCREENS__) || Config.teams.length);
|
||||
const showNewServerModal = () => Config.teams.length === 0;
|
||||
const showWelcomeScreen = () => !(Boolean(__SKIP_ONBOARDING_SCREENS__) || ServerManager.hasServers());
|
||||
const showNewServerModal = () => !ServerManager.hasServers();
|
||||
|
||||
/**
|
||||
* The 2 lines above need to be functions, otherwise the mainWindow.once() callback from previous
|
||||
@@ -127,7 +116,7 @@ export function handleMainWindowIsShown() {
|
||||
|
||||
const mainWindow = MainWindow.get();
|
||||
|
||||
log.debug('handleMainWindowIsShown', {configTeams: Config.teams, showWelcomeScreen, showNewServerModal, mainWindow: Boolean(mainWindow)});
|
||||
log.debug('handleMainWindowIsShown', {showWelcomeScreen, showNewServerModal, mainWindow: Boolean(mainWindow)});
|
||||
if (mainWindow?.isVisible()) {
|
||||
handleShowOnboardingScreens(showWelcomeScreen(), showNewServerModal(), true);
|
||||
} else {
|
||||
@@ -148,16 +137,11 @@ export function handleNewServerModal() {
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const modalPromise = ModalManager.addModal<TeamWithIndex[], Team>('newServer', html, preload, Config.teams.map((team, index) => ({...team, index})), mainWindow, Config.teams.length === 0);
|
||||
const modalPromise = ModalManager.addModal<MattermostTeam[], Team>('newServer', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
|
||||
if (modalPromise) {
|
||||
modalPromise.then((data) => {
|
||||
const teams = Config.teams;
|
||||
const order = teams.length;
|
||||
const newTeam = getDefaultConfigTeamFromTeam({...data, order});
|
||||
teams.push(newTeam);
|
||||
Config.setServers(teams);
|
||||
updateServerInfos([newTeam]);
|
||||
WindowManager.switchServer(newTeam.name, true);
|
||||
const newTeam = ServerManager.addServer(data);
|
||||
WindowManager.switchServer(newTeam.id, true);
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
@@ -169,8 +153,8 @@ export function handleNewServerModal() {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleEditServerModal(e: IpcMainEvent, name: string) {
|
||||
log.debug('handleEditServerModal', name);
|
||||
export function handleEditServerModal(e: IpcMainEvent, id: string) {
|
||||
log.debug('handleEditServerModal', id);
|
||||
|
||||
const html = getLocalURLString('editServer.html');
|
||||
|
||||
@@ -180,27 +164,21 @@ export function handleEditServerModal(e: IpcMainEvent, name: string) {
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const serverIndex = Config.teams.findIndex((team) => team.name === name);
|
||||
if (serverIndex < 0) {
|
||||
const server = ServerManager.getServer(id);
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
const modalPromise = ModalManager.addModal<{currentTeams: TeamWithIndex[]; team: TeamWithIndex}, Team>(
|
||||
const modalPromise = ModalManager.addModal<{currentTeams: MattermostTeam[]; team: MattermostTeam}, Team>(
|
||||
'editServer',
|
||||
html,
|
||||
preload,
|
||||
{
|
||||
currentTeams: Config.teams.map((team, index) => ({...team, index})),
|
||||
team: {...Config.teams[serverIndex], index: serverIndex},
|
||||
currentTeams: ServerManager.getAllServers().map((team) => team.toMattermostTeam()),
|
||||
team: server.toMattermostTeam(),
|
||||
},
|
||||
mainWindow);
|
||||
if (modalPromise) {
|
||||
modalPromise.then((data) => {
|
||||
const teams = Config.teams;
|
||||
teams[serverIndex].name = data.name;
|
||||
teams[serverIndex].url = data.url;
|
||||
Config.setServers(teams);
|
||||
updateServerInfos([teams[serverIndex]]);
|
||||
}).catch((e) => {
|
||||
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}`);
|
||||
@@ -211,34 +189,26 @@ export function handleEditServerModal(e: IpcMainEvent, name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleRemoveServerModal(e: IpcMainEvent, name: string) {
|
||||
log.debug('handleRemoveServerModal', name);
|
||||
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, name, mainWindow);
|
||||
const modalPromise = ModalManager.addModal<string, boolean>('removeServer', html, preload, server.name, mainWindow);
|
||||
if (modalPromise) {
|
||||
modalPromise.then((remove) => {
|
||||
if (remove) {
|
||||
const teams = Config.teams;
|
||||
const removedTeam = teams.findIndex((team) => team.name === name);
|
||||
if (removedTeam < 0) {
|
||||
return;
|
||||
}
|
||||
const removedOrder = teams[removedTeam].order;
|
||||
teams.splice(removedTeam, 1);
|
||||
teams.forEach((value) => {
|
||||
if (value.order > removedOrder) {
|
||||
value.order--;
|
||||
}
|
||||
});
|
||||
Config.setServers(teams);
|
||||
ServerManager.removeServer(server.id);
|
||||
}
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
@@ -262,16 +232,11 @@ export function handleWelcomeScreenModal() {
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const modalPromise = ModalManager.addModal<TeamWithIndex[], Team>('welcomeScreen', html, preload, Config.teams.map((team, index) => ({...team, index})), mainWindow, Config.teams.length === 0);
|
||||
const modalPromise = ModalManager.addModal<MattermostTeam[], MattermostTeam>('welcomeScreen', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
|
||||
if (modalPromise) {
|
||||
modalPromise.then((data) => {
|
||||
const teams = Config.teams;
|
||||
const order = teams.length;
|
||||
const newTeam = getDefaultConfigTeamFromTeam({...data, order});
|
||||
teams.push(newTeam);
|
||||
Config.setServers(teams);
|
||||
updateServerInfos([newTeam]);
|
||||
WindowManager.switchServer(newTeam.name, true);
|
||||
const newTeam = ServerManager.addServer(data);
|
||||
WindowManager.switchServer(newTeam.id, true);
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
@@ -314,17 +279,10 @@ export async function handleSelectDownload(event: IpcMainInvokeEvent, startFrom:
|
||||
return result.filePaths[0];
|
||||
}
|
||||
|
||||
export function handleUpdateLastActive(event: IpcMainEvent, serverName: string, viewName: string) {
|
||||
log.debug('handleUpdateLastActive', {serverName, viewName});
|
||||
export function handleUpdateLastActive(event: IpcMainEvent, tabId: string) {
|
||||
log.debug('handleUpdateLastActive', {tabId});
|
||||
|
||||
const teams = Config.teams;
|
||||
teams.forEach((team) => {
|
||||
if (team.name === serverName) {
|
||||
const viewOrder = team?.tabs.find((tab) => tab.name === viewName)?.order || 0;
|
||||
team.lastActiveTab = viewOrder;
|
||||
}
|
||||
});
|
||||
Config.setServers(teams, teams.find((team) => team.name === serverName)?.order || 0);
|
||||
ServerManager.updateLastActive(tabId);
|
||||
}
|
||||
|
||||
export function handlePingDomain(event: IpcMainInvokeEvent, url: string): Promise<string> {
|
||||
|
@@ -5,15 +5,11 @@ import fs from 'fs-extra';
|
||||
|
||||
import {dialog, screen} from 'electron';
|
||||
|
||||
import Config from 'common/config';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import {TAB_MESSAGING, TAB_FOCALBOARD, TAB_PLAYBOOKS} from 'common/tabs/TabView';
|
||||
import Utils from 'common/utils/util';
|
||||
|
||||
import {updatePaths} from 'main/constants';
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
|
||||
import {getDeeplinkingURL, updateServerInfos, resizeScreen, migrateMacAppStore} from './utils';
|
||||
import {getDeeplinkingURL, resizeScreen, migrateMacAppStore} from './utils';
|
||||
|
||||
jest.mock('fs-extra', () => ({
|
||||
readFileSync: jest.fn(),
|
||||
@@ -43,9 +39,6 @@ jest.mock('common/config', () => ({
|
||||
setServers: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/JsonFileManager');
|
||||
jest.mock('common/utils/util', () => ({
|
||||
isVersionGreaterThanOrEqualTo: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('main/autoUpdater', () => ({}));
|
||||
jest.mock('main/constants', () => ({
|
||||
@@ -56,9 +49,6 @@ jest.mock('main/i18nManager', () => ({
|
||||
}));
|
||||
jest.mock('main/menus/app', () => ({}));
|
||||
jest.mock('main/menus/tray', () => ({}));
|
||||
jest.mock('main/server/serverInfo', () => ({
|
||||
ServerInfo: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/tray/tray', () => ({}));
|
||||
jest.mock('main/views/viewManager', () => ({}));
|
||||
jest.mock('main/windows/mainWindow', () => ({}));
|
||||
@@ -69,107 +59,6 @@ jest.mock('./initialize', () => ({
|
||||
}));
|
||||
|
||||
describe('main/app/utils', () => {
|
||||
describe('updateServerInfos', () => {
|
||||
const tabs = [
|
||||
{
|
||||
name: TAB_MESSAGING,
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: TAB_FOCALBOARD,
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
name: TAB_PLAYBOOKS,
|
||||
order: 1,
|
||||
},
|
||||
];
|
||||
const teams = [
|
||||
{
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
Utils.isVersionGreaterThanOrEqualTo.mockImplementation((version) => version === '6.0.0');
|
||||
Config.setServers.mockImplementation((value) => {
|
||||
Config.teams = value;
|
||||
});
|
||||
const teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||
Config.teams = teamsCopy;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete Config.teams;
|
||||
});
|
||||
|
||||
it('should open all tabs', async () => {
|
||||
ServerInfo.mockReturnValue({promise: {
|
||||
name: 'server-1',
|
||||
siteURL: 'http://server-1.com',
|
||||
serverVersion: '6.0.0',
|
||||
hasPlaybooks: true,
|
||||
hasFocalboard: true,
|
||||
}});
|
||||
|
||||
updateServerInfos(Config.teams);
|
||||
await new Promise(setImmediate); // workaround since Promise.all seems to not let me wait here
|
||||
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === TAB_PLAYBOOKS).isOpen).toBe(true);
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === TAB_FOCALBOARD).isOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should open only playbooks', async () => {
|
||||
ServerInfo.mockReturnValue({promise: {
|
||||
name: 'server-1',
|
||||
siteURL: 'http://server-1.com',
|
||||
serverVersion: '6.0.0',
|
||||
hasPlaybooks: true,
|
||||
hasFocalboard: false,
|
||||
}});
|
||||
|
||||
updateServerInfos(Config.teams);
|
||||
await new Promise(setImmediate); // workaround since Promise.all seems to not let me wait here
|
||||
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === TAB_PLAYBOOKS).isOpen).toBe(true);
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === TAB_FOCALBOARD).isOpen).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should open none when server version is too old', async () => {
|
||||
ServerInfo.mockReturnValue({promise: {
|
||||
name: 'server-1',
|
||||
siteURL: 'http://server-1.com',
|
||||
serverVersion: '5.0.0',
|
||||
hasPlaybooks: true,
|
||||
hasFocalboard: true,
|
||||
}});
|
||||
|
||||
updateServerInfos(Config.teams);
|
||||
await new Promise(setImmediate); // workaround since Promise.all seems to not let me wait here
|
||||
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === TAB_PLAYBOOKS).isOpen).toBeUndefined();
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').tabs.find((tab) => tab.name === TAB_FOCALBOARD).isOpen).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should update server URL using site URL', async () => {
|
||||
ServerInfo.mockReturnValue({promise: {
|
||||
name: 'server-1',
|
||||
siteURL: 'http://server-2.com',
|
||||
serverVersion: '6.0.0',
|
||||
hasPlaybooks: true,
|
||||
hasFocalboard: true,
|
||||
}});
|
||||
|
||||
updateServerInfos(Config.teams);
|
||||
await new Promise(setImmediate); // workaround since Promise.all seems to not let me wait here
|
||||
|
||||
expect(Config.teams.find((team) => team.name === 'server-1').url).toBe('http://server-2.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDeeplinkingURL', () => {
|
||||
it('should return undefined if deeplinking URL is not last argument', () => {
|
||||
expect(getDeeplinkingURL(['mattermost', 'mattermost://server-1.com', '--oops'])).toBeUndefined();
|
||||
|
@@ -7,17 +7,16 @@ import fs from 'fs-extra';
|
||||
|
||||
import {app, BrowserWindow, Menu, Rectangle, Session, session, dialog, nativeImage, screen} from 'electron';
|
||||
|
||||
import {MigrationInfo, TeamWithTabs} from 'types/config';
|
||||
import {MigrationInfo} from 'types/config';
|
||||
import {RemoteInfo} from 'types/server';
|
||||
import {Boundaries} from 'types/utils';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS} from 'common/tabs/TabView';
|
||||
import urlUtils from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
import {APP_MENU_WILL_CLOSE} from 'common/communication';
|
||||
|
||||
import updateManager from 'main/autoUpdater';
|
||||
@@ -52,59 +51,6 @@ export function updateSpellCheckerLocales() {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateServerInfos(teams: TeamWithTabs[]) {
|
||||
log.silly('app.utils.updateServerInfos');
|
||||
const serverInfos: Array<Promise<RemoteInfo | string | undefined>> = [];
|
||||
teams.forEach((team) => {
|
||||
const serverInfo = new ServerInfo(new MattermostServer(team));
|
||||
serverInfos.push(serverInfo.promise);
|
||||
});
|
||||
Promise.all(serverInfos).then((data: Array<RemoteInfo | string | undefined>) => {
|
||||
const teams = Config.teams;
|
||||
let hasUpdates = false;
|
||||
teams.forEach((team) => {
|
||||
hasUpdates = hasUpdates || updateServerURL(data, team);
|
||||
hasUpdates = hasUpdates || openExtraTabs(data, team);
|
||||
});
|
||||
if (hasUpdates) {
|
||||
Config.setServers(teams);
|
||||
}
|
||||
}).catch((reason: any) => {
|
||||
log.error('Error getting server infos', reason);
|
||||
});
|
||||
}
|
||||
|
||||
function updateServerURL(data: Array<RemoteInfo | string | undefined>, team: TeamWithTabs) {
|
||||
const remoteInfo = data.find((info) => info && typeof info !== 'string' && info.name === team.name) as RemoteInfo;
|
||||
if (remoteInfo && remoteInfo.siteURL && team.url !== remoteInfo.siteURL) {
|
||||
team.url = remoteInfo.siteURL;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function openExtraTabs(data: Array<RemoteInfo | string | undefined>, team: TeamWithTabs) {
|
||||
let hasUpdates = false;
|
||||
const remoteInfo = data.find((info) => info && typeof info !== 'string' && info.name === team.name) as RemoteInfo;
|
||||
if (remoteInfo) {
|
||||
team.tabs.forEach((tab) => {
|
||||
if (tab.name !== TAB_MESSAGING && remoteInfo.serverVersion && Utils.isVersionGreaterThanOrEqualTo(remoteInfo.serverVersion, '6.0.0')) {
|
||||
if (tab.name === TAB_PLAYBOOKS && remoteInfo.hasPlaybooks && typeof tab.isOpen === 'undefined') {
|
||||
log.info(`opening ${team.name}___${tab.name} on hasPlaybooks`);
|
||||
tab.isOpen = true;
|
||||
hasUpdates = true;
|
||||
}
|
||||
if (tab.name === TAB_FOCALBOARD && remoteInfo.hasFocalboard && typeof tab.isOpen === 'undefined') {
|
||||
log.info(`opening ${team.name}___${tab.name} on hasFocalboard`);
|
||||
tab.isOpen = true;
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return hasUpdates;
|
||||
}
|
||||
|
||||
export function handleUpdateMenuEvent() {
|
||||
log.debug('handleUpdateMenuEvent');
|
||||
|
||||
@@ -117,7 +63,7 @@ export function handleUpdateMenuEvent() {
|
||||
|
||||
// set up context menu for tray icon
|
||||
if (shouldShowTrayIcon()) {
|
||||
const tMenu = createTrayMenu(Config.data!);
|
||||
const tMenu = createTrayMenu();
|
||||
setTrayMenu(tMenu);
|
||||
}
|
||||
}
|
||||
@@ -291,3 +237,18 @@ export function migrateMacAppStore() {
|
||||
log.error('MAS: An error occurred importing the existing configuration', e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateServerInfos(servers: MattermostServer[]) {
|
||||
const map: Map<string, RemoteInfo> = new Map();
|
||||
await Promise.all(servers.map((srv) => {
|
||||
const serverInfo = new ServerInfo(srv);
|
||||
return serverInfo.fetchRemoteInfo().
|
||||
then((data) => {
|
||||
map.set(srv.id, data);
|
||||
}).
|
||||
catch((error) => {
|
||||
log.warn('Could not get server info for', srv.name, error);
|
||||
});
|
||||
}));
|
||||
ServerManager.updateRemoteInfos(map);
|
||||
}
|
||||
|
@@ -4,14 +4,11 @@
|
||||
import events from 'events';
|
||||
import {ipcMain} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {UPDATE_MENTIONS, UPDATE_TRAY, UPDATE_BADGE, SESSION_EXPIRED, UPDATE_DROPDOWN_MENTIONS} from 'common/communication';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import WindowManager from './windows/windowManager';
|
||||
|
||||
const log = new Logger('AppState');
|
||||
|
||||
const status = {
|
||||
unreads: new Map<string, boolean>(),
|
||||
mentions: new Map<string, number>(),
|
||||
@@ -19,13 +16,13 @@ const status = {
|
||||
emitter: new events.EventEmitter(),
|
||||
};
|
||||
|
||||
const emitMentions = (serverName: string) => {
|
||||
const newMentions = getMentions(serverName);
|
||||
const newUnreads = getUnreads(serverName);
|
||||
const isExpired = getIsExpired(serverName);
|
||||
const emitMentions = (viewId: string) => {
|
||||
const newMentions = getMentions(viewId);
|
||||
const newUnreads = getUnreads(viewId);
|
||||
const isExpired = getIsExpired(viewId);
|
||||
|
||||
WindowManager.sendToRenderer(UPDATE_MENTIONS, serverName, newMentions, newUnreads, isExpired);
|
||||
log.silly('emitMentions', {serverName, isExpired, newMentions, newUnreads});
|
||||
WindowManager.sendToRenderer(UPDATE_MENTIONS, viewId, newMentions, newUnreads, isExpired);
|
||||
ServerManager.getViewLog(viewId, 'AppState').silly('emitMentions', {isExpired, newMentions, newUnreads});
|
||||
emitStatus();
|
||||
};
|
||||
|
||||
@@ -50,17 +47,17 @@ const emitStatus = () => {
|
||||
emitDropdown(status.expired, status.mentions, status.unreads);
|
||||
};
|
||||
|
||||
export const updateMentions = (serverName: string, mentions: number, unreads?: boolean) => {
|
||||
export const updateMentions = (viewId: string, mentions: number, unreads?: boolean) => {
|
||||
if (typeof unreads !== 'undefined') {
|
||||
status.unreads.set(serverName, Boolean(unreads));
|
||||
status.unreads.set(viewId, Boolean(unreads));
|
||||
}
|
||||
status.mentions.set(serverName, mentions || 0);
|
||||
emitMentions(serverName);
|
||||
status.mentions.set(viewId, mentions || 0);
|
||||
emitMentions(viewId);
|
||||
};
|
||||
|
||||
export const updateUnreads = (serverName: string, unreads: boolean) => {
|
||||
status.unreads.set(serverName, Boolean(unreads));
|
||||
emitMentions(serverName);
|
||||
export const updateUnreads = (viewId: string, unreads: boolean) => {
|
||||
status.unreads.set(viewId, Boolean(unreads));
|
||||
emitMentions(viewId);
|
||||
};
|
||||
|
||||
export const updateBadge = () => {
|
||||
@@ -70,16 +67,16 @@ export const updateBadge = () => {
|
||||
emitBadge(expired, mentions, unreads);
|
||||
};
|
||||
|
||||
const getUnreads = (serverName: string) => {
|
||||
return status.unreads.get(serverName) || false;
|
||||
const getUnreads = (viewId: string) => {
|
||||
return status.unreads.get(viewId) || false;
|
||||
};
|
||||
|
||||
const getMentions = (serverName: string) => {
|
||||
return status.mentions.get(serverName) || 0; // this might be undefined as a way to tell that we don't know as it might need to login still.
|
||||
const getMentions = (viewId: string) => {
|
||||
return status.mentions.get(viewId) || 0; // this might be undefined as a way to tell that we don't know as it might need to login still.
|
||||
};
|
||||
|
||||
const getIsExpired = (serverName: string) => {
|
||||
return status.expired.get(serverName) || false;
|
||||
const getIsExpired = (viewId: string) => {
|
||||
return status.expired.get(viewId) || false;
|
||||
};
|
||||
|
||||
const totalMentions = () => {
|
||||
@@ -111,19 +108,19 @@ const anyExpired = () => {
|
||||
// add any other event emitter methods if needed
|
||||
export const on = status.emitter.on.bind(status.emitter);
|
||||
|
||||
const setSessionExpired = (serverName: string, expired: boolean) => {
|
||||
const setSessionExpired = (viewId: string, expired: boolean) => {
|
||||
const isExpired = Boolean(expired);
|
||||
const old = status.expired.get(serverName);
|
||||
status.expired.set(serverName, isExpired);
|
||||
const old = status.expired.get(viewId);
|
||||
status.expired.set(viewId, isExpired);
|
||||
if (typeof old !== 'undefined' && old !== isExpired) {
|
||||
emitTray();
|
||||
}
|
||||
emitMentions(serverName);
|
||||
emitMentions(viewId);
|
||||
};
|
||||
|
||||
ipcMain.on(SESSION_EXPIRED, (event, isExpired, serverName) => {
|
||||
ipcMain.on(SESSION_EXPIRED, (event, isExpired, viewId) => {
|
||||
if (isExpired) {
|
||||
log.debug('SESSION_EXPIRED', {isExpired, serverName});
|
||||
ServerManager.getViewLog(viewId, 'AppState').debug('SESSION_EXPIRED', isExpired);
|
||||
}
|
||||
setSessionExpired(serverName, isExpired);
|
||||
setSessionExpired(viewId, isExpired);
|
||||
});
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import Config from 'common/config';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
@@ -15,7 +15,7 @@ const stepDescriptiveName = 'serverConnectivity';
|
||||
|
||||
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
|
||||
try {
|
||||
const teams = Config.teams || [];
|
||||
const teams = ServerManager.getAllServers();
|
||||
|
||||
await Promise.all(teams.map(async (team) => {
|
||||
logger.debug('Pinging server: ', team.url);
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {createTemplate} from './app';
|
||||
|
||||
@@ -49,13 +49,18 @@ jest.mock('macos-notification-state', () => ({
|
||||
jest.mock('main/i18nManager', () => ({
|
||||
localizeMessage: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
hasServers: jest.fn(),
|
||||
getCurrentServer: jest.fn(),
|
||||
getOrderedServers: jest.fn(),
|
||||
getOrderedTabsForServer: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/diagnostics', () => ({}));
|
||||
jest.mock('main/downloadsManager', () => ({
|
||||
hasDownloads: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/viewManager', () => ({}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
getCurrentTeamName: jest.fn(),
|
||||
sendToRenderer: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/settingsWindow', () => ({}));
|
||||
@@ -66,54 +71,42 @@ jest.mock('common/tabs/TabView', () => ({
|
||||
describe('main/menus/app', () => {
|
||||
const config = {
|
||||
enableServerManagement: true,
|
||||
teams: [{
|
||||
name: 'example',
|
||||
url: 'http://example.com',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'TAB_MESSAGING',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_FOCALBOARD',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_PLAYBOOKS',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 0,
|
||||
}, {
|
||||
name: 'github',
|
||||
url: 'https://github.com/',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'TAB_MESSAGING',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_FOCALBOARD',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_PLAYBOOKS',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 0,
|
||||
}],
|
||||
helpLink: 'http://link-to-help.site.com',
|
||||
};
|
||||
const servers = [
|
||||
{
|
||||
id: 'server-1',
|
||||
name: 'example',
|
||||
url: 'http://example.com',
|
||||
},
|
||||
{
|
||||
id: 'server-2',
|
||||
name: 'github',
|
||||
url: 'https:/ /github.com/',
|
||||
},
|
||||
];
|
||||
const tabs = [
|
||||
{
|
||||
id: 'tab-1',
|
||||
name: 'TAB_MESSAGING',
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
id: 'tab-2',
|
||||
name: 'TAB_FOCALBOARD',
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
id: 'tab-3',
|
||||
name: 'TAB_PLAYBOOKS',
|
||||
isOpen: true,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
ServerManager.getCurrentServer.mockReturnValue(servers[0]);
|
||||
ServerManager.getOrderedServers.mockReturnValue(servers);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(tabs);
|
||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||
});
|
||||
|
||||
@@ -192,6 +185,7 @@ describe('main/menus/app', () => {
|
||||
return id;
|
||||
}
|
||||
});
|
||||
ServerManager.hasServers.mockReturnValue(true);
|
||||
const menu = createTemplate(config);
|
||||
const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File');
|
||||
const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server');
|
||||
@@ -209,6 +203,7 @@ describe('main/menus/app', () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
ServerManager.hasServers.mockReturnValue(true);
|
||||
const modifiedConfig = {
|
||||
...config,
|
||||
enableServerManagement: false,
|
||||
@@ -230,11 +225,8 @@ describe('main/menus/app', () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const modifiedConfig = {
|
||||
...config,
|
||||
teams: [],
|
||||
};
|
||||
const menu = createTemplate(modifiedConfig);
|
||||
ServerManager.hasServers.mockReturnValue(false);
|
||||
const menu = createTemplate(config);
|
||||
const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File');
|
||||
const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server');
|
||||
expect(signInOption).toBe(undefined);
|
||||
@@ -247,31 +239,27 @@ describe('main/menus/app', () => {
|
||||
}
|
||||
return id;
|
||||
});
|
||||
const modifiedConfig = {
|
||||
teams: [...Array(15).keys()].map((key) => ({
|
||||
name: `server-${key}`,
|
||||
url: `http://server-${key}.com`,
|
||||
order: (key + 5) % 15,
|
||||
lastActiveTab: 0,
|
||||
tab: [
|
||||
{
|
||||
name: 'TAB_MESSAGING',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
const menu = createTemplate(modifiedConfig);
|
||||
const modifiedServers = [...Array(15).keys()].map((key) => ({
|
||||
id: `server-${key}`,
|
||||
name: `server-${key}`,
|
||||
url: `http://server-${key}.com`,
|
||||
}));
|
||||
const modifiedTabs = [
|
||||
{
|
||||
id: 'tab-1',
|
||||
type: 'TAB_MESSAGING',
|
||||
isOpen: true,
|
||||
},
|
||||
];
|
||||
ServerManager.getOrderedServers.mockReturnValue(modifiedServers);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedTabs);
|
||||
const menu = createTemplate(config);
|
||||
const windowMenu = menu.find((item) => item.label === '&Window');
|
||||
for (let i = 10; i < 15; i++) {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === `server-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === `server-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 4; i < 10; i++) {
|
||||
for (let i = 9; i < 15; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === `server-${i}`);
|
||||
expect(menuItem).toBe(undefined);
|
||||
}
|
||||
@@ -287,31 +275,21 @@ describe('main/menus/app', () => {
|
||||
}
|
||||
return id;
|
||||
});
|
||||
WindowManager.getCurrentTeamName.mockImplementation(() => config.teams[0].name);
|
||||
ServerManager.getCurrentServer.mockImplementation(() => ({id: servers[0].id}));
|
||||
|
||||
const modifiedConfig = {
|
||||
teams: [
|
||||
{
|
||||
...config.teams[0],
|
||||
tabs: [...Array(15).keys()].map((key) => ({
|
||||
name: `tab-${key}`,
|
||||
isOpen: true,
|
||||
order: (key + 5) % 15,
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
const menu = createTemplate(modifiedConfig);
|
||||
const modifiedTabs = [...Array(15).keys()].map((key) => ({
|
||||
id: `tab-${key}`,
|
||||
type: `tab-${key}`,
|
||||
isOpen: true,
|
||||
}));
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedTabs);
|
||||
const menu = createTemplate(config);
|
||||
const windowMenu = menu.find((item) => item.label === '&Window');
|
||||
for (let i = 10; i < 15; i++) {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 4; i < 10; i++) {
|
||||
for (let i = 9; i < 15; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
|
||||
expect(menuItem).toBe(undefined);
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import {getTabDisplayName, TabType} from 'common/tabs/TabView';
|
||||
import {Config} from 'common/config';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import {UpdateManager} from 'main/autoUpdater';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
@@ -49,7 +50,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
},
|
||||
});
|
||||
|
||||
if (config.enableServerManagement === true && config.teams.length > 0) {
|
||||
if (config.enableServerManagement === true && ServerManager.hasServers()) {
|
||||
platformAppMenu.push({
|
||||
label: localizeMessage('main.menus.app.file.signInToAnotherServer', 'Sign in to Another Server'),
|
||||
click() {
|
||||
@@ -231,7 +232,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
}],
|
||||
});
|
||||
|
||||
const teams = config.teams || [];
|
||||
const teams = ServerManager.getOrderedServers();
|
||||
const windowMenu = {
|
||||
id: 'window',
|
||||
label: localizeMessage('main.menus.app.window', '&Window'),
|
||||
@@ -251,29 +252,29 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
label: isMac ? localizeMessage('main.menus.app.window.closeWindow', 'Close Window') : localizeMessage('main.menus.app.window.close', 'Close'),
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
}, separatorItem,
|
||||
...(config.teams.length ? [{
|
||||
...(ServerManager.hasServers() ? [{
|
||||
label: localizeMessage('main.menus.app.window.showServers', 'Show Servers'),
|
||||
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`,
|
||||
click() {
|
||||
ipcMain.emit(OPEN_TEAMS_DROPDOWN);
|
||||
},
|
||||
}] : []),
|
||||
...teams.sort((teamA, teamB) => teamA.order - teamB.order).slice(0, 9).map((team, i) => {
|
||||
...teams.slice(0, 9).map((team, i) => {
|
||||
const items = [];
|
||||
items.push({
|
||||
label: team.name,
|
||||
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+${i + 1}`,
|
||||
click() {
|
||||
WindowManager.switchServer(team.name);
|
||||
WindowManager.switchServer(team.id);
|
||||
},
|
||||
});
|
||||
if (WindowManager.getCurrentTeamName() === team.name) {
|
||||
team.tabs.filter((tab) => tab.isOpen).sort((teamA, teamB) => teamA.order - teamB.order).slice(0, 9).forEach((tab, i) => {
|
||||
if (ServerManager.getCurrentServer().id === team.id) {
|
||||
ServerManager.getOrderedTabsForServer(team.id).slice(0, 9).forEach((tab, i) => {
|
||||
items.push({
|
||||
label: ` ${localizeMessage(`common.tabs.${tab.name}`, getTabDisplayName(tab.name as TabType))}`,
|
||||
label: ` ${localizeMessage(`common.tabs.${tab.type}`, getTabDisplayName(tab.type as TabType))}`,
|
||||
accelerator: `CmdOrCtrl+${i + 1}`,
|
||||
click() {
|
||||
WindowManager.switchTab(team.name, tab.name);
|
||||
WindowManager.switchTab(tab.id);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@@ -3,41 +3,35 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {createTemplate} from './tray';
|
||||
|
||||
jest.mock('main/i18nManager', () => ({
|
||||
localizeMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
getOrderedServers: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('main/windows/settingsWindow', () => ({}));
|
||||
jest.mock('main/windows/windowManager', () => ({}));
|
||||
|
||||
describe('main/menus/tray', () => {
|
||||
it('should show the first 9 servers (using order)', () => {
|
||||
const config = {
|
||||
teams: [...Array(15).keys()].map((key) => ({
|
||||
name: `server-${key}`,
|
||||
url: `http://server-${key}.com`,
|
||||
order: (key + 5) % 15,
|
||||
lastActiveTab: 0,
|
||||
tab: [
|
||||
{
|
||||
name: 'TAB_MESSAGING',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
})),
|
||||
};
|
||||
const menu = createTemplate(config);
|
||||
for (let i = 10; i < 15; i++) {
|
||||
const servers = [...Array(15).keys()].map((key) => ({
|
||||
id: `server-${key}`,
|
||||
name: `server-${key}`,
|
||||
url: `http://server-${key}.com`,
|
||||
}));
|
||||
ServerManager.getOrderedServers.mockReturnValue(servers);
|
||||
const menu = createTemplate();
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const menuItem = menu.find((item) => item.label === `server-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const menuItem = menu.find((item) => item.label === `server-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 4; i < 10; i++) {
|
||||
for (let i = 9; i < 15; i++) {
|
||||
const menuItem = menu.find((item) => item.label === `server-${i}`);
|
||||
expect(menuItem).toBe(undefined);
|
||||
}
|
||||
|
@@ -4,20 +4,20 @@
|
||||
'use strict';
|
||||
|
||||
import {Menu, MenuItem, MenuItemConstructorOptions} from 'electron';
|
||||
import {CombinedConfig} from 'types/config';
|
||||
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
|
||||
export function createTemplate(config: CombinedConfig) {
|
||||
const teams = config.teams;
|
||||
export function createTemplate() {
|
||||
const teams = ServerManager.getOrderedServers();
|
||||
const template = [
|
||||
...teams.sort((teamA, teamB) => teamA.order - teamB.order).slice(0, 9).map((team) => {
|
||||
...teams.slice(0, 9).map((team) => {
|
||||
return {
|
||||
label: team.name.length > 50 ? `${team.name.slice(0, 50)}...` : team.name,
|
||||
click: () => {
|
||||
WindowManager.switchServer(team.name);
|
||||
WindowManager.switchServer(team.id);
|
||||
},
|
||||
};
|
||||
}), {
|
||||
@@ -36,7 +36,7 @@ export function createTemplate(config: CombinedConfig) {
|
||||
return template;
|
||||
}
|
||||
|
||||
export function createMenu(config: CombinedConfig) {
|
||||
export function createMenu() {
|
||||
// Electron is enforcing certain variables that it doesn't need
|
||||
return Menu.buildFromTemplate(createTemplate(config) as Array<MenuItemConstructorOptions | MenuItem>);
|
||||
return Menu.buildFromTemplate(createTemplate() as Array<MenuItemConstructorOptions | MenuItem>);
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state
|
||||
|
||||
import {PLAY_SOUND} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
@@ -249,7 +248,7 @@ describe('main/notifications', () => {
|
||||
);
|
||||
const mention = mentions.find((m) => m.body === 'mention_click_body');
|
||||
mention.value.click();
|
||||
expect(WindowManager.switchTab).toBeCalledWith('server_name', TAB_MESSAGING);
|
||||
expect(WindowManager.switchTab).toHaveBeenCalledWith('server_id');
|
||||
});
|
||||
|
||||
it('linux/windows - should not flash frame when config item is not set', () => {
|
||||
|
@@ -10,7 +10,6 @@ import {MentionData} from 'types/notification';
|
||||
import Config from 'common/config';
|
||||
import {PLAY_SOUND} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
import {TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
|
||||
import ViewManager from '../views/viewManager';
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
@@ -76,7 +75,7 @@ export function displayMention(title: string, body: string, channel: {id: string
|
||||
mention.on('click', () => {
|
||||
log.debug('notification click', serverName, mention);
|
||||
if (serverName) {
|
||||
WindowManager.switchTab(serverName, TAB_MESSAGING);
|
||||
WindowManager.switchTab(view.id);
|
||||
webcontents.send('notification-clicked', {channel, teamId, url});
|
||||
}
|
||||
});
|
||||
|
@@ -9,8 +9,6 @@ import {ipcRenderer, contextBridge} from 'electron';
|
||||
import {
|
||||
GET_LANGUAGE_INFORMATION,
|
||||
QUIT,
|
||||
GET_VIEW_NAME,
|
||||
GET_VIEW_WEBCONTENTS_ID,
|
||||
OPEN_APP_MENU,
|
||||
CLOSE_TEAMS_DROPDOWN,
|
||||
OPEN_TEAMS_DROPDOWN,
|
||||
@@ -29,7 +27,6 @@ import {
|
||||
HISTORY,
|
||||
CHECK_FOR_UPDATES,
|
||||
UPDATE_CONFIGURATION,
|
||||
UPDATE_TEAMS,
|
||||
GET_CONFIGURATION,
|
||||
GET_DARK_MODE,
|
||||
REQUEST_HAS_DOWNLOADS,
|
||||
@@ -85,17 +82,16 @@ import {
|
||||
LOADING_SCREEN_ANIMATION_FINISHED,
|
||||
TOGGLE_LOADING_SCREEN_VISIBILITY,
|
||||
DOWNLOADS_DROPDOWN_FOCUSED,
|
||||
UPDATE_SERVER_ORDER,
|
||||
UPDATE_TAB_ORDER,
|
||||
GET_LAST_ACTIVE,
|
||||
GET_ORDERED_SERVERS,
|
||||
GET_ORDERED_TABS_FOR_SERVER,
|
||||
SERVERS_UPDATE,
|
||||
} from 'common/communication';
|
||||
|
||||
console.log('Preload initialized');
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
contextBridge.exposeInMainWorld('testHelper', {
|
||||
getViewName: () => ipcRenderer.invoke(GET_VIEW_NAME),
|
||||
getWebContentsId: () => ipcRenderer.invoke(GET_VIEW_WEBCONTENTS_ID),
|
||||
});
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('process', {
|
||||
platform: process.platform,
|
||||
env: {
|
||||
@@ -117,14 +113,14 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
openAppMenu: () => ipcRenderer.send(OPEN_APP_MENU),
|
||||
closeTeamsDropdown: () => ipcRenderer.send(CLOSE_TEAMS_DROPDOWN),
|
||||
openTeamsDropdown: () => ipcRenderer.send(OPEN_TEAMS_DROPDOWN),
|
||||
switchTab: (serverName, tabName) => ipcRenderer.send(SWITCH_TAB, serverName, tabName),
|
||||
closeTab: (serverName, tabName) => ipcRenderer.send(CLOSE_TAB, serverName, tabName),
|
||||
switchTab: (tabId) => ipcRenderer.send(SWITCH_TAB, tabId),
|
||||
closeTab: (tabId) => ipcRenderer.send(CLOSE_TAB, tabId),
|
||||
closeWindow: () => ipcRenderer.send(WINDOW_CLOSE),
|
||||
minimizeWindow: () => ipcRenderer.send(WINDOW_MINIMIZE),
|
||||
maximizeWindow: () => ipcRenderer.send(WINDOW_MAXIMIZE),
|
||||
restoreWindow: () => ipcRenderer.send(WINDOW_RESTORE),
|
||||
doubleClickOnWindow: (windowName) => ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, windowName),
|
||||
focusBrowserView: () => ipcRenderer.send(FOCUS_BROWSERVIEW),
|
||||
focusCurrentView: () => ipcRenderer.send(FOCUS_BROWSERVIEW),
|
||||
reloadCurrentView: () => ipcRenderer.send(RELOAD_CURRENT_VIEW),
|
||||
closeDownloadsDropdown: () => ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN),
|
||||
closeDownloadsDropdownMenu: () => ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN_MENU),
|
||||
@@ -133,25 +129,31 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
checkForUpdates: () => ipcRenderer.send(CHECK_FOR_UPDATES),
|
||||
updateConfiguration: (saveQueueItems) => ipcRenderer.send(UPDATE_CONFIGURATION, saveQueueItems),
|
||||
|
||||
updateTeams: (updatedTeams) => ipcRenderer.invoke(UPDATE_TEAMS, updatedTeams),
|
||||
getConfiguration: (option) => ipcRenderer.invoke(GET_CONFIGURATION, option),
|
||||
updateServerOrder: (serverOrder) => ipcRenderer.send(UPDATE_SERVER_ORDER, serverOrder),
|
||||
updateTabOrder: (serverId, tabOrder) => ipcRenderer.send(UPDATE_TAB_ORDER, serverId, tabOrder),
|
||||
getLastActive: () => ipcRenderer.invoke(GET_LAST_ACTIVE),
|
||||
getOrderedServers: () => ipcRenderer.invoke(GET_ORDERED_SERVERS),
|
||||
getOrderedTabsForServer: (serverId) => ipcRenderer.invoke(GET_ORDERED_TABS_FOR_SERVER, serverId),
|
||||
onUpdateServers: (listener) => ipcRenderer.on(SERVERS_UPDATE, () => listener()),
|
||||
|
||||
getConfiguration: () => ipcRenderer.invoke(GET_CONFIGURATION),
|
||||
getVersion: () => ipcRenderer.invoke('get-app-version'),
|
||||
getDarkMode: () => ipcRenderer.invoke(GET_DARK_MODE),
|
||||
requestHasDownloads: () => ipcRenderer.invoke(REQUEST_HAS_DOWNLOADS),
|
||||
getFullScreenStatus: () => ipcRenderer.invoke(GET_FULL_SCREEN_STATUS),
|
||||
getAvailableSpellCheckerLanguages: () => ipcRenderer.invoke(GET_AVAILABLE_SPELL_CHECKER_LANGUAGES),
|
||||
getAvailableLanguages: () => ipcRenderer.invoke(GET_AVAILABLE_LANGUAGES),
|
||||
getLocalConfiguration: (option) => ipcRenderer.invoke(GET_LOCAL_CONFIGURATION, option),
|
||||
getLocalConfiguration: () => ipcRenderer.invoke(GET_LOCAL_CONFIGURATION),
|
||||
getDownloadLocation: (downloadLocation) => ipcRenderer.invoke(GET_DOWNLOAD_LOCATION, downloadLocation),
|
||||
getLanguageInformation: () => ipcRenderer.invoke(GET_LANGUAGE_INFORMATION),
|
||||
|
||||
onSynchronizeConfig: (listener) => ipcRenderer.on('synchronize-config', () => listener()),
|
||||
onReloadConfiguration: (listener) => ipcRenderer.on(RELOAD_CONFIGURATION, () => listener()),
|
||||
onDarkModeChange: (listener) => ipcRenderer.on(DARK_MODE_CHANGE, (_, darkMode) => listener(darkMode)),
|
||||
onLoadRetry: (listener) => ipcRenderer.on(LOAD_RETRY, (_, viewName, retry, err, loadUrl) => listener(viewName, retry, err, loadUrl)),
|
||||
onLoadSuccess: (listener) => ipcRenderer.on(LOAD_SUCCESS, (_, viewName) => listener(viewName)),
|
||||
onLoadFailed: (listener) => ipcRenderer.on(LOAD_FAILED, (_, viewName, err, loadUrl) => listener(viewName, err, loadUrl)),
|
||||
onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverName, tabName) => listener(serverName, tabName)),
|
||||
onLoadRetry: (listener) => ipcRenderer.on(LOAD_RETRY, (_, viewId, retry, err, loadUrl) => listener(viewId, retry, err, loadUrl)),
|
||||
onLoadSuccess: (listener) => ipcRenderer.on(LOAD_SUCCESS, (_, viewId) => listener(viewId)),
|
||||
onLoadFailed: (listener) => ipcRenderer.on(LOAD_FAILED, (_, viewId, err, loadUrl) => listener(viewId, err, loadUrl)),
|
||||
onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverId, tabId) => listener(serverId, tabId)),
|
||||
onMaximizeChange: (listener) => ipcRenderer.on(MAXIMIZE_CHANGE, (_, maximize) => listener(maximize)),
|
||||
onEnterFullScreen: (listener) => ipcRenderer.on('enter-full-screen', () => listener()),
|
||||
onLeaveFullScreen: (listener) => ipcRenderer.on('leave-full-screen', () => listener()),
|
||||
@@ -195,10 +197,10 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
serverDropdown: {
|
||||
requestInfo: () => ipcRenderer.send(REQUEST_TEAMS_DROPDOWN_INFO),
|
||||
sendSize: (width, height) => ipcRenderer.send(RECEIVE_DROPDOWN_MENU_SIZE, width, height),
|
||||
switchServer: (server) => ipcRenderer.send(SWITCH_SERVER, server),
|
||||
switchServer: (serverId) => ipcRenderer.send(SWITCH_SERVER, serverId),
|
||||
showNewServerModal: () => ipcRenderer.send(SHOW_NEW_SERVER_MODAL),
|
||||
showEditServerModal: (serverName) => ipcRenderer.send(SHOW_EDIT_SERVER_MODAL, serverName),
|
||||
showRemoveServerModal: (serverName) => ipcRenderer.send(SHOW_REMOVE_SERVER_MODAL, serverName),
|
||||
showEditServerModal: (serverId) => ipcRenderer.send(SHOW_EDIT_SERVER_MODAL, serverId),
|
||||
showRemoveServerModal: (serverId) => ipcRenderer.send(SHOW_REMOVE_SERVER_MODAL, serverId),
|
||||
|
||||
onUpdateServerDropdown: (listener) => ipcRenderer.on(UPDATE_TEAMS_DROPDOWN, (_,
|
||||
teams,
|
||||
|
@@ -25,8 +25,7 @@ import {
|
||||
BROWSER_HISTORY_PUSH,
|
||||
APP_LOGGED_IN,
|
||||
APP_LOGGED_OUT,
|
||||
GET_VIEW_NAME,
|
||||
GET_VIEW_WEBCONTENTS_ID,
|
||||
GET_VIEW_INFO_FOR_TEST,
|
||||
DISPATCH_GET_DESKTOP_SOURCES,
|
||||
DESKTOP_SOURCES_RESULT,
|
||||
VIEW_FINISHED_RESIZING,
|
||||
@@ -45,15 +44,14 @@ const CLEAR_CACHE_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours
|
||||
let appVersion;
|
||||
let appName;
|
||||
let sessionExpired;
|
||||
let viewName;
|
||||
let viewId;
|
||||
let shouldSendNotifications;
|
||||
|
||||
console.log('Preload initialized');
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
contextBridge.exposeInMainWorld('testHelper', {
|
||||
getViewName: () => ipcRenderer.invoke(GET_VIEW_NAME),
|
||||
getWebContentsId: () => ipcRenderer.invoke(GET_VIEW_WEBCONTENTS_ID),
|
||||
getViewInfoForTest: () => ipcRenderer.invoke(GET_VIEW_INFO_FOR_TEST),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,8 +90,8 @@ window.addEventListener('load', () => {
|
||||
return;
|
||||
}
|
||||
watchReactAppUntilInitialized(() => {
|
||||
ipcRenderer.send(REACT_APP_INITIALIZED, viewName);
|
||||
ipcRenderer.send(BROWSER_HISTORY_BUTTON, viewName);
|
||||
ipcRenderer.send(REACT_APP_INITIALIZED, viewId);
|
||||
ipcRenderer.send(BROWSER_HISTORY_BUTTON, viewId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,27 +150,27 @@ window.addEventListener('message', ({origin, data = {}} = {}) => {
|
||||
}
|
||||
case 'browser-history-push': {
|
||||
const {path} = message;
|
||||
ipcRenderer.send(BROWSER_HISTORY_PUSH, viewName, path);
|
||||
ipcRenderer.send(BROWSER_HISTORY_PUSH, viewId, path);
|
||||
break;
|
||||
}
|
||||
case 'history-button': {
|
||||
ipcRenderer.send(BROWSER_HISTORY_BUTTON, viewName);
|
||||
ipcRenderer.send(BROWSER_HISTORY_BUTTON, viewId);
|
||||
break;
|
||||
}
|
||||
case 'get-desktop-sources': {
|
||||
ipcRenderer.send(DISPATCH_GET_DESKTOP_SOURCES, viewName, message);
|
||||
ipcRenderer.send(DISPATCH_GET_DESKTOP_SOURCES, viewId, message);
|
||||
break;
|
||||
}
|
||||
case CALLS_JOIN_CALL: {
|
||||
ipcRenderer.send(CALLS_JOIN_CALL, viewName, message);
|
||||
ipcRenderer.send(CALLS_JOIN_CALL, viewId, message);
|
||||
break;
|
||||
}
|
||||
case CALLS_WIDGET_SHARE_SCREEN: {
|
||||
ipcRenderer.send(CALLS_WIDGET_SHARE_SCREEN, viewName, message);
|
||||
ipcRenderer.send(CALLS_WIDGET_SHARE_SCREEN, viewId, message);
|
||||
break;
|
||||
}
|
||||
case CALLS_LEAVE_CALL: {
|
||||
ipcRenderer.send(CALLS_LEAVE_CALL, viewName, message);
|
||||
ipcRenderer.send(CALLS_LEAVE_CALL, viewId, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -202,12 +200,12 @@ const findUnread = (favicon) => {
|
||||
const result = document.getElementsByClassName(classPair);
|
||||
return result && result.length > 0;
|
||||
});
|
||||
ipcRenderer.send(UNREAD_RESULT, favicon, viewName, isUnread);
|
||||
ipcRenderer.send(UNREAD_RESULT, favicon, viewId, isUnread);
|
||||
};
|
||||
|
||||
ipcRenderer.on(IS_UNREAD, (event, favicon, server) => {
|
||||
if (typeof viewName === 'undefined') {
|
||||
viewName = server;
|
||||
if (typeof viewId === 'undefined') {
|
||||
viewId = server;
|
||||
}
|
||||
if (isReactAppInitialized()) {
|
||||
findUnread(favicon);
|
||||
@@ -219,13 +217,13 @@ ipcRenderer.on(IS_UNREAD, (event, favicon, server) => {
|
||||
});
|
||||
|
||||
ipcRenderer.on(SET_VIEW_OPTIONS, (_, name, shouldNotify) => {
|
||||
viewName = name;
|
||||
viewId = name;
|
||||
shouldSendNotifications = shouldNotify;
|
||||
});
|
||||
|
||||
function getUnreadCount() {
|
||||
// LHS not found => Log out => Count should be 0, but session may be expired.
|
||||
if (typeof viewName !== 'undefined') {
|
||||
if (typeof viewId !== 'undefined') {
|
||||
let isExpired;
|
||||
if (document.getElementById('sidebar-left') === null) {
|
||||
const extraParam = (new URLSearchParams(window.location.search)).get('extra');
|
||||
@@ -235,7 +233,7 @@ function getUnreadCount() {
|
||||
}
|
||||
if (isExpired !== sessionExpired) {
|
||||
sessionExpired = isExpired;
|
||||
ipcRenderer.send(SESSION_EXPIRED, sessionExpired, viewName);
|
||||
ipcRenderer.send(SESSION_EXPIRED, sessionExpired, viewId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,10 +306,10 @@ ipcRenderer.on(BROWSER_HISTORY_BUTTON, (event, enableBack, enableForward) => {
|
||||
|
||||
window.addEventListener('storage', (e) => {
|
||||
if (e.key === '__login__' && e.storageArea === localStorage && e.newValue) {
|
||||
ipcRenderer.send(APP_LOGGED_IN, viewName);
|
||||
ipcRenderer.send(APP_LOGGED_IN, viewId);
|
||||
}
|
||||
if (e.key === '__logout__' && e.storageArea === localStorage && e.newValue) {
|
||||
ipcRenderer.send(APP_LOGGED_OUT, viewName);
|
||||
ipcRenderer.send(APP_LOGGED_OUT, viewId);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -4,72 +4,56 @@
|
||||
import {ClientConfig, RemoteInfo} from 'types/server';
|
||||
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {getServerAPI} from './serverAPI';
|
||||
|
||||
const log = new Logger('ServerInfo');
|
||||
|
||||
export class ServerInfo {
|
||||
server: MattermostServer;
|
||||
remoteInfo: RemoteInfo;
|
||||
promise: Promise<RemoteInfo | string | undefined>;
|
||||
onRetrievedRemoteInfo?: (result?: RemoteInfo | string) => void;
|
||||
private server: MattermostServer;
|
||||
private remoteInfo: RemoteInfo;
|
||||
|
||||
constructor(server: MattermostServer) {
|
||||
this.server = server;
|
||||
this.remoteInfo = {name: server.name};
|
||||
|
||||
this.promise = new Promise<RemoteInfo | string | undefined>((resolve) => {
|
||||
this.onRetrievedRemoteInfo = resolve;
|
||||
});
|
||||
this.getRemoteInfo();
|
||||
this.remoteInfo = {};
|
||||
}
|
||||
|
||||
getRemoteInfo = () => {
|
||||
getServerAPI<ClientConfig>(
|
||||
fetchRemoteInfo = async () => {
|
||||
await this.getRemoteInfo<ClientConfig>(
|
||||
new URL(`${this.server.url.toString()}/api/v4/config/client?format=old`),
|
||||
false,
|
||||
this.onGetConfig,
|
||||
this.onRetrievedRemoteInfo,
|
||||
this.onRetrievedRemoteInfo);
|
||||
|
||||
getServerAPI<Array<{id: string; version: string}>>(
|
||||
);
|
||||
await this.getRemoteInfo<Array<{id: string; version: string}>>(
|
||||
new URL(`${this.server.url.toString()}/api/v4/plugins/webapp`),
|
||||
false,
|
||||
this.onGetPlugins,
|
||||
this.onRetrievedRemoteInfo,
|
||||
this.onRetrievedRemoteInfo);
|
||||
);
|
||||
|
||||
return this.remoteInfo;
|
||||
}
|
||||
|
||||
onGetConfig = (data: ClientConfig) => {
|
||||
private getRemoteInfo = <T>(
|
||||
url: URL,
|
||||
callback: (data: T) => void,
|
||||
) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
getServerAPI<T>(
|
||||
url,
|
||||
false,
|
||||
(data: T) => {
|
||||
callback(data);
|
||||
resolve();
|
||||
},
|
||||
() => reject(new Error('Aborted')),
|
||||
(error: Error) => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
private onGetConfig = (data: ClientConfig) => {
|
||||
this.remoteInfo.serverVersion = data.Version;
|
||||
this.remoteInfo.siteURL = data.SiteURL;
|
||||
this.remoteInfo.hasFocalboard = this.remoteInfo.hasFocalboard || data.BuildBoards === 'true';
|
||||
|
||||
this.trySendRemoteInfo();
|
||||
}
|
||||
|
||||
onGetPlugins = (data: Array<{id: string; version: string}>) => {
|
||||
private onGetPlugins = (data: Array<{id: string; version: string}>) => {
|
||||
this.remoteInfo.hasFocalboard = this.remoteInfo.hasFocalboard || data.some((plugin) => plugin.id === 'focalboard');
|
||||
this.remoteInfo.hasPlaybooks = data.some((plugin) => plugin.id === 'playbooks');
|
||||
|
||||
this.trySendRemoteInfo();
|
||||
}
|
||||
|
||||
trySendRemoteInfo = () => {
|
||||
log.debug('trySendRemoteInfo', this.server.name, this.remoteInfo);
|
||||
|
||||
if (this.isRemoteInfoRetrieved()) {
|
||||
this.onRetrievedRemoteInfo?.(this.remoteInfo);
|
||||
}
|
||||
}
|
||||
|
||||
isRemoteInfoRetrieved = () => {
|
||||
return !(
|
||||
typeof this.remoteInfo.serverVersion === 'undefined' ||
|
||||
typeof this.remoteInfo.hasFocalboard === 'undefined' ||
|
||||
typeof this.remoteInfo.hasPlaybooks === 'undefined'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -180,7 +180,7 @@ describe('main/views/MattermostView', () => {
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).not.toBeCalled();
|
||||
expect(WindowManager.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.name, expect.any(String), expect.any(String));
|
||||
expect(WindowManager.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.id, expect.any(String), expect.any(String));
|
||||
expect(mattermostView.status).toBe(-1);
|
||||
jest.runAllTimers();
|
||||
expect(retryInBackgroundFn).toBeCalled();
|
||||
@@ -374,14 +374,7 @@ describe('main/views/MattermostView', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(appState.updateMentions).toBeCalledWith(mattermostView.tab.name, 0, false);
|
||||
});
|
||||
|
||||
it('should destroy context menu', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(contextMenu.dispose).toBeCalled();
|
||||
expect(appState.updateMentions).toBeCalledWith(mattermostView.tab.id, 0, false);
|
||||
});
|
||||
|
||||
it('should clear outstanding timeouts', () => {
|
||||
@@ -479,12 +472,12 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
it('should parse mentions from title', () => {
|
||||
mattermostView.updateMentionsFromTitle('(7) Mattermost');
|
||||
expect(appState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.name, 7);
|
||||
expect(appState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.id, 7);
|
||||
});
|
||||
|
||||
it('should parse unreads from title', () => {
|
||||
mattermostView.updateMentionsFromTitle('* Mattermost');
|
||||
expect(appState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.name, 0);
|
||||
expect(appState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.id, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -18,12 +18,12 @@ import {
|
||||
SET_VIEW_OPTIONS,
|
||||
LOADSCREEN_END,
|
||||
BROWSER_HISTORY_BUTTON,
|
||||
SERVERS_URL_MODIFIED,
|
||||
} from 'common/communication';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {Logger} from 'common/log';
|
||||
import {TabView} from 'common/tabs/TabView';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
|
||||
@@ -45,7 +45,6 @@ const titleParser = /(\((\d+)\) )?(\* )?/g;
|
||||
|
||||
export class MattermostView extends EventEmitter {
|
||||
tab: TabView;
|
||||
serverInfo: ServerInfo;
|
||||
isVisible: boolean;
|
||||
|
||||
private log: Logger;
|
||||
@@ -60,10 +59,9 @@ export class MattermostView extends EventEmitter {
|
||||
private maxRetries: number;
|
||||
private altPressStatus: boolean;
|
||||
|
||||
constructor(tab: TabView, serverInfo: ServerInfo, options: BrowserViewConstructorOptions) {
|
||||
constructor(tab: TabView, options: BrowserViewConstructorOptions) {
|
||||
super();
|
||||
this.tab = tab;
|
||||
this.serverInfo = serverInfo;
|
||||
|
||||
const preload = getLocalPreload('preload.js');
|
||||
this.options = Object.assign({}, options);
|
||||
@@ -81,7 +79,7 @@ export class MattermostView extends EventEmitter {
|
||||
this.view = new BrowserView(this.options);
|
||||
this.resetLoadingStatus();
|
||||
|
||||
this.log = new Logger(this.name, 'MattermostView');
|
||||
this.log = ServerManager.getViewLog(this.id, 'MattermostView');
|
||||
this.log.verbose('View created');
|
||||
|
||||
this.view.webContents.on('did-finish-load', this.handleDidFinishLoad);
|
||||
@@ -103,10 +101,12 @@ export class MattermostView extends EventEmitter {
|
||||
MainWindow.get()?.on('blur', () => {
|
||||
this.altPressStatus = false;
|
||||
});
|
||||
|
||||
ServerManager.on(SERVERS_URL_MODIFIED, this.handleServerWasModified);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.tab.name;
|
||||
get id() {
|
||||
return this.tab.id;
|
||||
}
|
||||
get isAtRoot() {
|
||||
return this.atRoot;
|
||||
@@ -121,17 +121,6 @@ export class MattermostView extends EventEmitter {
|
||||
return this.view.webContents.id;
|
||||
}
|
||||
|
||||
updateServerInfo = (srv: MattermostServer) => {
|
||||
let reload;
|
||||
if (srv.url.toString() !== this.tab.server.url.toString()) {
|
||||
reload = () => this.reload();
|
||||
}
|
||||
this.tab.server = srv;
|
||||
this.serverInfo = new ServerInfo(srv);
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.tab.name, this.tab.shouldNotify);
|
||||
reload?.();
|
||||
}
|
||||
|
||||
onLogin = (loggedIn: boolean) => {
|
||||
if (this.isLoggedIn === loggedIn) {
|
||||
return;
|
||||
@@ -170,16 +159,6 @@ export class MattermostView extends EventEmitter {
|
||||
this.view.webContents.send(BROWSER_HISTORY_BUTTON, this.view.webContents.canGoBack(), this.view.webContents.canGoForward());
|
||||
}
|
||||
|
||||
updateTabView = (tab: TabView) => {
|
||||
let reload;
|
||||
if (tab.url.toString() !== this.tab.url.toString()) {
|
||||
reload = () => this.reload();
|
||||
}
|
||||
this.tab = tab;
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.name, this.tab.shouldNotify);
|
||||
reload?.();
|
||||
}
|
||||
|
||||
load = (someURL?: URL | string) => {
|
||||
if (!this.tab) {
|
||||
return;
|
||||
@@ -201,9 +180,9 @@ export class MattermostView extends EventEmitter {
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (err.code && err.code.startsWith('ERR_CERT')) {
|
||||
WindowManager.sendToRenderer(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.log.info('Invalid certificate, stop retrying until the user decides what to do.', err);
|
||||
WindowManager.sendToRenderer(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.status = Status.ERROR;
|
||||
return;
|
||||
}
|
||||
@@ -258,7 +237,7 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
destroy = () => {
|
||||
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
||||
appState.updateMentions(this.name, 0, false);
|
||||
appState.updateMentions(this.id, 0, false);
|
||||
MainWindow.get()?.removeBrowserView(this.view);
|
||||
|
||||
// workaround to eliminate zombie processes
|
||||
@@ -274,8 +253,6 @@ export class MattermostView extends EventEmitter {
|
||||
if (this.removeLoading) {
|
||||
clearTimeout(this.removeLoading);
|
||||
}
|
||||
|
||||
this.contextMenu.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +284,7 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
if (timedout) {
|
||||
this.log.verbose('timeout expired will show the browserview');
|
||||
this.emit(LOADSCREEN_END, this.name);
|
||||
this.emit(LOADSCREEN_END, this.id);
|
||||
}
|
||||
clearTimeout(this.removeLoading);
|
||||
delete this.removeLoading;
|
||||
@@ -376,13 +353,13 @@ export class MattermostView extends EventEmitter {
|
||||
const results = resultsIterator.next(); // we are only interested in the first set
|
||||
const mentions = (results && results.value && parseInt(results.value[MENTIONS_GROUP], 10)) || 0;
|
||||
|
||||
appState.updateMentions(this.name, mentions);
|
||||
appState.updateMentions(this.id, mentions);
|
||||
}
|
||||
|
||||
// if favicon is null, it will affect appState, but won't be memoized
|
||||
private findUnreadState = (favicon: string | null) => {
|
||||
try {
|
||||
this.view.webContents.send(IS_UNREAD, favicon, this.name);
|
||||
this.view.webContents.send(IS_UNREAD, favicon, this.id);
|
||||
} catch (err: any) {
|
||||
this.log.error('There was an error trying to request the unread state', err);
|
||||
}
|
||||
@@ -417,8 +394,8 @@ export class MattermostView extends EventEmitter {
|
||||
if (this.maxRetries-- > 0) {
|
||||
this.loadRetry(loadURL, err);
|
||||
} else {
|
||||
WindowManager.sendToRenderer(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
WindowManager.sendToRenderer(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.status = Status.ERROR;
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
@@ -442,14 +419,14 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
private loadRetry = (loadURL: string, err: Error) => {
|
||||
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
|
||||
WindowManager.sendToRenderer(LOAD_RETRY, this.name, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
|
||||
WindowManager.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`);
|
||||
}
|
||||
|
||||
private loadSuccess = (loadURL: string) => {
|
||||
return () => {
|
||||
this.log.verbose(`finished loading ${loadURL}`);
|
||||
WindowManager.sendToRenderer(LOAD_SUCCESS, this.name);
|
||||
WindowManager.sendToRenderer(LOAD_SUCCESS, this.id);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
if (this.status === Status.LOADING) {
|
||||
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
||||
@@ -457,7 +434,7 @@ export class MattermostView extends EventEmitter {
|
||||
}
|
||||
this.status = Status.WAITING_MM;
|
||||
this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true);
|
||||
this.emit(LOAD_SUCCESS, this.name, loadURL);
|
||||
this.emit(LOAD_SUCCESS, this.id, loadURL);
|
||||
const mainWindow = MainWindow.get();
|
||||
if (mainWindow) {
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
|
||||
@@ -470,7 +447,7 @@ export class MattermostView extends EventEmitter {
|
||||
*/
|
||||
|
||||
private handleDidFinishLoad = () => {
|
||||
this.log.debug('did-finish-load', this.name);
|
||||
this.log.debug('did-finish-load');
|
||||
|
||||
// wait for screen to truly finish loading before sending the message down
|
||||
const timeout = setInterval(() => {
|
||||
@@ -480,7 +457,7 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
if (!this.view.webContents.isLoading()) {
|
||||
try {
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.name, this.tab.shouldNotify);
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.id, this.tab.shouldNotify);
|
||||
clearTimeout(timeout);
|
||||
} catch (e) {
|
||||
this.log.error('failed to send view options to view');
|
||||
@@ -492,12 +469,17 @@ export class MattermostView extends EventEmitter {
|
||||
private handleDidNavigate = (event: Event, url: string) => {
|
||||
this.log.debug('handleDidNavigate', url);
|
||||
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldHaveBackBar(this.tab.url || '', url)) {
|
||||
this.setBounds(getWindowBoundaries(MainWindow.get()!, true));
|
||||
this.setBounds(getWindowBoundaries(mainWindow, true));
|
||||
WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, true);
|
||||
this.log.debug('show back button');
|
||||
} else {
|
||||
this.setBounds(getWindowBoundaries(MainWindow.get()!));
|
||||
this.setBounds(getWindowBoundaries(mainWindow));
|
||||
WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, false);
|
||||
this.log.debug('hide back button');
|
||||
}
|
||||
@@ -511,4 +493,10 @@ export class MattermostView extends EventEmitter {
|
||||
this.emit(UPDATE_TARGET_URL);
|
||||
}
|
||||
}
|
||||
|
||||
private handleServerWasModified = (serverIds: string) => {
|
||||
if (serverIds.includes(this.tab.server.id)) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,11 @@ jest.mock('../windows/windowManager', () => ({
|
||||
sendToRenderer: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
on: jest.fn(),
|
||||
getOrderedServers: jest.fn().mockReturnValue([]),
|
||||
}));
|
||||
|
||||
describe('main/views/teamDropdownView', () => {
|
||||
describe('getBounds', () => {
|
||||
beforeEach(() => {
|
||||
@@ -62,52 +67,4 @@ describe('main/views/teamDropdownView', () => {
|
||||
teamDropdownView.handleClose();
|
||||
expect(teamDropdownView.view.setBounds).toBeCalledWith({width: 0, height: 0, x: expect.any(Number), y: expect.any(Number)});
|
||||
});
|
||||
|
||||
describe('addGpoToTeams', () => {
|
||||
it('should return teams with "isGPO": false when no config.registryTeams exist', () => {
|
||||
const teamDropdownView = new TeamDropdownView();
|
||||
const teams = [{
|
||||
name: 'team-1',
|
||||
url: 'https://mattermost.team-1.com',
|
||||
}, {
|
||||
name: 'team-2',
|
||||
url: 'https://mattermost.team-2.com',
|
||||
}];
|
||||
const registryTeams = [];
|
||||
|
||||
expect(teamDropdownView.addGpoToTeams(teams, registryTeams)).toStrictEqual([{
|
||||
name: 'team-1',
|
||||
url: 'https://mattermost.team-1.com',
|
||||
isGpo: false,
|
||||
}, {
|
||||
name: 'team-2',
|
||||
url: 'https://mattermost.team-2.com',
|
||||
isGpo: false,
|
||||
}]);
|
||||
});
|
||||
it('should return teams with "isGPO": true if they exist in config.registryTeams', () => {
|
||||
const teamDropdownView = new TeamDropdownView();
|
||||
const teams = [{
|
||||
name: 'team-1',
|
||||
url: 'https://mattermost.team-1.com',
|
||||
}, {
|
||||
name: 'team-2',
|
||||
url: 'https://mattermost.team-2.com',
|
||||
}];
|
||||
const registryTeams = [{
|
||||
name: 'team-1',
|
||||
url: 'https://mattermost.team-1.com',
|
||||
}];
|
||||
|
||||
expect(teamDropdownView.addGpoToTeams(teams, registryTeams)).toStrictEqual([{
|
||||
name: 'team-1',
|
||||
url: 'https://mattermost.team-1.com',
|
||||
isGpo: true,
|
||||
}, {
|
||||
name: 'team-2',
|
||||
url: 'https://mattermost.team-2.com',
|
||||
isGpo: false,
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
import {BrowserView, ipcMain, IpcMainEvent} from 'electron';
|
||||
|
||||
import {CombinedConfig, Team, TeamWithTabs, TeamWithTabsAndGpo} from 'types/config';
|
||||
import {CombinedConfig, MattermostTeam} from 'types/config';
|
||||
|
||||
import {
|
||||
CLOSE_TEAMS_DROPDOWN,
|
||||
@@ -14,10 +14,12 @@ import {
|
||||
REQUEST_TEAMS_DROPDOWN_INFO,
|
||||
RECEIVE_DROPDOWN_MENU_SIZE,
|
||||
SET_ACTIVE_VIEW,
|
||||
SERVERS_UPDATE,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHADOW_WIDTH} from 'common/utils/constants';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
|
||||
@@ -30,7 +32,7 @@ const log = new Logger('TeamDropdownView');
|
||||
export default class TeamDropdownView {
|
||||
view: BrowserView;
|
||||
bounds?: Electron.Rectangle;
|
||||
teams: TeamWithTabsAndGpo[];
|
||||
teams: MattermostTeam[];
|
||||
activeTeam?: string;
|
||||
darkMode: boolean;
|
||||
enableServerManagement?: boolean;
|
||||
@@ -42,7 +44,8 @@ export default class TeamDropdownView {
|
||||
isOpen: boolean;
|
||||
|
||||
constructor() {
|
||||
this.teams = this.addGpoToTeams(Config.teams, []);
|
||||
this.teams = this.getOrderedTeams();
|
||||
this.hasGPOTeams = this.teams.some((srv) => srv.isPredefined);
|
||||
this.darkMode = Config.darkMode;
|
||||
this.enableServerManagement = Config.enableServerManagement;
|
||||
this.isOpen = false;
|
||||
@@ -69,6 +72,17 @@ export default class TeamDropdownView {
|
||||
ipcMain.on(RECEIVE_DROPDOWN_MENU_SIZE, this.handleReceivedMenuSize);
|
||||
ipcMain.on(SET_ACTIVE_VIEW, this.updateActiveTeam);
|
||||
AppState.on(UPDATE_DROPDOWN_MENTIONS, this.updateMentions);
|
||||
|
||||
ServerManager.on(SERVERS_UPDATE, this.updateServers);
|
||||
}
|
||||
|
||||
private getOrderedTeams = () => {
|
||||
return ServerManager.getOrderedServers().map((team) => team.toMattermostTeam());
|
||||
}
|
||||
|
||||
updateServers = () => {
|
||||
this.teams = this.getOrderedTeams();
|
||||
this.hasGPOTeams = this.teams.some((srv) => srv.isPredefined);
|
||||
}
|
||||
|
||||
updateConfig = (event: IpcMainEvent, config: CombinedConfig) => {
|
||||
@@ -76,23 +90,33 @@ export default class TeamDropdownView {
|
||||
|
||||
this.darkMode = config.darkMode;
|
||||
this.enableServerManagement = config.enableServerManagement;
|
||||
this.hasGPOTeams = config.registryTeams && config.registryTeams.length > 0;
|
||||
this.updateDropdown();
|
||||
}
|
||||
|
||||
updateActiveTeam = (event: IpcMainEvent, name: string) => {
|
||||
log.silly('updateActiveTeam', {name});
|
||||
updateActiveTeam = (event: IpcMainEvent, serverId: string) => {
|
||||
log.silly('updateActiveTeam', {serverId});
|
||||
|
||||
this.activeTeam = name;
|
||||
this.activeTeam = serverId;
|
||||
this.updateDropdown();
|
||||
}
|
||||
|
||||
private reduceNotifications = <T>(items: Map<string, T>, modifier: (base?: T, value?: T) => T) => {
|
||||
return [...items.keys()].reduce((map, key) => {
|
||||
const view = ServerManager.getTab(key);
|
||||
if (!view) {
|
||||
return map;
|
||||
}
|
||||
map.set(view.server.id, modifier(map.get(view.server.id), items.get(key)));
|
||||
return map;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
updateMentions = (expired: Map<string, boolean>, mentions: Map<string, number>, unreads: Map<string, boolean>) => {
|
||||
log.silly('updateMentions', {expired, mentions, unreads});
|
||||
|
||||
this.unreads = unreads;
|
||||
this.mentions = mentions;
|
||||
this.expired = expired;
|
||||
this.unreads = this.reduceNotifications(unreads, (base, value) => base || value || false);
|
||||
this.mentions = this.reduceNotifications(mentions, (base, value) => (base ?? 0) + (value ?? 0));
|
||||
this.expired = this.reduceNotifications(expired, (base, value) => base || value || false);
|
||||
this.updateDropdown();
|
||||
}
|
||||
|
||||
@@ -164,16 +188,4 @@ export default class TeamDropdownView {
|
||||
// @ts-ignore
|
||||
this.view.webContents.destroy();
|
||||
}
|
||||
|
||||
addGpoToTeams = (teams: TeamWithTabs[], registryTeams: Team[]): TeamWithTabsAndGpo[] => {
|
||||
if (!registryTeams || registryTeams.length === 0) {
|
||||
return teams.map((team) => ({...team, isGpo: false}));
|
||||
}
|
||||
return teams.map((team) => {
|
||||
return {
|
||||
...team,
|
||||
isGpo: registryTeams.some((regTeam) => regTeam!.url === team!.url),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -4,13 +4,12 @@
|
||||
/* eslint-disable max-lines */
|
||||
'use strict';
|
||||
|
||||
import {dialog, ipcMain} from 'electron';
|
||||
import {dialog} from 'electron';
|
||||
|
||||
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, MAIN_WINDOW_SHOWN} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {getTabViewName} from 'common/tabs/TabView';
|
||||
import {equalUrlsIgnoringSubpath} from 'common/utils/url';
|
||||
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, SET_ACTIVE_VIEW} from 'common/communication';
|
||||
import {TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import urlUtils from 'common/utils/url';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
@@ -71,32 +70,61 @@ jest.mock('main/views/loadingScreen', () => ({
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
getCurrentServer: jest.fn(),
|
||||
getOrderedTabsForServer: jest.fn(),
|
||||
getAllServers: jest.fn(),
|
||||
hasServers: jest.fn(),
|
||||
getLastActiveServer: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
lookupTabByURL: jest.fn(),
|
||||
getRemoteInfo: jest.fn(),
|
||||
on: 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('./MattermostView', () => ({
|
||||
MattermostView: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./modalManager', () => ({
|
||||
showModal: jest.fn(),
|
||||
isModalDisplayed: jest.fn(),
|
||||
}));
|
||||
jest.mock('./webContentEvents', () => ({}));
|
||||
jest.mock('../appState', () => ({}));
|
||||
|
||||
describe('main/views/viewManager', () => {
|
||||
describe('loadView', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
const viewManager = new ViewManager();
|
||||
const onceFn = jest.fn();
|
||||
const loadFn = jest.fn();
|
||||
const destroyFn = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
viewManager.showByName = jest.fn();
|
||||
viewManager.getServerView = jest.fn().mockImplementation((srv, tabName) => ({name: `${srv.name}-${tabName}`}));
|
||||
viewManager.showById = jest.fn();
|
||||
MainWindow.get.mockReturnValue({});
|
||||
MattermostView.mockImplementation((tab) => ({
|
||||
on: jest.fn(),
|
||||
load: loadFn,
|
||||
once: onceFn,
|
||||
destroy: destroyFn,
|
||||
name: tab.name,
|
||||
id: tab.id,
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -107,48 +135,39 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should add closed tabs to closedViews', () => {
|
||||
viewManager.loadView({name: 'server1'}, {}, {name: 'tab1', isOpen: false});
|
||||
expect(viewManager.closedViews.has('server1-tab1')).toBe(true);
|
||||
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: false});
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove from remove from closedViews when the tab is open', () => {
|
||||
viewManager.closedViews.set('server1-tab1', {});
|
||||
expect(viewManager.closedViews.has('server1-tab1')).toBe(true);
|
||||
viewManager.loadView({name: 'server1'}, {}, {name: 'tab1', isOpen: true});
|
||||
expect(viewManager.closedViews.has('server1-tab1')).toBe(false);
|
||||
viewManager.closedViews.set('tab1', {});
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(true);
|
||||
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: true});
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should add view to views map and add listeners', () => {
|
||||
viewManager.loadView({name: 'server1'}, {}, {name: 'tab1', isOpen: true}, 'http://server-1.com/subpath');
|
||||
expect(viewManager.views.has('server1-tab1')).toBe(true);
|
||||
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: true}, 'http://server-1.com/subpath');
|
||||
expect(viewManager.views.has('tab1')).toBe(true);
|
||||
expect(onceFn).toHaveBeenCalledWith(LOAD_SUCCESS, viewManager.activateView);
|
||||
expect(loadFn).toHaveBeenCalledWith('http://server-1.com/subpath');
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadConfiguration', () => {
|
||||
describe('handleReloadConfiguration', () => {
|
||||
const viewManager = new ViewManager();
|
||||
|
||||
beforeEach(() => {
|
||||
viewManager.loadView = jest.fn();
|
||||
viewManager.showByName = jest.fn();
|
||||
viewManager.showById = jest.fn();
|
||||
viewManager.showInitial = jest.fn();
|
||||
|
||||
const mainWindow = {
|
||||
viewManager.focus = jest.fn();
|
||||
MainWindow.get.mockReturnValue({
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
};
|
||||
MainWindow.get.mockReturnValue(mainWindow);
|
||||
});
|
||||
|
||||
viewManager.getServerView = jest.fn().mockImplementation((srv, tabName) => ({
|
||||
name: `${srv.name}-${tabName}`,
|
||||
url: new URL(`http://${srv.name}.com`),
|
||||
}));
|
||||
MattermostServer.mockImplementation((server) => ({
|
||||
name: server.name,
|
||||
url: new URL(server.url),
|
||||
}));
|
||||
const onceFn = jest.fn();
|
||||
const loadFn = jest.fn();
|
||||
const destroyFn = jest.fn();
|
||||
@@ -157,11 +176,10 @@ describe('main/views/viewManager', () => {
|
||||
load: loadFn,
|
||||
once: onceFn,
|
||||
destroy: destroyFn,
|
||||
name: tab.name,
|
||||
id: tab.id,
|
||||
updateServerInfo: jest.fn(),
|
||||
tab,
|
||||
}));
|
||||
getTabViewName.mockImplementation((a, b) => `${a}-${b}`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -172,342 +190,289 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should recycle existing views', () => {
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
const view = new MattermostView({
|
||||
name: 'server1-tab1',
|
||||
server: 'server1',
|
||||
id: 'tab1',
|
||||
server: {
|
||||
id: 'server1',
|
||||
},
|
||||
});
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.views.get('server1-tab1')).toBe(view);
|
||||
viewManager.views.set('tab1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(viewManager.views.get('tab1')).toBe(view);
|
||||
expect(makeSpy).not.toHaveBeenCalled();
|
||||
makeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should close tabs that arent open', () => {
|
||||
Config.teams = [
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
isOpen: false,
|
||||
},
|
||||
],
|
||||
id: 'tab1',
|
||||
isOpen: false,
|
||||
},
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.closedViews.has('server1-tab1')).toBe(true);
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should create new views for new tabs', () => {
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
Config.teams = [
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
name: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
id: 'tab1',
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
url: new URL('http://server1.com/tab'),
|
||||
},
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(makeSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
id: 'server1',
|
||||
name: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
expect.any(Object),
|
||||
{
|
||||
id: 'tab1',
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
url: new URL('http://server1.com/tab'),
|
||||
},
|
||||
'http://server1.com',
|
||||
);
|
||||
makeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should set focus to current view on reload', () => {
|
||||
const view = {
|
||||
name: 'server1-tab1',
|
||||
id: 'tab1',
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
id: 'server-1',
|
||||
},
|
||||
name: 'server1-tab1',
|
||||
id: 'tab1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
updateServerInfo: jest.fn(),
|
||||
focus: jest.fn(),
|
||||
};
|
||||
viewManager.currentView = 'server1-tab1';
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
Config.teams = [
|
||||
viewManager.currentView = 'tab1';
|
||||
viewManager.views.set('tab1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
id: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.showByName).toHaveBeenCalledWith('server1-tab1');
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(view.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show initial if currentView has been removed', () => {
|
||||
const view = {
|
||||
name: 'server1-tab1',
|
||||
id: 'tab1',
|
||||
tab: {
|
||||
name: 'server1-tab1',
|
||||
id: 'tab1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
updateServerInfo: jest.fn(),
|
||||
};
|
||||
viewManager.currentView = 'server1-tab1';
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
Config.teams = [
|
||||
viewManager.currentView = 'tab1';
|
||||
viewManager.views.set('tab1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server2',
|
||||
url: new URL('http://server2.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
name: 'server2',
|
||||
url: 'http://server2.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
id: 'tab1',
|
||||
isOpen: false,
|
||||
},
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(viewManager.showInitial).toBeCalled();
|
||||
});
|
||||
|
||||
it('should remove unused views', () => {
|
||||
const view = {
|
||||
name: 'server1-tab1',
|
||||
name: 'tab1',
|
||||
tab: {
|
||||
name: 'server1-tab1',
|
||||
name: 'tab1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
Config.teams = [
|
||||
viewManager.views.set('tab1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server2',
|
||||
url: new URL('http://server2.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
name: 'server2',
|
||||
url: 'http://server2.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
id: 'tab1',
|
||||
isOpen: false,
|
||||
},
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(view.destroy).toBeCalled();
|
||||
expect(viewManager.showInitial).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showInitial', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
const viewManager = new ViewManager();
|
||||
const window = {webContents: {send: jest.fn()}};
|
||||
|
||||
beforeEach(() => {
|
||||
Config.teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}];
|
||||
viewManager.showByName = jest.fn();
|
||||
getTabViewName.mockImplementation((server, tab) => `${server}_${tab}`);
|
||||
viewManager.showById = jest.fn();
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
ServerManager.hasServers.mockReturnValue(true);
|
||||
ServerManager.getCurrentServer.mockReturnValue({id: 'server-0'});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
delete viewManager.lastActiveServer;
|
||||
});
|
||||
|
||||
it('should show first server and first open tab in order when last active not defined', () => {
|
||||
it('should show last active tab and server', () => {
|
||||
ServerManager.getLastActiveServer.mockReturnValue({id: 'server-1'});
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-1'});
|
||||
viewManager.showInitial();
|
||||
expect(viewManager.showByName).toHaveBeenCalledWith('server-2_tab-3');
|
||||
});
|
||||
|
||||
it('should show first tab in order of last active server', () => {
|
||||
viewManager.lastActiveServer = 1;
|
||||
viewManager.showInitial();
|
||||
expect(viewManager.showByName).toHaveBeenCalledWith('server-1_tab-3');
|
||||
});
|
||||
|
||||
it('should show last active tab of first server', () => {
|
||||
Config.teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 2,
|
||||
}];
|
||||
viewManager.showInitial();
|
||||
expect(viewManager.showByName).toHaveBeenCalledWith('server-2_tab-2');
|
||||
});
|
||||
|
||||
it('should show next tab when last active tab is closed', () => {
|
||||
Config.teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 2,
|
||||
}];
|
||||
viewManager.showInitial();
|
||||
expect(viewManager.showByName).toHaveBeenCalledWith('server-2_tab-1');
|
||||
expect(viewManager.showById).toHaveBeenCalledWith('tab-1');
|
||||
});
|
||||
|
||||
it('should open new server modal when no servers exist', () => {
|
||||
viewManager.mainWindow = {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
};
|
||||
Config.teams = [];
|
||||
ServerManager.hasServers.mockReturnValue(false);
|
||||
viewManager.showInitial();
|
||||
expect(ipcMain.emit).toHaveBeenCalledWith(MAIN_WINDOW_SHOWN);
|
||||
expect(window.webContents.send).toHaveBeenCalledWith(SET_ACTIVE_VIEW);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showByName', () => {
|
||||
describe('handleBrowserHistoryPush', () => {
|
||||
const viewManager = new ViewManager();
|
||||
viewManager.handleBrowserHistoryButton = jest.fn();
|
||||
viewManager.showById = jest.fn();
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-messaging',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'other_type_1',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'other_type_2',
|
||||
order: 1,
|
||||
isOpen: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const view1 = {
|
||||
id: 'server-1_tab-messaging',
|
||||
isLoggedIn: true,
|
||||
tab: {
|
||||
type: TAB_MESSAGING,
|
||||
server: {
|
||||
url: 'http://server-1.com',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
};
|
||||
const view2 = {
|
||||
...view1,
|
||||
id: 'server-1_other_type_1',
|
||||
tab: {
|
||||
...view1.tab,
|
||||
type: 'other_type_1',
|
||||
},
|
||||
};
|
||||
const view3 = {
|
||||
...view1,
|
||||
id: 'server-1_other_type_2',
|
||||
tab: {
|
||||
...view1.tab,
|
||||
type: 'other_type_2',
|
||||
},
|
||||
};
|
||||
const views = new Map([
|
||||
['server-1_tab-messaging', view1],
|
||||
['server-1_other_type_1', view2],
|
||||
]);
|
||||
const closedViews = new Map([
|
||||
['server-1_other_type_2', view3],
|
||||
]);
|
||||
viewManager.getView = (viewId) => views.get(viewId);
|
||||
viewManager.isViewClosed = (viewId) => closedViews.has(viewId);
|
||||
viewManager.openClosedTab = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
ServerManager.getAllServers.mockReturnValue(servers);
|
||||
ServerManager.getCurrentServer.mockReturnValue(servers[0]);
|
||||
urlUtils.cleanPathName.mockImplementation((base, path) => path);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should open closed view if pushing to it', () => {
|
||||
viewManager.openClosedTab.mockImplementation((name) => {
|
||||
const view = closedViews.get(name);
|
||||
closedViews.delete(name);
|
||||
views.set(name, view);
|
||||
});
|
||||
ServerManager.lookupTabByURL.mockReturnValue({id: 'server-1_other_type_2'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_tab-messaging', '/other_type_2/subpath');
|
||||
expect(viewManager.openClosedTab).toBeCalledWith('server-1_other_type_2', 'http://server-1.com/other_type_2/subpath');
|
||||
});
|
||||
|
||||
it('should open redirect view if different from current view', () => {
|
||||
ServerManager.lookupTabByURL.mockReturnValue({id: 'server-1_other_type_1'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_tab-messaging', '/other_type_1/subpath');
|
||||
expect(viewManager.showById).toBeCalledWith('server-1_other_type_1');
|
||||
});
|
||||
|
||||
it('should ignore redirects to "/" to Messages from other tabs', () => {
|
||||
ServerManager.lookupTabByURL.mockReturnValue({id: 'server-1_tab-messaging'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_other_type_1', '/');
|
||||
expect(view1.sendToRenderer).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showById', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
const baseView = {
|
||||
isReady: jest.fn(),
|
||||
@@ -545,12 +510,12 @@ describe('main/views/viewManager', () => {
|
||||
};
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
|
||||
viewManager.showByName('server1-tab1');
|
||||
viewManager.showById('server1-tab1');
|
||||
expect(viewManager.currentView).toBeUndefined();
|
||||
expect(view.isReady).not.toBeCalled();
|
||||
expect(view.show).not.toBeCalled();
|
||||
|
||||
viewManager.showByName('some-view-name');
|
||||
viewManager.showById('some-view-name');
|
||||
expect(viewManager.currentView).toBeUndefined();
|
||||
expect(view.isReady).not.toBeCalled();
|
||||
expect(view.show).not.toBeCalled();
|
||||
@@ -569,7 +534,7 @@ describe('main/views/viewManager', () => {
|
||||
viewManager.views.set('oldView', oldView);
|
||||
viewManager.views.set('newView', newView);
|
||||
viewManager.currentView = 'oldView';
|
||||
viewManager.showByName('newView');
|
||||
viewManager.showById('newView');
|
||||
expect(oldView.hide).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -577,7 +542,7 @@ describe('main/views/viewManager', () => {
|
||||
const view = {...baseView};
|
||||
view.isErrored.mockReturnValue(true);
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.showByName('view1');
|
||||
viewManager.showById('view1');
|
||||
expect(view.show).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -586,7 +551,7 @@ describe('main/views/viewManager', () => {
|
||||
view.isErrored.mockReturnValue(false);
|
||||
view.needsLoadingScreen.mockImplementation(() => true);
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.showByName('view1');
|
||||
viewManager.showById('view1');
|
||||
expect(LoadingScreen.show).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -595,113 +560,12 @@ describe('main/views/viewManager', () => {
|
||||
view.needsLoadingScreen.mockImplementation(() => false);
|
||||
view.isErrored.mockReturnValue(false);
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.showByName('view1');
|
||||
viewManager.showById('view1');
|
||||
expect(viewManager.currentView).toBe('view1');
|
||||
expect(view.show).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getViewByURL', () => {
|
||||
const viewManager = new ViewManager();
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab',
|
||||
},
|
||||
{
|
||||
name: 'tab-type1',
|
||||
},
|
||||
{
|
||||
name: 'tab-type2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'server-2',
|
||||
url: 'http://server-2.com/subpath',
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-type1',
|
||||
},
|
||||
{
|
||||
name: 'tab-type2',
|
||||
},
|
||||
{
|
||||
name: 'tab',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
viewManager.getServerView = (srv, tabName) => {
|
||||
const postfix = tabName.split('-')[1];
|
||||
return {
|
||||
name: `${srv.name}_${tabName}`,
|
||||
url: new URL(`${srv.url.toString().replace(/\/$/, '')}${postfix ? `/${postfix}` : ''}`),
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Config.teams = servers.concat();
|
||||
MattermostServer.mockImplementation((server) => ({
|
||||
name: server.name,
|
||||
url: new URL(server.url),
|
||||
}));
|
||||
equalUrlsIgnoringSubpath.mockImplementation((url1, url2) => `${url1}`.startsWith(`${url2}`));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should match the correct server - base URL', () => {
|
||||
const inputURL = new URL('http://server-1.com');
|
||||
expect(viewManager.getViewByURL(inputURL)).toStrictEqual({name: 'server-1_tab', url: new URL('http://server-1.com')});
|
||||
});
|
||||
|
||||
it('should match the correct server - base tab', () => {
|
||||
const inputURL = new URL('http://server-1.com/team');
|
||||
expect(viewManager.getViewByURL(inputURL)).toStrictEqual({name: 'server-1_tab', url: new URL('http://server-1.com')});
|
||||
});
|
||||
|
||||
it('should match the correct server - different tab', () => {
|
||||
const inputURL = new URL('http://server-1.com/type1/app');
|
||||
expect(viewManager.getViewByURL(inputURL)).toStrictEqual({name: 'server-1_tab-type1', url: new URL('http://server-1.com/type1')});
|
||||
});
|
||||
|
||||
it('should return undefined for server with subpath and URL without', () => {
|
||||
const inputURL = new URL('http://server-2.com');
|
||||
expect(viewManager.getViewByURL(inputURL)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined for server with subpath and URL with wrong subpath', () => {
|
||||
const inputURL = new URL('http://server-2.com/different/subpath');
|
||||
expect(viewManager.getViewByURL(inputURL)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should match the correct server with a subpath - base URL', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath');
|
||||
expect(viewManager.getViewByURL(inputURL)).toStrictEqual({name: 'server-2_tab', url: new URL('http://server-2.com/subpath')});
|
||||
});
|
||||
|
||||
it('should match the correct server with a subpath - base tab', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath/team');
|
||||
expect(viewManager.getViewByURL(inputURL)).toStrictEqual({name: 'server-2_tab', url: new URL('http://server-2.com/subpath')});
|
||||
});
|
||||
|
||||
it('should match the correct server with a subpath - different tab', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath/type2/team');
|
||||
expect(viewManager.getViewByURL(inputURL)).toStrictEqual({name: 'server-2_tab-type2', url: new URL('http://server-2.com/subpath/type2')});
|
||||
});
|
||||
|
||||
it('should return undefined for wrong server', () => {
|
||||
const inputURL = new URL('http://server-3.com');
|
||||
expect(viewManager.getViewByURL(inputURL)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleDeepLink', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
const baseView = {
|
||||
@@ -719,7 +583,6 @@ describe('main/views/viewManager', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
viewManager.openClosedTab = jest.fn();
|
||||
viewManager.getViewByURL = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -729,7 +592,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should load URL into matching view', () => {
|
||||
viewManager.getViewByURL.mockImplementation(() => ({name: 'view1', url: 'http://server-1.com/'}));
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
const view = {...baseView};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
@@ -737,14 +600,10 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should send the URL to the view if its already loaded on a 6.0 server', () => {
|
||||
viewManager.getViewByURL.mockImplementation(() => ({name: 'view1', url: 'http://server-1.com/'}));
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
ServerManager.getRemoteInfo.mockReturnValue({serverVersion: '6.0.0'});
|
||||
const view = {
|
||||
...baseView,
|
||||
serverInfo: {
|
||||
remoteInfo: {
|
||||
serverVersion: '6.0.0',
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
server: {
|
||||
url: new URL('http://server-1.com'),
|
||||
@@ -758,7 +617,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should throw error if view is missing', () => {
|
||||
viewManager.getViewByURL.mockImplementation(() => ({name: 'view1', url: 'http://server-1.com/'}));
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
const view = {...baseView};
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
expect(view.load).not.toHaveBeenCalled();
|
||||
@@ -772,10 +631,10 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should reopen closed tab if called upon', () => {
|
||||
viewManager.getViewByURL.mockImplementation(() => ({name: 'view1', url: 'https://server-1.com/'}));
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
viewManager.closedViews.set('view1', {});
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
expect(viewManager.openClosedTab).toHaveBeenCalledWith('view1', 'https://server-1.com/deep/link?thing=yes');
|
||||
expect(viewManager.openClosedTab).toHaveBeenCalledWith('view1', 'http://server-1.com/deep/link?thing=yes');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {BrowserView, dialog, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserViewConstructorOptions} from 'electron/main';
|
||||
|
||||
import {Tab, TeamWithTabs} from 'types/config';
|
||||
import {BrowserView, dialog, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
|
||||
import {SECOND, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
import {
|
||||
@@ -16,28 +14,25 @@ import {
|
||||
BROWSER_HISTORY_PUSH,
|
||||
UPDATE_LAST_ACTIVE,
|
||||
UPDATE_URL_VIEW_WIDTH,
|
||||
MAIN_WINDOW_SHOWN,
|
||||
RELOAD_CURRENT_VIEW,
|
||||
SERVERS_UPDATE,
|
||||
REACT_APP_INITIALIZED,
|
||||
APP_LOGGED_IN,
|
||||
BROWSER_HISTORY_BUTTON,
|
||||
APP_LOGGED_OUT,
|
||||
APP_LOGGED_IN,
|
||||
RELOAD_CURRENT_VIEW,
|
||||
UNREAD_RESULT,
|
||||
GET_VIEW_NAME,
|
||||
HISTORY,
|
||||
GET_VIEW_INFO_FOR_TEST,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import urlUtils, {equalUrlsIgnoringSubpath} from 'common/utils/url';
|
||||
import urlUtils from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {getTabViewName, TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS} from 'common/tabs/TabView';
|
||||
import MessagingTabView from 'common/tabs/MessagingTabView';
|
||||
import FocalboardTabView from 'common/tabs/FocalboardTabView';
|
||||
import PlaybooksTabView from 'common/tabs/PlaybooksTabView';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {TabView, TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import * as appState from '../appState';
|
||||
@@ -52,21 +47,17 @@ const URL_VIEW_DURATION = 10 * SECOND;
|
||||
const URL_VIEW_HEIGHT = 20;
|
||||
|
||||
export class ViewManager {
|
||||
private closedViews: Map<string, {srv: MattermostServer; tab: Tab}>;
|
||||
private closedViews: Map<string, {srv: MattermostServer; tab: TabView}>;
|
||||
private views: Map<string, MattermostView>;
|
||||
private currentView?: string;
|
||||
|
||||
private urlViewCancel?: () => void;
|
||||
|
||||
private lastActiveServer?: number;
|
||||
private viewOptions: BrowserViewConstructorOptions;
|
||||
|
||||
constructor() {
|
||||
this.lastActiveServer = Config.lastActiveTeam;
|
||||
this.viewOptions = {webPreferences: {spellcheck: Config.useSpellChecker}};
|
||||
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();
|
||||
|
||||
ipcMain.handle(GET_VIEW_INFO_FOR_TEST, this.handleGetViewInfoForTest);
|
||||
ipcMain.on(HISTORY, this.handleHistory);
|
||||
ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized);
|
||||
ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush);
|
||||
@@ -75,23 +66,24 @@ export class ViewManager {
|
||||
ipcMain.on(APP_LOGGED_OUT, this.handleAppLoggedOut);
|
||||
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
|
||||
ipcMain.on(UNREAD_RESULT, this.handleFaviconIsUnread);
|
||||
ipcMain.handle(GET_VIEW_NAME, this.handleGetViewName);
|
||||
|
||||
ServerManager.on(SERVERS_UPDATE, this.handleReloadConfiguration);
|
||||
}
|
||||
|
||||
init = () => {
|
||||
this.getServers().forEach((server) => this.loadServer(server));
|
||||
LoadingScreen.show();
|
||||
ServerManager.getAllServers().forEach((server) => this.loadServer(server));
|
||||
this.showInitial();
|
||||
}
|
||||
|
||||
getView = (viewName: string) => {
|
||||
return this.views.get(viewName);
|
||||
getView = (viewId: string) => {
|
||||
return this.views.get(viewId);
|
||||
}
|
||||
|
||||
getCurrentView = () => {
|
||||
if (this.currentView) {
|
||||
return this.views.get(this.currentView);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -99,42 +91,50 @@ export class ViewManager {
|
||||
return [...this.views.values()].find((view) => view.webContentsId === webContentsId);
|
||||
}
|
||||
|
||||
showByName = (name: string) => {
|
||||
log.debug('viewManager.showByName', name);
|
||||
isViewClosed = (viewId: string) => {
|
||||
return this.closedViews.has(viewId);
|
||||
}
|
||||
|
||||
const newView = this.views.get(name);
|
||||
showById = (tabId: string) => {
|
||||
this.getViewLogger(tabId).debug('showById', tabId);
|
||||
|
||||
const newView = this.views.get(tabId);
|
||||
if (newView) {
|
||||
if (newView.isVisible) {
|
||||
return;
|
||||
}
|
||||
if (this.currentView && this.currentView !== name) {
|
||||
let hidePrevious;
|
||||
if (this.currentView && this.currentView !== tabId) {
|
||||
const previous = this.getCurrentView();
|
||||
if (previous) {
|
||||
previous.hide();
|
||||
hidePrevious = () => previous.hide();
|
||||
}
|
||||
}
|
||||
|
||||
this.currentView = name;
|
||||
this.currentView = tabId;
|
||||
if (!newView.isErrored()) {
|
||||
newView.show();
|
||||
if (newView.needsLoadingScreen()) {
|
||||
LoadingScreen.show();
|
||||
}
|
||||
}
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.name, newView.tab.type);
|
||||
ipcMain.emit(SET_ACTIVE_VIEW, true, newView.tab.server.name, newView.tab.type);
|
||||
hidePrevious?.();
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.id, newView.tab.id);
|
||||
ipcMain.emit(SET_ACTIVE_VIEW, true, newView.tab.server.id, newView.tab.id);
|
||||
if (newView.isReady()) {
|
||||
ipcMain.emit(UPDATE_LAST_ACTIVE, true, newView.tab.server.name, newView.tab.type);
|
||||
ipcMain.emit(UPDATE_LAST_ACTIVE, true, newView.tab.id);
|
||||
} else {
|
||||
log.warn(`couldn't show ${name}, not ready`);
|
||||
this.getViewLogger(tabId).warn(`couldn't show ${tabId}, not ready`);
|
||||
}
|
||||
} else {
|
||||
log.warn(`Couldn't find a view with name: ${name}`);
|
||||
this.getViewLogger(tabId).warn(`Couldn't find a view with name: ${tabId}`);
|
||||
}
|
||||
modalManager.showModal();
|
||||
}
|
||||
|
||||
focusCurrentView = () => {
|
||||
log.debug('focusCurrentView');
|
||||
|
||||
if (modalManager.isModalDisplayed()) {
|
||||
modalManager.focusCurrentModal();
|
||||
return;
|
||||
@@ -171,25 +171,24 @@ export class ViewManager {
|
||||
*/
|
||||
|
||||
handleDeepLink = (url: string | URL) => {
|
||||
// TODO: fix for new tabs
|
||||
if (url) {
|
||||
const parsedURL = urlUtils.parseURL(url)!;
|
||||
const tabView = this.getViewByURL(parsedURL, true);
|
||||
const tabView = ServerManager.lookupTabByURL(parsedURL, true);
|
||||
if (tabView) {
|
||||
const urlWithSchema = `${urlUtils.parseURL(tabView.url)?.origin}${parsedURL.pathname}${parsedURL.search}`;
|
||||
if (this.closedViews.has(tabView.name)) {
|
||||
this.openClosedTab(tabView.name, urlWithSchema);
|
||||
const urlWithSchema = `${tabView.url.origin}${parsedURL.pathname}${parsedURL.search}`;
|
||||
if (this.closedViews.has(tabView.id)) {
|
||||
this.openClosedTab(tabView.id, urlWithSchema);
|
||||
} else {
|
||||
const view = this.views.get(tabView.name);
|
||||
const view = this.views.get(tabView.id);
|
||||
if (!view) {
|
||||
log.error(`Couldn't find a view matching the name ${tabView.name}`);
|
||||
log.error(`Couldn't find a view matching the id ${tabView.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.isReady() && view.serverInfo.remoteInfo.serverVersion && Utils.isVersionGreaterThanOrEqualTo(view.serverInfo.remoteInfo.serverVersion, '6.0.0')) {
|
||||
if (view.isReady() && ServerManager.getRemoteInfo(view.tab.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(view.tab.server.id)?.serverVersion ?? '', '6.0.0')) {
|
||||
const pathName = `/${urlWithSchema.replace(view.tab.server.url.toString(), '')}`;
|
||||
view.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(view.name);
|
||||
this.deeplinkSuccess(view.id);
|
||||
} else {
|
||||
// attempting to change parsedURL protocol results in it not being modified.
|
||||
view.resetLoadingStatus();
|
||||
@@ -207,83 +206,67 @@ export class ViewManager {
|
||||
}
|
||||
};
|
||||
|
||||
private deeplinkSuccess = (viewName: string) => {
|
||||
log.debug('deeplinkSuccess', viewName);
|
||||
private deeplinkSuccess = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('deeplinkSuccess');
|
||||
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
this.showByName(viewName);
|
||||
view.removeListener(LOAD_FAILED, this.deeplinkFailed);
|
||||
this.showById(viewId);
|
||||
this.views.get(viewId)?.removeListener(LOAD_FAILED, this.deeplinkFailed);
|
||||
};
|
||||
|
||||
private deeplinkFailed = (viewName: string, err: string, url: string) => {
|
||||
log.error(`[${viewName}] failed to load deeplink ${url}: ${err}`);
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
view.removeListener(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
private deeplinkFailed = (viewId: string, err: string, url: string) => {
|
||||
this.getViewLogger(viewId).error(`failed to load deeplink ${url}`, err);
|
||||
this.views.get(viewId)?.removeListener(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* View loading helpers
|
||||
*/
|
||||
|
||||
private loadServer = (server: TeamWithTabs) => {
|
||||
const srv = new MattermostServer(server);
|
||||
const serverInfo = new ServerInfo(srv);
|
||||
server.tabs.forEach((tab) => this.loadView(srv, serverInfo, tab));
|
||||
private loadServer = (server: MattermostServer) => {
|
||||
const tabs = ServerManager.getOrderedTabsForServer(server.id);
|
||||
tabs.forEach((tab) => this.loadView(server, tab));
|
||||
}
|
||||
|
||||
private loadView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string) => {
|
||||
private loadView = (srv: MattermostServer, tab: TabView, url?: string) => {
|
||||
if (!tab.isOpen) {
|
||||
this.closedViews.set(getTabViewName(srv.name, tab.name), {srv, tab});
|
||||
this.closedViews.set(tab.id, {srv, tab});
|
||||
return;
|
||||
}
|
||||
const view = this.makeView(srv, serverInfo, tab, url);
|
||||
const view = this.makeView(srv, tab, url);
|
||||
this.addView(view);
|
||||
}
|
||||
|
||||
private makeView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string): MattermostView => {
|
||||
const tabView = this.getServerView(srv, tab.name);
|
||||
const view = new MattermostView(tabView, serverInfo, this.viewOptions);
|
||||
private makeView = (srv: MattermostServer, tab: TabView, url?: string): MattermostView => {
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
throw new Error('Cannot create view, no main window present');
|
||||
}
|
||||
|
||||
const view = new MattermostView(tab, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
||||
view.once(LOAD_SUCCESS, this.activateView);
|
||||
view.load(url);
|
||||
view.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
view.on(LOADSCREEN_END, this.finishLoading);
|
||||
view.on(LOAD_FAILED, this.failLoading);
|
||||
view.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
view.load(url);
|
||||
return view;
|
||||
}
|
||||
|
||||
private addView = (view: MattermostView): void => {
|
||||
this.views.set(view.name, view);
|
||||
if (this.closedViews.has(view.name)) {
|
||||
this.closedViews.delete(view.name);
|
||||
this.views.set(view.id, view);
|
||||
if (this.closedViews.has(view.id)) {
|
||||
this.closedViews.delete(view.id);
|
||||
}
|
||||
}
|
||||
|
||||
private showInitial = () => {
|
||||
log.verbose('showInitial');
|
||||
|
||||
const servers = this.getServers();
|
||||
if (servers.length) {
|
||||
const element = servers.find((e) => e.order === this.lastActiveServer) || servers.find((e) => e.order === 0);
|
||||
if (element && element.tabs.length) {
|
||||
let tab = element.tabs.find((tab) => tab.order === element.lastActiveTab) || element.tabs.find((tab) => tab.order === 0);
|
||||
if (!tab?.isOpen) {
|
||||
const openTabs = element.tabs.filter((tab) => tab.isOpen);
|
||||
tab = openTabs.find((e) => e.order === 0) || openTabs.concat().sort((a, b) => a.order - b.order)[0];
|
||||
}
|
||||
if (tab) {
|
||||
const tabView = getTabViewName(element.name, tab.name);
|
||||
this.showByName(tabView);
|
||||
}
|
||||
}
|
||||
if (ServerManager.hasServers()) {
|
||||
const lastActiveServer = ServerManager.getCurrentServer();
|
||||
const lastActiveTab = ServerManager.getLastActiveTabForServer(lastActiveServer.id);
|
||||
this.showById(lastActiveTab.id);
|
||||
} else {
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, null, null);
|
||||
ipcMain.emit(MAIN_WINDOW_SHOWN);
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,29 +274,28 @@ export class ViewManager {
|
||||
* Mattermost view event handlers
|
||||
*/
|
||||
|
||||
private activateView = (viewName: string) => {
|
||||
log.debug('activateView', viewName);
|
||||
private activateView = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('activateView');
|
||||
|
||||
if (this.currentView === viewName) {
|
||||
this.showByName(this.currentView);
|
||||
if (this.currentView === viewId) {
|
||||
this.showById(this.currentView);
|
||||
}
|
||||
}
|
||||
|
||||
private finishLoading = (server: string) => {
|
||||
log.debug('finishLoading', server);
|
||||
private finishLoading = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('finishLoading');
|
||||
|
||||
const view = this.views.get(server);
|
||||
if (view && this.getCurrentView() === view) {
|
||||
this.showByName(this.currentView!);
|
||||
if (this.currentView === viewId) {
|
||||
this.showById(this.currentView);
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
|
||||
private failLoading = (tabName: string) => {
|
||||
log.debug('failLoading', tabName);
|
||||
private failLoading = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('failLoading');
|
||||
|
||||
LoadingScreen.fade();
|
||||
if (this.currentView === tabName) {
|
||||
if (this.currentView === viewId) {
|
||||
this.getCurrentView()?.hide();
|
||||
}
|
||||
}
|
||||
@@ -344,7 +326,7 @@ export class ViewManager {
|
||||
const query = new Map([['url', urlString]]);
|
||||
const localURL = getLocalURLString('urlView.html', query);
|
||||
urlView.webContents.loadURL(localURL);
|
||||
mainWindow.addBrowserView(urlView);
|
||||
MainWindow.get()?.addBrowserView(urlView);
|
||||
const boundaries = this.views.get(this.currentView || '')?.getBounds() ?? mainWindow.getBounds();
|
||||
|
||||
const hideView = () => {
|
||||
@@ -372,7 +354,7 @@ export class ViewManager {
|
||||
height: URL_VIEW_HEIGHT,
|
||||
};
|
||||
|
||||
log.silly('showURLView setBounds', boundaries, bounds);
|
||||
log.silly('showURLView.setBounds', boundaries, bounds);
|
||||
urlView.setBounds(bounds);
|
||||
};
|
||||
|
||||
@@ -397,34 +379,30 @@ export class ViewManager {
|
||||
* Servers or tabs have been added or edited. We need to
|
||||
* close, open, or reload tabs, taking care to reuse tabs and
|
||||
* preserve focus on the currently selected tab. */
|
||||
reloadConfiguration = () => {
|
||||
log.debug('reloadConfiguration');
|
||||
private handleReloadConfiguration = () => {
|
||||
log.debug('handleReloadConfiguration');
|
||||
|
||||
const currentTabId: string | undefined = this.views.get(this.currentView as string)?.tab.id;
|
||||
|
||||
const current: Map<string, MattermostView> = new Map();
|
||||
for (const view of this.views.values()) {
|
||||
current.set(view.name, view);
|
||||
current.set(view.tab.id, view);
|
||||
}
|
||||
|
||||
const views: Map<string, MattermostView> = new Map();
|
||||
const closed: Map<string, {srv: MattermostServer; tab: Tab; name: string}> = new Map();
|
||||
const closed: Map<string, {srv: MattermostServer; tab: TabView}> = new Map();
|
||||
|
||||
const sortedTabs = this.getServers().flatMap((x) => [...x.tabs].
|
||||
sort((a, b) => a.order - b.order).
|
||||
map((t): [TeamWithTabs, Tab] => [x, t]));
|
||||
const sortedTabs = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
|
||||
map((t): [MattermostServer, TabView] => [x, t]));
|
||||
|
||||
for (const [team, tab] of sortedTabs) {
|
||||
const srv = new MattermostServer(team);
|
||||
const info = new ServerInfo(srv);
|
||||
const tabName = getTabViewName(team.name, tab.name);
|
||||
const recycle = current.get(tabName);
|
||||
for (const [srv, tab] of sortedTabs) {
|
||||
const recycle = current.get(tab.id);
|
||||
if (!tab.isOpen) {
|
||||
const view = this.getServerView(srv, tab.name);
|
||||
closed.set(tabName, {srv, tab, name: view.name});
|
||||
closed.set(tab.id, {srv, tab});
|
||||
} else if (recycle) {
|
||||
recycle.updateServerInfo(srv);
|
||||
views.set(tabName, recycle);
|
||||
views.set(tab.id, recycle);
|
||||
} else {
|
||||
views.set(tabName, this.makeView(srv, info, tab, team.url));
|
||||
views.set(tab.id, this.makeView(srv, tab));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,16 +417,16 @@ export class ViewManager {
|
||||
// commit views
|
||||
this.views = new Map();
|
||||
for (const x of views.values()) {
|
||||
this.views.set(x.name, x);
|
||||
this.views.set(x.id, x);
|
||||
}
|
||||
|
||||
// commit closed
|
||||
for (const x of closed.values()) {
|
||||
this.closedViews.set(x.name, {srv: x.srv, tab: x.tab});
|
||||
this.closedViews.set(x.tab.id, {srv: x.srv, tab: x.tab});
|
||||
}
|
||||
|
||||
if ((this.currentView && closed.has(this.currentView)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if (this.getServers().length) {
|
||||
if ((currentTabId && closed.has(currentTabId)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if (ServerManager.hasServers()) {
|
||||
this.currentView = undefined;
|
||||
this.showInitial();
|
||||
} else {
|
||||
@@ -457,12 +435,14 @@ export class ViewManager {
|
||||
}
|
||||
|
||||
// show the focused tab (or initial)
|
||||
if (this.currentView && views.has(this.currentView)) {
|
||||
const view = views.get(this.currentView);
|
||||
if (view) {
|
||||
this.currentView = view.name;
|
||||
this.showByName(view.name);
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, view.tab.server.name, view.tab.type);
|
||||
if (currentTabId && views.has(currentTabId)) {
|
||||
const view = views.get(currentTabId);
|
||||
if (view && view.id !== this.currentView) {
|
||||
this.currentView = view.id;
|
||||
this.showById(view.id);
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, view.tab.server.id, view.tab.id);
|
||||
} else {
|
||||
this.focusCurrentView();
|
||||
}
|
||||
} else {
|
||||
this.showInitial();
|
||||
@@ -481,21 +461,21 @@ export class ViewManager {
|
||||
this.getView(viewId)?.onLogin(false);
|
||||
}
|
||||
|
||||
private handleBrowserHistoryPush = (e: IpcMainEvent, viewName: string, pathName: string) => {
|
||||
log.debug('handleBrowserHistoryPush', {viewName, pathName});
|
||||
private handleBrowserHistoryPush = (e: IpcMainEvent, viewId: string, pathName: string) => {
|
||||
log.debug('handleBrowserHistoryPush', {viewId, pathName});
|
||||
|
||||
const currentView = this.views.get(viewName);
|
||||
const currentView = this.getView(viewId);
|
||||
const cleanedPathName = urlUtils.cleanPathName(currentView?.tab.server.url.pathname || '', pathName);
|
||||
const redirectedViewName = this.getViewByURL(`${currentView?.tab.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.name || viewName;
|
||||
if (this.closedViews.has(redirectedViewName)) {
|
||||
const redirectedviewId = ServerManager.lookupTabByURL(`${currentView?.tab.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.id || viewId;
|
||||
if (this.isViewClosed(redirectedviewId)) {
|
||||
// If it's a closed view, just open it and stop
|
||||
this.openClosedTab(redirectedViewName, `${currentView?.tab.server.url}${cleanedPathName}`);
|
||||
this.openClosedTab(redirectedviewId, `${currentView?.tab.server.url}${cleanedPathName}`);
|
||||
return;
|
||||
}
|
||||
let redirectedView = this.views.get(redirectedViewName) || currentView;
|
||||
if (redirectedView !== currentView && redirectedView?.tab.name === this.currentView && redirectedView?.isLoggedIn) {
|
||||
log.info('redirecting to a new view', redirectedView?.name || viewName);
|
||||
this.showByName(redirectedView?.name || viewName);
|
||||
let redirectedView = this.getView(redirectedviewId) || currentView;
|
||||
if (redirectedView !== currentView && redirectedView?.tab.server.id === ServerManager.getCurrentServer().id && redirectedView?.isLoggedIn) {
|
||||
log.info('redirecting to a new view', redirectedView?.id || viewId);
|
||||
this.showById(redirectedView?.id || viewId);
|
||||
} else {
|
||||
redirectedView = currentView;
|
||||
}
|
||||
@@ -504,21 +484,19 @@ export class ViewManager {
|
||||
if (!(redirectedView !== currentView && redirectedView?.tab.type === TAB_MESSAGING && cleanedPathName === '/')) {
|
||||
redirectedView?.sendToRenderer(BROWSER_HISTORY_PUSH, cleanedPathName);
|
||||
if (redirectedView) {
|
||||
this.handleBrowserHistoryButton(e, redirectedView.name);
|
||||
this.handleBrowserHistoryButton(e, redirectedView.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleBrowserHistoryButton = (e: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleBrowserHistoryButton', viewName);
|
||||
|
||||
this.getView(viewName)?.updateHistoryButton();
|
||||
private handleBrowserHistoryButton = (e: IpcMainEvent, viewId: string) => {
|
||||
this.getView(viewId)?.updateHistoryButton();
|
||||
}
|
||||
|
||||
private handleReactAppInitialized = (e: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleReactAppInitialized', viewName);
|
||||
private handleReactAppInitialized = (e: IpcMainEvent, viewId: string) => {
|
||||
log.debug('handleReactAppInitialized', viewId);
|
||||
|
||||
const view = this.views.get(viewName);
|
||||
const view = this.views.get(viewId);
|
||||
if (view) {
|
||||
view.setInitialized();
|
||||
if (this.getCurrentView() === view) {
|
||||
@@ -535,94 +513,53 @@ export class ViewManager {
|
||||
return;
|
||||
}
|
||||
view?.reload();
|
||||
this.showByName(view?.name);
|
||||
this.showById(view?.id);
|
||||
}
|
||||
|
||||
// if favicon is null, it means it is the initial load,
|
||||
// so don't memoize as we don't have the favicons and there is no rush to find out.
|
||||
private handleFaviconIsUnread = (e: Event, favicon: string, viewName: string, result: boolean) => {
|
||||
log.silly('handleFaviconIsUnread', {favicon, viewName, result});
|
||||
private handleFaviconIsUnread = (e: Event, favicon: string, viewId: string, result: boolean) => {
|
||||
log.silly('handleFaviconIsUnread', {favicon, viewId, result});
|
||||
|
||||
appState.updateUnreads(viewName, result);
|
||||
appState.updateUnreads(viewId, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
private openClosedTab = (name: string, url?: string) => {
|
||||
if (!this.closedViews.has(name)) {
|
||||
private openClosedTab = (id: string, url?: string) => {
|
||||
if (!this.closedViews.has(id)) {
|
||||
return;
|
||||
}
|
||||
const {srv, tab} = this.closedViews.get(name)!;
|
||||
const {srv, tab} = this.closedViews.get(id)!;
|
||||
tab.isOpen = true;
|
||||
this.loadView(srv, new ServerInfo(srv), tab, url);
|
||||
this.showByName(name);
|
||||
const view = this.views.get(name)!;
|
||||
this.loadView(srv, tab, url);
|
||||
this.showById(id);
|
||||
const view = this.views.get(id)!;
|
||||
view.isVisible = true;
|
||||
view.on(LOAD_SUCCESS, () => {
|
||||
view.isVisible = false;
|
||||
this.showByName(name);
|
||||
this.showById(id);
|
||||
});
|
||||
ipcMain.emit(OPEN_TAB, null, srv.name, tab.name);
|
||||
ipcMain.emit(OPEN_TAB, null, tab.id);
|
||||
}
|
||||
|
||||
getViewByURL = (inputURL: URL | string, ignoreScheme = false) => {
|
||||
log.silly('getViewByURL', `${inputURL}`, ignoreScheme);
|
||||
private getViewLogger = (viewId: string) => {
|
||||
return ServerManager.getViewLog(viewId, 'ViewManager');
|
||||
}
|
||||
|
||||
const parsedURL = urlUtils.parseURL(inputURL);
|
||||
if (!parsedURL) {
|
||||
return undefined;
|
||||
}
|
||||
const server = this.getServers().find((team) => {
|
||||
const parsedServerUrl = urlUtils.parseURL(team.url)!;
|
||||
return equalUrlsIgnoringSubpath(parsedURL, parsedServerUrl, ignoreScheme) && parsedURL.pathname.match(new RegExp(`^${parsedServerUrl.pathname}(.+)?(/(.+))?$`));
|
||||
});
|
||||
if (!server) {
|
||||
return undefined;
|
||||
}
|
||||
const mmServer = new MattermostServer(server);
|
||||
let selectedTab = this.getServerView(mmServer, TAB_MESSAGING);
|
||||
server.tabs.
|
||||
filter((tab) => tab.name !== TAB_MESSAGING).
|
||||
forEach((tab) => {
|
||||
const tabCandidate = this.getServerView(mmServer, tab.name);
|
||||
if (parsedURL.pathname.match(new RegExp(`^${tabCandidate.url.pathname}(/(.+))?`))) {
|
||||
selectedTab = tabCandidate;
|
||||
}
|
||||
});
|
||||
return selectedTab;
|
||||
}
|
||||
|
||||
private getServerView = (srv: MattermostServer, tabName: string) => {
|
||||
switch (tabName) {
|
||||
case TAB_MESSAGING:
|
||||
return new MessagingTabView(srv);
|
||||
case TAB_FOCALBOARD:
|
||||
return new FocalboardTabView(srv);
|
||||
case TAB_PLAYBOOKS:
|
||||
return new PlaybooksTabView(srv);
|
||||
default:
|
||||
throw new Error('Not implemeneted');
|
||||
}
|
||||
}
|
||||
|
||||
private getServers = () => {
|
||||
return Config.teams.concat();
|
||||
}
|
||||
|
||||
handleGetViewName = (event: IpcMainInvokeEvent) => {
|
||||
return this.getViewByWebContentsId(event.sender.id);
|
||||
}
|
||||
|
||||
setServerInitialized = (server: string) => {
|
||||
const view = this.views.get(server);
|
||||
if (view) {
|
||||
view.setInitialized();
|
||||
if (this.getCurrentView() === view) {
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
private handleGetViewInfoForTest = (event: IpcMainInvokeEvent) => {
|
||||
const view = this.getViewByWebContentsId(event.sender.id);
|
||||
if (!view) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: view.id,
|
||||
webContentsId: view.webContentsId,
|
||||
serverName: view.tab.server.name,
|
||||
tabType: view.tab.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,12 +23,13 @@ jest.mock('electron', () => ({
|
||||
session: {},
|
||||
}));
|
||||
jest.mock('main/contextMenu', () => jest.fn());
|
||||
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
}));
|
||||
jest.mock('../allowProtocolDialog', () => ({}));
|
||||
jest.mock('main/windows/callsWidgetWindow', () => ({}));
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
getViewByWebContentsId: jest.fn(),
|
||||
getViewByURL: jest.fn(),
|
||||
}));
|
||||
jest.mock('../windows/windowManager', () => ({
|
||||
getServerURLFromWebContentsId: jest.fn(),
|
||||
|
@@ -9,17 +9,18 @@ import urlUtils from 'common/utils/url';
|
||||
|
||||
import {flushCookiesStore} from 'main/app/utils';
|
||||
import ContextMenu from 'main/contextMenu';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
|
||||
import {protocols} from '../../../electron-builder.json';
|
||||
|
||||
import allowProtocolDialog from '../allowProtocolDialog';
|
||||
import {composeUserAgent} from '../utils';
|
||||
|
||||
import ViewManager from './viewManager';
|
||||
|
||||
type CustomLogin = {
|
||||
inProgress: boolean;
|
||||
}
|
||||
@@ -38,10 +39,16 @@ export class WebContentsEventManager {
|
||||
}
|
||||
|
||||
private log = (webContentsId?: number) => {
|
||||
if (webContentsId) {
|
||||
return log.withPrefix(String(webContentsId));
|
||||
if (!webContentsId) {
|
||||
return log;
|
||||
}
|
||||
return log;
|
||||
|
||||
const view = ViewManager.getViewByWebContentsId(webContentsId);
|
||||
if (!view) {
|
||||
return log;
|
||||
}
|
||||
|
||||
return ServerManager.getViewLog(view.id, 'WebContentsEventManager');
|
||||
}
|
||||
|
||||
private isTrustedPopupWindow = (webContentsId: number) => {
|
||||
@@ -59,7 +66,7 @@ export class WebContentsEventManager {
|
||||
return WindowManager.getServerURLFromWebContentsId(webContentsId);
|
||||
}
|
||||
|
||||
generateWillNavigate = (webContentsId: number) => {
|
||||
private generateWillNavigate = (webContentsId: number) => {
|
||||
return (event: Event, url: string) => {
|
||||
this.log(webContentsId).debug('will-navigate', url);
|
||||
|
||||
@@ -95,9 +102,9 @@ export class WebContentsEventManager {
|
||||
};
|
||||
};
|
||||
|
||||
generateDidStartNavigation = (webContentsId: number) => {
|
||||
private generateDidStartNavigation = (webContentsId: number) => {
|
||||
return (event: Event, url: string) => {
|
||||
this.log(webContentsId).debug('did-start-navigation', {webContentsId, url});
|
||||
this.log(webContentsId).debug('did-start-navigation', url);
|
||||
|
||||
const parsedURL = urlUtils.parseURL(url)!;
|
||||
const serverURL = this.getServerURLFromWebContentsId(webContentsId);
|
||||
@@ -114,12 +121,12 @@ export class WebContentsEventManager {
|
||||
};
|
||||
};
|
||||
|
||||
denyNewWindow = (details: Electron.HandlerDetails): {action: 'deny' | 'allow'} => {
|
||||
private denyNewWindow = (details: Electron.HandlerDetails): {action: 'deny' | 'allow'} => {
|
||||
this.log().warn(`Prevented popup window to open a new window to ${details.url}.`);
|
||||
return {action: 'deny'};
|
||||
};
|
||||
|
||||
generateNewWindowListener = (webContentsId: number, spellcheck?: boolean) => {
|
||||
private generateNewWindowListener = (webContentsId: number, spellcheck?: boolean) => {
|
||||
return (details: Electron.HandlerDetails): {action: 'deny' | 'allow'} => {
|
||||
this.log(webContentsId).debug('new-window', details.url);
|
||||
|
||||
@@ -199,7 +206,7 @@ export class WebContentsEventManager {
|
||||
this.popupWindow = {
|
||||
win: new BrowserWindow({
|
||||
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
//parent: WindowManager.getMainWindow(),
|
||||
parent: MainWindow.get(),
|
||||
show: false,
|
||||
center: true,
|
||||
webPreferences: {
|
||||
@@ -250,7 +257,7 @@ export class WebContentsEventManager {
|
||||
return {action: 'deny'};
|
||||
}
|
||||
|
||||
const otherServerURL = ViewManager.getViewByURL(parsedURL);
|
||||
const otherServerURL = ServerManager.lookupTabByURL(parsedURL);
|
||||
if (otherServerURL && urlUtils.isTeamUrl(otherServerURL.server.url, parsedURL, true)) {
|
||||
WindowManager.showMainWindow(parsedURL);
|
||||
return {action: 'deny'};
|
||||
|
@@ -645,7 +645,6 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
thumbnail: {
|
||||
toDataURL: jest.fn(),
|
||||
},
|
||||
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -741,7 +740,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
id: 'server-1',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
@@ -807,7 +806,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-2',
|
||||
id: 'server-2',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
@@ -874,7 +873,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-2',
|
||||
id: 'server-2',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
@@ -901,7 +900,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
const view = {
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
id: 'server-1',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
|
@@ -80,8 +80,8 @@ export class CallsWidgetWindow {
|
||||
return this.options?.callID;
|
||||
}
|
||||
|
||||
private get serverName() {
|
||||
return this.mainView?.tab.server.name;
|
||||
private get serverID() {
|
||||
return this.mainView?.tab.server.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,11 +450,11 @@ export class CallsWidgetWindow {
|
||||
private handleDesktopSourcesModalRequest = () => {
|
||||
log.debug('handleDesktopSourcesModalRequest');
|
||||
|
||||
if (!this.serverName) {
|
||||
if (!this.serverID) {
|
||||
return;
|
||||
}
|
||||
|
||||
WindowManager.switchServer(this.serverName);
|
||||
WindowManager.switchServer(this.serverID);
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(DESKTOP_SOURCES_MODAL_REQUEST);
|
||||
}
|
||||
@@ -468,11 +468,11 @@ export class CallsWidgetWindow {
|
||||
private handleCallsWidgetChannelLinkClick = () => {
|
||||
log.debug('handleCallsWidgetChannelLinkClick');
|
||||
|
||||
if (!this.serverName) {
|
||||
if (!this.serverID) {
|
||||
return;
|
||||
}
|
||||
|
||||
WindowManager.switchServer(this.serverName);
|
||||
WindowManager.switchServer(this.serverID);
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, this.options?.channelURL);
|
||||
}
|
||||
@@ -480,11 +480,11 @@ export class CallsWidgetWindow {
|
||||
private handleCallsError = (_: string, msg: CallsErrorMessage) => {
|
||||
log.debug('handleCallsError', msg);
|
||||
|
||||
if (!this.serverName) {
|
||||
if (!this.serverID) {
|
||||
return;
|
||||
}
|
||||
|
||||
WindowManager.switchServer(this.serverName);
|
||||
WindowManager.switchServer(this.serverID);
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(CALLS_ERROR, msg);
|
||||
}
|
||||
@@ -492,11 +492,11 @@ export class CallsWidgetWindow {
|
||||
private handleCallsLinkClick = (_: string, msg: CallsLinkClickMessage) => {
|
||||
log.debug('handleCallsLinkClick with linkURL', msg.link);
|
||||
|
||||
if (!this.serverName) {
|
||||
if (!this.serverID) {
|
||||
return;
|
||||
}
|
||||
|
||||
WindowManager.switchServer(this.serverName);
|
||||
WindowManager.switchServer(this.serverID);
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, msg.link);
|
||||
}
|
||||
|
@@ -11,9 +11,10 @@ import {app, BrowserWindow, BrowserWindowConstructorOptions, dialog, Event, glob
|
||||
|
||||
import {SavedWindowState} from 'types/mainWindow';
|
||||
|
||||
import {SELECT_NEXT_TAB, SELECT_PREVIOUS_TAB, GET_FULL_SCREEN_STATUS, FOCUS_THREE_DOT_MENU} from 'common/communication';
|
||||
import {SELECT_NEXT_TAB, SELECT_PREVIOUS_TAB, GET_FULL_SCREEN_STATUS, FOCUS_THREE_DOT_MENU, SERVERS_UPDATE} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MINIMUM_WINDOW_HEIGHT, MINIMUM_WINDOW_WIDTH} from 'common/utils/constants';
|
||||
import Utils from 'common/utils/util';
|
||||
import * as Validator from 'common/Validator';
|
||||
@@ -39,6 +40,8 @@ export class MainWindow {
|
||||
this.savedWindowState = this.getSavedWindowState();
|
||||
|
||||
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
|
||||
|
||||
ServerManager.on(SERVERS_UPDATE, this.handleUpdateConfig);
|
||||
}
|
||||
|
||||
init = () => {
|
||||
@@ -321,6 +324,13 @@ export class MainWindow {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Manager update handler
|
||||
*/
|
||||
private handleUpdateConfig = () => {
|
||||
this.win?.webContents.send(SERVERS_UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
const mainWindow = new MainWindow();
|
||||
|
@@ -6,13 +6,13 @@
|
||||
|
||||
import {systemPreferences} from 'electron';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {getTabViewName} from 'common/tabs/TabView';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {getAdjustedWindowBoundaries} from 'main/utils';
|
||||
import LoadingScreen from '../views/loadingScreen';
|
||||
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import ViewManager from '../views/viewManager';
|
||||
import LoadingScreen from '../views/loadingScreen';
|
||||
|
||||
import {WindowManager} from './windowManager';
|
||||
import MainWindow from './mainWindow';
|
||||
@@ -56,10 +56,10 @@ jest.mock('../utils', () => ({
|
||||
resetScreensharePermissionsMacOS: jest.fn(),
|
||||
}));
|
||||
jest.mock('../views/viewManager', () => ({
|
||||
isLoadingScreenHidden: jest.fn(),
|
||||
getView: jest.fn(),
|
||||
getViewByWebContentsId: jest.fn(),
|
||||
reloadConfiguration: jest.fn(),
|
||||
showById: jest.fn(),
|
||||
getCurrentView: jest.fn(),
|
||||
getView: jest.fn(),
|
||||
isViewClosed: jest.fn(),
|
||||
openClosedTab: jest.fn(),
|
||||
handleDeepLink: jest.fn(),
|
||||
@@ -81,12 +81,36 @@ jest.mock('./settingsWindow', () => ({
|
||||
}));
|
||||
jest.mock('./mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
focus: 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(),
|
||||
@@ -159,6 +183,7 @@ describe('main/windows/windowManager', () => {
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(mainWindow);
|
||||
jest.useFakeTimers();
|
||||
MainWindow.get.mockReturnValue(mainWindow);
|
||||
ViewManager.getCurrentView.mockReturnValue(view);
|
||||
getAdjustedWindowBoundaries.mockImplementation((width, height) => ({width, height}));
|
||||
});
|
||||
@@ -208,19 +233,18 @@ describe('main/windows/windowManager', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
windowManager.teamDropdown = {
|
||||
updateWindowBounds: jest.fn(),
|
||||
};
|
||||
const mainWindow = {
|
||||
getContentBounds: () => ({width: 1000, height: 900}),
|
||||
getSize: () => [1000, 900],
|
||||
};
|
||||
windowManager.teamDropdown = {
|
||||
updateWindowBounds: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ViewManager.getCurrentView.mockReturnValue(view);
|
||||
ViewManager.isLoadingScreenHidden.mockReturnValue(true);
|
||||
MainWindow.get.mockReturnValue(mainWindow);
|
||||
LoadingScreen.isHidden.mockReturnValue(true);
|
||||
ViewManager.getCurrentView.mockReturnValue(view);
|
||||
getAdjustedWindowBoundaries.mockImplementation((width, height) => ({width, height}));
|
||||
});
|
||||
|
||||
@@ -331,7 +355,6 @@ describe('main/windows/windowManager', () => {
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
delete windowManager.settingsWindow;
|
||||
});
|
||||
|
||||
it('should restore main window if minimized', () => {
|
||||
@@ -409,8 +432,6 @@ describe('main/windows/windowManager', () => {
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
delete windowManager.mainWindow;
|
||||
delete windowManager.settingsWindow;
|
||||
});
|
||||
|
||||
it('should do nothing when the windows arent set', () => {
|
||||
@@ -458,68 +479,35 @@ describe('main/windows/windowManager', () => {
|
||||
|
||||
describe('switchServer', () => {
|
||||
const windowManager = new WindowManager();
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 2,
|
||||
},
|
||||
];
|
||||
const map = servers.reduce((arr, item) => {
|
||||
item.tabs.forEach((tab) => {
|
||||
arr.push([`${item.name}_${tab.name}`, {}]);
|
||||
});
|
||||
return arr;
|
||||
}, []);
|
||||
const views = new Map(map);
|
||||
const views = new Map([
|
||||
['tab-1', {id: 'tab-1'}],
|
||||
['tab-2', {id: 'tab-2'}],
|
||||
['tab-3', {id: 'tab-3'}],
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
getTabViewName.mockImplementation((server, tab) => `${server}_${tab}`);
|
||||
Config.teams = servers.concat();
|
||||
ViewManager.getView.mockImplementation((name) => views.get(name));
|
||||
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();
|
||||
Config.teams = [];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -531,30 +519,33 @@ describe('main/windows/windowManager', () => {
|
||||
it('should do nothing if cannot find the server', () => {
|
||||
windowManager.switchServer('server-3');
|
||||
expect(getTabViewName).not.toBeCalled();
|
||||
expect(ViewManager.showByName).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.showByName).toHaveBeenCalledWith('server-1_tab-3');
|
||||
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.showByName).toHaveBeenCalledWith('server-2_tab-2');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('tab-2');
|
||||
});
|
||||
|
||||
it('should wait for view to exist if specified', () => {
|
||||
views.delete('server-1_tab-3');
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
||||
views.delete('tab-3');
|
||||
windowManager.switchServer('server-1', true);
|
||||
expect(ViewManager.showByName).not.toBeCalled();
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
expect(ViewManager.showByName).not.toBeCalled();
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
|
||||
views.set('server-1_tab-3', {});
|
||||
views.set('tab-3', {});
|
||||
jest.advanceTimersByTime(200);
|
||||
expect(ViewManager.showByName).toBeCalledWith('server-1_tab-3');
|
||||
expect(ViewManager.showById).toBeCalledWith('tab-3');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -563,75 +554,69 @@ describe('main/windows/windowManager', () => {
|
||||
windowManager.switchTab = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
Config.teams = [
|
||||
const tabs = [
|
||||
{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
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();
|
||||
Config.teams = [];
|
||||
});
|
||||
|
||||
it('should select next server when open', () => {
|
||||
ViewManager.getCurrentView.mockReturnValue({
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
id: 'server-1',
|
||||
},
|
||||
type: 'tab-3',
|
||||
},
|
||||
});
|
||||
|
||||
windowManager.selectTab((order) => order + 1);
|
||||
expect(windowManager.switchTab).toBeCalledWith('server-1', 'tab-2');
|
||||
expect(windowManager.switchTab).toBeCalledWith('tab-2');
|
||||
});
|
||||
|
||||
it('should select previous server when open', () => {
|
||||
ViewManager.getCurrentView.mockReturnValue({
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
id: 'server-1',
|
||||
},
|
||||
type: 'tab-2',
|
||||
},
|
||||
});
|
||||
|
||||
windowManager.selectTab((order, length) => (length + (order - 1)));
|
||||
expect(windowManager.switchTab).toBeCalledWith('server-1', 'tab-3');
|
||||
expect(windowManager.switchTab).toBeCalledWith('tab-3');
|
||||
});
|
||||
|
||||
it('should skip over closed tab', () => {
|
||||
ViewManager.getCurrentView.mockReturnValue({
|
||||
tab: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
id: 'server-1',
|
||||
},
|
||||
type: 'tab-2',
|
||||
},
|
||||
});
|
||||
windowManager.selectTab((order) => order + 1);
|
||||
expect(windowManager.switchTab).toBeCalledWith('server-1', 'tab-3');
|
||||
expect(windowManager.switchTab).toBeCalledWith('tab-3');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -639,6 +624,7 @@ describe('main/windows/windowManager', () => {
|
||||
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');
|
||||
|
@@ -2,25 +2,26 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
import path from 'path';
|
||||
|
||||
import {app, BrowserWindow, systemPreferences, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserWindow, systemPreferences, ipcMain, IpcMainEvent} from 'electron';
|
||||
|
||||
import {
|
||||
MAXIMIZE_CHANGE,
|
||||
FOCUS_THREE_DOT_MENU,
|
||||
GET_DARK_MODE,
|
||||
UPDATE_SHORTCUT_MENU,
|
||||
GET_VIEW_WEBCONTENTS_ID,
|
||||
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 {getTabViewName} from 'common/tabs/TabView';
|
||||
|
||||
import {MattermostView} from 'main/views/MattermostView';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {
|
||||
getAdjustedWindowBoundaries,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
|
||||
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';
|
||||
@@ -42,20 +44,22 @@ import SettingsWindow from './settingsWindow';
|
||||
const log = new Logger('WindowManager');
|
||||
|
||||
export class WindowManager {
|
||||
assetsDir: string;
|
||||
private teamDropdown?: TeamDropdownView;
|
||||
private downloadsDropdown?: DownloadsDropdownView;
|
||||
private downloadsDropdownMenu?: DownloadsDropdownMenuView;
|
||||
|
||||
teamDropdown?: TeamDropdownView;
|
||||
downloadsDropdown?: DownloadsDropdownView;
|
||||
downloadsDropdownMenu?: DownloadsDropdownMenuView;
|
||||
currentServerName?: string;
|
||||
missingScreensharePermissions?: boolean;
|
||||
private isResizing: boolean;
|
||||
|
||||
constructor() {
|
||||
this.assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||
this.isResizing = false;
|
||||
|
||||
ipcMain.handle(GET_DARK_MODE, this.handleGetDarkMode);
|
||||
ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId);
|
||||
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) => {
|
||||
@@ -102,21 +106,153 @@ export class WindowManager {
|
||||
this.initializeViewManager();
|
||||
}
|
||||
|
||||
handleMaximizeMainWindow = () => {
|
||||
// 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 = () => {
|
||||
this.downloadsDropdown?.updateWindowBounds();
|
||||
this.downloadsDropdownMenu?.updateWindowBounds();
|
||||
this.sendToRenderer(MAXIMIZE_CHANGE, true);
|
||||
}
|
||||
|
||||
handleUnmaximizeMainWindow = () => {
|
||||
private handleUnmaximizeMainWindow = () => {
|
||||
this.downloadsDropdown?.updateWindowBounds();
|
||||
this.downloadsDropdownMenu?.updateWindowBounds();
|
||||
this.sendToRenderer(MAXIMIZE_CHANGE, false);
|
||||
}
|
||||
|
||||
isResizing = false;
|
||||
|
||||
handleWillResizeMainWindow = (event: Event, newBounds: Electron.Rectangle) => {
|
||||
private handleWillResizeMainWindow = (event: Event, newBounds: Electron.Rectangle) => {
|
||||
log.silly('handleWillResizeMainWindow');
|
||||
|
||||
if (!MainWindow.get()) {
|
||||
@@ -146,21 +282,15 @@ export class WindowManager {
|
||||
ipcMain.emit(RESIZE_MODAL, null, newBounds);
|
||||
}
|
||||
|
||||
handleResizedMainWindow = () => {
|
||||
private handleResizedMainWindow = () => {
|
||||
log.silly('handleResizedMainWindow');
|
||||
|
||||
if (MainWindow.get()) {
|
||||
const bounds = this.getBounds();
|
||||
this.throttledWillResize(bounds);
|
||||
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
||||
this.teamDropdown?.updateWindowBounds();
|
||||
this.downloadsDropdown?.updateWindowBounds();
|
||||
this.downloadsDropdownMenu?.updateWindowBounds();
|
||||
}
|
||||
this.isResizing = false;
|
||||
}
|
||||
|
||||
handleViewFinishedResizing = () => {
|
||||
const bounds = this.getBounds();
|
||||
this.throttledWillResize(bounds);
|
||||
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
||||
this.teamDropdown?.updateWindowBounds();
|
||||
this.downloadsDropdown?.updateWindowBounds();
|
||||
this.downloadsDropdownMenu?.updateWindowBounds();
|
||||
this.isResizing = false;
|
||||
}
|
||||
|
||||
@@ -171,7 +301,7 @@ export class WindowManager {
|
||||
this.setCurrentViewBounds(newBounds);
|
||||
}
|
||||
|
||||
handleResizeMainWindow = () => {
|
||||
private handleResizeMainWindow = () => {
|
||||
log.silly('handleResizeMainWindow');
|
||||
|
||||
if (!MainWindow.get()) {
|
||||
@@ -194,7 +324,7 @@ export class WindowManager {
|
||||
ipcMain.emit(RESIZE_MODAL, null, bounds);
|
||||
};
|
||||
|
||||
setCurrentViewBounds = (bounds: {width: number; height: number}) => {
|
||||
private setCurrentViewBounds = (bounds: {width: number; height: number}) => {
|
||||
log.debug('setCurrentViewBounds', {bounds});
|
||||
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
@@ -228,64 +358,46 @@ export class WindowManager {
|
||||
return bounds as Electron.Rectangle;
|
||||
}
|
||||
|
||||
// max retries allows the message to get to the renderer even if it is sent while the app is starting up.
|
||||
sendToRendererWithRetry = (maxRetries: number, channel: string, ...args: unknown[]) => {
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow || !MainWindow.isReady) {
|
||||
if (maxRetries > 0) {
|
||||
log.info(`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;
|
||||
/*****************
|
||||
* 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();
|
||||
}
|
||||
mainWindow.webContents.send(channel, ...args);
|
||||
SettingsWindow.get()?.webContents.send(channel, ...args);
|
||||
}
|
||||
|
||||
sendToRenderer = (channel: string, ...args: unknown[]) => {
|
||||
this.sendToRendererWithRetry(3, channel, ...args);
|
||||
}
|
||||
|
||||
sendToAll = (channel: string, ...args: unknown[]) => {
|
||||
this.sendToRenderer(channel, ...args);
|
||||
SettingsWindow.get()?.webContents.send(channel, ...args);
|
||||
|
||||
// TODO: should we include popups?
|
||||
}
|
||||
|
||||
restoreMain = () => {
|
||||
log.info('restoreMain');
|
||||
|
||||
const mainWindow = MainWindow.get(true);
|
||||
if (!mainWindow) {
|
||||
throw new Error('Main window does not exist');
|
||||
private handleMinimize = () => {
|
||||
const focused = BrowserWindow.getFocusedWindow();
|
||||
if (focused) {
|
||||
focused.minimize();
|
||||
}
|
||||
|
||||
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 handleRestore = () => {
|
||||
const focused = BrowserWindow.getFocusedWindow();
|
||||
if (focused) {
|
||||
focused.restore();
|
||||
}
|
||||
if (focused?.isFullScreen()) {
|
||||
focused.setFullScreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
handleDoubleClick = (e: IpcMainEvent, windowType?: string) => {
|
||||
log.debug('WindowManager.handleDoubleClick', windowType);
|
||||
log.debug('handleDoubleClick', windowType);
|
||||
|
||||
let action = 'Maximize';
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -313,150 +425,6 @@ export class WindowManager {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
initializeViewManager = () => {
|
||||
ViewManager.init();
|
||||
this.initializeCurrentServerName();
|
||||
}
|
||||
|
||||
initializeCurrentServerName = () => {
|
||||
if (!this.currentServerName) {
|
||||
this.currentServerName = (Config.teams.find((team) => team.order === Config.lastActiveTeam) || Config.teams.find((team) => team.order === 0))?.name;
|
||||
}
|
||||
}
|
||||
|
||||
switchServer = (serverName: string, waitForViewToExist = false) => {
|
||||
log.debug('switchServer');
|
||||
this.showMainWindow();
|
||||
const server = Config.teams.find((team) => team.name === serverName);
|
||||
if (!server) {
|
||||
log.error('Cannot find server in config');
|
||||
return;
|
||||
}
|
||||
this.currentServerName = serverName;
|
||||
let nextTab = server.tabs.find((tab) => tab.isOpen && tab.order === (server.lastActiveTab || 0));
|
||||
if (!nextTab) {
|
||||
const openTabs = server.tabs.filter((tab) => tab.isOpen);
|
||||
nextTab = openTabs.find((e) => e.order === 0) || openTabs.concat().sort((a, b) => a.order - b.order)[0];
|
||||
}
|
||||
const tabViewName = getTabViewName(serverName, nextTab.name);
|
||||
if (waitForViewToExist) {
|
||||
const timeout = setInterval(() => {
|
||||
if (ViewManager.getView(tabViewName)) {
|
||||
ViewManager.showByName(tabViewName);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
ViewManager.showByName(tabViewName);
|
||||
}
|
||||
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
||||
}
|
||||
|
||||
switchTab = (serverName: string, tabName: string) => {
|
||||
log.debug('switchTab');
|
||||
this.showMainWindow();
|
||||
const tabViewName = getTabViewName(serverName, tabName);
|
||||
ViewManager.showByName(tabViewName);
|
||||
}
|
||||
|
||||
focusThreeDotMenu = () => {
|
||||
MainWindow.get()?.webContents.focus();
|
||||
MainWindow.get()?.webContents.send(FOCUS_THREE_DOT_MENU);
|
||||
}
|
||||
|
||||
handleLoadingScreenDataRequest = () => {
|
||||
return {
|
||||
darkMode: Config.darkMode || false,
|
||||
};
|
||||
}
|
||||
|
||||
close = () => {
|
||||
const focused = BrowserWindow.getFocusedWindow();
|
||||
focused?.close();
|
||||
}
|
||||
maximize = () => {
|
||||
const focused = BrowserWindow.getFocusedWindow();
|
||||
if (focused) {
|
||||
focused.maximize();
|
||||
}
|
||||
}
|
||||
minimize = () => {
|
||||
const focused = BrowserWindow.getFocusedWindow();
|
||||
if (focused) {
|
||||
focused.minimize();
|
||||
}
|
||||
}
|
||||
restore = () => {
|
||||
const focused = BrowserWindow.getFocusedWindow();
|
||||
if (focused) {
|
||||
focused.restore();
|
||||
}
|
||||
if (focused?.isFullScreen()) {
|
||||
focused.setFullScreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
reload = () => {
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (currentView) {
|
||||
LoadingScreen.show();
|
||||
currentView.reload();
|
||||
}
|
||||
}
|
||||
|
||||
selectNextTab = () => {
|
||||
this.selectTab((order) => order + 1);
|
||||
}
|
||||
|
||||
selectPreviousTab = () => {
|
||||
this.selectTab((order, length) => (length + (order - 1)));
|
||||
}
|
||||
|
||||
selectTab = (fn: (order: number, length: number) => number) => {
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTeamTabs = Config.teams.find((team) => team.name === currentView.tab.server.name)?.tabs;
|
||||
const filteredTabs = currentTeamTabs?.filter((tab) => tab.isOpen);
|
||||
const currentTab = currentTeamTabs?.find((tab) => tab.name === currentView.tab.type);
|
||||
if (!currentTeamTabs || !currentTab || !filteredTabs) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentOrder = currentTab.order;
|
||||
let nextIndex = -1;
|
||||
while (nextIndex === -1) {
|
||||
const nextOrder = (fn(currentOrder, currentTeamTabs.length) % currentTeamTabs.length);
|
||||
nextIndex = filteredTabs.findIndex((tab) => tab.order === nextOrder);
|
||||
currentOrder = nextOrder;
|
||||
}
|
||||
|
||||
const newTab = filteredTabs[nextIndex];
|
||||
this.switchTab(currentView.tab.server.name, newTab.name);
|
||||
}
|
||||
|
||||
handleGetDarkMode = () => {
|
||||
return Config.darkMode;
|
||||
}
|
||||
|
||||
getCurrentTeamName = () => {
|
||||
return this.currentServerName;
|
||||
}
|
||||
|
||||
handleGetWebContentsId = (event: IpcMainInvokeEvent) => {
|
||||
return event.sender.id;
|
||||
}
|
||||
|
||||
getServerURLFromWebContentsId = (id: number) => {
|
||||
if (CallsWidgetWindow.isCallsWidget(id)) {
|
||||
return CallsWidgetWindow.getURL();
|
||||
}
|
||||
|
||||
return ViewManager.getViewByWebContentsId(id)?.tab.server.url;
|
||||
}
|
||||
}
|
||||
|
||||
const windowManager = new WindowManager();
|
||||
|
Reference in New Issue
Block a user