Merge branch 'master' into css_patch

This commit is contained in:
Baltazar Gomez 2017-09-28 20:30:27 -06:00 committed by GitHub
commit 2e28582277
33 changed files with 288 additions and 199 deletions

View file

@ -8,15 +8,24 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased] ## [Unreleased]
### Added ### Added
* Add setting to automatically purchase low-cost content without a confirmation dialog
* New custom styled scrollbar [#574](https://github.com/lbryio/lbry-app/pull/574) * New custom styled scrollbar [#574](https://github.com/lbryio/lbry-app/pull/574)
* *
### Changed ### Changed
* Updated the daemon from 0.16.1 to [0.16.3](https://github.com/lbryio/lbry/releases/tag/v0.16.3) to improve download performance and download issue detection.
* Changed the File page to make it clearer how to to open the folder for a file.
* Improved tabs styles with a nice animation. [#547](https://github.com/lbryio/lbry-app/pull/576) * Improved tabs styles with a nice animation. [#547](https://github.com/lbryio/lbry-app/pull/576)
* Display search bar on discover page instead of title and remove duplicated icon. * Display search bar on discover page instead of title and remove duplicated icon.
* Minor update for themes. * Minor update for themes.
*
### Fixed ### Fixed
* Improve layout (and implementation) of the icon panel in file tiles and cards
* The folder icon representing a local download now shows up properly on Channel pages (#587)
* While editing a publish, the URL will no longer change if you select a new file. (#601)
* Fixed issues with opening the folder for a file (#606)
* Be consistent with the step property on credit inputs (#604)
* Fixed unresponsive header [#613](https://github.com/lbryio/lbry-app/issues/613) * Fixed unresponsive header [#613](https://github.com/lbryio/lbry-app/issues/613)
* Fixed dark theme issues with text content. * Fixed dark theme issues with text content.
* Minor css fixes. * Minor css fixes.

3
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,3 @@
## Contributing to LBRY
https://lbry.io/faq/contributing

View file

@ -49,43 +49,49 @@ to create distributable packages, which is run by calling:
## Development on Windows ## Development on Windows
### Windows Dependency ### Windows Dependency
1. Download and install `git` from <a href="https://git-for-windows.github.io/">github.io<a> (configure to use command prompt integration)
1. Download and install `npm` and `node` from <a href="https://nodejs.org/en/download/current/">nodejs.org<a> 2. Download and install `npm` and `node` from <a href="https://nodejs.org/en/download/current/">nodejs.org<a>
2. Download and install `python 2.7` from <a href="https://www.python.org/downloads/windows/">python.org</a> 3. Download and install `python 2.7` from <a href="https://www.python.org/downloads/windows/">python.org</a>
3. Download and Install `Microsoft Visual C++ Compiler for Python 2.7` from <a href="https://www.microsoft.com/en-us/download/confirmation.aspx?id=44266">Microsoft<a> 4. Download and Install `Microsoft Visual C++ Compiler for Python 2.7` from <a href="https://www.microsoft.com/en-us/download/confirmation.aspx?id=44266">Microsoft<a>
4. Download and install `.NET Framework 2.0 Software Development Kit (SDK) (x64)` from <a href="https://www.microsoft.com/en-gb/download/details.aspx?id=15354">Microsoft<a> 5. Download and install `.NET Framework 2.0 Software Development Kit (SDK) (x64)` from <a href="https://www.microsoft.com/en-gb/download/details.aspx?id=15354">Microsoft<a> (may need to extract setup.exe and install manually by running install.exe as Administrator)
### One-time Setup ### One-time Setup
1. Open command prompt in the root of the project and run the following; 1. Open command prompt as adminstrator and run the following:
```
npm install --global --production windows-build-tools
exit
```
2. Open command prompt in the root of the project and run the following:
``` ```
python -m pip install -r build\requirements.txt python -m pip install -r build\requirements.txt
python build\set_version.py python build\set_version.py
npm install -g yarn npm install -g yarn
yarn install yarn install
``` ```
2. Change directory to `app` and run the following; 3. Change directory to `app` and run the following;
``` ```
yarn install yarn install
node_modules\.bin\electron-rebuild node_modules\.bin\electron-rebuild
node_modules\.bin\electron-rebuild node_modules\.bin\electron-rebuild
cd .. cd ..
``` ```
3. Change directory to `ui` and run the following 4. Change directory to `ui` and run the following:
``` ```
yarn install yarn install
npm rebuild node-sass npm rebuild node-sass
node node_modules\node-sass\bin\node-sass --output dist\css --sourcemap=none scss\ node node_modules\node-sass\bin\node-sass --output dist\css --sourcemap=none scss\
node_modules\.bin\webpack --config webpack.dev.config.js node_modules\.bin\webpack --config webpack.dev.config.js
xcopy dist ..\app\dist xcopy /E dist ..\app\dist
cd .. cd ..
``` ```
4. Download the lbry daemon and cli binaries and place them in `app\dist\` 4. Download the lbry daemon and cli [binaries](https://github.com/lbryio/lbry/releases) and place them in `app\dist\`
### Building lbry-app ### Building lbry-app
1. run `node_modules\.bin\build -p never` from the root of the project. 1. run `node_modules\.bin\build -p never` from the root of the project.
### Running the electron app ### Running the electron app
1. Run `./node_modules/.bin/electron app` 1. Run `node_modules\.bin\electron app`
### Ongoing Development ### Ongoing Development
1. `cd ui` 1. `cd ui`

View file

@ -20,7 +20,7 @@
"electron-rebuild": "^1.5.11" "electron-rebuild": "^1.5.11"
}, },
"lbrySettings": { "lbrySettings": {
"lbrynetDaemonVersion": "0.16.1", "lbrynetDaemonVersion": "0.16.3",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip" "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip"
}, },
"license": "MIT" "license": "MIT"

View file

@ -1,5 +1,4 @@
import * as types from "constants/action_types"; import * as types from "constants/action_types";
import * as settings from "constants/settings";
import lbry from "lbry"; import lbry from "lbry";
import { import {
selectUpdateUrl, selectUpdateUrl,
@ -8,6 +7,7 @@ import {
selectUpgradeFilename, selectUpgradeFilename,
} from "selectors/app"; } from "selectors/app";
import { doFetchDaemonSettings } from "actions/settings"; import { doFetchDaemonSettings } from "actions/settings";
import { doBalanceSubscribe } from "actions/wallet";
import { doAuthenticate } from "actions/user"; import { doAuthenticate } from "actions/user";
import { doFetchFileInfosAndPublishedClaims } from "actions/file_info"; import { doFetchFileInfosAndPublishedClaims } from "actions/file_info";
@ -178,6 +178,7 @@ export function doDaemonReady() {
dispatch(doAuthenticate()); dispatch(doAuthenticate());
dispatch({ type: types.DAEMON_READY }); dispatch({ type: types.DAEMON_READY });
dispatch(doFetchDaemonSettings()); dispatch(doFetchDaemonSettings());
dispatch(doBalanceSubscribe());
dispatch(doFetchFileInfosAndPublishedClaims()); dispatch(doFetchFileInfosAndPublishedClaims());
}; };
} }

View file

@ -1,4 +1,5 @@
import * as types from "constants/action_types"; import * as types from "constants/action_types";
import * as settings from "constants/settings";
import lbry from "lbry"; import lbry from "lbry";
import lbryio from "lbryio"; import lbryio from "lbryio";
import lbryuri from "lbryuri"; import lbryuri from "lbryuri";
@ -322,29 +323,56 @@ export function doPurchaseUri(uri) {
const downloadingByOutpoint = selectDownloadingByOutpoint(state); const downloadingByOutpoint = selectDownloadingByOutpoint(state);
const alreadyDownloading = const alreadyDownloading =
fileInfo && !!downloadingByOutpoint[fileInfo.outpoint]; fileInfo && !!downloadingByOutpoint[fileInfo.outpoint];
const costInfo = makeSelectCostInfoForUri(uri)(state);
const { cost } = costInfo;
if ( function attemptPlay(cost, instantPurchaseMax = null) {
alreadyDownloading || if (cost > 0 && (!instantPurchaseMax || cost > instantPurchaseMax)) {
(fileInfo && fileInfo.completed && fileInfo.written_bytes > 0) dispatch(doOpenModal(modals.AFFIRM_PURCHASE, { uri }));
) { } else {
return; dispatch(doLoadVideo(uri));
}
} }
// we already fully downloaded the file. // we already fully downloaded the file.
if ( if (fileInfo && fileInfo.completed) {
cost === 0 || // If written_bytes is false that means the user has deleted/moved the
(fileInfo && (fileInfo.completed || fileInfo.download_directory)) // file manually on their file system, so we need to dispatch a
) { // doLoadVideo action to reconstruct the file from the blobs
return dispatch(doLoadVideo(uri)); if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri));
return Promise.resolve();
} }
// we are already downloading the file
if (alreadyDownloading) {
return Promise.resolve();
}
const costInfo = makeSelectCostInfoForUri(uri)(state);
const { cost } = costInfo;
if (cost > balance) { if (cost > balance) {
return dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS)); dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS));
return Promise.resolve();
} }
return dispatch(doOpenModal(modals.AFFIRM_PURCHASE, { uri })); if (
cost == 0 ||
!lbry.getClientSetting(settings.INSTANT_PURCHASE_ENABLED)
) {
attemptPlay(cost);
} else {
const instantPurchaseMax = lbry.getClientSetting(
settings.INSTANT_PURCHASE_MAX
);
if (instantPurchaseMax.currency == "LBC") {
attemptPlay(cost, instantPurchaseMax.amount);
} else {
// Need to convert currency of instant purchase maximum before trying to play
lbryio.getExchangeRates().then(({ lbc_usd }) => {
attemptPlay(cost, instantPurchaseMax.amount / lbc_usd);
});
}
}
}; };
} }

View file

@ -9,12 +9,23 @@ import { doOpenModal, doShowSnackBar } from "actions/app";
import { doNavigate } from "actions/navigation"; import { doNavigate } from "actions/navigation";
import * as modals from "constants/modal_types"; import * as modals from "constants/modal_types";
export function doUpdateBalance(balance) { export function doUpdateBalance() {
return { return function(dispatch, getState) {
type: types.UPDATE_BALANCE, lbry.wallet_balance().then(balance => {
data: { return dispatch({
balance: balance, type: types.UPDATE_BALANCE,
}, data: {
balance: balance,
},
});
});
};
}
export function doBalanceSubscribe() {
return function(dispatch, getState) {
dispatch(doUpdateBalance());
setInterval(() => dispatch(doUpdateBalance()), 5000);
}; };
} }

View file

@ -5,7 +5,6 @@ import { selectUser } from "selectors/user";
import { doCheckUpgradeAvailable, doAlertError } from "actions/app"; import { doCheckUpgradeAvailable, doAlertError } from "actions/app";
import { doRecordScroll } from "actions/navigation"; import { doRecordScroll } from "actions/navigation";
import { doFetchRewardedContent } from "actions/content"; import { doFetchRewardedContent } from "actions/content";
import { doUpdateBalance } from "actions/wallet";
import App from "./view"; import App from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
@ -16,7 +15,6 @@ const select = (state, props) => ({
const perform = dispatch => ({ const perform = dispatch => ({
alertError: errorList => dispatch(doAlertError(errorList)), alertError: errorList => dispatch(doAlertError(errorList)),
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
updateBalance: balance => dispatch(doUpdateBalance(balance)),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)),
}); });

View file

@ -10,7 +10,6 @@ class App extends React.PureComponent {
const { const {
alertError, alertError,
checkUpgradeAvailable, checkUpgradeAvailable,
updateBalance,
fetchRewardedContent, fetchRewardedContent,
} = this.props; } = this.props;
@ -22,10 +21,6 @@ class App extends React.PureComponent {
checkUpgradeAvailable(); checkUpgradeAvailable();
} }
lbry.balanceSubscribe(balance => {
updateBalance(balance);
});
fetchRewardedContent(); fetchRewardedContent();
this.scrollListener = () => this.props.recordScroll(window.scrollY); this.scrollListener = () => this.props.recordScroll(window.scrollY);

View file

@ -2,12 +2,13 @@ import React from "react";
import lbryuri from "lbryuri.js"; import lbryuri from "lbryuri.js";
import CardMedia from "component/cardMedia"; import CardMedia from "component/cardMedia";
import Link from "component/link"; import Link from "component/link";
import { TruncatedText, Icon } from "component/common"; import { TruncatedText } from "component/common";
import IconFeatured from "component/iconFeatured"; import Icon from "component/icon";
import FilePrice from "component/filePrice"; import FilePrice from "component/filePrice";
import UriIndicator from "component/uriIndicator"; import UriIndicator from "component/uriIndicator";
import NsfwOverlay from "component/nsfwOverlay"; import NsfwOverlay from "component/nsfwOverlay";
import TruncatedMarkdown from "component/truncatedMarkdown"; import TruncatedMarkdown from "component/truncatedMarkdown";
import * as icons from "constants/icons";
class FileCard extends React.PureComponent { class FileCard extends React.PureComponent {
constructor(props) { constructor(props) {
@ -94,11 +95,12 @@ class FileCard extends React.PureComponent {
<TruncatedText lines={1}>{title}</TruncatedText> <TruncatedText lines={1}>{title}</TruncatedText>
</div> </div>
<div className="card__subtitle"> <div className="card__subtitle">
<span style={{ float: "right" }}> <span className="card__indicators">
<FilePrice uri={uri} /> <FilePrice uri={uri} />
{isRewardContent && <span>{" "}<IconFeatured /></span>} {" "}
{fileInfo && {isRewardContent && <Icon icon={icons.FEATURED} />}
<span>{" "}<Icon fixed icon="icon-folder" /></span>} {" "}
{fileInfo && <Icon icon={icons.LOCAL} />}
</span> </span>
<UriIndicator uri={uri} /> <UriIndicator uri={uri} />
</div> </div>

View file

@ -29,8 +29,9 @@ class FileDetails extends React.PureComponent {
const { description, language, license } = metadata; const { description, language, license } = metadata;
const { height } = claim; const { height } = claim;
const mediaType = lbry.getMediaType(contentType); const mediaType = lbry.getMediaType(contentType);
const directory = fileInfo && fileInfo.download_path
? path.dirname(fileInfo.download_path) const downloadPath = fileInfo
? path.normalize(fileInfo.download_path)
: null; : null;
return ( return (
@ -59,12 +60,12 @@ class FileDetails extends React.PureComponent {
<tr> <tr>
<td>{__("License")}</td><td>{license}</td> <td>{__("License")}</td><td>{license}</td>
</tr> </tr>
{directory && {downloadPath &&
<tr> <tr>
<td>{__("Downloaded to")}</td> <td>{__("Downloaded to")}</td>
<td> <td>
<Link onClick={() => openFolder(directory)}> <Link onClick={() => openFolder(downloadPath)}>
{directory} {downloadPath}
</Link> </Link>
</td> </td>
</tr>} </tr>}

View file

@ -87,7 +87,8 @@ class FileList extends React.PureComponent {
<FileTile <FileTile
key={fileInfo.outpoint || fileInfo.claim_id} key={fileInfo.outpoint || fileInfo.claim_id}
uri={uri} uri={uri}
hidePrice={true} showPrice={false}
showLocal={false}
showActions={true} showActions={true}
showEmpty={this.props.fileTileShowEmpty} showEmpty={this.props.fileTileShowEmpty}
/> />

View file

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import * as icons from "constants/icons";
import lbryuri from "lbryuri.js"; import lbryuri from "lbryuri.js";
import CardMedia from "component/cardMedia"; import CardMedia from "component/cardMedia";
import FileActions from "component/fileActions"; import FileActions from "component/fileActions";
@ -6,12 +7,17 @@ import Link from "component/link";
import { TruncatedText } from "component/common.js"; import { TruncatedText } from "component/common.js";
import FilePrice from "component/filePrice"; import FilePrice from "component/filePrice";
import NsfwOverlay from "component/nsfwOverlay"; import NsfwOverlay from "component/nsfwOverlay";
import IconFeatured from "component/iconFeatured"; import Icon from "component/icon";
class FileTile extends React.PureComponent { class FileTile extends React.PureComponent {
static SHOW_EMPTY_PUBLISH = "publish"; static SHOW_EMPTY_PUBLISH = "publish";
static SHOW_EMPTY_PENDING = "pending"; static SHOW_EMPTY_PENDING = "pending";
static defaultProps = {
showPrice: true,
showLocal: true,
};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -59,8 +65,10 @@ class FileTile extends React.PureComponent {
isResolvingUri, isResolvingUri,
showEmpty, showEmpty,
navigate, navigate,
hidePrice, showPrice,
showLocal,
rewardedContentClaimIds, rewardedContentClaimIds,
fileInfo,
} = this.props; } = this.props;
const uri = lbryuri.normalize(this.props.uri); const uri = lbryuri.normalize(this.props.uri);
@ -111,8 +119,13 @@ class FileTile extends React.PureComponent {
<CardMedia title={title} thumbnail={thumbnail} /> <CardMedia title={title} thumbnail={thumbnail} />
<div className="file-tile__content"> <div className="file-tile__content">
<div className="card__title-primary"> <div className="card__title-primary">
{!hidePrice ? <FilePrice uri={this.props.uri} /> : null} <span className="card__indicators">
{isRewardContent && <IconFeatured />} {showPrice && <FilePrice uri={this.props.uri} />}
{" "}
{isRewardContent && <Icon icon={icons.FEATURED} />}
{" "}
{showLocal && fileInfo && <Icon icon={icons.LOCAL} />}
</span>
<div className="meta">{uri}</div> <div className="meta">{uri}</div>
<h3> <h3>
<TruncatedText lines={1}>{title}</TruncatedText> <TruncatedText lines={1}>{title}</TruncatedText>

View file

@ -0,0 +1,5 @@
import React from "react";
import { connect } from "react-redux";
import Icon from "./view";
export default connect(null, null)(Icon);

View file

@ -0,0 +1,40 @@
import React from "react";
import * as icons from "constants/icons";
export default class Icon extends React.PureComponent {
static propTypes = {
icon: React.PropTypes.string.isRequired,
fixed: React.PropTypes.bool,
};
static defaultProps = {
fixed: false,
};
getIconClass() {
const { icon } = this.props;
return icon.startsWith("icon-") ? icon : "icon-" + icon;
}
getIconTitle() {
switch (this.props.icon) {
case icons.FEATURED:
return __("Watch this and earn rewards.");
case icons.LOCAL:
return __("You have a copy of this file.");
default:
return "";
}
}
render() {
const className = this.getIconClass(),
title = this.getIconTitle();
const spanClassName =
"icon " + className + (this.props.fixed ? " icon-fixed-width " : "");
return <span className={spanClassName} title={title} />;
}
}

View file

@ -1,5 +0,0 @@
import React from "react";
import { connect } from "react-redux";
import IconFeatured from "./view";
export default connect(null, null)(IconFeatured);

View file

@ -1,15 +0,0 @@
import React from "react";
import { Icon } from "component/common.js";
const IconFeatured = props => {
return (
<span
className="icon-featured"
title={__("Watch content with this icon to earn weekly rewards.")}
>
<Icon icon="icon-rocket" fixed className="card__icon-featured-content" />
</span>
);
};
export default IconFeatured;

View file

@ -20,6 +20,7 @@ class PublishForm extends React.PureComponent {
this.state = { this.state = {
id: null, id: null,
uri: null,
rawName: "", rawName: "",
name: "", name: "",
bid: 10, bid: 10,
@ -166,7 +167,6 @@ class PublishForm extends React.PureComponent {
claim() { claim() {
const { claimsByUri } = this.props; const { claimsByUri } = this.props;
const { uri } = this.state; const { uri } = this.state;
return claimsByUri[uri]; return claimsByUri[uri];
} }
@ -437,9 +437,10 @@ class PublishForm extends React.PureComponent {
} }
onFileChange() { onFileChange() {
const { mode } = this.state;
if (this.refs.file.getValue()) { if (this.refs.file.getValue()) {
this.setState({ hasFile: true }); this.setState({ hasFile: true });
if (!this.state.customUrl) { if (!this.state.customUrl && mode !== "edit") {
let fileName = this._getFileName(this.refs.file.getValue()); let fileName = this._getFileName(this.refs.file.getValue());
this.nameChanged(fileName); this.nameChanged(fileName);
} }
@ -822,7 +823,7 @@ class PublishForm extends React.PureComponent {
<FormRow <FormRow
ref="bid" ref="bid"
type="number" type="number"
step="0.01" step="0.1"
label={__("Deposit")} label={__("Deposit")}
postfix="LBC" postfix="LBC"
onChange={event => { onChange={event => {

View file

@ -33,7 +33,7 @@ class WalletSend extends React.PureComponent {
<FormRow <FormRow
label={__("Amount")} label={__("Amount")}
postfix={__("LBC")} postfix={__("LBC")}
step="0.01" step="0.1"
min="0" min="0"
type="number" type="number"
placeholder="1.23" placeholder="1.23"

2
ui/js/constants/icons.js Normal file
View file

@ -0,0 +1,2 @@
export const FEATURED = "rocket";
export const LOCAL = "folder";

View file

@ -6,5 +6,7 @@ export const NEW_USER_ACKNOWLEDGED = "welcome_acknowledged";
export const LANGUAGE = "language"; export const LANGUAGE = "language";
export const SHOW_NSFW = "showNsfw"; export const SHOW_NSFW = "showNsfw";
export const SHOW_UNAVAILABLE = "showUnavailable"; export const SHOW_UNAVAILABLE = "showUnavailable";
export const INSTANT_PURCHASE_ENABLED = "instantPurchaseEnabled";
export const INSTANT_PURCHASE_MAX = "instantPurchaseMax";
export const THEME = "theme"; export const THEME = "theme";
export const THEMES = "themes"; export const THEMES = "themes";

View file

@ -37,10 +37,12 @@ let lbry = {
debug: false, debug: false,
useCustomLighthouseServers: false, useCustomLighthouseServers: false,
customLighthouseServers: [], customLighthouseServers: [],
showDeveloperMenu: false,
language: "en", language: "en",
theme: "light", theme: "light",
themes: [], themes: [],
instantPurchaseMax: null,
instantPurchaseEnabled: false,
instantPurchaseMax: { currency: "LBC", amount: 0.1 },
}, },
}; };
@ -227,9 +229,6 @@ lbry.publishDeprecated = function(
lbry.getClientSetting = function(setting) { lbry.getClientSetting = function(setting) {
var localStorageVal = localStorage.getItem("setting_" + setting); var localStorageVal = localStorage.getItem("setting_" + setting);
if (setting == "showDeveloperMenu") {
return true;
}
return localStorageVal === null return localStorageVal === null
? lbry.defaultClientSettings[setting] ? lbry.defaultClientSettings[setting]
: JSON.parse(localStorageVal); : JSON.parse(localStorageVal);
@ -239,13 +238,6 @@ lbry.setClientSetting = function(setting, value) {
return localStorage.setItem("setting_" + setting, JSON.stringify(value)); return localStorage.setItem("setting_" + setting, JSON.stringify(value));
}; };
lbry.formatName = function(name) {
// Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes)
name = name.replace("/s+/g", "-");
name = name.toLowerCase().replace(lbryuri.REGEXP_INVALID_URI, "");
return name;
};
lbry.imagePath = function(file) { lbry.imagePath = function(file) {
return "img/" + file; return "img/" + file;
}; };
@ -276,56 +268,6 @@ lbry.getMediaType = function(contentType, fileName) {
} }
}; };
lbry._subscribeIdCount = 0;
lbry._balanceSubscribeCallbacks = {};
lbry._balanceSubscribeInterval = 5000;
lbry._balanceUpdateInterval = null;
lbry._updateBalanceSubscribers = function() {
lbry.wallet_balance().then(function(balance) {
for (let callback of Object.values(lbry._balanceSubscribeCallbacks)) {
callback(balance);
}
});
if (
!lbry._balanceUpdateInterval &&
Object.keys(lbry._balanceSubscribeCallbacks).length
) {
lbry._balanceUpdateInterval = setInterval(() => {
lbry._updateBalanceSubscribers();
}, lbry._balanceSubscribeInterval);
}
};
lbry.balanceSubscribe = function(callback) {
const subscribeId = ++lbry._subscribeIdCount;
lbry._balanceSubscribeCallbacks[subscribeId] = callback;
lbry._updateBalanceSubscribers();
return subscribeId;
};
lbry.balanceUnsubscribe = function(subscribeId) {
delete lbry._balanceSubscribeCallbacks[subscribeId];
if (
lbry._balanceUpdateInterval &&
!Object.keys(lbry._balanceSubscribeCallbacks).length
) {
clearInterval(lbry._balanceUpdateInterval);
}
};
lbry.showMenuIfNeeded = function() {
const showingMenu = sessionStorage.getItem("menuShown") || null;
const chosenMenu = lbry.getClientSetting("showDeveloperMenu")
? "developer"
: "normal";
if (chosenMenu != showingMenu) {
menu.showMenubar(chosenMenu == "developer");
}
sessionStorage.setItem("menuShown", chosenMenu);
};
lbry.getAppVersionInfo = function() { lbry.getAppVersionInfo = function() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer.once("version-info-received", (event, versionInfo) => { ipcRenderer.once("version-info-received", (event, versionInfo) => {
@ -407,25 +349,6 @@ lbry.claim_list_mine = function(params = {}) {
}); });
}; };
lbry.claim_abandon = function(params = {}) {
return new Promise((resolve, reject) => {
apiCall("claim_abandon", params, resolve, reject);
});
};
lbry.block_show = function(params = {}) {
return new Promise((resolve, reject) => {
apiCall(
"block_show",
params,
block => {
resolve(block);
},
reject
);
});
};
lbry._resolveXhrs = {}; lbry._resolveXhrs = {};
lbry.resolve = function(params = {}) { lbry.resolve = function(params = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -16,14 +16,12 @@ const { remote, ipcRenderer, shell } = require("electron");
const contextMenu = remote.require("./menu/context-menu"); const contextMenu = remote.require("./menu/context-menu");
const app = require("./app"); const app = require("./app");
lbry.showMenuIfNeeded();
window.addEventListener("contextmenu", event => { window.addEventListener("contextmenu", event => {
contextMenu.showContextMenu( contextMenu.showContextMenu(
remote.getCurrentWindow(), remote.getCurrentWindow(),
event.x, event.x,
event.y, event.y,
lbry.getClientSetting("showDeveloperMenu") env === "development"
); );
event.preventDefault(); event.preventDefault();
}); });

View file

@ -52,6 +52,7 @@ class ChannelPage extends React.PureComponent {
name: claim.name, name: claim.name,
claimId: claim.claim_id, claimId: claim.claim_id,
})} })}
showLocal={true}
/> />
) )
: <span className="empty">{__("No content found.")}</span>; : <span className="empty">{__("No content found.")}</span>;

View file

@ -11,7 +11,6 @@ class DeveloperPage extends React.PureComponent {
super(props); super(props);
this.state = { this.state = {
showDeveloperMenu: lbry.getClientSetting("showDeveloperMenu"),
useCustomLighthouseServers: lbry.getClientSetting( useCustomLighthouseServers: lbry.getClientSetting(
"useCustomLighthouseServers" "useCustomLighthouseServers"
), ),
@ -22,14 +21,6 @@ class DeveloperPage extends React.PureComponent {
}; };
} }
handleShowDeveloperMenuChange(event) {
lbry.setClientSetting("showDeveloperMenu", event.target.checked);
lbry.showMenuIfNeeded();
this.setState({
showDeveloperMenu: event.target.checked,
});
}
handleUseCustomLighthouseServersChange(event) { handleUseCustomLighthouseServersChange(event) {
lbry.setClientSetting("useCustomLighthouseServers", event.target.checked); lbry.setClientSetting("useCustomLighthouseServers", event.target.checked);
this.setState({ this.setState({
@ -71,19 +62,6 @@ class DeveloperPage extends React.PureComponent {
<main> <main>
<section className="card"> <section className="card">
<h3>{__("Developer Settings")}</h3> <h3>{__("Developer Settings")}</h3>
<div className="form-row">
<label>
<FormField
type="checkbox"
onChange={event => {
this.handleShowDeveloperMenuChange();
}}
checked={this.state.showDeveloperMenu}
/>
{" "}
{__("Show developer menu")}
</label>
</div>
<div className="form-row"> <div className="form-row">
<label> <label>
<FormField <FormField

View file

@ -6,8 +6,10 @@ import { Thumbnail } from "component/common";
import FilePrice from "component/filePrice"; import FilePrice from "component/filePrice";
import FileDetails from "component/fileDetails"; import FileDetails from "component/fileDetails";
import UriIndicator from "component/uriIndicator"; import UriIndicator from "component/uriIndicator";
import IconFeatured from "component/iconFeatured"; import Icon from "component/icon";
import WalletSendTip from "component/walletSendTip"; import WalletSendTip from "component/walletSendTip";
import DateTime from "component/dateTime";
import * as icons from "constants/icons";
class FilePage extends React.PureComponent { class FilePage extends React.PureComponent {
componentDidMount() { componentDidMount() {
@ -77,7 +79,8 @@ class FilePage extends React.PureComponent {
{!fileInfo || fileInfo.written_bytes <= 0 {!fileInfo || fileInfo.written_bytes <= 0
? <span style={{ float: "right" }}> ? <span style={{ float: "right" }}>
<FilePrice uri={lbryuri.normalize(uri)} /> <FilePrice uri={lbryuri.normalize(uri)} />
{isRewardContent && <span>{" "}<IconFeatured /></span>} {isRewardContent &&
<span>{" "}<Icon icon={icons.FEATURED} /></span>}
</span> </span>
: null} : null}
<h1>{title}</h1> <h1>{title}</h1>

View file

@ -21,6 +21,13 @@ const select = state => ({
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
showNsfw: makeSelectClientSetting(settings.SHOW_NSFW)(state), showNsfw: makeSelectClientSetting(settings.SHOW_NSFW)(state),
showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state), showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state),
instantPurchaseEnabled: makeSelectClientSetting(
settings.INSTANT_PURCHASE_ENABLED
)(state),
instantPurchaseMax: makeSelectClientSetting(settings.INSTANT_PURCHASE_MAX)(
state
),
showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state),
theme: makeSelectClientSetting(settings.THEME)(state), theme: makeSelectClientSetting(settings.THEME)(state),
themes: makeSelectClientSetting(settings.THEMES)(state), themes: makeSelectClientSetting(settings.THEMES)(state),
language: selectCurrentLanguage(state), language: selectCurrentLanguage(state),

View file

@ -13,6 +13,8 @@ class SettingsPage extends React.PureComponent {
super(props); super(props);
this.state = { this.state = {
instantPurchaseEnabled: props.instantPurchaseEnabled,
instantPurchaseMax: props.instantPurchaseMax,
clearingCache: false, clearingCache: false,
}; };
} }
@ -59,6 +61,22 @@ class SettingsPage extends React.PureComponent {
this.props.setClientSetting(settings.THEME, value); this.props.setClientSetting(settings.THEME, value);
} }
oninstantPurchaseEnabledChange(enabled) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled);
this.setState({
instantPurchaseEnabled: enabled,
});
}
onInstantPurchaseMaxChange(newValue) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_MAX, newValue);
this.setState({
instantPurchaseMax: newValue,
});
}
// onMaxUploadPrefChange(isLimited) { // onMaxUploadPrefChange(isLimited) {
// if (!isLimited) { // if (!isLimited) {
// this.setDaemonSetting("max_upload", 0.0); // this.setDaemonSetting("max_upload", 0.0);
@ -113,6 +131,8 @@ class SettingsPage extends React.PureComponent {
language, language,
languages, languages,
showNsfw, showNsfw,
instantPurchaseEnabled,
instantPurchaseMax,
showUnavailable, showUnavailable,
theme, theme,
themes, themes,
@ -167,9 +187,14 @@ class SettingsPage extends React.PureComponent {
</section> </section>
<section className="card"> <section className="card">
<div className="card__content"> <div className="card__content">
<h3>{__("Max Purchase Price")}</h3> <h3>{__("Purchase Settings")}</h3>
</div> </div>
<div className="card__content"> <div className="card__content">
<div className="form-row__label-row">
<label className="form-row__label">
{__("Max Purchase Price")}
</label>
</div>
<FormRow <FormRow
type="radio" type="radio"
name="max_key_fee" name="max_key_fee"
@ -211,6 +236,47 @@ class SettingsPage extends React.PureComponent {
)} )}
</div> </div>
</div> </div>
<div className="card__content">
<div className="form-row__label-row">
<label className="form-row__label">
{__("Purchase Confirmations")}
</label>
</div>
<FormRow
type="radio"
name="instant_purchase_max"
checked={!this.state.instantPurchaseEnabled}
label={__("Ask for confirmation of all purchases")}
onClick={e => {
this.oninstantPurchaseEnabledChange(false);
}}
/>
<div className="form-row">
<FormField
type="radio"
name="instant_purchase_max"
checked={this.state.instantPurchaseEnabled}
label={
"Single-click purchasing of content less than" +
(this.state.instantPurchaseEnabled ? "" : "...")
}
onClick={e => {
this.oninstantPurchaseEnabledChange(true);
}}
/>
{this.state.instantPurchaseEnabled &&
<FormFieldPrice
min="0.1"
step="0.1"
onChange={val => this.onInstantPurchaseMaxChange(val)}
defaultValue={this.state.instantPurchaseMax}
/>}
</div>
<div className="form-field__helper">
When this option is chosen, LBRY won't ask you to confirm
downloads below the given price.
</div>
</div>
</section> </section>
<section className="card"> <section className="card">

View file

@ -6,6 +6,10 @@ import lbry from "lbry";
const reducers = {}; const reducers = {};
const defaultState = { const defaultState = {
clientSettings: { clientSettings: {
instantPurchaseEnabled: lbry.getClientSetting(
settings.INSTANT_PURCHASE_ENABLED
),
instantPurchaseMax: lbry.getClientSetting(settings.INSTANT_PURCHASE_MAX),
showNsfw: lbry.getClientSetting(settings.SHOW_NSFW), showNsfw: lbry.getClientSetting(settings.SHOW_NSFW),
showUnavailable: lbry.getClientSetting(settings.SHOW_UNAVAILABLE), showUnavailable: lbry.getClientSetting(settings.SHOW_UNAVAILABLE),
welcome_acknowledged: lbry.getClientSetting(settings.NEW_USER_ACKNOWLEDGED), welcome_acknowledged: lbry.getClientSetting(settings.NEW_USER_ACKNOWLEDGED),

View file

@ -180,7 +180,12 @@ export const selectMyChannelClaims = createSelector(
const ids = state.myChannelClaims || []; const ids = state.myChannelClaims || [];
const claims = []; const claims = [];
ids.forEach(id => claims.push(byId[id])); ids.forEach(id => {
if (byId[id]) {
//I'm not sure why this check is necessary, but it ought to be a quick fix for https://github.com/lbryio/lbry-app/issues/544
claims.push(byId[id]);
}
});
return claims; return claims;
} }

View file

@ -23,6 +23,17 @@
transform: translate(0, 0); transform: translate(0, 0);
} }
/* Adjustments for icon size and alignment */
.icon-rocket {
color: orangered;
font-size: 0.95em;
position: relative;
top: -0.04em;
margin-left: 0.025em;
margin-right: 0.025em;
}
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */ readers do not read off random characters that represent icons */
.icon-glass:before { .icon-glass:before {

View file

@ -167,6 +167,11 @@ $font-size-subtext-multiple: 0.82;
position: absolute; position: absolute;
top: 36% top: 36%
} }
.card__indicators {
float: right;
}
.card--small { .card--small {
width: var(--card-small-width); width: var(--card-small-width);
overflow-x: hidden; overflow-x: hidden;
@ -269,10 +274,6 @@ $padding-right-card-hover-hack: 30px;
right: 0; right: 0;
} }
.card__icon-featured-content {
color: orangered;
}
/* /*
if we keep doing things like this, we should add a real grid system, but I'm going to be a selective dick about it - Jeremy if we keep doing things like this, we should add a real grid system, but I'm going to be a selective dick about it - Jeremy
*/ */

View file

@ -3,12 +3,6 @@ $height-file-tile: $spacing-vertical * 6;
.file-tile__row { .file-tile__row {
overflow: hidden; overflow: hidden;
height: $height-file-tile; height: $height-file-tile;
.credit-amount {
float: right;
}
.icon-featured {
float: right;
}
//also a hack //also a hack
.card__media { .card__media {
height: $height-file-tile; height: $height-file-tile;