mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Typescript - Completed download manager
This commit is contained in:
parent
eeb1ff295e
commit
883c0ba3af
@ -10,6 +10,7 @@ import { screenSnippet } from './screen-snippet-handler';
|
||||
import { activate, handleKeyPress } from './window-actions';
|
||||
import { windowHandler } from './window-handler';
|
||||
import {
|
||||
downloadManagerAction,
|
||||
isValidWindow,
|
||||
sanitize,
|
||||
setDataUrl,
|
||||
@ -131,7 +132,12 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
|
||||
if (typeof displayId === 'string' && typeof id === 'number') {
|
||||
windowHandler.createScreenSharingIndicatorWindow(event.sender, displayId, id);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case apiCmds.downloadManagerAction:
|
||||
if (typeof arg.path === 'string') {
|
||||
downloadManagerAction(arg.type, arg.path);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import { AppMenu } from './app-menu';
|
||||
import { config, IConfig } from './config-handler';
|
||||
import { handleChildWindow } from './pop-out-window-handler';
|
||||
import { enterFullScreen, leaveFullScreen, throttledWindowChanges } from './window-actions';
|
||||
import { createComponentWindow, getBounds } from './window-utils';
|
||||
import { createComponentWindow, getBounds, handleDownloadManager } from './window-utils';
|
||||
|
||||
interface ICustomBrowserWindowConstructorOpts extends Electron.BrowserWindowConstructorOptions {
|
||||
winKey: string;
|
||||
@ -206,6 +206,9 @@ export class WindowHandler {
|
||||
this.mainWindow.show();
|
||||
});
|
||||
|
||||
// Download manager
|
||||
this.mainWindow.webContents.session.on('will-download', handleDownloadManager);
|
||||
|
||||
// store window ref
|
||||
this.addWindow(this.windowOpts.winKey, this.mainWindow);
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import * as electron from 'electron';
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as filesize from 'filesize';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
import { isMac } from '../common/env';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { getGuid } from '../common/utils';
|
||||
import { screenSnippet } from './screen-snippet-handler';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
@ -224,4 +226,56 @@ export const getBounds = (winPos: Electron.Rectangle, defaultWidth: number, defa
|
||||
}
|
||||
}
|
||||
return { width: defaultWidth, height: defaultHeight };
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Open downloaded file
|
||||
* @param type
|
||||
* @param filePath
|
||||
*/
|
||||
export const downloadManagerAction = (type, filePath) => {
|
||||
if (type === 'open') {
|
||||
const openResponse = electron.shell.openExternal(`file:///${filePath}`);
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
if (!openResponse && focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message: i18n.t('The file you are trying to open cannot be found in the specified path.')(),
|
||||
title: i18n.t('File not Found')(),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const showResponse = electron.shell.showItemInFolder(filePath);
|
||||
const focusedWindow = electron.BrowserWindow.getFocusedWindow();
|
||||
if (!showResponse && focusedWindow && !focusedWindow.isDestroyed()) {
|
||||
electron.dialog.showMessageBox(focusedWindow, {
|
||||
message: i18n.t('The file you are trying to open cannot be found in the specified path.')(),
|
||||
title: i18n.t('File not Found')(),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays a UI with downloaded file similar to chrome
|
||||
* Invoked by webContents session's will-download event
|
||||
*
|
||||
* @param _event
|
||||
* @param item {Electron.DownloadItem}
|
||||
* @param webContents {Electron.WebContents}
|
||||
*/
|
||||
export const handleDownloadManager = (_event, item: Electron.DownloadItem, webContents: Electron.WebContents) => {
|
||||
// Send file path when download is complete
|
||||
item.once('done', (_e, state) => {
|
||||
if (state === 'completed') {
|
||||
const data = {
|
||||
_id: getGuid(),
|
||||
savedPath: item.getSavePath() || '',
|
||||
total: filesize(item.getTotalBytes() || 0),
|
||||
fileName: item.getFilename() || 'No name',
|
||||
};
|
||||
webContents.send('downloadCompleted', data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -20,6 +20,7 @@ export enum apiCmds {
|
||||
keyPress = 'key-press',
|
||||
closeWindow = 'close-window',
|
||||
openScreenSharingIndicator = 'open-screen-sharing-indicator',
|
||||
downloadManagerAction = 'download-manager-action',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
@ -43,6 +44,8 @@ export interface IApiArgs {
|
||||
keyCode: number;
|
||||
windowType: WindowTypes;
|
||||
displayId: string;
|
||||
path: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export type WindowTypes = 'screen-picker';
|
||||
|
215
src/renderer/components/download-manager.tsx
Normal file
215
src/renderer/components/download-manager.tsx
Normal file
@ -0,0 +1,215 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import * as React from 'react';
|
||||
|
||||
import { apiCmds, apiName } from '../../common/api-interface';
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
|
||||
const DOWNLOAD_MANAGER_NAMESPACE = 'DownloadManager';
|
||||
interface IDownloadManager {
|
||||
_id: string;
|
||||
fileName: string;
|
||||
savedPath: string;
|
||||
total: number;
|
||||
flashing: boolean;
|
||||
}
|
||||
|
||||
interface IManagerState {
|
||||
items: IDownloadManager[];
|
||||
showMainComponent: boolean;
|
||||
}
|
||||
|
||||
type mouseEventLi = React.MouseEvent<HTMLLIElement>;
|
||||
type mouseEventDiv = React.MouseEvent<HTMLDivElement>;
|
||||
|
||||
export default class DownloadManager extends React.Component<{}, IManagerState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onClose: () => this.close(),
|
||||
onInjectItem: (_event, item: IDownloadManager) => this.injectItem(item),
|
||||
onOpenFile: (id: string) => (_event: mouseEventLi | mouseEventDiv) => this.openFile(id),
|
||||
onShowInFinder: (id: string) => (_event: mouseEventLi) => this.showInFinder(id),
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
items: [],
|
||||
showMainComponent: false,
|
||||
};
|
||||
this.getFileDisplayName = this.getFileDisplayName.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
ipcRenderer.on('downloadCompleted', this.eventHandlers.onInjectItem);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
ipcRenderer.removeListener('downloadCompleted', this.eventHandlers.onInjectItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main react render component
|
||||
*/
|
||||
public render(): React.ReactNode {
|
||||
const mainFooter = document.getElementById('footer');
|
||||
const { items } = this.state;
|
||||
if (mainFooter) {
|
||||
items && items.length ? mainFooter.classList.remove('hidden') : mainFooter.classList.add('hidden');
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div id='download-manager-footer' className='download-bar'>
|
||||
<ul id='download-main'>
|
||||
{this.renderItems()}
|
||||
</ul>
|
||||
<span
|
||||
id='close-download-bar'
|
||||
className='close-download-bar tempo-icon tempo-icon--close'
|
||||
onClick={this.eventHandlers.onClose}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through the items downloaded
|
||||
* @param item
|
||||
*/
|
||||
private mapItems(item: IDownloadManager): JSX.Element | undefined {
|
||||
if (!item) return;
|
||||
const { _id, total, fileName }: IDownloadManager = item;
|
||||
|
||||
const fileDisplayName = this.getFileDisplayName(fileName);
|
||||
// TODO: fix flashing issue
|
||||
return (
|
||||
<li id={_id} className='download-element'>
|
||||
<div className='download-item' id='dl-item' onClick={this.eventHandlers.onOpenFile(_id)}>
|
||||
<div className='file'>
|
||||
<div id='download-progress' className='download-complete flash'>
|
||||
<span className='tempo-icon tempo-icon--download download-complete-color'/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='downloaded-filename'>
|
||||
<h1 className='text-cutoff'>
|
||||
{fileDisplayName}
|
||||
</h1>
|
||||
<span id='per'>
|
||||
{total}{i18n.t('Downloaded', DOWNLOAD_MANAGER_NAMESPACE)()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id='menu' className='caret tempo-icon tempo-icon--dropdown'>
|
||||
<div id='download-action-menu' className='download-action-menu'>
|
||||
<ul id={_id}>
|
||||
<li id='download-open' onClick={this.eventHandlers.onOpenFile(_id)}>
|
||||
{i18n.t('Open', DOWNLOAD_MANAGER_NAMESPACE)()}
|
||||
</li>
|
||||
<li id='download-show-in-folder' onClick={this.eventHandlers.onShowInFinder(_id)}>
|
||||
{i18n.t('Show in Folder', DOWNLOAD_MANAGER_NAMESPACE)()}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject items to global var
|
||||
* @param args
|
||||
*/
|
||||
private injectItem(args: IDownloadManager): void {
|
||||
const { items } = this.state;
|
||||
const allItems = [ ...items, ...[args] ];
|
||||
this.setState({ items: allItems, showMainComponent: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide main footer which comes from the client
|
||||
*/
|
||||
private close(): void {
|
||||
this.setState({
|
||||
showMainComponent: !this.state.showMainComponent,
|
||||
items: [],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the downloaded file
|
||||
*
|
||||
* @param id {string}
|
||||
*/
|
||||
private openFile(id: string): void {
|
||||
const { items } = this.state;
|
||||
const fileIndex = items.findIndex((item) => {
|
||||
return item._id === id;
|
||||
});
|
||||
|
||||
if (fileIndex !== -1) {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.downloadManagerAction,
|
||||
path: items[ fileIndex ].savedPath,
|
||||
type: 'open',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the downloaded file in finder/explorer
|
||||
*
|
||||
* @param id {string}
|
||||
*/
|
||||
private showInFinder(id: string): void {
|
||||
const { items } = this.state;
|
||||
const fileIndex = items.findIndex((item) => {
|
||||
return item._id === id;
|
||||
});
|
||||
|
||||
if (fileIndex !== -1) {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.downloadManagerAction,
|
||||
path: items[ fileIndex ].savedPath,
|
||||
type: 'show',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and constructs file name
|
||||
*
|
||||
* @param fileName
|
||||
*/
|
||||
private getFileDisplayName(fileName: string): string {
|
||||
const { items } = this.state;
|
||||
let fileNameCount = 0;
|
||||
let fileDisplayName = fileName;
|
||||
|
||||
/* Check if a file with the same name exists
|
||||
* (akin to the user downloading a file with the same name again)
|
||||
* in the download bar
|
||||
*/
|
||||
for (const value of items) {
|
||||
if (fileName === value.fileName) {
|
||||
fileNameCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/* If it exists, add a count to the name like how Chrome does */
|
||||
if (fileNameCount) {
|
||||
const extLastIndex = fileDisplayName.lastIndexOf('.');
|
||||
const fileCount = ' (' + fileNameCount + ')';
|
||||
|
||||
fileDisplayName = fileDisplayName.slice(0, extLastIndex) + fileCount + fileDisplayName.slice(extLastIndex);
|
||||
}
|
||||
|
||||
return fileDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of items
|
||||
*/
|
||||
private renderItems(): Array<JSX.Element | undefined> {
|
||||
return this.state.items.map((items) => this.mapItems(items)).reverse();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { i18n } from '../common/i18n-preload';
|
||||
import DownloadManager from './components/download-manager';
|
||||
import SnackBar from './components/snack-bar';
|
||||
import WindowsTitleBar from './components/windows-title-bar';
|
||||
import { SSFApi } from './ssf-api';
|
||||
@ -51,6 +52,13 @@ ipcRenderer.on('page-load', (_event, { isWindowsOS, locale, resources }) => {
|
||||
// injects snack bar
|
||||
const snackBar = React.createElement(SnackBar);
|
||||
ReactDOM.render(snackBar, document.body.appendChild(document.createElement( 'div' )));
|
||||
|
||||
// injects download manager contents
|
||||
const downloadManager = React.createElement(DownloadManager);
|
||||
const footerSFE = document.getElementById('footer');
|
||||
if (footerSFE) {
|
||||
ReactDOM.render(downloadManager, footerSFE);
|
||||
}
|
||||
});
|
||||
|
||||
// Creates a custom tile bar for Windows
|
||||
|
Loading…
Reference in New Issue
Block a user