ELECTRON-1368 - Fix SnackBar & DownloadManager in pop-out (#740)

This commit is contained in:
Kiran Niranjan
2019-07-19 14:30:07 +05:30
committed by Vishwas Shashidhar
parent 9c12dd468e
commit abfda29141
13 changed files with 403 additions and 513 deletions

View File

@@ -72,7 +72,9 @@
<br/>
<input type="button" id="download-file1" value="Download"/>
<input type="button" id="download-file2" value="Download"/>
<div id="footer"></div>
<div id="footer" class="hidden">
<div id="download-manager-footer" class="download-bar"></div>
</div>
<br>
<hr>
<p>

View File

@@ -2,6 +2,7 @@
<head>
<meta charset="UTF-8">
<title>Test pop-out window</title>
<link rel="stylesheet" href="download-manager.css">
</head>
Test Window has been opened
<p>
@@ -18,8 +19,14 @@ Test Window has been opened
<br>
<a href="https://symphony.com" target="_blank">Open Symphony</a>
<hr>
<textarea id="text-val" rows="4">Writes some thing to the file</textarea>
<br/>
<input type="button" id="download-file1" value="Download"/>
<input type="button" id="download-file2" value="Download"/>
<div id="footer" class="hidden">
<div id="download-manager-footer" class="download-bar"></div>
</div>
<p>Badge Count:<p>
<button id='inc-badge'>increment badge count</button>
<br>
@@ -47,5 +54,34 @@ Test Window has been opened
window.open('https://symphony.com');
});
/**
* Download Manager api handler
*/
const download = (filename, text) => {
const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
// Start file download.
document.getElementById('download-file1').addEventListener('click', () => {
const filename = "hello.txt";
const text = document.getElementById("text-val").value;
download(filename, text);
}, false);
document.getElementById('download-file2').addEventListener('click', () => {
const filename = "bye.txt";
const text = document.getElementById("text-val").value;
download(filename, text);
}, false);
</script>
</html>

View File

@@ -1,19 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`download manager should render correctly 1`] = `
<div>
<div
className="download-bar"
id="download-manager-footer"
>
<ul
id="download-main"
/>
<span
className="close-download-bar tempo-icon tempo-icon--close"
id="close-download-bar"
onClick={[Function]}
/>
</div>
</div>
`;

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`snack bar should render correctly 1`] = `<div />`;

View File

@@ -1,124 +0,0 @@
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import DownloadManager from '../src/renderer/components/download-manager';
describe('download manager', () => {
it('should render correctly', () => {
const wrapper = shallow(React.createElement(DownloadManager));
expect(wrapper).toMatchSnapshot();
});
it('should call `close` correctly', () => {
const spy: jest.SpyInstance = jest.spyOn(DownloadManager.prototype, 'setState');
const wrapper: ShallowWrapper = shallow(React.createElement(DownloadManager));
wrapper.find('#close-download-bar').simulate('click');
expect(spy).toBeCalledWith({ items: [], showMainComponent: true });
});
it('should call `injectItem` correctly', () => {
const { ipcRenderer } = require('./__mocks__/electron');
const spy: jest.SpyInstance = jest.spyOn(DownloadManager.prototype, 'setState');
const objectDownloadCompleted: object = {
_id: 1,
fileName: 'test.png',
savedPath: 'path://test',
total: 1,
flashing: true,
};
shallow(React.createElement(DownloadManager));
ipcRenderer.send('downloadCompleted', objectDownloadCompleted);
expect(spy).toBeCalledWith({
items: [{
_id: 1,
count: 0,
fileName: 'test.png',
savedPath: 'path://test',
total: 1,
flashing: true,
}], showMainComponent: true,
});
});
describe('download element ready', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
wrapper = shallow(React.createElement(DownloadManager));
wrapper.setState({
items: [
{
_id: 1,
fileName: 'test.png',
savedPath: 'path://test',
total: 1,
flashing: true,
}],
});
});
it('should exist `download-element` class when there are items', () => {
expect(wrapper.find('.download-element')).toHaveLength(1);
});
it('should call `onOpenFile` correctly', () => {
const { ipcRenderer } = require('./__mocks__/electron');
const sendEventLabel: string = 'send';
const spy: jest.SpyInstance = jest.spyOn(ipcRenderer, sendEventLabel);
wrapper.find('#download-open').simulate('click');
expect(spy).toBeCalledWith('symphony-api', {
cmd: 'download-manager-action',
path: 'path://test',
type: 'open',
});
});
it('should call `showInFinder` correctly', () => {
const { ipcRenderer } = require('./__mocks__/electron');
const sendEventLabel: string = 'send';
const spy: jest.SpyInstance = jest.spyOn(ipcRenderer, sendEventLabel);
wrapper.find('#download-show-in-folder').simulate('click');
expect(spy).toBeCalledWith('symphony-api', {
cmd: 'download-manager-action',
path: 'path://test',
type: 'show',
});
});
});
describe('download completed event', () => {
const { ipcRenderer } = require('./__mocks__/electron');
const downloadCompletedLabelEvent: string = 'downloadCompleted';
const onLabelEvent: string = 'on';
const removeListenerLabelEvent: string = 'removeListener';
it('should call the `downloadCompleted event when component is mounted', () => {
const spy: jest.SpyInstance = jest.spyOn(ipcRenderer, onLabelEvent);
shallow(React.createElement(DownloadManager));
expect(spy).toBeCalledWith(downloadCompletedLabelEvent, expect.any(Function));
});
it('should remove listen `downloadCompleted` when component is unmounted', () => {
const spyMount: jest.SpyInstance = jest.spyOn(ipcRenderer, onLabelEvent);
const spyUnmount: jest.SpyInstance = jest.spyOn(ipcRenderer, removeListenerLabelEvent);
const wrapper: ShallowWrapper = shallow(React.createElement(DownloadManager));
expect(spyMount).toBeCalledWith(downloadCompletedLabelEvent, expect.any(Function));
wrapper.unmount();
expect(spyUnmount).toBeCalledWith(downloadCompletedLabelEvent, expect.any(Function));
});
});
});

View File

@@ -1,56 +0,0 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import SnackBar from '../src/renderer/components/snack-bar';
import { ipcRenderer } from './__mocks__/electron';
describe('snack bar', () => {
beforeEach(() => {
jest.useFakeTimers();
jest.restoreAllMocks();
});
// events label
const onEventLabel = 'on';
const removeListenerEventLabel = 'removeListener';
const windowEnterFullScreenEventLabel = 'window-enter-full-screen';
const windowLeaveFullScreenEventLabel = 'window-leave-full-screen';
it('should render correctly', () => {
const wrapper = shallow(React.createElement(SnackBar));
expect(wrapper).toMatchSnapshot();
});
it('should call mount correctly', () => {
const spy = jest.spyOn(ipcRenderer, onEventLabel);
shallow(React.createElement(SnackBar));
expect(spy).nthCalledWith(1, windowEnterFullScreenEventLabel, expect.any(Function));
expect(spy).nthCalledWith(2, windowLeaveFullScreenEventLabel, expect.any(Function));
});
it('should call unmount correctly', () => {
const spy = jest.spyOn(ipcRenderer, removeListenerEventLabel);
shallow(React.createElement(SnackBar)).unmount();
expect(spy).nthCalledWith(1, windowEnterFullScreenEventLabel, expect.any(Function));
expect(spy).nthCalledWith(2, windowLeaveFullScreenEventLabel, expect.any(Function));
});
it('should call `removeSnackBar` correctly', () => {
const spy = jest.spyOn(SnackBar.prototype, 'setState');
const expectedValue = { show: false };
shallow(React.createElement(SnackBar));
ipcRenderer.send(windowLeaveFullScreenEventLabel, null);
expect(spy).lastCalledWith(expectedValue);
jest.runOnlyPendingTimers();
});
it('should call `showSnackBar` correctly', () => {
const spy = jest.spyOn(SnackBar.prototype, 'setState');
const expectedValueFirst = { show: true };
const expectedValueSecond = { show: false };
shallow(React.createElement(SnackBar));
ipcRenderer.send(windowEnterFullScreenEventLabel, null);
expect(setTimeout).lastCalledWith(expect.any(Function), 3000);
expect(spy).nthCalledWith(1, expectedValueFirst);
jest.runOnlyPendingTimers();
expect(spy).nthCalledWith(2, expectedValueSecond);
});
});

View File

@@ -38,7 +38,6 @@ const saveWindowSettings = async (): Promise<void> => {
if (browserWindow.winName === apiName.mainWindowName) {
const isMaximized = browserWindow.isMaximized();
const isFullScreen = browserWindow.isFullScreen();
browserWindow.webContents.send(isFullScreen ? 'window-enter-full-screen' : 'window-leave-full-screen');
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ x, y, width, height, isMaximized, isFullScreen } } });
}
@@ -47,20 +46,24 @@ const saveWindowSettings = async (): Promise<void> => {
};
const windowMaximized = async (): Promise<void> => {
const windowMaximized = async (args: string | undefined): Promise<void> => {
const browserWindow = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow;
if (browserWindow && windowExists(browserWindow) && browserWindow.winName === apiName.mainWindowName) {
if (browserWindow && windowExists(browserWindow)) {
const isMaximized = browserWindow.isMaximized();
const isFullScreen = browserWindow.isFullScreen();
browserWindow.webContents.send(isFullScreen ? 'window-enter-full-screen' : 'window-leave-full-screen');
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } } });
if (args && typeof args === 'string') {
browserWindow.webContents.send(isFullScreen ? 'window-enter-full-screen' : 'window-leave-full-screen');
}
if (browserWindow.winName === apiName.mainWindowName) {
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } } });
}
}
};
const throttledWindowChanges = throttle(async () => {
const throttledWindowChanges = throttle(async (args) => {
await saveWindowSettings();
await windowMaximized();
await windowMaximized(args);
notification.moveNotificationToTop();
}, 1000);
@@ -202,10 +205,10 @@ export const monitorWindowActions = (window: BrowserWindow): void => {
window.on(event, throttledWindowChanges);
}
});
window.on('enter-full-screen', throttledWindowChanges);
window.on('enter-full-screen', throttledWindowChanges.bind(null, 'enter-full-screen'));
window.on('maximize', throttledWindowChanges);
window.on('leave-full-screen', throttledWindowChanges);
window.on('leave-full-screen', throttledWindowChanges.bind(null,'leave-full-screen'));
window.on('unmaximize', throttledWindowChanges);
if ((window as ICustomBrowserWindow).winName === apiName.mainWindowName) {

View File

@@ -16,7 +16,7 @@ import { config, IConfig } from './config-handler';
import { SpellChecker } from './spell-check-handler';
import { checkIfBuildExpired } from './ttl-handler';
import DesktopCapturerSource = Electron.DesktopCapturerSource;
import { IVersionInfo, versionHandler } from './version-handler.js';
import { IVersionInfo, versionHandler } from './version-handler';
import { handlePermissionRequests, monitorWindowActions } from './window-actions';
import {
createComponentWindow,

View File

@@ -0,0 +1,266 @@
import { ipcRenderer } from 'electron';
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;
count: number;
}
interface IManagerState {
items: IDownloadManager[];
showMainComponent: boolean;
}
export default class DownloadManager {
private readonly eventHandlers = {
onInjectItem: (_event, item: IDownloadManager) => this.injectItem(item),
};
private readonly itemsContainer: HTMLElement | null;
private readonly closeButton: HTMLElement | null;
private domParser: DOMParser;
private state: IManagerState;
constructor() {
this.state = {
items: [],
showMainComponent: false,
};
this.domParser = new DOMParser();
const parsedDownloadBar = this.domParser.parseFromString(this.render(), 'text/html');
this.itemsContainer = parsedDownloadBar.getElementById('download-main');
this.closeButton = parsedDownloadBar.getElementById('close-download-bar');
if (this.closeButton) {
this.closeButton.addEventListener('click', () => this.close());
}
this.getFileDisplayName = this.getFileDisplayName.bind(this);
}
/**
* initializes the event listeners
*/
public initDownloadManager(): void {
ipcRenderer.on('downloadCompleted', this.eventHandlers.onInjectItem);
}
/**
* Main react render component
*/
public render(): string {
return (`
<div id='download-manager' class='download-bar'>
<ul id='download-main' />
<span
id='close-download-bar'
class='close-download-bar tempo-icon tempo-icon--close' />
</div>
`);
}
/**
* Toggles footer visibility class based on download items
*/
private showOrHideDownloadBar(): void {
const mainFooter = document.getElementById('footer');
const { items } = this.state;
if (mainFooter) {
items && items.length ? mainFooter.classList.remove('hidden') : mainFooter.classList.add('hidden');
}
}
/**
* Loop through the items downloaded
*
* @param item {IDownloadManager}
*/
private renderItem(item: IDownloadManager): void {
const { _id, total, fileName }: IDownloadManager = item;
const fileDisplayName = this.getFileDisplayName(fileName, item);
const itemContainer = document.getElementById('download-main');
const parsedItem = this.domParser.parseFromString(`
<li id=${_id} class='download-element' title="${fileDisplayName}">
<div class='download-item' id='dl-item'>
<div class='file'>
<div id='download-progress' class='download-complete flash'>
<span class='tempo-icon tempo-icon--download download-complete-color'/>
</div>
</div>
<div class='downloaded-filename'>
<h1 class='text-cutoff'>
${fileDisplayName}
</h1>
<span id='per' title="${total} ${i18n.t('downloaded', DOWNLOAD_MANAGER_NAMESPACE)()}">
${total} ${i18n.t('downloaded', DOWNLOAD_MANAGER_NAMESPACE)()}
</span>
</div>
</div>
<div id='menu' class='caret tempo-icon tempo-icon--dropdown'>
<div id='download-action-menu' class='download-action-menu' style={width: '200px'}>
<ul id={_id}>
<li id='download-open' title="${i18n.t('Open', DOWNLOAD_MANAGER_NAMESPACE)()}">
${i18n.t('Open', DOWNLOAD_MANAGER_NAMESPACE)()}
</li>
<li id='download-show-in-folder' title="${i18n.t('Show in Folder', DOWNLOAD_MANAGER_NAMESPACE)()}">
${i18n.t('Show in Folder', DOWNLOAD_MANAGER_NAMESPACE)()}
</li>
</ul>
</div>
</div>
</li>`,
'text/html');
const progress = parsedItem.getElementById('download-progress');
const domItem = parsedItem.getElementById(_id);
// add event listeners
this.attachEventListener('dl-item', parsedItem, _id);
this.attachEventListener('download-open', parsedItem, _id);
this.attachEventListener('download-show-in-folder', parsedItem, _id);
if (itemContainer && domItem) {
itemContainer.prepend(domItem);
}
setTimeout(() => {
if (progress) {
progress.classList.remove('flash');
}
}, 4000);
}
/**
* Inject items to global var
*
* @param args {IDownloadManager}
*/
private injectItem(args: IDownloadManager): void {
const { items } = this.state;
let itemCount = 0;
for (const item of items) {
if (args.fileName === item.fileName) {
itemCount++;
}
}
args.count = itemCount;
const newItem = { ...args, ...{ flashing: true } };
const allItems = [ ...items, ...[ newItem ] ];
this.state = { items: allItems, showMainComponent: true };
// inserts download bar once
const downloadBar = document.getElementById('download-manager-footer');
if (this.itemsContainer && this.closeButton) {
this.showOrHideDownloadBar();
if (downloadBar) {
downloadBar.appendChild(this.itemsContainer);
downloadBar.appendChild(this.closeButton);
}
}
// appends items to the download bar
this.renderItem(newItem);
}
/**
* adds event listener for the give id
*
* @param id {String}
* @param item {Document}
* @param itemId {String}
*/
private attachEventListener(id: string, item: Document, itemId: string): void {
if (!item) {
return;
}
const element = item.getElementById(id);
if (element) {
switch (id) {
case 'dl-item':
case 'download-open':
element.addEventListener('click', () => this.openFile(itemId));
break;
case 'download-show-in-folder':
element.addEventListener('click', () => this.showInFinder(itemId));
}
}
}
/**
* Show or hide main footer which comes from the client
*/
private close(): void {
this.state = {
showMainComponent: !this.state.showMainComponent,
items: [],
};
if (this.itemsContainer) {
this.itemsContainer.innerHTML = '';
}
this.showOrHideDownloadBar();
}
/**
* 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 {String}
* @param item {IDownloadManager}
*/
private getFileDisplayName(fileName: string, item: IDownloadManager): string {
/* If it exists, add a count to the name like how Chrome does */
if (item.count > 0) {
const extLastIndex = fileName.lastIndexOf('.');
const fileCount = ' (' + item.count + ')';
fileName = fileName.slice(0, extLastIndex) + fileCount + fileName.slice(extLastIndex);
}
return fileName;
}
}

View File

@@ -1,220 +0,0 @@
import classNames from 'classnames';
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;
count: number;
}
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, flashing }: IDownloadManager = item;
setTimeout(() => {
const { items } = this.state;
const index = items.findIndex((i) => i._id === _id);
if (index !== -1) {
items[index].flashing = false;
this.setState({
items,
});
}
}, 4000);
const fileDisplayName = this.getFileDisplayName(fileName, item);
return (
<li key={_id} 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={classNames('download-complete', { flash: flashing })}>
<span className='tempo-icon tempo-icon--download download-complete-color'/>
</div>
</div>
<div className='downloaded-filename'>
<h1 className='text-cutoff' title={fileDisplayName}>
{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' style={{width: '200px'}}>
<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;
let itemCount = 0;
for (const item of items) {
if (args.fileName === item.fileName) {
itemCount++;
}
}
args.count = itemCount;
const allItems = [ ...items, ...[ { ...args, ...{ flashing: true } } ] ];
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
* @param item
*/
private getFileDisplayName(fileName: string, item: IDownloadManager): string {
/* If it exists, add a count to the name like how Chrome does */
if (item.count > 0) {
const extLastIndex = fileName.lastIndexOf('.');
const fileCount = ' (' + item.count + ')';
fileName = fileName.slice(0, extLastIndex) + fileCount + fileName.slice(extLastIndex);
}
return fileName;
}
/**
* Map of items
*/
private renderItems(): Array<JSX.Element | undefined> {
return this.state.items.map((items) => this.mapItems(items)).reverse();
}
}

View File

@@ -0,0 +1,79 @@
import { ipcRenderer } from 'electron';
import Timer = NodeJS.Timer;
import { i18n } from '../../common/i18n-preload';
const SNACKBAR_NAMESPACE = 'SnackBar';
export default class SnackBar {
private snackBarTimer: Timer | undefined;
private domParser: DOMParser;
private readonly body: HTMLCollectionOf<Element>;
private readonly snackBar: HTMLElement | null;
constructor() {
this.body = document.getElementsByTagName('body');
this.domParser = new DOMParser();
const snackBar = this.domParser.parseFromString(this.render(), 'text/html');
this.snackBar = snackBar.getElementById('snack-bar');
}
/**
* initializes the event listeners
*/
public initSnackBar(): void {
this.showSnackBar = this.showSnackBar.bind(this);
this.removeSnackBar = this.removeSnackBar.bind(this);
ipcRenderer.on('window-enter-full-screen', this.showSnackBar);
ipcRenderer.on('window-leave-full-screen', this.removeSnackBar);
}
/**
* Displays snackbar for 3sec
*/
public showSnackBar(): void {
setTimeout(() => {
if (this.body && this.body.length > 0 && this.snackBar) {
this.body[ 0 ].appendChild(this.snackBar);
this.snackBar.classList.add('SnackBar-show');
this.snackBarTimer = setTimeout(() => {
if (this.snackBar) {
if (document.getElementById('snack-bar')) {
this.body[ 0 ].removeChild(this.snackBar);
}
}
}, 3000);
}
}, 100);
}
/**
* Removes snackbar
*/
public removeSnackBar(): void {
if (this.body && this.body.length > 0 && this.snackBar) {
if (document.getElementById('snack-bar')) {
this.body[ 0 ].removeChild(this.snackBar);
if (this.snackBarTimer) {
clearTimeout(this.snackBarTimer);
}
}
}
}
/**
* Renders the custom title bar
*/
public render(): string {
return (
`<div id='snack-bar' class='SnackBar'>
<span >${i18n.t('Press ', SNACKBAR_NAMESPACE)()}</span>
<span class='SnackBar-esc'>${i18n.t('esc', SNACKBAR_NAMESPACE)()}</span>
<span >${i18n.t(' to exit full screen', SNACKBAR_NAMESPACE)()}</span>
</div>`
);
}
}

View File

@@ -1,69 +0,0 @@
import { ipcRenderer } from 'electron';
import * as React from 'react';
import Timer = NodeJS.Timer;
import { i18n } from '../../common/i18n-preload';
interface IState {
show: boolean;
}
const SNACKBAR_NAMESPACE = 'SnackBar';
export default class SnackBar extends React.Component<{}, IState> {
private snackBarTimer: Timer | undefined;
constructor(props) {
super(props);
this.state = {
show: false,
};
this.showSnackBar = this.showSnackBar.bind(this);
this.removeSnackBar = this.removeSnackBar.bind(this);
}
public componentDidMount(): void {
ipcRenderer.on('window-enter-full-screen', this.showSnackBar);
ipcRenderer.on('window-leave-full-screen', this.removeSnackBar);
}
public componentWillUnmount(): void {
ipcRenderer.removeListener('window-enter-full-screen', this.showSnackBar);
ipcRenderer.removeListener('window-leave-full-screen', this.removeSnackBar);
}
/**
* Displays snackbar for 3sec
*/
public showSnackBar(): void {
this.setState({ show: true });
this.snackBarTimer = setTimeout(() => {
this.removeSnackBar();
}, 3000);
}
/**
* Removes snackbar
*/
public removeSnackBar(): void {
this.setState({ show: false });
if (this.snackBarTimer) {
clearTimeout(this.snackBarTimer);
}
}
/**
* Renders the custom title bar
*/
public render(): JSX.Element | null {
const { show } = this.state;
return show ? (
<div className='SnackBar SnackBar-show'>
<span >{i18n.t('Press ', SNACKBAR_NAMESPACE)()}</span>
<span className='SnackBar-esc'>{i18n.t('esc', SNACKBAR_NAMESPACE)()}</span>
<span >{i18n.t(' to exit full screen', SNACKBAR_NAMESPACE)()}</span>
</div>
) : <div />;
}
}

View File

@@ -65,17 +65,12 @@ ipcRenderer.on('page-load', (_event, { locale, resources, enableCustomTitleBar,
});
// injects snack bar
const snackBar = React.createElement(SnackBar);
const snackBarContainer = document.createElement( 'div' );
document.body.appendChild(snackBarContainer);
ReactDOM.render(snackBar, snackBarContainer);
const snackBar = new SnackBar();
snackBar.initSnackBar();
// injects download manager contents
const downloadManager = React.createElement(DownloadManager);
const footerSFE = document.getElementById('footer');
if (footerSFE) {
ReactDOM.render(downloadManager, footerSFE);
}
const downloadManager = new DownloadManager();
downloadManager.initDownloadManager();
if (isMainWindow) {
setInterval(async () => {