Files
mattermostest/src/main/views/downloadsDropdownMenuView.ts
Devin Binnie 10295162e0 [MM-60086][MM-60610] Implement performanceMonitor, collect CPU/memory usage data and send via API (#3165)
* [MM-60086][MM-60610] Implement performanceMonitor, collect CPU/memory usage data and send via API

* Translations

* PR feedback

* Update api-types package
2024-10-18 10:13:39 -04:00

218 lines
7.8 KiB
TypeScript

// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {IpcMainEvent} from 'electron';
import {BrowserView, ipcMain} from 'electron';
import {
CLOSE_DOWNLOADS_DROPDOWN_MENU,
DOWNLOADS_DROPDOWN_MENU_CANCEL_DOWNLOAD,
DOWNLOADS_DROPDOWN_MENU_CLEAR_FILE,
DOWNLOADS_DROPDOWN_MENU_OPEN_FILE,
DOWNLOADS_DROPDOWN_MENU_SHOW_FILE_IN_FOLDER,
EMIT_CONFIGURATION,
MAIN_WINDOW_CREATED,
MAIN_WINDOW_RESIZED,
OPEN_DOWNLOADS_DROPDOWN_MENU,
REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO,
TOGGLE_DOWNLOADS_DROPDOWN_MENU,
UPDATE_DOWNLOADS_DROPDOWN_MENU,
UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM,
} from 'common/communication';
import Config from 'common/config';
import {Logger} from 'common/log';
import {
DOWNLOADS_DROPDOWN_FULL_WIDTH,
DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT,
DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH,
TAB_BAR_HEIGHT,
} from 'common/utils/constants';
import downloadsManager from 'main/downloadsManager';
import performanceMonitor from 'main/performanceMonitor';
import {getLocalPreload} from 'main/utils';
import MainWindow from 'main/windows/mainWindow';
import type {CoordinatesToJsonType, DownloadedItem, DownloadsMenuOpenEventPayload} from 'types/downloads';
const log = new Logger('DownloadsDropdownMenuView');
export class DownloadsDropdownMenuView {
private open: boolean;
private view?: BrowserView;
private bounds?: Electron.Rectangle;
private item?: DownloadedItem;
private coordinates?: CoordinatesToJsonType;
private windowBounds?: Electron.Rectangle;
constructor() {
this.open = false;
MainWindow.on(MAIN_WINDOW_CREATED, this.init);
MainWindow.on(MAIN_WINDOW_RESIZED, this.updateWindowBounds);
ipcMain.on(OPEN_DOWNLOADS_DROPDOWN_MENU, this.handleOpen);
ipcMain.on(CLOSE_DOWNLOADS_DROPDOWN_MENU, this.handleClose);
ipcMain.on(TOGGLE_DOWNLOADS_DROPDOWN_MENU, this.handleToggle);
ipcMain.on(EMIT_CONFIGURATION, this.updateDownloadsDropdownMenu);
ipcMain.on(REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO, this.updateDownloadsDropdownMenu);
ipcMain.on(DOWNLOADS_DROPDOWN_MENU_OPEN_FILE, this.openFile);
ipcMain.on(DOWNLOADS_DROPDOWN_MENU_SHOW_FILE_IN_FOLDER, this.showFileInFolder);
ipcMain.on(DOWNLOADS_DROPDOWN_MENU_CANCEL_DOWNLOAD, this.cancelDownload);
ipcMain.on(DOWNLOADS_DROPDOWN_MENU_CLEAR_FILE, this.clearFile);
ipcMain.on(UPDATE_DOWNLOADS_DROPDOWN_MENU, this.updateItem);
}
private init = () => {
this.windowBounds = MainWindow.getBounds();
if (!this.windowBounds) {
throw new Error('Cannot initialize downloadsDropdownMenuView, missing MainWindow');
}
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
const preload = getLocalPreload('internalAPI.js');
this.view = new BrowserView({webPreferences: {
preload,
// Workaround for this issue: https://github.com/electron/electron/issues/30993
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
transparent: true,
}});
performanceMonitor.registerView('DownloadsDropdownMenuView', this.view.webContents);
this.view.webContents.loadURL('mattermost-desktop://renderer/downloadsDropdownMenu.html');
MainWindow.get()?.addBrowserView(this.view);
};
/**
* This is called every time the "window" is resized so that we can position
* the downloads dropdown at the correct position
*/
private updateWindowBounds = (newBounds: Electron.Rectangle) => {
log.silly('updateWindowBounds');
this.windowBounds = newBounds;
this.updateDownloadsDropdownMenu();
this.repositionDownloadsDropdownMenu();
};
private updateItem = (event: IpcMainEvent, item: DownloadedItem) => {
log.debug('updateItem', {item});
this.item = item;
this.updateDownloadsDropdownMenu();
};
private updateDownloadsDropdownMenu = () => {
log.silly('updateDownloadsDropdownMenu');
this.view?.webContents.send(
UPDATE_DOWNLOADS_DROPDOWN_MENU,
this.item,
Config.darkMode,
);
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM, true, this.item);
this.repositionDownloadsDropdownMenu();
};
private handleOpen = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload = {} as DownloadsMenuOpenEventPayload) => {
log.debug('handleOpen', {bounds: this.bounds, payload});
if (!(this.bounds && this.view && this.windowBounds)) {
return;
}
const {item, coordinates} = payload;
log.debug('handleOpen', {item, coordinates});
this.open = true;
this.coordinates = coordinates;
this.item = item;
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
this.view.setBounds(this.bounds);
MainWindow.get()?.setTopBrowserView(this.view);
this.view.webContents.focus();
this.updateDownloadsDropdownMenu();
};
private handleClose = () => {
log.silly('handleClose');
this.open = false;
this.item = undefined;
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM);
this.view?.setBounds(this.getBounds(this.windowBounds?.width ?? 0, 0, 0));
MainWindow.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN_MENU);
};
private handleToggle = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload) => {
if (this.open) {
if (this.item?.location === payload.item.location) {
// clicking 3-dot in the same item
this.handleClose();
} else {
// clicking 3-dot in a different item
this.handleClose();
this.handleOpen(event, payload);
}
} else {
this.handleOpen(event, payload);
}
};
private openFile = () => {
downloadsManager.openFile(this.item);
this.handleClose();
};
private showFileInFolder = (e: IpcMainEvent, item: DownloadedItem) => {
downloadsManager.showFileInFolder(item);
this.handleClose();
};
private clearFile = () => {
downloadsManager.clearFile(this.item);
this.handleClose();
};
private cancelDownload = () => {
downloadsManager.cancelDownload(this.item);
this.handleClose();
};
private getBounds = (windowWidth: number, width: number, height: number) => {
// MUST return integers
return {
x: this.getX(windowWidth),
y: this.getY(),
width: Math.round(width),
height: Math.round(height),
};
};
private getX = (windowWidth: number) => {
const result = (windowWidth - DOWNLOADS_DROPDOWN_FULL_WIDTH - DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH) + (this.coordinates?.x || 0) + (this.coordinates?.width || 0);
if (result <= DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH) {
return 0;
}
return Math.round(result);
};
private getY = () => {
const result = TAB_BAR_HEIGHT + (this.coordinates?.y || 0) + (this.coordinates?.height || 0);
return Math.round(result);
};
private repositionDownloadsDropdownMenu = () => {
if (!this.windowBounds) {
return;
}
this.bounds = this.getBounds(this.windowBounds.width, DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
if (this.open) {
this.view?.setBounds(this.bounds);
}
};
}
const downloadsDropdownMenuView = new DownloadsDropdownMenuView();
export default downloadsDropdownMenuView;