[MM-52696] Upgrade and clean up Desktop App dev dependencies (#2970)
* Upgrade to ESLint v8 * Upgrade TypeScript, api-types, react-intl * Remove unnecessary dependencies * Update to React 17.0.2 * npm audit fixes, remove storybook * Lock some packages * Remove nan patch * Remove some deprecated dependencies * Fix lint/type/tests * Merge'd * Fix bad use of spawn * Fix notarize * Fix afterpack, switch to tsc es2020 * Fix api types * Use @mattermost/eslint-plugin
This commit is contained in:
@@ -27,7 +27,7 @@ describe('main/AppVersionManager', () => {
|
||||
fs.readFileSync.mockReturnValue('some bad JSON');
|
||||
Validator.validateAppState.mockReturnValue(false);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const appVersionManager = new AppVersionManager('somefilename.txt');
|
||||
|
||||
expect(fs.writeFile).toBeCalledWith('somefilename.txt', '{}', expect.any(Function));
|
||||
|
@@ -4,14 +4,13 @@
|
||||
|
||||
import {ipcMain} from 'electron';
|
||||
|
||||
import {AppState} from 'types/appState';
|
||||
|
||||
import {UPDATE_PATHS} from 'common/communication';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import {appVersionJson} from 'main/constants';
|
||||
|
||||
import type {AppState} from 'types/appState';
|
||||
|
||||
export class AppVersionManager extends JsonFileManager<AppState> {
|
||||
constructor(file: string) {
|
||||
super(file);
|
||||
@@ -24,7 +23,7 @@ export class AppVersionManager extends JsonFileManager<AppState> {
|
||||
if (!validatedJSON) {
|
||||
this.setJson({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
set lastAppVersion(version) {
|
||||
this.setValue('lastAppVersion', version);
|
||||
|
@@ -3,7 +3,6 @@
|
||||
'use strict';
|
||||
|
||||
import {spawn} from 'child_process';
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import {app, dialog} from 'electron';
|
||||
|
@@ -3,14 +3,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import {spawn} from 'child_process';
|
||||
import fs from 'fs';
|
||||
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import {app, dialog} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
const log = new Logger('CriticalErrorHandler');
|
||||
@@ -19,7 +17,7 @@ export class CriticalErrorHandler {
|
||||
init = () => {
|
||||
process.on('unhandledRejection', this.processUncaughtExceptionHandler);
|
||||
process.on('uncaughtException', this.processUncaughtExceptionHandler);
|
||||
}
|
||||
};
|
||||
|
||||
private processUncaughtExceptionHandler = (err: Error) => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
@@ -33,7 +31,7 @@ export class CriticalErrorHandler {
|
||||
this.showExceptionDialog(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private showExceptionDialog = (err: Error) => {
|
||||
const file = path.join(app.getPath('userData'), `uncaughtException-${Date.now()}.txt`);
|
||||
@@ -91,7 +89,7 @@ export class CriticalErrorHandler {
|
||||
}
|
||||
app.exit(-1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private openDetachedExternal = (url: string) => {
|
||||
const spawnOption = {detached: true, stdio: 'ignore' as const};
|
||||
@@ -105,7 +103,7 @@ export class CriticalErrorHandler {
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private createErrorReport = (err: Error) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
@@ -114,7 +112,7 @@ export class CriticalErrorHandler {
|
||||
return `Application: ${app.name} ${app.getVersion()}${__HASH_VERSION__ ? ` [commit: ${__HASH_VERSION__}]` : ''}\n` +
|
||||
`Platform: ${os.type()} ${os.release()} ${os.arch()}\n` +
|
||||
`${err.stack}`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const criticalErrorHandler = new CriticalErrorHandler();
|
||||
|
@@ -2,13 +2,14 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {app} from 'electron';
|
||||
import {Args} from 'types/args';
|
||||
import yargs from 'yargs';
|
||||
|
||||
import {protocols} from '../../electron-builder.json';
|
||||
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import type {Args} from 'types/args';
|
||||
|
||||
import {protocols} from '../../electron-builder.json';
|
||||
|
||||
export default function parse(args: string[]) {
|
||||
return validateArgs(parseArgs(triageArgs(args)));
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import {app, powerMonitor} from 'electron';
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
|
@@ -6,9 +6,8 @@ import fs from 'fs';
|
||||
|
||||
import {shell, dialog} from 'electron';
|
||||
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
||||
import {AllowProtocolDialog} from './allowProtocolDialog';
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
readFile: jest.fn(),
|
||||
|
@@ -7,14 +7,13 @@ import fs from 'fs';
|
||||
|
||||
import {dialog, shell} from 'electron';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
import buildConfig from 'common/config/buildConfig';
|
||||
import {Logger} from 'common/log';
|
||||
import * as Validator from 'common/Validator';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
import MainWindow from './windows/mainWindow';
|
||||
import {allowedProtocolFile} from './constants';
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
||||
const log = new Logger('AllowProtocolDialog');
|
||||
|
||||
@@ -35,14 +34,14 @@ export class AllowProtocolDialog {
|
||||
this.addScheme('https');
|
||||
buildConfig.allowedProtocols.forEach(this.addScheme);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
addScheme = (scheme: string) => {
|
||||
const proto = `${scheme}:`;
|
||||
if (!this.allowedProtocols.includes(proto)) {
|
||||
this.allowedProtocols.push(proto);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleDialogEvent = async (protocol: string, URL: string) => {
|
||||
try {
|
||||
@@ -88,7 +87,7 @@ export class AllowProtocolDialog {
|
||||
} catch (error) {
|
||||
log.warn('Could not open external URL', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const allowProtocolDialog = new AllowProtocolDialog();
|
||||
|
@@ -3,12 +3,11 @@
|
||||
|
||||
import {app, dialog} from 'electron';
|
||||
|
||||
import CertificateStore from 'main/certificateStore';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import {handleAppWillFinishLaunching, handleAppCertificateError, certificateErrorCallbacks} from 'main/app/app';
|
||||
import {getDeeplinkingURL, openDeepLink} from 'main/app/utils';
|
||||
import CertificateStore from 'main/certificateStore';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {app, BrowserWindow, Event, dialog, WebContents, Certificate, Details} from 'electron';
|
||||
import type {BrowserWindow, Event, WebContents, Certificate, Details} from 'electron';
|
||||
import {app, dialog} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import {parseURL} from 'common/utils/url';
|
||||
|
||||
import updateManager from 'main/autoUpdater';
|
||||
import CertificateStore from 'main/certificateStore';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
@@ -6,12 +6,10 @@ import {app} from 'electron';
|
||||
import {RELOAD_CONFIGURATION} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {setLoggingLevel} from 'common/log';
|
||||
|
||||
import {handleConfigUpdate} from 'main/app/config';
|
||||
import {handleMainWindowIsShown} from 'main/app/intercom';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import AutoLauncher from 'main/AutoLauncher';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {
|
||||
|
@@ -3,12 +3,9 @@
|
||||
|
||||
import {app, ipcMain, nativeTheme} from 'electron';
|
||||
|
||||
import {CombinedConfig, Config as ConfigType} from 'types/config';
|
||||
|
||||
import {DARK_MODE_CHANGE, EMIT_CONFIGURATION, RELOAD_CONFIGURATION} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger, setLoggingLevel} from 'common/log';
|
||||
|
||||
import AutoLauncher from 'main/AutoLauncher';
|
||||
import {setUnreadBadgeSetting} from 'main/badge';
|
||||
import Tray from 'main/tray/tray';
|
||||
@@ -16,6 +13,8 @@ import LoadingScreen from 'main/views/loadingScreen';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
|
||||
import type {CombinedConfig, Config as ConfigType} from 'types/config';
|
||||
|
||||
import {handleMainWindowIsShown} from './intercom';
|
||||
import {handleUpdateMenuEvent, updateSpellCheckerLocales} from './utils';
|
||||
|
||||
|
@@ -10,10 +10,6 @@ import('main/views/serverDropdownView');
|
||||
import('main/views/downloadsDropdownMenuView');
|
||||
import('main/views/downloadsDropdownView');
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' && module.hot) {
|
||||
module.hot.accept();
|
||||
}
|
||||
|
||||
// attempt to initialize the application
|
||||
try {
|
||||
initialize();
|
||||
|
@@ -6,7 +6,6 @@ import path from 'path';
|
||||
import {app, session} from 'electron';
|
||||
|
||||
import Config from 'common/config';
|
||||
|
||||
import parseArgs from 'main/ParseArgs';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
|
@@ -34,7 +34,7 @@ import {
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import AllowProtocolDialog from 'main/allowProtocolDialog';
|
||||
import AppVersionManager from 'main/AppVersionManager';
|
||||
import AuthManager from 'main/authManager';
|
||||
@@ -48,15 +48,12 @@ import downloadsManager from 'main/downloadsManager';
|
||||
import i18nManager from 'main/i18nManager';
|
||||
import parseArgs from 'main/ParseArgs';
|
||||
import PermissionsManager from 'main/permissionsManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import TrustedOriginsStore from 'main/trustedOrigins';
|
||||
import Tray from 'main/tray/tray';
|
||||
import TrustedOriginsStore from 'main/trustedOrigins';
|
||||
import UserActivityMonitor from 'main/UserActivityMonitor';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {protocols} from '../../../electron-builder.json';
|
||||
|
||||
import {
|
||||
handleAppBeforeQuit,
|
||||
handleAppBrowserWindowCreated,
|
||||
@@ -103,6 +100,8 @@ import {
|
||||
handleRestore,
|
||||
} from './windows';
|
||||
|
||||
import {protocols} from '../../../electron-builder.json';
|
||||
|
||||
export const mainProtocol = protocols?.[0]?.schemes?.[0];
|
||||
|
||||
const log = new Logger('App.Initialize');
|
||||
|
@@ -3,10 +3,10 @@
|
||||
|
||||
import {app} from 'electron';
|
||||
|
||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {
|
||||
handleWelcomeScreenModal,
|
||||
|
@@ -1,21 +1,20 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {app, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
|
||||
|
||||
import {UniqueServer} from 'types/config';
|
||||
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {app, Menu} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {ping} from 'common/utils/requests';
|
||||
|
||||
import NotificationManager from 'main/notifications';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import type {UniqueServer} from 'types/config';
|
||||
|
||||
import {handleAppBeforeQuit} from './app';
|
||||
|
||||
const log = new Logger('App.Intercom');
|
||||
|
@@ -1,12 +1,10 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {dialog, screen} from 'electron';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import {dialog, screen} from 'electron';
|
||||
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
|
||||
import {updatePaths} from 'main/constants';
|
||||
|
||||
import {getDeeplinkingURL, resizeScreen, migrateMacAppStore} from './utils';
|
||||
|
@@ -3,23 +3,18 @@
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import type {BrowserWindow, Rectangle, Session} from 'electron';
|
||||
import {app, Menu, session, dialog, nativeImage, screen} from 'electron';
|
||||
import isDev from 'electron-is-dev';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import {app, BrowserWindow, Menu, Rectangle, Session, session, dialog, nativeImage, screen} from 'electron';
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
import {MigrationInfo} from 'types/config';
|
||||
import {RemoteInfo} from 'types/server';
|
||||
import {Boundaries} from 'types/utils';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {APP_MENU_WILL_CLOSE} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import {Logger} from 'common/log';
|
||||
import type {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {isValidURI} from 'common/utils/url';
|
||||
|
||||
import updateManager from 'main/autoUpdater';
|
||||
import {migrationInfoPath, updatePaths} from 'main/constants';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
@@ -30,6 +25,10 @@ import Tray from 'main/tray/tray';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import type {MigrationInfo} from 'types/config';
|
||||
import type {RemoteInfo} from 'types/server';
|
||||
import type {Boundaries} from 'types/utils';
|
||||
|
||||
import {mainProtocol} from './initialize';
|
||||
|
||||
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserWindow, IpcMainEvent, systemPreferences} from 'electron';
|
||||
import type {IpcMainEvent} from 'electron';
|
||||
import {BrowserWindow, systemPreferences} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
const log = new Logger('App.Windows');
|
||||
|
||||
|
@@ -3,9 +3,9 @@
|
||||
'use strict';
|
||||
|
||||
import {AuthManager} from 'main/authManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
jest.mock('common/utils/url', () => {
|
||||
const actualUrl = jest.requireActual('common/utils/url');
|
||||
|
@@ -1,19 +1,18 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {AuthenticationResponseDetails, AuthInfo, WebContents, Event} from 'electron';
|
||||
|
||||
import {PermissionType} from 'types/trustedOrigin';
|
||||
import {LoginModalData} from 'types/auth';
|
||||
import type {AuthenticationResponseDetails, AuthInfo, WebContents, Event} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import {BASIC_AUTH_PERMISSION} from 'common/permissions';
|
||||
import {isCustomLoginURL, isTrustedURL, parseURL} from 'common/utils/url';
|
||||
|
||||
import modalManager from 'main/views/modalManager';
|
||||
import TrustedOriginsStore from 'main/trustedOrigins';
|
||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import modalManager from 'main/views/modalManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import type {LoginModalData} from 'types/auth';
|
||||
import type {PermissionType} from 'types/trustedOrigin';
|
||||
|
||||
const log = new Logger('AuthManager');
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
@@ -51,7 +50,7 @@ export class AuthManager {
|
||||
} else {
|
||||
this.popPermissionModal(request, authInfo, BASIC_AUTH_PERMISSION);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
popLoginModal = (request: AuthenticationResponseDetails, authInfo: AuthInfo) => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -70,7 +69,7 @@ export class AuthManager {
|
||||
this.handleCancelLoginEvent(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
popPermissionModal = (request: AuthenticationResponseDetails, authInfo: AuthInfo, permission: PermissionType) => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -89,7 +88,7 @@ export class AuthManager {
|
||||
this.handleCancelLoginEvent(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleLoginCredentialsEvent = (request: AuthenticationResponseDetails, username?: string, password?: string) => {
|
||||
const callback = this.loginCallbackMap.get(request.url);
|
||||
@@ -101,12 +100,12 @@ export class AuthManager {
|
||||
callback(username, password);
|
||||
}
|
||||
this.loginCallbackMap.delete(request.url);
|
||||
}
|
||||
};
|
||||
|
||||
handleCancelLoginEvent = (request: AuthenticationResponseDetails) => {
|
||||
log.info(`Cancelling request for ${request ? request.url : 'unknown'}`);
|
||||
this.handleLoginCredentialsEvent(request); // we use undefined to cancel the request
|
||||
}
|
||||
};
|
||||
|
||||
handlePermissionGranted(url: string, permission: PermissionType) {
|
||||
const parsedURL = parseURL(url);
|
||||
|
@@ -5,7 +5,6 @@ import {ipcMain as notMockedIpcMain} from 'electron';
|
||||
import {autoUpdater as notMockedAutoUpdater} from 'electron-updater';
|
||||
|
||||
import {CHECK_FOR_UPDATES} from 'common/communication';
|
||||
|
||||
import NotificationManager from 'main/notifications';
|
||||
|
||||
import {UpdateManager} from './autoUpdater';
|
||||
|
@@ -4,13 +4,8 @@
|
||||
import path from 'path';
|
||||
|
||||
import {dialog, ipcMain, app, nativeImage} from 'electron';
|
||||
import {autoUpdater, CancellationToken, ProgressInfo, UpdateInfo} from 'electron-updater';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import NotificationManager from 'main/notifications';
|
||||
import type {ProgressInfo, UpdateInfo} from 'electron-updater';
|
||||
import {autoUpdater, CancellationToken} from 'electron-updater';
|
||||
|
||||
import {
|
||||
CANCEL_UPGRADE,
|
||||
@@ -24,6 +19,10 @@ import {
|
||||
UPDATE_REMIND_LATER,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import NotificationManager from 'main/notifications';
|
||||
|
||||
const NEXT_NOTIFY = 86400000; // 24 hours
|
||||
const NEXT_CHECK = 3600000; // 1 hour
|
||||
@@ -109,44 +108,44 @@ export class UpdateManager {
|
||||
} else if (this.versionAvailable) {
|
||||
this.notifyUpgrade();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
notifyUpgrade = (): void => {
|
||||
ipcMain.emit(UPDATE_AVAILABLE, null, this.versionAvailable);
|
||||
NotificationManager.displayUpgrade(this.versionAvailable || 'unknown', this.handleDownload);
|
||||
}
|
||||
};
|
||||
|
||||
notifyDownloaded = (): void => {
|
||||
ipcMain.emit(UPDATE_DOWNLOADED, null, this.downloadedInfo);
|
||||
NotificationManager.displayRestartToUpgrade(this.versionDownloaded || 'unknown', this.handleUpdate);
|
||||
}
|
||||
};
|
||||
|
||||
handleDownload = (): void => {
|
||||
if (this.lastCheck) {
|
||||
clearTimeout(this.lastCheck);
|
||||
}
|
||||
autoUpdater.downloadUpdate(this.cancellationToken);
|
||||
}
|
||||
};
|
||||
|
||||
handleCancelDownload = (): void => {
|
||||
this.cancellationToken?.cancel();
|
||||
this.cancellationToken = new CancellationToken();
|
||||
}
|
||||
};
|
||||
|
||||
handleRemindLater = (): void => {
|
||||
// TODO
|
||||
}
|
||||
};
|
||||
|
||||
handleOnQuit = (): void => {
|
||||
if (this.versionDownloaded) {
|
||||
autoUpdater.quitAndInstall(true, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleUpdate = (): void => {
|
||||
downloadsManager.removeUpdateBeforeRestart();
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
};
|
||||
|
||||
displayNoUpgrade = (): void => {
|
||||
const version = app.getVersion();
|
||||
@@ -159,7 +158,7 @@ export class UpdateManager {
|
||||
buttons: [localizeMessage('label.ok', 'OK')],
|
||||
detail: localizeMessage('main.autoUpdater.noUpdate.detail', 'You are using the latest version of the {appName} Desktop App (version {version}). You\'ll be notified when a new version is available to install.', {appName: app.name, version}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checkForUpdates = (manually: boolean): void => {
|
||||
if (!Config.canUpgrade) {
|
||||
@@ -183,7 +182,7 @@ export class UpdateManager {
|
||||
});
|
||||
this.lastCheck = setTimeout(() => this.checkForUpdates(false), NEXT_CHECK);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const updateManager = new UpdateManager();
|
||||
|
@@ -4,9 +4,8 @@
|
||||
|
||||
import {app, nativeImage} from 'electron';
|
||||
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
||||
import * as Badge from './badge';
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {
|
||||
|
@@ -2,12 +2,12 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserWindow, app, nativeImage} from 'electron';
|
||||
import type {BrowserWindow} from 'electron';
|
||||
import {app, nativeImage} from 'electron';
|
||||
|
||||
import AppState from 'common/appState';
|
||||
import {UPDATE_APPSTATE_TOTALS} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
@@ -2,9 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
'use strict';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import {CertificateManager} from 'main/certificateManager';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
jest.mock('main/windows/mainWindow', () => ({
|
||||
get: jest.fn().mockImplementation(() => ({})),
|
||||
|
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Certificate, WebContents, Event} from 'electron';
|
||||
|
||||
import {CertificateModalData} from 'types/certificate';
|
||||
import type {Certificate, WebContents, Event} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import modalManager from './views/modalManager';
|
||||
import type {CertificateModalData} from 'types/certificate';
|
||||
|
||||
import {getLocalURLString, getLocalPreload} from './utils';
|
||||
import modalManager from './views/modalManager';
|
||||
import MainWindow from './windows/mainWindow';
|
||||
|
||||
const log = new Logger('CertificateManager');
|
||||
@@ -38,7 +38,7 @@ export class CertificateManager {
|
||||
} else {
|
||||
log.info(`There were ${list.length} candidate certificates. Skipping certificate selection`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
popCertificateModal = (url: string, list: Certificate[]) => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -57,7 +57,7 @@ export class CertificateManager {
|
||||
this.handleSelectedCertificate(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleSelectedCertificate = (server: string, cert?: Certificate) => {
|
||||
const callback = this.certificateRequestCallbackMap.get(server);
|
||||
@@ -75,7 +75,7 @@ export class CertificateManager {
|
||||
}
|
||||
}
|
||||
this.certificateRequestCallbackMap.delete(server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const certificateManager = new CertificateManager();
|
||||
|
@@ -5,14 +5,15 @@
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import {Certificate, ipcMain} from 'electron';
|
||||
|
||||
import {ComparableCertificate} from 'types/certificate';
|
||||
import type {Certificate} from 'electron';
|
||||
import {ipcMain} from 'electron';
|
||||
|
||||
import {UPDATE_PATHS} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import type {ComparableCertificate} from 'types/certificate';
|
||||
|
||||
import {certificateStorePath} from './constants';
|
||||
|
||||
function comparableCertificate(certificate: Certificate, dontTrust = false): ComparableCertificate {
|
||||
@@ -83,7 +84,7 @@ export class CertificateStore {
|
||||
// clicking "Don't ask again" checkbox before cancelling the connection.
|
||||
const dontTrust = this.data[targetURL.origin]?.dontTrust;
|
||||
return dontTrust === undefined ? false : dontTrust;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let certificateStore = new CertificateStore(certificateStorePath);
|
||||
|
@@ -2,8 +2,9 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, BrowserWindow, ContextMenuParams, Event} from 'electron';
|
||||
import electronContextMenu, {Options} from 'electron-context-menu';
|
||||
import type {BrowserView, BrowserWindow, ContextMenuParams, Event} from 'electron';
|
||||
import type {Options} from 'electron-context-menu';
|
||||
import electronContextMenu from 'electron-context-menu';
|
||||
|
||||
import {parseURL} from 'common/utils/url';
|
||||
|
||||
@@ -46,12 +47,12 @@ export default class ContextMenu {
|
||||
this.menuDispose();
|
||||
delete this.menuDispose;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reload = () => {
|
||||
this.dispose();
|
||||
|
||||
const options = {window: this.view, ...this.menuOptions};
|
||||
this.menuDispose = electronContextMenu(options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {DiagnosticsStepConstructorPayload} from 'types/diagnostics';
|
||||
import type {DiagnosticsStepConstructorPayload} from 'types/diagnostics';
|
||||
|
||||
import {addDurationToFnReturnObject} from './steps/internal/utils';
|
||||
|
||||
|
@@ -2,11 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shell} from 'electron';
|
||||
import log, {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticsReport} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
import log from 'electron-log';
|
||||
|
||||
import type {DiagnosticsReport} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from './DiagnosticStep';
|
||||
|
||||
import Step0 from './steps/step0.logLevel';
|
||||
import Step1 from './steps/step1.internetConnection';
|
||||
import Step10 from './steps/step10.crashReports';
|
||||
@@ -62,7 +63,7 @@ class DiagnosticsModule {
|
||||
this.logger.error('Diagnostics.run Error: ', {error});
|
||||
this.initializeValues(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initializeValues = (clear = false) => {
|
||||
this.logger.transports.file.level = 'silly';
|
||||
@@ -72,14 +73,14 @@ class DiagnosticsModule {
|
||||
this.stepCurrent = 0;
|
||||
this.stepTotal = clear ? 0 : this.getStepCount();
|
||||
this.report = [];
|
||||
}
|
||||
};
|
||||
|
||||
getStepCount = () => {
|
||||
const stepsCount = SORTED_STEPS.length;
|
||||
this.logger.debug('Diagnostics.getStepCount', {stepsCount});
|
||||
|
||||
return stepsCount;
|
||||
}
|
||||
};
|
||||
|
||||
executeSteps = async () => {
|
||||
this.logger.info('Diagnostics.executeSteps Started');
|
||||
@@ -112,7 +113,7 @@ class DiagnosticsModule {
|
||||
this.stepCurrent = index;
|
||||
}
|
||||
this.logger.info('Diagnostics.executeSteps Finished');
|
||||
}
|
||||
};
|
||||
|
||||
printReport = () => {
|
||||
const totalStepsCount = this.getStepCount();
|
||||
@@ -129,42 +130,42 @@ class DiagnosticsModule {
|
||||
this.logger.info(`| ---${this.fillSpaces(3)}| ---${this.fillSpaces(maxStepNameLength - 3)} | ---${this.fillSpaces(7)}|`);
|
||||
this.logger.info(`${successfulStepsCount} out of ${totalStepsCount} steps succeeded`);
|
||||
this.printStepEnd('Report');
|
||||
}
|
||||
};
|
||||
|
||||
showLogFile = () => {
|
||||
const pathToFile = this.getLoggerFilePath();
|
||||
|
||||
this.logger.debug('Diagnostics.showLogFile', {pathToFile});
|
||||
shell.showItemInFolder(pathToFile);
|
||||
}
|
||||
};
|
||||
|
||||
sendNotificationDiagnosticsStarted = () => {
|
||||
this.logger.debug('Diagnostics sendNotification DiagnosticsStarted');
|
||||
}
|
||||
};
|
||||
|
||||
isValidStep = (step: unknown) => {
|
||||
return step instanceof DiagnosticsStep;
|
||||
}
|
||||
};
|
||||
|
||||
getLoggerFilePath = () => {
|
||||
return this.logger.transports.file.getFile()?.path;
|
||||
}
|
||||
};
|
||||
|
||||
isRunning = () => {
|
||||
return this.stepTotal > 0 && this.stepCurrent >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
getSuccessfulStepsCount = () => {
|
||||
return this.report.filter((step) => step.succeeded).length;
|
||||
}
|
||||
};
|
||||
|
||||
private printStepStart = (name: string) => {
|
||||
this.logger.info(`${HASHTAGS} ${name} START ${HASHTAGS}`);
|
||||
}
|
||||
};
|
||||
|
||||
private printStepEnd = (name: string) => {
|
||||
this.logger.info(`${HASHTAGS} ${name} END ${HASHTAGS}`);
|
||||
}
|
||||
};
|
||||
|
||||
private addToReport(data: DiagnosticsReport[number]): void {
|
||||
this.report = [
|
||||
@@ -178,7 +179,7 @@ class DiagnosticsModule {
|
||||
return '';
|
||||
}
|
||||
return ' '.repeat(i);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const Diagnostics = new DiagnosticsModule();
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import {obfuscateByType} from './obfuscators';
|
||||
|
||||
|
@@ -4,12 +4,14 @@ import fs from 'fs';
|
||||
import https from 'https';
|
||||
import readline from 'readline';
|
||||
|
||||
import {BrowserWindow, Rectangle, WebContents} from 'electron';
|
||||
import log, {ElectronLog, LogLevel} from 'electron-log';
|
||||
import {AddDurationToFnReturnObject, LogFileLineData, LogLevelAmounts, WindowStatus} from 'types/diagnostics';
|
||||
import type {BrowserWindow, Rectangle, WebContents} from 'electron';
|
||||
import type {ElectronLog, LogLevel} from 'electron-log';
|
||||
import log from 'electron-log';
|
||||
|
||||
import {IS_ONLINE_ENDPOINT, LOGS_MAX_STRING_LENGTH, REGEX_LOG_FILE_LINE} from 'common/constants';
|
||||
|
||||
import type {AddDurationToFnReturnObject, LogFileLineData, LogLevelAmounts, WindowStatus} from 'types/diagnostics';
|
||||
|
||||
export function dateTimeInFilename(date?: Date) {
|
||||
const now = date ?? new Date();
|
||||
return `${now.getDate()}-${now.getMonth()}-${now.getFullYear()}_${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}-${now.getMilliseconds()}`;
|
||||
@@ -79,7 +81,7 @@ export async function isOnline(logger: ElectronLog = log, url = IS_ONLINE_ENDPOI
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Cannot parse response')
|
||||
logger.error('Cannot parse response');
|
||||
}
|
||||
}
|
||||
resolve(false);
|
||||
|
@@ -1,8 +1,9 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
|
@@ -4,15 +4,15 @@
|
||||
import path from 'path';
|
||||
|
||||
import {app} from 'electron';
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import loggerHooks from './internal/loggerHooks';
|
||||
import {dateTimeInFilename} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-0';
|
||||
const stepDescriptiveName = 'logConfig';
|
||||
|
||||
|
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import {isOnline} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-1';
|
||||
const stepDescriptiveName = 'internetConnection';
|
||||
|
||||
|
@@ -4,8 +4,9 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {app} from 'electron';
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
|
@@ -2,8 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {session} from 'electron';
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
|
@@ -2,14 +2,14 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import fs from 'fs';
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import Config from 'common/config';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import {configPath} from 'main/constants';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-2';
|
||||
|
@@ -1,16 +1,17 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {parseURL} from 'common/utils/url';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import {isOnline} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-3';
|
||||
const stepDescriptiveName = 'serverConnectivity';
|
||||
|
||||
|
@@ -2,11 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {session} from 'electron';
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import {COOKIE_NAME_AUTH_TOKEN, COOKIE_NAME_CSRF, COOKIE_NAME_USER_ID} from 'common/constants';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-4';
|
||||
|
@@ -1,16 +1,16 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import {browserWindowVisibilityStatus, webContentsCheck} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-5';
|
||||
const stepDescriptiveName = 'BrowserWindowsChecks';
|
||||
|
||||
|
@@ -3,14 +3,17 @@
|
||||
import fs from 'fs';
|
||||
|
||||
import {Notification, systemPreferences} from 'electron';
|
||||
import log, {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
import log from 'electron-log';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import config from 'common/config';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import {checkPathPermissions} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-6';
|
||||
const stepDescriptiveName = 'PermissionsCheck';
|
||||
|
||||
|
@@ -3,13 +3,14 @@
|
||||
import path from 'path';
|
||||
|
||||
import {app, powerMonitor} from 'electron';
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import {dateTimeInFilename} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-7';
|
||||
const stepDescriptiveName = 'PerformanceAndMemory';
|
||||
|
||||
|
@@ -1,16 +1,17 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import log, {ElectronLog} from 'electron-log';
|
||||
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
import log from 'electron-log';
|
||||
|
||||
import {getPercentage} from 'main/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import {readFileLineByLine} from './internal/utils';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-8';
|
||||
const stepDescriptiveName = 'LogHeuristics';
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ElectronLog} from 'electron-log';
|
||||
import {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
import type {ElectronLog} from 'electron-log';
|
||||
|
||||
import config from 'common/config';
|
||||
|
||||
import type {DiagnosticStepResponse} from 'types/diagnostics';
|
||||
|
||||
import DiagnosticsStep from '../DiagnosticStep';
|
||||
|
||||
const stepName = 'Step-9';
|
||||
|
@@ -1,15 +1,13 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {shell} from 'electron';
|
||||
|
||||
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {APP_UPDATE_KEY} from 'common/constants';
|
||||
|
||||
import {DownloadsManager} from 'main/downloadsManager';
|
||||
|
||||
const downloadLocationMock = '/path/to/downloads';
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {DownloadItem, Event, WebContents, FileFilter, ipcMain, dialog, shell, Menu, app, IpcMainInvokeEvent} from 'electron';
|
||||
import {ProgressInfo, UpdateInfo} from 'electron-updater';
|
||||
import {DownloadedItem, DownloadItemDoneEventState, DownloadedItems, DownloadItemState, DownloadItemUpdatedEventState} from 'types/downloads';
|
||||
import type {DownloadItem, Event, WebContents, FileFilter, IpcMainInvokeEvent} from 'electron';
|
||||
import {ipcMain, dialog, shell, Menu, app} from 'electron';
|
||||
import type {ProgressInfo, UpdateInfo} from 'electron-updater';
|
||||
|
||||
import {
|
||||
CANCEL_UPDATE_DOWNLOAD,
|
||||
@@ -25,16 +25,18 @@ import {
|
||||
UPDATE_PROGRESS,
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {APP_UPDATE_KEY, UPDATE_DOWNLOAD_ITEM} from 'common/constants';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import {Logger} from 'common/log';
|
||||
import {APP_UPDATE_KEY, UPDATE_DOWNLOAD_ITEM} from 'common/constants';
|
||||
import {DOWNLOADS_DROPDOWN_AUTOCLOSE_TIMEOUT, DOWNLOADS_DROPDOWN_MAX_ITEMS} from 'common/utils/constants';
|
||||
import * as Validator from 'common/Validator';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import NotificationManager from 'main/notifications';
|
||||
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
|
||||
|
||||
import type {DownloadedItem, DownloadItemDoneEventState, DownloadedItems, DownloadItemState, DownloadItemUpdatedEventState} from 'types/downloads';
|
||||
|
||||
import appVersionManager from './AppVersionManager';
|
||||
import {downloadsJson} from './constants';
|
||||
@@ -189,7 +191,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||
this.clearFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkForDeletedFiles = () => {
|
||||
log.debug('checkForDeletedFiles');
|
||||
@@ -388,7 +390,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||
startFrom,
|
||||
localizeMessage('main.downloadsManager.specifyDownloadsFolder', 'Specify the folder where files will download'),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private selectDefaultDownloadDirectory = async (startFrom: string, message: string) => {
|
||||
log.debug('handleSelectDownload', startFrom);
|
||||
@@ -398,7 +400,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||
properties:
|
||||
['openDirectory', 'createDirectory', 'dontAddToRecent', 'promptToCreate']});
|
||||
return result.filePaths[0];
|
||||
}
|
||||
};
|
||||
|
||||
private verifyMacAppStoreDownloadFolder = async (fileName: string) => {
|
||||
let downloadLocation = Config.downloadLocation;
|
||||
@@ -421,7 +423,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||
}
|
||||
|
||||
return downloadLocation;
|
||||
}
|
||||
};
|
||||
|
||||
private markFileAsDeleted = (item: DownloadedItem) => {
|
||||
const fileId = this.getDownloadedFileId(item);
|
||||
@@ -647,7 +649,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||
|
||||
private getBookmark = (item: DownloadItem) => {
|
||||
return this.bookmarks.get(this.getFileId(item))?.bookmark;
|
||||
}
|
||||
};
|
||||
|
||||
private getFileSize = (item: DownloadItem) => {
|
||||
const itemTotalBytes = item.getTotalBytes();
|
||||
|
@@ -6,7 +6,8 @@ import {ipcMain} from 'electron';
|
||||
import {GET_AVAILABLE_LANGUAGES, GET_LANGUAGE_INFORMATION} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {Language, languages} from '../../i18n/i18n';
|
||||
import type {Language} from '../../i18n/i18n';
|
||||
import {languages} from '../../i18n/i18n';
|
||||
|
||||
export function localizeMessage(s: string, defaultString = '', values: any = {}) {
|
||||
let str = i18nManager.currentLanguage.url[s] || defaultString;
|
||||
@@ -39,23 +40,23 @@ export class I18nManager {
|
||||
|
||||
log.warn('Failed to set new language', locale);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
getLanguages = () => {
|
||||
return languages;
|
||||
}
|
||||
};
|
||||
|
||||
getAvailableLanguages = () => {
|
||||
return Object.keys(languages);
|
||||
}
|
||||
};
|
||||
|
||||
isLanguageAvailable = (locale: string) => {
|
||||
return Boolean(this.getLanguages()[locale]);
|
||||
}
|
||||
};
|
||||
|
||||
getCurrentLanguage = () => {
|
||||
return this.currentLanguage;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const i18nManager = new I18nManager();
|
||||
|
@@ -5,11 +5,9 @@
|
||||
|
||||
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
|
||||
import {createTemplate} from './app';
|
||||
|
@@ -3,24 +3,24 @@
|
||||
// See LICENSE.txt for license information.
|
||||
'use strict';
|
||||
|
||||
import {app, ipcMain, Menu, MenuItemConstructorOptions, MenuItem, session, shell, WebContents, clipboard} from 'electron';
|
||||
import type {MenuItemConstructorOptions, MenuItem, WebContents} from 'electron';
|
||||
import {app, ipcMain, Menu, session, shell, clipboard} from 'electron';
|
||||
import log from 'electron-log';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import {OPEN_SERVERS_DROPDOWN, SHOW_NEW_SERVER_MODAL} from 'common/communication';
|
||||
import {t} from 'common/utils/util';
|
||||
import {getViewDisplayName, ViewType} from 'common/views/View';
|
||||
import {Config} from 'common/config';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import type {Config} from 'common/config';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {UpdateManager} from 'main/autoUpdater';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import {t} from 'common/utils/util';
|
||||
import {getViewDisplayName} from 'common/views/View';
|
||||
import type {ViewType} from 'common/views/View';
|
||||
import type {UpdateManager} from 'main/autoUpdater';
|
||||
import Diagnostics from 'main/diagnostics';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
|
||||
export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
const separatorItem: MenuItemConstructorOptions = {
|
||||
|
@@ -3,12 +3,11 @@
|
||||
// See LICENSE.txt for license information.
|
||||
'use strict';
|
||||
|
||||
import {Menu, MenuItem, MenuItemConstructorOptions} from 'electron';
|
||||
import type {MenuItem, MenuItemConstructorOptions} from 'electron';
|
||||
import {Menu} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
|
||||
|
@@ -4,12 +4,10 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import {app, Notification} from 'electron';
|
||||
import {v4 as uuid} from 'uuid';
|
||||
|
||||
import {app, Notification} from 'electron';
|
||||
|
||||
import Utils from 'common/utils/util';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||
|
@@ -4,16 +4,14 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import {app, Notification} from 'electron';
|
||||
import {v4 as uuid} from 'uuid';
|
||||
|
||||
import {app, Notification} from 'electron';
|
||||
|
||||
import {MentionOptions} from 'types/notification';
|
||||
|
||||
import Utils from 'common/utils/util';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
import type {MentionOptions} from 'types/notification';
|
||||
|
||||
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||
const appIconURL = path.resolve(assetsDir, 'appicon_48.png');
|
||||
|
||||
@@ -52,5 +50,5 @@ export class Mention extends Notification {
|
||||
|
||||
getNotificationSound = () => {
|
||||
return this.customSound;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -4,18 +4,17 @@
|
||||
'use strict';
|
||||
import notMockedCP from 'child_process';
|
||||
|
||||
import {Notification as NotMockedNotification, shell, app, BrowserWindow, WebContents} from 'electron';
|
||||
|
||||
import {getFocusAssist as notMockedGetFocusAssist} from 'windows-focus-assist';
|
||||
import type {BrowserWindow, WebContents} from 'electron';
|
||||
import {Notification as NotMockedNotification, shell, app} from 'electron';
|
||||
import {getDoNotDisturb as notMockedGetDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
import {getFocusAssist as notMockedGetFocusAssist} from 'windows-focus-assist';
|
||||
|
||||
import {PLAY_SOUND} from 'common/communication';
|
||||
import notMockedConfig from 'common/config';
|
||||
|
||||
import {localizeMessage as notMockedLocalizeMessage} from 'main/i18nManager';
|
||||
import notMockedPermissionsManager from 'main/permissionsManager';
|
||||
import notMockedMainWindow from 'main/windows/mainWindow';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import notMockedMainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import getLinuxDoNotDisturb from './dnd-linux';
|
||||
|
||||
@@ -50,7 +49,7 @@ jest.mock('electron', () => {
|
||||
|
||||
on = (event: string, callback: () => void) => {
|
||||
this.callbackMap.set(event, callback);
|
||||
}
|
||||
};
|
||||
|
||||
show = jest.fn().mockImplementation(() => {
|
||||
this.callbackMap.get('show')?.();
|
||||
|
@@ -2,23 +2,22 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {app, shell, Notification} from 'electron';
|
||||
|
||||
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {PLAY_SOUND, NOTIFICATION_CLICKED} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import getLinuxDoNotDisturb from './dnd-linux';
|
||||
import getWindowsDoNotDisturb from './dnd-windows';
|
||||
import {DownloadNotification} from './Download';
|
||||
import {Mention} from './Mention';
|
||||
import {NewVersionNotification, UpgradeNotification} from './Upgrade';
|
||||
|
||||
import PermissionsManager from '../permissionsManager';
|
||||
import ViewManager from '../views/viewManager';
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
|
||||
import {Mention} from './Mention';
|
||||
import {DownloadNotification} from './Download';
|
||||
import {NewVersionNotification, UpgradeNotification} from './Upgrade';
|
||||
import getLinuxDoNotDisturb from './dnd-linux';
|
||||
import getWindowsDoNotDisturb from './dnd-windows';
|
||||
|
||||
const log = new Logger('Notifications');
|
||||
|
||||
class NotificationManager {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
import {dialog} from 'electron';
|
||||
|
||||
import {parseURL, isTrustedURL} from 'common/utils/url';
|
||||
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
@@ -1,9 +1,10 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
import type {
|
||||
PermissionRequestHandlerHandlerDetails,
|
||||
WebContents,
|
||||
WebContents} from 'electron';
|
||||
import {
|
||||
app,
|
||||
dialog,
|
||||
ipcMain,
|
||||
@@ -12,9 +13,8 @@ import {
|
||||
import {UPDATE_PATHS} from 'common/communication';
|
||||
import JsonFileManager from 'common/JsonFileManager';
|
||||
import {Logger} from 'common/log';
|
||||
import {t} from 'common/utils/util';
|
||||
import {isTrustedURL, parseURL} from 'common/utils/url';
|
||||
|
||||
import {t} from 'common/utils/util';
|
||||
import {permissionsJson} from 'main/constants';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
@@ -69,7 +69,7 @@ export class PermissionsManager extends JsonFileManager<Permissions> {
|
||||
permission,
|
||||
details.securityOrigin ?? details.requestingUrl,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
doPermissionRequest = async (
|
||||
webContentsId: number,
|
||||
@@ -172,7 +172,7 @@ export class PermissionsManager extends JsonFileManager<Permissions> {
|
||||
|
||||
// We've checked everything so we're okay to grant the remaining cases
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
t('main.permissionsManager.checkPermission.dialog.message.media');
|
||||
|
@@ -1,11 +1,10 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {IpcRendererEvent, contextBridge, ipcRenderer, webFrame} from 'electron';
|
||||
import type {IpcRendererEvent} from 'electron';
|
||||
import {contextBridge, ipcRenderer, webFrame} from 'electron';
|
||||
|
||||
import {ExternalAPI} from 'types/externalAPI';
|
||||
|
||||
import {DesktopAPI} from '@mattermost/desktop-api';
|
||||
import type {DesktopAPI} from '@mattermost/desktop-api';
|
||||
|
||||
import {
|
||||
NOTIFY_MENTION,
|
||||
@@ -42,6 +41,8 @@ import {
|
||||
LEGACY_OFF,
|
||||
} from 'common/communication';
|
||||
|
||||
import type {ExternalAPI} from 'types/externalAPI';
|
||||
|
||||
const createListener: ExternalAPI['createListener'] = (channel: string, listener: (...args: never[]) => void) => {
|
||||
const listenerWithEvent = (_: IpcRendererEvent, ...args: unknown[]) =>
|
||||
listener(...args as never[]);
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ClientConfig, RemoteInfo} from 'types/server';
|
||||
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import type {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {parseURL} from 'common/utils/url';
|
||||
|
||||
import type {ClientConfig, RemoteInfo} from 'types/server';
|
||||
|
||||
import {getServerAPI} from './serverAPI';
|
||||
|
||||
export class ServerInfo {
|
||||
@@ -24,7 +24,7 @@ export class ServerInfo {
|
||||
);
|
||||
|
||||
return this.remoteInfo;
|
||||
}
|
||||
};
|
||||
|
||||
fetchRemoteInfo = async () => {
|
||||
await this.fetchConfigData();
|
||||
@@ -34,7 +34,7 @@ export class ServerInfo {
|
||||
);
|
||||
|
||||
return this.remoteInfo;
|
||||
}
|
||||
};
|
||||
|
||||
private getRemoteInfo = <T>(
|
||||
callback: (data: T) => void,
|
||||
@@ -54,17 +54,17 @@ export class ServerInfo {
|
||||
() => reject(new Error('Aborted')),
|
||||
(error: Error) => reject(error));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onGetConfig = (data: ClientConfig) => {
|
||||
this.remoteInfo.serverVersion = data.Version;
|
||||
this.remoteInfo.siteURL = data.SiteURL;
|
||||
this.remoteInfo.siteName = data.SiteName;
|
||||
this.remoteInfo.hasFocalboard = this.remoteInfo.hasFocalboard || data.BuildBoards === 'true';
|
||||
}
|
||||
};
|
||||
|
||||
private onGetPlugins = (data: Array<{id: string; version: string}>) => {
|
||||
this.remoteInfo.hasFocalboard = this.remoteInfo.hasFocalboard || data.some((plugin) => plugin.id === 'focalboard');
|
||||
this.remoteInfo.hasPlaybooks = data.some((plugin) => plugin.id === 'playbooks');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {handleConfigUpdate} from 'main/app/config';
|
||||
|
||||
import AutoLauncher from 'main/AutoLauncher';
|
||||
|
||||
import Tray from './tray';
|
||||
|
@@ -8,7 +8,6 @@ import {app, nativeImage, Tray, systemPreferences, nativeTheme} from 'electron';
|
||||
import AppState from 'common/appState';
|
||||
import {UPDATE_APPSTATE_TOTALS} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
@@ -44,7 +43,7 @@ export class TrayIcon {
|
||||
this.tray.on('click', this.onClick);
|
||||
this.tray.on('right-click', () => this.tray?.popUpContextMenu());
|
||||
this.tray.on('balloon-click', this.onClick);
|
||||
}
|
||||
};
|
||||
|
||||
refreshImages = (trayIconTheme: string) => {
|
||||
const systemTheme = nativeTheme.shouldUseDarkColors ? 'light' : 'dark';
|
||||
@@ -98,13 +97,13 @@ export class TrayIcon {
|
||||
this.update(this.status, this.message);
|
||||
}
|
||||
return this.images;
|
||||
}
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
if (process.platform === 'win32') {
|
||||
this.tray?.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setMenu = (tMenu: Electron.Menu) => this.tray?.setContextMenu(tMenu);
|
||||
|
||||
@@ -117,7 +116,7 @@ export class TrayIcon {
|
||||
this.message = message;
|
||||
this.tray.setImage(this.images[status]);
|
||||
this.tray.setToolTip(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Linux note: the click event was fixed in Electron v23, but only fires when the OS supports StatusIconLinuxDbus
|
||||
// There is a fallback case that will make sure the icon is displayed, but will only support the context menu
|
||||
@@ -163,7 +162,7 @@ export class TrayIcon {
|
||||
} else {
|
||||
this.update('normal', app.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const tray = new TrayIcon();
|
||||
|
@@ -2,8 +2,8 @@
|
||||
// See LICENSE.txt for license information.
|
||||
'use strict';
|
||||
|
||||
import {TrustedOriginsStore} from 'main/trustedOrigins';
|
||||
import {BASIC_AUTH_PERMISSION} from 'common/permissions';
|
||||
import {TrustedOriginsStore} from 'main/trustedOrigins';
|
||||
|
||||
jest.mock('path', () => ({
|
||||
resolve: jest.fn(),
|
||||
|
@@ -7,12 +7,12 @@ import fs from 'fs';
|
||||
|
||||
import {ipcMain} from 'electron';
|
||||
|
||||
import {TrustedOrigin, PermissionType} from 'types/trustedOrigin';
|
||||
|
||||
import {UPDATE_PATHS} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import type {TrustedOrigin, PermissionType} from 'types/trustedOrigin';
|
||||
|
||||
import {trustedOriginsStoreFile} from './constants';
|
||||
|
||||
const log = new Logger('TrustedOriginsStore');
|
||||
@@ -34,7 +34,7 @@ export class TrustedOriginsStore {
|
||||
storeData = null;
|
||||
}
|
||||
return storeData;
|
||||
}
|
||||
};
|
||||
|
||||
load = () => {
|
||||
const storeData = this.readFromFile();
|
||||
@@ -46,7 +46,7 @@ export class TrustedOriginsStore {
|
||||
}
|
||||
}
|
||||
this.data = new Map(Object.entries(result));
|
||||
}
|
||||
};
|
||||
|
||||
// don't use this, is for ease of mocking it on testing
|
||||
saveToFile(stringMap: string) {
|
||||
@@ -77,11 +77,11 @@ export class TrustedOriginsStore {
|
||||
// enables usage of `targetURL` for `permission`
|
||||
addPermission = (targetURL: URL, permission: PermissionType) => {
|
||||
this.set(targetURL, {[permission]: true});
|
||||
}
|
||||
};
|
||||
|
||||
delete = (targetURL: URL) => {
|
||||
return this.data?.delete(targetURL.origin);
|
||||
}
|
||||
};
|
||||
|
||||
isExisting = (targetURL: URL) => {
|
||||
return this.data?.has(targetURL.origin) || false;
|
||||
@@ -95,7 +95,7 @@ export class TrustedOriginsStore {
|
||||
|
||||
const urlPermissions = this.data?.get(targetURL.origin);
|
||||
return urlPermissions ? urlPermissions[permission] : undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const trustedOriginsStore = new TrustedOriginsStore(trustedOriginsStoreFile);
|
||||
|
@@ -2,21 +2,20 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import {exec as execOriginal} from 'child_process';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {promisify} from 'util';
|
||||
const exec = promisify(execOriginal);
|
||||
|
||||
import {app, BrowserWindow} from 'electron';
|
||||
|
||||
import {Args} from 'types/args';
|
||||
import type {BrowserWindow} from 'electron';
|
||||
import {app} from 'electron';
|
||||
|
||||
import {BACK_BAR_HEIGHT, customLoginRegexPaths, PRODUCTION, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
import Utils from 'common/utils/util';
|
||||
import {isAdminUrl, isPluginUrl, isTeamUrl, isUrlType, parseURL} from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
|
||||
import type {Args} from 'types/args';
|
||||
|
||||
export function isInsideRectangle(container: Electron.Rectangle, rect: Electron.Rectangle) {
|
||||
return container.x <= rect.x && container.y <= rect.y && container.width >= rect.width && container.height >= rect.height;
|
||||
|
@@ -8,11 +8,11 @@ import {LOAD_FAILED, TOGGLE_BACK_BUTTON, UPDATE_TARGET_URL} from 'common/communi
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import MessagingView from 'common/views/MessagingView';
|
||||
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import Utils from '../utils';
|
||||
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {
|
||||
|
@@ -2,11 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, app, ipcMain} from 'electron';
|
||||
import {BrowserViewConstructorOptions, Event, Input} from 'electron/main';
|
||||
|
||||
import type {BrowserViewConstructorOptions, Event, Input} from 'electron/main';
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import {RELOAD_INTERVAL, MAX_SERVER_RETRIES, SECOND, MAX_LOADING_SCREEN_SECONDS} from 'common/utils/constants';
|
||||
import AppState from 'common/appState';
|
||||
import {
|
||||
LOAD_RETRY,
|
||||
@@ -21,18 +19,18 @@ import {
|
||||
CLOSE_SERVERS_DROPDOWN,
|
||||
CLOSE_DOWNLOADS_DROPDOWN,
|
||||
} from 'common/communication';
|
||||
import type {Logger} from 'common/log';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {Logger} from 'common/log';
|
||||
import {RELOAD_INTERVAL, MAX_SERVER_RETRIES, SECOND, MAX_LOADING_SCREEN_SECONDS} from 'common/utils/constants';
|
||||
import {isInternalURL, parseURL} from 'common/utils/url';
|
||||
import {MattermostView} from 'common/views/View';
|
||||
|
||||
import type {MattermostView} from 'common/views/View';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import WebContentsEventManager from './webContentEvents';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import {getWindowBoundaries, getLocalPreload, composeUserAgent, shouldHaveBackBar} from '../utils';
|
||||
|
||||
import WebContentsEventManager from './webContentEvents';
|
||||
|
||||
enum Status {
|
||||
LOADING,
|
||||
READY,
|
||||
@@ -52,7 +50,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
private loggedIn: boolean;
|
||||
private atRoot: boolean;
|
||||
private options: BrowserViewConstructorOptions;
|
||||
private removeLoading?: number;
|
||||
private removeLoading?: NodeJS.Timeout;
|
||||
private contextMenu: ContextMenu;
|
||||
private status?: Status;
|
||||
private retryLoad?: NodeJS.Timeout;
|
||||
@@ -142,7 +140,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
goToOffset = (offset: number) => {
|
||||
if (this.browserView.webContents.canGoToOffset(offset)) {
|
||||
@@ -154,7 +152,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getBrowserHistoryStatus = () => {
|
||||
if (this.currentURL?.toString() === this.view.url.toString()) {
|
||||
@@ -168,12 +166,12 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
canGoBack: this.browserView.webContents.canGoBack(),
|
||||
canGoForward: this.browserView.webContents.canGoForward(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
updateHistoryButton = () => {
|
||||
const {canGoBack, canGoForward} = this.getBrowserHistoryStatus();
|
||||
this.browserView.webContents.send(BROWSER_HISTORY_STATUS_UPDATED, canGoBack, canGoForward);
|
||||
}
|
||||
};
|
||||
|
||||
load = (someURL?: URL | string) => {
|
||||
if (!this.browserView) {
|
||||
@@ -208,7 +206,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
}
|
||||
this.loadRetry(loadURL, err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
show = () => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -228,32 +226,32 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
if (this.status === Status.READY) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hide = () => {
|
||||
if (this.isVisible) {
|
||||
this.isVisible = false;
|
||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reload = () => {
|
||||
this.resetLoadingStatus();
|
||||
AppState.updateExpired(this.id, false);
|
||||
this.load();
|
||||
}
|
||||
};
|
||||
|
||||
getBounds = () => {
|
||||
return this.browserView.getBounds();
|
||||
}
|
||||
};
|
||||
|
||||
openFind = () => {
|
||||
this.browserView.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
}
|
||||
};
|
||||
|
||||
setBounds = (boundaries: Electron.Rectangle) => {
|
||||
this.browserView.setBounds(boundaries);
|
||||
}
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
||||
@@ -273,7 +271,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
if (this.removeLoading) {
|
||||
clearTimeout(this.removeLoading);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Code to turn off the old method of getting unreads
|
||||
@@ -282,7 +280,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
offLegacyUnreads = () => {
|
||||
this.browserView.webContents.off('page-title-updated', this.handleTitleUpdate);
|
||||
this.browserView.webContents.off('page-favicon-updated', this.handleFaviconUpdate);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Status hooks
|
||||
@@ -294,19 +292,19 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
this.status = Status.LOADING;
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
isReady = () => {
|
||||
return this.status === Status.READY;
|
||||
}
|
||||
};
|
||||
|
||||
isErrored = () => {
|
||||
return this.status === Status.ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
needsLoadingScreen = () => {
|
||||
return !(this.status === Status.READY || this.status === Status.ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
setInitialized = (timedout?: boolean) => {
|
||||
this.status = Status.READY;
|
||||
@@ -317,7 +315,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
}
|
||||
clearTimeout(this.removeLoading);
|
||||
delete this.removeLoading;
|
||||
}
|
||||
};
|
||||
|
||||
openDevTools = () => {
|
||||
// Workaround for a bug with our Dev Tools on Mac
|
||||
@@ -336,7 +334,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
}
|
||||
|
||||
this.browserView.webContents.openDevTools({mode: 'detach'});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* WebContents hooks
|
||||
@@ -344,11 +342,11 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
|
||||
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||
this.browserView.webContents.send(channel, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
isDestroyed = () => {
|
||||
return this.browserView.webContents.isDestroyed();
|
||||
}
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
if (this.browserView.webContents) {
|
||||
@@ -356,7 +354,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
} 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)
|
||||
@@ -386,7 +384,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
if (this.isAltKeyReleased(input)) {
|
||||
MainWindow.focusThreeDotMenu();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unreads/mentions handlers
|
||||
@@ -398,7 +396,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
const mentions = (results && results.value && parseInt(results.value[MENTIONS_GROUP], 10)) || 0;
|
||||
|
||||
AppState.updateMentions(this.id, mentions);
|
||||
}
|
||||
};
|
||||
|
||||
// if favicon is null, it will affect appState, but won't be memoized
|
||||
private findUnreadState = (favicon: string | null) => {
|
||||
@@ -407,13 +405,13 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
} 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);
|
||||
@@ -421,7 +419,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
// 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
|
||||
@@ -446,7 +444,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private retryInBackground = (loadURL: string) => {
|
||||
return () => {
|
||||
@@ -459,13 +457,13 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private loadRetry = (loadURL: string, err: Error) => {
|
||||
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
|
||||
MainWindow.sendToRenderer(LOAD_RETRY, this.id, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());
|
||||
this.log.info(`failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`);
|
||||
}
|
||||
};
|
||||
|
||||
private loadSuccess = (loadURL: string) => {
|
||||
return () => {
|
||||
@@ -484,7 +482,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* WebContents event handlers
|
||||
@@ -511,7 +509,7 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, false);
|
||||
this.log.debug('hide back button');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleUpdateTarget = (e: Event, url: string) => {
|
||||
this.log.silly('handleUpdateTarget', e, url);
|
||||
@@ -521,11 +519,11 @@ export class MattermostBrowserView extends EventEmitter {
|
||||
} else {
|
||||
this.emit(UPDATE_TARGET_URL, url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleServerWasModified = (serverIds: string) => {
|
||||
if (serverIds.includes(this.view.server.id)) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@
|
||||
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
|
||||
import {DOWNLOADS_DROPDOWN_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {DownloadsDropdownMenuView} from './downloadsDropdownMenuView';
|
||||
|
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {BrowserView, ipcMain, IpcMainEvent} from 'electron';
|
||||
|
||||
import {CoordinatesToJsonType, DownloadedItem, DownloadsMenuOpenEventPayload} from 'types/downloads';
|
||||
import type {IpcMainEvent} from 'electron';
|
||||
import {BrowserView, ipcMain} from 'electron';
|
||||
|
||||
import {
|
||||
CLOSE_DOWNLOADS_DROPDOWN_MENU,
|
||||
@@ -19,18 +18,20 @@ import {
|
||||
UPDATE_DOWNLOADS_DROPDOWN_MENU,
|
||||
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
||||
} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import {
|
||||
DOWNLOADS_DROPDOWN_FULL_WIDTH,
|
||||
DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT,
|
||||
DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH,
|
||||
TAB_BAR_HEIGHT,
|
||||
} from 'common/utils/constants';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import type {CoordinatesToJsonType, DownloadedItem, DownloadsMenuOpenEventPayload} from 'types/downloads';
|
||||
|
||||
const log = new Logger('DownloadsDropdownMenuView');
|
||||
|
||||
export class DownloadsDropdownMenuView {
|
||||
@@ -76,7 +77,7 @@ export class DownloadsDropdownMenuView {
|
||||
}});
|
||||
this.view.webContents.loadURL(getLocalURLString('downloadsDropdownMenu.html'));
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is called every time the "window" is resized so that we can position
|
||||
@@ -88,14 +89,14 @@ export class DownloadsDropdownMenuView {
|
||||
this.windowBounds = newBounds;
|
||||
this.updateDownloadsDropdownMenu();
|
||||
this.repositionDownloadsDropdownMenu();
|
||||
}
|
||||
};
|
||||
|
||||
private updateItem = (event: IpcMainEvent, item: DownloadedItem) => {
|
||||
log.debug('updateItem', {item});
|
||||
|
||||
this.item = item;
|
||||
this.updateDownloadsDropdownMenu();
|
||||
}
|
||||
};
|
||||
|
||||
private updateDownloadsDropdownMenu = () => {
|
||||
log.silly('updateDownloadsDropdownMenu');
|
||||
@@ -107,7 +108,7 @@ export class DownloadsDropdownMenuView {
|
||||
);
|
||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM, true, this.item);
|
||||
this.repositionDownloadsDropdownMenu();
|
||||
}
|
||||
};
|
||||
|
||||
private handleOpen = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload = {} as DownloadsMenuOpenEventPayload) => {
|
||||
log.debug('handleOpen', {bounds: this.bounds, payload});
|
||||
@@ -128,7 +129,7 @@ export class DownloadsDropdownMenuView {
|
||||
MainWindow.get()?.setTopBrowserView(this.view);
|
||||
this.view.webContents.focus();
|
||||
this.updateDownloadsDropdownMenu();
|
||||
}
|
||||
};
|
||||
|
||||
private handleClose = () => {
|
||||
log.silly('handleClose');
|
||||
@@ -138,7 +139,7 @@ export class DownloadsDropdownMenuView {
|
||||
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM);
|
||||
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
|
||||
MainWindow.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN_MENU);
|
||||
}
|
||||
};
|
||||
|
||||
private handleToggle = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload) => {
|
||||
if (this.open) {
|
||||
@@ -153,27 +154,27 @@ export class DownloadsDropdownMenuView {
|
||||
} else {
|
||||
this.handleOpen(event, payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private openFile = () => {
|
||||
downloadsManager.openFile(this.item);
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private showFileInFolder = (e: IpcMainEvent, item: DownloadedItem) => {
|
||||
downloadsManager.showFileInFolder(item);
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private clearFile = () => {
|
||||
downloadsManager.clearFile(this.item);
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private cancelDownload = () => {
|
||||
downloadsManager.cancelDownload(this.item);
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private getBounds = (windowWidth: number, width: number, height: number) => {
|
||||
// MUST return integers
|
||||
@@ -183,7 +184,7 @@ export class DownloadsDropdownMenuView {
|
||||
width: Math.round(width),
|
||||
height: Math.round(height),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private getX = (windowWidth: number) => {
|
||||
const result = (windowWidth - DOWNLOADS_DROPDOWN_FULL_WIDTH - DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH) + (this.coordinates?.x || 0) + (this.coordinates?.width || 0);
|
||||
@@ -191,12 +192,12 @@ export class DownloadsDropdownMenuView {
|
||||
return 0;
|
||||
}
|
||||
return Math.round(result);
|
||||
}
|
||||
};
|
||||
|
||||
private getY = () => {
|
||||
const result = TAB_BAR_HEIGHT + (this.coordinates?.y || 0) + (this.coordinates?.height || 0);
|
||||
return Math.round(result);
|
||||
}
|
||||
};
|
||||
|
||||
private repositionDownloadsDropdownMenu = () => {
|
||||
if (!this.windowBounds) {
|
||||
@@ -207,7 +208,7 @@ export class DownloadsDropdownMenuView {
|
||||
if (this.open) {
|
||||
this.view?.setBounds(this.bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const downloadsDropdownMenuView = new DownloadsDropdownMenuView();
|
||||
|
@@ -6,7 +6,6 @@
|
||||
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
|
||||
|
||||
import {DOWNLOADS_DROPDOWN_FULL_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {DownloadsDropdownView} from './downloadsDropdownView';
|
||||
|
@@ -1,9 +1,8 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
|
||||
import {DownloadedItem} from 'types/downloads';
|
||||
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserView, ipcMain} from 'electron';
|
||||
|
||||
import {
|
||||
CLOSE_DOWNLOADS_DROPDOWN,
|
||||
@@ -19,14 +18,15 @@ import {
|
||||
MAIN_WINDOW_CREATED,
|
||||
MAIN_WINDOW_RESIZED,
|
||||
} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import {TAB_BAR_HEIGHT, DOWNLOADS_DROPDOWN_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, DOWNLOADS_DROPDOWN_FULL_WIDTH} from 'common/utils/constants';
|
||||
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import downloadsManager from 'main/downloadsManager';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import type {DownloadedItem} from 'types/downloads';
|
||||
|
||||
const log = new Logger('DownloadsDropdownView');
|
||||
|
||||
export class DownloadsDropdownView {
|
||||
@@ -70,7 +70,7 @@ export class DownloadsDropdownView {
|
||||
this.view.webContents.loadURL(getLocalURLString('downloadsDropdown.html'));
|
||||
this.view.webContents.session.webRequest.onHeadersReceived(downloadsManager.webRequestOnHeadersReceivedHandler);
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is called every time the "window" is resized so that we can position
|
||||
@@ -82,13 +82,13 @@ export class DownloadsDropdownView {
|
||||
this.windowBounds = newBounds;
|
||||
this.updateDownloadsDropdown();
|
||||
this.repositionDownloadsDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
private updateDownloadsDropdownMenuItem = (event: IpcMainEvent, item?: DownloadedItem) => {
|
||||
log.silly('updateDownloadsDropdownMenuItem', {item});
|
||||
this.item = item;
|
||||
this.updateDownloadsDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
private updateDownloadsDropdown = () => {
|
||||
log.silly('updateDownloadsDropdown');
|
||||
@@ -100,7 +100,7 @@ export class DownloadsDropdownView {
|
||||
MainWindow.getBounds(),
|
||||
this.item,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private handleOpen = () => {
|
||||
log.debug('handleOpen', {bounds: this.bounds});
|
||||
@@ -114,7 +114,7 @@ export class DownloadsDropdownView {
|
||||
this.view.webContents.focus();
|
||||
downloadsManager.onOpen();
|
||||
MainWindow.sendToRenderer(OPEN_DOWNLOADS_DROPDOWN);
|
||||
}
|
||||
};
|
||||
|
||||
private handleClose = () => {
|
||||
log.silly('handleClose');
|
||||
@@ -122,18 +122,18 @@ export class DownloadsDropdownView {
|
||||
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
|
||||
downloadsManager.onClose();
|
||||
MainWindow.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN);
|
||||
}
|
||||
};
|
||||
|
||||
private clearDownloads = () => {
|
||||
downloadsManager.clearDownloadsDropDown();
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
private openFile = (e: IpcMainEvent, item: DownloadedItem) => {
|
||||
log.debug('openFile', {item});
|
||||
|
||||
downloadsManager.openFile(item);
|
||||
}
|
||||
};
|
||||
|
||||
private getBounds = (windowWidth: number, width: number, height: number) => {
|
||||
// Must always use integers
|
||||
@@ -143,7 +143,7 @@ export class DownloadsDropdownView {
|
||||
width: Math.round(width),
|
||||
height: Math.round(height),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private getX = (windowWidth: number) => {
|
||||
const result = windowWidth - DOWNLOADS_DROPDOWN_FULL_WIDTH;
|
||||
@@ -151,11 +151,11 @@ export class DownloadsDropdownView {
|
||||
return 0;
|
||||
}
|
||||
return Math.round(result);
|
||||
}
|
||||
};
|
||||
|
||||
private getY = () => {
|
||||
return Math.round(TAB_BAR_HEIGHT);
|
||||
}
|
||||
};
|
||||
|
||||
private repositionDownloadsDropdown = () => {
|
||||
if (!(this.bounds && this.windowBounds)) {
|
||||
@@ -169,7 +169,7 @@ export class DownloadsDropdownView {
|
||||
if (downloadsManager.getIsOpen()) {
|
||||
this.view?.setBounds(this.bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleReceivedDownloadsDropdownSize = (event: IpcMainEvent, width: number, height: number) => {
|
||||
log.silly('handleReceivedDownloadsDropdownSize', {width, height});
|
||||
@@ -182,11 +182,11 @@ export class DownloadsDropdownView {
|
||||
if (downloadsManager.getIsOpen()) {
|
||||
this.view?.setBounds(this.bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private getDownloadImageThumbnailLocation = (event: IpcMainInvokeEvent, location: string) => {
|
||||
return location;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const downloadsDropdownView = new DownloadsDropdownView();
|
||||
|
@@ -5,7 +5,6 @@ import {BrowserView, app, ipcMain} from 'electron';
|
||||
|
||||
import {DARK_MODE_CHANGE, LOADING_SCREEN_ANIMATION_FINISHED, MAIN_WINDOW_RESIZED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {getLocalPreload, getLocalURLString, getWindowBoundaries} from 'main/utils';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
@@ -34,11 +33,11 @@ export class LoadingScreen {
|
||||
|
||||
setDarkMode = (darkMode: boolean) => {
|
||||
this.view?.webContents.send(DARK_MODE_CHANGE, darkMode);
|
||||
}
|
||||
};
|
||||
|
||||
isHidden = () => {
|
||||
return this.state === LoadingScreenState.HIDDEN;
|
||||
}
|
||||
};
|
||||
|
||||
show = () => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -67,14 +66,14 @@ export class LoadingScreen {
|
||||
}
|
||||
|
||||
this.setBounds();
|
||||
}
|
||||
};
|
||||
|
||||
fade = () => {
|
||||
if (this.view && this.state === LoadingScreenState.VISIBLE) {
|
||||
this.state = LoadingScreenState.FADING;
|
||||
this.view.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private create = () => {
|
||||
const preload = getLocalPreload('internalAPI.js');
|
||||
@@ -88,7 +87,7 @@ export class LoadingScreen {
|
||||
}});
|
||||
const localURL = getLocalURLString('loadingScreen.html');
|
||||
this.view.webContents.loadURL(localURL);
|
||||
}
|
||||
};
|
||||
|
||||
private handleAnimationFinished = () => {
|
||||
log.debug('handleLoadingScreenAnimationFinished');
|
||||
@@ -101,7 +100,7 @@ export class LoadingScreen {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
app.emit('e2e-app-loaded');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private setBounds = () => {
|
||||
if (this.view) {
|
||||
@@ -111,7 +110,7 @@ export class LoadingScreen {
|
||||
}
|
||||
this.view.setBounds(getWindowBoundaries(mainWindow));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const loadingScreen = new LoadingScreen();
|
||||
|
@@ -1,10 +1,9 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserWindow, ipcMain} from 'electron';
|
||||
import {IpcMainEvent, IpcMainInvokeEvent} from 'electron/main';
|
||||
|
||||
import {CombinedConfig} from 'types/config';
|
||||
import type {BrowserWindow} from 'electron';
|
||||
import {ipcMain} from 'electron';
|
||||
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron/main';
|
||||
|
||||
import {
|
||||
RETRIEVE_MODAL_INFO,
|
||||
@@ -18,11 +17,12 @@ import {
|
||||
MAIN_WINDOW_RESIZED,
|
||||
} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import {getAdjustedWindowBoundaries} from 'main/utils';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import WebContentsEventManager from 'main/views/webContentEvents';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import WebContentsEventManager from 'main/views/webContentEvents';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import type {CombinedConfig} from 'types/config';
|
||||
|
||||
import {ModalView} from './modalView';
|
||||
|
||||
@@ -62,7 +62,7 @@ export class ModalManager {
|
||||
return modalPromise;
|
||||
}
|
||||
return this.modalPromises.get(key) as Promise<T2>;
|
||||
}
|
||||
};
|
||||
|
||||
findModalByCaller = (event: IpcMainInvokeEvent) => {
|
||||
if (this.modalQueue.length) {
|
||||
@@ -72,7 +72,7 @@ export class ModalManager {
|
||||
return requestModal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
handleInfoRequest = (event: IpcMainInvokeEvent) => {
|
||||
log.debug('handleInfoRequest');
|
||||
@@ -82,7 +82,7 @@ export class ModalManager {
|
||||
return requestModal.handleInfoRequest();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
showModal = () => {
|
||||
const withDevTools = process.env.MM_DEBUG_MODALS || false;
|
||||
@@ -96,7 +96,7 @@ export class ModalManager {
|
||||
modal.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleModalFinished = (mode: 'resolve' | 'reject', event: IpcMainEvent, data: unknown) => {
|
||||
log.debug('handleModalFinished', {mode, data});
|
||||
@@ -117,7 +117,7 @@ export class ModalManager {
|
||||
MainWindow.sendToRenderer(MODAL_CLOSE);
|
||||
ViewManager.focusCurrentView();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleModalResult = (event: IpcMainEvent, data: unknown) => this.handleModalFinished('resolve', event, data);
|
||||
|
||||
@@ -125,11 +125,11 @@ export class ModalManager {
|
||||
|
||||
filterActive = () => {
|
||||
this.modalQueue = this.modalQueue.filter((modal) => modal.isActive());
|
||||
}
|
||||
};
|
||||
|
||||
isModalDisplayed = () => {
|
||||
return this.modalQueue.some((modal) => modal.isActive());
|
||||
}
|
||||
};
|
||||
|
||||
handleResizeModal = (bounds: Electron.Rectangle) => {
|
||||
log.debug('handleResizeModal', {bounds, modalQueueLength: this.modalQueue.length});
|
||||
@@ -138,13 +138,13 @@ export class ModalManager {
|
||||
const currentModal = this.modalQueue[0];
|
||||
currentModal.view.setBounds(getAdjustedWindowBoundaries(bounds.width, bounds.height));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
focusCurrentModal = () => {
|
||||
if (this.isModalDisplayed()) {
|
||||
this.modalQueue[0].view.webContents.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleEmitConfiguration = (event: IpcMainEvent, config: CombinedConfig) => {
|
||||
if (this.modalQueue.length) {
|
||||
@@ -154,12 +154,12 @@ export class ModalManager {
|
||||
this.modalQueue.forEach((modal) => {
|
||||
modal.view.webContents.send(DARK_MODE_CHANGE, config.darkMode);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleGetModalUncloseable = (event: IpcMainInvokeEvent) => {
|
||||
const modalView = this.modalQueue.find((modal) => modal.view.webContents.id === event.sender.id);
|
||||
return modalView?.uncloseable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const modalManager = new ModalManager();
|
||||
|
@@ -1,7 +1,8 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, BrowserWindow} from 'electron';
|
||||
import type {BrowserWindow} from 'electron';
|
||||
import {BrowserView} from 'electron';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
@@ -90,7 +91,7 @@ export class ModalView<T, T2> {
|
||||
this.log.info(`showing dev tools for ${this.key}`);
|
||||
this.view.webContents.openDevTools({mode: 'detach'});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hide = () => {
|
||||
if (this.windowAttached) {
|
||||
@@ -108,11 +109,11 @@ export class ModalView<T, T2> {
|
||||
delete this.windowAttached;
|
||||
this.status = Status.ACTIVE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleInfoRequest = () => {
|
||||
return this.data;
|
||||
}
|
||||
};
|
||||
|
||||
reject = (data: T2) => {
|
||||
if (this.onReject) {
|
||||
@@ -120,7 +121,7 @@ export class ModalView<T, T2> {
|
||||
}
|
||||
this.hide();
|
||||
this.status = Status.DONE;
|
||||
}
|
||||
};
|
||||
|
||||
resolve = (data: T2) => {
|
||||
if (this.onResolve) {
|
||||
@@ -128,7 +129,7 @@ export class ModalView<T, T2> {
|
||||
}
|
||||
this.hide();
|
||||
this.status = Status.DONE;
|
||||
}
|
||||
};
|
||||
|
||||
isActive = () => this.status !== Status.DONE;
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
'use strict';
|
||||
|
||||
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHADOW_WIDTH} from 'common/utils/constants';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {ServerDropdownView} from './serverDropdownView';
|
||||
|
@@ -1,12 +1,10 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, ipcMain, IpcMainEvent} from 'electron';
|
||||
|
||||
import {UniqueServer} from 'types/config';
|
||||
import type {IpcMainEvent} from 'electron';
|
||||
import {BrowserView, ipcMain} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import AppState from 'common/appState';
|
||||
import {
|
||||
CLOSE_SERVERS_DROPDOWN,
|
||||
@@ -22,11 +20,12 @@ import {
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHADOW_WIDTH} from 'common/utils/constants';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
|
||||
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHADOW_WIDTH} from 'common/utils/constants';
|
||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
|
||||
import type {UniqueServer} from 'types/config';
|
||||
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
|
||||
const log = new Logger('ServerDropdownView');
|
||||
@@ -71,7 +70,7 @@ export class ServerDropdownView {
|
||||
private updateWindowBounds = (newBounds: Electron.Rectangle) => {
|
||||
this.windowBounds = newBounds;
|
||||
this.updateDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
private init = () => {
|
||||
log.info('init');
|
||||
@@ -90,7 +89,7 @@ export class ServerDropdownView {
|
||||
this.windowBounds = MainWindow.getBounds();
|
||||
this.updateDropdown();
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
}
|
||||
};
|
||||
|
||||
private updateDropdown = () => {
|
||||
log.silly('updateDropdown');
|
||||
@@ -107,12 +106,12 @@ export class ServerDropdownView {
|
||||
this.mentions,
|
||||
this.unreads,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private updateServers = () => {
|
||||
this.setOrderedServers();
|
||||
this.updateDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
private updateMentions = (expired: Map<string, boolean>, mentions: Map<string, number>, unreads: Map<string, boolean>) => {
|
||||
log.silly('updateMentions', {expired, mentions, unreads});
|
||||
@@ -121,7 +120,7 @@ export class ServerDropdownView {
|
||||
this.mentions = this.reduceNotifications(this.mentions, mentions, (base, value) => (base ?? 0) + (value ?? 0));
|
||||
this.expired = this.reduceNotifications(this.expired, expired, (base, value) => base || value || false);
|
||||
this.updateDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Menu open/close/size handlers
|
||||
@@ -141,7 +140,7 @@ export class ServerDropdownView {
|
||||
this.view.webContents.focus();
|
||||
MainWindow.sendToRenderer(OPEN_SERVERS_DROPDOWN);
|
||||
this.isOpen = true;
|
||||
}
|
||||
};
|
||||
|
||||
private handleClose = () => {
|
||||
log.silly('handleClose');
|
||||
@@ -149,7 +148,7 @@ export class ServerDropdownView {
|
||||
this.view?.setBounds(this.getBounds(0, 0));
|
||||
MainWindow.sendToRenderer(CLOSE_SERVERS_DROPDOWN);
|
||||
this.isOpen = false;
|
||||
}
|
||||
};
|
||||
|
||||
private handleReceivedMenuSize = (event: IpcMainEvent, width: number, height: number) => {
|
||||
log.silly('handleReceivedMenuSize', {width, height});
|
||||
@@ -158,7 +157,7 @@ export class ServerDropdownView {
|
||||
if (this.isOpen) {
|
||||
this.view?.setBounds(this.bounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
@@ -171,7 +170,7 @@ export class ServerDropdownView {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private reduceNotifications = <T>(inputMap: Map<string, T>, items: Map<string, T>, modifier: (base?: T, value?: T) => T) => {
|
||||
inputMap.clear();
|
||||
@@ -183,12 +182,12 @@ export class ServerDropdownView {
|
||||
map.set(view.server.id, modifier(map.get(view.server.id), items.get(key)));
|
||||
return map;
|
||||
}, inputMap);
|
||||
}
|
||||
};
|
||||
|
||||
private setOrderedServers = () => {
|
||||
this.servers = ServerManager.getOrderedServers().map((server) => server.toUniqueServer());
|
||||
this.hasGPOServers = this.servers.some((srv) => srv.isPredefined);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const serverDropdownView = new ServerDropdownView();
|
||||
|
@@ -7,17 +7,15 @@
|
||||
import {dialog} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, SET_ACTIVE_VIEW} from 'common/communication';
|
||||
import {TAB_MESSAGING} from 'common/views/View';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import urlUtils from 'common/utils/url';
|
||||
|
||||
import {TAB_MESSAGING} from 'common/views/View';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import LoadingScreen from './loadingScreen';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import {ViewManager} from './viewManager';
|
||||
import LoadingScreen from './loadingScreen';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {
|
||||
|
@@ -1,13 +1,12 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserView, dialog, ipcMain, IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import type {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserView, dialog, ipcMain} from 'electron';
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import AppState from 'common/appState';
|
||||
import {SECOND, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
import {
|
||||
UPDATE_TARGET_URL,
|
||||
LOAD_SUCCESS,
|
||||
@@ -37,20 +36,21 @@ import {
|
||||
} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
import Utils from 'common/utils/util';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import type {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {MattermostView, TAB_MESSAGING} from 'common/views/View';
|
||||
import {SECOND, TAB_BAR_HEIGHT} from 'common/utils/constants';
|
||||
import {getFormattedPathName, parseURL} from 'common/utils/url';
|
||||
|
||||
import Utils from 'common/utils/util';
|
||||
import type {MattermostView} from 'common/views/View';
|
||||
import {TAB_MESSAGING} from 'common/views/View';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {getLocalURLString, getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
|
||||
|
||||
import LoadingScreen from './loadingScreen';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import modalManager from './modalManager';
|
||||
import LoadingScreen from './loadingScreen';
|
||||
|
||||
import {getLocalURLString, getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
|
||||
|
||||
const log = new Logger('ViewManager');
|
||||
const URL_VIEW_DURATION = 10 * SECOND;
|
||||
@@ -93,26 +93,26 @@ export class ViewManager {
|
||||
LoadingScreen.show();
|
||||
ServerManager.getAllServers().forEach((server) => this.loadServer(server));
|
||||
this.showInitial();
|
||||
}
|
||||
};
|
||||
|
||||
getView = (viewId: string) => {
|
||||
return this.views.get(viewId);
|
||||
}
|
||||
};
|
||||
|
||||
getCurrentView = () => {
|
||||
if (this.currentView) {
|
||||
return this.views.get(this.currentView);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
getViewByWebContentsId = (webContentsId: number) => {
|
||||
return [...this.views.values()].find((view) => view.webContentsId === webContentsId);
|
||||
}
|
||||
};
|
||||
|
||||
isViewClosed = (viewId: string) => {
|
||||
return this.closedViews.has(viewId);
|
||||
}
|
||||
};
|
||||
|
||||
showById = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('showById', viewId);
|
||||
@@ -144,7 +144,7 @@ export class ViewManager {
|
||||
this.getViewLogger(viewId).warn(`Couldn't find a view with name: ${viewId}`);
|
||||
}
|
||||
modalManager.showModal();
|
||||
}
|
||||
};
|
||||
|
||||
focusCurrentView = () => {
|
||||
log.debug('focusCurrentView');
|
||||
@@ -158,7 +158,7 @@ export class ViewManager {
|
||||
if (view) {
|
||||
view.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reload = () => {
|
||||
const currentView = this.getCurrentView();
|
||||
@@ -166,7 +166,7 @@ export class ViewManager {
|
||||
LoadingScreen.show();
|
||||
currentView.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sendToAllViews = (channel: string, ...args: unknown[]) => {
|
||||
this.views.forEach((view) => {
|
||||
@@ -174,11 +174,11 @@ export class ViewManager {
|
||||
view.sendToRenderer(channel, ...args);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sendToFind = () => {
|
||||
this.getCurrentView()?.openFind();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deep linking
|
||||
@@ -231,7 +231,7 @@ export class ViewManager {
|
||||
private deeplinkFailed = (viewId: string, err: string, url: string) => {
|
||||
this.getViewLogger(viewId).error(`failed to load deeplink ${url}`, err);
|
||||
this.views.get(viewId)?.removeListener(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* View loading helpers
|
||||
@@ -240,7 +240,7 @@ export class ViewManager {
|
||||
private loadServer = (server: MattermostServer) => {
|
||||
const views = ServerManager.getOrderedTabsForServer(server.id);
|
||||
views.forEach((view) => this.loadView(server, view));
|
||||
}
|
||||
};
|
||||
|
||||
private loadView = (srv: MattermostServer, view: MattermostView, url?: string) => {
|
||||
if (!view.isOpen) {
|
||||
@@ -249,7 +249,7 @@ export class ViewManager {
|
||||
}
|
||||
const browserView = this.makeView(srv, view, url);
|
||||
this.addView(browserView);
|
||||
}
|
||||
};
|
||||
|
||||
private makeView = (srv: MattermostServer, view: MattermostView, url?: string): MattermostBrowserView => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -264,14 +264,14 @@ export class ViewManager {
|
||||
browserView.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
browserView.load(url);
|
||||
return browserView;
|
||||
}
|
||||
};
|
||||
|
||||
private addView = (view: MattermostBrowserView): void => {
|
||||
this.views.set(view.id, view);
|
||||
if (this.closedViews.has(view.id)) {
|
||||
this.closedViews.delete(view.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private showInitial = () => {
|
||||
log.verbose('showInitial');
|
||||
@@ -285,7 +285,7 @@ export class ViewManager {
|
||||
} else {
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mattermost view event handlers
|
||||
@@ -297,7 +297,7 @@ export class ViewManager {
|
||||
if (this.currentView === viewId) {
|
||||
this.showById(this.currentView);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private finishLoading = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('finishLoading');
|
||||
@@ -306,7 +306,7 @@ export class ViewManager {
|
||||
this.showById(this.currentView);
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private failLoading = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('failLoading');
|
||||
@@ -315,7 +315,7 @@ export class ViewManager {
|
||||
if (this.currentView === viewId) {
|
||||
this.getCurrentView()?.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private showURLView = (url: URL | string) => {
|
||||
log.silly('showURLView', url);
|
||||
@@ -390,7 +390,7 @@ export class ViewManager {
|
||||
hideView();
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Event Handlers
|
||||
@@ -468,19 +468,19 @@ export class ViewManager {
|
||||
} else {
|
||||
this.showInitial();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleHistory = (event: IpcMainEvent, offset: number) => {
|
||||
this.getCurrentView()?.goToOffset(offset);
|
||||
}
|
||||
};
|
||||
|
||||
private handleAppLoggedIn = (event: IpcMainEvent) => {
|
||||
this.getViewByWebContentsId(event.sender.id)?.onLogin(true);
|
||||
}
|
||||
};
|
||||
|
||||
private handleAppLoggedOut = (event: IpcMainEvent) => {
|
||||
this.getViewByWebContentsId(event.sender.id)?.onLogin(false);
|
||||
}
|
||||
};
|
||||
|
||||
private handleBrowserHistoryPush = (e: IpcMainEvent, pathName: string) => {
|
||||
log.debug('handleBrowserHistoryPush', e.sender.id, pathName);
|
||||
@@ -512,13 +512,13 @@ export class ViewManager {
|
||||
redirectedView?.sendToRenderer(BROWSER_HISTORY_PUSH, cleanedPathName);
|
||||
redirectedView?.updateHistoryButton();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleRequestBrowserHistoryStatus = (e: IpcMainInvokeEvent) => {
|
||||
log.silly('handleRequestBrowserHistoryStatus', e.sender.id);
|
||||
|
||||
return this.getViewByWebContentsId(e.sender.id)?.getBrowserHistoryStatus();
|
||||
}
|
||||
};
|
||||
|
||||
private handleReactAppInitialized = (e: IpcMainEvent) => {
|
||||
log.debug('handleReactAppInitialized', e.sender.id);
|
||||
@@ -530,7 +530,7 @@ export class ViewManager {
|
||||
LoadingScreen.fade();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleReloadCurrentView = () => {
|
||||
log.debug('handleReloadCurrentView');
|
||||
@@ -541,7 +541,7 @@ export class ViewManager {
|
||||
}
|
||||
view?.reload();
|
||||
this.showById(view?.id);
|
||||
}
|
||||
};
|
||||
|
||||
private handleLegacyOff = (e: IpcMainEvent) => {
|
||||
log.silly('handleLegacyOff', {webContentsId: e.sender.id});
|
||||
@@ -551,7 +551,7 @@ export class ViewManager {
|
||||
return;
|
||||
}
|
||||
view.offLegacyUnreads();
|
||||
}
|
||||
};
|
||||
|
||||
// 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.
|
||||
@@ -563,7 +563,7 @@ export class ViewManager {
|
||||
return;
|
||||
}
|
||||
AppState.updateUnreads(view.id, result);
|
||||
}
|
||||
};
|
||||
|
||||
private handleUnreadsAndMentionsChanged = (e: IpcMainEvent, isUnread: boolean, mentionCount: number) => {
|
||||
log.silly('handleUnreadsAndMentionsChanged', {webContentsId: e.sender.id, isUnread, mentionCount});
|
||||
@@ -574,7 +574,7 @@ export class ViewManager {
|
||||
}
|
||||
AppState.updateUnreads(view.id, isUnread);
|
||||
AppState.updateMentions(view.id, mentionCount);
|
||||
}
|
||||
};
|
||||
|
||||
private handleSessionExpired = (event: IpcMainEvent, isExpired: boolean) => {
|
||||
const view = this.getViewByWebContentsId(event.sender.id);
|
||||
@@ -584,7 +584,7 @@ export class ViewManager {
|
||||
ServerManager.getViewLog(view.id, 'ViewManager').debug('handleSessionExpired', isExpired);
|
||||
|
||||
AppState.updateExpired(view.id, isExpired);
|
||||
}
|
||||
};
|
||||
|
||||
private handleSetCurrentViewBounds = (newBounds: Electron.Rectangle) => {
|
||||
log.debug('handleSetCurrentViewBounds', newBounds);
|
||||
@@ -594,7 +594,7 @@ export class ViewManager {
|
||||
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.view.url, currentView.currentURL));
|
||||
currentView.setBounds(adjustedBounds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper functions
|
||||
@@ -615,11 +615,11 @@ export class ViewManager {
|
||||
this.showById(id);
|
||||
});
|
||||
ipcMain.emit(OPEN_VIEW, null, view.id);
|
||||
}
|
||||
};
|
||||
|
||||
private getViewLogger = (viewId: string) => {
|
||||
return ServerManager.getViewLog(viewId, 'ViewManager');
|
||||
}
|
||||
};
|
||||
|
||||
private handleGetViewInfoForTest = (event: IpcMainInvokeEvent) => {
|
||||
const view = this.getViewByWebContentsId(event.sender.id);
|
||||
@@ -632,7 +632,7 @@ export class ViewManager {
|
||||
serverName: view.view.server.name,
|
||||
viewType: view.view.type,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const viewManager = new ViewManager();
|
||||
|
@@ -6,14 +6,13 @@
|
||||
import {shell, BrowserWindow} from 'electron';
|
||||
|
||||
import {getLevel} from 'common/log';
|
||||
|
||||
import ContextMenu from 'main/contextMenu';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import allowProtocolDialog from '../allowProtocolDialog';
|
||||
|
||||
import {WebContentsEventManager} from './webContentEvents';
|
||||
|
||||
import allowProtocolDialog from '../allowProtocolDialog';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {},
|
||||
shell: {
|
||||
|
@@ -3,7 +3,8 @@
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import {BrowserWindow, session, shell, WebContents, Event} from 'electron';
|
||||
import type {WebContents, Event} from 'electron';
|
||||
import {BrowserWindow, session, shell} from 'electron';
|
||||
|
||||
import Config from 'common/config';
|
||||
import {Logger, getLevel} from 'common/log';
|
||||
@@ -25,16 +26,13 @@ import {
|
||||
isValidURI,
|
||||
parseURL,
|
||||
} from 'common/utils/url';
|
||||
|
||||
import {flushCookiesStore} from 'main/app/utils';
|
||||
import ContextMenu from 'main/contextMenu';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {protocols} from '../../../electron-builder.json';
|
||||
|
||||
import allowProtocolDialog from '../allowProtocolDialog';
|
||||
import {composeUserAgent} from '../utils';
|
||||
|
||||
@@ -73,14 +71,14 @@ export class WebContentsEventManager {
|
||||
}
|
||||
|
||||
return ServerManager.getViewLog(view.id, 'WebContentsEventManager');
|
||||
}
|
||||
};
|
||||
|
||||
private isTrustedPopupWindow = (webContentsId: number) => {
|
||||
if (!this.popupWindow) {
|
||||
return false;
|
||||
}
|
||||
return webContentsId === this.popupWindow.win.webContents.id;
|
||||
}
|
||||
};
|
||||
|
||||
private getServerURLFromWebContentsId = (webContentsId: number) => {
|
||||
if (this.popupWindow && webContentsId === this.popupWindow.win.webContents.id) {
|
||||
@@ -92,7 +90,7 @@ export class WebContentsEventManager {
|
||||
}
|
||||
|
||||
return ViewManager.getViewByWebContentsId(webContentsId)?.view.server.url;
|
||||
}
|
||||
};
|
||||
|
||||
private generateWillNavigate = (webContentsId: number) => {
|
||||
return (event: Event, url: string) => {
|
||||
@@ -320,7 +318,7 @@ export class WebContentsEventManager {
|
||||
}
|
||||
|
||||
logFn(...entries);
|
||||
}
|
||||
};
|
||||
|
||||
removeWebContentsListeners = (id: number) => {
|
||||
if (this.listeners[id]) {
|
||||
|
@@ -6,7 +6,6 @@
|
||||
import {BrowserWindow, desktopCapturer, systemPreferences, ipcMain} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import {CALLS_WIDGET_SHARE_SCREEN, BROWSER_HISTORY_PUSH, UPDATE_SHORTCUT_MENU} from 'common/communication';
|
||||
import {
|
||||
MINIMUM_CALLS_WIDGET_WIDTH,
|
||||
@@ -14,14 +13,13 @@ import {
|
||||
CALLS_PLUGIN_ID,
|
||||
} from 'common/utils/constants';
|
||||
import urlUtils from 'common/utils/url';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import {
|
||||
resetScreensharePermissionsMacOS,
|
||||
openScreensharePermissionsSettingsMacOS,
|
||||
} from 'main/utils';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import WebContentsEventManager from 'main/views/webContentEvents';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {CallsWidgetWindow} from './callsWidgetWindow';
|
||||
|
||||
@@ -830,7 +828,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
expect(ViewManager.handleDeepLink).toHaveBeenCalledWith(new URL(url));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('isOpen', () => {
|
||||
const callsWidgetWindow = new CallsWidgetWindow();
|
||||
|
||||
|
@@ -1,19 +1,10 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {BrowserWindow, desktopCapturer, ipcMain, IpcMainEvent, Rectangle, systemPreferences, Event, IpcMainInvokeEvent} from 'electron';
|
||||
|
||||
import {
|
||||
CallsJoinCallMessage,
|
||||
CallsWidgetWindowConfig,
|
||||
} from 'types/calls';
|
||||
import type {IpcMainEvent, Rectangle, Event, IpcMainInvokeEvent} from 'electron';
|
||||
import {BrowserWindow, desktopCapturer, ipcMain, systemPreferences} from 'electron';
|
||||
|
||||
import ServerViewState from 'app/serverViewState';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import {CALLS_PLUGIN_ID, MINIMUM_CALLS_WIDGET_HEIGHT, MINIMUM_CALLS_WIDGET_WIDTH} from 'common/utils/constants';
|
||||
import Utils from 'common/utils/util';
|
||||
import {getFormattedPathName, isCallsPopOutURL, parseURL} from 'common/utils/url';
|
||||
import {
|
||||
BROWSER_HISTORY_PUSH,
|
||||
CALLS_ERROR,
|
||||
@@ -30,17 +21,25 @@ import {
|
||||
GET_DESKTOP_SOURCES,
|
||||
UPDATE_SHORTCUT_MENU,
|
||||
} from 'common/communication';
|
||||
|
||||
import {MattermostBrowserView} from 'main/views/MattermostBrowserView';
|
||||
import {Logger} from 'common/log';
|
||||
import {CALLS_PLUGIN_ID, MINIMUM_CALLS_WIDGET_HEIGHT, MINIMUM_CALLS_WIDGET_WIDTH} from 'common/utils/constants';
|
||||
import {getFormattedPathName, isCallsPopOutURL, parseURL} from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
import {
|
||||
composeUserAgent,
|
||||
getLocalPreload,
|
||||
openScreensharePermissionsSettingsMacOS,
|
||||
resetScreensharePermissionsMacOS,
|
||||
} from 'main/utils';
|
||||
import type {MattermostBrowserView} from 'main/views/MattermostBrowserView';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import webContentsEventManager from 'main/views/webContentEvents';
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import type {
|
||||
CallsJoinCallMessage,
|
||||
CallsWidgetWindowConfig,
|
||||
} from 'types/calls';
|
||||
|
||||
const log = new Logger('CallsWidgetWindow');
|
||||
|
||||
@@ -98,15 +97,15 @@ export class CallsWidgetWindow {
|
||||
|
||||
public openDevTools = () => {
|
||||
this.win?.webContents.openDevTools({mode: 'detach'});
|
||||
}
|
||||
};
|
||||
|
||||
getViewURL = () => {
|
||||
return this.mainView?.view.server.url;
|
||||
}
|
||||
};
|
||||
|
||||
isCallsWidget = (webContentsId: number) => {
|
||||
return webContentsId === this.win?.webContents.id || webContentsId === this.popOut?.webContents.id;
|
||||
}
|
||||
};
|
||||
|
||||
private getWidgetURL = () => {
|
||||
if (!this.mainView) {
|
||||
@@ -128,7 +127,7 @@ export class CallsWidgetWindow {
|
||||
}
|
||||
|
||||
return u.toString();
|
||||
}
|
||||
};
|
||||
|
||||
private init = (view: MattermostBrowserView, options: CallsWidgetWindowConfig) => {
|
||||
this.win = new BrowserWindow({
|
||||
@@ -170,7 +169,7 @@ export class CallsWidgetWindow {
|
||||
}).catch((reason) => {
|
||||
log.error(`failed to load: ${reason}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private close = async () => {
|
||||
log.debug('close');
|
||||
@@ -189,7 +188,7 @@ export class CallsWidgetWindow {
|
||||
this.win?.on('closed', resolve);
|
||||
this.win?.close();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private setBounds(bounds: Rectangle) {
|
||||
if (!this.win) {
|
||||
@@ -216,7 +215,7 @@ export class CallsWidgetWindow {
|
||||
delete this.win;
|
||||
delete this.mainView;
|
||||
delete this.options;
|
||||
}
|
||||
};
|
||||
|
||||
private onNavigate = (ev: Event, url: string) => {
|
||||
if (url === this.getWidgetURL()) {
|
||||
@@ -224,7 +223,7 @@ export class CallsWidgetWindow {
|
||||
}
|
||||
log.warn(`prevented widget window from navigating to: ${url}`);
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
private onShow = () => {
|
||||
log.debug('onShow');
|
||||
@@ -254,7 +253,7 @@ export class CallsWidgetWindow {
|
||||
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
||||
|
||||
this.setBounds(initialBounds);
|
||||
}
|
||||
};
|
||||
|
||||
private onPopOutOpen = ({url}: { url: string }) => {
|
||||
if (!(this.mainView && this.options)) {
|
||||
@@ -276,7 +275,7 @@ export class CallsWidgetWindow {
|
||||
|
||||
log.warn(`onPopOutOpen: prevented window open to ${url}`);
|
||||
return {action: 'deny' as const};
|
||||
}
|
||||
};
|
||||
|
||||
private onPopOutCreate = (win: BrowserWindow) => {
|
||||
this.popOut = win;
|
||||
@@ -312,7 +311,7 @@ export class CallsWidgetWindow {
|
||||
log.error('did-frame-finish-load, failed to reload with correct userAgent', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/************************
|
||||
* IPC HANDLERS
|
||||
@@ -340,7 +339,7 @@ export class CallsWidgetWindow {
|
||||
};
|
||||
|
||||
this.setBounds(newBounds);
|
||||
}
|
||||
};
|
||||
|
||||
private handleShareScreen = (ev: IpcMainEvent, sourceID: string, withAudio: boolean) => {
|
||||
log.debug('handleShareScreen', {sourceID, withAudio});
|
||||
@@ -351,7 +350,7 @@ export class CallsWidgetWindow {
|
||||
}
|
||||
|
||||
this.win?.webContents.send(CALLS_WIDGET_SHARE_SCREEN, sourceID, withAudio);
|
||||
}
|
||||
};
|
||||
|
||||
private handlePopOutFocus = () => {
|
||||
if (!this.popOut) {
|
||||
@@ -361,7 +360,7 @@ export class CallsWidgetWindow {
|
||||
this.popOut.restore();
|
||||
}
|
||||
this.popOut.focus();
|
||||
}
|
||||
};
|
||||
|
||||
private handleGetDesktopSources = async (event: IpcMainInvokeEvent, opts: Electron.SourcesOptions) => {
|
||||
log.debug('handleGetDesktopSources', opts);
|
||||
@@ -431,7 +430,7 @@ export class CallsWidgetWindow {
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private handleCreateCallsWidgetWindow = async (event: IpcMainInvokeEvent, msg: CallsJoinCallMessage) => {
|
||||
log.debug('createCallsWidgetWindow');
|
||||
@@ -479,13 +478,13 @@ export class CallsWidgetWindow {
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
};
|
||||
|
||||
private handleCallsLeave = () => {
|
||||
log.debug('handleCallsLeave');
|
||||
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
private forwardToMainApp = (channel: string) => {
|
||||
return (event: IpcMainEvent, ...args: any) => {
|
||||
@@ -503,7 +502,7 @@ export class CallsWidgetWindow {
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(channel, ...args);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private handleCallsLinkClick = (event: IpcMainEvent, url: string) => {
|
||||
log.debug('handleCallsLinkClick', url);
|
||||
@@ -528,7 +527,7 @@ export class CallsWidgetWindow {
|
||||
ServerViewState.switchServer(this.serverID);
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, url);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
@@ -547,7 +546,7 @@ export class CallsWidgetWindow {
|
||||
ServerViewState.switchServer(this.serverID);
|
||||
MainWindow.get()?.focus();
|
||||
this.mainView?.sendToRenderer(BROWSER_HISTORY_PUSH, this.options?.channelURL);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const callsWidgetWindow = new CallsWidgetWindow();
|
||||
|
@@ -2,7 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import {BrowserWindow, screen, app, globalShortcut, dialog} from 'electron';
|
||||
@@ -12,11 +11,11 @@ import Config from 'common/config';
|
||||
import {DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH} from 'common/utils/constants';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import {MainWindow} from './mainWindow';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import {isInsideRectangle} from '../utils';
|
||||
|
||||
import {MainWindow} from './mainWindow';
|
||||
|
||||
jest.mock('path', () => ({
|
||||
join: jest.fn(),
|
||||
resolve: jest.fn(),
|
||||
@@ -54,10 +53,6 @@ jest.mock('common/utils/util', () => ({
|
||||
isVersionGreaterThanOrEqualTo: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('global', () => ({
|
||||
willAppQuit: false,
|
||||
}));
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
readFileSync: jest.fn(),
|
||||
writeFileSync: jest.fn(),
|
||||
@@ -79,8 +74,6 @@ jest.mock('main/i18nManager', () => ({
|
||||
localizeMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('main/windows/mainWindow', () => {
|
||||
describe('init', () => {
|
||||
const baseWindow = {
|
||||
|
@@ -2,17 +2,13 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import os from 'os';
|
||||
|
||||
import type {BrowserWindowConstructorOptions, Event, Input} from 'electron';
|
||||
import {app, BrowserWindow, dialog, globalShortcut, ipcMain, screen} from 'electron';
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import {app, BrowserWindow, BrowserWindowConstructorOptions, dialog, Event, globalShortcut, Input, ipcMain, screen} from 'electron';
|
||||
|
||||
import {SavedWindowState} from 'types/mainWindow';
|
||||
|
||||
import AppState from 'common/appState';
|
||||
import {
|
||||
SELECT_NEXT_TAB,
|
||||
@@ -35,10 +31,11 @@ import ServerManager from 'common/servers/serverManager';
|
||||
import {DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MINIMUM_WINDOW_HEIGHT, MINIMUM_WINDOW_WIDTH, SECOND} from 'common/utils/constants';
|
||||
import Utils from 'common/utils/util';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
import {boundsInfoPath} from 'main/constants';
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
|
||||
import type {SavedWindowState} from 'types/mainWindow';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import {getLocalPreload, getLocalURLString, isInsideRectangle} from '../utils';
|
||||
|
||||
@@ -51,7 +48,7 @@ export class MainWindow extends EventEmitter {
|
||||
private savedWindowState?: SavedWindowState;
|
||||
private ready: boolean;
|
||||
private isResizing: boolean;
|
||||
private lastEmittedBounds?: Electron.Rectangle
|
||||
private lastEmittedBounds?: Electron.Rectangle;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -158,7 +155,7 @@ export class MainWindow extends EventEmitter {
|
||||
});
|
||||
|
||||
this.emit(MAIN_WINDOW_CREATED);
|
||||
}
|
||||
};
|
||||
|
||||
get isReady() {
|
||||
return this.ready;
|
||||
@@ -166,7 +163,7 @@ export class MainWindow extends EventEmitter {
|
||||
|
||||
get = () => {
|
||||
return this.win;
|
||||
}
|
||||
};
|
||||
|
||||
show = () => {
|
||||
if (this.win && this.isReady) {
|
||||
@@ -186,7 +183,7 @@ export class MainWindow extends EventEmitter {
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getBounds = (): Electron.Rectangle | undefined => {
|
||||
if (!this.win) {
|
||||
@@ -202,18 +199,18 @@ export class MainWindow extends EventEmitter {
|
||||
}
|
||||
|
||||
return this.win.getContentBounds();
|
||||
}
|
||||
};
|
||||
|
||||
focusThreeDotMenu = () => {
|
||||
if (this.win) {
|
||||
this.win.webContents.focus();
|
||||
this.win.webContents.send(FOCUS_THREE_DOT_MENU);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sendToRenderer = (channel: string, ...args: unknown[]) => {
|
||||
this.sendToRendererWithRetry(3, channel, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
private sendToRendererWithRetry = (maxRetries: number, channel: string, ...args: unknown[]) => {
|
||||
if (!this.win || !this.isReady) {
|
||||
@@ -228,7 +225,7 @@ export class MainWindow extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this.win.webContents.send(channel, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
private shouldStartFullScreen = () => {
|
||||
if (global?.args?.fullscreen !== undefined) {
|
||||
@@ -239,11 +236,11 @@ export class MainWindow extends EventEmitter {
|
||||
return Config.startInFullscreen;
|
||||
}
|
||||
return this.savedWindowState?.fullscreen || false;
|
||||
}
|
||||
};
|
||||
|
||||
private isFramelessWindow = () => {
|
||||
return os.platform() === 'darwin' || (os.platform() === 'win32' && Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.2'));
|
||||
}
|
||||
};
|
||||
|
||||
private getSavedWindowState = () => {
|
||||
let savedWindowState: any;
|
||||
@@ -264,7 +261,7 @@ export class MainWindow extends EventEmitter {
|
||||
savedWindowState = {width: DEFAULT_WINDOW_WIDTH, height: DEFAULT_WINDOW_HEIGHT};
|
||||
}
|
||||
return savedWindowState;
|
||||
}
|
||||
};
|
||||
|
||||
private saveWindowState = (file: string, window: BrowserWindow) => {
|
||||
const windowState: SavedWindowState = {
|
||||
@@ -278,7 +275,7 @@ export class MainWindow extends EventEmitter {
|
||||
// [Linux] error happens only when the window state is changed before the config dir is created.
|
||||
log.error('failed to save window state', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onBeforeInputEvent = (event: Event, input: Input) => {
|
||||
// Register keyboard shortcuts
|
||||
@@ -293,7 +290,7 @@ export class MainWindow extends EventEmitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onFocus = () => {
|
||||
// Only add shortcuts when window is in focus
|
||||
@@ -305,7 +302,7 @@ export class MainWindow extends EventEmitter {
|
||||
|
||||
this.emit(MAIN_WINDOW_RESIZED, this.getBounds());
|
||||
this.emit(MAIN_WINDOW_FOCUSED);
|
||||
}
|
||||
};
|
||||
|
||||
private onBlur = () => {
|
||||
if (!this.win) {
|
||||
@@ -323,7 +320,7 @@ export class MainWindow extends EventEmitter {
|
||||
// 'blur' event was effective in order to avoid this.
|
||||
// Ideally, app should detect that OS is shutting down.
|
||||
this.saveWindowState(boundsInfoPath, this.win);
|
||||
}
|
||||
};
|
||||
|
||||
private onClose = (event: Event) => {
|
||||
log.debug('onClose');
|
||||
@@ -394,13 +391,13 @@ export class MainWindow extends EventEmitter {
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onClosed = () => {
|
||||
log.verbose('main window closed');
|
||||
delete this.win;
|
||||
this.ready = false;
|
||||
}
|
||||
};
|
||||
|
||||
private onUnresponsive = () => {
|
||||
if (!this.win) {
|
||||
@@ -421,7 +418,7 @@ export class MainWindow extends EventEmitter {
|
||||
app.relaunch();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private emitBounds = (bounds?: Electron.Rectangle, force?: boolean) => {
|
||||
// Workaround since the window bounds aren't updated immediately when the window is maximized for some reason
|
||||
@@ -438,27 +435,27 @@ export class MainWindow extends EventEmitter {
|
||||
this.emit(MAIN_WINDOW_RESIZED, newBounds);
|
||||
this.lastEmittedBounds = newBounds;
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
|
||||
private onMaximize = () => {
|
||||
this.win?.webContents.send(MAXIMIZE_CHANGE, true);
|
||||
this.emitBounds();
|
||||
}
|
||||
};
|
||||
|
||||
private onUnmaximize = () => {
|
||||
this.win?.webContents.send(MAXIMIZE_CHANGE, false);
|
||||
this.emitBounds();
|
||||
}
|
||||
};
|
||||
|
||||
private onEnterFullScreen = () => {
|
||||
this.win?.webContents.send('enter-full-screen');
|
||||
this.emitBounds();
|
||||
}
|
||||
};
|
||||
|
||||
private onLeaveFullScreen = () => {
|
||||
this.win?.webContents.send('leave-full-screen');
|
||||
this.emitBounds();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizing code
|
||||
@@ -487,7 +484,7 @@ export class MainWindow extends EventEmitter {
|
||||
|
||||
this.isResizing = true;
|
||||
this.emitBounds(newBounds);
|
||||
}
|
||||
};
|
||||
|
||||
private onResize = () => {
|
||||
log.silly('onResize');
|
||||
@@ -497,7 +494,7 @@ export class MainWindow extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this.emitBounds();
|
||||
}
|
||||
};
|
||||
|
||||
private onResized = () => {
|
||||
log.debug('onResized');
|
||||
@@ -505,18 +502,18 @@ export class MainWindow extends EventEmitter {
|
||||
// Because this is the final window state after a resize, we force the size here
|
||||
this.emitBounds(this.getBounds(), true);
|
||||
this.isResizing = false;
|
||||
}
|
||||
};
|
||||
|
||||
private handleViewFinishedResizing = () => {
|
||||
this.isResizing = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Server Manager update handler
|
||||
*/
|
||||
private handleUpdateConfig = () => {
|
||||
this.win?.webContents.send(SERVERS_UPDATE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* App State update handler
|
||||
@@ -524,7 +521,7 @@ export class MainWindow extends EventEmitter {
|
||||
|
||||
private handleUpdateAppStateForViewId = (viewId: string, isExpired: boolean, newMentions: number, newUnreads: boolean) => {
|
||||
this.win?.webContents.send(UPDATE_MENTIONS, viewId, newMentions, newUnreads, isExpired);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mainWindow = new MainWindow();
|
||||
|
@@ -7,11 +7,11 @@ import {SHOW_SETTINGS_WINDOW} from 'common/communication';
|
||||
import Config from 'common/config';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import MainWindow from './mainWindow';
|
||||
|
||||
import ContextMenu from '../contextMenu';
|
||||
import {getLocalPreload, getLocalURLString} from '../utils';
|
||||
|
||||
import MainWindow from './mainWindow';
|
||||
|
||||
const log = new Logger('SettingsWindow');
|
||||
|
||||
export class SettingsWindow {
|
||||
@@ -27,15 +27,15 @@ export class SettingsWindow {
|
||||
} else {
|
||||
this.create();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
get = () => {
|
||||
return this.win;
|
||||
}
|
||||
};
|
||||
|
||||
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||
this.win?.webContents.send(channel, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
private create = () => {
|
||||
const mainWindow = MainWindow.get();
|
||||
@@ -77,7 +77,7 @@ export class SettingsWindow {
|
||||
// Adding this arbitrary delay seems to get rid of it (it happens very frequently)
|
||||
setTimeout(() => MainWindow.get()?.focus(), 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const settingsWindow = new SettingsWindow();
|
||||
|
Reference in New Issue
Block a user