[MM-14093] Rename 'team' to 'server' and 'tab' to 'view' in most cases, some additional cleanup (#2711)
* Rename MattermostTeam -> UniqueServer, MattermostTab -> UniqueView * Rename 'team' to 'server' * Some further cleanup * Rename weirdly named function * Rename 'tab' to 'view' in most instances * Fix i18n * PR feedback
This commit is contained in:
@@ -48,10 +48,10 @@ You're now all set! See the [User Guide](#user-guide) below for instructions.
|
||||
|
||||
## User Guide
|
||||
|
||||
After launching, you need to configure the application to interact with your team.
|
||||
After launching, you need to configure the application to interact with your server.
|
||||
|
||||
1. If you don't see a page titled "Settings", select **File** > **Settings...** from the menu bar.
|
||||
2. Click **Add new team** next to the right of Team Management section.
|
||||
2. Click **Add new server** next to the right of Server Management section.
|
||||
3. Enter **Name** and a valid **URL**, which begins with either `http://` or `https://`.
|
||||
4. Click **Add**.
|
||||
5. Click **Save**.
|
||||
|
@@ -153,7 +153,7 @@ describe('common/Validator', () => {
|
||||
version: 3,
|
||||
};
|
||||
|
||||
it('should ensure messaging tab is open', () => {
|
||||
it('should ensure messaging view is open', () => {
|
||||
const modifiedConfig = {
|
||||
...config,
|
||||
teams: [
|
||||
|
@@ -12,7 +12,7 @@ import {ComparableCertificate} from 'types/certificate';
|
||||
import {PermissionType, TrustedOrigin} from 'types/trustedOrigin';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import {TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
import {TAB_MESSAGING} from 'common/views/View';
|
||||
import {isValidURL} from 'common/utils/url';
|
||||
|
||||
const log = new Logger('Validator');
|
||||
@@ -206,45 +206,45 @@ function cleanURL(url: string): string {
|
||||
return updatedURL;
|
||||
}
|
||||
|
||||
function cleanTeam<T extends {name: string; url: string}>(team: T) {
|
||||
function cleanServer<T extends {name: string; url: string}>(server: T) {
|
||||
return {
|
||||
...team,
|
||||
url: cleanURL(team.url),
|
||||
...server,
|
||||
url: cleanURL(server.url),
|
||||
};
|
||||
}
|
||||
|
||||
function cleanTeamWithTabs(team: ConfigServer) {
|
||||
function cleanServerWithViews(server: ConfigServer) {
|
||||
return {
|
||||
...cleanTeam(team),
|
||||
tabs: team.tabs.map((tab) => {
|
||||
...cleanServer(server),
|
||||
tabs: server.tabs.map((view) => {
|
||||
return {
|
||||
...tab,
|
||||
isOpen: tab.name === TAB_MESSAGING ? true : tab.isOpen,
|
||||
...view,
|
||||
isOpen: view.name === TAB_MESSAGING ? true : view.isOpen,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function cleanTeams<T extends {name: string; url: string}>(teams: T[], func: (team: T) => T) {
|
||||
let newTeams = teams;
|
||||
if (Array.isArray(newTeams) && newTeams.length) {
|
||||
function cleanServers<T extends {name: string; url: string}>(servers: T[], func: (server: T) => T) {
|
||||
let newServers = servers;
|
||||
if (Array.isArray(newServers) && newServers.length) {
|
||||
// first replace possible backslashes with forward slashes
|
||||
newTeams = newTeams.map((team) => func(team));
|
||||
newServers = newServers.map((server) => func(server));
|
||||
|
||||
// next filter out urls that are still invalid so all is not lost
|
||||
newTeams = newTeams.filter(({url}) => isValidURL(url));
|
||||
newServers = newServers.filter(({url}) => isValidURL(url));
|
||||
}
|
||||
return newTeams;
|
||||
return newServers;
|
||||
}
|
||||
|
||||
// validate v.1 config.json
|
||||
export function validateV1ConfigData(data: ConfigV1) {
|
||||
data.teams = cleanTeams(data.teams, cleanTeam);
|
||||
data.teams = cleanServers(data.teams, cleanServer);
|
||||
return validateAgainstSchema(data, configDataSchemaV1);
|
||||
}
|
||||
|
||||
export function validateV2ConfigData(data: ConfigV2) {
|
||||
data.teams = cleanTeams(data.teams, cleanTeam);
|
||||
data.teams = cleanServers(data.teams, cleanServer);
|
||||
if (data.spellCheckerURL && !isValidURL(data.spellCheckerURL)) {
|
||||
log.error('Invalid download location for spellchecker dictionary, removing from config');
|
||||
delete data.spellCheckerURL;
|
||||
@@ -253,7 +253,7 @@ export function validateV2ConfigData(data: ConfigV2) {
|
||||
}
|
||||
|
||||
export function validateV3ConfigData(data: ConfigV3) {
|
||||
data.teams = cleanTeams(data.teams, cleanTeamWithTabs);
|
||||
data.teams = cleanServers(data.teams, cleanServerWithViews);
|
||||
if (data.spellCheckerURL && !isValidURL(data.spellCheckerURL)) {
|
||||
log.error('Invalid download location for spellchecker dictionary, removing from config');
|
||||
delete data.spellCheckerURL;
|
||||
|
@@ -3,8 +3,8 @@
|
||||
|
||||
export const SWITCH_SERVER = 'switch-server';
|
||||
export const SWITCH_TAB = 'switch-tab';
|
||||
export const CLOSE_TAB = 'close-tab';
|
||||
export const OPEN_TAB = 'open-tab';
|
||||
export const CLOSE_VIEW = 'close-view';
|
||||
export const OPEN_VIEW = 'open-view';
|
||||
export const SET_ACTIVE_VIEW = 'set-active-view';
|
||||
export const FOCUS_BROWSERVIEW = 'focus-browserview';
|
||||
export const HISTORY = 'history';
|
||||
@@ -76,10 +76,10 @@ export const FOCUS_THREE_DOT_MENU = 'focus-three-dot-menu';
|
||||
|
||||
export const LOADSCREEN_END = 'loadscreen-end';
|
||||
|
||||
export const OPEN_TEAMS_DROPDOWN = 'open-teams-dropdown';
|
||||
export const CLOSE_TEAMS_DROPDOWN = 'close-teams-dropdown';
|
||||
export const UPDATE_TEAMS_DROPDOWN = 'update-teams-dropdown';
|
||||
export const REQUEST_TEAMS_DROPDOWN_INFO = 'request-teams-dropdown-info';
|
||||
export const OPEN_SERVERS_DROPDOWN = 'open-servers-dropdown';
|
||||
export const CLOSE_SERVERS_DROPDOWN = 'close-servers-dropdown';
|
||||
export const UPDATE_SERVERS_DROPDOWN = 'update-servers-dropdown';
|
||||
export const REQUEST_SERVERS_DROPDOWN_INFO = 'request-servers-dropdown-info';
|
||||
export const RECEIVE_DROPDOWN_MENU_SIZE = 'receive-dropdown-menu-size';
|
||||
|
||||
export const UPDATE_AVAILABLE = 'update-available';
|
||||
|
@@ -64,7 +64,7 @@ describe('common/config/RegistryConfig', () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: originalPlatform,
|
||||
});
|
||||
expect(registryConfig.data.teams).toContainEqual({
|
||||
expect(registryConfig.data.servers).toContainEqual({
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
});
|
||||
|
@@ -6,7 +6,7 @@ import {EventEmitter} from 'events';
|
||||
import WindowsRegistry from 'winreg';
|
||||
import WindowsRegistryUTF8 from 'winreg-utf8';
|
||||
|
||||
import {RegistryConfig as RegistryConfigType, Team} from 'types/config';
|
||||
import {RegistryConfig as RegistryConfigType, Server} from 'types/config';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class RegistryConfig extends EventEmitter {
|
||||
super();
|
||||
this.initialized = false;
|
||||
this.data = {
|
||||
teams: [],
|
||||
servers: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class RegistryConfig extends EventEmitter {
|
||||
try {
|
||||
const servers = await this.getServersListFromRegistry();
|
||||
if (servers.length) {
|
||||
this.data.teams!.push(...servers);
|
||||
this.data.servers!.push(...servers);
|
||||
}
|
||||
} catch (error) {
|
||||
log.warn('Nothing retrieved for \'DefaultServerList\'', error);
|
||||
@@ -78,7 +78,7 @@ export default class RegistryConfig extends EventEmitter {
|
||||
*/
|
||||
async getServersListFromRegistry() {
|
||||
const defaultServers = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`);
|
||||
return defaultServers.flat(2).reduce((servers: Team[], server) => {
|
||||
return defaultServers.flat(2).reduce((servers: Server[], server) => {
|
||||
if (server) {
|
||||
servers.push({
|
||||
name: (server as WindowsRegistry.RegistryItem).name,
|
||||
|
@@ -8,20 +8,20 @@ import {BuildConfig} from 'types/config';
|
||||
|
||||
/**
|
||||
* Build-time configuration. End-users can't change these parameters.
|
||||
* @prop {Object[]} defaultTeams
|
||||
* @prop {string} defaultTeams[].name - The tab name for default team.
|
||||
* @prop {string} defaultTeams[].url - The URL for default team.
|
||||
* @prop {string} defaultTeams[].order - Sort order for team tabs (0, 1, 2)
|
||||
* @prop {Object[]} defaultServers
|
||||
* @prop {string} defaultServers[].name - The view name for default server.
|
||||
* @prop {string} defaultServers[].url - The URL for default server.
|
||||
* @prop {string} defaultServers[].order - Sort order for server views (0, 1, 2)
|
||||
* @prop {string} helpLink - The URL for "Help->Learn More..." menu item.
|
||||
* If null is specified, the menu disappears.
|
||||
* @prop {boolean} enableServerManagement - Whether users can edit servers configuration.
|
||||
* Specify at least one server for "defaultTeams"
|
||||
* Specify at least one server for "defaultServers"
|
||||
* when "enableServerManagement is set to false
|
||||
* @prop {[]} managedResources - Defines which paths are managed
|
||||
* @prop {[]} allowedProtocols - Defines which protocols should be automatically allowed
|
||||
*/
|
||||
const buildConfig: BuildConfig = {
|
||||
defaultTeams: [/*
|
||||
defaultServers: [/*
|
||||
{
|
||||
name: 'example',
|
||||
url: 'https://example.com'
|
||||
|
@@ -24,54 +24,54 @@ jest.mock('common/Validator', () => ({
|
||||
validateConfigData: (configData) => (configData.version === 3 ? configData : null),
|
||||
}));
|
||||
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getDefaultConfigTeamFromTeam: (value) => ({
|
||||
jest.mock('common/views/View', () => ({
|
||||
getDefaultViewsForConfigServer: (value) => ({
|
||||
...value,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
const buildTeam = {
|
||||
name: 'build-team-1',
|
||||
const buildServer = {
|
||||
name: 'build-server-1',
|
||||
order: 0,
|
||||
url: 'http://build-team-1.com',
|
||||
url: 'http://build-server-1.com',
|
||||
};
|
||||
|
||||
const buildTeamWithTabs = {
|
||||
...buildTeam,
|
||||
const buildServerWithViews = {
|
||||
...buildServer,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const registryTeam = {
|
||||
name: 'registry-team-1',
|
||||
const registryServer = {
|
||||
name: 'registry-server-1',
|
||||
order: 0,
|
||||
url: 'http://registry-team-1.com',
|
||||
url: 'http://registry-server-1.com',
|
||||
};
|
||||
|
||||
const team = {
|
||||
name: 'team-1',
|
||||
const server = {
|
||||
name: 'server-1',
|
||||
order: 0,
|
||||
url: 'http://team-1.com',
|
||||
url: 'http://server-1.com',
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -86,7 +86,7 @@ jest.mock('common/config/migrationPreferences', () => jest.fn());
|
||||
|
||||
jest.mock('common/config/buildConfig', () => {
|
||||
return {
|
||||
defaultTeams: [buildTeam],
|
||||
defaultServers: [buildServer],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('common/config', () => {
|
||||
const config = new Config();
|
||||
config.reload = jest.fn();
|
||||
config.init(configPath, appName, appPath);
|
||||
expect(config.predefinedTeams).toContainEqual(buildTeamWithTabs);
|
||||
expect(config.predefinedServers).toContainEqual(buildServerWithViews);
|
||||
});
|
||||
|
||||
describe('loadRegistry', () => {
|
||||
@@ -107,16 +107,16 @@ describe('common/config', () => {
|
||||
const config = new Config();
|
||||
config.reload = jest.fn();
|
||||
config.init(configPath, appName, appPath);
|
||||
config.onLoadRegistry({teams: [registryTeam]});
|
||||
config.onLoadRegistry({servers: [registryServer]});
|
||||
expect(config.reload).toHaveBeenCalled();
|
||||
expect(config.predefinedTeams).toContainEqual({
|
||||
...registryTeam,
|
||||
expect(config.predefinedServers).toContainEqual({
|
||||
...registryServer,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -159,19 +159,19 @@ describe('common/config', () => {
|
||||
expect(config.saveLocalConfigData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not allow teams to be set using this method', () => {
|
||||
it('should not allow servers to be set using this method', () => {
|
||||
const config = new Config();
|
||||
config.reload = jest.fn();
|
||||
config.init(configPath, appName, appPath);
|
||||
config.localConfigData = {teams: [team]};
|
||||
config.localConfigData = {teams: [server]};
|
||||
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);
|
||||
config.set('teams', [{...buildServerWithViews, name: 'build-team-2'}]);
|
||||
expect(config.localConfigData.teams).not.toContainEqual({...buildServerWithViews, name: 'build-team-2'});
|
||||
expect(config.localConfigData.teams).toContainEqual(server);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,8 +186,8 @@ describe('common/config', () => {
|
||||
});
|
||||
config.saveLocalConfigData = jest.fn();
|
||||
|
||||
config.setServers([{...buildTeamWithTabs, name: 'build-team-2'}, team], 0);
|
||||
expect(config.localConfigData.teams).toContainEqual({...buildTeamWithTabs, name: 'build-team-2'});
|
||||
config.setServers([{...buildServerWithViews, name: 'build-server-2'}, server], 0);
|
||||
expect(config.localConfigData.teams).toContainEqual({...buildServerWithViews, name: 'build-server-2'});
|
||||
expect(config.localConfigData.lastActiveTeam).toBe(0);
|
||||
expect(config.regenerateCombinedConfigData).toHaveBeenCalled();
|
||||
expect(config.saveLocalConfigData).toHaveBeenCalled();
|
||||
@@ -341,7 +341,7 @@ describe('common/config', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include any teams in the combined config', () => {
|
||||
it('should not include any servers in the combined config', () => {
|
||||
const config = new Config();
|
||||
config.reload = jest.fn();
|
||||
config.init(configPath, appName, appPath);
|
||||
@@ -349,20 +349,20 @@ describe('common/config', () => {
|
||||
config.localConfigData = {};
|
||||
config.buildConfigData = {enableServerManagement: true};
|
||||
config.registryConfigData = {};
|
||||
config.predefinedTeams.push(team, team);
|
||||
config.predefinedServers.push(server, server);
|
||||
config.useNativeWindow = false;
|
||||
config.localConfigData = {teams: [
|
||||
team,
|
||||
server,
|
||||
{
|
||||
...team,
|
||||
name: 'local-team-2',
|
||||
url: 'http://local-team-2.com',
|
||||
...server,
|
||||
name: 'local-server-2',
|
||||
url: 'http://local-server-2.com',
|
||||
},
|
||||
{
|
||||
...team,
|
||||
name: 'local-team-1',
|
||||
...server,
|
||||
name: 'local-server-1',
|
||||
order: 1,
|
||||
url: 'http://local-team-1.com',
|
||||
url: 'http://local-server-1.com',
|
||||
},
|
||||
]};
|
||||
|
||||
|
@@ -18,7 +18,7 @@ import {
|
||||
} from 'types/config';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||
import {getDefaultViewsForConfigServer} from 'common/views/View';
|
||||
import Utils, {copy} from 'common/utils/util';
|
||||
import * as Validator from 'common/Validator';
|
||||
|
||||
@@ -36,7 +36,7 @@ export class Config extends EventEmitter {
|
||||
private appPath?: string;
|
||||
|
||||
private registryConfig: RegistryConfig;
|
||||
private predefinedServers: ConfigServer[];
|
||||
private _predefinedServers: ConfigServer[];
|
||||
private useNativeWindow: boolean;
|
||||
|
||||
private combinedData?: CombinedConfig;
|
||||
@@ -49,9 +49,9 @@ export class Config extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.registryConfig = new RegistryConfig();
|
||||
this.predefinedServers = [];
|
||||
if (buildConfig.defaultTeams) {
|
||||
this.predefinedServers.push(...buildConfig.defaultTeams.map((team, index) => getDefaultConfigTeamFromTeam({...team, order: index})));
|
||||
this._predefinedServers = [];
|
||||
if (buildConfig.defaultServers) {
|
||||
this._predefinedServers.push(...buildConfig.defaultServers.map((server, index) => getDefaultViewsForConfigServer({...server, order: index})));
|
||||
}
|
||||
try {
|
||||
this.useNativeWindow = os.platform() === 'win32' && !Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.2');
|
||||
@@ -138,10 +138,10 @@ export class Config extends EventEmitter {
|
||||
this.saveLocalConfigData();
|
||||
}
|
||||
|
||||
setServers = (servers: ConfigServer[], lastActiveTeam?: number) => {
|
||||
log.debug('setServers', servers, lastActiveTeam);
|
||||
setServers = (servers: ConfigServer[], lastActiveServer?: number) => {
|
||||
log.debug('setServers', servers, lastActiveServer);
|
||||
|
||||
this.localConfigData = Object.assign({}, this.localConfigData, {teams: servers, lastActiveTeam: lastActiveTeam ?? this.localConfigData?.lastActiveTeam});
|
||||
this.localConfigData = Object.assign({}, this.localConfigData, {teams: servers, lastActiveTeam: lastActiveServer ?? this.localConfigData?.lastActiveTeam});
|
||||
this.regenerateCombinedConfigData();
|
||||
this.saveLocalConfigData();
|
||||
}
|
||||
@@ -172,11 +172,11 @@ export class Config extends EventEmitter {
|
||||
get darkMode() {
|
||||
return this.combinedData?.darkMode ?? defaultPreferences.darkMode;
|
||||
}
|
||||
get localTeams() {
|
||||
get localServers() {
|
||||
return this.localConfigData?.teams ?? defaultPreferences.teams;
|
||||
}
|
||||
get predefinedTeams() {
|
||||
return this.predefinedServers;
|
||||
get predefinedServers() {
|
||||
return this._predefinedServers;
|
||||
}
|
||||
get enableHardwareAcceleration() {
|
||||
return this.combinedData?.enableHardwareAcceleration ?? defaultPreferences.enableHardwareAcceleration;
|
||||
@@ -229,7 +229,7 @@ export class Config extends EventEmitter {
|
||||
get minimizeToTray() {
|
||||
return this.combinedData?.minimizeToTray;
|
||||
}
|
||||
get lastActiveTeam() {
|
||||
get lastActiveServer() {
|
||||
return this.combinedData?.lastActiveTeam;
|
||||
}
|
||||
get alwaysClose() {
|
||||
@@ -252,17 +252,17 @@ export class Config extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the teams from registry into the config object and reload
|
||||
* Gets the servers from registry into the config object and reload
|
||||
*
|
||||
* @param {object} registryData Team configuration from the registry and if teams can be managed by user
|
||||
* @param {object} registryData Server configuration from the registry and if servers can be managed by user
|
||||
*/
|
||||
|
||||
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})));
|
||||
if (this.registryConfigData.servers) {
|
||||
this._predefinedServers.push(...this.registryConfigData.servers.map((server, index) => getDefaultViewsForConfigServer({...server, order: index})));
|
||||
}
|
||||
this.reload();
|
||||
}
|
||||
@@ -378,7 +378,8 @@ export class Config extends EventEmitter {
|
||||
|
||||
// We don't want to include the servers in the combined config, they should only be accesible via the ServerManager
|
||||
delete (this.combinedData as any).teams;
|
||||
delete (this.combinedData as any).defaultTeams;
|
||||
delete (this.combinedData as any).servers;
|
||||
delete (this.combinedData as any).defaultServers;
|
||||
|
||||
if (this.combinedData) {
|
||||
this.combinedData.appName = this.appName;
|
||||
|
@@ -4,15 +4,15 @@
|
||||
import {upgradeV0toV1, upgradeV1toV2, upgradeV2toV3} from 'common/config/upgradePreferences';
|
||||
import pastDefaultPreferences from 'common/config/pastDefaultPreferences';
|
||||
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getDefaultConfigTeamFromTeam: (value) => ({
|
||||
jest.mock('common/views/View', () => ({
|
||||
getDefaultViewsForConfigServer: (value) => ({
|
||||
...value,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -27,7 +27,7 @@ describe('common/config/upgradePreferences', () => {
|
||||
version: 1,
|
||||
teams: [
|
||||
{
|
||||
name: 'Primary team',
|
||||
name: 'Primary server',
|
||||
url: config.url,
|
||||
},
|
||||
],
|
||||
@@ -39,10 +39,10 @@ describe('common/config/upgradePreferences', () => {
|
||||
const config = {
|
||||
version: 1,
|
||||
teams: [{
|
||||
name: 'Primary team',
|
||||
name: 'Primary server',
|
||||
url: 'http://server-1.com',
|
||||
}, {
|
||||
name: 'Secondary team',
|
||||
name: 'Secondary server',
|
||||
url: 'http://server-2.com',
|
||||
}],
|
||||
showTrayIcon: true,
|
||||
@@ -64,11 +64,11 @@ describe('common/config/upgradePreferences', () => {
|
||||
...config,
|
||||
version: 2,
|
||||
teams: [{
|
||||
name: 'Primary team',
|
||||
name: 'Primary server',
|
||||
url: 'http://server-1.com',
|
||||
order: 0,
|
||||
}, {
|
||||
name: 'Secondary team',
|
||||
name: 'Secondary server',
|
||||
url: 'http://server-2.com',
|
||||
order: 1,
|
||||
}],
|
||||
@@ -80,11 +80,11 @@ describe('common/config/upgradePreferences', () => {
|
||||
const config = {
|
||||
version: 2,
|
||||
teams: [{
|
||||
name: 'Primary team',
|
||||
name: 'Primary server',
|
||||
url: 'http://server-1.com',
|
||||
order: 0,
|
||||
}, {
|
||||
name: 'Secondary team',
|
||||
name: 'Secondary server',
|
||||
url: 'http://server-2.com',
|
||||
order: 1,
|
||||
}],
|
||||
@@ -111,27 +111,27 @@ describe('common/config/upgradePreferences', () => {
|
||||
...config,
|
||||
version: 3,
|
||||
teams: [{
|
||||
name: 'Primary team',
|
||||
name: 'Primary server',
|
||||
url: 'http://server-1.com',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
}, {
|
||||
name: 'Secondary team',
|
||||
name: 'Secondary server',
|
||||
url: 'http://server-2.com',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
},
|
||||
{
|
||||
name: 'tab2',
|
||||
name: 'view2',
|
||||
},
|
||||
],
|
||||
}],
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
import {ConfigV3, ConfigV2, ConfigV1, ConfigV0, AnyConfig} from 'types/config';
|
||||
|
||||
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||
import {getDefaultViewsForConfigServer} from 'common/views/View';
|
||||
|
||||
import pastDefaultPreferences from './pastDefaultPreferences';
|
||||
|
||||
@@ -15,7 +15,7 @@ function deepCopy<T>(object: T): T {
|
||||
export function upgradeV0toV1(configV0: ConfigV0) {
|
||||
const config = deepCopy(pastDefaultPreferences[1]);
|
||||
config.teams.push({
|
||||
name: 'Primary team',
|
||||
name: 'Primary server',
|
||||
url: configV0.url,
|
||||
});
|
||||
return config;
|
||||
@@ -37,7 +37,7 @@ export function upgradeV2toV3(configV2: ConfigV2) {
|
||||
const config: ConfigV3 = Object.assign({}, deepCopy<ConfigV3>(pastDefaultPreferences[3]), configV2);
|
||||
config.version = 3;
|
||||
config.teams = configV2.teams.map((value) => {
|
||||
return getDefaultConfigTeamFromTeam(value);
|
||||
return getDefaultViewsForConfigServer(value);
|
||||
});
|
||||
config.lastActiveTeam = 0;
|
||||
config.spellCheckerLocales = [];
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
import {v4 as uuid} from 'uuid';
|
||||
|
||||
import {MattermostTeam, Team} from 'types/config';
|
||||
import {UniqueServer, Server} from 'types/config';
|
||||
|
||||
import {parseURL} from 'common/utils/url';
|
||||
|
||||
@@ -13,7 +13,7 @@ export class MattermostServer {
|
||||
url!: URL;
|
||||
isPredefined: boolean;
|
||||
|
||||
constructor(server: Team, isPredefined: boolean) {
|
||||
constructor(server: Server, isPredefined: boolean) {
|
||||
this.id = uuid();
|
||||
|
||||
this.name = server.name;
|
||||
@@ -29,7 +29,7 @@ export class MattermostServer {
|
||||
}
|
||||
}
|
||||
|
||||
toMattermostTeam = (): MattermostTeam => {
|
||||
toUniqueServer = (): UniqueServer => {
|
||||
return {
|
||||
name: this.name,
|
||||
url: this.url.toString(),
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {TAB_MESSAGING, TAB_FOCALBOARD, TAB_PLAYBOOKS} from 'common/tabs/TabView';
|
||||
import {TAB_MESSAGING, TAB_FOCALBOARD, TAB_PLAYBOOKS} from 'common/views/View';
|
||||
import {parseURL, isInternalURL} from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
|
||||
@@ -31,12 +31,12 @@ describe('common/servers/serverManager', () => {
|
||||
server.url = new URL(url);
|
||||
};
|
||||
serverManager.servers = new Map([['server-1', server]]);
|
||||
serverManager.tabs = new Map([
|
||||
['tab-1', {id: 'tab-1', type: TAB_MESSAGING, isOpen: true, server}],
|
||||
['tab-2', {id: 'tab-2', type: TAB_PLAYBOOKS, server}],
|
||||
['tab-3', {id: 'tab-3', type: TAB_FOCALBOARD, server}],
|
||||
serverManager.views = new Map([
|
||||
['view-1', {id: 'view-1', type: TAB_MESSAGING, isOpen: true, server}],
|
||||
['view-2', {id: 'view-2', type: TAB_PLAYBOOKS, server}],
|
||||
['view-3', {id: 'view-3', type: TAB_FOCALBOARD, server}],
|
||||
]);
|
||||
serverManager.tabOrder = new Map([['server-1', ['tab-1', 'tab-2', 'tab-3']]]);
|
||||
serverManager.viewOrder = new Map([['server-1', ['view-1', 'view-2', 'view-3']]]);
|
||||
serverManager.persistServers = jest.fn();
|
||||
Utils.isVersionGreaterThanOrEqualTo.mockImplementation((version) => version === '6.0.0');
|
||||
});
|
||||
@@ -52,7 +52,7 @@ describe('common/servers/serverManager', () => {
|
||||
expect(serverManager.persistServers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should open all tabs', async () => {
|
||||
it('should open all views', async () => {
|
||||
serverManager.updateRemoteInfos(new Map([['server-1', {
|
||||
siteURL: 'http://server-1.com',
|
||||
serverVersion: '6.0.0',
|
||||
@@ -60,8 +60,8 @@ describe('common/servers/serverManager', () => {
|
||||
hasFocalboard: true,
|
||||
}]]));
|
||||
|
||||
expect(serverManager.tabs.get('tab-2').isOpen).toBe(true);
|
||||
expect(serverManager.tabs.get('tab-3').isOpen).toBe(true);
|
||||
expect(serverManager.views.get('view-2').isOpen).toBe(true);
|
||||
expect(serverManager.views.get('view-3').isOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should open only playbooks', async () => {
|
||||
@@ -72,8 +72,8 @@ describe('common/servers/serverManager', () => {
|
||||
hasFocalboard: false,
|
||||
}]]));
|
||||
|
||||
expect(serverManager.tabs.get('tab-2').isOpen).toBe(true);
|
||||
expect(serverManager.tabs.get('tab-3').isOpen).toBeUndefined();
|
||||
expect(serverManager.views.get('view-2').isOpen).toBe(true);
|
||||
expect(serverManager.views.get('view-3').isOpen).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should open none when server version is too old', async () => {
|
||||
@@ -84,8 +84,8 @@ describe('common/servers/serverManager', () => {
|
||||
hasFocalboard: true,
|
||||
}]]));
|
||||
|
||||
expect(serverManager.tabs.get('tab-2').isOpen).toBeUndefined();
|
||||
expect(serverManager.tabs.get('tab-3').isOpen).toBeUndefined();
|
||||
expect(serverManager.views.get('view-2').isOpen).toBeUndefined();
|
||||
expect(serverManager.views.get('view-3').isOpen).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should update server URL using site URL', async () => {
|
||||
@@ -100,7 +100,7 @@ describe('common/servers/serverManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('lookupTabByURL', () => {
|
||||
describe('lookupViewByURL', () => {
|
||||
const serverManager = new ServerManager();
|
||||
serverManager.getAllServers = () => [
|
||||
{id: 'server-1', url: new URL('http://server-1.com')},
|
||||
@@ -109,16 +109,16 @@ describe('common/servers/serverManager', () => {
|
||||
serverManager.getOrderedTabsForServer = (serverId) => {
|
||||
if (serverId === 'server-1') {
|
||||
return [
|
||||
{id: 'tab-1', url: new URL('http://server-1.com')},
|
||||
{id: 'tab-1-type-1', url: new URL('http://server-1.com/type1')},
|
||||
{id: 'tab-1-type-2', url: new URL('http://server-1.com/type2')},
|
||||
{id: 'view-1', url: new URL('http://server-1.com')},
|
||||
{id: 'view-1-type-1', url: new URL('http://server-1.com/type1')},
|
||||
{id: 'view-1-type-2', url: new URL('http://server-1.com/type2')},
|
||||
];
|
||||
}
|
||||
if (serverId === 'server-2') {
|
||||
return [
|
||||
{id: 'tab-2', url: new URL('http://server-2.com/subpath')},
|
||||
{id: 'tab-2-type-1', url: new URL('http://server-2.com/subpath/type1')},
|
||||
{id: 'tab-2-type-2', url: new URL('http://server-2.com/subpath/type2')},
|
||||
{id: 'view-2', url: new URL('http://server-2.com/subpath')},
|
||||
{id: 'view-2-type-1', url: new URL('http://server-2.com/subpath/type1')},
|
||||
{id: 'view-2-type-2', url: new URL('http://server-2.com/subpath/type2')},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
@@ -135,47 +135,47 @@ describe('common/servers/serverManager', () => {
|
||||
|
||||
it('should match the correct server - base URL', () => {
|
||||
const inputURL = new URL('http://server-1.com');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toStrictEqual({id: 'tab-1', url: new URL('http://server-1.com')});
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toStrictEqual({id: 'view-1', url: new URL('http://server-1.com')});
|
||||
});
|
||||
|
||||
it('should match the correct server - base tab', () => {
|
||||
const inputURL = new URL('http://server-1.com/team');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toStrictEqual({id: 'tab-1', url: new URL('http://server-1.com')});
|
||||
it('should match the correct server - base view', () => {
|
||||
const inputURL = new URL('http://server-1.com/server');
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toStrictEqual({id: 'view-1', url: new URL('http://server-1.com')});
|
||||
});
|
||||
|
||||
it('should match the correct server - different tab', () => {
|
||||
it('should match the correct server - different view', () => {
|
||||
const inputURL = new URL('http://server-1.com/type1/app');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toStrictEqual({id: 'tab-1-type-1', url: new URL('http://server-1.com/type1')});
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toStrictEqual({id: 'view-1-type-1', url: new URL('http://server-1.com/type1')});
|
||||
});
|
||||
|
||||
it('should return undefined for server with subpath and URL without', () => {
|
||||
const inputURL = new URL('http://server-2.com');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toBe(undefined);
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined for server with subpath and URL with wrong subpath', () => {
|
||||
const inputURL = new URL('http://server-2.com/different/subpath');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toBe(undefined);
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should match the correct server with a subpath - base URL', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toStrictEqual({id: 'tab-2', url: new URL('http://server-2.com/subpath')});
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toStrictEqual({id: 'view-2', url: new URL('http://server-2.com/subpath')});
|
||||
});
|
||||
|
||||
it('should match the correct server with a subpath - base tab', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath/team');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toStrictEqual({id: 'tab-2', url: new URL('http://server-2.com/subpath')});
|
||||
it('should match the correct server with a subpath - base view', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath/server');
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toStrictEqual({id: 'view-2', url: new URL('http://server-2.com/subpath')});
|
||||
});
|
||||
|
||||
it('should match the correct server with a subpath - different tab', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath/type2/team');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toStrictEqual({id: 'tab-2-type-2', url: new URL('http://server-2.com/subpath/type2')});
|
||||
it('should match the correct server with a subpath - different view', () => {
|
||||
const inputURL = new URL('http://server-2.com/subpath/type2/server');
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toStrictEqual({id: 'view-2-type-2', url: new URL('http://server-2.com/subpath/type2')});
|
||||
});
|
||||
|
||||
it('should return undefined for wrong server', () => {
|
||||
const inputURL = new URL('http://server-3.com');
|
||||
expect(serverManager.lookupTabByURL(inputURL)).toBe(undefined);
|
||||
expect(serverManager.lookupViewByURL(inputURL)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import {Team, ConfigServer, ConfigTab} from 'types/config';
|
||||
import {Server, ConfigServer, ConfigView} from 'types/config';
|
||||
import {RemoteInfo} from 'types/server';
|
||||
|
||||
import Config from 'common/config';
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
} from 'common/communication';
|
||||
import {Logger, getLevel} from 'common/log';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import {TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS, TabView, getDefaultTabs} from 'common/tabs/TabView';
|
||||
import MessagingTabView from 'common/tabs/MessagingTabView';
|
||||
import FocalboardTabView from 'common/tabs/FocalboardTabView';
|
||||
import PlaybooksTabView from 'common/tabs/PlaybooksTabView';
|
||||
import {TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS, MattermostView, getDefaultViews} from 'common/views/View';
|
||||
import MessagingView from 'common/views/MessagingView';
|
||||
import FocalboardView from 'common/views/FocalboardView';
|
||||
import PlaybooksView from 'common/views/PlaybooksView';
|
||||
import {isInternalURL, parseURL} from 'common/utils/url';
|
||||
import Utils from 'common/utils/util';
|
||||
|
||||
@@ -28,9 +28,9 @@ export class ServerManager extends EventEmitter {
|
||||
private serverOrder: string[];
|
||||
private currentServerId?: string;
|
||||
|
||||
private tabs: Map<string, TabView>;
|
||||
private tabOrder: Map<string, string[]>;
|
||||
private lastActiveTab: Map<string, string>;
|
||||
private views: Map<string, MattermostView>;
|
||||
private viewOrder: Map<string, string[]>;
|
||||
private lastActiveView: Map<string, string>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -38,25 +38,25 @@ export class ServerManager extends EventEmitter {
|
||||
this.servers = new Map();
|
||||
this.remoteInfo = new Map();
|
||||
this.serverOrder = [];
|
||||
this.tabs = new Map();
|
||||
this.tabOrder = new Map();
|
||||
this.lastActiveTab = new Map();
|
||||
this.views = new Map();
|
||||
this.viewOrder = new Map();
|
||||
this.lastActiveView = new Map();
|
||||
}
|
||||
|
||||
getOrderedTabsForServer = (serverId: string) => {
|
||||
log.withPrefix(serverId).debug('getOrderedTabsForServer');
|
||||
|
||||
const tabOrder = this.tabOrder.get(serverId);
|
||||
if (!tabOrder) {
|
||||
const viewOrder = this.viewOrder.get(serverId);
|
||||
if (!viewOrder) {
|
||||
return [];
|
||||
}
|
||||
return tabOrder.reduce((tabs, tabId) => {
|
||||
const tab = this.tabs.get(tabId);
|
||||
if (tab) {
|
||||
tabs.push(tab);
|
||||
return viewOrder.reduce((views, viewId) => {
|
||||
const view = this.views.get(viewId);
|
||||
if (view) {
|
||||
views.push(view);
|
||||
}
|
||||
return tabs;
|
||||
}, [] as TabView[]);
|
||||
return views;
|
||||
}, [] as MattermostView[]);
|
||||
}
|
||||
|
||||
getOrderedServers = () => {
|
||||
@@ -87,22 +87,22 @@ export class ServerManager extends EventEmitter {
|
||||
getLastActiveTabForServer = (serverId: string) => {
|
||||
log.withPrefix(serverId).debug('getLastActiveTabForServer');
|
||||
|
||||
const lastActiveTab = this.lastActiveTab.get(serverId);
|
||||
if (lastActiveTab) {
|
||||
const tab = this.tabs.get(lastActiveTab);
|
||||
if (tab && tab?.isOpen) {
|
||||
return tab;
|
||||
const lastActiveView = this.lastActiveView.get(serverId);
|
||||
if (lastActiveView) {
|
||||
const view = this.views.get(lastActiveView);
|
||||
if (view && view?.isOpen) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return this.getFirstOpenTabForServer(serverId);
|
||||
return this.getFirstOpenViewForServer(serverId);
|
||||
}
|
||||
|
||||
getServer = (id: string) => {
|
||||
return this.servers.get(id);
|
||||
}
|
||||
|
||||
getTab = (id: string) => {
|
||||
return this.tabs.get(id);
|
||||
getView = (id: string) => {
|
||||
return this.views.get(id);
|
||||
}
|
||||
|
||||
getAllServers = () => {
|
||||
@@ -122,7 +122,7 @@ export class ServerManager extends EventEmitter {
|
||||
remoteInfos.forEach((remoteInfo, serverId) => {
|
||||
this.remoteInfo.set(serverId, remoteInfo);
|
||||
hasUpdates = this.updateServerURL(serverId) || hasUpdates;
|
||||
hasUpdates = this.openExtraTabs(serverId) || hasUpdates;
|
||||
hasUpdates = this.openExtraViews(serverId) || hasUpdates;
|
||||
});
|
||||
|
||||
if (hasUpdates) {
|
||||
@@ -130,8 +130,8 @@ export class ServerManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
lookupTabByURL = (inputURL: URL | string, ignoreScheme = false) => {
|
||||
log.silly('lookupTabByURL', `${inputURL}`, ignoreScheme);
|
||||
lookupViewByURL = (inputURL: URL | string, ignoreScheme = false) => {
|
||||
log.silly('lookupViewByURL', `${inputURL}`, ignoreScheme);
|
||||
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL) {
|
||||
@@ -143,17 +143,17 @@ export class ServerManager extends EventEmitter {
|
||||
if (!server) {
|
||||
return undefined;
|
||||
}
|
||||
const tabs = this.getOrderedTabsForServer(server.id);
|
||||
const views = this.getOrderedTabsForServer(server.id);
|
||||
|
||||
let selectedTab = tabs.find((tab) => tab && tab.type === TAB_MESSAGING);
|
||||
tabs.
|
||||
filter((tab) => tab && tab.type !== TAB_MESSAGING).
|
||||
forEach((tab) => {
|
||||
if (parsedURL.pathname.match(new RegExp(`^${tab.url.pathname}(/(.+))?`))) {
|
||||
selectedTab = tab;
|
||||
let selectedView = views.find((view) => view && view.type === TAB_MESSAGING);
|
||||
views.
|
||||
filter((view) => view && view.type !== TAB_MESSAGING).
|
||||
forEach((view) => {
|
||||
if (parsedURL.pathname.match(new RegExp(`^${view.url.pathname}(/(.+))?`))) {
|
||||
selectedView = view;
|
||||
}
|
||||
});
|
||||
return selectedTab;
|
||||
return selectedView;
|
||||
}
|
||||
|
||||
updateServerOrder = (serverOrder: string[]) => {
|
||||
@@ -163,14 +163,14 @@ export class ServerManager extends EventEmitter {
|
||||
this.persistServers();
|
||||
}
|
||||
|
||||
updateTabOrder = (serverId: string, tabOrder: string[]) => {
|
||||
log.withPrefix(serverId).debug('updateTabOrder', tabOrder);
|
||||
updateTabOrder = (serverId: string, viewOrder: string[]) => {
|
||||
log.withPrefix(serverId).debug('updateTabOrder', viewOrder);
|
||||
|
||||
this.tabOrder.set(serverId, tabOrder);
|
||||
this.viewOrder.set(serverId, viewOrder);
|
||||
this.persistServers();
|
||||
}
|
||||
|
||||
addServer = (server: Team) => {
|
||||
addServer = (server: Server) => {
|
||||
const newServer = new MattermostServer(server, false);
|
||||
|
||||
if (this.servers.has(newServer.id)) {
|
||||
@@ -179,13 +179,13 @@ export class ServerManager extends EventEmitter {
|
||||
this.servers.set(newServer.id, newServer);
|
||||
|
||||
this.serverOrder.push(newServer.id);
|
||||
const tabOrder: string[] = [];
|
||||
getDefaultTabs().forEach((tab) => {
|
||||
const newTab = this.getTabView(newServer, tab.name, tab.isOpen);
|
||||
this.tabs.set(newTab.id, newTab);
|
||||
tabOrder.push(newTab.id);
|
||||
const viewOrder: string[] = [];
|
||||
getDefaultViews().forEach((view) => {
|
||||
const newView = this.getNewView(newServer, view.name, view.isOpen);
|
||||
this.views.set(newView.id, newView);
|
||||
viewOrder.push(newView.id);
|
||||
});
|
||||
this.tabOrder.set(newServer.id, tabOrder);
|
||||
this.viewOrder.set(newServer.id, viewOrder);
|
||||
|
||||
if (!this.currentServerId) {
|
||||
this.currentServerId = newServer.id;
|
||||
@@ -197,7 +197,7 @@ export class ServerManager extends EventEmitter {
|
||||
return newServer;
|
||||
}
|
||||
|
||||
editServer = (serverId: string, server: Team) => {
|
||||
editServer = (serverId: string, server: Server) => {
|
||||
const existingServer = this.servers.get(serverId);
|
||||
if (!existingServer) {
|
||||
return;
|
||||
@@ -212,11 +212,11 @@ export class ServerManager extends EventEmitter {
|
||||
existingServer.updateURL(server.url);
|
||||
this.servers.set(serverId, existingServer);
|
||||
|
||||
this.tabOrder.get(serverId)?.forEach((tabId) => {
|
||||
const tab = this.tabs.get(tabId);
|
||||
if (tab) {
|
||||
tab.server = existingServer;
|
||||
this.tabs.set(tabId, tab);
|
||||
this.viewOrder.get(serverId)?.forEach((viewId) => {
|
||||
const view = this.views.get(viewId);
|
||||
if (view) {
|
||||
view.server = existingServer;
|
||||
this.views.set(viewId, view);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -225,9 +225,9 @@ export class ServerManager extends EventEmitter {
|
||||
}
|
||||
|
||||
removeServer = (serverId: string) => {
|
||||
this.tabOrder.get(serverId)?.forEach((tabId) => this.tabs.delete(tabId));
|
||||
this.tabOrder.delete(serverId);
|
||||
this.lastActiveTab.delete(serverId);
|
||||
this.viewOrder.get(serverId)?.forEach((viewId) => this.views.delete(viewId));
|
||||
this.viewOrder.delete(serverId);
|
||||
this.lastActiveView.delete(serverId);
|
||||
|
||||
const index = this.serverOrder.findIndex((id) => id === serverId);
|
||||
this.serverOrder.splice(index, 1);
|
||||
@@ -241,26 +241,26 @@ export class ServerManager extends EventEmitter {
|
||||
this.persistServers();
|
||||
}
|
||||
|
||||
setTabIsOpen = (tabId: string, isOpen: boolean) => {
|
||||
const tab = this.tabs.get(tabId);
|
||||
if (!tab) {
|
||||
setViewIsOpen = (viewId: string, isOpen: boolean) => {
|
||||
const view = this.views.get(viewId);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
tab.isOpen = isOpen;
|
||||
view.isOpen = isOpen;
|
||||
|
||||
this.persistServers();
|
||||
}
|
||||
|
||||
updateLastActive = (tabId: string) => {
|
||||
const tab = this.tabs.get(tabId);
|
||||
if (!tab) {
|
||||
updateLastActive = (viewId: string) => {
|
||||
const view = this.views.get(viewId);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
this.lastActiveTab.set(tab.server.id, tabId);
|
||||
this.lastActiveView.set(view.server.id, viewId);
|
||||
|
||||
this.currentServerId = tab.server.id;
|
||||
this.currentServerId = view.server.id;
|
||||
|
||||
const serverOrder = this.serverOrder.findIndex((srv) => srv === tab.server.id);
|
||||
const serverOrder = this.serverOrder.findIndex((srv) => srv === view.server.id);
|
||||
if (serverOrder < 0) {
|
||||
throw new Error('Server order corrupt, ID not found.');
|
||||
}
|
||||
@@ -270,26 +270,26 @@ export class ServerManager extends EventEmitter {
|
||||
|
||||
reloadFromConfig = () => {
|
||||
const serverOrder: string[] = [];
|
||||
Config.predefinedTeams.forEach((team) => {
|
||||
const id = this.initServer(team, true);
|
||||
Config.predefinedServers.forEach((server) => {
|
||||
const id = this.initServer(server, true);
|
||||
serverOrder.push(id);
|
||||
});
|
||||
if (Config.enableServerManagement) {
|
||||
Config.localTeams.sort((a, b) => a.order - b.order).forEach((team) => {
|
||||
const id = this.initServer(team, false);
|
||||
Config.localServers.sort((a, b) => a.order - b.order).forEach((server) => {
|
||||
const id = this.initServer(server, false);
|
||||
serverOrder.push(id);
|
||||
});
|
||||
}
|
||||
this.filterOutDuplicateTeams();
|
||||
this.filterOutDuplicateServers();
|
||||
this.serverOrder = serverOrder;
|
||||
if (Config.lastActiveTeam && this.serverOrder[Config.lastActiveTeam]) {
|
||||
this.currentServerId = this.serverOrder[Config.lastActiveTeam];
|
||||
if (Config.lastActiveServer && this.serverOrder[Config.lastActiveServer]) {
|
||||
this.currentServerId = this.serverOrder[Config.lastActiveServer];
|
||||
} else {
|
||||
this.currentServerId = this.serverOrder[0];
|
||||
}
|
||||
}
|
||||
|
||||
private filterOutDuplicateTeams = () => {
|
||||
private filterOutDuplicateServers = () => {
|
||||
const servers = [...this.servers.keys()].map((key) => ({key, value: this.servers.get(key)!}));
|
||||
const uniqueServers = new Set();
|
||||
servers.forEach((server) => {
|
||||
@@ -301,38 +301,38 @@ export class ServerManager extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
private initServer = (team: ConfigServer, isPredefined: boolean) => {
|
||||
const server = new MattermostServer(team, isPredefined);
|
||||
private initServer = (configServer: ConfigServer, isPredefined: boolean) => {
|
||||
const server = new MattermostServer(configServer, isPredefined);
|
||||
this.servers.set(server.id, server);
|
||||
|
||||
log.withPrefix(server.id).debug('initialized server');
|
||||
|
||||
const tabOrder: string[] = [];
|
||||
team.tabs.sort((a, b) => a.order - b.order).forEach((tab) => {
|
||||
const tabView = this.getTabView(server, tab.name, tab.isOpen);
|
||||
log.withPrefix(tabView.id).debug('initialized tab');
|
||||
const viewOrder: string[] = [];
|
||||
configServer.tabs.sort((a, b) => a.order - b.order).forEach((view) => {
|
||||
const mattermostView = this.getNewView(server, view.name, view.isOpen);
|
||||
log.withPrefix(mattermostView.id).debug('initialized view');
|
||||
|
||||
this.tabs.set(tabView.id, tabView);
|
||||
tabOrder.push(tabView.id);
|
||||
this.views.set(mattermostView.id, mattermostView);
|
||||
viewOrder.push(mattermostView.id);
|
||||
});
|
||||
this.tabOrder.set(server.id, tabOrder);
|
||||
if (typeof team.lastActiveTab !== 'undefined') {
|
||||
this.lastActiveTab.set(server.id, tabOrder[team.lastActiveTab]);
|
||||
this.viewOrder.set(server.id, viewOrder);
|
||||
if (typeof configServer.lastActiveTab !== 'undefined') {
|
||||
this.lastActiveView.set(server.id, viewOrder[configServer.lastActiveTab]);
|
||||
}
|
||||
return server.id;
|
||||
}
|
||||
|
||||
private getFirstOpenTabForServer = (serverId: string) => {
|
||||
const tabOrder = this.getOrderedTabsForServer(serverId);
|
||||
const openTabs = tabOrder.filter((tab) => tab.isOpen);
|
||||
const firstTab = openTabs[0];
|
||||
if (!firstTab) {
|
||||
throw new Error(`No tabs open for server id ${serverId}`);
|
||||
private getFirstOpenViewForServer = (serverId: string) => {
|
||||
const viewOrder = this.getOrderedTabsForServer(serverId);
|
||||
const openViews = viewOrder.filter((view) => view.isOpen);
|
||||
const firstView = openViews[0];
|
||||
if (!firstView) {
|
||||
throw new Error(`No views open for server id ${serverId}`);
|
||||
}
|
||||
return firstTab;
|
||||
return firstView;
|
||||
}
|
||||
|
||||
private persistServers = async (lastActiveTeam?: number) => {
|
||||
private persistServers = async (lastActiveServer?: number) => {
|
||||
this.emit(SERVERS_UPDATE);
|
||||
|
||||
const localServers = [...this.servers.values()].
|
||||
@@ -343,18 +343,18 @@ export class ServerManager extends EventEmitter {
|
||||
servers.push(this.toConfigServer(srv));
|
||||
return servers;
|
||||
}, [] as ConfigServer[]);
|
||||
await Config.setServers(localServers, lastActiveTeam);
|
||||
await Config.setServers(localServers, lastActiveServer);
|
||||
}
|
||||
|
||||
private getLastActiveTab = (serverId: string) => {
|
||||
let lastActiveTab: number | undefined;
|
||||
if (this.lastActiveTab.has(serverId)) {
|
||||
const index = this.tabOrder.get(serverId)?.indexOf(this.lastActiveTab.get(serverId)!);
|
||||
private getLastActiveView = (serverId: string) => {
|
||||
let lastActiveView: number | undefined;
|
||||
if (this.lastActiveView.has(serverId)) {
|
||||
const index = this.viewOrder.get(serverId)?.indexOf(this.lastActiveView.get(serverId)!);
|
||||
if (typeof index !== 'undefined' && index >= 0) {
|
||||
lastActiveTab = index;
|
||||
lastActiveView = index;
|
||||
}
|
||||
}
|
||||
return lastActiveTab;
|
||||
return lastActiveView;
|
||||
}
|
||||
|
||||
private toConfigServer = (server: MattermostServer): ConfigServer => {
|
||||
@@ -362,30 +362,30 @@ export class ServerManager extends EventEmitter {
|
||||
name: server.name,
|
||||
url: `${server.url}`,
|
||||
order: this.serverOrder.indexOf(server.id),
|
||||
lastActiveTab: this.getLastActiveTab(server.id),
|
||||
tabs: this.tabOrder.get(server.id)?.reduce((tabs, tabId, index) => {
|
||||
const tab = this.tabs.get(tabId);
|
||||
if (!tab) {
|
||||
return tabs;
|
||||
lastActiveTab: this.getLastActiveView(server.id),
|
||||
tabs: this.viewOrder.get(server.id)?.reduce((views, viewId, index) => {
|
||||
const view = this.views.get(viewId);
|
||||
if (!view) {
|
||||
return views;
|
||||
}
|
||||
tabs.push({
|
||||
name: tab?.type,
|
||||
views.push({
|
||||
name: view?.type,
|
||||
order: index,
|
||||
isOpen: tab.isOpen,
|
||||
isOpen: view.isOpen,
|
||||
});
|
||||
return tabs;
|
||||
}, [] as ConfigTab[]) ?? [],
|
||||
return views;
|
||||
}, [] as ConfigView[]) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
private getTabView = (srv: MattermostServer, tabName: string, isOpen?: boolean) => {
|
||||
switch (tabName) {
|
||||
private getNewView = (srv: MattermostServer, viewName: string, isOpen?: boolean) => {
|
||||
switch (viewName) {
|
||||
case TAB_MESSAGING:
|
||||
return new MessagingTabView(srv, isOpen);
|
||||
return new MessagingView(srv, isOpen);
|
||||
case TAB_FOCALBOARD:
|
||||
return new FocalboardTabView(srv, isOpen);
|
||||
return new FocalboardView(srv, isOpen);
|
||||
case TAB_PLAYBOOKS:
|
||||
return new PlaybooksTabView(srv, isOpen);
|
||||
return new PlaybooksView(srv, isOpen);
|
||||
default:
|
||||
throw new Error('Not implemeneted');
|
||||
}
|
||||
@@ -407,7 +407,7 @@ export class ServerManager extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
private openExtraTabs = (serverId: string) => {
|
||||
private openExtraViews = (serverId: string) => {
|
||||
const server = this.servers.get(serverId);
|
||||
const remoteInfo = this.remoteInfo.get(serverId);
|
||||
|
||||
@@ -420,21 +420,21 @@ export class ServerManager extends EventEmitter {
|
||||
}
|
||||
|
||||
let hasUpdates = false;
|
||||
const tabOrder = this.tabOrder.get(serverId);
|
||||
if (tabOrder) {
|
||||
tabOrder.forEach((tabId) => {
|
||||
const tab = this.tabs.get(tabId);
|
||||
if (tab) {
|
||||
if (tab.type === TAB_PLAYBOOKS && remoteInfo.hasPlaybooks && typeof tab.isOpen === 'undefined') {
|
||||
log.withPrefix(tab.id).verbose('opening Playbooks');
|
||||
tab.isOpen = true;
|
||||
this.tabs.set(tabId, tab);
|
||||
const viewOrder = this.viewOrder.get(serverId);
|
||||
if (viewOrder) {
|
||||
viewOrder.forEach((viewId) => {
|
||||
const view = this.views.get(viewId);
|
||||
if (view) {
|
||||
if (view.type === TAB_PLAYBOOKS && remoteInfo.hasPlaybooks && typeof view.isOpen === 'undefined') {
|
||||
log.withPrefix(view.id).verbose('opening Playbooks');
|
||||
view.isOpen = true;
|
||||
this.views.set(viewId, view);
|
||||
hasUpdates = true;
|
||||
}
|
||||
if (tab.type === TAB_FOCALBOARD && remoteInfo.hasFocalboard && typeof tab.isOpen === 'undefined') {
|
||||
log.withPrefix(tab.id).verbose('opening Boards');
|
||||
tab.isOpen = true;
|
||||
this.tabs.set(tabId, tab);
|
||||
if (view.type === TAB_FOCALBOARD && remoteInfo.hasFocalboard && typeof view.isOpen === 'undefined') {
|
||||
log.withPrefix(view.id).verbose('opening Boards');
|
||||
view.isOpen = true;
|
||||
this.views.set(viewId, view);
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,7 @@ export class ServerManager extends EventEmitter {
|
||||
};
|
||||
|
||||
getViewLog = (viewId: string, ...additionalPrefixes: string[]) => {
|
||||
const view = this.getTab(viewId);
|
||||
const view = this.getView(viewId);
|
||||
if (!view) {
|
||||
return new Logger(viewId);
|
||||
}
|
||||
|
@@ -14,11 +14,11 @@ import {
|
||||
isTrustedURL,
|
||||
} from 'common/utils/url';
|
||||
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getServerView: (srv, tab) => {
|
||||
jest.mock('common/views/View', () => ({
|
||||
getServerView: (srv, view) => {
|
||||
return {
|
||||
name: `${srv.name}_${tab.name}`,
|
||||
url: `${srv.url}${srv.url.toString().endsWith('/') ? '' : '/'}${tab.name.split('-')[1] || ''}`,
|
||||
name: `${srv.name}_${view.name}`,
|
||||
url: `${srv.url}${srv.url.toString().endsWith('/') ? '' : '/'}${view.name.split('-')[1] || ''}`,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
@@ -119,8 +119,8 @@ describe('common/utils/util', () => {
|
||||
describe('escapeRegex', () => {
|
||||
it('should escape special chars in string when used inside regex', () => {
|
||||
const str = 'Language C++';
|
||||
const regex = `${escapeRegex(str)}___TAB_[A-Z]+`;
|
||||
expect(new RegExp(regex).test('Language C++___TAB_ABCDEF')).toBe(true);
|
||||
const regex = `${escapeRegex(str)}___VIEW_[A-Z]+`;
|
||||
expect(new RegExp(regex).test('Language C++___VIEW_ABCDEF')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,13 +3,13 @@
|
||||
|
||||
import {v4 as uuid} from 'uuid';
|
||||
|
||||
import {MattermostTab} from 'types/config';
|
||||
import {UniqueView} from 'types/config';
|
||||
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
|
||||
import {TabType, TabView} from './TabView';
|
||||
import {ViewType, MattermostView} from './View';
|
||||
|
||||
export default abstract class BaseTabView implements TabView {
|
||||
export default abstract class BaseView implements MattermostView {
|
||||
id: string;
|
||||
server: MattermostServer;
|
||||
isOpen?: boolean;
|
||||
@@ -22,14 +22,14 @@ export default abstract class BaseTabView implements TabView {
|
||||
get url(): URL {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
get type(): TabType {
|
||||
get type(): ViewType {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
get shouldNotify(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
toMattermostTab = (): MattermostTab => {
|
||||
toUniqueView = (): UniqueView => {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.type,
|
@@ -3,15 +3,15 @@
|
||||
|
||||
import {getFormattedPathName} from 'common/utils/url';
|
||||
|
||||
import BaseTabView from './BaseTabView';
|
||||
import {TabType, TAB_FOCALBOARD} from './TabView';
|
||||
import BaseView from './BaseView';
|
||||
import {ViewType, TAB_FOCALBOARD} from './View';
|
||||
|
||||
export default class FocalboardTabView extends BaseTabView {
|
||||
export default class FocalboardView extends BaseView {
|
||||
get url(): URL {
|
||||
return new URL(`${this.server.url.origin}${getFormattedPathName(this.server.url.pathname)}boards`);
|
||||
}
|
||||
|
||||
get type(): TabType {
|
||||
get type(): ViewType {
|
||||
return TAB_FOCALBOARD;
|
||||
}
|
||||
}
|
@@ -1,15 +1,15 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import BaseTabView from './BaseTabView';
|
||||
import {TabType, TAB_MESSAGING} from './TabView';
|
||||
import BaseView from './BaseView';
|
||||
import {ViewType, TAB_MESSAGING} from './View';
|
||||
|
||||
export default class MessagingTabView extends BaseTabView {
|
||||
export default class MessagingView extends BaseView {
|
||||
get url(): URL {
|
||||
return this.server.url;
|
||||
}
|
||||
|
||||
get type(): TabType {
|
||||
get type(): ViewType {
|
||||
return TAB_MESSAGING;
|
||||
}
|
||||
|
@@ -3,15 +3,15 @@
|
||||
|
||||
import {getFormattedPathName} from 'common/utils/url';
|
||||
|
||||
import BaseTabView from './BaseTabView';
|
||||
import {TabType, TAB_PLAYBOOKS} from './TabView';
|
||||
import BaseView from './BaseView';
|
||||
import {ViewType, TAB_PLAYBOOKS} from './View';
|
||||
|
||||
export default class PlaybooksTabView extends BaseTabView {
|
||||
export default class PlaybooksView extends BaseView {
|
||||
get url(): URL {
|
||||
return new URL(`${this.server.url.origin}${getFormattedPathName(this.server.url.pathname)}playbooks`);
|
||||
}
|
||||
|
||||
get type(): TabType {
|
||||
get type(): ViewType {
|
||||
return TAB_PLAYBOOKS;
|
||||
}
|
||||
}
|
@@ -1,35 +1,35 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MattermostTab, Team} from 'types/config';
|
||||
import {UniqueView, Server} from 'types/config';
|
||||
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
|
||||
export const TAB_MESSAGING = 'TAB_MESSAGING';
|
||||
export const TAB_FOCALBOARD = 'TAB_FOCALBOARD';
|
||||
export const TAB_PLAYBOOKS = 'TAB_PLAYBOOKS';
|
||||
export type TabType = typeof TAB_MESSAGING | typeof TAB_FOCALBOARD | typeof TAB_PLAYBOOKS;
|
||||
export type ViewType = typeof TAB_MESSAGING | typeof TAB_FOCALBOARD | typeof TAB_PLAYBOOKS;
|
||||
|
||||
export interface TabView {
|
||||
export interface MattermostView {
|
||||
id: string;
|
||||
server: MattermostServer;
|
||||
isOpen?: boolean;
|
||||
|
||||
get type(): TabType;
|
||||
get type(): ViewType;
|
||||
get url(): URL;
|
||||
get shouldNotify(): boolean;
|
||||
|
||||
toMattermostTab(): MattermostTab;
|
||||
toUniqueView(): UniqueView;
|
||||
}
|
||||
|
||||
export function getDefaultConfigTeamFromTeam(team: Team & {order: number; lastActiveTab?: number}) {
|
||||
export function getDefaultViewsForConfigServer(server: Server & {order: number; lastActiveView?: number}) {
|
||||
return {
|
||||
...team,
|
||||
tabs: getDefaultTabs(),
|
||||
...server,
|
||||
tabs: getDefaultViews(),
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultTabs() {
|
||||
export function getDefaultViews() {
|
||||
return [
|
||||
{
|
||||
name: TAB_MESSAGING,
|
||||
@@ -47,8 +47,8 @@ export function getDefaultTabs() {
|
||||
];
|
||||
}
|
||||
|
||||
export function getTabDisplayName(tabType: TabType) {
|
||||
switch (tabType) {
|
||||
export function getViewDisplayName(viewType: ViewType) {
|
||||
switch (viewType) {
|
||||
case TAB_MESSAGING:
|
||||
return 'Channels';
|
||||
case TAB_FOCALBOARD:
|
||||
@@ -60,6 +60,6 @@ export function getTabDisplayName(tabType: TabType) {
|
||||
}
|
||||
}
|
||||
|
||||
export function canCloseTab(tabType: TabType) {
|
||||
return tabType !== TAB_MESSAGING;
|
||||
export function canCloseView(viewType: ViewType) {
|
||||
return viewType !== TAB_MESSAGING;
|
||||
}
|
@@ -95,9 +95,9 @@ describe('main/app/app', () => {
|
||||
const promise = Promise.resolve({});
|
||||
const certificate = {};
|
||||
const view = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
name: 'test-team',
|
||||
name: 'test-server',
|
||||
url: new URL(testURL),
|
||||
},
|
||||
},
|
||||
@@ -163,7 +163,7 @@ describe('main/app/app', () => {
|
||||
expect(CertificateStore.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should load URL using MattermostView when trusting certificate', async () => {
|
||||
it('should load URL using MattermostBrowserView when trusting certificate', async () => {
|
||||
dialog.showMessageBox.mockResolvedValue({response: 0});
|
||||
await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback);
|
||||
expect(callback).toHaveBeenCalledWith(true);
|
||||
|
@@ -96,8 +96,8 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
|
||||
const errorID = `${parsedURL.origin}:${error}`;
|
||||
|
||||
const view = ViewManager.getViewByWebContentsId(webContents.id);
|
||||
if (view?.tab.server) {
|
||||
const serverURL = parseURL(view.tab.server.url);
|
||||
if (view?.view.server) {
|
||||
const serverURL = parseURL(view.view.server.url);
|
||||
if (serverURL && serverURL.origin !== parsedURL.origin) {
|
||||
log.warn(`Ignoring certificate for unmatched origin ${parsedURL.origin}, will not trust`);
|
||||
callback(false);
|
||||
|
@@ -93,14 +93,14 @@ describe('main/app/config', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should recheck teams after config update if registry data is pulled in', () => {
|
||||
it('should recheck servers after config update if registry data is pulled in', () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'win32',
|
||||
});
|
||||
Config.registryConfigData = {};
|
||||
|
||||
handleConfigUpdate({teams: []});
|
||||
handleConfigUpdate({servers: []});
|
||||
expect(handleMainWindowIsShown).toHaveBeenCalled();
|
||||
|
||||
Object.defineProperty(process, 'platform', {
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import {initialize} from './initialize';
|
||||
|
||||
// TODO: Singletons, we need DI :D
|
||||
import('main/views/teamDropdownView');
|
||||
import('main/views/serverDropdownView');
|
||||
import('main/views/downloadsDropdownMenuView');
|
||||
import('main/views/downloadsDropdownView');
|
||||
|
||||
|
@@ -278,7 +278,7 @@ describe('main/app/initialize', () => {
|
||||
|
||||
it('should allow permission requests for supported types from trusted URLs', async () => {
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
url: new URL('http://server-1.com'),
|
||||
},
|
||||
|
@@ -14,8 +14,8 @@ import {
|
||||
SHOW_NEW_SERVER_MODAL,
|
||||
NOTIFY_MENTION,
|
||||
SWITCH_TAB,
|
||||
CLOSE_TAB,
|
||||
OPEN_TAB,
|
||||
CLOSE_VIEW,
|
||||
OPEN_VIEW,
|
||||
SHOW_EDIT_SERVER_MODAL,
|
||||
SHOW_REMOVE_SERVER_MODAL,
|
||||
UPDATE_SHORTCUT_MENU,
|
||||
@@ -100,8 +100,8 @@ import {
|
||||
switchServer,
|
||||
} from './servers';
|
||||
import {
|
||||
handleCloseTab, handleGetLastActive, handleGetOrderedTabsForServer, handleOpenTab,
|
||||
} from './tabs';
|
||||
handleCloseView, handleGetLastActive, handleGetOrderedViewsForServer, handleOpenView,
|
||||
} from './views';
|
||||
import {
|
||||
clearAppCache,
|
||||
getDeeplinkingURL,
|
||||
@@ -279,8 +279,8 @@ function initializeInterCommunicationEventListeners() {
|
||||
|
||||
ipcMain.on(SWITCH_SERVER, (event, serverId) => switchServer(serverId));
|
||||
ipcMain.on(SWITCH_TAB, (event, viewId) => ViewManager.showById(viewId));
|
||||
ipcMain.on(CLOSE_TAB, handleCloseTab);
|
||||
ipcMain.on(OPEN_TAB, handleOpenTab);
|
||||
ipcMain.on(CLOSE_VIEW, handleCloseView);
|
||||
ipcMain.on(OPEN_VIEW, handleOpenView);
|
||||
|
||||
ipcMain.on(QUIT, handleQuit);
|
||||
|
||||
@@ -296,10 +296,10 @@ function initializeInterCommunicationEventListeners() {
|
||||
ipcMain.on(UPDATE_CONFIGURATION, updateConfiguration);
|
||||
|
||||
ipcMain.on(UPDATE_SERVER_ORDER, (event, serverOrder) => ServerManager.updateServerOrder(serverOrder));
|
||||
ipcMain.on(UPDATE_TAB_ORDER, (event, serverId, tabOrder) => ServerManager.updateTabOrder(serverId, tabOrder));
|
||||
ipcMain.on(UPDATE_TAB_ORDER, (event, serverId, viewOrder) => ServerManager.updateTabOrder(serverId, viewOrder));
|
||||
ipcMain.handle(GET_LAST_ACTIVE, handleGetLastActive);
|
||||
ipcMain.handle(GET_ORDERED_SERVERS, () => ServerManager.getOrderedServers().map((srv) => srv.toMattermostTeam()));
|
||||
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, handleGetOrderedTabsForServer);
|
||||
ipcMain.handle(GET_ORDERED_SERVERS, () => ServerManager.getOrderedServers().map((srv) => srv.toUniqueServer()));
|
||||
ipcMain.handle(GET_ORDERED_TABS_FOR_SERVER, handleGetOrderedViewsForServer);
|
||||
|
||||
ipcMain.handle(GET_DARK_MODE, handleGetDarkMode);
|
||||
ipcMain.on(WINDOW_CLOSE, handleClose);
|
||||
@@ -453,7 +453,7 @@ async function initializeAfterAppReady() {
|
||||
}
|
||||
|
||||
const requestingURL = webContents.getURL();
|
||||
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
|
||||
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.view.server.url;
|
||||
|
||||
if (!serverURL) {
|
||||
callback(false);
|
||||
|
@@ -16,14 +16,14 @@ jest.mock('common/config', () => ({
|
||||
}));
|
||||
jest.mock('main/notifications', () => ({}));
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
setTabIsOpen: jest.fn(),
|
||||
setViewIsOpen: jest.fn(),
|
||||
getAllServers: jest.fn(),
|
||||
hasServers: jest.fn(),
|
||||
addServer: jest.fn(),
|
||||
editServer: jest.fn(),
|
||||
removeServer: jest.fn(),
|
||||
getServer: jest.fn(),
|
||||
getTab: jest.fn(),
|
||||
getView: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/utils', () => ({
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
import {app, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
import {MentionData} from 'types/notification';
|
||||
|
||||
import {Logger} from 'common/log';
|
||||
@@ -93,11 +93,11 @@ export function handleWelcomeScreenModal() {
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const modalPromise = ModalManager.addModal<MattermostTeam[], MattermostTeam>('welcomeScreen', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
|
||||
const modalPromise = ModalManager.addModal<UniqueServer[], UniqueServer>('welcomeScreen', html, preload, ServerManager.getAllServers().map((server) => server.toUniqueServer()), mainWindow, !ServerManager.hasServers());
|
||||
if (modalPromise) {
|
||||
modalPromise.then((data) => {
|
||||
const newTeam = ServerManager.addServer(data);
|
||||
switchServer(newTeam.id, true);
|
||||
const newServer = ServerManager.addServer(data);
|
||||
switchServer(newServer.id, true);
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {getDefaultConfigTeamFromTeam} from 'common/tabs/TabView';
|
||||
import {getDefaultViewsForConfigServer} from 'common/views/View';
|
||||
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import {getLocalURLString, getLocalPreload} from 'main/utils';
|
||||
@@ -18,19 +18,19 @@ jest.mock('electron', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
setTabIsOpen: jest.fn(),
|
||||
setViewIsOpen: jest.fn(),
|
||||
getAllServers: jest.fn(),
|
||||
hasServers: jest.fn(),
|
||||
addServer: jest.fn(),
|
||||
editServer: jest.fn(),
|
||||
removeServer: jest.fn(),
|
||||
getServer: jest.fn(),
|
||||
getTab: jest.fn(),
|
||||
getView: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
getServerLog: jest.fn(),
|
||||
}));
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getDefaultConfigTeamFromTeam: jest.fn(),
|
||||
jest.mock('common/views/View', () => ({
|
||||
getDefaultViewsForConfigServer: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/views/modalManager', () => ({
|
||||
addModal: jest.fn(),
|
||||
@@ -50,22 +50,22 @@ jest.mock('main/views/viewManager', () => ({
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'tab-3',
|
||||
name: 'view-3',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
];
|
||||
const teams = [
|
||||
const servers = [
|
||||
{
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
@@ -77,9 +77,9 @@ const teams = [
|
||||
describe('main/app/servers', () => {
|
||||
describe('switchServer', () => {
|
||||
const views = new Map([
|
||||
['tab-1', {id: 'tab-1'}],
|
||||
['tab-2', {id: 'tab-2'}],
|
||||
['tab-3', {id: 'tab-3'}],
|
||||
['view-1', {id: 'view-1'}],
|
||||
['view-2', {id: 'view-2'}],
|
||||
['view-3', {id: 'view-3'}],
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -119,69 +119,69 @@ describe('main/app/servers', () => {
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should show first open tab in order when last active not defined', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
||||
it('should show first open view in order when last active not defined', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-3'});
|
||||
Servers.switchServer('server-1');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('tab-3');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('view-3');
|
||||
});
|
||||
|
||||
it('should show last active tab of chosen server', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
|
||||
it('should show last active view of chosen server', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-2'});
|
||||
Servers.switchServer('server-2');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('tab-2');
|
||||
expect(ViewManager.showById).toHaveBeenCalledWith('view-2');
|
||||
});
|
||||
|
||||
it('should wait for view to exist if specified', () => {
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-3'});
|
||||
views.delete('tab-3');
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-3'});
|
||||
views.delete('view-3');
|
||||
Servers.switchServer('server-1', true);
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
expect(ViewManager.showById).not.toBeCalled();
|
||||
|
||||
views.set('tab-3', {});
|
||||
views.set('view-3', {});
|
||||
jest.advanceTimersByTime(200);
|
||||
expect(ViewManager.showById).toBeCalledWith('tab-3');
|
||||
expect(ViewManager.showById).toBeCalledWith('view-3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleNewServerModal', () => {
|
||||
let teamsCopy;
|
||||
let serversCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||
serversCopy = JSON.parse(JSON.stringify(servers));
|
||||
ServerManager.getAllServers.mockReturnValue([]);
|
||||
ServerManager.addServer.mockImplementation(() => {
|
||||
const newTeam = {
|
||||
const newServer = {
|
||||
id: 'server-1',
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
tabs,
|
||||
};
|
||||
teamsCopy = [
|
||||
...teamsCopy,
|
||||
newTeam,
|
||||
serversCopy = [
|
||||
...serversCopy,
|
||||
newServer,
|
||||
];
|
||||
return newTeam;
|
||||
return newServer;
|
||||
});
|
||||
ServerManager.hasServers.mockReturnValue(Boolean(teamsCopy.length));
|
||||
ServerManager.hasServers.mockReturnValue(Boolean(serversCopy.length));
|
||||
ServerManager.getServerLog.mockReturnValue({debug: jest.fn(), error: jest.fn()});
|
||||
|
||||
getDefaultConfigTeamFromTeam.mockImplementation((team) => ({
|
||||
...team,
|
||||
getDefaultViewsForConfigServer.mockImplementation((server) => ({
|
||||
...server,
|
||||
tabs,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should add new team to the config', async () => {
|
||||
it('should add new server to the config', async () => {
|
||||
const data = {
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
};
|
||||
const promise = Promise.resolve(data);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
@@ -190,10 +190,10 @@ describe('main/app/servers', () => {
|
||||
await promise;
|
||||
|
||||
expect(ServerManager.addServer).toHaveBeenCalledWith(data);
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
tabs,
|
||||
}));
|
||||
|
||||
@@ -203,31 +203,31 @@ describe('main/app/servers', () => {
|
||||
});
|
||||
|
||||
describe('handleEditServerModal', () => {
|
||||
let teamsCopy;
|
||||
let serversCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||
serversCopy = JSON.parse(JSON.stringify(servers));
|
||||
ServerManager.getServer.mockImplementation((id) => {
|
||||
if (id !== teamsCopy[0].id) {
|
||||
if (id !== serversCopy[0].id) {
|
||||
return undefined;
|
||||
}
|
||||
return {...teamsCopy[0], toMattermostTeam: jest.fn()};
|
||||
return {...serversCopy[0], toUniqueServer: jest.fn()};
|
||||
});
|
||||
ServerManager.editServer.mockImplementation((id, team) => {
|
||||
if (id !== teamsCopy[0].id) {
|
||||
ServerManager.editServer.mockImplementation((id, server) => {
|
||||
if (id !== serversCopy[0].id) {
|
||||
return;
|
||||
}
|
||||
const newTeam = {
|
||||
...teamsCopy[0],
|
||||
...team,
|
||||
const newServer = {
|
||||
...serversCopy[0],
|
||||
...server,
|
||||
};
|
||||
teamsCopy = [newTeam];
|
||||
serversCopy = [newServer];
|
||||
});
|
||||
ServerManager.getAllServers.mockReturnValue(teamsCopy.map((team) => ({...team, toMattermostTeam: jest.fn()})));
|
||||
ServerManager.getAllServers.mockReturnValue(serversCopy.map((server) => ({...server, toUniqueServer: jest.fn()})));
|
||||
});
|
||||
|
||||
it('should do nothing when the server cannot be found', () => {
|
||||
@@ -235,58 +235,58 @@ describe('main/app/servers', () => {
|
||||
expect(ModalManager.addModal).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should edit the existing team', async () => {
|
||||
it('should edit the existing server', async () => {
|
||||
const promise = Promise.resolve({
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
});
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
Servers.handleEditServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
||||
expect(serversCopy).not.toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
tabs,
|
||||
}));
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'new-team',
|
||||
url: 'http://new-team.com',
|
||||
name: 'new-server',
|
||||
url: 'http://new-server.com',
|
||||
tabs,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleRemoveServerModal', () => {
|
||||
let teamsCopy;
|
||||
let serversCopy;
|
||||
|
||||
beforeEach(() => {
|
||||
getLocalURLString.mockReturnValue('/some/index.html');
|
||||
getLocalPreload.mockReturnValue('/some/preload.js');
|
||||
MainWindow.get.mockReturnValue({});
|
||||
|
||||
teamsCopy = JSON.parse(JSON.stringify(teams));
|
||||
serversCopy = JSON.parse(JSON.stringify(servers));
|
||||
ServerManager.getServer.mockImplementation((id) => {
|
||||
if (id !== teamsCopy[0].id) {
|
||||
if (id !== serversCopy[0].id) {
|
||||
return undefined;
|
||||
}
|
||||
return teamsCopy[0];
|
||||
return serversCopy[0];
|
||||
});
|
||||
ServerManager.removeServer.mockImplementation(() => {
|
||||
teamsCopy = [];
|
||||
serversCopy = [];
|
||||
});
|
||||
ServerManager.getAllServers.mockReturnValue(teamsCopy);
|
||||
ServerManager.getAllServers.mockReturnValue(serversCopy);
|
||||
});
|
||||
|
||||
it('should remove the existing team', async () => {
|
||||
it('should remove the existing server', async () => {
|
||||
const promise = Promise.resolve(true);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
Servers.handleRemoveServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(teamsCopy).not.toContainEqual(expect.objectContaining({
|
||||
expect(serversCopy).not.toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
@@ -294,11 +294,11 @@ describe('main/app/servers', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('should not remove the existing team when clicking Cancel', async () => {
|
||||
it('should not remove the existing server when clicking Cancel', async () => {
|
||||
const promise = Promise.resolve(false);
|
||||
ModalManager.addModal.mockReturnValue(promise);
|
||||
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
@@ -307,7 +307,7 @@ describe('main/app/servers', () => {
|
||||
|
||||
Servers.handleRemoveServerModal(null, 'server-1');
|
||||
await promise;
|
||||
expect(teamsCopy).toContainEqual(expect.objectContaining({
|
||||
expect(serversCopy).toContainEqual(expect.objectContaining({
|
||||
id: 'server-1',
|
||||
name: 'server-1',
|
||||
url: 'http://server-1.com',
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
import {IpcMainEvent, ipcMain} from 'electron';
|
||||
|
||||
import {MattermostTeam, Team} from 'types/config';
|
||||
import {UniqueServer, Server} from 'types/config';
|
||||
|
||||
import {UPDATE_SHORTCUT_MENU} from 'common/communication';
|
||||
import {Logger} from 'common/log';
|
||||
@@ -24,16 +24,16 @@ export const switchServer = (serverId: string, waitForViewToExist = false) => {
|
||||
ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config');
|
||||
return;
|
||||
}
|
||||
const nextTab = ServerManager.getLastActiveTabForServer(serverId);
|
||||
const nextView = ServerManager.getLastActiveTabForServer(serverId);
|
||||
if (waitForViewToExist) {
|
||||
const timeout = setInterval(() => {
|
||||
if (ViewManager.getView(nextTab.id)) {
|
||||
ViewManager.showById(nextTab.id);
|
||||
if (ViewManager.getView(nextView.id)) {
|
||||
ViewManager.showById(nextView.id);
|
||||
clearInterval(timeout);
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
ViewManager.showById(nextTab.id);
|
||||
ViewManager.showById(nextView.id);
|
||||
}
|
||||
ipcMain.emit(UPDATE_SHORTCUT_MENU);
|
||||
};
|
||||
@@ -49,11 +49,11 @@ export const handleNewServerModal = () => {
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
const modalPromise = ModalManager.addModal<MattermostTeam[], Team>('newServer', html, preload, ServerManager.getAllServers().map((team) => team.toMattermostTeam()), mainWindow, !ServerManager.hasServers());
|
||||
const modalPromise = ModalManager.addModal<UniqueServer[], Server>('newServer', html, preload, ServerManager.getAllServers().map((server) => server.toUniqueServer()), mainWindow, !ServerManager.hasServers());
|
||||
if (modalPromise) {
|
||||
modalPromise.then((data) => {
|
||||
const newTeam = ServerManager.addServer(data);
|
||||
switchServer(newTeam.id, true);
|
||||
const newServer = ServerManager.addServer(data);
|
||||
switchServer(newServer.id, true);
|
||||
}).catch((e) => {
|
||||
// e is undefined for user cancellation
|
||||
if (e) {
|
||||
@@ -80,13 +80,13 @@ export const handleEditServerModal = (e: IpcMainEvent, id: string) => {
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
const modalPromise = ModalManager.addModal<{currentTeams: MattermostTeam[]; team: MattermostTeam}, Team>(
|
||||
const modalPromise = ModalManager.addModal<{currentServers: UniqueServer[]; server: UniqueServer}, Server>(
|
||||
'editServer',
|
||||
html,
|
||||
preload,
|
||||
{
|
||||
currentTeams: ServerManager.getAllServers().map((team) => team.toMattermostTeam()),
|
||||
team: server.toMattermostTeam(),
|
||||
currentServers: ServerManager.getAllServers().map((server) => server.toUniqueServer()),
|
||||
server: server.toUniqueServer(),
|
||||
},
|
||||
mainWindow);
|
||||
if (modalPromise) {
|
||||
|
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import {
|
||||
handleCloseTab,
|
||||
handleOpenTab,
|
||||
} from './tabs';
|
||||
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
setTabIsOpen: jest.fn(),
|
||||
getTab: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
showById: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('main/app/tabs', () => {
|
||||
describe('handleCloseTab', () => {
|
||||
it('should close the specified tab and switch to the next open tab', () => {
|
||||
ServerManager.getTab.mockReturnValue({server: {id: 'server-1'}});
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-2'});
|
||||
handleCloseTab(null, 'tab-3');
|
||||
expect(ServerManager.setTabIsOpen).toBeCalledWith('tab-3', false);
|
||||
expect(ViewManager.showById).toBeCalledWith('tab-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleOpenTab', () => {
|
||||
it('should open the specified tab', () => {
|
||||
handleOpenTab(null, 'tab-1');
|
||||
expect(ViewManager.showById).toBeCalledWith('tab-1');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,73 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
const log = new Logger('App.Tabs');
|
||||
|
||||
export const handleCloseTab = (event: IpcMainEvent, tabId: string) => {
|
||||
log.debug('handleCloseTab', {tabId});
|
||||
|
||||
const tab = ServerManager.getTab(tabId);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
ServerManager.setTabIsOpen(tabId, false);
|
||||
const nextTab = ServerManager.getLastActiveTabForServer(tab.server.id);
|
||||
ViewManager.showById(nextTab.id);
|
||||
};
|
||||
|
||||
export const handleOpenTab = (event: IpcMainEvent, tabId: string) => {
|
||||
log.debug('handleOpenTab', {tabId});
|
||||
|
||||
ServerManager.setTabIsOpen(tabId, true);
|
||||
ViewManager.showById(tabId);
|
||||
};
|
||||
|
||||
export const selectNextTab = () => {
|
||||
selectTab((order) => order + 1);
|
||||
};
|
||||
|
||||
export const selectPreviousTab = () => {
|
||||
selectTab((order, length) => (length + (order - 1)));
|
||||
};
|
||||
|
||||
export const handleGetOrderedTabsForServer = (event: IpcMainInvokeEvent, serverId: string) => {
|
||||
return ServerManager.getOrderedTabsForServer(serverId).map((tab) => tab.toMattermostTab());
|
||||
};
|
||||
|
||||
export const handleGetLastActive = () => {
|
||||
const server = ServerManager.getCurrentServer();
|
||||
const tab = ServerManager.getLastActiveTabForServer(server.id);
|
||||
return {server: server.id, tab: tab.id};
|
||||
};
|
||||
|
||||
const selectTab = (fn: (order: number, length: number) => number) => {
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTeamTabs = ServerManager.getOrderedTabsForServer(currentView.tab.server.id).map((tab, index) => ({tab, index}));
|
||||
const filteredTabs = currentTeamTabs?.filter((tab) => tab.tab.isOpen);
|
||||
const currentTab = currentTeamTabs?.find((tab) => tab.tab.type === currentView.tab.type);
|
||||
if (!currentTeamTabs || !currentTab || !filteredTabs) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentOrder = currentTab.index;
|
||||
let nextIndex = -1;
|
||||
while (nextIndex === -1) {
|
||||
const nextOrder = (fn(currentOrder, currentTeamTabs.length) % currentTeamTabs.length);
|
||||
nextIndex = filteredTabs.findIndex((tab) => tab.index === nextOrder);
|
||||
currentOrder = nextOrder;
|
||||
}
|
||||
|
||||
const newTab = filteredTabs[nextIndex].tab;
|
||||
ViewManager.showById(newTab.id);
|
||||
};
|
39
src/main/app/views.test.js
Normal file
39
src/main/app/views.test.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
import {
|
||||
handleCloseView,
|
||||
handleOpenView,
|
||||
} from './views';
|
||||
|
||||
jest.mock('common/servers/serverManager', () => ({
|
||||
setViewIsOpen: jest.fn(),
|
||||
getView: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('main/views/viewManager', () => ({
|
||||
showById: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('main/app/views', () => {
|
||||
describe('handleCloseView', () => {
|
||||
it('should close the specified view and switch to the next open view', () => {
|
||||
ServerManager.getView.mockReturnValue({server: {id: 'server-1'}});
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-2'});
|
||||
handleCloseView(null, 'view-3');
|
||||
expect(ServerManager.setViewIsOpen).toBeCalledWith('view-3', false);
|
||||
expect(ViewManager.showById).toBeCalledWith('view-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleOpenView', () => {
|
||||
it('should open the specified view', () => {
|
||||
handleOpenView(null, 'view-1');
|
||||
expect(ViewManager.showById).toBeCalledWith('view-1');
|
||||
});
|
||||
});
|
||||
});
|
73
src/main/app/views.ts
Normal file
73
src/main/app/views.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {IpcMainEvent, IpcMainInvokeEvent} from 'electron';
|
||||
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {Logger} from 'common/log';
|
||||
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
const log = new Logger('App.Views');
|
||||
|
||||
export const handleCloseView = (event: IpcMainEvent, viewId: string) => {
|
||||
log.debug('handleCloseView', {viewId});
|
||||
|
||||
const view = ServerManager.getView(viewId);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
ServerManager.setViewIsOpen(viewId, false);
|
||||
const nextView = ServerManager.getLastActiveTabForServer(view.server.id);
|
||||
ViewManager.showById(nextView.id);
|
||||
};
|
||||
|
||||
export const handleOpenView = (event: IpcMainEvent, viewId: string) => {
|
||||
log.debug('handleOpenView', {viewId});
|
||||
|
||||
ServerManager.setViewIsOpen(viewId, true);
|
||||
ViewManager.showById(viewId);
|
||||
};
|
||||
|
||||
export const selectNextView = () => {
|
||||
selectView((order) => order + 1);
|
||||
};
|
||||
|
||||
export const selectPreviousView = () => {
|
||||
selectView((order, length) => (length + (order - 1)));
|
||||
};
|
||||
|
||||
export const handleGetOrderedViewsForServer = (event: IpcMainInvokeEvent, serverId: string) => {
|
||||
return ServerManager.getOrderedTabsForServer(serverId).map((view) => view.toUniqueView());
|
||||
};
|
||||
|
||||
export const handleGetLastActive = () => {
|
||||
const server = ServerManager.getCurrentServer();
|
||||
const view = ServerManager.getLastActiveTabForServer(server.id);
|
||||
return {server: server.id, view: view.id};
|
||||
};
|
||||
|
||||
const selectView = (fn: (order: number, length: number) => number) => {
|
||||
const currentView = ViewManager.getCurrentView();
|
||||
if (!currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentServerViews = ServerManager.getOrderedTabsForServer(currentView.view.server.id).map((view, index) => ({view, index}));
|
||||
const filteredViews = currentServerViews?.filter((view) => view.view.isOpen);
|
||||
const currentServerView = currentServerViews?.find((view) => view.view.type === currentView.view.type);
|
||||
if (!currentServerViews || !currentServerView || !filteredViews) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentOrder = currentServerView.index;
|
||||
let nextIndex = -1;
|
||||
while (nextIndex === -1) {
|
||||
const nextOrder = (fn(currentOrder, currentServerViews.length) % currentServerViews.length);
|
||||
nextIndex = filteredViews.findIndex((view) => view.index === nextOrder);
|
||||
currentOrder = nextOrder;
|
||||
}
|
||||
|
||||
const newView = filteredViews[nextIndex].view;
|
||||
ViewManager.showById(newView.id);
|
||||
};
|
@@ -7,54 +7,6 @@ import MainWindow from 'main/windows/mainWindow';
|
||||
import ModalManager from 'main/views/modalManager';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
|
||||
jest.mock('common/config', () => ({
|
||||
teams: [{
|
||||
name: 'example',
|
||||
url: 'http://example.com',
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'TAB_MESSAGING',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_FOCALBOARD',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_PLAYBOOKS',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 0,
|
||||
}, {
|
||||
name: 'github',
|
||||
url: 'https://github.com/',
|
||||
order: 1,
|
||||
tabs: [
|
||||
{
|
||||
name: 'TAB_MESSAGING',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_FOCALBOARD',
|
||||
order: 1,
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
name: 'TAB_PLAYBOOKS',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 0,
|
||||
}],
|
||||
}));
|
||||
|
||||
jest.mock('common/utils/url', () => {
|
||||
const actualUrl = jest.requireActual('common/utils/url');
|
||||
return {
|
||||
@@ -116,35 +68,35 @@ describe('main/authManager', () => {
|
||||
});
|
||||
|
||||
it('should popLoginModal when isTrustedURL', () => {
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://trustedurl.com/')}}});
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://trustedurl.com/')}}});
|
||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://trustedurl.com/'}, null, jest.fn());
|
||||
expect(authManager.popLoginModal).toBeCalled();
|
||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should popLoginModal when isCustomLoginURL', () => {
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://customloginurl.com/')}}});
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://customloginurl.com/')}}});
|
||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://customloginurl.com/'}, null, jest.fn());
|
||||
expect(authManager.popLoginModal).toBeCalled();
|
||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should popLoginModal when has permission', () => {
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://haspermissionurl.com/')}}});
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://haspermissionurl.com/')}}});
|
||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://haspermissionurl.com/'}, null, jest.fn());
|
||||
expect(authManager.popLoginModal).toBeCalled();
|
||||
expect(authManager.popPermissionModal).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should popPermissionModal when anything else is true', () => {
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://someotherurl.com/')}}});
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://someotherurl.com/')}}});
|
||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, jest.fn());
|
||||
expect(authManager.popLoginModal).not.toBeCalled();
|
||||
expect(authManager.popPermissionModal).toBeCalled();
|
||||
});
|
||||
|
||||
it('should set login callback when logging in', () => {
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({tab: {server: {url: new URL('http://someotherurl.com/')}}});
|
||||
ViewManager.getViewByWebContentsId.mockReturnValue({view: {server: {url: new URL('http://someotherurl.com/')}}});
|
||||
const callback = jest.fn();
|
||||
authManager.handleAppLogin({preventDefault: jest.fn()}, {id: 1}, {url: 'http://someotherurl.com/'}, null, callback);
|
||||
expect(authManager.loginCallbackMap.get('http://someotherurl.com/')).toEqual(callback);
|
||||
|
@@ -40,7 +40,7 @@ export class AuthManager {
|
||||
if (!parsedURL) {
|
||||
return;
|
||||
}
|
||||
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.url;
|
||||
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.view.server.url;
|
||||
if (!serverURL) {
|
||||
return;
|
||||
}
|
||||
|
@@ -15,26 +15,26 @@ const stepDescriptiveName = 'serverConnectivity';
|
||||
|
||||
const run = async (logger: ElectronLog): Promise<DiagnosticStepResponse> => {
|
||||
try {
|
||||
const teams = ServerManager.getAllServers();
|
||||
const servers = ServerManager.getAllServers();
|
||||
|
||||
await Promise.all(teams.map(async (team) => {
|
||||
logger.debug('Pinging server: ', team.url);
|
||||
await Promise.all(servers.map(async (server) => {
|
||||
logger.debug('Pinging server: ', server.url);
|
||||
|
||||
if (!team.name || !team.url) {
|
||||
throw new Error(`Invalid server configuration. Team Url: ${team.url}, team name: ${team.name}`);
|
||||
if (!server.name || !server.url) {
|
||||
throw new Error(`Invalid server configuration. Server Url: ${server.url}, server name: ${server.name}`);
|
||||
}
|
||||
|
||||
const serverOnline = await isOnline(logger, `${team.url}/api/v4/system/ping`);
|
||||
const serverOnline = await isOnline(logger, `${server.url}/api/v4/system/ping`);
|
||||
|
||||
if (!serverOnline) {
|
||||
throw new Error(`Server appears to be offline. Team url: ${team.url}`);
|
||||
throw new Error(`Server appears to be offline. Server url: ${server.url}`);
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
message: `${stepName} finished successfully`,
|
||||
succeeded: true,
|
||||
payload: teams,
|
||||
payload: servers,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn(`Diagnostics ${stepName} Failure`, {error});
|
||||
|
@@ -554,7 +554,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
|
||||
log.debug('doneEventController', {state});
|
||||
|
||||
if (state === 'completed' && !this.open) {
|
||||
displayDownloadCompleted(path.basename(item.savePath), item.savePath, ViewManager.getViewByWebContentsId(webContents.id)?.tab.server.name ?? '');
|
||||
displayDownloadCompleted(path.basename(item.savePath), item.savePath, ViewManager.getViewByWebContentsId(webContents.id)?.view.server.name ?? '');
|
||||
}
|
||||
|
||||
const bookmark = this.bookmarks.get(this.getFileId(item));
|
||||
|
@@ -67,8 +67,8 @@ jest.mock('main/windows/mainWindow', () => ({
|
||||
sendToRenderer: jest.fn(),
|
||||
}));
|
||||
jest.mock('main/windows/settingsWindow', () => ({}));
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getTabDisplayName: (name) => name,
|
||||
jest.mock('common/views/View', () => ({
|
||||
getViewDisplayName: (name) => name,
|
||||
}));
|
||||
|
||||
describe('main/menus/app', () => {
|
||||
@@ -88,19 +88,19 @@ describe('main/menus/app', () => {
|
||||
url: 'https:/ /github.com/',
|
||||
},
|
||||
];
|
||||
const tabs = [
|
||||
const views = [
|
||||
{
|
||||
id: 'tab-1',
|
||||
id: 'view-1',
|
||||
name: 'TAB_MESSAGING',
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
id: 'tab-2',
|
||||
id: 'view-2',
|
||||
name: 'TAB_FOCALBOARD',
|
||||
isOpen: true,
|
||||
},
|
||||
{
|
||||
id: 'tab-3',
|
||||
id: 'view-3',
|
||||
name: 'TAB_PLAYBOOKS',
|
||||
isOpen: true,
|
||||
},
|
||||
@@ -109,7 +109,7 @@ describe('main/menus/app', () => {
|
||||
beforeEach(() => {
|
||||
ServerManager.getCurrentServer.mockReturnValue(servers[0]);
|
||||
ServerManager.getOrderedServers.mockReturnValue(servers);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(tabs);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(views);
|
||||
getDarwinDoNotDisturb.mockReturnValue(false);
|
||||
});
|
||||
|
||||
@@ -217,7 +217,7 @@ describe('main/menus/app', () => {
|
||||
expect(signInOption).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should not show `Sign in to Another Server` if no teams are configured', () => {
|
||||
it('should not show `Sign in to Another Server` if no servers are configured', () => {
|
||||
localizeMessage.mockImplementation((id) => {
|
||||
switch (id) {
|
||||
case 'main.menus.app.file':
|
||||
@@ -247,15 +247,15 @@ describe('main/menus/app', () => {
|
||||
name: `server-${key}`,
|
||||
url: `http://server-${key}.com`,
|
||||
}));
|
||||
const modifiedTabs = [
|
||||
const modifiedViews = [
|
||||
{
|
||||
id: 'tab-1',
|
||||
id: 'view-1',
|
||||
type: 'TAB_MESSAGING',
|
||||
isOpen: true,
|
||||
},
|
||||
];
|
||||
ServerManager.getOrderedServers.mockReturnValue(modifiedServers);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedTabs);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedViews);
|
||||
const menu = createTemplate(config);
|
||||
const windowMenu = menu.find((item) => item.label === '&Window');
|
||||
for (let i = 0; i < 9; i++) {
|
||||
@@ -268,32 +268,32 @@ describe('main/menus/app', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should show the first 9 tabs (using order) in the Window menu', () => {
|
||||
it('should show the first 9 views (using order) in the Window menu', () => {
|
||||
localizeMessage.mockImplementation((id) => {
|
||||
if (id === 'main.menus.app.window') {
|
||||
return '&Window';
|
||||
}
|
||||
if (id.startsWith('common.tabs')) {
|
||||
return id.replace('common.tabs.', '');
|
||||
if (id.startsWith('common.views')) {
|
||||
return id.replace('common.views.', '');
|
||||
}
|
||||
return id;
|
||||
});
|
||||
ServerManager.getCurrentServer.mockImplementation(() => ({id: servers[0].id}));
|
||||
|
||||
const modifiedTabs = [...Array(15).keys()].map((key) => ({
|
||||
id: `tab-${key}`,
|
||||
type: `tab-${key}`,
|
||||
const modifiedViews = [...Array(15).keys()].map((key) => ({
|
||||
id: `view-${key}`,
|
||||
type: `view-${key}`,
|
||||
isOpen: true,
|
||||
}));
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedTabs);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue(modifiedViews);
|
||||
const menu = createTemplate(config);
|
||||
const windowMenu = menu.find((item) => item.label === '&Window');
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` view-${i}`);
|
||||
expect(menuItem).not.toBe(undefined);
|
||||
}
|
||||
for (let i = 9; i < 15; i++) {
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` tab-${i}`);
|
||||
const menuItem = windowMenu.submenu.find((item) => item.label === ` view-${i}`);
|
||||
expect(menuItem).toBe(undefined);
|
||||
}
|
||||
});
|
||||
|
@@ -6,9 +6,9 @@
|
||||
import {app, ipcMain, Menu, MenuItemConstructorOptions, MenuItem, session, shell, WebContents, clipboard} from 'electron';
|
||||
import log from 'electron-log';
|
||||
|
||||
import {OPEN_TEAMS_DROPDOWN, SHOW_NEW_SERVER_MODAL} from 'common/communication';
|
||||
import {OPEN_SERVERS_DROPDOWN, SHOW_NEW_SERVER_MODAL} from 'common/communication';
|
||||
import {t} from 'common/utils/util';
|
||||
import {getTabDisplayName, TabType} from 'common/tabs/TabView';
|
||||
import {getViewDisplayName, ViewType} from 'common/views/View';
|
||||
import {Config} from 'common/config';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
@@ -18,7 +18,7 @@ import downloadsManager from 'main/downloadsManager';
|
||||
import Diagnostics from 'main/diagnostics';
|
||||
import ViewManager from 'main/views/viewManager';
|
||||
import SettingsWindow from 'main/windows/settingsWindow';
|
||||
import {selectNextTab, selectPreviousTab} from 'main/app/tabs';
|
||||
import {selectNextView, selectPreviousView} from 'main/app/views';
|
||||
import {switchServer} from 'main/app/servers';
|
||||
|
||||
export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
@@ -233,7 +233,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
}],
|
||||
});
|
||||
|
||||
const teams = ServerManager.getOrderedServers();
|
||||
const servers = ServerManager.getOrderedServers();
|
||||
const windowMenu = {
|
||||
id: 'window',
|
||||
label: localizeMessage('main.menus.app.window', '&Window'),
|
||||
@@ -257,25 +257,25 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
label: localizeMessage('main.menus.app.window.showServers', 'Show Servers'),
|
||||
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`,
|
||||
click() {
|
||||
ipcMain.emit(OPEN_TEAMS_DROPDOWN);
|
||||
ipcMain.emit(OPEN_SERVERS_DROPDOWN);
|
||||
},
|
||||
}] : []),
|
||||
...teams.slice(0, 9).map((team, i) => {
|
||||
...servers.slice(0, 9).map((server, i) => {
|
||||
const items = [];
|
||||
items.push({
|
||||
label: team.name,
|
||||
label: server.name,
|
||||
accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+${i + 1}`,
|
||||
click() {
|
||||
switchServer(team.id);
|
||||
switchServer(server.id);
|
||||
},
|
||||
});
|
||||
if (ServerManager.getCurrentServer().id === team.id) {
|
||||
ServerManager.getOrderedTabsForServer(team.id).slice(0, 9).forEach((tab, i) => {
|
||||
if (ServerManager.getCurrentServer().id === server.id) {
|
||||
ServerManager.getOrderedTabsForServer(server.id).slice(0, 9).forEach((view, i) => {
|
||||
items.push({
|
||||
label: ` ${localizeMessage(`common.tabs.${tab.type}`, getTabDisplayName(tab.type as TabType))}`,
|
||||
label: ` ${localizeMessage(`common.views.${view.type}`, getViewDisplayName(view.type as ViewType))}`,
|
||||
accelerator: `CmdOrCtrl+${i + 1}`,
|
||||
click() {
|
||||
ViewManager.showById(tab.id);
|
||||
ViewManager.showById(view.id);
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -285,16 +285,16 @@ export function createTemplate(config: Config, updateManager: UpdateManager) {
|
||||
label: localizeMessage('main.menus.app.window.selectNextTab', 'Select Next Tab'),
|
||||
accelerator: 'Ctrl+Tab',
|
||||
click() {
|
||||
selectNextTab();
|
||||
selectNextView();
|
||||
},
|
||||
enabled: (teams.length > 1),
|
||||
enabled: (servers.length > 1),
|
||||
}, {
|
||||
label: localizeMessage('main.menus.app.window.selectPreviousTab', 'Select Previous Tab'),
|
||||
accelerator: 'Ctrl+Shift+Tab',
|
||||
click() {
|
||||
selectPreviousTab();
|
||||
selectPreviousView();
|
||||
},
|
||||
enabled: (teams.length > 1),
|
||||
enabled: (servers.length > 1),
|
||||
}, ...(isMac ? [separatorItem, {
|
||||
role: 'front',
|
||||
label: localizeMessage('main.menus.app.window.bringAllToFront', 'Bring All to Front'),
|
||||
|
@@ -12,13 +12,13 @@ import SettingsWindow from 'main/windows/settingsWindow';
|
||||
import {switchServer} from 'main/app/servers';
|
||||
|
||||
export function createTemplate() {
|
||||
const teams = ServerManager.getOrderedServers();
|
||||
const servers = ServerManager.getOrderedServers();
|
||||
const template = [
|
||||
...teams.slice(0, 9).map((team) => {
|
||||
...servers.slice(0, 9).map((server) => {
|
||||
return {
|
||||
label: team.name.length > 50 ? `${team.name.slice(0, 50)}...` : team.name,
|
||||
label: server.name.length > 50 ? `${server.name.slice(0, 50)}...` : server.name,
|
||||
click: () => {
|
||||
switchServer(team.id);
|
||||
switchServer(server.id);
|
||||
},
|
||||
};
|
||||
}), {
|
||||
|
@@ -76,7 +76,7 @@ jest.mock('macos-notification-state', () => ({
|
||||
jest.mock('../views/viewManager', () => ({
|
||||
getViewByWebContentsId: () => ({
|
||||
id: 'server_id',
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
name: 'server_name',
|
||||
},
|
||||
@@ -231,7 +231,7 @@ describe('main/notifications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should switch tab when clicking on notification', () => {
|
||||
it('should switch view when clicking on notification', () => {
|
||||
displayMention(
|
||||
'click_test',
|
||||
'mention_click_body',
|
||||
|
@@ -40,7 +40,7 @@ export function displayMention(title: string, body: string, channel: {id: string
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
const serverName = view.tab.server.name;
|
||||
const serverName = view.view.server.name;
|
||||
|
||||
const options = {
|
||||
title: `${serverName}: ${title}`,
|
||||
|
@@ -10,10 +10,10 @@ import {
|
||||
GET_LANGUAGE_INFORMATION,
|
||||
QUIT,
|
||||
OPEN_APP_MENU,
|
||||
CLOSE_TEAMS_DROPDOWN,
|
||||
OPEN_TEAMS_DROPDOWN,
|
||||
CLOSE_SERVERS_DROPDOWN,
|
||||
OPEN_SERVERS_DROPDOWN,
|
||||
SWITCH_TAB,
|
||||
CLOSE_TAB,
|
||||
CLOSE_VIEW,
|
||||
WINDOW_CLOSE,
|
||||
WINDOW_MINIMIZE,
|
||||
WINDOW_MAXIMIZE,
|
||||
@@ -59,8 +59,8 @@ import {
|
||||
DOWNLOADS_DROPDOWN_MENU_OPEN_FILE,
|
||||
UPDATE_DOWNLOADS_DROPDOWN_MENU,
|
||||
REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO,
|
||||
UPDATE_TEAMS_DROPDOWN,
|
||||
REQUEST_TEAMS_DROPDOWN_INFO,
|
||||
UPDATE_SERVERS_DROPDOWN,
|
||||
REQUEST_SERVERS_DROPDOWN_INFO,
|
||||
RECEIVE_DROPDOWN_MENU_SIZE,
|
||||
SWITCH_SERVER,
|
||||
SHOW_NEW_SERVER_MODAL,
|
||||
@@ -111,10 +111,10 @@ contextBridge.exposeInMainWorld('mas', {
|
||||
contextBridge.exposeInMainWorld('desktop', {
|
||||
quit: (reason, stack) => ipcRenderer.send(QUIT, reason, stack),
|
||||
openAppMenu: () => ipcRenderer.send(OPEN_APP_MENU),
|
||||
closeTeamsDropdown: () => ipcRenderer.send(CLOSE_TEAMS_DROPDOWN),
|
||||
openTeamsDropdown: () => ipcRenderer.send(OPEN_TEAMS_DROPDOWN),
|
||||
switchTab: (tabId) => ipcRenderer.send(SWITCH_TAB, tabId),
|
||||
closeTab: (tabId) => ipcRenderer.send(CLOSE_TAB, tabId),
|
||||
closeServersDropdown: () => ipcRenderer.send(CLOSE_SERVERS_DROPDOWN),
|
||||
openServersDropdown: () => ipcRenderer.send(OPEN_SERVERS_DROPDOWN),
|
||||
switchTab: (viewId) => ipcRenderer.send(SWITCH_TAB, viewId),
|
||||
closeView: (viewId) => ipcRenderer.send(CLOSE_VIEW, viewId),
|
||||
closeWindow: () => ipcRenderer.send(WINDOW_CLOSE),
|
||||
minimizeWindow: () => ipcRenderer.send(WINDOW_MINIMIZE),
|
||||
maximizeWindow: () => ipcRenderer.send(WINDOW_MAXIMIZE),
|
||||
@@ -130,7 +130,7 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
updateConfiguration: (saveQueueItems) => ipcRenderer.send(UPDATE_CONFIGURATION, saveQueueItems),
|
||||
|
||||
updateServerOrder: (serverOrder) => ipcRenderer.send(UPDATE_SERVER_ORDER, serverOrder),
|
||||
updateTabOrder: (serverId, tabOrder) => ipcRenderer.send(UPDATE_TAB_ORDER, serverId, tabOrder),
|
||||
updateTabOrder: (serverId, viewOrder) => ipcRenderer.send(UPDATE_TAB_ORDER, serverId, viewOrder),
|
||||
getLastActive: () => ipcRenderer.invoke(GET_LAST_ACTIVE),
|
||||
getOrderedServers: () => ipcRenderer.invoke(GET_ORDERED_SERVERS),
|
||||
getOrderedTabsForServer: (serverId) => ipcRenderer.invoke(GET_ORDERED_TABS_FOR_SERVER, serverId),
|
||||
@@ -153,7 +153,7 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
onLoadRetry: (listener) => ipcRenderer.on(LOAD_RETRY, (_, viewId, retry, err, loadUrl) => listener(viewId, retry, err, loadUrl)),
|
||||
onLoadSuccess: (listener) => ipcRenderer.on(LOAD_SUCCESS, (_, viewId) => listener(viewId)),
|
||||
onLoadFailed: (listener) => ipcRenderer.on(LOAD_FAILED, (_, viewId, err, loadUrl) => listener(viewId, err, loadUrl)),
|
||||
onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverId, tabId) => listener(serverId, tabId)),
|
||||
onSetActiveView: (listener) => ipcRenderer.on(SET_ACTIVE_VIEW, (_, serverId, viewId) => listener(serverId, viewId)),
|
||||
onMaximizeChange: (listener) => ipcRenderer.on(MAXIMIZE_CHANGE, (_, maximize) => listener(maximize)),
|
||||
onEnterFullScreen: (listener) => ipcRenderer.on('enter-full-screen', () => listener()),
|
||||
onLeaveFullScreen: (listener) => ipcRenderer.on('leave-full-screen', () => listener()),
|
||||
@@ -162,8 +162,8 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
onModalClose: (listener) => ipcRenderer.on(MODAL_CLOSE, () => listener()),
|
||||
onToggleBackButton: (listener) => ipcRenderer.on(TOGGLE_BACK_BUTTON, (_, showExtraBar) => listener(showExtraBar)),
|
||||
onUpdateMentions: (listener) => ipcRenderer.on(UPDATE_MENTIONS, (_event, view, mentions, unreads, isExpired) => listener(view, mentions, unreads, isExpired)),
|
||||
onCloseTeamsDropdown: (listener) => ipcRenderer.on(CLOSE_TEAMS_DROPDOWN, () => listener()),
|
||||
onOpenTeamsDropdown: (listener) => ipcRenderer.on(OPEN_TEAMS_DROPDOWN, () => listener()),
|
||||
onCloseServersDropdown: (listener) => ipcRenderer.on(CLOSE_SERVERS_DROPDOWN, () => listener()),
|
||||
onOpenServersDropdown: (listener) => ipcRenderer.on(OPEN_SERVERS_DROPDOWN, () => listener()),
|
||||
onCloseDownloadsDropdown: (listener) => ipcRenderer.on(CLOSE_DOWNLOADS_DROPDOWN, () => listener()),
|
||||
onOpenDownloadsDropdown: (listener) => ipcRenderer.on(OPEN_DOWNLOADS_DROPDOWN, () => listener()),
|
||||
onShowDownloadsDropdownButtonBadge: (listener) => ipcRenderer.on(SHOW_DOWNLOADS_DROPDOWN_BUTTON_BADGE, () => listener()),
|
||||
@@ -195,29 +195,29 @@ contextBridge.exposeInMainWorld('desktop', {
|
||||
},
|
||||
|
||||
serverDropdown: {
|
||||
requestInfo: () => ipcRenderer.send(REQUEST_TEAMS_DROPDOWN_INFO),
|
||||
requestInfo: () => ipcRenderer.send(REQUEST_SERVERS_DROPDOWN_INFO),
|
||||
sendSize: (width, height) => ipcRenderer.send(RECEIVE_DROPDOWN_MENU_SIZE, width, height),
|
||||
switchServer: (serverId) => ipcRenderer.send(SWITCH_SERVER, serverId),
|
||||
showNewServerModal: () => ipcRenderer.send(SHOW_NEW_SERVER_MODAL),
|
||||
showEditServerModal: (serverId) => ipcRenderer.send(SHOW_EDIT_SERVER_MODAL, serverId),
|
||||
showRemoveServerModal: (serverId) => ipcRenderer.send(SHOW_REMOVE_SERVER_MODAL, serverId),
|
||||
|
||||
onUpdateServerDropdown: (listener) => ipcRenderer.on(UPDATE_TEAMS_DROPDOWN, (_,
|
||||
teams,
|
||||
activeTeam,
|
||||
onUpdateServerDropdown: (listener) => ipcRenderer.on(UPDATE_SERVERS_DROPDOWN, (_,
|
||||
servers,
|
||||
activeServer,
|
||||
darkMode,
|
||||
enableServerManagement,
|
||||
hasGPOTeams,
|
||||
hasGPOServers,
|
||||
expired,
|
||||
mentions,
|
||||
unreads,
|
||||
windowBounds,
|
||||
) => listener(
|
||||
teams,
|
||||
activeTeam,
|
||||
servers,
|
||||
activeServer,
|
||||
darkMode,
|
||||
enableServerManagement,
|
||||
hasGPOTeams,
|
||||
hasGPOServers,
|
||||
expired,
|
||||
mentions,
|
||||
unreads,
|
||||
|
@@ -20,7 +20,7 @@ import {
|
||||
SET_VIEW_OPTIONS,
|
||||
REACT_APP_INITIALIZED,
|
||||
USER_ACTIVITY_UPDATE,
|
||||
CLOSE_TEAMS_DROPDOWN,
|
||||
CLOSE_SERVERS_DROPDOWN,
|
||||
BROWSER_HISTORY_BUTTON,
|
||||
BROWSER_HISTORY_PUSH,
|
||||
APP_LOGGED_IN,
|
||||
@@ -272,7 +272,7 @@ function isDownloadLink(el) {
|
||||
}
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
ipcRenderer.send(CLOSE_TEAMS_DROPDOWN);
|
||||
ipcRenderer.send(CLOSE_SERVERS_DROPDOWN);
|
||||
const el = e.target;
|
||||
if (!isDownloadLink(el)) {
|
||||
ipcRenderer.send(CLOSE_DOWNLOADS_DROPDOWN);
|
||||
|
@@ -6,13 +6,13 @@
|
||||
import AppState from 'common/appState';
|
||||
import {LOAD_FAILED, TOGGLE_BACK_BUTTON, UPDATE_TARGET_URL} from 'common/communication';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import MessagingTabView from 'common/tabs/MessagingTabView';
|
||||
import MessagingView from 'common/views/MessagingView';
|
||||
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
import ContextMenu from '../contextMenu';
|
||||
import Utils from '../utils';
|
||||
|
||||
import {MattermostView} from './MattermostView';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
|
||||
jest.mock('electron', () => ({
|
||||
app: {
|
||||
@@ -59,12 +59,12 @@ jest.mock('../utils', () => ({
|
||||
}));
|
||||
|
||||
const server = new MattermostServer({name: 'server_name', url: 'http://server-1.com'});
|
||||
const tabView = new MessagingTabView(server, true);
|
||||
const view = new MessagingView(server, true);
|
||||
|
||||
describe('main/views/MattermostView', () => {
|
||||
describe('main/views/MattermostBrowserView', () => {
|
||||
describe('load', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -74,38 +74,38 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
it('should load provided URL when provided', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('http://server-2.com');
|
||||
await promise;
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-2.com/', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-2.com/', expect.any(Object));
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-2.com/');
|
||||
});
|
||||
|
||||
it('should load server URL when not provided', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load();
|
||||
await promise;
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
|
||||
});
|
||||
|
||||
it('should load server URL when bad url provided', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('a-bad<url');
|
||||
await promise;
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com/');
|
||||
});
|
||||
|
||||
it('should call retry when failing to load', async () => {
|
||||
const error = new Error('test');
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('a-bad<url');
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com/', error);
|
||||
});
|
||||
|
||||
@@ -113,23 +113,23 @@ describe('main/views/MattermostView', () => {
|
||||
const error = new Error('test');
|
||||
error.code = 'ERR_CERT_ERROR';
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.load('a-bad<url');
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('retry', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
const retryInBackgroundFn = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => Promise.resolve());
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => Promise.resolve());
|
||||
mattermostView.loadSuccess = jest.fn();
|
||||
mattermostView.loadRetry = jest.fn();
|
||||
mattermostView.emit = jest.fn();
|
||||
@@ -143,16 +143,16 @@ describe('main/views/MattermostView', () => {
|
||||
});
|
||||
|
||||
it('should do nothing when webcontents are destroyed', () => {
|
||||
const webContents = mattermostView.view.webContents;
|
||||
mattermostView.view.webContents = null;
|
||||
const webContents = mattermostView.browserView.webContents;
|
||||
mattermostView.browserView.webContents = null;
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
expect(mattermostView.loadSuccess).not.toBeCalled();
|
||||
mattermostView.view.webContents = webContents;
|
||||
mattermostView.browserView.webContents = webContents;
|
||||
});
|
||||
|
||||
it('should call loadSuccess on successful load', async () => {
|
||||
const promise = Promise.resolve();
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
await promise;
|
||||
expect(mattermostView.loadSuccess).toBeCalledWith('http://server-1.com');
|
||||
@@ -162,10 +162,10 @@ describe('main/views/MattermostView', () => {
|
||||
mattermostView.maxRetries = 10;
|
||||
const error = new Error('test');
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com', error);
|
||||
});
|
||||
|
||||
@@ -173,12 +173,12 @@ describe('main/views/MattermostView', () => {
|
||||
mattermostView.maxRetries = 0;
|
||||
const error = new Error('test');
|
||||
const promise = Promise.reject(error);
|
||||
mattermostView.view.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.browserView.webContents.loadURL.mockImplementation(() => promise);
|
||||
mattermostView.retry('http://server-1.com')();
|
||||
await expect(promise).rejects.toThrow(error);
|
||||
expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.browserView.webContents.loadURL).toBeCalledWith('http://server-1.com', expect.any(Object));
|
||||
expect(mattermostView.loadRetry).not.toBeCalled();
|
||||
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.id, expect.any(String), expect.any(String));
|
||||
expect(MainWindow.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.view.id, expect.any(String), expect.any(String));
|
||||
expect(mattermostView.status).toBe(-1);
|
||||
jest.runAllTimers();
|
||||
expect(retryInBackgroundFn).toBeCalled();
|
||||
@@ -187,7 +187,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('goToOffset', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.reload = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
@@ -196,18 +196,18 @@ describe('main/views/MattermostView', () => {
|
||||
});
|
||||
|
||||
it('should only go to offset if it can', () => {
|
||||
mattermostView.view.webContents.canGoToOffset.mockReturnValue(false);
|
||||
mattermostView.browserView.webContents.canGoToOffset.mockReturnValue(false);
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.view.webContents.goToOffset).not.toBeCalled();
|
||||
expect(mattermostView.browserView.webContents.goToOffset).not.toBeCalled();
|
||||
|
||||
mattermostView.view.webContents.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.browserView.webContents.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.goToOffset(1);
|
||||
expect(mattermostView.view.webContents.goToOffset).toBeCalled();
|
||||
expect(mattermostView.browserView.webContents.goToOffset).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call reload if an error occurs', () => {
|
||||
mattermostView.view.webContents.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.view.webContents.goToOffset.mockImplementation(() => {
|
||||
mattermostView.browserView.webContents.canGoToOffset.mockReturnValue(true);
|
||||
mattermostView.browserView.webContents.goToOffset.mockImplementation(() => {
|
||||
throw new Error('hi');
|
||||
});
|
||||
mattermostView.goToOffset(1);
|
||||
@@ -217,8 +217,8 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('onLogin', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.getURL = jest.fn();
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.getURL = jest.fn();
|
||||
mattermostView.reload = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
@@ -227,19 +227,19 @@ describe('main/views/MattermostView', () => {
|
||||
});
|
||||
|
||||
it('should reload view when URL is not on subpath of original server URL', () => {
|
||||
mattermostView.view.webContents.getURL.mockReturnValue('http://server-2.com/subpath');
|
||||
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-2.com/subpath');
|
||||
mattermostView.onLogin(true);
|
||||
expect(mattermostView.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload if URLs are matching', () => {
|
||||
mattermostView.view.webContents.getURL.mockReturnValue('http://server-1.com');
|
||||
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-1.com');
|
||||
mattermostView.onLogin(true);
|
||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not reload if URL is subpath of server URL', () => {
|
||||
mattermostView.view.webContents.getURL.mockReturnValue('http://server-1.com/subpath');
|
||||
mattermostView.browserView.webContents.getURL.mockReturnValue('http://server-1.com/subpath');
|
||||
mattermostView.onLogin(true);
|
||||
expect(mattermostView.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -247,7 +247,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('loadSuccess', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
@@ -275,7 +275,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('show', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
@@ -293,7 +293,7 @@ describe('main/views/MattermostView', () => {
|
||||
it('should add browser view to window and set bounds when request is true and view not currently visible', () => {
|
||||
mattermostView.isVisible = false;
|
||||
mattermostView.show();
|
||||
expect(window.addBrowserView).toBeCalledWith(mattermostView.view);
|
||||
expect(window.addBrowserView).toBeCalledWith(mattermostView.browserView);
|
||||
expect(mattermostView.setBounds).toBeCalled();
|
||||
expect(mattermostView.isVisible).toBe(true);
|
||||
});
|
||||
@@ -314,7 +314,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('hide', () => {
|
||||
const window = {addBrowserView: jest.fn(), removeBrowserView: jest.fn(), on: jest.fn(), setTopBrowserView: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -323,7 +323,7 @@ describe('main/views/MattermostView', () => {
|
||||
it('should remove browser view', () => {
|
||||
mattermostView.isVisible = true;
|
||||
mattermostView.hide();
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
|
||||
expect(mattermostView.isVisible).toBe(false);
|
||||
});
|
||||
|
||||
@@ -336,7 +336,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('updateHistoryButton', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -345,7 +345,7 @@ describe('main/views/MattermostView', () => {
|
||||
it('should erase history and set isAtRoot when navigating to root URL', () => {
|
||||
mattermostView.atRoot = false;
|
||||
mattermostView.updateHistoryButton();
|
||||
expect(mattermostView.view.webContents.clearHistory).toHaveBeenCalled();
|
||||
expect(mattermostView.browserView.webContents.clearHistory).toHaveBeenCalled();
|
||||
expect(mattermostView.isAtRoot).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -362,22 +362,22 @@ describe('main/views/MattermostView', () => {
|
||||
});
|
||||
|
||||
it('should remove browser view from window', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.view);
|
||||
expect(window.removeBrowserView).toBeCalledWith(mattermostView.browserView);
|
||||
});
|
||||
|
||||
it('should clear mentions', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.destroy = jest.fn();
|
||||
mattermostView.destroy();
|
||||
expect(AppState.clear).toBeCalledWith(mattermostView.tab.id);
|
||||
expect(AppState.clear).toBeCalledWith(mattermostView.view.id);
|
||||
});
|
||||
|
||||
it('should clear outstanding timeouts', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
mattermostView.view.webContents.destroy = jest.fn();
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
mattermostView.browserView.webContents.destroy = jest.fn();
|
||||
const spy = jest.spyOn(global, 'clearTimeout');
|
||||
mattermostView.retryLoad = 999;
|
||||
mattermostView.removeLoading = 1000;
|
||||
@@ -388,7 +388,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('handleInputEvents', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
it('should open three dot menu on pressing Alt', () => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -413,7 +413,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('handleDidNavigate', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -435,7 +435,7 @@ describe('main/views/MattermostView', () => {
|
||||
|
||||
describe('handleUpdateTarget', () => {
|
||||
const window = {on: jest.fn()};
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
beforeEach(() => {
|
||||
MainWindow.get.mockReturnValue(window);
|
||||
@@ -466,16 +466,16 @@ describe('main/views/MattermostView', () => {
|
||||
});
|
||||
|
||||
describe('updateMentionsFromTitle', () => {
|
||||
const mattermostView = new MattermostView(tabView, {}, {});
|
||||
const mattermostView = new MattermostBrowserView(view, {}, {});
|
||||
|
||||
it('should parse mentions from title', () => {
|
||||
mattermostView.updateMentionsFromTitle('(7) Mattermost');
|
||||
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.id, 7);
|
||||
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.view.id, 7);
|
||||
});
|
||||
|
||||
it('should parse unreads from title', () => {
|
||||
mattermostView.updateMentionsFromTitle('* Mattermost');
|
||||
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.tab.id, 0);
|
||||
expect(AppState.updateMentions).toHaveBeenCalledWith(mattermostView.view.id, 0);
|
||||
});
|
||||
});
|
||||
});
|
@@ -23,7 +23,7 @@ import {
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {Logger} from 'common/log';
|
||||
import {isInternalURL, parseURL} from 'common/utils/url';
|
||||
import {TabView} from 'common/tabs/TabView';
|
||||
import {MattermostView} from 'common/views/View';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
@@ -42,12 +42,12 @@ enum Status {
|
||||
const MENTIONS_GROUP = 2;
|
||||
const titleParser = /(\((\d+)\) )?(\* )?/g;
|
||||
|
||||
export class MattermostView extends EventEmitter {
|
||||
tab: TabView;
|
||||
export class MattermostBrowserView extends EventEmitter {
|
||||
view: MattermostView;
|
||||
isVisible: boolean;
|
||||
|
||||
private log: Logger;
|
||||
private view: BrowserView;
|
||||
private browserView: BrowserView;
|
||||
private loggedIn: boolean;
|
||||
private atRoot: boolean;
|
||||
private options: BrowserViewConstructorOptions;
|
||||
@@ -58,9 +58,9 @@ export class MattermostView extends EventEmitter {
|
||||
private maxRetries: number;
|
||||
private altPressStatus: boolean;
|
||||
|
||||
constructor(tab: TabView, options: BrowserViewConstructorOptions) {
|
||||
constructor(view: MattermostView, options: BrowserViewConstructorOptions) {
|
||||
super();
|
||||
this.tab = tab;
|
||||
this.view = view;
|
||||
|
||||
const preload = getLocalPreload('preload.js');
|
||||
this.options = Object.assign({}, options);
|
||||
@@ -75,24 +75,24 @@ export class MattermostView extends EventEmitter {
|
||||
this.isVisible = false;
|
||||
this.loggedIn = false;
|
||||
this.atRoot = true;
|
||||
this.view = new BrowserView(this.options);
|
||||
this.browserView = new BrowserView(this.options);
|
||||
this.resetLoadingStatus();
|
||||
|
||||
this.log = ServerManager.getViewLog(this.id, 'MattermostView');
|
||||
this.log = ServerManager.getViewLog(this.id, 'MattermostBrowserView');
|
||||
this.log.verbose('View created');
|
||||
|
||||
this.view.webContents.on('did-finish-load', this.handleDidFinishLoad);
|
||||
this.view.webContents.on('page-title-updated', this.handleTitleUpdate);
|
||||
this.view.webContents.on('page-favicon-updated', this.handleFaviconUpdate);
|
||||
this.view.webContents.on('update-target-url', this.handleUpdateTarget);
|
||||
this.view.webContents.on('did-navigate', this.handleDidNavigate);
|
||||
this.browserView.webContents.on('did-finish-load', this.handleDidFinishLoad);
|
||||
this.browserView.webContents.on('page-title-updated', this.handleTitleUpdate);
|
||||
this.browserView.webContents.on('page-favicon-updated', this.handleFaviconUpdate);
|
||||
this.browserView.webContents.on('update-target-url', this.handleUpdateTarget);
|
||||
this.browserView.webContents.on('did-navigate', this.handleDidNavigate);
|
||||
if (process.platform !== 'darwin') {
|
||||
this.view.webContents.on('before-input-event', this.handleInputEvents);
|
||||
this.browserView.webContents.on('before-input-event', this.handleInputEvents);
|
||||
}
|
||||
|
||||
WebContentsEventManager.addWebContentsEventListeners(this.view.webContents);
|
||||
WebContentsEventManager.addWebContentsEventListeners(this.browserView.webContents);
|
||||
|
||||
this.contextMenu = new ContextMenu({}, this.view);
|
||||
this.contextMenu = new ContextMenu({}, this.browserView);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
|
||||
this.altPressStatus = false;
|
||||
@@ -105,7 +105,7 @@ export class MattermostView extends EventEmitter {
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.tab.id;
|
||||
return this.view.id;
|
||||
}
|
||||
get isAtRoot() {
|
||||
return this.atRoot;
|
||||
@@ -114,10 +114,10 @@ export class MattermostView extends EventEmitter {
|
||||
return this.loggedIn;
|
||||
}
|
||||
get currentURL() {
|
||||
return parseURL(this.view.webContents.getURL());
|
||||
return parseURL(this.browserView.webContents.getURL());
|
||||
}
|
||||
get webContentsId() {
|
||||
return this.view.webContents.id;
|
||||
return this.browserView.webContents.id;
|
||||
}
|
||||
|
||||
onLogin = (loggedIn: boolean) => {
|
||||
@@ -127,19 +127,19 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
this.loggedIn = loggedIn;
|
||||
|
||||
// If we're logging in from a different tab, force a reload
|
||||
// If we're logging in from a different view, force a reload
|
||||
if (loggedIn &&
|
||||
this.currentURL?.toString() !== this.tab.url.toString() &&
|
||||
!this.currentURL?.toString().startsWith(this.tab.url.toString())
|
||||
this.currentURL?.toString() !== this.view.url.toString() &&
|
||||
!this.currentURL?.toString().startsWith(this.view.url.toString())
|
||||
) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
goToOffset = (offset: number) => {
|
||||
if (this.view.webContents.canGoToOffset(offset)) {
|
||||
if (this.browserView.webContents.canGoToOffset(offset)) {
|
||||
try {
|
||||
this.view.webContents.goToOffset(offset);
|
||||
this.browserView.webContents.goToOffset(offset);
|
||||
this.updateHistoryButton();
|
||||
} catch (error) {
|
||||
this.log.error(error);
|
||||
@@ -149,17 +149,17 @@ export class MattermostView extends EventEmitter {
|
||||
}
|
||||
|
||||
updateHistoryButton = () => {
|
||||
if (this.currentURL?.toString() === this.tab.url.toString()) {
|
||||
this.view.webContents.clearHistory();
|
||||
if (this.currentURL?.toString() === this.view.url.toString()) {
|
||||
this.browserView.webContents.clearHistory();
|
||||
this.atRoot = true;
|
||||
} else {
|
||||
this.atRoot = false;
|
||||
}
|
||||
this.view.webContents.send(BROWSER_HISTORY_BUTTON, this.view.webContents.canGoBack(), this.view.webContents.canGoForward());
|
||||
this.browserView.webContents.send(BROWSER_HISTORY_BUTTON, this.browserView.webContents.canGoBack(), this.browserView.webContents.canGoForward());
|
||||
}
|
||||
|
||||
load = (someURL?: URL | string) => {
|
||||
if (!this.tab) {
|
||||
if (!this.browserView) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -170,13 +170,13 @@ export class MattermostView extends EventEmitter {
|
||||
loadURL = parsedURL.toString();
|
||||
} else {
|
||||
this.log.error('Cannot parse provided url, using current server url', someURL);
|
||||
loadURL = this.tab.url.toString();
|
||||
loadURL = this.view.url.toString();
|
||||
}
|
||||
} else {
|
||||
loadURL = this.tab.url.toString();
|
||||
loadURL = this.view.url.toString();
|
||||
}
|
||||
this.log.verbose(`Loading ${loadURL}`);
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (err.code && err.code.startsWith('ERR_CERT')) {
|
||||
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||
@@ -205,9 +205,9 @@ export class MattermostView extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this.isVisible = true;
|
||||
mainWindow.addBrowserView(this.view);
|
||||
mainWindow.setTopBrowserView(this.view);
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
|
||||
mainWindow.addBrowserView(this.browserView);
|
||||
mainWindow.setTopBrowserView(this.browserView);
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
|
||||
if (this.status === Status.READY) {
|
||||
this.focus();
|
||||
}
|
||||
@@ -216,7 +216,7 @@ export class MattermostView extends EventEmitter {
|
||||
hide = () => {
|
||||
if (this.isVisible) {
|
||||
this.isVisible = false;
|
||||
MainWindow.get()?.removeBrowserView(this.view);
|
||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,27 +226,27 @@ export class MattermostView extends EventEmitter {
|
||||
}
|
||||
|
||||
getBounds = () => {
|
||||
return this.view.getBounds();
|
||||
return this.browserView.getBounds();
|
||||
}
|
||||
|
||||
openFind = () => {
|
||||
this.view.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
this.browserView.webContents.sendInputEvent({type: 'keyDown', keyCode: 'F', modifiers: [process.platform === 'darwin' ? 'cmd' : 'ctrl', 'shift']});
|
||||
}
|
||||
|
||||
setBounds = (boundaries: Electron.Rectangle) => {
|
||||
this.view.setBounds(boundaries);
|
||||
this.browserView.setBounds(boundaries);
|
||||
}
|
||||
|
||||
destroy = () => {
|
||||
WebContentsEventManager.removeWebContentsListeners(this.webContentsId);
|
||||
AppState.clear(this.id);
|
||||
MainWindow.get()?.removeBrowserView(this.view);
|
||||
MainWindow.get()?.removeBrowserView(this.browserView);
|
||||
|
||||
// workaround to eliminate zombie processes
|
||||
// https://github.com/mattermost/desktop/pull/1519
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.view.webContents.destroy();
|
||||
this.browserView.webContents.destroy();
|
||||
|
||||
this.isVisible = false;
|
||||
if (this.retryLoad) {
|
||||
@@ -293,7 +293,7 @@ export class MattermostView extends EventEmitter {
|
||||
}
|
||||
|
||||
openDevTools = () => {
|
||||
this.view.webContents.openDevTools({mode: 'detach'});
|
||||
this.browserView.webContents.openDevTools({mode: 'detach'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,16 +301,16 @@ export class MattermostView extends EventEmitter {
|
||||
*/
|
||||
|
||||
sendToRenderer = (channel: string, ...args: any[]) => {
|
||||
this.view.webContents.send(channel, ...args);
|
||||
this.browserView.webContents.send(channel, ...args);
|
||||
}
|
||||
|
||||
isDestroyed = () => {
|
||||
return this.view.webContents.isDestroyed();
|
||||
return this.browserView.webContents.isDestroyed();
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
if (this.view.webContents) {
|
||||
this.view.webContents.focus();
|
||||
if (this.browserView.webContents) {
|
||||
this.browserView.webContents.focus();
|
||||
} else {
|
||||
this.log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.');
|
||||
}
|
||||
@@ -361,7 +361,7 @@ export class MattermostView extends EventEmitter {
|
||||
// if favicon is null, it will affect appState, but won't be memoized
|
||||
private findUnreadState = (favicon: string | null) => {
|
||||
try {
|
||||
this.view.webContents.send(IS_UNREAD, favicon, this.id);
|
||||
this.browserView.webContents.send(IS_UNREAD, favicon, this.id);
|
||||
} catch (err: any) {
|
||||
this.log.error('There was an error trying to request the unread state', err);
|
||||
}
|
||||
@@ -388,17 +388,17 @@ export class MattermostView extends EventEmitter {
|
||||
private retry = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.view || !this.view.webContents) {
|
||||
if (!this.browserView || !this.browserView.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch((err) => {
|
||||
if (this.maxRetries-- > 0) {
|
||||
this.loadRetry(loadURL, err);
|
||||
} else {
|
||||
MainWindow.sendToRenderer(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||
this.emit(LOAD_FAILED, this.id, err.toString(), loadURL.toString());
|
||||
this.log.info(`Couldn't establish a connection with ${loadURL}, will continue to retry in the background`, err);
|
||||
this.log.info(`Couldn't esviewlish a connection with ${loadURL}, will continue to retry in the background`, err);
|
||||
this.status = Status.ERROR;
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
}
|
||||
@@ -409,10 +409,10 @@ export class MattermostView extends EventEmitter {
|
||||
private retryInBackground = (loadURL: string) => {
|
||||
return () => {
|
||||
// window was closed while retrying
|
||||
if (!this.view || !this.view.webContents) {
|
||||
if (!this.browserView || !this.browserView.webContents) {
|
||||
return;
|
||||
}
|
||||
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
const loading = this.browserView.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
|
||||
loading.then(this.loadSuccess(loadURL)).catch(() => {
|
||||
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
|
||||
});
|
||||
@@ -431,7 +431,7 @@ export class MattermostView extends EventEmitter {
|
||||
MainWindow.sendToRenderer(LOAD_SUCCESS, this.id);
|
||||
this.maxRetries = MAX_SERVER_RETRIES;
|
||||
if (this.status === Status.LOADING) {
|
||||
this.updateMentionsFromTitle(this.view.webContents.getTitle());
|
||||
this.updateMentionsFromTitle(this.browserView.webContents.getTitle());
|
||||
this.findUnreadState(null);
|
||||
}
|
||||
this.status = Status.WAITING_MM;
|
||||
@@ -439,7 +439,7 @@ export class MattermostView extends EventEmitter {
|
||||
this.emit(LOAD_SUCCESS, this.id, loadURL);
|
||||
const mainWindow = MainWindow.get();
|
||||
if (mainWindow && this.currentURL) {
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.tab.url || '', this.currentURL)));
|
||||
this.setBounds(getWindowBoundaries(mainWindow, shouldHaveBackBar(this.view.url || '', this.currentURL)));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -453,13 +453,13 @@ export class MattermostView extends EventEmitter {
|
||||
|
||||
// wait for screen to truly finish loading before sending the message down
|
||||
const timeout = setInterval(() => {
|
||||
if (!this.view.webContents) {
|
||||
if (!this.browserView.webContents) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.view.webContents.isLoading()) {
|
||||
if (!this.browserView.webContents.isLoading()) {
|
||||
try {
|
||||
this.view.webContents.send(SET_VIEW_OPTIONS, this.id, this.tab.shouldNotify);
|
||||
this.browserView.webContents.send(SET_VIEW_OPTIONS, this.id, this.view.shouldNotify);
|
||||
clearTimeout(timeout);
|
||||
} catch (e) {
|
||||
this.log.error('failed to send view options to view');
|
||||
@@ -480,7 +480,7 @@ export class MattermostView extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldHaveBackBar(this.tab.url || '', parsedURL)) {
|
||||
if (shouldHaveBackBar(this.view.url || '', parsedURL)) {
|
||||
this.setBounds(getWindowBoundaries(mainWindow, true));
|
||||
MainWindow.sendToRenderer(TOGGLE_BACK_BUTTON, true);
|
||||
this.log.debug('show back button');
|
||||
@@ -494,7 +494,7 @@ export class MattermostView extends EventEmitter {
|
||||
private handleUpdateTarget = (e: Event, url: string) => {
|
||||
this.log.silly('handleUpdateTarget', url);
|
||||
const parsedURL = parseURL(url);
|
||||
if (parsedURL && isInternalURL(parsedURL, this.tab.server.url)) {
|
||||
if (parsedURL && isInternalURL(parsedURL, this.view.server.url)) {
|
||||
this.emit(UPDATE_TARGET_URL);
|
||||
} else {
|
||||
this.emit(UPDATE_TARGET_URL, url);
|
||||
@@ -502,7 +502,7 @@ export class MattermostView extends EventEmitter {
|
||||
}
|
||||
|
||||
private handleServerWasModified = (serverIds: string) => {
|
||||
if (serverIds.includes(this.tab.server.id)) {
|
||||
if (serverIds.includes(this.view.server.id)) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@ import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHA
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {TeamDropdownView} from './teamDropdownView';
|
||||
import {ServerDropdownView} from './serverDropdownView';
|
||||
|
||||
jest.mock('main/utils', () => ({
|
||||
getLocalPreload: (file) => file,
|
||||
@@ -38,36 +38,36 @@ jest.mock('common/servers/serverManager', () => ({
|
||||
getOrderedServers: jest.fn().mockReturnValue([]),
|
||||
}));
|
||||
|
||||
describe('main/views/teamDropdownView', () => {
|
||||
describe('main/views/serverDropdownView', () => {
|
||||
describe('getBounds', () => {
|
||||
beforeEach(() => {
|
||||
MainWindow.getBounds.mockReturnValue({width: 500, height: 400, x: 0, y: 0});
|
||||
});
|
||||
|
||||
const teamDropdownView = new TeamDropdownView();
|
||||
const serverDropdownView = new ServerDropdownView();
|
||||
if (process.platform === 'darwin') {
|
||||
it('should account for three dot menu, tab bar and shadow', () => {
|
||||
expect(teamDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH_MAC - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
|
||||
expect(serverDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH_MAC - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
|
||||
});
|
||||
} else {
|
||||
it('should account for three dot menu, tab bar and shadow', () => {
|
||||
expect(teamDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
|
||||
expect(serverDropdownView.getBounds(400, 300)).toStrictEqual({x: THREE_DOT_MENU_WIDTH - MENU_SHADOW_WIDTH, y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH, width: 400, height: 300});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should change the view bounds based on open/closed state', () => {
|
||||
const teamDropdownView = new TeamDropdownView();
|
||||
teamDropdownView.view = {
|
||||
const serverDropdownView = new ServerDropdownView();
|
||||
serverDropdownView.view = {
|
||||
setBounds: jest.fn(),
|
||||
webContents: {
|
||||
focus: jest.fn(),
|
||||
},
|
||||
};
|
||||
teamDropdownView.bounds = {width: 400, height: 300};
|
||||
teamDropdownView.handleOpen();
|
||||
expect(teamDropdownView.view.setBounds).toBeCalledWith(teamDropdownView.bounds);
|
||||
teamDropdownView.handleClose();
|
||||
expect(teamDropdownView.view.setBounds).toBeCalledWith({width: 0, height: 0, x: expect.any(Number), y: expect.any(Number)});
|
||||
serverDropdownView.bounds = {width: 400, height: 300};
|
||||
serverDropdownView.handleOpen();
|
||||
expect(serverDropdownView.view.setBounds).toBeCalledWith(serverDropdownView.bounds);
|
||||
serverDropdownView.handleClose();
|
||||
expect(serverDropdownView.view.setBounds).toBeCalledWith({width: 0, height: 0, x: expect.any(Number), y: expect.any(Number)});
|
||||
});
|
||||
});
|
@@ -3,16 +3,16 @@
|
||||
|
||||
import {BrowserView, ipcMain, IpcMainEvent} from 'electron';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import AppState from 'common/appState';
|
||||
import {
|
||||
CLOSE_TEAMS_DROPDOWN,
|
||||
CLOSE_SERVERS_DROPDOWN,
|
||||
EMIT_CONFIGURATION,
|
||||
OPEN_TEAMS_DROPDOWN,
|
||||
UPDATE_TEAMS_DROPDOWN,
|
||||
OPEN_SERVERS_DROPDOWN,
|
||||
UPDATE_SERVERS_DROPDOWN,
|
||||
UPDATE_APPSTATE,
|
||||
REQUEST_TEAMS_DROPDOWN_INFO,
|
||||
REQUEST_SERVERS_DROPDOWN_INFO,
|
||||
RECEIVE_DROPDOWN_MENU_SIZE,
|
||||
SERVERS_UPDATE,
|
||||
MAIN_WINDOW_CREATED,
|
||||
@@ -27,12 +27,12 @@ import {getLocalPreload, getLocalURLString} from 'main/utils';
|
||||
|
||||
import MainWindow from '../windows/mainWindow';
|
||||
|
||||
const log = new Logger('TeamDropdownView');
|
||||
const log = new Logger('ServerDropdownView');
|
||||
|
||||
export class TeamDropdownView {
|
||||
export class ServerDropdownView {
|
||||
private view?: BrowserView;
|
||||
private teams: MattermostTeam[];
|
||||
private hasGPOTeams: boolean;
|
||||
private servers: UniqueServer[];
|
||||
private hasGPOServers: boolean;
|
||||
private isOpen: boolean;
|
||||
private bounds: Electron.Rectangle;
|
||||
|
||||
@@ -43,8 +43,8 @@ export class TeamDropdownView {
|
||||
private windowBounds?: Electron.Rectangle;
|
||||
|
||||
constructor() {
|
||||
this.teams = [];
|
||||
this.hasGPOTeams = false;
|
||||
this.servers = [];
|
||||
this.hasGPOServers = false;
|
||||
this.isOpen = false;
|
||||
this.bounds = this.getBounds(0, 0);
|
||||
|
||||
@@ -55,12 +55,12 @@ export class TeamDropdownView {
|
||||
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||
MainWindow.on(MAIN_WINDOW_RESIZED, this.updateWindowBounds);
|
||||
|
||||
ipcMain.on(OPEN_TEAMS_DROPDOWN, this.handleOpen);
|
||||
ipcMain.on(CLOSE_TEAMS_DROPDOWN, this.handleClose);
|
||||
ipcMain.on(OPEN_SERVERS_DROPDOWN, this.handleOpen);
|
||||
ipcMain.on(CLOSE_SERVERS_DROPDOWN, this.handleClose);
|
||||
ipcMain.on(RECEIVE_DROPDOWN_MENU_SIZE, this.handleReceivedMenuSize);
|
||||
|
||||
ipcMain.on(EMIT_CONFIGURATION, this.updateDropdown);
|
||||
ipcMain.on(REQUEST_TEAMS_DROPDOWN_INFO, this.updateDropdown);
|
||||
ipcMain.on(REQUEST_SERVERS_DROPDOWN_INFO, this.updateDropdown);
|
||||
|
||||
AppState.on(UPDATE_APPSTATE, this.updateMentions);
|
||||
ServerManager.on(SERVERS_UPDATE, this.updateServers);
|
||||
@@ -84,7 +84,7 @@ export class TeamDropdownView {
|
||||
}});
|
||||
this.view.webContents.loadURL(getLocalURLString('dropdown.html'));
|
||||
|
||||
this.setOrderedTeams();
|
||||
this.setOrderedServers();
|
||||
this.windowBounds = MainWindow.getBounds();
|
||||
this.updateDropdown();
|
||||
MainWindow.get()?.addBrowserView(this.view);
|
||||
@@ -94,13 +94,13 @@ export class TeamDropdownView {
|
||||
log.silly('updateDropdown');
|
||||
|
||||
this.view?.webContents.send(
|
||||
UPDATE_TEAMS_DROPDOWN,
|
||||
this.teams,
|
||||
UPDATE_SERVERS_DROPDOWN,
|
||||
this.servers,
|
||||
Config.darkMode,
|
||||
this.windowBounds,
|
||||
ServerManager.hasServers() ? ServerManager.getCurrentServer().id : undefined,
|
||||
Config.enableServerManagement,
|
||||
this.hasGPOTeams,
|
||||
this.hasGPOServers,
|
||||
this.expired,
|
||||
this.mentions,
|
||||
this.unreads,
|
||||
@@ -108,7 +108,7 @@ export class TeamDropdownView {
|
||||
}
|
||||
|
||||
private updateServers = () => {
|
||||
this.setOrderedTeams();
|
||||
this.setOrderedServers();
|
||||
this.updateDropdown();
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export class TeamDropdownView {
|
||||
this.view.setBounds(this.bounds);
|
||||
MainWindow.get()?.setTopBrowserView(this.view);
|
||||
this.view.webContents.focus();
|
||||
MainWindow.sendToRenderer(OPEN_TEAMS_DROPDOWN);
|
||||
MainWindow.sendToRenderer(OPEN_SERVERS_DROPDOWN);
|
||||
this.isOpen = true;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export class TeamDropdownView {
|
||||
log.debug('handleClose');
|
||||
|
||||
this.view?.setBounds(this.getBounds(0, 0));
|
||||
MainWindow.sendToRenderer(CLOSE_TEAMS_DROPDOWN);
|
||||
MainWindow.sendToRenderer(CLOSE_SERVERS_DROPDOWN);
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ export class TeamDropdownView {
|
||||
private reduceNotifications = <T>(inputMap: Map<string, T>, items: Map<string, T>, modifier: (base?: T, value?: T) => T) => {
|
||||
inputMap.clear();
|
||||
return [...items.keys()].reduce((map, key) => {
|
||||
const view = ServerManager.getTab(key);
|
||||
const view = ServerManager.getView(key);
|
||||
if (!view) {
|
||||
return map;
|
||||
}
|
||||
@@ -183,11 +183,11 @@ export class TeamDropdownView {
|
||||
}, inputMap);
|
||||
}
|
||||
|
||||
private setOrderedTeams = () => {
|
||||
this.teams = ServerManager.getOrderedServers().map((team) => team.toMattermostTeam());
|
||||
this.hasGPOTeams = this.teams.some((srv) => srv.isPredefined);
|
||||
private setOrderedServers = () => {
|
||||
this.servers = ServerManager.getOrderedServers().map((server) => server.toUniqueServer());
|
||||
this.hasGPOServers = this.servers.some((srv) => srv.isPredefined);
|
||||
}
|
||||
}
|
||||
|
||||
const teamDropdownView = new TeamDropdownView();
|
||||
export default teamDropdownView;
|
||||
const serverDropdownView = new ServerDropdownView();
|
||||
export default serverDropdownView;
|
@@ -7,13 +7,13 @@
|
||||
import {dialog} from 'electron';
|
||||
|
||||
import {BROWSER_HISTORY_PUSH, LOAD_SUCCESS, SET_ACTIVE_VIEW} from 'common/communication';
|
||||
import {TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
import {TAB_MESSAGING} from 'common/views/View';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import urlUtils from 'common/utils/url';
|
||||
|
||||
import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {MattermostView} from './MattermostView';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import {ViewManager} from './viewManager';
|
||||
import LoadingScreen from './loadingScreen';
|
||||
|
||||
@@ -30,12 +30,9 @@ jest.mock('electron', () => ({
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
jest.mock('common/config', () => ({
|
||||
teams: [],
|
||||
}));
|
||||
jest.mock('common/tabs/TabView', () => ({
|
||||
getTabViewName: jest.fn((a, b) => `${a}-${b}`),
|
||||
TAB_MESSAGING: 'tab',
|
||||
jest.mock('common/views/View', () => ({
|
||||
getViewName: jest.fn((a, b) => `${a}-${b}`),
|
||||
TAB_MESSAGING: 'view',
|
||||
}));
|
||||
|
||||
jest.mock('common/servers/MattermostServer', () => ({
|
||||
@@ -79,7 +76,7 @@ jest.mock('common/servers/serverManager', () => ({
|
||||
getLastActiveServer: jest.fn(),
|
||||
getLastActiveTabForServer: jest.fn(),
|
||||
updateLastActive: jest.fn(),
|
||||
lookupTabByURL: jest.fn(),
|
||||
lookupViewByURL: jest.fn(),
|
||||
getRemoteInfo: jest.fn(),
|
||||
on: jest.fn(),
|
||||
getServerLog: () => ({
|
||||
@@ -100,8 +97,8 @@ jest.mock('common/servers/serverManager', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('./MattermostView', () => ({
|
||||
MattermostView: jest.fn(),
|
||||
jest.mock('./MattermostBrowserView', () => ({
|
||||
MattermostBrowserView: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./modalManager', () => ({
|
||||
@@ -121,12 +118,12 @@ describe('main/views/viewManager', () => {
|
||||
beforeEach(() => {
|
||||
viewManager.showById = jest.fn();
|
||||
MainWindow.get.mockReturnValue({});
|
||||
MattermostView.mockImplementation((tab) => ({
|
||||
MattermostBrowserView.mockImplementation((view) => ({
|
||||
on: jest.fn(),
|
||||
load: loadFn,
|
||||
once: onceFn,
|
||||
destroy: destroyFn,
|
||||
id: tab.id,
|
||||
id: view.id,
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -136,21 +133,21 @@ describe('main/views/viewManager', () => {
|
||||
viewManager.views = new Map();
|
||||
});
|
||||
|
||||
it('should add closed tabs to closedViews', () => {
|
||||
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: false});
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(true);
|
||||
it('should add closed views to closedViews', () => {
|
||||
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: false});
|
||||
expect(viewManager.closedViews.has('view1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove from remove from closedViews when the tab is open', () => {
|
||||
viewManager.closedViews.set('tab1', {});
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(true);
|
||||
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: true});
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(false);
|
||||
it('should remove from remove from closedViews when the view is open', () => {
|
||||
viewManager.closedViews.set('view1', {});
|
||||
expect(viewManager.closedViews.has('view1')).toBe(true);
|
||||
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: true});
|
||||
expect(viewManager.closedViews.has('view1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should add view to views map and add listeners', () => {
|
||||
viewManager.loadView({id: 'server1'}, {id: 'tab1', isOpen: true}, 'http://server-1.com/subpath');
|
||||
expect(viewManager.views.has('tab1')).toBe(true);
|
||||
viewManager.loadView({id: 'server1'}, {id: 'view1', isOpen: true}, 'http://server-1.com/subpath');
|
||||
expect(viewManager.views.has('view1')).toBe(true);
|
||||
expect(onceFn).toHaveBeenCalledWith(LOAD_SUCCESS, viewManager.activateView);
|
||||
expect(loadFn).toHaveBeenCalledWith('http://server-1.com/subpath');
|
||||
});
|
||||
@@ -173,14 +170,14 @@ describe('main/views/viewManager', () => {
|
||||
const onceFn = jest.fn();
|
||||
const loadFn = jest.fn();
|
||||
const destroyFn = jest.fn();
|
||||
MattermostView.mockImplementation((tab) => ({
|
||||
MattermostBrowserView.mockImplementation((view) => ({
|
||||
on: jest.fn(),
|
||||
load: loadFn,
|
||||
once: onceFn,
|
||||
destroy: destroyFn,
|
||||
id: tab.id,
|
||||
id: view.id,
|
||||
updateServerInfo: jest.fn(),
|
||||
tab,
|
||||
view,
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -193,45 +190,45 @@ describe('main/views/viewManager', () => {
|
||||
|
||||
it('should recycle existing views', () => {
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
const view = new MattermostView({
|
||||
id: 'tab1',
|
||||
const view = new MattermostBrowserView({
|
||||
id: 'view1',
|
||||
server: {
|
||||
id: 'server1',
|
||||
},
|
||||
});
|
||||
viewManager.views.set('tab1', view);
|
||||
viewManager.views.set('view1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
isOpen: true,
|
||||
},
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(viewManager.views.get('tab1')).toBe(view);
|
||||
expect(viewManager.views.get('view1')).toBe(view);
|
||||
expect(makeSpy).not.toHaveBeenCalled();
|
||||
makeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should close tabs that arent open', () => {
|
||||
it('should close views that arent open', () => {
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
isOpen: false,
|
||||
},
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
expect(viewManager.closedViews.has('tab1')).toBe(true);
|
||||
expect(viewManager.closedViews.has('view1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should create new views for new tabs', () => {
|
||||
it('should create new views for new views', () => {
|
||||
const makeSpy = jest.spyOn(viewManager, 'makeView');
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
@@ -240,10 +237,10 @@ describe('main/views/viewManager', () => {
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
name: 'tab1',
|
||||
id: 'view1',
|
||||
name: 'view1',
|
||||
isOpen: true,
|
||||
url: new URL('http://server1.com/tab'),
|
||||
url: new URL('http://server1.com/view'),
|
||||
},
|
||||
]);
|
||||
viewManager.handleReloadConfiguration();
|
||||
@@ -254,10 +251,10 @@ describe('main/views/viewManager', () => {
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
{
|
||||
id: 'tab1',
|
||||
name: 'tab1',
|
||||
id: 'view1',
|
||||
name: 'view1',
|
||||
isOpen: true,
|
||||
url: new URL('http://server1.com/tab'),
|
||||
url: new URL('http://server1.com/view'),
|
||||
},
|
||||
);
|
||||
makeSpy.mockRestore();
|
||||
@@ -265,27 +262,27 @@ describe('main/views/viewManager', () => {
|
||||
|
||||
it('should set focus to current view on reload', () => {
|
||||
const view = {
|
||||
id: 'tab1',
|
||||
tab: {
|
||||
id: 'view1',
|
||||
view: {
|
||||
server: {
|
||||
id: 'server-1',
|
||||
},
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
updateServerInfo: jest.fn(),
|
||||
focus: jest.fn(),
|
||||
};
|
||||
viewManager.currentView = 'tab1';
|
||||
viewManager.views.set('tab1', view);
|
||||
viewManager.currentView = 'view1';
|
||||
viewManager.views.set('view1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server1',
|
||||
url: new URL('http://server1.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
isOpen: true,
|
||||
},
|
||||
]);
|
||||
@@ -295,23 +292,23 @@ describe('main/views/viewManager', () => {
|
||||
|
||||
it('should show initial if currentView has been removed', () => {
|
||||
const view = {
|
||||
id: 'tab1',
|
||||
tab: {
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
view: {
|
||||
id: 'view1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
updateServerInfo: jest.fn(),
|
||||
};
|
||||
viewManager.currentView = 'tab1';
|
||||
viewManager.views.set('tab1', view);
|
||||
viewManager.currentView = 'view1';
|
||||
viewManager.views.set('view1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server2',
|
||||
url: new URL('http://server2.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
isOpen: false,
|
||||
},
|
||||
]);
|
||||
@@ -321,21 +318,21 @@ describe('main/views/viewManager', () => {
|
||||
|
||||
it('should remove unused views', () => {
|
||||
const view = {
|
||||
name: 'tab1',
|
||||
tab: {
|
||||
name: 'tab1',
|
||||
name: 'view1',
|
||||
view: {
|
||||
name: 'view1',
|
||||
url: new URL('http://server1.com'),
|
||||
},
|
||||
destroy: jest.fn(),
|
||||
};
|
||||
viewManager.views.set('tab1', view);
|
||||
viewManager.views.set('view1', view);
|
||||
ServerManager.getAllServers.mockReturnValue([{
|
||||
id: 'server2',
|
||||
url: new URL('http://server2.com'),
|
||||
}]);
|
||||
ServerManager.getOrderedTabsForServer.mockReturnValue([
|
||||
{
|
||||
id: 'tab1',
|
||||
id: 'view1',
|
||||
isOpen: false,
|
||||
},
|
||||
]);
|
||||
@@ -360,11 +357,11 @@ describe('main/views/viewManager', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should show last active tab and server', () => {
|
||||
it('should show last active view and server', () => {
|
||||
ServerManager.getLastActiveServer.mockReturnValue({id: 'server-1'});
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'tab-1'});
|
||||
ServerManager.getLastActiveTabForServer.mockReturnValue({id: 'view-1'});
|
||||
viewManager.showInitial();
|
||||
expect(viewManager.showById).toHaveBeenCalledWith('tab-1');
|
||||
expect(viewManager.showById).toHaveBeenCalledWith('view-1');
|
||||
});
|
||||
|
||||
it('should open new server modal when no servers exist', () => {
|
||||
@@ -385,7 +382,7 @@ describe('main/views/viewManager', () => {
|
||||
order: 0,
|
||||
tabs: [
|
||||
{
|
||||
name: 'tab-messaging',
|
||||
name: 'view-messaging',
|
||||
order: 0,
|
||||
isOpen: true,
|
||||
},
|
||||
@@ -403,9 +400,9 @@ describe('main/views/viewManager', () => {
|
||||
},
|
||||
];
|
||||
const view1 = {
|
||||
id: 'server-1_tab-messaging',
|
||||
id: 'server-1_view-messaging',
|
||||
isLoggedIn: true,
|
||||
tab: {
|
||||
view: {
|
||||
type: TAB_MESSAGING,
|
||||
server: {
|
||||
url: 'http://server-1.com',
|
||||
@@ -416,21 +413,21 @@ describe('main/views/viewManager', () => {
|
||||
const view2 = {
|
||||
...view1,
|
||||
id: 'server-1_other_type_1',
|
||||
tab: {
|
||||
...view1.tab,
|
||||
view: {
|
||||
...view1.view,
|
||||
type: 'other_type_1',
|
||||
},
|
||||
};
|
||||
const view3 = {
|
||||
...view1,
|
||||
id: 'server-1_other_type_2',
|
||||
tab: {
|
||||
...view1.tab,
|
||||
view: {
|
||||
...view1.view,
|
||||
type: 'other_type_2',
|
||||
},
|
||||
};
|
||||
const views = new Map([
|
||||
['server-1_tab-messaging', view1],
|
||||
['server-1_view-messaging', view1],
|
||||
['server-1_other_type_1', view2],
|
||||
]);
|
||||
const closedViews = new Map([
|
||||
@@ -438,7 +435,7 @@ describe('main/views/viewManager', () => {
|
||||
]);
|
||||
viewManager.getView = (viewId) => views.get(viewId);
|
||||
viewManager.isViewClosed = (viewId) => closedViews.has(viewId);
|
||||
viewManager.openClosedTab = jest.fn();
|
||||
viewManager.openClosedView = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
ServerManager.getAllServers.mockReturnValue(servers);
|
||||
@@ -451,24 +448,24 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should open closed view if pushing to it', () => {
|
||||
viewManager.openClosedTab.mockImplementation((name) => {
|
||||
viewManager.openClosedView.mockImplementation((name) => {
|
||||
const view = closedViews.get(name);
|
||||
closedViews.delete(name);
|
||||
views.set(name, view);
|
||||
});
|
||||
ServerManager.lookupTabByURL.mockReturnValue({id: 'server-1_other_type_2'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_tab-messaging', '/other_type_2/subpath');
|
||||
expect(viewManager.openClosedTab).toBeCalledWith('server-1_other_type_2', 'http://server-1.com/other_type_2/subpath');
|
||||
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_other_type_2'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_view-messaging', '/other_type_2/subpath');
|
||||
expect(viewManager.openClosedView).toBeCalledWith('server-1_other_type_2', 'http://server-1.com/other_type_2/subpath');
|
||||
});
|
||||
|
||||
it('should open redirect view if different from current view', () => {
|
||||
ServerManager.lookupTabByURL.mockReturnValue({id: 'server-1_other_type_1'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_tab-messaging', '/other_type_1/subpath');
|
||||
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_other_type_1'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_view-messaging', '/other_type_1/subpath');
|
||||
expect(viewManager.showById).toBeCalledWith('server-1_other_type_1');
|
||||
});
|
||||
|
||||
it('should ignore redirects to "/" to Messages from other tabs', () => {
|
||||
ServerManager.lookupTabByURL.mockReturnValue({id: 'server-1_tab-messaging'});
|
||||
it('should ignore redirects to "/" to Messages from other views', () => {
|
||||
ServerManager.lookupViewByURL.mockReturnValue({id: 'server-1_view-messaging'});
|
||||
viewManager.handleBrowserHistoryPush(null, 'server-1_other_type_1', '/');
|
||||
expect(view1.sendToRenderer).not.toBeCalled();
|
||||
});
|
||||
@@ -487,11 +484,11 @@ describe('main/views/viewManager', () => {
|
||||
send: jest.fn(),
|
||||
},
|
||||
},
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
name: 'server-1',
|
||||
},
|
||||
type: 'tab-1',
|
||||
type: 'view-1',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -510,9 +507,9 @@ describe('main/views/viewManager', () => {
|
||||
...baseView,
|
||||
isVisible: true,
|
||||
};
|
||||
viewManager.views.set('server1-tab1', view);
|
||||
viewManager.views.set('server1-view1', view);
|
||||
|
||||
viewManager.showById('server1-tab1');
|
||||
viewManager.showById('server1-view1');
|
||||
expect(viewManager.currentView).toBeUndefined();
|
||||
expect(view.isReady).not.toBeCalled();
|
||||
expect(view.show).not.toBeCalled();
|
||||
@@ -584,7 +581,7 @@ describe('main/views/viewManager', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
viewManager.openClosedTab = jest.fn();
|
||||
viewManager.openClosedView = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -594,7 +591,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should load URL into matching view', () => {
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
const view = {...baseView};
|
||||
viewManager.views.set('view1', view);
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
@@ -602,11 +599,11 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should send the URL to the view if its already loaded on a 6.0 server', () => {
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
ServerManager.getRemoteInfo.mockReturnValue({serverVersion: '6.0.0'});
|
||||
const view = {
|
||||
...baseView,
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
url: new URL('http://server-1.com'),
|
||||
},
|
||||
@@ -619,7 +616,7 @@ describe('main/views/viewManager', () => {
|
||||
});
|
||||
|
||||
it('should throw error if view is missing', () => {
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
const view = {...baseView};
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
expect(view.load).not.toHaveBeenCalled();
|
||||
@@ -632,11 +629,11 @@ describe('main/views/viewManager', () => {
|
||||
expect(dialog.showErrorBox).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reopen closed tab if called upon', () => {
|
||||
ServerManager.lookupTabByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
it('should reopen closed view if called upon', () => {
|
||||
ServerManager.lookupViewByURL.mockImplementation(() => ({id: 'view1', url: new URL('http://server-1.com/')}));
|
||||
viewManager.closedViews.set('view1', {});
|
||||
viewManager.handleDeepLink('mattermost://server-1.com/deep/link?thing=yes');
|
||||
expect(viewManager.openClosedTab).toHaveBeenCalledWith('view1', 'http://server-1.com/deep/link?thing=yes');
|
||||
expect(viewManager.openClosedView).toHaveBeenCalledWith('view1', 'http://server-1.com/deep/link?thing=yes');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
LOAD_FAILED,
|
||||
LOADSCREEN_END,
|
||||
SET_ACTIVE_VIEW,
|
||||
OPEN_TAB,
|
||||
OPEN_VIEW,
|
||||
BROWSER_HISTORY_PUSH,
|
||||
UPDATE_URL_VIEW_WIDTH,
|
||||
SERVERS_UPDATE,
|
||||
@@ -33,7 +33,7 @@ import {Logger} from 'common/log';
|
||||
import Utils from 'common/utils/util';
|
||||
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||
import ServerManager from 'common/servers/serverManager';
|
||||
import {TabView, TAB_MESSAGING} from 'common/tabs/TabView';
|
||||
import {MattermostView, TAB_MESSAGING} from 'common/views/View';
|
||||
import {parseURL} from 'common/utils/url';
|
||||
|
||||
import {localizeMessage} from 'main/i18nManager';
|
||||
@@ -41,7 +41,7 @@ import MainWindow from 'main/windows/mainWindow';
|
||||
|
||||
import {getLocalURLString, getLocalPreload, getAdjustedWindowBoundaries, shouldHaveBackBar} from '../utils';
|
||||
|
||||
import {MattermostView} from './MattermostView';
|
||||
import {MattermostBrowserView} from './MattermostBrowserView';
|
||||
import modalManager from './modalManager';
|
||||
import LoadingScreen from './loadingScreen';
|
||||
|
||||
@@ -50,14 +50,14 @@ const URL_VIEW_DURATION = 10 * SECOND;
|
||||
const URL_VIEW_HEIGHT = 20;
|
||||
|
||||
export class ViewManager {
|
||||
private closedViews: Map<string, {srv: MattermostServer; tab: TabView}>;
|
||||
private views: Map<string, MattermostView>;
|
||||
private closedViews: Map<string, {srv: MattermostServer; view: MattermostView}>;
|
||||
private views: Map<string, MattermostBrowserView>;
|
||||
private currentView?: string;
|
||||
|
||||
private urlViewCancel?: () => void;
|
||||
|
||||
constructor() {
|
||||
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
|
||||
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only views on the renderer need that.
|
||||
this.closedViews = new Map();
|
||||
|
||||
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
|
||||
@@ -102,23 +102,23 @@ export class ViewManager {
|
||||
return this.closedViews.has(viewId);
|
||||
}
|
||||
|
||||
showById = (tabId: string) => {
|
||||
this.getViewLogger(tabId).debug('showById', tabId);
|
||||
showById = (viewId: string) => {
|
||||
this.getViewLogger(viewId).debug('showById', viewId);
|
||||
|
||||
const newView = this.views.get(tabId);
|
||||
const newView = this.views.get(viewId);
|
||||
if (newView) {
|
||||
if (newView.isVisible) {
|
||||
return;
|
||||
}
|
||||
let hidePrevious;
|
||||
if (this.currentView && this.currentView !== tabId) {
|
||||
if (this.currentView && this.currentView !== viewId) {
|
||||
const previous = this.getCurrentView();
|
||||
if (previous) {
|
||||
hidePrevious = () => previous.hide();
|
||||
}
|
||||
}
|
||||
|
||||
this.currentView = tabId;
|
||||
this.currentView = viewId;
|
||||
if (!newView.isErrored()) {
|
||||
newView.show();
|
||||
if (newView.needsLoadingScreen()) {
|
||||
@@ -126,10 +126,10 @@ export class ViewManager {
|
||||
}
|
||||
}
|
||||
hidePrevious?.();
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.tab.server.id, newView.tab.id);
|
||||
ServerManager.updateLastActive(newView.tab.id);
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, newView.view.server.id, newView.view.id);
|
||||
ServerManager.updateLastActive(newView.view.id);
|
||||
} else {
|
||||
this.getViewLogger(tabId).warn(`Couldn't find a view with name: ${tabId}`);
|
||||
this.getViewLogger(viewId).warn(`Couldn't find a view with name: ${viewId}`);
|
||||
}
|
||||
modalManager.showModal();
|
||||
}
|
||||
@@ -175,28 +175,28 @@ export class ViewManager {
|
||||
handleDeepLink = (url: string | URL) => {
|
||||
if (url) {
|
||||
const parsedURL = parseURL(url)!;
|
||||
const tabView = ServerManager.lookupTabByURL(parsedURL, true);
|
||||
if (tabView) {
|
||||
const urlWithSchema = `${tabView.url.origin}${parsedURL.pathname}${parsedURL.search}`;
|
||||
if (this.closedViews.has(tabView.id)) {
|
||||
this.openClosedTab(tabView.id, urlWithSchema);
|
||||
const view = ServerManager.lookupViewByURL(parsedURL, true);
|
||||
if (view) {
|
||||
const urlWithSchema = `${view.url.origin}${parsedURL.pathname}${parsedURL.search}`;
|
||||
if (this.closedViews.has(view.id)) {
|
||||
this.openClosedView(view.id, urlWithSchema);
|
||||
} else {
|
||||
const view = this.views.get(tabView.id);
|
||||
if (!view) {
|
||||
log.error(`Couldn't find a view matching the id ${tabView.id}`);
|
||||
const browserView = this.views.get(view.id);
|
||||
if (!browserView) {
|
||||
log.error(`Couldn't find a view matching the id ${view.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.isReady() && ServerManager.getRemoteInfo(view.tab.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(view.tab.server.id)?.serverVersion ?? '', '6.0.0')) {
|
||||
const pathName = `/${urlWithSchema.replace(view.tab.server.url.toString(), '')}`;
|
||||
view.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(view.id);
|
||||
if (browserView.isReady() && ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion && Utils.isVersionGreaterThanOrEqualTo(ServerManager.getRemoteInfo(browserView.view.server.id)?.serverVersion ?? '', '6.0.0')) {
|
||||
const pathName = `/${urlWithSchema.replace(browserView.view.server.url.toString(), '')}`;
|
||||
browserView.sendToRenderer(BROWSER_HISTORY_PUSH, pathName);
|
||||
this.deeplinkSuccess(browserView.id);
|
||||
} else {
|
||||
// attempting to change parsedURL protocol results in it not being modified.
|
||||
view.resetLoadingStatus();
|
||||
view.load(urlWithSchema);
|
||||
view.once(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
view.once(LOAD_FAILED, this.deeplinkFailed);
|
||||
browserView.resetLoadingStatus();
|
||||
browserView.load(urlWithSchema);
|
||||
browserView.once(LOAD_SUCCESS, this.deeplinkSuccess);
|
||||
browserView.once(LOAD_FAILED, this.deeplinkFailed);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -225,35 +225,35 @@ export class ViewManager {
|
||||
*/
|
||||
|
||||
private loadServer = (server: MattermostServer) => {
|
||||
const tabs = ServerManager.getOrderedTabsForServer(server.id);
|
||||
tabs.forEach((tab) => this.loadView(server, tab));
|
||||
const views = ServerManager.getOrderedTabsForServer(server.id);
|
||||
views.forEach((view) => this.loadView(server, view));
|
||||
}
|
||||
|
||||
private loadView = (srv: MattermostServer, tab: TabView, url?: string) => {
|
||||
if (!tab.isOpen) {
|
||||
this.closedViews.set(tab.id, {srv, tab});
|
||||
private loadView = (srv: MattermostServer, view: MattermostView, url?: string) => {
|
||||
if (!view.isOpen) {
|
||||
this.closedViews.set(view.id, {srv, view});
|
||||
return;
|
||||
}
|
||||
const view = this.makeView(srv, tab, url);
|
||||
this.addView(view);
|
||||
const browserView = this.makeView(srv, view, url);
|
||||
this.addView(browserView);
|
||||
}
|
||||
|
||||
private makeView = (srv: MattermostServer, tab: TabView, url?: string): MattermostView => {
|
||||
private makeView = (srv: MattermostServer, view: MattermostView, url?: string): MattermostBrowserView => {
|
||||
const mainWindow = MainWindow.get();
|
||||
if (!mainWindow) {
|
||||
throw new Error('Cannot create view, no main window present');
|
||||
}
|
||||
|
||||
const view = new MattermostView(tab, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
||||
view.once(LOAD_SUCCESS, this.activateView);
|
||||
view.on(LOADSCREEN_END, this.finishLoading);
|
||||
view.on(LOAD_FAILED, this.failLoading);
|
||||
view.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
view.load(url);
|
||||
return view;
|
||||
const browserView = new MattermostBrowserView(view, {webPreferences: {spellcheck: Config.useSpellChecker}});
|
||||
browserView.once(LOAD_SUCCESS, this.activateView);
|
||||
browserView.on(LOADSCREEN_END, this.finishLoading);
|
||||
browserView.on(LOAD_FAILED, this.failLoading);
|
||||
browserView.on(UPDATE_TARGET_URL, this.showURLView);
|
||||
browserView.load(url);
|
||||
return browserView;
|
||||
}
|
||||
|
||||
private addView = (view: MattermostView): void => {
|
||||
private addView = (view: MattermostBrowserView): void => {
|
||||
this.views.set(view.id, view);
|
||||
if (this.closedViews.has(view.id)) {
|
||||
this.closedViews.delete(view.id);
|
||||
@@ -265,8 +265,8 @@ export class ViewManager {
|
||||
|
||||
if (ServerManager.hasServers()) {
|
||||
const lastActiveServer = ServerManager.getCurrentServer();
|
||||
const lastActiveTab = ServerManager.getLastActiveTabForServer(lastActiveServer.id);
|
||||
this.showById(lastActiveTab.id);
|
||||
const lastActiveView = ServerManager.getLastActiveTabForServer(lastActiveServer.id);
|
||||
this.showById(lastActiveView.id);
|
||||
} else {
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW);
|
||||
}
|
||||
@@ -382,33 +382,33 @@ export class ViewManager {
|
||||
*/
|
||||
|
||||
/** Called when a new configuration is received
|
||||
* Servers or tabs have been added or edited. We need to
|
||||
* close, open, or reload tabs, taking care to reuse tabs and
|
||||
* preserve focus on the currently selected tab. */
|
||||
* Servers or views have been added or edited. We need to
|
||||
* close, open, or reload views, taking care to reuse views and
|
||||
* preserve focus on the currently selected view. */
|
||||
private handleReloadConfiguration = () => {
|
||||
log.debug('handleReloadConfiguration');
|
||||
|
||||
const currentTabId: string | undefined = this.views.get(this.currentView as string)?.tab.id;
|
||||
const currentViewId: string | undefined = this.views.get(this.currentView as string)?.view.id;
|
||||
|
||||
const current: Map<string, MattermostView> = new Map();
|
||||
const current: Map<string, MattermostBrowserView> = new Map();
|
||||
for (const view of this.views.values()) {
|
||||
current.set(view.tab.id, view);
|
||||
current.set(view.view.id, view);
|
||||
}
|
||||
|
||||
const views: Map<string, MattermostView> = new Map();
|
||||
const closed: Map<string, {srv: MattermostServer; tab: TabView}> = new Map();
|
||||
const views: Map<string, MattermostBrowserView> = new Map();
|
||||
const closed: Map<string, {srv: MattermostServer; view: MattermostView}> = new Map();
|
||||
|
||||
const sortedTabs = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
|
||||
map((t): [MattermostServer, TabView] => [x, t]));
|
||||
const sortedViews = ServerManager.getAllServers().flatMap((x) => ServerManager.getOrderedTabsForServer(x.id).
|
||||
map((t): [MattermostServer, MattermostView] => [x, t]));
|
||||
|
||||
for (const [srv, tab] of sortedTabs) {
|
||||
const recycle = current.get(tab.id);
|
||||
if (!tab.isOpen) {
|
||||
closed.set(tab.id, {srv, tab});
|
||||
for (const [srv, view] of sortedViews) {
|
||||
const recycle = current.get(view.id);
|
||||
if (!view.isOpen) {
|
||||
closed.set(view.id, {srv, view});
|
||||
} else if (recycle) {
|
||||
views.set(tab.id, recycle);
|
||||
views.set(view.id, recycle);
|
||||
} else {
|
||||
views.set(tab.id, this.makeView(srv, tab));
|
||||
views.set(view.id, this.makeView(srv, view));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,10 +428,10 @@ export class ViewManager {
|
||||
|
||||
// commit closed
|
||||
for (const x of closed.values()) {
|
||||
this.closedViews.set(x.tab.id, {srv: x.srv, tab: x.tab});
|
||||
this.closedViews.set(x.view.id, {srv: x.srv, view: x.view});
|
||||
}
|
||||
|
||||
if ((currentTabId && closed.has(currentTabId)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if ((currentViewId && closed.has(currentViewId)) || (this.currentView && this.closedViews.has(this.currentView))) {
|
||||
if (ServerManager.hasServers()) {
|
||||
this.currentView = undefined;
|
||||
this.showInitial();
|
||||
@@ -440,13 +440,13 @@ export class ViewManager {
|
||||
}
|
||||
}
|
||||
|
||||
// show the focused tab (or initial)
|
||||
if (currentTabId && views.has(currentTabId)) {
|
||||
const view = views.get(currentTabId);
|
||||
// show the focused view (or initial)
|
||||
if (currentViewId && views.has(currentViewId)) {
|
||||
const view = views.get(currentViewId);
|
||||
if (view && view.id !== this.currentView) {
|
||||
this.currentView = view.id;
|
||||
this.showById(view.id);
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, view.tab.server.id, view.tab.id);
|
||||
MainWindow.get()?.webContents.send(SET_ACTIVE_VIEW, view.view.server.id, view.view.id);
|
||||
} else {
|
||||
this.focusCurrentView();
|
||||
}
|
||||
@@ -475,17 +475,17 @@ export class ViewManager {
|
||||
return;
|
||||
}
|
||||
let cleanedPathName = pathName;
|
||||
if (currentView.tab.server.url.pathname !== '/' && pathName.startsWith(currentView.tab.server.url.pathname)) {
|
||||
cleanedPathName = pathName.replace(currentView.tab.server.url.pathname, '');
|
||||
if (currentView.view.server.url.pathname !== '/' && pathName.startsWith(currentView.view.server.url.pathname)) {
|
||||
cleanedPathName = pathName.replace(currentView.view.server.url.pathname, '');
|
||||
}
|
||||
const redirectedviewId = ServerManager.lookupTabByURL(`${currentView.tab.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.id || viewId;
|
||||
const redirectedviewId = ServerManager.lookupViewByURL(`${currentView.view.server.url.toString().replace(/\/$/, '')}${cleanedPathName}`)?.id || viewId;
|
||||
if (this.isViewClosed(redirectedviewId)) {
|
||||
// If it's a closed view, just open it and stop
|
||||
this.openClosedTab(redirectedviewId, `${currentView.tab.server.url}${cleanedPathName}`);
|
||||
this.openClosedView(redirectedviewId, `${currentView.view.server.url}${cleanedPathName}`);
|
||||
return;
|
||||
}
|
||||
let redirectedView = this.getView(redirectedviewId) || currentView;
|
||||
if (redirectedView !== currentView && redirectedView?.tab.server.id === ServerManager.getCurrentServer().id && redirectedView?.isLoggedIn) {
|
||||
if (redirectedView !== currentView && redirectedView?.view.server.id === ServerManager.getCurrentServer().id && redirectedView?.isLoggedIn) {
|
||||
log.info('redirecting to a new view', redirectedView?.id || viewId);
|
||||
this.showById(redirectedView?.id || viewId);
|
||||
} else {
|
||||
@@ -493,7 +493,7 @@ export class ViewManager {
|
||||
}
|
||||
|
||||
// Special case check for Channels to not force a redirect to "/", causing a refresh
|
||||
if (!(redirectedView !== currentView && redirectedView?.tab.type === TAB_MESSAGING && cleanedPathName === '/')) {
|
||||
if (!(redirectedView !== currentView && redirectedView?.view.type === TAB_MESSAGING && cleanedPathName === '/')) {
|
||||
redirectedView?.sendToRenderer(BROWSER_HISTORY_PUSH, cleanedPathName);
|
||||
if (redirectedView) {
|
||||
this.handleBrowserHistoryButton(e, redirectedView.id);
|
||||
@@ -547,7 +547,7 @@ export class ViewManager {
|
||||
|
||||
const currentView = this.getCurrentView();
|
||||
if (currentView && currentView.currentURL) {
|
||||
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.tab.url, currentView.currentURL));
|
||||
const adjustedBounds = getAdjustedWindowBoundaries(newBounds.width, newBounds.height, shouldHaveBackBar(currentView.view.url, currentView.currentURL));
|
||||
currentView.setBounds(adjustedBounds);
|
||||
}
|
||||
}
|
||||
@@ -556,21 +556,21 @@ export class ViewManager {
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
private openClosedTab = (id: string, url?: string) => {
|
||||
private openClosedView = (id: string, url?: string) => {
|
||||
if (!this.closedViews.has(id)) {
|
||||
return;
|
||||
}
|
||||
const {srv, tab} = this.closedViews.get(id)!;
|
||||
tab.isOpen = true;
|
||||
this.loadView(srv, tab, url);
|
||||
const {srv, view} = this.closedViews.get(id)!;
|
||||
view.isOpen = true;
|
||||
this.loadView(srv, view, url);
|
||||
this.showById(id);
|
||||
const view = this.views.get(id)!;
|
||||
view.isVisible = true;
|
||||
view.on(LOAD_SUCCESS, () => {
|
||||
view.isVisible = false;
|
||||
const browserView = this.views.get(id)!;
|
||||
browserView.isVisible = true;
|
||||
browserView.on(LOAD_SUCCESS, () => {
|
||||
browserView.isVisible = false;
|
||||
this.showById(id);
|
||||
});
|
||||
ipcMain.emit(OPEN_TAB, null, tab.id);
|
||||
ipcMain.emit(OPEN_VIEW, null, view.id);
|
||||
}
|
||||
|
||||
private getViewLogger = (viewId: string) => {
|
||||
@@ -585,8 +585,8 @@ export class ViewManager {
|
||||
return {
|
||||
id: view.id,
|
||||
webContentsId: view.webContentsId,
|
||||
serverName: view.tab.server.name,
|
||||
tabType: view.tab.type,
|
||||
serverName: view.view.server.name,
|
||||
viewType: view.view.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -81,7 +81,7 @@ export class WebContentsEventManager {
|
||||
return CallsWidgetWindow.getURL();
|
||||
}
|
||||
|
||||
return ViewManager.getViewByWebContentsId(webContentsId)?.tab.server.url;
|
||||
return ViewManager.getViewByWebContentsId(webContentsId)?.view.server.url;
|
||||
}
|
||||
|
||||
private generateWillNavigate = (webContentsId: number) => {
|
||||
@@ -274,7 +274,7 @@ export class WebContentsEventManager {
|
||||
return {action: 'deny'};
|
||||
}
|
||||
|
||||
const otherServerURL = ServerManager.lookupTabByURL(parsedURL);
|
||||
const otherServerURL = ServerManager.lookupViewByURL(parsedURL);
|
||||
if (otherServerURL && isTeamUrl(otherServerURL.server.url, parsedURL, true)) {
|
||||
ViewManager.handleDeepLink(parsedURL);
|
||||
return {action: 'deny'};
|
||||
|
@@ -247,7 +247,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
title: 'call test title #/&',
|
||||
};
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
url: new URL('http://localhost:8065'),
|
||||
},
|
||||
@@ -262,7 +262,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
|
||||
it('getWidgetURL - under subpath', () => {
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
url: new URL('http://localhost:8065/subpath'),
|
||||
},
|
||||
@@ -339,7 +339,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
beforeEach(() => {
|
||||
callsWidgetWindow.options = {callID: 'id'};
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
url: new URL('http://localhost:8065'),
|
||||
},
|
||||
@@ -491,7 +491,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
callsWidgetWindow.close = jest.fn();
|
||||
callsWidgetWindow.getWidgetURL = jest.fn();
|
||||
const view = {
|
||||
name: 'server-1_tab-messaging',
|
||||
name: 'server-1_view-messaging',
|
||||
serverInfo: {
|
||||
server: {
|
||||
url: new URL('http://server-1.com'),
|
||||
@@ -526,12 +526,12 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
|
||||
it('should create calls widget window', async () => {
|
||||
expect(callsWidgetWindow.win).toBeUndefined();
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
|
||||
expect(callsWidgetWindow.win).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create with correct initial configuration', async () => {
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
|
||||
expect(BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({
|
||||
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||
@@ -560,7 +560,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
const window = {webContents: {id: 2}};
|
||||
callsWidgetWindow.win = window;
|
||||
callsWidgetWindow.options = {callID: 'test'};
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test'});
|
||||
expect(callsWidgetWindow.win).toEqual(window);
|
||||
});
|
||||
|
||||
@@ -568,7 +568,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
const window = {webContents: {id: 2}};
|
||||
callsWidgetWindow.win = window;
|
||||
callsWidgetWindow.getCallID = jest.fn(() => 'test');
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_tab-messaging', {callID: 'test2'});
|
||||
await callsWidgetWindow.handleCreateCallsWidgetWindow('server-1_view-messaging', {callID: 'test2'});
|
||||
expect(callsWidgetWindow.win).not.toEqual(window);
|
||||
});
|
||||
});
|
||||
@@ -580,18 +580,18 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
send: jest.fn(),
|
||||
},
|
||||
};
|
||||
const teams = [
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
views: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
@@ -599,24 +599,24 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
views: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 2,
|
||||
lastActiveView: 2,
|
||||
},
|
||||
];
|
||||
const map = teams.reduce((arr, item) => {
|
||||
item.tabs.forEach((tab) => {
|
||||
arr.push([`${item.name}_${tab.name}`, {
|
||||
const map = servers.reduce((arr, item) => {
|
||||
item.views.forEach((view) => {
|
||||
arr.push([`${item.name}_${view.name}`, {
|
||||
sendToRenderer: jest.fn(),
|
||||
}]);
|
||||
});
|
||||
@@ -649,9 +649,9 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
||||
|
||||
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('desktop-sources-result', [
|
||||
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('desktop-sources-result', [
|
||||
{
|
||||
id: 'screen0',
|
||||
},
|
||||
@@ -663,11 +663,11 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
|
||||
it('should send error with no sources', async () => {
|
||||
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]);
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-2_tab-1', null);
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-2_view-1', null);
|
||||
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(views.get('server-2_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
expect(views.get('server-2_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
|
||||
@@ -684,16 +684,16 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
]);
|
||||
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
|
||||
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
||||
|
||||
expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen');
|
||||
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledTimes(1);
|
||||
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledTimes(1);
|
||||
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -713,7 +713,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
]);
|
||||
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
|
||||
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
||||
|
||||
expect(callsWidgetWindow.missingScreensharePermissions).toBe(true);
|
||||
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1);
|
||||
@@ -721,11 +721,11 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
expect(views.get('server-1_tab-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', {
|
||||
err: 'screen-permissions',
|
||||
});
|
||||
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_tab-1', null);
|
||||
await callsWidgetWindow.handleGetDesktopSources('server-1_view-1', null);
|
||||
|
||||
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2);
|
||||
expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1);
|
||||
@@ -739,25 +739,25 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
describe('handleDesktopSourcesModalRequest', () => {
|
||||
const callsWidgetWindow = new CallsWidgetWindow();
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
id: 'server-1',
|
||||
},
|
||||
},
|
||||
sendToRenderer: jest.fn(),
|
||||
};
|
||||
const teams = [
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
views: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
@@ -765,24 +765,24 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
views: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 2,
|
||||
lastActiveView: 2,
|
||||
},
|
||||
];
|
||||
const map = teams.reduce((arr, item) => {
|
||||
item.tabs.forEach((tab) => {
|
||||
arr.push([`${item.name}_${tab.name}`, {}]);
|
||||
const map = servers.reduce((arr, item) => {
|
||||
item.views.forEach((view) => {
|
||||
arr.push([`${item.name}_${view.name}`, {}]);
|
||||
});
|
||||
return arr;
|
||||
}, []);
|
||||
@@ -805,7 +805,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
describe('handleCallsWidgetChannelLinkClick', () => {
|
||||
const callsWidgetWindow = new CallsWidgetWindow();
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
id: 'server-2',
|
||||
},
|
||||
@@ -813,18 +813,18 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
sendToRenderer: jest.fn(),
|
||||
};
|
||||
callsWidgetWindow.getChannelURL = jest.fn();
|
||||
const teams = [
|
||||
const servers = [
|
||||
{
|
||||
name: 'server-1',
|
||||
order: 1,
|
||||
tabs: [
|
||||
views: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
@@ -832,24 +832,24 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
}, {
|
||||
name: 'server-2',
|
||||
order: 0,
|
||||
tabs: [
|
||||
views: [
|
||||
{
|
||||
name: 'tab-1',
|
||||
name: 'view-1',
|
||||
order: 0,
|
||||
isOpen: false,
|
||||
},
|
||||
{
|
||||
name: 'tab-2',
|
||||
name: 'view-2',
|
||||
order: 2,
|
||||
isOpen: true,
|
||||
},
|
||||
],
|
||||
lastActiveTab: 2,
|
||||
lastActiveView: 2,
|
||||
},
|
||||
];
|
||||
const map = teams.reduce((arr, item) => {
|
||||
item.tabs.forEach((tab) => {
|
||||
arr.push([`${item.name}_${tab.name}`, {}]);
|
||||
const map = servers.reduce((arr, item) => {
|
||||
item.views.forEach((view) => {
|
||||
arr.push([`${item.name}_${view.name}`, {}]);
|
||||
});
|
||||
return arr;
|
||||
}, []);
|
||||
@@ -872,7 +872,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
describe('handleCallsError', () => {
|
||||
const callsWidgetWindow = new CallsWidgetWindow();
|
||||
callsWidgetWindow.mainView = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
id: 'server-2',
|
||||
},
|
||||
@@ -899,7 +899,7 @@ describe('main/windows/callsWidgetWindow', () => {
|
||||
|
||||
describe('handleCallsLinkClick', () => {
|
||||
const view = {
|
||||
tab: {
|
||||
view: {
|
||||
server: {
|
||||
id: 'server-1',
|
||||
},
|
||||
|
@@ -14,7 +14,7 @@ import {
|
||||
CallsWidgetWindowConfig,
|
||||
} from 'types/calls';
|
||||
|
||||
import {MattermostView} from 'main/views/MattermostView';
|
||||
import {MattermostBrowserView} from 'main/views/MattermostBrowserView';
|
||||
|
||||
import {getLocalPreload, openScreensharePermissionsSettingsMacOS, resetScreensharePermissionsMacOS} from 'main/utils';
|
||||
|
||||
@@ -47,7 +47,7 @@ const log = new Logger('CallsWidgetWindow');
|
||||
|
||||
export class CallsWidgetWindow {
|
||||
private win?: BrowserWindow;
|
||||
private mainView?: MattermostView;
|
||||
private mainView?: MattermostBrowserView;
|
||||
private options?: CallsWidgetWindowConfig;
|
||||
private missingScreensharePermissions?: boolean;
|
||||
|
||||
@@ -82,7 +82,7 @@ export class CallsWidgetWindow {
|
||||
}
|
||||
|
||||
private get serverID() {
|
||||
return this.mainView?.tab.server.id;
|
||||
return this.mainView?.view.server.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +101,7 @@ export class CallsWidgetWindow {
|
||||
if (!this.mainView) {
|
||||
return undefined;
|
||||
}
|
||||
const u = parseURL(this.mainView.tab.server.url.toString()) as URL;
|
||||
const u = parseURL(this.mainView.view.server.url.toString()) as URL;
|
||||
|
||||
u.pathname = getFormattedPathName(u.pathname);
|
||||
u.pathname += `plugins/${CALLS_PLUGIN_ID}/standalone/widget.html`;
|
||||
@@ -119,7 +119,7 @@ export class CallsWidgetWindow {
|
||||
return u.toString();
|
||||
}
|
||||
|
||||
private init = (view: MattermostView, options: CallsWidgetWindowConfig) => {
|
||||
private init = (view: MattermostBrowserView, options: CallsWidgetWindowConfig) => {
|
||||
this.win = new BrowserWindow({
|
||||
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||
@@ -271,7 +271,7 @@ export class CallsWidgetWindow {
|
||||
if (!parsedURL) {
|
||||
return {action: 'deny' as const};
|
||||
}
|
||||
if (isCallsPopOutURL(this.mainView?.tab.server.url, parsedURL, this.options?.callID)) {
|
||||
if (isCallsPopOutURL(this.mainView?.view.server.url, parsedURL, this.options?.callID)) {
|
||||
return {
|
||||
action: 'allow' as const,
|
||||
overrideBrowserWindowOptions: {
|
||||
|
@@ -421,7 +421,7 @@ describe('main/windows/mainWindow', () => {
|
||||
expect(window.setFullScreen).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should select tabs using alt+cmd+arrow keys on Mac', () => {
|
||||
it('should select views using alt+cmd+arrow keys on Mac', () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'darwin',
|
||||
|
@@ -5,7 +5,7 @@ import React, {useState, useCallback, useEffect} from 'react';
|
||||
import {useIntl, FormattedMessage} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import {isValidURL, parseURL} from 'common/utils/url';
|
||||
import {MODAL_TRANSITION_TIMEOUT} from 'common/utils/constants';
|
||||
@@ -22,8 +22,8 @@ import 'renderer/css/components/ConfigureServer.scss';
|
||||
import 'renderer/css/components/LoadingScreen.css';
|
||||
|
||||
type ConfigureServerProps = {
|
||||
currentTeams: MattermostTeam[];
|
||||
team?: MattermostTeam;
|
||||
currentServers: UniqueServer[];
|
||||
server?: UniqueServer;
|
||||
mobileView?: boolean;
|
||||
darkMode?: boolean;
|
||||
messageTitle?: string;
|
||||
@@ -32,12 +32,12 @@ type ConfigureServerProps = {
|
||||
alternateLinkMessage?: string;
|
||||
alternateLinkText?: string;
|
||||
alternateLinkURL?: string;
|
||||
onConnect: (data: MattermostTeam) => void;
|
||||
onConnect: (data: UniqueServer) => void;
|
||||
};
|
||||
|
||||
function ConfigureServer({
|
||||
currentTeams,
|
||||
team,
|
||||
currentServers,
|
||||
server,
|
||||
mobileView,
|
||||
darkMode,
|
||||
messageTitle,
|
||||
@@ -54,7 +54,7 @@ function ConfigureServer({
|
||||
name: prevName,
|
||||
url: prevURL,
|
||||
id,
|
||||
} = team || {};
|
||||
} = server || {};
|
||||
|
||||
const [transition, setTransition] = useState<'inFromRight' | 'outToLeft'>();
|
||||
const [name, setName] = useState(prevName || '');
|
||||
@@ -92,14 +92,14 @@ function ConfigureServer({
|
||||
|
||||
if (!newName) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newTeamModal.error.nameRequired',
|
||||
id: 'renderer.components.newServerModal.error.nameRequired',
|
||||
defaultMessage: 'Name is required.',
|
||||
});
|
||||
}
|
||||
|
||||
if (currentTeams.find(({name: existingName}) => existingName === newName)) {
|
||||
if (currentServers.find(({name: existingName}) => existingName === newName)) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newTeamModal.error.serverNameExists',
|
||||
id: 'renderer.components.newServerModal.error.serverNameExists',
|
||||
defaultMessage: 'A server with the same name already exists.',
|
||||
});
|
||||
}
|
||||
@@ -110,28 +110,28 @@ function ConfigureServer({
|
||||
const validateURL = async (fullURL: string) => {
|
||||
if (!fullURL) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newTeamModal.error.urlRequired',
|
||||
id: 'renderer.components.newServerModal.error.urlRequired',
|
||||
defaultMessage: 'URL is required.',
|
||||
});
|
||||
}
|
||||
|
||||
if (!parseURL(fullURL)) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newTeamModal.error.urlIncorrectFormatting',
|
||||
id: 'renderer.components.newServerModal.error.urlIncorrectFormatting',
|
||||
defaultMessage: 'URL is not formatted correctly.',
|
||||
});
|
||||
}
|
||||
|
||||
if (!isValidURL(fullURL)) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newTeamModal.error.urlNeedsHttp',
|
||||
id: 'renderer.components.newServerModal.error.urlNeedsHttp',
|
||||
defaultMessage: 'URL should start with http:// or https://.',
|
||||
});
|
||||
}
|
||||
|
||||
if (currentTeams.find(({url: existingURL}) => parseURL(existingURL)?.toString === parseURL(fullURL)?.toString())) {
|
||||
if (currentServers.find(({url: existingURL}) => parseURL(existingURL)?.toString === parseURL(fullURL)?.toString())) {
|
||||
return formatMessage({
|
||||
id: 'renderer.components.newTeamModal.error.serverUrlExists',
|
||||
id: 'renderer.components.newServerModal.error.serverUrlExists',
|
||||
defaultMessage: 'A server with the same URL already exists.',
|
||||
});
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import {Container, Row} from 'react-bootstrap';
|
||||
import {DropResult} from 'react-beautiful-dnd';
|
||||
import {injectIntl, IntlShape} from 'react-intl';
|
||||
|
||||
import {MattermostTab, MattermostTeam} from 'types/config';
|
||||
import {UniqueView, UniqueServer} from 'types/config';
|
||||
import {DownloadedItems} from 'types/downloads';
|
||||
|
||||
import restoreButton from '../../assets/titlebar/chrome-restore.svg';
|
||||
@@ -23,7 +23,7 @@ import {playSound} from '../notificationSounds';
|
||||
import TabBar from './TabBar';
|
||||
import ExtraBar from './ExtraBar';
|
||||
import ErrorView from './ErrorView';
|
||||
import TeamDropdownButton from './TeamDropdownButton';
|
||||
import ServerDropdownButton from './ServerDropdownButton';
|
||||
import DownloadsDropdownButton from './DownloadsDropdown/DownloadsDropdownButton';
|
||||
|
||||
import '../css/components/UpgradeButton.scss';
|
||||
@@ -47,8 +47,8 @@ type Props = {
|
||||
type State = {
|
||||
activeServerId?: string;
|
||||
activeTabId?: string;
|
||||
servers: MattermostTeam[];
|
||||
tabs: Map<string, MattermostTab[]>;
|
||||
servers: UniqueServer[];
|
||||
tabs: Map<string, UniqueView[]>;
|
||||
sessionsExpired: Record<string, boolean>;
|
||||
unreadCounts: Record<string, boolean>;
|
||||
mentionCounts: Record<string, number>;
|
||||
@@ -147,7 +147,7 @@ class MainPage extends React.PureComponent<Props, State> {
|
||||
|
||||
setInitialActiveTab = async () => {
|
||||
const lastActive = await window.desktop.getLastActive();
|
||||
this.setActiveView(lastActive.server, lastActive.tab);
|
||||
this.setActiveView(lastActive.server, lastActive.view);
|
||||
}
|
||||
|
||||
updateServers = async () => {
|
||||
@@ -239,11 +239,11 @@ class MainPage extends React.PureComponent<Props, State> {
|
||||
this.setState({unreadCounts: newUnreads, mentionCounts: newMentionCounts, sessionsExpired: expired});
|
||||
});
|
||||
|
||||
window.desktop.onCloseTeamsDropdown(() => {
|
||||
window.desktop.onCloseServersDropdown(() => {
|
||||
this.setState({isMenuOpen: false});
|
||||
});
|
||||
|
||||
window.desktop.onOpenTeamsDropdown(() => {
|
||||
window.desktop.onOpenServersDropdown(() => {
|
||||
this.setState({isMenuOpen: true});
|
||||
});
|
||||
|
||||
@@ -290,7 +290,7 @@ class MainPage extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
handleCloseDropdowns = () => {
|
||||
window.desktop.closeTeamsDropdown();
|
||||
window.desktop.closeServersDropdown();
|
||||
this.closeDownloadsDropdown();
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ class MainPage extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
handleCloseTab = (tabId: string) => {
|
||||
window.desktop.closeTab(tabId);
|
||||
window.desktop.closeView(tabId);
|
||||
}
|
||||
|
||||
handleDragAndDrop = async (dropResult: DropResult) => {
|
||||
@@ -399,7 +399,7 @@ class MainPage extends React.PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const {intl} = this.props;
|
||||
let currentTabs: MattermostTab[] = [];
|
||||
let currentTabs: UniqueView[] = [];
|
||||
if (this.state.activeServerId) {
|
||||
currentTabs = this.state.tabs.get(this.state.activeServerId) ?? [];
|
||||
}
|
||||
@@ -538,7 +538,7 @@ class MainPage extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
</button>
|
||||
{activeServer && (
|
||||
<TeamDropdownButton
|
||||
<ServerDropdownButton
|
||||
isDisabled={this.state.modalOpen}
|
||||
activeServerName={activeServer.name}
|
||||
totalMentionCount={totalMentionCount}
|
||||
|
@@ -6,15 +6,15 @@ import React from 'react';
|
||||
import {Modal, Button, FormGroup, FormControl, FormLabel, FormText} from 'react-bootstrap';
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import {isValidURL} from 'common/utils/url';
|
||||
|
||||
type Props = {
|
||||
onClose?: () => void;
|
||||
onSave?: (team: MattermostTeam) => void;
|
||||
team?: MattermostTeam;
|
||||
currentTeams?: MattermostTeam[];
|
||||
onSave?: (server: UniqueServer) => void;
|
||||
server?: UniqueServer;
|
||||
currentServers?: UniqueServer[];
|
||||
editMode?: boolean;
|
||||
show?: boolean;
|
||||
restoreFocus?: boolean;
|
||||
@@ -24,16 +24,16 @@ type Props = {
|
||||
};
|
||||
|
||||
type State = {
|
||||
teamName: string;
|
||||
teamUrl: string;
|
||||
teamId?: string;
|
||||
teamOrder: number;
|
||||
serverName: string;
|
||||
serverUrl: string;
|
||||
serverId?: string;
|
||||
serverOrder: number;
|
||||
saveStarted: boolean;
|
||||
}
|
||||
|
||||
class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
class NewServerModal extends React.PureComponent<Props, State> {
|
||||
wasShown?: boolean;
|
||||
teamUrlInputRef?: HTMLInputElement;
|
||||
serverUrlInputRef?: HTMLInputElement;
|
||||
|
||||
static defaultProps = {
|
||||
restoreFocus: true,
|
||||
@@ -44,90 +44,90 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
|
||||
this.wasShown = false;
|
||||
this.state = {
|
||||
teamName: '',
|
||||
teamUrl: '',
|
||||
teamOrder: props.currentOrder || 0,
|
||||
serverName: '',
|
||||
serverUrl: '',
|
||||
serverOrder: props.currentOrder || 0,
|
||||
saveStarted: false,
|
||||
};
|
||||
}
|
||||
|
||||
initializeOnShow() {
|
||||
this.setState({
|
||||
teamName: this.props.team ? this.props.team.name : '',
|
||||
teamUrl: this.props.team ? this.props.team.url : '',
|
||||
teamId: this.props.team?.id,
|
||||
serverName: this.props.server ? this.props.server.name : '',
|
||||
serverUrl: this.props.server ? this.props.server.url : '',
|
||||
serverId: this.props.server?.id,
|
||||
saveStarted: false,
|
||||
});
|
||||
}
|
||||
|
||||
getTeamNameValidationError() {
|
||||
getServerNameValidationError() {
|
||||
if (!this.state.saveStarted) {
|
||||
return null;
|
||||
}
|
||||
if (this.props.currentTeams) {
|
||||
const currentTeams = [...this.props.currentTeams];
|
||||
if (currentTeams.find((team) => team.id !== this.state.teamId && team.name === this.state.teamName)) {
|
||||
if (this.props.currentServers) {
|
||||
const currentServers = [...this.props.currentServers];
|
||||
if (currentServers.find((server) => server.id !== this.state.serverId && server.name === this.state.serverName)) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.error.serverNameExists'
|
||||
id='renderer.components.newServerModal.error.serverNameExists'
|
||||
defaultMessage='A server with the same name already exists.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.state.teamName.length > 0 ? null : (
|
||||
return this.state.serverName.length > 0 ? null : (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.error.nameRequired'
|
||||
id='renderer.components.newServerModal.error.nameRequired'
|
||||
defaultMessage='Name is required.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getTeamNameValidationState() {
|
||||
return this.getTeamNameValidationError() === null ? null : 'error';
|
||||
getServerNameValidationState() {
|
||||
return this.getServerNameValidationError() === null ? null : 'error';
|
||||
}
|
||||
|
||||
handleTeamNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleServerNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
teamName: e.target.value,
|
||||
serverName: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
getTeamUrlValidationError() {
|
||||
getServerUrlValidationError() {
|
||||
if (!this.state.saveStarted) {
|
||||
return null;
|
||||
}
|
||||
if (this.props.currentTeams) {
|
||||
const currentTeams = [...this.props.currentTeams];
|
||||
if (currentTeams.find((team) => team.id !== this.state.teamId && team.url === this.state.teamUrl)) {
|
||||
if (this.props.currentServers) {
|
||||
const currentServers = [...this.props.currentServers];
|
||||
if (currentServers.find((server) => server.id !== this.state.serverId && server.url === this.state.serverUrl)) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.error.serverUrlExists'
|
||||
id='renderer.components.newServerModal.error.serverUrlExists'
|
||||
defaultMessage='A server with the same URL already exists.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.state.teamUrl.length === 0) {
|
||||
if (this.state.serverUrl.length === 0) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.error.urlRequired'
|
||||
id='renderer.components.newServerModal.error.urlRequired'
|
||||
defaultMessage='URL is required.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!(/^https?:\/\/.*/).test(this.state.teamUrl.trim())) {
|
||||
if (!(/^https?:\/\/.*/).test(this.state.serverUrl.trim())) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.error.urlNeedsHttp'
|
||||
id='renderer.components.newServerModal.error.urlNeedsHttp'
|
||||
defaultMessage='URL should start with http:// or https://.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!isValidURL(this.state.teamUrl.trim())) {
|
||||
if (!isValidURL(this.state.serverUrl.trim())) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.error.urlIncorrectFormatting'
|
||||
id='renderer.components.newServerModal.error.urlIncorrectFormatting'
|
||||
defaultMessage='URL is not formatted correctly.'
|
||||
/>
|
||||
);
|
||||
@@ -135,32 +135,32 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
getTeamUrlValidationState() {
|
||||
return this.getTeamUrlValidationError() === null ? null : 'error';
|
||||
getServerUrlValidationState() {
|
||||
return this.getServerUrlValidationError() === null ? null : 'error';
|
||||
}
|
||||
|
||||
handleTeamUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const teamUrl = e.target.value;
|
||||
this.setState({teamUrl});
|
||||
handleServerUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const serverUrl = e.target.value;
|
||||
this.setState({serverUrl});
|
||||
}
|
||||
|
||||
addProtocolToUrl = (teamUrl: string): Promise<void> => {
|
||||
if (teamUrl.startsWith('http://') || teamUrl.startsWith('https://')) {
|
||||
addProtocolToUrl = (serverUrl: string): Promise<void> => {
|
||||
if (serverUrl.startsWith('http://') || serverUrl.startsWith('https://')) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return window.desktop.modals.pingDomain(teamUrl).
|
||||
return window.desktop.modals.pingDomain(serverUrl).
|
||||
then((result: string) => {
|
||||
this.setState({teamUrl: `${result}://${this.state.teamUrl}`});
|
||||
this.setState({serverUrl: `${result}://${this.state.serverUrl}`});
|
||||
}).
|
||||
catch(() => {
|
||||
console.error(`Could not ping url: ${teamUrl}`);
|
||||
console.error(`Could not ping url: ${serverUrl}`);
|
||||
});
|
||||
}
|
||||
|
||||
getError() {
|
||||
const nameError = this.getTeamNameValidationError();
|
||||
const urlError = this.getTeamUrlValidationError();
|
||||
const nameError = this.getServerNameValidationError();
|
||||
const urlError = this.getServerUrlValidationError();
|
||||
|
||||
if (nameError && urlError) {
|
||||
return (
|
||||
@@ -179,20 +179,20 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
validateForm() {
|
||||
return this.getTeamNameValidationState() === null &&
|
||||
this.getTeamUrlValidationState() === null;
|
||||
return this.getServerNameValidationState() === null &&
|
||||
this.getServerUrlValidationState() === null;
|
||||
}
|
||||
|
||||
save = async () => {
|
||||
await this.addProtocolToUrl(this.state.teamUrl);
|
||||
await this.addProtocolToUrl(this.state.serverUrl);
|
||||
this.setState({
|
||||
saveStarted: true,
|
||||
}, () => {
|
||||
if (this.validateForm()) {
|
||||
this.props.onSave?.({
|
||||
url: this.state.teamUrl,
|
||||
name: this.state.teamName,
|
||||
id: this.state.teamId,
|
||||
url: this.state.serverUrl,
|
||||
name: this.state.serverName,
|
||||
id: this.state.serverId,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -219,14 +219,14 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
if (this.props.editMode) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.title.edit'
|
||||
id='renderer.components.newServerModal.title.edit'
|
||||
defaultMessage='Edit Server'
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.title.add'
|
||||
id='renderer.components.newServerModal.title.add'
|
||||
defaultMessage='Add Server'
|
||||
/>
|
||||
);
|
||||
@@ -241,11 +241,11 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<Modal
|
||||
bsClass='modal'
|
||||
className='NewTeamModal'
|
||||
className='NewServerModal'
|
||||
show={this.props.show}
|
||||
id='newServerModal'
|
||||
enforceFocus={true}
|
||||
onEntered={() => this.teamUrlInputRef?.focus()}
|
||||
onEntered={() => this.serverUrlInputRef?.focus()}
|
||||
onHide={this.props.onClose}
|
||||
restoreFocus={this.props.restoreFocus}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
@@ -272,58 +272,58 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.serverURL'
|
||||
id='renderer.components.newServerModal.serverURL'
|
||||
defaultMessage='Server URL'
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl
|
||||
id='teamUrlInput'
|
||||
id='serverUrlInput'
|
||||
type='text'
|
||||
value={this.state.teamUrl}
|
||||
value={this.state.serverUrl}
|
||||
placeholder='https://example.com'
|
||||
onChange={this.handleTeamUrlChange}
|
||||
onChange={this.handleServerUrlChange}
|
||||
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
ref={(ref: HTMLInputElement) => {
|
||||
this.teamUrlInputRef = ref;
|
||||
this.serverUrlInputRef = ref;
|
||||
if (this.props.setInputRef) {
|
||||
this.props.setInputRef(ref);
|
||||
}
|
||||
}}
|
||||
isInvalid={Boolean(this.getTeamUrlValidationState())}
|
||||
isInvalid={Boolean(this.getServerUrlValidationState())}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<FormControl.Feedback/>
|
||||
<FormText>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.serverURL.description'
|
||||
id='renderer.components.newServerModal.serverURL.description'
|
||||
defaultMessage='The URL of your Mattermost server. Must start with http:// or https://.'
|
||||
/>
|
||||
</FormText>
|
||||
</FormGroup>
|
||||
<FormGroup className='NewTeamModal-noBottomSpace'>
|
||||
<FormGroup className='NewServerModal-noBottomSpace'>
|
||||
<FormLabel>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.serverDisplayName'
|
||||
id='renderer.components.newServerModal.serverDisplayName'
|
||||
defaultMessage='Server Display Name'
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl
|
||||
id='teamNameInput'
|
||||
id='serverNameInput'
|
||||
type='text'
|
||||
value={this.state.teamName}
|
||||
placeholder={this.props.intl.formatMessage({id: 'renderer.components.newTeamModal.serverDisplayName', defaultMessage: 'Server Display Name'})}
|
||||
onChange={this.handleTeamNameChange}
|
||||
value={this.state.serverName}
|
||||
placeholder={this.props.intl.formatMessage({id: 'renderer.components.newServerModal.serverDisplayName', defaultMessage: 'Server Display Name'})}
|
||||
onChange={this.handleServerNameChange}
|
||||
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
isInvalid={Boolean(this.getTeamNameValidationState())}
|
||||
isInvalid={Boolean(this.getServerNameValidationState())}
|
||||
/>
|
||||
<FormControl.Feedback/>
|
||||
<FormText className='NewTeamModal-noBottomSpace'>
|
||||
<FormText className='NewServerModal-noBottomSpace'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newTeamModal.serverDisplayName.description'
|
||||
id='renderer.components.newServerModal.serverDisplayName.description'
|
||||
defaultMessage='The name of the server displayed on your desktop app tab bar.'
|
||||
/>
|
||||
</FormText>
|
||||
@@ -367,4 +367,4 @@ class NewTeamModal extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(NewTeamModal);
|
||||
export default injectIntl(NewServerModal);
|
@@ -5,7 +5,7 @@ import classNames from 'classnames';
|
||||
import React, {useEffect} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import '../css/components/TeamDropdownButton.scss';
|
||||
import '../css/components/ServerDropdownButton.scss';
|
||||
|
||||
type Props = {
|
||||
isDisabled?: boolean;
|
||||
@@ -16,7 +16,7 @@ type Props = {
|
||||
darkMode: boolean;
|
||||
}
|
||||
|
||||
const TeamDropdownButton: React.FC<Props> = (props: Props) => {
|
||||
const ServerDropdownButton: React.FC<Props> = (props: Props) => {
|
||||
const {isDisabled, activeServerName, totalMentionCount, hasUnreads, isMenuOpen, darkMode} = props;
|
||||
const buttonRef: React.RefObject<HTMLButtonElement> = React.createRef();
|
||||
|
||||
@@ -30,22 +30,22 @@ const TeamDropdownButton: React.FC<Props> = (props: Props) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (isMenuOpen) {
|
||||
window.desktop.closeTeamsDropdown();
|
||||
window.desktop.closeServersDropdown();
|
||||
} else {
|
||||
window.desktop.openTeamsDropdown();
|
||||
window.desktop.openServersDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
let badgeDiv: React.ReactNode;
|
||||
if (totalMentionCount > 0) {
|
||||
badgeDiv = (
|
||||
<div className='TeamDropdownButton__badge-count'>
|
||||
<div className='ServerDropdownButton__badge-count'>
|
||||
<span>{totalMentionCount > 99 ? '99+' : totalMentionCount}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (hasUnreads) {
|
||||
badgeDiv = (
|
||||
<div className='TeamDropdownButton__badge-unreads'/>
|
||||
<div className='ServerDropdownButton__badge-unreads'/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const TeamDropdownButton: React.FC<Props> = (props: Props) => {
|
||||
<button
|
||||
ref={buttonRef}
|
||||
disabled={isDisabled}
|
||||
className={classNames('TeamDropdownButton', {
|
||||
className={classNames('ServerDropdownButton', {
|
||||
disabled: isDisabled,
|
||||
isMenuOpen,
|
||||
darkMode,
|
||||
@@ -63,14 +63,14 @@ const TeamDropdownButton: React.FC<Props> = (props: Props) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className='TeamDropdownButton__badge'>
|
||||
<div className='ServerDropdownButton__badge'>
|
||||
<i className='icon-server-variant'/>
|
||||
{badgeDiv}
|
||||
</div>
|
||||
{activeServerName && <span>{activeServerName}</span>}
|
||||
{!activeServerName &&
|
||||
<FormattedMessage
|
||||
id='renderer.components.teamDropdownButton.noServersConfigured'
|
||||
id='renderer.components.serverDropdownButton.noServersConfigured'
|
||||
defaultMessage='No servers configured'
|
||||
/>
|
||||
}
|
||||
@@ -79,4 +79,4 @@ const TeamDropdownButton: React.FC<Props> = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamDropdownButton;
|
||||
export default ServerDropdownButton;
|
@@ -8,9 +8,9 @@ import {DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDra
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {MattermostTab} from 'types/config';
|
||||
import {UniqueView} from 'types/config';
|
||||
|
||||
import {TabType, canCloseTab, getTabDisplayName} from 'common/tabs/TabView';
|
||||
import {ViewType, canCloseView, getViewDisplayName} from 'common/views/View';
|
||||
|
||||
type Props = {
|
||||
activeTabId?: string;
|
||||
@@ -19,7 +19,7 @@ type Props = {
|
||||
isDarkMode: boolean;
|
||||
onSelect: (id: string) => void;
|
||||
onCloseTab: (id: string) => void;
|
||||
tabs: MattermostTab[];
|
||||
tabs: UniqueView[];
|
||||
sessionsExpired: Record<string, boolean>;
|
||||
unreadCounts: Record<string, boolean>;
|
||||
mentionCounts: Record<string, number>;
|
||||
@@ -80,7 +80,7 @@ class TabBar extends React.PureComponent<Props> {
|
||||
return (
|
||||
<Draggable
|
||||
key={tab.id}
|
||||
draggableId={`teamTabItem-${tab.id}`}
|
||||
draggableId={`serverTabItem-${tab.id}`}
|
||||
index={index}
|
||||
>
|
||||
{(provided, snapshot) => {
|
||||
@@ -98,10 +98,10 @@ class TabBar extends React.PureComponent<Props> {
|
||||
<NavItem
|
||||
ref={provided.innerRef}
|
||||
as='li'
|
||||
id={`teamTabItem${index}`}
|
||||
id={`serverTabItem${index}`}
|
||||
draggable={false}
|
||||
title={this.props.intl.formatMessage({id: `common.tabs.${tab.name}`, defaultMessage: getTabDisplayName(tab.name as TabType)})}
|
||||
className={classNames('teamTabItem', {
|
||||
title={this.props.intl.formatMessage({id: `common.tabs.${tab.name}`, defaultMessage: getViewDisplayName(tab.name as ViewType)})}
|
||||
className={classNames('serverTabItem', {
|
||||
active: this.props.activeTabId === tab.id,
|
||||
dragging: snapshot.isDragging,
|
||||
})}
|
||||
@@ -121,12 +121,12 @@ class TabBar extends React.PureComponent<Props> {
|
||||
<div className='TabBar-tabSeperator'>
|
||||
<FormattedMessage
|
||||
id={`common.tabs.${tab.name}`}
|
||||
defaultMessage={getTabDisplayName(tab.name as TabType)}
|
||||
defaultMessage={getViewDisplayName(tab.name as ViewType)}
|
||||
/>
|
||||
{ badgeDiv }
|
||||
{canCloseTab(tab.name as TabType) &&
|
||||
{canCloseView(tab.name as ViewType) &&
|
||||
<button
|
||||
className='teamTabItem__close'
|
||||
className='serverTabItem__close'
|
||||
onClick={this.onCloseTab(tab.id!)}
|
||||
>
|
||||
<i className='icon-close'/>
|
||||
|
@@ -5,7 +5,7 @@ body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.NewTeamModal-noBottomSpace {
|
||||
.NewServerModal-noBottomSpace {
|
||||
padding-bottom: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.NewTeamModal-noBottomSpace {
|
||||
.NewServerModal-noBottomSpace {
|
||||
padding-bottom: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
.TeamDropdownButton {
|
||||
.ServerDropdownButton {
|
||||
background-color: transparent;
|
||||
border-width: 0 1px;
|
||||
border-color: rgba(61, 60, 64, 0.08);
|
||||
@@ -17,7 +17,7 @@
|
||||
&:not(.disabled):hover {
|
||||
background-color: #f4f4f4;
|
||||
|
||||
.TeamDropdownButton__badge-count, .TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-count, .ServerDropdownButton__badge-unreads {
|
||||
border-color: #f4f4f4;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
&:not(.disabled):focus, &.isMenuOpen {
|
||||
background-color: #fff;
|
||||
|
||||
.TeamDropdownButton__badge-count, .TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-count, .ServerDropdownButton__badge-unreads {
|
||||
border-color: #fff;
|
||||
}
|
||||
}
|
||||
@@ -49,11 +49,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdownButton__badge {
|
||||
.ServerDropdownButton__badge {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.TeamDropdownButton__badge-count {
|
||||
.ServerDropdownButton__badge-count {
|
||||
background: #F74343;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
@@ -77,7 +77,7 @@
|
||||
box-sizing: unset;
|
||||
}
|
||||
|
||||
.TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-unreads {
|
||||
background: #579eff;
|
||||
border-radius: 100px;
|
||||
width: 12px;
|
||||
@@ -88,13 +88,13 @@
|
||||
border: 2px solid #efefef;
|
||||
}
|
||||
|
||||
.TeamDropdownButton.darkMode {
|
||||
.ServerDropdownButton.darkMode {
|
||||
border-color: rgba(221, 221, 221, 0.08);
|
||||
|
||||
&:hover {
|
||||
background-color: #292929;
|
||||
|
||||
.TeamDropdownButton__badge-count, .TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-count, .ServerDropdownButton__badge-unreads {
|
||||
border-color: #292929;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@
|
||||
&:focus, &.isMenuOpen {
|
||||
background-color: #1f1f1f;
|
||||
|
||||
.TeamDropdownButton__badge-count, .TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-count, .ServerDropdownButton__badge-unreads {
|
||||
border-color: #1f1f1f;
|
||||
}
|
||||
}
|
||||
@@ -115,15 +115,15 @@
|
||||
color: rgba(221, 221, 221, 0.56);
|
||||
}
|
||||
|
||||
.TeamDropdownButton__badge-count {
|
||||
.ServerDropdownButton__badge-count {
|
||||
border-color: #2e2e2e;
|
||||
}
|
||||
|
||||
.TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-unreads {
|
||||
border-color: #2e2e2e;
|
||||
}
|
||||
|
||||
.TeamDropdownButton__badge-unreads {
|
||||
.ServerDropdownButton__badge-unreads {
|
||||
background: #196CAF;
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.TabBar .teamTabItem span {
|
||||
.TabBar .serverTabItem span {
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -64,7 +64,7 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem>a>div.TabBar-tabSeperator>.teamTabItem__close {
|
||||
.TabBar>li.serverTabItem>a>div.TabBar-tabSeperator>.serverTabItem__close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(61,60,64,0.32);
|
||||
@@ -72,19 +72,19 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem>a>div.TabBar-tabSeperator>.teamTabItem__close:hover {
|
||||
.TabBar>li.serverTabItem>a>div.TabBar-tabSeperator>.serverTabItem__close:hover {
|
||||
color: #3d3c40;
|
||||
}
|
||||
|
||||
.TabBar.darkMode>li.teamTabItem>a>div.TabBar-tabSeperator>.teamTabItem__close:hover {
|
||||
.TabBar.darkMode>li.serverTabItem>a>div.TabBar-tabSeperator>.serverTabItem__close:hover {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem>a>div.TabBar-tabSeperator>.teamTabItem__close>i::before {
|
||||
.TabBar>li.serverTabItem>a>div.TabBar-tabSeperator>.serverTabItem__close>i::before {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.TabBar.darkMode>li.teamTabItem>a>div.TabBar-tabSeperator>.teamTabItem__close {
|
||||
.TabBar.darkMode>li.serverTabItem>a>div.TabBar-tabSeperator>.serverTabItem__close {
|
||||
color: rgba(221,221,221,0.32);
|
||||
}
|
||||
|
||||
@@ -99,39 +99,39 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem.active>a, .TabBar>li.teamTabItem.dragging>a {
|
||||
.TabBar>li.serverTabItem.active>a, .TabBar>li.serverTabItem.dragging>a {
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
color: #3d3c40;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.TabBar.darkMode>li.teamTabItem.active>a, .TabBar.darkMode>li.teamTabItem.dragging>a {
|
||||
.TabBar.darkMode>li.serverTabItem.active>a, .TabBar.darkMode>li.serverTabItem.dragging>a {
|
||||
background-color: #1f1f1f;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem:not(.active)+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||
.TabBar>li.serverTabItem:not(.active)+li.serverTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||
border-left: 1px solid rgba(61,60,64,0.08);
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.TabBar.darkMode>li.teamTabItem:not(.active)+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||
.TabBar.darkMode>li.serverTabItem:not(.active)+li.serverTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||
border-left: 1px solid rgba(221,221,221,0.08);
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem:not(.active):not(.disabled):hover+.TabBar-addServerButton>a>div.TabBar-tabSeperator {
|
||||
.TabBar>li.serverTabItem:not(.active):not(.disabled):hover+.TabBar-addServerButton>a>div.TabBar-tabSeperator {
|
||||
border-left: none;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem:not(.active):not(.disabled):hover+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||
.TabBar>li.serverTabItem:not(.active):not(.disabled):hover+li.serverTabItem:not(.active)>a>div.TabBar-tabSeperator {
|
||||
border-left: none;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.TabBar>li.teamTabItem:not(.active):not(.disabled)+li.teamTabItem:not(.active)>a:hover>div.TabBar-tabSeperator {
|
||||
.TabBar>li.serverTabItem:not(.active):not(.disabled)+li.serverTabItem:not(.active)>a:hover>div.TabBar-tabSeperator {
|
||||
border-left: none;
|
||||
margin-left: 0px;
|
||||
}
|
||||
@@ -194,6 +194,6 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.TabBar .teamTabItem-unread {
|
||||
.TabBar .serverTabItem-unread {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
@import url("HoveringURL.css");
|
||||
@import url("MainPage.css");
|
||||
@import url("MattermostView.css");
|
||||
@import url("NewTeamModal.css");
|
||||
@import url("NewServerModal.css");
|
||||
@import url("PermissionRequestDialog.css");
|
||||
@import url("TabBar.css");
|
||||
@import url("UpdaterPage.css");
|
||||
|
@@ -14,7 +14,7 @@ body {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.TeamDropdown {
|
||||
.ServerDropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -26,7 +26,7 @@ body {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.TeamDropdown__droppable {
|
||||
.ServerDropdown__droppable {
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
@@ -40,21 +40,21 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__header {
|
||||
.ServerDropdown__header {
|
||||
padding: 6px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: -webkit-fill-available;
|
||||
user-select: none;
|
||||
|
||||
.TeamDropdown__servers {
|
||||
.ServerDropdown__servers {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #3D3C40;
|
||||
}
|
||||
|
||||
.TeamDropdown__keyboardShortcut {
|
||||
.ServerDropdown__keyboardShortcut {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
@@ -63,14 +63,14 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__divider {
|
||||
.ServerDropdown__divider {
|
||||
border-top: 1px solid rgba(61, 60, 64, 0.08);
|
||||
border-bottom: 0;
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.TeamDropdown__button {
|
||||
.ServerDropdown__button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 8px 18px 8px 7px;
|
||||
@@ -83,12 +83,12 @@ body {
|
||||
&:not(.anyDragging):hover {
|
||||
background-color: rgba(61, 60, 64, 0.08);
|
||||
|
||||
.TeamDropdown__button-edit, .TeamDropdown__button-remove {
|
||||
.ServerDropdown__button-edit, .ServerDropdown__button-remove {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.TeamDropdown__draggable-handle > i.icon-drag-vertical {
|
||||
.ServerDropdown__draggable-handle > i.icon-drag-vertical {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -97,17 +97,17 @@ body {
|
||||
background-color: rgba(22, 109, 224, 0.08);
|
||||
outline: none;
|
||||
|
||||
.TeamDropdown__button-edit, .TeamDropdown__button-remove {
|
||||
.ServerDropdown__button-edit, .ServerDropdown__button-remove {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.TeamDropdown__draggable-handle > i.icon-drag-vertical {
|
||||
.ServerDropdown__draggable-handle > i.icon-drag-vertical {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.dragging .TeamDropdown__draggable-handle {
|
||||
&.dragging .ServerDropdown__draggable-handle {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ body {
|
||||
color: #166de0;
|
||||
}
|
||||
|
||||
> .TeamDropdown__draggable-handle > span, &.addServer > span {
|
||||
> .ServerDropdown__draggable-handle > span, &.addServer > span {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #3D3C40;
|
||||
@@ -147,7 +147,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__draggable-handle {
|
||||
.ServerDropdown__draggable-handle {
|
||||
cursor: pointer !important;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
@@ -174,7 +174,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__badge {
|
||||
.ServerDropdown__badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -182,7 +182,7 @@ body {
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.TeamDropdown__badge-dot {
|
||||
.ServerDropdown__badge-dot {
|
||||
background: #579EFF;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
@@ -190,7 +190,7 @@ body {
|
||||
flex: 0 0 8px;
|
||||
}
|
||||
|
||||
.TeamDropdown__badge-count {
|
||||
.ServerDropdown__badge-count {
|
||||
background: #F74343;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
@@ -211,7 +211,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__indicators {
|
||||
.ServerDropdown__indicators {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
|
||||
@@ -229,7 +229,7 @@ body {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.TeamDropdown__button-remove:hover, &.TeamDropdown__button-remove:focus {
|
||||
&.ServerDropdown__button-remove:hover, &.ServerDropdown__button-remove:focus {
|
||||
background: rgba(247, 67, 67, 0.16);
|
||||
box-shadow: 0px 0px 0px 4px rgba(247, 67, 67, 0.16);
|
||||
}
|
||||
@@ -240,7 +240,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__button-edit, .TeamDropdown__button-remove {
|
||||
.ServerDropdown__button-edit, .ServerDropdown__button-remove {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -250,10 +250,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__button-remove {
|
||||
.ServerDropdown__button-remove {
|
||||
margin-right: 7px;
|
||||
|
||||
+ .TeamDropdown__badge {
|
||||
+ .ServerDropdown__badge {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
@@ -262,22 +262,22 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown.darkMode {
|
||||
.ServerDropdown.darkMode {
|
||||
background: #1f1f1f;
|
||||
|
||||
.TeamDropdown__header > span {
|
||||
.ServerDropdown__header > span {
|
||||
color: #DDD;
|
||||
|
||||
&.TeamDropdown__keyboardShortcut {
|
||||
&.ServerDropdown__keyboardShortcut {
|
||||
color: rgba(221, 221, 221, 0.56);
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__divider {
|
||||
.ServerDropdown__divider {
|
||||
border-color: rgba(221, 221, 221, 0.08);
|
||||
}
|
||||
|
||||
.TeamDropdown__button {
|
||||
.ServerDropdown__button {
|
||||
&:not(.anyDragging):hover {
|
||||
background-color: rgba(221, 221, 221, 0.08);
|
||||
}
|
||||
@@ -290,37 +290,37 @@ body {
|
||||
color: rgba(221, 221, 221, 0.56);
|
||||
}
|
||||
|
||||
> .TeamDropdown__draggable-handle > span, &.addServer > span {
|
||||
> .ServerDropdown__draggable-handle > span, &.addServer > span {
|
||||
color: #DDD;
|
||||
}
|
||||
|
||||
.TeamDropdown__button-remove i {
|
||||
.ServerDropdown__button-remove i {
|
||||
color: #F74343;
|
||||
}
|
||||
|
||||
.TeamDropdown__button-edit:hover, .TeamDropdown__button-edit:focus {
|
||||
.ServerDropdown__button-edit:hover, .ServerDropdown__button-edit:focus {
|
||||
background: rgba(221, 223, 228, 0.08);
|
||||
box-shadow: 0px 0px 0px 4px rgba(221, 223, 228, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.TeamDropdown__badge-expired i {
|
||||
.ServerDropdown__badge-expired i {
|
||||
color: rgba(221, 221, 221, 0.56);
|
||||
}
|
||||
|
||||
.TeamDropdown__button-edit > i {
|
||||
.ServerDropdown__button-edit > i {
|
||||
color: rgba(221, 221, 221, 0.56);
|
||||
}
|
||||
|
||||
.TeamDropdown__draggable-handle > i.icon-drag-vertical::before {
|
||||
.ServerDropdown__draggable-handle > i.icon-drag-vertical::before {
|
||||
color:rgba(221, 221, 221, 0.32);
|
||||
}
|
||||
|
||||
.TeamDropdown__badge-count i {
|
||||
.ServerDropdown__badge-count i {
|
||||
color: #e81023;
|
||||
}
|
||||
|
||||
.TeamDropdown__badge-dot {
|
||||
.ServerDropdown__badge-dot {
|
||||
background: #196CAF;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
@import '~bootstrap-dark/src/bootstrap-dark.css';
|
||||
|
||||
.TeamListItem:hover {
|
||||
.ServerListItem:hover {
|
||||
background: #242a30;
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import {DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDraggingStyle} from 'react-beautiful-dnd';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH_MAC} from 'common/utils/constants';
|
||||
|
||||
@@ -16,16 +16,16 @@ import './css/dropdown.scss';
|
||||
import IntlProvider from './intl_provider';
|
||||
|
||||
type State = {
|
||||
teams?: MattermostTeam[];
|
||||
teamOrder?: string[];
|
||||
orderedTeams?: MattermostTeam[];
|
||||
activeTeam?: string;
|
||||
servers?: UniqueServer[];
|
||||
serverOrder?: string[];
|
||||
orderedServers?: UniqueServer[];
|
||||
activeServer?: string;
|
||||
darkMode?: boolean;
|
||||
enableServerManagement?: boolean;
|
||||
unreads?: Map<string, boolean>;
|
||||
mentions?: Map<string, number>;
|
||||
expired?: Map<string, boolean>;
|
||||
hasGPOTeams?: boolean;
|
||||
hasGPOServers?: boolean;
|
||||
isAnyDragging: boolean;
|
||||
windowBounds?: Electron.Rectangle;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ function getStyle(style?: DraggingStyle | NotDraggingStyle) {
|
||||
}
|
||||
return style;
|
||||
}
|
||||
class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
class ServerDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
buttonRefs: Map<number, HTMLButtonElement>;
|
||||
addServerRef: React.RefObject<HTMLButtonElement>;
|
||||
focusedIndex: number | null;
|
||||
@@ -59,22 +59,22 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
}
|
||||
|
||||
handleUpdate = (
|
||||
teams: MattermostTeam[],
|
||||
servers: UniqueServer[],
|
||||
darkMode: boolean,
|
||||
windowBounds: Electron.Rectangle,
|
||||
activeTeam?: string,
|
||||
activeServer?: string,
|
||||
enableServerManagement?: boolean,
|
||||
hasGPOTeams?: boolean,
|
||||
hasGPOServers?: boolean,
|
||||
expired?: Map<string, boolean>,
|
||||
mentions?: Map<string, number>,
|
||||
unreads?: Map<string, boolean>,
|
||||
) => {
|
||||
this.setState({
|
||||
teams,
|
||||
activeTeam,
|
||||
servers,
|
||||
activeServer,
|
||||
darkMode,
|
||||
enableServerManagement,
|
||||
hasGPOTeams,
|
||||
hasGPOServers,
|
||||
unreads,
|
||||
mentions,
|
||||
expired,
|
||||
@@ -82,12 +82,12 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
});
|
||||
}
|
||||
|
||||
selectServer = (team: MattermostTeam) => {
|
||||
selectServer = (server: UniqueServer) => {
|
||||
return () => {
|
||||
if (!team.id) {
|
||||
if (!server.id) {
|
||||
return;
|
||||
}
|
||||
window.desktop.serverDropdown.switchServer(team.id);
|
||||
window.desktop.serverDropdown.switchServer(server.id);
|
||||
this.closeMenu();
|
||||
};
|
||||
}
|
||||
@@ -95,7 +95,7 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
closeMenu = () => {
|
||||
if (!this.state.isAnyDragging) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
window.desktop.closeTeamsDropdown();
|
||||
window.desktop.closeServersDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,8 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
isActiveTeam = (team: MattermostTeam) => {
|
||||
return team.id === this.state.activeTeam;
|
||||
isActiveServer = (server: UniqueServer) => {
|
||||
return server.id === this.state.activeServer;
|
||||
}
|
||||
|
||||
onDragStart = () => {
|
||||
@@ -123,17 +123,17 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
this.setState({isAnyDragging: false});
|
||||
return;
|
||||
}
|
||||
if (!this.state.teams) {
|
||||
if (!this.state.servers) {
|
||||
throw new Error('No config');
|
||||
}
|
||||
const teamsCopy = this.state.teams.concat();
|
||||
const serversCopy = this.state.servers.concat();
|
||||
|
||||
const team = teamsCopy.splice(removedIndex, 1);
|
||||
const newOrder = addedIndex < this.state.teams.length ? addedIndex : this.state.teams.length - 1;
|
||||
teamsCopy.splice(newOrder, 0, team[0]);
|
||||
const server = serversCopy.splice(removedIndex, 1);
|
||||
const newOrder = addedIndex < this.state.servers.length ? addedIndex : this.state.servers.length - 1;
|
||||
serversCopy.splice(newOrder, 0, server[0]);
|
||||
|
||||
this.setState({teams: teamsCopy, isAnyDragging: false});
|
||||
window.desktop.updateServerOrder(teamsCopy.map((team) => team.id!));
|
||||
this.setState({servers: serversCopy, isAnyDragging: false});
|
||||
window.desktop.updateServerOrder(serversCopy.map((server) => server.id!));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -151,18 +151,18 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
window.removeEventListener('keydown', this.handleKeyboardShortcuts);
|
||||
}
|
||||
|
||||
setButtonRef = (teamIndex: number, refMethod?: (element: HTMLButtonElement) => unknown) => {
|
||||
setButtonRef = (serverIndex: number, refMethod?: (element: HTMLButtonElement) => unknown) => {
|
||||
return (ref: HTMLButtonElement) => {
|
||||
this.addButtonRef(teamIndex, ref);
|
||||
this.addButtonRef(serverIndex, ref);
|
||||
refMethod?.(ref);
|
||||
};
|
||||
}
|
||||
|
||||
addButtonRef = (teamIndex: number, ref: HTMLButtonElement | null) => {
|
||||
addButtonRef = (serverIndex: number, ref: HTMLButtonElement | null) => {
|
||||
if (ref) {
|
||||
this.buttonRefs.set(teamIndex, ref);
|
||||
this.buttonRefs.set(serverIndex, ref);
|
||||
ref.addEventListener('focusin', () => {
|
||||
this.focusedIndex = teamIndex;
|
||||
this.focusedIndex = serverIndex;
|
||||
});
|
||||
ref.addEventListener('blur', () => {
|
||||
this.focusedIndex = null;
|
||||
@@ -203,30 +203,30 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
}
|
||||
}
|
||||
|
||||
editServer = (teamId: string) => {
|
||||
if (this.teamIsPredefined(teamId)) {
|
||||
editServer = (serverId: string) => {
|
||||
if (this.serverIsPredefined(serverId)) {
|
||||
return () => {};
|
||||
}
|
||||
return (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation();
|
||||
window.desktop.serverDropdown.showEditServerModal(teamId);
|
||||
window.desktop.serverDropdown.showEditServerModal(serverId);
|
||||
this.closeMenu();
|
||||
};
|
||||
}
|
||||
|
||||
removeServer = (teamId: string) => {
|
||||
if (this.teamIsPredefined(teamId)) {
|
||||
removeServer = (serverId: string) => {
|
||||
if (this.serverIsPredefined(serverId)) {
|
||||
return () => {};
|
||||
}
|
||||
return (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation();
|
||||
window.desktop.serverDropdown.showRemoveServerModal(teamId);
|
||||
window.desktop.serverDropdown.showRemoveServerModal(serverId);
|
||||
this.closeMenu();
|
||||
};
|
||||
}
|
||||
|
||||
teamIsPredefined = (teamId: string) => {
|
||||
return this.state.teams?.some((team) => team.id === teamId && team.isPredefined);
|
||||
serverIsPredefined = (serverId: string) => {
|
||||
return this.state.servers?.some((server) => server.id === serverId && server.isPredefined);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -234,7 +234,7 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
<IntlProvider>
|
||||
<div
|
||||
onClick={this.preventPropagation}
|
||||
className={classNames('TeamDropdown', {
|
||||
className={classNames('ServerDropdown', {
|
||||
darkMode: this.state.darkMode,
|
||||
})}
|
||||
style={{
|
||||
@@ -242,101 +242,101 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
maxWidth: this.state.windowBounds ? (this.state.windowBounds.width - THREE_DOT_MENU_WIDTH_MAC) : undefined,
|
||||
}}
|
||||
>
|
||||
<div className='TeamDropdown__header'>
|
||||
<span className='TeamDropdown__servers'>
|
||||
<div className='ServerDropdown__header'>
|
||||
<span className='ServerDropdown__servers'>
|
||||
<FormattedMessage
|
||||
id='renderer.dropdown.servers'
|
||||
defaultMessage='Servers'
|
||||
/>
|
||||
</span>
|
||||
<span className='TeamDropdown__keyboardShortcut'>
|
||||
<span className='ServerDropdown__keyboardShortcut'>
|
||||
{window.process.platform === 'darwin' ? '⌃⌘S' : 'Ctrl + Shift + S'}
|
||||
</span>
|
||||
</div>
|
||||
<hr className='TeamDropdown__divider'/>
|
||||
<hr className='ServerDropdown__divider'/>
|
||||
<DragDropContext
|
||||
onDragStart={this.onDragStart}
|
||||
onDragEnd={this.onDragEnd}
|
||||
>
|
||||
<Droppable
|
||||
isDropDisabled={this.state.hasGPOTeams}
|
||||
droppableId='TeamDropdown__droppable'
|
||||
isDropDisabled={this.state.hasGPOServers}
|
||||
droppableId='ServerDropdown__droppable'
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
className='TeamDropdown__droppable'
|
||||
className='ServerDropdown__droppable'
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{this.state.teams?.map((team, orderedIndex) => {
|
||||
const index = this.state.teams?.indexOf(team);
|
||||
const sessionExpired = this.state.expired?.get(team.id!);
|
||||
const hasUnreads = this.state.unreads?.get(team.id!);
|
||||
const mentionCount = this.state.mentions?.get(team.id!);
|
||||
{this.state.servers?.map((server, orderedIndex) => {
|
||||
const index = this.state.servers?.indexOf(server);
|
||||
const sessionExpired = this.state.expired?.get(server.id!);
|
||||
const hasUnreads = this.state.unreads?.get(server.id!);
|
||||
const mentionCount = this.state.mentions?.get(server.id!);
|
||||
|
||||
let badgeDiv: React.ReactNode;
|
||||
if (sessionExpired) {
|
||||
badgeDiv = (
|
||||
<div className='TeamDropdown__badge-expired'>
|
||||
<div className='ServerDropdown__badge-expired'>
|
||||
<i className='icon-alert-circle-outline'/>
|
||||
</div>
|
||||
);
|
||||
} else if (mentionCount && mentionCount > 0) {
|
||||
badgeDiv = (
|
||||
<div className='TeamDropdown__badge-count'>
|
||||
<div className='ServerDropdown__badge-count'>
|
||||
<span>{mentionCount > 99 ? '99+' : mentionCount}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (hasUnreads) {
|
||||
badgeDiv = (
|
||||
<div className='TeamDropdown__badge-dot'/>
|
||||
<div className='ServerDropdown__badge-dot'/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={index}
|
||||
draggableId={`TeamDropdown__draggable-${index}`}
|
||||
draggableId={`ServerDropdown__draggable-${index}`}
|
||||
index={orderedIndex}
|
||||
disableInteractiveElementBlocking={true}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<button
|
||||
className={classNames('TeamDropdown__button', {
|
||||
className={classNames('ServerDropdown__button', {
|
||||
dragging: snapshot.isDragging,
|
||||
anyDragging: this.state.isAnyDragging,
|
||||
active: this.isActiveTeam(team),
|
||||
active: this.isActiveServer(server),
|
||||
})}
|
||||
ref={this.setButtonRef(orderedIndex, provided.innerRef)}
|
||||
{...provided.draggableProps}
|
||||
onClick={this.selectServer(team)}
|
||||
onClick={this.selectServer(server)}
|
||||
style={getStyle(provided.draggableProps.style)}
|
||||
>
|
||||
<div
|
||||
className={classNames('TeamDropdown__draggable-handle', {
|
||||
className={classNames('ServerDropdown__draggable-handle', {
|
||||
dragging: snapshot.isDragging,
|
||||
})}
|
||||
{...provided.dragHandleProps}
|
||||
onClick={this.handleClickOnDragHandle}
|
||||
>
|
||||
<i className='icon-drag-vertical'/>
|
||||
{this.isActiveTeam(team) ? <i className='icon-check'/> : <i className='icon-server-variant'/>}
|
||||
<span>{team.name}</span>
|
||||
{this.isActiveServer(server) ? <i className='icon-check'/> : <i className='icon-server-variant'/>}
|
||||
<span>{server.name}</span>
|
||||
</div>
|
||||
{!team.isPredefined && <div className='TeamDropdown__indicators'>
|
||||
{!server.isPredefined && <div className='ServerDropdown__indicators'>
|
||||
<button
|
||||
className='TeamDropdown__button-edit'
|
||||
onClick={this.editServer(team.id!)}
|
||||
className='ServerDropdown__button-edit'
|
||||
onClick={this.editServer(server.id!)}
|
||||
>
|
||||
<i className='icon-pencil-outline'/>
|
||||
</button>
|
||||
<button
|
||||
className='TeamDropdown__button-remove'
|
||||
onClick={this.removeServer(team.id!)}
|
||||
className='ServerDropdown__button-remove'
|
||||
onClick={this.removeServer(server.id!)}
|
||||
>
|
||||
<i className='icon-trash-can-outline'/>
|
||||
</button>
|
||||
{badgeDiv && <div className='TeamDropdown__badge'>
|
||||
{badgeDiv && <div className='ServerDropdown__badge'>
|
||||
{badgeDiv}
|
||||
</div>}
|
||||
</div>}
|
||||
@@ -350,13 +350,13 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
<hr className='TeamDropdown__divider'/>
|
||||
<hr className='ServerDropdown__divider'/>
|
||||
{this.state.enableServerManagement &&
|
||||
<button
|
||||
ref={(ref) => {
|
||||
this.addButtonRef(this.state.teams?.length || 0, ref);
|
||||
this.addButtonRef(this.state.servers?.length || 0, ref);
|
||||
}}
|
||||
className='TeamDropdown__button addServer'
|
||||
className='ServerDropdown__button addServer'
|
||||
onClick={this.addServer}
|
||||
>
|
||||
<i className='icon-plus'/>
|
||||
@@ -373,6 +373,6 @@ class TeamDropdown extends React.PureComponent<Record<string, never>, State> {
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<TeamDropdown/>,
|
||||
<ServerDropdown/>,
|
||||
document.getElementById('app'),
|
||||
);
|
||||
|
@@ -7,49 +7,49 @@ import 'renderer/css/modals.css';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import IntlProvider from 'renderer/intl_provider';
|
||||
|
||||
import NewTeamModal from '../../components/NewTeamModal'; //'./addServer.jsx';
|
||||
import NewServerModal from '../../components/NewServerModal';
|
||||
|
||||
import setupDarkMode from '../darkMode';
|
||||
|
||||
setupDarkMode();
|
||||
|
||||
type ModalInfo = {
|
||||
team: MattermostTeam;
|
||||
currentTeams: MattermostTeam[];
|
||||
server: UniqueServer;
|
||||
currentServers: UniqueServer[];
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
window.desktop.modals.cancelModal();
|
||||
};
|
||||
|
||||
const onSave = (data: MattermostTeam) => {
|
||||
const onSave = (data: UniqueServer) => {
|
||||
window.desktop.modals.finishModal(data);
|
||||
};
|
||||
|
||||
const EditServerModalWrapper: React.FC = () => {
|
||||
const [server, setServer] = useState<MattermostTeam>();
|
||||
const [currentTeams, setCurrentTeams] = useState<MattermostTeam[]>();
|
||||
const [server, setServer] = useState<UniqueServer>();
|
||||
const [currentServers, setCurrentServers] = useState<UniqueServer[]>();
|
||||
|
||||
useEffect(() => {
|
||||
window.desktop.modals.getModalInfo<ModalInfo>().then(({team, currentTeams}) => {
|
||||
setServer(team);
|
||||
setCurrentTeams(currentTeams);
|
||||
window.desktop.modals.getModalInfo<ModalInfo>().then(({server, currentServers}) => {
|
||||
setServer(server);
|
||||
setCurrentServers(currentServers);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IntlProvider>
|
||||
<NewTeamModal
|
||||
<NewServerModal
|
||||
onClose={onClose}
|
||||
onSave={onSave}
|
||||
editMode={true}
|
||||
show={Boolean(server)}
|
||||
team={server}
|
||||
currentTeams={currentTeams}
|
||||
server={server}
|
||||
currentServers={currentServers}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
@@ -35,7 +35,7 @@ class LoadingScreenRoot extends React.PureComponent<Props, State> {
|
||||
window.desktop.loadingScreen.onToggleLoadingScreenVisibility(this.onToggleLoadingScreenVisibility);
|
||||
|
||||
window.addEventListener('click', () => {
|
||||
window.desktop.closeTeamsDropdown();
|
||||
window.desktop.closeServersDropdown();
|
||||
window.desktop.closeDownloadsDropdown();
|
||||
});
|
||||
}
|
||||
|
@@ -7,11 +7,11 @@ import 'renderer/css/modals.css';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import IntlProvider from 'renderer/intl_provider';
|
||||
|
||||
import NewTeamModal from '../../components/NewTeamModal'; //'./addServer.jsx';
|
||||
import NewServerModal from '../../components/NewServerModal';
|
||||
|
||||
import setupDarkMode from '../darkMode';
|
||||
|
||||
@@ -21,31 +21,31 @@ const onClose = () => {
|
||||
window.desktop.modals.cancelModal();
|
||||
};
|
||||
|
||||
const onSave = (data: MattermostTeam) => {
|
||||
const onSave = (data: UniqueServer) => {
|
||||
window.desktop.modals.finishModal(data);
|
||||
};
|
||||
|
||||
const NewServerModalWrapper: React.FC = () => {
|
||||
const [unremoveable, setUnremovable] = useState<boolean>();
|
||||
const [currentTeams, setCurrentTeams] = useState<MattermostTeam[]>();
|
||||
const [currentServers, setCurrentServers] = useState<UniqueServer[]>();
|
||||
|
||||
useEffect(() => {
|
||||
window.desktop.modals.isModalUncloseable().then((uncloseable) => {
|
||||
setUnremovable(uncloseable);
|
||||
});
|
||||
window.desktop.modals.getModalInfo<MattermostTeam[]>().then((teams) => {
|
||||
setCurrentTeams(teams);
|
||||
window.desktop.modals.getModalInfo<UniqueServer[]>().then((servers) => {
|
||||
setCurrentServers(servers);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IntlProvider>
|
||||
<NewTeamModal
|
||||
<NewServerModal
|
||||
onClose={unremoveable ? undefined : onClose}
|
||||
onSave={onSave}
|
||||
editMode={false}
|
||||
show={true}
|
||||
currentTeams={currentTeams}
|
||||
currentServers={currentServers}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import {MattermostTeam} from 'types/config';
|
||||
import {UniqueServer} from 'types/config';
|
||||
|
||||
import IntlProvider from 'renderer/intl_provider';
|
||||
|
||||
@@ -15,7 +15,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
const MOBILE_SCREEN_WIDTH = 1200;
|
||||
|
||||
const onConnect = (data: MattermostTeam) => {
|
||||
const onConnect = (data: UniqueServer) => {
|
||||
window.desktop.modals.finishModal(data);
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ const WelcomeScreenModalWrapper = () => {
|
||||
const [darkMode, setDarkMode] = useState(false);
|
||||
const [getStarted, setGetStarted] = useState(false);
|
||||
const [mobileView, setMobileView] = useState(false);
|
||||
const [currentTeams, setCurrentTeams] = useState<MattermostTeam[]>([]);
|
||||
const [currentServers, setCurrentServers] = useState<UniqueServer[]>([]);
|
||||
|
||||
const handleWindowResize = () => {
|
||||
setMobileView(window.innerWidth < MOBILE_SCREEN_WIDTH);
|
||||
@@ -38,8 +38,8 @@ const WelcomeScreenModalWrapper = () => {
|
||||
setDarkMode(result);
|
||||
});
|
||||
|
||||
window.desktop.modals.getModalInfo<MattermostTeam[]>().then((result) => {
|
||||
setCurrentTeams(result);
|
||||
window.desktop.modals.getModalInfo<UniqueServer[]>().then((result) => {
|
||||
setCurrentServers(result);
|
||||
});
|
||||
|
||||
handleWindowResize();
|
||||
@@ -60,7 +60,7 @@ const WelcomeScreenModalWrapper = () => {
|
||||
<ConfigureServer
|
||||
mobileView={mobileView}
|
||||
darkMode={darkMode}
|
||||
currentTeams={currentTeams}
|
||||
currentServers={currentServers}
|
||||
onConnect={onConnect}
|
||||
/>
|
||||
) : (
|
||||
|
@@ -1,32 +1,32 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export type Tab = {
|
||||
export type View = {
|
||||
name: string;
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
export type Team = {
|
||||
export type Server = {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type ConfigTab = Tab & {
|
||||
export type ConfigView = View & {
|
||||
order: number;
|
||||
}
|
||||
|
||||
export type ConfigServer = Team & {
|
||||
export type ConfigServer = Server & {
|
||||
order: number;
|
||||
lastActiveTab?: number;
|
||||
tabs: ConfigTab[];
|
||||
tabs: ConfigView[];
|
||||
}
|
||||
|
||||
export type MattermostTeam = Team & {
|
||||
export type UniqueServer = Server & {
|
||||
id?: string;
|
||||
isPredefined?: boolean;
|
||||
}
|
||||
|
||||
export type MattermostTab = Tab & {
|
||||
export type UniqueView = View & {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
@@ -61,59 +61,49 @@ export type ConfigV3 = {
|
||||
appLanguage?: string;
|
||||
}
|
||||
|
||||
export type ConfigV2 = {
|
||||
version: 2;
|
||||
teams: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
order: number;
|
||||
}>;
|
||||
showTrayIcon: boolean;
|
||||
trayIconTheme: string;
|
||||
minimizeToTray: boolean;
|
||||
notifications: {
|
||||
flashWindow: number;
|
||||
bounceIcon: boolean;
|
||||
bounceIconType: 'critical' | 'informational';
|
||||
};
|
||||
showUnreadBadge: boolean;
|
||||
useSpellChecker: boolean;
|
||||
enableHardwareAcceleration: boolean;
|
||||
autostart: boolean;
|
||||
spellCheckerLocale: string;
|
||||
spellCheckerURL?: string;
|
||||
darkMode: boolean;
|
||||
downloadLocation?: string;
|
||||
}
|
||||
export type ConfigV2 =
|
||||
Omit<ConfigV3,
|
||||
'version' |
|
||||
'teams' |
|
||||
'hideOnStart' |
|
||||
'spellCheckerLocales' |
|
||||
'lastActiveTeam' |
|
||||
'startInFullscreen' |
|
||||
'autoCheckForUpdates' |
|
||||
'alwaysMinimize' |
|
||||
'alwaysClose' |
|
||||
'logLevel' |
|
||||
'appLanguage'
|
||||
> & {
|
||||
version: 2;
|
||||
teams: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
order: number;
|
||||
}>;
|
||||
spellCheckerLocale: string;
|
||||
}
|
||||
|
||||
export type ConfigV1 = {
|
||||
version: 1;
|
||||
teams: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
}>;
|
||||
showTrayIcon: boolean;
|
||||
trayIconTheme: string;
|
||||
minimizeToTray: boolean;
|
||||
notifications: {
|
||||
flashWindow: number;
|
||||
bounceIcon: boolean;
|
||||
bounceIconType: 'critical' | 'informational';
|
||||
};
|
||||
showUnreadBadge: boolean;
|
||||
useSpellChecker: boolean;
|
||||
spellCheckerURL?: string;
|
||||
enableHardwareAcceleration: boolean;
|
||||
autostart: boolean;
|
||||
spellCheckerLocale: string;
|
||||
}
|
||||
export type ConfigV1 =
|
||||
Omit<ConfigV2,
|
||||
'version' |
|
||||
'teams' |
|
||||
'darkMode' |
|
||||
'downloadLocation'
|
||||
> & {
|
||||
version: 1;
|
||||
teams: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export type ConfigV0 = {version: 0; url: string};
|
||||
|
||||
export type AnyConfig = ConfigV3 | ConfigV2 | ConfigV1 | ConfigV0;
|
||||
|
||||
export type BuildConfig = {
|
||||
defaultTeams?: Team[];
|
||||
defaultServers?: Server[];
|
||||
helpLink: string;
|
||||
enableServerManagement: boolean;
|
||||
enableAutoUpdater: boolean;
|
||||
@@ -122,12 +112,12 @@ export type BuildConfig = {
|
||||
}
|
||||
|
||||
export type RegistryConfig = {
|
||||
teams: Team[];
|
||||
servers: Server[];
|
||||
enableServerManagement: boolean;
|
||||
enableAutoUpdater: boolean;
|
||||
}
|
||||
|
||||
export type CombinedConfig = Omit<ConfigV3, 'teams'> & Omit<BuildConfig, 'defaultTeams'> & {
|
||||
export type CombinedConfig = Omit<Config, 'teams'> & Omit<BuildConfig, 'defaultServers'> & {
|
||||
appName: string;
|
||||
useNativeWindow: boolean;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import {ipcRenderer, Rectangle} from 'electron/renderer';
|
||||
|
||||
import {Language} from '../../i18n/i18n';
|
||||
|
||||
import {CombinedConfig, LocalConfiguration, MattermostTab, MattermostTeam} from './config';
|
||||
import {CombinedConfig, LocalConfiguration, UniqueView, UniqueServer} from './config';
|
||||
import {DownloadedItem, DownloadedItems, DownloadsMenuOpenEventPayload} from './downloads';
|
||||
import {SaveQueueItem} from './settings';
|
||||
|
||||
@@ -32,10 +32,10 @@ declare global {
|
||||
desktop: {
|
||||
quit: (reason: string, stack: string) => void;
|
||||
openAppMenu: () => void;
|
||||
closeTeamsDropdown: () => void;
|
||||
openTeamsDropdown: () => void;
|
||||
switchTab: (tabId: string) => void;
|
||||
closeTab: (tabId: string) => void;
|
||||
closeServersDropdown: () => void;
|
||||
openServersDropdown: () => void;
|
||||
switchTab: (viewId: string) => void;
|
||||
closeView: (viewId: string) => void;
|
||||
closeWindow: () => void;
|
||||
minimizeWindow: () => void;
|
||||
maximizeWindow: () => void;
|
||||
@@ -51,10 +51,10 @@ declare global {
|
||||
updateConfiguration: (saveQueueItems: SaveQueueItem[]) => void;
|
||||
|
||||
updateServerOrder: (serverOrder: string[]) => Promise<void>;
|
||||
updateTabOrder: (serverId: string, tabOrder: string[]) => Promise<void>;
|
||||
getLastActive: () => Promise<{server: string; tab: string}>;
|
||||
getOrderedServers: () => Promise<MattermostTeam[]>;
|
||||
getOrderedTabsForServer: (serverId: string) => Promise<MattermostTab[]>;
|
||||
updateTabOrder: (serverId: string, viewOrder: string[]) => Promise<void>;
|
||||
getLastActive: () => Promise<{server: string; view: string}>;
|
||||
getOrderedServers: () => Promise<UniqueServer[]>;
|
||||
getOrderedTabsForServer: (serverId: string) => Promise<UniqueView[]>;
|
||||
onUpdateServers: (listener: () => void) => void;
|
||||
|
||||
getConfiguration: () => Promise<CombinedConfig[keyof CombinedConfig] | CombinedConfig>;
|
||||
@@ -74,7 +74,7 @@ declare global {
|
||||
onLoadRetry: (listener: (viewId: string, retry: Date, err: string, loadUrl: string) => void) => void;
|
||||
onLoadSuccess: (listener: (viewId: string) => void) => void;
|
||||
onLoadFailed: (listener: (viewId: string, err: string, loadUrl: string) => void) => void;
|
||||
onSetActiveView: (listener: (serverId: string, tabId: string) => void) => void;
|
||||
onSetActiveView: (listener: (serverId: string, viewId: string) => void) => void;
|
||||
onMaximizeChange: (listener: (maximize: boolean) => void) => void;
|
||||
onEnterFullScreen: (listener: () => void) => void;
|
||||
onLeaveFullScreen: (listener: () => void) => void;
|
||||
@@ -83,8 +83,8 @@ declare global {
|
||||
onModalClose: (listener: () => void) => void;
|
||||
onToggleBackButton: (listener: (showExtraBar: boolean) => void) => void;
|
||||
onUpdateMentions: (listener: (view: string, mentions: number, unreads: boolean, isExpired: boolean) => void) => void;
|
||||
onCloseTeamsDropdown: (listener: () => void) => void;
|
||||
onOpenTeamsDropdown: (listener: () => void) => void;
|
||||
onCloseServersDropdown: (listener: () => void) => void;
|
||||
onOpenServersDropdown: (listener: () => void) => void;
|
||||
onCloseDownloadsDropdown: (listener: () => void) => void;
|
||||
onOpenDownloadsDropdown: (listener: () => void) => void;
|
||||
onShowDownloadsDropdownButtonBadge: (listener: () => void) => void;
|
||||
@@ -139,12 +139,12 @@ declare global {
|
||||
showRemoveServerModal: (serverId: string) => void;
|
||||
|
||||
onUpdateServerDropdown: (listener: (
|
||||
teams: MattermostTeam[],
|
||||
servers: UniqueServer[],
|
||||
darkMode: boolean,
|
||||
windowBounds: Rectangle,
|
||||
activeTeam?: string,
|
||||
activeServer?: string,
|
||||
enableServerManagement?: boolean,
|
||||
hasGPOTeams?: boolean,
|
||||
hasGPOServers?: boolean,
|
||||
expired?: Map<string, boolean>,
|
||||
mentions?: Map<string, number>,
|
||||
unreads?: Map<string, boolean>,
|
||||
|
Reference in New Issue
Block a user