[MM-14093] Rename 'team' to 'server' and 'tab' to 'view' in most cases, some additional cleanup (#2711)

* Rename MattermostTeam -> UniqueServer, MattermostTab -> UniqueView

* Rename 'team' to 'server'

* Some further cleanup

* Rename weirdly named function

* Rename 'tab' to 'view' in most instances

* Fix i18n

* PR feedback
This commit is contained in:
Devin Binnie
2023-05-08 09:17:01 -04:00
committed by GitHub
parent 9f75ddcf0f
commit 316beba950
110 changed files with 1698 additions and 1757 deletions

View File

@@ -95,9 +95,9 @@ describe('main/app/app', () => {
const promise = Promise.resolve({});
const certificate = {};
const view = {
tab: {
view: {
server: {
name: 'test-team',
name: 'test-server',
url: new URL(testURL),
},
},
@@ -163,7 +163,7 @@ describe('main/app/app', () => {
expect(CertificateStore.save).toHaveBeenCalled();
});
it('should load URL using MattermostView when trusting certificate', async () => {
it('should load URL using MattermostBrowserView when trusting certificate', async () => {
dialog.showMessageBox.mockResolvedValue({response: 0});
await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback);
expect(callback).toHaveBeenCalledWith(true);

View File

@@ -96,8 +96,8 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
const errorID = `${parsedURL.origin}:${error}`;
const view = ViewManager.getViewByWebContentsId(webContents.id);
if (view?.tab.server) {
const serverURL = parseURL(view.tab.server.url);
if (view?.view.server) {
const serverURL = parseURL(view.view.server.url);
if (serverURL && serverURL.origin !== parsedURL.origin) {
log.warn(`Ignoring certificate for unmatched origin ${parsedURL.origin}, will not trust`);
callback(false);

View File

@@ -93,14 +93,14 @@ describe('main/app/config', () => {
});
});
it('should recheck teams after config update if registry data is pulled in', () => {
it('should recheck servers after config update if registry data is pulled in', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'win32',
});
Config.registryConfigData = {};
handleConfigUpdate({teams: []});
handleConfigUpdate({servers: []});
expect(handleMainWindowIsShown).toHaveBeenCalled();
Object.defineProperty(process, 'platform', {

View File

@@ -6,7 +6,7 @@
import {initialize} from './initialize';
// TODO: Singletons, we need DI :D
import('main/views/teamDropdownView');
import('main/views/serverDropdownView');
import('main/views/downloadsDropdownMenuView');
import('main/views/downloadsDropdownView');

View File

@@ -278,7 +278,7 @@ describe('main/app/initialize', () => {
it('should allow permission requests for supported types from trusted URLs', async () => {
ViewManager.getViewByWebContentsId.mockReturnValue({
tab: {
view: {
server: {
url: new URL('http://server-1.com'),
},

View File

@@ -14,8 +14,8 @@ import {
SHOW_NEW_SERVER_MODAL,
NOTIFY_MENTION,
SWITCH_TAB,
CLOSE_TAB,
OPEN_TAB,
CLOSE_VIEW,
OPEN_VIEW,
SHOW_EDIT_SERVER_MODAL,
SHOW_REMOVE_SERVER_MODAL,
UPDATE_SHORTCUT_MENU,
@@ -100,8 +100,8 @@ import {
switchServer,
} from './servers';
import {
handleCloseTab, handleGetLastActive, handleGetOrderedTabsForServer, handleOpenTab,
} from './tabs';
handleCloseView, handleGetLastActive, handleGetOrderedViewsForServer, handleOpenView,
} from './views';
import {
clearAppCache,
getDeeplinkingURL,
@@ -279,8 +279,8 @@ function initializeInterCommunicationEventListeners() {
ipcMain.on(SWITCH_SERVER, (event, serverId) => switchServer(serverId));
ipcMain.on(SWITCH_TAB, (event, viewId) => ViewManager.showById(viewId));
ipcMain.on(CLOSE_TAB, handleCloseTab);
ipcMain.on(OPEN_TAB, handleOpenTab);
ipcMain.on(CLOSE_VIEW, handleCloseView);
ipcMain.on(OPEN_VIEW, handleOpenView);
ipcMain.on(QUIT, handleQuit);
@@ -296,10 +296,10 @@ function initializeInterCommunicationEventListeners() {
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.on(UPDATE_TAB_ORDER, (event, serverId, viewOrder) => ServerManager.updateTabOrder(serverId, viewOrder));
ipcMain.handle(GET_LAST_ACTIVE, handleGetLastActive);
ipcMain.handle(GET_ORDERED_SERVERS, () => ServerManager.getOrderedServers().map((srv) => srv.toMattermostTeam()));
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, handleGetOrderedTabsForServer);
ipcMain.handle(GET_ORDERED_SERVERS, () => ServerManager.getOrderedServers().map((srv) => srv.toUniqueServer()));
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, handleGetOrderedViewsForServer);
ipcMain.handle(GET_DARK_MODE, handleGetDarkMode);
ipcMain.on(WINDOW_CLOSE, handleClose);
@@ -453,7 +453,7 @@ async function initializeAfterAppReady() {
}
const requestingURL = webContents.getURL();
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.view.server.url;
if (!serverURL) {
callback(false);

View File

@@ -16,14 +16,14 @@ jest.mock('common/config', () => ({
}));
jest.mock('main/notifications', () => ({}));
jest.mock('common/servers/serverManager', () => ({
setTabIsOpen: jest.fn(),
setViewIsOpen: jest.fn(),
getAllServers: jest.fn(),
hasServers: jest.fn(),
addServer: jest.fn(),
editServer: jest.fn(),
removeServer: jest.fn(),
getServer: jest.fn(),
getTab: jest.fn(),
getView: jest.fn(),
getLastActiveTabForServer: jest.fn(),
}));
jest.mock('main/utils', () => ({

View File

@@ -3,7 +3,7 @@
import {app, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
import {MattermostTeam} from 'types/config';
import {UniqueServer} from 'types/config';
import {MentionData} from 'types/notification';
import {Logger} from 'common/log';
@@ -93,11 +93,11 @@ export function handleWelcomeScreenModal() {
if (!mainWindow) {
return;
}
const modalPromise = ModalManager.addModal<MattermostTeam[], MattermostTeam>('welcomeScreen', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
const modalPromise = ModalManager.addModal<UniqueServer[], UniqueServer>('welcomeScreen', html, preload, ServerManager.getAllServers().map((server) => server.toUniqueServer()), mainWindow, !ServerManager.hasServers());
if (modalPromise) {
modalPromise.then((data) => {
const newTeam = ServerManager.addServer(data);
switchServer(newTeam.id, true);
const newServer = ServerManager.addServer(data);
switchServer(newServer.id, true);
}).catch((e) => {
// e is undefined for user cancellation
if (e) {

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import ServerManager from 'common/servers/serverManager';
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
import {getDefaultViewsForConfigServer} from 'common/views/View';
import ModalManager from 'main/views/modalManager';
import {getLocalURLString, getLocalPreload} from 'main/utils';
@@ -18,19 +18,19 @@ jest.mock('electron', () => ({
}));
jest.mock('common/servers/serverManager', () => ({
setTabIsOpen: jest.fn(),
setViewIsOpen: jest.fn(),
getAllServers: jest.fn(),
hasServers: jest.fn(),
addServer: jest.fn(),
editServer: jest.fn(),
removeServer: jest.fn(),
getServer: jest.fn(),
getTab: jest.fn(),
getView: jest.fn(),
getLastActiveTabForServer: jest.fn(),
getServerLog: jest.fn(),
}));
jest.mock('common/tabs/TabView', () => ({
getDefaultConfigTeamFromTeam: jest.fn(),
jest.mock('common/views/View', () => ({
getDefaultViewsForConfigServer: jest.fn(),
}));
jest.mock('main/views/modalManager', () => ({
addModal: jest.fn(),
@@ -50,22 +50,22 @@ jest.mock('main/views/viewManager', () => ({
const tabs = [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
{
name: 'tab-3',
name: 'view-3',
order: 1,
isOpen: true,
},
];
const teams = [
const servers = [
{
id: 'server-1',
name: 'server-1',
@@ -77,9 +77,9 @@ const teams = [
describe('main/app/servers', () => {
describe('switchServer', () => {
const views = new Map([
['tab-1', {id: 'tab-1'}],
['tab-2', {id: 'tab-2'}],
['tab-3', {id: 'tab-3'}],
['view-1', {id: 'view-1'}],
['view-2', {id: 'view-2'}],
['view-3', {id: 'view-3'}],
]);
beforeEach(() => {
@@ -119,69 +119,69 @@ describe('main/app/servers', () => {
expect(ViewManager.showById).not.toBeCalled();
});
it('should show first open tab in order when last active not defined', () => {
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
it('should show first open view in order when last active not defined', () => {
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-3'});
Servers.switchServer('server-1');
expect(ViewManager.showById).toHaveBeenCalledWith('tab-3');
expect(ViewManager.showById).toHaveBeenCalledWith('view-3');
});
it('should show last active tab of chosen server', () => {
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
it('should show last active view of chosen server', () => {
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-2'});
Servers.switchServer('server-2');
expect(ViewManager.showById).toHaveBeenCalledWith('tab-2');
expect(ViewManager.showById).toHaveBeenCalledWith('view-2');
});
it('should wait for view to exist if specified', () => {
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
views.delete('tab-3');
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-3'});
views.delete('view-3');
Servers.switchServer('server-1', true);
expect(ViewManager.showById).not.toBeCalled();
jest.advanceTimersByTime(200);
expect(ViewManager.showById).not.toBeCalled();
views.set('tab-3', {});
views.set('view-3', {});
jest.advanceTimersByTime(200);
expect(ViewManager.showById).toBeCalledWith('tab-3');
expect(ViewManager.showById).toBeCalledWith('view-3');
});
});
describe('handleNewServerModal', () => {
let teamsCopy;
let serversCopy;
beforeEach(() => {
getLocalURLString.mockReturnValue('/some/index.html');
getLocalPreload.mockReturnValue('/some/preload.js');
MainWindow.get.mockReturnValue({});
teamsCopy = JSON.parse(JSON.stringify(teams));
serversCopy = JSON.parse(JSON.stringify(servers));
ServerManager.getAllServers.mockReturnValue([]);
ServerManager.addServer.mockImplementation(() => {
const newTeam = {
const newServer = {
id: 'server-1',
name: 'new-team',
url: 'http://new-team.com',
name: 'new-server',
url: 'http://new-server.com',
tabs,
};
teamsCopy = [
...teamsCopy,
newTeam,
serversCopy = [
...serversCopy,
newServer,
];
return newTeam;
return newServer;
});
ServerManager.hasServers.mockReturnValue(Boolean(teamsCopy.length));
ServerManager.hasServers.mockReturnValue(Boolean(serversCopy.length));
ServerManager.getServerLog.mockReturnValue({debug: jest.fn(), error: jest.fn()});
getDefaultConfigTeamFromTeam.mockImplementation((team) => ({
...team,
getDefaultViewsForConfigServer.mockImplementation((server) => ({
...server,
tabs,
}));
});
it('should add new team to the config', async () => {
it('should add new server to the config', async () => {
const data = {
name: 'new-team',
url: 'http://new-team.com',
name: 'new-server',
url: 'http://new-server.com',
};
const promise = Promise.resolve(data);
ModalManager.addModal.mockReturnValue(promise);
@@ -190,10 +190,10 @@ describe('main/app/servers', () => {
await promise;
expect(ServerManager.addServer).toHaveBeenCalledWith(data);
expect(teamsCopy).toContainEqual(expect.objectContaining({
expect(serversCopy).toContainEqual(expect.objectContaining({
id: 'server-1',
name: 'new-team',
url: 'http://new-team.com',
name: 'new-server',
url: 'http://new-server.com',
tabs,
}));
@@ -203,31 +203,31 @@ describe('main/app/servers', () => {
});
describe('handleEditServerModal', () => {
let teamsCopy;
let serversCopy;
beforeEach(() => {
getLocalURLString.mockReturnValue('/some/index.html');
getLocalPreload.mockReturnValue('/some/preload.js');
MainWindow.get.mockReturnValue({});
teamsCopy = JSON.parse(JSON.stringify(teams));
serversCopy = JSON.parse(JSON.stringify(servers));
ServerManager.getServer.mockImplementation((id) => {
if (id !== teamsCopy[0].id) {
if (id !== serversCopy[0].id) {
return undefined;
}
return {...teamsCopy[0], toMattermostTeam: jest.fn()};
return {...serversCopy[0], toUniqueServer: jest.fn()};
});
ServerManager.editServer.mockImplementation((id, team) => {
if (id !== teamsCopy[0].id) {
ServerManager.editServer.mockImplementation((id, server) => {
if (id !== serversCopy[0].id) {
return;
}
const newTeam = {
...teamsCopy[0],
...team,
const newServer = {
...serversCopy[0],
...server,
};
teamsCopy = [newTeam];
serversCopy = [newServer];
});
ServerManager.getAllServers.mockReturnValue(teamsCopy.map((team) => ({...team, toMattermostTeam: jest.fn()})));
ServerManager.getAllServers.mockReturnValue(serversCopy.map((server) => ({...server, toUniqueServer: jest.fn()})));
});
it('should do nothing when the server cannot be found', () => {
@@ -235,58 +235,58 @@ describe('main/app/servers', () => {
expect(ModalManager.addModal).not.toBeCalled();
});
it('should edit the existing team', async () => {
it('should edit the existing server', async () => {
const promise = Promise.resolve({
name: 'new-team',
url: 'http://new-team.com',
name: 'new-server',
url: 'http://new-server.com',
});
ModalManager.addModal.mockReturnValue(promise);
Servers.handleEditServerModal(null, 'server-1');
await promise;
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
expect(serversCopy).not.toContainEqual(expect.objectContaining({
id: 'server-1',
name: 'server-1',
url: 'http://server-1.com',
tabs,
}));
expect(teamsCopy).toContainEqual(expect.objectContaining({
expect(serversCopy).toContainEqual(expect.objectContaining({
id: 'server-1',
name: 'new-team',
url: 'http://new-team.com',
name: 'new-server',
url: 'http://new-server.com',
tabs,
}));
});
});
describe('handleRemoveServerModal', () => {
let teamsCopy;
let serversCopy;
beforeEach(() => {
getLocalURLString.mockReturnValue('/some/index.html');
getLocalPreload.mockReturnValue('/some/preload.js');
MainWindow.get.mockReturnValue({});
teamsCopy = JSON.parse(JSON.stringify(teams));
serversCopy = JSON.parse(JSON.stringify(servers));
ServerManager.getServer.mockImplementation((id) => {
if (id !== teamsCopy[0].id) {
if (id !== serversCopy[0].id) {
return undefined;
}
return teamsCopy[0];
return serversCopy[0];
});
ServerManager.removeServer.mockImplementation(() => {
teamsCopy = [];
serversCopy = [];
});
ServerManager.getAllServers.mockReturnValue(teamsCopy);
ServerManager.getAllServers.mockReturnValue(serversCopy);
});
it('should remove the existing team', async () => {
it('should remove the existing server', async () => {
const promise = Promise.resolve(true);
ModalManager.addModal.mockReturnValue(promise);
Servers.handleRemoveServerModal(null, 'server-1');
await promise;
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
expect(serversCopy).not.toContainEqual(expect.objectContaining({
id: 'server-1',
name: 'server-1',
url: 'http://server-1.com',
@@ -294,11 +294,11 @@ describe('main/app/servers', () => {
}));
});
it('should not remove the existing team when clicking Cancel', async () => {
it('should not remove the existing server when clicking Cancel', async () => {
const promise = Promise.resolve(false);
ModalManager.addModal.mockReturnValue(promise);
expect(teamsCopy).toContainEqual(expect.objectContaining({
expect(serversCopy).toContainEqual(expect.objectContaining({
id: 'server-1',
name: 'server-1',
url: 'http://server-1.com',
@@ -307,7 +307,7 @@ describe('main/app/servers', () => {
Servers.handleRemoveServerModal(null, 'server-1');
await promise;
expect(teamsCopy).toContainEqual(expect.objectContaining({
expect(serversCopy).toContainEqual(expect.objectContaining({
id: 'server-1',
name: 'server-1',
url: 'http://server-1.com',

View File

@@ -3,7 +3,7 @@
import {IpcMainEvent, ipcMain} from 'electron';
import {MattermostTeam, Team} from 'types/config';
import {UniqueServer, Server} from 'types/config';
import {UPDATE_SHORTCUT_MENU} from 'common/communication';
import {Logger} from 'common/log';
@@ -24,16 +24,16 @@ export const switchServer = (serverId: string, waitForViewToExist = false) => {
ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config');
return;
}
const nextTab = ServerManager.getLastActiveTabForServer(serverId);
const nextView = ServerManager.getLastActiveTabForServer(serverId);
if (waitForViewToExist) {
const timeout = setInterval(() => {
if (ViewManager.getView(nextTab.id)) {
ViewManager.showById(nextTab.id);
if (ViewManager.getView(nextView.id)) {
ViewManager.showById(nextView.id);
clearInterval(timeout);
}
}, 100);
} else {
ViewManager.showById(nextTab.id);
ViewManager.showById(nextView.id);
}
ipcMain.emit(UPDATE_SHORTCUT_MENU);
};
@@ -49,11 +49,11 @@ export const handleNewServerModal = () => {
if (!mainWindow) {
return;
}
const modalPromise = ModalManager.addModal<MattermostTeam[], Team>('newServer', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
const modalPromise = ModalManager.addModal<UniqueServer[], Server>('newServer', html, preload, ServerManager.getAllServers().map((server) => server.toUniqueServer()), mainWindow, !ServerManager.hasServers());
if (modalPromise) {
modalPromise.then((data) => {
const newTeam = ServerManager.addServer(data);
switchServer(newTeam.id, true);
const newServer = ServerManager.addServer(data);
switchServer(newServer.id, true);
}).catch((e) => {
// e is undefined for user cancellation
if (e) {
@@ -80,13 +80,13 @@ export const handleEditServerModal = (e: IpcMainEvent, id: string) => {
if (!server) {
return;
}
const modalPromise = ModalManager.addModal<{currentTeams: MattermostTeam[]; team: MattermostTeam}, Team>(
const modalPromise = ModalManager.addModal<{currentServers: UniqueServer[]; server: UniqueServer}, Server>(
'editServer',
html,
preload,
{
currentTeams: ServerManager.getAllServers().map((team) => team.toMattermostTeam()),
team: server.toMattermostTeam(),
currentServers: ServerManager.getAllServers().map((server) => server.toUniqueServer()),
server: server.toUniqueServer(),
},
mainWindow);
if (modalPromise) {

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import ServerManager from 'common/servers/serverManager';
import ViewManager from 'main/views/viewManager';
import {
handleCloseTab,
handleOpenTab,
} from './tabs';
jest.mock('common/servers/serverManager', () => ({
setTabIsOpen: jest.fn(),
getTab: jest.fn(),
getLastActiveTabForServer: jest.fn(),
}));
jest.mock('main/views/viewManager', () => ({
showById: jest.fn(),
}));
describe('main/app/tabs', () => {
describe('handleCloseTab', () => {
it('should close the specified tab and switch to the next open tab', () => {
ServerManager.getTab.mockReturnValue({server: {id: 'server-1'}});
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
handleCloseTab(null, 'tab-3');
expect(ServerManager.setTabIsOpen).toBeCalledWith('tab-3', false);
expect(ViewManager.showById).toBeCalledWith('tab-2');
});
});
describe('handleOpenTab', () => {
it('should open the specified tab', () => {
handleOpenTab(null, 'tab-1');
expect(ViewManager.showById).toBeCalledWith('tab-1');
});
});
});

View File

@@ -1,73 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
import ServerManager from 'common/servers/serverManager';
import {Logger} from 'common/log';
import ViewManager from 'main/views/viewManager';
const log = new Logger('App.Tabs');
export const handleCloseTab = (event: IpcMainEvent, tabId: string) => {
log.debug('handleCloseTab', {tabId});
const tab = ServerManager.getTab(tabId);
if (!tab) {
return;
}
ServerManager.setTabIsOpen(tabId, false);
const nextTab = ServerManager.getLastActiveTabForServer(tab.server.id);
ViewManager.showById(nextTab.id);
};
export const handleOpenTab = (event: IpcMainEvent, tabId: string) => {
log.debug('handleOpenTab', {tabId});
ServerManager.setTabIsOpen(tabId, true);
ViewManager.showById(tabId);
};
export const selectNextTab = () => {
selectTab((order) => order + 1);
};
export const selectPreviousTab = () => {
selectTab((order, length) => (length + (order - 1)));
};
export const handleGetOrderedTabsForServer = (event: IpcMainInvokeEvent, serverId: string) => {
return ServerManager.getOrderedTabsForServer(serverId).map((tab) => tab.toMattermostTab());
};
export const handleGetLastActive = () => {
const server = ServerManager.getCurrentServer();
const tab = ServerManager.getLastActiveTabForServer(server.id);
return {server: server.id, tab: tab.id};
};
const selectTab = (fn: (order: number, length: number) => number) => {
const currentView = ViewManager.getCurrentView();
if (!currentView) {
return;
}
const currentTeamTabs = ServerManager.getOrderedTabsForServer(currentView.tab.server.id).map((tab, index) => ({tab, index}));
const filteredTabs = currentTeamTabs?.filter((tab) => tab.tab.isOpen);
const currentTab = currentTeamTabs?.find((tab) => tab.tab.type === currentView.tab.type);
if (!currentTeamTabs || !currentTab || !filteredTabs) {
return;
}
let currentOrder = currentTab.index;
let nextIndex = -1;
while (nextIndex === -1) {
const nextOrder = (fn(currentOrder, currentTeamTabs.length) % currentTeamTabs.length);
nextIndex = filteredTabs.findIndex((tab) => tab.index === nextOrder);
currentOrder = nextOrder;
}
const newTab = filteredTabs[nextIndex].tab;
ViewManager.showById(newTab.id);
};

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import ServerManager from 'common/servers/serverManager';
import ViewManager from 'main/views/viewManager';
import {
handleCloseView,
handleOpenView,
} from './views';
jest.mock('common/servers/serverManager', () => ({
setViewIsOpen: jest.fn(),
getView: jest.fn(),
getLastActiveTabForServer: jest.fn(),
}));
jest.mock('main/views/viewManager', () => ({
showById: jest.fn(),
}));
describe('main/app/views', () => {
describe('handleCloseView', () => {
it('should close the specified view and switch to the next open view', () => {
ServerManager.getView.mockReturnValue({server: {id: 'server-1'}});
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-2'});
handleCloseView(null, 'view-3');
expect(ServerManager.setViewIsOpen).toBeCalledWith('view-3', false);
expect(ViewManager.showById).toBeCalledWith('view-2');
});
});
describe('handleOpenView', () => {
it('should open the specified view', () => {
handleOpenView(null, 'view-1');
expect(ViewManager.showById).toBeCalledWith('view-1');
});
});
});

73
src/main/app/views.ts Normal file
View File

@@ -0,0 +1,73 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
import ServerManager from 'common/servers/serverManager';
import {Logger} from 'common/log';
import ViewManager from 'main/views/viewManager';
const log = new Logger('App.Views');
export const handleCloseView = (event: IpcMainEvent, viewId: string) => {
log.debug('handleCloseView', {viewId});
const view = ServerManager.getView(viewId);
if (!view) {
return;
}
ServerManager.setViewIsOpen(viewId, false);
const nextView = ServerManager.getLastActiveTabForServer(view.server.id);
ViewManager.showById(nextView.id);
};
export const handleOpenView = (event: IpcMainEvent, viewId: string) => {
log.debug('handleOpenView', {viewId});
ServerManager.setViewIsOpen(viewId, true);
ViewManager.showById(viewId);
};
export const selectNextView = () => {
selectView((order) => order + 1);
};
export const selectPreviousView = () => {
selectView((order, length) => (length + (order - 1)));
};
export const handleGetOrderedViewsForServer = (event: IpcMainInvokeEvent, serverId: string) => {
return ServerManager.getOrderedTabsForServer(serverId).map((view) => view.toUniqueView());
};
export const handleGetLastActive = () => {
const server = ServerManager.getCurrentServer();
const view = ServerManager.getLastActiveTabForServer(server.id);
return {server: server.id, view: view.id};
};
const selectView = (fn: (order: number, length: number) => number) => {
const currentView = ViewManager.getCurrentView();
if (!currentView) {
return;
}
const currentServerViews = ServerManager.getOrderedTabsForServer(currentView.view.server.id).map((view, index) => ({view, index}));
const filteredViews = currentServerViews?.filter((view) => view.view.isOpen);
const currentServerView = currentServerViews?.find((view) => view.view.type === currentView.view.type);
if (!currentServerViews || !currentServerView || !filteredViews) {
return;
}
let currentOrder = currentServerView.index;
let nextIndex = -1;
while (nextIndex === -1) {
const nextOrder = (fn(currentOrder, currentServerViews.length) % currentServerViews.length);
nextIndex = filteredViews.findIndex((view) => view.index === nextOrder);
currentOrder = nextOrder;
}
const newView = filteredViews[nextIndex].view;
ViewManager.showById(newView.id);
};

View File

@@ -7,54 +7,6 @@ import MainWindow from 'main/windows/mainWindow';
import ModalManager from 'main/views/modalManager';
import ViewManager from 'main/views/viewManager';
jest.mock('common/config', () => ({
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,
}],
}));
jest.mock('common/utils/url', () => {
const actualUrl = jest.requireActual('common/utils/url');
return {
@@ -116,35 +68,35 @@ describe('main/authManager', () => {
});
it('should popLoginModal when isTrustedURL', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://trustedurl.com/')}}});
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://trustedurl.com/')}}});
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://trustedurl.com/'}, null, jest.fn());
expect(authManager.popLoginModal).toBeCalled();
expect(authManager.popPermissionModal).not.toBeCalled();
});
it('should popLoginModal when isCustomLoginURL', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://customloginurl.com/')}}});
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://customloginurl.com/')}}});
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://customloginurl.com/'}, null, jest.fn());
expect(authManager.popLoginModal).toBeCalled();
expect(authManager.popPermissionModal).not.toBeCalled();
});
it('should popLoginModal when has permission', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://haspermissionurl.com/')}}});
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://haspermissionurl.com/')}}});
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://haspermissionurl.com/'}, null, jest.fn());
expect(authManager.popLoginModal).toBeCalled();
expect(authManager.popPermissionModal).not.toBeCalled();
});
it('should popPermissionModal when anything else is true', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://someotherurl.com/')}}});
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://someotherurl.com/')}}});
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, jest.fn());
expect(authManager.popLoginModal).not.toBeCalled();
expect(authManager.popPermissionModal).toBeCalled();
});
it('should set login callback when logging in', () => {
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://someotherurl.com/')}}});
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://someotherurl.com/')}}});
const callback = jest.fn();
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, callback);
expect(authManager.loginCallbackMap.get('http://someotherurl.com/')).toEqual(callback);

View File

@@ -40,7 +40,7 @@ export class AuthManager {
if (!parsedURL) {
return;
}
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.view.server.url;
if (!serverURL) {
return;
}

View File

@@ -15,26 +15,26 @@ const stepDescriptiveName = 'serverConnectivity';
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
try {
const teams = ServerManager.getAllServers();
const servers = ServerManager.getAllServers();
await Promise.all(teams.map(async (team) => {
logger.debug('Pinging server: ', team.url);
await Promise.all(servers.map(async (server) => {
logger.debug('Pinging server: ', server.url);
if (!team.name || !team.url) {
throw new Error(`Invalid server configuration. Team Url: ${team.url}, team name: ${team.name}`);
if (!server.name || !server.url) {
throw new Error(`Invalid server configuration. Server Url: ${server.url}, server name: ${server.name}`);
}
const serverOnline = await isOnline(logger, `${team.url}/api/v4/system/ping`);
const serverOnline = await isOnline(logger, `${server.url}/api/v4/system/ping`);
if (!serverOnline) {
throw new Error(`Server appears to be offline. Team url: ${team.url}`);
throw new Error(`Server appears to be offline. Server url: ${server.url}`);
}
}));
return {
message: `${stepName} finished successfully`,
succeeded: true,
payload: teams,
payload: servers,
};
} catch (error) {
logger.warn(`Diagnostics ${stepName} Failure`, {error});

View File

@@ -554,7 +554,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
log.debug('doneEventController', {state});
if (state === 'completed' && !this.open) {
displayDownloadCompleted(path.basename(item.savePath), item.savePath, ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.name ?? '');
displayDownloadCompleted(path.basename(item.savePath), item.savePath, ViewManager.getViewByWebContentsId(webContents.id)?.view.server.name ?? '');
}
const bookmark = this.bookmarks.get(this.getFileId(item));

View File

@@ -67,8 +67,8 @@ jest.mock('main/windows/mainWindow', () => ({
sendToRenderer: jest.fn(),
}));
jest.mock('main/windows/settingsWindow', () => ({}));
jest.mock('common/tabs/TabView', () => ({
getTabDisplayName: (name) => name,
jest.mock('common/views/View', () => ({
getViewDisplayName: (name) => name,
}));
describe('main/menus/app', () => {
@@ -88,19 +88,19 @@ describe('main/menus/app', () => {
url: 'https:/ /github.com/',
},
];
const tabs = [
const views = [
{
id: 'tab-1',
id: 'view-1',
name: 'TAB_MESSAGING',
isOpen: true,
},
{
id: 'tab-2',
id: 'view-2',
name: 'TAB_FOCALBOARD',
isOpen: true,
},
{
id: 'tab-3',
id: 'view-3',
name: 'TAB_PLAYBOOKS',
isOpen: true,
},
@@ -109,7 +109,7 @@ describe('main/menus/app', () => {
beforeEach(() => {
ServerManager.getCurrentServer.mockReturnValue(servers[0]);
ServerManager.getOrderedServers.mockReturnValue(servers);
ServerManager.getOrderedTabsForServer.mockReturnValue(tabs);
ServerManager.getOrderedTabsForServer.mockReturnValue(views);
getDarwinDoNotDisturb.mockReturnValue(false);
});
@@ -217,7 +217,7 @@ describe('main/menus/app', () => {
expect(signInOption).toBe(undefined);
});
it('should not show `Sign in to Another Server` if no teams are configured', () => {
it('should not show `Sign in to Another Server` if no servers are configured', () => {
localizeMessage.mockImplementation((id) => {
switch (id) {
case 'main.menus.app.file':
@@ -247,15 +247,15 @@ describe('main/menus/app', () => {
name: `server-${key}`,
url: `http://server-${key}.com`,
}));
const modifiedTabs = [
const modifiedViews = [
{
id: 'tab-1',
id: 'view-1',
type: 'TAB_MESSAGING',
isOpen: true,
},
];
ServerManager.getOrderedServers.mockReturnValue(modifiedServers);
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedTabs);
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedViews);
const menu = createTemplate(config);
const windowMenu = menu.find((item) => item.label === '&Window');
for (let i = 0; i < 9; i++) {
@@ -268,32 +268,32 @@ describe('main/menus/app', () => {
}
});
it('should show the first 9 tabs (using order) in the Window menu', () => {
it('should show the first 9 views (using order) in the Window menu', () => {
localizeMessage.mockImplementation((id) => {
if (id === 'main.menus.app.window') {
return '&Window';
}
if (id.startsWith('common.tabs')) {
return id.replace('common.tabs.', '');
if (id.startsWith('common.views')) {
return id.replace('common.views.', '');
}
return id;
});
ServerManager.getCurrentServer.mockImplementation(() => ({id: servers[0].id}));
const modifiedTabs = [...Array(15).keys()].map((key) => ({
id: `tab-${key}`,
type: `tab-${key}`,
const modifiedViews = [...Array(15).keys()].map((key) => ({
id: `view-${key}`,
type: `view-${key}`,
isOpen: true,
}));
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedTabs);
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedViews);
const menu = createTemplate(config);
const windowMenu = menu.find((item) => item.label === '&Window');
for (let i = 0; i < 9; i++) {
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
const menuItem = windowMenu.submenu.find((item) => item.label === ` view-${i}`);
expect(menuItem).not.toBe(undefined);
}
for (let i = 9; i < 15; i++) {
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
const menuItem = windowMenu.submenu.find((item) => item.label === ` view-${i}`);
expect(menuItem).toBe(undefined);
}
});

View File

@@ -6,9 +6,9 @@
import {app, ipcMain, Menu, MenuItemConstructorOptions, MenuItem, session, shell, WebContents, clipboard} from 'electron';
import log from 'electron-log';
import {OPEN_TEAMS_DROPDOWN, SHOW_NEW_SERVER_MODAL} from 'common/communication';
import {OPEN_SERVERS_DROPDOWN, SHOW_NEW_SERVER_MODAL} from 'common/communication';
import {t} from 'common/utils/util';
import {getTabDisplayName, TabType} from 'common/tabs/TabView';
import {getViewDisplayName, ViewType} from 'common/views/View';
import {Config} from 'common/config';
import {localizeMessage} from 'main/i18nManager';
@@ -18,7 +18,7 @@ import downloadsManager from 'main/downloadsManager';
import Diagnostics from 'main/diagnostics';
import ViewManager from 'main/views/viewManager';
import SettingsWindow from 'main/windows/settingsWindow';
import {selectNextTab, selectPreviousTab} from 'main/app/tabs';
import {selectNextView, selectPreviousView} from 'main/app/views';
import {switchServer} from 'main/app/servers';
export function createTemplate(config: Config, updateManager: UpdateManager) {
@@ -233,7 +233,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
}],
});
const teams = ServerManager.getOrderedServers();
const servers = ServerManager.getOrderedServers();
const windowMenu = {
id: 'window',
label: localizeMessage('main.menus.app.window', '&Window'),
@@ -257,25 +257,25 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
label: localizeMessage('main.menus.app.window.showServers', 'Show Servers'),
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`,
click() {
ipcMain.emit(OPEN_TEAMS_DROPDOWN);
ipcMain.emit(OPEN_SERVERS_DROPDOWN);
},
}] : []),
...teams.slice(0, 9).map((team, i) => {
...servers.slice(0, 9).map((server, i) => {
const items = [];
items.push({
label: team.name,
label: server.name,
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+${i + 1}`,
click() {
switchServer(team.id);
switchServer(server.id);
},
});
if (ServerManager.getCurrentServer().id === team.id) {
ServerManager.getOrderedTabsForServer(team.id).slice(0, 9).forEach((tab, i) => {
if (ServerManager.getCurrentServer().id === server.id) {
ServerManager.getOrderedTabsForServer(server.id).slice(0, 9).forEach((view, i) => {
items.push({
label: ` ${localizeMessage(`common.tabs.${tab.type}`, getTabDisplayName(tab.type as TabType))}`,
label: ` ${localizeMessage(`common.views.${view.type}`, getViewDisplayName(view.type as ViewType))}`,
accelerator: `CmdOrCtrl+${i + 1}`,
click() {
ViewManager.showById(tab.id);
ViewManager.showById(view.id);
},
});
});
@@ -285,16 +285,16 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
label: localizeMessage('main.menus.app.window.selectNextTab', 'Select Next Tab'),
accelerator: 'Ctrl+Tab',
click() {
selectNextTab();
selectNextView();
},
enabled: (teams.length > 1),
enabled: (servers.length > 1),
}, {
label: localizeMessage('main.menus.app.window.selectPreviousTab', 'Select Previous Tab'),
accelerator: 'Ctrl+Shift+Tab',
click() {
selectPreviousTab();
selectPreviousView();
},
enabled: (teams.length > 1),
enabled: (servers.length > 1),
}, ...(isMac ? [separatorItem, {
role: 'front',
label: localizeMessage('main.menus.app.window.bringAllToFront', 'Bring All to Front'),

View File

@@ -12,13 +12,13 @@ import SettingsWindow from 'main/windows/settingsWindow';
import {switchServer} from 'main/app/servers';
export function createTemplate() {
const teams = ServerManager.getOrderedServers();
const servers = ServerManager.getOrderedServers();
const template = [
...teams.slice(0, 9).map((team) => {
...servers.slice(0, 9).map((server) => {
return {
label: team.name.length > 50 ? `${team.name.slice(0, 50)}...` : team.name,
label: server.name.length > 50 ? `${server.name.slice(0, 50)}...` : server.name,
click: () => {
switchServer(team.id);
switchServer(server.id);
},
};
}), {

View File

@@ -76,7 +76,7 @@ jest.mock('macos-notification-state', () => ({
jest.mock('../views/viewManager', () => ({
getViewByWebContentsId: () => ({
id: 'server_id',
tab: {
view: {
server: {
name: 'server_name',
},
@@ -231,7 +231,7 @@ describe('main/notifications', () => {
});
});
it('should switch tab when clicking on notification', () => {
it('should switch view when clicking on notification', () => {
displayMention(
'click_test',
'mention_click_body',

View File

@@ -40,7 +40,7 @@ export function displayMention(title: string, body: string, channel: {id: string
if (!view) {
return;
}
const serverName = view.tab.server.name;
const serverName = view.view.server.name;
const options = {
title: `${serverName}: ${title}`,

View File

@@ -10,10 +10,10 @@ import {
GET_LANGUAGE_INFORMATION,
QUIT,
OPEN_APP_MENU,
CLOSE_TEAMS_DROPDOWN,
OPEN_TEAMS_DROPDOWN,
CLOSE_SERVERS_DROPDOWN,
OPEN_SERVERS_DROPDOWN,
SWITCH_TAB,
CLOSE_TAB,
CLOSE_VIEW,
WINDOW_CLOSE,
WINDOW_MINIMIZE,
WINDOW_MAXIMIZE,
@@ -59,8 +59,8 @@ import {
DOWNLOADS_DROPDOWN_MENU_OPEN_FILE,
UPDATE_DOWNLOADS_DROPDOWN_MENU,
REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO,
UPDATE_TEAMS_DROPDOWN,
REQUEST_TEAMS_DROPDOWN_INFO,
UPDATE_SERVERS_DROPDOWN,
REQUEST_SERVERS_DROPDOWN_INFO,
RECEIVE_DROPDOWN_MENU_SIZE,
SWITCH_SERVER,
SHOW_NEW_SERVER_MODAL,
@@ -111,10 +111,10 @@ contextBridge.exposeInMainWorld('mas', {
contextBridge.exposeInMainWorld('desktop', {
quit: (reason, stack) => ipcRenderer.send(QUIT, reason, stack),
openAppMenu: () => ipcRenderer.send(OPEN_APP_MENU),
closeTeamsDropdown: () => ipcRenderer.send(CLOSE_TEAMS_DROPDOWN),
openTeamsDropdown: () => ipcRenderer.send(OPEN_TEAMS_DROPDOWN),
switchTab: (tabId) => ipcRenderer.send(SWITCH_TAB, tabId),
closeTab: (tabId) => ipcRenderer.send(CLOSE_TAB, tabId),
closeServersDropdown: () => ipcRenderer.send(CLOSE_SERVERS_DROPDOWN),
openServersDropdown: () => ipcRenderer.send(OPEN_SERVERS_DROPDOWN),
switchTab: (viewId) => ipcRenderer.send(SWITCH_TAB, viewId),
closeView: (viewId) => ipcRenderer.send(CLOSE_VIEW, viewId),
closeWindow: () => ipcRenderer.send(WINDOW_CLOSE),
minimizeWindow: () => ipcRenderer.send(WINDOW_MINIMIZE),
maximizeWindow: () => ipcRenderer.send(WINDOW_MAXIMIZE),
@@ -130,7 +130,7 @@ contextBridge.exposeInMainWorld('desktop', {
updateConfiguration: (saveQueueItems) => ipcRenderer.send(UPDATE_CONFIGURATION, saveQueueItems),
updateServerOrder: (serverOrder) => ipcRenderer.send(UPDATE_SERVER_ORDER, serverOrder),
updateTabOrder: (serverId, tabOrder) => ipcRenderer.send(UPDATE_TAB_ORDER, serverId, tabOrder),
updateTabOrder: (serverId, viewOrder) => ipcRenderer.send(UPDATE_TAB_ORDER, serverId, viewOrder),
getLastActive: () => ipcRenderer.invoke(GET_LAST_ACTIVE),
getOrderedServers: () => ipcRenderer.invoke(GET_ORDERED_SERVERS),
getOrderedTabsForServer: (serverId) => ipcRenderer.invoke(GET_ORDERED_TABS_FOR_SERVER, serverId),
@@ -153,7 +153,7 @@ contextBridge.exposeInMainWorld('desktop', {
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)),
onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverId, viewId) => listener(serverId, viewId)),
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()),
@@ -162,8 +162,8 @@ contextBridge.exposeInMainWorld('desktop', {
onModalClose: (listener) => ipcRenderer.on(MODAL_CLOSE, () => listener()),
onToggleBackButton: (listener) => ipcRenderer.on(TOGGLE_BACK_BUTTON, (_, showExtraBar) => listener(showExtraBar)),
onUpdateMentions: (listener) => ipcRenderer.on(UPDATE_MENTIONS, (_event, view, mentions, unreads, isExpired) => listener(view, mentions, unreads, isExpired)),
onCloseTeamsDropdown: (listener) => ipcRenderer.on(CLOSE_TEAMS_DROPDOWN, () => listener()),
onOpenTeamsDropdown: (listener) => ipcRenderer.on(OPEN_TEAMS_DROPDOWN, () => listener()),
onCloseServersDropdown: (listener) => ipcRenderer.on(CLOSE_SERVERS_DROPDOWN, () => listener()),
onOpenServersDropdown: (listener) => ipcRenderer.on(OPEN_SERVERS_DROPDOWN, () => listener()),
onCloseDownloadsDropdown: (listener) => ipcRenderer.on(CLOSE_DOWNLOADS_DROPDOWN, () => listener()),
onOpenDownloadsDropdown: (listener) => ipcRenderer.on(OPEN_DOWNLOADS_DROPDOWN, () => listener()),
onShowDownloadsDropdownButtonBadge: (listener) => ipcRenderer.on(SHOW_DOWNLOADS_DROPDOWN_BUTTON_BADGE, () => listener()),
@@ -195,29 +195,29 @@ contextBridge.exposeInMainWorld('desktop', {
},
serverDropdown: {
requestInfo: () => ipcRenderer.send(REQUEST_TEAMS_DROPDOWN_INFO),
requestInfo: () => ipcRenderer.send(REQUEST_SERVERS_DROPDOWN_INFO),
sendSize: (width, height) => ipcRenderer.send(RECEIVE_DROPDOWN_MENU_SIZE, width, height),
switchServer: (serverId) => ipcRenderer.send(SWITCH_SERVER, serverId),
showNewServerModal: () => ipcRenderer.send(SHOW_NEW_SERVER_MODAL),
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,
activeTeam,
onUpdateServerDropdown: (listener) => ipcRenderer.on(UPDATE_SERVERS_DROPDOWN, (_,
servers,
activeServer,
darkMode,
enableServerManagement,
hasGPOTeams,
hasGPOServers,
expired,
mentions,
unreads,
windowBounds,
) => listener(
teams,
activeTeam,
servers,
activeServer,
darkMode,
enableServerManagement,
hasGPOTeams,
hasGPOServers,
expired,
mentions,
unreads,

View File

@@ -20,7 +20,7 @@ import {
SET_VIEW_OPTIONS,
REACT_APP_INITIALIZED,
USER_ACTIVITY_UPDATE,
CLOSE_TEAMS_DROPDOWN,
CLOSE_SERVERS_DROPDOWN,
BROWSER_HISTORY_BUTTON,
BROWSER_HISTORY_PUSH,
APP_LOGGED_IN,
@@ -272,7 +272,7 @@ function isDownloadLink(el) {
}
window.addEventListener('click', (e) => {
ipcRenderer.send(CLOSE_TEAMS_DROPDOWN);
ipcRenderer.send(CLOSE_SERVERS_DROPDOWN);
const el = e.target;
if (!isDownloadLink(el)) {
ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN);

View File

@@ -6,13 +6,13 @@
import AppState from 'common/appState';
import {LOAD_FAILED, TOGGLE_BACK_BUTTON, UPDATE_TARGET_URL} from 'common/communication';
import {MattermostServer} from 'common/servers/MattermostServer';
import MessagingTabView from 'common/tabs/MessagingTabView';
import MessagingView from 'common/views/MessagingView';
import MainWindow from '../windows/mainWindow';
import ContextMenu from '../contextMenu';
import Utils from '../utils';
import {MattermostView} from './MattermostView';
import {MattermostBrowserView} from './MattermostBrowserView';
jest.mock('electron', () => ({
app: {
@@ -59,12 +59,12 @@ jest.mock('../utils', () => ({
}));
const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
const tabView = new MessagingTabView(server, true);
const view = new MessagingView(server, true);
describe('main/views/MattermostView', () => {
describe('main/views/MattermostBrowserView', () => {
describe('load', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
MainWindow.get.mockReturnValue(window);
@@ -74,38 +74,38 @@ describe('main/views/MattermostView', () => {
it('should load provided URL when provided', async () => {
const promise = Promise.resolve();
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.load('http://server-2.com');
await promise;
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-2.com/', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-2.com/', expect.any(Object));
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-2.com/');
});
it('should load server URL when not provided', async () => {
const promise = Promise.resolve();
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.load();
await promise;
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
});
it('should load server URL when bad url provided', async () => {
const promise = Promise.resolve();
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.load('a-bad<url');
await promise;
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
});
it('should call retry when failing to load', async () => {
const error = new Error('test');
const promise = Promise.reject(error);
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.load('a-bad<url');
await expect(promise).rejects.toThrow(error);
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com/', error);
});
@@ -113,23 +113,23 @@ describe('main/views/MattermostView', () => {
const error = new Error('test');
error.code = 'ERR_CERT_ERROR';
const promise = Promise.reject(error);
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.load('a-bad<url');
await expect(promise).rejects.toThrow(error);
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
expect(mattermostView.loadRetry).not.toBeCalled();
});
});
describe('retry', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
const retryInBackgroundFn = jest.fn();
beforeEach(() => {
jest.useFakeTimers();
MainWindow.get.mockReturnValue(window);
mattermostView.view.webContents.loadURL.mockImplementation(() => Promise.resolve());
mattermostView.browserView.webContents.loadURL.mockImplementation(() => Promise.resolve());
mattermostView.loadSuccess = jest.fn();
mattermostView.loadRetry = jest.fn();
mattermostView.emit = jest.fn();
@@ -143,16 +143,16 @@ describe('main/views/MattermostView', () => {
});
it('should do nothing when webcontents are destroyed', () => {
const webContents = mattermostView.view.webContents;
mattermostView.view.webContents = null;
const webContents = mattermostView.browserView.webContents;
mattermostView.browserView.webContents = null;
mattermostView.retry('http://server-1.com')();
expect(mattermostView.loadSuccess).not.toBeCalled();
mattermostView.view.webContents = webContents;
mattermostView.browserView.webContents = webContents;
});
it('should call loadSuccess on successful load', async () => {
const promise = Promise.resolve();
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.retry('http://server-1.com')();
await promise;
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com');
@@ -162,10 +162,10 @@ describe('main/views/MattermostView', () => {
mattermostView.maxRetries = 10;
const error = new Error('test');
const promise = Promise.reject(error);
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.retry('http://server-1.com')();
await expect(promise).rejects.toThrow(error);
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com', error);
});
@@ -173,12 +173,12 @@ describe('main/views/MattermostView', () => {
mattermostView.maxRetries = 0;
const error = new Error('test');
const promise = Promise.reject(error);
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
mattermostView.retry('http://server-1.com')();
await expect(promise).rejects.toThrow(error);
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
expect(mattermostView.loadRetry).not.toBeCalled();
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.id, expect.any(String), expect.any(String));
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.view.id, expect.any(String), expect.any(String));
expect(mattermostView.status).toBe(-1);
jest.runAllTimers();
expect(retryInBackgroundFn).toBeCalled();
@@ -187,7 +187,7 @@ describe('main/views/MattermostView', () => {
describe('goToOffset', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
mattermostView.reload = jest.fn();
afterEach(() => {
@@ -196,18 +196,18 @@ describe('main/views/MattermostView', () => {
});
it('should only go to offset if it can', () => {
mattermostView.view.webContents.canGoToOffset.mockReturnValue(false);
mattermostView.browserView.webContents.canGoToOffset.mockReturnValue(false);
mattermostView.goToOffset(1);
expect(mattermostView.view.webContents.goToOffset).not.toBeCalled();
expect(mattermostView.browserView.webContents.goToOffset).not.toBeCalled();
mattermostView.view.webContents.canGoToOffset.mockReturnValue(true);
mattermostView.browserView.webContents.canGoToOffset.mockReturnValue(true);
mattermostView.goToOffset(1);
expect(mattermostView.view.webContents.goToOffset).toBeCalled();
expect(mattermostView.browserView.webContents.goToOffset).toBeCalled();
});
it('should call reload if an error occurs', () => {
mattermostView.view.webContents.canGoToOffset.mockReturnValue(true);
mattermostView.view.webContents.goToOffset.mockImplementation(() => {
mattermostView.browserView.webContents.canGoToOffset.mockReturnValue(true);
mattermostView.browserView.webContents.goToOffset.mockImplementation(() => {
throw new Error('hi');
});
mattermostView.goToOffset(1);
@@ -217,8 +217,8 @@ describe('main/views/MattermostView', () => {
describe('onLogin', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
mattermostView.view.webContents.getURL = jest.fn();
const mattermostView = new MattermostBrowserView(view, {}, {});
mattermostView.browserView.webContents.getURL = jest.fn();
mattermostView.reload = jest.fn();
afterEach(() => {
@@ -227,19 +227,19 @@ describe('main/views/MattermostView', () => {
});
it('should reload view when URL is not on subpath of original server URL', () => {
mattermostView.view.webContents.getURL.mockReturnValue('http://server-2.com/subpath');
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-2.com/subpath');
mattermostView.onLogin(true);
expect(mattermostView.reload).toHaveBeenCalled();
});
it('should not reload if URLs are matching', () => {
mattermostView.view.webContents.getURL.mockReturnValue('http://server-1.com');
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-1.com');
mattermostView.onLogin(true);
expect(mattermostView.reload).not.toHaveBeenCalled();
});
it('should not reload if URL is subpath of server URL', () => {
mattermostView.view.webContents.getURL.mockReturnValue('http://server-1.com/subpath');
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-1.com/subpath');
mattermostView.onLogin(true);
expect(mattermostView.reload).not.toHaveBeenCalled();
});
@@ -247,7 +247,7 @@ describe('main/views/MattermostView', () => {
describe('loadSuccess', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
jest.useFakeTimers();
@@ -275,7 +275,7 @@ describe('main/views/MattermostView', () => {
describe('show', () => {
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
jest.useFakeTimers();
@@ -293,7 +293,7 @@ describe('main/views/MattermostView', () => {
it('should add browser view to window and set bounds when request is true and view not currently visible', () => {
mattermostView.isVisible = false;
mattermostView.show();
expect(window.addBrowserView).toBeCalledWith(mattermostView.view);
expect(window.addBrowserView).toBeCalledWith(mattermostView.browserView);
expect(mattermostView.setBounds).toBeCalled();
expect(mattermostView.isVisible).toBe(true);
});
@@ -314,7 +314,7 @@ describe('main/views/MattermostView', () => {
describe('hide', () => {
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
MainWindow.get.mockReturnValue(window);
@@ -323,7 +323,7 @@ describe('main/views/MattermostView', () => {
it('should remove browser view', () => {
mattermostView.isVisible = true;
mattermostView.hide();
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
expect(mattermostView.isVisible).toBe(false);
});
@@ -336,7 +336,7 @@ describe('main/views/MattermostView', () => {
describe('updateHistoryButton', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
MainWindow.get.mockReturnValue(window);
@@ -345,7 +345,7 @@ describe('main/views/MattermostView', () => {
it('should erase history and set isAtRoot when navigating to root URL', () => {
mattermostView.atRoot = false;
mattermostView.updateHistoryButton();
expect(mattermostView.view.webContents.clearHistory).toHaveBeenCalled();
expect(mattermostView.browserView.webContents.clearHistory).toHaveBeenCalled();
expect(mattermostView.isAtRoot).toBe(true);
});
});
@@ -362,22 +362,22 @@ describe('main/views/MattermostView', () => {
});
it('should remove browser view from window', () => {
const mattermostView = new MattermostView(tabView, {}, {});
mattermostView.view.webContents.destroy = jest.fn();
const mattermostView = new MattermostBrowserView(view, {}, {});
mattermostView.browserView.webContents.destroy = jest.fn();
mattermostView.destroy();
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
});
it('should clear mentions', () => {
const mattermostView = new MattermostView(tabView, {}, {});
mattermostView.view.webContents.destroy = jest.fn();
const mattermostView = new MattermostBrowserView(view, {}, {});
mattermostView.browserView.webContents.destroy = jest.fn();
mattermostView.destroy();
expect(AppState.clear).toBeCalledWith(mattermostView.tab.id);
expect(AppState.clear).toBeCalledWith(mattermostView.view.id);
});
it('should clear outstanding timeouts', () => {
const mattermostView = new MattermostView(tabView, {}, {});
mattermostView.view.webContents.destroy = jest.fn();
const mattermostView = new MattermostBrowserView(view, {}, {});
mattermostView.browserView.webContents.destroy = jest.fn();
const spy = jest.spyOn(global, 'clearTimeout');
mattermostView.retryLoad = 999;
mattermostView.removeLoading = 1000;
@@ -388,7 +388,7 @@ describe('main/views/MattermostView', () => {
describe('handleInputEvents', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
it('should open three dot menu on pressing Alt', () => {
MainWindow.get.mockReturnValue(window);
@@ -413,7 +413,7 @@ describe('main/views/MattermostView', () => {
describe('handleDidNavigate', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
MainWindow.get.mockReturnValue(window);
@@ -435,7 +435,7 @@ describe('main/views/MattermostView', () => {
describe('handleUpdateTarget', () => {
const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
beforeEach(() => {
MainWindow.get.mockReturnValue(window);
@@ -466,16 +466,16 @@ describe('main/views/MattermostView', () => {
});
describe('updateMentionsFromTitle', () => {
const mattermostView = new MattermostView(tabView, {}, {});
const mattermostView = new MattermostBrowserView(view, {}, {});
it('should parse mentions from title', () => {
mattermostView.updateMentionsFromTitle('(7) Mattermost');
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.id, 7);
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.view.id, 7);
});
it('should parse unreads from title', () => {
mattermostView.updateMentionsFromTitle('* Mattermost');
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.id, 0);
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.view.id, 0);
});
});
});

View File

@@ -23,7 +23,7 @@ import {
import ServerManager from 'common/servers/serverManager';
import {Logger} from 'common/log';
import {isInternalURL, parseURL} from 'common/utils/url';
import {TabView} from 'common/tabs/TabView';
import {MattermostView} from 'common/views/View';
import MainWindow from 'main/windows/mainWindow';
@@ -42,12 +42,12 @@ enum Status {
const MENTIONS_GROUP = 2;
const titleParser = /(\((\d+)\) )?(\* )?/g;
export class MattermostView extends EventEmitter {
tab: TabView;
export class MattermostBrowserView extends EventEmitter {
view: MattermostView;
isVisible: boolean;
private log: Logger;
private view: BrowserView;
private browserView: BrowserView;
private loggedIn: boolean;
private atRoot: boolean;
private options: BrowserViewConstructorOptions;
@@ -58,9 +58,9 @@ export class MattermostView extends EventEmitter {
private maxRetries: number;
private altPressStatus: boolean;
constructor(tab: TabView, options: BrowserViewConstructorOptions) {
constructor(view: MattermostView, options: BrowserViewConstructorOptions) {
super();
this.tab = tab;
this.view = view;
const preload = getLocalPreload('preload.js');
this.options = Object.assign({}, options);
@@ -75,24 +75,24 @@ export class MattermostView extends EventEmitter {
this.isVisible = false;
this.loggedIn = false;
this.atRoot = true;
this.view = new BrowserView(this.options);
this.browserView = new BrowserView(this.options);
this.resetLoadingStatus();
this.log = ServerManager.getViewLog(this.id, 'MattermostView');
this.log = ServerManager.getViewLog(this.id, 'MattermostBrowserView');
this.log.verbose('View created');
this.view.webContents.on('did-finish-load', this.handleDidFinishLoad);
this.view.webContents.on('page-title-updated', this.handleTitleUpdate);
this.view.webContents.on('page-favicon-updated', this.handleFaviconUpdate);
this.view.webContents.on('update-target-url', this.handleUpdateTarget);
this.view.webContents.on('did-navigate', this.handleDidNavigate);
this.browserView.webContents.on('did-finish-load', this.handleDidFinishLoad);
this.browserView.webContents.on('page-title-updated', this.handleTitleUpdate);
this.browserView.webContents.on('page-favicon-updated', this.handleFaviconUpdate);
this.browserView.webContents.on('update-target-url', this.handleUpdateTarget);
this.browserView.webContents.on('did-navigate', this.handleDidNavigate);
if (process.platform !== 'darwin') {
this.view.webContents.on('before-input-event', this.handleInputEvents);
this.browserView.webContents.on('before-input-event', this.handleInputEvents);
}
WebContentsEventManager.addWebContentsEventListeners(this.view.webContents);
WebContentsEventManager.addWebContentsEventListeners(this.browserView.webContents);
this.contextMenu = new ContextMenu({}, this.view);
this.contextMenu = new ContextMenu({}, this.browserView);
this.maxRetries = MAX_SERVER_RETRIES;
this.altPressStatus = false;
@@ -105,7 +105,7 @@ export class MattermostView extends EventEmitter {
}
get id() {
return this.tab.id;
return this.view.id;
}
get isAtRoot() {
return this.atRoot;
@@ -114,10 +114,10 @@ export class MattermostView extends EventEmitter {
return this.loggedIn;
}
get currentURL() {
return parseURL(this.view.webContents.getURL());
return parseURL(this.browserView.webContents.getURL());
}
get webContentsId() {
return this.view.webContents.id;
return this.browserView.webContents.id;
}
onLogin = (loggedIn: boolean) => {
@@ -127,19 +127,19 @@ export class MattermostView extends EventEmitter {
this.loggedIn = loggedIn;
// If we're logging in from a different tab, force a reload
// If we're logging in from a different view, force a reload
if (loggedIn &&
this.currentURL?.toString() !== this.tab.url.toString() &&
!this.currentURL?.toString().startsWith(this.tab.url.toString())
this.currentURL?.toString() !== this.view.url.toString() &&
!this.currentURL?.toString().startsWith(this.view.url.toString())
) {
this.reload();
}
}
goToOffset = (offset: number) => {
if (this.view.webContents.canGoToOffset(offset)) {
if (this.browserView.webContents.canGoToOffset(offset)) {
try {
this.view.webContents.goToOffset(offset);
this.browserView.webContents.goToOffset(offset);
this.updateHistoryButton();
} catch (error) {
this.log.error(error);
@@ -149,17 +149,17 @@ export class MattermostView extends EventEmitter {
}
updateHistoryButton = () => {
if (this.currentURL?.toString() === this.tab.url.toString()) {
this.view.webContents.clearHistory();
if (this.currentURL?.toString() === this.view.url.toString()) {
this.browserView.webContents.clearHistory();
this.atRoot = true;
} else {
this.atRoot = false;
}
this.view.webContents.send(BROWSER_HISTORY_BUTTON, this.view.webContents.canGoBack(), this.view.webContents.canGoForward());
this.browserView.webContents.send(BROWSER_HISTORY_BUTTON, this.browserView.webContents.canGoBack(), this.browserView.webContents.canGoForward());
}
load = (someURL?: URL | string) => {
if (!this.tab) {
if (!this.browserView) {
return;
}
@@ -170,13 +170,13 @@ export class MattermostView extends EventEmitter {
loadURL = parsedURL.toString();
} else {
this.log.error('Cannot parse provided url, using current server url', someURL);
loadURL = this.tab.url.toString();
loadURL = this.view.url.toString();
}
} else {
loadURL = this.tab.url.toString();
loadURL = this.view.url.toString();
}
this.log.verbose(`Loading ${loadURL}`);
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
loading.then(this.loadSuccess(loadURL)).catch((err) => {
if (err.code && err.code.startsWith('ERR_CERT')) {
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
@@ -205,9 +205,9 @@ export class MattermostView extends EventEmitter {
return;
}
this.isVisible = true;
mainWindow.addBrowserView(this.view);
mainWindow.setTopBrowserView(this.view);
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
mainWindow.addBrowserView(this.browserView);
mainWindow.setTopBrowserView(this.browserView);
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
if (this.status === Status.READY) {
this.focus();
}
@@ -216,7 +216,7 @@ export class MattermostView extends EventEmitter {
hide = () => {
if (this.isVisible) {
this.isVisible = false;
MainWindow.get()?.removeBrowserView(this.view);
MainWindow.get()?.removeBrowserView(this.browserView);
}
}
@@ -226,27 +226,27 @@ export class MattermostView extends EventEmitter {
}
getBounds = () => {
return this.view.getBounds();
return this.browserView.getBounds();
}
openFind = () => {
this.view.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
this.browserView.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
}
setBounds = (boundaries: Electron.Rectangle) => {
this.view.setBounds(boundaries);
this.browserView.setBounds(boundaries);
}
destroy = () => {
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
AppState.clear(this.id);
MainWindow.get()?.removeBrowserView(this.view);
MainWindow.get()?.removeBrowserView(this.browserView);
// workaround to eliminate zombie processes
// https://github.com/mattermost/desktop/pull/1519
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.view.webContents.destroy();
this.browserView.webContents.destroy();
this.isVisible = false;
if (this.retryLoad) {
@@ -293,7 +293,7 @@ export class MattermostView extends EventEmitter {
}
openDevTools = () => {
this.view.webContents.openDevTools({mode: 'detach'});
this.browserView.webContents.openDevTools({mode: 'detach'});
}
/**
@@ -301,16 +301,16 @@ export class MattermostView extends EventEmitter {
*/
sendToRenderer = (channel: string, ...args: any[]) => {
this.view.webContents.send(channel, ...args);
this.browserView.webContents.send(channel, ...args);
}
isDestroyed = () => {
return this.view.webContents.isDestroyed();
return this.browserView.webContents.isDestroyed();
}
focus = () => {
if (this.view.webContents) {
this.view.webContents.focus();
if (this.browserView.webContents) {
this.browserView.webContents.focus();
} else {
this.log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.');
}
@@ -361,7 +361,7 @@ export class MattermostView extends EventEmitter {
// 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.id);
this.browserView.webContents.send(IS_UNREAD, favicon, this.id);
} catch (err: any) {
this.log.error('There was an error trying to request the unread state', err);
}
@@ -388,17 +388,17 @@ export class MattermostView extends EventEmitter {
private retry = (loadURL: string) => {
return () => {
// window was closed while retrying
if (!this.view || !this.view.webContents) {
if (!this.browserView || !this.browserView.webContents) {
return;
}
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
loading.then(this.loadSuccess(loadURL)).catch((err) => {
if (this.maxRetries-- > 0) {
this.loadRetry(loadURL, err);
} else {
MainWindow.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.log.info(`Couldn't esviewlish a connection with ${loadURL}, will continue to retry in the background`, err);
this.status = Status.ERROR;
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
}
@@ -409,10 +409,10 @@ export class MattermostView extends EventEmitter {
private retryInBackground = (loadURL: string) => {
return () => {
// window was closed while retrying
if (!this.view || !this.view.webContents) {
if (!this.browserView || !this.browserView.webContents) {
return;
}
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
loading.then(this.loadSuccess(loadURL)).catch(() => {
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
});
@@ -431,7 +431,7 @@ export class MattermostView extends EventEmitter {
MainWindow.sendToRenderer(LOAD_SUCCESS, this.id);
this.maxRetries = MAX_SERVER_RETRIES;
if (this.status === Status.LOADING) {
this.updateMentionsFromTitle(this.view.webContents.getTitle());
this.updateMentionsFromTitle(this.browserView.webContents.getTitle());
this.findUnreadState(null);
}
this.status = Status.WAITING_MM;
@@ -439,7 +439,7 @@ export class MattermostView extends EventEmitter {
this.emit(LOAD_SUCCESS, this.id, loadURL);
const mainWindow = MainWindow.get();
if (mainWindow && this.currentURL) {
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
}
};
}
@@ -453,13 +453,13 @@ export class MattermostView extends EventEmitter {
// wait for screen to truly finish loading before sending the message down
const timeout = setInterval(() => {
if (!this.view.webContents) {
if (!this.browserView.webContents) {
return;
}
if (!this.view.webContents.isLoading()) {
if (!this.browserView.webContents.isLoading()) {
try {
this.view.webContents.send(SET_VIEW_OPTIONS, this.id, this.tab.shouldNotify);
this.browserView.webContents.send(SET_VIEW_OPTIONS, this.id, this.view.shouldNotify);
clearTimeout(timeout);
} catch (e) {
this.log.error('failed to send view options to view');
@@ -480,7 +480,7 @@ export class MattermostView extends EventEmitter {
return;
}
if (shouldHaveBackBar(this.tab.url || '', parsedURL)) {
if (shouldHaveBackBar(this.view.url || '', parsedURL)) {
this.setBounds(getWindowBoundaries(mainWindow, true));
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, true);
this.log.debug('show back button');
@@ -494,7 +494,7 @@ export class MattermostView extends EventEmitter {
private handleUpdateTarget = (e: Event, url: string) => {
this.log.silly('handleUpdateTarget', url);
const parsedURL = parseURL(url);
if (parsedURL && isInternalURL(parsedURL, this.tab.server.url)) {
if (parsedURL && isInternalURL(parsedURL, this.view.server.url)) {
this.emit(UPDATE_TARGET_URL);
} else {
this.emit(UPDATE_TARGET_URL, url);
@@ -502,7 +502,7 @@ export class MattermostView extends EventEmitter {
}
private handleServerWasModified = (serverIds: string) => {
if (serverIds.includes(this.tab.server.id)) {
if (serverIds.includes(this.view.server.id)) {
this.reload();
}
}

View File

@@ -7,7 +7,7 @@ import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHA
import MainWindow from 'main/windows/mainWindow';
import {TeamDropdownView} from './teamDropdownView';
import {ServerDropdownView} from './serverDropdownView';
jest.mock('main/utils', () => ({
getLocalPreload: (file) => file,
@@ -38,36 +38,36 @@ jest.mock('common/servers/serverManager', () => ({
getOrderedServers: jest.fn().mockReturnValue([]),
}));
describe('main/views/teamDropdownView', () => {
describe('main/views/serverDropdownView', () => {
describe('getBounds', () => {
beforeEach(() => {
MainWindow.getBounds.mockReturnValue({width: 500, height: 400, x: 0, y: 0});
});
const teamDropdownView = new TeamDropdownView();
const serverDropdownView = new ServerDropdownView();
if (process.platform === 'darwin') {
it('should account for three dot menu, tab bar and shadow', () => {
expect(teamDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH_MAC - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
expect(serverDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH_MAC - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
});
} else {
it('should account for three dot menu, tab bar and shadow', () => {
expect(teamDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
expect(serverDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
});
}
});
it('should change the view bounds based on open/closed state', () => {
const teamDropdownView = new TeamDropdownView();
teamDropdownView.view = {
const serverDropdownView = new ServerDropdownView();
serverDropdownView.view = {
setBounds: jest.fn(),
webContents: {
focus: jest.fn(),
},
};
teamDropdownView.bounds = {width: 400, height: 300};
teamDropdownView.handleOpen();
expect(teamDropdownView.view.setBounds).toBeCalledWith(teamDropdownView.bounds);
teamDropdownView.handleClose();
expect(teamDropdownView.view.setBounds).toBeCalledWith({width: 0, height: 0, x: expect.any(Number), y: expect.any(Number)});
serverDropdownView.bounds = {width: 400, height: 300};
serverDropdownView.handleOpen();
expect(serverDropdownView.view.setBounds).toBeCalledWith(serverDropdownView.bounds);
serverDropdownView.handleClose();
expect(serverDropdownView.view.setBounds).toBeCalledWith({width: 0, height: 0, x: expect.any(Number), y: expect.any(Number)});
});
});

View File

@@ -3,16 +3,16 @@
import {BrowserView, ipcMain, IpcMainEvent} from 'electron';
import {MattermostTeam} from 'types/config';
import {UniqueServer} from 'types/config';
import AppState from 'common/appState';
import {
CLOSE_TEAMS_DROPDOWN,
CLOSE_SERVERS_DROPDOWN,
EMIT_CONFIGURATION,
OPEN_TEAMS_DROPDOWN,
UPDATE_TEAMS_DROPDOWN,
OPEN_SERVERS_DROPDOWN,
UPDATE_SERVERS_DROPDOWN,
UPDATE_APPSTATE,
REQUEST_TEAMS_DROPDOWN_INFO,
REQUEST_SERVERS_DROPDOWN_INFO,
RECEIVE_DROPDOWN_MENU_SIZE,
SERVERS_UPDATE,
MAIN_WINDOW_CREATED,
@@ -27,12 +27,12 @@ import {getLocalPreload, getLocalURLString} from 'main/utils';
import MainWindow from '../windows/mainWindow';
const log = new Logger('TeamDropdownView');
const log = new Logger('ServerDropdownView');
export class TeamDropdownView {
export class ServerDropdownView {
private view?: BrowserView;
private teams: MattermostTeam[];
private hasGPOTeams: boolean;
private servers: UniqueServer[];
private hasGPOServers: boolean;
private isOpen: boolean;
private bounds: Electron.Rectangle;
@@ -43,8 +43,8 @@ export class TeamDropdownView {
private windowBounds?: Electron.Rectangle;
constructor() {
this.teams = [];
this.hasGPOTeams = false;
this.servers = [];
this.hasGPOServers = false;
this.isOpen = false;
this.bounds = this.getBounds(0, 0);
@@ -55,12 +55,12 @@ export class TeamDropdownView {
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
MainWindow.on(MAIN_WINDOW_RESIZED, this.updateWindowBounds);
ipcMain.on(OPEN_TEAMS_DROPDOWN, this.handleOpen);
ipcMain.on(CLOSE_TEAMS_DROPDOWN, this.handleClose);
ipcMain.on(OPEN_SERVERS_DROPDOWN, this.handleOpen);
ipcMain.on(CLOSE_SERVERS_DROPDOWN, this.handleClose);
ipcMain.on(RECEIVE_DROPDOWN_MENU_SIZE, this.handleReceivedMenuSize);
ipcMain.on(EMIT_CONFIGURATION, this.updateDropdown);
ipcMain.on(REQUEST_TEAMS_DROPDOWN_INFO, this.updateDropdown);
ipcMain.on(REQUEST_SERVERS_DROPDOWN_INFO, this.updateDropdown);
AppState.on(UPDATE_APPSTATE, this.updateMentions);
ServerManager.on(SERVERS_UPDATE, this.updateServers);
@@ -84,7 +84,7 @@ export class TeamDropdownView {
}});
this.view.webContents.loadURL(getLocalURLString('dropdown.html'));
this.setOrderedTeams();
this.setOrderedServers();
this.windowBounds = MainWindow.getBounds();
this.updateDropdown();
MainWindow.get()?.addBrowserView(this.view);
@@ -94,13 +94,13 @@ export class TeamDropdownView {
log.silly('updateDropdown');
this.view?.webContents.send(
UPDATE_TEAMS_DROPDOWN,
this.teams,
UPDATE_SERVERS_DROPDOWN,
this.servers,
Config.darkMode,
this.windowBounds,
ServerManager.hasServers() ? ServerManager.getCurrentServer().id : undefined,
Config.enableServerManagement,
this.hasGPOTeams,
this.hasGPOServers,
this.expired,
this.mentions,
this.unreads,
@@ -108,7 +108,7 @@ export class TeamDropdownView {
}
private updateServers = () => {
this.setOrderedTeams();
this.setOrderedServers();
this.updateDropdown();
}
@@ -137,7 +137,7 @@ export class TeamDropdownView {
this.view.setBounds(this.bounds);
MainWindow.get()?.setTopBrowserView(this.view);
this.view.webContents.focus();
MainWindow.sendToRenderer(OPEN_TEAMS_DROPDOWN);
MainWindow.sendToRenderer(OPEN_SERVERS_DROPDOWN);
this.isOpen = true;
}
@@ -145,7 +145,7 @@ export class TeamDropdownView {
log.debug('handleClose');
this.view?.setBounds(this.getBounds(0, 0));
MainWindow.sendToRenderer(CLOSE_TEAMS_DROPDOWN);
MainWindow.sendToRenderer(CLOSE_SERVERS_DROPDOWN);
this.isOpen = false;
}
@@ -174,7 +174,7 @@ export class TeamDropdownView {
private reduceNotifications = <T>(inputMap: Map<string, T>, items: Map<string, T>, modifier: (base?: T, value?: T) => T) => {
inputMap.clear();
return [...items.keys()].reduce((map, key) => {
const view = ServerManager.getTab(key);
const view = ServerManager.getView(key);
if (!view) {
return map;
}
@@ -183,11 +183,11 @@ export class TeamDropdownView {
}, inputMap);
}
private setOrderedTeams = () => {
this.teams = ServerManager.getOrderedServers().map((team) => team.toMattermostTeam());
this.hasGPOTeams = this.teams.some((srv) => srv.isPredefined);
private setOrderedServers = () => {
this.servers = ServerManager.getOrderedServers().map((server) => server.toUniqueServer());
this.hasGPOServers = this.servers.some((srv) => srv.isPredefined);
}
}
const teamDropdownView = new TeamDropdownView();
export default teamDropdownView;
const serverDropdownView = new ServerDropdownView();
export default serverDropdownView;

View File

@@ -7,13 +7,13 @@
import {dialog} from 'electron';
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, SET_ACTIVE_VIEW} from 'common/communication';
import {TAB_MESSAGING} from 'common/tabs/TabView';
import {TAB_MESSAGING} from 'common/views/View';
import ServerManager from 'common/servers/serverManager';
import urlUtils from 'common/utils/url';
import MainWindow from 'main/windows/mainWindow';
import {MattermostView} from './MattermostView';
import {MattermostBrowserView} from './MattermostBrowserView';
import {ViewManager} from './viewManager';
import LoadingScreen from './loadingScreen';
@@ -30,12 +30,9 @@ jest.mock('electron', () => ({
handle: jest.fn(),
},
}));
jest.mock('common/config', () => ({
teams: [],
}));
jest.mock('common/tabs/TabView', () => ({
getTabViewName: jest.fn((a, b) => `${a}-${b}`),
TAB_MESSAGING: 'tab',
jest.mock('common/views/View', () => ({
getViewName: jest.fn((a, b) => `${a}-${b}`),
TAB_MESSAGING: 'view',
}));
jest.mock('common/servers/MattermostServer', () => ({
@@ -79,7 +76,7 @@ jest.mock('common/servers/serverManager', () => ({
getLastActiveServer: jest.fn(),
getLastActiveTabForServer: jest.fn(),
updateLastActive: jest.fn(),
lookupTabByURL: jest.fn(),
lookupViewByURL: jest.fn(),
getRemoteInfo: jest.fn(),
on: jest.fn(),
getServerLog: () => ({
@@ -100,8 +97,8 @@ jest.mock('common/servers/serverManager', () => ({
}),
}));
jest.mock('./MattermostView', () => ({
MattermostView: jest.fn(),
jest.mock('./MattermostBrowserView', () => ({
MattermostBrowserView: jest.fn(),
}));
jest.mock('./modalManager', () => ({
@@ -121,12 +118,12 @@ describe('main/views/viewManager', () => {
beforeEach(() => {
viewManager.showById = jest.fn();
MainWindow.get.mockReturnValue({});
MattermostView.mockImplementation((tab) => ({
MattermostBrowserView.mockImplementation((view) => ({
on: jest.fn(),
load: loadFn,
once: onceFn,
destroy: destroyFn,
id: tab.id,
id: view.id,
}));
});
@@ -136,21 +133,21 @@ describe('main/views/viewManager', () => {
viewManager.views = new Map();
});
it('should add closed tabs to closedViews', () => {
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: false});
expect(viewManager.closedViews.has('tab1')).toBe(true);
it('should add closed views to closedViews', () => {
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: false});
expect(viewManager.closedViews.has('view1')).toBe(true);
});
it('should remove from remove from closedViews when the tab is open', () => {
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 remove from remove from closedViews when the view is open', () => {
viewManager.closedViews.set('view1', {});
expect(viewManager.closedViews.has('view1')).toBe(true);
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: true});
expect(viewManager.closedViews.has('view1')).toBe(false);
});
it('should add view to views map and add listeners', () => {
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: true}, 'http://server-1.com/subpath');
expect(viewManager.views.has('tab1')).toBe(true);
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: true}, 'http://server-1.com/subpath');
expect(viewManager.views.has('view1')).toBe(true);
expect(onceFn).toHaveBeenCalledWith(LOAD_SUCCESS, viewManager.activateView);
expect(loadFn).toHaveBeenCalledWith('http://server-1.com/subpath');
});
@@ -173,14 +170,14 @@ describe('main/views/viewManager', () => {
const onceFn = jest.fn();
const loadFn = jest.fn();
const destroyFn = jest.fn();
MattermostView.mockImplementation((tab) => ({
MattermostBrowserView.mockImplementation((view) => ({
on: jest.fn(),
load: loadFn,
once: onceFn,
destroy: destroyFn,
id: tab.id,
id: view.id,
updateServerInfo: jest.fn(),
tab,
view,
}));
});
@@ -193,45 +190,45 @@ describe('main/views/viewManager', () => {
it('should recycle existing views', () => {
const makeSpy = jest.spyOn(viewManager, 'makeView');
const view = new MattermostView({
id: 'tab1',
const view = new MattermostBrowserView({
id: 'view1',
server: {
id: 'server1',
},
});
viewManager.views.set('tab1', view);
viewManager.views.set('view1', view);
ServerManager.getAllServers.mockReturnValue([{
id: 'server1',
url: new URL('http://server1.com'),
}]);
ServerManager.getOrderedTabsForServer.mockReturnValue([
{
id: 'tab1',
id: 'view1',
isOpen: true,
},
]);
viewManager.handleReloadConfiguration();
expect(viewManager.views.get('tab1')).toBe(view);
expect(viewManager.views.get('view1')).toBe(view);
expect(makeSpy).not.toHaveBeenCalled();
makeSpy.mockRestore();
});
it('should close tabs that arent open', () => {
it('should close views that arent open', () => {
ServerManager.getAllServers.mockReturnValue([{
id: 'server1',
url: new URL('http://server1.com'),
}]);
ServerManager.getOrderedTabsForServer.mockReturnValue([
{
id: 'tab1',
id: 'view1',
isOpen: false,
},
]);
viewManager.handleReloadConfiguration();
expect(viewManager.closedViews.has('tab1')).toBe(true);
expect(viewManager.closedViews.has('view1')).toBe(true);
});
it('should create new views for new tabs', () => {
it('should create new views for new views', () => {
const makeSpy = jest.spyOn(viewManager, 'makeView');
ServerManager.getAllServers.mockReturnValue([{
id: 'server1',
@@ -240,10 +237,10 @@ describe('main/views/viewManager', () => {
}]);
ServerManager.getOrderedTabsForServer.mockReturnValue([
{
id: 'tab1',
name: 'tab1',
id: 'view1',
name: 'view1',
isOpen: true,
url: new URL('http://server1.com/tab'),
url: new URL('http://server1.com/view'),
},
]);
viewManager.handleReloadConfiguration();
@@ -254,10 +251,10 @@ describe('main/views/viewManager', () => {
url: new URL('http://server1.com'),
},
{
id: 'tab1',
name: 'tab1',
id: 'view1',
name: 'view1',
isOpen: true,
url: new URL('http://server1.com/tab'),
url: new URL('http://server1.com/view'),
},
);
makeSpy.mockRestore();
@@ -265,27 +262,27 @@ describe('main/views/viewManager', () => {
it('should set focus to current view on reload', () => {
const view = {
id: 'tab1',
tab: {
id: 'view1',
view: {
server: {
id: 'server-1',
},
id: 'tab1',
id: 'view1',
url: new URL('http://server1.com'),
},
destroy: jest.fn(),
updateServerInfo: jest.fn(),
focus: jest.fn(),
};
viewManager.currentView = 'tab1';
viewManager.views.set('tab1', view);
viewManager.currentView = 'view1';
viewManager.views.set('view1', view);
ServerManager.getAllServers.mockReturnValue([{
id: 'server1',
url: new URL('http://server1.com'),
}]);
ServerManager.getOrderedTabsForServer.mockReturnValue([
{
id: 'tab1',
id: 'view1',
isOpen: true,
},
]);
@@ -295,23 +292,23 @@ describe('main/views/viewManager', () => {
it('should show initial if currentView has been removed', () => {
const view = {
id: 'tab1',
tab: {
id: 'tab1',
id: 'view1',
view: {
id: 'view1',
url: new URL('http://server1.com'),
},
destroy: jest.fn(),
updateServerInfo: jest.fn(),
};
viewManager.currentView = 'tab1';
viewManager.views.set('tab1', view);
viewManager.currentView = 'view1';
viewManager.views.set('view1', view);
ServerManager.getAllServers.mockReturnValue([{
id: 'server2',
url: new URL('http://server2.com'),
}]);
ServerManager.getOrderedTabsForServer.mockReturnValue([
{
id: 'tab1',
id: 'view1',
isOpen: false,
},
]);
@@ -321,21 +318,21 @@ describe('main/views/viewManager', () => {
it('should remove unused views', () => {
const view = {
name: 'tab1',
tab: {
name: 'tab1',
name: 'view1',
view: {
name: 'view1',
url: new URL('http://server1.com'),
},
destroy: jest.fn(),
};
viewManager.views.set('tab1', view);
viewManager.views.set('view1', view);
ServerManager.getAllServers.mockReturnValue([{
id: 'server2',
url: new URL('http://server2.com'),
}]);
ServerManager.getOrderedTabsForServer.mockReturnValue([
{
id: 'tab1',
id: 'view1',
isOpen: false,
},
]);
@@ -360,11 +357,11 @@ describe('main/views/viewManager', () => {
jest.resetAllMocks();
});
it('should show last active tab and server', () => {
it('should show last active view and server', () => {
ServerManager.getLastActiveServer.mockReturnValue({id: 'server-1'});
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-1'});
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-1'});
viewManager.showInitial();
expect(viewManager.showById).toHaveBeenCalledWith('tab-1');
expect(viewManager.showById).toHaveBeenCalledWith('view-1');
});
it('should open new server modal when no servers exist', () => {
@@ -385,7 +382,7 @@ describe('main/views/viewManager', () => {
order: 0,
tabs: [
{
name: 'tab-messaging',
name: 'view-messaging',
order: 0,
isOpen: true,
},
@@ -403,9 +400,9 @@ describe('main/views/viewManager', () => {
},
];
const view1 = {
id: 'server-1_tab-messaging',
id: 'server-1_view-messaging',
isLoggedIn: true,
tab: {
view: {
type: TAB_MESSAGING,
server: {
url: 'http://server-1.com',
@@ -416,21 +413,21 @@ describe('main/views/viewManager', () => {
const view2 = {
...view1,
id: 'server-1_other_type_1',
tab: {
...view1.tab,
view: {
...view1.view,
type: 'other_type_1',
},
};
const view3 = {
...view1,
id: 'server-1_other_type_2',
tab: {
...view1.tab,
view: {
...view1.view,
type: 'other_type_2',
},
};
const views = new Map([
['server-1_tab-messaging', view1],
['server-1_view-messaging', view1],
['server-1_other_type_1', view2],
]);
const closedViews = new Map([
@@ -438,7 +435,7 @@ describe('main/views/viewManager', () => {
]);
viewManager.getView = (viewId) => views.get(viewId);
viewManager.isViewClosed = (viewId) => closedViews.has(viewId);
viewManager.openClosedTab = jest.fn();
viewManager.openClosedView = jest.fn();
beforeEach(() => {
ServerManager.getAllServers.mockReturnValue(servers);
@@ -451,24 +448,24 @@ describe('main/views/viewManager', () => {
});
it('should open closed view if pushing to it', () => {
viewManager.openClosedTab.mockImplementation((name) => {
viewManager.openClosedView.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');
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_other_type_2'});
viewManager.handleBrowserHistoryPush(null, 'server-1_view-messaging', '/other_type_2/subpath');
expect(viewManager.openClosedView).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');
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_other_type_1'});
viewManager.handleBrowserHistoryPush(null, 'server-1_view-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'});
it('should ignore redirects to "/" to Messages from other views', () => {
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_view-messaging'});
viewManager.handleBrowserHistoryPush(null, 'server-1_other_type_1', '/');
expect(view1.sendToRenderer).not.toBeCalled();
});
@@ -487,11 +484,11 @@ describe('main/views/viewManager', () => {
send: jest.fn(),
},
},
tab: {
view: {
server: {
name: 'server-1',
},
type: 'tab-1',
type: 'view-1',
},
};
@@ -510,9 +507,9 @@ describe('main/views/viewManager', () => {
...baseView,
isVisible: true,
};
viewManager.views.set('server1-tab1', view);
viewManager.views.set('server1-view1', view);
viewManager.showById('server1-tab1');
viewManager.showById('server1-view1');
expect(viewManager.currentView).toBeUndefined();
expect(view.isReady).not.toBeCalled();
expect(view.show).not.toBeCalled();
@@ -584,7 +581,7 @@ describe('main/views/viewManager', () => {
};
beforeEach(() => {
viewManager.openClosedTab = jest.fn();
viewManager.openClosedView = jest.fn();
});
afterEach(() => {
@@ -594,7 +591,7 @@ describe('main/views/viewManager', () => {
});
it('should load URL into matching view', () => {
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
ServerManager.lookupViewByURL.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');
@@ -602,11 +599,11 @@ describe('main/views/viewManager', () => {
});
it('should send the URL to the view if its already loaded on a 6.0 server', () => {
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
ServerManager.getRemoteInfo.mockReturnValue({serverVersion: '6.0.0'});
const view = {
...baseView,
tab: {
view: {
server: {
url: new URL('http://server-1.com'),
},
@@ -619,7 +616,7 @@ describe('main/views/viewManager', () => {
});
it('should throw error if view is missing', () => {
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
ServerManager.lookupViewByURL.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();
@@ -632,11 +629,11 @@ describe('main/views/viewManager', () => {
expect(dialog.showErrorBox).toHaveBeenCalled();
});
it('should reopen closed tab if called upon', () => {
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
it('should reopen closed view if called upon', () => {
ServerManager.lookupViewByURL.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', 'http://server-1.com/deep/link?thing=yes');
expect(viewManager.openClosedView).toHaveBeenCalledWith('view1', 'http://server-1.com/deep/link?thing=yes');
});
});
});

View File

@@ -11,7 +11,7 @@ import {
LOAD_FAILED,
LOADSCREEN_END,
SET_ACTIVE_VIEW,
OPEN_TAB,
OPEN_VIEW,
BROWSER_HISTORY_PUSH,
UPDATE_URL_VIEW_WIDTH,
SERVERS_UPDATE,
@@ -33,7 +33,7 @@ import {Logger} from 'common/log';
import Utils from 'common/utils/util';
import {MattermostServer} from 'common/servers/MattermostServer';
import ServerManager from 'common/servers/serverManager';
import {TabView, TAB_MESSAGING} from 'common/tabs/TabView';
import {MattermostView, TAB_MESSAGING} from 'common/views/View';
import {parseURL} from 'common/utils/url';
import {localizeMessage} from 'main/i18nManager';
@@ -41,7 +41,7 @@ import MainWindow from 'main/windows/mainWindow';
import {getLocalURLString, getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
import {MattermostView} from './MattermostView';
import {MattermostBrowserView} from './MattermostBrowserView';
import modalManager from './modalManager';
import LoadingScreen from './loadingScreen';
@@ -50,14 +50,14 @@ const URL_VIEW_DURATION = 10 * SECOND;
const URL_VIEW_HEIGHT = 20;
export class ViewManager {
private closedViews: Map<string, {srv: MattermostServer; tab: TabView}>;
private views: Map<string, MattermostView>;
private closedViews: Map<string, {srv: MattermostServer; view: MattermostView}>;
private views: Map<string, MattermostBrowserView>;
private currentView?: string;
private urlViewCancel?: () => void;
constructor() {
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only views on the renderer need that.
this.closedViews = new Map();
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
@@ -102,23 +102,23 @@ export class ViewManager {
return this.closedViews.has(viewId);
}
showById = (tabId: string) => {
this.getViewLogger(tabId).debug('showById', tabId);
showById = (viewId: string) => {
this.getViewLogger(viewId).debug('showById', viewId);
const newView = this.views.get(tabId);
const newView = this.views.get(viewId);
if (newView) {
if (newView.isVisible) {
return;
}
let hidePrevious;
if (this.currentView && this.currentView !== tabId) {
if (this.currentView && this.currentView !== viewId) {
const previous = this.getCurrentView();
if (previous) {
hidePrevious = () => previous.hide();
}
}
this.currentView = tabId;
this.currentView = viewId;
if (!newView.isErrored()) {
newView.show();
if (newView.needsLoadingScreen()) {
@@ -126,10 +126,10 @@ export class ViewManager {
}
}
hidePrevious?.();
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.id, newView.tab.id);
ServerManager.updateLastActive(newView.tab.id);
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.view.server.id, newView.view.id);
ServerManager.updateLastActive(newView.view.id);
} else {
this.getViewLogger(tabId).warn(`Couldn't find a view with name: ${tabId}`);
this.getViewLogger(viewId).warn(`Couldn't find a view with name: ${viewId}`);
}
modalManager.showModal();
}
@@ -175,28 +175,28 @@ export class ViewManager {
handleDeepLink = (url: string | URL) => {
if (url) {
const parsedURL = parseURL(url)!;
const tabView = ServerManager.lookupTabByURL(parsedURL, true);
if (tabView) {
const urlWithSchema = `${tabView.url.origin}${parsedURL.pathname}${parsedURL.search}`;
if (this.closedViews.has(tabView.id)) {
this.openClosedTab(tabView.id, urlWithSchema);
const view = ServerManager.lookupViewByURL(parsedURL, true);
if (view) {
const urlWithSchema = `${view.url.origin}${parsedURL.pathname}${parsedURL.search}`;
if (this.closedViews.has(view.id)) {
this.openClosedView(view.id, urlWithSchema);
} else {
const view = this.views.get(tabView.id);
if (!view) {
log.error(`Couldn't find a view matching the id ${tabView.id}`);
const browserView = this.views.get(view.id);
if (!browserView) {
log.error(`Couldn't find a view matching the id ${view.id}`);
return;
}
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.id);
if (browserView.isReady() && ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion ?? '', '6.0.0')) {
const pathName = `/${urlWithSchema.replace(browserView.view.server.url.toString(), '')}`;
browserView.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
this.deeplinkSuccess(browserView.id);
} else {
// attempting to change parsedURL protocol results in it not being modified.
view.resetLoadingStatus();
view.load(urlWithSchema);
view.once(LOAD_SUCCESS, this.deeplinkSuccess);
view.once(LOAD_FAILED, this.deeplinkFailed);
browserView.resetLoadingStatus();
browserView.load(urlWithSchema);
browserView.once(LOAD_SUCCESS, this.deeplinkSuccess);
browserView.once(LOAD_FAILED, this.deeplinkFailed);
}
}
} else {
@@ -225,35 +225,35 @@ export class ViewManager {
*/
private loadServer = (server: MattermostServer) => {
const tabs = ServerManager.getOrderedTabsForServer(server.id);
tabs.forEach((tab) => this.loadView(server, tab));
const views = ServerManager.getOrderedTabsForServer(server.id);
views.forEach((view) => this.loadView(server, view));
}
private loadView = (srv: MattermostServer, tab: TabView, url?: string) => {
if (!tab.isOpen) {
this.closedViews.set(tab.id, {srv, tab});
private loadView = (srv: MattermostServer, view: MattermostView, url?: string) => {
if (!view.isOpen) {
this.closedViews.set(view.id, {srv, view});
return;
}
const view = this.makeView(srv, tab, url);
this.addView(view);
const browserView = this.makeView(srv, view, url);
this.addView(browserView);
}
private makeView = (srv: MattermostServer, tab: TabView, url?: string): MattermostView => {
private makeView = (srv: MattermostServer, view: MattermostView, url?: string): MattermostBrowserView => {
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.on(LOADSCREEN_END, this.finishLoading);
view.on(LOAD_FAILED, this.failLoading);
view.on(UPDATE_TARGET_URL, this.showURLView);
view.load(url);
return view;
const browserView = new MattermostBrowserView(view, {webPreferences: {spellcheck: Config.useSpellChecker}});
browserView.once(LOAD_SUCCESS, this.activateView);
browserView.on(LOADSCREEN_END, this.finishLoading);
browserView.on(LOAD_FAILED, this.failLoading);
browserView.on(UPDATE_TARGET_URL, this.showURLView);
browserView.load(url);
return browserView;
}
private addView = (view: MattermostView): void => {
private addView = (view: MattermostBrowserView): void => {
this.views.set(view.id, view);
if (this.closedViews.has(view.id)) {
this.closedViews.delete(view.id);
@@ -265,8 +265,8 @@ export class ViewManager {
if (ServerManager.hasServers()) {
const lastActiveServer = ServerManager.getCurrentServer();
const lastActiveTab = ServerManager.getLastActiveTabForServer(lastActiveServer.id);
this.showById(lastActiveTab.id);
const lastActiveView = ServerManager.getLastActiveTabForServer(lastActiveServer.id);
this.showById(lastActiveView.id);
} else {
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW);
}
@@ -382,33 +382,33 @@ export class ViewManager {
*/
/** Called when a new configuration is received
* 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. */
* Servers or views have been added or edited. We need to
* close, open, or reload views, taking care to reuse views and
* preserve focus on the currently selected view. */
private handleReloadConfiguration = () => {
log.debug('handleReloadConfiguration');
const currentTabId: string | undefined = this.views.get(this.currentView as string)?.tab.id;
const currentViewId: string | undefined = this.views.get(this.currentView as string)?.view.id;
const current: Map<string, MattermostView> = new Map();
const current: Map<string, MattermostBrowserView> = new Map();
for (const view of this.views.values()) {
current.set(view.tab.id, view);
current.set(view.view.id, view);
}
const views: Map<string, MattermostView> = new Map();
const closed: Map<string, {srv: MattermostServer; tab: TabView}> = new Map();
const views: Map<string, MattermostBrowserView> = new Map();
const closed: Map<string, {srv: MattermostServer; view: MattermostView}> = new Map();
const sortedTabs = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
map((t): [MattermostServer, TabView] => [x, t]));
const sortedViews = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
map((t): [MattermostServer, MattermostView] => [x, t]));
for (const [srv, tab] of sortedTabs) {
const recycle = current.get(tab.id);
if (!tab.isOpen) {
closed.set(tab.id, {srv, tab});
for (const [srv, view] of sortedViews) {
const recycle = current.get(view.id);
if (!view.isOpen) {
closed.set(view.id, {srv, view});
} else if (recycle) {
views.set(tab.id, recycle);
views.set(view.id, recycle);
} else {
views.set(tab.id, this.makeView(srv, tab));
views.set(view.id, this.makeView(srv, view));
}
}
@@ -428,10 +428,10 @@ export class ViewManager {
// commit closed
for (const x of closed.values()) {
this.closedViews.set(x.tab.id, {srv: x.srv, tab: x.tab});
this.closedViews.set(x.view.id, {srv: x.srv, view: x.view});
}
if ((currentTabId && closed.has(currentTabId)) || (this.currentView && this.closedViews.has(this.currentView))) {
if ((currentViewId && closed.has(currentViewId)) || (this.currentView && this.closedViews.has(this.currentView))) {
if (ServerManager.hasServers()) {
this.currentView = undefined;
this.showInitial();
@@ -440,13 +440,13 @@ export class ViewManager {
}
}
// show the focused tab (or initial)
if (currentTabId && views.has(currentTabId)) {
const view = views.get(currentTabId);
// show the focused view (or initial)
if (currentViewId && views.has(currentViewId)) {
const view = views.get(currentViewId);
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);
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, view.view.server.id, view.view.id);
} else {
this.focusCurrentView();
}
@@ -475,17 +475,17 @@ export class ViewManager {
return;
}
let cleanedPathName = pathName;
if (currentView.tab.server.url.pathname !== '/' && pathName.startsWith(currentView.tab.server.url.pathname)) {
cleanedPathName = pathName.replace(currentView.tab.server.url.pathname, '');
if (currentView.view.server.url.pathname !== '/' && pathName.startsWith(currentView.view.server.url.pathname)) {
cleanedPathName = pathName.replace(currentView.view.server.url.pathname, '');
}
const redirectedviewId = ServerManager.lookupTabByURL(`${currentView.tab.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.id || viewId;
const redirectedviewId = ServerManager.lookupViewByURL(`${currentView.view.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.id || viewId;
if (this.isViewClosed(redirectedviewId)) {
// If it's a closed view, just open it and stop
this.openClosedTab(redirectedviewId, `${currentView.tab.server.url}${cleanedPathName}`);
this.openClosedView(redirectedviewId, `${currentView.view.server.url}${cleanedPathName}`);
return;
}
let redirectedView = this.getView(redirectedviewId) || currentView;
if (redirectedView !== currentView && redirectedView?.tab.server.id === ServerManager.getCurrentServer().id && redirectedView?.isLoggedIn) {
if (redirectedView !== currentView && redirectedView?.view.server.id === ServerManager.getCurrentServer().id && redirectedView?.isLoggedIn) {
log.info('redirecting to a new view', redirectedView?.id || viewId);
this.showById(redirectedView?.id || viewId);
} else {
@@ -493,7 +493,7 @@ export class ViewManager {
}
// Special case check for Channels to not force a redirect to "/", causing a refresh
if (!(redirectedView !== currentView && redirectedView?.tab.type === TAB_MESSAGING && cleanedPathName === '/')) {
if (!(redirectedView !== currentView && redirectedView?.view.type === TAB_MESSAGING && cleanedPathName === '/')) {
redirectedView?.sendToRenderer(BROWSER_HISTORY_PUSH, cleanedPathName);
if (redirectedView) {
this.handleBrowserHistoryButton(e, redirectedView.id);
@@ -547,7 +547,7 @@ export class ViewManager {
const currentView = this.getCurrentView();
if (currentView && currentView.currentURL) {
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.tab.url, currentView.currentURL));
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.view.url, currentView.currentURL));
currentView.setBounds(adjustedBounds);
}
}
@@ -556,21 +556,21 @@ export class ViewManager {
* Helper functions
*/
private openClosedTab = (id: string, url?: string) => {
private openClosedView = (id: string, url?: string) => {
if (!this.closedViews.has(id)) {
return;
}
const {srv, tab} = this.closedViews.get(id)!;
tab.isOpen = true;
this.loadView(srv, tab, url);
const {srv, view} = this.closedViews.get(id)!;
view.isOpen = true;
this.loadView(srv, view, url);
this.showById(id);
const view = this.views.get(id)!;
view.isVisible = true;
view.on(LOAD_SUCCESS, () => {
view.isVisible = false;
const browserView = this.views.get(id)!;
browserView.isVisible = true;
browserView.on(LOAD_SUCCESS, () => {
browserView.isVisible = false;
this.showById(id);
});
ipcMain.emit(OPEN_TAB, null, tab.id);
ipcMain.emit(OPEN_VIEW, null, view.id);
}
private getViewLogger = (viewId: string) => {
@@ -585,8 +585,8 @@ export class ViewManager {
return {
id: view.id,
webContentsId: view.webContentsId,
serverName: view.tab.server.name,
tabType: view.tab.type,
serverName: view.view.server.name,
viewType: view.view.type,
};
}
}

View File

@@ -81,7 +81,7 @@ export class WebContentsEventManager {
return CallsWidgetWindow.getURL();
}
return ViewManager.getViewByWebContentsId(webContentsId)?.tab.server.url;
return ViewManager.getViewByWebContentsId(webContentsId)?.view.server.url;
}
private generateWillNavigate = (webContentsId: number) => {
@@ -274,7 +274,7 @@ export class WebContentsEventManager {
return {action: 'deny'};
}
const otherServerURL = ServerManager.lookupTabByURL(parsedURL);
const otherServerURL = ServerManager.lookupViewByURL(parsedURL);
if (otherServerURL && isTeamUrl(otherServerURL.server.url, parsedURL, true)) {
ViewManager.handleDeepLink(parsedURL);
return {action: 'deny'};

View File

@@ -247,7 +247,7 @@ describe('main/windows/callsWidgetWindow', () => {
title: 'call test title #/&',
};
callsWidgetWindow.mainView = {
tab: {
view: {
server: {
url: new URL('http://localhost:8065'),
},
@@ -262,7 +262,7 @@ describe('main/windows/callsWidgetWindow', () => {
it('getWidgetURL - under subpath', () => {
callsWidgetWindow.mainView = {
tab: {
view: {
server: {
url: new URL('http://localhost:8065/subpath'),
},
@@ -339,7 +339,7 @@ describe('main/windows/callsWidgetWindow', () => {
beforeEach(() => {
callsWidgetWindow.options = {callID: 'id'};
callsWidgetWindow.mainView = {
tab: {
view: {
server: {
url: new URL('http://localhost:8065'),
},
@@ -491,7 +491,7 @@ describe('main/windows/callsWidgetWindow', () => {
callsWidgetWindow.close = jest.fn();
callsWidgetWindow.getWidgetURL = jest.fn();
const view = {
name: 'server-1_tab-messaging',
name: 'server-1_view-messaging',
serverInfo: {
server: {
url: new URL('http://server-1.com'),
@@ -526,12 +526,12 @@ describe('main/windows/callsWidgetWindow', () => {
it('should create calls widget window', async () => {
expect(callsWidgetWindow.win).toBeUndefined();
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
expect(callsWidgetWindow.win).toBeDefined();
});
it('should create with correct initial configuration', async () => {
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
expect(BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
@@ -560,7 +560,7 @@ describe('main/windows/callsWidgetWindow', () => {
const window = {webContents: {id: 2}};
callsWidgetWindow.win = window;
callsWidgetWindow.options = {callID: 'test'};
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
expect(callsWidgetWindow.win).toEqual(window);
});
@@ -568,7 +568,7 @@ describe('main/windows/callsWidgetWindow', () => {
const window = {webContents: {id: 2}};
callsWidgetWindow.win = window;
callsWidgetWindow.getCallID = jest.fn(() => 'test');
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test2'});
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test2'});
expect(callsWidgetWindow.win).not.toEqual(window);
});
});
@@ -580,18 +580,18 @@ describe('main/windows/callsWidgetWindow', () => {
send: jest.fn(),
},
};
const teams = [
const servers = [
{
name: 'server-1',
order: 1,
tabs: [
views: [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
@@ -599,24 +599,24 @@ describe('main/windows/callsWidgetWindow', () => {
}, {
name: 'server-2',
order: 0,
tabs: [
views: [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
],
lastActiveTab: 2,
lastActiveView: 2,
},
];
const map = teams.reduce((arr, item) => {
item.tabs.forEach((tab) => {
arr.push([`${item.name}_${tab.name}`, {
const map = servers.reduce((arr, item) => {
item.views.forEach((view) => {
arr.push([`${item.name}_${view.name}`, {
sendToRenderer: jest.fn(),
}]);
});
@@ -649,9 +649,9 @@ describe('main/windows/callsWidgetWindow', () => {
},
]);
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('desktop-sources-result', [
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('desktop-sources-result', [
{
id: 'screen0',
},
@@ -663,11 +663,11 @@ describe('main/windows/callsWidgetWindow', () => {
it('should send error with no sources', async () => {
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]);
await callsWidgetWindow.handleGetDesktopSources('server-2_tab-1', null);
await callsWidgetWindow.handleGetDesktopSources('server-2_view-1', null);
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions',
});
expect(views.get('server-2_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
expect(views.get('server-2_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions',
});
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
@@ -684,16 +684,16 @@ describe('main/windows/callsWidgetWindow', () => {
]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen');
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions',
});
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions',
});
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledTimes(1);
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledTimes(1);
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
});
@@ -713,7 +713,7 @@ describe('main/windows/callsWidgetWindow', () => {
]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
expect(callsWidgetWindow.missingScreensharePermissions).toBe(true);
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1);
@@ -721,11 +721,11 @@ describe('main/windows/callsWidgetWindow', () => {
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions',
});
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions',
});
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2);
expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1);
@@ -739,25 +739,25 @@ describe('main/windows/callsWidgetWindow', () => {
describe('handleDesktopSourcesModalRequest', () => {
const callsWidgetWindow = new CallsWidgetWindow();
callsWidgetWindow.mainView = {
tab: {
view: {
server: {
id: 'server-1',
},
},
sendToRenderer: jest.fn(),
};
const teams = [
const servers = [
{
name: 'server-1',
order: 1,
tabs: [
views: [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
@@ -765,24 +765,24 @@ describe('main/windows/callsWidgetWindow', () => {
}, {
name: 'server-2',
order: 0,
tabs: [
views: [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
],
lastActiveTab: 2,
lastActiveView: 2,
},
];
const map = teams.reduce((arr, item) => {
item.tabs.forEach((tab) => {
arr.push([`${item.name}_${tab.name}`, {}]);
const map = servers.reduce((arr, item) => {
item.views.forEach((view) => {
arr.push([`${item.name}_${view.name}`, {}]);
});
return arr;
}, []);
@@ -805,7 +805,7 @@ describe('main/windows/callsWidgetWindow', () => {
describe('handleCallsWidgetChannelLinkClick', () => {
const callsWidgetWindow = new CallsWidgetWindow();
callsWidgetWindow.mainView = {
tab: {
view: {
server: {
id: 'server-2',
},
@@ -813,18 +813,18 @@ describe('main/windows/callsWidgetWindow', () => {
sendToRenderer: jest.fn(),
};
callsWidgetWindow.getChannelURL = jest.fn();
const teams = [
const servers = [
{
name: 'server-1',
order: 1,
tabs: [
views: [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
@@ -832,24 +832,24 @@ describe('main/windows/callsWidgetWindow', () => {
}, {
name: 'server-2',
order: 0,
tabs: [
views: [
{
name: 'tab-1',
name: 'view-1',
order: 0,
isOpen: false,
},
{
name: 'tab-2',
name: 'view-2',
order: 2,
isOpen: true,
},
],
lastActiveTab: 2,
lastActiveView: 2,
},
];
const map = teams.reduce((arr, item) => {
item.tabs.forEach((tab) => {
arr.push([`${item.name}_${tab.name}`, {}]);
const map = servers.reduce((arr, item) => {
item.views.forEach((view) => {
arr.push([`${item.name}_${view.name}`, {}]);
});
return arr;
}, []);
@@ -872,7 +872,7 @@ describe('main/windows/callsWidgetWindow', () => {
describe('handleCallsError', () => {
const callsWidgetWindow = new CallsWidgetWindow();
callsWidgetWindow.mainView = {
tab: {
view: {
server: {
id: 'server-2',
},
@@ -899,7 +899,7 @@ describe('main/windows/callsWidgetWindow', () => {
describe('handleCallsLinkClick', () => {
const view = {
tab: {
view: {
server: {
id: 'server-1',
},

View File

@@ -14,7 +14,7 @@ import {
CallsWidgetWindowConfig,
} from 'types/calls';
import {MattermostView} from 'main/views/MattermostView';
import {MattermostBrowserView} from 'main/views/MattermostBrowserView';
import {getLocalPreload, openScreensharePermissionsSettingsMacOS, resetScreensharePermissionsMacOS} from 'main/utils';
@@ -47,7 +47,7 @@ const log = new Logger('CallsWidgetWindow');
export class CallsWidgetWindow {
private win?: BrowserWindow;
private mainView?: MattermostView;
private mainView?: MattermostBrowserView;
private options?: CallsWidgetWindowConfig;
private missingScreensharePermissions?: boolean;
@@ -82,7 +82,7 @@ export class CallsWidgetWindow {
}
private get serverID() {
return this.mainView?.tab.server.id;
return this.mainView?.view.server.id;
}
/**
@@ -101,7 +101,7 @@ export class CallsWidgetWindow {
if (!this.mainView) {
return undefined;
}
const u = parseURL(this.mainView.tab.server.url.toString()) as URL;
const u = parseURL(this.mainView.view.server.url.toString()) as URL;
u.pathname = getFormattedPathName(u.pathname);
u.pathname += `plugins/${CALLS_PLUGIN_ID}/standalone/widget.html`;
@@ -119,7 +119,7 @@ export class CallsWidgetWindow {
return u.toString();
}
private init = (view: MattermostView, options: CallsWidgetWindowConfig) => {
private init = (view: MattermostBrowserView, options: CallsWidgetWindowConfig) => {
this.win = new BrowserWindow({
width: MINIMUM_CALLS_WIDGET_WIDTH,
height: MINIMUM_CALLS_WIDGET_HEIGHT,
@@ -271,7 +271,7 @@ export class CallsWidgetWindow {
if (!parsedURL) {
return {action: 'deny' as const};
}
if (isCallsPopOutURL(this.mainView?.tab.server.url, parsedURL, this.options?.callID)) {
if (isCallsPopOutURL(this.mainView?.view.server.url, parsedURL, this.options?.callID)) {
return {
action: 'allow' as const,
overrideBrowserWindowOptions: {

View File

@@ -421,7 +421,7 @@ describe('main/windows/mainWindow', () => {
expect(window.setFullScreen).toHaveBeenCalledWith(false);
});
it('should select tabs using alt+cmd+arrow keys on Mac', () => {
it('should select views using alt+cmd+arrow keys on Mac', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'darwin',