[MM-50485] Migrate app to ServerManager, remove view names and replace with IDs (#2672)

* Migrate app to ServerManager, remove view names and replace with IDs

* Fixed a test

* Fixed a bug when adding the initial server

* Merge'd

* Bug fixes and PR feedback
This commit is contained in:
Devin Binnie
2023-04-12 12:52:34 -04:00
committed by GitHub
parent d87097b1eb
commit 686b4ac9f1
58 changed files with 1570 additions and 2175 deletions

View File

@@ -5,7 +5,7 @@ import React, {useState, useCallback, useEffect} from 'react';
import {useIntl, FormattedMessage} from 'react-intl';
import classNames from 'classnames';
import {TeamWithIndex} from 'types/config';
import {MattermostTeam} from 'types/config';
import womanLaptop from 'renderer/assets/svg/womanLaptop.svg';
@@ -22,8 +22,8 @@ import 'renderer/css/components/ConfigureServer.scss';
import 'renderer/css/components/LoadingScreen.css';
type ConfigureServerProps = {
currentTeams: TeamWithIndex[];
team?: TeamWithIndex;
currentTeams: MattermostTeam[];
team?: MattermostTeam;
mobileView?: boolean;
darkMode?: boolean;
messageTitle?: string;
@@ -32,7 +32,7 @@ type ConfigureServerProps = {
alternateLinkMessage?: string;
alternateLinkText?: string;
alternateLinkURL?: string;
onConnect: (data: TeamWithIndex) => void;
onConnect: (data: MattermostTeam) => void;
};
function ConfigureServer({
@@ -53,8 +53,7 @@ function ConfigureServer({
const {
name: prevName,
url: prevURL,
order = 0,
index = NaN,
id,
} = team || {};
const [transition, setTransition] = useState<'inFromRight' | 'outToLeft'>();
@@ -200,8 +199,7 @@ function ConfigureServer({
onConnect({
url: fullURL,
name,
index,
order,
id,
});
}, MODAL_TRANSITION_TIMEOUT);
};

View File

@@ -10,12 +10,9 @@ import {Container, Row} from 'react-bootstrap';
import {DropResult} from 'react-beautiful-dnd';
import {injectIntl, IntlShape} from 'react-intl';
import {TeamWithTabs} from 'types/config';
import {MattermostTab, MattermostTeam} from 'types/config';
import {DownloadedItems} from 'types/downloads';
import {getTabViewName} from 'common/tabs/TabView';
import {escapeRegex} from 'common/utils/util';
import restoreButton from '../../assets/titlebar/chrome-restore.svg';
import maximizeButton from '../../assets/titlebar/chrome-maximize.svg';
import minimizeButton from '../../assets/titlebar/chrome-minimize.svg';
@@ -40,9 +37,6 @@ enum Status {
}
type Props = {
teams: TeamWithTabs[];
lastActiveTeam?: number;
moveTabs: (teamName: string, originalOrder: number, newOrder: number) => number | undefined;
openMenu: () => void;
darkMode: boolean;
appName: string;
@@ -51,8 +45,10 @@ type Props = {
};
type State = {
activeServerName?: string;
activeTabName?: string;
activeServerId?: string;
activeTabId?: string;
servers: MattermostTeam[];
tabs: Map<string, MattermostTab[]>;
sessionsExpired: Record<string, boolean>;
unreadCounts: Record<string, boolean>;
mentionCounts: Record<string, number>;
@@ -87,21 +83,14 @@ class MainPage extends React.PureComponent<Props, State> {
this.topBar = React.createRef();
this.threeDotMenu = React.createRef();
const firstServer = this.props.teams.find((team) => team.order === this.props.lastActiveTeam) || this.props.teams.find((team) => team.order === 0);
let firstTab = firstServer?.tabs.find((tab) => tab.order === firstServer.lastActiveTab) || firstServer?.tabs.find((tab) => tab.order === 0);
if (!firstTab?.isOpen) {
const openTabs = firstServer?.tabs.filter((tab) => tab.isOpen) || [];
firstTab = openTabs?.find((e) => e.order === 0) || openTabs[0];
}
this.state = {
activeServerName: firstServer?.name,
activeTabName: firstTab?.name,
servers: [],
tabs: new Map(),
sessionsExpired: {},
unreadCounts: {},
mentionCounts: {},
maximized: false,
tabViewStatus: new Map(this.props.teams.map((team) => team.tabs.map((tab) => getTabViewName(team.name, tab.name))).flat().map((tabViewName) => [tabViewName, {status: Status.LOADING}])),
tabViewStatus: new Map(),
darkMode: this.props.darkMode,
isMenuOpen: false,
isDownloadsDropdownOpen: false,
@@ -112,10 +101,10 @@ class MainPage extends React.PureComponent<Props, State> {
}
getTabViewStatus() {
if (!this.state.activeServerName || !this.state.activeTabName) {
if (!this.state.activeTabId) {
return undefined;
}
return this.state.tabViewStatus.get(getTabViewName(this.state.activeServerName, this.state.activeTabName)) ?? {status: Status.NOSERVERS};
return this.state.tabViewStatus.get(this.state.activeTabId) ?? {status: Status.NOSERVERS};
}
updateTabStatus(tabViewName: string, newStatusValue: TabViewStatus) {
@@ -135,13 +124,49 @@ class MainPage extends React.PureComponent<Props, State> {
}
}
componentDidMount() {
getServersAndTabs = async () => {
const servers = await window.desktop.getOrderedServers();
const tabs = new Map();
const tabViewStatus = new Map(this.state.tabViewStatus);
await Promise.all(
servers.map((srv) => window.desktop.getOrderedTabsForServer(srv.id!).
then((tabs) => ({id: srv.id, tabs}))),
).then((serverTabs) => {
serverTabs.forEach((serverTab) => {
tabs.set(serverTab.id, serverTab.tabs);
serverTab.tabs.forEach((tab) => {
if (!tabViewStatus.has(tab.id!)) {
tabViewStatus.set(tab.id!, {status: Status.LOADING});
}
});
});
});
this.setState({servers, tabs, tabViewStatus});
return Boolean(servers.length);
}
setInitialActiveTab = async () => {
const lastActive = await window.desktop.getLastActive();
this.setActiveView(lastActive.server, lastActive.tab);
}
updateServers = async () => {
const hasServers = await this.getServersAndTabs();
if (hasServers && !(this.state.activeServerId && this.state.activeTabId)) {
await this.setInitialActiveTab();
}
}
async componentDidMount() {
// request downloads
this.requestDownloadsLength();
await this.requestDownloadsLength();
await this.updateServers();
window.desktop.onUpdateServers(this.updateServers);
// set page on retry
window.desktop.onLoadRetry((viewName, retry, err, loadUrl) => {
console.log(`${viewName}: failed to load ${err}, but retrying`);
window.desktop.onLoadRetry((viewId, retry, err, loadUrl) => {
console.log(`${viewId}: failed to load ${err}, but retrying`);
const statusValue = {
status: Status.RETRY,
extra: {
@@ -150,15 +175,15 @@ class MainPage extends React.PureComponent<Props, State> {
url: loadUrl,
},
};
this.updateTabStatus(viewName, statusValue);
this.updateTabStatus(viewId, statusValue);
});
window.desktop.onLoadSuccess((viewName) => {
this.updateTabStatus(viewName, {status: Status.DONE});
window.desktop.onLoadSuccess((viewId) => {
this.updateTabStatus(viewId, {status: Status.DONE});
});
window.desktop.onLoadFailed((viewName, err, loadUrl) => {
console.log(`${viewName}: failed to load ${err}`);
window.desktop.onLoadFailed((viewId, err, loadUrl) => {
console.log(`${viewId}: failed to load ${err}`);
const statusValue = {
status: Status.FAILED,
extra: {
@@ -166,7 +191,7 @@ class MainPage extends React.PureComponent<Props, State> {
url: loadUrl,
},
};
this.updateTabStatus(viewName, statusValue);
this.updateTabStatus(viewId, statusValue);
});
window.desktop.onDarkModeChange((darkMode) => {
@@ -174,9 +199,7 @@ class MainPage extends React.PureComponent<Props, State> {
});
// can't switch tabs sequentially for some reason...
window.desktop.onSetActiveView((serverName, tabName) => {
this.setState({activeServerName: serverName, activeTabName: tabName});
});
window.desktop.onSetActiveView(this.setActiveView);
window.desktop.onMaximizeChange(this.handleMaximizeState);
@@ -259,6 +282,13 @@ class MainPage extends React.PureComponent<Props, State> {
window.removeEventListener('click', this.handleCloseDropdowns);
}
setActiveView = (serverId: string, tabId: string) => {
if (serverId === this.state.activeServerId && tabId === this.state.activeTabId) {
return;
}
this.setState({activeServerId: serverId, activeTabId: tabId});
}
handleCloseDropdowns = () => {
window.desktop.closeTeamsDropdown();
this.closeDownloadsDropdown();
@@ -272,18 +302,12 @@ class MainPage extends React.PureComponent<Props, State> {
this.setState({fullScreen: isFullScreen});
}
handleSelectTab = (name: string) => {
if (!this.state.activeServerName) {
return;
}
window.desktop.switchTab(this.state.activeServerName, name);
handleSelectTab = (tabId: string) => {
window.desktop.switchTab(tabId);
}
handleCloseTab = (name: string) => {
if (!this.state.activeServerName) {
return;
}
window.desktop.closeTab(this.state.activeServerName, name);
handleCloseTab = (tabId: string) => {
window.desktop.closeTab(tabId);
}
handleDragAndDrop = async (dropResult: DropResult) => {
@@ -292,20 +316,22 @@ class MainPage extends React.PureComponent<Props, State> {
if (addedIndex === undefined || removedIndex === addedIndex) {
return;
}
if (!this.state.activeServerName) {
return;
}
const currentTabs = this.props.teams.find((team) => team.name === this.state.activeServerName)?.tabs;
if (!currentTabs) {
if (!(this.state.activeServerId && this.state.tabs.has(this.state.activeServerId))) {
// TODO: figure out something here
return;
}
const teamIndex = this.props.moveTabs(this.state.activeServerName, removedIndex, addedIndex < currentTabs.length ? addedIndex : currentTabs.length - 1);
if (!teamIndex) {
return;
}
const name = currentTabs[teamIndex].name;
this.handleSelectTab(name);
const currentTabs = this.state.tabs.get(this.state.activeServerId)!;
const tabsCopy = currentTabs.concat();
const tab = tabsCopy.splice(removedIndex, 1);
const newOrder = addedIndex < currentTabs.length ? addedIndex : currentTabs.length - 1;
tabsCopy.splice(newOrder, 0, tab[0]);
window.desktop.updateTabOrder(this.state.activeServerId, tabsCopy.map((tab) => tab.id!));
const tabs = new Map(this.state.tabs);
tabs.set(this.state.activeServerId, tabsCopy);
this.setState({tabs});
this.handleSelectTab(tab[0].id!);
}
handleClose = (e: React.MouseEvent<HTMLDivElement>) => {
@@ -336,7 +362,7 @@ class MainPage extends React.PureComponent<Props, State> {
}
focusOnWebView = () => {
window.desktop.focusBrowserView();
window.desktop.focusCurrentView();
this.handleCloseDropdowns();
}
@@ -373,7 +399,10 @@ class MainPage extends React.PureComponent<Props, State> {
render() {
const {intl} = this.props;
const currentTabs = this.props.teams.find((team) => team.name === this.state.activeServerName)?.tabs || [];
let currentTabs: MattermostTab[] = [];
if (this.state.activeServerId) {
currentTabs = this.state.tabs.get(this.state.activeServerId) ?? [];
}
const tabsRow = (
<TabBar
@@ -383,8 +412,8 @@ class MainPage extends React.PureComponent<Props, State> {
sessionsExpired={this.state.sessionsExpired}
unreadCounts={this.state.unreadCounts}
mentionCounts={this.state.mentionCounts}
activeServerName={this.state.activeServerName}
activeTabName={this.state.activeTabName}
activeServerId={this.state.activeServerId}
activeTabId={this.state.activeTabId}
onSelect={this.handleSelectTab}
onCloseTab={this.handleCloseTab}
onDrop={this.handleDragAndDrop}
@@ -463,20 +492,22 @@ class MainPage extends React.PureComponent<Props, State> {
);
}
const serverMatch = `${escapeRegex(this.state.activeServerName)}___TAB_[A-Z]+`;
const totalMentionCount = Object.keys(this.state.mentionCounts).reduce((sum, key) => {
// Strip out current server from unread and mention counts
if (this.state.activeServerName && key.match(serverMatch)) {
if (this.state.tabs.get(this.state.activeServerId!)?.map((tab) => tab.id).includes(key)) {
return sum;
}
return sum + this.state.mentionCounts[key];
}, 0);
const hasAnyUnreads = Object.keys(this.state.unreadCounts).reduce((sum, key) => {
if (this.state.activeServerName && key.match(serverMatch)) {
if (this.state.tabs.get(this.state.activeServerId!)?.map((tab) => tab.id).includes(key)) {
return sum;
}
return sum || this.state.unreadCounts[key];
}, false);
const activeServer = this.state.servers.find((srv) => srv.id === this.state.activeServerId);
const topRow = (
<Row
className={topBarClassName}
@@ -486,7 +517,7 @@ class MainPage extends React.PureComponent<Props, State> {
ref={this.topBar}
className={'topBar-bg'}
>
{window.process.platform !== 'linux' && this.props.teams.length === 0 && (
{window.process.platform !== 'linux' && this.state.servers.length === 0 && (
<div className='app-title'>
{intl.formatMessage({id: 'renderer.components.mainPage.titleBar', defaultMessage: 'Mattermost'})}
</div>
@@ -506,10 +537,10 @@ class MainPage extends React.PureComponent<Props, State> {
})}
/>
</button>
{this.props.teams.length !== 0 && (
{activeServer && (
<TeamDropdownButton
isDisabled={this.state.modalOpen}
activeServerName={this.state.activeServerName}
activeServerName={activeServer.name}
totalMentionCount={totalMentionCount}
hasUnreads={hasAnyUnreads}
isMenuOpen={this.state.isMenuOpen}
@@ -524,14 +555,14 @@ class MainPage extends React.PureComponent<Props, State> {
);
const views = () => {
if (!this.props.teams.length) {
if (!activeServer) {
return null;
}
let component;
const tabStatus = this.getTabViewStatus();
if (!tabStatus) {
if (this.state.activeTabName || this.state.activeServerName) {
console.error(`Not tabStatus for ${this.state.activeTabName}`);
if (this.state.activeTabId) {
console.error(`Not tabStatus for ${this.state.activeTabId}`);
}
return null;
}
@@ -539,7 +570,7 @@ class MainPage extends React.PureComponent<Props, State> {
case Status.FAILED:
component = (
<ErrorView
id={this.state.activeTabName + '-fail'}
id={activeServer.name + '-fail'}
errorInfo={tabStatus.extra?.error}
url={tabStatus.extra ? tabStatus.extra.url : ''}
active={true}

View File

@@ -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 {TeamWithIndex} from 'types/config';
import {MattermostTeam} from 'types/config';
import urlUtils from 'common/utils/url';
type Props = {
onClose?: () => void;
onSave?: (team: TeamWithIndex) => void;
team?: TeamWithIndex;
currentTeams?: TeamWithIndex[];
onSave?: (team: MattermostTeam) => void;
team?: MattermostTeam;
currentTeams?: MattermostTeam[];
editMode?: boolean;
show?: boolean;
restoreFocus?: boolean;
@@ -26,7 +26,7 @@ type Props = {
type State = {
teamName: string;
teamUrl: string;
teamIndex?: number;
teamId?: string;
teamOrder: number;
saveStarted: boolean;
}
@@ -55,8 +55,7 @@ class NewTeamModal extends React.PureComponent<Props, State> {
this.setState({
teamName: this.props.team ? this.props.team.name : '',
teamUrl: this.props.team ? this.props.team.url : '',
teamIndex: this.props.team?.index,
teamOrder: this.props.team ? this.props.team.order : (this.props.currentOrder || 0),
teamId: this.props.team?.id,
saveStarted: false,
});
}
@@ -67,10 +66,7 @@ class NewTeamModal extends React.PureComponent<Props, State> {
}
if (this.props.currentTeams) {
const currentTeams = [...this.props.currentTeams];
if (this.props.editMode && this.props.team) {
currentTeams.splice(this.props.team.index, 1);
}
if (currentTeams.find((team) => team.name === this.state.teamName)) {
if (currentTeams.find((team) => team.id !== this.state.teamId && team.name === this.state.teamName)) {
return (
<FormattedMessage
id='renderer.components.newTeamModal.error.serverNameExists'
@@ -103,10 +99,7 @@ class NewTeamModal extends React.PureComponent<Props, State> {
}
if (this.props.currentTeams) {
const currentTeams = [...this.props.currentTeams];
if (this.props.editMode && this.props.team) {
currentTeams.splice(this.props.team.index, 1);
}
if (currentTeams.find((team) => team.url === this.state.teamUrl)) {
if (currentTeams.find((team) => team.id !== this.state.teamId && team.url === this.state.teamUrl)) {
return (
<FormattedMessage
id='renderer.components.newTeamModal.error.serverUrlExists'
@@ -199,8 +192,7 @@ class NewTeamModal extends React.PureComponent<Props, State> {
this.props.onSave?.({
url: this.state.teamUrl,
name: this.state.teamName,
index: this.state.teamIndex!,
order: this.state.teamOrder,
id: this.state.teamId,
});
}
});

View File

@@ -8,18 +8,18 @@ import {DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDra
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
import classNames from 'classnames';
import {ConfigTab} from 'types/config';
import {MattermostTab} from 'types/config';
import {getTabViewName, TabType, canCloseTab, getTabDisplayName} from 'common/tabs/TabView';
import {TabType, canCloseTab, getTabDisplayName} from 'common/tabs/TabView';
type Props = {
activeTabName?: string;
activeServerName?: string;
activeTabId?: string;
activeServerId?: string;
id: string;
isDarkMode: boolean;
onSelect: (name: string, index: number) => void;
onCloseTab: (name: string) => void;
tabs: ConfigTab[];
onSelect: (id: string) => void;
onCloseTab: (id: string) => void;
tabs: MattermostTab[];
sessionsExpired: Record<string, boolean>;
unreadCounts: Record<string, boolean>;
mentionCounts: Record<string, number>;
@@ -41,25 +41,21 @@ function getStyle(style?: DraggingStyle | NotDraggingStyle) {
}
class TabBar extends React.PureComponent<Props> {
onCloseTab = (name: string) => {
onCloseTab = (id: string) => {
return (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
this.props.onCloseTab(name);
this.props.onCloseTab(id);
};
}
render() {
const orderedTabs = this.props.tabs.concat().sort((a, b) => a.order - b.order);
const tabs = orderedTabs.map((tab, orderedIndex) => {
const index = this.props.tabs.indexOf(tab);
const tabName = getTabViewName(this.props.activeServerName!, tab.name);
const sessionExpired = this.props.sessionsExpired[tabName];
const hasUnreads = this.props.unreadCounts[tabName];
const tabs = this.props.tabs.map((tab, index) => {
const sessionExpired = this.props.sessionsExpired[tab.id!];
const hasUnreads = this.props.unreadCounts[tab.id!];
let mentionCount = 0;
if (this.props.mentionCounts[tabName] > 0) {
mentionCount = this.props.mentionCounts[tabName];
if (this.props.mentionCounts[tab.id!] > 0) {
mentionCount = this.props.mentionCounts[tab.id!];
}
let badgeDiv: React.ReactNode;
@@ -83,9 +79,9 @@ class TabBar extends React.PureComponent<Props> {
return (
<Draggable
key={index}
draggableId={`teamTabItem${index}`}
index={orderedIndex}
key={tab.id}
draggableId={`teamTabItem-${tab.id}`}
index={index}
>
{(provided, snapshot) => {
if (!tab.isOpen) {
@@ -106,7 +102,7 @@ class TabBar extends React.PureComponent<Props> {
draggable={false}
title={this.props.intl.formatMessage({id: `common.tabs.${tab.name}`, defaultMessage: getTabDisplayName(tab.name as TabType)})}
className={classNames('teamTabItem', {
active: this.props.activeTabName === tab.name,
active: this.props.activeTabId === tab.id,
dragging: snapshot.isDragging,
})}
{...provided.draggableProps}
@@ -116,10 +112,10 @@ class TabBar extends React.PureComponent<Props> {
<NavLink
eventKey={index}
draggable={false}
active={this.props.activeTabName === tab.name}
active={this.props.activeTabId === tab.id}
disabled={this.props.tabsDisabled}
onSelect={() => {
this.props.onSelect(tab.name, index);
this.props.onSelect(tab.id!);
}}
>
<div className='TabBar-tabSeperator'>
@@ -131,7 +127,7 @@ class TabBar extends React.PureComponent<Props> {
{canCloseTab(tab.name as TabType) &&
<button
className='teamTabItem__close'
onClick={this.onCloseTab(tab.name)}
onClick={this.onCloseTab(tab.id!)}
>
<i className='icon-close'/>
</button>