[MM-36421] Replace server tabs with dropdown menu (#1647)

* WIP

* WIP

* PoC for dropdown - logic stuff

* Most of the logic for the dropdown

* Working dropdown menu to pick servers

* Mentions, unreads and expired working. Many styles are working.

* Some more styles

* Dark mode and other stuff

* Some cleanup

* Generate build

* PR feedback

* More PR feedback
This commit is contained in:
Devin Binnie
2021-07-07 18:49:48 -04:00
committed by GitHub
parent 4130c2c37d
commit 880af87ddf
32 changed files with 1792 additions and 117 deletions

View File

@@ -4,7 +4,7 @@
import events from 'events';
import {ipcMain} from 'electron';
import {UPDATE_MENTIONS, UPDATE_TRAY, UPDATE_BADGE, SESSION_EXPIRED} from 'common/communication';
import {UPDATE_MENTIONS, UPDATE_TRAY, UPDATE_BADGE, SESSION_EXPIRED, UPDATE_DROPDOWN_MENTIONS} from 'common/communication';
import * as WindowManager from './windows/windowManager';
@@ -32,12 +32,17 @@ const emitBadge = (expired?: boolean, mentions?: number, unreads?: boolean) => {
status.emitter.emit(UPDATE_BADGE, expired, mentions, unreads);
};
const emitDropdown = (expired?: Map<string, boolean>, mentions?: Map<string, number>, unreads?: Map<string, boolean>) => {
status.emitter.emit(UPDATE_DROPDOWN_MENTIONS, expired, mentions, unreads);
};
export const emitStatus = () => {
const expired = anyExpired();
const mentions = totalMentions();
const unreads = anyUnreads();
emitTray(expired, mentions, unreads);
emitBadge(expired, mentions, unreads);
emitDropdown(status.expired, status.mentions, status.unreads);
};
export const updateMentions = (serverName: string, mentions: number, unreads?: boolean) => {

View File

@@ -35,6 +35,7 @@ import {
SHOW_SETTINGS_WINDOW,
RELOAD_CONFIGURATION,
USER_ACTIVITY_UPDATE,
EMIT_CONFIGURATION,
} from 'common/communication';
import Config from 'common/config';
@@ -268,6 +269,7 @@ function handleConfigUpdate(newConfig: Config) {
}
ipcMain.emit('update-menu', true, config);
ipcMain.emit(EMIT_CONFIGURATION, true, newConfig.data);
}
function handleConfigSynchronize() {
@@ -284,6 +286,8 @@ function handleConfigSynchronize() {
if (app.isReady()) {
WindowManager.sendToRenderer(RELOAD_CONFIGURATION);
}
ipcMain.emit(EMIT_CONFIGURATION, true, config.data);
}
function handleReloadConfig() {
@@ -302,6 +306,8 @@ function handleDarkModeChange(darkMode: boolean) {
refreshTrayImages(config.trayIconTheme);
WindowManager.sendToRenderer(DARK_MODE_CHANGE, darkMode);
WindowManager.updateLoadingScreenDarkMode(darkMode);
ipcMain.emit(EMIT_CONFIGURATION, true, config.data);
}
//

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
'use strict';
import {ipcRenderer} from 'electron';
import {
UPDATE_TEAMS_DROPDOWN,
REQUEST_TEAMS_DROPDOWN_INFO,
RECEIVE_DROPDOWN_MENU_SIZE,
SEND_DROPDOWN_MENU_SIZE,
SWITCH_SERVER,
CLOSE_TEAMS_DROPDOWN,
SHOW_NEW_SERVER_MODAL,
} from 'common/communication';
console.log('preloaded for the dropdown!');
window.addEventListener('message', async (event) => {
switch (event.data.type) {
case REQUEST_TEAMS_DROPDOWN_INFO:
ipcRenderer.send(REQUEST_TEAMS_DROPDOWN_INFO);
break;
case SEND_DROPDOWN_MENU_SIZE:
ipcRenderer.send(RECEIVE_DROPDOWN_MENU_SIZE, event.data.data.width, event.data.data.height);
break;
case SWITCH_SERVER:
ipcRenderer.send(SWITCH_SERVER, event.data.data);
break;
case SHOW_NEW_SERVER_MODAL:
ipcRenderer.send(SHOW_NEW_SERVER_MODAL);
break;
case CLOSE_TEAMS_DROPDOWN:
ipcRenderer.send(CLOSE_TEAMS_DROPDOWN);
break;
default:
console.log(`got a message: ${event}`);
console.log(event);
}
});
ipcRenderer.on(UPDATE_TEAMS_DROPDOWN, (event, teams, activeTeam, darkMode, expired, mentions, unreads) => {
window.postMessage({type: UPDATE_TEAMS_DROPDOWN, data: {teams, activeTeam, darkMode, expired, mentions, unreads}}, window.location.href);
});

View File

@@ -6,7 +6,13 @@
import {ipcRenderer} from 'electron';
import {RECEIVED_LOADING_SCREEN_DATA, GET_LOADING_SCREEN_DATA, LOADING_SCREEN_ANIMATION_FINISHED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication';
import {
RECEIVED_LOADING_SCREEN_DATA,
GET_LOADING_SCREEN_DATA,
LOADING_SCREEN_ANIMATION_FINISHED,
TOGGLE_LOADING_SCREEN_VISIBILITY,
CLOSE_TEAMS_DROPDOWN,
} from 'common/communication';
console.log('preloaded for the loading screen!');
@@ -31,3 +37,7 @@ ipcRenderer.on(GET_LOADING_SCREEN_DATA, (_, result) => {
ipcRenderer.on(TOGGLE_LOADING_SCREEN_VISIBILITY, (_, toggle) => {
window.postMessage({type: TOGGLE_LOADING_SCREEN_VISIBILITY, data: toggle}, window.location.href);
});
window.addEventListener('click', () => {
ipcRenderer.send(CLOSE_TEAMS_DROPDOWN);
});

View File

@@ -9,7 +9,7 @@
import {ipcRenderer, webFrame} from 'electron';
import log from 'electron-log';
import {NOTIFY_MENTION, IS_UNREAD, UNREAD_RESULT, SESSION_EXPIRED, SET_SERVER_NAME, REACT_APP_INITIALIZED, USER_ACTIVITY_UPDATE} from 'common/communication';
import {NOTIFY_MENTION, IS_UNREAD, UNREAD_RESULT, SESSION_EXPIRED, SET_SERVER_NAME, REACT_APP_INITIALIZED, USER_ACTIVITY_UPDATE, CLOSE_TEAMS_DROPDOWN} from 'common/communication';
const UNREAD_COUNT_INTERVAL = 1000;
const CLEAR_CACHE_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours
@@ -205,4 +205,8 @@ setInterval(() => {
webFrame.clearCache();
}, CLEAR_CACHE_INTERVAL);
window.addEventListener('click', () => {
ipcRenderer.send(CLOSE_TEAMS_DROPDOWN);
});
/* eslint-enable no-magic-numbers */

View File

@@ -7,12 +7,9 @@ import path from 'path';
import {Args} from 'types/args';
import {PRODUCTION} from 'common/utils/constants';
import {BACK_BAR_HEIGHT, PRODUCTION, TAB_BAR_HEIGHT} from 'common/utils/constants';
import Utils from 'common/utils/util';
const TAB_BAR_HEIGHT = 40;
const BACK_BAR_HEIGHT = 36;
export function shouldBeHiddenOnStartup(parsedArgv: Args) {
if (parsedArgv.hidden) {
return true;

View File

@@ -73,19 +73,17 @@ export class MattermostView extends EventEmitter {
this.window = win;
const preload = getLocalPreload('preload.js');
this.options = {
webPreferences: {
contextIsolation: process.env.NODE_ENV !== 'test',
preload,
additionalArguments: [
`version=${app.getVersion()}`,
`appName=${app.name}`,
],
enableRemoteModule: process.env.NODE_ENV === 'test',
nodeIntegration: process.env.NODE_ENV === 'test',
...options.webPreferences,
},
...options,
this.options = Object.assign({}, options);
this.options.webPreferences = {
contextIsolation: process.env.NODE_ENV !== 'test',
preload,
additionalArguments: [
`version=${app.getVersion()}`,
`appName=${app.name}`,
],
enableRemoteModule: process.env.NODE_ENV === 'test',
nodeIntegration: process.env.NODE_ENV === 'test',
...options.webPreferences,
};
this.isVisible = false;
this.view = new BrowserView(this.options);

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BrowserView, BrowserWindow, ipcMain, IpcMainEvent} from 'electron';
import {CombinedConfig, Team} from 'types/config';
import {
CLOSE_TEAMS_DROPDOWN,
EMIT_CONFIGURATION,
OPEN_TEAMS_DROPDOWN,
UPDATE_TEAMS_DROPDOWN,
UPDATE_DROPDOWN_MENTIONS,
REQUEST_TEAMS_DROPDOWN_INFO,
RECEIVE_DROPDOWN_MENU_SIZE,
SET_SERVER_KEY,
} from 'common/communication';
import * as AppState from '../appState';
import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH, THREE_DOT_MENU_WIDTH_MAC, MENU_SHADOW_WIDTH} from 'common/utils/constants';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import * as WindowManager from '../windows/windowManager';
export default class TeamDropdownView {
view: BrowserView;
bounds?: Electron.Rectangle;
teams: Team[];
activeTeam?: string;
darkMode: boolean;
unreads?: Map<string, boolean>;
mentions?: Map<string, number>;
expired?: Map<string, boolean>;
window: BrowserWindow;
constructor(window: BrowserWindow, teams: Team[], darkMode: boolean) {
this.teams = teams;
this.window = window;
this.darkMode = darkMode;
const preload = getLocalPreload('dropdown.js');
this.view = new BrowserView({webPreferences: {
contextIsolation: process.env.NODE_ENV !== 'test',
preload,
nodeIntegration: process.env.NODE_ENV === 'test',
enableRemoteModule: process.env.NODE_ENV === 'test',
}});
this.view.webContents.loadURL(getLocalURLString('dropdown.html'));
ipcMain.on(OPEN_TEAMS_DROPDOWN, this.handleOpen);
ipcMain.on(CLOSE_TEAMS_DROPDOWN, this.handleClose);
ipcMain.on(EMIT_CONFIGURATION, this.updateConfig);
ipcMain.on(REQUEST_TEAMS_DROPDOWN_INFO, this.updateDropdown);
ipcMain.on(RECEIVE_DROPDOWN_MENU_SIZE, this.handleReceivedMenuSize);
ipcMain.on(SET_SERVER_KEY, this.updateActiveTeam);
AppState.on(UPDATE_DROPDOWN_MENTIONS, this.updateMentions);
}
updateConfig = (event: IpcMainEvent, config: CombinedConfig) => {
this.teams = config.teams;
this.darkMode = config.darkMode;
this.updateDropdown();
}
updateActiveTeam = (event: IpcMainEvent, name: string) => {
this.activeTeam = name;
this.updateDropdown();
}
updateMentions = (expired: Map<string, boolean>, mentions: Map<string, number>, unreads: Map<string, boolean>) => {
this.unreads = unreads;
this.mentions = mentions;
this.expired = expired;
this.updateDropdown();
}
updateDropdown = () => {
this.view.webContents.send(UPDATE_TEAMS_DROPDOWN, this.teams, this.activeTeam, this.darkMode, this.expired, this.mentions, this.unreads);
}
handleOpen = () => {
this.window.addBrowserView(this.view);
const bounds = this.view.getBounds();
this.view.setBounds(this.getBounds(bounds.width, bounds.height));
this.window.setTopBrowserView(this.view);
this.view.webContents.focus();
WindowManager.sendToRenderer(OPEN_TEAMS_DROPDOWN);
}
handleClose = () => {
this.window.removeBrowserView(this.view);
WindowManager.sendToRenderer(CLOSE_TEAMS_DROPDOWN);
}
handleReceivedMenuSize = (event: IpcMainEvent, width: number, height: number) => {
const bounds = this.getBounds(width, height);
this.view.setBounds(bounds);
}
getBounds = (width: number, height: number) => {
return {
x: (process.platform === 'darwin' ? THREE_DOT_MENU_WIDTH_MAC : THREE_DOT_MENU_WIDTH) - MENU_SHADOW_WIDTH,
y: TAB_BAR_HEIGHT - MENU_SHADOW_WIDTH,
width,
height,
};
}
destroy = () => {
// 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();
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import log from 'electron-log';
import {BrowserView, BrowserWindow, dialog} from 'electron';
import {BrowserView, BrowserWindow, dialog, ipcMain} from 'electron';
import {BrowserViewConstructorOptions} from 'electron/main';
import {CombinedConfig, Team} from 'types/config';
@@ -131,6 +131,7 @@ export class ViewManager {
return;
}
newView.window.webContents.send(SET_SERVER_KEY, serverInfo.order);
ipcMain.emit(SET_SERVER_KEY, true, name);
if (newView.isReady()) {
// if view is not ready, the renderer will have something to display instead.
newView.show();

View File

@@ -15,6 +15,8 @@ import {getAdjustedWindowBoundaries} from '../utils';
import {ViewManager} from '../views/viewManager';
import CriticalErrorHandler from '../CriticalErrorHandler';
import TeamDropdownView from '../views/teamDropdownView';
import {createSettingsWindow} from './settingsWindow';
import createMainWindow from './mainWindow';
@@ -25,6 +27,7 @@ type WindowManagerStatus = {
settingsWindow?: BrowserWindow;
config?: CombinedConfig;
viewManager?: ViewManager;
teamDropdown?: TeamDropdownView;
};
const status: WindowManagerStatus = {};
@@ -108,6 +111,8 @@ export function showMainWindow(deeplinkingURL?: string | URL) {
if (status.viewManager) {
status.viewManager.updateMainWindow(status.mainWindow);
}
status.teamDropdown = new TeamDropdownView(status.mainWindow, status.config.teams, status.config.darkMode);
}
initializeViewManager();