[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:
@@ -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) => {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
45
src/main/preload/dropdown.js
Normal file
45
src/main/preload/dropdown.js
Normal 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);
|
||||
});
|
@@ -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);
|
||||
});
|
||||
|
@@ -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 */
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
114
src/main/views/teamDropdownView.ts
Normal file
114
src/main/views/teamDropdownView.ts
Normal 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();
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
||||
|
Reference in New Issue
Block a user