[MM-51964] Clean up MattermostView, remove tuple in preparation for id (#2668)
undefined
This commit is contained in:
parent
53fb8c8fd3
commit
88eb2e2c70
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -21,7 +21,6 @@
|
|||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/register": "7.17.7",
|
||||
"@bloomberg/record-tuple-polyfill": "^0.0.4",
|
||||
"@electron/fuses": "1.6.0",
|
||||
"@electron/universal": "1.3.1",
|
||||
"@mattermost/compass-icons": "0.1.32",
|
||||
|
@ -2108,12 +2107,6 @@
|
|||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@bloomberg/record-tuple-polyfill": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@bloomberg/record-tuple-polyfill/-/record-tuple-polyfill-0.0.4.tgz",
|
||||
"integrity": "sha512-h0OYmPR3A5Dfbetra/GzxBAzQk8sH7LhRkRUTdagX6nrtlUgJGYCTv4bBK33jsTQw9HDd8PE2x1Ma+iRKEDUsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@develar/schema-utils": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
|
||||
|
@ -34773,12 +34766,6 @@
|
|||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||
"dev": true
|
||||
},
|
||||
"@bloomberg/record-tuple-polyfill": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@bloomberg/record-tuple-polyfill/-/record-tuple-polyfill-0.0.4.tgz",
|
||||
"integrity": "sha512-h0OYmPR3A5Dfbetra/GzxBAzQk8sH7LhRkRUTdagX6nrtlUgJGYCTv4bBK33jsTQw9HDd8PE2x1Ma+iRKEDUsw==",
|
||||
"dev": true
|
||||
},
|
||||
"@develar/schema-utils": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
|
||||
|
|
|
@ -139,7 +139,6 @@
|
|||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/register": "7.17.7",
|
||||
"@bloomberg/record-tuple-polyfill": "^0.0.4",
|
||||
"@electron/fuses": "1.6.0",
|
||||
"@electron/universal": "1.3.1",
|
||||
"@mattermost/compass-icons": "0.1.32",
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {v4 as uuid} from 'uuid';
|
||||
import {Tuple as tuple} from '@bloomberg/record-tuple-polyfill';
|
||||
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
|
||||
import {getTabViewName, TabType, TabView, TabTuple} from './TabView';
|
||||
import {getTabViewName, TabType, TabView} from './TabView';
|
||||
|
||||
export default abstract class BaseTabView implements TabView {
|
||||
id: string;
|
||||
|
@ -21,9 +20,6 @@ export default abstract class BaseTabView implements TabView {
|
|||
get name(): string {
|
||||
return getTabViewName(this.server.name, this.type);
|
||||
}
|
||||
get urlTypeTuple(): TabTuple {
|
||||
return tuple(this.server.url.href, this.type) as TabTuple;
|
||||
}
|
||||
get url(): URL {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ export const TAB_MESSAGING = 'TAB_MESSAGING';
|
|||
export const TAB_FOCALBOARD = 'TAB_FOCALBOARD';
|
||||
export const TAB_PLAYBOOKS = 'TAB_PLAYBOOKS';
|
||||
export type TabType = typeof TAB_MESSAGING | typeof TAB_FOCALBOARD | typeof TAB_PLAYBOOKS;
|
||||
export type TabTuple = [string, TabType];
|
||||
|
||||
export interface TabView {
|
||||
id: string;
|
||||
|
@ -20,7 +19,6 @@ export interface TabView {
|
|||
get type(): TabType;
|
||||
get url(): URL;
|
||||
get shouldNotify(): boolean;
|
||||
get urlTypeTuple(): TabTuple;
|
||||
}
|
||||
|
||||
export function getDefaultTeamWithTabsFromTeam(team: FullTeam) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import MessagingTabView from 'common/tabs/MessagingTabView';
|
|||
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
import * as WindowManager from '../windows/windowManager';
|
||||
import ContextMenu from '../contextMenu';
|
||||
import * as appState from '../appState';
|
||||
import Utils from '../utils';
|
||||
|
||||
|
@ -24,6 +25,12 @@ jest.mock('electron', () => ({
|
|||
on: jest.fn(),
|
||||
getTitle: () => 'title',
|
||||
getURL: () => 'http://server-1.com',
|
||||
clearHistory: jest.fn(),
|
||||
send: jest.fn(),
|
||||
canGoBack: jest.fn(),
|
||||
canGoForward: jest.fn(),
|
||||
goToOffset: jest.fn(),
|
||||
canGoToOffset: jest.fn(),
|
||||
},
|
||||
})),
|
||||
ipcMain: {
|
||||
|
@ -42,6 +49,7 @@ jest.mock('../appState', () => ({
|
|||
updateMentions: jest.fn(),
|
||||
}));
|
||||
jest.mock('./webContentEvents', () => ({
|
||||
addWebContentsEventListeners: jest.fn(),
|
||||
removeWebContentsListeners: jest.fn(),
|
||||
}));
|
||||
jest.mock('../contextMenu', () => jest.fn());
|
||||
|
@ -53,7 +61,7 @@ jest.mock('../utils', () => ({
|
|||
}));
|
||||
|
||||
const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
|
||||
const tabView = new MessagingTabView(server);
|
||||
const tabView = new MessagingTabView(server, true);
|
||||
|
||||
describe('main/views/MattermostView', () => {
|
||||
describe('load', () => {
|
||||
|
@ -179,6 +187,66 @@ describe('main/views/MattermostView', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('goToOffset', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.reload = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should only go to offset if it can', () => {
|
||||
mattermostView.view.webContents.canGoToOffset.mockReturnValue(false);
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.view.webContents.goToOffset).not.toBeCalled();
|
||||
|
||||
mattermostView.view.webContents.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.view.webContents.goToOffset).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call reload if an error occurs', () => {
|
||||
mattermostView.view.webContents.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.view.webContents.goToOffset.mockImplementation(() => {
|
||||
throw new Error('hi');
|
||||
});
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.reload).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onLogin', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.getURL = jest.fn();
|
||||
mattermostView.reload = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
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.onLogin(true);
|
||||
expect(mattermostView.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload if URLs are matching', () => {
|
||||
mattermostView.view.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.onLogin(true);
|
||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadSuccess', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
|
@ -208,7 +276,7 @@ describe('main/views/MattermostView', () => {
|
|||
});
|
||||
|
||||
describe('show', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn()};
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -226,59 +294,99 @@ 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(true);
|
||||
mattermostView.show();
|
||||
expect(window.addBrowserView).toBeCalledWith(mattermostView.view);
|
||||
expect(mattermostView.setBounds).toBeCalled();
|
||||
expect(mattermostView.isVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove browser view when request is false', () => {
|
||||
mattermostView.isVisible = true;
|
||||
mattermostView.show(false);
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
|
||||
expect(mattermostView.isVisible).toBe(false);
|
||||
});
|
||||
|
||||
it('should do nothing when not toggling', () => {
|
||||
mattermostView.isVisible = true;
|
||||
mattermostView.show(true);
|
||||
mattermostView.show();
|
||||
expect(window.addBrowserView).not.toBeCalled();
|
||||
expect(window.removeBrowserView).not.toBeCalled();
|
||||
|
||||
mattermostView.isVisible = false;
|
||||
mattermostView.show(false);
|
||||
expect(window.addBrowserView).not.toBeCalled();
|
||||
expect(window.removeBrowserView).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should focus view if view is ready', () => {
|
||||
mattermostView.status = 1;
|
||||
mattermostView.isVisible = false;
|
||||
mattermostView.show(true);
|
||||
mattermostView.show();
|
||||
expect(mattermostView.focus).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hide', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
});
|
||||
|
||||
it('should remove browser view', () => {
|
||||
mattermostView.isVisible = true;
|
||||
mattermostView.hide();
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
|
||||
expect(mattermostView.isVisible).toBe(false);
|
||||
});
|
||||
|
||||
it('should do nothing when not toggling', () => {
|
||||
mattermostView.isVisible = false;
|
||||
mattermostView.hide();
|
||||
expect(window.removeBrowserView).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateHistoryButton', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
});
|
||||
|
||||
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.isAtRoot).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
const window = {removeBrowserView: jest.fn(), on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const contextMenu = {
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
ContextMenu.mockReturnValue(contextMenu);
|
||||
});
|
||||
|
||||
it('should remove browser view from window', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
|
||||
});
|
||||
|
||||
it('should clear mentions', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(appState.updateMentions).toBeCalledWith(mattermostView.tab.name, 0, false);
|
||||
});
|
||||
|
||||
it('should destroy context menu', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(contextMenu.dispose).toBeCalled();
|
||||
});
|
||||
|
||||
it('should clear outstanding timeouts', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
const spy = jest.spyOn(global, 'clearTimeout');
|
||||
mattermostView.retryLoad = 999;
|
||||
mattermostView.removeLoading = 1000;
|
||||
|
|
|
@ -6,7 +6,6 @@ import {BrowserViewConstructorOptions, Event, Input} from 'electron/main';
|
|||
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import Util from 'common/utils/util';
|
||||
import {RELOAD_INTERVAL, MAX_SERVER_RETRIES, SECOND, MAX_LOADING_SCREEN_SECONDS} from 'common/utils/constants';
|
||||
import urlUtils from 'common/utils/url';
|
||||
import {
|
||||
|
@ -20,21 +19,21 @@ import {
|
|||
LOADSCREEN_END,
|
||||
BROWSER_HISTORY_BUTTON,
|
||||
} from 'common/communication';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {TabView, TabTuple} from 'common/tabs/TabView';
|
||||
import {Logger} from 'common/log';
|
||||
import {TabView} from 'common/tabs/TabView';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
|
||||
import {ServerInfo} from 'main/server/serverInfo';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import WindowManager from 'main/windows/windowManager';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import {getWindowBoundaries, getLocalPreload, composeUserAgent, shouldHaveBackBar} from '../utils';
|
||||
import WindowManager from '../windows/windowManager';
|
||||
import * as appState from '../appState';
|
||||
|
||||
import WebContentsEventManager from './webContentEvents';
|
||||
|
||||
export enum Status {
|
||||
enum Status {
|
||||
LOADING,
|
||||
READY,
|
||||
WAITING_MM,
|
||||
|
@ -42,27 +41,23 @@ export enum Status {
|
|||
}
|
||||
|
||||
const MENTIONS_GROUP = 2;
|
||||
const log = new Logger('MattermostView');
|
||||
const titleParser = /(\((\d+)\) )?(\* )?/g;
|
||||
|
||||
export class MattermostView extends EventEmitter {
|
||||
tab: TabView;
|
||||
view: BrowserView;
|
||||
isVisible: boolean;
|
||||
isLoggedIn: boolean;
|
||||
isAtRoot: boolean;
|
||||
options: BrowserViewConstructorOptions;
|
||||
serverInfo: ServerInfo;
|
||||
isVisible: boolean;
|
||||
|
||||
removeLoading?: number;
|
||||
|
||||
currentFavicon?: string;
|
||||
hasBeenShown: boolean;
|
||||
contextMenu: ContextMenu;
|
||||
|
||||
status?: Status;
|
||||
retryLoad?: NodeJS.Timeout;
|
||||
maxRetries: number;
|
||||
|
||||
private log: Logger;
|
||||
private view: BrowserView;
|
||||
private loggedIn: boolean;
|
||||
private atRoot: boolean;
|
||||
private options: BrowserViewConstructorOptions;
|
||||
private removeLoading?: number;
|
||||
private contextMenu: ContextMenu;
|
||||
private status?: Status;
|
||||
private retryLoad?: NodeJS.Timeout;
|
||||
private maxRetries: number;
|
||||
private altPressStatus: boolean;
|
||||
|
||||
constructor(tab: TabView, serverInfo: ServerInfo, options: BrowserViewConstructorOptions) {
|
||||
|
@ -81,38 +76,24 @@ export class MattermostView extends EventEmitter {
|
|||
...options.webPreferences,
|
||||
};
|
||||
this.isVisible = false;
|
||||
this.isLoggedIn = false;
|
||||
this.isAtRoot = true;
|
||||
this.loggedIn = false;
|
||||
this.atRoot = true;
|
||||
this.view = new BrowserView(this.options);
|
||||
this.resetLoadingStatus();
|
||||
|
||||
log.verbose(`BrowserView created for server ${this.tab.name}`);
|
||||
|
||||
this.hasBeenShown = false;
|
||||
this.log = new Logger(this.name, 'MattermostView');
|
||||
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);
|
||||
if (process.platform !== 'darwin') {
|
||||
this.view.webContents.on('before-input-event', this.handleInputEvents);
|
||||
}
|
||||
|
||||
this.view.webContents.on('did-finish-load', () => {
|
||||
log.debug('did-finish-load', this.tab.name);
|
||||
|
||||
// wait for screen to truly finish loading before sending the message down
|
||||
const timeout = setInterval(() => {
|
||||
if (!this.view.webContents) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.view.webContents.isLoading()) {
|
||||
try {
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.tab.name, this.tab.shouldNotify);
|
||||
clearTimeout(timeout);
|
||||
} catch (e) {
|
||||
log.error('failed to send view options to view', this.tab.name);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
WebContentsEventManager.addWebContentsEventListeners(this.view.webContents);
|
||||
|
||||
this.contextMenu = new ContextMenu({}, this.view);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
|
@ -124,28 +105,79 @@ export class MattermostView extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
// use the same name as the server
|
||||
// TODO: we'll need unique identifiers if we have multiple instances of the same server in different tabs (1:N relationships)
|
||||
get name() {
|
||||
return this.tab.name;
|
||||
}
|
||||
|
||||
get urlTypeTuple(): TabTuple {
|
||||
return this.tab.urlTypeTuple;
|
||||
get isAtRoot() {
|
||||
return this.atRoot;
|
||||
}
|
||||
get isLoggedIn() {
|
||||
return this.loggedIn;
|
||||
}
|
||||
get currentURL() {
|
||||
return this.view.webContents.getURL();
|
||||
}
|
||||
get webContentsId() {
|
||||
return this.view.webContents.id;
|
||||
}
|
||||
|
||||
updateServerInfo = (srv: MattermostServer) => {
|
||||
let reload;
|
||||
if (srv.url.toString() !== this.tab.server.url.toString()) {
|
||||
reload = () => this.reload();
|
||||
}
|
||||
this.tab.server = srv;
|
||||
this.serverInfo = new ServerInfo(srv);
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.tab.name, this.tab.shouldNotify);
|
||||
reload?.();
|
||||
}
|
||||
|
||||
resetLoadingStatus = () => {
|
||||
if (this.status !== Status.LOADING) { // if it's already loading, don't touch anything
|
||||
delete this.retryLoad;
|
||||
this.status = Status.LOADING;
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
onLogin = (loggedIn: boolean) => {
|
||||
if (this.isLoggedIn === loggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loggedIn = loggedIn;
|
||||
|
||||
// If we're logging in from a different tab, force a reload
|
||||
if (loggedIn &&
|
||||
this.currentURL !== this.tab.url.toString() &&
|
||||
!this.currentURL.startsWith(this.tab.url.toString())
|
||||
) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
goToOffset = (offset: number) => {
|
||||
if (this.view.webContents.canGoToOffset(offset)) {
|
||||
try {
|
||||
this.view.webContents.goToOffset(offset);
|
||||
this.updateHistoryButton();
|
||||
} catch (error) {
|
||||
this.log.error(error);
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateHistoryButton = () => {
|
||||
if (urlUtils.parseURL(this.currentURL)?.toString() === this.tab.url.toString()) {
|
||||
this.view.webContents.clearHistory();
|
||||
this.atRoot = true;
|
||||
} else {
|
||||
this.atRoot = false;
|
||||
}
|
||||
this.view.webContents.send(BROWSER_HISTORY_BUTTON, this.view.webContents.canGoBack(), this.view.webContents.canGoForward());
|
||||
}
|
||||
|
||||
updateTabView = (tab: TabView) => {
|
||||
let reload;
|
||||
if (tab.url.toString() !== this.tab.url.toString()) {
|
||||
reload = () => this.reload();
|
||||
}
|
||||
this.tab = tab;
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.name, this.tab.shouldNotify);
|
||||
reload?.();
|
||||
}
|
||||
|
||||
load = (someURL?: URL | string) => {
|
||||
|
@ -159,19 +191,19 @@ export class MattermostView extends EventEmitter {
|
|||
if (parsedURL) {
|
||||
loadURL = parsedURL.toString();
|
||||
} else {
|
||||
log.error('Cannot parse provided url, using current server url', someURL);
|
||||
this.log.error('Cannot parse provided url, using current server url', someURL);
|
||||
loadURL = this.tab.url.toString();
|
||||
}
|
||||
} else {
|
||||
loadURL = this.tab.url.toString();
|
||||
}
|
||||
log.verbose(`[${Util.shorten(this.tab.name)}] Loading ${loadURL}`);
|
||||
this.log.verbose(`Loading ${loadURL}`);
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (err.code && err.code.startsWith('ERR_CERT')) {
|
||||
WindowManager.sendToRenderer(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString());
|
||||
log.info(`[${Util.shorten(this.tab.name)}] Invalid certificate, stop retrying until the user decides what to do: ${err}.`);
|
||||
WindowManager.sendToRenderer(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.log.info('Invalid certificate, stop retrying until the user decides what to do.', err);
|
||||
this.status = Status.ERROR;
|
||||
return;
|
||||
}
|
||||
|
@ -183,85 +215,28 @@ export class MattermostView extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
retry = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.view || !this.view.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (this.maxRetries-- > 0) {
|
||||
this.loadRetry(loadURL, err);
|
||||
} else {
|
||||
WindowManager.sendToRenderer(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString());
|
||||
log.info(`[${Util.shorten(this.tab.name)}] Couldn't establish a connection with ${loadURL}: ${err}. Will continue to retry in the background.`);
|
||||
this.status = Status.ERROR;
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
retryInBackground = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.view || !this.view.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch(() => {
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
loadRetry = (loadURL: string, err: Error) => {
|
||||
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
|
||||
WindowManager.sendToRenderer(LOAD_RETRY, this.tab.name, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
|
||||
log.info(`[${Util.shorten(this.tab.name)}] failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
|
||||
}
|
||||
|
||||
loadSuccess = (loadURL: string) => {
|
||||
return () => {
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.verbose(`[${Util.shorten(this.tab.name)}] finished loading ${loadURL}`);
|
||||
WindowManager.sendToRenderer(LOAD_SUCCESS, this.tab.name);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
if (this.status === Status.LOADING) {
|
||||
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
||||
this.findUnreadState(null);
|
||||
}
|
||||
this.status = Status.WAITING_MM;
|
||||
this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true);
|
||||
this.emit(LOAD_SUCCESS, this.tab.name, loadURL);
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.view.webContents.getURL())));
|
||||
};
|
||||
}
|
||||
|
||||
show = (requestedVisibility?: boolean) => {
|
||||
show = () => {
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasBeenShown = true;
|
||||
const request = typeof requestedVisibility === 'undefined' ? true : requestedVisibility;
|
||||
if (request && !this.isVisible) {
|
||||
mainWindow.addBrowserView(this.view);
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.view.webContents.getURL())));
|
||||
if (this.status === Status.READY) {
|
||||
this.focus();
|
||||
}
|
||||
} else if (!request && this.isVisible) {
|
||||
mainWindow.removeBrowserView(this.view);
|
||||
if (this.isVisible) {
|
||||
return;
|
||||
}
|
||||
this.isVisible = true;
|
||||
mainWindow.addBrowserView(this.view);
|
||||
mainWindow.setTopBrowserView(this.view);
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
|
||||
if (this.status === Status.READY) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
hide = () => {
|
||||
if (this.isVisible) {
|
||||
this.isVisible = false;
|
||||
MainWindow.get()?.removeBrowserView(this.view);
|
||||
}
|
||||
this.isVisible = request;
|
||||
}
|
||||
|
||||
reload = () => {
|
||||
|
@ -269,41 +244,21 @@ export class MattermostView extends EventEmitter {
|
|||
this.load();
|
||||
}
|
||||
|
||||
hide = () => this.show(false);
|
||||
getBounds = () => {
|
||||
return this.view.getBounds();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
destroy = () => {
|
||||
WebContentsEventManager.removeWebContentsListeners(this.view.webContents.id);
|
||||
appState.updateMentions(this.tab.name, 0, false);
|
||||
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
||||
appState.updateMentions(this.name, 0, false);
|
||||
MainWindow.get()?.removeBrowserView(this.view);
|
||||
|
||||
// workaround to eliminate zombie processes
|
||||
|
@ -319,13 +274,19 @@ export class MattermostView extends EventEmitter {
|
|||
if (this.removeLoading) {
|
||||
clearTimeout(this.removeLoading);
|
||||
}
|
||||
|
||||
this.contextMenu.dispose();
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
if (this.view.webContents) {
|
||||
this.view.webContents.focus();
|
||||
} else {
|
||||
log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.');
|
||||
/**
|
||||
* Status hooks
|
||||
*/
|
||||
|
||||
resetLoadingStatus = () => {
|
||||
if (this.status !== Status.LOADING) { // if it's already loading, don't touch anything
|
||||
delete this.retryLoad;
|
||||
this.status = Status.LOADING;
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,25 +306,41 @@ export class MattermostView extends EventEmitter {
|
|||
this.status = Status.READY;
|
||||
|
||||
if (timedout) {
|
||||
log.info(`${this.tab.name} timeout expired will show the browserview`);
|
||||
this.emit(LOADSCREEN_END, this.tab.name);
|
||||
this.log.verbose('timeout expired will show the browserview');
|
||||
this.emit(LOADSCREEN_END, this.name);
|
||||
}
|
||||
clearTimeout(this.removeLoading);
|
||||
delete this.removeLoading;
|
||||
}
|
||||
|
||||
isInitialized = () => {
|
||||
return this.status === Status.READY;
|
||||
}
|
||||
|
||||
openDevTools = () => {
|
||||
this.view.webContents.openDevTools({mode: 'detach'});
|
||||
}
|
||||
|
||||
getWebContents = () => {
|
||||
return this.view.webContents;
|
||||
/**
|
||||
* WebContents hooks
|
||||
*/
|
||||
|
||||
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||
this.view.webContents.send(channel, ...args);
|
||||
}
|
||||
|
||||
isDestroyed = () => {
|
||||
return this.view.webContents.isDestroyed();
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
if (this.view.webContents) {
|
||||
this.view.webContents.focus();
|
||||
} else {
|
||||
this.log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ALT key handling for the 3-dot menu (Windows/Linux)
|
||||
*/
|
||||
|
||||
private registerAltKeyPressed = (input: Input) => {
|
||||
const isAltPressed = input.key === 'Alt' && input.alt === true && input.control === false && input.shift === false && input.meta === false;
|
||||
|
||||
|
@ -380,8 +357,8 @@ export class MattermostView extends EventEmitter {
|
|||
return input.type === 'keyUp' && this.altPressStatus === true;
|
||||
};
|
||||
|
||||
handleInputEvents = (_: Event, input: Input) => {
|
||||
log.silly('handleInputEvents', {tabName: this.tab.name, input});
|
||||
private handleInputEvents = (_: Event, input: Input) => {
|
||||
this.log.silly('handleInputEvents', input);
|
||||
|
||||
this.registerAltKeyPressed(input);
|
||||
|
||||
|
@ -390,61 +367,148 @@ export class MattermostView extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
handleDidNavigate = (event: Event, url: string) => {
|
||||
log.debug('handleDidNavigate', {tabName: this.tab.name, url});
|
||||
/**
|
||||
* Unreads/mentions handlers
|
||||
*/
|
||||
|
||||
private updateMentionsFromTitle = (title: string) => {
|
||||
const resultsIterator = title.matchAll(titleParser);
|
||||
const results = resultsIterator.next(); // we are only interested in the first set
|
||||
const mentions = (results && results.value && parseInt(results.value[MENTIONS_GROUP], 10)) || 0;
|
||||
|
||||
appState.updateMentions(this.name, mentions);
|
||||
}
|
||||
|
||||
// if favicon is null, it will affect appState, but won't be memoized
|
||||
private findUnreadState = (favicon: string | null) => {
|
||||
try {
|
||||
this.view.webContents.send(IS_UNREAD, favicon, this.name);
|
||||
} catch (err: any) {
|
||||
this.log.error('There was an error trying to request the unread state', err);
|
||||
}
|
||||
}
|
||||
|
||||
private handleTitleUpdate = (e: Event, title: string) => {
|
||||
this.log.debug('handleTitleUpdate', title);
|
||||
|
||||
this.updateMentionsFromTitle(title);
|
||||
}
|
||||
|
||||
private handleFaviconUpdate = (e: Event, favicons: string[]) => {
|
||||
this.log.silly('handleFaviconUpdate', favicons);
|
||||
|
||||
// if unread state is stored for that favicon, retrieve value.
|
||||
// if not, get related info from preload and store it for future changes
|
||||
this.findUnreadState(favicons[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading/retry logic
|
||||
*/
|
||||
|
||||
private retry = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.view || !this.view.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (this.maxRetries-- > 0) {
|
||||
this.loadRetry(loadURL, err);
|
||||
} else {
|
||||
WindowManager.sendToRenderer(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.name, err.toString(), loadURL.toString());
|
||||
this.log.info(`Couldn't establish a connection with ${loadURL}, will continue to retry in the background`, err);
|
||||
this.status = Status.ERROR;
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private retryInBackground = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.view || !this.view.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch(() => {
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private loadRetry = (loadURL: string, err: Error) => {
|
||||
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
|
||||
WindowManager.sendToRenderer(LOAD_RETRY, this.name, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
|
||||
this.log.info(`failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
|
||||
}
|
||||
|
||||
private loadSuccess = (loadURL: string) => {
|
||||
return () => {
|
||||
this.log.verbose(`finished loading ${loadURL}`);
|
||||
WindowManager.sendToRenderer(LOAD_SUCCESS, this.name);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
if (this.status === Status.LOADING) {
|
||||
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
||||
this.findUnreadState(null);
|
||||
}
|
||||
this.status = Status.WAITING_MM;
|
||||
this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true);
|
||||
this.emit(LOAD_SUCCESS, this.name, loadURL);
|
||||
const mainWindow = MainWindow.get();
|
||||
if (mainWindow) {
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WebContents event handlers
|
||||
*/
|
||||
|
||||
private handleDidFinishLoad = () => {
|
||||
this.log.debug('did-finish-load', this.name);
|
||||
|
||||
// wait for screen to truly finish loading before sending the message down
|
||||
const timeout = setInterval(() => {
|
||||
if (!this.view.webContents) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.view.webContents.isLoading()) {
|
||||
try {
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.name, this.tab.shouldNotify);
|
||||
clearTimeout(timeout);
|
||||
} catch (e) {
|
||||
this.log.error('failed to send view options to view');
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private handleDidNavigate = (event: Event, url: string) => {
|
||||
this.log.debug('handleDidNavigate', url);
|
||||
|
||||
if (shouldHaveBackBar(this.tab.url || '', url)) {
|
||||
this.setBounds(getWindowBoundaries(MainWindow.get()!, true));
|
||||
WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, true);
|
||||
log.info('show back button');
|
||||
this.log.debug('show back button');
|
||||
} else {
|
||||
this.setBounds(getWindowBoundaries(MainWindow.get()!));
|
||||
WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, false);
|
||||
log.info('hide back button');
|
||||
this.log.debug('hide back button');
|
||||
}
|
||||
}
|
||||
|
||||
handleUpdateTarget = (e: Event, url: string) => {
|
||||
log.silly('handleUpdateTarget', {tabName: this.tab.name, url});
|
||||
private handleUpdateTarget = (e: Event, url: string) => {
|
||||
this.log.silly('handleUpdateTarget', url);
|
||||
if (url && !urlUtils.isInternalURL(urlUtils.parseURL(url), this.tab.server.url)) {
|
||||
this.emit(UPDATE_TARGET_URL, url);
|
||||
} else {
|
||||
this.emit(UPDATE_TARGET_URL);
|
||||
}
|
||||
}
|
||||
|
||||
titleParser = /(\((\d+)\) )?(\* )?/g
|
||||
|
||||
handleTitleUpdate = (e: Event, title: string) => {
|
||||
log.debug('handleTitleUpdate', {tabName: this.tab.name, title});
|
||||
|
||||
this.updateMentionsFromTitle(title);
|
||||
}
|
||||
|
||||
updateMentionsFromTitle = (title: string) => {
|
||||
const resultsIterator = title.matchAll(this.titleParser);
|
||||
const results = resultsIterator.next(); // we are only interested in the first set
|
||||
const mentions = (results && results.value && parseInt(results.value[MENTIONS_GROUP], 10)) || 0;
|
||||
|
||||
appState.updateMentions(this.tab.name, mentions);
|
||||
}
|
||||
|
||||
handleFaviconUpdate = (e: Event, favicons: string[]) => {
|
||||
log.silly('handleFaviconUpdate', {tabName: this.tab.name, favicons});
|
||||
|
||||
// if unread state is stored for that favicon, retrieve value.
|
||||
// if not, get related info from preload and store it for future changes
|
||||
this.currentFavicon = favicons[0];
|
||||
this.findUnreadState(favicons[0]);
|
||||
}
|
||||
|
||||
// if favicon is null, it will affect appState, but won't be memoized
|
||||
findUnreadState = (favicon: string | null) => {
|
||||
try {
|
||||
this.view.webContents.send(IS_UNREAD, favicon, this.tab.name);
|
||||
} catch (err: any) {
|
||||
log.error(`There was an error trying to request the unread state: ${err}`);
|
||||
log.error(err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
'use strict';
|
||||
|
||||
import {dialog, ipcMain} from 'electron';
|
||||
import {Tuple as tuple} from '@bloomberg/record-tuple-polyfill';
|
||||
|
||||
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, MAIN_WINDOW_SHOWN} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
|
@ -127,65 +126,6 @@ describe('main/views/viewManager', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('handleAppLoggedIn', () => {
|
||||
const viewManager = new ViewManager({});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should reload view when URL is not on subpath of original server URL', () => {
|
||||
const view = {
|
||||
load: jest.fn(),
|
||||
view: {
|
||||
webContents: {
|
||||
getURL: () => 'http://server-2.com/subpath',
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
url: new URL('http://server-1.com/'),
|
||||
},
|
||||
};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.handleAppLoggedIn({}, 'view1');
|
||||
expect(view.load).toHaveBeenCalledWith(new URL('http://server-1.com/'));
|
||||
});
|
||||
|
||||
it('should not reload if URLs are matching', () => {
|
||||
const view = {
|
||||
load: jest.fn(),
|
||||
view: {
|
||||
webContents: {
|
||||
getURL: () => 'http://server-1.com/',
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
url: new URL('http://server-1.com/'),
|
||||
},
|
||||
};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.handleAppLoggedIn({}, 'view1');
|
||||
expect(view.load).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload if URL is subpath of server URL', () => {
|
||||
const view = {
|
||||
load: jest.fn(),
|
||||
view: {
|
||||
webContents: {
|
||||
getURL: () => 'http://server-1.com/subpath',
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
url: new URL('http://server-1.com/'),
|
||||
},
|
||||
};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.handleAppLoggedIn({}, 'view1');
|
||||
expect(view.load).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadConfiguration', () => {
|
||||
const viewManager = new ViewManager();
|
||||
|
||||
|
@ -203,7 +143,6 @@ describe('main/views/viewManager', () => {
|
|||
|
||||
viewManager.getServerView = jest.fn().mockImplementation((srv, tabName) => ({
|
||||
name: `${srv.name}-${tabName}`,
|
||||
urlTypeTuple: tuple(`http://${srv.name}.com/`, tabName),
|
||||
url: new URL(`http://${srv.name}.com`),
|
||||
}));
|
||||
MattermostServer.mockImplementation((server) => ({
|
||||
|
@ -219,10 +158,10 @@ describe('main/views/viewManager', () => {
|
|||
once: onceFn,
|
||||
destroy: destroyFn,
|
||||
name: tab.name,
|
||||
urlTypeTuple: tab.urlTypeTuple,
|
||||
updateServerInfo: jest.fn(),
|
||||
tab,
|
||||
}));
|
||||
getTabViewName.mockImplementation((a, b) => `${a}-${b}`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -249,7 +188,6 @@ 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);
|
||||
|
@ -303,7 +241,7 @@ describe('main/views/viewManager', () => {
|
|||
name: 'tab1',
|
||||
isOpen: true,
|
||||
},
|
||||
'http://server1.com/',
|
||||
'http://server1.com',
|
||||
);
|
||||
makeSpy.mockRestore();
|
||||
});
|
||||
|
@ -318,7 +256,6 @@ describe('main/views/viewManager', () => {
|
|||
name: 'server1-tab1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
urlTypeTuple: tuple('http://server1.com/', 'tab1'),
|
||||
destroy: jest.fn(),
|
||||
updateServerInfo: jest.fn(),
|
||||
};
|
||||
|
@ -348,7 +285,6 @@ describe('main/views/viewManager', () => {
|
|||
name: 'server1-tab1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
urlTypeTuple: ['http://server.com/', 'tab1'],
|
||||
destroy: jest.fn(),
|
||||
updateServerInfo: jest.fn(),
|
||||
};
|
||||
|
@ -772,12 +708,8 @@ describe('main/views/viewManager', () => {
|
|||
resetLoadingStatus: jest.fn(),
|
||||
load: jest.fn(),
|
||||
once: jest.fn(),
|
||||
isInitialized: jest.fn(),
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
isReady: jest.fn(),
|
||||
sendToRenderer: jest.fn(),
|
||||
serverInfo: {
|
||||
remoteInfo: {
|
||||
serverVersion: '1.0.0',
|
||||
|
@ -819,10 +751,10 @@ describe('main/views/viewManager', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
view.isInitialized.mockImplementation(() => true);
|
||||
view.isReady.mockImplementation(() => true);
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
expect(view.view.webContents.send).toHaveBeenCalledWith(BROWSER_HISTORY_PUSH, '/deep/link?thing=yes');
|
||||
expect(view.sendToRenderer).toHaveBeenCalledWith(BROWSER_HISTORY_PUSH, '/deep/link?thing=yes');
|
||||
});
|
||||
|
||||
it('should throw error if view is missing', () => {
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
import {BrowserView, dialog, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserViewConstructorOptions} from 'electron/main';
|
||||
|
||||
import {Tuple as tuple} from '@bloomberg/record-tuple-polyfill';
|
||||
|
||||
import {Tab, TeamWithTabs} from 'types/config';
|
||||
|
||||
import {SECOND, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
|
@ -26,13 +24,14 @@ import {
|
|||
APP_LOGGED_OUT,
|
||||
UNREAD_RESULT,
|
||||
GET_VIEW_NAME,
|
||||
HISTORY,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import urlUtils, {equalUrlsIgnoringSubpath} from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {getTabViewName, TabTuple, TabType, TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS} from 'common/tabs/TabView';
|
||||
import {getTabViewName, TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS} from 'common/tabs/TabView';
|
||||
import MessagingTabView from 'common/tabs/MessagingTabView';
|
||||
import FocalboardTabView from 'common/tabs/FocalboardTabView';
|
||||
import PlaybooksTabView from 'common/tabs/PlaybooksTabView';
|
||||
|
@ -46,7 +45,6 @@ import {getLocalURLString, getLocalPreload} from '../utils';
|
|||
|
||||
import {MattermostView} from './MattermostView';
|
||||
import modalManager from './modalManager';
|
||||
import WebContentsEventManager from './webContentEvents';
|
||||
import LoadingScreen from './loadingScreen';
|
||||
|
||||
const log = new Logger('ViewManager');
|
||||
|
@ -69,6 +67,7 @@ export class ViewManager {
|
|||
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
|
||||
this.closedViews = new Map();
|
||||
|
||||
ipcMain.on(HISTORY, this.handleHistory);
|
||||
ipcMain.on(REACT_APP_INITIALIZED, this.handleReactAppInitialized);
|
||||
ipcMain.on(BROWSER_HISTORY_PUSH, this.handleBrowserHistoryPush);
|
||||
ipcMain.on(BROWSER_HISTORY_BUTTON, this.handleBrowserHistoryButton);
|
||||
|
@ -97,7 +96,7 @@ export class ViewManager {
|
|||
}
|
||||
|
||||
getViewByWebContentsId = (webContentsId: number) => {
|
||||
return [...this.views.values()].find((view) => view.view.webContents.id === webContentsId);
|
||||
return [...this.views.values()].find((view) => view.webContentsId === webContentsId);
|
||||
}
|
||||
|
||||
showByName = (name: string) => {
|
||||
|
@ -157,8 +156,8 @@ export class ViewManager {
|
|||
|
||||
sendToAllViews = (channel: string, ...args: unknown[]) => {
|
||||
this.views.forEach((view) => {
|
||||
if (!view.view.webContents.isDestroyed()) {
|
||||
view.view.webContents.send(channel, ...args);
|
||||
if (!view.isDestroyed()) {
|
||||
view.sendToRenderer(channel, ...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -187,9 +186,9 @@ export class ViewManager {
|
|||
return;
|
||||
}
|
||||
|
||||
if (view.isInitialized() && view.serverInfo.remoteInfo.serverVersion && Utils.isVersionGreaterThanOrEqualTo(view.serverInfo.remoteInfo.serverVersion, '6.0.0')) {
|
||||
if (view.isReady() && 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);
|
||||
view.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(view.name);
|
||||
} else {
|
||||
// attempting to change parsedURL protocol results in it not being modified.
|
||||
|
@ -298,12 +297,6 @@ export class ViewManager {
|
|||
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);
|
||||
}
|
||||
|
||||
private finishLoading = (server: string) => {
|
||||
|
@ -352,7 +345,7 @@ export class ViewManager {
|
|||
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 boundaries = this.views.get(this.currentView || '')?.getBounds() ?? mainWindow.getBounds();
|
||||
|
||||
const hideView = () => {
|
||||
delete this.urlViewCancel;
|
||||
|
@ -407,15 +400,13 @@ export class ViewManager {
|
|||
reloadConfiguration = () => {
|
||||
log.debug('reloadConfiguration');
|
||||
|
||||
const focusedTuple: TabTuple | undefined = this.views.get(this.currentView as string)?.urlTypeTuple;
|
||||
|
||||
const current: Map<TabTuple, MattermostView> = new Map();
|
||||
const current: Map<string, MattermostView> = new Map();
|
||||
for (const view of this.views.values()) {
|
||||
current.set(view.urlTypeTuple, view);
|
||||
current.set(view.name, view);
|
||||
}
|
||||
|
||||
const views: Map<TabTuple, MattermostView> = new Map();
|
||||
const closed: Map<TabTuple, {srv: MattermostServer; tab: Tab; name: string}> = new Map();
|
||||
const views: Map<string, MattermostView> = new Map();
|
||||
const closed: Map<string, {srv: MattermostServer; tab: Tab; name: string}> = new Map();
|
||||
|
||||
const sortedTabs = this.getServers().flatMap((x) => [...x.tabs].
|
||||
sort((a, b) => a.order - b.order).
|
||||
|
@ -424,16 +415,16 @@ export class ViewManager {
|
|||
for (const [team, tab] of sortedTabs) {
|
||||
const srv = new MattermostServer(team);
|
||||
const info = new ServerInfo(srv);
|
||||
const tabTuple = tuple(new URL(team.url).href, tab.name as TabType);
|
||||
const recycle = current.get(tabTuple);
|
||||
const tabName = getTabViewName(team.name, tab.name);
|
||||
const recycle = current.get(tabName);
|
||||
if (!tab.isOpen) {
|
||||
const view = this.getServerView(srv, tab.name);
|
||||
closed.set(tabTuple, {srv, tab, name: view.name});
|
||||
closed.set(tabName, {srv, tab, name: view.name});
|
||||
} else if (recycle) {
|
||||
recycle.updateServerInfo(srv);
|
||||
views.set(tabTuple, recycle);
|
||||
views.set(tabName, recycle);
|
||||
} else {
|
||||
views.set(tabTuple, this.makeView(srv, info, tab, tabTuple[0]));
|
||||
views.set(tabName, this.makeView(srv, info, tab, team.url));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -456,7 +447,7 @@ export class ViewManager {
|
|||
this.closedViews.set(x.name, {srv: x.srv, tab: x.tab});
|
||||
}
|
||||
|
||||
if ((focusedTuple && closed.has(focusedTuple)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if ((this.currentView && closed.has(this.currentView)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if (this.getServers().length) {
|
||||
this.currentView = undefined;
|
||||
this.showInitial();
|
||||
|
@ -466,8 +457,8 @@ export class ViewManager {
|
|||
}
|
||||
|
||||
// show the focused tab (or initial)
|
||||
if (focusedTuple && views.has(focusedTuple)) {
|
||||
const view = views.get(focusedTuple);
|
||||
if (this.currentView && views.has(this.currentView)) {
|
||||
const view = views.get(this.currentView);
|
||||
if (view) {
|
||||
this.currentView = view.name;
|
||||
this.showByName(view.name);
|
||||
|
@ -478,25 +469,16 @@ export class ViewManager {
|
|||
}
|
||||
}
|
||||
|
||||
private handleAppLoggedIn = (event: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleAppLoggedIn', viewName);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
private handleHistory = (event: IpcMainEvent, offset: number) => {
|
||||
this.getCurrentView()?.goToOffset(offset);
|
||||
}
|
||||
|
||||
private handleAppLoggedOut = (event: IpcMainEvent, viewName: string) => {
|
||||
log.debug('handleAppLoggedOut', viewName);
|
||||
private handleAppLoggedIn = (event: IpcMainEvent, viewId: string) => {
|
||||
this.getView(viewId)?.onLogin(true);
|
||||
}
|
||||
|
||||
const view = this.views.get(viewName);
|
||||
if (view && view.isLoggedIn) {
|
||||
view.isLoggedIn = false;
|
||||
}
|
||||
private handleAppLoggedOut = (event: IpcMainEvent, viewId: string) => {
|
||||
this.getView(viewId)?.onLogin(false);
|
||||
}
|
||||
|
||||
private handleBrowserHistoryPush = (e: IpcMainEvent, viewName: string, pathName: string) => {
|
||||
|
@ -520,7 +502,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 === '/')) {
|
||||
redirectedView?.view.webContents.send(BROWSER_HISTORY_PUSH, cleanedPathName);
|
||||
redirectedView?.sendToRenderer(BROWSER_HISTORY_PUSH, cleanedPathName);
|
||||
if (redirectedView) {
|
||||
this.handleBrowserHistoryButton(e, redirectedView.name);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import {protocols} from '../../../electron-builder.json';
|
|||
import allowProtocolDialog from '../allowProtocolDialog';
|
||||
import {composeUserAgent} from '../utils';
|
||||
|
||||
import {MattermostView} from './MattermostView';
|
||||
import ViewManager from './viewManager';
|
||||
|
||||
type CustomLogin = {
|
||||
|
@ -268,24 +267,6 @@ export class WebContentsEventManager {
|
|||
}
|
||||
};
|
||||
|
||||
addMattermostViewEventListeners = (mmview: MattermostView) => {
|
||||
this.addWebContentsEventListeners(
|
||||
mmview.view.webContents,
|
||||
(contents: WebContents) => {
|
||||
contents.on('page-title-updated', mmview.handleTitleUpdate);
|
||||
contents.on('page-favicon-updated', mmview.handleFaviconUpdate);
|
||||
contents.on('update-target-url', mmview.handleUpdateTarget);
|
||||
contents.on('did-navigate', mmview.handleDidNavigate);
|
||||
},
|
||||
(contents: WebContents) => {
|
||||
contents.removeListener('page-title-updated', mmview.handleTitleUpdate);
|
||||
contents.removeListener('page-favicon-updated', mmview.handleFaviconUpdate);
|
||||
contents.removeListener('update-target-url', mmview.handleUpdateTarget);
|
||||
contents.removeListener('did-navigate', mmview.handleDidNavigate);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
addWebContentsEventListeners = (
|
||||
contents: WebContents,
|
||||
addListeners?: (contents: WebContents) => void,
|
||||
|
|
|
@ -52,12 +52,8 @@ describe('main/windows/callsWidgetWindow', () => {
|
|||
};
|
||||
|
||||
const mainView = {
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
id: 'mainViewID',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
webContentsId: 'mainViewID',
|
||||
serverInfo: {
|
||||
server: {
|
||||
name: 'test-server-name',
|
||||
|
@ -434,12 +430,12 @@ describe('main/windows/callsWidgetWindow', () => {
|
|||
widgetWindow.onJoinedCall({
|
||||
sender: {id: 'badID'},
|
||||
}, message);
|
||||
expect(widgetWindow.mainView.view.webContents.send).not.toHaveBeenCalled();
|
||||
expect(widgetWindow.mainView.sendToRenderer).not.toHaveBeenCalled();
|
||||
|
||||
widgetWindow.onJoinedCall({
|
||||
sender: baseWindow.webContents,
|
||||
}, 'widget', message);
|
||||
expect(widgetWindow.mainView.view.webContents.send).toHaveBeenCalledWith(CALLS_JOINED_CALL, message);
|
||||
expect(widgetWindow.mainView.sendToRenderer).toHaveBeenCalledWith(CALLS_JOINED_CALL, message);
|
||||
});
|
||||
|
||||
it('menubar disabled on popout', () => {
|
||||
|
@ -589,7 +585,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
|||
})).toEqual(false);
|
||||
|
||||
expect(widgetWindow.isAllowedEvent({
|
||||
sender: widgetWindow.mainView.view.webContents,
|
||||
sender: {id: 'mainViewID'},
|
||||
})).toEqual(true);
|
||||
|
||||
expect(widgetWindow.isAllowedEvent({
|
||||
|
|
|
@ -190,7 +190,7 @@ export default class CallsWidgetWindow extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
|
||||
this.mainView.view.webContents.send(CALLS_JOINED_CALL, message);
|
||||
this.mainView.sendToRenderer(CALLS_JOINED_CALL, message);
|
||||
}
|
||||
|
||||
private setBounds(bounds: Rectangle) {
|
||||
|
@ -297,7 +297,7 @@ export default class CallsWidgetWindow extends EventEmitter {
|
|||
// Only allow events coming from either the widget window or the
|
||||
// original Mattermost view that initiated it.
|
||||
return event.sender.id === this.getWebContentsId() ||
|
||||
event.sender.id === this.getMainView().getWebContents().id;
|
||||
event.sender.id === this.getMainView().webContentsId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -563,61 +563,6 @@ describe('main/windows/windowManager', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('handleHistory', () => {
|
||||
const windowManager = new WindowManager();
|
||||
|
||||
it('should only go to offset if it can', () => {
|
||||
const view = {
|
||||
view: {
|
||||
webContents: {
|
||||
goToOffset: jest.fn(),
|
||||
canGoToOffset: () => false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ViewManager.getCurrentView.mockReturnValue(view);
|
||||
|
||||
windowManager.handleHistory(null, 1);
|
||||
expect(view.view.webContents.goToOffset).not.toBeCalled();
|
||||
|
||||
ViewManager.getCurrentView.mockReturnValue({
|
||||
...view,
|
||||
view: {
|
||||
...view.view,
|
||||
webContents: {
|
||||
...view.view.webContents,
|
||||
canGoToOffset: () => true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
windowManager.handleHistory(null, 1);
|
||||
expect(view.view.webContents.goToOffset).toBeCalled();
|
||||
});
|
||||
|
||||
it('should load base URL if an error occurs', () => {
|
||||
const view = {
|
||||
load: jest.fn(),
|
||||
tab: {
|
||||
url: 'http://server-1.com',
|
||||
},
|
||||
view: {
|
||||
webContents: {
|
||||
goToOffset: jest.fn(),
|
||||
canGoToOffset: () => true,
|
||||
},
|
||||
},
|
||||
};
|
||||
view.view.webContents.goToOffset.mockImplementation(() => {
|
||||
throw new Error('hi');
|
||||
});
|
||||
ViewManager.getCurrentView.mockReturnValue(view);
|
||||
|
||||
windowManager.handleHistory(null, 1);
|
||||
expect(view.load).toBeCalledWith('http://server-1.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectTab', () => {
|
||||
const windowManager = new WindowManager();
|
||||
windowManager.switchTab = jest.fn();
|
||||
|
@ -801,11 +746,7 @@ describe('main/windows/windowManager', () => {
|
|||
const map = Config.teams.reduce((arr, item) => {
|
||||
item.tabs.forEach((tab) => {
|
||||
arr.push([`${item.name}_${tab.name}`, {
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
}]);
|
||||
});
|
||||
return arr;
|
||||
|
@ -838,7 +779,7 @@ describe('main/windows/windowManager', () => {
|
|||
|
||||
await windowManager.handleGetDesktopSources('server-1_tab-1', null);
|
||||
|
||||
expect(ViewManager.getView('server-1_tab-1').view.webContents.send).toHaveBeenCalledWith('desktop-sources-result', [
|
||||
expect(ViewManager.getView('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('desktop-sources-result', [
|
||||
{
|
||||
id: 'screen0',
|
||||
},
|
||||
|
@ -854,7 +795,7 @@ describe('main/windows/windowManager', () => {
|
|||
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(ViewManager.getView('server-2_tab-1').view.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
expect(ViewManager.getView('server-2_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
|
||||
|
@ -877,10 +818,10 @@ describe('main/windows/windowManager', () => {
|
|||
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(ViewManager.getView('server-1_tab-1').view.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
expect(ViewManager.getView('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(ViewManager.getView('server-1_tab-1').view.webContents.send).toHaveBeenCalledTimes(1);
|
||||
expect(ViewManager.getView('server-1_tab-1').sendToRenderer).toHaveBeenCalledTimes(1);
|
||||
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -908,7 +849,7 @@ describe('main/windows/windowManager', () => {
|
|||
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(ViewManager.getView('server-1_tab-1').view.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
expect(ViewManager.getView('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
|
||||
|
@ -932,11 +873,7 @@ describe('main/windows/windowManager', () => {
|
|||
return {
|
||||
getServerName: () => 'server-1',
|
||||
getMainView: jest.fn().mockReturnValue({
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
@ -1007,11 +944,7 @@ describe('main/windows/windowManager', () => {
|
|||
return {
|
||||
getServerName: () => 'server-2',
|
||||
getMainView: jest.fn().mockReturnValue({
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
}),
|
||||
getChannelURL: jest.fn(),
|
||||
};
|
||||
|
@ -1086,11 +1019,7 @@ describe('main/windows/windowManager', () => {
|
|||
return {
|
||||
getServerName: () => 'server-2',
|
||||
getMainView: jest.fn().mockReturnValue({
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
@ -1107,7 +1036,7 @@ describe('main/windows/windowManager', () => {
|
|||
windowManager.handleCallsError('', {err: 'client-error'});
|
||||
expect(windowManager.switchServer).toHaveBeenCalledWith('server-2');
|
||||
expect(mainWindow.focus).toHaveBeenCalled();
|
||||
expect(windowManager.callsWidgetWindow.getMainView().view.webContents.send).toHaveBeenCalledWith('calls-error', {err: 'client-error'});
|
||||
expect(windowManager.callsWidgetWindow.getMainView().sendToRenderer).toHaveBeenCalledWith('calls-error', {err: 'client-error'});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1115,11 +1044,7 @@ describe('main/windows/windowManager', () => {
|
|||
const windowManager = new WindowManager();
|
||||
windowManager.switchServer = jest.fn();
|
||||
const view1 = {
|
||||
view: {
|
||||
webContents: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -1141,7 +1066,7 @@ describe('main/windows/windowManager', () => {
|
|||
windowManager.callsWidgetWindow = new CallsWidgetWindow();
|
||||
windowManager.handleCallsLinkClick('', {link: '/other/subpath'});
|
||||
expect(windowManager.switchServer).toHaveBeenCalledWith('server-1');
|
||||
expect(view1.view.webContents.send).toBeCalledWith('browser-history-push', '/other/subpath');
|
||||
expect(view1.sendToRenderer).toBeCalledWith('browser-history-push', '/other/subpath');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
|
||||
import {
|
||||
MAXIMIZE_CHANGE,
|
||||
HISTORY,
|
||||
FOCUS_THREE_DOT_MENU,
|
||||
GET_DARK_MODE,
|
||||
UPDATE_SHORTCUT_MENU,
|
||||
|
@ -74,7 +73,6 @@ export class WindowManager {
|
|||
constructor() {
|
||||
this.assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||
|
||||
ipcMain.on(HISTORY, this.handleHistory);
|
||||
ipcMain.handle(GET_DARK_MODE, this.handleGetDarkMode);
|
||||
ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId);
|
||||
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
||||
|
@ -133,7 +131,7 @@ export class WindowManager {
|
|||
if (this.callsWidgetWindow) {
|
||||
this.switchServer(this.callsWidgetWindow.getServerName());
|
||||
MainWindow.get()?.focus();
|
||||
this.callsWidgetWindow.getMainView().view.webContents.send(DESKTOP_SOURCES_MODAL_REQUEST);
|
||||
this.callsWidgetWindow.getMainView().sendToRenderer(DESKTOP_SOURCES_MODAL_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +141,7 @@ export class WindowManager {
|
|||
if (this.callsWidgetWindow) {
|
||||
this.switchServer(this.callsWidgetWindow.getServerName());
|
||||
MainWindow.get()?.focus();
|
||||
this.callsWidgetWindow.getMainView().view.webContents.send(BROWSER_HISTORY_PUSH, this.callsWidgetWindow.getChannelURL());
|
||||
this.callsWidgetWindow.getMainView().sendToRenderer(BROWSER_HISTORY_PUSH, this.callsWidgetWindow.getChannelURL());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +151,7 @@ export class WindowManager {
|
|||
if (this.callsWidgetWindow) {
|
||||
this.switchServer(this.callsWidgetWindow.getServerName());
|
||||
MainWindow.get()?.focus();
|
||||
this.callsWidgetWindow.getMainView().view.webContents.send(CALLS_ERROR, msg);
|
||||
this.callsWidgetWindow.getMainView().sendToRenderer(CALLS_ERROR, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +161,7 @@ export class WindowManager {
|
|||
if (this.callsWidgetWindow) {
|
||||
this.switchServer(this.callsWidgetWindow.getServerName());
|
||||
MainWindow.get()?.focus();
|
||||
this.callsWidgetWindow.getMainView().view.webContents.send(BROWSER_HISTORY_PUSH, msg.link);
|
||||
this.callsWidgetWindow.getMainView().sendToRenderer(BROWSER_HISTORY_PUSH, msg.link);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,7 +312,7 @@ export class WindowManager {
|
|||
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (currentView) {
|
||||
const adjustedBounds = getAdjustedWindowBoundaries(bounds.width, bounds.height, shouldHaveBackBar(currentView.tab.url, currentView.view.webContents.getURL()));
|
||||
const adjustedBounds = getAdjustedWindowBoundaries(bounds.width, bounds.height, shouldHaveBackBar(currentView.tab.url, currentView.currentURL));
|
||||
this.setBoundsFunction(currentView, adjustedBounds);
|
||||
}
|
||||
}
|
||||
|
@ -520,27 +518,6 @@ export class WindowManager {
|
|||
}
|
||||
}
|
||||
|
||||
sendToFind = () => {
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (currentView) {
|
||||
currentView.view.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
}
|
||||
}
|
||||
|
||||
handleHistory = (event: IpcMainEvent, offset: number) => {
|
||||
log.debug('handleHistory', offset);
|
||||
|
||||
const activeView = ViewManager.getCurrentView();
|
||||
if (activeView && activeView.view.webContents.canGoToOffset(offset)) {
|
||||
try {
|
||||
activeView.view.webContents.goToOffset(offset);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
activeView.load(activeView.tab.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectNextTab = () => {
|
||||
this.selectTab((order) => order + 1);
|
||||
}
|
||||
|
@ -627,7 +604,7 @@ export class WindowManager {
|
|||
|
||||
if (!hasScreenPermissions || !sources.length) {
|
||||
log.info('missing screen permissions');
|
||||
view.view.webContents.send(CALLS_ERROR, screenPermissionsErrMsg);
|
||||
view.sendToRenderer(CALLS_ERROR, screenPermissionsErrMsg);
|
||||
this.callsWidgetWindow?.win.webContents.send(CALLS_ERROR, screenPermissionsErrMsg);
|
||||
return;
|
||||
}
|
||||
|
@ -641,12 +618,12 @@ export class WindowManager {
|
|||
});
|
||||
|
||||
if (message.length > 0) {
|
||||
view.view.webContents.send(DESKTOP_SOURCES_RESULT, message);
|
||||
view.sendToRenderer(DESKTOP_SOURCES_RESULT, message);
|
||||
}
|
||||
}).catch((err) => {
|
||||
log.error('desktopCapturer.getSources failed', err);
|
||||
|
||||
view.view.webContents.send(CALLS_ERROR, screenPermissionsErrMsg);
|
||||
view.sendToRenderer(CALLS_ERROR, screenPermissionsErrMsg);
|
||||
this.callsWidgetWindow?.win.webContents.send(CALLS_ERROR, screenPermissionsErrMsg);
|
||||
});
|
||||
}
|
||||
|
|
16
src/types/external/tuple-record-polyfill.d.ts
vendored
16
src/types/external/tuple-record-polyfill.d.ts
vendored
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
declare module '@bloomberg/record-tuple-polyfill' {
|
||||
export function Tuple<A>(A): [A];
|
||||
export function Tuple<A, B>(a: A, b: B): [A, B];
|
||||
export function Tuple<A, B, C>(a: A, b: B, c: C): [A, B, C];
|
||||
export function Tuple<A, B, C, D>(a: A, b: B, c: C, d: D): [A, B, C, D];
|
||||
export function Tuple<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E): [A, B, C, D, E];
|
||||
export function Tuple<A, B, C, D, E, F>(a: A, b: B, c: C, d: D, e: E, f: F): [A, B, C, D, E, F];
|
||||
export function Tuple<A, B, C, D, E, F, G>(a: A, b: B, c: C, d: D, e: E, f: F, g: G): [A, B, C, D, E, F, G];
|
||||
export function Tuple<A, B, C, D, E, F, G, H>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): [A, B, C, D, E, F, G, H];
|
||||
export function Tuple<A, B, C, D, E, F, G, H, I>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I): [A, B, C, D, E, F, G, H, I];
|
||||
export function Tuple<A, B, C, D, E, F, G, H, I, J>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J): [A, B, C, D, E, F, G, H, I, J];
|
||||
export function Record<T>(x: {[key: string]: T}): {[key: string]: T};
|
||||
}
|
Loading…
Reference in a new issue