Merge branch 'master' into export-csv

This commit is contained in:
Baltazar Gomez 2018-02-27 19:18:39 -07:00 committed by GitHub
commit 3505e439b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 178 deletions

View file

@ -8,6 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased] ## [Unreleased]
### Added ### Added
* Save app state when closing to tray ([#968](https://github.com/lbryio/lbry-app/issues/968))
* Added ability to export wallet transactions to JSON and CSV format ([#976](https://github.com/lbryio/lbry-app/pull/976)) * Added ability to export wallet transactions to JSON and CSV format ([#976](https://github.com/lbryio/lbry-app/pull/976))
* *
@ -16,7 +17,9 @@ Web UI version numbers should always match the corresponding version of LBRY App
* *
### Fixed ### Fixed
* * Fixed sort by date of published content ([#986](https://github.com/lbryio/lbry-app/issues/986))
* Fix night mode start time, set to 9PM (#1050)
* Fix night mode start time, set to 9PM ([#1050](https://github.com/lbryio/lbry-app/issues/1050))
* *
### Deprecated ### Deprecated

View file

@ -1,63 +0,0 @@
import { app, Menu, Tray as ElectronTray } from 'electron';
import path from 'path';
import createWindow from './createWindow';
export default class Tray {
window;
updateAttachedWindow;
tray;
constructor(window, updateAttachedWindow) {
this.window = window;
this.updateAttachedWindow = updateAttachedWindow;
}
create() {
let iconPath;
switch (process.platform) {
case 'darwin': {
iconPath = path.join(__static, '/img/tray/mac/trayTemplate.png');
break;
}
case 'win32': {
iconPath = path.join(__static, '/img/tray/windows/tray.ico');
break;
}
default: {
iconPath = path.join(__static, '/img/tray/default/tray.png');
}
}
this.tray = new ElectronTray(iconPath);
this.tray.on('double-click', () => {
if (!this.window || this.window.isDestroyed()) {
this.window = createWindow();
this.updateAttachedWindow(this.window);
} else {
this.window.show();
this.window.focus();
}
});
this.tray.setToolTip('LBRY App');
const template = [
{
label: `Open ${app.getName()}`,
click: () => {
if (!this.window || this.window.isDestroyed()) {
this.window = createWindow();
this.updateAttachedWindow(this.window);
} else {
this.window.show();
this.window.focus();
}
},
},
{ role: 'quit' },
];
const contextMenu = Menu.buildFromTemplate(template);
this.tray.setContextMenu(contextMenu);
}
}

41
src/main/createTray.js Normal file
View file

@ -0,0 +1,41 @@
import { app, Menu, Tray } from 'electron';
import path from 'path';
export default window => {
let iconPath;
switch (process.platform) {
case 'darwin': {
iconPath = path.join(__static, '/img/tray/mac/trayTemplate.png');
break;
}
case 'win32': {
iconPath = path.join(__static, '/img/tray/windows/tray.ico');
break;
}
default: {
iconPath = path.join(__static, '/img/tray/default/tray.png');
}
}
const tray = new Tray(iconPath);
tray.on('double-click', () => {
window.show();
});
tray.setToolTip('LBRY App');
const template = [
{
label: `Open ${app.getName()}`,
click: () => {
window.show();
},
},
{ role: 'quit' },
];
const contextMenu = Menu.buildFromTemplate(template);
tray.setContextMenu(contextMenu);
return tray;
};

View file

@ -2,7 +2,7 @@ import { app, BrowserWindow, dialog } from 'electron';
import setupBarMenu from './menu/setupBarMenu'; import setupBarMenu from './menu/setupBarMenu';
import setupContextMenu from './menu/setupContextMenu'; import setupContextMenu from './menu/setupContextMenu';
export default deepLinkingURIArg => { export default appState => {
let windowConfiguration = { let windowConfiguration = {
backgroundColor: '#155B4A', backgroundColor: '#155B4A',
minWidth: 800, minWidth: 800,
@ -35,11 +35,7 @@ export default deepLinkingURIArg => {
let deepLinkingURI; let deepLinkingURI;
// Protocol handler for win32 // Protocol handler for win32
if ( if (process.platform === 'win32' && String(process.argv[1]).startsWith('lbry')) {
!deepLinkingURIArg &&
process.platform === 'win32' &&
String(process.argv[1]).startsWith('lbry')
) {
// Keep only command line / deep linked arguments // Keep only command line / deep linked arguments
// Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to // Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
// restore the original URI that was typed. // restore the original URI that was typed.
@ -48,15 +44,16 @@ export default deepLinkingURIArg => {
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as // - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well. // an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
deepLinkingURI = process.argv[1].replace(/\/$/, '').replace('/#', '#'); deepLinkingURI = process.argv[1].replace(/\/$/, '').replace('/#', '#');
} else {
deepLinkingURI = deepLinkingURIArg;
} }
setupBarMenu(); setupBarMenu();
setupContextMenu(window); setupContextMenu(window);
window.on('closed', () => { window.on('close', event => {
window = null; if (!appState.isQuitting) {
event.preventDefault();
window.hide();
}
}); });
window.on('focus', () => { window.on('focus', () => {

View file

@ -8,7 +8,7 @@ import https from 'https';
import { shell, app, ipcMain, dialog } from 'electron'; import { shell, app, ipcMain, dialog } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import Daemon from './Daemon'; import Daemon from './Daemon';
import Tray from './Tray'; import createTray from './createTray';
import createWindow from './createWindow'; import createWindow from './createWindow';
autoUpdater.autoDownload = true; autoUpdater.autoDownload = true;
@ -32,11 +32,7 @@ let rendererWindow;
let tray; let tray;
let daemon; let daemon;
let isQuitting; const appState = {};
const updateRendererWindow = window => {
rendererWindow = window;
};
const installExtensions = async () => { const installExtensions = async () => {
// eslint-disable-next-line import/no-extraneous-dependencies,global-require // eslint-disable-next-line import/no-extraneous-dependencies,global-require
@ -64,7 +60,7 @@ app.on('ready', async () => {
daemon = new Daemon(); daemon = new Daemon();
daemon.on('exit', () => { daemon.on('exit', () => {
daemon = null; daemon = null;
if (!isQuitting) { if (!appState.isQuitting) {
dialog.showErrorBox( dialog.showErrorBox(
'Daemon has Exited', 'Daemon has Exited',
'The daemon may have encountered an unexpected error, or another daemon instance is already running.' 'The daemon may have encountered an unexpected error, or another daemon instance is already running.'
@ -77,15 +73,12 @@ app.on('ready', async () => {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
await installExtensions(); await installExtensions();
} }
rendererWindow = createWindow(); rendererWindow = createWindow(appState);
tray = new Tray(rendererWindow, updateRendererWindow); tray = createTray(rendererWindow);
tray.create();
}); });
app.on('activate', () => { app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the rendererWindow.show();
// dock icon is clicked and there are no other windows open.
if (!rendererWindow) rendererWindow = createWindow();
}); });
app.on('will-quit', event => { app.on('will-quit', event => {
@ -117,7 +110,7 @@ app.on('will-quit', event => {
return; return;
} }
isQuitting = true; appState.isQuitting = true;
if (daemon) daemon.quit(); if (daemon) daemon.quit();
}); });
@ -126,18 +119,13 @@ app.on('will-finish-launching', () => {
// Protocol handler for macOS // Protocol handler for macOS
app.on('open-url', (event, URL) => { app.on('open-url', (event, URL) => {
event.preventDefault(); event.preventDefault();
if (rendererWindow && !rendererWindow.isDestroyed()) { rendererWindow.webContents.send('open-uri-requested', URL);
rendererWindow.webContents.send('open-uri-requested', URL); rendererWindow.show();
rendererWindow.show();
rendererWindow.focus();
} else {
rendererWindow = createWindow(URL);
}
}); });
}); });
app.on('window-all-closed', () => { app.on('before-quit', () => {
// Subscribe to event so the app doesn't quit when closing the window. appState.isQuitting = true;
}); });
ipcMain.on('upgrade', (event, installerPath) => { ipcMain.on('upgrade', (event, installerPath) => {
@ -224,7 +212,7 @@ ipcMain.on('set-auth-token', (event, token) => {
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
dialog.showErrorBox('Error Encountered', `Caught error: ${error}`); dialog.showErrorBox('Error Encountered', `Caught error: ${error}`);
isQuitting = true; appState.isQuitting = true;
if (daemon) daemon.quit(); if (daemon) daemon.quit();
app.exit(1); app.exit(1);
}); });
@ -240,14 +228,8 @@ const isSecondInstance = app.makeSingleInstance(argv => {
URI = argv[1].replace(/\/$/, '').replace('/#', '#'); URI = argv[1].replace(/\/$/, '').replace('/#', '#');
} }
if (rendererWindow && !rendererWindow.isDestroyed()) { rendererWindow.webContents.send('open-uri-requested', URI);
rendererWindow.webContents.send('open-uri-requested', URI); rendererWindow.show();
rendererWindow.show();
rendererWindow.focus();
} else {
rendererWindow = createWindow(URI);
}
}); });
if (isSecondInstance) { if (isSecondInstance) {

View file

@ -9,12 +9,33 @@ class FileList extends React.PureComponent {
super(props); super(props);
this.state = { this.state = {
sortBy: 'date', sortBy: 'dateNew',
}; };
this._sortFunctions = { this._sortFunctions = {
date(fileInfos) { dateNew(fileInfos) {
return fileInfos.slice().reverse(); return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
const height1 = fileInfo1.height
const height2 = fileInfo2.height
if (height1 > height2) {
return -1;
} else if (height1 < height2) {
return 1;
}
return 0;
});
},
dateOld(fileInfos) {
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
const height1 = fileInfo1.height
const height2 = fileInfo2.height
if (height1 < height2) {
return -1;
} else if (height1 > height2) {
return 1;
}
return 0;
});
}, },
title(fileInfos) { title(fileInfos) {
return fileInfos.slice().sort((fileInfo1, fileInfo2) => { return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
@ -95,7 +116,8 @@ class FileList extends React.PureComponent {
<span className="sort-section"> <span className="sort-section">
{__('Sort by')}{' '} {__('Sort by')}{' '}
<FormField type="select" onChange={this.handleSortChanged.bind(this)}> <FormField type="select" onChange={this.handleSortChanged.bind(this)}>
<option value="date">{__('Date')}</option> <option value="dateNew">{__('Newest First')}</option>
<option value="dateOld">{__('Oldest First')}</option>
<option value="title">{__('Title')}</option> <option value="title">{__('Title')}</option>
</FormField> </FormField>
</span> </span>

View file

@ -1,5 +1,4 @@
const { remote } = require('electron'); import { remote } from 'electron';
import React from 'react'; import React from 'react';
import { Thumbnail } from 'component/common'; import { Thumbnail } from 'component/common';
import player from 'render-media'; import player from 'render-media';
@ -21,33 +20,21 @@ class VideoPlayer extends React.PureComponent {
this.togglePlayListener = this.togglePlay.bind(this); this.togglePlayListener = this.togglePlay.bind(this);
} }
componentWillReceiveProps(next) {
const el = this.refs.media.children[0];
if (!this.props.paused && next.paused && !el.paused) el.pause();
}
componentDidMount() { componentDidMount() {
const container = this.refs.media; const container = this.media;
const { const { contentType, changeVolume, volume, position, claim } = this.props;
contentType,
downloadPath,
mediaType,
changeVolume,
volume,
position,
claim,
uri,
} = this.props;
const loadedMetadata = e => { const loadedMetadata = () => {
this.setState({ hasMetadata: true, startedPlaying: true }); this.setState({ hasMetadata: true, startedPlaying: true });
this.refs.media.children[0].play(); this.media.children[0].play();
}; };
const renderMediaCallback = err => {
if (err) this.setState({ unplayable: true }); const renderMediaCallback = error => {
if (error) this.setState({ unplayable: true });
}; };
// Handle fullscreen change for the Windows platform // Handle fullscreen change for the Windows platform
const win32FullScreenChange = e => { const win32FullScreenChange = () => {
const win = remote.BrowserWindow.getFocusedWindow(); const win = remote.BrowserWindow.getFocusedWindow();
if (process.platform === 'win32') { if (process.platform === 'win32') {
win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu()); win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu());
@ -61,13 +48,13 @@ class VideoPlayer extends React.PureComponent {
player.append( player.append(
this.file(), this.file(),
container, container,
{ autoplay: false, controls: true }, { autoplay: true, controls: true },
renderMediaCallback.bind(this) renderMediaCallback.bind(this)
); );
} }
document.addEventListener('keydown', this.togglePlayListener); document.addEventListener('keydown', this.togglePlayListener);
const mediaElement = this.refs.media.children[0]; const mediaElement = this.media.children[0];
if (mediaElement) { if (mediaElement) {
mediaElement.currentTime = position || 0; mediaElement.currentTime = position || 0;
mediaElement.addEventListener('play', () => this.props.doPlay()); mediaElement.addEventListener('play', () => this.props.doPlay());
@ -87,29 +74,38 @@ class VideoPlayer extends React.PureComponent {
} }
} }
componentWillReceiveProps(next) {
const el = this.media.children[0];
if (!this.props.paused && next.paused && !el.paused) el.pause();
}
componentDidUpdate() {
const { contentType, downloadCompleted } = this.props;
const { startedPlaying } = this.state;
if (this.playableType() && !startedPlaying && downloadCompleted) {
const container = this.media.children[0];
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
this.renderAudio(this.media, true);
} else {
player.render(this.file(), container, {
autoplay: true,
controls: true,
});
}
}
}
componentWillUnmount() { componentWillUnmount() {
document.removeEventListener('keydown', this.togglePlayListener); document.removeEventListener('keydown', this.togglePlayListener);
const mediaElement = this.refs.media.children[0]; const mediaElement = this.media.children[0];
if (mediaElement) { if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlayListener); mediaElement.removeEventListener('click', this.togglePlayListener);
} }
this.props.doPause(); this.props.doPause();
} }
renderAudio(container, autoplay) {
if (container.firstChild) {
container.firstChild.remove();
}
// clear the container
const { downloadPath } = this.props;
const audio = document.createElement('audio');
audio.autoplay = autoplay;
audio.controls = true;
audio.src = downloadPath;
container.appendChild(audio);
}
togglePlay(event) { togglePlay(event) {
// ignore all events except click and spacebar keydown, or input events in a form control // ignore all events except click and spacebar keydown, or input events in a form control
if ( if (
@ -119,7 +115,7 @@ class VideoPlayer extends React.PureComponent {
return; return;
} }
event.preventDefault(); event.preventDefault();
const mediaElement = this.refs.media.children[0]; const mediaElement = this.media.children[0];
if (mediaElement) { if (mediaElement) {
if (!mediaElement.paused) { if (!mediaElement.paused) {
mediaElement.pause(); mediaElement.pause();
@ -129,24 +125,6 @@ class VideoPlayer extends React.PureComponent {
} }
} }
componentDidUpdate() {
const { contentType, downloadCompleted } = this.props;
const { startedPlaying } = this.state;
if (this.playableType() && !startedPlaying && downloadCompleted) {
const container = this.refs.media.children[0];
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
this.renderAudio(this.refs.media, true);
} else {
player.render(this.file(), container, {
autoplay: true,
controls: true,
});
}
}
}
file() { file() {
const { downloadPath, filename } = this.props; const { downloadPath, filename } = this.props;
@ -162,14 +140,26 @@ class VideoPlayer extends React.PureComponent {
return ['audio', 'video'].indexOf(mediaType) !== -1; return ['audio', 'video'].indexOf(mediaType) !== -1;
} }
renderAudio(container, autoplay) {
if (container.firstChild) {
container.firstChild.remove();
}
// clear the container
const { downloadPath } = this.props;
const audio = document.createElement('audio');
audio.autoplay = autoplay;
audio.controls = true;
audio.src = downloadPath;
container.appendChild(audio);
}
render() { render() {
const { mediaType, poster } = this.props; const { mediaType, poster } = this.props;
const { hasMetadata, unplayable } = this.state; const { hasMetadata, unplayable } = this.state;
const noMetadataMessage = 'Waiting for metadata.'; const noMetadataMessage = 'Waiting for metadata.';
const unplayableMessage = "Sorry, looks like we can't play this file."; const unplayableMessage = "Sorry, looks like we can't play this file.";
const needsMetadata = this.playableType();
return ( return (
<div> <div>
{['audio', 'application'].indexOf(mediaType) !== -1 && {['audio', 'application'].indexOf(mediaType) !== -1 &&
@ -179,7 +169,12 @@ class VideoPlayer extends React.PureComponent {
!hasMetadata && !hasMetadata &&
!unplayable && <LoadingScreen status={noMetadataMessage} />} !unplayable && <LoadingScreen status={noMetadataMessage} />}
{unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />} {unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />}
<div ref="media" className="media" /> <div
ref={container => {
this.media = container;
}}
className="media"
/>
</div> </div>
); );
} }

View file

@ -15,7 +15,7 @@ jsonrpc.call = (
return response.json().then(json => { return response.json().then(json => {
let error; let error;
if (json.error) { if (json.error) {
error = new Error(json.error); error = new Error(json.error.message);
} else { } else {
error = new Error('Protocol error with unknown response signature'); error = new Error('Protocol error with unknown response signature');
} }

View file

@ -70,9 +70,8 @@ export function doUpdateIsNight() {
const momentNow = moment(); const momentNow = moment();
return { return {
type: ACTIONS.UPDATE_IS_NIGHT, type: ACTIONS.UPDATE_IS_NIGHT,
data: { data: { isNight: (() => {
isNight: (() => { const startNightMoment = moment('21:00', 'HH:mm');
const startNightMoment = moment('19:00', 'HH:mm');
const endNightMoment = moment('8:00', 'HH:mm'); const endNightMoment = moment('8:00', 'HH:mm');
return !(momentNow.isAfter(endNightMoment) && momentNow.isBefore(startNightMoment)); return !(momentNow.isAfter(endNightMoment) && momentNow.isBefore(startNightMoment));
})(), })(),

View file

@ -2,10 +2,10 @@ import { remote } from 'electron';
const application = remote.app; const application = remote.app;
const { dock } = application; const { dock } = application;
const win = remote.BrowserWindow.getFocusedWindow(); const browserWindow = remote.getCurrentWindow();
const setBadge = text => { const setBadge = text => {
if (!dock) return; if (!dock) return;
if (win.isFocused()) return; if (browserWindow.isFocused()) return;
dock.setBadge(text); dock.setBadge(text);
}; };