Migrate viewManager to singleton (#2656)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, app, ipcMain} from 'electron';
|
||||
import {BrowserView, app} from 'electron';
|
||||
import {BrowserViewConstructorOptions, Event, Input} from 'electron/main';
|
||||
|
||||
import {EventEmitter} from 'events';
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
LOAD_FAILED,
|
||||
UPDATE_TARGET_URL,
|
||||
IS_UNREAD,
|
||||
UNREAD_RESULT,
|
||||
TOGGLE_BACK_BUTTON,
|
||||
SET_VIEW_OPTIONS,
|
||||
LOADSCREEN_END,
|
||||
BROWSER_HISTORY_BUTTON,
|
||||
} from 'common/communication';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {TabView, TabTuple} from 'common/tabs/TabView';
|
||||
@@ -234,7 +234,6 @@ export class MattermostView extends EventEmitter {
|
||||
WindowManager.sendToRenderer(LOAD_SUCCESS, this.tab.name);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
if (this.status === Status.LOADING) {
|
||||
ipcMain.on(UNREAD_RESULT, this.handleFaviconIsUnread);
|
||||
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
||||
this.findUnreadState(null);
|
||||
}
|
||||
@@ -272,6 +271,32 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
hide = () => this.show(false);
|
||||
|
||||
openFind = () => {
|
||||
this.view.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
}
|
||||
|
||||
goToOffset = (offset: number) => {
|
||||
if (this.view.webContents.canGoToOffset(offset)) {
|
||||
try {
|
||||
this.view.webContents.goToOffset(offset);
|
||||
this.updateHistoryButton();
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateHistoryButton = () => {
|
||||
if (urlUtils.parseURL(this.view.webContents.getURL())?.toString() === this.tab.url.toString()) {
|
||||
this.view.webContents.clearHistory();
|
||||
this.isAtRoot = true;
|
||||
} else {
|
||||
this.isAtRoot = false;
|
||||
}
|
||||
this.view.webContents.send(BROWSER_HISTORY_BUTTON, this.view.webContents.canGoBack(), this.view.webContents.canGoForward());
|
||||
}
|
||||
|
||||
setBounds = (boundaries: Electron.Rectangle) => {
|
||||
this.view.setBounds(boundaries);
|
||||
}
|
||||
@@ -422,14 +447,4 @@ export class MattermostView extends EventEmitter {
|
||||
log.error(err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
// if favicon is null, it means it is the initial load,
|
||||
// so don't memoize as we don't have the favicons and there is no rush to find out.
|
||||
handleFaviconIsUnread = (e: Event, favicon: string, viewName: string, result: boolean) => {
|
||||
log.silly('handleFaviconIsUnread', {favicon, viewName, result});
|
||||
|
||||
if (this.tab && viewName === this.tab.name) {
|
||||
appState.updateUnreads(viewName, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -61,6 +61,10 @@ jest.mock('electron', () => {
|
||||
Notification: NotificationMock,
|
||||
};
|
||||
});
|
||||
jest.mock('main/downloadsManager', () => ({
|
||||
onOpen: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn(),
|
||||
getBounds: jest.fn(),
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as WindowManager from '../windows/windowManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import {ModalManager} from './modalManager';
|
||||
|
||||
@@ -26,9 +26,11 @@ jest.mock('main/views/webContentEvents', () => ({
|
||||
jest.mock('./modalView', () => ({
|
||||
ModalView: jest.fn(),
|
||||
}));
|
||||
jest.mock('../windows/windowManager', () => ({
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
focusCurrentView: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/windowManager', () => ({
|
||||
sendToRenderer: jest.fn(),
|
||||
focusBrowserView: jest.fn(),
|
||||
}));
|
||||
jest.mock('process', () => ({
|
||||
env: {},
|
||||
@@ -161,7 +163,7 @@ describe('main/views/modalManager', () => {
|
||||
modalManager.modalQueue.pop();
|
||||
modalManager.modalPromises.delete('test2');
|
||||
modalManager.handleModalFinished('resolve', {sender: {id: 1}}, 'something');
|
||||
expect(WindowManager.focusBrowserView).toBeCalled();
|
||||
expect(ViewManager.focusCurrentView).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -22,6 +22,7 @@ import {Logger} from 'common/log';
|
||||
|
||||
import {getAdjustedWindowBoundaries} from 'main/utils';
|
||||
import WebContentsEventManager from 'main/views/webContentEvents';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
|
||||
import {ModalView} from './modalView';
|
||||
@@ -115,7 +116,7 @@ export class ModalManager {
|
||||
this.showModal();
|
||||
} else {
|
||||
WindowManager.sendToRenderer(MODAL_CLOSE);
|
||||
WindowManager.focusBrowserView();
|
||||
ViewManager.focusCurrentView();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,8 @@
|
||||
import {dialog, ipcMain} from 'electron';
|
||||
import {Tuple as tuple} from '@bloomberg/record-tuple-polyfill';
|
||||
|
||||
import {LOAD_SUCCESS, MAIN_WINDOW_SHOWN, BROWSER_HISTORY_PUSH} from 'common/communication';
|
||||
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, MAIN_WINDOW_SHOWN} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {getTabViewName} from 'common/tabs/TabView';
|
||||
import {equalUrlsIgnoringSubpath} from 'common/utils/url';
|
||||
@@ -28,9 +29,12 @@ jest.mock('electron', () => ({
|
||||
ipcMain: {
|
||||
emit: jest.fn(),
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('common/config', () => ({
|
||||
teams: [],
|
||||
}));
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getTabViewName: jest.fn((a, b) => `${a}-${b}`),
|
||||
TAB_MESSAGING: 'tab',
|
||||
@@ -41,6 +45,9 @@ jest.mock('common/servers/MattermostServer', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('common/utils/url', () => ({
|
||||
isTeamUrl: jest.fn(),
|
||||
isAdminUrl: jest.fn(),
|
||||
cleanPathName: jest.fn(),
|
||||
parseURL: (url) => {
|
||||
try {
|
||||
return new URL(url);
|
||||
@@ -73,6 +80,7 @@ jest.mock('./modalManager', () => ({
|
||||
showModal: jest.fn(),
|
||||
}));
|
||||
jest.mock('./webContentEvents', () => ({}));
|
||||
jest.mock('../appState', () => ({}));
|
||||
|
||||
describe('main/views/viewManager', () => {
|
||||
describe('loadView', () => {
|
||||
@@ -119,12 +127,11 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadViewIfNeeded', () => {
|
||||
describe('handleAppLoggedIn', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
viewManager.views = new Map();
|
||||
});
|
||||
|
||||
it('should reload view when URL is not on subpath of original server URL', () => {
|
||||
@@ -140,7 +147,7 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.reloadViewIfNeeded('view1');
|
||||
viewManager.handleAppLoggedIn({}, 'view1');
|
||||
expect(view.load).toHaveBeenCalledWith(new URL('http://server-1.com/'));
|
||||
});
|
||||
|
||||
@@ -157,7 +164,7 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.reloadViewIfNeeded('view1');
|
||||
viewManager.handleAppLoggedIn({}, 'view1');
|
||||
expect(view.load).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -174,7 +181,7 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.reloadViewIfNeeded('view1');
|
||||
viewManager.handleAppLoggedIn({}, 'view1');
|
||||
expect(view.load).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -186,6 +193,7 @@ describe('main/views/viewManager', () => {
|
||||
viewManager.loadView = jest.fn();
|
||||
viewManager.showByName = jest.fn();
|
||||
viewManager.showInitial = jest.fn();
|
||||
|
||||
const mainWindow = {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
@@ -225,14 +233,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should recycle existing views', () => {
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
const view = new MattermostView({
|
||||
name: 'server1-tab1',
|
||||
urlTypeTuple: tuple(new URL('http://server1.com').href, 'tab1'),
|
||||
server: 'server1',
|
||||
});
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.reloadConfiguration([
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
@@ -244,14 +245,22 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
];
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
const view = new MattermostView({
|
||||
name: 'server1-tab1',
|
||||
urlTypeTuple: tuple(new URL('http://server1.com').href, 'tab1'),
|
||||
server: 'server1',
|
||||
});
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.views.get('server1-tab1')).toBe(view);
|
||||
expect(makeSpy).not.toHaveBeenCalled();
|
||||
makeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should close tabs that arent open', () => {
|
||||
viewManager.reloadConfiguration([
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
@@ -263,13 +272,14 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.closedViews.has('server1-tab1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should create new views for new tabs', () => {
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
viewManager.reloadConfiguration([
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
@@ -281,7 +291,8 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(makeSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'server1',
|
||||
@@ -313,7 +324,7 @@ describe('main/views/viewManager', () => {
|
||||
};
|
||||
viewManager.currentView = 'server1-tab1';
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.reloadConfiguration([
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server1',
|
||||
url: 'http://server1.com',
|
||||
@@ -325,7 +336,8 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.showByName).toHaveBeenCalledWith('server1-tab1');
|
||||
});
|
||||
|
||||
@@ -342,7 +354,7 @@ describe('main/views/viewManager', () => {
|
||||
};
|
||||
viewManager.currentView = 'server1-tab1';
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.reloadConfiguration([
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server2',
|
||||
url: 'http://server2.com',
|
||||
@@ -354,7 +366,8 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(viewManager.showInitial).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -368,7 +381,7 @@ describe('main/views/viewManager', () => {
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.reloadConfiguration([
|
||||
Config.teams = [
|
||||
{
|
||||
name: 'server2',
|
||||
url: 'http://server2.com',
|
||||
@@ -380,65 +393,64 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
];
|
||||
viewManager.reloadConfiguration();
|
||||
expect(view.destroy).toBeCalled();
|
||||
expect(viewManager.showInitial).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showInitial', () => {
|
||||
const teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}];
|
||||
const viewManager = new ViewManager({});
|
||||
viewManager.getServers = () => teams.concat();
|
||||
|
||||
beforeEach(() => {
|
||||
Config.teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
}];
|
||||
viewManager.showByName = jest.fn();
|
||||
getTabViewName.mockImplementation((server, tab) => `${server}_${tab}`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
viewManager.getServers = () => teams;
|
||||
delete viewManager.lastActiveServer;
|
||||
});
|
||||
|
||||
@@ -454,7 +466,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should show last active tab of first server', () => {
|
||||
viewManager.getServers = () => [{
|
||||
Config.teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
@@ -501,7 +513,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should show next tab when last active tab is closed', () => {
|
||||
viewManager.getServers = () => [{
|
||||
Config.teams = [{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
@@ -553,7 +565,7 @@ describe('main/views/viewManager', () => {
|
||||
send: jest.fn(),
|
||||
},
|
||||
};
|
||||
viewManager.getServers = () => [];
|
||||
Config.teams = [];
|
||||
viewManager.showInitial();
|
||||
expect(ipcMain.emit).toHaveBeenCalledWith(MAIN_WINDOW_SHOWN);
|
||||
});
|
||||
@@ -654,8 +666,8 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
describe('getViewByURL', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
viewManager.getServers = () => [
|
||||
const viewManager = new ViewManager();
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
@@ -696,6 +708,7 @@ describe('main/views/viewManager', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Config.teams = servers.concat();
|
||||
MattermostServer.mockImplementation((name, url) => ({
|
||||
name,
|
||||
url: new URL(url),
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, dialog, ipcMain, IpcMainEvent} from 'electron';
|
||||
import {BrowserView, dialog, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserViewConstructorOptions} from 'electron/main';
|
||||
|
||||
import {Tuple as tuple} from '@bloomberg/record-tuple-polyfill';
|
||||
@@ -20,6 +19,13 @@ import {
|
||||
UPDATE_LAST_ACTIVE,
|
||||
UPDATE_URL_VIEW_WIDTH,
|
||||
MAIN_WINDOW_SHOWN,
|
||||
RELOAD_CURRENT_VIEW,
|
||||
REACT_APP_INITIALIZED,
|
||||
APP_LOGGED_IN,
|
||||
BROWSER_HISTORY_BUTTON,
|
||||
APP_LOGGED_OUT,
|
||||
UNREAD_RESULT,
|
||||
GET_VIEW_NAME,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
@@ -35,6 +41,7 @@ import {localizeMessage} from 'main/i18nManager';
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import * as appState from '../appState';
|
||||
import {getLocalURLString, getLocalPreload} from '../utils';
|
||||
|
||||
import {MattermostView} from './MattermostView';
|
||||
@@ -47,32 +54,200 @@ const URL_VIEW_DURATION = 10 * SECOND;
|
||||
const URL_VIEW_HEIGHT = 20;
|
||||
|
||||
export class ViewManager {
|
||||
lastActiveServer?: number;
|
||||
viewOptions: BrowserViewConstructorOptions;
|
||||
closedViews: Map<string, {srv: MattermostServer; tab: Tab}>;
|
||||
views: Map<string, MattermostView>;
|
||||
currentView?: string;
|
||||
urlView?: BrowserView;
|
||||
urlViewCancel?: () => void;
|
||||
private closedViews: Map<string, {srv: MattermostServer; tab: Tab}>;
|
||||
private views: Map<string, MattermostView>;
|
||||
private currentView?: string;
|
||||
|
||||
private urlViewCancel?: () => void;
|
||||
|
||||
private lastActiveServer?: number;
|
||||
private viewOptions: BrowserViewConstructorOptions;
|
||||
|
||||
constructor() {
|
||||
this.lastActiveServer = Config.lastActiveTeam;
|
||||
this.viewOptions = {webPreferences: {spellcheck: Config.useSpellChecker}};
|
||||
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
|
||||
this.closedViews = new Map();
|
||||
|
||||
ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized);
|
||||
ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush);
|
||||
ipcMain.on(BROWSER_HISTORY_BUTTON, this.handleBrowserHistoryButton);
|
||||
ipcMain.on(APP_LOGGED_IN, this.handleAppLoggedIn);
|
||||
ipcMain.on(APP_LOGGED_OUT, this.handleAppLoggedOut);
|
||||
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
|
||||
ipcMain.on(UNREAD_RESULT, this.handleFaviconIsUnread);
|
||||
ipcMain.handle(GET_VIEW_NAME, this.handleGetViewName);
|
||||
}
|
||||
|
||||
getServers = () => {
|
||||
return Config.teams.concat();
|
||||
init = () => {
|
||||
this.getServers().forEach((server) => this.loadServer(server));
|
||||
this.showInitial();
|
||||
}
|
||||
|
||||
loadServer = (server: TeamWithTabs) => {
|
||||
getView = (viewName: string) => {
|
||||
return this.views.get(viewName);
|
||||
}
|
||||
|
||||
getCurrentView = () => {
|
||||
if (this.currentView) {
|
||||
return this.views.get(this.currentView);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getViewByWebContentsId = (webContentsId: number) => {
|
||||
return [...this.views.values()].find((view) => view.view.webContents.id === webContentsId);
|
||||
}
|
||||
|
||||
showByName = (name: string) => {
|
||||
log.debug('viewManager.showByName', name);
|
||||
|
||||
const newView = this.views.get(name);
|
||||
if (newView) {
|
||||
if (newView.isVisible) {
|
||||
return;
|
||||
}
|
||||
if (this.currentView && this.currentView !== name) {
|
||||
const previous = this.getCurrentView();
|
||||
if (previous) {
|
||||
previous.hide();
|
||||
}
|
||||
}
|
||||
|
||||
this.currentView = name;
|
||||
if (!newView.isErrored()) {
|
||||
newView.show();
|
||||
if (newView.needsLoadingScreen()) {
|
||||
LoadingScreen.show();
|
||||
}
|
||||
}
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.name, newView.tab.type);
|
||||
ipcMain.emit(SET_ACTIVE_VIEW, true, newView.tab.server.name, newView.tab.type);
|
||||
if (newView.isReady()) {
|
||||
ipcMain.emit(UPDATE_LAST_ACTIVE, true, newView.tab.server.name, newView.tab.type);
|
||||
} else {
|
||||
log.warn(`couldn't show ${name}, not ready`);
|
||||
}
|
||||
} else {
|
||||
log.warn(`Couldn't find a view with name: ${name}`);
|
||||
}
|
||||
modalManager.showModal();
|
||||
}
|
||||
|
||||
focusCurrentView = () => {
|
||||
if (modalManager.isModalDisplayed()) {
|
||||
modalManager.focusCurrentModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const view = this.getCurrentView();
|
||||
if (view) {
|
||||
view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
reload = () => {
|
||||
const currentView = this.getCurrentView();
|
||||
if (currentView) {
|
||||
LoadingScreen.show();
|
||||
currentView.reload();
|
||||
}
|
||||
}
|
||||
|
||||
sendToAllViews = (channel: string, ...args: unknown[]) => {
|
||||
this.views.forEach((view) => {
|
||||
if (!view.view.webContents.isDestroyed()) {
|
||||
view.view.webContents.send(channel, ...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendToFind = () => {
|
||||
this.getCurrentView()?.openFind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep linking
|
||||
*/
|
||||
|
||||
handleDeepLink = (url: string | URL) => {
|
||||
// TODO: fix for new tabs
|
||||
if (url) {
|
||||
const parsedURL = urlUtils.parseURL(url)!;
|
||||
const tabView = this.getViewByURL(parsedURL, true);
|
||||
if (tabView) {
|
||||
const urlWithSchema = `${urlUtils.parseURL(tabView.url)?.origin}${parsedURL.pathname}${parsedURL.search}`;
|
||||
if (this.closedViews.has(tabView.name)) {
|
||||
this.openClosedTab(tabView.name, urlWithSchema);
|
||||
} else {
|
||||
const view = this.views.get(tabView.name);
|
||||
if (!view) {
|
||||
log.error(`Couldn't find a view matching the name ${tabView.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.isInitialized() && view.serverInfo.remoteInfo.serverVersion && Utils.isVersionGreaterThanOrEqualTo(view.serverInfo.remoteInfo.serverVersion, '6.0.0')) {
|
||||
const pathName = `/${urlWithSchema.replace(view.tab.server.url.toString(), '')}`;
|
||||
view.view.webContents.send(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(view.name);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dialog.showErrorBox(
|
||||
localizeMessage('main.views.viewManager.handleDeepLink.error.title', 'No matching server'),
|
||||
localizeMessage('main.views.viewManager.handleDeepLink.error.body', 'There is no configured server in the app that matches the requested url: {url}', {url: parsedURL.toString()}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private deeplinkSuccess = (viewName: string) => {
|
||||
log.debug('deeplinkSuccess', viewName);
|
||||
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
this.showByName(viewName);
|
||||
view.removeListener(LOAD_FAILED, this.deeplinkFailed);
|
||||
};
|
||||
|
||||
private deeplinkFailed = (viewName: string, err: string, url: string) => {
|
||||
log.error(`[${viewName}] failed to load deeplink ${url}: ${err}`);
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
view.removeListener(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* View loading helpers
|
||||
*/
|
||||
|
||||
private loadServer = (server: TeamWithTabs) => {
|
||||
const srv = new MattermostServer(server.name, server.url);
|
||||
const serverInfo = new ServerInfo(srv);
|
||||
server.tabs.forEach((tab) => this.loadView(srv, serverInfo, tab));
|
||||
}
|
||||
|
||||
makeView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string): MattermostView => {
|
||||
private loadView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string) => {
|
||||
if (!tab.isOpen) {
|
||||
this.closedViews.set(getTabViewName(srv.name, tab.name), {srv, tab});
|
||||
return;
|
||||
}
|
||||
const view = this.makeView(srv, serverInfo, tab, url);
|
||||
this.addView(view);
|
||||
}
|
||||
|
||||
private makeView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string): MattermostView => {
|
||||
const tabView = this.getServerView(srv, tab.name);
|
||||
const view = new MattermostView(tabView, serverInfo, this.viewOptions);
|
||||
view.once(LOAD_SUCCESS, this.activateView);
|
||||
@@ -83,38 +258,153 @@ export class ViewManager {
|
||||
return view;
|
||||
}
|
||||
|
||||
addView = (view: MattermostView): void => {
|
||||
private addView = (view: MattermostView): void => {
|
||||
this.views.set(view.name, view);
|
||||
if (this.closedViews.has(view.name)) {
|
||||
this.closedViews.delete(view.name);
|
||||
}
|
||||
}
|
||||
|
||||
loadView = (srv: MattermostServer, serverInfo: ServerInfo, tab: Tab, url?: string) => {
|
||||
if (!tab.isOpen) {
|
||||
this.closedViews.set(getTabViewName(srv.name, tab.name), {srv, tab});
|
||||
private showInitial = () => {
|
||||
log.verbose('showInitial');
|
||||
|
||||
const servers = this.getServers();
|
||||
if (servers.length) {
|
||||
const element = servers.find((e) => e.order === this.lastActiveServer) || servers.find((e) => e.order === 0);
|
||||
if (element && element.tabs.length) {
|
||||
let tab = element.tabs.find((tab) => tab.order === element.lastActiveTab) || element.tabs.find((tab) => tab.order === 0);
|
||||
if (!tab?.isOpen) {
|
||||
const openTabs = element.tabs.filter((tab) => tab.isOpen);
|
||||
tab = openTabs.find((e) => e.order === 0) || openTabs.concat().sort((a, b) => a.order - b.order)[0];
|
||||
}
|
||||
if (tab) {
|
||||
const tabView = getTabViewName(element.name, tab.name);
|
||||
this.showByName(tabView);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, null, null);
|
||||
ipcMain.emit(MAIN_WINDOW_SHOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mattermost view event handlers
|
||||
*/
|
||||
|
||||
private activateView = (viewName: string) => {
|
||||
log.debug('activateView', viewName);
|
||||
|
||||
if (this.currentView === viewName) {
|
||||
this.showByName(this.currentView);
|
||||
}
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
log.error(`Couldn't find a view with the name ${viewName}`);
|
||||
return;
|
||||
}
|
||||
const view = this.makeView(srv, serverInfo, tab, url);
|
||||
this.addView(view);
|
||||
WebContentsEventManager.addMattermostViewEventListeners(view);
|
||||
}
|
||||
|
||||
reloadViewIfNeeded = (viewName: string) => {
|
||||
const view = this.views.get(viewName);
|
||||
if (view && view.view.webContents.getURL() !== view.tab.url.toString() && !view.view.webContents.getURL().startsWith(view.tab.url.toString())) {
|
||||
view.load(view.tab.url);
|
||||
private finishLoading = (server: string) => {
|
||||
log.debug('finishLoading', server);
|
||||
|
||||
const view = this.views.get(server);
|
||||
if (view && this.getCurrentView() === view) {
|
||||
this.showByName(this.currentView!);
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
|
||||
load = () => {
|
||||
this.getServers().forEach((server) => this.loadServer(server));
|
||||
private failLoading = (tabName: string) => {
|
||||
log.debug('failLoading', tabName);
|
||||
|
||||
LoadingScreen.fade();
|
||||
if (this.currentView === tabName) {
|
||||
this.getCurrentView()?.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private showURLView = (url: URL | string) => {
|
||||
log.silly('showURLView', url);
|
||||
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.urlViewCancel) {
|
||||
this.urlViewCancel();
|
||||
}
|
||||
if (url && url !== '') {
|
||||
const urlString = typeof url === 'string' ? url : url.toString();
|
||||
const preload = getLocalPreload('desktopAPI.js');
|
||||
const urlView = new BrowserView({
|
||||
webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
const query = new Map([['url', urlString]]);
|
||||
const localURL = getLocalURLString('urlView.html', query);
|
||||
urlView.webContents.loadURL(localURL);
|
||||
mainWindow.addBrowserView(urlView);
|
||||
const boundaries = this.views.get(this.currentView || '')?.view.getBounds() ?? mainWindow.getBounds();
|
||||
|
||||
const hideView = () => {
|
||||
delete this.urlViewCancel;
|
||||
try {
|
||||
mainWindow.removeBrowserView(urlView);
|
||||
} catch (e) {
|
||||
log.error('Failed to remove URL view', e);
|
||||
}
|
||||
|
||||
// workaround to eliminate zombie processes
|
||||
// https://github.com/mattermost/desktop/pull/1519
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
urlView.webContents.destroy();
|
||||
};
|
||||
|
||||
const adjustWidth = (event: IpcMainEvent, width: number) => {
|
||||
log.silly('showURLView.adjustWidth', width);
|
||||
|
||||
const bounds = {
|
||||
x: 0,
|
||||
y: (boundaries.height + TAB_BAR_HEIGHT) - URL_VIEW_HEIGHT,
|
||||
width: width + 5, // add some padding to ensure that we don't cut off the border
|
||||
height: URL_VIEW_HEIGHT,
|
||||
};
|
||||
|
||||
log.silly('showURLView setBounds', boundaries, bounds);
|
||||
urlView.setBounds(bounds);
|
||||
};
|
||||
|
||||
ipcMain.on(UPDATE_URL_VIEW_WIDTH, adjustWidth);
|
||||
|
||||
const timeout = setTimeout(hideView,
|
||||
URL_VIEW_DURATION);
|
||||
|
||||
this.urlViewCancel = () => {
|
||||
clearTimeout(timeout);
|
||||
ipcMain.removeListener(UPDATE_URL_VIEW_WIDTH, adjustWidth);
|
||||
hideView();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Handlers
|
||||
*/
|
||||
|
||||
/** 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. */
|
||||
reloadConfiguration = (configServers: TeamWithTabs[]) => {
|
||||
reloadConfiguration = () => {
|
||||
log.debug('reloadConfiguration');
|
||||
|
||||
const focusedTuple: TabTuple | undefined = this.views.get(this.currentView as string)?.urlTypeTuple;
|
||||
@@ -127,7 +417,7 @@ export class ViewManager {
|
||||
const views: Map<TabTuple, MattermostView> = new Map();
|
||||
const closed: Map<TabTuple, {srv: MattermostServer; tab: Tab; name: string}> = new Map();
|
||||
|
||||
const sortedTabs = configServers.flatMap((x) => [...x.tabs].
|
||||
const sortedTabs = this.getServers().flatMap((x) => [...x.tabs].
|
||||
sort((a, b) => a.order - b.order).
|
||||
map((t): [TeamWithTabs, Tab] => [x, t]));
|
||||
|
||||
@@ -167,7 +457,7 @@ export class ViewManager {
|
||||
}
|
||||
|
||||
if ((focusedTuple && closed.has(focusedTuple)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if (configServers.length) {
|
||||
if (this.getServers().length) {
|
||||
this.currentView = undefined;
|
||||
this.showInitial();
|
||||
} else {
|
||||
@@ -188,101 +478,97 @@ export class ViewManager {
|
||||
}
|
||||
}
|
||||
|
||||
showInitial = () => {
|
||||
log.verbose('showInitial');
|
||||
private handleAppLoggedIn = (event: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleAppLoggedIn', viewName);
|
||||
|
||||
const servers = this.getServers();
|
||||
if (servers.length) {
|
||||
const element = servers.find((e) => e.order === this.lastActiveServer) || servers.find((e) => e.order === 0);
|
||||
if (element && element.tabs.length) {
|
||||
let tab = element.tabs.find((tab) => tab.order === element.lastActiveTab) || element.tabs.find((tab) => tab.order === 0);
|
||||
if (!tab?.isOpen) {
|
||||
const openTabs = element.tabs.filter((tab) => tab.isOpen);
|
||||
tab = openTabs.find((e) => e.order === 0) || openTabs.concat().sort((a, b) => a.order - b.order)[0];
|
||||
}
|
||||
if (tab) {
|
||||
const tabView = getTabViewName(element.name, tab.name);
|
||||
this.showByName(tabView);
|
||||
}
|
||||
const view = this.views.get(viewName);
|
||||
if (view && !view.isLoggedIn) {
|
||||
view.isLoggedIn = true;
|
||||
if (view.view.webContents.getURL() !== view.tab.url.toString() && !view.view.webContents.getURL().startsWith(view.tab.url.toString())) {
|
||||
view.load(view.tab.url);
|
||||
}
|
||||
} else {
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, null, null);
|
||||
ipcMain.emit(MAIN_WINDOW_SHOWN);
|
||||
}
|
||||
}
|
||||
|
||||
showByName = (name: string) => {
|
||||
log.debug('showByName', name);
|
||||
private handleAppLoggedOut = (event: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleAppLoggedOut', viewName);
|
||||
|
||||
const newView = this.views.get(name);
|
||||
if (newView) {
|
||||
if (newView.isVisible) {
|
||||
return;
|
||||
}
|
||||
if (this.currentView && this.currentView !== name) {
|
||||
const previous = this.getCurrentView();
|
||||
if (previous) {
|
||||
previous.hide();
|
||||
}
|
||||
}
|
||||
|
||||
this.currentView = name;
|
||||
if (!newView.isErrored()) {
|
||||
newView.show();
|
||||
if (newView.needsLoadingScreen()) {
|
||||
LoadingScreen.show();
|
||||
}
|
||||
}
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.name, newView.tab.type);
|
||||
ipcMain.emit(SET_ACTIVE_VIEW, true, newView.tab.server.name, newView.tab.type);
|
||||
if (newView.isReady()) {
|
||||
ipcMain.emit(UPDATE_LAST_ACTIVE, true, newView.tab.server.name, newView.tab.type);
|
||||
} else {
|
||||
log.warn(`couldn't show ${name}, not ready`);
|
||||
}
|
||||
} else {
|
||||
log.warn(`Couldn't find a view with name: ${name}`);
|
||||
const view = this.views.get(viewName);
|
||||
if (view && view.isLoggedIn) {
|
||||
view.isLoggedIn = false;
|
||||
}
|
||||
modalManager.showModal();
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
if (modalManager.isModalDisplayed()) {
|
||||
modalManager.focusCurrentModal();
|
||||
private handleBrowserHistoryPush = (e: IpcMainEvent, viewName: string, pathName: string) => {
|
||||
log.debug('handleBrowserHistoryPush', {viewName, pathName});
|
||||
|
||||
const currentView = this.views.get(viewName);
|
||||
const cleanedPathName = urlUtils.cleanPathName(currentView?.tab.server.url.pathname || '', pathName);
|
||||
const redirectedViewName = this.getViewByURL(`${currentView?.tab.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.name || viewName;
|
||||
if (this.closedViews.has(redirectedViewName)) {
|
||||
// If it's a closed view, just open it and stop
|
||||
this.openClosedTab(redirectedViewName, `${currentView?.tab.server.url}${cleanedPathName}`);
|
||||
return;
|
||||
}
|
||||
let redirectedView = this.views.get(redirectedViewName) || currentView;
|
||||
if (redirectedView !== currentView && redirectedView?.tab.name === this.currentView && redirectedView?.isLoggedIn) {
|
||||
log.info('redirecting to a new view', redirectedView?.name || viewName);
|
||||
this.showByName(redirectedView?.name || viewName);
|
||||
} else {
|
||||
redirectedView = currentView;
|
||||
}
|
||||
|
||||
// Special case check for Channels to not force a redirect to "/", causing a refresh
|
||||
if (!(redirectedView !== currentView && redirectedView?.tab.type === TAB_MESSAGING && cleanedPathName === '/')) {
|
||||
redirectedView?.view.webContents.send(BROWSER_HISTORY_PUSH, cleanedPathName);
|
||||
if (redirectedView) {
|
||||
this.handleBrowserHistoryButton(e, redirectedView.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleBrowserHistoryButton = (e: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleBrowserHistoryButton', viewName);
|
||||
|
||||
this.getView(viewName)?.updateHistoryButton();
|
||||
}
|
||||
|
||||
private handleReactAppInitialized = (e: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleReactAppInitialized', viewName);
|
||||
|
||||
const view = this.views.get(viewName);
|
||||
if (view) {
|
||||
view.setInitialized();
|
||||
if (this.getCurrentView() === view) {
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleReloadCurrentView = () => {
|
||||
log.debug('handleReloadCurrentView');
|
||||
|
||||
const view = this.getCurrentView();
|
||||
if (view) {
|
||||
view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
activateView = (viewName: string) => {
|
||||
log.debug('activateView', viewName);
|
||||
|
||||
if (this.currentView === viewName) {
|
||||
this.showByName(this.currentView);
|
||||
}
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
log.error(`Couldn't find a view with the name ${viewName}`);
|
||||
return;
|
||||
}
|
||||
WebContentsEventManager.addMattermostViewEventListeners(view);
|
||||
view?.reload();
|
||||
this.showByName(view?.name);
|
||||
}
|
||||
|
||||
finishLoading = (server: string) => {
|
||||
log.debug('finishLoading', server);
|
||||
// if favicon is null, it means it is the initial load,
|
||||
// so don't memoize as we don't have the favicons and there is no rush to find out.
|
||||
private handleFaviconIsUnread = (e: Event, favicon: string, viewName: string, result: boolean) => {
|
||||
log.silly('handleFaviconIsUnread', {favicon, viewName, result});
|
||||
|
||||
const view = this.views.get(server);
|
||||
if (view && this.getCurrentView() === view) {
|
||||
this.showByName(this.currentView!);
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
appState.updateUnreads(viewName, result);
|
||||
}
|
||||
|
||||
openClosedTab = (name: string, url?: string) => {
|
||||
/**
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
private openClosedTab = (name: string, url?: string) => {
|
||||
if (!this.closedViews.has(name)) {
|
||||
return;
|
||||
}
|
||||
@@ -299,142 +585,6 @@ export class ViewManager {
|
||||
ipcMain.emit(OPEN_TAB, null, srv.name, tab.name);
|
||||
}
|
||||
|
||||
failLoading = (tabName: string) => {
|
||||
log.debug('failLoading', tabName);
|
||||
|
||||
LoadingScreen.fade();
|
||||
if (this.currentView === tabName) {
|
||||
this.getCurrentView()?.hide();
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentView() {
|
||||
if (this.currentView) {
|
||||
return this.views.get(this.currentView);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
openViewDevTools = () => {
|
||||
const view = this.getCurrentView();
|
||||
if (view) {
|
||||
view.openDevTools();
|
||||
} else {
|
||||
log.error(`couldn't find ${this.currentView}`);
|
||||
}
|
||||
}
|
||||
|
||||
findViewByWebContent(webContentId: number) {
|
||||
let found = null;
|
||||
let view;
|
||||
const entries = this.views.values();
|
||||
|
||||
for (view of entries) {
|
||||
const wc = view.getWebContents();
|
||||
if (wc && wc.id === webContentId) {
|
||||
found = view;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
showURLView = (url: URL | string) => {
|
||||
log.silly('showURLView', url);
|
||||
|
||||
if (this.urlViewCancel) {
|
||||
this.urlViewCancel();
|
||||
}
|
||||
if (url && url !== '') {
|
||||
const urlString = typeof url === 'string' ? url : url.toString();
|
||||
const preload = getLocalPreload('desktopAPI.js');
|
||||
const urlView = new BrowserView({
|
||||
webPreferences: {
|
||||
preload,
|
||||
|
||||
// Workaround for this issue: https://github.com/electron/electron/issues/30993
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
transparent: true,
|
||||
}});
|
||||
const query = new Map([['url', urlString]]);
|
||||
const localURL = getLocalURLString('urlView.html', query);
|
||||
urlView.webContents.loadURL(localURL);
|
||||
MainWindow.get()?.addBrowserView(urlView);
|
||||
const boundaries = this.views.get(this.currentView || '')?.view.getBounds() ?? MainWindow.get()!.getBounds();
|
||||
|
||||
const hideView = () => {
|
||||
delete this.urlViewCancel;
|
||||
try {
|
||||
MainWindow.get()?.removeBrowserView(urlView);
|
||||
} catch (e) {
|
||||
log.error('Failed to remove URL view', e);
|
||||
}
|
||||
|
||||
// workaround to eliminate zombie processes
|
||||
// https://github.com/mattermost/desktop/pull/1519
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
urlView.webContents.destroy();
|
||||
};
|
||||
|
||||
const adjustWidth = (event: IpcMainEvent, width: number) => {
|
||||
log.silly('showURLView.adjustWidth', width);
|
||||
|
||||
const bounds = {
|
||||
x: 0,
|
||||
y: (boundaries.height + TAB_BAR_HEIGHT) - URL_VIEW_HEIGHT,
|
||||
width: width + 5, // add some padding to ensure that we don't cut off the border
|
||||
height: URL_VIEW_HEIGHT,
|
||||
};
|
||||
|
||||
log.silly('showURLView setBounds', boundaries, bounds);
|
||||
urlView.setBounds(bounds);
|
||||
};
|
||||
|
||||
ipcMain.on(UPDATE_URL_VIEW_WIDTH, adjustWidth);
|
||||
|
||||
const timeout = setTimeout(hideView,
|
||||
URL_VIEW_DURATION);
|
||||
|
||||
this.urlViewCancel = () => {
|
||||
clearTimeout(timeout);
|
||||
ipcMain.removeListener(UPDATE_URL_VIEW_WIDTH, adjustWidth);
|
||||
hideView();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setServerInitialized = (server: string) => {
|
||||
const view = this.views.get(server);
|
||||
if (view) {
|
||||
view.setInitialized();
|
||||
if (this.getCurrentView() === view) {
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deeplinkSuccess = (viewName: string) => {
|
||||
log.debug('deeplinkSuccess', viewName);
|
||||
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
this.showByName(viewName);
|
||||
view.removeListener(LOAD_FAILED, this.deeplinkFailed);
|
||||
};
|
||||
|
||||
deeplinkFailed = (viewName: string, err: string, url: string) => {
|
||||
log.error(`[${viewName}] failed to load deeplink ${url}: ${err}`);
|
||||
const view = this.views.get(viewName);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
view.removeListener(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
}
|
||||
|
||||
getViewByURL = (inputURL: URL | string, ignoreScheme = false) => {
|
||||
log.silly('getViewByURL', `${inputURL}`, ignoreScheme);
|
||||
|
||||
@@ -449,6 +599,7 @@ export class ViewManager {
|
||||
if (!server) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mmServer = new MattermostServer(server.name, server.url);
|
||||
let selectedTab = this.getServerView(mmServer, TAB_MESSAGING);
|
||||
server.tabs.
|
||||
@@ -462,51 +613,6 @@ export class ViewManager {
|
||||
return selectedTab;
|
||||
}
|
||||
|
||||
handleDeepLink = (url: string | URL) => {
|
||||
// TODO: fix for new tabs
|
||||
if (url) {
|
||||
const parsedURL = urlUtils.parseURL(url)!;
|
||||
const tabView = this.getViewByURL(parsedURL, true);
|
||||
if (tabView) {
|
||||
const urlWithSchema = `${urlUtils.parseURL(tabView.url)?.origin}${parsedURL.pathname}${parsedURL.search}`;
|
||||
if (this.closedViews.has(tabView.name)) {
|
||||
this.openClosedTab(tabView.name, urlWithSchema);
|
||||
} else {
|
||||
const view = this.views.get(tabView.name);
|
||||
if (!view) {
|
||||
log.error(`Couldn't find a view matching the name ${tabView.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.isInitialized() && view.serverInfo.remoteInfo.serverVersion && Utils.isVersionGreaterThanOrEqualTo(view.serverInfo.remoteInfo.serverVersion, '6.0.0')) {
|
||||
const pathName = `/${urlWithSchema.replace(view.tab.server.url.toString(), '')}`;
|
||||
view.view.webContents.send(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(view.name);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dialog.showErrorBox(
|
||||
localizeMessage('main.views.viewManager.handleDeepLink.error.title', 'No matching server'),
|
||||
localizeMessage('main.views.viewManager.handleDeepLink.error.body', 'There is no configured server in the app that matches the requested url: {url}', {url: parsedURL.toString()}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sendToAllViews = (channel: string, ...args: unknown[]) => {
|
||||
this.views.forEach((view) => {
|
||||
if (!view.view.webContents.isDestroyed()) {
|
||||
view.view.webContents.send(channel, ...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getServerView = (srv: MattermostServer, tabName: string) => {
|
||||
switch (tabName) {
|
||||
case TAB_MESSAGING:
|
||||
@@ -519,4 +625,25 @@ export class ViewManager {
|
||||
throw new Error('Not implemeneted');
|
||||
}
|
||||
}
|
||||
|
||||
private getServers = () => {
|
||||
return Config.teams.concat();
|
||||
}
|
||||
|
||||
handleGetViewName = (event: IpcMainInvokeEvent) => {
|
||||
return this.getViewByWebContentsId(event.sender.id);
|
||||
}
|
||||
|
||||
setServerInitialized = (server: string) => {
|
||||
const view = this.views.get(server);
|
||||
if (view) {
|
||||
view.setInitialized();
|
||||
if (this.getCurrentView() === view) {
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const viewManager = new ViewManager();
|
||||
export default viewManager;
|
||||
|
@@ -25,6 +25,10 @@ jest.mock('electron', () => ({
|
||||
jest.mock('main/contextMenu', () => jest.fn());
|
||||
|
||||
jest.mock('../allowProtocolDialog', () => ({}));
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
getViewByWebContentsId: jest.fn(),
|
||||
getViewByURL: jest.fn(),
|
||||
}));
|
||||
jest.mock('../windows/windowManager', () => ({
|
||||
getServerURLFromWebContentsId: jest.fn(),
|
||||
showMainWindow: jest.fn(),
|
||||
|
@@ -18,6 +18,7 @@ import allowProtocolDialog from '../allowProtocolDialog';
|
||||
import {composeUserAgent} from '../utils';
|
||||
|
||||
import {MattermostView} from './MattermostView';
|
||||
import ViewManager from './viewManager';
|
||||
|
||||
type CustomLogin = {
|
||||
inProgress: boolean;
|
||||
@@ -249,7 +250,7 @@ export class WebContentsEventManager {
|
||||
return {action: 'deny'};
|
||||
}
|
||||
|
||||
const otherServerURL = WindowManager.viewManager?.getViewByURL(parsedURL);
|
||||
const otherServerURL = ViewManager.getViewByURL(parsedURL);
|
||||
if (otherServerURL && urlUtils.isTeamUrl(otherServerURL.server.url, parsedURL, true)) {
|
||||
WindowManager.showMainWindow(parsedURL);
|
||||
return {action: 'deny'};
|
||||
|
Reference in New Issue
Block a user