Merge branch 'master' into daemon_err

This commit is contained in:
Baltazar Gomez 2018-02-27 19:21:52 -07:00 committed by GitHub
commit f6b1e804f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 166 additions and 180 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 startup-troubleshooting FAQ URL to daemon error ([#1039](https://github.com/lbryio/lbry-app/pull/1039)) * Added startup-troubleshooting FAQ URL to daemon error ([#1039](https://github.com/lbryio/lbry-app/pull/1039))
* *
@ -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. \n\n' + 'The daemon may have encountered an unexpected error, or another daemon instance is already running. \n\n' +
@ -79,15 +75,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 => {
@ -119,7 +112,7 @@ app.on('will-quit', event => {
return; return;
} }
isQuitting = true; appState.isQuitting = true;
if (daemon) daemon.quit(); if (daemon) daemon.quit();
}); });
@ -128,18 +121,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) => {
@ -226,7 +214,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);
}); });
@ -242,14 +230,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

@ -14,6 +14,8 @@ class SettingsPage extends React.PureComponent {
this.state = { this.state = {
clearingCache: false, clearingCache: false,
}; };
this.onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
} }
clearCache() { clearCache() {
@ -62,11 +64,16 @@ class SettingsPage extends React.PureComponent {
onThemeChange(event) { onThemeChange(event) {
const { value } = event.target; const { value } = event.target;
if (value === 'dark') {
this.onAutomaticDarkModeChange(false);
}
this.props.setClientSetting(settings.THEME, value); this.props.setClientSetting(settings.THEME, value);
} }
onAutomaticDarkModeChange(event) { onAutomaticDarkModeChange(value) {
this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, event.target.checked); this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, value);
} }
onInstantPurchaseEnabledChange(enabled) { onInstantPurchaseEnabledChange(enabled) {
@ -143,6 +150,7 @@ class SettingsPage extends React.PureComponent {
</main> </main>
); );
} }
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SubHeader /> <SubHeader />
@ -325,8 +333,9 @@ class SettingsPage extends React.PureComponent {
<FormRow <FormRow
type="checkbox" type="checkbox"
onChange={this.onAutomaticDarkModeChange.bind(this)} disabled={theme === 'dark'}
defaultChecked={automaticDarkModeEnabled} onChange={(e) => this.onAutomaticDarkModeChange(e.target.checked)}
checked={automaticDarkModeEnabled}
label={__('Automatic dark mode (9pm to 8am)')} label={__('Automatic dark mode (9pm to 8am)')}
/> />
</div> </div>

View file

@ -71,7 +71,7 @@ export function doUpdateIsNight() {
return { return {
type: ACTIONS.UPDATE_IS_NIGHT, type: ACTIONS.UPDATE_IS_NIGHT,
data: { isNight: (() => { data: { isNight: (() => {
const startNightMoment = moment('19:00', 'HH:mm'); const startNightMoment = moment('21: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);
}; };