[MM-36432][MM-37072][MM-37073] Logic to support Focalboard and Playbooks tabs (#1680)

* Tab stuff

* Inter-tab navigation

* Close tab functionality

* [MM-36342][MM-37072] Logic to support Focalboard and Playbooks tabs

* Update to version 5.0

* Update config.yml

* Updated routes

* Update names for products

* [MM-37073] Close unneeded tabs when not using v6.0

* Merge'd

* Update config.yml

* Update config.yml

* Fix menu names

* PR feedback

* blank

* blank

* blank

* PR feedback

* Update config.yml

* PR feedback

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Devin Binnie
2021-09-01 12:45:37 -04:00
committed by GitHub
parent 58bb16fbb6
commit 37c637efe9
27 changed files with 570 additions and 232 deletions

View File

@@ -103,6 +103,7 @@ const configDataSchemaV3 = Joi.object<ConfigV3>({
tabs: Joi.array().items(Joi.object({
name: Joi.string().required(),
order: Joi.number().integer().min(0),
isClosed: Joi.boolean().default(false),
})).default([]),
})).default([]),
showTrayIcon: Joi.boolean().default(false),

View File

@@ -13,10 +13,9 @@ import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-install
import log from 'electron-log';
import 'airbnb-js-shims/target/es2015';
import {Team} from 'types/config';
import {Team, TeamWithTabs} from 'types/config';
import {MentionData} from 'types/notification';
import {RemoteInfo} from 'types/server';
import {Boundaries} from 'types/utils';
import {
@@ -37,14 +36,17 @@ import {
USER_ACTIVITY_UPDATE,
EMIT_CONFIGURATION,
SWITCH_TAB,
CLOSE_TAB,
OPEN_TAB,
SHOW_EDIT_SERVER_MODAL,
SHOW_REMOVE_SERVER_MODAL,
UPDATE_SHORTCUT_MENU,
OPEN_TEAMS_DROPDOWN,
} from 'common/communication';
import Config from 'common/config';
import {getDefaultTeamWithTabsFromTeam} from 'common/tabs/TabView';
import Utils from 'common/utils/util';
import {MattermostServer} from 'common/servers/MattermostServer';
import {getDefaultTeamWithTabsFromTeam, TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS} from 'common/tabs/TabView';
import Utils, {isServerVersionGreaterThanOrEqualTo} from 'common/utils/util';
import urlUtils from 'common/utils/url';
@@ -71,6 +73,7 @@ import {destroyTray, refreshTrayImages, setTrayMenu, setupTray} from './tray/tra
import {AuthManager} from './authManager';
import {CertificateManager} from './certificateManager';
import {setupBadge, setUnreadBadgeSetting} from './badge';
import {ServerInfo} from './server/serverInfo';
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept();
@@ -245,6 +248,8 @@ function initializeInterCommunicationEventListeners() {
ipcMain.on(SWITCH_SERVER, handleSwitchServer);
ipcMain.on(SWITCH_TAB, handleSwitchTab);
ipcMain.on(CLOSE_TAB, handleCloseTab);
ipcMain.on(OPEN_TAB, handleOpenTab);
ipcMain.on(QUIT, handleQuit);
@@ -493,6 +498,37 @@ function handleSwitchTab(event: IpcMainEvent, serverName: string, tabName: strin
WindowManager.switchTab(serverName, tabName);
}
function handleCloseTab(event: IpcMainEvent, serverName: string, tabName: string) {
const teams = config.teams;
teams.forEach((team) => {
if (team.name === serverName) {
team.tabs.forEach((tab) => {
if (tab.name === tabName) {
tab.isClosed = true;
}
});
}
});
const nextTab = teams.find((team) => team.name === serverName)!.tabs.filter((tab) => !tab.isClosed)[0].name;
WindowManager.switchTab(serverName, nextTab);
config.set('teams', teams);
}
function handleOpenTab(event: IpcMainEvent, serverName: string, tabName: string) {
const teams = config.teams;
teams.forEach((team) => {
if (team.name === serverName) {
team.tabs.forEach((tab) => {
if (tab.name === tabName) {
tab.isClosed = false;
}
});
}
});
WindowManager.switchTab(serverName, tabName);
config.set('teams', teams);
}
function handleNewServerModal() {
const html = getLocalURLString('newServer.html');
@@ -507,8 +543,10 @@ function handleNewServerModal() {
modalPromise.then((data) => {
const teams = config.teams;
const order = teams.length;
teams.push(getDefaultTeamWithTabsFromTeam({...data, order}));
const newTeam = getDefaultTeamWithTabsFromTeam({...data, order});
teams.push(newTeam);
config.set('teams', teams);
updateServerInfos([newTeam]);
}).catch((e) => {
// e is undefined for user cancellation
if (e) {
@@ -590,6 +628,7 @@ function handleRemoveServerModal(e: IpcMainEvent, name: string) {
}
function initializeAfterAppReady() {
updateServerInfos(config.teams);
app.setAppUserModelId('Mattermost.Desktop'); // Use explicit AppUserModelID
const defaultSession = session.defaultSession;
@@ -751,6 +790,41 @@ function handleMentionNotification(event: IpcMainEvent, title: string, body: str
displayMention(title, body, channel, teamId, url, silent, event.sender, data);
}
function updateServerInfos(teams: TeamWithTabs[]) {
const serverInfos: Array<Promise<RemoteInfo | string | undefined>> = [];
teams.forEach((team) => {
const serverInfo = new ServerInfo(new MattermostServer(team.name, team.url));
serverInfos.push(serverInfo.promise);
});
Promise.all(serverInfos).then((data: Array<RemoteInfo | string | undefined>) => {
const teams = config.teams;
teams.forEach((team) => closeUnneededTabs(data, team));
config.set('teams', teams);
}).catch((reason: any) => {
log.error('Error getting server infos', reason);
});
}
function closeUnneededTabs(data: Array<RemoteInfo | string | undefined>, team: TeamWithTabs) {
const remoteInfo = data.find((info) => info && typeof info !== 'string' && info.name === team.name) as RemoteInfo;
if (remoteInfo) {
team.tabs.forEach((tab) => {
if (tab.name === TAB_PLAYBOOKS && !remoteInfo.hasPlaybooks) {
log.info(`closing ${team.name}___${tab.name} on !hasPlaybooks`);
tab.isClosed = true;
}
if (tab.name === TAB_FOCALBOARD && !remoteInfo.hasFocalboard) {
log.info(`closing ${team.name}___${tab.name} on !hasFocalboard`);
tab.isClosed = true;
}
if (tab.name !== TAB_MESSAGING && remoteInfo.serverVersion && !isServerVersionGreaterThanOrEqualTo(remoteInfo.serverVersion, '6.0.0')) {
log.info(`closing ${team.name}___${tab.name} on !serverVersion`);
tab.isClosed = true;
}
});
}
}
function handleOpenAppMenu() {
const windowMenu = Menu.getApplicationMenu();
if (!windowMenu) {

View File

@@ -7,6 +7,7 @@ import {app, ipcMain, Menu, MenuItemConstructorOptions, MenuItem, session, shell
import {SHOW_NEW_SERVER_MODAL} from 'common/communication';
import Config from 'common/config';
import {TabType, getTabDisplayName} from 'common/tabs/TabView';
import * as WindowManager from '../windows/windowManager';
@@ -217,7 +218,7 @@ function createTemplate(config: Config) {
if (WindowManager.getCurrentTeamName() === team.name) {
team.tabs.slice(0, 9).sort((teamA, teamB) => teamA.order - teamB.order).forEach((tab, i) => {
items.push({
label: ` ${tab.name}`, // TODO
label: ` ${getTabDisplayName(tab.name as TabType)}`,
accelerator: `CmdOrCtrl+${i + 1}`,
click() {
WindowManager.switchTab(team.name, tab.name);

View File

@@ -12,7 +12,17 @@ import {ipcRenderer, webFrame} from 'electron';
// we'll be able to use it again if there is a workaround for the 'os' import
//import log from 'electron-log';
import {NOTIFY_MENTION, IS_UNREAD, UNREAD_RESULT, SESSION_EXPIRED, SET_VIEW_NAME, REACT_APP_INITIALIZED, USER_ACTIVITY_UPDATE, CLOSE_TEAMS_DROPDOWN} from 'common/communication';
import {
NOTIFY_MENTION,
IS_UNREAD,
UNREAD_RESULT,
SESSION_EXPIRED,
SET_VIEW_NAME,
REACT_APP_INITIALIZED,
USER_ACTIVITY_UPDATE,
CLOSE_TEAMS_DROPDOWN,
BROWSER_HISTORY_PUSH,
} from 'common/communication';
const UNREAD_COUNT_INTERVAL = 1000;
const CLEAR_CACHE_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours
@@ -114,6 +124,11 @@ window.addEventListener('message', ({origin, data = {}} = {}) => {
ipcRenderer.send(NOTIFY_MENTION, title, body, channel, teamId, url, silent, messageData);
break;
}
case 'browser-history-push': {
const {path} = message;
ipcRenderer.send(BROWSER_HISTORY_PUSH, viewName, path);
break;
}
default:
if (typeof type === 'undefined') {
console.log('ignoring message of undefined type:');
@@ -211,4 +226,16 @@ window.addEventListener('click', () => {
ipcRenderer.send(CLOSE_TEAMS_DROPDOWN);
});
ipcRenderer.on(BROWSER_HISTORY_PUSH, (event, pathName) => {
window.postMessage(
{
type: 'browser-history-push-return',
message: {
pathName,
},
},
window.location.origin,
);
});
/* eslint-enable no-magic-numbers */

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {net, session} from 'electron';
import log from 'electron-log';
export async function getServerAPI<T>(url: URL, isAuthenticated: boolean, onSuccess?: (data: T) => void, onAbort?: () => void, onError?: (error: Error) => void) {
if (isAuthenticated) {
const cookies = await session.defaultSession.cookies.get({});
if (!cookies) {
log.error('Cannot authenticate, no cookies present');
return;
}
// Filter out cookies that aren't part of our domain
const filteredCookies = cookies.filter((cookie) => cookie.domain && url.toString().indexOf(cookie.domain) >= 0);
const userId = filteredCookies.find((cookie) => cookie.name === 'MMUSERID');
const csrf = filteredCookies.find((cookie) => cookie.name === 'MMCSRF');
const authToken = filteredCookies.find((cookie) => cookie.name === 'MMAUTHTOKEN');
if (!userId || !csrf || !authToken) {
// Missing cookies needed for req
log.error(`Cannot authenticate, required cookies for ${url.origin} not found`);
return;
}
}
const req = net.request({
url: url.toString(),
session: session.defaultSession,
useSessionCookies: true,
});
if (onSuccess) {
req.on('response', (response: Electron.IncomingMessage) => {
if (response.statusCode === 200) {
response.on('data', (chunk: Buffer) => {
const raw = `${chunk}`;
const data = JSON.parse(raw) as T;
onSuccess(data);
});
} else {
onError?.(new Error(`Bad status code requesting from ${url.toString()}`));
}
});
}
if (onAbort) {
req.on('abort', onAbort);
}
if (onError) {
req.on('error', onError);
}
req.end();
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {RemoteInfo} from 'types/server';
import {MattermostServer} from 'common/servers/MattermostServer';
import {getServerAPI} from './serverAPI';
export class ServerInfo {
server: MattermostServer;
remoteInfo: RemoteInfo;
promise: Promise<RemoteInfo | string | undefined>;
onRetrievedRemoteInfo?: (result?: RemoteInfo | string) => void;
constructor(server: MattermostServer) {
this.server = server;
this.remoteInfo = {name: server.name};
this.promise = new Promise<RemoteInfo | string | undefined>((resolve) => {
this.onRetrievedRemoteInfo = resolve;
});
this.getRemoteInfo();
}
getRemoteInfo = () => {
getServerAPI<{Version: string}>(
new URL(`${this.server.url.toString()}/api/v4/config/client?format=old`),
false,
this.onGetConfig,
this.onRetrievedRemoteInfo,
this.onRetrievedRemoteInfo);
getServerAPI<Array<{id: string; version: string}>>(
new URL(`${this.server.url.toString()}/api/v4/plugins/webapp`),
false,
this.onGetPlugins,
this.onRetrievedRemoteInfo,
this.onRetrievedRemoteInfo);
}
onGetConfig = (data: {Version: string}) => {
this.remoteInfo.serverVersion = data.Version;
this.trySendRemoteInfo();
}
onGetPlugins = (data: Array<{id: string; version: string}>) => {
this.remoteInfo.hasFocalboard = data.some((plugin) => plugin.id === 'focalboard');
this.remoteInfo.hasPlaybooks = data.some((plugin) => plugin.id === 'com.mattermost.plugin-incident-management');
this.trySendRemoteInfo();
}
trySendRemoteInfo = () => {
if (this.isRemoteInfoRetrieved()) {
this.onRetrievedRemoteInfo?.(this.remoteInfo);
}
}
isRemoteInfoRetrieved = () => {
return !(
typeof this.remoteInfo.serverVersion === 'undefined' ||
typeof this.remoteInfo.hasFocalboard === 'undefined' ||
typeof this.remoteInfo.hasPlaybooks === 'undefined'
);
}
}

View File

@@ -57,7 +57,6 @@ export class MattermostView extends EventEmitter {
usesAsteriskForUnreads?: boolean;
currentFavicon?: string;
isInitialized: boolean;
hasBeenShown: boolean;
altLastPressed?: boolean;
contextMenu: ContextMenu;
@@ -90,7 +89,6 @@ export class MattermostView extends EventEmitter {
log.info(`BrowserView created for server ${this.tab.name}`);
this.isInitialized = false;
this.hasBeenShown = false;
if (process.platform !== 'darwin') {
@@ -98,6 +96,10 @@ export class MattermostView extends EventEmitter {
this.view.webContents.on('before-input-event', this.handleInputEvents);
}
this.view.webContents.on('did-finish-load', () => {
this.view.webContents.send(SET_VIEW_NAME, this.tab.name);
});
this.contextMenu = new ContextMenu({}, this.view);
this.maxRetries = MAX_SERVER_RETRIES;
}
@@ -179,7 +181,6 @@ export class MattermostView extends EventEmitter {
this.status = Status.WAITING_MM;
this.removeLoading = setTimeout(this.setInitialized, MAX_LOADING_SCREEN_SECONDS, true);
this.emit(LOAD_SUCCESS, this.tab.name, loadURL);
this.view.webContents.send(SET_VIEW_NAME, this.tab.name);
this.setBounds(getWindowBoundaries(this.window, !(urlUtils.isTeamUrl(this.tab.url || '', this.view.webContents.getURL()) || urlUtils.isAdminUrl(this.tab.url || '', this.view.webContents.getURL()))));
};
}
@@ -259,6 +260,10 @@ export class MattermostView extends EventEmitter {
delete this.removeLoading;
}
isInitialized = () => {
return this.status === Status.READY;
}
openDevTools = () => {
this.view.webContents.openDevTools({mode: 'detach'});
}

View File

@@ -15,6 +15,7 @@ import {
GET_LOADING_SCREEN_DATA,
LOADSCREEN_END,
SET_ACTIVE_VIEW,
OPEN_TAB,
} from 'common/communication';
import urlUtils from 'common/utils/url';
@@ -33,6 +34,7 @@ const URL_VIEW_HEIGHT = 36;
export class ViewManager {
configServers: TeamWithTabs[];
viewOptions: BrowserViewConstructorOptions;
closedViews: Map<string, {srv: MattermostServer; tab: Tab}>;
views: Map<string, MattermostView>;
currentView?: string;
urlView?: BrowserView;
@@ -45,6 +47,7 @@ export class ViewManager {
this.viewOptions = {webPreferences: {spellcheck: config.useSpellChecker}};
this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that.
this.mainWindow = mainWindow;
this.closedViews = new Map();
}
updateMainWindow = (mainWindow: BrowserWindow) => {
@@ -60,15 +63,19 @@ export class ViewManager {
server.tabs.forEach((tab) => this.loadView(srv, tab));
}
loadView = (srv: MattermostServer, tab: Tab) => {
loadView = (srv: MattermostServer, tab: Tab, url?: string) => {
const tabView = getServerView(srv, tab);
if (tab.isClosed) {
this.closedViews.set(tabView.name, {srv, tab});
return;
}
const view = new MattermostView(tabView, this.mainWindow, this.viewOptions);
this.views.set(tabView.name, view);
if (!this.loadingScreen) {
this.createLoadingScreen();
}
view.once(LOAD_SUCCESS, this.activateView);
view.load();
view.load(url);
view.on(UPDATE_TARGET_URL, this.showURLView);
view.on(LOADSCREEN_END, this.finishLoading);
view.once(LOAD_FAILED, this.failLoading);
@@ -92,7 +99,9 @@ export class ViewManager {
if (recycle && recycle.isVisible) {
setFocus = recycle.name;
}
if (recycle && recycle.tab.name === tabView.name && recycle.tab.url.toString() === urlUtils.parseURL(tabView.url)!.toString()) {
if (tab.isClosed) {
this.closedViews.set(tabView.name, {srv, tab});
} else if (recycle && recycle.tab.name === tabView.name && recycle.tab.url.toString() === urlUtils.parseURL(tabView.url)!.toString()) {
oldviews.delete(recycle.name);
this.views.set(recycle.name, recycle);
} else {
@@ -114,7 +123,8 @@ export class ViewManager {
if (this.configServers.length) {
const element = this.configServers.find((e) => e.order === 0);
if (element) {
const tab = element.tabs.find((e) => e.order === 0);
const openTabs = element.tabs.filter((tab) => !tab.isClosed);
const tab = openTabs.find((e) => e.order === 0) || openTabs[0];
if (tab) {
const tabView = getTabViewName(element.name, tab.name);
this.showByName(tabView);
@@ -193,6 +203,24 @@ export class ViewManager {
}
}
openClosedTab = (name: string, url?: string) => {
if (!this.closedViews.has(name)) {
return;
}
const {srv, tab} = this.closedViews.get(name)!;
tab.isClosed = false;
this.closedViews.delete(name);
this.loadView(srv, tab, url);
this.showByName(name);
const view = this.views.get(name)!;
view.isVisible = true;
view.on(LOAD_SUCCESS, () => {
view.isVisible = false;
this.showByName(name);
});
ipcMain.emit(OPEN_TAB, null, srv.name, tab.name);
}
failLoading = () => {
this.fadeLoadingScreen();
}
@@ -360,18 +388,22 @@ export class ViewManager {
const parsedURL = urlUtils.parseURL(url)!;
const tabView = urlUtils.getView(parsedURL, this.configServers, true);
if (tabView) {
const view = this.views.get(tabView.name);
if (!view) {
log.error(`Couldn't find a view matching the name ${tabView.name}`);
return;
}
const urlWithSchema = `${urlUtils.parseURL(tabView.url)?.origin}${parsedURL.pathname}${parsedURL.search}`;
if (this.closedViews.has(tabView.name)) {
this.openClosedTab(tabView.name, urlWithSchema);
} else {
const view = this.views.get(tabView.name);
if (!view) {
log.error(`Couldn't find a view matching the name ${tabView.name}`);
return;
}
// attempting to change parsedURL protocol results in it not being modified.
const urlWithSchema = `${view.tab.url.origin}${parsedURL.pathname}${parsedURL.search}`;
view.resetLoadingStatus();
view.load(urlWithSchema);
view.once(LOAD_SUCCESS, this.deeplinkSuccess);
view.once(LOAD_FAILED, this.deeplinkFailed);
// attempting to change parsedURL protocol results in it not being modified.
view.resetLoadingStatus();
view.load(urlWithSchema);
view.once(LOAD_SUCCESS, this.deeplinkSuccess);
view.once(LOAD_FAILED, this.deeplinkFailed);
}
} else {
dialog.showErrorBox('No matching server', `there is no configured server in the app that matches the requested url: ${parsedURL.toString()}`);
}

View File

@@ -74,9 +74,11 @@ const generateDidStartNavigation = (getServersFunction: () => TeamWithTabs[]) =>
return;
}
const serverURL = urlUtils.parseURL(server?.url || '');
if (server && urlUtils.isCustomLoginURL(parsedURL, server, serverList)) {
customLogins[contentID].inProgress = true;
} else if (server && customLogins[contentID].inProgress && urlUtils.isInternalURL(server.url, parsedURL)) {
} else if (server && customLogins[contentID].inProgress && urlUtils.isInternalURL(serverURL || new URL(''), parsedURL)) {
customLogins[contentID].inProgress = false;
}
};

View File

@@ -16,6 +16,7 @@ import {
FOCUS_THREE_DOT_MENU,
GET_DARK_MODE,
UPDATE_SHORTCUT_MENU,
BROWSER_HISTORY_PUSH,
} from 'common/communication';
import urlUtils from 'common/utils/url';
@@ -50,6 +51,7 @@ ipcMain.handle(GET_LOADING_SCREEN_DATA, handleLoadingScreenDataRequest);
ipcMain.handle(GET_DARK_MODE, handleGetDarkMode);
ipcMain.on(REACT_APP_INITIALIZED, handleReactAppInitialized);
ipcMain.on(LOADING_SCREEN_ANIMATION_FINISHED, handleLoadingScreenAnimationFinished);
ipcMain.on(BROWSER_HISTORY_PUSH, handleBrowserHistoryPush);
export function setConfig(data: CombinedConfig) {
if (data) {
@@ -480,15 +482,21 @@ export function selectNextTab() {
}
const currentTeamTabs = status.config?.teams.find((team) => team.name === currentView.tab.server.name)?.tabs;
const filteredTabs = currentTeamTabs?.filter((tab) => !tab.isClosed);
const currentTab = currentTeamTabs?.find((tab) => tab.name === currentView.tab.type);
if (!currentTeamTabs || !currentTab) {
if (!currentTeamTabs || !currentTab || !filteredTabs) {
return;
}
const currentOrder = currentTab.order;
const nextOrder = ((currentOrder + 1) % currentTeamTabs.length);
const nextIndex = currentTeamTabs.findIndex((tab) => tab.order === nextOrder);
const newTab = currentTeamTabs[nextIndex];
let currentOrder = currentTab.order;
let nextIndex = -1;
while (nextIndex === -1) {
const nextOrder = ((currentOrder + 1) % currentTeamTabs.length);
nextIndex = filteredTabs.findIndex((tab) => tab.order === nextOrder);
currentOrder = nextOrder;
}
const newTab = filteredTabs[nextIndex];
switchTab(currentView.tab.server.name, newTab.name);
}
@@ -499,17 +507,22 @@ export function selectPreviousTab() {
}
const currentTeamTabs = status.config?.teams.find((team) => team.name === currentView.tab.server.name)?.tabs;
const filteredTabs = currentTeamTabs?.filter((tab) => !tab.isClosed);
const currentTab = currentTeamTabs?.find((tab) => tab.name === currentView.tab.type);
if (!currentTeamTabs || !currentTab) {
if (!currentTeamTabs || !currentTab || !filteredTabs) {
return;
}
const currentOrder = currentTab.order;
// js modulo operator returns a negative number if result is negative, so we have to ensure it's positive
const nextOrder = ((currentTeamTabs.length + (currentOrder - 1)) % currentTeamTabs.length);
const nextIndex = currentTeamTabs.findIndex((tab) => tab.order === nextOrder);
const newTab = currentTeamTabs[nextIndex];
let currentOrder = currentTab.order;
let nextIndex = -1;
while (nextIndex === -1) {
const nextOrder = ((currentTeamTabs.length + (currentOrder - 1)) % currentTeamTabs.length);
nextIndex = filteredTabs.findIndex((tab) => tab.order === nextOrder);
currentOrder = nextOrder;
}
const newTab = filteredTabs[nextIndex];
switchTab(currentView.tab.server.name, newTab.name);
}
@@ -517,6 +530,20 @@ function handleGetDarkMode() {
return status.config?.darkMode;
}
function handleBrowserHistoryPush(e: IpcMainEvent, viewName: string, pathName: string) {
const currentView = status.viewManager?.views.get(viewName);
const redirectedViewName = urlUtils.getView(`${currentView?.tab.server.url}${pathName}`, status.config!.teams)?.name || viewName;
if (status.viewManager?.closedViews.has(redirectedViewName)) {
status.viewManager.openClosedTab(redirectedViewName, `${currentView?.tab.server.url}${pathName}`);
}
const redirectedView = status.viewManager?.views.get(redirectedViewName) || currentView;
if (redirectedView !== currentView) {
log.info('redirecting to a new view', redirectedView?.name || viewName);
status.viewManager?.showByName(redirectedView?.name || viewName);
}
redirectedView?.view.webContents.send(BROWSER_HISTORY_PUSH, pathName);
}
export function getCurrentTeamName() {
return status.currentServerName;
}