[MM-22239] Downloads dropdown (#2227)

* WIP: show/hide temp downloads dropdown

* WIP: Position downloads dropdown correctly under the button

* WIP: Use correct width for dropdown so that right radius and shadows are displayed

* WIP: Add items to download list after finished downloading

* WIP: Add download item base components

* Add "clear all" functionality

* Use type Record<> for downloads saved in config

* Add styling to files in the downloads dropdown

* Open file in folder when clicking it from downloads dropdown. Center svg in parent element

* Update scrollbar styling

* Update scrollbar styling

* Update state of downloaded items if deleted from folder

* Add progress bar in downloads

* Use "x-uncompressed-content-length" in file downloads.

* Keep downloads open when clicking outside their browserview

* Use correct color for downloads dropdown button

* Add better styling to downloads dropdown button

* Allow only 50 download files maximum. Oldest file is being removed if reached

* Autoclose downloads dropdown after 4s of download finish

* Add file thumbnails

* Dont show second dialog if first dismissed

* Add red badge when downloads running and dropdown closed

* Add menu item for Downloads

* Add support for more code file extensions

* Open downloads dropdown instead of folder from the menu

* Run lint:js and fix problems

* Add tests for utils

* Fix issue with dropdown not displaying

* Remove unecessary comment

* Move downloads to separate json file, outside Config

* Add downloads dropdown menu for the 3-dot button

* Dont show dev tools for downloads

* Add cancel download functionality

* Add dark mode styling

* Use View state for downloadsMenu open state

* Fix some style issues

* Add image preview for downloaded images

* Remove extra devTool in weback config

* Fix issue with paths on windows

* Align items left in downloads menu

* Use pretty-bytes for file sizes

* Show download remaining time

* Close downloads dropdown when clicking outside

* Show different units in received bytes when they are different from the total units (kb/mb)

* Dont hide downloads when mattermost view is clicked

* Keep downloads open if download button is clicked

* Use closest() to check for download clicks

* Fix unit tests.
Add tests for new Views and downloadManager
Add @types/jest as devDependency for intellisense

* Remove unecessary tsconfig for jest

* Fix types error

* Add all critical tests for downloadsManager

* WIP: add e2e tests for downloads

* WIP: add e2e tests for downloads

* Rename downloads spec file

* WIP: make vscode debugger work for e2e tests

* Remove unused mock

* Remove defaults for v4 config

* Use electron-mocha for e2e debugger

* Fix e2e tests spawning JsonFileManager twice

* Add async fs functions and add tests for download item UI

* Add async fs functions and add tests for download item UI

* Improve tests with "waitForSelector" to wait for visible elements

* Wait for page load before assertions

* Add tests for file uploads/downloads

* Dont show native notification for completed downloads if dropdown is open

* Increment filenames if file already exists

* Fix antializing in downloads dropdown

* Fix styling of downloads header

* Increase dimensions of green/red icons in downloads

* Fix styling of 3-dot button

* Fix unit tests

* Show 3-dot button only on hover or click

* PR review fixes

* Revert vscode debug fixes

* Mock fs.constants

* Mock fs instead of JsonFileManager in downlaods tests

* Mock fs instead of JsonFileManager in downlaods tests

* Add necessary mocks for downloads manager

* Mark file as deleted if user deleted it

* Fix min-height of downloads dropdown and 3-dot icon position

* Add more tests

* Make size of downloads dropdown dynamic based on content

* Combine log statements

* Close 3-dot menu if user clicks elsewhere

* Move application updates inside downloads dropdown

* Fix update issues

* Fix ipc event payload

* Add missing prop

* Remove unused translations

* Fix failing test

* Fix version unknown

* Remove commented out component
This commit is contained in:
Tasos Boulis
2022-10-07 11:40:27 +03:00
committed by GitHub
parent cf6ca93627
commit 131b5fa2ac
74 changed files with 4805 additions and 264 deletions

View File

@@ -0,0 +1,223 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BrowserView, BrowserWindow, ipcMain, IpcMainEvent} from 'electron';
import log from 'electron-log';
import {CombinedConfig} from 'types/config';
import {CoordinatesToJsonType, DownloadedItem, DownloadsMenuOpenEventPayload} from 'types/downloads';
import {
CLOSE_DOWNLOADS_DROPDOWN_MENU,
DOWNLOADS_DROPDOWN_MENU_CANCEL_DOWNLOAD,
DOWNLOADS_DROPDOWN_MENU_CLEAR_FILE,
DOWNLOADS_DROPDOWN_MENU_OPEN_FILE,
DOWNLOADS_DROPDOWN_SHOW_FILE_IN_FOLDER,
EMIT_CONFIGURATION,
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 {
DOWNLOADS_DROPDOWN_FULL_WIDTH,
DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT,
DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH,
TAB_BAR_HEIGHT,
} from 'common/utils/constants';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import WindowManager from '../windows/windowManager';
import downloadsManager from 'main/downloadsManager';
export default class DownloadsDropdownMenuView {
open: boolean;
view: BrowserView;
bounds?: Electron.Rectangle;
item?: DownloadedItem;
coordinates?: CoordinatesToJsonType;
darkMode: boolean;
window: BrowserWindow;
windowBounds: Electron.Rectangle;
constructor(window: BrowserWindow, darkMode: boolean) {
this.open = false;
this.item = undefined;
this.coordinates = undefined;
this.window = window;
this.darkMode = darkMode;
this.windowBounds = this.window.getContentBounds();
this.bounds = this.getBounds(DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
const preload = getLocalPreload('downloadsDropdownMenu.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,
}});
this.view.webContents.loadURL(getLocalURLString('downloadsDropdownMenu.html'));
this.window.addBrowserView(this.view);
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.updateConfig);
ipcMain.on(REQUEST_DOWNLOADS_DROPDOWN_MENU_INFO, this.updateDownloadsDropdownMenu);
ipcMain.on(DOWNLOADS_DROPDOWN_MENU_OPEN_FILE, this.openFile);
ipcMain.on(DOWNLOADS_DROPDOWN_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);
}
updateItem = (event: IpcMainEvent, item: DownloadedItem) => {
log.debug('DownloadsDropdownMenuView.updateItem', {item});
this.item = item;
this.updateDownloadsDropdownMenu();
}
updateConfig = (event: IpcMainEvent, config: CombinedConfig) => {
log.debug('DownloadsDropdownMenuView.updateConfig');
this.darkMode = config.darkMode;
this.updateDownloadsDropdownMenu();
}
/**
* This is called every time the "window" is resized so that we can position
* the downloads dropdown at the correct position
*/
updateWindowBounds = () => {
log.debug('DownloadsDropdownMenuView.updateWindowBounds');
this.windowBounds = this.window.getContentBounds();
this.updateDownloadsDropdownMenu();
this.repositionDownloadsDropdownMenu();
}
updateDownloadsDropdownMenu = () => {
log.debug('DownloadsDropdownMenuView.updateDownloadsDropdownMenu');
this.view.webContents.send(
UPDATE_DOWNLOADS_DROPDOWN_MENU,
this.item,
this.darkMode,
);
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM, true, this.item);
this.repositionDownloadsDropdownMenu();
}
handleOpen = (event: IpcMainEvent, payload: DownloadsMenuOpenEventPayload = {} as DownloadsMenuOpenEventPayload) => {
log.debug('DownloadsDropdownMenuView.handleOpen', {bounds: this.bounds, payload});
if (!this.bounds) {
return;
}
const {item, coordinates} = payload;
log.debug('DownloadsDropdownMenuView.handleOpen', {item, coordinates});
this.open = true;
this.coordinates = coordinates;
this.item = item;
this.bounds = this.getBounds(DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
this.view.setBounds(this.bounds);
this.window.setTopBrowserView(this.view);
this.view.webContents.focus();
this.updateDownloadsDropdownMenu();
}
handleClose = () => {
log.debug('DownloadsDropdownMenuView.handleClose');
this.open = false;
this.item = undefined;
ipcMain.emit(UPDATE_DOWNLOADS_DROPDOWN_MENU_ITEM);
this.view.setBounds(this.getBounds(0, 0));
WindowManager.sendToRenderer(CLOSE_DOWNLOADS_DROPDOWN_MENU);
}
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);
}
}
openFile = () => {
downloadsManager.openFile(this.item);
this.handleClose();
}
clearFile = () => {
downloadsManager.clearFile(this.item);
this.handleClose();
}
cancelDownload = () => {
downloadsManager.cancelDownload(this.item);
this.handleClose();
}
showFileInFolder = (e: IpcMainEvent, item: DownloadedItem) => {
log.debug('DownloadsDropdownMenuView.showFileInFolder', {item});
downloadsManager.showFileInFolder(item);
this.handleClose();
}
getBounds = (width: number, height: number) => {
// MUST return integers
return {
x: this.getX(),
y: this.getY(),
width: Math.round(width),
height: Math.round(height),
};
}
getX = () => {
const result = (this.windowBounds.width - 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);
}
getY = () => {
const result = TAB_BAR_HEIGHT + (this.coordinates?.y || 0) + (this.coordinates?.height || 0);
return Math.round(result);
}
repositionDownloadsDropdownMenu = () => {
this.bounds = this.getBounds(DOWNLOADS_DROPDOWN_MENU_FULL_WIDTH, DOWNLOADS_DROPDOWN_MENU_FULL_HEIGHT);
if (downloadsManager.getIsOpen()) {
this.view.setBounds(this.bounds);
}
}
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();
}
}