Refactor config, move ipc calls to app module, some cleanup (#2669)
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
|
|
||||||
import {Args} from 'types/args';
|
import {Args} from 'types/args';
|
||||||
import {AnyConfig, ConfigV0, ConfigV1, ConfigV2, ConfigV3, TeamWithTabs} from 'types/config';
|
import {AnyConfig, ConfigV0, ConfigV1, ConfigV2, ConfigV3, ConfigServer} from 'types/config';
|
||||||
import {DownloadedItems} from 'types/downloads';
|
import {DownloadedItems} from 'types/downloads';
|
||||||
import {SavedWindowState} from 'types/mainWindow';
|
import {SavedWindowState} from 'types/mainWindow';
|
||||||
import {AppState} from 'types/appState';
|
import {AppState} from 'types/appState';
|
||||||
@@ -213,7 +213,7 @@ function cleanTeam<T extends {name: string; url: string}>(team: T) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanTeamWithTabs(team: TeamWithTabs) {
|
function cleanTeamWithTabs(team: ConfigServer) {
|
||||||
return {
|
return {
|
||||||
...cleanTeam(team),
|
...cleanTeam(team),
|
||||||
tabs: team.tabs.map((tab) => {
|
tabs: team.tabs.map((tab) => {
|
||||||
|
@@ -67,7 +67,6 @@ describe('common/config/RegistryConfig', () => {
|
|||||||
expect(registryConfig.data.teams).toContainEqual({
|
expect(registryConfig.data.teams).toContainEqual({
|
||||||
name: 'server-1',
|
name: 'server-1',
|
||||||
url: 'http://server-1.com',
|
url: 'http://server-1.com',
|
||||||
order: 0,
|
|
||||||
});
|
});
|
||||||
expect(registryConfig.data.enableAutoUpdater).toBe(true);
|
expect(registryConfig.data.enableAutoUpdater).toBe(true);
|
||||||
expect(registryConfig.data.enableServerManagement).toBe(true);
|
expect(registryConfig.data.enableServerManagement).toBe(true);
|
||||||
|
@@ -6,7 +6,7 @@ import {EventEmitter} from 'events';
|
|||||||
import WindowsRegistry from 'winreg';
|
import WindowsRegistry from 'winreg';
|
||||||
import WindowsRegistryUTF8 from 'winreg-utf8';
|
import WindowsRegistryUTF8 from 'winreg-utf8';
|
||||||
|
|
||||||
import {RegistryConfig as RegistryConfigType, FullTeam} from 'types/config';
|
import {RegistryConfig as RegistryConfigType, Team} from 'types/config';
|
||||||
|
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
@@ -78,12 +78,11 @@ export default class RegistryConfig extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async getServersListFromRegistry() {
|
async getServersListFromRegistry() {
|
||||||
const defaultServers = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`);
|
const defaultServers = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`);
|
||||||
return defaultServers.flat(2).reduce((servers: FullTeam[], server, index) => {
|
return defaultServers.flat(2).reduce((servers: Team[], server) => {
|
||||||
if (server) {
|
if (server) {
|
||||||
servers.push({
|
servers.push({
|
||||||
name: (server as WindowsRegistry.RegistryItem).name,
|
name: (server as WindowsRegistry.RegistryItem).name,
|
||||||
url: (server as WindowsRegistry.RegistryItem).value,
|
url: (server as WindowsRegistry.RegistryItem).value,
|
||||||
order: index,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return servers;
|
return servers;
|
||||||
|
@@ -1,22 +1,19 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
import {Config} from 'common/config';
|
import {Config} from 'common/config';
|
||||||
|
|
||||||
const configPath = '/fake/config/path';
|
const configPath = '/fake/config/path';
|
||||||
|
const appName = 'app-name';
|
||||||
|
const appPath = '/my/app/path';
|
||||||
|
|
||||||
jest.mock('electron', () => ({
|
jest.mock('fs', () => ({
|
||||||
app: {
|
readFileSync: jest.fn(),
|
||||||
name: 'Mattermost',
|
writeFileSync: jest.fn(),
|
||||||
getPath: jest.fn(),
|
existsSync: jest.fn(),
|
||||||
getAppPath: () => '/path/to/app',
|
mkdirSync: jest.fn(),
|
||||||
},
|
|
||||||
ipcMain: {
|
|
||||||
on: jest.fn(),
|
|
||||||
},
|
|
||||||
nativeTheme: {
|
|
||||||
shouldUseDarkColors: false,
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('common/Validator', () => ({
|
jest.mock('common/Validator', () => ({
|
||||||
@@ -28,7 +25,7 @@ jest.mock('common/Validator', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('common/tabs/TabView', () => ({
|
jest.mock('common/tabs/TabView', () => ({
|
||||||
getDefaultTeamWithTabsFromTeam: (value) => ({
|
getDefaultConfigTeamFromTeam: (value) => ({
|
||||||
...value,
|
...value,
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
@@ -99,15 +96,18 @@ jest.mock('common/config/RegistryConfig', () => {
|
|||||||
|
|
||||||
describe('common/config', () => {
|
describe('common/config', () => {
|
||||||
it('should load buildConfig', () => {
|
it('should load buildConfig', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
expect(config.predefinedTeams).toContainEqual(buildTeamWithTabs);
|
expect(config.predefinedTeams).toContainEqual(buildTeamWithTabs);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadRegistry', () => {
|
describe('loadRegistry', () => {
|
||||||
it('should load the registry items and reload the config', () => {
|
it('should load the registry items and reload the config', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
config.reload = jest.fn();
|
config.reload = jest.fn();
|
||||||
config.loadRegistry({teams: [registryTeam]});
|
config.init(configPath, appName, appPath);
|
||||||
|
config.onLoadRegistry({teams: [registryTeam]});
|
||||||
expect(config.reload).toHaveBeenCalled();
|
expect(config.reload).toHaveBeenCalled();
|
||||||
expect(config.predefinedTeams).toContainEqual({
|
expect(config.predefinedTeams).toContainEqual({
|
||||||
...registryTeam,
|
...registryTeam,
|
||||||
@@ -125,7 +125,8 @@ describe('common/config', () => {
|
|||||||
|
|
||||||
describe('reload', () => {
|
describe('reload', () => {
|
||||||
it('should emit update event', () => {
|
it('should emit update event', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.loadDefaultConfigData = jest.fn();
|
config.loadDefaultConfigData = jest.fn();
|
||||||
config.loadBuildConfigData = jest.fn();
|
config.loadBuildConfigData = jest.fn();
|
||||||
config.loadLocalConfigFile = jest.fn();
|
config.loadLocalConfigFile = jest.fn();
|
||||||
@@ -134,6 +135,7 @@ describe('common/config', () => {
|
|||||||
config.combinedData = {test: 'test'};
|
config.combinedData = {test: 'test'};
|
||||||
});
|
});
|
||||||
config.emit = jest.fn();
|
config.emit = jest.fn();
|
||||||
|
fs.existsSync.mockReturnValue(true);
|
||||||
|
|
||||||
config.reload();
|
config.reload();
|
||||||
expect(config.emit).toHaveBeenNthCalledWith(1, 'update', {test: 'test'});
|
expect(config.emit).toHaveBeenNthCalledWith(1, 'update', {test: 'test'});
|
||||||
@@ -142,7 +144,9 @@ describe('common/config', () => {
|
|||||||
|
|
||||||
describe('set', () => {
|
describe('set', () => {
|
||||||
it('should set an arbitrary value and save to local config data', () => {
|
it('should set an arbitrary value and save to local config data', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {};
|
config.localConfigData = {};
|
||||||
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
|
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
@@ -155,24 +159,46 @@ describe('common/config', () => {
|
|||||||
expect(config.saveLocalConfigData).toHaveBeenCalled();
|
expect(config.saveLocalConfigData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set teams without including predefined', () => {
|
it('should not allow teams to be set using this method', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
|
config.localConfigData = {teams: [team]};
|
||||||
|
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
|
||||||
|
config.combinedData = {...config.localConfigData};
|
||||||
|
});
|
||||||
|
config.saveLocalConfigData = jest.fn();
|
||||||
|
|
||||||
|
config.set('teams', [{...buildTeamWithTabs, name: 'build-team-2'}]);
|
||||||
|
expect(config.localConfigData.teams).not.toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
|
||||||
|
expect(config.localConfigData.teams).toContainEqual(team);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setServers', () => {
|
||||||
|
it('should set only local servers', () => {
|
||||||
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {};
|
config.localConfigData = {};
|
||||||
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
|
config.regenerateCombinedConfigData = jest.fn().mockImplementation(() => {
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
});
|
});
|
||||||
config.saveLocalConfigData = jest.fn();
|
config.saveLocalConfigData = jest.fn();
|
||||||
|
|
||||||
config.set('teams', [{...buildTeamWithTabs, name: 'build-team-2'}, team]);
|
config.setServers([{...buildTeamWithTabs, name: 'build-team-2'}, team], 0);
|
||||||
expect(config.localConfigData.teams).not.toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
|
expect(config.localConfigData.teams).toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
|
||||||
expect(config.localConfigData.teams).toContainEqual(team);
|
expect(config.localConfigData.lastActiveTeam).toBe(0);
|
||||||
expect(config.predefinedTeams).toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
|
expect(config.regenerateCombinedConfigData).toHaveBeenCalled();
|
||||||
|
expect(config.saveLocalConfigData).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('saveLocalConfigData', () => {
|
describe('saveLocalConfigData', () => {
|
||||||
it('should emit update event on save', () => {
|
it('should emit update event on save', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {test: 'test'};
|
config.localConfigData = {test: 'test'};
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
|
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
|
||||||
@@ -185,7 +211,9 @@ describe('common/config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should emit error when fs.writeSync throws an error', () => {
|
it('should emit error when fs.writeSync throws an error', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {test: 'test'};
|
config.localConfigData = {test: 'test'};
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
|
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
|
||||||
@@ -198,7 +226,9 @@ describe('common/config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should emit error when writeFile throws an error', () => {
|
it('should emit error when writeFile throws an error', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {test: 'test'};
|
config.localConfigData = {test: 'test'};
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
config.writeFile = jest.fn().mockImplementation(() => {
|
config.writeFile = jest.fn().mockImplementation(() => {
|
||||||
@@ -212,7 +242,9 @@ describe('common/config', () => {
|
|||||||
|
|
||||||
it('should retry when file is locked', () => {
|
it('should retry when file is locked', () => {
|
||||||
const testFunc = jest.fn();
|
const testFunc = jest.fn();
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {test: 'test'};
|
config.localConfigData = {test: 'test'};
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
|
config.writeFile = jest.fn().mockImplementation((configFilePath, data, callback) => {
|
||||||
@@ -228,37 +260,41 @@ describe('common/config', () => {
|
|||||||
|
|
||||||
describe('loadLocalConfigFile', () => {
|
describe('loadLocalConfigFile', () => {
|
||||||
it('should use defaults if readFileSync fails', () => {
|
it('should use defaults if readFileSync fails', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.defaultConfigData = {test: 'test'};
|
config.defaultConfigData = {test: 'test'};
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
config.readFileSync = jest.fn().mockImplementation(() => {
|
fs.existsSync.mockReturnValue(true);
|
||||||
|
fs.readFileSync.mockImplementation(() => {
|
||||||
throw new Error('Error message');
|
throw new Error('Error message');
|
||||||
});
|
});
|
||||||
config.writeFileSync = jest.fn();
|
config.writeFile = jest.fn();
|
||||||
|
|
||||||
const configData = config.loadLocalConfigFile();
|
const configData = config.loadLocalConfigFile();
|
||||||
expect(configData).toStrictEqual({test: 'test'});
|
expect(configData).toStrictEqual({test: 'test'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use defaults if validation fails', () => {
|
it('should use defaults if validation fails', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.defaultConfigData = {test: 'test'};
|
config.defaultConfigData = {test: 'test'};
|
||||||
config.combinedData = {...config.localConfigData};
|
config.combinedData = {...config.localConfigData};
|
||||||
config.readFileSync = jest.fn().mockImplementation(() => {
|
fs.existsSync.mockReturnValue(true);
|
||||||
return {version: -1};
|
fs.readFileSync.mockReturnValue('{"version": -1}');
|
||||||
});
|
config.writeFile = jest.fn();
|
||||||
config.writeFileSync = jest.fn();
|
|
||||||
|
|
||||||
const configData = config.loadLocalConfigFile();
|
const configData = config.loadLocalConfigFile();
|
||||||
expect(configData).toStrictEqual({test: 'test'});
|
expect(configData).toStrictEqual({test: 'test'});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return config data if valid', () => {
|
it('should return config data if valid', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
config.readFileSync = jest.fn().mockImplementation(() => {
|
config.init(configPath, appName, appPath);
|
||||||
return {version: 3};
|
fs.existsSync.mockReturnValue(true);
|
||||||
});
|
fs.readFileSync.mockReturnValue('{"version": 3}');
|
||||||
config.writeFileSync = jest.fn();
|
config.writeFile = jest.fn();
|
||||||
|
|
||||||
const configData = config.loadLocalConfigFile();
|
const configData = config.loadLocalConfigFile();
|
||||||
expect(configData).toStrictEqual({version: 3});
|
expect(configData).toStrictEqual({version: 3});
|
||||||
@@ -267,7 +303,9 @@ describe('common/config', () => {
|
|||||||
|
|
||||||
describe('checkForConfigUpdates', () => {
|
describe('checkForConfigUpdates', () => {
|
||||||
it('should upgrade to latest version', () => {
|
it('should upgrade to latest version', () => {
|
||||||
const config = new Config(configPath);
|
const config = new Config();
|
||||||
|
config.reload = jest.fn();
|
||||||
|
config.init(configPath, appName, appPath);
|
||||||
config.defaultConfigData = {version: 10};
|
config.defaultConfigData = {version: 10};
|
||||||
config.writeFileSync = jest.fn();
|
config.writeFileSync = jest.fn();
|
||||||
|
|
||||||
@@ -276,111 +314,67 @@ describe('common/config', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('regenerateCombinedConfigData', () => {
|
// TODO: Re-enable when we migrate to ServerManager fully
|
||||||
it('should combine config from all sources', () => {
|
// describe('regenerateCombinedConfigData', () => {
|
||||||
const config = new Config(configPath);
|
// it('should combine config from all sources', () => {
|
||||||
config.predefinedTeams = [];
|
// const config = new Config();
|
||||||
config.useNativeWindow = false;
|
// config.reload = jest.fn();
|
||||||
config.defaultConfigData = {defaultSetting: 'default', otherDefaultSetting: 'default'};
|
// config.init(configPath, appName, appPath);
|
||||||
config.localConfigData = {otherDefaultSetting: 'local', localSetting: 'local', otherLocalSetting: 'local'};
|
// config.useNativeWindow = false;
|
||||||
config.buildConfigData = {otherLocalSetting: 'build', buildSetting: 'build', otherBuildSetting: 'build'};
|
// config.defaultConfigData = {defaultSetting: 'default', otherDefaultSetting: 'default'};
|
||||||
config.registryConfigData = {otherBuildSetting: 'registry', registrySetting: 'registry'};
|
// config.localConfigData = {otherDefaultSetting: 'local', localSetting: 'local', otherLocalSetting: 'local'};
|
||||||
|
// config.buildConfigData = {otherLocalSetting: 'build', buildSetting: 'build', otherBuildSetting: 'build'};
|
||||||
|
// config.registryConfigData = {otherBuildSetting: 'registry', registrySetting: 'registry'};
|
||||||
|
|
||||||
config.regenerateCombinedConfigData();
|
// config.regenerateCombinedConfigData();
|
||||||
config.combinedData.darkMode = false;
|
// config.combinedData.darkMode = false;
|
||||||
expect(config.combinedData).toStrictEqual({
|
// expect(config.combinedData).toStrictEqual({
|
||||||
teams: [],
|
// appName: 'app-name',
|
||||||
registryTeams: [],
|
// useNativeWindow: false,
|
||||||
appName: 'Mattermost',
|
// darkMode: false,
|
||||||
useNativeWindow: false,
|
// otherBuildSetting: 'registry',
|
||||||
darkMode: false,
|
// registrySetting: 'registry',
|
||||||
otherBuildSetting: 'registry',
|
// otherLocalSetting: 'build',
|
||||||
registrySetting: 'registry',
|
// buildSetting: 'build',
|
||||||
otherLocalSetting: 'build',
|
// otherDefaultSetting: 'local',
|
||||||
buildSetting: 'build',
|
// localSetting: 'local',
|
||||||
otherDefaultSetting: 'local',
|
// defaultSetting: 'default',
|
||||||
localSetting: 'local',
|
// });
|
||||||
defaultSetting: 'default',
|
// });
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should combine teams from all sources and filter duplicates', () => {
|
// it('should not include any teams in the combined config', () => {
|
||||||
const config = new Config(configPath);
|
// const config = new Config();
|
||||||
config.defaultConfigData = {};
|
// config.reload = jest.fn();
|
||||||
config.localConfigData = {};
|
// config.init(configPath, appName, appPath);
|
||||||
config.buildConfigData = {enableServerManagement: true};
|
// config.defaultConfigData = {};
|
||||||
config.registryConfigData = {};
|
// config.localConfigData = {};
|
||||||
config.predefinedTeams = [team, team];
|
// config.buildConfigData = {enableServerManagement: true};
|
||||||
config.useNativeWindow = false;
|
// config.registryConfigData = {};
|
||||||
config.localConfigData = {teams: [
|
// config.predefinedTeams.push(team, team);
|
||||||
team,
|
// config.useNativeWindow = false;
|
||||||
{
|
// config.localConfigData = {teams: [
|
||||||
...team,
|
// team,
|
||||||
name: 'local-team-2',
|
// {
|
||||||
url: 'http://local-team-2.com',
|
// ...team,
|
||||||
},
|
// name: 'local-team-2',
|
||||||
{
|
// url: 'http://local-team-2.com',
|
||||||
...team,
|
// },
|
||||||
name: 'local-team-1',
|
// {
|
||||||
order: 1,
|
// ...team,
|
||||||
url: 'http://local-team-1.com',
|
// name: 'local-team-1',
|
||||||
},
|
// order: 1,
|
||||||
]};
|
// url: 'http://local-team-1.com',
|
||||||
|
// },
|
||||||
|
// ]};
|
||||||
|
|
||||||
config.regenerateCombinedConfigData();
|
// config.regenerateCombinedConfigData();
|
||||||
config.combinedData.darkMode = false;
|
// config.combinedData.darkMode = false;
|
||||||
expect(config.combinedData).toStrictEqual({
|
// expect(config.combinedData).toStrictEqual({
|
||||||
teams: [
|
// appName: 'app-name',
|
||||||
team,
|
// useNativeWindow: false,
|
||||||
{
|
// darkMode: false,
|
||||||
...team,
|
// enableServerManagement: true,
|
||||||
name: 'local-team-2',
|
// });
|
||||||
order: 1,
|
// });
|
||||||
url: 'http://local-team-2.com',
|
// });
|
||||||
},
|
|
||||||
{
|
|
||||||
...team,
|
|
||||||
name: 'local-team-1',
|
|
||||||
order: 2,
|
|
||||||
url: 'http://local-team-1.com',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
registryTeams: [],
|
|
||||||
appName: 'Mattermost',
|
|
||||||
useNativeWindow: false,
|
|
||||||
darkMode: false,
|
|
||||||
enableServerManagement: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not include local teams if enableServerManagement is false', () => {
|
|
||||||
const config = new Config(configPath);
|
|
||||||
config.defaultConfigData = {};
|
|
||||||
config.localConfigData = {};
|
|
||||||
config.buildConfigData = {enableServerManagement: false};
|
|
||||||
config.registryConfigData = {};
|
|
||||||
config.predefinedTeams = [team, team];
|
|
||||||
config.useNativeWindow = false;
|
|
||||||
config.localConfigData = {teams: [
|
|
||||||
team,
|
|
||||||
{
|
|
||||||
...team,
|
|
||||||
name: 'local-team-1',
|
|
||||||
order: 1,
|
|
||||||
url: 'http://local-team-1.com',
|
|
||||||
},
|
|
||||||
]};
|
|
||||||
|
|
||||||
config.regenerateCombinedConfigData();
|
|
||||||
config.combinedData.darkMode = false;
|
|
||||||
expect(config.combinedData).toStrictEqual({
|
|
||||||
teams: [team],
|
|
||||||
registryTeams: [],
|
|
||||||
appName: 'Mattermost',
|
|
||||||
useNativeWindow: false,
|
|
||||||
darkMode: false,
|
|
||||||
enableServerManagement: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -7,7 +7,6 @@ import os from 'os';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import {ipcMain, nativeTheme, app} from 'electron';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnyConfig,
|
AnyConfig,
|
||||||
@@ -15,18 +14,13 @@ import {
|
|||||||
CombinedConfig,
|
CombinedConfig,
|
||||||
ConfigServer,
|
ConfigServer,
|
||||||
Config as ConfigType,
|
Config as ConfigType,
|
||||||
LocalConfiguration,
|
|
||||||
RegistryConfig as RegistryConfigType,
|
RegistryConfig as RegistryConfigType,
|
||||||
TeamWithTabs,
|
|
||||||
} from 'types/config';
|
} from 'types/config';
|
||||||
|
|
||||||
import {UPDATE_TEAMS, GET_CONFIGURATION, UPDATE_CONFIGURATION, GET_LOCAL_CONFIGURATION, UPDATE_PATHS} from 'common/communication';
|
|
||||||
import * as Validator from 'common/Validator';
|
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
import {getDefaultTeamWithTabsFromTeam} from 'common/tabs/TabView';
|
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||||
import Utils from 'common/utils/util';
|
import Utils, {copy} from 'common/utils/util';
|
||||||
|
import * as Validator from 'common/Validator';
|
||||||
import {configPath} from 'main/constants';
|
|
||||||
|
|
||||||
import defaultPreferences, {getDefaultDownloadLocation} from './defaultPreferences';
|
import defaultPreferences, {getDefaultDownloadLocation} from './defaultPreferences';
|
||||||
import upgradeConfigData from './upgradePreferences';
|
import upgradeConfigData from './upgradePreferences';
|
||||||
@@ -36,61 +30,28 @@ import migrateConfigItems from './migrationPreferences';
|
|||||||
|
|
||||||
const log = new Logger('Config');
|
const log = new Logger('Config');
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles loading and merging all sources of configuration as well as saving user provided config
|
|
||||||
*/
|
|
||||||
|
|
||||||
function checkWriteableApp() {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
try {
|
|
||||||
fs.accessSync(path.join(path.dirname(app.getAppPath()), '../../'), fs.constants.W_OK);
|
|
||||||
|
|
||||||
// check to make sure that app-update.yml exists
|
|
||||||
if (!fs.existsSync(path.join(process.resourcesPath, 'app-update.yml'))) {
|
|
||||||
log.warn('app-update.yml does not exist, disabling auto-updates');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log.info(`${app.getAppPath()}: ${error}`);
|
|
||||||
log.warn('autoupgrade disabled');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
return __CAN_UPGRADE__; // prevent showing the option if the path is not writeable, like in a managed environment.
|
|
||||||
}
|
|
||||||
|
|
||||||
// temporarily disabling auto updater for macOS due to security issues
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
return process.platform !== 'darwin' && __CAN_UPGRADE__;
|
|
||||||
}
|
|
||||||
export class Config extends EventEmitter {
|
export class Config extends EventEmitter {
|
||||||
configFilePath: string;
|
private configFilePath?: string;
|
||||||
|
private appName?: string;
|
||||||
|
private appPath?: string;
|
||||||
|
|
||||||
registryConfig: RegistryConfig;
|
private registryConfig: RegistryConfig;
|
||||||
|
private predefinedServers: ConfigServer[];
|
||||||
|
private useNativeWindow: boolean;
|
||||||
|
|
||||||
combinedData?: CombinedConfig;
|
private combinedData?: CombinedConfig;
|
||||||
registryConfigData?: Partial<RegistryConfigType>;
|
private localConfigData?: ConfigType;
|
||||||
defaultConfigData?: ConfigType;
|
private registryConfigData?: Partial<RegistryConfigType>;
|
||||||
buildConfigData?: BuildConfig;
|
private defaultConfigData?: ConfigType;
|
||||||
localConfigData?: ConfigType;
|
private buildConfigData?: BuildConfig;
|
||||||
useNativeWindow: boolean;
|
private canUpgradeValue?: boolean;
|
||||||
canUpgradeValue?: boolean
|
|
||||||
|
|
||||||
predefinedTeams: TeamWithTabs[];
|
constructor() {
|
||||||
|
|
||||||
constructor(configFilePath: string) {
|
|
||||||
super();
|
super();
|
||||||
this.configFilePath = configFilePath;
|
|
||||||
this.canUpgradeValue = checkWriteableApp();
|
|
||||||
this.registryConfig = new RegistryConfig();
|
this.registryConfig = new RegistryConfig();
|
||||||
this.predefinedTeams = [];
|
this.predefinedServers = [];
|
||||||
if (buildConfig.defaultTeams) {
|
if (buildConfig.defaultTeams) {
|
||||||
this.predefinedTeams.push(...buildConfig.defaultTeams.map((team) => getDefaultTeamWithTabsFromTeam(team)));
|
this.predefinedServers.push(...buildConfig.defaultTeams.map((team, index) => getDefaultConfigTeamFromTeam({...team, order: index})));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.useNativeWindow = os.platform() === 'win32' && !Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.2');
|
this.useNativeWindow = os.platform() === 'win32' && !Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.2');
|
||||||
@@ -99,45 +60,30 @@ export class Config extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// separating constructor from init so main can setup event listeners
|
init = (configFilePath: string, appName: string, appPath: string) => {
|
||||||
init = (): void => {
|
this.configFilePath = configFilePath;
|
||||||
|
this.appName = appName;
|
||||||
|
this.appPath = appPath;
|
||||||
|
this.canUpgradeValue = this.checkWriteableApp();
|
||||||
|
|
||||||
this.reload();
|
this.reload();
|
||||||
ipcMain.handle(GET_CONFIGURATION, this.handleGetConfiguration);
|
|
||||||
ipcMain.handle(GET_LOCAL_CONFIGURATION, this.handleGetLocalConfiguration);
|
|
||||||
ipcMain.handle(UPDATE_TEAMS, this.handleUpdateTeams);
|
|
||||||
ipcMain.on(UPDATE_CONFIGURATION, this.updateConfiguration);
|
|
||||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
|
||||||
nativeTheme.on('updated', this.handleUpdateTheme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initRegistry = () => {
|
initRegistry = () => {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
this.registryConfig = new RegistryConfig();
|
this.registryConfig = new RegistryConfig();
|
||||||
this.registryConfig.once(REGISTRY_READ_EVENT, (data) => {
|
this.registryConfig.once(REGISTRY_READ_EVENT, (data) => {
|
||||||
this.loadRegistry(data);
|
this.onLoadRegistry(data);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
this.registryConfig.init();
|
this.registryConfig.init();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the teams from registry into the config object and reload
|
|
||||||
*
|
|
||||||
* @param {object} registryData Team configuration from the registry and if teams can be managed by user
|
|
||||||
*/
|
|
||||||
|
|
||||||
loadRegistry = (registryData: Partial<RegistryConfigType>): void => {
|
|
||||||
log.verbose('Config.loadRegistry', {registryData});
|
|
||||||
|
|
||||||
this.registryConfigData = registryData;
|
|
||||||
if (this.registryConfigData.teams) {
|
|
||||||
this.predefinedTeams.push(...this.registryConfigData.teams.map((team) => getDefaultTeamWithTabsFromTeam(team)));
|
|
||||||
}
|
|
||||||
this.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload all sources of config data
|
* Reload all sources of config data
|
||||||
*
|
*
|
||||||
@@ -146,15 +92,21 @@ export class Config extends EventEmitter {
|
|||||||
* @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes
|
* @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes
|
||||||
*/
|
*/
|
||||||
reload = (): void => {
|
reload = (): void => {
|
||||||
this.defaultConfigData = this.loadDefaultConfigData();
|
this.defaultConfigData = copy(defaultPreferences);
|
||||||
this.buildConfigData = this.loadBuildConfigData();
|
this.buildConfigData = copy(buildConfig);
|
||||||
|
|
||||||
const loadedConfig = this.loadLocalConfigFile();
|
const loadedConfig = this.loadLocalConfigFile();
|
||||||
this.localConfigData = this.checkForConfigUpdates(loadedConfig);
|
this.localConfigData = this.checkForConfigUpdates(loadedConfig);
|
||||||
|
|
||||||
this.regenerateCombinedConfigData();
|
this.regenerateCombinedConfigData();
|
||||||
|
|
||||||
this.emit('update', this.combinedData);
|
this.emit('update', this.combinedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
* Setters and Getters
|
||||||
|
*********************/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to save a single config property
|
* Used to save a single config property
|
||||||
*
|
*
|
||||||
@@ -166,18 +118,8 @@ export class Config extends EventEmitter {
|
|||||||
this.setMultiple({[key]: data});
|
this.setMultiple({[key]: data});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateConfiguration = (event: Electron.IpcMainEvent, properties: Array<{key: keyof ConfigType; data: ConfigType[keyof ConfigType]}> = []): Partial<ConfigType> | undefined => {
|
setConfigPath = (configPath: string) => {
|
||||||
log.debug('Config.updateConfiguration', properties);
|
this.configFilePath = configPath;
|
||||||
|
|
||||||
if (properties.length) {
|
|
||||||
const newData = properties.reduce((obj, data) => {
|
|
||||||
(obj as any)[data.key] = data.data;
|
|
||||||
return obj;
|
|
||||||
}, {} as Partial<ConfigType>);
|
|
||||||
this.setMultiple(newData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.localConfigData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,20 +130,12 @@ export class Config extends EventEmitter {
|
|||||||
setMultiple = (newData: Partial<ConfigType>) => {
|
setMultiple = (newData: Partial<ConfigType>) => {
|
||||||
log.debug('setMultiple', newData);
|
log.debug('setMultiple', newData);
|
||||||
|
|
||||||
this.localConfigData = Object.assign({}, this.localConfigData, newData);
|
if (newData.darkMode && newData.darkMode !== this.darkMode) {
|
||||||
if (newData.teams && this.localConfigData) {
|
this.emit('darkModeChange', newData.darkMode);
|
||||||
this.localConfigData.teams = this.filterOutPredefinedTeams(newData.teams as TeamWithTabs[]);
|
|
||||||
this.predefinedTeams = this.filterInPredefinedTeams(newData.teams as TeamWithTabs[]);
|
|
||||||
}
|
}
|
||||||
|
this.localConfigData = Object.assign({}, this.localConfigData, {...newData, teams: this.localConfigData?.teams});
|
||||||
this.regenerateCombinedConfigData();
|
this.regenerateCombinedConfigData();
|
||||||
this.saveLocalConfigData();
|
this.saveLocalConfigData();
|
||||||
|
|
||||||
return this.localConfigData; //this is the only part that changes
|
|
||||||
}
|
|
||||||
|
|
||||||
setRegistryConfigData = (registryConfigData = {teams: []}): void => {
|
|
||||||
this.registryConfigData = Object.assign({}, registryConfigData);
|
|
||||||
this.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setServers = (servers: ConfigServer[], lastActiveTeam?: number) => {
|
setServers = (servers: ConfigServer[], lastActiveTeam?: number) => {
|
||||||
@@ -212,50 +146,6 @@ export class Config extends EventEmitter {
|
|||||||
this.saveLocalConfigData();
|
this.saveLocalConfigData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to replace the existing config data with new config data
|
|
||||||
*
|
|
||||||
* @param {object} configData a new, config data object to completely replace the existing config data
|
|
||||||
*/
|
|
||||||
replace = (configData: ConfigType) => {
|
|
||||||
const newConfigData = configData;
|
|
||||||
|
|
||||||
this.localConfigData = Object.assign({}, this.localConfigData, newConfigData);
|
|
||||||
|
|
||||||
this.regenerateCombinedConfigData();
|
|
||||||
this.saveLocalConfigData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to save the current set of local config data to disk
|
|
||||||
*
|
|
||||||
* @emits {update} emitted once all data has been saved
|
|
||||||
* @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes
|
|
||||||
* @emits {error} emitted if saving local config data to file fails
|
|
||||||
*/
|
|
||||||
saveLocalConfigData = (): void => {
|
|
||||||
if (!this.localConfigData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('Saving config data to file...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.writeFile(this.configFilePath, this.localConfigData, (error: NodeJS.ErrnoException | null) => {
|
|
||||||
if (error) {
|
|
||||||
if (error.code === 'EBUSY') {
|
|
||||||
this.saveLocalConfigData();
|
|
||||||
} else {
|
|
||||||
this.emit('error', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.emit('update', this.combinedData);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.emit('error', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getters for accessing the various config data inputs
|
// getters for accessing the various config data inputs
|
||||||
|
|
||||||
get data() {
|
get data() {
|
||||||
@@ -288,6 +178,9 @@ export class Config extends EventEmitter {
|
|||||||
get localTeams() {
|
get localTeams() {
|
||||||
return this.localConfigData?.teams ?? defaultPreferences.teams;
|
return this.localConfigData?.teams ?? defaultPreferences.teams;
|
||||||
}
|
}
|
||||||
|
get predefinedTeams() {
|
||||||
|
return this.predefinedServers;
|
||||||
|
}
|
||||||
get enableHardwareAcceleration() {
|
get enableHardwareAcceleration() {
|
||||||
return this.combinedData?.enableHardwareAcceleration ?? defaultPreferences.enableHardwareAcceleration;
|
return this.combinedData?.enableHardwareAcceleration ?? defaultPreferences.enableHardwareAcceleration;
|
||||||
}
|
}
|
||||||
@@ -361,29 +254,67 @@ export class Config extends EventEmitter {
|
|||||||
return this.combinedData?.appLanguage;
|
return this.combinedData?.appLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialization/processing methods
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of the app's default config data
|
* Gets the teams from registry into the config object and reload
|
||||||
|
*
|
||||||
|
* @param {object} registryData Team configuration from the registry and if teams can be managed by user
|
||||||
*/
|
*/
|
||||||
loadDefaultConfigData = () => {
|
|
||||||
return this.copy(defaultPreferences);
|
private onLoadRegistry = (registryData: Partial<RegistryConfigType>): void => {
|
||||||
|
log.debug('loadRegistry', {registryData});
|
||||||
|
|
||||||
|
this.registryConfigData = registryData;
|
||||||
|
if (this.registryConfigData.teams) {
|
||||||
|
this.predefinedTeams.push(...this.registryConfigData.teams.map((team, index) => getDefaultConfigTeamFromTeam({...team, order: index})));
|
||||||
|
}
|
||||||
|
this.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of the app's build config data
|
* Config file loading methods
|
||||||
*/
|
*/
|
||||||
loadBuildConfigData = () => {
|
|
||||||
return this.copy(buildConfig);
|
/**
|
||||||
|
* Used to save the current set of local config data to disk
|
||||||
|
*
|
||||||
|
* @emits {update} emitted once all data has been saved
|
||||||
|
* @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes
|
||||||
|
* @emits {error} emitted if saving local config data to file fails
|
||||||
|
*/
|
||||||
|
private saveLocalConfigData = (): void => {
|
||||||
|
if (!(this.configFilePath && this.localConfigData)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.verbose('Saving config data to file...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.writeFile(this.configFilePath, this.localConfigData, (error: NodeJS.ErrnoException | null) => {
|
||||||
|
if (error) {
|
||||||
|
if (error.code === 'EBUSY') {
|
||||||
|
this.saveLocalConfigData();
|
||||||
|
} else {
|
||||||
|
this.emit('error', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit('update', this.combinedData);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.emit('error', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found
|
* Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found
|
||||||
*/
|
*/
|
||||||
loadLocalConfigFile = (): AnyConfig => {
|
private loadLocalConfigFile = (): AnyConfig => {
|
||||||
|
if (!this.configFilePath) {
|
||||||
|
throw new Error('Unable to read from config, no path specified');
|
||||||
|
}
|
||||||
|
|
||||||
let configData: AnyConfig;
|
let configData: AnyConfig;
|
||||||
try {
|
try {
|
||||||
configData = this.readFileSync(this.configFilePath);
|
configData = JSON.parse(fs.readFileSync(this.configFilePath, 'utf8'));
|
||||||
|
|
||||||
// validate based on config file version
|
// validate based on config file version
|
||||||
configData = Validator.validateConfigData(configData);
|
configData = Validator.validateConfigData(configData);
|
||||||
@@ -393,9 +324,9 @@ export class Config extends EventEmitter {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warn('Failed to load configuration file from the filesystem. Using defaults.');
|
log.warn('Failed to load configuration file from the filesystem. Using defaults.');
|
||||||
configData = this.copy(this.defaultConfigData);
|
configData = copy(this.defaultConfigData);
|
||||||
|
|
||||||
this.writeFileSync(this.configFilePath, configData);
|
this.writeFile(this.configFilePath, configData);
|
||||||
}
|
}
|
||||||
return configData;
|
return configData;
|
||||||
}
|
}
|
||||||
@@ -405,18 +336,22 @@ export class Config extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {*} data locally stored data
|
* @param {*} data locally stored data
|
||||||
*/
|
*/
|
||||||
checkForConfigUpdates = (data: AnyConfig) => {
|
private checkForConfigUpdates = (data: AnyConfig) => {
|
||||||
|
if (!this.configFilePath) {
|
||||||
|
throw new Error('Config not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
let configData = data;
|
let configData = data;
|
||||||
if (this.defaultConfigData) {
|
if (this.defaultConfigData) {
|
||||||
try {
|
try {
|
||||||
if (configData.version !== this.defaultConfigData.version) {
|
if (configData.version !== this.defaultConfigData.version) {
|
||||||
configData = upgradeConfigData(configData);
|
configData = upgradeConfigData(configData);
|
||||||
this.writeFileSync(this.configFilePath, configData);
|
this.writeFile(this.configFilePath, configData);
|
||||||
log.info(`Configuration updated to version ${this.defaultConfigData.version} successfully.`);
|
log.info(`Configuration updated to version ${this.defaultConfigData.version} successfully.`);
|
||||||
}
|
}
|
||||||
const didMigrate = migrateConfigItems(configData);
|
const didMigrate = migrateConfigItems(configData);
|
||||||
if (didMigrate) {
|
if (didMigrate) {
|
||||||
this.writeFileSync(this.configFilePath, configData);
|
this.writeFile(this.configFilePath, configData);
|
||||||
log.info('Migrating config items successfully.');
|
log.info('Migrating config items successfully.');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -430,43 +365,45 @@ export class Config extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Properly combines all sources of data into a single, manageable set of all config data
|
* Properly combines all sources of data into a single, manageable set of all config data
|
||||||
*/
|
*/
|
||||||
regenerateCombinedConfigData = () => {
|
private regenerateCombinedConfigData = () => {
|
||||||
|
if (!this.appName) {
|
||||||
|
throw new Error('Config not initialized, cannot regenerate');
|
||||||
|
}
|
||||||
|
|
||||||
// combine all config data in the correct order
|
// combine all config data in the correct order
|
||||||
this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.registryConfigData, {useNativeWindow: this.useNativeWindow});
|
this.combinedData = Object.assign({},
|
||||||
|
this.defaultConfigData,
|
||||||
|
this.localConfigData,
|
||||||
|
this.buildConfigData,
|
||||||
|
this.registryConfigData,
|
||||||
|
{useNativeWindow: this.useNativeWindow},
|
||||||
|
);
|
||||||
|
|
||||||
// remove unecessary data pulled from default and build config
|
// We don't want to include the servers in the combined config, they should only be accesible via the ServerManager
|
||||||
delete this.combinedData!.defaultTeams;
|
//delete (this.combinedData as any).teams;
|
||||||
|
delete (this.combinedData as any).defaultTeams;
|
||||||
// IMPORTANT: properly combine teams from all sources
|
|
||||||
let combinedTeams: TeamWithTabs[] = [];
|
|
||||||
|
|
||||||
|
if (this.combinedData) {
|
||||||
|
// TODO: This can be removed after we fully migrate to ServerManager
|
||||||
|
let combinedTeams: ConfigServer[] = [];
|
||||||
combinedTeams.push(...this.predefinedTeams);
|
combinedTeams.push(...this.predefinedTeams);
|
||||||
|
|
||||||
// - add locally defined teams only if server management is enabled
|
|
||||||
if (this.localConfigData && this.enableServerManagement) {
|
if (this.localConfigData && this.enableServerManagement) {
|
||||||
combinedTeams.push(...this.localConfigData.teams || []);
|
combinedTeams.push(...this.localConfigData.teams || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.predefinedTeams = this.filterOutDuplicateTeams(this.predefinedTeams);
|
|
||||||
combinedTeams = this.filterOutDuplicateTeams(combinedTeams);
|
combinedTeams = this.filterOutDuplicateTeams(combinedTeams);
|
||||||
combinedTeams = this.sortUnorderedTeams(combinedTeams);
|
combinedTeams = this.sortUnorderedTeams(combinedTeams);
|
||||||
|
|
||||||
if (this.combinedData) {
|
|
||||||
this.combinedData.teams = combinedTeams;
|
this.combinedData.teams = combinedTeams;
|
||||||
this.combinedData.registryTeams = this.registryConfigData?.teams || [];
|
|
||||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
this.combinedData.appName = this.appName;
|
||||||
this.combinedData.darkMode = nativeTheme.shouldUseDarkColors;
|
|
||||||
}
|
|
||||||
this.combinedData.appName = app.name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the provided list of teams with duplicates filtered out
|
* Returns the provided list of teams with duplicates filtered out
|
||||||
*
|
* TODO: This can be removed after we fully migrate to ServerManager
|
||||||
* @param {array} teams array of teams to check for duplicates
|
* @param {array} teams array of teams to check for duplicates
|
||||||
*/
|
*/
|
||||||
filterOutDuplicateTeams = (teams: TeamWithTabs[]) => {
|
private filterOutDuplicateTeams = (teams: ConfigServer[]) => {
|
||||||
let newTeams = teams;
|
let newTeams = teams;
|
||||||
const uniqueURLs = new Set();
|
const uniqueURLs = new Set();
|
||||||
newTeams = newTeams.filter((team) => {
|
newTeams = newTeams.filter((team) => {
|
||||||
@@ -475,41 +412,12 @@ export class Config extends EventEmitter {
|
|||||||
return newTeams;
|
return newTeams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the provided array fo teams with existing teams filtered out
|
|
||||||
* @param {array} teams array of teams to check for already defined teams
|
|
||||||
*/
|
|
||||||
filterOutPredefinedTeams = (teams: TeamWithTabs[]) => {
|
|
||||||
let newTeams = teams;
|
|
||||||
|
|
||||||
// filter out predefined teams
|
|
||||||
newTeams = newTeams.filter((newTeam) => {
|
|
||||||
return this.predefinedTeams.findIndex((existingTeam) => newTeam.url === existingTeam.url) === -1; // eslint-disable-line max-nested-callbacks
|
|
||||||
});
|
|
||||||
|
|
||||||
return newTeams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the provided array fo teams with existing teams includes
|
|
||||||
* @param {array} teams array of teams to check for already defined teams
|
|
||||||
*/
|
|
||||||
filterInPredefinedTeams = (teams: TeamWithTabs[]) => {
|
|
||||||
let newTeams = teams;
|
|
||||||
|
|
||||||
// filter out predefined teams
|
|
||||||
newTeams = newTeams.filter((newTeam) => {
|
|
||||||
return this.predefinedTeams.findIndex((existingTeam) => newTeam.url === existingTeam.url) >= 0; // eslint-disable-line max-nested-callbacks
|
|
||||||
});
|
|
||||||
|
|
||||||
return newTeams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a default sort order to the team list, if no order is specified.
|
* Apply a default sort order to the team list, if no order is specified.
|
||||||
* @param {array} teams to sort
|
* @param {array} teams to sort
|
||||||
|
* TODO: This can be removed after we fully migrate to ServerManager
|
||||||
*/
|
*/
|
||||||
sortUnorderedTeams = (teams: TeamWithTabs[]) => {
|
private sortUnorderedTeams = (teams: ConfigServer[]) => {
|
||||||
// We want to preserve the array order of teams in the config, otherwise a lot of bugs will occur
|
// We want to preserve the array order of teams in the config, otherwise a lot of bugs will occur
|
||||||
const mappedTeams = teams.map((team, index) => ({team, originalOrder: index}));
|
const mappedTeams = teams.map((team, index) => ({team, originalOrder: index}));
|
||||||
|
|
||||||
@@ -538,12 +446,7 @@ export class Config extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper functions
|
// helper functions
|
||||||
|
private writeFile = (filePath: string, configData: Partial<ConfigType>, callback?: fs.NoParamCallback) => {
|
||||||
readFileSync = (filePath: string) => {
|
|
||||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFile = (filePath: string, configData: Partial<ConfigType>, callback: fs.NoParamCallback) => {
|
|
||||||
if (!this.defaultConfigData) {
|
if (!this.defaultConfigData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -552,101 +455,52 @@ export class Config extends EventEmitter {
|
|||||||
throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version);
|
throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version);
|
||||||
}
|
}
|
||||||
const json = JSON.stringify(configData, null, ' ');
|
const json = JSON.stringify(configData, null, ' ');
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
fs.writeFile(filePath, json, 'utf8', callback);
|
fs.writeFile(filePath, json, 'utf8', callback);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
writeFileSync = (filePath: string, config: Partial<ConfigType>) => {
|
|
||||||
if (!this.defaultConfigData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.version !== this.defaultConfigData.version) {
|
|
||||||
throw new Error('version ' + config.version + ' is not equal to ' + this.defaultConfigData.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dir = path.dirname(filePath);
|
const dir = path.dirname(filePath);
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir);
|
fs.mkdirSync(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = JSON.stringify(config, null, ' ');
|
|
||||||
fs.writeFileSync(filePath, json, 'utf8');
|
fs.writeFileSync(filePath, json, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
merge = <T, T2>(base: T, target: T2) => {
|
|
||||||
return Object.assign({}, base, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copy = <T>(data: T) => {
|
private checkWriteableApp = () => {
|
||||||
return Object.assign({}, data);
|
if (!this.appPath) {
|
||||||
|
throw new Error('Config not initialized, cannot regenerate');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGetConfiguration = (event: Electron.IpcMainInvokeEvent, option: keyof CombinedConfig) => {
|
if (process.platform === 'win32') {
|
||||||
log.debug('Config.handleGetConfiguration', option);
|
try {
|
||||||
|
fs.accessSync(path.join(path.dirname(this.appPath), '../../'), fs.constants.W_OK);
|
||||||
|
|
||||||
const config = {...this.combinedData};
|
// check to make sure that app-update.yml exists
|
||||||
if (option) {
|
if (!fs.existsSync(path.join(process.resourcesPath, 'app-update.yml'))) {
|
||||||
return config[option];
|
log.warn('app-update.yml does not exist, disabling auto-updates');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return config;
|
} catch (error) {
|
||||||
|
log.info(`${this.appPath}: ${error}`);
|
||||||
|
log.warn('autoupgrade disabled');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGetLocalConfiguration = (event: Electron.IpcMainInvokeEvent, option: keyof ConfigType) => {
|
// eslint-disable-next-line no-undef
|
||||||
log.debug('Config.handleGetLocalConfiguration', option);
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
const config: Partial<LocalConfiguration> = {...this.localConfigData};
|
return __CAN_UPGRADE__; // prevent showing the option if the path is not writeable, like in a managed environment.
|
||||||
config.appName = app.name;
|
|
||||||
config.enableServerManagement = this.combinedData?.enableServerManagement;
|
|
||||||
config.canUpgrade = this.canUpgrade;
|
|
||||||
if (option) {
|
|
||||||
return config[option];
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateTeams = (event: Electron.IpcMainInvokeEvent, newTeams: TeamWithTabs[]) => {
|
// temporarily disabling auto updater for macOS due to security issues
|
||||||
log.debug('Config.handleUpdateTeams');
|
// eslint-disable-next-line no-undef
|
||||||
log.silly('Config.handleUpdateTeams', newTeams);
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
this.set('teams', newTeams);
|
return process.platform !== 'darwin' && __CAN_UPGRADE__;
|
||||||
return this.combinedData!.teams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects changes in darkmode if it is windows or osx, updates the config and propagates the changes
|
|
||||||
* @emits 'darkModeChange'
|
|
||||||
*/
|
|
||||||
handleUpdateTheme = () => {
|
|
||||||
log.debug('Config.handleUpdateTheme');
|
|
||||||
|
|
||||||
if (this.combinedData && this.combinedData.darkMode !== nativeTheme.shouldUseDarkColors) {
|
|
||||||
this.combinedData.darkMode = nativeTheme.shouldUseDarkColors;
|
|
||||||
this.emit('darkModeChange', this.combinedData.darkMode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const config = new Config();
|
||||||
* Manually toggles dark mode for OSes that don't have a native dark mode setting
|
|
||||||
* @emits 'darkModeChange'
|
|
||||||
*/
|
|
||||||
toggleDarkModeManually = () => {
|
|
||||||
if (!this.combinedData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('darkMode', !this.combinedData.darkMode);
|
|
||||||
this.emit('darkModeChange', this.combinedData.darkMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = new Config(configPath);
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
||||||
ipcMain.on(UPDATE_PATHS, () => {
|
|
||||||
log.debug('Config.UPDATE_PATHS');
|
|
||||||
|
|
||||||
config.configFilePath = configPath;
|
|
||||||
if (config.combinedData) {
|
|
||||||
config.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
@@ -5,7 +5,7 @@ import {upgradeV0toV1, upgradeV1toV2, upgradeV2toV3} from 'common/config/upgrade
|
|||||||
import pastDefaultPreferences from 'common/config/pastDefaultPreferences';
|
import pastDefaultPreferences from 'common/config/pastDefaultPreferences';
|
||||||
|
|
||||||
jest.mock('common/tabs/TabView', () => ({
|
jest.mock('common/tabs/TabView', () => ({
|
||||||
getDefaultTeamWithTabsFromTeam: (value) => ({
|
getDefaultConfigTeamFromTeam: (value) => ({
|
||||||
...value,
|
...value,
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
@@ -122,7 +122,6 @@ describe('common/config/upgradePreferences', () => {
|
|||||||
name: 'tab2',
|
name: 'tab2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
lastActiveTab: 0,
|
|
||||||
}, {
|
}, {
|
||||||
name: 'Secondary team',
|
name: 'Secondary team',
|
||||||
url: 'http://server-2.com',
|
url: 'http://server-2.com',
|
||||||
@@ -135,7 +134,6 @@ describe('common/config/upgradePreferences', () => {
|
|||||||
name: 'tab2',
|
name: 'tab2',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
lastActiveTab: 0,
|
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import {ConfigV3, ConfigV2, ConfigV1, ConfigV0, AnyConfig} from 'types/config';
|
import {ConfigV3, ConfigV2, ConfigV1, ConfigV0, AnyConfig} from 'types/config';
|
||||||
|
|
||||||
import {getDefaultTeamWithTabsFromTeam} from 'common/tabs/TabView';
|
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||||
|
|
||||||
import pastDefaultPreferences from './pastDefaultPreferences';
|
import pastDefaultPreferences from './pastDefaultPreferences';
|
||||||
|
|
||||||
@@ -37,10 +37,7 @@ export function upgradeV2toV3(configV2: ConfigV2) {
|
|||||||
const config: ConfigV3 = Object.assign({}, deepCopy<ConfigV3>(pastDefaultPreferences[3]), configV2);
|
const config: ConfigV3 = Object.assign({}, deepCopy<ConfigV3>(pastDefaultPreferences[3]), configV2);
|
||||||
config.version = 3;
|
config.version = 3;
|
||||||
config.teams = configV2.teams.map((value) => {
|
config.teams = configV2.teams.map((value) => {
|
||||||
return {
|
return getDefaultConfigTeamFromTeam(value);
|
||||||
...getDefaultTeamWithTabsFromTeam(value),
|
|
||||||
lastActiveTab: 0,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
config.lastActiveTeam = 0;
|
config.lastActiveTeam = 0;
|
||||||
config.spellCheckerLocales = [];
|
config.spellCheckerLocales = [];
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {FullTeam} from 'types/config';
|
import {Team} from 'types/config';
|
||||||
|
|
||||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export interface TabView {
|
|||||||
get shouldNotify(): boolean;
|
get shouldNotify(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultTeamWithTabsFromTeam(team: FullTeam) {
|
export function getDefaultConfigTeamFromTeam(team: Team & {order: number; lastActiveTab?: number}) {
|
||||||
return {
|
return {
|
||||||
...team,
|
...team,
|
||||||
tabs: getDefaultTabs(),
|
tabs: getDefaultTabs(),
|
||||||
|
@@ -66,6 +66,10 @@ export const escapeRegex = (s?: string) => {
|
|||||||
return s.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
return s.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function copy<T>(data: T) {
|
||||||
|
return Object.assign({}, data);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
runMode,
|
runMode,
|
||||||
shorten,
|
shorten,
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {app, ipcMain} from 'electron';
|
import {app, ipcMain, nativeTheme} from 'electron';
|
||||||
|
|
||||||
import {CombinedConfig} from 'types/config';
|
import {CombinedConfig, ConfigServer, Config as ConfigType} from 'types/config';
|
||||||
|
|
||||||
import {DARK_MODE_CHANGE, EMIT_CONFIGURATION, RELOAD_CONFIGURATION} from 'common/communication';
|
import {DARK_MODE_CHANGE, EMIT_CONFIGURATION, RELOAD_CONFIGURATION} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
@@ -25,6 +25,49 @@ const log = new Logger('App.Config');
|
|||||||
// config event handlers
|
// config event handlers
|
||||||
//
|
//
|
||||||
|
|
||||||
|
export function handleGetConfiguration() {
|
||||||
|
log.debug('handleGetConfiguration');
|
||||||
|
|
||||||
|
return Config.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleGetLocalConfiguration() {
|
||||||
|
log.debug('handleGetLocalConfiguration');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...Config.localData,
|
||||||
|
appName: app.name,
|
||||||
|
enableServerManagement: Config.enableServerManagement,
|
||||||
|
canUpgrade: Config.canUpgrade,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateConfiguration(event: Electron.IpcMainEvent, properties: Array<{key: keyof ConfigType; data: ConfigType[keyof ConfigType]}> = []) {
|
||||||
|
log.debug('updateConfiguration', properties);
|
||||||
|
|
||||||
|
if (properties.length) {
|
||||||
|
const newData = properties.reduce((obj, data) => {
|
||||||
|
(obj as any)[data.key] = data.data;
|
||||||
|
return obj;
|
||||||
|
}, {} as Partial<ConfigType>);
|
||||||
|
Config.setMultiple(newData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleUpdateTheme() {
|
||||||
|
log.debug('Config.handleUpdateTheme');
|
||||||
|
|
||||||
|
Config.set('darkMode', nativeTheme.shouldUseDarkColors);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleUpdateTeams(event: Electron.IpcMainInvokeEvent, newTeams: ConfigServer[]) {
|
||||||
|
log.debug('Config.handleUpdateTeams');
|
||||||
|
log.silly('Config.handleUpdateTeams', newTeams);
|
||||||
|
|
||||||
|
Config.setServers(newTeams);
|
||||||
|
return Config.teams;
|
||||||
|
}
|
||||||
|
|
||||||
export function handleConfigUpdate(newConfig: CombinedConfig) {
|
export function handleConfigUpdate(newConfig: CombinedConfig) {
|
||||||
if (newConfig.logLevel) {
|
if (newConfig.logLevel) {
|
||||||
setLoggingLevel(newConfig.logLevel);
|
setLoggingLevel(newConfig.logLevel);
|
||||||
|
@@ -59,6 +59,9 @@ jest.mock('electron', () => ({
|
|||||||
removeHandler: jest.fn(),
|
removeHandler: jest.fn(),
|
||||||
removeListener: jest.fn(),
|
removeListener: jest.fn(),
|
||||||
},
|
},
|
||||||
|
nativeTheme: {
|
||||||
|
on: jest.fn(),
|
||||||
|
},
|
||||||
screen: {
|
screen: {
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
},
|
},
|
||||||
@@ -111,6 +114,7 @@ jest.mock('main/allowProtocolDialog', () => ({
|
|||||||
jest.mock('main/app/app', () => ({}));
|
jest.mock('main/app/app', () => ({}));
|
||||||
jest.mock('main/app/config', () => ({
|
jest.mock('main/app/config', () => ({
|
||||||
handleConfigUpdate: jest.fn(),
|
handleConfigUpdate: jest.fn(),
|
||||||
|
handleUpdateTheme: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/app/intercom', () => ({
|
jest.mock('main/app/intercom', () => ({
|
||||||
handleMainWindowIsShown: jest.fn(),
|
handleMainWindowIsShown: jest.fn(),
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {app, ipcMain, session} from 'electron';
|
import {app, ipcMain, nativeTheme, session} from 'electron';
|
||||||
import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer';
|
import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer';
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
@@ -35,6 +35,11 @@ import {
|
|||||||
PING_DOMAIN,
|
PING_DOMAIN,
|
||||||
MAIN_WINDOW_SHOWN,
|
MAIN_WINDOW_SHOWN,
|
||||||
OPEN_APP_MENU,
|
OPEN_APP_MENU,
|
||||||
|
GET_CONFIGURATION,
|
||||||
|
GET_LOCAL_CONFIGURATION,
|
||||||
|
UPDATE_CONFIGURATION,
|
||||||
|
UPDATE_PATHS,
|
||||||
|
UPDATE_TEAMS,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
@@ -47,7 +52,7 @@ import AutoLauncher from 'main/AutoLauncher';
|
|||||||
import updateManager from 'main/autoUpdater';
|
import updateManager from 'main/autoUpdater';
|
||||||
import {setupBadge} from 'main/badge';
|
import {setupBadge} from 'main/badge';
|
||||||
import CertificateManager from 'main/certificateManager';
|
import CertificateManager from 'main/certificateManager';
|
||||||
import {updatePaths} from 'main/constants';
|
import {configPath, updatePaths} from 'main/constants';
|
||||||
import CriticalErrorHandler from 'main/CriticalErrorHandler';
|
import CriticalErrorHandler from 'main/CriticalErrorHandler';
|
||||||
import downloadsManager from 'main/downloadsManager';
|
import downloadsManager from 'main/downloadsManager';
|
||||||
import i18nManager from 'main/i18nManager';
|
import i18nManager from 'main/i18nManager';
|
||||||
@@ -71,7 +76,15 @@ import {
|
|||||||
handleAppWindowAllClosed,
|
handleAppWindowAllClosed,
|
||||||
handleChildProcessGone,
|
handleChildProcessGone,
|
||||||
} from './app';
|
} from './app';
|
||||||
import {handleConfigUpdate, handleDarkModeChange} from './config';
|
import {
|
||||||
|
handleConfigUpdate,
|
||||||
|
handleDarkModeChange,
|
||||||
|
handleGetConfiguration,
|
||||||
|
handleGetLocalConfiguration,
|
||||||
|
handleUpdateTheme,
|
||||||
|
updateConfiguration,
|
||||||
|
handleUpdateTeams,
|
||||||
|
} from './config';
|
||||||
import {
|
import {
|
||||||
handleMainWindowIsShown,
|
handleMainWindowIsShown,
|
||||||
handleAppVersion,
|
handleAppVersion,
|
||||||
@@ -177,7 +190,15 @@ async function initializeConfig() {
|
|||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
Config.init();
|
Config.init(configPath, app.name, app.getAppPath());
|
||||||
|
ipcMain.on(UPDATE_PATHS, () => {
|
||||||
|
log.debug('Config.UPDATE_PATHS');
|
||||||
|
|
||||||
|
Config.setConfigPath(configPath);
|
||||||
|
if (Config.data) {
|
||||||
|
Config.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +253,11 @@ function initializeBeforeAppReady() {
|
|||||||
} else if (mainProtocol) {
|
} else if (mainProtocol) {
|
||||||
app.setAsDefaultProtocolClient(mainProtocol);
|
app.setAsDefaultProtocolClient(mainProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||||
|
nativeTheme.on('updated', handleUpdateTheme);
|
||||||
|
handleUpdateTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeInterCommunicationEventListeners() {
|
function initializeInterCommunicationEventListeners() {
|
||||||
@@ -269,6 +295,10 @@ function initializeInterCommunicationEventListeners() {
|
|||||||
ipcMain.on(START_UPDATE_DOWNLOAD, handleStartDownload);
|
ipcMain.on(START_UPDATE_DOWNLOAD, handleStartDownload);
|
||||||
ipcMain.on(START_UPGRADE, handleStartUpgrade);
|
ipcMain.on(START_UPGRADE, handleStartUpgrade);
|
||||||
ipcMain.handle(PING_DOMAIN, handlePingDomain);
|
ipcMain.handle(PING_DOMAIN, handlePingDomain);
|
||||||
|
ipcMain.handle(GET_CONFIGURATION, handleGetConfiguration);
|
||||||
|
ipcMain.handle(GET_LOCAL_CONFIGURATION, handleGetLocalConfiguration);
|
||||||
|
ipcMain.handle(UPDATE_TEAMS, handleUpdateTeams);
|
||||||
|
ipcMain.on(UPDATE_CONFIGURATION, updateConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeAfterAppReady() {
|
function initializeAfterAppReady() {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {getDefaultTeamWithTabsFromTeam} from 'common/tabs/TabView';
|
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||||
|
|
||||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
@@ -20,10 +20,10 @@ import {
|
|||||||
} from './intercom';
|
} from './intercom';
|
||||||
|
|
||||||
jest.mock('common/config', () => ({
|
jest.mock('common/config', () => ({
|
||||||
set: jest.fn(),
|
setServers: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('common/tabs/TabView', () => ({
|
jest.mock('common/tabs/TabView', () => ({
|
||||||
getDefaultTeamWithTabsFromTeam: jest.fn(),
|
getDefaultConfigTeamFromTeam: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('main/notifications', () => ({}));
|
jest.mock('main/notifications', () => ({}));
|
||||||
jest.mock('main/utils', () => ({
|
jest.mock('main/utils', () => ({
|
||||||
@@ -75,8 +75,8 @@ const teams = [
|
|||||||
describe('main/app/intercom', () => {
|
describe('main/app/intercom', () => {
|
||||||
describe('handleCloseTab', () => {
|
describe('handleCloseTab', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||||
});
|
});
|
||||||
@@ -94,8 +94,8 @@ describe('main/app/intercom', () => {
|
|||||||
|
|
||||||
describe('handleOpenTab', () => {
|
describe('handleOpenTab', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||||
});
|
});
|
||||||
@@ -117,12 +117,12 @@ describe('main/app/intercom', () => {
|
|||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
MainWindow.get.mockReturnValue({});
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||||
|
|
||||||
getDefaultTeamWithTabsFromTeam.mockImplementation((team) => ({
|
getDefaultConfigTeamFromTeam.mockImplementation((team) => ({
|
||||||
...team,
|
...team,
|
||||||
tabs,
|
tabs,
|
||||||
}));
|
}));
|
||||||
@@ -156,8 +156,8 @@ describe('main/app/intercom', () => {
|
|||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
MainWindow.get.mockReturnValue({});
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||||
});
|
});
|
||||||
@@ -199,8 +199,8 @@ describe('main/app/intercom', () => {
|
|||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
MainWindow.get.mockReturnValue({});
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.teams = JSON.parse(JSON.stringify(teams));
|
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||||
});
|
});
|
||||||
@@ -248,8 +248,8 @@ describe('main/app/intercom', () => {
|
|||||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||||
MainWindow.get.mockReturnValue({});
|
MainWindow.get.mockReturnValue({});
|
||||||
|
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.teams = JSON.parse(JSON.stringify([]));
|
Config.teams = JSON.parse(JSON.stringify([]));
|
||||||
});
|
});
|
||||||
@@ -271,8 +271,8 @@ describe('main/app/intercom', () => {
|
|||||||
isVisible: () => true,
|
isVisible: () => true,
|
||||||
});
|
});
|
||||||
|
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
Config.registryConfigData = {
|
Config.registryConfigData = {
|
||||||
teams: JSON.parse(JSON.stringify([{
|
teams: JSON.parse(JSON.stringify([{
|
||||||
@@ -281,6 +281,7 @@ describe('main/app/intercom', () => {
|
|||||||
url: 'https://someurl.here',
|
url: 'https://someurl.here',
|
||||||
}])),
|
}])),
|
||||||
};
|
};
|
||||||
|
Config.teams = JSON.parse(JSON.stringify(teams));
|
||||||
|
|
||||||
handleMainWindowIsShown();
|
handleMainWindowIsShown();
|
||||||
expect(ModalManager.addModal).not.toHaveBeenCalled();
|
expect(ModalManager.addModal).not.toHaveBeenCalled();
|
||||||
|
@@ -8,7 +8,7 @@ import {MentionData} from 'types/notification';
|
|||||||
|
|
||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
import {getDefaultTeamWithTabsFromTeam} from 'common/tabs/TabView';
|
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||||
import {ping} from 'common/utils/requests';
|
import {ping} from 'common/utils/requests';
|
||||||
|
|
||||||
import {displayMention} from 'main/notifications';
|
import {displayMention} from 'main/notifications';
|
||||||
@@ -69,7 +69,7 @@ export function handleCloseTab(event: IpcMainEvent, serverName: string, tabName:
|
|||||||
});
|
});
|
||||||
const nextTab = teams.find((team) => team.name === serverName)!.tabs.filter((tab) => tab.isOpen)[0].name;
|
const nextTab = teams.find((team) => team.name === serverName)!.tabs.filter((tab) => tab.isOpen)[0].name;
|
||||||
WindowManager.switchTab(serverName, nextTab);
|
WindowManager.switchTab(serverName, nextTab);
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleOpenTab(event: IpcMainEvent, serverName: string, tabName: string) {
|
export function handleOpenTab(event: IpcMainEvent, serverName: string, tabName: string) {
|
||||||
@@ -86,7 +86,7 @@ export function handleOpenTab(event: IpcMainEvent, serverName: string, tabName:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
WindowManager.switchTab(serverName, tabName);
|
WindowManager.switchTab(serverName, tabName);
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleShowOnboardingScreens(showWelcomeScreen: boolean, showNewServerModal: boolean, mainWindowIsVisible: boolean) {
|
export function handleShowOnboardingScreens(showWelcomeScreen: boolean, showNewServerModal: boolean, mainWindowIsVisible: boolean) {
|
||||||
@@ -153,9 +153,9 @@ export function handleNewServerModal() {
|
|||||||
modalPromise.then((data) => {
|
modalPromise.then((data) => {
|
||||||
const teams = Config.teams;
|
const teams = Config.teams;
|
||||||
const order = teams.length;
|
const order = teams.length;
|
||||||
const newTeam = getDefaultTeamWithTabsFromTeam({...data, order});
|
const newTeam = getDefaultConfigTeamFromTeam({...data, order});
|
||||||
teams.push(newTeam);
|
teams.push(newTeam);
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
updateServerInfos([newTeam]);
|
updateServerInfos([newTeam]);
|
||||||
WindowManager.switchServer(newTeam.name, true);
|
WindowManager.switchServer(newTeam.name, true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
@@ -198,7 +198,7 @@ export function handleEditServerModal(e: IpcMainEvent, name: string) {
|
|||||||
const teams = Config.teams;
|
const teams = Config.teams;
|
||||||
teams[serverIndex].name = data.name;
|
teams[serverIndex].name = data.name;
|
||||||
teams[serverIndex].url = data.url;
|
teams[serverIndex].url = data.url;
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
updateServerInfos([teams[serverIndex]]);
|
updateServerInfos([teams[serverIndex]]);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
// e is undefined for user cancellation
|
// e is undefined for user cancellation
|
||||||
@@ -238,7 +238,7 @@ export function handleRemoveServerModal(e: IpcMainEvent, name: string) {
|
|||||||
value.order--;
|
value.order--;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
}
|
}
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
// e is undefined for user cancellation
|
// e is undefined for user cancellation
|
||||||
@@ -267,9 +267,9 @@ export function handleWelcomeScreenModal() {
|
|||||||
modalPromise.then((data) => {
|
modalPromise.then((data) => {
|
||||||
const teams = Config.teams;
|
const teams = Config.teams;
|
||||||
const order = teams.length;
|
const order = teams.length;
|
||||||
const newTeam = getDefaultTeamWithTabsFromTeam({...data, order});
|
const newTeam = getDefaultConfigTeamFromTeam({...data, order});
|
||||||
teams.push(newTeam);
|
teams.push(newTeam);
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
updateServerInfos([newTeam]);
|
updateServerInfos([newTeam]);
|
||||||
WindowManager.switchServer(newTeam.name, true);
|
WindowManager.switchServer(newTeam.name, true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
@@ -324,10 +324,7 @@ export function handleUpdateLastActive(event: IpcMainEvent, serverName: string,
|
|||||||
team.lastActiveTab = viewOrder;
|
team.lastActiveTab = viewOrder;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Config.setMultiple({
|
Config.setServers(teams, teams.find((team) => team.name === serverName)?.order || 0);
|
||||||
teams,
|
|
||||||
lastActiveTeam: teams.find((team) => team.name === serverName)?.order || 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handlePingDomain(event: IpcMainInvokeEvent, url: string): Promise<string> {
|
export function handlePingDomain(event: IpcMainInvokeEvent, url: string): Promise<string> {
|
||||||
|
@@ -40,7 +40,7 @@ jest.mock('electron', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('common/config', () => ({
|
jest.mock('common/config', () => ({
|
||||||
set: jest.fn(),
|
setServers: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('common/JsonFileManager');
|
jest.mock('common/JsonFileManager');
|
||||||
jest.mock('common/utils/util', () => ({
|
jest.mock('common/utils/util', () => ({
|
||||||
@@ -95,8 +95,8 @@ describe('main/app/utils', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Utils.isVersionGreaterThanOrEqualTo.mockImplementation((version) => version === '6.0.0');
|
Utils.isVersionGreaterThanOrEqualTo.mockImplementation((version) => version === '6.0.0');
|
||||||
Config.set.mockImplementation((name, value) => {
|
Config.setServers.mockImplementation((value) => {
|
||||||
Config[name] = value;
|
Config.teams = value;
|
||||||
});
|
});
|
||||||
const teamsCopy = JSON.parse(JSON.stringify(teams));
|
const teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||||
Config.teams = teamsCopy;
|
Config.teams = teamsCopy;
|
||||||
|
@@ -47,8 +47,8 @@ export function openDeepLink(deeplinkingUrl: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateSpellCheckerLocales() {
|
export function updateSpellCheckerLocales() {
|
||||||
if (Config.data?.spellCheckerLocales.length && app.isReady()) {
|
if (Config.spellCheckerLocales.length && app.isReady()) {
|
||||||
session.defaultSession.setSpellCheckerLanguages(Config.data?.spellCheckerLocales);
|
session.defaultSession.setSpellCheckerLanguages(Config.spellCheckerLocales);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ export function updateServerInfos(teams: TeamWithTabs[]) {
|
|||||||
hasUpdates = hasUpdates || openExtraTabs(data, team);
|
hasUpdates = hasUpdates || openExtraTabs(data, team);
|
||||||
});
|
});
|
||||||
if (hasUpdates) {
|
if (hasUpdates) {
|
||||||
Config.set('teams', teams);
|
Config.setServers(teams);
|
||||||
}
|
}
|
||||||
}).catch((reason: any) => {
|
}).catch((reason: any) => {
|
||||||
log.error('Error getting server infos', reason);
|
log.error('Error getting server infos', reason);
|
||||||
|
@@ -8,6 +8,8 @@ import {DiagnosticStepResponse} from 'types/diagnostics';
|
|||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import * as Validator from 'common/Validator';
|
import * as Validator from 'common/Validator';
|
||||||
|
|
||||||
|
import {configPath} from 'main/constants';
|
||||||
|
|
||||||
import DiagnosticsStep from '../DiagnosticStep';
|
import DiagnosticsStep from '../DiagnosticStep';
|
||||||
|
|
||||||
const stepName = 'Step-2';
|
const stepName = 'Step-2';
|
||||||
@@ -15,13 +17,13 @@ const stepDescriptiveName = 'configValidation';
|
|||||||
|
|
||||||
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
|
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
|
||||||
try {
|
try {
|
||||||
const configData = JSON.parse(fs.readFileSync(Config.configFilePath, 'utf8'));
|
const configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
|
||||||
// validate based on config file version
|
// validate based on config file version
|
||||||
const validData = Validator.validateConfigData(configData);
|
const validData = Validator.validateConfigData(configData);
|
||||||
|
|
||||||
if (!validData) {
|
if (!validData) {
|
||||||
throw new Error(`Config validation failed. Config: ${JSON.stringify(Config.combinedData, null, 4)}`);
|
throw new Error(`Config validation failed. Config: ${JSON.stringify(Config.data, null, 4)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -15,7 +15,7 @@ const stepDescriptiveName = 'serverConnectivity';
|
|||||||
|
|
||||||
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
|
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
|
||||||
try {
|
try {
|
||||||
const teams = Config.combinedData?.teams || [];
|
const teams = Config.teams || [];
|
||||||
|
|
||||||
await Promise.all(teams.map(async (team) => {
|
await Promise.all(teams.map(async (team) => {
|
||||||
logger.debug('Pinging server: ', team.url);
|
logger.debug('Pinging server: ', team.url);
|
||||||
|
@@ -65,7 +65,6 @@ jest.mock('common/tabs/TabView', () => ({
|
|||||||
|
|
||||||
describe('main/menus/app', () => {
|
describe('main/menus/app', () => {
|
||||||
const config = {
|
const config = {
|
||||||
data: {
|
|
||||||
enableServerManagement: true,
|
enableServerManagement: true,
|
||||||
teams: [{
|
teams: [{
|
||||||
name: 'example',
|
name: 'example',
|
||||||
@@ -113,7 +112,6 @@ describe('main/menus/app', () => {
|
|||||||
lastActiveTab: 0,
|
lastActiveTab: 0,
|
||||||
}],
|
}],
|
||||||
helpLink: 'http://link-to-help.site.com',
|
helpLink: 'http://link-to-help.site.com',
|
||||||
},
|
|
||||||
};
|
};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||||
@@ -218,7 +216,7 @@ describe('main/menus/app', () => {
|
|||||||
const menu = createTemplate(modifiedConfig);
|
const menu = createTemplate(modifiedConfig);
|
||||||
const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File');
|
const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File');
|
||||||
const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server');
|
const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server');
|
||||||
expect(signInOption).not.toBe(undefined);
|
expect(signInOption).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show `Sign in to Another Server` if no teams are configured', () => {
|
it('should not show `Sign in to Another Server` if no teams are configured', () => {
|
||||||
@@ -239,7 +237,7 @@ describe('main/menus/app', () => {
|
|||||||
const menu = createTemplate(modifiedConfig);
|
const menu = createTemplate(modifiedConfig);
|
||||||
const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File');
|
const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File');
|
||||||
const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server');
|
const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server');
|
||||||
expect(signInOption).not.toBe(undefined);
|
expect(signInOption).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show the first 9 servers (using order) in the Window menu', () => {
|
it('should show the first 9 servers (using order) in the Window menu', () => {
|
||||||
@@ -250,8 +248,6 @@ describe('main/menus/app', () => {
|
|||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
const modifiedConfig = {
|
const modifiedConfig = {
|
||||||
data: {
|
|
||||||
...config.data,
|
|
||||||
teams: [...Array(15).keys()].map((key) => ({
|
teams: [...Array(15).keys()].map((key) => ({
|
||||||
name: `server-${key}`,
|
name: `server-${key}`,
|
||||||
url: `http://server-${key}.com`,
|
url: `http://server-${key}.com`,
|
||||||
@@ -264,7 +260,6 @@ describe('main/menus/app', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})),
|
})),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const menu = createTemplate(modifiedConfig);
|
const menu = createTemplate(modifiedConfig);
|
||||||
const windowMenu = menu.find((item) => item.label === '&Window');
|
const windowMenu = menu.find((item) => item.label === '&Window');
|
||||||
@@ -292,14 +287,12 @@ describe('main/menus/app', () => {
|
|||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
WindowManager.getCurrentTeamName.mockImplementation(() => config.data.teams[0].name);
|
WindowManager.getCurrentTeamName.mockImplementation(() => config.teams[0].name);
|
||||||
|
|
||||||
const modifiedConfig = {
|
const modifiedConfig = {
|
||||||
data: {
|
|
||||||
...config.data,
|
|
||||||
teams: [
|
teams: [
|
||||||
{
|
{
|
||||||
...config.data.teams[0],
|
...config.teams[0],
|
||||||
tabs: [...Array(15).keys()].map((key) => ({
|
tabs: [...Array(15).keys()].map((key) => ({
|
||||||
name: `tab-${key}`,
|
name: `tab-${key}`,
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
@@ -307,7 +300,6 @@ describe('main/menus/app', () => {
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const menu = createTemplate(modifiedConfig);
|
const menu = createTemplate(modifiedConfig);
|
||||||
const windowMenu = menu.find((item) => item.label === '&Window');
|
const windowMenu = menu.find((item) => item.label === '&Window');
|
||||||
|
@@ -49,7 +49,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.data?.enableServerManagement === true && config.data?.teams.length > 0) {
|
if (config.enableServerManagement === true && config.teams.length > 0) {
|
||||||
platformAppMenu.push({
|
platformAppMenu.push({
|
||||||
label: localizeMessage('main.menus.app.file.signInToAnotherServer', 'Sign in to Another Server'),
|
label: localizeMessage('main.menus.app.file.signInToAnotherServer', 'Sign in to Another Server'),
|
||||||
click() {
|
click() {
|
||||||
@@ -203,7 +203,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
|||||||
viewSubMenu.push({
|
viewSubMenu.push({
|
||||||
label: localizeMessage('main.menus.app.view.toggleDarkMode', 'Toggle Dark Mode'),
|
label: localizeMessage('main.menus.app.view.toggleDarkMode', 'Toggle Dark Mode'),
|
||||||
click() {
|
click() {
|
||||||
config.toggleDarkModeManually();
|
config.set('darkMode', !config.darkMode);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
|||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
const teams = config.data?.teams || [];
|
const teams = config.teams || [];
|
||||||
const windowMenu = {
|
const windowMenu = {
|
||||||
id: 'window',
|
id: 'window',
|
||||||
label: localizeMessage('main.menus.app.window', '&Window'),
|
label: localizeMessage('main.menus.app.window', '&Window'),
|
||||||
@@ -251,7 +251,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
|||||||
label: isMac ? localizeMessage('main.menus.app.window.closeWindow', 'Close Window') : localizeMessage('main.menus.app.window.close', 'Close'),
|
label: isMac ? localizeMessage('main.menus.app.window.closeWindow', 'Close Window') : localizeMessage('main.menus.app.window.close', 'Close'),
|
||||||
accelerator: 'CmdOrCtrl+W',
|
accelerator: 'CmdOrCtrl+W',
|
||||||
}, separatorItem,
|
}, separatorItem,
|
||||||
...(config.data?.teams.length ? [{
|
...(config.teams.length ? [{
|
||||||
label: localizeMessage('main.menus.app.window.showServers', 'Show Servers'),
|
label: localizeMessage('main.menus.app.window.showServers', 'Show Servers'),
|
||||||
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`,
|
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`,
|
||||||
click() {
|
click() {
|
||||||
@@ -325,11 +325,11 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config.data?.helpLink) {
|
if (config.helpLink) {
|
||||||
submenu.push({
|
submenu.push({
|
||||||
label: localizeMessage('main.menus.app.help.learnMore', 'Learn More...'),
|
label: localizeMessage('main.menus.app.help.learnMore', 'Learn More...'),
|
||||||
click() {
|
click() {
|
||||||
shell.openExternal(config.data!.helpLink);
|
shell.openExternal(config.helpLink!);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
submenu.push(separatorItem);
|
submenu.push(separatorItem);
|
||||||
|
@@ -19,6 +19,7 @@ import {
|
|||||||
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
import Config from 'common/config';
|
||||||
import {
|
import {
|
||||||
DOWNLOADS_DROPDOWN_FULL_WIDTH,
|
DOWNLOADS_DROPDOWN_FULL_WIDTH,
|
||||||
DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT,
|
DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT,
|
||||||
@@ -26,10 +27,9 @@ import {
|
|||||||
TAB_BAR_HEIGHT,
|
TAB_BAR_HEIGHT,
|
||||||
} from 'common/utils/constants';
|
} from 'common/utils/constants';
|
||||||
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
|
|
||||||
import WindowManager from '../windows/windowManager';
|
|
||||||
import downloadsManager from 'main/downloadsManager';
|
import downloadsManager from 'main/downloadsManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
import WindowManager from 'main/windows/windowManager';
|
||||||
|
|
||||||
const log = new Logger('DownloadsDropdownMenuView');
|
const log = new Logger('DownloadsDropdownMenuView');
|
||||||
|
|
||||||
@@ -42,11 +42,11 @@ export default class DownloadsDropdownMenuView {
|
|||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
windowBounds: Electron.Rectangle;
|
windowBounds: Electron.Rectangle;
|
||||||
|
|
||||||
constructor(darkMode: boolean) {
|
constructor() {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.item = undefined;
|
this.item = undefined;
|
||||||
this.coordinates = undefined;
|
this.coordinates = undefined;
|
||||||
this.darkMode = darkMode;
|
this.darkMode = Config.darkMode;
|
||||||
|
|
||||||
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN_MENU, this.handleOpen);
|
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN_MENU, this.handleOpen);
|
||||||
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN_MENU, this.handleClose);
|
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN_MENU, this.handleClose);
|
||||||
|
@@ -62,6 +62,7 @@ jest.mock('electron', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
jest.mock('main/downloadsManager', () => ({
|
jest.mock('main/downloadsManager', () => ({
|
||||||
|
getDownloads: jest.fn(),
|
||||||
onOpen: jest.fn(),
|
onOpen: jest.fn(),
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
@@ -19,11 +19,12 @@ import {
|
|||||||
DOWNLOADS_DROPDOWN_OPEN_FILE,
|
DOWNLOADS_DROPDOWN_OPEN_FILE,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
import Config from 'common/config';
|
||||||
import {TAB_BAR_HEIGHT, DOWNLOADS_DROPDOWN_WIDTH, DOWNLOADS_DROPDOWN_HEIGHT, DOWNLOADS_DROPDOWN_FULL_WIDTH} from 'common/utils/constants';
|
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 WindowManager from '../windows/windowManager';
|
import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
import downloadsManager from 'main/downloadsManager';
|
import downloadsManager from 'main/downloadsManager';
|
||||||
|
import WindowManager from 'main/windows/windowManager';
|
||||||
import MainWindow from 'main/windows/mainWindow';
|
import MainWindow from 'main/windows/mainWindow';
|
||||||
|
|
||||||
const log = new Logger('DownloadsDropdownView');
|
const log = new Logger('DownloadsDropdownView');
|
||||||
@@ -36,9 +37,9 @@ export default class DownloadsDropdownView {
|
|||||||
view: BrowserView;
|
view: BrowserView;
|
||||||
windowBounds: Electron.Rectangle;
|
windowBounds: Electron.Rectangle;
|
||||||
|
|
||||||
constructor(downloads: DownloadedItems, darkMode: boolean) {
|
constructor() {
|
||||||
this.downloads = downloads;
|
this.downloads = downloadsManager.getDownloads();
|
||||||
this.darkMode = darkMode;
|
this.darkMode = Config.darkMode;
|
||||||
|
|
||||||
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN, this.handleOpen);
|
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN, this.handleOpen);
|
||||||
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN, this.handleClose);
|
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN, this.handleClose);
|
||||||
|
@@ -15,10 +15,6 @@ jest.mock('electron', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('common/config', () => ({
|
|
||||||
teams: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('main/views/webContentEvents', () => ({
|
jest.mock('main/views/webContentEvents', () => ({
|
||||||
addWebContentsEventListeners: jest.fn(),
|
addWebContentsEventListeners: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
@@ -17,7 +17,6 @@ import {
|
|||||||
GET_MODAL_UNCLOSEABLE,
|
GET_MODAL_UNCLOSEABLE,
|
||||||
RESIZE_MODAL,
|
RESIZE_MODAL,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import Config from 'common/config';
|
|
||||||
import {Logger} from 'common/log';
|
import {Logger} from 'common/log';
|
||||||
|
|
||||||
import {getAdjustedWindowBoundaries} from 'main/utils';
|
import {getAdjustedWindowBoundaries} from 'main/utils';
|
||||||
@@ -91,7 +90,7 @@ export class ModalManager {
|
|||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
WindowManager.sendToRenderer(MODAL_OPEN);
|
WindowManager.sendToRenderer(MODAL_OPEN);
|
||||||
modal.show(undefined, Boolean(withDevTools));
|
modal.show(undefined, Boolean(withDevTools));
|
||||||
WebContentsEventManager.addWebContentsEventListeners(modal.view.webContents, () => Config.teams.concat());
|
WebContentsEventManager.addWebContentsEventListeners(modal.view.webContents);
|
||||||
} else {
|
} else {
|
||||||
WindowManager.sendToRenderer(MODAL_CLOSE);
|
WindowManager.sendToRenderer(MODAL_CLOSE);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
|
@@ -42,7 +42,7 @@ describe('main/views/teamDropdownView', () => {
|
|||||||
MainWindow.getBounds.mockReturnValue({width: 500, height: 400, x: 0, y: 0});
|
MainWindow.getBounds.mockReturnValue({width: 500, height: 400, x: 0, y: 0});
|
||||||
});
|
});
|
||||||
|
|
||||||
const teamDropdownView = new TeamDropdownView([], false, true);
|
const teamDropdownView = new TeamDropdownView();
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
it('should account for three dot menu, tab bar and shadow', () => {
|
it('should account for three dot menu, tab bar and shadow', () => {
|
||||||
expect(teamDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH_MAC - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
|
expect(teamDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH_MAC - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
|
||||||
@@ -55,7 +55,7 @@ describe('main/views/teamDropdownView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should change the view bounds based on open/closed state', () => {
|
it('should change the view bounds based on open/closed state', () => {
|
||||||
const teamDropdownView = new TeamDropdownView([], false, true);
|
const teamDropdownView = new TeamDropdownView();
|
||||||
teamDropdownView.bounds = {width: 400, height: 300};
|
teamDropdownView.bounds = {width: 400, height: 300};
|
||||||
teamDropdownView.handleOpen();
|
teamDropdownView.handleOpen();
|
||||||
expect(teamDropdownView.view.setBounds).toBeCalledWith(teamDropdownView.bounds);
|
expect(teamDropdownView.view.setBounds).toBeCalledWith(teamDropdownView.bounds);
|
||||||
@@ -65,7 +65,7 @@ describe('main/views/teamDropdownView', () => {
|
|||||||
|
|
||||||
describe('addGpoToTeams', () => {
|
describe('addGpoToTeams', () => {
|
||||||
it('should return teams with "isGPO": false when no config.registryTeams exist', () => {
|
it('should return teams with "isGPO": false when no config.registryTeams exist', () => {
|
||||||
const teamDropdownView = new TeamDropdownView([], false, true);
|
const teamDropdownView = new TeamDropdownView();
|
||||||
const teams = [{
|
const teams = [{
|
||||||
name: 'team-1',
|
name: 'team-1',
|
||||||
url: 'https://mattermost.team-1.com',
|
url: 'https://mattermost.team-1.com',
|
||||||
@@ -86,7 +86,7 @@ describe('main/views/teamDropdownView', () => {
|
|||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
it('should return teams with "isGPO": true if they exist in config.registryTeams', () => {
|
it('should return teams with "isGPO": true if they exist in config.registryTeams', () => {
|
||||||
const teamDropdownView = new TeamDropdownView([], false, true);
|
const teamDropdownView = new TeamDropdownView();
|
||||||
const teams = [{
|
const teams = [{
|
||||||
name: 'team-1',
|
name: 'team-1',
|
||||||
url: 'https://mattermost.team-1.com',
|
url: 'https://mattermost.team-1.com',
|
||||||
|
@@ -15,9 +15,12 @@ import {
|
|||||||
RECEIVE_DROPDOWN_MENU_SIZE,
|
RECEIVE_DROPDOWN_MENU_SIZE,
|
||||||
SET_ACTIVE_VIEW,
|
SET_ACTIVE_VIEW,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
|
import Config from 'common/config';
|
||||||
import {Logger} from 'common/log';
|
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 {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 {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||||
|
|
||||||
import * as AppState from '../appState';
|
import * as AppState from '../appState';
|
||||||
import WindowManager from '../windows/windowManager';
|
import WindowManager from '../windows/windowManager';
|
||||||
import MainWindow from '../windows/mainWindow';
|
import MainWindow from '../windows/mainWindow';
|
||||||
@@ -38,10 +41,10 @@ export default class TeamDropdownView {
|
|||||||
windowBounds?: Electron.Rectangle;
|
windowBounds?: Electron.Rectangle;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
||||||
constructor(teams: TeamWithTabs[], darkMode: boolean, enableServerManagement: boolean) {
|
constructor() {
|
||||||
this.teams = this.addGpoToTeams(teams, []);
|
this.teams = this.addGpoToTeams(Config.teams, []);
|
||||||
this.darkMode = darkMode;
|
this.darkMode = Config.darkMode;
|
||||||
this.enableServerManagement = enableServerManagement;
|
this.enableServerManagement = Config.enableServerManagement;
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
|
|
||||||
this.windowBounds = MainWindow.getBounds();
|
this.windowBounds = MainWindow.getBounds();
|
||||||
|
@@ -36,7 +36,6 @@ import {SECOND} from 'common/utils/constants';
|
|||||||
import Config from 'common/config';
|
import Config from 'common/config';
|
||||||
import {getTabViewName} from 'common/tabs/TabView';
|
import {getTabViewName} from 'common/tabs/TabView';
|
||||||
|
|
||||||
import downloadsManager from 'main/downloadsManager';
|
|
||||||
import {MattermostView} from 'main/views/MattermostView';
|
import {MattermostView} from 'main/views/MattermostView';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -208,9 +207,9 @@ export class WindowManager {
|
|||||||
mainWindow.on('enter-full-screen', () => this.sendToRenderer('enter-full-screen'));
|
mainWindow.on('enter-full-screen', () => this.sendToRenderer('enter-full-screen'));
|
||||||
mainWindow.on('leave-full-screen', () => this.sendToRenderer('leave-full-screen'));
|
mainWindow.on('leave-full-screen', () => this.sendToRenderer('leave-full-screen'));
|
||||||
|
|
||||||
this.teamDropdown = new TeamDropdownView(Config.teams, Config.darkMode, Config.enableServerManagement);
|
this.teamDropdown = new TeamDropdownView();
|
||||||
this.downloadsDropdown = new DownloadsDropdownView(downloadsManager.getDownloads(), Config.darkMode);
|
this.downloadsDropdown = new DownloadsDropdownView();
|
||||||
this.downloadsDropdownMenu = new DownloadsDropdownMenuView(Config.darkMode);
|
this.downloadsDropdownMenu = new DownloadsDropdownMenuView();
|
||||||
|
|
||||||
this.initializeViewManager();
|
this.initializeViewManager();
|
||||||
}
|
}
|
||||||
|
@@ -120,7 +120,7 @@ export type BuildConfig = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RegistryConfig = {
|
export type RegistryConfig = {
|
||||||
teams: FullTeam[];
|
teams: Team[];
|
||||||
enableServerManagement: boolean;
|
enableServerManagement: boolean;
|
||||||
enableAutoUpdater: boolean;
|
enableAutoUpdater: boolean;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user