progress towards fixing settings and costs

This commit is contained in:
Jeremy Kauffman 2017-05-17 17:52:45 -04:00
parent aa935c1c07
commit 3fa4d0dfe7
22 changed files with 313 additions and 146 deletions

View file

@ -31,8 +31,7 @@ export function doNavigate(path, params = {}) {
const state = getState()
const pageTitle = selectPageTitle(state)
history.pushState(params, pageTitle, url)
window.document.title = pageTitle
dispatch(doHistoryPush(params, pageTitle, url))
}
}
@ -54,7 +53,14 @@ export function doHistoryBack() {
}
}
export function doLogoClick() {
export function doHistoryPush(params, title, relativeUrl) {
return function(dispatch, getState) {
let pathParts = window.location.pathname.split('/')
pathParts[pathParts.length - 1] = relativeUrl.replace(/^\//, '')
const url = pathParts.join('/')
history.pushState(params, title, url)
window.document.title = title
}
}
export function doOpenModal(modal) {

View file

@ -259,7 +259,7 @@ export function doLoadVideo(uri) {
}
}
export function doWatchVideo(uri) {
export function doPurchaseUri(uri) {
return function(dispatch, getState) {
const state = getState()
const balance = selectBalance(state)

View file

@ -1,16 +1,43 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import lbryio from 'lbryio'
import {
selectClaimsByUri
} from 'selectors/claims'
import {
selectSettingsIsGenerous
} from 'selectors/settings'
export function doFetchCostInfoForUri(uri) {
return function(dispatch, getState) {
dispatch({
type: types.FETCH_COST_INFO_STARTED,
data: {
uri,
}
})
const state = getState(),
claim = selectClaimsByUri(state)[uri],
isGenerous = selectSettingsIsGenerous(state)
lbry.getCostInfo(uri).then(costInfo => {
//
// function getCostGenerous(uri) {
// console.log('get cost generous: ' + uri)
// // If generous is on, the calculation is simple enough that we might as well do it here in the front end
// lbry.resolve({uri: uri}).then((resolutionInfo) => {
// console.log('resolve inside getCostGenerous ' + uri)
// console.log(resolutionInfo)
// if (!resolutionInfo) {
// return reject(new Error("Unused URI"));
// }
// });
// }
function begin() {
dispatch({
type: types.FETCH_COST_INFO_STARTED,
data: {
uri,
}
})
}
function resolve(costInfo) {
dispatch({
type: types.FETCH_COST_INFO_COMPLETED,
data: {
@ -18,15 +45,25 @@ export function doFetchCostInfoForUri(uri) {
costInfo,
}
})
}).catch(() => {
dispatch({
type: types.FETCH_COST_INFO_COMPLETED,
data: {
uri,
costInfo: null
}
})
})
}
if (isGenerous && claim) {
let cost
const fee = claim.value.stream.metadata.fee;
if (fee === undefined ) {
resolve({ cost: 0, includesData: true })
} else if (fee.currency == 'LBC') {
resolve({ cost: fee.amount, includesData: true })
} else {
begin()
lbryio.getExchangeRates().then(({lbc_usd}) => {
resolve({ cost: fee.amount / lbc_usd, includesData: true })
});
}
} else {
begin()
lbry.getCostInfo(uri).then(resolve)
}
}
}

View file

@ -6,6 +6,7 @@ import {
} from 'actions/content'
import {
doNavigate,
doHistoryPush
} from 'actions/app'
import {
selectCurrentPage,
@ -29,6 +30,8 @@ export function doSearch(query) {
if(page != 'search') {
dispatch(doNavigate('search', { query: query }))
} else {
dispatch(doHistoryPush({ query }, "Search for " + query, '/search'))
}
lighthouse.search(query).then(results => {

31
ui/js/actions/settings.js Normal file
View file

@ -0,0 +1,31 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
export function doFetchDaemonSettings() {
return function(dispatch, getState) {
lbry.get_settings().then((settings) => {
dispatch({
type: types.DAEMON_SETTINGS_RECEIVED,
data: {
settings
}
})
})
}
}
export function doSetDaemonSetting(key, value) {
return function(dispatch, getState) {
let settings = {};
settings[key] = value;
lbry.settings_set(settings).then(settings)
lbry.get_settings().then((settings) => {
dispatch({
type: types.DAEMON_SETTINGS_RECEIVED,
data: {
settings
}
})
})
}
}

View file

@ -29,7 +29,7 @@ import {
doDeleteFile,
} from 'actions/file_info'
import {
doWatchVideo,
doPurchaseUri,
doLoadVideo,
} from 'actions/content'
import FileActions from './view'
@ -57,7 +57,7 @@ const perform = (dispatch) => ({
openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)),
deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)),
openModal: (modal) => dispatch(doOpenModal(modal)),
startDownload: (uri) => dispatch(doWatchVideo(uri)),
startDownload: (uri) => dispatch(doPurchaseUri(uri)),
loadVideo: (uri) => dispatch(doLoadVideo(uri))
})

View file

@ -70,7 +70,7 @@ class FileCard extends React.Component {
<div className="card__title-identity">
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
<div className="card__subtitle">
<span style={{float: "right"}}><FilePrice uri={uri} /></span>
{ !isResolvingUri && <span style={{float: "right"}}><FilePrice uri={uri} /></span> }
<UriIndicator uri={uri} />
</div>
</div>

View file

@ -21,7 +21,7 @@ const makeSelect = () => {
const perform = (dispatch) => ({
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(uri))
// cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(uri))
})
export default connect(makeSelect, perform)(FilePrice)

View file

@ -1,5 +1,5 @@
import React from 'react';
import SettingsPage from 'page/settings.js';
import SettingsPage from 'page/settings';
import HelpPage from 'page/help';
import ReportPage from 'page/report.js';
import StartPage from 'page/start.js';

View file

@ -9,7 +9,7 @@ import {
selectCurrentModal,
} from 'selectors/app'
import {
doWatchVideo,
doPurchaseUri,
doLoadVideo,
} from 'actions/content'
import {
@ -47,7 +47,7 @@ const makeSelect = () => {
const perform = (dispatch) => ({
loadVideo: (uri) => dispatch(doLoadVideo(uri)),
watchVideo: (uri) => dispatch(doWatchVideo(uri)),
purchaseUri: (uri) => dispatch(doPurchaseUri(uri)),
closeModal: () => dispatch(doCloseModal()),
})

View file

@ -10,6 +10,15 @@ class VideoPlayButton extends React.Component {
this.props.loadVideo(this.props.uri)
}
onWatchClick() {
console.log(this.props)
this.props.purchaseUri(this.props.uri).then(() => {
if (!this.props.modal) {
this.props.startPlaying()
}
})
}
render() {
const {
button,
@ -42,7 +51,7 @@ class VideoPlayButton extends React.Component {
label={label ? label : ""}
className="video__play-button"
icon="icon-play"
onClick={onWatchClick} />
onClick={this.onWatchClick} />
{modal}
<Modal contentLabel="Not enough credits" isOpen={modal == 'notEnoughCredits'} onConfirmed={closeModal}>
You don't have enough LBRY credits to pay for this stream.
@ -71,16 +80,6 @@ class Video extends React.Component {
this.state = { isPlaying: false }
}
onWatchClick() {
this.props.watchVideo(this.props.uri).then(() => {
if (!this.props.modal) {
this.setState({
isPlaying: true
})
}
})
}
startPlaying() {
this.setState({
isPlaying: true
@ -100,9 +99,6 @@ class Video extends React.Component {
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0
console.log('video render')
console.log(this.props)
let loadStatusMessage = ''
if (isLoading) {
@ -112,40 +108,45 @@ class Video extends React.Component {
}
return (
<div className={"video " + this.props.className + (isPlaying && isReadyToPlay ? " video--active" : " video--hidden")}>{
isPlaying ?
<div className={"video " + this.props.className + (isPlaying || isReadyToPlay ? " video--active" : " video--hidden")}>{
isPlaying || isReadyToPlay ?
(!isReadyToPlay ?
<span>this is the world's worst loading screen and we shipped our software with it anyway... <br /><br />{loadStatusMessage}</span> :
<VideoPlayer downloadPath={fileInfo.download_path} />) :
<VideoPlayer poster={metadata.thumbnail} autoplay={isPlaying} downloadPath={fileInfo.download_path} />) :
<div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}>
<VideoPlayButton onWatchClick={this.onWatchClick.bind(this)}
startPlaying={this.startPlaying.bind(this)} {...this.props} />
<VideoPlayButton startPlaying={this.startPlaying.bind(this)} {...this.props} />
</div>
}</div>
);
}
}
class VideoPlayer extends React.PureComponent {
class VideoPlayer extends React.Component {
componentDidMount() {
const elem = this.refs.video
const {
autoplay,
downloadPath,
contentType,
} = this.props
const players = plyr.setup(elem)
players[0].play()
if (autoplay) {
players[0].play()
}
}
render() {
const {
downloadPath,
contentType,
poster,
} = this.props
//<source src={downloadPath} type={contentType} />
return (
<video controls id="video" ref="video">
<source src={downloadPath} type={contentType} />
<video controls id="video" ref="video" style={{backgroundImage: "url('" + poster + "')"}} >
</video>
)
}

View file

@ -62,3 +62,6 @@ export const FETCH_MY_CLAIMS_COMPLETED = 'FETCH_MY_CLAIMS_COMPLETED'
export const SEARCH_STARTED = 'SEARCH_STARTED'
export const SEARCH_COMPLETED = 'SEARCH_COMPLETED'
export const SEARCH_CANCELLED = 'SEARCH_CANCELLED'
// Settings
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED'

View file

@ -163,20 +163,6 @@ lbry.checkAddressIsMine = function(address, callback) {
lbry.call('address_is_mine', {address: address}, callback);
}
lbry.getDaemonSettings = function(callback) {
lbry.call('get_settings', {}, callback);
}
lbry.setDaemonSettings = function(settings, callback) {
lbry.call('set_settings', settings, callback);
}
lbry.setDaemonSetting = function(setting, value, callback) {
var setSettingsArgs = {};
setSettingsArgs[setting] = value;
lbry.call('set_settings', setSettingsArgs, callback)
}
lbry.sendToAddress = function(amount, address, callback, errorCallback) {
lbry.call("send_amount_to_address", { "amount" : amount, "address": address }, callback, errorCallback);
}
@ -208,7 +194,81 @@ lbry.getPeersForBlobHash = function(blobHash, callback) {
}
});
}
//
// lbry.costPromiseCache = {}
// lbry.getCostInfo = function(uri) {
// if (lbry.costPromiseCache[uri] === undefined) {
// lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
// const COST_INFO_CACHE_KEY = 'cost_info_cache';
// let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
//
// function cacheAndResolve(cost, includesData) {
// console.log('getCostInfo cacheAndResolve ' + uri)
// console.log(cost)
// costInfoCache[uri] = {cost, includesData};
// setSession(COST_INFO_CACHE_KEY, costInfoCache);
// resolve({cost, includesData});
// }
//
// if (!uri) {
// return reject(new Error(`URI required.`));
// }
//
// if (costInfoCache[uri] && costInfoCache[uri].cost) {
// return resolve(costInfoCache[uri])
// }
//
// function getCost(uri, size) {
// lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
// cacheAndResolve(cost, size !== null);
// }, reject);
// }
//
// function getCostGenerous(uri) {
// console.log('get cost generous: ' + uri)
// // If generous is on, the calculation is simple enough that we might as well do it here in the front end
// lbry.resolve({uri: uri}).then((resolutionInfo) => {
// console.log('resolve inside getCostGenerous ' + uri)
// console.log(resolutionInfo)
// if (!resolutionInfo) {
// return reject(new Error("Unused URI"));
// }
// const fee = resolutionInfo.claim.value.stream.metadata.fee;
// if (fee === undefined) {
// cacheAndResolve(0, true);
// } else if (fee.currency == 'LBC') {
// cacheAndResolve(fee.amount, true);
// } else {
// lbryio.getExchangeRates().then(({lbc_usd}) => {
// cacheAndResolve(fee.amount / lbc_usd, true);
// });
// }
// });
// }
//
// const uriObj = lbryuri.parse(uri);
// const name = uriObj.path || uriObj.name;
//
// lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
// if (is_generous_host) {
// return getCostGenerous(uri);
// }
//
// lighthouse.get_size_for_name(name).then((size) => {
// if (size) {
// getCost(name, size);
// }
// else {
// getCost(name, null);
// }
// }, () => {
// getCost(name, null);
// });
// });
// });
// }
// return lbry.costPromiseCache[uri];
// }
/**
* Takes a LBRY URI; will first try and calculate a total cost using
* Lighthouse. If Lighthouse can't be reached, it just retrives the
@ -227,6 +287,8 @@ lbry.getCostInfo = function(uri) {
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
function cacheAndResolve(cost, includesData) {
console.log('getCostInfo cacheAndResolve ' + uri)
console.log(cost)
costInfoCache[uri] = {cost, includesData};
setSession(COST_INFO_CACHE_KEY, costInfoCache);
resolve({cost, includesData});
@ -246,44 +308,17 @@ lbry.getCostInfo = function(uri) {
}, reject);
}
function getCostGenerous(uri) {
// If generous is on, the calculation is simple enough that we might as well do it here in the front end
lbry.resolve({uri: uri}).then((resolutionInfo) => {
if (!resolutionInfo) {
return reject(new Error("Unused URI"));
}
const fee = resolutionInfo.claim.value.stream.metadata.fee;
if (fee === undefined) {
cacheAndResolve(0, true);
} else if (fee.currency == 'LBC') {
cacheAndResolve(fee.amount, true);
} else {
lbryio.getExchangeRates().then(({lbc_usd}) => {
cacheAndResolve(fee.amount / lbc_usd, true);
});
}
});
}
const uriObj = lbryuri.parse(uri);
const name = uriObj.path || uriObj.name;
lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
if (is_generous_host) {
return getCostGenerous(uri);
lighthouse.get_size_for_name(name).then((size) => {
if (size) {
getCost(name, size);
}
lighthouse.get_size_for_name(name).then((size) => {
if (size) {
getCost(name, size);
}
else {
getCost(name, null);
}
}, () => {
else {
getCost(name, null);
});
});
}
})
});
}
return lbry.costPromiseCache[uri];
@ -663,21 +698,6 @@ lbry.cancelResolve = function(params={}) {
}
}
// Adds caching.
lbry._settingsPromise = null;
lbry.settings_get = function(params={}) {
if (params.allow_cached && lbry._settingsPromise) {
return lbry._settingsPromise;
}
lbry._settingsPromise = new Promise((resolve, reject) => {
lbry.call('settings_get', {}, (settings) => {
setSession('settings', settings);
resolve(settings);
}, reject);
});
return lbry._settingsPromise;
}
// lbry.get = function(params={}) {
// return function(params={}) {
// return new Promise((resolve, reject) => {

View file

@ -10,9 +10,13 @@ import {AuthOverlay} from './component/auth.js';
import { Provider } from 'react-redux';
import store from 'store.js';
import {
doDaemonReady,
doChangePath,
doDaemonReady,
doHistoryPush
} from 'actions/app'
import {
doFetchDaemonSettings
} from 'actions/settings'
import parseQueryParams from 'util/query_params'
const {remote, ipcRenderer} = require('electron');
@ -28,11 +32,13 @@ window.addEventListener('contextmenu', (event) => {
});
window.addEventListener('popstate', (event) => {
const pathname = document.location.pathname
const queryString = document.location.search
if (pathname.match(/dist/)) return
const pathParts = document.location.pathname.split('/')
const route = '/' + pathParts[pathParts.length - 1]
app.store.dispatch(doChangePath(`${pathname}${queryString}`))
if (route.match(/html$/)) return
app.store.dispatch(doChangePath(`${route}${queryString}`))
})
ipcRenderer.on('open-uri-requested', (event, uri) => {
@ -48,7 +54,8 @@ var init = function() {
function onDaemonReady() {
app.store.dispatch(doDaemonReady())
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
window.history.pushState({}, "Discover", '/discover');
app.store.dispatch(doHistoryPush({}, "Discover", "/discover"))
app.store.dispatch(doFetchDaemonSettings())
ReactDOM.render(<Provider store={store}><div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div></Provider>, canvas)
}

View file

@ -8,6 +8,8 @@ import {BusyMessage} from 'component/common.js';
class SearchPage extends React.Component{
render() {
console.log('render search page')
const {
query,
} = this.props

View file

@ -0,0 +1,21 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doSetDaemonSetting
} from 'actions/settings'
import {
selectDaemonSettings
} from 'selectors/settings'
import SettingsPage from './view'
const select = (state) => ({
daemonSettings: selectDaemonSettings(state)
})
const perform = (dispatch) => ({
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
})
export default connect(select, perform)(SettingsPage)

View file

@ -1,19 +1,11 @@
import React from 'react';
import {FormField, FormRow} from '../component/form.js';
import {FormField, FormRow} from 'component/form.js';
import SubHeader from 'component/subHeader'
import lbry from '../lbry.js';
import lbry from 'lbry.js';
var SettingsPage = React.createClass({
_onSettingSaveSuccess: function() {
// This is bad.
// document.dispatchEvent(new CustomEvent('globalNotice', {
// detail: {
// message: "Settings saved",
// },
// }))
},
setDaemonSetting: function(name, value) {
lbry.setDaemonSetting(name, value, this._onSettingSaveSuccess)
this.props.setDaemonSetting(name, value)
},
setClientSetting: function(name, value) {
lbry.setClientSetting(name, value)
@ -51,21 +43,15 @@ var SettingsPage = React.createClass({
this.setDaemonSetting('max_download', Number(event.target.value));
},
getInitialState: function() {
const daemonSettings = this.props.daemonSettings
return {
settings: null,
isMaxUpload: daemonSettings && daemonSettings.max_upload != 0,
isMaxDownload: daemonSettings && daemonSettings.max_download != 0,
showNsfw: lbry.getClientSetting('showNsfw'),
showUnavailable: lbry.getClientSetting('showUnavailable'),
}
},
componentWillMount: function() {
lbry.getDaemonSettings((settings) => {
this.setState({
daemonSettings: settings,
isMaxUpload: settings.max_upload != 0,
isMaxDownload: settings.max_download != 0
});
});
},
onShowNsfwChange: function(event) {
lbry.setClientSetting('showNsfw', event.target.checked);
},
@ -73,8 +59,12 @@ var SettingsPage = React.createClass({
},
render: function() {
if (!this.state.daemonSettings) {
return null;
const {
daemonSettings
} = this.props
if (!daemonSettings) {
return <main className="main--single-column"><span className="empty">Failed to load settings.</span></main>;
}
/*
<section className="card">
@ -84,7 +74,7 @@ var SettingsPage = React.createClass({
<div className="card__content">
<FormRow type="checkbox"
onChange={this.onRunOnStartChange}
defaultChecked={this.state.daemonSettings.run_on_startup}
defaultChecked={daemonSettings.run_on_startup}
label="Run LBRY automatically when I start my computer" />
</div>
</section>
@ -99,7 +89,7 @@ var SettingsPage = React.createClass({
<div className="card__content">
<FormRow type="text"
name="download_directory"
defaultValue={this.state.daemonSettings.download_directory}
defaultValue={daemonSettings.download_directory}
helper="LBRY downloads will be saved here."
onChange={this.onDownloadDirChange} />
</div>
@ -125,7 +115,7 @@ var SettingsPage = React.createClass({
<FormField type="number"
min="0"
step=".5"
defaultValue={this.state.daemonSettings.max_upload}
defaultValue={daemonSettings.max_upload}
placeholder="10"
className="form-field__input--inline"
onChange={this.onMaxUploadFieldChange}
@ -153,7 +143,7 @@ var SettingsPage = React.createClass({
<FormField type="number"
min="0"
step=".5"
defaultValue={this.state.daemonSettings.max_download}
defaultValue={daemonSettings.max_download}
placeholder="10"
className="form-field__input--inline"
onChange={this.onMaxDownloadFieldChange}
@ -188,7 +178,7 @@ var SettingsPage = React.createClass({
<div className="card__content">
<FormRow type="checkbox"
onChange={this.onShareDataChange}
defaultChecked={this.state.daemonSettings.share_usage_data}
defaultChecked={daemonSettings.share_usage_data}
label="Help make LBRY better by contributing diagnostic data about my usage" />
</div>
</section>

View file

@ -0,0 +1,16 @@
import * as types from 'constants/action_types'
const reducers = {}
const defaultState = {}
reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) {
return Object.assign({}, state, {
daemonSettings: action.data.settings
})
}
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

View file

@ -0,0 +1,20 @@
import {
createSelector,
} from 'reselect'
const _selectState = state => state.settings || {}
export const selectDaemonSettings = createSelector(
_selectState,
(state) => state.daemonSettings || {}
)
export const selectClientSettings = createSelector(
_selectState,
(state) => state.clientSettings
)
export const selectSettingsIsGenerous = createSelector(
selectDaemonSettings,
(settings) => settings && settings.is_generous_host
)

View file

@ -13,6 +13,7 @@ import costInfoReducer from 'reducers/cost_info'
import fileInfoReducer from 'reducers/file_info'
import rewardsReducer from 'reducers/rewards'
import searchReducer from 'reducers/search'
import settingsReducer from 'reducers/settings'
import walletReducer from 'reducers/wallet'
function isFunction(object) {
@ -54,6 +55,7 @@ const reducers = redux.combineReducers({
costInfo: costInfoReducer,
rewards: rewardsReducer,
search: searchReducer,
settings: settingsReducer,
wallet: walletReducer,
});

View file

@ -3,6 +3,9 @@ video {
box-sizing: border-box;
max-height: 100%;
max-width: 100%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
}
.video {
@ -15,6 +18,7 @@ video {
max-width: $width-page-constrained;
max-height: $height-video-embedded;
height: $height-video-embedded;
position: relative; /*for .plyr below*/
video {
height: 100%;
}
@ -24,6 +28,10 @@ video {
&.video--active {
/*background: none;*/
}
.plyr {
top: 50%;
transform: translateY(-50%);
}
}
.video__cover {