From a79e7aeb4c3c51a683542a722888c064c577734c Mon Sep 17 00:00:00 2001 From: Devin Binnie <52460000+devinbinnie@users.noreply.github.com> Date: Mon, 16 Aug 2021 09:17:45 -0400 Subject: [PATCH] [MM-36428][MM-36434][MM-36435] Keyboard navigation and menu updates for new tab/dropdown layout (#1695) * [MM-36428][MM-36434][MM-36435] Keyboard navigation and menu updates for new tab/dropdown layout * Shortcuts for Windows/Linux * Update config.yml * Fixed up the shortcuts * Fixed the new server modal popping up where there are GPO teams only --- .circleci/config.yml | 2 +- src/common/communication.ts | 1 + src/main/main.ts | 29 ++++++++- src/main/menus/app.ts | 33 ++++++---- src/main/windows/windowManager.ts | 19 +++++- src/renderer/components/MainPage.tsx | 1 + .../components/TeamDropdownButton.tsx | 5 +- .../css/components/TeamDropdownButton.scss | 8 ++- src/renderer/dropdown.tsx | 60 ++++++++++++++++++- 9 files changed, 139 insertions(+), 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 67450bd9..ef0a4cbd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -449,7 +449,7 @@ workflows: branches: only: - /^release-\d+(\.\d+){1,2}(-rc.*)?/ - - pull/1691 + - pull/1695 - store_artifacts: # for master/PR builds diff --git a/src/common/communication.ts b/src/common/communication.ts index 75b1faa4..4b011bc2 100644 --- a/src/common/communication.ts +++ b/src/common/communication.ts @@ -23,6 +23,7 @@ export const UPDATE_TEAMS = 'update-teams'; export const DARK_MODE_CHANGE = 'dark_mode_change'; export const GET_DARK_MODE = 'get-dark-mode'; export const USER_ACTIVITY_UPDATE = 'user-activity-update'; +export const UPDATE_SHORTCUT_MENU = 'update-shortcut-menu'; export const LOAD_RETRY = 'load_retry'; export const LOAD_SUCCESS = 'load_success'; diff --git a/src/main/main.ts b/src/main/main.ts index 97eaa328..576e72b3 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -7,7 +7,7 @@ import fs from 'fs'; import path from 'path'; -import electron, {BrowserWindow, IpcMainEvent, IpcMainInvokeEvent, Rectangle} from 'electron'; +import electron, {BrowserWindow, globalShortcut, IpcMainEvent, IpcMainInvokeEvent, Rectangle} from 'electron'; import isDev from 'electron-is-dev'; import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer'; import log from 'electron-log'; @@ -39,6 +39,8 @@ import { SWITCH_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'; @@ -96,6 +98,7 @@ let appVersion = null; let config: Config; let authManager: AuthManager; let certificateManager: CertificateManager; +let didCheckForAddServerModal = false; /** * Main entry point for the application, ensures that everything initializes in the proper order @@ -233,6 +236,7 @@ function initializeInterCommunicationEventListeners() { ipcMain.on(NOTIFY_MENTION, handleMentionNotification); ipcMain.handle('get-app-version', handleAppVersion); ipcMain.on('update-menu', handleUpdateMenuEvent); + ipcMain.on(UPDATE_SHORTCUT_MENU, handleUpdateShortcutMenuEvent); ipcMain.on(FOCUS_BROWSERVIEW, WindowManager.focusBrowserView); if (process.platform !== 'darwin') { @@ -301,6 +305,14 @@ function handleConfigSynchronize() { WindowManager.sendToRenderer(RELOAD_CONFIGURATION); } + if (process.platform === 'win32' && !didCheckForAddServerModal && typeof config.registryConfigData !== 'undefined') { + didCheckForAddServerModal = true; + if (config.teams.length === 0) { + handleNewServerModal(); + } + } + + ipcMain.emit('update-menu', true, config); ipcMain.emit(EMIT_CONFIGURATION, true, config.data); } @@ -646,8 +658,11 @@ function initializeAfterAppReady() { WindowManager.showMainWindow(deeplinkingURL); - if (config.teams.length === 0) { - WindowManager.showSettingsWindow(); + // only check for non-Windows, as with Windows we have to wait for GPO teams + if (process.platform !== 'win32' || typeof config.registryConfigData !== 'undefined') { + if (config.teams.length === 0) { + handleNewServerModal(); + } } criticalErrorHandler.setMainWindow(WindowManager.getMainWindow()!); @@ -722,6 +737,10 @@ function initializeAfterAppReady() { // is the requesting url trusted? callback(urlUtils.isTrustedURL(requestingURL, config.teams)); }); + + globalShortcut.register(`${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`, () => { + ipcMain.emit(OPEN_TEAMS_DROPDOWN); + }); } // @@ -761,6 +780,10 @@ function handleUpdateMenuEvent(event: IpcMainEvent, menuConfig: Config) { } } +function handleUpdateShortcutMenuEvent(event: IpcMainEvent) { + handleUpdateMenuEvent(event, config); +} + async function handleSelectDownload(event: IpcMainInvokeEvent, startFrom: string) { const message = 'Specify the folder where files will download'; const result = await dialog.showOpenDialog({defaultPath: startFrom || config.downloadLocation, diff --git a/src/main/menus/app.ts b/src/main/menus/app.ts index f451ab84..54fa06f3 100644 --- a/src/main/menus/app.ts +++ b/src/main/menus/app.ts @@ -3,9 +3,9 @@ // See LICENSE.txt for license information. 'use strict'; -import {app, Menu, MenuItemConstructorOptions, MenuItem, session, shell, WebContents, webContents} from 'electron'; +import {app, ipcMain, Menu, MenuItemConstructorOptions, MenuItem, session, shell, WebContents, webContents} from 'electron'; -import {ADD_SERVER} from 'common/communication'; +import {SHOW_NEW_SERVER_MODAL} from 'common/communication'; import Config from 'common/config'; import * as WindowManager from '../windows/windowManager'; @@ -44,7 +44,7 @@ function createTemplate(config: Config) { platformAppMenu.push({ label: 'Sign in to Another Server', click() { - WindowManager.sendToRenderer(ADD_SERVER); + ipcMain.emit(SHOW_NEW_SERVER_MODAL); }, }); } @@ -151,7 +151,7 @@ function createTemplate(config: Config) { } }, }, { - label: 'Developer Tools for Current Server', + label: 'Developer Tools for Current Tab', click() { WindowManager.openBrowserViewDevTools(); }, @@ -206,22 +206,35 @@ function createTemplate(config: Config) { role: 'close', accelerator: 'CmdOrCtrl+W', }, separatorItem, ...teams.slice(0, 9).sort((teamA, teamB) => teamA.order - teamB.order).map((team, i) => { - return { + const items = []; + items.push({ label: team.name, - accelerator: `CmdOrCtrl+${i + 1}`, + accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+${i + 1}`, click() { WindowManager.switchServer(team.name); }, - }; - }), separatorItem, { - label: 'Select Next Server', + }); + 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 + accelerator: `CmdOrCtrl+${i + 1}`, + click() { + WindowManager.switchTab(team.name, tab.name); + }, + }); + }); + } + return items; + }).flat(), separatorItem, { + label: 'Select Next Tab', accelerator: 'Ctrl+Tab', click() { WindowManager.selectNextTab(); }, enabled: (teams.length > 1), }, { - label: 'Select Previous Server', + label: 'Select Previous Tab', accelerator: 'Ctrl+Shift+Tab', click() { WindowManager.selectPreviousTab(); diff --git a/src/main/windows/windowManager.ts b/src/main/windows/windowManager.ts index e9a566a1..92d17067 100644 --- a/src/main/windows/windowManager.ts +++ b/src/main/windows/windowManager.ts @@ -7,7 +7,16 @@ import log from 'electron-log'; import {CombinedConfig} from 'types/config'; -import {MAXIMIZE_CHANGE, HISTORY, GET_LOADING_SCREEN_DATA, REACT_APP_INITIALIZED, LOADING_SCREEN_ANIMATION_FINISHED, FOCUS_THREE_DOT_MENU, GET_DARK_MODE} from 'common/communication'; +import { + MAXIMIZE_CHANGE, + HISTORY, + GET_LOADING_SCREEN_DATA, + REACT_APP_INITIALIZED, + LOADING_SCREEN_ANIMATION_FINISHED, + FOCUS_THREE_DOT_MENU, + GET_DARK_MODE, + UPDATE_SHORTCUT_MENU, +} from 'common/communication'; import urlUtils from 'common/utils/url'; import {getTabViewName} from 'common/tabs/TabView'; @@ -30,6 +39,7 @@ type WindowManagerStatus = { config?: CombinedConfig; viewManager?: ViewManager; teamDropdown?: TeamDropdownView; + currentServerName?: string; }; const status: WindowManagerStatus = {}; @@ -338,6 +348,7 @@ function initializeViewManager() { status.viewManager = new ViewManager(status.config, status.mainWindow); status.viewManager.load(); status.viewManager.showInitial(); + status.currentServerName = status.config.teams.find((team) => team.order === 0)?.name; } } @@ -348,9 +359,11 @@ export function switchServer(serverName: string) { log.error('Cannot find server in config'); return; } + status.currentServerName = serverName; const lastActiveTab = server.tabs[server.lastActiveTab || 0]; const tabViewName = getTabViewName(serverName, lastActiveTab.name); status.viewManager?.showByName(tabViewName); + ipcMain.emit(UPDATE_SHORTCUT_MENU); } export function switchTab(serverName: string, tabName: string) { @@ -503,3 +516,7 @@ export function selectPreviousTab() { function handleGetDarkMode() { return status.config?.darkMode; } + +export function getCurrentTeamName() { + return status.currentServerName; +} diff --git a/src/renderer/components/MainPage.tsx b/src/renderer/components/MainPage.tsx index 99bfb008..90ae4e9f 100644 --- a/src/renderer/components/MainPage.tsx +++ b/src/renderer/components/MainPage.tsx @@ -400,6 +400,7 @@ export default class MainPage extends React.PureComponent { 0} diff --git a/src/renderer/components/TeamDropdownButton.tsx b/src/renderer/components/TeamDropdownButton.tsx index b53528a1..f8ce841f 100644 --- a/src/renderer/components/TeamDropdownButton.tsx +++ b/src/renderer/components/TeamDropdownButton.tsx @@ -10,6 +10,7 @@ import '../css/components/TeamDropdownButton.scss'; import '../css/compass-icons.css'; type Props = { + isDisabled?: boolean; activeServerName: string; totalMentionCount: number; hasUnreads: boolean; @@ -18,7 +19,7 @@ type Props = { } const TeamDropdownButton: React.FC = (props: Props) => { - const {activeServerName, totalMentionCount, hasUnreads, isMenuOpen, darkMode} = props; + const {isDisabled, activeServerName, totalMentionCount, hasUnreads, isMenuOpen, darkMode} = props; const handleToggleButton = (event: React.MouseEvent) => { event.preventDefault(); @@ -41,7 +42,9 @@ const TeamDropdownButton: React.FC = (props: Props) => { return (